“非 SSA 中还要利用到达定义分析核对操作数:若循环内定义提供这个操作数,该定义必须是到该使用处唯一的来源,并已被证明不变;还须排除旧值从循环入口或其他赋值到达。仅仅看到“源文件里只写了一行…”
形式陈述
先把“到达”说清楚
固定单过程、单线程的控制流图,每条赋值只修改一个局部变量;所有变量使用前有定义,输入参数在人工入口定义。这里没有间接写内存、异常边或隐藏的函数副作用。整数运算可以按数学整数解释,本分析只关心变量的读写位置。
给赋值 x:=e 标号 d。若存在从 d 之后到程序点 p 的有限图路径,中间没有另一次对 x 的赋值,就说 d 到达 p。路径可以经过循环,但不能跨过同一变量的重新定义。某个定义到达 p,不代表每次执行都用它,也不保证这条图路径在具体数据下可行。
输入 a 的定义写作 input:a,这样“参数本来就有值”和“程序忘了赋值”不被混成一回事。若语言允许未初始化读取,应增加明确的未初始化来源并在使用处诊断;不能把空集合解释成任意合法输入。
杀掉旧来源,再生成新来源
对每个变量 x,预先列出程序里全部定义身份
减法删除的不是旧数值,而是“这些赋值还可能提供 x 当前值”的资格。先读取右侧、再更新左侧,所以 x:=x+1 对 x 的使用要查 执行前 的 S;随后新定义才取代旧定义。
直线基本块的转移按语句顺序复合。若一个块先执行 d1:x:=1,再执行 d2:x:=2,块出口只能生成 d2。把块内全部定义简单并成 GEN,会错误地让 d1 穿过 d2 到达出口。计算块摘要时,从空集合顺序执行上述转移,得到 GEN;KILL 收集块内被赋值变量的全部其他定义,即可写成
直觉
在 x:=1; x:=2; return x 中,返回值来自第二次赋值。加入分支以后,答案可能不再只有一个:一条路最后写了 1,另一条路最后写了 2。到达定义分析给每次赋值一个身份,列出一个程序点上可能提供当前值的那些身份。它记录值的来源,不直接计算值是多少。
例子与边界
一张菱形图中的七次赋值
以下用块名加块内序号标识赋值;p 是布尔输入,a、b 是整数输入。
E: E.1 x := a+b
E.2 y := x
if p goto T else F
T: T.1 u := a+b
goto J
F: F.1 u := a+b
goto J
J: J.1 z := a+b
J.2 dead := z+1
J.3 r := u+y
return r
进入 J 时,x 只可能来自 E.1,y 只可能来自 E.2,u 则可能来自 T.1 或 F.1。完整集合是
两个 u 定义同时出现在集合里,表示两种不同路径,绝不表示一次执行拥有两个“当前 u”。J.1 和 J.2 尚未执行,所以它们不能提前进入这个入口集合。执行 J.3 时,只从当时的集合中筛出定义 u 与 y 的身份,就得到该条加法的定义—使用连接。
推论与应用
把循环变成有限求解
这是单调数据流框架的前向 may 实例,合流用并集:
人工入口的 Boundary 是输入定义集,其他块为零。先删除从入口在图上不可达的块,再将所有 IN、OUT 初始化为空,把全部保留块放入工作队列。取出 B 后重算;只有 OUT 改变,才将 B 的后继加入队列。队列去重只影响工作量,不改变方程。
先处理不可达性有实质作用。若有无人能进入的 U,执行 U.1 u:=99; goto J,不该让 U.1 污染 J。普通 gen 转移会从空集合产生 U.1,故“所有节点从空开始”本身并不能解决不可达区域的问题。
再看一个循环:E 令 d0:x:=0,H 检查是否继续,B 执行 d1:x:=x+1 并回 H,退出 X 返回 x。第一次传播后,H 收到 d0;回边传播后,H 又收到 d1,最终
为什么不会遗漏真实来源
对一条有限图路径的长度归纳。零长度入口路径由输入定义覆盖;沿不写 x 的语句走一步,原来源保留;沿写 x 的语句走一步,旧来源被删,新身份被加入。汇合的并集覆盖任意前驱。因此任何真正到达的定义都会进入某轮传播结果。
反方向也成立于本页的图路径定义:初始事实有入口见证,GEN 有经过本语句的见证,并集选择某个前驱见证,KILL 只删事实。每个保留下来的身份都可附上一条没有再次写同变量的图路径。有限不动点因而恰好等于图路径上的到达定义;具体运行是图路径的子集,结果对真实执行可靠,但可能多报不可行路径的来源。
有限身份集有 D 项,每个节点集合只会增长,最多新增 D 项,故过程终止。N 个块、M 条边、最大入度
从分析事实走向变换
如果使用 x 的地方只看到一个定义 d,可以连接这次使用与 d;还不能仅凭这一点,把 d 的右侧文本原样搬来。例如 d 是 x:=a+b,随后 a 被改写,再使用 x。x 仍来自 d,却等于旧 a+b。若改成现场重算,就丢失了捕获旧操作数值的效果。复制传播与可用表达式分别检查这种失效条件。
迁移任务:把菱形图 F 的定义改为 F.1 u:=0。进入 J 的身份集合不变,返回值却在 p 为假时从 E.3 u:=9:进入 J 的 u 来源应为 T.1 与 E.3。两问用来区分“身份集合”与“数值计算”,以及跳过写入与写入常量零。
OPT-8 完整任务给出菱形图的全表、实际 IR 变换和检查器;每种分析在同一份输入上回答不同的问题。
参考资料
- Alfred V. Aho, Monica S. Lam, Ravi Sethi, Jeffrey D. Ullman, Compilers: Principles, Techniques, and Tools, 2nd ed., Pearson, 2007,§9.2.4 “Reaching Definitions”、§9.3 的数据流基础。本文的赋值身份、输入集合和迁移例为独立教学实例。
- Gary A. Kildall, “A Unified Approach to Global Program Optimization,” POPL 1973, pp. 194–206,出版页。本文把通用不动点框架限制为有限定义集合。