Learn-LLVM-17 chapter04学习笔记

LLVM 17 learning

第 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)brswitchret 等,控制流转移

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-gnuaarch64-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)

  1. Defs 中有定义,直接返回
  2. 否则调用 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

下一章将处理聚合数据结构、函数调用约定等。

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy