后端Pass简介——Hardware loops
Hardware loops
这个 pass 有 608 行
这个 HardwareLoops Pass 的主要作用,是在 LLVM 编译流程中把“普通的”软件循环转换成底层目标机器所支持的 硬件循环。
正好来区分一下软件循环和硬件循环:
- 软件循环例子:
MOV R0, #0 ; i = 0
MOV R1, #n ; R1 保存 n
loop_soft:
CMP R0, R1 ; 比较 i 和 n
BGE end_soft ; 若 i >= n,跳出
; ——— 循环体 ———
ADD R0, R0, #1 ; i = i + 1
B loop_soft ; 跳回比较
end_soft:
- 硬件循环(hardware loop):
- 原理:利用处理器提供的==专门指令或内建硬件计数器来管理循环迭代次数==。编译器在循环外一次性设置好迭代次数,循环内部不用再做显式比较和计数器更新,硬件在每次迭代结束时自动递减并判断是否继续。
- 迭代逻辑放到专门的微架构单元里做,大幅减少指令数量和分支开销,提高指令流的连续性和能效;尤其适合迭代次数已知或可推导的内层小环路。
- 例子:LLVM IR 中的硬件循环 Intrinsics
假设循环迭代次数存于 %n 中,LLVM 会插入两条 Intrinsic:
; 在循环外设置迭代次数(+1 用于测试退出条件)
%cnt = call i32 @llvm.set_loop_iterations.i32(i32 %n_plus_one)
; 无条件跳到 loop.header
br label %loop.header
loop.header:
; loop body…
; 在退出分支处,硬件自动递减并返回新计数
%newcnt = call i32 @llvm.loop_decrement.i32(i32 %cnt)
; 当 newcnt != 0 时,跳回 header;否则退出
%cond = icmp ne i32 %newcnt, 0
br i1 %cond, label %loop.header, label %loop.exit
loop.exit:
; 后续代码…
在真正支持硬件环路指令的处理器(如一些 DSP、Cortex-M 的增扩指令集、TI C6000 VLIW DSP 等),这两条 Intrinsic 会被下变换成诸如:
LOOP #N, loop_body ; 一条指令同时设置计数并跳到 loop_body
loop_body:
… ; 循环体
; 硬件自动在内部递减,决定是否回到 loop_body
该 Pass 是可以调参的,但是这些参数是供开发者使用的!测试
static cl::opt<bool>
ForceHardwareLoops("force-hardware-loops", cl::Hidden, cl::init(false),
cl::desc("Force hardware loops intrinsics to be inserted"));
static cl::opt<bool>
ForceHardwareLoopPHI(
"force-hardware-loop-phi", cl::Hidden, cl::init(false),
cl::desc("Force hardware loop counter to be updated through a phi"));
static cl::opt<bool>
ForceNestedLoop("force-nested-hardware-loop", cl::Hidden, cl::init(false),
cl::desc("Force allowance of nested hardware loops"));
static cl::opt<unsigned>
LoopDecrement("hardware-loop-decrement", cl::Hidden, cl::init(1),
cl::desc("Set the loop decrement value"));
static cl::opt<unsigned>
CounterBitWidth("hardware-loop-counter-bitwidth", cl::Hidden, cl::init(32),
cl::desc("Set the loop counter bitwidth"));
static cl::opt<bool>
ForceGuardLoopEntry(
"force-hardware-loop-guard", cl::Hidden, cl::init(false),
cl::desc("Force generation of loop guard intrinsic"));
STATISTIC(NumHWLoops, "Number of loops converted to hardware loops");
该 Pass 依赖很多分析:
- LoopAnalysis
- ScalarEvolutionAnalysis
- DominatorTreeAnalysis
- TargetIRAnalysis
- TargetLibraryAnalysis
- AssumptionAnalysis
- OptimizationRemarkEmitterAnalysis
- DataLayout
入口为:
bool HardwareLoopsImpl::run(Function &F) {
LLVMContext &Ctx = F.getContext();
for (Loop *L : LI)
if (L->isOutermost())
TryConvertLoop(L, Ctx);
return MadeChange;
}
核心逻辑在:
bool HardwareLoopsImpl::TryConvertLoop(Loop *L, LLVMContext &Ctx) {
// 1. 先处理所有嵌套子循环
bool AnyChanged = false;
for (Loop *SL : *L)
AnyChanged |= TryConvertLoop(SL, Ctx);
if (AnyChanged) {
// 如果有任何一个内层循环被转换过(MadeChange = true),
// 而目标又不允许嵌套硬件环路,就报错并直接返回 true,阻止对父循环的进一步尝试。
reportHWLoopFailure("nested hardware-loops not supported", "HWLoopNested",
ORE, L);
return true; // 停止在这一层继续查找
}
// 2. 调试打印
LLVM_DEBUG(dbgs() << "HWLoops: Loop " << L->getHeader()->getName() << "\n");
// 3. 构造针对这个 Loop 的分析信息对象
HardwareLoopInfo HWLoopInfo(L);
// 3.1 如果结构太复杂(不可约、CFG 奇怪等),就算了
if (!HWLoopInfo.canAnalyze(LI)) {
reportHWLoopFailure("cannot analyze loop, irreducible control flow",
"HWLoopCannotAnalyze", ORE, L);
return false;
}
// 3.2 如果没强制插入,又被后端 TTI 评估为“不盈利”,就跳过
if (!Opts.Force &&
!TTI.isHardwareLoopProfitable(L, SE, AC, TLI, HWLoopInfo)) {
reportHWLoopFailure("it's not profitable to create a hardware-loop",
"HWLoopNotProfitable", ORE, L);
return false;
}
// 4. 根据命令行或测试选项,覆盖默认的计数器宽度和递减值
if (Opts.Bitwidth.has_value()) {
HWLoopInfo.CountType =
IntegerType::get(Ctx, Opts.Bitwidth.value());
}
if (Opts.Decrement.has_value()) {
HWLoopInfo.LoopDecrement =
ConstantInt::get(HWLoopInfo.CountType,
Opts.Decrement.value());
}
// 5. 真正尝试把这个 Loop 转成硬件环路
MadeChange |= TryConvertLoop(HWLoopInfo);
// 6. 返回值——告诉调用者:
// • 如果已做过修改(MadeChange == true),并且此循环不允许嵌套(IsNestingLegal == false)且又没强制嵌套,
// 则返回 true,表示“搜索应当停止”(上层不要再处理这个父循环)。
// • 其它情况返回 false,表示“本轮尝试结束但可以继续往上级或兄弟循环搜”。
return MadeChange && (!HWLoopInfo.IsNestingLegal && !Opts.ForceNested);
}
同时也有代码函数: TTI.isHardwareLoopProfitable
评论