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
相关推荐
劈星斩月3 天前
从“画图”到“算数”:GPU如何站上AI时代的C位
人工智能·gpu
逻极3 天前
Windows 平台 Ollama AMD GPU 一键编译指南:基于 ROCm 7.1 的自动化实战
人工智能·windows·stm32·自动化·gpu·amd·ollama
Luchang-Li5 天前
GPU传输带宽等信息监控nvidia-smi
人工智能·gpu·监控·性能·带宽
插件开发7 天前
CUDA11-VS2015安装-工具链测试-Helloworld程序
c++·gpu·cuda
DigitalOcean7 天前
微调后的 LLM 如何部署到生产环境?从GPU 推理端点的搭建、测试与上线全流程
llm·gpu
ALINX技术博客9 天前
【黑金云课堂】FPGA技术教程Linux开发:摄像头GPU渲染显示/Qt OpenGLES使用
linux·qt·fpga开发·gpu
模型时代11 天前
GPU之外,立讯精密想吃下AI数据中心的“连接生意“
gpu·数据中心·立讯精密
IC修真院12 天前
高赞问题:NPU可不可以代替GPU?
gpu·ic设计·芯片·微电子·数字ic·npu
黑暗森林观察者13 天前
AMD 送了块顶级 GPU 给黑客,他能打穿英伟达 20 年的软件帝国吗?
gpu
派勤电子15 天前
2026 支持 FPGA 工控机 AI 加速应用场景详解
gpu·fpga·npu·工控机·ai工控机·fpga工控机·工业级工控机