-
Notifications
You must be signed in to change notification settings - Fork 12.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Casting u128::MAX to f32 is undefined #41799
Comments
|
If it is currently UB then maybe #10185 needs to be reopened. |
|
Yes, because the maximum value of a u64 fits in a f32 fine. |
Cross-linking #10184. |
Don't forget #15536. |
Hi! Sorry if this is a stupid suggestion… but as someone who uses u128 (in several cryptographic libraries) and doesn't use floats, I wonder how many |
If a new |
@isislovecruft Rust is a general purpose programming language and it already has a syntax for doing conversions that one could expect to work on all the primitive types, so it not supporting Furthermore, consider a code like this:
Now this macro works just fine for every single integer and float combo. Forbidding it for i/u128 would extend the discrepancy to macros as well. I hope that’s enough of demonstration of a need to keep functionality consistent. |
@nagisa The consistency issue, esp. w.r.t. macros, makes perfect sense. Thanks for taking the time to point that out. |
%1 = uitofp i128 %0 to float which, on x86_64, i686 and aarch64, becomes a call to the compiler_rt function call __floatuntisf@PLT Directly calling the function, instead of generating the The current implementation casts |
It makes no sense to do that over just adding the relevant branches into the IR, because it inhibits pretty much every optimisation with the values involved. |
@nagisa Yes we could do this if a >= 0xffffff80000000000000000000000000_u128 {
f32::INFINITY
} else {
a as f32
} producing %1 = icmp ugt i128 %0, -10141204801825835211973625643009
%2 = uitofp i128 %0 to float
%_0.0 = select i1 %1, float 0x7FF0000000000000, float %2 producing (unfortunately the function will always be called, and the cold branch check remains) mov rbx, rsi
shr rbx, 39
call __floatuntisf@PLT
cmp rbx, 33554430 ; 0x1fffffe
jbe .LBB0_2
movss xmm0, dword ptr [rip + .LCPI0_0]
.LBB0_2:
ret
.LCPI0_0:
.long 2139095040 ; 0x7f800000 |
Or just add a lint/error if |
LLVM will want some solution that’s more general and we want to avoid having custom patches in our LLVM, so I do not think that would work.
Interesting option, but does not help with code like target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
define internal i128 @foo() unnamed_addr {
start:
ret i128 -1
}
define float @bar() unnamed_addr {
start:
%0 = call i128 @foo()
%1 = uitofp i128 %0 to float
ret float %1
} which is still very UB, even if it does not produce a literal |
Ignoring LLVM and C/C++ for the moment, IEEE 754 section 5.4.1 "Arithmetic operations" defines
Integer to floating-point conversions typically use "to nearest" rounding. IEEE 754 section 4.3.1 "Rounding-direction attributes to nearest" defines "no nearest" and says:
|
Marking P-medium to match other similar bugs. |
These don't appear to have a stable ABI as noted in rust-lang#41799 and the work in compiler-builtins definitely seems to be confirming it!
rustc: Flag {i,u}128 as unsafe for FFI These don't appear to have a stable ABI as noted in #41799 and the work in compiler-builtins definitely seems to be confirming it!
Saturating casts between integers and floats Introduces a new flag, `-Z saturating-float-casts`, which makes code generation for int->float and float->int casts safe (`undef`-free), implementing [the saturating semantics laid out by](#10184 (comment)) @jorendorff for float->int casts and overflowing to infinity for `u128::MAX` -> `f32`. Constant evaluation in trans was changed to behave like HIR const eval already did, i.e., saturate for u128->f32 and report an error for problematic float->int casts. Many thanks to @eddyb, whose APFloat port simplified many parts of this patch, and made HIR constant evaluation recognize dangerous float casts as mentioned above. Also thanks to @ActuallyaDeviloper whose branchless implementation served as inspiration for this implementation. cc #10184 #41799 fixes #45134
#45205 has been merged, so there's now an opt-in solution to this issue ( |
@rkruppe I'd say we should do a public call, asking people to test the feature and report back. Then if there are no major issues after idk, 2-3 weeks (some people only read the weekly newsletter), we could flip the defaults and allow an opt out. It could stay this way on the nightly channel and if there continue to be no issues, we can let it ride the trains and include it in the next beta to be added to the next stable (without an opt out, as it would require a -C option and you won't be able to remove the opt-out again). If we hear about issues while the feature is in the beta channel, we can disable it on the beta channel as well. |
@est31 Huh, that's a bit more scrutiny than I would have expected. To be clear, in the context of this issue I am only talking about u128 -> f32 casts, the float -> int direction covered by #10184 can and should be rolled out independently. With that in mind, do you think the change to u128 -> f32 casts is more likely to cause regressions than the average bug fix? |
I've file a PR (#45900) to just make the u128->f32 behavior the default, refocusing the -Z flag to just cover float->int casts. As with any other change, we'll have up to six weeks in nightly and six weeks of beta to find and fix any regressions there may be. |
... rather than being gated by -Z saturating-float-casts. There are several reasons for this: 1. Const eval already implements this behavior. 2. Unlike with float->int casts, this behavior is uncontroversially the right behavior and it is not as performance critical. Thus there is no particular need to make the bug fix for u128->f32 casts opt-in. 3. Having two orthogonal features under one flag is silly, and never should have happened in the first place. 4. Benchmarking float->int casts with the -Z flag should not pick up performance changes due to the u128->f32 casts (assuming there are any). Fixes rust-lang#41799
…lexcrichton Make saturating u128 -> f32 casts the default behavior ... rather than being gated by `-Z saturating-float-casts`. There are several reasons for this: 1. Const eval already implements this behavior. 2. Unlike with float->int casts, this behavior is uncontroversially the right behavior and it is not as performance critical. Thus there is no particular need to make the bug fix for u128->f32 casts opt-in. 3. Having two orthogonal features under one flag is silly, and never should have happened in the first place. 4. Benchmarking float->int casts with the -Z flag should not pick up performance changes due to the u128->f32 casts (assuming there are any). Fixes #41799
FYI overflowing s/uitofp casts are no longer considered undefined as of https://reviews.llvm.org/D47807. So we should be able to drop the extra range checking code somewhere in the future. |
Given this code (playpen):
It should print
Ok(2742605)
. In release mode, that happens, but in debug mode, it printsErr("Overflow")
.The value is mostly irrelevant, it would also work for
42.0
.cc @nagisa
cc #35118 tracking issue
The text was updated successfully, but these errors were encountered: