AMDGPU后端RegMask使用介绍

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 是什么

RegMaskMachineOperand 的一种特殊操作数,不是普通寄存器 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 getCalleeSavedRegsgetCallPreservedMask
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
相关推荐
kevinli13 小时前
帮Apple修Bug
ios·llvm
fhqlongteng2 天前
RK3576上electron调用GPU的功能设置方法
前端·javascript·electron·gpu·rk3576
Dfreedom.4 天前
算子融合:从硬件本质到性能飞跃的深度学习优化艺术
人工智能·深度学习·gpu·gpu加速·模型加速·算子融合·模型计算
佳杰云星5 天前
如何给大模型集群选“大脑”?智算调度与管理平台 10 维选型指南(附选型评分表)
人工智能·kubernetes·大模型·云计算·gpu·算力调度·智算中心
archi-dreamer6 天前
AMDGPU 后端 ABI 总览
gpu·llvm·编译器与工具链
lbaihao12 天前
LLVM Cpu0 调用规则解析
开发语言·前端·python·llvm
林多14 天前
【Android】 GPU过度绘制实现原理
android·gpu·性能·实现原理·过度绘制·overdraw
lbaihao15 天前
LLVM Cpu 后端中具体的指令定义和模式匹配规则
llvm
lbaihao15 天前
LLVM 后端中 Cpu 目标机器的 SelectionDAG 节点定义
llvm