10.5 SSA 形式与 Phi 节点
静态单赋值(SSA)只立一条规矩:每个变量恰好赋值一次。重新赋值变成新版本,于是 x = x + 1 变为 x2 = x1 + 1。
这种“单一定义”让定义-使用(def-use)链显式化,这正是现代优化器普遍工作在 SSA 上的原因。
正在加载交互实验...
正在加载概念检查...
正在加载本节练习...
汇合难题
版本化在汇合处会崩。如果两个前驱分别定义 x1 和 x2,流出去的是哪个版本?SSA 用 phi 节点回答:
code
x3 = phi(x1, x2)phi 根据控制来自哪个前驱选出正确版本。只有当多条路径把不同版本汇到一起时才需要它。
正在加载交互实验...
正在加载概念检查...
正在加载本节练习...
放置与收益
phi 应落在每个定义的支配边界,也就是独立路径重新汇合的精确点。放在别处要么浪费,要么破坏形式。
有了干净的定义-使用链,常量传播、死代码消除、值编号都更简单更强。SSA 正是让这些优化遍在好 IR 上近乎轻松的原因。
最小 SSA 与剪枝 SSA
在每个边界都放 phi 会过多。最小 SSA 只在必要处插入;剪枝 SSA 更进一步,丢弃在汇合处已死的 phi。phi 越少分析越便宜,放置算法因此值回票价。
退出 SSA
硬件没有 phi 指令,代码生成前必须解构 SSA:每个 phi 变成各前驱块里的拷贝。草率处理会撞上 lost-copy 与交换(swap)问题——并行 phi 语义被错误地串行化。正确解构会插入临时变量保留 phi 的“同时”性。
为什么赢
看常量传播。普通代码里 x 三次重赋值,分析得追踪哪条定义到达每个使用;SSA 里每个 x_i 只有一个定义,常量自然流到所有使用。多数主流优化都吃到这份折扣,这也是 LLVM、GCC、V8、HotSpot 默认用 SSA 中层 IR 的原因。
正在加载概念检查...