一、LLVM整体架构概览 (LLVM Overall Architecture)
1.1 三段式编译器架构 (Three-Stage Compiler Architecture)
LLVM采用了革命性的三段式编译器设计,这种架构使得编译器具有极高的可扩展性和可维护性:
┌─────────────────────────────────────────────────────────────┐
│ 前端 (Frontend) │
│ C/C++/Obj-C/Rust/Fortran → LLVM IR │
│ 组件: Clang, Rustc, Flang, etc. │
└────────────────────┬────────────────────────────────────────┘
│ LLVM IR (.ll / .bc)
▼
┌─────────────────────────────────────────────────────────────┐
│ 中端优化器 (Middle/Optimizer) │
│ 与平台无关的IR优化 │
│ - 标量优化: SROA, GVN, LICM │
│ - 向量化: Loop Vectorizer, SLP │
│ - IPO: Inliner, GlobalOpt │
└────────────────────┬────────────────────────────────────────┘
│ Optimized LLVM IR
▼
┌─────────────────────────────────────────────────────────────┐
│ 后端 (Backend) │
│ LLVM IR → 机器码 (Machine Code) │
│ 支持30+架构: X86, AArch64, RISC-V, GPU, etc. │
└─────────────────────────────────────────────────────────────┘
前端 (Frontend) - Clang
- 位置:
llvm-project/clang/ - 职责: 将C/C++/Objective-C代码编译为LLVM IR
- 核心源码目录:
-
clang/lib/AST/- 抽象语法树 (Abstract Syntax Tree) -
clang/lib/Sema/- 语义分析 (Semantic Analysis) -
clang/lib/CodeGen/- IR代码生成 (关键目录!)-
CodeGenFunction.cpp(135KB) - 函数级代码生成入口 -
CGExpr.cpp(294KB) - 表达式转IR -
CGStmt.cpp(128KB) - 语句转IR -
CGCall.cpp(255KB) - 函数调用与ABI处理
-
-
中端优化器 (Middle/Optimizer)
- 位置:
llvm-project/llvm/lib/Transforms/ - 职责: 执行与目标平台无关的IR优化
- 核心组件:
-
InstCombine/- 指令合并与简化 (最基础的优化) -
Scalar/- 标量优化-
SROA.cpp- Scalar Replacement of Aggregates -
GVN.cpp- Global Value Numbering -
LICM.cpp- Loop Invariant Code Motion
-
-
Vectorize/- 自动向量化 -
IPO/- 过程间优化 (Inter-Procedural Optimization)
-
后端 (Backend) - Target CodeGen
- 位置:
llvm-project/llvm/lib/Target/ - 职责: 将优化后的IR转换为目标平台的机器码
- 支持架构: X86, AArch64, RISC-V, ARM, PowerPC, NVPTX, AMDGPU等30+架构
- 核心流程:
- Instruction Selection (指令选择) - IR → SelectionDAG → MachineInstr
- Register Allocation (寄存器分配) - 虚拟寄存器 → 物理寄存器
- Instruction Scheduling (指令调度) - 重排指令以优化流水线
- Code Emission (代码生成) - 生成汇编/机器码
1.2 核心IR表示 (Intermediate Representation)
LLVM IR是LLVM的核心创新,它是一种类型化的三地址码表示:
- 位置:
llvm-project/llvm/lib/IR/ - 关键文件:
-
Instructions.cpp(162KB) - IR指令的实现 -
Function.cpp(40KB) - 函数IR结构 -
BasicBlock.cpp(36KB) - 基本块IR结构 -
Value.cpp(45KB) - IR值基类 -
Type.cpp(40KB) - 类型系统 -
Verifier.cpp(325KB) - IR验证器 (确保IR正确性,极其重要!)
-
IR的三种表示形式:
- 内存表示 - C++对象 (Value, Instruction, BasicBlock, Function, Module)
-
文本表示 - 人类可读的
.ll文件 -
位码表示 - 紧凑的二进制
.bc文件
IR示例:
define i32 @add(i32 %a, i32 %b) {
entry:
%sum = add nsw i32 %a, %b
ret i32 %sum
}
二、C++ → IR → 汇编 → 字节码完整流程
2.1 完整编译流程与关键源码追踪
让我们通过一个具体的例子追踪代码的完整转换过程:
C++源代码:
int sum_array(int* arr, int n) {
int sum = 0;
for (int i = 0; i < n; ++i) {
sum += arr[i];
}
return sum;
}
阶段1: Clang前端 (C++ → LLVM IR)
源码入口: clang/lib/CodeGen/CodeGenFunction.cpp
关键学习文件:
-
clang/lib/CodeGen/CGExpr.cpp(294KB)- 学习C++表达式如何转换为IR指令
- 例如:
arr[i]如何变为getelementptr+load
-
clang/lib/CodeGen/CGStmt.cpp(128KB)- 学习控制流语句 (if, for, while) 如何转换为IR基本块和branch指令
-
clang/lib/CodeGen/CGCall.cpp(255KB)- 学习函数调用的ABI处理
- 参数传递、返回值处理
-
clang/lib/CodeGen/CGClass.cpp(128KB)- 学习C++特有特性 (类、继承、虚函数) 的IR表示
生成的IR (未优化):
define i32 @sum_array(i32* %arr, i32 %n) {
entry:
%sum = alloca i32, align 4
%i = alloca i32, align 4
store i32 0, i32* %sum, align 4
store i32 0, i32* %i, align 4
br label %for.cond
for.cond:
%0 = load i32, i32* %i, align 4
%cmp = icmp slt i32 %0, %n
br i1 %cmp, label %for.body, label %for.end
for.body:
%1 = load i32, i32* %i, align 4
%idxprom = sext i32 %1 to i64
%arrayidx = getelementptr inbounds i32, i32* %arr, i64 %idxprom
%2 = load i32, i32* %arrayidx, align 4
%3 = load i32, i32* %sum, align 4
%add = add nsw i32 %3, %2
store i32 %add, i32* %sum, align 4
br label %for.inc
for.inc:
%4 = load i32, i32* %i, align 4
%inc = add nsw i32 %4, 1
store i32 %inc, i32* %i, align 4
br label %for.cond
for.end:
%5 = load i32, i32* %sum, align 4
ret i32 %5
}
阶段2: IR优化 (IR → Optimized IR)
源码入口: llvm/lib/Passes/PassBuilderPipelines.cpp (2508行)
这是定义优化流水线的核心文件,指定了不同优化级别 (-O1, -O2, -O3) 下Pass的执行顺序。
关键学习文件:
-
llvm/lib/Transforms/InstCombine/InstCombine.cpp- 局部指令优化,如常量折叠、死代码消除
-
llvm/lib/Transforms/Scalar/SROA.cpp- 将alloca内存操作提升为SSA寄存器
- 消除上面IR中的load/store操作
-
llvm/lib/Transforms/Scalar/GVN.cpp- 全局值编号,消除冗余计算
-
llvm/lib/Transforms/Scalar/LICM.cpp- 循环不变量外提
优化后的IR (-O2):
define i32 @sum_array(i32* %arr, i32 %n) {
entry:
%cmp = icmp sgt i32 %n, 0
br i1 %cmp, label %for.body.preheader, label %for.end
for.body.preheader:
br label %for.body
for.body:
%i.0 = phi i32 [ 0, %for.body.preheader ], [ %inc, %for.body ]
%sum.0 = phi i32 [ 0, %for.body.preheader ], [ %add, %for.body ]
%idxprom = sext i32 %i.0 to i64
%arrayidx = getelementptr inbounds i32, i32* %arr, i64 %idxprom
%val = load i32, i32* %arrayidx, align 4
%add = add nsw i32 %sum.0, %val
%inc = add nuw nsw i32 %i.0, 1
%exitcond = icmp eq i32 %inc, %n
br i1 %exitcond, label %for.end, label %for.body
for.end:
%sum.0.lcssa = phi i32 [ 0, %entry ], [ %add, %for.body ]
ret i32 %sum.0.lcssa
}
阶段3: 代码生成 (Optimized IR → Assembly)
源码入口: llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
关键学习文件:
-
llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp(510KB)- 将LLVM IR转换为SelectionDAG (有向无环图)
- 这是指令选择的第一步
-
llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp(1210KB!)- LLVM中最大的单个源文件
- 对SelectionDAG进行优化和简化
-
llvm/lib/CodeGen/SelectionDAG/LegalizeDAG.cpp(253KB)- 将DAG中的操作合法化为目标平台支持的操作
- 例如: 将i64操作转换为目标平台支持的i32操作
-
llvm/lib/CodeGen/SelectionDAG/InstrEmitter.cpp(56KB)- 将优化后的DAG转换为MachineInstr
-
llvm/lib/CodeGen/RegAllocGreedy.cpp(111KB)- 贪心寄存器分配算法
- 将虚拟寄存器映射到物理寄存器
生成的X86-64汇编:
sum_array:
test esi, esi # 检查n是否<=0
jle .LBB0_3 # 如果是,跳转到结尾
xor eax, eax # sum = 0
xor ecx, ecx # i = 0
.LBB0_2: # 循环体
add eax, dword ptr [rdi + 4*rcx] # sum += arr[i]
inc ecx # i++
cmp ecx, esi # 比较i和n
jl .LBB0_2 # 如果i<n,继续循环
.LBB0_3: # 函数返回
ret
阶段4: 汇编 → 对象文件/字节码
源码入口: llvm/lib/MC/ (Machine Code)
关键学习文件:
-
llvm/lib/MC/MCAsmBackend.cpp- 汇编后端 -
llvm/lib/MC/MCCodeEmitter.cpp- 机器码编码器 -
llvm/lib/MC/MCObjectWriter.cpp- 对象文件写入器 -
llvm/lib/CodeGen/AsmPrinter/- 汇编代码打印
生成的对象文件: ELF格式的二进制文件,包含机器码、符号表、重定位信息等。
2.2 实践学习工具与命令
# 1. 查看C++到IR的转换 (前端输出)
clang -S -emit-llvm input.cpp -o output.ll
# 或生成未优化的IR
clang -S -emit-llvm -O0 input.cpp -o output_O0.ll
# 2. 查看优化后的IR
clang -S -emit-llvm -O2 input.cpp -o output_O2.ll
# 3. 使用opt工具单独运行优化Pass
opt -S -O2 input.ll -o output_O2.ll
# 4. 查看优化过程中每个Pass的效果
opt -S -O2 -mllvm -print-after-all input.ll 2>pass_output.txt
# 5. 查看IR到汇编的转换 (后端输出)
llc input.ll -o output.s
# 指定目标架构
llc -march=x86-64 input.ll -o output_x86.s
llc -march=aarch64 input.ll -o output_aarch64.s
# 6. 查看完整的编译阶段
clang -ccc-print-phases input.cpp
# 输出示例:
# 0: input.cpp, c++, {preprocess}
# 1: 0, c++, {compile}
# 2: 1, assembler, {assemble}
# 3: 2, object, {link}
# 7. 生成字节码文件 (bitcode)
clang -c -emit-llvm input.cpp -o output.bc
# 8. 字节码与文本IR互转
llvm-dis output.bc -o output.ll # 字节码 → 文本
llvm-as output.ll -o output.bc # 文本 → 字节码
三、各CPU架构指令处理异同
3.1 Target后端通用架构
每个CPU架构的后端都遵循相同的组织结构,这使得添加新架构变得系统化:
llvm/lib/Target/<ARCH>/
├── <ARCH>.td # 目标架构的顶级TableGen定义
├── <ARCH>ISelLowering.cpp # IR → SelectionDAG lowering (通常是最大文件)
├── <ARCH>ISelDAGToDAG.cpp # DAG模式匹配,选择具体指令
├── <ARCH>InstrInfo.td # 指令集定义 (TableGen DSL)
├── <ARCH>RegisterInfo.td # 寄存器文件定义
├── <ARCH>CallingConv.td # 调用约定定义
├── <ARCH>Subtarget.cpp/h # CPU子目标特性 (不同型号的差异)
├── <ARCH>TargetMachine.cpp # TargetMachine实现 (入口点)
├── <ARCH>TargetTransformInfo.cpp # TTI: 为优化器提供目标相关信息
├── <ARCH>FrameLowering.cpp # 栈帧布局
├── <ARCH>InstrInfo.cpp # 指令信息查询
├── <ARCH>RegisterInfo.cpp # 寄存器信息查询
├── <ARCH>AsmPrinter.cpp # 汇编代码输出
├── MCTargetDesc/ # 机器代码层描述
├── TargetInfo/ # 目标注册信息
└── Schedule*.td # 指令调度模型 (性能优化)
3.2 三大主流架构深度对比
X86架构 (llvm/lib/Target/X86/)
架构特点:
- CISC (Complex Instruction Set Computer)
- 寄存器数量少 (16个通用寄存器 x86-64)
- 复杂的指令编码
- 丰富的历史包袱 (16位/32位/64位兼容)
- 复杂的SIMD演进 (MMX → SSE → AVX → AVX2 → AVX-512)
关键文件:
-
X86ISelLowering.cpp(2556KB!) - LLVM项目中最大的单个源文件- 处理X86特有的IR lowering逻辑
- 包含大量平台特定的优化策略
X86InstrAVX512.td(716KB) - AVX-512指令定义X86InstrSSE.td(414KB) - SSE指令定义X86InstrInfo.cpp(355KB) - 指令信息查询
X86特有优化Pass:
X86CmovConversion.cpp - 将条件分支转换为cmov指令
X86FixupBWInsts.cpp - 修正字节/字指令的问题
X86AvoidStoreForwardingBlocks.cpp - 避免存储转发阻塞
X86DomainReassignment.cpp - 寄存器域重分配 (X87 vs SSE)
X86FloatingPoint.cpp - X87浮点栈管理
X86VZeroUpper.cpp - 插入VZEROUPPER指令 (AVX/SSE转换)
调度模型 (Scheduling Models):
X86SchedBroadwell.td - Broadwell微架构
X86SchedIceLake.td - Ice Lake微架构
X86SchedSapphireRapids.td - Sapphire Rapids (236KB!)
X86SchedZnver3.td - AMD Zen 3
AArch64架构 (llvm/lib/Target/AArch64/)
架构特点:
- RISC (Reduced Instruction Set Computer)
- 31个通用寄存器 (64位)
- 固定的指令长度 (32位)
- 负载-存储架构 (load-store architecture)
- SVE (Scalable Vector Extension) 可伸缩向量扩展
关键文件:
-
AArch64ISelLowering.cpp- IR lowering -
AArch64InstrInfo.td- 指令定义 -
AArch64SVEInstrFormat.td- SVE向量指令 -
AArch64TargetTransformInfo.cpp- TTI实现
AArch64特有优化Pass:
AArch64A57FPLoadBalancing.cpp - A57浮点负载均衡
AArch64CondBrTuning.cpp - 条件分支调优
AArch64LoadStoreOptimizer.cpp - 负载存储优化
AArch64SIMDInstructionOpt.cpp - SIMD指令优化
RISC-V架构 (llvm/lib/Target/RISCV/)
架构特点:
- 开源RISC架构
- 模块化设计 (基础ISA + 扩展)
- 31个通用寄存器
- 可扩展的向量扩展 (RISC-V V)
关键文件:
-
RISCVISelLowering.cpp- IR lowering -
RISCVInstrInfo.td- 基础指令 -
RISCVInstrInfoV.td- 向量扩展指令
RISC-V特有优化Pass:
RISCVInsertWriteVXRS.cpp - 插入向量寄存器写回
RISCVVSetVliBypass.cpp - VSetVLI旁路优化
3.3 架构差异关键学习点
1. 调用约定 (Calling Convention)
X86-64 (System V AMD64 ABI):
参数传递顺序:
1. RDI, RSI, RDX, RCX, R8, R9 (前6个整数/指针参数)
2. XMM0-XMM7 (前8个浮点参数)
3. 其余参数通过栈传递
文件: X86CallingConv.td (49KB)
AArch64 (AAPCS64):
参数传递顺序:
1. X0-X7 (前8个整数/指针参数)
2. V0-V7 (前8个浮点/向量参数)
3. 其余参数通过栈传递
文件: AArch64CallingConvention.td
关键差异:
- X86有复杂的红色区域 (Red Zone) 概念
- AArch64有更规则的寄存器使用
- 栈对齐要求不同
2. 寄存器分配策略
X86:
- 寄存器压力极大 (只有16个通用寄存器)
- 频繁需要spill (溢出到内存)
- 寄存器分配器的质量对性能影响巨大
- 需要智能的live range splitting
AArch64/RISC-V:
- 31个通用寄存器,压力较小
- spill频率较低
- 但仍需要优化以充分利用寄存器
3. 指令选择模式 (Instruction Selection)
学习TableGen DSL (Domain Specific Language):
// 示例: 定义一个加法指令
def ADDrr : Instruction {
let Namespace = "X86";
let DAG = (outs GR32:$dst), (ins GR32:$src1, GR32:$src2);
let Inst{31-0} = 0x01C0; // 机器码编码
let Pattern = [(set GR32:$dst, (add GR32:$src1, GR32:$src2))];
let Constraints = "$src1 = $dst"; // src1和dst必须是同一寄存器
}
关键概念:
-
Pattern: 描述这个指令可以匹配什么样的DAG模式 -
DAG: 定义操作数和结果 -
Inst: 机器码编码 -
Constraints: 操作数约束
4. 调度模型 (Scheduling Machine Model)
定义指令的延迟、吞吐量和执行端口:
// 示例: 定义指令的调度信息
def : InstRW<[WriteALU], (insts ADDrr)>;
def WriteALU : WriteRes<ALUUnit, [ALU]>;
关键指标:
- Latency (延迟): 指令执行需要多少个周期
- Throughput (吞吐量): 每个周期能执行多少条此类指令
- Execution Ports (执行端口): 指令可以在哪些执行单元运行
四、基于IR中间表达的Codegen优化
4.1 Pass基础设施
LLVM的优化通过Pass机制实现,Pass是可组合的优化单元。
核心文件:
-
llvm/include/llvm/Pass.h- Pass基类定义 -
llvm/lib/Passes/PassBuilder.cpp(116KB) - 新Pass管理器构建器 -
llvm/lib/Passes/PassBuilderPipelines.cpp(105KB) - 优化流水线定义 -
llvm/lib/Passes/PassRegistry.def(39KB) - Pass注册表
Pass的分类:
按作用范围分:
├── FunctionPass - 函数内优化 (不修改函数边界)
├── ModulePass - 模块级优化 (可跨函数)
├── CallGraphSCCPass - 调用图SCC顺序优化
├── LoopPass - 循环级优化
└── RegionPass - 区域级优化
按新旧分:
├── Legacy Pass Manager (旧) - 基于PassManager类
└── New Pass Manager (新) - 基于PassManager<T>模板
4.2 优化Pass分类学习路径
层级1: 函数内标量优化 (Scalar Optimizations)
位置: llvm/lib/Transforms/Scalar/
核心Pass:
-
InstCombine (指令合并)
- 文件:
llvm/lib/Transforms/InstCombine/ - 作用: 局部指令优化,最基本的优化
- 示例:
; 优化前 %1 = add i32 %x, 0 ; 优化后 %1 = %x (直接消除)
- 文件:
-
SROA (Scalar Replacement of Aggregates)
- 文件:
SROA.cpp - 作用: 将聚合体 (struct, array) 的内存操作转换为标量SSA
- 重要性: 几乎是所有优化的前提
- 示例:
; 优化前 %p = alloca { i32, i32 } %q = getelementptr { i32, i32 }, { i32, i32 }* %p, i32 0, i32 0 store i32 1, i32* %q ; 优化后 ; 直接变成 SSA 寄存器,消除 alloca/store/load
- 文件:
-
GVN (Global Value Numbering)
- 文件:
GVN.cpp - 作用: 消除全局冗余计算
- 原理: 为每个计算分配编号,相同编号的计算只执行一次
- 文件:
-
EarlyCSE (Early Common Subexpression Elimination)
- 文件:
EarlyCSE.cpp - 作用: 早期公共子表达式消除
- 比GVN轻量,在优化早期运行
- 文件:
-
LICM (Loop Invariant Code Motion)
- 文件:
LICM.cpp - 作用: 将循环不变量外提到循环外
- 示例:
// 优化前 for (int i = 0; i < n; ++i) { sum += arr[i] * constant; // constant每次循环都乘 } // 优化后 temp = constant; for (int i = 0; i < n; ++i) { sum += arr[i] * temp; // 不需要每次乘 }
- 文件:
-
Loop Unroll (循环展开)
- 文件:
LoopUnrollPass.cpp - 作用: 展开循环以减少分支开销
- 示例:
// 优化前 for (int i = 0; i < 4; ++i) { sum += arr[i]; } // 优化后 sum += arr[0]; sum += arr[1]; sum += arr[2]; sum += arr[3];
- 文件:
层级2: 过程间优化 (Inter-Procedural Optimization, IPO)
位置: llvm/lib/Transforms/IPO/
核心Pass:
-
Inliner (内联)
- 文件:
Inliner.cpp - 作用: 将函数调用替换为函数体
- 最重要的优化之一,为后续优化创造机会
- 启发式策略: 基于代码大小增长、调用频率等
- 文件:
-
GlobalOpt (全局优化)
- 文件:
GlobalOpt.cpp - 作用: 全局变量优化
- 例如: 将只被内部使用的全局变量转为局部变量
- 文件:
-
GlobalDCE (Dead Code Elimination)
- 文件:
GlobalDCE.cpp - 作用: 消除未使用的全局符号
- 文件:
-
ArgumentPromotion (参数提升)
- 文件:
ArgumentPromotion.cpp - 作用: 将指针参数拆分为多个标量参数
- 示例:
// 优化前 void foo(struct {int a, int b}* s); // 优化后 void foo(int a, int b);
- 文件:
-
SCCP (Sparse Conditional Constant Propagation)
- 文件:
SCCP.cpp - 作用: 稀疏条件常量传播
- 结合控制流分析进行常量传播
- 文件:
层级3: 向量化优化 (Vectorization)
位置: llvm/lib/Transforms/Vectorize/
核心Pass:
-
Loop Vectorizer (循环向量化)
- 文件:
LoopVectorize.cpp - 作用: 将标量循环转换为向量指令
- 示例:
// 优化前 (标量) for (int i = 0; i < n; ++i) { c[i] = a[i] + b[i]; } // 优化后 (SIMD向量) // 使用AVX/SSE指令,每次处理4/8个元素 for (int i = 0; i < n; i += 4) { __m256 va = load(&a[i]); __m256 vb = load(&b[i]); __m256 vc = _mm256_add_ps(va, vb); store(&c[i], vc); }
- 文件:
-
SLP Vectorizer (Superword-Level Parallelism)
- 文件:
SLPVectorizer.cpp - 作用: 在基本块内发现并行向量机会
- 不依赖循环,寻找独立的标量操作组合为向量
- 文件:
-
VectorCombine (向量指令合并)
- 文件:
VectorCombine.cpp - 作用: 优化已向量化的代码
- 文件:
4.3 TargetTransformInfo (TTI) 机制
TTI是连接前端优化器和后端目标架构的桥梁:
作用:
- 前端优化器通过TTI查询目标架构的特性
- 后端通过TTI告诉优化器"什么在这个平台上是便宜的/昂贵的"
关键文件:
-
llvm/include/llvm/Analysis/TargetTransformInfo.h- TTI接口定义 -
llvm/lib/Target/X86/X86TargetTransformInfo.cpp(353KB) - X86实现 -
llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp- AArch64实现
TTI提供的信息示例:
// 查询某种操作的代价
unsigned getArithmeticInstrCost(unsigned Opcode, Type *Ty, ...);
// 查询向量化因子
unsigned getNumberOfRegisters(bool Vector);
// 查询内存访问代价
unsigned getMemoryOpCost(unsigned Opcode, Type *Src, ...);
// 查询是否支持某种操作
bool hasVectorInstrs();
4.4 SelectionDAG优化流程
SelectionDAG是LLVM指令选择的核心数据结构:
完整流程:
LLVM IR
↓
SelectionDAGBuilder (IR → DAG)
↓ [SelectionDAGBuilder.cpp 510KB]
SelectionDAG
↓
DAGCombiner (DAG优化)
↓ [DAGCombiner.cpp 1210KB - LLVM最大文件!]
Optimized DAG
↓
LegalizeDAG (类型/操作合法化)
↓ [LegalizeDAG.cpp 253KB]
Legal DAG
↓
Instruction Selection (模式匹配选择指令)
↓ [ISelDAGToDAG.cpp]
Machine DAG
↓
ScheduleDAG (指令调度)
↓
Scheduled Machine DAG
↓
InstrEmitter (DAG → MachineInstr)
↓ [InstrEmitter.cpp 56KB]
MachineInstr
DAG节点示例:
add
/ \
load load
| |
ptr ptr
4.5 GlobalISel (新一代指令选择)
GlobalISel是LLVM正在推广的新一代指令选择框架:
位置: llvm/lib/CodeGen/GlobalISel/
优势:
- 比SelectionDAG更模块化
- 支持GISel Combiner (类似InstCombine的DAG优化)
- 更好的类型合法性处理
核心文件:
-
InstructionSelect.cpp- 指令选择 -
Legalizer.cpp- 合法化 -
RegisterBankInfo.cpp- 寄存器银行信息
五、GPU异构处理源码学习
5.1 GPU编译整体架构
GPU编译与CPU编译有显著差异,主要体现在:
- 不同的执行模型 (SIMT vs 标量)
- 不同的内存层次结构
- 不同的地址空间
CUDA/HIP支持 (Clang前端)
位置: clang/lib/CodeGen/
核心文件:
-
CGCUDANV.cpp(55KB) - CUDA NVPTX运行时支持 -
CGCUDARuntime.cpp/h- CUDA运行时接口 -
CGOpenMPRuntimeGPU.cpp(93KB) - OpenMP GPU offload支持 -
CGGPUBuiltin.cpp(7KB) - GPU内置函数处理 -
CGOpenMPRuntime.cpp(561KB!) - OpenMP运行时 (包含GPU支持)
GPU Target后端
1. NVPTX (NVIDIA GPU)
位置: llvm/lib/Target/NVPTX/
编译流程:
CUDA C++ → Clang → LLVM IR → NVPTX后端 → PTX → ptxas → SASS
关键文件:
-
NVPTXISelLowering.cpp(294KB) - IR lowering -
NVPTXISelDAGToDAG.cpp(85KB) - 指令选择 -
NVPTXInstrInfo.td(100KB) - 指令定义 -
NVPTXIntrinsics.td(289KB) - NVIDIA内置函数 -
NVPTXAsmPrinter.cpp(71KB) - PTX汇编输出 -
NVPTXTargetTransformInfo.cpp- GPU优化提示
NVPTX特有优化:
NVPTXLowerArgs.cpp - 参数lowering (GPU特有的ABI)
NVPTXImageOptimizer.cpp - 图像/纹理优化
NVPTXAllocaHoisting.cpp - alloca提升
NVVMReflect.cpp - NVVM反射优化
NVVMIntrRange.cpp - NVVM intrinsic范围优化
输出示例 (PTX代码):
.visible .entry sum_array(
.param .u64 sum_array_param_0,
.param .u32 sum_array_param_1
)
{
.reg .s32 %r<5>;
.reg .s64 %rd<6>;
ld.param.u64 %rd1, [sum_array_param_0];
ld.param.u32 %r1, [sum_array_param_1];
// ... PTX指令
}
2. AMDGPU (AMD GPU)
位置: llvm/lib/Target/AMDGPU/ (300+ 文件,最复杂的GPU后端!)
编译流程:
OpenCL/HIP → Clang → LLVM IR → AMDGPU后端 → GCN ISA → HSACO
关键文件:
-
AMDGPUISelLowering.cpp(231KB) - IR lowering -
AMDGPUInstructionSelector.cpp(259KB) - 指令选择 -
SIISelLowering.cpp(779KB!) - Southern Islands系列 lowering -
AMDGPULegalizerInfo.cpp(306KB) - GlobalISel合法化 -
AMDGPURegisterBankInfo.cpp(222KB) - 寄存器银行
AMDGPU特有优化:
AMDGPUAnnotateUniformValues.cpp - 统一值标注 (warp内所有线程相同)
AMDGPUPromoteAlloca.cpp - 寄存器/共享内存提升
AMDGPUCodeGenPrepare.cpp - 代码生成准备
SILoadStoreOptimizer.cpp - 负载存储优化
SIOptimizeExecMasking.cpp - 执行掩码优化 (warp divergence)
GCNHazardRecognizer.cpp - GCN硬件冒险检测
5.2 OpenMP Offload机制
OpenMP Offload允许将计算任务offload到GPU等加速器:
整体架构:
位置: /home/admin/work/llvm-project/offload/
├── libomptarget/ # OpenMP目标端运行时
├── plugins-nextgen/ # GPU插件 (CUDA, AMD, Level Zero)
└── tools/ # offload编译工具
Clang端的Offload支持:
-
clang/lib/CodeGen/CGOpenMPRuntime.cpp(561KB) - OpenMP运行时 -
clang/lib/CodeGen/CGOpenMPRuntimeGPU.cpp(93KB) - GPU专用OpenMP -
clang/lib/CodeGen/CGStmtOpenMP.cpp(369KB) - OpenMP语句代码生成
编译流程示例:
#pragma omp target teams distribute parallel for
for (int i = 0; i < n; ++i) {
c[i] = a[i] + b[i];
}
编译命令:
clang -fopenmp -fopenmp-targets=nvptx64-nvidia-cuda input.cpp -o output
5.3 GPU vs CPU优化差异关键点
1. 内存模型差异
CPU内存模型:
寄存器 → L1 Cache → L2 Cache → L3 Cache → 主存
GPU内存模型:
寄存器文件 → 共享内存 (Shared Memory) → 常量内存 → 全局内存 (Global Memory)
(可编程,低延迟) (只读,缓存) (高延迟,高带宽)
LLVM IR中的地址空间:
addrspace(0) - 全局/默认地址空间
addrspace(1) - GPU全局内存
addrspace(3) - GPU共享内存
addrspace(4) - GPU常量内存
addrspace(5) - GPU局部内存
2. 执行模型差异
CPU: 标量执行,单线程或少量线程
GPU: SIMT (Single Instruction Multiple Threads)
- Warp/Wavefront: 32 (NVIDIA) 或 64 (AMD) 个线程为一组
- 所有线程执行相同指令,但处理不同数据
- Branch Divergence: warp内线程走不同分支时性能下降
处理Divergence的优化:
AMDGPU:
- SIOptimizeExecMasking.cpp - 执行掩码优化
- AMDGPUUnifyDivergentExitNodes.cpp - 统一发散退出节点
NVPTX:
- 通过控制流重构减少divergence
3. 特殊GPU优化技术
内存合并 (Memory Coalescing):
// 好的访问模式 (合并)
for (int i = 0; i < 256; ++i) {
data[i] = ...; // 相邻线程访问相邻内存
}
// 差的访问模式 (不合并)
for (int i = 0; i < 256; ++i) {
data[i * 32] = ...; // 相邻线程访问间隔很大的内存
}
共享内存优化:
AMDGPUPromoteAlloca.cpp - 将频繁访问的alloca提升到共享内存
NVPTXSharedMemOpt - NVIDIA的共享内存优化
5.4 GPU学习实践路径
# 1. 编译CUDA代码到PTX
clang -cc1 -triple nvptx64-nvidia-cuda -emit-llvm input.cu -o output.ll
# 2. 使用NVPTX后端生成PTX
llc -march=nvptx64 output.ll -o output.ptx
# 3. 编译AMDGPU代码
llc -march=amdgcn -mcpu=gfx906 output.ll -o output.amdgcn
# 4. OpenMP offload完整编译
clang -fopenmp -fopenmp-targets=nvptx64-nvidia-cuda input.cpp -o output
# 5. 查看GPU特定的IR优化
opt -S -O3 -mtriple=nvptx64-nvidia-cuda input.ll -o output_gpu.ll
六、系统学习路线建议
阶段1: 基础入门 (1-2个月)
目标: 理解LLVM IR和基本的编译流程
学习内容:
-
理解IR表示
- 阅读
llvm/docs/LangRef.rst- IR语言参考手册 - 编写简单C++代码,用
clang -S -emit-llvm观察IR - 学习
llvm/lib/IR/核心数据结构
- 阅读
-
学习Pass机制
- 阅读
llvm/docs/WritingAnLLVMPass.rst - 编写简单的"Hello World" Pass
- 学习使用
opt工具
- 阅读
-
理解完整编译流程
- 追踪一个函数从C++到汇编的完整路径
- 使用
-mllvm -print-after-all查看每个Pass后的IR变化
实践项目:
- 编写一个简单的常量传播Pass
- 用不同优化级别编译代码,对比IR差异
阶段2: 中端优化深入 (2-3个月)
目标: 掌握主要的IR优化技术
学习内容:
-
标量优化
- 深入学习 SROA, GVN, EarlyCSE, LICM
- 阅读源码:
llvm/lib/Transforms/Scalar/ - 用
-O1,-O2,-O3对比优化效果
-
向量化
- 学习 Loop Vectorizer, SLP Vectorizer
- 阅读
llvm/lib/Transforms/Vectorize/ - 理解 TargetTransformInfo 的作用
-
过程间优化
- 学习 Inliner, GlobalOpt
- 理解 LTO (Link Time Optimization)
- 阅读
llvm/lib/Linker/,llvm/lib/LTO/
实践项目:
- 分析一个实际程序的优化报告 (
-Rpass=*) - 修改某个优化的启发式策略,观察效果
阶段3: 后端CodeGen深入 (3-4个月)
目标: 理解指令选择、寄存器分配和指令调度
学习内容:
-
指令选择
- 学习 TableGen 语法 (
*.td文件) - 理解 SelectionDAG 的完整流程
- 选择一个相对简单的架构深入学习 (推荐 RISC-V)
- 学习 TableGen 语法 (
-
寄存器分配
- 学习
llvm/lib/CodeGen/RegAllocGreedy.cpp - 理解 LiveInterval, VirtRegMap
- 学习 spill/reload 机制
- 学习
-
指令调度
- 学习 ScheduleDAG
- 理解机器模型 (SchedMachineModel)
- 对比不同CPU的调度表
实践项目:
- 为一个简单的RISC-like架构添加新指令
- 分析寄存器分配的效果
阶段4: GPU异构计算 (3-4个月)
目标: 掌握GPU编译和优化技术
学习内容:
-
GPU架构基础
- 学习 CUDA/OpenCL 编程模型
- 理解 SIMT、warp、shared memory
- 阅读 NVPTX/AMDGPU 后端文档
-
GPU优化技术
- 学习地址空间优化
- 理解 warp divergence 处理
- 学习内存合并 (coalescing)
-
OpenMP Offload
- 学习
offload/目录结构 - 理解 target region 编译流程
- 实践多GPU编译
- 学习
实践项目:
- 编写一个简单的GPU kernel,观察编译结果
- 分析GPU代码的性能瓶颈
阶段5: 高级主题与社区参与 (持续)
目标: 成为LLVM贡献者
学习内容:
-
性能分析工具
- 学习
llvm-mca(Machine Code Analyzer) - 使用
opt-viewer分析优化决策 - 学习 PGO (Profile Guided Optimization)
- 学习
-
新特性开发
- 参与 LLVM 社区 (Discourse, Discord)
- 阅读 RFC (Request for Comments)
- 学习提交 Patch 的流程
实践项目:
- 提交第一个bug fix
- 实现一个新的小优化Pass
七、关键源码文件索引
必读核心文件 (按学习优先级排序)
- llvm/docs/LangRef.rst - IR语言参考手册 (理解IR的基础)
- llvm/lib/Passes/PassBuilderPipelines.cpp - 优化流水线定义
- llvm/lib/Transforms/InstCombine/InstCombine.cpp - 指令合并实现
- llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp - 指令选择入口
- llvm/lib/Target/X86/X86ISelLowering.cpp - X86 lowering (最大文件)
- llvm/lib/Target/NVPTX/NVPTXISelLowering.cpp - GPU lowering示例
- llvm/lib/Transforms/Scalar/SROA.cpp - SROA优化
- llvm/lib/Transforms/Vectorize/LoopVectorize.cpp - 循环向量化
高级调试技巧
# 1. 查看Pass执行顺序
opt -O2 -debug-pass=Arguments input.ll
# 2. 查看每个Pass后的IR (极其有用!)
opt -O2 -mllvm -print-after-all input.ll 2>&1 | less
# 3. 查看DAG选择过程
llc -debug-only=isel input.ll 2>&1 | less
# 4. 查看寄存器分配过程
llc -debug-only=regalloc input.ll 2>&1 | less
# 5. 生成优化报告
opt -O2 -Rpass=.* -Rpass-analysis=.* input.ll -o output.ll
# 6. 使用opt-viewer可视化
opt-viewer.py opt_record.yaml
# 7. 分析机器码性能
llvm-mca -mcpu=skylake output.s
# 8. 查看特定Pass的统计信息
opt -O2 -stats input.ll -o output.ll 2>&1 | grep "PassName"
八、学习资源推荐
官方文档
-
LLVM Language Reference:
llvm/docs/LangRef.rst- IR语法和语义的权威参考 -
LLVM Programmer's Manual:
llvm/docs/ProgrammersManual.rst- 开发者手册 -
Writing an LLVM Pass:
llvm/docs/WritingAnLLVMPass.rst- Pass编写教程 -
CodeGenerator Design:
llvm/docs/CodeGenerator.rst- 后端设计文档 - Target-independent Code Generator: 理解CodeGen架构
实践工具链
- clang - C/C++/Obj-C前端编译器
- opt - LLVM IR优化器和转换工具
- llc - LLVM后端,生成汇编/机器码
- llvm-dis / llvm-as - 字节码与文本IR互转
- llvm-mca - 机器码性能分析器
- lli - LLVM JIT执行器
- opt-viewer - 优化决策可视化工具
社区与交流
- LLVM Discourse Forum: https://discourse.llvm.org/ - 官方论坛
- LLVM Discord: https://discord.gg/xS7Z362 - 实时聊天
- LLVM Office Hours: 定期技术分享 (关注官网获取时间)
- LLVM GitHub: https://github.com/llvm/llvm-project - 源代码和Issue
推荐书籍
- "Getting Started with LLVM Core Libraries" - 入门必读
- "Optimizing Compilers for Modern Architectures" - 编译器优化经典
- "Engineering a Compiler" - 编译器工程
在线课程
- CMU 15-745: Optimizing Compilers (使用LLVM)
- UIUC CS 426: Compiler Construction
附录:LLVM项目结构快速参考
llvm-project/
├── clang/ # C/C++/Obj-C前端
│ ├── lib/AST/ # 抽象语法树
│ ├── lib/Sema/ # 语义分析
│ └── lib/CodeGen/ # IR生成
│
├── llvm/ # LLVM核心
│ ├── lib/IR/ # IR实现
│ ├── lib/Transforms/ # 优化Pass
│ │ ├── InstCombine/ # 指令合并
│ │ ├── Scalar/ # 标量优化
│ │ ├── Vectorize/ # 向量化
│ │ └── IPO/ # 过程间优化
│ ├── lib/CodeGen/ # 代码生成
│ │ ├── SelectionDAG/ # 指令选择
│ │ └── GlobalISel/ # 新一代指令选择
│ ├── lib/Target/ # 目标后端
│ │ ├── X86/ # X86后端
│ │ ├── AArch64/ # ARM 64位后端
│ │ ├── RISCV/ # RISC-V后端
│ │ ├── NVPTX/ # NVIDIA GPU后端
│ │ └── AMDGPU/ # AMD GPU后端
│ └── lib/Passes/ # Pass管理器
│
├── lld/ # 链接器
├── libcxx/ # C++标准库
├── compiler-rt/ # 运行时库
├── lldb/ # 调试器
└── offload/ # OpenMP offload运行时
学习建议:
- 从简单的例子开始,逐步深入
- 多用调试工具观察IR变化
- 阅读源码时结合文档
- 参与社区,提出问题
- 动手实践,编写自己的Pass
祝学习顺利!