“对变量 $v$,令 $\operatorname{Def}(v)$ 为原程序中含 $v$ 定义的块集合。经典算法以工作表 $W:=\operatorname{Def}(v)$ 和已放置集合…”
形式陈述 ​
设基本块
位于
φ 不是运行时把所有参数求值后任意挑选的普通函数。未走路径上的参数无需在本次执行中有值。一个块顶部的多个 φ 概念上并行读取各自前驱端的旧环境,再同时写入结果;顺序执行会制造错误的数据依赖。块参数表示把同一语义写为跳转实参与块形参,也必须保持边到实参位置的对应。
良构性还要求每条进入边恰有一个参数槽,参数类型与结果类型一致,且参数定义在对应边的尾端可用。若同一前驱块有两条不同标签的边进入
直觉
变量版本化像给每次赋值发一张不同颜色的票。控制流汇合时,后续代码需要一张统一票,却不能预先知道执行来自哪条路。φ 节点站在汇合入口查看“刚才走的是哪条边”,把那条边携带的版本改名为新的统一版本。选择由控制历史决定,而不是由数值大小、真假或某个运行时随机函数决定。
将 φ 放在块入口还有一项结构意义:选中的定义必须沿对应前驱路径可得,而 φ 的结果从块入口起支配后续使用。它把原本隐含在路径中的 reaching-definition 选择显式化,因而既是语义合流点,也是 SSA 图中的定义节点。
例子与边界
菱形 CFG 有
沿
循环头
第一次从
并行边界可由交换例看清。在块
推论与应用
φ 的放置位置由定义路径何处汇合决定,经典算法使用迭代支配边界;但“可能汇合”不等于“变量此后有用”。minimal SSA、semi-pruned SSA 与 pruned SSA 对死 φ 的处理不同,活跃性条件必须另行说明。把每个多前驱块都为每个变量放 φ 虽可能保持语义,却会产生大量无用定义。
数据流分析通常把 φ 参数视为位于对应前驱边上的使用,而把 φ 结果视为块入口的定义。于是活跃性从某个 φ 参数只沿那条前驱传播,不应把同一 φ 的全部参数都标成每个前驱的使用。这个边敏感约定既影响 pruned 放置,也影响后续干涉图;把 φ 当块内普通指令会制造本来不存在的同时活跃。
φ 也不应被直接交给没有这种指令的机器。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, §§3–5.
- Keith D. Cooper and Linda Torczon, Engineering a Compiler, 3rd ed., Morgan Kaufmann, 2023, Chapter 9.
- Fabrice Rastello, ed., SSA-based Compiler Design, Springer, 2022, Chapters 2 and 9.