编译器:针对复杂分支/跳转的寄存器分配

时间:2015-05-14 04:28:44

标签: compiler-construction compiler-theory

我对优化器及其工作原理表示了兴趣,特别是在寄存器分配方面。我有一些编写高级解释器的背景,这些解释器无需生成有效的机器代码,因此围绕编译器构造的部分与解析,构建AST等相关,对我来说相当简单。

作为一个学习项目,我一直在尝试使用玩具编译器,它只比机器级略高,主要区别在于它适用于变量而不是寄存器。

我很困惑的是低级优化器部分,特别是关于来自IR的寄存器分配以及如何受分支/跳转影响,即使使用最基本的启发式算法(不包括SSA和SSA等高级主题) phi节点。

基本示例:

a = ...
b = ...
c = ...
d = ...
e = ...
f = ...
g = ...

jump_if x == 1, section1
jump_if x == 2, section2
jump_if x == 3, section3
etc

a = a + b - c * 2
jump end

section1:
; all kinds of stuff happens here with some of the above variables
jump end

section2:
; all kinds of stuff happens here with some of the above variables
jump end

section3:
; all kinds of stuff happens here with some of the above variables
jump section1 ; tricky branch!!!

end:

也许我们可以抛出循环逻辑和各种其他分支来使这个例子更加复杂。

我不明白的是,如果我们将它们全部一起考虑,而不是单独考虑每条路径,那么所有这些分支路径都可能使所有变量都超过“实时”。

我似乎缺少的是某种基于堆栈的块结构,因此我们可以使用嵌套块,其中寄存器分配可以考虑最内层块及其外部块引用的变量,并分别执行该寄存器分配启发式在每个块/分支路径上。

在更高级别的基于块的IR中,推断分支路径似乎要容易得多,因为分支将被限制在一个块内,但是低级IR只是在机器级别之上略微抽象而已。有完全无约束的分支,你可以在那里跳/分支?

我见过的大多数IR示例都是机器代码的低级抽象,因此它们似乎经常让我们对我们如何以可能真正使用的方式进行分支(例如跳转表)变得非常混乱很难推断出这样的块/部分/路径。

人们通常如何处理这个问题?是否有一个算法和一个干净的组织/代码设计可以分解所有可能的分支路径给定这样的低级代码,允许这样灵活的分支/跳转?

1 个答案:

答案 0 :(得分:2)

注册分配是一个长期存在的问题。该领域已有许多研究论文。该领域最近流行的算法是SSA和线性扫描寄存器分配。

静态单一分配表(SSA)已经获得了一些流行度以帮助注册分配。我们的想法是将程序逻辑转换为一个只能分配一次的变量,并且每个变量在使用之前都需要分配。通常假设可以使用无限数量的寄存器。

一旦程序转换为SSA格式,就可以更容易地转换为寄存器分配。这可以通过多项式时间中的图着色来完成(这样做的流行编译器是LLVM)。这是一个非常复杂的话题。我建议你阅读这方面的几篇论文。

    Ron Cytron等人的
  1. Efficiently Computing Static Single Assignment Form and the Control Dependence Graph。他是SSA领域的先驱之一。

  2. Marc M. Brandis等人的
  3. Single-Pass Generation of Static Single Assignment Form for Structured Languages

  4. Keith D. Cooper等人
  5. A Simple, Fast Dominance Algorithm

  6. 如果您不想将SSA作为中间步骤处理,您可能需要查看Massimiliano Poletto的Linear Scan Register Allocation。这种贪心算法用于许多基于非LLVM的编译器,包括v8和JVM。