Skip to content
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

Closed
est31 opened this Issue May 7, 2017 · 22 comments

Comments

Projects
None yet
@est31
Copy link
Contributor

est31 commented May 7, 2017

Given this code (playpen):

#![feature(i128_type,i128)]

fn cast(src: f32) -> Result<u128, &'static str> {
    use std::{u128, f32};

    Err(if src != src {
        "NaN"
    } else if src < u128::MIN as f32 {
        "Underflow"
    } else if src > u128::MAX as f32 {
        "Overflow"
    } else {
        return Ok(src as u128);
    })
}
fn main() {
    let f = 2742605.0f32;
    println!("{:?}", cast(f));
}

It should print Ok(2742605). In release mode, that happens, but in debug mode, it prints Err("Overflow").

The value is mostly irrelevant, it would also work for 42.0.

cc @nagisa
cc #35118 tracking issue

@est31 est31 changed the title i128 casting issue i128 casting issue on debug May 7, 2017

@nagisa

This comment has been minimized.

Copy link
Contributor

nagisa commented May 7, 2017

u128::MAX as f32 is undefined. It ends up being as float undef in LLVM. This issue is in a similar vein as the float to integer casts. All we really need is a decision on how to handle this. For u128->f32 it seems pretty obvious to simply produce an infinity.

@ollie27

This comment has been minimized.

Copy link
Contributor

ollie27 commented May 7, 2017

If it is currently UB then maybe #10185 needs to be reopened.

@nagisa

This comment has been minimized.

Copy link
Contributor

nagisa commented May 7, 2017

u64::MAX to f32 converts just fine.

@ghost

This comment has been minimized.

Copy link

ghost commented May 7, 2017

Yes, because the maximum value of a u64 fits in a f32 fine.

@nagisa nagisa changed the title i128 casting issue on debug Casting i128 to f32 is undefined May 8, 2017

@nagisa nagisa changed the title Casting i128 to f32 is undefined Casting u128::MAX to f32 is undefined May 8, 2017

@nagisa nagisa added the T-lang label May 8, 2017

@nagisa

This comment has been minimized.

Copy link
Contributor

nagisa commented May 8, 2017

Cross-linking #10184.

@ollie27

This comment has been minimized.

Copy link
Contributor

ollie27 commented May 8, 2017

Don't forget #15536.

@isislovecruft

This comment has been minimized.

Copy link

isislovecruft commented May 8, 2017

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 u128 users actually need safe casting to floats? Is this something one would do in graphics development? If there isn't much use for it, perhaps a simple fix would be to disallow 42u128 as f32 casts, i.e. they should have to first cast to u64.

@nodakai

This comment has been minimized.

Copy link
Contributor

nodakai commented May 13, 2017

If a new as-cast rule is going to be introduced, please make it generic so that it can be naturally extensible to something like mini-floats or GMP types

@nagisa

This comment has been minimized.

Copy link
Contributor

nagisa commented May 13, 2017

@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 as-casts for only this particular conversion would make the language inconsistent.

Furthermore, consider a code like this:

macro_rules! foo {
    ($x: ident) = {
        /* some stuff */
        $x as f32 
        /* rest of the stuff */
     }
}

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.

@isislovecruft

This comment has been minimized.

Copy link

isislovecruft commented May 15, 2017

@nagisa The consistency issue, esp. w.r.t. macros, makes perfect sense. Thanks for taking the time to point that out.

@kennytm

This comment has been minimized.

Copy link
Member

kennytm commented May 15, 2017

u128 as f32 currently generates this LLVM IR:

%1 = uitofp i128 %0 to float

which, on x86_64, i686 and aarch64, becomes a call to the compiler_rt function __floatuntisf

call	__floatuntisf@PLT

Directly calling the function, instead of generating the uitofp instruction, seems to be able to hide the UB?

The current implementation casts u128::MAX to +∞.

@nagisa

This comment has been minimized.

Copy link
Contributor

nagisa commented May 15, 2017

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.

@kennytm

This comment has been minimized.

Copy link
Member

kennytm commented May 15, 2017

@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
@kennytm

This comment has been minimized.

Copy link
Member

kennytm commented May 15, 2017

u128::MAX as f32 gives undef during LLVM-level constant-folding with LLVMConstUIToFP. Could the range check placed there as a first step, to prevent a naked undef being generated?


Or just add a lint/error if MirConstContext::trans() produces an undef Const? Or wait for miri?

@nagisa

This comment has been minimized.

Copy link
Contributor

nagisa commented May 15, 2017

Could the range check placed there as a first step, to prevent a naked undef being generated?

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.

Or just add a lint/error if MirConstContext::trans() produces an undef Const?

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 undef at the moment after the optimisations.

@sunfishcode

This comment has been minimized.

Copy link
Contributor

sunfishcode commented Jun 9, 2017

Ignoring LLVM and C/C++ for the moment, IEEE 754 section 5.4.1 "Arithmetic operations" defines convertFromInt and says:

If the converted value is not exactly representable in the destination format, the result is determined according to the applicable rounding-direction attribute [...].

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:

[...] an infinitely precise result with magnitude at least bemax(b − ½b1−p) shall round to ∞ with no change in sign [...].

u128::MAX fits that condition when converted to f32. So by IEEE 754 rules, for what that's worth, the result is defined and has the value ∞.

@aturon

This comment has been minimized.

Copy link
Member

aturon commented Aug 3, 2017

Marking P-medium to match other similar bugs.

alexcrichton added a commit to alexcrichton/rust that referenced this issue Sep 2, 2017

rustc: Flag {i,u}128 as unsafe for FFI
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!

alexcrichton added a commit to alexcrichton/rust that referenced this issue Sep 2, 2017

rustc: Flag {i,u}128 as unsafe for FFI
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!

bors added a commit that referenced this issue Sep 3, 2017

Auto merge of #44261 - alexcrichton:u128-ffi-unsafe, r=eddyb
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!

bors added a commit that referenced this issue Sep 3, 2017

Auto merge of #44261 - alexcrichton:u128-ffi-unsafe, r=eddyb
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!

@bstrie bstrie added the B-unstable label Sep 20, 2017

bors added a commit that referenced this issue Oct 18, 2017

Auto merge of #45205 - rkruppe:saturating-casts, r=<try>
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 also behaves the same way &ndash; even without the -Z flag, because it doesn't affect run time or type checking.
The HIR constant evaluator is unaffected because it currently works correctly for u128->f32 and returns an error for problematic float->casts. That error is conservatively correct and we should be careful to not accept more code in constant expressions.

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

bors added a commit that referenced this issue Nov 7, 2017

Auto merge of #45205 - rkruppe:saturating-casts, r=eddyb
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

bors added a commit that referenced this issue Nov 8, 2017

Auto merge of #45205 - rkruppe:saturating-casts, r=eddyb
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
@rkruppe

This comment has been minimized.

Copy link
Member

rkruppe commented Nov 8, 2017

#45205 has been merged, so there's now an opt-in solution to this issue (-Z saturating-float-casts). Presumably we'll want to make this behavior the default at some point in the future – does anyone have opinions on when that should be and what prerequisites there are (if any)?

@est31

This comment has been minimized.

Copy link
Contributor Author

est31 commented Nov 8, 2017

@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.

@rkruppe

This comment has been minimized.

Copy link
Member

rkruppe commented Nov 8, 2017

@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?

rkruppe added a commit to rkruppe/rust that referenced this issue Nov 9, 2017

Make saturating u128 -> f32 casts the default behavior, rather than
being gated by -Z saturating-float-casts. There are several reasons for
this:

1. 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.
2. Having two orthogonal features under one flag is silly, and never
should have happened in the first place.
3. 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
@rkruppe

This comment has been minimized.

Copy link
Member

rkruppe commented Nov 9, 2017

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.

rkruppe added a commit to rkruppe/rust that referenced this issue Nov 9, 2017

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 rust-lang#41799

rkruppe added a commit to rkruppe/rust that referenced this issue Nov 10, 2017

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 rust-lang#41799

rkruppe added a commit to rkruppe/rust that referenced this issue Nov 10, 2017

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 rust-lang#41799

bors added a commit that referenced this issue Nov 12, 2017

Auto merge of #45900 - rkruppe:u128-to-f32-saturation-by-default, r=a…
…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

@bors bors closed this in #45900 Nov 12, 2017

@nikic

This comment has been minimized.

Copy link
Contributor

nikic commented Jun 28, 2018

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.