本节摘要:LLVM IR 按"模块 → 函数 → 基本块 → 指令"四层组织,带一套严格类型系统,原生就是 SSA 形式——本册第3章的每个概念在这里都有具体语法对应物:
%name是版本化变量,phi是汇合仲裁,getelementptr是地址计算的结构化表达。本节精读一段含分支与循环的 IR,逐条指认它对应前文哪个概念,并讲清类型系统为什么是 IR 优化的地基。读完你应当能把中等复杂度的 IR 函数读出"人话",并在见到生僻指令时知道去哪个概念族里找解释。
把前六章的概念装进一个真实系统,最好的入口是读一段真的 IR。本节采取"先总览后精读"的次序:先立四层结构,再逐条解剖。
逻辑层级自上而下:模块(Module,一个编译单元,含全局变量与函数表)→ 函数(Function,带签名与属性)→ 基本块(BasicBlock,显式命名、以终结指令收尾)→ 指令(Instruction,严格 SSA)。看这个 C 函数和它的 IR:
int sum(int n) { int s = 0; for (int i = 0; i < n; i++) s = s + i; return s; }
define i32 @sum(i32 %n) { entry: br label %loop.header ; 无条件跳转:块的终结指令 loop.header: ; 循环头 = 天然汇合点(3.2) %i = phi i32 [ 0, %entry ], [ %i.next, %loop.body ] %s = phi i32 [ 0, %entry ], [ %s.next, %loop.body ] %cond = icmp slt i32 %i, %n ; 有符号比较 br i1 %cond, label %loop.body, label %exit loop.body: %s.next = add i32 %s, %i ; 4.2 的循环体,i、s 的迭代 %i.next = add i32 %i, 1 br label %loop.header exit: ret i32 %s ; s 的最后版本从这里离开 }
逐条对应本册概念。%i = phi i32 [ 0, %entry ], [ %i.next, %loop.body ]——3.2 讲的循环头 phi:首圈从 entry 取 0,后续圈从回边取迭代值;参数按"前驱块"对号,正是 3.3 重命名算法"按前驱序填参数"的语法体现。icmp + br 是 4.2 样例里 if i <= 10 goto 的落地。块的显式划分意味着前端的输出已经是基本块化 CFG,4.3 的分析直接可跑。虚拟寄存器 % 前缀暗示 6.2 的处境:无限寄存器世界,等着分配器降落。
LLVM IR 是强类型的:每个值都带类型,i32、i64、float、指针 ptr、向量 <4 x i32>。类型不只是记账——它直接喂给优化与选择:add i32 与 add i64 是不同指令,向量化 pass 看类型就知道 SIMD 通道怎么切(5.2 的向量化在 IR 层的锚点)。第2章讲"语义分析把类型固化进程序表示",在这里兑现:IR 里不再需要"这个值是什么类型"的推断,类型写在每个值上。
三条高频指令值得单独点名:
%p = getelementptr [10 x i32], ptr %arr, i64 0, i64 %idx ; GEP:结构化地址计算。不读内存,只算"第 idx 个 i32 的地址"。 ; 数组 a[i] 的 IR 形态——4.2 里"数组访问"的模糊说法在这里精确化。 %v = load i32, ptr %p ; 读内存,产生 SSA 新值 store i32 %v, ptr %q ; 写内存。两者是 3.4 Memory SSA 的版本化对象 %r = call i32 @f(i32 %x) ; 调用。5.3 IPO 的分析对象: ; 只读/无副作用等属性标在函数上,供跨函数推理
GEP 的设计哲学值得咀嚼:它把"地址算术"从普通整数运算里剥离出来,编译器由此知道"这是指针运算"而非"这是普通加法"——别名分析(4.5)、寻址模式选择(6.1 的复合寻址)都靠这个区分获得下手点。类型化的地址计算让"哪个数组、哪个元素、偏移多少"在 IR 上永远显式。
LLVM IR 的 SSA 不变式(每个值唯一定义、phi 在块首、支配关系成立)由构造者保证,核心验证器随时检查——前端产出不合法 IR 会当场报错,而不是带着病运行。这与本册的叙事严丝合缝:3.3 的构造算法是"正确性证明",LLVM 把证明固化成了运行期检查。两个实用推论:
💡 关键直觉:读 LLVM IR 不要逐字翻译回 C,要按"块 → 汇合点 phi → 回边"的结构读。IR 的形状就是 CFG 的形状:看到 phi 就找汇合点,看到
br回跳就找到了循环——第3、4章的全部图形直觉在这里直接可用。
本节要点回顾:
IR 的逻辑结构清楚了,但它在磁盘与内存里以三种不同面貌存在——下一节拆开这三种形态。
问:IR 里的 undef 与 poison 是什么? 两个"不承诺值"的记号:undef 表示"随便什么值都行"(未初始化的读取),poison 表示"这是一个错误状态,传播下去行为未定义"(如带符号溢出的结果)。它们给优化留出了大刀阔斧的空间:编译器可以给 undef 任选一个让代码更快的值,而不违背语义。理解这两个记号,就理解了"未定义行为是优化的燃料"这句话在 IR 层的样子。
问:属性(attribute)与元数据(metadata)有什么区别? 属性挂在函数或参数上,参与优化语义(readonly、norecurse 之类,5.3 的 IPO 靠它们推理);元数据是"旁路信息",优化可以忽略它而不出错(调试信息、分支概率提示)。两者是"承重墙与贴画"的关系:删属性可能改变行为,删元数据只是丢了提示。
问:怎么快速确认一段 IR 是不是 SSA 合法的? 肉眼看三条:每个非参数名字在全文里是否只有一个定义点;phi 是否都在块首;phi 的参数个数是否等于块的前驱数。三条全过基本就是合法的——余下的支配关系检查交给验证器,那正是它的职责。