后端Pass简介——InitUndef
InitUndef
InitUndef 这个 MachineFunctionPass 的主要目的,==是在寄存器分配阶段之前==,把“undef” 或者 “NoReg” (表示隐含定义被故意绕过 MachineCSE 优化的占位符)这些“未初始化”或“占位”寄存器操作,转成真正的伪指令(INIT_UNDEF/IMPLICIT_DEF)来初始化,然后再把路径中可能只定义了部分子寄存器的情况,==用插入子寄存器伪初始化的方式补全==。这样做可以防止在遇到“early-clobber”(早占用)约束或者子寄存器活跃性分析时,寄存器分配器因输入是 undef 而分配到非法重叠寄存器导致崩溃或非法指令。
这段话涉及的知识盲区有点多,下面来讲解分析一下:
- 为什么要伪指令? 因为 undef 或 NoReg 并不是真正的定义,直接让寄存器分配器看到会导致它把未初始化的值当作有效源操作数,插入伪指令(INIT_UNDEF/IMPLICIT_DEF)就能在分配前显式地“初始化”这些虚拟寄存器,避免错误分配。
- 什么是 “early-clobber”(早占用)? 在指令定义操作数上打上 early-clobber 标记表示==该写入必须在同一条指令的其他源操作数读取之前完成==,用来强制寄存器分配器不要给这些源操作数分配同一个物理寄存器。
- 什么是非法重叠寄存器? 当两个操作数被分配到同一个物理寄存器或其子寄存器区间有交集,但硬件或指令格式不允许这样的重叠时,就属于非法重叠,会导致生成的机器指令不合法或运行时崩溃。
- MachineCSE 是什么? Machine-level Common Subexpression Elimination(机器级公共子表达式消除),在生成的机器指令层面识别并合并重复的计算,以减少冗余指令和提高代码效率。
所以现在好理解一些:在寄存器分配之前,如果一个操作数被标记为 undef 或 NoReg(MachineCSE 用来避开某些优化的占位符),寄存器分配器就会把它当作还没初始化的“空洞”,随意分配到物理寄存器上;而
有些指令对写入位置打了 early-clobber (“早占用”)标记,==要求写操作必须在同条指令的读操作之前完成==,这时候如果读操作也被分到同一个寄存器或重叠的子寄存器区,就会违反硬件或编码格式的约束——也就是“非法重叠寄存器”,会生成不合法指令或运行时崩溃。 InitUndef 这个 Pass 就在这之前插入 INIT_UNDEF 或 IMPLICIT_DEF 这样的伪指令,把所有虚拟寄存器显式“占位”初始化一遍,并对只定义了部分子寄存器的情况用 INSERT_SUBREG 补全,从而让寄存器分配器在面对 early-clobber 约束和子寄存器活跃性分析时,不会把“空洞”当正常值用,也不会分配出冲突或重叠的寄存器,保证生成的指令安全合法。
该 Pass 仅 200 行,核心的 LLVM API 与流程就是:
- 创建伪寄存器:
Register newVReg = MRI->createVirtualRegister(regClass);
- 用于生成一个新的虚拟寄存器来承载初始化值。
- 插入伪指令:
BuildMI(*BB, insertPos, DL, TII->get(TargetOpcode::INIT_UNDEF), newVReg);
- 或者 TargetOpcode::IMPLICIT_DEF,在指定位置插入“初始化”伪指令。
- 检测 early-clobber:
any_of(MI.all_defs(), [](auto &MO){ return MO.isEarlyClobber(); });
- 找到带有 early-clobber 标记的定义操作数。
- 子寄存器活跃性:
DeadLaneDetector DLD(MRI, TRI); DLD.computeSubRegisterLaneBitInfo();
- 统计哪些子寄存器 lane 已定义/已用,以决定是否要插入 INSERT_SUBREG。
- 修正操作数:
if (UseMO.isUndef()) { newVReg = MRI->createVirtualRegister(...); BuildMI(..., INIT_UNDEF, newVReg); UseMO.setReg(newVReg); UseMO.setIsUndef(false); }
- 把原本的 undef/NoReg 操作数替换成新寄存器。
简而言之,Pass 就是在遍历每条 MachineInstr:
- 用 isEarlyClobber 找到潜在冲突;
- 对“undef”或“占位”寄存器调用 createVirtualRegister+BuildMI(…INIT_UNDEF/IMPLICIT_DEF);
- (若启用)用 DeadLaneDetector 决定哪些子寄存器缺失,再插入 INSERT_SUBREG;
- 最后把原操作数指向新寄存器,确保寄存器分配器不会分配到非法或重叠的物理寄存器。