rustfloating-pointroundinginline-assemblyllvm-codegen

Setting a non-default rounding mode with Rust inline asm isn't respected by the LLVM optimizer?


I am working on a Rust crate which changes the rounding mode (+inf, -inf, nearest, or truncate).

The functions that change the rounding mode are written using inline assembly:

fn upward() {
    let cw: u32 = 0;
    unsafe {
    asm!("stmxcsr $0;
          mov $0, %eax;
          or $$0x4000, %eax;
          mov %eax, $0;
          ldmxcsr $0;"
          : "=*m"(&cw)
          : "*m"(&cw)
          : "{eax}"
        );
    }
}

When I compile the code in debug mode it works as intended, I get 0.3333333333337 for one-third when rounding toward positive infinity, but when I compile in release mode I get the same result no matter what rounding mode I set. I guess this behavior is due to the optimizations that the LLVM backend does.

If I knew which LLVM passes are responsible for this optimization, I can disable them as I don't see any other workaround at the moment.


Solution

  • Basically, you can't do this. LLVM assumes that all floating point operations use the default rounding mode, and that the floating-point control register is never read or modified.

    There's been some discussion of this issue recently on the LLVM-dev mailing list, if you're interested.

    In the meantime, the only reliable workaround is to use inline assembly, like asm!("addsd $0, $1".

    Rust's standard library also assumes that you don't modify the rounding mode (in particular, the code for converting between floating-point and strings is sensitive to this).