Skip to content

算法Algorithm

支配作用域值编号

Dominance-scoped value numbering · Dominator-based value numbering · DVNT

沿 SSA 支配树维护可回退的表达式表,用值代表识别跨块冗余,区分等值与载体可用,保守处理未解循环φ并执行变换后的程序。

形式陈述 ​

比较操作数的值,而不只比较名字 ​

程序先做 t=a+b,又把 a 复制到 u,随后计算 x=u+b。两次加法的文本不同,但 u 代表的还是 a 的值。值编号为已证明相等的值选一个共同代表,再以“操作码与各操作数代表”作为表达式键,因此能够识别这次重算。

本页输入是一张已经验证的SSA控制流图。每个名字恰有一个静态定义;普通使用由定义支配,同块定义还要位于使用之前;φ 的每个输入按对应前驱边检查可得性。所有块入口可达,人工入口没有入边和 φ。循环允许存在,块内指令数有限。

数值是数学整数。参考语言的操作为复制、加、减、乘、小于和相等;比较返回0或1,分支把0视作假、其他整数视作真。操作纯粹、确定、总定义,没有内存、调用、溢出、异常、随机性或可观察副作用。表达式键保留操作码和参数顺序,不自动重排加法,也不使用任意代数恒等式。

输出是值代表表、逐项替换证据和可执行 SSA 程序。为便于逐状态核对,参考变换保留所有源定义:冗余普通运算改为从代表复制,原 φ 也保留。后续使用可改读代表,但这不是删除全部无用复制或 φ 的终点;死代码删除仍是另一阶段。

两张表不能混为一张 ​

第一张表 VN 把每个已处理的 SSA 名字映到一个代表;代表是另一个 SSA 名字或整数常量。参数起初代表自身,复制 x:=y 令 VN(x)=VN(y)。本页用名字作值编号,不是给运行时整数重新分配内存地址。

第二张表 H 把表达式键映到一个当前可复用的结果名字。关键字例如 add(VN(a),VN(b))。H 只保存当前块和其支配树祖先已经处理的计算,离开某个树子分支时撤销该分支加入的条目。VN 可继续保存已处理名字的等值信息,H 却不能因此把兄弟分支的结果当成已初始化载体。

先用支配关系得到支配树,再按树作深度优先遍历。树孩子按 CFG 的逆后序排列,以尽量在处理汇合点前处理其前驱;最终是否处理一个 φ 仍以“所有输入编号已经知道”为准,不把遍历顺序当成解决所有回边的保证。

进入块时建立一个新的 H 作用域。顺序处理普通指令 x:=op(a₁,…,aₖ):先把操作数换成 VN 代表,再查询完整键。如果 H 已有该键的载体 r,记录 VN(x)=r,把指令改为 x:=r;否则保留计算,令 VN(x)=x,并在当前作用域登记键→x。处理完全部支配树孩子后,撤销本块新增的键。用新增键列表回退即可,不必为每个孩子复制整张表。

φ 只在证据足够时建立等值关系 ​

处理块首 φ 时,先检查每个入边操作数是否已有 VN。若全部已知且代表相同,φ 的结果可取该共同代表。若全部已知,且同一块前面另一个 φ 的“前驱标签→值代表”映射完全相同,则两结果取同一代表。

其他 φ 给结果一个新代表,特别是尚未处理的回边输入。这个决定保持保守:不猜测两个循环变量将来恰好同步增长,也不反复求解一般循环同余。即使两个 φ 最后确实总相等,本算法也可能不发现。

比较 φ 必须保留前驱标签。φ(T:a,F:b) 与 φ(T:b,F:a) 通常不同;把输入排序成无标签集合会丢掉选择方向。不同块的 φ 也不能仅凭参数列表相同就认作同一选择,因为到达它们的控制条件可能不同。

参考器保留原 φ 定义,并在整个遍历后把各个输入槽换成已证明相等、在对应边上可得的代表。普通指令在本块顺序处理中改写,分支和返回读取也用同一代表表。保留定义使输出仍能交出所有原名字的边界状态,但不扩大可以建立等值关系的条件。

直觉

VN 像一本“这些名字已经证明谈的是同一个值”的记录。H 则回答更具体的问题:“沿当前所有入口路径,哪个名字现在真的装着这个值?”第一本记录可以提到别处的名字,第二本不能拿尚未执行的分支给当前路径供货。

支配树提供自然的作用域。走进一个孩子,它继承祖先计算;处理完后回到父节点,那些仅在该孩子里得到的载体就退出作用域。它与词法环境的入栈、出栈相似,但这里的树来自每条控制路径都必须经过的节点。

例子与边界

一份包含复制与两个 φ 的完整例子 ​

输入 a=2、b=3、c=4,p 决定走哪一条分支:

text
E: t := a+b
   u := a
   if p goto T else F
T: x := u+b
   left := x*c
   goto J
F: y := a+b
   right := y*c
   goto J
J: z := phi(T:left, F:right)
   w := phi(T:left, F:right)
   r := z+w
   k := w+z
   s := t+t
   return (r,k,s)

入口建立 VN(u)=a,并把 add(a,b)→t 放入 H。T 中 x 的键因此是 add(a,b),可改为 x:=t;F 中 y 同样改为 y:=t。left 与 right 的乘法表达式虽然也相同,却分别位于兄弟分支,没有共同支配的乘法载体,故两条乘法都保留。

J 的 z 合并两条不同名字,先保留代表 z。w 的逐前驱映射与 z 完全一致,因此 VN(w)=z。r 的键变成 add(z,z),k 的键也变成 add(z,z),于是 k:=r。这个等值不依赖加法交换律:真正被比较的两个有序参数已经逐项相同。

无论 p 为0还是1,源与目标都得到 z=w=20、r=k=40、s=10,返回 (40,40,10)。目标中的 x、y、k 仍有原位置的定义,只是改为复制;两个 φ 也仍执行。每个经过的源块末尾,所有已经定义的源名字都与原程序同值。

值相等与可用载体要分别证明

静态算术指令从8条降到5条;一次运行只走一边,动态算术从6次降到4次。复制次数从1次增到3次,φ 数量不变。这里只报告语义层操作计数,不把算术减少自动换成机器码加速。

兄弟分支为什么不能借名字 ​

把入口的 t 计算删去,令 T 计算 x=a+b,F 计算 y=a+b,J 用 φ(T:x,F:y) 返回所选值。两条路都返回5,但 x 没有在 F 路定义。如果 H 在退出 T 后不回退,F 可能错误改为 y:=x;取 p=0,执行会读到未初始化的 x。

这个反例不是说两次加法不相等,而是说不能用一边的存储载体代表另一边的运行。若要让相同值跨分支统一,还需要合法合流或专门的放置算法;仅把 H 改成永不删除的全局哈希表会丢掉支配保证。

静态名字在循环中仍可每轮改变 ​

循环头用 i:=φ(E:0,B:next) 与 sum:=φ(E:0,B:acc)。当 i<n 时进入 B,执行 one=i+1、again=i+1、acc=sum+again、next=i+1,再回到头;否则返回 sum。

第一次访问循环头时,next 和 acc 尚未编号,因此两个 φ 都取得新代表。循环体仍可把 again 与 next 改为 one 的复制,因为 one 在这一轮先算出同一 i+1。n=3 时两边都返回6,源体每轮4次加法,目标每轮2次,共12对6;n=0 时两边都没有体内加法。

one 不是跨全部迭代不变的常量。它在每次进入 B 时重新定义为1、2、3,后面的复制读取当轮 one。SSA 的“单赋值”是静态定义点唯一,不能据此缓存第一次循环的运行值。

推论与应用

支配作用域怎样保证实际值可复用 ​

先建立局部表不变量:H 中每个载体的定义位于当前支配树祖先,或位于当前块已处理的较早位置;它记录的键使用已证明等值的操作数代表。新增计算满足不变量,沿树下降只继承支配祖先,退出时删除局部键,因而兄弟分支不会泄漏载体。

仅说“SSA 名字不会重新赋值”仍不足以证明循环。本页需要动态版本的事实:若某个操作数定义支配载体定义,而载体又支配当前复用点,那么在最近一次载体计算后、到这次复用前,操作数不可能被重新执行定义却绕过载体。否则,把首次到达那个操作数定义的入口路径与这段绕过载体的后缀拼接,就得到一条到复用点却不经过载体的入口路径,矛盾。同块内则由普通指令的顺序保证。

因此载体保存的操作数版本仍适用于这次复用。对处理顺序归纳,复制传播保持等值,已有键逐分量使用同值操作数,纯确定操作便得到同一结果。将 x:=op(...) 改为 x:=r 不改变 x 或任何其他源名字的值。

φ 的平凡情况在每条入边都取同一个已知代表;同块重复情况则在同一条实际入边选择相同代表。未解回边保持独立,不需要循环猜测。把这些关系用于后续普通读取或对应 φ 槽,仍保持选择到的实际值;原 φ 同时读取旧入边值的约定不变。

逐块组合,分支条件与返回值相同,所有原块都按同一顺序执行。每块只做有限个纯总运算或复制,所以不会把有限执行变成发散,也不会吞掉原来的无限控制流。对可能不终止的程序,有限测试只能检查前缀;全局结论由这项逐块关系保证。

分开计算分析、变换与目标成本 ​

若已经给出正确的支配树,令 K 为指令与参数槽的总大小,N、M 为块数和边数。在固定元数、固定大小键及平均常数时间哈希假设下,普通指令扫描、作用域登记和撤回共 O(K+N+M);VN 与作用域表占 O(K+N) 空间。没有重复复制整张 H,是这个界成立的关键。

参考器还为确定性展示排序支配树孩子和 φ 前驱槽,分别计 O(N log(N+1)) 与各 φ 的 O(d log(d+1)),d 为该 φ 的输入槽数。程序复制、最后的槽改写与输入输出验证也要计费;数学大整数的计算和字符串比较不能按无限精度常数免费处理。

支配关系并非免费输入。参考器为透明采用集合交的同步方程,而不是实现高效支配树算法:非入口集合从全体块开始,每个严格轮次至少删除一项,粗略至多 N² 个严格轮次;每轮集合工作可界为 O(N(N+M)),得到保守 O(N³(N+M)) 上界与 O(N²+M+K) 存储。输入和输出都验证 SSA,包含重复的支配检查;这不改变粗界,却应包含在实际运行成本中。

这个算法既不完备判定程序值等价,也不保证最少动态计算。它保留未解循环 φ,不能凭空发现分配律等代数关系,无法直接消除只有部分路径先算过的表达式。边局部部分冗余消除转而在缺失入边补计算,并单独证明载体初始化和路径计数,不应与本页哈希表作用域混在一起。

可执行终点要求交出实际代表表、被替换的三条指令、两个分支和循环的状态轨迹,并运行兄弟载体泄漏的失败例。随后检查一种需要新增边计算、仅靠本页不能完成的冗余。

参考资料
  • Preston Briggs、Keith D. Cooper、L. Taylor Simpson,Value Numbering,作者托管预稿,对应 Software: Practice and Experience 27(6), 1997, pp.701–724。实际读取预稿PDF第5–9页,Figure4及 φ 与统一哈希表讨论;预稿带占位出版头,不以其PDF页码代替正式刊页。本页保留源定义以便逐状态核验,并明确其循环 φ 保守边界
  • Andrew Myers,Cornell CS4120: Redundancy Elimination,值编号与冗余消除的教学顺序;本页的支配作用域算法、证明和主例另按原始论文与明确执行器展开
关系图谱5 个相邻概念 · 2 类关系

拖动节点调整位置。

显示关系

显示:依赖

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