后端Pass简介——ExpandLargeDivRem
ExpandLargeDivRem
这个 Pass(ExpandLargeDivRem)的主要作用,是在 LLVM 后端针对那些 位宽超过目标硬件或配置所能直接支持的整数除法/取余指令(div/rem),将它们“拆解”成对外部自动生成函数的调用,从而保证在不支持大整数除法的架构上也能正确地执行。具体来说:
- 检测不支持的指令
在遍历函数中的所有 UDiv、SDiv、URem、SRem 指令时,先读取目标子系统(TargetLowering)报告的最大可合法处理位宽,或者使用用户通过 -expand-div-rem-bits 强制设置的阈值。如果除法/取余操作的位宽超过该阈值,则认定为“超大”指令需要特殊处理。 - 跳过简单情况
- 对于常数幂二的除数(如除以 8、16 等),后端往往有专门的移位或乘法优化,所以这里不做拆分。
- 可扩展向量类型暂不支持,只有定长向量会被拆解。
- 向量指令的标量化(scalarize)
如果指令作用于一个向量(VectorType),先将其拆成若干条标量除法/取余,然后把结果再插回到向量中,这一步收集生成新的 BinaryOperator,后续一起处理。 - 调用库函数实现
对于剩下的每一条超大位宽的标量除法/取余指令,分别调用 expandDivision 或 expandRemainder(来自 Transforms/Utils/IntegerDivision.h)来替换原有指令。它们会生成对 LLVM 自动生成的软除法/取余函数(libcall)的调用,如 __udivti3、__divti3 等。 - 保持其它分析结果
Legacy Pass(基于 FunctionPass)和新的 PassManager 插件都做了必要的初始化和 AnalysisUsage 设置,以便与其他优化和分析阶段正确协同。
例如如下例子:
; 声明软除法/取余函数(通常由 LLVM 构建体系自动生成或由 compiler-rt 提供)
declare i128 @__udivti3(i128, i128)
declare i128 @__umodti3(i128, i128)
define i128 @foo(i128 %a, i128 %b) {
entry:
; 原生除法和取余指令(硬件不支持 128 位)
%quot = udiv i128 %a, %b
%rem = urem i128 %a, %b
ret i128 %quot
}
经过 Pass 处理后转为库调用:
define i128 @foo(i128 %a, i128 %b) {
entry:
; 将原先的 udiv/urem 指令全部替换成对外部“软除法/取余”函数的调用
%quot = call i128 @__udivti3(i128 %a, i128 %b)
%rem = call i128 @__umodti3(i128 %a, i128 %b)
ret i128 %quot
}
向量类型的例子
; 原始
%vquot = udiv <2 x i128> %va, %vb
经过处理后(先拆为标量,然后分别处理再拼接):
; 伪 IR 示意
%e0 = extractelement <2 x i128> %va, i32 0
%e1 = extractelement <2 x i128> %va, i32 1
%f0 = extractelement <2 x i128> %vb, i32 0
%f1 = extractelement <2 x i128> %vb, i32 1
%r0 = call i128 @__udivti3(i128 %e0, i128 %f0)
%r1 = call i128 @__udivti3(i128 %e1, i128 %f1)
%out = insertelement <2 x i128> poison, i128 %r0, i32 0
%out2 = insertelement <2 x i128> %out, i128 %r1, i32 1
评论