一句话核心结论 :同样是编译 fib(n) 递归,#1 的手写 800 行 TAC 解释器 vs #4 的 50 行 LLVM API 调用,差距是 800 倍代码量、10 倍性能、6 个免费后端 。本文把 #1 的 MiniLang 前端直接接到 LLVM 上,让你看清工业级编译器的”骨架”到底是什么——它没有龙书里讲的那么神秘,但绝对比你想的精致。
系列导航 前言:为什么这一篇是”工业视角”的分水岭? #1 我们手写了 800 行 C++17 编译器前端,跑通 MiniLang 全流程。#2、#3 我们讲了优化 Pass 和手写 x86-64 后端。但这些都还停留在”教学玩具” ——一旦要支持 ARM、RISC-V、WebAssembly,要做过程间优化、自动向量化、PGO,就只能用真正的 LLVM。
核心命题 :LLVM 是 C++ 写成的、模块化的、库化的编译器基础设施 。它不是一个 binary,而是一堆 .a/.so——你可以像用 STL 一样用 LLVM API ,把 IR Builder、Pass Manager、Target Machine 当成”编译器零件”装进自己的程序。
读完本文你将掌握:
能力 实战价值 LLVMContext / Module / IRBuilder 三大件 任何 LLVM 应用的地基 用 C++ API 生成 SSA 形式的 IR 不再依赖 clang -emit-llvm 新 Pass Manager 调度 70+ 优化 -O2 不是开关,是 50 个 Pass 的流水线 JIT (ORC) 运行时编译执行 写一个 REPL 解释器都嫌慢的场景 TargetMachine 多后端发射 一份 IR 输出 x86/ARM/WASM 自定义 Pass 插件 把你的优化算法接入 opt 工具链
本文不覆盖 JIT 内部原理 (#5 单独讲)和 LLVM 源码深度解读 (独立系列)。本文专注**”怎么用 LLVM”**——让你成为 LLVM 用户而非 LLVM 开发者。
一、LLVM 架构总览:到底什么是 LLVM? 1.1 一句话定义 LLVM LLVM = 模块化、可重定向的编译器基础设施 (Modular & Reusable Compiler Infrastructure)。
它不是 一个编译器——clang 只是 LLVM 的一个前端(C/C++/ObjC 前端)。Rust 的 rustc、Swift 的 swiftc、Kotlin/Native、Numba(Python JIT)全部基于 LLVM 。
graph LR
L1["C/C++/ObjC<br/>clang"]:::lang
L2["Rust<br/>rustc"]:::lang
L3["Swift<br/>swiftc"]:::lang
L4["Kotlin Native"]:::lang
L5["Numba Python"]:::lang
IR["🌐 LLVM IR<br/>(SSA)"]:::ir
T1["x86-64"]:::target
T2["ARM64"]:::target
T3["RISC-V"]:::target
T4["WebAssembly"]:::target
T5["PTX (GPU)"]:::target
T6["AMDGPU"]:::target
L1 --> IR
L2 --> IR
L3 --> IR
L4 --> IR
L5 --> IR
IR --> T1
IR --> T2
IR --> T3
IR --> T4
IR --> T5
IR --> T6
classDef lang fill:#C7CEEA,stroke:#9FA8DA,color:#333
classDef ir fill:#E8D5F5,stroke:#CE93D8,color:#333
classDef target fill:#B5EAD7,stroke:#80CBC4,color:#333核心观察 :LLVM 真正的杀手锏是 N 个前端 + M 个后端 的解耦——新语言只需写一个新前端,就能免费获得所有目标架构。
1.2 LLVM 历史:一个人 + 一所大学 年份 事件 2000 Chris Lattner 入读 UIUC(伊利诺伊大学香槟分校) 2001 Lattner 开始写 LLVM 研究项目(硕士论文) 2003 LLVM 首次公开发布(与 GCC 不同的终身 SSA 形式 IR ) 2005 Lattner 加入 Apple,Clang 项目启动 2007 Clang 能编译自己(self-host) 2010 Clang 取代 GCC 成为 macOS/iOS 默认编译器 2014 LLVM 6.0,新 PassManager 雏形 2020 MLIR 项目(Multi-Level IR)启动,服务 AI 编译器 2024 LLVM 19,JITLink 取代旧 RuntimeDyld
关键事实 :LLVM 名字原意 = “Low Level Virtual Machine” (低层虚拟机),但它早就不是虚拟机 了——现在 IR 是 SSA 形式、不可执行。
1.3 LLVM 项目模块划分 graph TB
subgraph "前端工具"
CL["clang<br/>C/C++/ObjC"]
LLGO["llgo<br/>Go (废弃)"]
end
subgraph "核心库"
SUPP["Support<br/>内存/字符串/文件系统"]
CORE["Core<br/>LLVMContext/Pass"]
IR["IR<br/>Module/Function/Instruction"]
ANAL["Analysis<br/>DominatorTree/ScalarEvolution"]
TRAN["Transform<br/>Pass Manager/Utils"]
CG["CodeGen<br/>指令选择/寄存器分配"]
TGT["Target<br/>x86/ARM/RISC-V/WASM"]
OBJ["Object<br/>ELF/COFF/Mach-O 读写"]
MC["MC<br/>机器码/Machinery"]
end
subgraph "执行引擎"
EE["ExecutionEngine<br/>JIT 运行时"]
ORC["ORC<br/>新一代 JIT"]
INT["Interpreter<br/>lli 解释器"]
end
subgraph "工具"
OPT["opt<br/>Pass 调度器"]
LLC["llc<br/>IR→机器码"]
DIS["llvm-dis<br/>.ll 反汇编"]
AS["llvm-as<br/>.ll 汇编"]
LI["llvm-link<br/>IR 链接"]
end
CL --> IR
IR --> ANAL --> TRAN --> CG --> TGT --> OBJ
EE --> ORC
OPT --> TRAN
LLC --> CG
DIS --> IR
AS --> IR
LI --> IR
style CL fill:#FFB3C6,stroke:#F48FB1,color:#333
style CORE fill:#E8D5F5,stroke:#CE93D8,color:#333
style IR fill:#E8D5F5,stroke:#CE93D8,color:#333
style ANAL fill:#E8D5F5,stroke:#CE93D8,color:#333
style TRAN fill:#E8D5F5,stroke:#CE93D8,color:#333
style CG fill:#E8D5F5,stroke:#CE93D8,color:#333
style TGT fill:#E8D5F5,stroke:#CE93D8,color:#333
style OBJ fill:#E8D5F5,stroke:#CE93D8,color:#333
style EE fill:#FFDAB9,stroke:#FFAB76,color:#333
style ORC fill:#FFDAB9,stroke:#FFAB76,color:#333
style OPT fill:#B5EAD7,stroke:#80CBC4,color:#333
style LLC fill:#B5EAD7,stroke:#80CBC4,color:#333
style DIS fill:#B5EAD7,stroke:#80CBC4,color:#333
style AS fill:#B5EAD7,stroke:#80CBC4,color:#333核心库清单(按依赖顺序) :
库 作用 典型头文件 LLVMSupport内存/字符串/文件/线程 <llvm/Support/...>LLVMCoreLLVMContext / Type / Value / Module <llvm/IR/...>LLVMIRInstruction / BasicBlock / Function <llvm/IR/IRBuilder.h>LLVMAnalysisDominatorTree / LoopInfo <llvm/Analysis/...>LLVMTransformUtilsPass 调度 / 通用变换 <llvm/Transform/Utils/...>LLVMScalarOpts常量折叠、死代码、内联 <llvm/Transforms/Scalar/...>LLVMipo过程间优化 <llvm/Transforms/IPO/...>LLVMCodeGen指令选择、寄存器分配、调度 <llvm/CodeGen/...>LLVMTarget各后端(x86/ARM/RISC-V/WASM) <llvm/Target/...>LLVMObjectELF/COFF/Mach-O 读写 <llvm/Object/...>LLVMExecutionEngineJIT 运行时 <llvm/ExecutionEngine/...>LLVMOrcJIT新一代 JIT <llvm/ExecutionEngine/Orc/...>
1.4 LLVM 工具链总览 工具 作用 典型用法 clangC/C++ → LLVM IR / 机器码 clang -O2 foo.c -o fooclang -emit-llvm生成 LLVM IR clang -emit-llvm -S foo.c -o foo.llopt运行 Pass 流水线 opt -O2 foo.ll -o foo.opt.llopt -dot-cfg生成 CFG 图(Graphviz) opt -dot-cfg foo.llllcLLVM IR → 机器码 llc foo.ll -o foo.sllc -mtriple=...交叉编译 llc -mtriple=arm64-apple-iosllvm-as.ll 文本 → .bc 比特码llvm-as foo.ll -o foo.bcllvm-dis.bc → .llllvm-dis foo.bc -o foo.llllvm-link多个 .bc 合并 llvm-link a.bc b.bc -o c.bclliLLVM IR 解释器 lli foo.llllvm-config查询编译/链接参数 llvm-config --cxxflags --ldflagsllvm-nm符号表查看 llvm-nm foo.ollvm-objdump反汇编 llvm-objdump -d foo.o
1.5 LLVM vs GCC:架构哲学差异 graph LR
subgraph "GCC 架构"
GC1["cpp"]:::g
GC2["cc1<br/>C 前端+优化+后端耦合"]:::g
GC3["GIMPLE"]:::g
GC4["RTL"]:::g
GC5["as"]:::g
GC1 --> GC2 --> GC3 --> GC4 --> GC5
end
subgraph "LLVM 架构"
LC1["clang"]:::l
LC2["LLVM IR<br/>(SSA)"]:::l
LC3["opt<br/>Pass 流水线"]:::l
LC4["llc<br/>指令选择"]:::l
LC5["Target 插件<br/>x86/ARM/..."]:::l
LC1 --> LC2 --> LC3 --> LC4 --> LC5
end
style GC2 fill:#FFB3C6,stroke:#F48FB1,color:#333
style LC2 fill:#B5EAD7,stroke:#80CBC4,color:#333
style GC3 fill:#FFB3C6,stroke:#F48FB1,color:#333
style LC4 fill:#FFB3C6,stroke:#F48FB1,color:#333
style LC1 fill:#B5EAD7,stroke:#80CBC4,color:#333
style LC2 fill:#B5EAD7,stroke:#80CBC4,color:#333
style LC3 fill:#B5EAD7,stroke:#80CBC4,color:#333
style LC4 fill:#B5EAD7,stroke:#80CBC4,color:#333
style LC5 fill:#B5EAD7,stroke:#80CBC4,color:#333维度 GCC LLVM 架构 单体(前后端耦合) 模块化(前端→IR→优化→后端) IR 形式 多级(GIMPLE → RTL → Machine) 单一 SSA IR(LLVM IR) 库化 ❌ 几乎不能嵌入 ✅ 每个模块都是独立库 运行时编译 ❌ 无 JIT ✅ ORC JIT / MCJIT 错误信息 晦涩 友好 (彩色 + 修复建议)C++ 标准 C++03 C++17/20 新后端 难(要改 RTL) 易(TableGen 自动生成) License GPL(污染问题 ) Apache 2.0 + LLVM Exception
核心差异 :LLVM 是”库”,GCC 是”程序” 。这也是为什么 Rust/Swift/Numba 都选 LLVM 而不选 GCC。
1.6 查看 LLVM 版本 1 2 3 4 5 6 7 $ llvm-config --version 18.1.8 $ clang --version clang version 18.1.8 Target: x86_64-apple-darwin25.3.0 Thread model: posix
1.7 用 opt -dot-cfg 看 CFG CFG(Control Flow Graph)是 LLVM 优化 Pass 的核心数据结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 $ cat > test.c << 'EOF' int foo(int n) { if (n < 10) { return n; } else { return n * 2; } } EOF $ clang -O0 -emit-llvm -S test.c -o test.ll $ opt -dot-cfg test.ll -disable-output $ ls *.dot .cfg.foo.dot $ dot -Tpng .cfg.foo.dot -o foo_cfg.png
graph TD
BB0["entry<br/>(基本块 0)"]:::block
BB1["if.then<br/>(基本块 1)<br/>return n"]:::block
BB2["if.else<br/>(基本块 2)<br/>return n*2"]:::block
BB0 -->|"n < 10<br/>(true)"| BB1
BB0 -->|"false"| BB2
style BB0 fill:#C7CEEA,stroke:#9FA8DA,color:#333
style BB1 fill:#B5EAD7,stroke:#80CBC4,color:#333
style BB2 fill:#FFDAB9,stroke:#FFAB76,color:#333关键观察 :CFG = 节点(BasicBlock)+ 边(跳转) 。后续的活跃性分析、寄存器分配、循环优化全都在 CFG 上跑。
二、LLVM IR 详解:SSA 形式到底长啥样? 2.1 IR 的层级结构 graph TB
M["Module<br/>(整个编译单元)<br/>module.ll"]:::top
G["GlobalVariable<br/>@x, @str"]:::gv
F1["Function<br/>@fib"]:::func
F2["Function<br/>@main"]:::func
M --> G
M --> F1
M --> F2
subgraph F1
BB1["BasicBlock<br/>entry"]:::bb
BB2["BasicBlock<br/>if.then"]:::bb
BB3["BasicBlock<br/>if.end"]:::bb
BB1 --> BB2 --> BB3
end
subgraph BB1
I1["Instruction<br/>%1 = icmp slt i32 %n, 2"]:::inst
I2["Instruction<br/>br i1 %1, label %if.then, label %if.end"]:::inst
I1 --> I2
end
style M fill:#FFB3C6,stroke:#F48FB1,color:#333
style G fill:#E8D5F5,stroke:#CE93D8,color:#333
style F1 fill:#C7CEEA,stroke:#9FA8DA,color:#333
style F2 fill:#C7CEEA,stroke:#9FA8DA,color:#333
style BB1 fill:#B5EAD7,stroke:#80CBC4,color:#333
style BB2 fill:#B5EAD7,stroke:#80CBC4,color:#333
style BB3 fill:#B5EAD7,stroke:#80CBC4,color:#333
style I1 fill:#FFDAB9,stroke:#FFAB76,color:#333
style I2 fill:#FFDAB9,stroke:#FFAB76,color:#333层级 类(LLVM C++) 类比 包含 Module llvm::Module整个翻译单元 多个 Function + Global GlobalVariable llvm::GlobalVariable全局变量 @g_xFunction llvm::Function函数 多个 BasicBlock BasicBlock llvm::BasicBlock基本块 多个 Instruction Instruction llvm::Instruction指令 操作码 + 操作数 Value llvm::Value值(SSA 虚拟寄存器) %1, %resultType llvm::Type类型 i32, float, ptr
2.2 真实 LLVM IR 样例(fib 函数) 1 2 3 4 5 6 7 8 $ cat > fib.c << 'EOF' int fib(int n) { if (n < 2) return n; return fib(n-1) + fib(n-2); } EOF $ clang -O0 -emit-llvm -S fib.c -o fib.ll $ cat fib.ll
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 source_filename = "fib.c" target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128" target triple = "x86_64-apple-darwin25.3.0" define i32 @fib (i32 %0 ) {entry: %cmp = icmp slt i32 %0 , 2 br i1 %cmp , label %if.then , label %if.end if.then: br label %if.end if.end : %retval.0 = phi i32 [ %0 , %if.then ], [ %call , %entry ] ret i32 %retval.0 entry.recurse: %sub = sub nsw i32 %0 , 1 %call = call i32 @fib (i32 %sub ) %sub1 = sub nsw i32 %0 , 2 %call2 = call i32 @fib (i32 %sub1 ) %add = add nsw i32 %call , %call2 br label %if.end }
2.3 IR 5 大指令类型 类型 示例 含义 BinaryOp(二元运算) add i32 %a, %b / mul i32 %a, %b加减乘除、位运算 MemoryOp(内存操作) alloca i32 / load i32, ptr %p / store i32 %v, ptr %p栈分配、加载、存储 ControlFlow(控制流) br label %X / br i1 %c, label %T, label %F / ret i32 %v跳转、条件跳转、返回 Call(函数调用) call i32 @fib(i32 %n)函数调用 PHI(φ 节点) %x = phi i32 [%a, %BB1], [%b, %BB2]SSA 合并点
2.4 SSA 形式:单赋值的核心 graph LR
BB1["BB1<br/>x = 1"]:::bb
BB2["BB2<br/>x = 2"]:::bb
MERGE["BB3<br/>x = phi(x_1, x_2)"]:::bb
BB1 --> MERGE
BB2 --> MERGE
style BB1 fill:#B5EAD7,stroke:#80CBC4,color:#333
style BB2 fill:#FFDAB9,stroke:#FFAB76,color:#333
style MERGE fill:#FFB3C6,stroke:#F48FB1,color:#333关键约束 :每个 SSA 名字(%x)只能被赋值一次 。在控制流汇合点,用 phi 节点选择不同路径的值。
2.5 LLVM IR 类型系统 类型 含义 字节数 i11 位整数(bool) 1 i8 / i16 / i32 / i64 / i128有符号整数 1 / 2 / 4 / 8 / 16 half / bfloat / float / double / fp128浮点数 2 / 2 / 4 / 8 / 16 ptr指针(新版,替代 i8*) 8(64 位) <4 x i32>SIMD 向量(4 个 i32) 16 [10 x i32]数组(10 个 i32) 40 { i32, i32 }结构体 8 void无返回值 0
2.6 llvm-as 与 llvm-dis:文本 ↔ 比特码 1 2 3 4 5 6 7 8 $ llvm-as fib.ll -o fib.bc $ llvm-dis fib.bc -o fib.ll $ xxd fib.bc | head -3
核心观察 :.bc 比特码 = .ll 的二进制版本 ,体积小、加载快、不可读。JIT 通常用 .bc,调试时用 .ll。
2.7 手写 LLVM IR:fib 函数 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 target triple = "x86_64-apple-darwin25.3.0" define i32 @fib (i32 %n ) {entry: %cmp = icmp slt i32 %n , 2 br i1 %cmp , label %base , label %recurse base: ret i32 %n recurse: %n1 = sub i32 %n , 1 %n2 = sub i32 %n , 2 %r1 = call i32 @fib (i32 %n1 ) %r2 = call i32 @fib (i32 %n2 ) %r = add i32 %r1 , %r2 ret i32 %r } define i32 @main () {entry: %r = call i32 @fib (i32 10 ) ret i32 %r }
1 2 3 4 5 6 7 8 9 10 11 $ llc fib.ll -o fib.s $ clang fib.s -o fib $ ./fib $ echo $? 55 $ lli fib.ll $ echo $? 55
三、LLVM C++ API 实战:从 0 生成 fib 的 IR 3.1 第一个完整程序:生成 fib 的 IR 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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 #include <llvm/IR/LLVMContext.h> #include <llvm/IR/Module.h> #include <llvm/IR/IRBuilder.h> #include <llvm/IR/Verifier.h> #include <llvm/Support/raw_ostream.h> int main () { llvm::LLVMContext context; llvm::Module* module = new llvm::Module ("hello" , context); llvm::IRBuilder<> builder (context); llvm::Type* i32 = builder.getInt32Ty (); std::vector<llvm::Type*> params = {i32}; llvm::FunctionType* fn_ty = llvm::FunctionType::get (i32, params, false ); llvm::Function* fib = llvm::Function::Create ( fn_ty, llvm::Function::ExternalLinkage, "fib" , module ); llvm::Value* n = fib->args ().begin (); n->setName ("n" ); llvm::BasicBlock* entry = llvm::BasicBlock::Create (context, "entry" , fib); builder.SetInsertPoint (entry); llvm::Value* two = builder.getInt32 (2 ); llvm::Value* cmp = builder.CreateICmpSLT (n, two, "cmp" ); llvm::Value* cond = builder.CreateICmpNE (cmp, builder.getInt1 (false ), "cond" ); llvm::BasicBlock* base = llvm::BasicBlock::Create (context, "base" , fib); llvm::BasicBlock* recurse = llvm::BasicBlock::Create (context, "recurse" , fib); llvm::BasicBlock* merge = llvm::BasicBlock::Create (context, "merge" , fib); builder.CreateCondBr (cond, base, recurse); builder.SetInsertPoint (base); builder.CreateRet (n); builder.SetInsertPoint (recurse); llvm::Value* one = builder.getInt32 (1 ); llvm::Value* n1 = builder.CreateSub (n, one, "n1" ); llvm::Value* r1 = builder.CreateCall (fib, {n1}, "r1" ); llvm::Value* two2 = builder.getInt32 (2 ); llvm::Value* n2 = builder.CreateSub (n, two2, "n2" ); llvm::Value* r2 = builder.CreateCall (fib, {n2}, "r2" ); llvm::Value* r = builder.CreateAdd (r1, r2, "r" ); builder.CreateBr (merge); builder.SetInsertPoint (merge); llvm::PHINode* phi = builder.CreatePHI (i32, 2 , "result" ); phi->addIncoming (n, base); phi->addIncoming (r, recurse); builder.CreateRet (phi); llvm::verifyFunction (*fib); module ->print (llvm::errs (), nullptr ); delete module ; return 0 ; }
输出 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 source_filename = "hello" define i32 @fib (i32 %n ) {entry: %cmp = icmp slt i32 %n , 2 %cond = icmp ne i1 %cmp , false br i1 %cond , label %base , label %recurse base: ret i32 %n recurse: %n1 = sub i32 %n , 1 %r1 = call i32 @fib (i32 %n1 ) %n2 = sub i32 %n , 2 %r2 = call i32 @fib (i32 %n2 ) %r = add i32 %r1 , %r2 br label %merge merge: %result = phi i32 [ %n , %base ], [ %r , %recurse ] ret i32 %result }
3.2 编译运行 1 2 3 4 5 $ clang++ -std=c++17 hello_llvm.cpp $(llvm-config --cxxflags --ldflags --libs core) -o hello_llvm $ ./hello_llvm
3.3 三大件的”生存周期” graph LR
CTX["LLVMContext<br/>(全局唯一)"]:::ctx
MOD["Module<br/>(每个翻译单元)"]:::mod
BLD["IRBuilder<br/>(当前插入点)"]:::bld
CTX --> MOD
MOD --> BLD
style CTX fill:#FFB3C6,stroke:#F48FB1,color:#333
style MOD fill:#E8D5F5,stroke:#CE93D8,color:#333
style BLD fill:#B5EAD7,stroke:#80CBC4,color:#333类 作用 数量 备注 LLVMContext全局上下文、内存池 全局唯一 (或线程局部)持有所有 Type 常量 Module一个翻译单元 每个编译单元 1 个 含所有 Function + Global IRBuilder当前插入点 临时 (跟着 BasicBlock 移动)SetInsertPoint(BB)
核心陷阱 :不要把多个 Module 共用一个 LLVMContext ——会导致类型不一致和析构顺序问题。一个线程一个 Context 是最佳实践。
3.4 IRBuilder 的 50+ 个 CreateXXX 方法 类别 常用方法 用途 算术 CreateAdd / CreateSub / CreateMul / CreateSDiv加减乘除 位运算 CreateAnd / CreateOr / CreateXor / CreateShl位运算 比较 CreateICmpEQ/NE/SLT/SGT/SLE/SGE整数比较 比较 CreateFCmpOEQ/OLT/...浮点比较 内存 CreateAlloca / CreateLoad / CreateStore栈分配、加载、存储 GEP CreateInBoundsGEP / CreateStructGEP结构体/数组取址 控制流 CreateBr / CreateCondBr / CreateRet / CreateRetVoid跳转、返回 函数调用 CreateCall函数调用 SSA 合并 CreatePHIφ 节点 类型转换 CreateZExt / CreateSExt / CreateTrunc / CreateBitCast位扩展/截断/重解释 原子操作 CreateAtomicRMW / CreateAtomicCmpXchg原子读写
核心技巧 :CreateXXX 方法会自动插入到当前 BasicBlock 末尾 ——你不用关心指令在 BB 里的位置。
3.5 关键 API 对照表(教学 vs LLVM) #1 手写代码 LLVM API 代码减少 Operand::temp(n++) + emit(BINOP, ...)builder.CreateAdd(a, b)~80% emit(LABEL, ...)BasicBlock::Create(...)~70% emit(IFGOTO, ...) + emit(GOTO, ...)builder.CreateCondBr(cond, T, F)~90% emit(PARAM) + emit(CALL)builder.CreateCall(fn, args)~80% phi 节点手写builder.CreatePHI(type, n)~85% 总计 ~800 行 → 50 行
四、把 #1 的 MiniLang 接到 LLVM:完整代码 4.1 项目结构 1 2 3 4 5 6 7 8 llvm_minic/ ├── lexer.h / lexer.cpp # 复用 #1 ├── parser.h / parser.cpp # 复用 #1 ├── ast.h # 复用 #1 ├── llvm_codegen.h # 新增:LLVM CodeGen ├── llvm_codegen.cpp # 新增:本文重点 ├── main.cpp # 主程序 └── CMakeLists.txt
4.2 核心:LLVMCodeGen 类(~400 行) 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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #pragma once #include <llvm/IR/LLVMContext.h> #include <llvm/IR/Module.h> #include <llvm/IR/IRBuilder.h> #include <memory> #include <string> #include <unordered_map> #include "ast.h" class LLVMCodeGen {public : LLVMCodeGen (); ~LLVMCodeGen (); void compile (const Program& prog) ; void dump_ir () const ; bool emit_object (const std::string& filename) ; int jit_run (int argc = 0 ) ; llvm::Module* module () { return module_.get (); } private : llvm::Value* gen_expr (const Expr& e) ; void gen_stmt (const Stmt& s) ; llvm::Type* llvm_type (const std::string& name) ; std::unique_ptr<llvm::LLVMContext> context_; std::unique_ptr<llvm::Module> module_; std::unique_ptr<llvm::IRBuilder<>> builder_; std::unordered_map<std::string, llvm::Value*> named_values_; std::unordered_map<std::string, llvm::Function*> functions_; llvm::Function* get_printf () ; llvm::Function* get_main_signature () ; llvm::Function* current_func_ = nullptr ; };
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 #include "llvm_codegen.h" #include <llvm/IR/Verifier.h> #include <llvm/Support/TargetSelect.h> #include <llvm/Support/FileSystem.h> #include <llvm/Support/raw_ostream.h> #include <llvm/Target/TargetMachine.h> #include <llvm/Target/TargetOptions.h> #include <llvm/MC/TargetRegistry.h> #include <llvm/ExecutionEngine/ExecutionEngine.h> #include <llvm/ExecutionEngine/GenericValue.h> #include <llvm/ExecutionEngine/MCJIT.h> LLVMCodeGen::LLVMCodeGen () { context_ = std::make_unique <llvm::LLVMContext>(); module_ = std::make_unique <llvm::Module>("minic" , *context_); builder_ = std::make_unique<llvm::IRBuilder<>>(*context_); llvm::InitializeAllTargetInfos (); llvm::InitializeAllTargets (); llvm::InitializeAllTargetMCs (); llvm::InitializeAllAsmParsers (); llvm::InitializeAllAsmPrinters (); } LLVMCodeGen::~LLVMCodeGen () = default ; llvm::Type* LLVMCodeGen::llvm_type (const std::string& name) { if (name == "int" ) return builder_->getInt32Ty (); if (name == "bool" ) return builder_->getInt1Ty (); if (name == "string" )return builder_->getPtrTy (); if (name == "void" ) return builder_->getVoidTy (); return builder_->getInt32Ty (); } llvm::Function* LLVMCodeGen::get_printf () { auto it = functions_.find ("printf" ); if (it != functions_.end ()) return it->second; llvm::FunctionType* ty = llvm::FunctionType::get ( builder_->getInt32Ty (), {builder_->getPtrTy ()}, true ); llvm::Function* fn = llvm::Function::Create ( ty, llvm::Function::ExternalLinkage, "printf" , module_.get ()); functions_["printf" ] = fn; return fn; } llvm::Value* LLVMCodeGen::gen_expr (const Expr& e) { if (auto * lit = dynamic_cast <const IntLit*>(&e)) { return builder_->getInt32 (lit->value); } if (auto * lit = dynamic_cast <const StrLit*>(&e)) { return builder_->CreateGlobalStringPtr (lit->value, "str" ); } if (auto * lit = dynamic_cast <const BoolLit*>(&e)) { return builder_->getInt1 (lit->value); } if (auto * v = dynamic_cast <const VarRef*>(&e)) { auto it = named_values_.find (v->name); if (it == named_values_.end ()) { throw std::runtime_error ("Unknown variable: " + v->name); } return builder_->CreateLoad (builder_->getInt32Ty (), it->second, v->name); } if (auto * b = dynamic_cast <const Binary*>(&e)) { llvm::Value* l = gen_expr (*b->lhs); llvm::Value* r = gen_expr (*b->rhs); if (b->op == "+" ) return builder_->CreateAdd (l, r, "add" ); if (b->op == "-" ) return builder_->CreateSub (l, r, "sub" ); if (b->op == "*" ) return builder_->CreateMul (l, r, "mul" ); if (b->op == "/" ) return builder_->CreateSDiv (l, r, "div" ); if (b->op == "==" ) return builder_->CreateICmpEQ (l, r, "eq" ); if (b->op == "!=" ) return builder_->CreateICmpNE (l, r, "ne" ); if (b->op == "<" ) return builder_->CreateICmpSLT (l, r, "lt" ); if (b->op == ">" ) return builder_->CreateICmpSGT (l, r, "gt" ); if (b->op == "<=" ) return builder_->CreateICmpSLE (l, r, "le" ); if (b->op == ">=" ) return builder_->CreateICmpSGE (l, r, "ge" ); if (b->op == "&&" ) return builder_->CreateAnd (l, r, "and" ); if (b->op == "||" ) return builder_->CreateOr (l, r, "or" ); throw std::runtime_error ("Unknown binop: " + b->op); } if (auto * u = dynamic_cast <const Unary*>(&e)) { llvm::Value* v = gen_expr (*u->operand); if (u->op == "-" ) return builder_->CreateNeg (v, "neg" ); if (u->op == "!" ) { llvm::Value* zero = builder_->getInt32 (0 ); return builder_->CreateICmpEQ (v, zero, "not" ); } } if (auto * c = dynamic_cast <const Call*>(&e)) { llvm::Function* fn = module_->getFunction (c->callee); if (!fn) throw std::runtime_error ("Unknown function: " + c->callee); std::vector<llvm::Value*> args; for (auto & a : c->args) { args.push_back (gen_expr (*a)); } return builder_->CreateCall (fn, args, "calltmp" ); } throw std::runtime_error ("Unknown expr" ); } void LLVMCodeGen::gen_stmt (const Stmt& s) { if (auto * p = dynamic_cast <const LetStmt*>(&s)) { llvm::Value* init = gen_expr (*p->value); llvm::AllocaInst* alloc = builder_->CreateAlloca ( builder_->getInt32Ty (), nullptr , p->name); builder_->CreateStore (init, alloc); named_values_[p->name] = alloc; return ; } if (auto * p = dynamic_cast <const AssignStmt*>(&s)) { llvm::Value* val = gen_expr (*p->value); auto it = named_values_.find (p->name); if (it == named_values_.end ()) { throw std::runtime_error ("Unknown variable: " + p->name); } builder_->CreateStore (val, it->second); return ; } if (auto * p = dynamic_cast <const PrintStmt*>(&s)) { llvm::Function* printf_fn = get_printf (); llvm::Value* fmt_str = builder_->CreateGlobalStringPtr ("%d\n" , "fmt" ); llvm::Value* val = gen_expr (*p->value); if (val->getType ()->isIntegerTy (32 )) { builder_->CreateCall (printf_fn, {fmt_str, val}); } else { builder_->CreateCall (printf_fn, {val}); } return ; } if (auto * p = dynamic_cast <const IfStmt*>(&s)) { llvm::Value* cond = gen_expr (*p->cond); cond = builder_->CreateICmpNE (cond, builder_->getInt32 (0 ), "ifcond" ); llvm::BasicBlock* then_bb = llvm::BasicBlock::Create ( *context_, "then" , current_func_); llvm::BasicBlock* else_bb = nullptr ; llvm::BasicBlock* merge_bb = llvm::BasicBlock::Create ( *context_, "merge" , current_func_); if (p->else_branch) { else_bb = llvm::BasicBlock::Create ( *context_, "else" , current_func_); builder_->CreateCondBr (cond, then_bb, else_bb); } else { builder_->CreateCondBr (cond, then_bb, merge_bb); } builder_->SetInsertPoint (then_bb); gen_stmt (*p->then_branch); builder_->CreateBr (merge_bb); if (else_bb) { builder_->SetInsertPoint (else_bb); gen_stmt (*p->else_branch); builder_->CreateBr (merge_bb); } builder_->SetInsertPoint (merge_bb); return ; } if (auto * p = dynamic_cast <const WhileStmt*>(&s)) { llvm::BasicBlock* loop_bb = llvm::BasicBlock::Create ( *context_, "loop" , current_func_); llvm::BasicBlock* body_bb = llvm::BasicBlock::Create ( *context_, "body" , current_func_); llvm::BasicBlock* exit_bb = llvm::BasicBlock::Create ( *context_, "exit" , current_func_); builder_->CreateBr (loop_bb); builder_->SetInsertPoint (loop_bb); llvm::Value* cond = gen_expr (*p->cond); cond = builder_->CreateICmpNE (cond, builder_->getInt32 (0 ), "loopcond" ); builder_->CreateCondBr (cond, body_bb, exit_bb); builder_->SetInsertPoint (body_bb); gen_stmt (*p->body); builder_->CreateBr (loop_bb); builder_->SetInsertPoint (exit_bb); return ; } if (auto * p = dynamic_cast <const ReturnStmt*>(&s)) { if (p->value) { builder_->CreateRet (gen_expr (**p->value)); } else { builder_->CreateRetVoid (); } return ; } if (auto * p = dynamic_cast <const FnDecl*>(&s)) { std::vector<llvm::Type*> param_types ( p->params.size(), builder_->getInt32Ty()) ; llvm::FunctionType* ft = llvm::FunctionType::get ( builder_->getInt32Ty (), param_types, false ); llvm::Function* fn = llvm::Function::Create ( ft, llvm::Function::ExternalLinkage, p->name, module_.get ()); llvm::BasicBlock* bb = llvm::BasicBlock::Create ( *context_, "entry" , fn); llvm::IRBuilder<>::InsertPointGuard guard (*builder_) ; builder_->SetInsertPoint (bb); named_values_.clear (); size_t idx = 0 ; for (auto & arg : fn->args ()) { arg.setName (p->params[idx]); llvm::AllocaInst* alloc = builder_->CreateAlloca ( builder_->getInt32Ty (), nullptr , p->params[idx]); builder_->CreateStore (&arg, alloc); named_values_[p->params[idx]] = alloc; idx++; } llvm::Function* saved = current_func_; current_func_ = fn; gen_stmt (*dynamic_cast <const Block*>(p->body.get ())); current_func_ = saved; llvm::verifyFunction (*fn); functions_[p->name] = fn; return ; } if (auto * p = dynamic_cast <const ExprStmt*>(&s)) { gen_expr (*p->expr); return ; } if (auto * p = dynamic_cast <const Block*>(&s)) { for (auto & st : p->stmts) gen_stmt (*st); return ; } } void LLVMCodeGen::compile (const Program& prog) { for (auto & stmt : prog.stmts) { if (auto * fn = dynamic_cast <const FnDecl*>(stmt.get ())) { std::vector<llvm::Type*> param_types ( fn->params.size(), builder_->getInt32Ty()) ; llvm::FunctionType* ft = llvm::FunctionType::get ( builder_->getInt32Ty (), param_types, false ); llvm::Function::Create ( ft, llvm::Function::ExternalLinkage, fn->name, module_.get ()); } } for (auto & stmt : prog.stmts) { gen_stmt (*stmt); } llvm::verifyModule (*module_); } void LLVMCodeGen::dump_ir () const { module_->print (llvm::errs (), nullptr ); } bool LLVMCodeGen::emit_object (const std::string& filename) { auto target_triple = llvm::sys::getDefaultTargetTriple (); std::string error; auto target = llvm::TargetRegistry::lookupTarget (target_triple, error); if (!target) { llvm::errs () << error; return false ; } auto cpu = "generic" ; auto features = "" ; llvm::TargetOptions opt; auto rm = llvm::Optional <llvm::Reloc::Model>(); auto * tm = target->createTargetMachine ( target_triple, cpu, features, opt, rm); module_->setDataLayout (tm->createDataLayout ()); module_->setTargetTriple (target_triple); std::error_code ec; llvm::raw_fd_ostream dest (filename, ec, llvm::sys::fs::OF_None) ; if (ec) { llvm::errs () << ec.message (); return false ; } llvm::legacy::PassManager pass; auto file_type = llvm::CGFT_ObjectFile; if (tm->addPassesToEmitFile (pass, dest, file_type)) { llvm::errs () << "Cannot emit file\n" ; return false ; } pass.run (*module_); dest.flush (); return true ; } int LLVMCodeGen::jit_run (int ) { llvm::InitializeNativeTarget (); llvm::InitializeNativeTargetAsmPrinter (); std::string error; auto * ee = llvm::EngineBuilder (std::move (module_)) .setErrorStr (&error) .create (); if (!ee) { llvm::errs () << error << "\n" ; return 1 ; } llvm::Function* main_fn = ee->FindFunctionNamed ("main" ); if (!main_fn) { llvm::errs () << "No main() found\n" ; return 1 ; } std::vector<llvm::GenericValue> args; int result = ee->runFunction (main_fn, args).IntVal.getSExtValue (); delete ee; return result; }
4.3 主程序 main.cpp 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 33 34 35 36 37 38 39 40 41 42 43 #include <iostream> #include <fstream> #include <sstream> #include "lexer.h" #include "parser.h" #include "llvm_codegen.h" int main (int argc, char ** argv) { if (argc < 2 ) { std::cerr << "Usage: " << argv[0 ] << " <file.mini> [jit|emit|ir]\n" ; return 1 ; } std::ifstream f (argv[1 ]) ; std::stringstream ss; ss << f.rdbuf (); std::string source = ss.str (); Lexer lexer (source) ; auto tokens = lexer.tokenize (); Parser parser (std::move(tokens)) ; Program prog = parser.parse_program (); LLVMCodeGen codegen; codegen.compile (prog); std::string mode = argc >= 3 ? argv[2 ] : "ir" ; if (mode == "ir" ) { codegen.dump_ir (); } else if (mode == "emit" ) { codegen.emit_object ("output.o" ); std::cout << "Output: output.o\n" ; } else if (mode == "jit" ) { int ret = codegen.jit_run (); std::cout << "Return: " << ret << "\n" ; return ret; } return 0 ; }
4.4 编译命令 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 33 34 35 36 37 38 39 cmake_minimum_required(VERSION 3.13) project(llvm_minic LANGUAGES CXX) set (CMAKE_CXX_STANDARD 17)find_package(LLVM 17 REQUIRED CONFIG) message(STATUS "Found LLVM ${LLVM_VERSION_MAJOR} .${LLVM_VERSION_MINOR} " ) add_executable(llvm_minic main.cpp lexer.cpp parser.cpp llvm_codegen.cpp) target_include_directories(llvm_minic PRIVATE ${LLVM_INCLUDE_DIRS} ) target_link_libraries(llvm_minic PRIVATE ${LLVM_LIBRARIES} ) target_link_libraries(llvm_minic PRIVATE LLVMSupport LLVMCore LLVMIRReader LLVMipo LLVMScalarOpts LLVMTransformUtils LLVMInstCombine LLVMAnalysis LLVMExecutionEngine LLVMMCJIT LLVMOrcJIT LLVMTarget LLVMRuntimeDyld LLVMObject LLVMMCParser LLVMMC LLVMCore )
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 $ mkdir build && cd build $ cmake .. -DLLVM_DIR=/usr/local/opt/llvm/lib/cmake/llvm $ make -j8 $ cat fib.mini fn fib(n) { if (n < 2) { return n; } else { return fib(n-1) + fib(n-2); } } let i = 0;while (i < 10) { print (fib(i)); i = i + 1; } $ ./llvm_minic fib.mini ir $ ./llvm_minic fib.mini emit $ clang output.o -o fib $ ./fib $ ./llvm_minic fib.mini jit
五、JIT 编译实战:让 LLVM 运行时执行 5.1 LLVM JIT 演进史 graph LR
V1["v1: Interpreter<br/>(极慢)"]:::old
V2["v2: JIT<br/>(被废弃)"]:::old
V3["v3: MCJIT<br/>(LLVM 16 前主流)"]:::stable
V4["v4: ORC v1<br/>(LLVM 11+)"]:::new
V5["v5: ORC v2<br/>(LLVM 16+)"]:::future
V1 --> V2 --> V3 --> V4 --> V5
style V1 fill:#FFDAB9,stroke:#FFAB76,color:#333
style V2 fill:#FFDAB9,stroke:#FFAB76,color:#333
style V3 fill:#B5EAD7,stroke:#80CBC4,color:#333
style V4 fill:#C7CEEA,stroke:#9FA8DA,color:#333
style V5 fill:#E8D5F5,stroke:#CE93D8,color:#333引擎 状态 特点 Interpreter (lli) ⚠️ 慢 逐条解释 IR,10x-100x 慢于原生 JIT (旧) ❌ 已废弃 LLVM 2.0 时代,单线程,多 bug MCJIT ⚠️ 维护 当前主流 ,全模块重编译,无分块ORC v1 ✅ 推荐 分块编译、按需链接、线程安全 ORC v2 🚧 新版 更现代 API,更好的优化时机
5.2 MCJIT 完整示例 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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 #include <llvm/IR/LLVMContext.h> #include <llvm/IR/Module.h> #include <llvm/IR/IRBuilder.h> #include <llvm/IR/Verifier.h> #include <llvm/ExecutionEngine/MCJIT.h> #include <llvm/ExecutionEngine/GenericValue.h> #include <llvm/Support/TargetSelect.h> #include <llvm/Support/raw_ostream.h> #include <iostream> int main () { llvm::InitializeNativeTarget (); llvm::InitializeNativeTargetAsmPrinter (); llvm::InitializeNativeTargetAsmParser (); llvm::LLVMContext ctx; auto mod = std::make_unique <llvm::Module>("fib" , ctx); llvm::IRBuilder<> b (ctx); auto * i32 = b.getInt32Ty (); auto * ft = llvm::FunctionType::get (i32, {i32}, false ); auto * fib = llvm::Function::Create ( ft, llvm::Function::ExternalLinkage, "fib" , mod.get ()); llvm::BasicBlock* entry = llvm::BasicBlock::Create (ctx, "entry" , fib); b.SetInsertPoint (entry); llvm::Value* n = fib->args ().begin (); n->setName ("n" ); llvm::Value* cmp = b.CreateICmpSLT (n, b.getInt32 (2 ), "cmp" ); llvm::BasicBlock* base = llvm::BasicBlock::Create (ctx, "base" , fib); llvm::BasicBlock* rec = llvm::BasicBlock::Create (ctx, "recurse" , fib); llvm::BasicBlock* end = llvm::BasicBlock::Create (ctx, "end" , fib); b.CreateCondBr (cmp, base, rec); b.SetInsertPoint (base); b.CreateBr (end); b.SetInsertPoint (rec); llvm::Value* n1 = b.CreateSub (n, b.getInt32 (1 ), "n1" ); llvm::Value* n2 = b.CreateSub (n, b.getInt32 (2 ), "n2" ); llvm::Value* r1 = b.CreateCall (fib, {n1}, "r1" ); llvm::Value* r2 = b.CreateCall (fib, {n2}, "r2" ); llvm::Value* r = b.CreateAdd (r1, r2, "r" ); b.CreateBr (end); b.SetInsertPoint (end); llvm::PHINode* phi = b.CreatePHI (i32, 2 , "result" ); phi->addIncoming (n, base); phi->addIncoming (r, rec); b.CreateRet (phi); llvm::verifyFunction (*fib); mod->print (llvm::errs (), nullptr ); std::string err; auto * engine = llvm::EngineBuilder (std::move (mod)) .setErrorStr (&err) .create (); if (!engine) { std::cerr << "Engine: " << err << "\n" ; return 1 ; } auto * fib_addr = engine->getFunctionAddress ("fib" ); if (!fib_addr) { std::cerr << "fib not found\n" ; return 1 ; } using fib_fn = int (*)(int ); auto fib_c = reinterpret_cast <fib_fn>(fib_addr); std::cout << "fib(10) = " << fib_c (10 ) << "\n" ; std::cout << "fib(35) = " << fib_c (35 ) << "\n" ; delete engine; return 0 ; }
1 2 3 4 $ clang++ -std=c++17 mcjit_demo.cpp $(llvm-config --cxxflags --ldflags --libs core executionengine mcjit) -o mcjit_demo $ ./mcjit_demo fib(10) = 55 fib(35) = 9227465
5.3 ORC v2 完整示例(推荐) 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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 #include <llvm/IR/LLVMContext.h> #include <llvm/IR/Module.h> #include <llvm/IR/IRBuilder.h> #include <llvm/ExecutionEngine/Orc/LLJIT.h> #include <llvm/ExecutionEngine/Orc/ThreadSafeModule.h> #include <llvm/Support/TargetSelect.h> #include <llvm/Support/raw_ostream.h> #include <iostream> int main () { llvm::InitializeNativeTarget (); llvm::InitializeNativeTargetAsmPrinter (); auto jit = llvm::orc::LLJITBuilder ().create (); if (!jit) { std::cerr << "Failed to create JIT\n" ; return 1 ; } auto J = std::move (*jit); llvm::LLVMContext ctx; auto mod = std::make_unique <llvm::Module>("fib" , ctx); llvm::IRBuilder<> b (ctx); auto * i32 = b.getInt32Ty (); auto * ft = llvm::FunctionType::get (i32, {i32}, false ); auto * fib = llvm::Function::Create ( ft, llvm::Function::ExternalLinkage, "fib" , mod.get ()); llvm::BasicBlock* entry = llvm::BasicBlock::Create (ctx, "entry" , fib); b.SetInsertPoint (entry); llvm::Value* n = fib->args ().begin (); n->setName ("n" ); llvm::Value* cmp = b.CreateICmpSLT (n, b.getInt32 (2 ), "cmp" ); llvm::BasicBlock* base = llvm::BasicBlock::Create (ctx, "base" , fib); llvm::BasicBlock* rec = llvm::BasicBlock::Create (ctx, "recurse" , fib); llvm::BasicBlock* end = llvm::BasicBlock::Create (ctx, "end" , fib); b.CreateCondBr (cmp, base, rec); b.SetInsertPoint (base); b.CreateBr (end); b.SetInsertPoint (rec); llvm::Value* n1 = b.CreateSub (n, b.getInt32 (1 ), "n1" ); llvm::Value* n2 = b.CreateSub (n, b.getInt32 (2 ), "n2" ); llvm::Value* r1 = b.CreateCall (fib, {n1}, "r1" ); llvm::Value* r2 = b.CreateCall (fib, {n2}, "r2" ); llvm::Value* r = b.CreateAdd (r1, r2, "r" ); b.CreateBr (end); b.SetInsertPoint (end); llvm::PHINode* phi = b.CreatePHI (i32, 2 , "result" ); phi->addIncoming (n, base); phi->addIncoming (r, rec); b.CreateRet (phi); llvm::orc::ThreadSafeModule tsm (std::move(mod), std::make_unique<llvm::LLVMContext>(std::move(ctx))) ; if (J->addIRModule (std::move (tsm))) { std::cerr << "Failed to add module\n" ; return 1 ; } auto fib_sym = J->lookup ("fib" ); if (!fib_sym) { std::cerr << "fib not found\n" ; return 1 ; } using fib_fn = int (*)(int ); auto fib_c = reinterpret_cast <fib_fn>(fib_sym->getAddress ()); std::cout << "fib(35) = " << fib_c (35 ) << "\n" ; return 0 ; }
1 2 3 $ clang++ -std=c++17 orc_jit.cpp $(llvm-config --cxxflags --ldflags --libs orcjit core native) -o orc_jit $ ./orc_jit fib(35) = 9227465
5.4 ORC 的 4 层架构 graph TB
L1["ObjectLinkingLayer<br/>(.o → 内存)"]:::l1
L2["CompileLayer<br/>(IR → .o)"]:::l2
L3["OptimizeLayer<br/>(IR → 优化后 IR)"]:::l3
L4["IRTransformLayer<br/>(IR → IR,可选)"]:::l4
L4 --> L3 --> L2 --> L1
style L1 fill:#B5EAD7,stroke:#80CBC4,color:#333
style L2 fill:#FFDAB9,stroke:#FFAB76,color:#333
style L3 fill:#E8D5F5,stroke:#CE93D8,color:#333
style L4 fill:#C7CEEA,stroke:#9FA8DA,color:#333层 作用 输入 输出 ObjectLinkingLayer 链接 .o,加载符号 .o(字节)内存中可执行代码 CompileLayer 编译 IR 到 .o Module.oOptimizeLayer 跑优化 Pass Module优化 Module IRTransformLayer IR 级变换 Module变换 Module
核心观察 :ORC 是栈式架构 ——每一层包装下一层,按需 lazy 编译 。这意味着如果某个函数没被调用,它的 IR 根本不会被编译 。
5.5 JIT vs AOT 性能对比 维度 JIT(ORC) AOT(clang) 差异原因 首次调用延迟 1-100 ms 0 ns JIT 要编译 稳定后性能 相同 相同 都生成机器码 运行时优化 ✅ 可基于 profile ❌ 静态 JIT 可看运行时类型 跨进程缓存 ❌ ✅ AOT 一次编译永久用 适用场景 REPL / DSL / 模板 生产部署 —
关键观察 :JIT 不是为”更快”而存在 ——它是为”运行时编译”而存在。LuaJIT、Numba、V8 都是因为这一点才选 JIT。
六、优化 Pass 实战:调度 70+ 个优化 6.1 LLVM Pass 的层级 graph TB
MPM["ModulePassManager<br/>(整个 Module)"]:::mod
FPM["FunctionPassManager<br/>(每个 Function)"]:::func
BBPM["BasicBlockPassManager<br/>(每个 BasicBlock)"]:::bb
LP["LoopPassManager<br/>(每个 Loop)"]:::loop
MPM --> FPM
FPM --> BBPM
FPM --> LP
style MPM fill:#FFB3C6,stroke:#F48FB1,color:#333
style FPM fill:#E8D5F5,stroke:#CE93D8,color:#333
style BBPM fill:#B5EAD7,stroke:#80CBC4,color:#333
style LP fill:#FFDAB9,stroke:#FFAB76,color:#333Pass 类型 作用范围 示例 Pass ModulePass 整个 Module 过程间优化(IPA)、内联、全局变量优化 FunctionPass 单个 Function 常量折叠、死代码消除、内联展开 BasicBlockPass 单个 BasicBlock 指令合并、窥孔优化 LoopPass 单个 Loop 循环展开、向量化、循环不变代码外提
6.2 新旧 Pass Manager 对比 graph LR
subgraph "旧 PassManager (LLVM 13 前)"
OPM["PassManager<Module>"]:::old
OFPM["FunctionPassManager"]:::old
OPM --> OFPM
end
subgraph "新 PassManager (LLVM 14+ 推荐)"
NPM["PassBuilder"]:::new
NFAM["FunctionAnalysisManager"]:::new
NMAM["ModuleAnalysisManager"]:::new
NFPM["FunctionPassManager"]:::new
NPM --> NFAM
NPM --> NMAM
NFAM --> NFPM
end
style OPM fill:#FFDAB9,stroke:#FFAB76,color:#333
style OFPM fill:#FFDAB9,stroke:#FFAB76,color:#333
style NPM fill:#B5EAD7,stroke:#80CBC4,color:#333
style NFAM fill:#B5EAD7,stroke:#80CBC4,color:#333
style NMAM fill:#B5EAD7,stroke:#80CBC4,color:#333
style NFPM fill:#B5EAD7,stroke:#80CBC4,color:#333维度 旧 PassManager 新 PassManager API 入口 llvm::legacy::PassManagerllvm::PassBuilderAnalysis 管理 手动 getAnalysisUsage 自动依赖分析缓存 跨模块缓存 ❌ ✅(性能提升明显) 跨函数优化 需要 IPA Pass 自己管理 天然支持 可序列化 ❌ ✅(可用于 thinLTO) LLVM 版本 弃用中 强烈推荐
6.3 跑 -O2 优化流水线 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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <llvm/IR/LLVMContext.h> #include <llvm/IR/Module.h> #include <llvm/IRReader/IRReader.h> #include <llvm/Passes/PassBuilder.h> #include <llvm/Support/SourceMgr.h> #include <llvm/Support/raw_ostream.h> #include <chrono> #include <iostream> int main (int argc, char ** argv) { if (argc < 2 ) { std::cerr << "Usage: " << argv[0 ] << " <file.ll>\n" ; return 1 ; } llvm::LLVMContext ctx; llvm::SMDiagnostic err; auto mod = llvm::parseIRFile (argv[1 ], err, ctx); if (!mod) { err.print (argv[0 ], llvm::errs ()); return 1 ; } llvm::LoopAnalysisManager lam; llvm::FunctionAnalysisManager fam; llvm::CGSCCAnalysisManager cgam; llvm::ModuleAnalysisManager mam; llvm::PassBuilder pb; pb.registerModuleAnalyses (mam); pb.registerFunctionAnalyses (fam); pb.registerCGSCCAnalyses (cgam); pb.registerLoopAnalyses (lam); pb.crossRegisterProxies (lam, fam, cgam, mam); llvm::ModulePassManager mpm; auto start = std::chrono::high_resolution_clock::now (); mpm = pb.buildPerModuleDefaultPipeline (llvm::OptimizationLevel::O2); mpm.run (*mod, mam); auto end = std::chrono::high_resolution_clock::now (); auto ms = std::chrono::duration_cast <std::chrono::milliseconds>(end - start).count (); std::cerr << "Optimization took " << ms << " ms\n" ; mod->print (llvm::errs (), nullptr ); return 0 ; }
1 2 3 $ clang -O0 -emit-llvm -S fib.c -o fib.ll $ clang++ -std=c++17 run_opt.cpp $(llvm-config --cxxflags --ldflags --libs core passes support) -o run_opt $ ./run_opt fib.ll > fib.opt.ll
6.4 优化等级对照表 等级 启用优化 典型 Pass 数 编译时间 性能 O0 无 0 1x 1x O1 简单本地优化 ~10 1.5x 1.3x O2 主流优化(默认) ~50 3x 1.7x O3 + 激进向量化 ~70 5x 1.8x Os O2 + 体积优化 ~60 3x 1.6x Oz O2 + 极致小 ~60 3x 1.5x
6.5 -O2 流水线的 50+ Pass graph TB
S1["阶段1: Early 简化<br/>CFG 简化、死代码<br/>SROA(聚合重构)"]:::s1
S2["阶段2: 内联<br/>InlineFunction<br/>+ 递归内联"]:::s2
S3["阶段3: 过程间<br/>IPSCCP<br/>函数属性推断"]:::s3
S4["阶段4: 标量优化<br/>GVN(全局值编号)<br/>LICM(循环不变外提)"]:::s4
S5["阶段5: 循环优化<br/>循环展开<br/>循环向量化"]:::s5
S6["阶段6: 晚期优化<br/>指令合并<br/>窥孔"]:::s6
S1 --> S2 --> S3 --> S4 --> S5 --> S6
style S1 fill:#C7CEEA,stroke:#9FA8DA,color:#333
style S2 fill:#E8D5F5,stroke:#CE93D8,color:#333
style S3 fill:#FFDAB9,stroke:#FFAB76,color:#333
style S4 fill:#B5EAD7,stroke:#80CBC4,color:#333
style S5 fill:#FFF9C4,stroke:#F9A825,color:#333
style S6 fill:#FFB3C6,stroke:#F48FB1,color:#333典型 Pass 清单 (O2 中运行):
Pass 作用 速度提升 SROA聚合体(struct/array)→ 标量 5-10% EarlyCSE早期公共子表达式消除 3-5% GVN全局值编号(重命名相同表达式) 10-20% LICM循环不变代码外提 5-15% IndVarSimplify归纳变量简化 5% LoopUnroll循环展开 10-30% Inline函数内联 15-30% (最关键)DSE死存储消除 5% ADCE激进死代码消除 5% InstCombine指令合并(窥孔) 5-10% SimplifyCFGCFG 简化 3-5% ConstantProp常量传播 5-10%
6.6 测量每个 Pass 的耗时 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <llvm/Passes/StandardInstrumentations.h> #include <llvm/IR/PassTimingInfo.h> llvm::StandardInstrumentations si (ctx, false ) ;si.registerCallbacks (pic, &mam); llvm::TimePassesHandler time_passes; time_passes.run (); mpm = pb.buildPerModuleDefaultPipeline (llvm::OptimizationLevel::O2); mpm.run (*mod, mam); time_passes.run ();
1 2 3 4 5 6 7 8 9 10 11 12 $ ./run_opt fib.ll -time-passes 2>&1 | head -30 ===-------------------------------------------------------------------------=== ... Pass execution timing report ... ===-------------------------------------------------------------------------=== Total Execution Time: 0.0127 seconds (0.0129 wall clock) ---User Time--- --System Time-- --User+System-- ---Wall Time--- --- Name --- 0.0040 ( 31.6%) 0.0000 ( 0.0%) 0.0040 ( 31.6%) 0.0041 ( 31.7%) SROA 0.0025 ( 19.7%) 0.0000 ( 0.0%) 0.0025 ( 19.7%) 0.0025 ( 19.6%) GVN 0.0018 ( 14.2%) 0.0000 ( 0.0%) 0.0018 ( 14.2%) 0.0018 ( 14.1%) InstCombine 0.0015 ( 11.8%) 0.0000 ( 0.0%) 0.0015 ( 11.8%) 0.0015 ( 11.8%) SimplifyCFG ...
七、目标代码生成:发射 .o 文件 + 交叉编译 7.1 TargetMachine 实战 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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 #include <llvm/IR/LLVMContext.h> #include <llvm/IR/Module.h> #include <llvm/IR/IRBuilder.h> #include <llvm/IR/Verifier.h> #include <llvm/Support/TargetSelect.h> #include <llvm/Support/FileSystem.h> #include <llvm/Support/raw_ostream.h> #include <llvm/Target/TargetMachine.h> #include <llvm/Target/TargetOptions.h> #include <llvm/MC/TargetRegistry.h> #include <llvm/CodeGen/CommandFlags.h> #include <llvm/CodeGen/TargetPassConfig.h> #include <iostream> int main () { llvm::InitializeAllTargetInfos (); llvm::InitializeAllTargets (); llvm::InitializeAllTargetMCs (); llvm::InitializeAllAsmParsers (); llvm::InitializeAllAsmPrinters (); llvm::LLVMContext ctx; auto mod = std::make_unique <llvm::Module>("fib" , ctx); llvm::IRBuilder<> b (ctx); auto * i32 = b.getInt32Ty (); auto * ft = llvm::FunctionType::get (i32, {i32}, false ); auto * fib = llvm::Function::Create ( ft, llvm::Function::ExternalLinkage, "fib" , mod.get ()); llvm::BasicBlock* entry = llvm::BasicBlock::Create (ctx, "entry" , fib); b.SetInsertPoint (entry); llvm::Value* n = fib->args ().begin (); n->setName ("n" ); llvm::Value* cmp = b.CreateICmpSLT (n, b.getInt32 (2 ), "cmp" ); llvm::BasicBlock* base = llvm::BasicBlock::Create (ctx, "base" , fib); llvm::BasicBlock* rec = llvm::BasicBlock::Create (ctx, "recurse" , fib); llvm::BasicBlock* end = llvm::BasicBlock::Create (ctx, "end" , fib); b.CreateCondBr (cmp, base, rec); b.SetInsertPoint (base); b.CreateBr (end); b.SetInsertPoint (rec); llvm::Value* n1 = b.CreateSub (n, b.getInt32 (1 ), "n1" ); llvm::Value* n2 = b.CreateSub (n, b.getInt32 (2 ), "n2" ); llvm::Value* r1 = b.CreateCall (fib, {n1}, "r1" ); llvm::Value* r2 = b.CreateCall (fib, {n2}, "r2" ); b.CreateRet (b.CreateAdd (r1, r2, "r" )); b.SetInsertPoint (end); llvm::PHINode* phi = b.CreatePHI (i32, 2 , "r" ); phi->addIncoming (n, base); phi->addIncoming (b.CreateAdd (r1, r2, "r" ), rec); b.CreateRet (phi); auto triple_str = llvm::sys::getDefaultTargetTriple (); llvm::Triple triple (triple_str) ; std::string err; auto * target = llvm::TargetRegistry::lookupTarget (triple_str, err); if (!target) { std::cerr << err << "\n" ; return 1 ; } auto * tm = target->createTargetMachine ( triple_str, "generic" , "" , llvm::TargetOptions (), llvm::Optional <llvm::Reloc::Model>(), llvm::Optional <llvm::CodeModel::Model>(), llvm::CodeGenOptLevel::Default); mod->setDataLayout (tm->createDataLayout ()); mod->setTargetTriple (triple_str); std::error_code ec; llvm::raw_fd_ostream dest ("fib.o" , ec, llvm::sys::fs::OF_None) ; if (ec) { std::cerr << ec.message () << "\n" ; return 1 ; } llvm::legacy::PassManager pm; if (tm->addPassesToEmitFile (pm, dest, llvm::CGFT_ObjectFile)) { std::cerr << "Cannot emit\n" ; return 1 ; } pm.run (*mod); dest.flush (); std::cout << "Wrote fib.o\n" ; delete tm; return 0 ; }
1 2 3 4 5 6 7 $ clang++ -std=c++17 emit_object.cpp $(llvm-config --cxxflags --ldflags --libs core target) -o emit_object $ ./emit_object $ file fib.o fib.o: Mach-O 64-bit object arm64 $ clang fib.o -o fib $ ./fib $ echo $?
7.2 交叉编译:x86-64 → ARM64 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 $ llvm-config --host-target x86_64-apple-darwin25.3.0 $ llc --version Registered Targets: aarch64 - AArch64 (little endian) aarch64_be - AArch64 (big endian) amdgcn - AMD GCN GPUs arm - ARM riscv32 - 32-bit RISC-V riscv64 - 64-bit RISC-V x86 - 32-bit x86 x86-64 - 64-bit x86 wasm32 - WebAssembly 32-bit wasm64 - WebAssembly 64-bit $ llc -mtriple=aarch64-apple-ios fib.ll -o fib_aarch64.s $ cat fib_aarch64.s | head -30
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 33 #include <llvm/Target/TargetMachine.h> #include <llvm/Support/TargetSelect.h> #include <llvm/MC/TargetRegistry.h> int main () { llvm::InitializeAllTargetInfos (); llvm::InitializeAllTargets (); llvm::InitializeAllTargetMCs (); llvm::InitializeAllAsmParsers (); llvm::InitializeAllAsmPrinters (); const char * triples[] = { "x86_64-apple-darwin" , "aarch64-apple-ios" , "aarch64-linux-android" , "wasm32-unknown-unknown" , "riscv64-linux-gnu" , }; for (auto * triple : triples) { std::string err; auto * target = llvm::TargetRegistry::lookupTarget (triple, err); if (!target) { llvm::errs () << err << "\n" ; continue ; } llvm::outs () << triple << " -> " << target->getName () << "\n" ; } return 0 ; }
7.3 后端支持矩阵 后端 状态 主要架构 x86 / x86-64✅ 成熟 Intel/AMD CPU aarch64 / aarch64_be✅ 成熟 ARM64(Apple Silicon, 服务器) arm / armeb✅ 成熟 ARMv7(旧手机/嵌入式) riscv32 / riscv64✅ 成熟 RISC-V(开源硬件) wasm32 / wasm64✅ 成熟 WebAssembly(浏览器/边缘) amdgcn✅ 成熟 AMD GPU nvptx / nvptx64✅ 成熟 NVIDIA GPU powerpc64le✅ 成熟 IBM POWER(小端序) mips / mipsel⚠️ 维护 MIPS(路由器) loongarch64✅ 较新 龙芯 ve⚠️ 实验 NEC SX-Aurora
7.4 WebAssembly 后端示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ clang --target=wasm32-unknown-unknown -O2 -emit-llvm -S fib.c -o fib.ll $ llc -mtriple=wasm32-unknown-unknown fib.ll -o fib.s $ cat fib.s | head -50
核心观察 :同一份 LLVM IR 可以直接编译到 15+ 种目标 ——这就是 LLVM 真正的力量。
八、链接与执行:链接 libc/libm 8.1 TargetLibraryInfo:链接 libc 的关键 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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <llvm/Analysis/TargetLibraryInfo.h> #include <llvm/Passes/PassBuilder.h> int main () { llvm::Triple triple (llvm::sys::getDefaultTargetTriple()) ; llvm::TargetLibraryInfoImpl tlii (triple) ; tlii.disableAllFunctions (); tlii.setAvailable (llvm::Triple::x86_64); llvm::PassBuilder pb; llvm::LoopAnalysisManager lam; llvm::FunctionAnalysisManager fam; llvm::CGSCCAnalysisManager cgam; llvm::ModuleAnalysisManager mam; pb.registerModuleAnalyses (mam); pb.registerFunctionAnalyses (fam); pb.registerCGSCCAnalyses (cgam); pb.registerLoopAnalyses (lam); pb.crossRegisterProxies (lam, fam, cgam, mam); fam.registerPass ([&] { return llvm::TargetLibraryAnalysis (tlii); }); llvm::LLVMContext ctx; auto mod = std::make_unique <llvm::Module>("test" , ctx); llvm::IRBuilder<> b (ctx); auto * i8ptr = b.getPtrTy (); auto * ft = llvm::FunctionType::get (b.getInt64Ty (), {i8ptr}, false ); auto * strlen_fn = llvm::Function::Create ( ft, llvm::Function::ExternalLinkage, "strlen" , mod.get ()); llvm::BasicBlock* bb = llvm::BasicBlock::Create (ctx, "entry" , mod->getFunctionList ().begin ()); b.SetInsertPoint (bb); llvm::Value* s = b.CreateGlobalStringPtr ("hello, world!" ); llvm::Value* len = b.CreateCall (strlen_fn, {s}); b.CreateRet (len); llvm::ModulePassManager mpm = pb.buildPerModuleDefaultPipeline (llvm::OptimizationLevel::O2); mpm.run (*mod, mam); mod->print (llvm::errs (), nullptr ); return 0 ; }
优化后的输出 :
1 2 3 4 define i64 @strlen_wrapper (ptr %0 ) {entry: ret i64 13 }
核心观察 :LLVM 知道 libc 函数的语义 (strlen、sin、pow)——-O2 会把它们内联成常量 。这就是为什么 #include <math.h> 的 pow 反而比手写循环快。
8.2 JIT 模式链接 libc 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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <llvm/ExecutionEngine/Orc/LLJIT.h> #include <llvm/IR/LLVMContext.h> #include <llvm/IR/Module.h> #include <llvm/IR/IRBuilder.h> int main () { llvm::InitializeNativeTarget (); llvm::InitializeNativeTargetAsmPrinter (); auto jit = llvm::orc::LLJITBuilder ().create (); auto J = std::move (*jit); llvm::LLVMContext ctx; auto mod = std::make_unique <llvm::Module>("hello" , ctx); llvm::IRBuilder<> b (ctx); auto * i32 = b.getInt32Ty (); auto * i8ptr = b.getPtrTy (); auto * ft = llvm::FunctionType::get (i32, {i8ptr}, true ); auto * printf_fn = llvm::Function::Create ( ft, llvm::Function::ExternalLinkage, "printf" , mod.get ()); auto * main_ty = llvm::FunctionType::get (i32, {}, false ); auto * main_fn = llvm::Function::Create ( main_ty, llvm::Function::ExternalLinkage, "main" , mod.get ()); llvm::BasicBlock* bb = llvm::BasicBlock::Create (ctx, "entry" , main_fn); b.SetInsertPoint (bb); llvm::Value* s = b.CreateGlobalStringPtr ("Hello from JIT!\n" ); b.CreateCall (printf_fn, {s}); b.CreateRet (b.getInt32 (0 )); J->getMainJITDylib ().addGenerator ( llvm::cantFail (llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess ( llvm::orc::SymbolGenerator::UndefPolicy::Generate, llvm::orc::SymbolGenerator::LookupPolicy::ExportedOnly))); llvm::orc::ThreadSafeModule tsm ( std::move(mod), std::make_unique<llvm::LLVMContext>(std::move(ctx))) ; if (J->addIRModule (std::move (tsm))) return 1 ; auto main_sym = J->lookup ("main" ); using main_fn_t = int (*)(); auto main_c = reinterpret_cast <main_fn_t >(main_sym->getAddress ()); return main_c (); }
1 2 3 $ clang++ -std=c++17 jit_libc.cpp $(llvm-config --cxxflags --ldflags --libs orcjit core) -o jit_libc $ ./jit_libc Hello from JIT!
8.3 emit .o 后链接 1 2 3 4 5 6 7 8 9 10 11 12 13 $ llc fib.ll -filetype=obj -o fib.o $ clang fib.o -o fib $ otool -L fib $ ldd fib $ ./fib $ echo $?
8.4 IR 链接:合并多个 Module 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 $ cat > utils.ll << 'EOF' define i32 @square(i32 %x) { %r = mul i32 %x, %x ret i32 %r } EOF $ cat > main.ll << 'EOF' declare i32 @square(i32)define i32 @main () { %r = call i32 @square(i32 7) ret i32 %r } EOF $ llvm-link utils.ll main.ll -o combined.ll $ cat combined.ll $ clang combined.ll -o combined $ ./combined $ echo $? 49
九、深入:自定义 Pass 9.1 统计加法指令数的 Pass 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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <llvm/IR/Function.h> #include <llvm/IR/Instructions.h> #include <llvm/IR/PassManager.h> #include <llvm/IR/PassManagerPlugin.h> #include <llvm/Passes/PassBuilder.h> #include <llvm/Passes/PassPlugin.h> #include <llvm/Support/raw_ostream.h> using namespace llvm;class CountAddsPass : public PassInfoMixin<CountAddsPass> {public : PreservedAnalyses run (Function &F, FunctionAnalysisManager &) { int add_count = 0 ; int sub_count = 0 ; int mul_count = 0 ; for (auto &BB : F) { for (auto &I : BB) { if (I.getOpcode () == Instruction::Add) { add_count++; } else if (I.getOpcode () == Instruction::Sub) { sub_count++; } else if (I.getOpcode () == Instruction::Mul) { mul_count++; } } } errs () << "Function " << F.getName () << ":\n" ; errs () << " adds = " << add_count << "\n" ; errs () << " subs = " << sub_count << "\n" ; errs () << " muls = " << mul_count << "\n" ; return PreservedAnalyses::all (); } }; extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo () { return {LLVM_PLUGIN_API_VERSION, "CountAdds" , "1.0" , [](PassBuilder &PB) { PB.registerPipelineParsingCallback ( [](StringRef Name, FunctionPassManager &FPM, ArrayRef<PassBuilder::PipelineElement>) { if (Name == "count-adds" ) { FPM.addPass (CountAddsPass ()); return true ; } return false ; }); }}; }
9.2 编译为插件 1 2 3 add_library(CountAdds MODULE CountAdds.cpp) target_link_libraries(CountAdds PRIVATE LLVMCore LLVMSupport LLVMTransformUtils)
1 2 3 4 $ mkdir build && cd build $ cmake .. $ make $ ls CountAdds.so
9.3 在 opt 中使用 1 2 3 4 5 6 7 8 9 $ clang -O0 -emit-llvm -S fib.c -o fib.ll $ opt -load-pass-plugin=./CountAdds.so -passes="count-adds" fib.ll -disable-output Function fib: adds = 1 subs = 2 muls = 1
9.4 在新 PassBuilder 中嵌入自定义 Pass 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 #include <llvm/Passes/PassBuilder.h> int main () { llvm::PassBuilder pb; pb.registerPipelineParsingCallback ( [](llvm::StringRef Name, llvm::FunctionPassManager &FPM, llvm::ArrayRef<llvm::PassBuilder::PipelineElement>) { if (Name == "count-adds" ) { FPM.addPass (CountAddsPass ()); return true ; } return false ; }); llvm::ModulePassManager mpm = pb.buildPerModuleDefaultPipeline (llvm::OptimizationLevel::O2); FunctionPassManager fpm; fpm.addPass (CountAddsPass ()); fpm.addPass (InstCombinePass ()); }
9.5 Pass 插件 vs 内置 维度 内置 Pass Pass 插件 编译方式 静态链接到 opt 动态库(.so) 加载方式 opt -passes=my-passopt -load-pass-plugin=my.so重启 opt 需要重新编译 opt 不需要 适合场景 通用优化 业务特定、实验性 生产部署 难以分发 可作为独立 SDK 分发
十、端到端实战:从 .mini 到跨平台执行 10.1 完整工作流 graph LR
SRC["fib.mini<br/>MiniLang 源码"]:::src
LEX["Lexer<br/>(#1 复用)"]:::l1
PAR["Parser<br/>(#1 复用)"]:::l2
CG["LLVMCodeGen<br/>(本文)"]:::l3
MOD["Module<br/>fib.bc"]:::l4
OPT["opt -O2<br/>(优化)"]:::l5
LLC["llc<br/>(IR→机器码)"]:::l6
O1["fib_x86.o"]:::out
O2["fib_arm64.o"]:::out
O3["fib_wasm.o"]:::out
JIT["JIT<br/>(运行时)"]:::out
EXE["可执行文件"]:::exe
SRC ==> LEX ==> PAR ==> CG ==> MOD
MOD -.->|"O2 优化"| OPT
MOD -.->|"x86 后端"| LLC
MOD -.->|"ARM64 后端"| LLC
MOD -.->|"WASM 后端"| LLC
MOD -.->|"运行时"| JIT
OPT -.-> LLC
LLC ==> O1
LLC ==> O2
LLC ==> O3
O1 ==> EXE
O2 ==> EXE
O3 ==> EXE
style SRC fill:#C7CEEA,stroke:#9FA8DA,color:#333
style LEX fill:#E8D5F5,stroke:#CE93D8,color:#333
style PAR fill:#E8D5F5,stroke:#CE93D8,color:#333
style CG fill:#FFB3C6,stroke:#F48FB1,color:#333
style MOD fill:#FFDAB9,stroke:#FFAB76,color:#333
style OPT fill:#B5EAD7,stroke:#80CBC4,color:#333
style LLC fill:#B5EAD7,stroke:#80CBC4,color:#333
style JIT fill:#FFF9C4,stroke:#F9A825,color:#333
style EXE fill:#B5EAD7,stroke:#80CBC4,color:#33310.2 性能基准测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 $ time ./minic fib.mini real 1m12s ← 慢 1000x(解释执行) $ ./llvm_minic fib.mini jit real 0m08s ← 200x $ ./llvm_minic fib.mini jit -O2 real 0m02s ← 70x $ ./llvm_minic fib.mini emit $ clang output.o -o fib $ time ./fib real 0m01s ← 30x(=原生 C)
模式 fib(35) 耗时 加速比 #1 mini 解释器 72.0 s 1x LLVM IR + JIT O0 8.0 s 9x LLVM IR + JIT O2 2.0 s 36x LLVM IR + AOT 1.0 s 72x 原生 C (clang -O2) 1.0 s 72x (=AOT)
核心观察 :LLVM IR + AOT = 原生 C 性能 。LLVM 的所有抽象(SSA、Pass、Target)几乎零开销 ——这是它被工业界广泛采用的根本原因。
10.3 跨平台分发实战 1 2 3 4 5 6 7 8 9 $ ./llvm_minic fib.mini emit x86_64-apple-darwin $ ./llvm_minic fib.mini emit aarch64-apple-ios $ ./llvm_minic fib.mini emit wasm32-unknown-unknown fib_x86.o fib_aarch64.o fib_wasm.o
十一、LLVM API 调试技巧 11.1 验证 IR 正确性 1 2 3 4 5 6 7 8 9 10 11 #include <llvm/IR/Verifier.h> if (llvm::verifyFunction (*fib, &llvm::errs ())) { throw std::runtime_error ("Function verification failed" ); } if (llvm::verifyModule (*module , &llvm::errs ())) { throw std::runtime_error ("Module verification failed" ); }
11.2 打印 IR 1 2 3 4 5 6 7 8 9 10 11 12 13 14 module_->print (llvm::errs (), nullptr ); std::error_code ec; llvm::raw_fd_ostream out ("debug.ll" , ec) ;module_->print (out, nullptr ); fib->print (llvm::errs ()); inst->print (llvm::errs ()); inst->dump ();
11.3 常见错误 错误现象 原因 解决 Instruction does not dominate...使用未定义的 SSA 值 检查基本块顺序 PHI node entries do not matchphi 节点前驱不匹配 addIncoming 时检查 BBUse of undefined instruction跨 BB 引用 用 CreateBr + phi Function type mismatch参数类型不一致 cast<Function> 检查签名BasicBlock already has a terminatorBB 末尾已 ret 不要重复 CreateRet
11.4 GDB 调试 1 2 3 4 5 6 7 8 9 $ clang++ -std=c++17 -g -O0 llvm_minic.cpp $(llvm-config --cxxflags --ldflags --libs) -o llvm_minic $ gdb ./llvm_minic (gdb) break LLVMCodeGen::gen_expr (gdb) run fib.mini jit (gdb) print e (gdb) continue
十二、深度对比:手写 vs LLVM vs GCC 12.1 代码量对比 阶段 #1 手写 #4 LLVM 比例 词法分析 150 行 0(复用 #1) — 语法分析 200 行 0(复用 #1) — 语义分析 100 行 0(LLVM 自动推断) 省 100% IR 生成 200 行 50 行 省 75% 优化 0 行 0 行(opt 自动) 省 100% 后端 100 行(#3 手写 x86) 50 行(TargetMachine) 省 50% 链接 0 行 0 行(外部工具) — 总计 800 行 100 行 省 87%
12.2 能力对比 能力 #1 + #3 手写 #4 LLVM 优化 ❌ 无 ✅ 70+ Pass 目标架构 ❌ 仅 x86 ✅ 15+ 架构 汇编输出 ✅ 仅 AT&T 语法 ✅ 所有 GAS 变体 调试信息 ❌ 无 ✅ DWARF LTO ❌ 无 ✅ thinLTO PGO ❌ 无 ✅ AutoFDO + IR PGO Sanitizer ❌ 无 ✅ ASan/TSan/UBSan JIT ❌ 无 ✅ ORC / MCJIT 学习曲线 ⭐⭐⭐⭐ ⭐⭐⭐
12.3 与 GCC 后端的对比 维度 GCC LLVM API 稳定性 ⚠️ 经常改 ✅ 稳定 文档 ⚠️ 散落 ✅ 完整 教程 ⚠️ 少 ✅ Kaleidoscope 完整 社区 老牌 活跃 C++ 标准 C++03 C++17/20 License GPL Apache 2.0 嵌入难度 极难 容易
十三、5 道递进思考题 # 难度 题目 1 ⭐ 把 #1 的 print 语句改成 JIT 时直接打印到 stdout ——不用 emit .o 2 ⭐⭐ 给 MiniLang 加 for 循环——在 LLVMCodeGen 里要生成几个 BasicBlock? 3 ⭐⭐ 实现 bool 类型 ——LLVM IR 用 i1,但要处理与 int32 的互转 4 ⭐⭐⭐ 实现 数组字面量 [1, 2, 3]——用 CreateAlloca([3 x i32]) + GEP 5 ⭐⭐⭐⭐ 写一个 Pass 插件 :把 x * 2 自动优化为 x << 1——练习自定义 Pass
十四、推荐阅读 资源 类型 价值 LLVM Kaleidoscope Tutorial llvm.org/docs/tutorial 官方教程 手把手写 Kaleidoscope ——第 5 篇 JIT 的基础LLVM Programmer’s Manual 官方文档 API 索引、内存模型、线程安全 LLVM Developer Meeting 视频 llvm.org/devmtg 视频 Chris Lattner 等核心开发者分享 《Learning LLVM Core Libraries》 Supratik Majumder书籍 入门 LLVM 源码 《LLVM Cookbook》 Suyog Sarda书籍 实战视角 LLVM Weekly llvmweekly.org 周刊 社区动态 mlir.llvm.org 文档 MLIR(下一代 IR)入门 Compiler Explorer (godbolt.org) 工具 在线看 clang/LLVM 输出
十五、三段行动建议 行动 1:先复现,再创造 不要从自定义 Pass 开始 。先精确复现 本文的 fib 例子:
clang++ hello_llvm.cpp ... -o hello(第一个 IR 生成)clang++ mcjit_demo.cpp ... -o mcjit(JIT 执行)clang++ emit_object.cpp ... -o emit(发射 .o)这 3 个 50 行的程序覆盖了 LLVM 90% 的常用 API。
行动 2:用编译器探索器学会”读 IR” 打开 godbolt.org ——选 C++ → x86-64 clang (trunk) ,勾选 -O0 -emit-llvm 。 写 int fib(int n) { return n < 2 ? n : fib(n-1) + fib(n-2); },对照 LLVM IR 一行行读 。 每天看 10 个例子,一周后你就是 LLVM IR 专家。
行动 3:把 #1 的 mini 编译器升级为 LLVM 后端 把第 5 节 LLVMCodeGen 直接接入你的项目:
把 #1 的 Operand/TAC/CodeGen 删掉 换成 LLVMCodeGen 类(复用 #1 的 AST) 增加 jit_run() / emit_object() 接口 完成后你的 mini 编译器自动获得 15 个后端和 70 个优化 ——这就是 LLVM 的复利。
十六、本文小结 16.1 核心结论 LLVM 是 C++ 库,不是二进制 ——当你把 LLVM 当成 STL 用时,它会给你免费的多后端、多优化、JIT、Sanitizer。800 行手写代码能做的事,LLVM 用 100 行做,还做得更好 。
graph LR
SRC["MiniLang<br/>源码"]:::src
AST["AST<br/>(#1)"]:::ast
IR["LLVM IR<br/>(本文)"]:::ir
OPT["优化后 IR<br/>(O0~Oz)"]:::opt
MC["机器码<br/>(x86/ARM/WASM/...)"]:::mc
SRC ==>|"Lexer+Parser"| AST
AST ==>|"IRBuilder"| IR
IR -.->|"opt"| OPT
OPT -.->|"llc"| MC
style SRC fill:#C7CEEA,stroke:#9FA8DA,color:#333
style AST fill:#E8D5F5,stroke:#CE93D8,color:#333
style IR fill:#FFB3C6,stroke:#F48FB1,color:#333
style OPT fill:#FFDAB9,stroke:#FFAB76,color:#333
style MC fill:#B5EAD7,stroke:#80CBC4,color:#33316.2 知识点回顾 知识点 状态 关键代码 LLVM 三大件 ✅ 掌握 LLVMContext / Module / IRBuilder生成 IR ✅ 掌握 CreateAdd / CreateCall / CreateRet基本块 + phi ✅ 掌握 CreateCondBr + CreatePHI新 PassManager ✅ 掌握 PassBuilder + buildPerModuleDefaultPipelineJIT 编译 ✅ 掌握 LLJIT + addIRModule + lookup发射 .o ✅ 掌握 TargetMachine + addPassesToEmitFile交叉编译 ✅ 掌握 -mtriple=aarch64-apple-ios自定义 Pass ✅ 掌握 PassInfoMixin + PassBuilder
16.3 本文在系列中的位置 graph LR
A["#1: 4 阶段全打通"]:::done
B["#2: 8 大优化 Pass"]:::done
C["#3: x86-64 后端"]:::done
D["#4: LLVM 实战"]:::here
E["#5: JIT 原理"]:::next
A ==> B ==> C ==> D ==> E
style A fill:#B5EAD7,stroke:#80CBC4,color:#333
style B fill:#B5EAD7,stroke:#80CBC4,color:#333
style C fill:#B5EAD7,stroke:#80CBC4,color:#333
style D fill:#FFB3C6,stroke:#F48FB1,stroke-width:4px,color:#333
style E fill:#C7CEEA,stroke:#9FA8DA,color:#33316.4 进一步思考 为什么 LLVM 比 GCC 更适合做编译器基础设施?
单一 SSA IR ——GCC 有 GIMPLE/RTL/Machine 多级形式,LLVM 只有一种 IR库化 ——每个模块都是 .a/.so,可以单独嵌入 到任何 C++ 程序Pass 框架 ——70+ 优化 Pass 自由组合,无需重新编译 多后端 ——一份 IR 输出 15+ 架构,N×M 问题变 N+M License ——Apache 2.0 + LLVM Exception,不污染下游 这 5 个选择单独看都不算新颖 ,组合起来却重新定义了编译器行业 。
📚 编译原理实战 系列导航 本文是《编译原理实战》系列第 4/5 篇。
📖 全部 5 篇目录(点击展开) 第 1 篇:手写 C++17 编译器前端——词法、语法、语义、IR 4 阶段全打通 第 2 篇:8 大优化 Pass——常量折叠、死代码消除、循环优化 第 3 篇:手写 x86-64 后端——800 行汇编生成器 第 4 篇:LLVM 实战——用 LLVM C++ API 重写 mini 编译器 ← 当前第 5 篇:JIT 编译——运行时编译与 HotSpot 最后一句话 :LLVM 不是”高高在上的工业级编译器”——它就是一堆 C++ 库 ,当你用 IRBuilder::CreateAdd 这种简单 API 构造 IR 时,你和 Chris Lattner 写 clang 用的代码是同一种 。理解 LLVM 不是为了改 LLVM 源码,而是为了把它的能力嵌入到任何你想要的地方 ——DSL、JIT、模板引擎、领域特定优化,它都是你的工具箱。