Skip to content

第7章:指令选择、调度与寄存器分配

从 IR 到目标指令

指令选择把 IR 运算覆盖为目标指令。有些机器能用一条寻址指令完成缩放加法,有些需要多条操作。选择需要同时考虑合法操作数、立即数范围、延迟和代码体积。

局部最低指令数不一定带来最低运行时间。复杂指令可能延迟高、占用稀缺端口,还可能增加寄存器压力。

调度与依赖

指令调度在不违反依赖和可见行为的前提下调整顺序,使独立工作覆盖等待。依赖图包含真实数据依赖、内存依赖和必要的控制约束。

load 后紧接使用可能停顿,若中间插入一个独立加法就能利用该周期。但若 load 可能抛异常,移动其他有副作用指令越过它仍要符合异常语义。

寄存器分配

虚拟寄存器数量通常多于物理寄存器。两个值若同时活跃就可能需要不同位置,用干涉图表示后,可将分配近似看作图着色:颜色对应可用物理寄存器。

实际机器还有固定寄存器、成对寄存器、调用约定等约束,因此不是无约束图着色的直接复制。线性扫描则按活跃区间处理,通常编译速度较快,适合一些即时编译场景。

溢出与重写

寄存器不够时,把部分值存到栈槽,使用前加载、修改后保存,称为 spill。插入这些指令会改变活跃区间和调度,因此分配不是一次简单编号就结束。

假设 a,b,c 同时活跃,机器只有两个可用寄存器。若 c 很少使用而 a,b 在循环内频繁访问,溢出 c 可能更便宜。只按变量名字或定义先后选择,没有考虑动态执行成本。

调用边界

跨函数调用仍需使用的值,要根据调用者保存和被调用者保存规则处理。将其放在调用会破坏的寄存器中而不保存,会得到偶发的错误结果。

栈帧还要满足对齐、返回地址、局部对象和溢出槽布局。后端正确性不仅是算术指令选对,还包括整个执行约定一致。

练习

  1. 给出三个值两两干涉、仅两个物理寄存器时的分配与溢出方案。
  2. 为一段含 load、独立计算和使用 load 结果的指令排一个合法顺序。
  3. 展开循环为什么可能一方面减少分支,另一方面增加 spill?

上次更新: