7.1 LLVM IR 的逻辑与物理结构


7.1 LLVM IR 的逻辑与物理结构

本节摘要: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 是强类型的:每个值都带类型,i32i64float、指针 ptr、向量 <4 x i32>。类型不只是记账——它直接喂给优化与选择:add i32add 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 上永远显式。

三、SSA 落地细节:不变式由谁保证

LLVM IR 的 SSA 不变式(每个值唯一定义、phi 在块首、支配关系成立)由构造者保证,核心验证器随时检查——前端产出不合法 IR 会当场报错,而不是带着病运行。这与本册的叙事严丝合缝:3.3 的构造算法是"正确性证明",LLVM 把证明固化成了运行期检查。两个实用推论:

  1. 支配关系可查:IR 内部维护着支配树(4.4 的算法),任何 pass 想问"块 A 是否支配块 B"是一次现成查询——SSA 构造时用过的地基,优化 pass 继续白嫖。
  2. phi 有严格位置语义:phi 必须是块的第一批指令,参数对应前驱;pass 移动指令时若把 phi 挤出块首,验证器立即拒绝——3.3 提过的"phi 参数随块重排易错",在 LLVM 里由验证器兜底。

💡 关键直觉:读 LLVM IR 不要逐字翻译回 C,要按"块 → 汇合点 phi → 回边"的结构读。IR 的形状就是 CFG 的形状:看到 phi 就找汇合点,看到 br 回跳就找到了循环——第3、4章的全部图形直觉在这里直接可用。

本节要点回顾:

  • 四层结构:模块 → 函数 → 基本块(显式、终结指令收尾)→ 指令(严格 SSA);
  • 循环头 phi:IR 里识别循环的最快标记,参数按前驱对号——3.2/3.3 的语法化身;
  • 类型系统:每个值带类型,向量化与指令选择的依据,语义分析的遗产;
  • GEP/load/store/call:地址计算结构化、内存访问版本化对象、IPO 的分析面;
  • SSA 不变式由验证器强制:支配树常驻内存,pass 随查随用。

IR 的逻辑结构清楚了,但它在磁盘与内存里以三种不同面貌存在——下一节拆开这三种形态。

常见问题三则

问:IR 里的 undef 与 poison 是什么? 两个"不承诺值"的记号:undef 表示"随便什么值都行"(未初始化的读取),poison 表示"这是一个错误状态,传播下去行为未定义"(如带符号溢出的结果)。它们给优化留出了大刀阔斧的空间:编译器可以给 undef 任选一个让代码更快的值,而不违背语义。理解这两个记号,就理解了"未定义行为是优化的燃料"这句话在 IR 层的样子。

问:属性(attribute)与元数据(metadata)有什么区别? 属性挂在函数或参数上,参与优化语义(readonlynorecurse 之类,5.3 的 IPO 靠它们推理);元数据是"旁路信息",优化可以忽略它而不出错(调试信息、分支概率提示)。两者是"承重墙与贴画"的关系:删属性可能改变行为,删元数据只是丢了提示。

问:怎么快速确认一段 IR 是不是 SSA 合法的? 肉眼看三条:每个非参数名字在全文里是否只有一个定义点;phi 是否都在块首;phi 的参数个数是否等于块的前驱数。三条全过基本就是合法的——余下的支配关系检查交给验证器,那正是它的职责。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U