“SSA 构造把多次赋值拆成版本,消解并不是简单删除下标。不同版本可能同时活跃,强行恢复同一源变量名会覆盖值。正确的输出允许继续保留许多不同临时量,只是不再含 φ 伪指令。”
形式陈述 ​
经典 SSA 构造分成不可互换的两阶段。第一阶段运行φ 节点放置算法,为每个变量在所需汇合块插入未完全命名的 φ。第二阶段取得由支配树算法计算的树,从入口深度优先遍历,并为每个原变量
进入块
核心不变量是:遍历到任一使用时,栈顶就是沿当前支配树路径最近的定义,因此该定义支配使用。入口参数先作为人工入口定义压栈;若栈为空却遇到使用,应按语言规则报告未初始化、插入未定义值,或拒绝输入,不能默默造一个常量。
算法还应区分普通使用与 φ 的边使用。普通指令在所在块的执行点读取栈顶;
直觉
支配树遍历像带着一叠按词法路径生效的版本卡片下行。进入一个定义所在块就盖上一张新卡,离开该块覆盖的支配子树就揭掉它。兄弟子树彼此不可见,因此回溯弹栈是正确性的一部分,而不是内存优化。
φ 参数在前驱块处理结束时填写,因为“从哪条边来”决定应看到哪个栈顶。φ 的结果则在进入自身块时先定义,供块内普通指令和循环体使用。将这两件事颠倒,会在循环头读到回边版本或在菱形中把两条边写成同一参数。
例子与边界
对
若忘记退出
循环例可逐栈执行。入口创建
不可达块不在入口支配树中。可先删除它们,或为不可达区域另建根并规定 undef 语义;直接让 DFS 遗漏而仍保留旧名字,会产出混合格式。内存、异常边和调用效果也需由独立模型承载:标量重命名不会解决未知别名,漏掉异常后继则可能漏填 φ 参数。
推论与应用
完成后可线性检查两项性质:每个 SSA 名字恰有一个定义,且普通使用的定义支配使用;φ 参数则要求其定义支配对应前驱边的末端。后一个条件不能误用“定义支配 φ 所在块”,因为某一分支定义通常不支配汇合块。
构造算法通常近似线性于 CFG、支配树和产生的 φ 总量,但最坏输出规模本身可能较大。实际实现会采用 pruned 放置、按变量稀疏工作表或 sealed-block 等替代构造法。无论实现路径如何,若声称得到同一 SSA 语义,都必须恢复边选择、唯一定义与支配使用这三项接口。
参考资料
- Ron Cytron, Jeanne Ferrante, Barry K. Rosen, Mark N. Wegman, and F. Kenneth Zadeck, “Efficiently Computing Static Single Assignment Form and the Control Dependence Graph,” ACM TOPLAS 13(4), 1991, §5, Figures 11–12.
- Andrew W. Appel, Modern Compiler Implementation in ML, Cambridge University Press, 1998, Chapter 19.
- Keith D. Cooper and Linda Torczon, Engineering a Compiler, 3rd ed., Morgan Kaufmann, 2023, §9.3.