Skip to content

Redundant checks in floating point to integer casts on WASM #73591

Closed
@CryZe

Description

@CryZe

If you compile the following Rust code to WASM:

pub unsafe fn cast(x: f64) -> u8 {
    x.to_int_unchecked()
}

it compiles to the following with Rust 1.45:

example::cast:
        block           
        local.get       0
        f64.const       0x1p32
        f64.lt  
        local.get       0
        f64.const       0x0p0
        f64.ge  
        i32.and 
        i32.eqz
        br_if           0
        local.get       0
        i32.trunc_f64_u
        return
        end_block
        i32.const       0
        end_function

The same happens in Rust <1.45 if you use as _. In both cases the following LLVM IR is emitted:

define hidden i8 @_ZN7example4cast17he66904755097d668E(double %x) unnamed_addr #0 !dbg !5 {
  %0 = fptoui double %x to i8, !dbg !9
  ret i8 %0, !dbg !22
}

fptoui is explicitly defined to be UB if an out of range value is provided.

If the value cannot fit in ty2, the result is a poison value.

However it seems like the WASM backend in LLVM ignores that and emits additional bounds checks.

Additionally this gets even worse in Rust 1.45 if you use as _ instead:

example::cast:
        local.get       0
        f64.const       0x1.fep7
        f64.gt  
        local.set       1
        block           
        block           
        local.get       0
        f64.const       0x0p0
        local.get       0
        f64.const       0x0p0
        f64.gt  
        f64.select
        local.tee       0
        f64.const       0x1p32
        f64.lt  
        local.get       0
        f64.const       0x0p0
        f64.ge  
        i32.and 
        i32.eqz
        br_if           0
        local.get       0
        i32.trunc_f64_u
        local.set       2
        br              1
        end_block
        i32.const       0
        local.set       2
        end_block
        i32.const       -1
        local.get       2
        local.get       1
        i32.select
        end_function

This means that in the end in the WASM VM will do 3 range checks (the one emitted by rust, the one emitted by the llvm backend, and the one it needs to do to possibly trap).

Additionally Rust's saturating checks don't play well with WASM's nontrapping-fptoint target-feature as there's still redundant checks:

example::cast:
        i64.const       9223372036854775807
        local.get       0
        f32.const       -0x1p63
        local.get       0
        f32.const       -0x1p63
        f32.gt  
        f32.select
        i64.trunc_sat_f32_s
        local.get       0
        f32.const       0x1.fffffep62
        f32.gt  
        i64.select
        i64.const       0
        local.get       0
        local.get       0
        f32.eq  
        i64.select
        end_function

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-LLVMArea: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.C-bugCategory: This is a bug.I-slowIssue: Problems and improvements with respect to performance of generated code.O-wasmTarget: WASM (WebAssembly), http://webassembly.org/

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions