后端Pass简介——ExpandFp
ExpandFp
该 Pass 行数为 600+,是一个辅助 Pass
一些后端(尤其是 x86_64)无法直接对超过一定精度(如 128 位)的浮点与整数互转指令做硬件指令生成(Lowering);默认的 FPToUI/FPToSI、UIToFP/SIToFP 指令只能处理较小的位宽。为了支持更大位宽的转换,需要在 IR 层面用一系列基本操作(位移、掩码、比较、select、phi 等)手工模拟这一转换逻辑。
ExpandFp Pass 会扫描函数中的所有浮点〈→〉整数互转指令,当目标转换的整数位宽超过阈值时(默认无限制),就替换成等价的 IR 序列:
- 对于 FP→Int,将浮点先解释成原始比特表示(bitcast),提取符号、阶码、尾数,按 IEEE 浮点格式的规则手动生成整数结果;
- 对于 Int→FP,则基于整数位宽和目标浮点格式的尾数位数构造位级操作,再用 bitcast/fptrunc 等恢复成浮点。
该 Pass 可以调参但不建议调:
static cl::opt<unsigned>
ExpandFpConvertBits("expand-fp-convert-bits", cl::Hidden,
cl::init(llvm::IntegerType::MAX_INT_BITS),
cl::desc("Convert FP↔Int for integer bitwidth > N"));
举例来看,:
define dso_local i64 @foo(float %a) local_unnamed_addr #0 {
entry:
; 将 float %a 的比特位当作 i32 来看,提取原始 IEEE-754 表示
%0 = bitcast float %a to i32
; 将 32 位整数零扩展为 64 位,用于后续位操作
%conv.i = zext i32 %0 to i64
; 判断原始 32 位位模式是否大于 -1(即判断符号位:正数为 true,负数为 false)
%tobool = icmp sgt i32 %0, -1
; 根据符号位选 1 或 -1,后面要乘以尾数部分恢复正负号
%sign = select i1 %tobool, i64 1, i64 -1
; 右移 23 位,得到原始浮点数的 Exponent 字段(8 位)
%exp = lshr i64 %conv.i, 23
; 只保留低 8 位(掩码 0xFF),得到真正的指数值
%exp8 = and i64 %exp, 255
; 取低 23 位(尾数字段),掩码 0x7FFFFF
%mantissa = and i64 %conv.i, 0x7FFFFF
; 在尾数上加上隐含的最高位 (1<<23),得到 [1.mantissa] 的完整尾数
%mant_imp = or i64 %mantissa, 0x800000
; 判断指数是否小于偏置 127(即 abs(value) < 1.0)
%is_small = icmp ult i64 %exp8, 127
; 如果指数 < 127,直接跳到 cleanup 块返回 0,否则进入 if.end 计算非小于 1.0 的情况
br i1 %is_small, label %cleanup, label %if.end
if.end:
; 对于 exponent ≥ 127 的情况,需要调整尾数和指数差来计算整数值
; 计算 e' = exp8 - 127(无符号转换成 i64),e' 即小数点左移的位数
%e_dash = sub i64 %exp8, 127
; 计算尾数完整值 mant_imp >> e',相当于 (1.mantissa) * 2^(exp-127)
%scaled = lshr i64 %mant_imp, %e_dash
; 乘以符号,得到最终的有符号整数
%signed = mul i64 %scaled, %sign
; 跳转到 cleanup 合并结果
br label %cleanup
cleanup: ; preds = %entry, %if.end
; 定义一个 φ 节点,根据来路(entry或if.end)选择结果
%result = phi i64 [
0, %entry // 如果 <1.0,直接返回 0
], [
%signed, %if.end // 否则返回上面计算的 signed
]
ret i64 %result ; 返回最终 i64 值
}
只有极少数场景才用到这套“软件模拟”逻辑,不会在热点代码里大规模出现性能开销。
从源码上看,该 Pass 覆盖不依赖分析。
- runImpl(Function &F, …)
- 遍历函数里所有指令,
switch
收集需要展开的 FPToUI/FPToSI/UIToFP/SIToFP。 - 分别对标量和向量指令做收集,最后调用对应的展开函数。
- 返回 true 表示函数被修改。
- 遍历函数里所有指令,
- expandFPToI(Instruction FPToI)
- 使用 IRBuilder 构造位级操作:bitcast、zext/sext、icmp、select、lshr、and、or、phi。
- 根据尾数(mantissa)和阶码(exponent)位宽计算偏移与掩码。
- 分支分四种情况:小数部分、正/负无穷、尾数范围、溢出修正,最后合并到 cleanup 块。
- 替换原始 FPToUI 或 FPToSI 指令,并删除其引用。
- expandIToFP(Instruction IToFP)
- 与 expandFPToI 对称:对整数位宽大于浮点尾数时,构造 ashr、ctlz、xor、switch、shl/shr 等一系列操作,还可能用到 Intrinsic::ctlz(计算前导零)。
- 最终通过 bitcast 或 fptrunc 恢复成 float/double/fp80。
评论