Skip to content

第5章:控制流图与 SSA

基本块与控制流

基本块是一段从入口进入、仅在末尾转移控制的直线代码。CFG 的边表示可能的控制转移,不等于运行时一定执行的顺序。

text
entry:
    if c goto left else right
left:
    x = 1
    goto join
right:
    x = 2
    goto join
join:
    y = x + 3

joinx 取决于来自哪个前驱。把整个程序当一串顺序赋值处理,会丢失这个关系。

静态单赋值

SSA 要求每个 SSA 名只定义一次。上例可改为左右分支分别定义 x1=1x2=2,合流点写

text
x3 = phi(left: x1, right: x2)
y1 = x3 + 3

ϕ\phi 根据实际进入该块的前驱选择对应值,不是把两个值都求出后任选,也不是普通函数调用。循环也需要 ϕ\phi 合并入口值与回边值。

SSA 的“静态一次”不意味着循环中的那条定义只执行一次;同一静态指令仍可在不同迭代执行。

支配关系

若从入口到 B 的每条路径都经过 A,则 A 支配 B。普通 SSA 使用要求其定义支配使用;ϕ\phi 操作数则按对应前驱边解释。

支配边界刻画不同定义可能合流的位置,可用于放置 ϕ\phi。将表达式移动到某块前,必须确认所有路径都具有所需定义,且不会引入原本不发生的副作用或异常。

内存不自动成为 SSA 值

标量寄存器值容易重命名,内存中的 *p*q 可能是同一位置。一次 store 会影响哪些后续 load,需要别名分析或更明确的内存依赖表示。

text
a = load p
store q, 7
b = load p

只有证明 pq 不别名,或证明写入不会改变读取结果,才能把 b 替换为 a。SSA 名不同不能证明指向的对象不同。

练习

  1. 把求和循环转换为含循环入口 ϕ\phi 的 SSA。
  2. 判断菱形 CFG 中哪些块支配合流块。
  3. 为什么将 SSA 销毁成机器代码时,ϕ\phi 常需要变成前驱边上的并行复制?

上次更新: