Skip to content

算法Algorithm

到达定义分析

Reaching definitions analysis · 到达定值分析

沿控制流保留每个变量可能最近执行的赋值,以入口定义、逐语句杀生与最小不动点建立定义到使用的可靠连接。

形式陈述 ​

先把“到达”说清楚 ​

固定单过程、单线程的控制流图,每条赋值只修改一个局部变量;所有变量使用前有定义,输入参数在人工入口定义。这里没有间接写内存、异常边或隐藏的函数副作用。整数运算可以按数学整数解释,本分析只关心变量的读写位置。

给赋值 x:=e 标号 d。若存在从 d 之后到程序点 p 的有限图路径,中间没有另一次对 x 的赋值,就说 d 到达 p。路径可以经过循环,但不能跨过同一变量的重新定义。某个定义到达 p,不代表每次执行都用它,也不保证这条图路径在具体数据下可行。

输入 a 的定义写作 input:a,这样“参数本来就有值”和“程序忘了赋值”不被混成一回事。若语言允许未初始化读取,应增加明确的未初始化来源并在使用处诊断;不能把空集合解释成任意合法输入。

杀掉旧来源,再生成新来源 ​

对每个变量 x,预先列出程序里全部定义身份 Defs(x),包括适用的输入定义。赋值 d:x:=e 的转移是

fd(S)=(S∖Defs(x))∪{d}.

减法删除的不是旧数值,而是“这些赋值还可能提供 x 当前值”的资格。先读取右侧、再更新左侧,所以 x:=x+1 对 x 的使用要查 执行前 的 S;随后新定义才取代旧定义。

直线基本块的转移按语句顺序复合。若一个块先执行 d1:x:=1,再执行 d2:x:=2,块出口只能生成 d2。把块内全部定义简单并成 GEN,会错误地让 d1 穿过 d2 到达出口。计算块摘要时,从空集合顺序执行上述转移,得到 GEN;KILL 收集块内被赋值变量的全部其他定义,即可写成 GEN∪(S∖KILL)。

直觉

在 x:=1; x:=2; return x 中,返回值来自第二次赋值。加入分支以后,答案可能不再只有一个:一条路最后写了 1,另一条路最后写了 2。到达定义分析给每次赋值一个身份,列出一个程序点上可能提供当前值的那些身份。它记录值的来源,不直接计算值是多少。

例子与边界

一张菱形图中的七次赋值 ​

以下用块名加块内序号标识赋值;p 是布尔输入,a、b 是整数输入。

text
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。完整集合是

IN[J]={input:a,input:b,input:p,E.1,E.2,T.1,F.1}.

两个 u 定义同时出现在集合里,表示两种不同路径,绝不表示一次执行拥有两个“当前 u”。J.1 和 J.2 尚未执行,所以它们不能提前进入这个入口集合。执行 J.3 时,只从当时的集合中筛出定义 u 与 y 的身份,就得到该条加法的定义—使用连接。

推论与应用

把循环变成有限求解 ​

这是单调数据流框架的前向 may 实例,合流用并集:

IN[B]=Boundary[B]∪⋃P∈pred(B)OUT[P],OUT[B]=fB(IN[B]).

人工入口的 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,最终 IN[H]=IN[X]={d0,d1}。B 中右侧 x 的来源也是这两者,而 B 出口只有 d1。循环反复执行的同一赋值仍用同一个静态身份,分析不为每轮创造新 d1。

为什么不会遗漏真实来源 ​

对一条有限图路径的长度归纳。零长度入口路径由输入定义覆盖;沿不写 x 的语句走一步,原来源保留;沿写 x 的语句走一步,旧来源被删,新身份被加入。汇合的并集覆盖任意前驱。因此任何真正到达的定义都会进入某轮传播结果。

反方向也成立于本页的图路径定义:初始事实有入口见证,GEN 有经过本语句的见证,并集选择某个前驱见证,KILL 只删事实。每个保留下来的身份都可附上一条没有再次写同变量的图路径。有限不动点因而恰好等于图路径上的到达定义;具体运行是图路径的子集,结果对真实执行可靠,但可能多报不可行路径的来源。

有限身份集有 D 项,每个节点集合只会增长,最多新增 D 项,故过程终止。N 个块、M 条边、最大入度 Δ,用宽 w 的位集时,一次完整转移和每份前驱合并需 O((1+⌈D/w⌉)) 个字操作;基本工作队列的保守界可写成 O((N+MD)(Δ+1)(1+⌈D/w⌉)),另计构建块摘要。显式保存 IN/OUT 需要 O(N(1+⌈D/w⌉)) 个字。这里的1项计入空事实域仍需执行的队列和边界工作,不声称每个程序点只访问一次。

从分析事实走向变换 ​

如果使用 x 的地方只看到一个定义 d,可以连接这次使用与 d;还不能仅凭这一点,把 d 的右侧文本原样搬来。例如 d 是 x:=a+b,随后 a 被改写,再使用 x。x 仍来自 d,却等于旧 a+b。若改成现场重算,就丢失了捕获旧操作数值的效果。复制传播与可用表达式分别检查这种失效条件。

迁移任务:把菱形图 F 的定义改为 F.1 u:=0。进入 J 的身份集合不变,返回值却在 p 为假时从 2(a+b) 变成 a+b。再删除 F.1,并在 E 新增 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,出版页。本文把通用不动点框架限制为有限定义集合。
关系图谱11 个相邻概念 · 2 类关系

拖动节点调整位置。

显示关系

显示:依赖

  1. 前置三跳
  2. 前置二跳
  3. 前置一跳
  4. 当前条目
  5. 后续一跳
  6. 后续二跳
  7. 后续三跳
文字版关系按与当前条目的最短距离分组
类型化关系