构造静态单赋值形式的简单方法

21 阅读4分钟

最大静态单赋值形式(maximal SSA form)

插入Φ函数

在具有多个前趋的每个程序块起始处,为当前过程中定义或使用的每个名字y,插入一个Φ函数,如y <- Φ(y, y)

对于CFG中的每一块前趋块,Φ函数都应该有一个参数与之对应。这一规则在需要插入Φ函数之处插入一个Φ函数

当然它也插入了许多非必要的Φ函数

重命名

在插入Φ函数之后,编译器可以计算可达定义。由于插入的Φ函数也是定义,它们确保了对任一使用处都只有一个定义能够到达。接下来,编译器可以重命名每个使用处的变量和临时值,以反映到达该处的定义

总结

上面步骤构造出了一个正确的静态单赋值形式。每个变量都刚好定义一次,而每个引用都使用了某个不同定义的名字

但该步骤产生的静态单赋值形式可能具有很多不必要的Φ函数

这些额外的Φ函数可能会带来问题。它们会降低在静态单赋值形式上执行的某些种类分析的精确度,它们会占用空间,使得编译器浪费内存来表示冗余(即形如xj <- Φ(xi, xi)) 或不活动的Φ函数

它们同样会增加使用由此产生的静态单赋值形式的任何算法的代价,因为相关的算法必须遍历所有不必要的Φ函数

我们将此版本的静态单赋值形式称为最大静态单赋值形式(maximal SSA forom).构建具有较少Φ函数的静态单赋值形式需要的工作,特别地,编译器必须分析代码来确定不同的值在CFG中何处会聚

支配边界

最大静态单赋值形式的主要问题是包含了很多的Φ函数。为了减少Φ函数的数目,编译器必须更谨慎地判断何处真正需要Φ函数。放置Φ函数的关键在于,理解在每个汇合点处究竟哪个变量需要Φ函数

为高效并有效地解决这个问题,编译器可以改变问题的表述方式。对每个程序块i,编译器可以判断对程序块i中某些定义需要插入Φ函数的程序块的集合。在这种计算中,支配性发挥了关键作用

考虑CFG的结点n中的一个定义。该值到达某个结点m时,如果 n ∈ DOM(m),则该值不需要Φ函数,因为到达m的每条代码路径都必然经由n

该值无法到达m的唯一可能是有另一个同名定义的干扰,即在n和m之间的某个结点p中,出现了与该值同名的另一个定义。在这种情况下,在n中的定义无需Φ函数,而p中的重新定义则需要

结点n中的定义,仅在CFG中n支配区域以外的汇合点,才需要插入相应的Φ函数。更正式地说,结点n中的定义,仅在满足下述两个条件的汇合点才需要插入对应的Φ函数

    1. n 支配 m 的一个前趋(q ∈ preds(m) 且 n ∈ DOM(m) )
    1. n 并不严格支配 m

计算支配边界

为高效地插入Φ函数,需要为流图中的每个结点计算支配边界。我们可以将为图中每个结点n计算DF(n)的任务表述为一个数据流问题

image.png

image.png

图9-8所示。因为CFG中只有汇合点才是支配边界的成员,首先识别出图中的所有汇合点。对于一个汇合点j,考察其在CFG中的每个前趋结点

该算法基于三个见解

    1. DF集合中的结点必定是图中的汇合点
    1. 对于一个汇合点j,j的每个前趋结点k必定有 j ∈ DF(k), 因为如果j具有多个前趋结点,则k是无法支配j的
    1. 如果对j的某些前趋结点k,j ∈ DF(k), 那么对每个结点 l ∈ Dom(k), 必定有 j ∈ DF(l), 除非 l ∈ DOm(j)