Appearance
第5章:控制流图与 SSA
基本块与控制流
基本块是一段从入口进入、仅在末尾转移控制的直线代码。CFG 的边表示可能的控制转移,不等于运行时一定执行的顺序。
text
entry:
if c goto left else right
left:
x = 1
goto join
right:
x = 2
goto join
join:
y = x + 3join 的 x 取决于来自哪个前驱。把整个程序当一串顺序赋值处理,会丢失这个关系。
静态单赋值
SSA 要求每个 SSA 名只定义一次。上例可改为左右分支分别定义 x1=1、x2=2,合流点写
text
x3 = phi(left: x1, right: x2)
y1 = x3 + 3根据实际进入该块的前驱选择对应值,不是把两个值都求出后任选,也不是普通函数调用。循环也需要 合并入口值与回边值。
SSA 的“静态一次”不意味着循环中的那条定义只执行一次;同一静态指令仍可在不同迭代执行。
支配关系
若从入口到 B 的每条路径都经过 A,则 A 支配 B。普通 SSA 使用要求其定义支配使用; 操作数则按对应前驱边解释。
支配边界刻画不同定义可能合流的位置,可用于放置 。将表达式移动到某块前,必须确认所有路径都具有所需定义,且不会引入原本不发生的副作用或异常。
内存不自动成为 SSA 值
标量寄存器值容易重命名,内存中的 *p 和 *q 可能是同一位置。一次 store 会影响哪些后续 load,需要别名分析或更明确的内存依赖表示。
text
a = load p
store q, 7
b = load p只有证明 p 与 q 不别名,或证明写入不会改变读取结果,才能把 b 替换为 a。SSA 名不同不能证明指向的对象不同。
练习
- 把求和循环转换为含循环入口 的 SSA。
- 判断菱形 CFG 中哪些块支配合流块。
- 为什么将 SSA 销毁成机器代码时, 常需要变成前驱边上的并行复制?