直接在ast节点上实现codegen()方法是最轻量可控的起点,各子类重写返回value*,通过irbuilder生成llvm ir,需确保上下文完备、类型匹配、控制流标签正确、ssa合规。

直接在 AST 节点上加 codegen() 方法是最轻量、最可控的起点。别急着套访问者模式或引入复杂调度,先让每个 AST 类自己负责生成对应的 LLVM IR。
每个 AST 节点必须实现 codegen()
这是 LLVM 官方教程和多数手写编译器采用的惯用法:给基类 ExprAST 声明纯虚函数 virtual Value* codegen() = 0;,所有子类(如 NumberExprAST、BinaryExprAST、ForStmtAST)各自重写它。
关键不是“怎么调用”,而是“调用时上下文是否完备”:
-
codegen()必须能访问当前模块(Module*)、当前函数(Function*)、当前基本块(BasicBlock*)以及符号表(比如std::map<:string allocainst></:string>)——这些通常通过全局上下文对象或构造时传入 - 返回值类型是
Value*,不是void;哪怕语句(如ReturnStmtAST)不产生值,也要返回nullptr或统一用IRBuilder::CreateRet()后显式返回void类型的Value* - 不要在
codegen()里做语义检查;类型错误、未声明变量等应由前序阶段(语义分析)捕获,codegen()只管“已确认合法的 AST 怎么翻译”
IRBuilder 是你写 IR 的唯一执笔工具
LLVM 不允许手动 new 指令或拼字符串生成 IR。IRBuilder 是封装了插入位置、类型推导、SSA 值命名等细节的“智能笔”。它必须绑定到某个 BasicBlock 和 InsertPoint 才能工作。
常见误操作:
- 在函数体外(比如模块初始化时)就创建
IRBuilder—— 此时没地方插指令,会 crash 或静默失败 - 多个 AST 节点共用同一个
IRBuilder实例,但没及时调用SetInsertPoint()切换位置 —— 指令插错地方,控制流断裂 - 对同一变量连续调用
builder.CreateAdd()却没用AllocaInst+StoreInst+LoadInst管理内存 —— SSA 要求每个值只定义一次,变量赋值必须转为内存操作
示例:一个加法表达式生成两行 IR:
Value *lhs = LHS->codegen(); Value *rhs = RHS->codegen(); return builder.CreateAdd(lhs, rhs, "addtmp"); // 自动生成 %addtmp = add i32 %lhs, %rhs
控制流节点(if/for/while)必须显式生成标签与跳转
AST 里的 IfStmtAST 或 ForStmtAST 不是“执行分支”,而是“描述分支结构”。codegen() 必须把它展开成线性指令序列:条件计算 → 条件跳转 → then 块标签 → then 内容 → 无条件跳转 → else 块标签 → else 内容。
容易被忽略的点:
- 每个
BasicBlock必须有且仅有一个终止指令(BranchInst、ReturnInst、UnreachableInst),否则verifyFunction()会报错 -
ForStmtAST的“更新语句”不能塞进循环体末尾再跳回去;必须拆成独立 basic block,并确保跳转目标是“条件判断块”,而不是“循环体开头” - 嵌套 if 中,内层
else块的标签名要带作用域前缀(如if1.else、if1.then.if2.else),否则链接时报重复定义
类型系统不匹配是 runtime panic 的高频原因
LLVM IR 是强类型的:i32 和 double 不能混用;Value* 的 getType() 必须和指令参数类型严格一致。AST 层面的“隐式转换”(比如整数字面量参与浮点运算)必须在 codegen() 阶段显式插入 FPExt 或 SIToFP。
典型报错信息:
LLVM ERROR: Cannot select: t15: i32 = add t13, t14 —— 实际是因为 t13 是 double,但 add 指令被误用成了整数版
应对方式:
- 所有字面量节点(
NumberExprAST)在codegen()中根据原始文本判断是 int 还是 float,返回对应ConstantFP或ConstantInt - 二元运算前,先用
getTypesMatch()或手动比对lhs->getType() == rhs->getType();不等则插入转换指令 - 函数调用参数逐个检查,不匹配就 wrap 一层
builder.CreateBitCast()或CreateSExt()
IR 生成不是翻译练习,是把高级结构“压平”成可优化、可调度、可验证的线性指令流。每一块 missing label、每一个 type mismatch、每一次忘记 SetInsertPoint(),都会让后续的 llc 或 opt 直接拒收整个 module。











