Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upWasm traps on saturating float casts #46298
Comments
This comment has been minimized.
This comment has been minimized.
|
I haven't look at the rustc side of this in detail yet, but the LLVM wasm backend does have a known bug in this area: LLVM's fptosi/fptoui are documented to (silently) return undef on invalid/overflow, which means that trapping is a bug from LLVM's perspective. https://reviews.llvm.org/D38524 is a patch which fixes the bug, though it needs a little more work before landing. Beyond that, there's also the proposal to add non-trapping float-to-int conversion operators to WebAssembly, which is mostly through the standards process and is now in the "implementation phase". Once this is widely implemented, https://reviews.llvm.org/D38524 will become unnecessary. But also, the proposed semantics are the same as Rust's new saturating conversion semantics, so eventually we'll be able to map a Rust float-to-int conversion directly to a single wasm opcode. |
This comment has been minimized.
This comment has been minimized.
|
Oh awesome thanks for the report @rkruppe and the info @sunfishcode! I'm glad I was misinterpreting the LLVM reference! Sounds like we should probably just wait for this to get fixed upstream? In the meantime I think it's probably safe to ignore the one test on wasm for now. |
TimNN
added
A-LLVM
C-bug
labels
Nov 28, 2017
This comment has been minimized.
This comment has been minimized.
|
I've now landed the LLVM patch upstream (r319128). |
This comment has been minimized.
This comment has been minimized.
|
Awesome thanks @sunfishcode! It looks like though we probably won't be able to turn that on by default for some time now, right? If so @rkruppe just to confirm, it'd be a perf loss to change the behavior of saturating casts, right? |
This comment has been minimized.
This comment has been minimized.
|
@alexcrichton It also changes the default behavior. If you can use LLVM trunk, or backport the patch, I expect it'll fix the test failure. |
This comment has been minimized.
This comment has been minimized.
|
Oh I missed that, perfect! I'll backport to our fork and get this in master |
This comment has been minimized.
This comment has been minimized.
|
Ok I've done a backport but it unfortunately didn't fix the test, I was still seeing "integer result unrespresentable". I minimized it down to: #[no_mangle]
pub extern fn foo(a: f64) -> bool {
a as u64 != 0
}which when invoked as define zeroext i1 @foo(double %a) unnamed_addr #0 {
start:
%0 = fptoui double %a to i64
%1 = fcmp ogt double %a, 0x43EFFFFFFFFFFFFF
%2 = icmp ne i64 %0, 0
%not. = fcmp oge double %a, 0.000000e+00
%3 = and i1 %not., %2
%4 = or i1 %1, %3
ret i1 %4
}so like before the If I remove the define zeroext i1 @foo(double %a) unnamed_addr #0 {
start:
%0 = fptoui double %a to i64
%1 = icmp ne i64 %0, 0
ret i1 %1
}(as one might expect) and the wasm output is: (func $0 (; 0 ;) (type $0) (param $var$0 f64) (result i32)
(local $var$1 i64)
(block $label$1
(block $label$2
(br_if $label$2
(i32.eqz
(f64.lt
(get_local $var$0)
(f64.const 18446744073709551615)
)
)
)
(set_local $var$1
(i64.trunc_u/f64
(get_local $var$0)
)
)
(br $label$1)
)
(set_local $var$1
(i64.const 0)
)
)
(i64.ne
(get_local $var$1)
(i64.const 0)
)
)@sunfishcode did I perhaps mess up the backport? Or is there maybe a missing comparison with 0 for the unsigned types? |
This comment has been minimized.
This comment has been minimized.
|
Oh, it's missing the comparison with 0 for unsigned types. I'll need to submit another patch. |
This comment has been minimized.
This comment has been minimized.
|
The missing comparison with 0 is fixed in r319354. |
This comment has been minimized.
This comment has been minimized.
|
Thanks! I'm testing with that now to ensure we're all good. |
alexcrichton
added a commit
to alexcrichton/rust
that referenced
this issue
Nov 30, 2017
This comment has been minimized.
This comment has been minimized.
|
It appears to work like a charm, thanks again @sunfishcode and @rkruppe! |
rkruppe commentedNov 27, 2017
•
edited by eddyb
In https://internals.rust-lang.org/t/help-us-benchmark-saturating-float-casts/6231/19 @alexcrichton reported that wasm traps on out-of-range floats being cast to integers. This is a problem for the current implementation of saturating float casts, which executes the cast unconditionally and then only uses it if the input was in range. LLVM says that the result is undefined (i.e.,
undef) rather than the behavior being undefined. On all other targets I'm aware of, the operation just gets lowered to an instruction that returns some integer result for those cases.I've checked the wasm spec, which says:
As wasm doesn't want everything-goes UB as in C or LLVM or Rust, "undefined" operations are specified to trap. So this is intentional in wasm, which makes sense to me. They'd rather trap than return a questionable result, and they certainly can't tolerate hardware-dependent behavior.
So how do we work around this? We could emit branches instead of unconditionally executing the cast, at the cost of a more complicated implementation (need to juggle multiple basic blocks) and other downsides (branch predictor pressure, input-data-dependent performance). It might also be an argument in favor of making the saturation explicit in MIR rather than doing it during LLVM IR codegen:
cc @sunfishcode @eddyb