10.2 三地址码
三地址码(TAC)是最常见的 IR 形态。规则很简单:每条指令最多做一个运算、带少量操作数,典型形如 t = a op b。
“扁平”就是它的价值。嵌套表达式被展开成显式指令序列,中间结果获得名字,后续每个分析都看到统一格式。
正在加载交互实验...
正在加载概念检查...
正在加载本节练习...
临时变量与顺序
a + b * c 不可能一条搞定。优先级要求先算 b * c 进临时,再做加法:
code
t1 = b * c
t2 = a + t1括号会改变形状,(a + b) * c 顺序相反。临时变量不是噪声,它编码了求值顺序和定义-使用(def-use)结构,正是优化器依赖的东西。
正在加载交互实验...
正在加载概念检查...
正在加载本节练习...
扁平为什么强过树
树隐藏指令顺序,TAC 把它摊开。每个运算被隔离并命名,编译器才能安全地移动、折叠或删除指令。
TAC 也是天然的 SSA 前身:临时变量即将变成带版本的值,寄存器分配稍后再决定哪些临时共用硬件寄存器。
常见指令形态
TAC 不止 t = a op b。一个小而规整的指令集就能覆盖大半语言:
- 二元:
t = a op b
- 一元:
t = op a
- 拷贝:
x = y
- 跳转:
goto L
- 条件跳转:
if x relop y goto L
- 调用:
param a; t = call f, n
- 下标/字段:
t = a[i]、a[i] = t
规整正是关键:优化器与分配器只处理十几种形态,而不是上千条文法。
编码:四元式与三元式
两种经典编码。四元式存 (op, arg1, arg2, result),显式命名结果;三元式省掉结果名、按位置引用指令。四元式易于重排,是常用选择;三元式更紧凑但指令一移动就脆。
例子:条件
源码 if (a < b) m = a; else m = b; 大致降低为:
code
t1 = a < b
if t1 goto L1
m = b
goto L2
L1: m = a
L2:结构化控制流变成标签与跳转。这种摊平,正是稍后切出基本块与控制流图的前提。
正在加载概念检查...