SlotIndex机制--以AMDGPU为例

SlotIndex机制--以AMDGPU为例

适用范围 :LLVM AMDGPU(GCN/SI+)后端的 Machine IR、LiveIntervals、寄存器分配前优化与调度分析

通用实现llvm/include/llvm/CodeGen/SlotIndexes.hllvm/lib/CodeGen/SlotIndexes.cppllvm/include/llvm/CodeGen/LiveInterval.h


1. SlotIndex 解决什么问题

LLVM 后端进入 Machine IR(MIR) 后,很多分析不能只用"第几条 MachineInstr"描述程序位置。寄存器活跃性需要回答更细的问题:

text 复制代码
某个寄存器值在一条指令之前、指令内部 early-clobber 时刻、普通 use/def 时刻、指令之后是否仍然 live?

SlotIndex 就是 MIR 层的程序位置坐标。它给每条非 debug/pseudo 的 MachineInstr 分配一个可比较的 index,并在同一条指令内部继续划分多个 slot。LiveInterval / LiveRange 使用这些位置作为区间端点,从而表达寄存器值的存活范围。

对 AMDGPU 来说,SlotIndex 尤其重要:

  • VGPR、SGPR、AGPR live range 长度直接影响寄存器压力、spill 和 occupancy。
  • EXECVCC、SCC 等特殊寄存器经常参与控制流和 predication 优化,必须精确判断 reaching def。
  • WQM/WWM、waterfall loop、divergent 控制流会让活跃性变得比普通 CPU 后端更敏感。
  • MFMA(CDNA GPU 中的矩阵融合乘加)等指令可能带 early-clobber 约束,同一条 MI 内部的 slot 先后关系会影响寄存器冲突判断。
text 复制代码
SlotIndex 是 LLVM 后端寄存器活跃性分析的时间轴;AMDGPU 的 pre-RA 优化、寄存器压力分析和调度逻辑大量依赖它。

2. SlotIndex 内部结构

SlotIndex 是对 IndexListEntry 的轻量包装。IndexListEntry 保存:

  • 对应的 MachineInstr *
  • 该指令的基础整数 index。

SlotIndex 自身还携带 2 bit 的 slot 编号。通用定义位于 llvm/include/llvm/CodeGen/SlotIndexes.h

cpp 复制代码
/// SlotIndex - An opaque wrapper around machine indexes.
class SlotIndex {
   friend class SlotIndexes;
   enum Slot {
     /// Basic block boundary.  Used for live ranges entering and leaving a
     /// block without being live in the layout neighbor.  Also used as the
     /// def slot of PHI-defs.
     Slot_Block,

     /// Early-clobber register use/def slot.
     Slot_EarlyClobber,

     /// Normal register use/def slot.
     Slot_Register,

     /// Dead def kill point.
     Slot_Dead,

     Slot_Count
   };
}

四种 slot 的语义如下:

Slot 打印后缀 典型含义
Slot_Block B 基本块边界;也用于 PHI-def
Slot_EarlyClobber e early-clobber use/def
Slot_Register r 普通寄存器 use/def
Slot_Dead d dead def 的 kill 点

SlotIndex::print 会把 slot 打印为 "Berd" 中的一个字符。因此如果某条 MI 的 base index 是 32,同一条指令上的几个位置可以表示为:

text 复制代码
32B  指令边界 / block slot
32e  early-clobber slot
32r  普通 register slot
32d  dead slot

这也是为什么 LiveInterval dump 中经常能看到类似 [32r, 80r)96B144d 的位置。


3. SlotIndexes 如何给 MachineInstr 编号

SlotIndexes 是 MachineFunction 级 analysis/pass。它遍历 MachineFunction 中的所有 MBB 和 MI,并维护几类映射:

数据结构 作用
mi2iMap MachineInstr * 到 base SlotIndex 的映射
MBBRanges 每个 MBB 的 [start, end) index 范围
idx2MBBMap 从 index 反查所在 MBB
indexList 所有 index entry 的有序链表

初始化时,每条有效 MI 之间默认留出 SlotIndex::InstrDist 的间距:

cpp 复制代码
enum {
  InstrDist = 4 * Slot_Count
};

Slot_Count 是 4,所以默认间距是 16。这样后续插入新指令时,很多情况下可以在相邻 index 的空隙里局部插入,避免频繁全函数重编号。

遍历编号时会跳过 debug/pseudo 指令:

cpp 复制代码
for (MachineInstr &MI : MBB) {
  if (MI.isDebugOrPseudoInstr())
    continue;

  indexList.push_back(*createEntry(&MI, index += SlotIndex::InstrDist));
  mi2iMap.insert(std::make_pair(
      &MI, SlotIndex(&indexList.back(), SlotIndex::Slot_Block)));
}

常用接口包括:

接口 作用
getInstructionIndex(MI) 获得某条 MI 的 base index
getRegSlot() 转到普通 register slot
getRegSlot(true) 转到 early-clobber slot
getBaseIndex() 转到 block/base slot
getDeadSlot() 转到 dead slot
getMBBStartIdx(MBB) 获得 MBB 起始位置
getMBBEndIdx(MBB) 获得 MBB 结束位置

4. 与LiveInterval 有什么关系

SlotIndex 本身不保存寄存器活跃信息,它只是程序位置。真正的活跃性由 LiveInterval / LiveRange 表示

text 复制代码
LiveInterval = 若干个基于 SlotIndex 的半开区间 [start, end)

例如:

text 复制代码
%42:vgpr_32 = [32r, 80r), [112B, 160d)

含义是:

  • %4232r 开始 live。
  • 80r 前结束第一段 live range。
  • 112B 重新变为 live,持续到 160d

常见查询模式:

cpp 复制代码
SlotIndex SI = LIS->getInstructionIndex(MI).getRegSlot();
LiveInterval &LI = LIS->getInterval(Reg);

if (LI.liveAt(SI)) {
  // Reg is live at this program point.
}

对于 AMDGPU,LiveInterval 还经常带 subrange。VGPR/AGPR tuple、subreg 或 lane mask 相关优化会用 SlotIndexLaneBitmask 判断某个 lane 在某个程序点是否 live。


5. AMDGPU示例1:判断两条指令之间是否有重新定义def

SIOptimizeExecMaskingPreRA.cpp 中有一个典型 pre-RA 优化,会把如下序列:

text 复制代码
%sel = V_CNDMASK_B32_e64 0, 1, %cc
%cmp = V_CMP_NE_U32 1, %sel
$vcc = S_AND_B64 $exec, %cmp
S_CBRANCH_VCC[N]Z

折叠成:

text 复制代码
$vcc = S_ANDN2_B64 $exec, %cc
S_CBRANCH_VCC[N]Z

这个变换必须保证 %ccV_CNDMASKS_AND_B64 之间没有被重新定义。否则,折叠后的 S_ANDN2_B64 会使用错误的值。

AMDGPU 后端用 SlotIndex 精确查询这两个程序点上的 live value:

cpp 复制代码
static bool isDefBetween(const LiveRange &LR, SlotIndex AndIdx,
                         SlotIndex SelIdx) {
  LiveQueryResult AndLRQ = LR.Query(AndIdx);
  return (!AndLRQ.isKill() && AndLRQ.valueIn() != LR.Query(SelIdx).valueOut());
}

SlotIndex AndIdx = LIS->getInstructionIndex(And).getRegSlot();
SlotIndex SelIdx = LIS->getInstructionIndex(Sel).getRegSlot();

这里的关键点:

  • getInstructionIndex(And) 取得 S_AND_B64 的 base index。
  • .getRegSlot() 转到普通寄存器 use/def 时刻。
  • LR.Query(AndIdx) 查询 And 位置看到的 live value。
  • LR.Query(SelIdx) 查询 Sel 位置输出的 live value。
  • 两者不是同一个 value 时,说明中间存在影响语义的 def,不能折叠。

这类代码体现了 SlotIndex 的核心价值:优化不是简单比较指令前后顺序,而是在 LiveRange 时间轴上比较具体程序点的值流动。


6. AMDGPU示例2:GCN 寄存器压力分析

GCNRegPressure.cpp 会在寄存器压力分析中查找某个虚拟寄存器在两个程序点之间是否有 use:

cpp 复制代码
SlotIndex InstSlot = LIS->getInstructionIndex(*MI).getRegSlot();
bool InRange = Upward ? (InstSlot > PriorUseIdx && InstSlot <= NextUseIdx)
                      : (InstSlot >= PriorUseIdx && InstSlot < NextUseIdx);

这里 SlotIndex 让"两个 use 之间"变成一个可比较的半开范围。对于 AMDGPU,这不仅影响普通 live range,还影响 lane mask:

cpp 复制代码
if ((S.LaneMask & LaneMaskFilter).any() && S.liveAt(SI))
  LiveMask |= S.LaneMask;

也就是说,在某个 SlotIndex 上,一个 VGPR 的部分 lane 可能 live,另一部分 lane 可能不 live。GCN 调度、寄存器压力估算、rematerialization 等逻辑都需要这种精度。


7. AMDGPU示例3:early-clobber def 的 slot 修正

AMDGPU部分指令,特别是某些 MFMA 相关指令,可能有 early-clobber def。普通 def 位于 register slot,early-clobber def 位于 early-clobber slot。

如果后端把一条 MI 转成 early-clobber 形式,LiveInterval 中对应 def 的位置也要从 r slot 改到 e slot。SIInstrInfo.cpp 中有类似处理:

cpp 复制代码
SlotIndex OldIndex = LIS->getInstructionIndex(*MIB).getRegSlot(false);
SlotIndex NewIndex = LIS->getInstructionIndex(*MIB).getRegSlot(true);

auto &LI = LIS->getInterval(Def.getReg());
auto *S = LR.find(OldIndex);
if (S != LR.end() && S->start == OldIndex) {
  S->start = NewIndex;
  S->valno->def = NewIndex;
}

这个例子说明:同一条 MachineInstr 内的 er 不是装饰信息,而是会影响寄存器干涉关系的真实程序点。如果 early-clobber def 仍被记录在普通 register slot,寄存器分配可能错误地认为某些 use/def 不冲突。


8. 插入、删除和替换 MachineInstr 时的注意点

AMDGPU 后端的很多 pass 会在 LiveIntervals 已经可用时改写 MI,例如:

  • 替换一条指令为另一条指令。
  • 删除 compare/select/and 等中间指令。
  • 插入 WQM/WWM、waterfall 或 spill/reload 相关指令。
  • 调整 early-clobber、implicit def/use 或 tied operand。

如果当前 pass 依赖或保留 LiveIntervals,修改 MI 后必须同步维护 SlotIndexes 和 LiveIntervals。常见手段包括:

操作 常用接口
替换 MI LiveIntervals::ReplaceMachineInstrInMaps
删除 MI removeIntervalremoveAllRegUnitsForPhysReg、从 maps 中移除
新增/移动 MI 修复 SlotIndexes,并重新计算相关 vreg interval
不确定如何局部维护 对受影响寄存器调用 createAndComputeVirtRegInterval

经验规则:

text 复制代码
只要 pass 在 LiveIntervals 可用之后移动、删除、替换 MachineInstr,就必须重新检查 SlotIndex 和 LiveInterval 是否仍一致。

如果没有维护好,常见后果包括:

  • Instruction not found in maps 断言。
  • LiveRange segment 起止点指向旧 MI。
  • VNInfo::def 与 segment start 不一致。
  • 寄存器分配错误判断干涉关系。
  • AMDGPU pre-RA 优化误删或错误折叠 EXEC / VCC 相关序列。

9. 调试建议

调试 AMDGPU SlotIndex / LiveInterval 问题时,通常先看 MIR 和 LiveInterval dump:

bash 复制代码
llc -mtriple=amdgcn-amd-amdhsa -mcpu=<gfx> input.ll -stop-after=<pass> -o out.mir
llc -mtriple=amdgcn-amd-amdhsa -mcpu=<gfx> input.ll -print-after-all 2> trace.log

源码中也可以临时打印:

cpp 复制代码
SlotIndex Idx = LIS->getInstructionIndex(MI);
dbgs() << "MI index: " << Idx
       << " reg slot: " << Idx.getRegSlot()
       << " dead slot: " << Idx.getDeadSlot() << '\n';

LIS->getInterval(Reg).dump();

排查时重点确认:

  • 当前 MI 是否已经存在于 SlotIndexes::mi2iMap
  • 查询的是 base slot、register slot,还是 early-clobber slot。
  • LiveRange 的 segment start/end 是否和 VNInfo::def 一致。
  • 替换或删除 MI 后,相关 vreg/phys reg interval 是否同步更新。
  • 对 physical register,是否还需要检查 regunit live range。

10. 文章总结

可以把 SlotIndex 理解为 MIR 层的细粒度时间轴:

text 复制代码
MachineInstr 顺序:
  MI0        MI1        MI2

SlotIndex:
  16B 16e 16r 16d
  32B 32e 32r 32d
  48B 48e 48r 48d

在 AMDGPU 后端中,它主要用于:

  • 表达 VGPR/SGPR/AGPR 的 live range 端点。
  • 查询 EXECVCC、SCC 和普通 vreg 的 reaching def。
  • 做 pre-RA peephole / exec masking 优化的合法性检查。
  • 估算 GCN register pressure 和 lane-level liveness。
  • 正确处理 early-clobber 指令内部的 use/def 顺序。
  • 在 MI 替换、删除、插入后维护 LiveIntervals 的一致性。

SlotIndex 不直接决定寄存器分配结果,但它是 LiveIntervals 的坐标系。坐标系一旦错,后续的 AMDGPU 活跃性、干涉判断、寄存器压力和优化合法性都会跟着出错。

相关推荐
archi-dreamer2 小时前
LiveInterval分析–以AMDGPU为例
gpu·llvm·编译器与工具链
人月神话-Lee21 小时前
【图像处理】Core Image 与 GPU 渲染管线——让滤镜飞起来
图像处理·人工智能·ios·chatgpt·ai编程·swift·gpu
AKAMAI3 天前
针对 Akamai Cloud 上的 NVIDIA RTX Pro 6000 Blackwell 进行基准测试
云计算·gpu
caodongwang3 天前
GPU Direct RDMA调研
gpu·rdma·gdr
archi-dreamer5 天前
AMDGPU后端RegMask使用介绍
gpu·llvm·编译器与工具链
kevinli5 天前
帮Apple修Bug
ios·llvm
fhqlongteng6 天前
RK3576上electron调用GPU的功能设置方法
前端·javascript·electron·gpu·rk3576
Dfreedom.9 天前
算子融合:从硬件本质到性能飞跃的深度学习优化艺术
人工智能·深度学习·gpu·gpu加速·模型加速·算子融合·模型计算
佳杰云星10 天前
如何给大模型集群选“大脑”?智算调度与管理平台 10 维选型指南(附选型评分表)
人工智能·kubernetes·大模型·云计算·gpu·算力调度·智算中心