mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1720 字
5 分钟
LLVM 架构深入
2026-01-31

LLVM 是当今最重要的编译器基础设施——Clang、Rust、Swift 等语言都使用 LLVM 作为后端。理解 LLVM 的架构,就理解了现代编译器的设计哲学:模块化、可扩展、库化。

现在来看LLVM——IR 有哪些层次?Pass 框架如何工作?优化流水线如何组织?如何编写自定义 Pass?

一、LLVM 的设计哲学#

1.1 LLVM 的核心思想#

LLVM 的名字不是缩写(虽然有人说是 Low Level Virtual Machine),它是一个编译器基础设施——一组可复用的编译器组件库:

graph TB subgraph LLVM设计哲学 LIB["库化设计<br/>编译器 = 组件库的组合"] MOD["模块化<br/>前端/中端/后端独立"] EXT["可扩展<br/>自定义 Pass/后端"] TYPE["类型化 IR<br/>编译期安全"] end style LIB fill:#e3f2fd,stroke:#1565c0 style MOD fill:#e8f5e9,stroke:#2e7d32 style EXT fill:#fff3e0,stroke:#e65100 style TYPE fill:#fce4ec,stroke:#c62828

1.2 LLVM vs GCC 架构对比#

特性LLVMGCC
设计库化(可嵌入其他程序)单体(命令行工具)
IRLLVM IR(SSA,统一)GIMPLE + RTL(两层)
优化框架Pass 管理器GCC Pass
可扩展性高(自定义 Pass/后端)低(需修改 GCC 源码)
许可证Apache 2.0GPL
IDE 集成好(libclang)
编译速度中等中等

二、LLVM IR 的层次#

2.1 三种 IR 表示#

LLVM IR 有三种等价的表示形式:

表示格式用途
人类可读.ll 文本调试、学习
位码.bc 二进制快速序列化
内存中C++ 对象编译器内部
# 三种表示的转换
clang -emit-llvm -S hello.c -o hello.ll # 源码 → .ll
clang -emit-llvm -c hello.c -o hello.bc # 源码 → .bc
llvm-dis hello.bc -o hello.ll # .bc → .ll
llvm-as hello.ll -o hello.bc # .ll → .bc

2.2 LLVM IR 的核心概念#

; 函数定义
define i32 @add(i32 %a, i32 %b) #0 {
entry:
%result = add i32 %a, %b
ret i32 %result
}
; 全局变量
@global_var = global i32 42
; 常量
@const_str = private unnamed_addr constant [13 x i8] c"hello world\0A\00"
; 类型
%struct.Point = type { i32, i32 }
; 属性
attributes #0 = { nounwind uwtable }

2.3 LLVM IR 类型系统#

类型语法说明
整数i1, i8, i16, i32, i64, i128任意位宽
浮点half, float, double, fp128IEEE 754
指针ptr (opaque)统一指针类型
数组[4 x i32]固定长度
向量<4 x i32>SIMD
结构体{ i32, float }匿名
命名结构体%struct.Point = type { i32, i32 }有名
函数i32 (i32, i32)*函数指针

2.4 IR 验证#

LLVM 有一个内置的 IR 验证器,检查 IR 的合法性:

# 验证 IR
opt -passes=verify hello.ll -o /dev/null
# 常见的验证错误
# - 非 SSA 形式(虚拟寄存器多次定义)
# - 类型不匹配
# - phi 节点参数数量不匹配
# - 基本块未以终止指令结尾
# - 使用未定义的虚拟寄存器

三、Pass 框架#

3.1 Pass 的概念#

Pass 是 LLVM 的基本优化单元——每个 Pass 执行一个特定的变换或分析:

flowchart TB INPUT["输入 IR"] --> P1["Pass 1<br/>mem2reg"] P1 --> P2["Pass 2<br/>instcombine"] P2 --> P3["Pass 3<br/>GVN"] P3 --> P4["Pass 4<br/>LICM"] P4 --> OUTPUT["输出 IR"] style INPUT fill:#e3f2fd,stroke:#1565c0 style OUTPUT fill:#e8f5e9,stroke:#2e7d32

3.2 Pass 的分类#

类型说明示例
变换 Pass修改 IRinstcombine, LICM, DCE
分析 Pass计算信息,不修改 IRDominatorTree, LoopInfo, AA
不可变 Pass提供不变信息TargetTransformInfo
工具 Pass辅助功能Verifier, Printer

3.3 编写自定义 Pass#

// LLVM 自定义 Pass 示例(C++)
#include "llvm/IR/Function.h"
#include "llvm/IR/PassManager.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
// 定义一个函数级变换 Pass
struct HelloPass : public PassInfoMixin<HelloPass> {
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) {
errs() << "Hello from: " << F.getName() << "\n";
// 遍历基本块和指令
for (BasicBlock &BB : F) {
for (Instruction &I : BB) {
// 统计指令类型
if (auto *BO = dyn_cast<BinaryOperator>(&I)) {
errs() << " Found binary op: " << BO->getOpcodeName() << "\n";
}
}
}
// 返回哪些分析结果仍然有效
return PreservedAnalyses::all();
}
// 注册为默认启用
static bool isRequired() { return true; }
};
// 插件注册
extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo
llvmGetPassPluginInfo() {
return {
LLVM_PLUGIN_API_VERSION, "HelloPass", "0.1",
[](PassBuilder &PB) {
PB.registerPipelineParsingCallback(
[](StringRef Name, FunctionPassManager &FPM,
ArrayRef<PassBuilder::PipelineElement>) {
if (Name == "hello-pass") {
FPM.addPass(HelloPass());
return true;
}
return false;
}
);
}
};
}

3.4 Pass 管理器#

LLVM 使用Pass 管理器来组织和调度 Pass:

# 使用 opt 运行 Pass
opt -passes='mem2reg,instcombine,gvn,licm' hello.ll -S -o hello_opt.ll
# 查看可用的 Pass
opt --print-passes
# 调试 Pass 执行
opt -debug-pass-manager -passes='default<O2>' hello.ll -o /dev/null

3.5 优化流水线#

LLVM 的 -O2 优化流水线包含数十个 Pass,按阶段组织:

flowchart TB subgraph 早期优化 M2R["mem2reg<br/>SSA提升"] IC1["instcombine<br/>指令合并"] SIMPLIFY["simplifycfg<br/>简化控制流"] end subgraph 循环优化 LICM_P["LICM<br/>不变量外提"] INDVAR["indvars<br/>归纳变量"] UNROLL["loop-unroll<br/>循环展开"] VECTOR["loop-vectorize<br/>向量化"] end subgraph 全局优化 GVN_P["GVN<br/>全局值编号"] CPROP["SCCP<br/>常量传播"] DCE_P["DCE<br/>死代码消除"] end subgraph 晚期优化 IC2["instcombine<br/>再次合并"] AGGRESSIVE["aggressive-instcombine<br/>激进合并"] ALIGN["align-all<br/>对齐"] end M2R --> IC1 --> SIMPLIFY --> LICM_P --> INDVAR --> UNROLL --> VECTOR VECTOR --> GVN_P --> CPROP --> DCE_P --> IC2 --> AGGRESSIVE style 早期优化 fill:#e3f2fd,stroke:#1565c0 style 循环优化 fill:#e8f5e9,stroke:#2e7d32 style 全局优化 fill:#fff3e0,stroke:#e65100 style 晚期优化 fill:#fce4ec,stroke:#c62828

3.5 新 Pass 管理器(New Pass Manager)#

LLVM 从版本 13 开始全面使用新 Pass 管理器——它取代了旧的管理器,提供了更好的分析结果缓存和更细粒度的控制:

flowchart TB PM["新 Pass 管理器架构"] PM --> MPM["ModulePassManager<br/>模块级 Pass"] PM --> FPM["FunctionPassManager<br/>函数级 Pass"] PM --> LPM["LoopPassManager<br/>循环级 Pass"] PM --> CGPM["CGSCCPassManager<br/>调用图级 Pass"] MPM --> M_ANAL["ModuleAnalysisManager"] FPM --> F_ANAL["FunctionAnalysisManager"] LPM --> L_ANAL["LoopAnalysisManager"] style PM fill:#e3f2fd,stroke:#1565c0 style MPM fill:#e8f5e9,stroke:#2e7d32 style FPM fill:#fff3e0,stroke:#e65100

新旧 Pass 管理器的关键区别:

维度旧 Pass 管理器新 Pass 管理器
分析缓存手动管理自动管理(AnalysisManager)
Pass 间通信通过全局数据通过 AnalysisManager 查询
增量编译困难自然支持
Pass 依赖隐式显式(isRequired())
线程安全是(每个函数独立 AnalysisManager)
失败处理abort回退到更低优化级别

3.6 分析 Pass 与变换 Pass 的协作#

分析 Pass 为变换 Pass 提供决策依据——变换 Pass 查询分析结果,然后修改 IR:

// 变换 Pass 查询分析结果
struct MyOptPass : public PassInfoMixin<MyOptPass> {
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) {
// 查询支配树分析
auto &DT = AM.getResult<DominatorTreeAnalysis>(F);
// 查询循环信息分析
auto &LI = AM.getResult<LoopAnalysis>(F);
// 基于分析结果做变换
for (Loop *L : LI) {
if (isSafeToHoist(L, DT)) {
hoistLoopInvariants(L);
}
}
// 声明哪些分析结果仍然有效
return PreservedAnalyses::none();
}
};

常用的分析 Pass 及其用途:

分析 Pass提供信息被谁使用
DominatorTreeAnalysis支配关系LICM, GVN, 循环优化
LoopAnalysis循环结构循环展开, 向量化
ScalarEvolutionAnalysis循环变量演化归纳变量优化
AAManager别名分析指针优化, 内存优化
TargetTransformInfo目标代价模型内联, 向量化决策
BranchProbabilityInfo分支概率基本块布局
MemorySSAAnalysis内存访问层次内存优化

3.7 LLVM IR 实例详解#

; 一个完整的 LLVM IR 函数示例
; 源码: int sum(int *arr, int n) {
; int s = 0;
; for (int i = 0; i < n; i++) s += arr[i];
; return s;
; }
define i32 @sum(ptr %arr, i32 %n) #0 {
entry:
%cmp0 = icmp sge i32 %n, 1
br i1 %cmp0, label %loop.header, label %exit
loop.header:
%i = phi i32 [ 0, %entry ], [ %i.next, %loop.latch ]
%s = phi i32 [ 0, %entry ], [ %s.next, %loop.latch ]
%idx = zext i32 %i to i64
%ptr = getelementptr inbounds i32, ptr %arr, i64 %idx
%val = load i32, ptr %ptr, align 4
%s.next = add i32 %s, %val
%i.next = add i32 %i, 1
%cmp = icmp slt i32 %i.next, %n
br i1 %cmp, label %loop.header, label %exit
exit:
%s.result = phi i32 [ 0, %entry ], [ %s.next, %loop.header ]
ret i32 %s.result
}
attributes #0 = { nounwind uwtable }
Note

注意 phi 节点的用法——它是 SSA 的核心机制。phi i32 [ 0, %entry ], [ %s.next, %loop.header ] 表示:如果从 entry 进入,值为 0;如果从 loop.header 进入,值为 %s.next。phi 节点解决了”同一变量在不同路径上有不同值”的问题。

3.8 opt 与 llc 工具详解#

opt 和 llc 是 LLVM 最常用的两个命令行工具:

# opt:IR 级优化器
opt -passes=mem2reg input.ll -S -o output.ll
opt -passes=default<O2> input.ll -S -o output.ll
opt --print-passes
# llc:IR → 目标代码
llc -mtriple=x86_64-linux-gnu input.ll -o output.s
llc -mtriple=aarch64-linux-gnu input.ll -o output.s
llc -filetype=obj input.ll -o output.o
# 调试后端
llc -debug-only=isel input.ll -o output.s
llc -debug-only=regalloc input.ll -o output.s
opt 常用 Pass 组合效果
-passes=mem2reg提升 alloca 到 SSA 寄存器
-passes=instcombine指令合并简化
-passes=gvn全局值编号消除冗余
-passes=licm循环不变量外提
-passes=simplifycfg简化控制流图
-passes=dce死代码消除
-passes=loop-unroll循环展开
-passes=loop-vectorize循环向量化

四、LLVM 后端 Pipeline#

4.1 后端的阶段#

flowchart TB IR["LLVM IR"] --> ISEL["指令选择<br/>DAGISel"] ISEL --> SCHED1["指令调度(前RA)"] SCHED1 --> RA["寄存器分配"] RA --> SCHED2["指令调度(后RA)"] SCHED2 --> PEI["Prologue/Epilogue<br/>插入"] PEI --> EMIT["代码发射"] EMIT --> ASM["汇编输出"] style IR fill:#e3f2fd,stroke:#1565c0 style RA fill:#fce4ec,stroke:#c62828 style ASM fill:#e8f5e9,stroke:#2e7d32

4.2 后端的关键数据结构#

数据结构说明阶段
SelectionDAG指令选择用的 DAG指令选择
MachineInstr机器指令表示调度、RA
MachineBasicBlock机器基本块全部
MachineFunction机器函数全部
MCInst最终指令编码代码发射

五、调试信息#

5.1 DWARF 调试格式#

LLVM 生成 DWARF 格式的调试信息,支持源码级调试:

# 生成带调试信息的可执行文件
clang -g hello.c -o hello_debug
# 查看调试信息
readelf --debug-dump hello_debug | head -50
# 使用 GDB 调试
gdb hello_debug
(gdb) break main
(gdb) run
(gdb) step
(gdb) print x

5.2 调试信息与优化#

优化级别调试体验说明
-O0 -g完美变量、行号完全准确
-O1 -g部分变量可能被优化掉
-O2 -g中等内联后行号可能不准确
-O3 -g大量优化,调试困难
Tip

LLVM 支持 -g -O2 的”优化调试”——它尽量在优化的同时保留调试信息。但某些优化(如内联、循环变换)会使调试体验变差。如果需要精确调试,建议使用 -O0

六、LLVM 工具链#

6.1 核心 LLVM 工具#

工具功能常用命令
clangC/C++ 编译器clang -O2 hello.c
optIR 优化器opt -passes=instcombine hello.ll -S
llcIR → 汇编llc hello.ll -o hello.s
lliIR 解释器/JITlli hello.bc
llvm-as.ll → .bcllvm-as hello.ll -o hello.bc
llvm-dis.bc → .llllvm-dis hello.bc -o hello.ll
llvm-linkIR 链接器llvm-link a.ll b.ll -o combined.ll
llvm-arIR 归档器llvm-ar rcs lib.a a.o b.o
lld链接器lld hello.o -o hello

6.2 诊断工具#

工具功能
llvm-objdump反汇编目标文件
llvm-readelf查看 ELF 信息
llvm-nm查看符号表
llvm-size查看段大小
llvm-dwarfdump查看 DWARF 调试信息
llvm-profdata分析 Profile 数据
llvm-cov代码覆盖率

七、动手实践#

7.1 实验一:完整的 LLVM 编译流程#

cat > hello.c << 'EOF'
int add(int a, int b) { return a + b; }
int main() { return add(3, 4); }
EOF
# 1. 生成 LLVM IR
clang -emit-llvm -S -O0 hello.c -o hello.ll
cat hello.ll
# 2. 优化 IR
opt -passes='mem2reg,instcombine,simplifycfg' hello.ll -S -o hello_opt.ll
cat hello_opt.ll
# 3. 生成汇编
llc hello_opt.ll -o hello.s
cat hello.s
# 4. 汇编和链接
clang hello.s -o hello
./hello; echo $?

7.2 实验二:编写自定义 Pass#

# 创建 Pass 插件
cat > HelloPass.cpp << 'EOF'
// 使用上面的 HelloPass 代码
EOF
# 编译 Pass 插件
clang -shared -fPIC HelloPass.cpp -o HelloPass.so \
$(llvm-config --cxxflags --ldflags)
# 使用 Pass
opt -load-pass-plugin=./HelloPass.so -passes=hello-pass hello.ll -S

7.3 实验三:对比优化级别#

for opt in O0 O1 O2 O3; do
clang -$opt -S hello.c -o hello_${opt}.s
echo "$opt: $(wc -l < hello_${opt}.s) lines"
done

八、本章小结#

上一章中,走完了从机器指令到可执行文件的最后旅程——ELF 格式、重定位、符号解析、动态链接。至此,编译流水线的每个阶段都已拆解完毕。现在换一个视角:不问”每个阶段做什么”,而问”一个工业级编译器基础设施如何组织这些阶段”。LLVM 就是最好的研究对象。

概念要点
LLVM 哲学库化、模块化、可扩展、类型化 IR
IR 层次.ll(文本)、.bc(位码)、内存中(C++ 对象)
Pass 框架变换 Pass + 分析 Pass,新 Pass 管理器
优化流水线早期优化 → 循环优化 → 全局优化 → 晚期优化
后端 Pipeline指令选择 → 调度 → RA → 调度 → PEI → 发射
调试信息DWARF 格式,-g 生成
工具链clang, opt, llc, lli, llvm-as, llvm-dis, lld

LLVM 架构的要点基本都在这里了。下一章进入 JIT 编译,看看 LLVM 的 ORC API 如何支持运行时代码生成。

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

LLVM 架构深入
https://blog.souloss.com/posts/compiler/llvm-architecture/
作者
Souloss
发布于
2026-01-31
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时