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 覆盖不依赖分析。

  1. runImpl(Function &F, …)
    • 遍历函数里所有指令,switch 收集需要展开的 FPToUI/FPToSI/UIToFP/SIToFP。
    • 分别对标量和向量指令做收集,最后调用对应的展开函数。
    • 返回 true 表示函数被修改。
  2. expandFPToI(Instruction FPToI)
    • 使用 IRBuilder 构造位级操作:bitcast、zext/sext、icmp、select、lshr、and、or、phi。
    • 根据尾数(mantissa)和阶码(exponent)位宽计算偏移与掩码。
    • 分支分四种情况:小数部分、正/负无穷、尾数范围、溢出修正,最后合并到 cleanup 块。
    • 替换原始 FPToUI 或 FPToSI 指令,并删除其引用。
  3. expandIToFP(Instruction IToFP)
    • 与 expandFPToI 对称:对整数位宽大于浮点尾数时,构造 ashr、ctlz、xor、switch、shl/shr 等一系列操作,还可能用到 Intrinsic::ctlz(计算前导零)。
    • 最终通过 bitcast 或 fptrunc 恢复成 float/double/fp80。