Open
Description
The following IR (compiler explorer):
target triple = "wasm32-unknown-wasi"
define half @from_bits(i16 %x) {
%res = bitcast i16 %x to half
ret half %res
}
define i16 @to_bits(half %x) {
%res = bitcast half %x to i16
ret i16 %res
}
define i16 @roundtrip() {
%h = call half @from_bits(i16 64513) ; A bitpattern of a signalling NaN
%res = call i16 @to_bits(half %h)
ret i16 %res
}
Is compiled into the following WASM:
from_bits: # @from_bits
local.get 0
call __extendhfsf2
end_function
to_bits: # @to_bits
local.get 0
call __truncsfhf2
i32.const 65535
i32.and
end_function
roundtrip: # @roundtrip
i32.const 64513
call from_bits
call to_bits
end_function
rountrip
should return 64513
(0xfc01), as all from_bits
and to_bits
do is bitcast to and from half
. However, on WASM it instead returns 65025
(0xfe01), as __extendhfsf2
and __truncsfhf2
both quieten signalling NaNs.
This Rust program, when compiled with rustc 1.81.0-nightly (3cb521a43 2024-06-22)
with rustc --target wasm32-wasip1 code.rs
and run with wasmtime
, demonstrates the issue.
#![feature(f16)]
fn main() {
assert_eq!(f16::from_bits(0xfc01).to_bits(), 0xfc01);
}
The assertion should succeed, but on WASM it fails.
This solution to this is probably to either:
- Change the
half
ABI to be passed and returned in the low bits of a WASMi32
, the same as a LLVMi16
. This would match the ABI ofhalf
in the__extendhfsf2
and__truncsfhf2
builtins. I noticed that the ABI for 16-bit floats is not specified in the WASM Basic C ABI document, and Clang doesn't support_Float16
on WASM either, so this might be possible with regards to backwards compatibility concerns. - If that's not possible or desirable, convert
half
to and fromf32
losslessly with regards to NaN bits. This would mean either adding extra codegen around the relevant__extendhfsf2
and__truncsfhf2
calls to ensure that signalling NaNs don't get quietened, or adding new builtins that are the same as__extendhfsf2
and__truncsfhf2
but don't quieten signalling NaNs.