《LLVM IR 学习手记(一):无量表达式编译器的实现与实践总结》

LLVM IR 层次结构基本介绍

下图展示了 LLVM IR 的层次结构:

层次结构概述

Module(模块) 是 IR 的最外层结构,作为顶层容器,每个 Module 至少包含一个 Function,是一个完整的编译单元。它通常对应一个源文件或链接后的结果,包含了生成代码所需的所有信息。

Module 的核心作用

  • 代码与数据的容器:Module 承载了所有的函数、全局变量和全局元数据,这意味着编写的代码和全局数据最终都会被组织在一个或多个 Module 中。
  • 独立的编译单元 :每个 Module 通常对应于源代码的一个翻译单元(如 .c.cpp 文件),包含了生成目标代码所需的全部信息,可以独立进行优化和代码生成。
  • 链接的基础 :多个 Module 可以被 LLVM 链接器合并,链接器会处理函数和全局变量的定义与声明,解析外部引用,并合并符号表。Module 中全局变量和函数的链接类型(如 privateinternalexternallinkonceweak 等)决定了它们在链接时的行为。
  • 优化的单元:许多 LLVM 的优化过程(Pass)以 Module 为粒度进行,优化器会分析 Module 中的 IR,并实施诸如内联、循环优化、死代码消除等变换。

Function(函数) 是 Module 中定义的函数集合,包括函数声明和定义,封装了可重用的代码单元,是优化和代码生成的主要对象。

BasicBlock(基本块) 组成 Function,每个 BasicBlock 包含一系列 LLVM 指令。

Instruction(指令) 是层次结构的最底层,每个 BasicBlock 是线性顺序排列的 Instruction 序列,有一个入口点和一个出口点。


无量表达式编译器实现

测试文件

expr.txt 内容如下:

ini 复制代码
2 + 3 * 5; 1 + 2 -3;
(2 + 1) * 4 / 6;

词法分析器(Lexer)

词法分析器负责将字符流(char stream)转换为标记(token)。token 是源代码经过词法分析后得到的最小语义单元。

文法定义

css 复制代码
prog : (expr? ";")*
expr : term (("+" | "-") term)* ;
term : factor (("*" | "/") factor)* ;
factor : number | "(" expr ")" ;
number: ([0-9])+ ;

实现代码

lexer.h
cpp 复制代码
#pragma once

#include "llvm/ADT/StringRef.h"
#include "llvm/Support/raw_ostream.h"

enum class TokenType {
    unknown,   // 未知
    number,    // [0-9]+
    plus,      // +
    minus,     // -
    star,      // *
    slash,     // /
    l_parent,  // (
    r_parent,  // )
    semi,      // ;
    eof        // 文件结束
};

class Token {
public:
    Token() {
        row = col = -1;
        tokenType = TokenType::unknown;
        value = -1;
    }
    void Dump();
public:
    int row, col;
    TokenType tokenType;
    int value;
    llvm::StringRef content;
};

class Lexer {
public:
    Lexer(llvm::StringRef sourceCode);
    void NextToken(Token& token);
public:
    const char* BufPtr;
    const char* LineHeadPtr;
    const char* BufEnd;
    int row;
};
lexer.cc
cpp 复制代码
#include "lexer.h"

void Token::Dump() {
    llvm::outs() << "{" << content << ", row = " << row << ", col = " << col << "}\n";
}

bool IsWhiteSpace(char ch) {
    return ch == ' ' || ch == '\r' || ch == '\n';
}

bool IsDigit(char ch) {
    return ch >= '0' && ch <= '9';
}

Lexer::Lexer(llvm::StringRef sourceCode) {
    BufPtr = sourceCode.begin();
    LineHeadPtr = sourceCode.begin();
    BufEnd = sourceCode.end();
    row = 1;
}

void Lexer::NextToken(Token &token) {
    token.row = row;

    while (IsWhiteSpace(*BufPtr)) {
        if (*BufPtr == '\n') {
            row += 1;
            LineHeadPtr = BufPtr + 1;
        }
        ++BufPtr;
    }

    token.col = BufPtr - LineHeadPtr + 1;

    if (BufPtr >= BufEnd) {
        token.tokenType = TokenType::eof;
        return;
    }

    const char* start = BufPtr;
    if (IsDigit(*BufPtr)) {
        int len = 0;
        int val = 0;
        while (IsDigit(*BufPtr)) {
            val = val * 10 + *BufPtr++ - '0';
            ++len;
        }
        token.value = val;
        token.tokenType = TokenType::number;
        token.content = llvm::StringRef(start, len);
    } else {
        switch (*BufPtr) {
            case '+': {
                token.tokenType = TokenType::plus;
                token.content = llvm::StringRef(start, 1);
                BufPtr++;
                break;
            }
            case '-': {
                token.tokenType = TokenType::minus;
                token.content = llvm::StringRef(start, 1);
                BufPtr++;
                break;
            }
            case '*': {
                token.tokenType = TokenType::star;
                token.content = llvm::StringRef(start, 1);
                BufPtr++;
                break;
            }
            case '/': {
                token.tokenType = TokenType::slash;
                token.content = llvm::StringRef(start, 1);
                BufPtr++;
                break;
            }
            case '(': {
                token.tokenType = TokenType::l_parent;
                token.content = llvm::StringRef(start, 1);
                BufPtr++;
                break;
            }
            case ')': {
                token.tokenType = TokenType::r_parent;
                token.content = llvm::StringRef(start, 1);
                BufPtr++;
                break;
            }
            case ';': {
                token.tokenType = TokenType::semi;
                token.content = llvm::StringRef(start, 1);
                BufPtr++;
                break;
            }
            default: {
                token.tokenType = TokenType::unknown;
                BufPtr++;
                break;
            }
        }
    }
}
测试结果

输出与测试文件一致,验证了 Lexer 的正确性。


语法分析器(Parser)

语法分析器根据语法规则将 token 组织成抽象语法树(AST)。

实现代码

ast.h
cpp 复制代码
#pragma once

#include <vector>
#include <memory>
#include "llvm/IR/Value.h"

enum class OpCode { add, sub, mul, div };

class Program;
class Expr;
class BinaryExpr;
class FactorExpr;

class Visitor {
public:
    virtual ~Visitor() {}
    virtual llvm::Value* VisitProgram(Program* p) = 0;
    virtual llvm::Value* VisitBinaryExpr(BinaryExpr* binaryExpr) = 0;
    virtual llvm::Value* VisitFactorExpr(FactorExpr* factorExpr) = 0;
};

class Expr {
public:
    virtual ~Expr() {}
    virtual llvm::Value* Accept(Visitor* v) { return nullptr; }
};

class BinaryExpr : public Expr {
public:
    llvm::Value* Accept(Visitor* v) override {
        return v->VisitBinaryExpr(this);
    }
public:
    OpCode op;
    std::shared_ptr<Expr> left;
    std::shared_ptr<Expr> right;
};

class FactorExpr : public Expr {
public:
    llvm::Value* Accept(Visitor* v) override {
        return v->VisitFactorExpr(this);
    }
public:
    int number;
};

class Program {
public:
    std::vector<std::shared_ptr<Expr>> ExprVec;
};
parser.h
cpp 复制代码
#pragma once

#include "ast.h"
#include "lexer.h"

class Parser {
public:
    Parser(Lexer &lexer) : lexer(lexer) { Advance(); }
    std::shared_ptr<Program> ParseProgram();
private:
    std::shared_ptr<Expr> ParseExpr();
    std::shared_ptr<Expr> ParseTerm();
    std::shared_ptr<Expr> ParseFactor();
    bool Expect(TokenType tokenType);
    bool Consume(TokenType tokenType);
    bool Advance();
private:
    Lexer &lexer;
    Token token;
};
parser.cc
cpp 复制代码
#include "parser.h"
#include <cassert>

std::shared_ptr<Program> Parser::ParseProgram() {
    std::vector<std::shared_ptr<Expr>> ExprVec;
    while (token.tokenType != TokenType::eof) {
        if (token.tokenType == TokenType::semi) {
            Advance();
            continue;
        }
        auto expr = ParseExpr();
        ExprVec.push_back(expr);
    }
    auto program = std::make_shared<Program>();
    program->ExprVec = std::move(ExprVec);
    return program;
}

std::shared_ptr<Expr> Parser::ParseExpr() {
    std::shared_ptr<Expr> left = ParseTerm();
    while (token.tokenType == TokenType::plus || token.tokenType == TokenType::minus) {
        OpCode op = (token.tokenType == TokenType::plus) ? OpCode::add : OpCode::sub;
        Advance();
        auto binaryExpr = std::make_shared<BinaryExpr>();
        binaryExpr->left = left;
        binaryExpr->op = op;
        binaryExpr->right = ParseTerm();
        left = binaryExpr;
    }
    return left;
}

std::shared_ptr<Expr> Parser::ParseTerm() {
    std::shared_ptr<Expr> left = ParseFactor();
    while (token.tokenType == TokenType::star || token.tokenType == TokenType::slash) {
        OpCode op = (token.tokenType == TokenType::star) ? OpCode::mul : OpCode::div;
        Advance();
        auto binaryExpr = std::make_shared<BinaryExpr>();
        binaryExpr->left = left;
        binaryExpr->op = op;
        binaryExpr->right = ParseFactor();
        left = binaryExpr;
    }
    return left;
}

std::shared_ptr<Expr> Parser::ParseFactor() {
    if (token.tokenType == TokenType::l_parent) {
        Advance();
        auto expr = ParseExpr();
        assert(Expect(TokenType::r_parent));
        Advance();
        return expr;
    } else {
        auto factorExpr = std::make_shared<FactorExpr>();
        factorExpr->number = token.value;
        Advance();
        return factorExpr;
    }
}

bool Parser::Expect(TokenType tokenType) {
    return token.tokenType == tokenType;
}

bool Parser::Consume(TokenType tokenType) {
    if (Expect(tokenType)) {
        Advance();
        return true;
    }
    return false;
}

bool Parser::Advance() {
    lexer.NextToken(token);
    return true;
}
访问者模式实现

为了验证 AST,实现了 PrintVisitor 来打印表达式。

printVisitor.h
cpp 复制代码
#pragma once

#include "ast.h"
#include "parser.h"

class PrintVisitor : public Visitor {
public:
    PrintVisitor(std::shared_ptr<Program> program);
    llvm::Value* VisitProgram(Program* p) override;
    llvm::Value* VisitBinaryExpr(BinaryExpr* binaryExpr) override;
    llvm::Value* VisitFactorExpr(FactorExpr* factorExpr) override;
};
printVisitor.cc
cpp 复制代码
#include "printVisitor.h"

PrintVisitor::PrintVisitor(std::shared_ptr<Program> program) {
    VisitProgram(program.get());
}

llvm::Value* PrintVisitor::VisitProgram(Program* p) {
    for (auto &expr : p->ExprVec) {
        expr->Accept(this);
        llvm::outs() << "\n";
    }
    return nullptr;
}

llvm::Value* PrintVisitor::VisitBinaryExpr(BinaryExpr* binaryExpr) {
    binaryExpr->left->Accept(this);
    binaryExpr->right->Accept(this);
    switch (binaryExpr->op) {
        case OpCode::add: llvm::outs() << " + "; break;
        case OpCode::sub: llvm::outs() << " - "; break;
        case OpCode::mul: llvm::outs() << " * "; break;
        case OpCode::div: llvm::outs() << " / "; break;
    }
    return nullptr;
}

llvm::Value* PrintVisitor::VisitFactorExpr(FactorExpr* factorExpr) {
    llvm::outs() << factorExpr->number;
    return nullptr;
}
测试结果

输出与预期一致,验证了 Parser 的正确性。


代码生成(CodeGen)

代码生成器将 AST 转换为 LLVM IR。

实现代码

codegen.h
cpp 复制代码
#pragma once

#include "ast.h"
#include "parser.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/IRBuilder.h"

class CodeGen : public Visitor {
public:
    CodeGen(std::shared_ptr<Program> program) {
        module = std::make_shared<llvm::Module>("expr", context);
        VisitProgram(program.get());
    }
    llvm::Value* VisitProgram(Program* p) override;
    llvm::Value* VisitBinaryExpr(BinaryExpr* binaryExpr) override;
    llvm::Value* VisitFactorExpr(FactorExpr* factorExpr) override;
private:
    llvm::LLVMContext context;
    llvm::IRBuilder<> irBuilder{context};
    std::shared_ptr<llvm::Module> module;
};
codegen.cc
cpp 复制代码
#include "codegen.h"
#include "llvm/IR/Verifier.h"

llvm::Value* CodeGen::VisitProgram(Program* p) {
    auto printFunctionType = llvm::FunctionType::get(irBuilder.getInt32Ty(), {irBuilder.getInt8PtrTy()}, true);
    auto printFunction = llvm::Function::Create(printFunctionType, llvm::GlobalValue::ExternalLinkage, "printf", module.get());
    auto mainFunctionType = llvm::FunctionType::get(irBuilder.getInt32Ty(), false);
    auto mainFunction = llvm::Function::Create(mainFunctionType, llvm::GlobalValue::ExternalLinkage, "main", module.get());
    llvm::BasicBlock* entryBlock = llvm::BasicBlock::Create(context, "entry", mainFunction);
    irBuilder.SetInsertPoint(entryBlock);
    for (auto expr : p->ExprVec) {
        llvm::Value* v = expr->Accept(this);
        irBuilder.CreateCall(printFunction, {irBuilder.CreateGlobalStringPtr("expr value: %d\n"), v});
    }
    llvm::Value* ret = irBuilder.CreateRet(irBuilder.getInt32(0));
    llvm::verifyFunction(*mainFunction);
    module->print(llvm::outs(), nullptr);
    return ret;
}

llvm::Value* CodeGen::VisitBinaryExpr(BinaryExpr* binaryExpr) {
    auto left = binaryExpr->left->Accept(this);
    auto right = binaryExpr->right->Accept(this);
    switch (binaryExpr->op) {
        case OpCode::add: return irBuilder.CreateNSWAdd(left, right, "add");
        case OpCode::sub: return irBuilder.CreateNSWSub(left, right, "sub");
        case OpCode::mul: return irBuilder.CreateNSWMul(left, right, "mul");
        case OpCode::div: return irBuilder.CreateSDiv(left, right, "div");
    }
    return nullptr;
}

llvm::Value* CodeGen::VisitFactorExpr(FactorExpr* factorExpr) {
    return irBuilder.getInt32(factorExpr->number);
}
测试结果

生成的 IR 正确,LLVM 在 IR 层面进行了常量折叠,优化了表达式计算。


测试编译器

生成 IR 文件

bash 复制代码
bin/expr test/expr.txt > test/expr.ll

生成的 IR 内容

llvm 复制代码
; ModuleID = 'expr'
source_filename = "expr"

@0 = private unnamed_addr constant [16 x i8] c"expr value: %d\0A\00", align 1
@1 = private unnamed_addr constant [16 x i8] c"expr value: %d\0A\00", align 1
@2 = private unnamed_addr constant [16 x i8] c"expr value: %d\0A\00", align 1

declare i32 @printf(ptr, ...)

define i32 @main() {
entry:
  %0 = call i32 (ptr, ...) @printf(ptr @0, i32 17)
  %1 = call i32 (ptr, ...) @printf(ptr @1, i32 0)
  %2 = call i32 (ptr, ...) @printf(ptr @2, i32 2)
  ret i32 0
}

运行 IR

bash 复制代码
lli test/expr.ll

运行结果

bash 复制代码
expr value: 17
expr value: 0
expr value: 2

结果正确,验证了整个编译器的正确性和功能性。

注意:LLVM 在 IR 层面进行了常量折叠(Constant Folding),即在编译阶段计算常量表达式,减少了运行时的计算量,提高了效率。

相关推荐
Moonbit1 天前
MoonBit 三周年 | 用代码写就 AI 时代的语言答卷
后端·程序员·编程语言
阿里云云原生2 天前
再见 Cursor,Qoder 真香!这波要改写 AI 编程格局
编程语言
CYRUS_STUDIO2 天前
一文搞懂 Frida Stalker:对抗 OLLVM 的算法还原利器
android·逆向·llvm
数据智能老司机2 天前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
Moonbit3 天前
用MoonBit开发一个C编译器
后端·编程语言·编译器
Moonbit4 天前
MoonBit 正式加入 WebAssembly Component Model 官方文档 !
前端·后端·编程语言
CoovallyAIHub4 天前
微软发布 Visual Studio 2026 Insider:AI深度集成,性能大提升,让开发效率倍增(附下载地址)
后端·编程语言·visual studio