« 上一篇下一篇 »

数据流分析中的保守主义

实际数据流值是通过程序的所有可能执行路径定义的。所有的数据流模式计算得到的 都是对实际数据流值的估算。我们必须保证所有的估算误差都在“安全”的方向上。如果一个策略性决定不允许我们改变程序计算出的内容,它就被认为是“安全的”(或者说“保守的”)。 遗憾的是,安全的策略会让我们错失一些能够保持程序含义的代码改进机会。但实际上对所有的代码优化技术而言,没有哪个安全的策略可以不错失任何机会。使用不安全策略就是以改变程序含义的代价来加快代码速度。一般来说,这是不可接受的。

因此在设计一个数据流模式的时候,我们必须知道这些信息将如何被使用,并保证我们做出的任何估算都是在“保守”或者说“安全”的方向上。每个模式和应用都要单独考虑。比如,如果我们把到达定值信息用于常量折叠,那么把一个实际不可到达的定值当作可到达就是安全的(我们可能在x实际是一个常量且可以被折叠的情况下认为不是一个常量),但是把一个实际可到达的定值当作不可到达就是不安全的(我们可能把*替换为一个常量,但是实际上程序有时会赋予一个不同于该常量的值)。

到达定值的传递方程

现在我们为到达定值问题设置约束。我们首先检查单个语句的细节。考虑一个定值

d: U = V+W

在这里,+号代表了一个一般性的二元运算符。以后我们经常会这么做。

这个语句“生成”了一个变量u的定值d,并“杀死”了程序中其他对U的定值,而进人这个语 句的其他定值都没有受到影响。因此,定值d的传递函数可以被表示为

fd(x) =gendU(x-killd)

其中,即由这个语句生成的定值的集合,而是程序中所有其他对u的定值。一个基本块的传递函数可以通过把它包含的所有语句的传递函数组合起来而构造得到。下面我们会看到,函数的组合仍然是这种形式。我们把这种形式称为“生成-杀死形式”。假设有两个函数f1(x) = gen1 U (x - kill1)和f2 (x) = gen2 U (x - kill2)。那么

f2(f1(x)) =gen2 U {gen1 U(x-kill1)- kill2 )

=(gen2 U (gen1- kill2) ) U (x - ( kill'1U kill2 ))

这个规则可以扩展到由任意多个语句组成的基本块。假设基本块B有n个语句,而第i个语 句的传递函数为fi(x) -geni U(x- killi),i = 1 , 2,…,n,那么基本块B的传递函数可以写成:

fB(x) =genBU(x -hillB)

其中

killB = kill1 U kill2 U killn

而genB = genn U (genn _ 1 - hilln ) U (genn_2 - killn -1- killn ) U (genl - kill2 - kill3 — killn )

因此,和单个语句一样,一个基本块也会生成一个定值集合并杀死一个定值集合。集合中包含了所有在紧靠基本块之后的点上“可见”的该基本块中的定值——我们把它们称为“向下可见”(downwards exposed)的。在一个基本块中,一个定值是向下可见的,仅当它没有被同一个基本块中较后的对同一变量的定值“杀死”。一个基本块的集就是所有被块中各个语句杀死的定值的集合。请注意,一个定值可能同时出现在基本块的集和集中。在这种情况下,该定值会被这个基本块生成,即优先考虑该定值是否在炉gen集中。这是因为在gen-kill形式中,kill集会在gen集之前被使用。

基本块d.2: a = 4的集是{d2},因为 d1不是向下可见的。基本块的kill集包括了d1和d2,因为d1杀死了d2, 杀死了d1. 虽然如此,因为减去集的运算先于和gen集的并集运算,这个基本块的传递函数的结果中总是包含定值d2。

« 上一篇下一篇 »