AMDGPU后端RegMask使用介绍
摘要 :本文系统介绍了 LLVM MI 层 RegMask 在 AMDGPU 后端中的核心作用与实现机制。RegMask 作为 MachineOperand 的特殊操作数,通过位图编码描述指令执行后哪些物理寄存器被保留(preserved)而非被破坏(clobbered)。在 AMDGPU 中,RegMask 主要服务于 call 边界上的寄存器活跃性分析和分配约束,其来源是 AMDGPUCallingConv.td 中定义的 calling convention 和 CSR(Callee-Saved Registers)。文章详细阐述了 RegMask 与 PEI/CSR 的分工、AMDGPU 如何根据调用约定选择不同的 mask、如何在lowering 阶段将 mask 挂载到 call 指令,以及后续寄存器分配、LiveIntervals、ShrinkWrap、Tail Call 等 pass 如何消费 RegMask。
适用范围 :
llvm/lib/Target/AMDGPU(GCN/SI+)
主要 ABI 基准 :CSR_AMDGPU_RegMask,GFX90A+ 对应CSR_AMDGPU_GFX90AInsts_RegMask
本文说明 LLVM MI 层 RegMask 的语义,以及 AMDGPU 后端如何从 calling convention 生成、挂载并消费 RegMask。它主要服务于 call 边界上的寄存器活跃性和分配约束。
1. RegMask 是什么
RegMask 是 MachineOperand 的一种特殊操作数,不是普通寄存器 use/def。它的载体是 const uint32_t * 位图,按物理寄存器枚举 id 编码,用来描述一条指令执行后哪些物理寄存器仍然保持原值。
主要用于描述:
这条 MachineInstr 执行后,哪些物理寄存器仍然被保留,哪些会被 clobber(破坏)。
LLVM 里的约定位值语义如下:
| 位值 | 含义 |
|---|---|
1 |
Preserved:该物理寄存器在指令后仍保持指令前的值 |
0 |
Clobbered:该物理寄存器可能被指令 clobber(破坏) |
因此,RegMask 不是"被 clobber 的集合",而是"被 preserve 的集合"。LLVM 判断某个物理寄存器是否被 mask clobber 时,会反向检查对应 bit:
cpp
static bool clobbersPhysReg(const uint32_t *RegMask, MCRegister PhysReg) {
return !(RegMask[PhysReg.id() / 32] & (1u << (PhysReg.id() % 32)));
}
实际 pass 中通常通过 MachineOperand 查询:
cpp
if (MO.isRegMask() && MO.clobbersPhysReg(PhysReg)) {
// PhysReg may be clobbered by this instruction.
}
RegMask 最常见的使用者是 call 指令。call 通常不会显式列出所有被破坏的物理寄存器,而是携带一个 regmask operand,一次性表达"哪些寄存器跨 call 仍可认为有效"。
2. 与 PEI / CSR 的分工
RegMask 和 callee-saved register(CSR)来自同一套 ABI 信息,但使用阶段不同:
| 机制 | 阶段 | 描述的问题 |
|---|---|---|
RegMask |
call 指令所在的 caller 侧,RegAlloc 及相关 MI 分析阶段 | 这个 call 之后哪些物理寄存器仍保持原值 |
getCalleeSavedRegs / PEI |
当前函数自身的 prologue / epilogue 插入阶段 | 当前函数若使用了 callee-saved 寄存器,需要保存和恢复哪些寄存器 |
简单说:
text
RegMask = call 边界上的 caller 约束
PEI = 被调函数体内的 callee save/restore 实现
二者都依赖 ABI,但不能混为一谈。RegMask 决定 live range 能否跨 call 放在某个物理寄存器上;PEI 决定当前函数入口/出口是否要生成 spill/restore。
3. AMDGPU 的 CSR 和 RegMask 来源
AMDGPU 的 calling convention、CSR 和部分专用 mask 定义在 AMDGPUCallingConv.td。
典型定义如下:
cpp
def CSR_AMDGPU_VGPRs : CalleeSavedRegs<
(add (sequence "VGPR%u", 40, 47),
...
(sequence "VGPR%u", 248, 255))
>;
def CSR_AMDGPU_SGPRs : CalleeSavedRegs<
(add (sequence "SGPR%u", 30, 39),
...
(sequence "SGPR%u", 96, 105))
>;
def CSR_AMDGPU : CalleeSavedRegs<
(add CSR_AMDGPU_VGPRs, CSR_AMDGPU_SGPRs)
>;
TableGen 会为这些 CalleeSavedRegs 生成两类重要产物:
CSR_AMDGPU_SaveList: 用于 frame lowering,告诉 prologue/epilogue 要保存哪些 callee-saved reg。CSR_AMDGPU_RegMask: 用于 call MI 的 RegMask 操作数,告诉寄存器分配和 liveness 哪些 reg 经过 call 后仍然有效。
AMDGPU 还定义了一些只为获得 mask 的集合:
cpp
// Trivial class to denote when a def is used only to get a RegMask, i.e.
// SaveList is ignored and the def is not used as part of any calling
// convention.
class RegMask<dag mask> : CalleeSavedRegs<mask>;
def AMDGPU_AllVGPRs : RegMask<
(sequence "VGPR%u", 0, 255)
>;
def AMDGPU_AllAGPRs : RegMask<
(sequence "AGPR%u", 0, 255)
>;
这些 RegMask<...> 定义的重点不是参与普通 calling convention 的 save list,而是生成对应的 *_RegMask,供目标后端在特殊场景下直接使用。
4. AMDGPU 如何选择RegMask(call-preserved mask)
核心入口是 SIRegisterInfo::getCallPreservedMask,按 calling convention 返回不同 mask:
cpp
const uint32_t *SIRegisterInfo::getCallPreservedMask(const MachineFunction &MF,
CallingConv::ID CC) const {
switch (CC) {
case CallingConv::C:
case CallingConv::Fast:
case CallingConv::Cold:
return ST.hasGFX90AInsts() ? CSR_AMDGPU_GFX90AInsts_RegMask
: CSR_AMDGPU_RegMask;
case CallingConv::AMDGPU_Gfx:
return ST.hasGFX90AInsts() ? CSR_AMDGPU_SI_Gfx_GFX90AInsts_RegMask
: CSR_AMDGPU_SI_Gfx_RegMask;
case CallingConv::AMDGPU_CS_Chain:
case CallingConv::AMDGPU_CS_ChainPreserve:
return AMDGPU_AllVGPRs_RegMask;
default:
return nullptr;
}
}
常见 calling convention 和 mask 对应关系:
| CallingConv | RegMask |
|---|---|
C / Fast / Cold |
CSR_AMDGPU_RegMask,GFX90A+ 使用 CSR_AMDGPU_GFX90AInsts_RegMask |
AMDGPU_Gfx |
CSR_AMDGPU_SI_Gfx_RegMask,GFX90A+ 使用 CSR_AMDGPU_SI_Gfx_GFX90AInsts_RegMask |
AMDGPU_CS_Chain / AMDGPU_CS_ChainPreserve |
AMDGPU_AllVGPRs_RegMask |
| 其它未支持 call-preserved mask 的 CC | nullptr |
GFX90A 变体的差异主要来自 AGPR:在支持 GFX90A 指令的子目标上,AMDGPU 需要把 AGPR preservation 纳入 CSR / RegMask 体系。
AMDGPU_CS_Chain / AMDGPU_CS_ChainPreserve 的注释说明这些调用不会正常返回,因此后端可以使用特殊 mask 简化后续分析。
需要注意的是:getCalleeSavedRegs() 和 getCallPreservedMask() 是配套但用途不同的接口。
- getCalleeSavedRegs() 服务于当前函数自身保存/恢复:
cpp
const MCPhysReg *SIRegisterInfo::getCalleeSavedRegs(
const MachineFunction *MF) const {
CallingConv::ID CC = MF->getFunction().getCallingConv();
switch (CC) {
case CallingConv::C:
case CallingConv::Fast:
case CallingConv::Cold:
return ST.hasGFX90AInsts() ? CSR_AMDGPU_GFX90AInsts_SaveList
: CSR_AMDGPU_SaveList;
...
}
}
- getCallPreservedMask() 服务于"当前函数里的一条 call 会破坏什么"。
5. RegMask 如何被放进 call 指令
在 SelectionDAG lowering call 时,AMDGPU 会把 call-preserved mask 加到 call node 的 operand 列表末尾:
cpp
// Add argument registers to the end of the list so that they are known live
// into the call.
for (auto &[Reg, Val] : RegsToPass)
Ops.push_back(DAG.getRegister(Reg, Val.getValueType()));
// Add a register mask operand representing the call-preserved registers.
const uint32_t *Mask = TRI->getCallPreservedMask(MF, CallConv);
assert(Mask && "Missing call preserved mask for calling convention");
Ops.push_back(DAG.getRegisterMask(Mask));
这里有两个不同概念:
- 参数寄存器通过
DAG.getRegister(Reg, VT)作为 call 的 live-in/use 传入。 DAG.getRegisterMask(Mask)描述 call 之后哪些物理寄存器被 preserve。
之后 InstrEmitter 会把 RegisterMaskSDNode 转成 MIR 中的 MachineOperand::RegMask。因此在 MIR 层,一条 call 通常长成:
text
CALL ..., implicit $arg-regs, regmask $csr_amdgpu_regmask
具体打印格式会因目标指令和 MIR printer 版本不同而变化,但语义是一致的:regmask 是 call 的隐式 clobber/preserve 描述。
6. 后续 Pass 如何消费 RegMask
6.1 寄存器分配/VirtRegMap
VirtRegMap 会扫描所有 MI operands,如果遇到 RegMask,就把 mask 中 clobber 的物理寄存器记录到 MachineRegisterInfo,告诉 MachineRegisterInfo 哪些物理寄存器被 mask clobber:
cpp
for (MachineOperand &MO : MI.operands()) {
// Make sure MRI knows about registers clobbered by regmasks.
if (MO.isRegMask())
MRI->addPhysRegsUsedFromRegMask(MO.getRegMask());
}
这一步保证物理寄存器使用信息能看到 call 的隐式 clobber,而不是只依赖显式 def。
6.2 LiveIntervals/RegAlloc
活跃区间分析会记录 regmask 所在位置。寄存器分配器检查某个 live range 是否能跨过 call 时,会判断候选物理寄存器是否被该 call 的 regmask clobber。
直观规则是:
text
不能把跨 call 仍需保持旧值的 live range 分配到 call-clobbered physreg 上。
可以写成:
text
MustSpillOrSplit = { R | LiveAcrossCall && clobbersPhysReg(RegMask, R) }
如果一个虚拟寄存器 live range 跨过 call,而候选物理寄存器在该 call 的 RegMask 中 bit 为 0,分配器需要选择别的寄存器,或者 split/spill/reload。
6.3 ShrinkWrap/CSR 分析
ShrinkWrap 也会把 RegMask 当成隐式 clobber 来源:
cpp
if (MO.isRegMask()) {
// Check if this regmask clobbers any of the CSRs.
for (unsigned Reg : getCurrentCSRs(RS)) {
if (MO.clobbersPhysReg(Reg)) {
UseOrDefCSR = true;
break;
}
}
}
这说明 RegMask 不只是服务于寄存器分配,也会影响 callee-saved 相关分析。
6.4 Tail Call 判断
AMDGPU 判断 tail call 是否安全时,会比较 caller 和 callee 的 preserved mask:
cpp
const uint32_t *CalleePreserved = TRI->getCallPreservedMask(MF, CalleeCC);
if (!TRI->regmaskSubsetEqual(CallerPreserved, CalleePreserved))
return false;
含义是:
text
callee 必须 preserve caller 期望 preserve 的所有寄存器。
否则普通 call 不能安全地变成 tail call,因为 tail call 会复用当前函数返回路径,不能破坏 caller ABI 对寄存器 preservation 的要求。
7. 写 AMDGPU MI Pass 时的注意点
如果你在写 MI pass,例如 copy propagation、peephole、liveness 相关 pass,需要特别注意以下问题。
7.1 不要把 RegMask 当普通寄存器 operand
RegMask operand 不是 MO.isReg(),不能调用 MO.getReg():
cpp
if (MO.isRegMask()) {
const uint32_t *Mask = MO.getRegMask();
...
}
如果 pass 只处理普通寄存器 use/def,应明确跳过:
cpp
if (!MO.isReg())
continue;
7.2 RA 后移动指令时要尊重 RegMask
如果一个 pass 在寄存器分配后移动 copy、删除 copy 或跨 call 调整指令顺序,不能只看显式 def。call 的 clobber 信息可能只存在于 RegMask 中。
例如:
text
%x = COPY $sgpr40
CALL ..., regmask CSR_AMDGPU_RegMask
use %x
如果 %x 最终依赖的物理寄存器被 call 的 RegMask clobber,那么跨 call 保持这个值就是非法的。正常寄存器分配会避免这种情况;但 RA 后的自定义 MI pass 如果移动指令,就必须重新验证这种约束。
7.3 检查物理寄存器 clobber 时要考虑别名
AMDGPU 有 SGPR/VGPR/AGPR 以及多寄存器组合。判断一个物理寄存器是否被 clobber 时,不应手写整数范围判断,优先使用 LLVM 提供的接口:
cpp
if (MO.isRegMask() && MO.clobbersPhysReg(PhysReg))
...
这样可以让 LLVM 的寄存器别名和 TableGen 生成信息参与判断。
8. 关键链路总结
AMDGPU 中 RegMask 的链路如下:
- AMDGPUCallingConv.td 定义 CSR_AMDGPU*
- TableGen 生成 CSR_AMDGPU*_RegMask
- SIRegisterInfo::getCallPreservedMask() 按 calling convention 返回 mask
- SIISelLowering lowering call 时加入 DAG.getRegisterMask(Mask)
- MI 层 call 指令携带 MachineOperand::RegMask
- RegAlloc、ShrinkWrap、TailCall、调度/liveness 等 pass 用它判断 physreg 是否被 call clobber
text
AMDGPUCallingConv.td
def CSR_AMDGPU : CalleeSavedRegs<...>
↓ TableGen
CSR_AMDGPU_SaveList / CSR_AMDGPU_RegMask
↓
SIRegisterInfo::getCallPreservedMask
↓
SIISelLowering::LowerCall
DAG.getRegisterMask(Mask)
↓
InstrEmitter
MachineOperand::RegMask
↓
LiveIntervals / VirtRegMap / RegAlloc / ShrinkWrap / TailCall
一句话概括:
text
RegMask是LLVM后端在MI层表达"这条指令隐式破坏哪些物理寄存器"的压缩机制;
在AMDGPU中,它主要由calling convention 的CalleeSavedRegs 生成,并随 call 指令传播。
9. 关键源文件
| 文件 | 内容 |
|---|---|
llvm/lib/Target/AMDGPU/AMDGPUCallingConv.td |
定义 calling convention、CSR、专用 RegMask<...> |
llvm/lib/Target/AMDGPU/SIRegisterInfo.cpp |
getCalleeSavedRegs、getCallPreservedMask |
llvm/lib/Target/AMDGPU/SIISelLowering.cpp |
SelectionDAG LowerCall 中挂载 DAG.getRegisterMask |
llvm/lib/Target/AMDGPU/AMDGPUCallLowering.cpp |
GlobalISel 路径中比较 caller/callee preserved mask |
llvm/lib/CodeGen/VirtRegMap.cpp |
扫描 MI 上的 RegMask 并更新 MRI 物理寄存器使用信息 |
llvm/lib/CodeGen/ShrinkWrap.cpp |
检查 RegMask 是否 clobber CSR |