根本原因是物理寄存器不足导致虚拟寄存器溢出(spill)到栈内存,进而插入非原逻辑所需的load/store指令;spill与reload是额外访存的直接来源。

LLVM寄存器分配产生额外访存,根本原因是物理寄存器数量不足时,必须将部分虚拟寄存器“溢出”(spill)到栈内存中,后续再通过 load 和 store 指令来回搬移数据——这些指令就是额外访存的直接来源。
spill 与 reload 是额外访存的直接源头
当寄存器分配器发现当前活跃的虚拟寄存器数超过可用物理寄存器数时,它会选一个“代价最低”的虚拟寄存器执行 spill:即在定义点后插入 store,在下次使用前插入 load。这组配对指令不是原程序逻辑所需,纯粹由寄存器压力引发。
- spill 不是写入任意内存地址,而是写入函数栈帧中由
FrameIndex标识的栈槽(stack slot),该槽在 MachineFunction 初始化阶段分配 - reload 并非总能省略:即使某虚拟寄存器只被 spill 一次,只要它在多个基本块中被使用,就可能触发多次 reload
- LLVM 的
Spillerpass(如InlineSpiller或LocalSpiller)负责生成这些指令,其插入位置直接影响 cache 局部性和指令级并行度
不同分配器的 spill 行为差异显著
溢出频率和 reload 开销高度依赖所选分配器策略,而非仅由代码本身决定:
-
Fast Register Allocator几乎不尝试合并(coalescing),也不做活跃区间切分(splitting),导致更早、更频繁地 spill —— 尤其在含大量 PHI 或 SSA 形式复杂的函数中 -
Greedy Register Allocator(当前默认)会在 spill 前尝试切分活跃区间,但切分本身会引入新定义点,可能催生更多 reload;若切分失败,仍 fallback 到 spill -
Basic Register Allocator使用线性扫描,在变量生命周期交错密集时容易误判“最远使用点”,把本可保留的寄存器提前 spill
alias 分析缺失会放大访存开销
LLVM 在寄存器分配阶段不进行跨指令的 memory alias 分析,因此无法判断两个 spill 槽是否实际互斥。结果是:
- 即使两个虚拟寄存器绝不会同时活跃,也可能被分配到不同栈槽,浪费栈空间并增加 stack pointer 调整开销
- 某些 backend(如 AMDGPU)启用
StackSlotColoringpass 来复用栈槽,但该 pass 依赖准确的活跃区间和 alias 信息,而标准 x86/aarch64 pipeline 中常被跳过 - triton 编译器在 GPU 上显式做
alias分析以复用 shared memory,但 LLVM 主线对通用 CPU 的栈 slot 复用支持仍较弱
真正难处理的不是“有没有 spill”,而是 spill 后 reload 的时机是否破坏了关键路径——比如在一个 tight loop 的循环体内插入 reload,会让原本可驻留寄存器的变量反复进出内存,这种访存模式极难被硬件预取器识别。

















