Description
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