后端Pass简介——DetectDeadLanes
DetectDeadLanes
这个 Pass(“Detect Dead Lanes”) 的目的是在涉及子寄存器(subregister)的时候,跟踪虚拟寄存器中各个“lane”上的定义和使用情况,以便在后续阶段正确地识别“死定义”(dead definitions)或“未定义使用”(undefined uses)。
当寄存器被拆分成若干子寄存器或通过 COPY-like 指令(如 PHI、REG_SEQUENCE、INSERT_SUBREG、EXTRACT_SUBREG 等)传递时,普通的寄存器活性分析无法直接得知哪些子部分是真正被使用或定义的。本 Pass 通过位掩码(LaneBitmask)对每个虚拟寄存器的每个子 lane 做数据流分析,从而在 Machine IR 层标记那些确实“死掉”或“未定义”的操作数。
那么什么是 lane 呢?
“Lane”在这里指寄存器的==某个子部分(subregister)对应的位集合==。对于一个虚拟寄存器,LLVM 用一个位掩码(LaneBitmask)来表示其中哪些“lane”(即哪些子寄存器位域)已经被定义或使用。
可以理解为高 12 位,低 20 位这种情况。分别对应一个 lane。
官方示例:
%0 = some definition
%1 = IMPLICIT_DEF
%2 = REG_SEQUENCE %0, sub0, %1, sub1
%3 = EXTRACT_SUBREG %2, sub1
= use %3
寄存器和子寄存器(subregister/subreg)
- 在某些架构中,一个寄存器可以分成若干片段(比如 64 位寄存器可分成两个 32 位子寄存器,或向量寄存器按元素分割)。LLVM 中会用 SubRegIndex 表示如何从一个寄存器抽取或插入某个子部分。
- “lane”就是对应子寄存器的那个部分在位层面的表示。LLVM 用 LaneBitmask 来表示一组位(可能是一个子寄存器对应的全部位,也可能是更细粒度的位组合)。
源码的代码量中等偏低,五百多行。入口同样支持两种新旧 Pass 管理器。
其不依赖任何 Analysis。
bool DetectDeadLanes::run(MachineFunction &MF) {
// Don't bother if we won't track subregister liveness later. This pass is
// required for correctness if subregister liveness is enabled because the // register coalescer cannot deal with hidden dead defs. However without // subregister liveness enabled, the expected benefits of this pass are small // so we safe the compile time. MRI = &MF.getRegInfo();
if (!MRI->subRegLivenessEnabled()) {
LLVM_DEBUG(dbgs() << "Skipping Detect dead lanes pass\n");
return false;
}
TRI = MRI->getTargetRegisterInfo();
DeadLaneDetector DLD(MRI, TRI);
bool Changed = false;
bool Again;
do {
DLD.computeSubRegisterLaneBitInfo();
bool LocalChanged;
std::tie(LocalChanged, Again) = modifySubRegisterOperandStatus(DLD, MF);
Changed |= LocalChanged;
} while (Again);
return Changed;
}
入口逻辑为:
- 先用最新掩码推断哪些 lane 被用或定义;
- 再标记那些死或未定义的操作数;
- 如果标记产生的新未定义信息又会影响其它寄存器的 lane 分析,则重新计算,直到收敛。
最终Changed
会反映是否在 MachineFunction 上做出了任何变动。
Pass 的原理
核心目标:在涉及子寄存器(subregister/lane)时,精细地跟踪虚拟寄存器中各子 lane 的定义与使用,以便识别死定义(never used 的子 lane 定义)和未定义使用(use 未被定义的子 lane)。
- 初始化
- 对每个虚拟寄存器,调用
determineInitialDefinedLanes
和determineInitialUsedLanes
:- 如果寄存器无明确定义或是 live-in,视为全 lane 已定义;若定义来自 COPY-like,则暂设置为“由 Copy 定义”,并通过源操作数推测部分初始已定义 lane;若定义是 IMPLICIT_DEF 或 dead def,则初始定义掩码为空。
- 使用遍历 use 操作,若对整个寄存器使用则视全 lane 使用,否则按 subregister index 标记对应子 lane。对 COPY-like use,先延后由数据流分析确定具体 lane。
- 对于从 COPY-like 定义标记的寄存器,加入工作表(worklist)以便传播。
- 如果寄存器无明确定义或是 live-in,视为全 lane 已定义;若定义来自 COPY-like,则暂设置为“由 Copy 定义”,并通过源操作数推测部分初始已定义 lane;若定义是 IMPLICIT_DEF 或 dead def,则初始定义掩码为空。
- 数据流迭代(computeSubRegisterLaneBitInfo)
- 维护一个 worklist,当某寄存器的 DefinedLanes/UsedLanes 更新时,将其加入。
- 反复从 worklist 取寄存器,做两方面传播:
- 反向传播 UsedLanes:对于该寄存器的定义指令(唯一定义),通过
transferUsedLanesStep
,根据指令类型将被使用的子 lane 映射到源寄存器的对应子 lane,更新它们的 UsedLanes。 - 正向传播 DefinedLanes:对于该寄存器的每个使用场景,通过
transferDefinedLanesStep
,将定义的 lane 映射到下游使用寄存器的定义掩码中,更新它们的 DefinedLanes。
- 反向传播 UsedLanes:对于该寄存器的定义指令(唯一定义),通过
- 每次更新时如掩码有新增位,就再将相应寄存器索引放回 worklist,直到无更多更新,收敛结束。
- 标记死定义与未定义使用(modifySubRegisterOperandStatus)
- 遍历 MachineFunction 中所有 MachineInstr 的操作数:
- 若某 Def 操作数对应寄存器的 UsedLanes 全为 0,且尚未标记为 dead,则标记为 dead def。
- 若某 Use 操作数对应子 lane 在 DefinedLanes∧UsedLanes 掩码上没有位,或特定 COPY-like 场景下映射后证明该使用确实未定义,则标记为 undef use。
- 返回两个标志:
LocalChanged
:本次是否有任何操作数状态变化;Again
:是否因某些跨类 copy 的 undef 标记而需要重新做数据流传播(新标记可能影响其他寄存器的 lane 信息)。
- 遍历 MachineFunction 中所有 MachineInstr 的操作数:
- 外层迭代直到稳定
- 用
do { computeSubRegisterLaneBitInfo(); tie(LocalChanged, Again)=modify...; } while (Again);
:- 首先做完整的 lane-level 数据流分析,然后标记 dead/undef。
- 若
modifySubRegisterOperandStatus
返回Again=true
,说明标记产生的新信息会影响其它寄存器的掩码,需要重新跑一轮数据流;循环直到Again=false
。
- 同时累积
Changed |= LocalChanged
,以便指示 Pass 最终是否对 MachineFunction 做出了修改。
- 用
- 映射机制
- 利用
composeSubRegIndexLaneMask
/reverseComposeSubRegIndexLaneMask
等,按子寄索引在父寄与子寄之间精确映射位掩码。 - 对跨寄存器类(cross-copy)场景,不尝试精确子 lane 跟踪,而是视作整体定义/使用。
- 利用
例外场景
当然这个 Pass 也不是谁都能追踪。有一些例外场景:
- 跨寄存器类的拷贝(cross-copy):如果一个 COPY-like 指令在源/目标寄存器类之间不兼容,
isCrossCopy
会返回 true,此时分析不做细粒度子 lane 跟踪,而是当作“无法精确传播”,通常视作全 lane 定义/使用。这会导致无法发现该场景下的部分死定义或未定义使用,而只能保守处理。 - 非“COPY-like”但涉及子寄存器的指令
- …
评论