I never contributed to LLVM or interacted with the community here, so I would like to ask how I can propose an optimization in the code gen for x86 with SSE2. Also, I’m not sure whether the LLVM project would even accept that optimization, so is this forum the right place to discuss it with relevant experts first?
To give some detail on the optimization: On x86, ISD::FTRUNC compiles to a single instruction with SSE4.1 (or AVX, etc.) but a library call to truncf on SSE2. The soft-float implementation of the library plus the call overhead makes this very slow. This is a problem in Rust, where generic x86 binaries can typically only assume SSE2.
So I would like to propose a custom lowering for ISD::FTRUNC and ISD::FROUND for x86 SSE2. Here’s a Rust version of the scalar algorithm for trunc. (SSE2 SIMD instructions for all used operations exist.)
fn trunc_sse2_scalar(x: f32) -> f32 {
// Clear the sign bit
let abs = x.abs();
// Determine which values to return unchanged
const INT_THRESHOLD: i32 = f32::to_bits(2147483648_u32 as f32) as i32;
let return_unchanged = abs.to_bits() as i32 >= INT_THRESHOLD;
// Truncate using the native SSE2 f32 <-> i32 conversions
let abs_trunc = (abs as i32) as f32;
let trunc = abs_trunc.copysign(x);
if return_unchanged { x } else { trunc }
}
This is bit-exact to the library truncf and around 10x faster on my machine when used in a loop.
The algorithm for ISD::FROUND is almost identical. The only difference is that next_down(0.5) is added to the absolute value before truncation via integer cast.