简介
复制传播(copy propagation),在赋值表达式x=y,用y去替换x的使用直到x被再次赋值为止。
x = y
if (x > 1)
{
s = x * f(x);
}
将会被转换成
x = y
if (y > 1)
{
s = y * f(y);
}
经过复制传播之后会产生一些死代码,这时候就可以通过死代码消除优化去处理。在该例子中转换后的x = y就是死代码,可以被删除掉。
原理
支配结点树
如果每一条从流图的入口节点到节点n的路径都经过节点d,我们就说d支配(dominate)n,记为d dom n。请注意,在这个定义下每个节点支配它自己。
例:考虑下图中的以结点1作为入口结点的流图。
入口节点支配所有结点(这个结论对所有流图都成立),
结点2只能支配它自己,因为控制流可以通过1->3开头的路径到达所有其他结点,
所以结点3支配除1、2之外的所有结。
结点4支配出1、2、3之外的所有其他结点,因为所有从1开始的路径的开头要么是1->2->3->4,要么是1->3->4。
结点5和6都只支配它们自身,因为控制流可以选择从它们中的某一个结点通过,从而绕过另一个结点。
结点7支配结点7、8、9、10。
结点8支配结点8、9、10。
结点9和10支配它们自身。
一种有用的表示支配结点信息的方法是用支配结点树(dominator tree)来表示。在树中,入口结点就是根结点,并且每个结点d只支配它们在树中的后代结点。
如下图显示了上述流图的支配结点树。
支配结点的性质决定了一定存在支配结点树:每个结点n具有唯一的直接支配结点(immediate dominator)m,在从入口结点到达结点n的任何路径中,它是n的最后一个支配结点。用dom关系来表示,n的直接支配节点m具有以下性质:
如果d≠n且d dom n,那么d dom m。
我们将会给一个简单的算法来计算流图中各个结点n的所有支配结点。这个算法基于如下原理:如果p1,p2,…,pk是n的所有前驱并且d≠n,那么d dom n当且仅当对于每个i,d dom pi。这个问题可以写成一个前向数据流分析问题。数据流的值域是基本块的集合。一个结点的支配结点集合(它自己除外)是它的所有前驱的支配结点的交集;因此这个问题的交汇运算是交集运算。基本块B的传递函数直接把B自身加入到自身加入到输入结点集合中。问题的边界是ENTRY结点支配它自身。最后,内部结点的初始集是全集,也就是所有结点的集合。
算法:寻找支配节点。
输入:一个流图G,G的结点集是N,边集是E,而入口结点是ENTRY。
输出:对于N中的各个结点n,给出D(n),即支配n的所有结点的集合。
方法:求出由下图给定参数的数据流问题的解。输入流图的基本块就是结点。对于N中的所有结点n,D(n)=OUT[n]。
根据此算法,我们求上面例子中的支配结点集。
令D(n)为OUT[n]中的结点的集合。
结点1是入口结点,算法的第一行首先把{1}赋给D(1)。
结点2的前驱只有1,因此D(2)={2} ∪ D(1),这样D(2)就被设置为{1,2}。
结点3的前驱是1、2、4、8,因为所有内部结点的值都被初始化为结点的全集N,
D(3) = {3} ∪ ( {1} ∩ {1,2} ∩ {1,2,…,10} ∩ {1,2,…,10} ) = {1,3}
D(4) = {4} ∪ (D(3) ∩ D(7)) = {4} ∪ ( {1,3} ∩ {1,2,…,10} ) = {1,3,4}
D(5) = {5} ∪ D(4) = {5} ∪ {1,3,4} = {1,3,4,5}
D(6) = {6} ∪ D(4) = {6} ∪ {1,3,4} = {1,3,4,6}
D(7) = {7} ∪ (D(5) ∩ D(6) ∩ D(10)) = {7} ∪ ( {1,3,4,5} ∩ {1,3,4,6} ∩ {1,2,…,10} ) = {1,3,4,7}
D(8) = {8} ∪ D(7) = {8} ∪ {1,3,4,7} = {1,3,4,7,8}
D(9) = {9} ∪ D(8) = {9} ∪ {1,3,4,7,8} = {1,3,4,7,8,9}
D(10) = {10} ∪ D(8) = {10} ∪ {1,3,4,7,8} = {1,3,4,7,8,10}
因为在后续的迭代中,这些值不在改变,它们就是这个支配结点问题的最终答案。
UD Chain/DU Chain
在编译器优化中,UD链(Use-Definition Chain)是一种重要的数据结构。它用于表示程序中变量的使用和定义之间的关系。具体来说,对于每个变量的使用(Use),UD链都会链接到该变量的定义(Definition)。
id:0 MOV R2,R3
id:1 ADD R0,R1,R2
在上述汇编代码中R2,被定义(define)在id:0的指令中,他的使用(use)在id:1的指令。
实现
复制传播可以分为前向复制传播和后向复制传播。
- 前向复制传播
id:0 MOV R2,R3
id:1 ADD R0,R1,R2
==>
ADD R0,R1,R3
分析ADD指令的源操作数,一共两个源操作数R1、R2。
分析R2,找到R2的定义(define),发现是在id:0指令中,将源操作数R3替换id:1的R2。
- 后向复制传播
id:0 ADD R0,R1,R2
id:1 MOV R3,R0
==>
ADD R3,R1,R2
分析MOV指令的源操作数R0。
找到MOV指令源操作数R0的定义(define),发现是在id:0指令中,将源操作数R0替换id:1的R3。
未完待续…