第 4 章 生成 IR 代码的基础知识 - 学习笔记
本文为《Learn LLVM 17》第 4 章的学习笔记,主要介绍如何从修饰后的 AST 生成 LLVM IR 代码,包括 SSA 形式构造、控制流映射、模块与驱动程序设置。
1. 本章概览
完成 AST 构建后,下一步是代码生成:将 AST 转换为 LLVM IR。LLVM IR 类似人类可读的三地址码,需要系统化地将语言概念(如控制结构)翻译成低层 IR。
1.1 学习目标
| 主题 |
内容 |
| IR 生成 |
从 AST 生成 IR,控制流映射到基本块 |
| SSA 形式 |
使用 AST 编号算法生成 SSA 格式 IR |
| 模块与驱动 |
CodeGenerator、CGModule、CGProcedure 三层结构 |
| 输出 |
汇编文本、目标代码、IR 文件 |
2. LLVM IR 基础
2.1 IR 语言主要元素
- 模块 (Module):编译单元,包含函数和数据对象
- 函数 (Function):至少一个基本块
- 基本块 (BasicBlock):线性指令序列,以终止指令结束
- 终止指令 (Terminator):
br、switch、ret 等,控制流转移
2.2 类型与数据布局
| 源类型 |
LLVM 类型 |
说明 |
| INTEGER |
i64 |
64 位整数 |
| BOOLEAN |
i1 |
1 位布尔 |
| void |
void |
无返回值 |
目标数据布局 (target datalayout):指定字节序、对齐、栈对齐等。格式如 e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128。
目标三元组 (target triple):架构-厂商-操作系统,例如 x86_64-pc-linux-gnu、aarch64-unknown-linux-gnu。
2.3 静态单赋值 (SSA) 形式
SSA 的核心规则:每个虚拟寄存器只写入一次。好处:
- 建立 def-use 和 use-def 链
- 便于常量传播、公共子表达式消除等优化
- 所有现代编译器广泛使用
phi 指令:在基本块合并处选择来自不同前驱的值:
1
|
%b.loop = phi i32 [ %rem, %while.body ], [ %b, %entry ]
|
- phi 必须是基本块的第一条指令
- 第一个基本块(entry)不能以 phi 开头
2.4 生成 IR 的两种策略
| 策略 |
方法 |
优点 |
缺点 |
| load/store |
为局部变量分配内存槽,用 load/store 读写 |
实现简单 |
需 mem2reg 优化转为 SSA,会删除大量的IR指令 |
| 直接 SSA |
使用 phi 等直接生成 SSA |
生成的 IR 更优 |
实现复杂 |
tinylang 采用直接 SSA,基于 Braun 等人的论文实现。
3. 控制流映射到基本块
3.1 基本块规则
- 格式良好的基本块:线性指令序列,以终止指令结束
- 每个基本块只有一个入口点和一个出口点
- 基本块内不允许 phi 或分支(除终止指令)
3.2 WHILE 语句的基本块结构
1
2
3
|
[前序语句] → while.cond → while.body → while.cond
↓ ↓
after.while ← (循环出口)
|
3.3 代码实现:emitStmt(WhileStatement)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
// Chapter04/tinylang/lib/CodeGen/CGProcedure.cpp:352-375
void CGProcedure::emitStmt(WhileStatement *Stmt) {
llvm::BasicBlock *WhileCondBB = llvm::BasicBlock::Create(
CGM.getLLVMCtx(), "while.cond", Fn);
llvm::BasicBlock *WhileBodyBB = llvm::BasicBlock::Create(
CGM.getLLVMCtx(), "while.body", Fn);
llvm::BasicBlock *AfterWhileBB = llvm::BasicBlock::Create(
CGM.getLLVMCtx(), "after.while", Fn);
Builder.CreateBr(WhileCondBB); // 当前块跳到条件块
sealBlock(Curr); // 密封当前块
setCurr(WhileCondBB);
llvm::Value *Cond = emitExpr(Stmt->getCond());
Builder.CreateCondBr(Cond, WhileBodyBB, AfterWhileBB);
setCurr(WhileBodyBB);
emit(Stmt->getWhileStmts());
Builder.CreateBr(WhileCondBB); // 循环体末尾回到条件
sealBlock(WhileCondBB); // 密封条件块(前驱已全部确定)
sealBlock(Curr);
setCurr(AfterWhileBB);
}
|
3.4 IF 语句的基本块结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
// Chapter04/tinylang/lib/CodeGen/CGProcedure.cpp:316-350
void CGProcedure::emitStmt(IfStatement *Stmt) {
bool HasElse = Stmt->getElseStmts().size() > 0;
llvm::BasicBlock *IfBB = llvm::BasicBlock::Create(...);
llvm::BasicBlock *ElseBB = HasElse ? ... : nullptr;
llvm::BasicBlock *AfterIfBB = llvm::BasicBlock::Create(...);
llvm::Value *Cond = emitExpr(Stmt->getCond());
Builder.CreateCondBr(Cond, IfBB, HasElse ? ElseBB : AfterIfBB);
sealBlock(Curr);
setCurr(IfBB);
emit(Stmt->getIfStmts());
if (!Curr->getTerminator()) Builder.CreateBr(AfterIfBB);
sealBlock(Curr);
if (HasElse) {
setCurr(ElseBB);
emit(Stmt->getElseStmts());
if (!Curr->getTerminator()) Builder.CreateBr(AfterIfBB);
sealBlock(Curr);
}
setCurr(AfterIfBB);
}
|
4. SSA 形式构造算法
4.1 算法来源
基于 Braun et al. 的论文《Simple and Efficient Construction of Static Single Assignment Form》(CC 2013),适用于结构化控制流(无 goto)。
4.2 基本数据结构
1
2
3
4
5
6
7
8
9
|
// Chapter04/tinylang/include/tinylang/CodeGen/CGProcedure.h:26-41
struct BasicBlockDef {
llvm::DenseMap<Decl *, llvm::TrackingVH<llvm::Value>> Defs; // 变量/形参 → 当前值
llvm::DenseMap<llvm::PHINode *, Decl *> IncompletePhis; // 待补全的 phi
unsigned Sealed : 1; // 块是否已密封
};
llvm::DenseMap<llvm::BasicBlock *, BasicBlockDef> CurrentDef;
|
- Defs:本块内变量/形参的当前 SSA 值
- IncompletePhis:块尚未密封时创建的 phi,需在密封时补全操作数
- Sealed:块的所有前驱已知时置为 true
4.3 读写局部变量
写:writeLocalVariable(BB, Decl, Val) → CurrentDef[BB].Defs[Decl] = Val
读:readLocalVariable(BB, Decl):
- 若
Defs 中有定义,直接返回
- 否则调用
readLocalVariableRecursive
4.4 递归读取与 phi 插入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
// Chapter04/tinylang/lib/CodeGen/CGProcedure.cpp:33-53
llvm::Value *CGProcedure::readLocalVariableRecursive(
llvm::BasicBlock *BB, Decl *Decl) {
llvm::Value *Val = nullptr;
if (!CurrentDef[BB].Sealed) {
// 块未密封:插入空 phi,避免循环
llvm::PHINode *Phi = addEmptyPhi(BB, Decl);
CurrentDef[BB].IncompletePhis[Phi] = Decl;
Val = Phi;
} else if (auto *PredBB = BB->getSinglePredecessor()) {
// 单前驱:直接从前驱读取
Val = readLocalVariable(PredBB, Decl);
} else {
// 多前驱:创建 phi,收集所有前驱的值
llvm::PHINode *Phi = addEmptyPhi(BB, Decl);
writeLocalVariable(BB, Decl, Phi);
Val = addPhiOperands(BB, Decl, Phi);
}
writeLocalVariable(BB, Decl, Val);
return Val;
}
|
4.5 密封块 (sealBlock)
当块的所有前驱已知时调用:
1
2
3
4
5
6
7
8
9
|
// Chapter04/tinylang/lib/CodeGen/CGProcedure.cpp:98-106
void CGProcedure::sealBlock(llvm::BasicBlock *BB) {
for (auto PhiDecl : CurrentDef[BB].IncompletePhis) {
addPhiOperands(BB, PhiDecl.second, PhiDecl.first);
}
CurrentDef[BB].IncompletePhis.clear();
CurrentDef[BB].Sealed = true;
}
|
4.6 phi 指令优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// Chapter04/tinylang/lib/CodeGen/CGProcedure.cpp:72-96
llvm::Value *CGProcedure::optimizePhi(llvm::PHINode *Phi) {
llvm::Value *Same = nullptr;
for (llvm::Value *V : Phi->incoming_values()) {
if (V == Same || V == Phi) continue;
if (Same && V != Same) return Phi; // 有不同值,保留 phi
Same = V;
}
if (Same == nullptr)
Same = llvm::UndefValue::get(Phi->getType());
// 替换 phi 后,递归优化使用该 phi 的其他 phi
Phi->replaceAllUsesWith(Same);
Phi->eraseFromParent();
for (auto *P : CandidatePhis) optimizePhi(P);
return Same;
}
|
- 所有操作数相同:用该值替换 phi
- 无操作数:用
UndefValue 替换
- 替换后递归优化其他 phi
5. 变量与参数访问
5.1 readVariable / writeVariable 分发
| 声明类型 |
作用域 |
读取方式 |
写入方式 |
| 局部变量 |
当前过程 |
readLocalVariable |
writeLocalVariable |
| 全局变量 |
模块 |
CreateLoad(CGM.getGlobal(D)) |
CreateStore(Val, CGM.getGlobal(D)) |
| 按值形参 |
当前过程 |
readLocalVariable |
writeLocalVariable |
| VAR 形参 |
引用 |
CreateLoad(FormalParams[FP]) |
CreateStore(Val, FormalParams[FP]) |
5.2 类型映射
1
2
3
4
5
6
7
8
9
10
11
12
|
// Chapter04/tinylang/lib/CodeGen/CGProcedure.cpp:154-166
llvm::Type *CGProcedure::mapType(Decl *Decl, bool HonorReference) {
if (auto *FP = llvm::dyn_cast<FormalParameterDeclaration>(Decl)) {
if (FP->isVar() && HonorReference)
return llvm::PointerType::get(CGM.getLLVMCtx(), 0);
return CGM.convertType(FP->getType());
}
if (auto *V = llvm::dyn_cast<VariableDeclaration>(Decl))
return CGM.convertType(V->getType());
return CGM.convertType(llvm::cast<TypeDeclaration>(Decl));
}
|
- VAR 参数:
HonorReference=true 时返回指针类型
- 读取 VAR 参数值:
mapType(FP, false) 返回底层类型
5.3 VAR 参数属性
1
2
3
4
5
6
7
8
9
10
|
// Chapter04/tinylang/lib/CodeGen/CGProcedure.cpp:195-204
if (FP->isVar()) {
llvm::AttrBuilder Attr(CGM.getLLVMCtx());
llvm::TypeSize Sz = CGM.getModule()->getDataLayout()
.getTypeStoreSize(CGM.convertType(FP->getType()));
Attr.addDereferenceableAttr(Sz); // 指针可解引用
Attr.addAttribute(llvm::Attribute::NoCapture); // 不捕获
Arg.addAttrs(Attr);
}
|
6. 代码生成器架构
6.1 三层结构
1
2
3
|
CodeGenerator (顶层入口)
└── CGModule (模块级:全局变量、过程)
└── CGProcedure (过程级:函数体、SSA)
|
6.2 CodeGenerator
1
2
3
4
5
6
7
8
9
10
11
|
// Chapter04/tinylang/lib/CodeGen/CodeGenerator.cpp
std::unique_ptr<llvm::Module>
CodeGenerator::run(ModuleDeclaration *Mod, std::string FileName) {
std::unique_ptr<llvm::Module> M =
std::make_unique<llvm::Module>(FileName, Ctx);
M->setTargetTriple(TM->getTargetTriple().getTriple());
M->setDataLayout(TM->createDataLayout());
CGModule CGM(M.get());
CGM.run(Mod);
return M;
}
|
6.3 CGModule
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// Chapter04/tinylang/lib/CodeGen/CGModule.cpp:42-60
void CGModule::run(ModuleDeclaration *Mod) {
for (auto *Decl : Mod->getDecls()) {
if (auto *Var = llvm::dyn_cast<VariableDeclaration>(Decl)) {
llvm::GlobalVariable *V = new llvm::GlobalVariable(
*M, convertType(Var->getType()),
/*isConstant=*/false,
llvm::GlobalValue::PrivateLinkage, nullptr,
mangleName(Var));
Globals[Var] = V;
} else if (auto *Proc = llvm::dyn_cast<ProcedureDeclaration>(Decl)) {
CGProcedure CGP(*this);
CGP.run(Proc);
}
}
}
|
注意:若 readVariable/writeVariable 需要区分全局变量,需在 run 开头设置 this->Mod = Mod(当前实现中 Mod 成员可能未正确初始化)。
6.4 名称修饰 (Name Mangling)
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// Chapter04/tinylang/lib/CodeGen/CGModule.cpp:25-35
std::string CGModule::mangleName(Decl *D) {
std::string Mangled("_t");
llvm::SmallVector<llvm::StringRef, 4> Parts;
for (; D; D = D->getEnclosingDecl())
Parts.push_back(D->getName());
while (!Parts.empty()) {
llvm::StringRef Name = Parts.pop_back_val();
Mangled.append(llvm::Twine(Name.size()).concat(Name).str());
}
return Mangled;
}
|
- 格式:
_t + 长度前缀 + 名称
- 例:
Gcd.GCD → _t3Gcd3GCD
7. 表达式生成
7.1 emitExpr 分发
| 表达式类型 |
处理 |
| InfixExpression |
emitInfixExpr |
| PrefixExpression |
emitPrefixExpr |
| VariableAccess |
readVariable |
| ConstantAccess |
emitExpr(Decl->getExpr()) |
| IntegerLiteral |
ConstantInt::get |
| BooleanLiteral |
ConstantInt::get |
7.2 中缀运算符映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// Chapter04/tinylang/lib/CodeGen/CGProcedure.cpp:210-261
plus → CreateNSWAdd
minus → CreateNSWSub
star → CreateNSWMul
kw_DIV → CreateSDiv
kw_MOD → CreateSRem
equal → CreateICmpEQ
hash → CreateICmpNE
less → CreateICmpSLT
lessequal → CreateICmpSLE
greater → CreateICmpSGT
greaterequal → CreateICmpSGE
kw_AND → CreateAnd
kw_OR → CreateOr
|
7.3 前缀运算符
+:恒等
-:CreateNeg
NOT:CreateNot
8. 函数体生成
8.1 run 流程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
// Chapter04/tinylang/lib/CodeGen/CGProcedure.cpp:402-340
void CGProcedure::run(ProcedureDeclaration *Proc) {
this->Proc = Proc;
Fty = createFunctionType(Proc);
Fn = createFunction(Proc, Fty);
llvm::BasicBlock *BB = llvm::BasicBlock::Create(
CGM.getLLVMCtx(), "entry", Fn);
setCurr(BB);
// 初始化形参映射
for (auto Pair : llvm::enumerate(Fn->args())) {
FormalParams[FP] = Arg;
writeLocalVariable(Curr, FP, Arg);
}
// 聚合类型局部变量(如有)分配 alloca
for (auto *D : Proc->getDecls()) {
if (auto *Var = llvm::dyn_cast<VariableDeclaration>(D)) {
if (Ty->isAggregateType()) {
llvm::Value *Val = Builder.CreateAlloca(Ty);
writeLocalVariable(Curr, Var, Val);
}
}
}
emit(Proc->getStmts());
if (!Curr->getTerminator())
Builder.CreateRetVoid();
sealBlock(Curr);
}
|
8.2 创建函数
1
2
|
createFunctionType(Proc) → llvm::FunctionType::get(ResultTy, ParamTypes, false)
createFunction(Proc, Fty) → llvm::Function::Create(FTy, ExternalLinkage, mangleName(Proc), M)
|
9. 驱动程序与目标
9.1 Driver 流程
1
2
3
4
|
main → InitLLVM → 初始化目标 → createTargetMachine
→ 解析 (Lexer → Parser → Sema)
→ CodeGenerator::create(Ctx, TM) → CG->run(Mod, InputFile)
→ emit(Argv[0], M.get(), TM, InputFile)
|
9.2 createTargetMachine
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// Chapter04/tinylang/tools/driver/Driver.cpp:58-85
llvm::TargetMachine *createTargetMachine(const char *Argv0) {
llvm::Triple Triple = !MTriple.empty()
? llvm::Triple::normalize(MTriple)
: llvm::sys::getDefaultTargetTriple();
llvm::TargetOptions TargetOptions =
codegen::InitTargetOptionsFromCodeGenFlags(Triple);
std::string CPUStr = codegen::getCPUStr();
std::string FeatureStr = codegen::getFeaturesStr();
const llvm::Target *Target =
llvm::TargetRegistry::lookupTarget(codegen::getMArch(), Triple, Error);
if (!Target) { /* 错误处理 */ }
return Target->createTargetMachine(
Triple.getTriple(), CPUStr, FeatureStr, TargetOptions,
std::optional<llvm::Reloc::Model>(codegen::getRelocModel()));
}
|
9.3 emit 输出类型
| 选项 |
输出 |
--filetype=asm --emit-llvm |
.ll IR 文件 |
--filetype=asm |
.s 汇编文件 |
--filetype=obj |
.o 目标文件 |
9.4 命令行选项
1
2
3
4
5
|
InputFile // 位置参数,默认 "-"
OutputFilename (-o) // 输出文件名
MTriple // 覆盖目标三元组
EmitLLVM // 输出 IR 而非汇编
+ codegen::RegisterCodeGenFlags // -march, -mcpu, -mattr 等
|
10. 编译与测试
10.1 生成目标文件
1
2
|
tinylang --filetype=obj Gcd.mod
# 生成 Gcd.o
|
10.2 生成 IR
1
2
|
tinylang --filetype=asm --emit-llvm -o - Gcd.mod
# 输出到标准输出
|
10.3 与 C 链接测试
1
2
3
4
5
6
7
8
9
|
// callgcd.c
extern long _t3Gcd3GCD(long, long);
int main(int argc, char *argv[]) {
printf("gcd(25, 20) = %ld\n", _t3Gcd3GCD(25, 20));
printf("gcd(3, 5) = %ld\n", _t3Gcd3GCD(3, 5));
printf("gcd(21, 28) = %ld\n", _t3Gcd3GCD(21, 28));
return 0;
}
|
1
2
3
|
tinylang --filetype=obj Gcd.mod
clang callgcd.c Gcd.o -o gcd
./gcd
|
11. 项目目录结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
Chapter04/tinylang/
├── include/tinylang/
│ ├── CodeGen/
│ │ ├── CodeGenerator.h
│ │ ├── CGModule.h
│ │ └── CGProcedure.h
│ ├── AST/、Basic/、Lexer/、Parser/、Sema/
├── lib/
│ ├── CodeGen/
│ │ ├── CodeGenerator.cpp
│ │ ├── CGModule.cpp
│ │ └── CGProcedure.cpp
│ └── ...
├── tools/driver/
│ └── Driver.cpp
└── examples/
├── Gcd.mod
└── callgcd.ll
|
12. 小结
| 内容 |
要点 |
| IR 生成 |
基本块、控制流、终止指令 |
| SSA |
块密封、phi 插入、optimizePhi |
| 变量 |
局部/全局、按值/VAR 形参 |
| 驱动 |
TargetMachine、PassManager、emit |
| 输出 |
.ll / .s / .o |
下一章将处理聚合数据结构、函数调用约定等。