【问题标题】:Register Allocation in Compilers编译器中的寄存器分配
【发布时间】:2015-08-11 08:01:31
【问题描述】:

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

【问题讨论】:

    标签: compiler-construction code-generation cpu-registers


    【解决方案1】:

    Hardware registers 很昂贵(在芯片面积和寻址它们所需的指令位数方面),因此通常数量很少。 Spilling 发生在给定程序点的 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 槽,所有使用都替换为加载。一些编译器还可以选择执行寄存器到寄存器溢出,这意味着将值存储到不同类的寄存器并从不同类的寄存器中加载。例如,在 x86-64 上,编译器可能会将值从通用寄存器(如 rax)溢出到 SIMD 寄存器(如 xmm0)中。这有利于减少内存流量。

    作为溢出的替代方法,编译器可能会执行实时范围分割。这涉及将生存范围分解成更小的部分 - 仅在分割点插入负载和存储 - 以便对原本无法着色的干涉图进行着色。

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

    【讨论】:

    • 很棒的解释,所以编译器也使用启发式方法来估计寄存器溢出?太棒了,你能指出一些关于那里使用的启发式算法的资源吗?谢谢,并赞成。
    • 我在这方面找不到任何可访问的资源。 Cooper & Torczon 建议可以使用循环嵌套作为估计。基本上,我们假设每个循环执行 N 次(我们可以选择 N=10)。因此,循环中引用的变量的成本是存储和加载它所需的内存操作次数的 N 倍。类似地,在双重嵌套循环中引用的变量的成本是操作次数的 N^2 倍。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-09
    相关资源
    最近更新 更多