我一直在研究这个主题,但没有找到好的具体答案。假设您在代码中有这些表达式:
B = 2
…
B = B + 5
…
B = J + B
…
(这些是非常简单的例子,我知道它们不现实)
B
在这些行中有许多不同的值。第一行是2
,稍后会变为7
,稍后会变为7 + J
。编译器需要跟踪B
的这些不同值,因此一种方法是重命名它们。例如,当B
重新定义为B = B+5
时,可以将其更改为B1 = B+5
。最后一次重新定义将看起来像B2 = J+B1
。
这个想法背后的动机涉及我正在建设的优化计划。它涉及用与它们相关的表达式替换变量。但是,如果重新定义了变量,则字符“B”可以同时表示多个事物。我用来跟踪事物的方法就是我上面所描述的,重新定义变量名。
这就是编译器的工作方式吗?这有名字吗?
我正试图尽可能多地找出在重新定义变量的情况下编译器重新定义变量的过程。
如果它有所帮助,我相信这将在编译的预处理阶段完成,我相信它与宏观扩展类似。
编辑:我在这个问题上添加了更多的背景信息。
答案 0 :(得分:3)
你的预感是正确的,许多现代编译器使用流分析来重命名变量,以便每个变量都是唯一的。生成的表单称为“单一静态分配”或简称SSA。
输入:
B = 2
B = B + 5
B = J + B
输出:
B1 = 2
B2 = B1 + 5
B3 = J + B2
使用分支和循环还有其他部分,例如:
输入:
if X < 5
B = Y + Z
else
B = 2
B = B + 1
输出:
if X < 5:
B1 = Y + Z
else
B2 = 2
B3 = phi(B1, B2)
B4 = B3 + 1
“phi”功能选择其输入中的任何一个。
这不是在预处理期间完成的,它是在代码编译成某些IR(通常由基本块组成)之后完成的。它与宏观扩张并不相似。
答案 1 :(得分:3)
您所描述的内容已被形式化为静态单一分配(SSA)表单。它比“在赋值时重命名变量”更具侵入性,因为你必须知道在控制流面前读取的“当前”变量,例如,如果你重写它:
if (a) x = 0;
else x = 1;
print(x);
进入这个,你必须插入一个所谓的phi节点,在print
选择正确的值:
if (a) x0 = 0;
else x1 = 1;
print(<which x?>)
通常,IR内置SSA,因此代码在转换为IR(或之后不久)时转换为SSA。宏扩展在此之前发生很久,通常在令牌流或AST上,具体取决于宏的强大程度。
但请注意,这绝不是强制性的。它对某些优化很有用,但不是必需的(有些优化根本没有好处)。您可以使用可变变量执行相同的优化(并且许多具有SSA的编译器IR至少将堆保留为非SSA),它不太方便且可能更昂贵。例如,在传播常量时,您必须确保常量和正在替换的用户之间没有任何其他赋值,但是如果没有SSA,您可以很容易地检查它。