编译器中的寄存器分配

Register Allocation in Compilers

编译器后端必须将变量分配到内存或寄存器的代码生成的寄存器分配阶段出现的寄存器溢出或溢出代码是什么意思?。

Hardware registers are expensive (both in terms of die area and the number of instruction bits required to address them), and therefore generally quite few in number. Spilling occurs when the number of live variables(或者更准确地说,生存范围的数量)在给定程序点超过了可用寄存器的数量。

考虑以下示例程序在具有两个硬件寄存器的假想机器中执行。假设编译器除了寄存器分配之外不执行任何优化。

a := 1   ; liveout: {a}
b := 2   ; liveout: {a,b}
c := 3   ; liveout: {a,b,c}
d := a + b + c

由于ab被用于d的定义中,所以它们的生命范围跨越了c的定义。但是由于机器只有两个寄存器,所以在定义d时不可能把abc都保存在一个寄存器中。至少要洒出一个。

在最简单的溢出形式中,溢出变量的所有定义都替换为存储到 stack 槽,所有使用都替换为加载。一些编译器还可以选择执行寄存器到寄存器溢出,这意味着值存储到不同 class 的寄存器并从中加载。例如,在 x86-64 上,编译器可以将值从 rax 这样的通用寄存器溢出到像 xmm0 这样的 SIMD 寄存器中。这有减少内存流量的好处。

作为溢出的替代方法,编译器可以改为执行活动范围拆分。这涉及将活动范围分成更小的部分 - 仅在分割点插入加载和存储 - 以启用否则无法着色的干涉图的着色。

正如您可能想象的那样,选择溢出哪个变量对结果代码的性能有重大影响。任意溢出在紧密循环中使用或定义的变量可能会产生灾难性的后果。因此,一个好的编译器可能会应用某种形式的启发式方法来估计在做出选择之前溢出每个变量的成本。