后端Pass简介——EarlyIfConversion
EarlyIfConversion
这是一个比较重要的 Pass,代码量 1300 多行。Early If-Conversion 旨在 在指令选择(SelectionDAG)后、寄存器分配(RegAlloc)前,把一些简单的条件分支转换成顺序执行==并用条件移动(cmov 或者其他选择指令)来完成,减少分支指令,从而降低 mispredict 带来的性能损失。==
概念好懂,其支持两种控制流形状:
Head
/ \
TBB FBB
\ /
Tail
# Head 块末尾有一个单分支跳转到 TBB/FBB,再汇合到 Tail。
# Pass 会把 TBB 或 FBB 中的指令按需合并到 Head 中,并在原来 Tail 中的 φ 节点处插入选择指令。
Head
/ \
TBB FBB
\ /
\ /
Tail
# 类似三角形,但两条分支上都有指令需要合并。
# 将两边的指令都移动到 Head,并同样在 Tail 处用选择指令合并值。
下面可以看一个具体的例子:
ifconv 前
; Compare eax 和 ebx
cmp eax, ebx
je .Ltrue
; False path: 计算 sum = eax + ebx
lea ecx, [eax + ebx]
jmp .Lend
.Ltrue:
; True path: 计算 diff = eax - ebx
lea ecx, [eax - ebx]
.Lend:
; 此后使用 ecx,原本依赖分支结果
ifconv 后:
; 先顺序执行两条路径中的计算,推测性执行
lea ecx, [eax + ebx] ; false result (sum)
lea edx, [eax - ebx] ; true result (diff)
cmp eax, ebx
cmove ecx, edx ; 如果 ZF=1 (eax==ebx),ecx ← edx (diff)
; 之后直接使用 ecx,无需分支
- Speculate:先执行两条路径的指令,分别把“假”和“真”结果放到不同寄存器(这里是 ecx/edx)。
- Select:根据条件标志用一条 cmov(conditional move)在 ecx 中挑选最终值。
- 消除分支:不再需要 je/jmp 跳转,避免 mispredict 惩罚。
该 Pass 是可调参数和携带统计信息的 Pass:
// Absolute maximum number of instructions allowed per speculated block.
// This bypasses all other heuristics, so it should be set fairly high.
// 最大允许每个 speculated block(推测执行块)包含的指令数。如果遇到超过该阈值的 basic block,就跳过 if-conversion。
static cl::opt<unsigned>
BlockInstrLimit("early-ifcvt-limit", cl::init(30), cl::Hidden,
cl::desc("Maximum number of instructions per speculated block."));
// “压测模式”:关闭所有启发式判断,对遇到的每个可转换结构都强行尝试 if-conversion。主要用于回归测试或功能验证,不建议在常规编译中打开。
// Stress testing mode - disable heuristics.
static cl::opt<bool> Stress("stress-early-ifcvt", cl::Hidden,
cl::desc("Turn all knobs to 11"));
STATISTIC(NumDiamondsSeen, "Number of diamonds");
STATISTIC(NumDiamondsConv, "Number of diamonds converted");
STATISTIC(NumTrianglesSeen, "Number of triangles");
STATISTIC(NumTrianglesConv, "Number of triangles converted");
再来看一下代码:
核心是:
bool EarlyIfConverter::tryConvertIf(MachineBasicBlock *MBB) {
bool Changed = false;
while (IfConv.canConvertIf(MBB) && shouldConvertIf()) {
// 1. 清理之前的分析状态(方便多次尝试)
invalidateTraces();
// 2. 真正做转换,将被移除的分支块记录到 RemoveBlocks
SmallVector<MachineBasicBlock *, 4> RemoveBlocks;
IfConv.convertIf(RemoveBlocks);
Changed = true;
// 3. 更新支配树(Dominator Tree)
updateDomTree(DomTree, IfConv, RemoveBlocks);
// 4. 从函数中删除已被合并的块
for (MachineBasicBlock *B : RemoveBlocks)
B->eraseFromParent();
// 5. 更新 LoopInfo,移除已经消失的块
updateLoops(Loops, RemoveBlocks);
}
return Changed;
}
所以这个也是一个迭代的 Pass。
评论