理论教育 编译器设计之路:常量传播基础

编译器设计之路:常量传播基础

时间:2023-11-04 理论教育 版权反馈
【摘要】:请读者注意,常量传播的基本思想仅仅指出了其核心目标是尽可能用常量替代变量的引用,而不是减少IR指令。本书将重点讨论关于过程内常量传播的实现,即全局常量传播。讨论常量传播的关键在于确定常量传播是否安全,这是至关重要的。例7-4 GCC常量传播的实例分析。也就是说,在这种情形下,GCC的常量传播算法是不安全的。至此,已经讨论了常量传播的基本话题。常量传播的目的就是为常量折叠创造优化的条件。

编译器设计之路:常量传播基础

常量传播(constant propagation)是一种代码转换,其基本思想如下:对于给定关于某个变量v和一个常量c的赋值V+_c,在没有出现其他关于v定值的程序范围内,编译器则用c来替代出现的v的引用。常量传播是一种实现代价较小且效果显著的转换,它为进一步的常量折叠创造了新的契机。下面,通过一个简单的实例来解释常量传播的方法及意义。

例7-2 局部常量传播的实例,见表7-4。

这是一个简单的常量传播的实例。试比较(b)、(c)两列,不难发现,IR的语句规模似乎并没有任何减小。请读者注意,常量传播的基本思想仅仅指出了其核心目标是尽可能用常量替代变量的引用,而不是减少IR指令。其根据基本思想的描述,从(b)到(c)的转换似乎已经达到了预期的效果,例如,第0行对TO的定值已经分别传播到了第1、4行IR中,而第2行对Tl的定值已经分别传播到了第3、4行IR中。那么,这种传播的意义又是什么呢?仔细观察(c)列,不难发现,第4行IR的两个操作数都变成了常量,这是常量折叠的契机。换句话说,在优化阶段讨论常量传播的主要目的之一就是为常量折叠创造优化的条件。

表7-4 局部常量传播实例分析

978-7-111-32164-4-Chapter07-42.jpg

下面,再来看一个稍复杂的实例。

例7-3 全局常量传播的实例,见表7-5。

表7-5 全局常量传播实例分析

978-7-111-32164-4-Chapter07-43.jpg

从实现的角度而言,例7-3与例7-2的主要差别就在于例7-3的常量传播的范围已经突破了一个基本块的限制,是在整个过程范围内进行的。在这种情况下,需要考察从入口至当前IR的每一条可能到达路径的定值信息后,才能决策是否传播以及如何传播。在例7-3中,由于if语句真、假分支中都存在对i的常量定值,且常量的值都是20,因此,该常量值是可以传播到j:=i+j中的。假设某一个分支中不存在对i的常量定值,或者两个常量的值不同,此时,进行常量传播是不允许的。本书将重点讨论关于过程内常量传播的实现,即全局常量传播。为了便于讲解,除了有明确说明之外,本书提及的“常量传播”都是指全局常量传播,而不是局部常量传播。

讨论常量传播的关键在于确定常量传播是否安全,这是至关重要的。从ud链的角度来思考这个问题就变得非常简单了。ud链描述的就是可以到达某一变量引用点的所有关于该变量的定值信息。也就是说,讨论某一IR的操作数是否满足常量传播的条件,就只需要考察该操作数ud链中的每个定值点即可。如果定值点都满足以下四个条件:

(1)IR的操作码是赋值或类型转换运算。(www.daowen.com)

(2)Opl皆为值相同的常量。

(3)结果操作数不是间接寻址方式。

(4)不存在未初始化的路径。那么,常量传播就是安全的。对于前3个条件,一般读者不会有什么异议。而第4个条件通常是容易忽视的。在这个问题上,即使是经典的GCC,也存在百密一疏之处。

除了常量传播的安全性之外,判定两个常量值是否相等的标准也是编译器设计者感兴趣的。例如,10与10.0是否相等,10.0000001与10.000000是否相等,这些问题都必须由编译器设计者来明确回答。在实践中,不同的编译器关于这类问题的解释与处理是不尽相同的。很多时候,这个标准是依赖于运行机或者目标机的系统结构而定的。譬如,浮点数的表示、浮点指令集等。下面,笔者通过实例来分析GCC中关于常量传播的一个疏漏之处。

例7-4 GCC常量传播的实例分析。

如果读者使用GCC 3.2的-02参数编译表7-6中(a)列源程序后,不难发现,生成的可执行文件的运行结果永远是“23”,并不依赖于aa()函数的返回值。实际上,从(c)列的汇编程序片段中,读者是很容易证明这一结论的。请读者注意(c)列第13行与(b)列第17行的区别,这是调用printf之前的传参指令,不难发现,(c)列程序中是直接将常量“23”压栈,而(b)列程序中是将“-8(%ebp)”所示存储单元中的数据压栈。显然,从程序的原始语义来说,(c)列程序是不正确的。也就是说,在这种情形下,GCC的常量传播算法是不安全的。

表7-6 GCC常量传播的实例分析

978-7-111-32164-4-Chapter07-44.jpg

问题的本质在于GCC的常量传播并没有考虑未初始化路径的因素。从ud链角度分析,很容易理解这个错误。在整个程序中,b的唯一定值点就是b=23。因此,不考虑未初始化路径的情况下,编译器必然认为这种情形是满足常量传播要求,以至于将常量“23”直接传播到了printf语句中参数b的引用处。

至此,已经讨论了常量传播的基本话题。常量传播的目的就是为常量折叠创造优化的条件。因此,在很多情况下,为了保证优化效果,常量折叠是紧接着常量传播进行的。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈