From 368a4c6808c49baf5e03f9debeff3d53a013e968 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 3 Aug 2024 11:51:16 +0200 Subject: [PATCH 1/3] float to/from bits and classify: update comments regarding non-conformant hardware --- library/core/src/num/f128.rs | 110 +----------- library/core/src/num/f16.rs | 163 +++-------------- library/core/src/num/f32.rs | 169 +++--------------- library/core/src/num/f64.rs | 143 +++------------ tests/ui/consts/const-float-bits-conv.rs | 22 +++ .../ui/consts/const-float-bits-reject-conv.rs | 68 ------- .../const-float-bits-reject-conv.stderr | 115 ------------ 7 files changed, 107 insertions(+), 683 deletions(-) delete mode 100644 tests/ui/consts/const-float-bits-reject-conv.rs delete mode 100644 tests/ui/consts/const-float-bits-reject-conv.stderr diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs index 0c04f47fe7df1..38e69e7641ab4 100644 --- a/library/core/src/num/f128.rs +++ b/library/core/src/num/f128.rs @@ -290,7 +290,7 @@ impl f128 { #[inline] #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] pub(crate) const fn abs_private(self) -> f128 { - // SAFETY: This transmutation is fine. Probably. For the reasons std is using it. + // SAFETY: This transmutation is fine just like in `to_bits`/`from_bits`. unsafe { mem::transmute::(mem::transmute::(self) & !Self::SIGN_MASK) } @@ -439,22 +439,12 @@ impl f128 { #[unstable(feature = "f128", issue = "116909")] #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] pub const fn classify(self) -> FpCategory { - // Other float types cannot use a bitwise classify because they may suffer a variety - // of errors if the backend chooses to cast to different float types (x87). `f128` cannot - // fit into any other float types so this is not a concern, and we rely on bit patterns. + // Other float types suffer from various platform bugs that violate the usual IEEE semantics + // and also make bitwise classification not always work reliably. However, `f128` cannot fit + // into any other float types so this is not a concern, and we can rely on bit patterns. - // SAFETY: POD bitcast, same as in `to_bits`. - let bits = unsafe { mem::transmute::(self) }; - Self::classify_bits(bits) - } - - /// This operates on bits, and only bits, so it can ignore concerns about weird FPUs. - /// FIXME(jubilee): In a just world, this would be the entire impl for classify, - /// plus a transmute. We do not live in a just world, but we can make it more so. - #[inline] - #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] - const fn classify_bits(b: u128) -> FpCategory { - match (b & Self::MAN_MASK, b & Self::EXP_MASK) { + let bits = self.to_bits(); + match (bits & Self::MAN_MASK, bits & Self::EXP_MASK) { (0, Self::EXP_MASK) => FpCategory::Infinite, (_, Self::EXP_MASK) => FpCategory::Nan, (0, 0) => FpCategory::Zero, @@ -922,48 +912,7 @@ impl f128 { #[must_use = "this returns the result of the operation, without modifying the original"] pub const fn to_bits(self) -> u128 { // SAFETY: `u128` is a plain old datatype so we can always transmute to it. - // ...sorta. - // - // It turns out that at runtime, it is possible for a floating point number - // to be subject to a floating point mode that alters nonzero subnormal numbers - // to zero on reads and writes, aka "denormals are zero" and "flush to zero". - // - // And, of course evaluating to a NaN value is fairly nondeterministic. - // More precisely: when NaN should be returned is knowable, but which NaN? - // So far that's defined by a combination of LLVM and the CPU, not Rust. - // This function, however, allows observing the bitstring of a NaN, - // thus introspection on CTFE. - // - // In order to preserve, at least for the moment, const-to-runtime equivalence, - // we reject any of these possible situations from happening. - #[inline] - #[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")] - const fn ct_f128_to_u128(ct: f128) -> u128 { - // FIXME(f16_f128): we should use `.classify()` like `f32` and `f64`, but that - // is not available on all platforms (needs `netf2` and `unordtf2`). So classify - // the bits instead. - - // SAFETY: this is a POD transmutation - let bits = unsafe { mem::transmute::(ct) }; - match f128::classify_bits(bits) { - FpCategory::Nan => { - panic!("const-eval error: cannot use f128::to_bits on a NaN") - } - FpCategory::Subnormal => { - panic!("const-eval error: cannot use f128::to_bits on a subnormal number") - } - FpCategory::Infinite | FpCategory::Normal | FpCategory::Zero => bits, - } - } - - #[inline(always)] // See https://github.com/rust-lang/compiler-builtins/issues/491 - fn rt_f128_to_u128(x: f128) -> u128 { - // SAFETY: `u128` is a plain old datatype so we can always... uh... - // ...look, just pretend you forgot what you just read. - // Stability concerns. - unsafe { mem::transmute(x) } - } - intrinsics::const_eval_select((self,), ct_f128_to_u128, rt_f128_to_u128) + unsafe { mem::transmute(self) } } /// Raw transmutation from `u128`. @@ -1011,49 +960,8 @@ impl f128 { #[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")] pub const fn from_bits(v: u128) -> Self { // It turns out the safety issues with sNaN were overblown! Hooray! - // SAFETY: `u128` is a plain old datatype so we can always transmute from it - // ...sorta. - // - // It turns out that at runtime, it is possible for a floating point number - // to be subject to floating point modes that alter nonzero subnormal numbers - // to zero on reads and writes, aka "denormals are zero" and "flush to zero". - // This is not a problem usually, but at least one tier2 platform for Rust - // actually exhibits this behavior by default: thumbv7neon - // aka "the Neon FPU in AArch32 state" - // - // And, of course evaluating to a NaN value is fairly nondeterministic. - // More precisely: when NaN should be returned is knowable, but which NaN? - // So far that's defined by a combination of LLVM and the CPU, not Rust. - // This function, however, allows observing the bitstring of a NaN, - // thus introspection on CTFE. - // - // In order to preserve, at least for the moment, const-to-runtime equivalence, - // reject any of these possible situations from happening. - #[inline] - #[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")] - const fn ct_u128_to_f128(ct: u128) -> f128 { - match f128::classify_bits(ct) { - FpCategory::Subnormal => { - panic!("const-eval error: cannot use f128::from_bits on a subnormal number") - } - FpCategory::Nan => { - panic!("const-eval error: cannot use f128::from_bits on NaN") - } - FpCategory::Infinite | FpCategory::Normal | FpCategory::Zero => { - // SAFETY: It's not a frumious number - unsafe { mem::transmute::(ct) } - } - } - } - - #[inline(always)] // See https://github.com/rust-lang/compiler-builtins/issues/491 - fn rt_u128_to_f128(x: u128) -> f128 { - // SAFETY: `u128` is a plain old datatype so we can always... uh... - // ...look, just pretend you forgot what you just read. - // Stability concerns. - unsafe { mem::transmute(x) } - } - intrinsics::const_eval_select((v,), ct_u128_to_f128, rt_u128_to_f128) + // SAFETY: `u128` is a plain old datatype so we can always transmute from it. + unsafe { mem::transmute(v) } } /// Returns the memory representation of this floating point number as a byte array in diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs index e5b1148e19215..41bd34a270238 100644 --- a/library/core/src/num/f16.rs +++ b/library/core/src/num/f16.rs @@ -284,7 +284,7 @@ impl f16 { #[inline] #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] pub(crate) const fn abs_private(self) -> f16 { - // SAFETY: This transmutation is fine. Probably. For the reasons std is using it. + // SAFETY: This transmutation is fine just like in `to_bits`/`from_bits`. unsafe { mem::transmute::(mem::transmute::(self) & !Self::SIGN_MASK) } } @@ -426,15 +426,15 @@ impl f16 { pub const fn classify(self) -> FpCategory { // A previous implementation for f32/f64 tried to only use bitmask-based checks, // using `to_bits` to transmute the float to its bit repr and match on that. - // Unfortunately, floating point numbers can be much worse than that. - // This also needs to not result in recursive evaluations of `to_bits`. + // If we only cared about being "technically" correct, that's an entirely legit + // implementation. // - - // Platforms without native support generally convert to `f32` to perform operations, - // and most of these platforms correctly round back to `f16` after each operation. - // However, some platforms have bugs where they keep the excess `f32` precision (e.g. - // WASM, see llvm/llvm-project#96437). This implementation makes a best-effort attempt - // to account for that excess precision. + // Unfortunately, there are platforms out there that do not correctly implement the IEEE + // float semantics Rust relies on: some hardware flushes denormals to zero, and some + // platforms convert to `f32` to perform operations without properly rounding back (e.g. + // WASM, see llvm/llvm-project#96437). These are platforms bugs, and Rust will misbehave on + // such platforms, but we can at least try to make things seem as sane as possible by being + // careful here. if self.is_infinite() { // Thus, a value may compare unequal to infinity, despite having a "full" exponent mask. FpCategory::Infinite @@ -446,49 +446,20 @@ impl f16 { // as correctness requires avoiding equality tests that may be Subnormal == -0.0 // because it may be wrong under "denormals are zero" and "flush to zero" modes. // Most of std's targets don't use those, but they are used for thumbv7neon. - // So, this does use bitpattern matching for the rest. - - // SAFETY: f16 to u16 is fine. Usually. - // If classify has gotten this far, the value is definitely in one of these categories. - unsafe { f16::partial_classify(self) } - } - } - - /// This doesn't actually return a right answer for NaN on purpose, - /// seeing as how it cannot correctly discern between a floating point NaN, - /// and some normal floating point numbers truncated from an x87 FPU. - /// - /// # Safety - /// - /// This requires making sure you call this function for values it answers correctly on, - /// otherwise it returns a wrong answer. This is not important for memory safety per se, - /// but getting floats correct is important for not accidentally leaking const eval - /// runtime-deviating logic which may or may not be acceptable. - #[inline] - #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] - const unsafe fn partial_classify(self) -> FpCategory { - // SAFETY: The caller is not asking questions for which this will tell lies. - let b = unsafe { mem::transmute::(self) }; - match (b & Self::MAN_MASK, b & Self::EXP_MASK) { - (0, Self::EXP_MASK) => FpCategory::Infinite, - (0, 0) => FpCategory::Zero, - (_, 0) => FpCategory::Subnormal, - _ => FpCategory::Normal, - } - } - - /// This operates on bits, and only bits, so it can ignore concerns about weird FPUs. - /// FIXME(jubilee): In a just world, this would be the entire impl for classify, - /// plus a transmute. We do not live in a just world, but we can make it more so. - #[inline] - #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] - const fn classify_bits(b: u16) -> FpCategory { - match (b & Self::MAN_MASK, b & Self::EXP_MASK) { - (0, Self::EXP_MASK) => FpCategory::Infinite, - (_, Self::EXP_MASK) => FpCategory::Nan, - (0, 0) => FpCategory::Zero, - (_, 0) => FpCategory::Subnormal, - _ => FpCategory::Normal, + // So, this does use bitpattern matching for the rest. On x87, due to the incorrect + // float codegen on this hardware, this doesn't actually return a right answer for NaN + // because it cannot correctly discern between a floating point NaN, and some normal + // floating point numbers truncated from an x87 FPU -- but we took care of NaN above, so + // we are fine. + // FIXME(jubilee): This probably could at least answer things correctly for Infinity, + // like the f64 version does, but I need to run more checks on how things go on x86. + // I fear losing mantissa data that would have answered that differently. + let b = self.to_bits(); + match (b & Self::MAN_MASK, b & Self::EXP_MASK) { + (0, 0) => FpCategory::Zero, + (_, 0) => FpCategory::Subnormal, + _ => FpCategory::Normal, + } } } @@ -952,48 +923,7 @@ impl f16 { #[must_use = "this returns the result of the operation, without modifying the original"] pub const fn to_bits(self) -> u16 { // SAFETY: `u16` is a plain old datatype so we can always transmute to it. - // ...sorta. - // - // It turns out that at runtime, it is possible for a floating point number - // to be subject to a floating point mode that alters nonzero subnormal numbers - // to zero on reads and writes, aka "denormals are zero" and "flush to zero". - // - // And, of course evaluating to a NaN value is fairly nondeterministic. - // More precisely: when NaN should be returned is knowable, but which NaN? - // So far that's defined by a combination of LLVM and the CPU, not Rust. - // This function, however, allows observing the bitstring of a NaN, - // thus introspection on CTFE. - // - // In order to preserve, at least for the moment, const-to-runtime equivalence, - // we reject any of these possible situations from happening. - #[inline] - #[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")] - const fn ct_f16_to_u16(ct: f16) -> u16 { - // FIXME(f16_f128): we should use `.classify()` like `f32` and `f64`, but we don't yet - // want to rely on that on all platforms because it is nondeterministic (e.g. x86 has - // convention discrepancies calling intrinsics). So just classify the bits instead. - - // SAFETY: this is a POD transmutation - let bits = unsafe { mem::transmute::(ct) }; - match f16::classify_bits(bits) { - FpCategory::Nan => { - panic!("const-eval error: cannot use f16::to_bits on a NaN") - } - FpCategory::Subnormal => { - panic!("const-eval error: cannot use f16::to_bits on a subnormal number") - } - FpCategory::Infinite | FpCategory::Normal | FpCategory::Zero => bits, - } - } - - #[inline(always)] // See https://github.com/rust-lang/compiler-builtins/issues/491 - fn rt_f16_to_u16(x: f16) -> u16 { - // SAFETY: `u16` is a plain old datatype so we can always... uh... - // ...look, just pretend you forgot what you just read. - // Stability concerns. - unsafe { mem::transmute(x) } - } - intrinsics::const_eval_select((self,), ct_f16_to_u16, rt_f16_to_u16) + unsafe { mem::transmute(self) } } /// Raw transmutation from `u16`. @@ -1040,49 +970,8 @@ impl f16 { #[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")] pub const fn from_bits(v: u16) -> Self { // It turns out the safety issues with sNaN were overblown! Hooray! - // SAFETY: `u16` is a plain old datatype so we can always transmute from it - // ...sorta. - // - // It turns out that at runtime, it is possible for a floating point number - // to be subject to floating point modes that alter nonzero subnormal numbers - // to zero on reads and writes, aka "denormals are zero" and "flush to zero". - // This is not a problem usually, but at least one tier2 platform for Rust - // actually exhibits this behavior by default: thumbv7neon - // aka "the Neon FPU in AArch32 state" - // - // And, of course evaluating to a NaN value is fairly nondeterministic. - // More precisely: when NaN should be returned is knowable, but which NaN? - // So far that's defined by a combination of LLVM and the CPU, not Rust. - // This function, however, allows observing the bitstring of a NaN, - // thus introspection on CTFE. - // - // In order to preserve, at least for the moment, const-to-runtime equivalence, - // reject any of these possible situations from happening. - #[inline] - #[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")] - const fn ct_u16_to_f16(ct: u16) -> f16 { - match f16::classify_bits(ct) { - FpCategory::Subnormal => { - panic!("const-eval error: cannot use f16::from_bits on a subnormal number") - } - FpCategory::Nan => { - panic!("const-eval error: cannot use f16::from_bits on NaN") - } - FpCategory::Infinite | FpCategory::Normal | FpCategory::Zero => { - // SAFETY: It's not a frumious number - unsafe { mem::transmute::(ct) } - } - } - } - - #[inline(always)] // See https://github.com/rust-lang/compiler-builtins/issues/491 - fn rt_u16_to_f16(x: u16) -> f16 { - // SAFETY: `u16` is a plain old datatype so we can always... uh... - // ...look, just pretend you forgot what you just read. - // Stability concerns. - unsafe { mem::transmute(x) } - } - intrinsics::const_eval_select((v,), ct_u16_to_f16, rt_u16_to_f16) + // SAFETY: `u16` is a plain old datatype so we can always transmute from it. + unsafe { mem::transmute(v) } } /// Returns the memory representation of this floating point number as a byte array in diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index 7710e23edf0ba..719727e2f1e0a 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -529,7 +529,7 @@ impl f32 { #[inline] #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] pub(crate) const fn abs_private(self) -> f32 { - // SAFETY: This transmutation is fine. Probably. For the reasons std is using it. + // SAFETY: This transmutation is fine just like in `to_bits`/`from_bits`. unsafe { mem::transmute::(mem::transmute::(self) & !Self::SIGN_MASK) } } @@ -654,18 +654,20 @@ impl f32 { pub const fn classify(self) -> FpCategory { // A previous implementation tried to only use bitmask-based checks, // using f32::to_bits to transmute the float to its bit repr and match on that. - // Unfortunately, floating point numbers can be much worse than that. - // This also needs to not result in recursive evaluations of f64::to_bits. + // If we only cared about being "technically" correct, that's an entirely legit + // implementation. + // + // Unfortunately, there is hardware out there that does not correctly implement the IEEE + // float semantics Rust relies on: x87 uses a too-large mantissa and exponent, and some + // hardware flushes subnormals to zero. These are platforms bugs, and Rust will misbehave on + // such hardware, but we can at least try to make things seem as sane as possible by being + // careful here. // - // On some processors, in some cases, LLVM will "helpfully" lower floating point ops, - // in spite of a request for them using f32 and f64, to things like x87 operations. - // These have an f64's mantissa, but can have a larger than normal exponent. // FIXME(jubilee): Using x87 operations is never necessary in order to function // on x86 processors for Rust-to-Rust calls, so this issue should not happen. // Code generation should be adjusted to use non-C calling conventions, avoiding this. - // if self.is_infinite() { - // Thus, a value may compare unequal to infinity, despite having a "full" exponent mask. + // A value may compare unequal to infinity, despite having a "full" exponent mask. FpCategory::Infinite } else if self.is_nan() { // And it may not be NaN, as it can simply be an "overextended" finite value. @@ -675,48 +677,20 @@ impl f32 { // as correctness requires avoiding equality tests that may be Subnormal == -0.0 // because it may be wrong under "denormals are zero" and "flush to zero" modes. // Most of std's targets don't use those, but they are used for thumbv7neon. - // So, this does use bitpattern matching for the rest. - - // SAFETY: f32 to u32 is fine. Usually. - // If classify has gotten this far, the value is definitely in one of these categories. - unsafe { f32::partial_classify(self) } - } - } - - // This doesn't actually return a right answer for NaN on purpose, - // seeing as how it cannot correctly discern between a floating point NaN, - // and some normal floating point numbers truncated from an x87 FPU. - // FIXME(jubilee): This probably could at least answer things correctly for Infinity, - // like the f64 version does, but I need to run more checks on how things go on x86. - // I fear losing mantissa data that would have answered that differently. - // - // # Safety - // This requires making sure you call this function for values it answers correctly on, - // otherwise it returns a wrong answer. This is not important for memory safety per se, - // but getting floats correct is important for not accidentally leaking const eval - // runtime-deviating logic which may or may not be acceptable. - #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] - const unsafe fn partial_classify(self) -> FpCategory { - // SAFETY: The caller is not asking questions for which this will tell lies. - let b = unsafe { mem::transmute::(self) }; - match (b & Self::MAN_MASK, b & Self::EXP_MASK) { - (0, 0) => FpCategory::Zero, - (_, 0) => FpCategory::Subnormal, - _ => FpCategory::Normal, - } - } - - // This operates on bits, and only bits, so it can ignore concerns about weird FPUs. - // FIXME(jubilee): In a just world, this would be the entire impl for classify, - // plus a transmute. We do not live in a just world, but we can make it more so. - #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] - const fn classify_bits(b: u32) -> FpCategory { - match (b & Self::MAN_MASK, b & Self::EXP_MASK) { - (0, Self::EXP_MASK) => FpCategory::Infinite, - (_, Self::EXP_MASK) => FpCategory::Nan, - (0, 0) => FpCategory::Zero, - (_, 0) => FpCategory::Subnormal, - _ => FpCategory::Normal, + // So, this does use bitpattern matching for the rest. On x87, due to the incorrect + // float codegen on this hardware, this doesn't actually return a right answer for NaN + // because it cannot correctly discern between a floating point NaN, and some normal + // floating point numbers truncated from an x87 FPU -- but we took care of NaN above, so + // we are fine. + // FIXME(jubilee): This probably could at least answer things correctly for Infinity, + // like the f64 version does, but I need to run more checks on how things go on x86. + // I fear losing mantissa data that would have answered that differently. + let b = self.to_bits(); + match (b & Self::MAN_MASK, b & Self::EXP_MASK) { + (0, 0) => FpCategory::Zero, + (_, 0) => FpCategory::Subnormal, + _ => FpCategory::Normal, + } } } @@ -1143,51 +1117,7 @@ impl f32 { #[inline] pub const fn to_bits(self) -> u32 { // SAFETY: `u32` is a plain old datatype so we can always transmute to it. - // ...sorta. - // - // It turns out that at runtime, it is possible for a floating point number - // to be subject to a floating point mode that alters nonzero subnormal numbers - // to zero on reads and writes, aka "denormals are zero" and "flush to zero". - // This is not a problem per se, but at least one tier2 platform for Rust - // actually exhibits this behavior by default. - // - // In addition, on x86 targets with SSE or SSE2 disabled and the x87 FPU enabled, - // i.e. not soft-float, the way Rust does parameter passing can actually alter - // a number that is "not infinity" to have the same exponent as infinity, - // in a slightly unpredictable manner. - // - // And, of course evaluating to a NaN value is fairly nondeterministic. - // More precisely: when NaN should be returned is knowable, but which NaN? - // So far that's defined by a combination of LLVM and the CPU, not Rust. - // This function, however, allows observing the bitstring of a NaN, - // thus introspection on CTFE. - // - // In order to preserve, at least for the moment, const-to-runtime equivalence, - // we reject any of these possible situations from happening. - #[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")] - const fn ct_f32_to_u32(ct: f32) -> u32 { - match ct.classify() { - FpCategory::Nan => { - panic!("const-eval error: cannot use f32::to_bits on a NaN") - } - FpCategory::Subnormal => { - panic!("const-eval error: cannot use f32::to_bits on a subnormal number") - } - FpCategory::Infinite | FpCategory::Normal | FpCategory::Zero => { - // SAFETY: We have a normal floating point number. Now we transmute, i.e. do a bitcopy. - unsafe { mem::transmute::(ct) } - } - } - } - - #[inline(always)] // See https://github.com/rust-lang/compiler-builtins/issues/491 - fn rt_f32_to_u32(x: f32) -> u32 { - // SAFETY: `u32` is a plain old datatype so we can always... uh... - // ...look, just pretend you forgot what you just read. - // Stability concerns. - unsafe { mem::transmute(x) } - } - intrinsics::const_eval_select((self,), ct_f32_to_u32, rt_f32_to_u32) + unsafe { mem::transmute(self) } } /// Raw transmutation from `u32`. @@ -1232,53 +1162,8 @@ impl f32 { #[inline] pub const fn from_bits(v: u32) -> Self { // It turns out the safety issues with sNaN were overblown! Hooray! - // SAFETY: `u32` is a plain old datatype so we can always transmute from it - // ...sorta. - // - // It turns out that at runtime, it is possible for a floating point number - // to be subject to floating point modes that alter nonzero subnormal numbers - // to zero on reads and writes, aka "denormals are zero" and "flush to zero". - // This is not a problem usually, but at least one tier2 platform for Rust - // actually exhibits this behavior by default: thumbv7neon - // aka "the Neon FPU in AArch32 state" - // - // In addition, on x86 targets with SSE or SSE2 disabled and the x87 FPU enabled, - // i.e. not soft-float, the way Rust does parameter passing can actually alter - // a number that is "not infinity" to have the same exponent as infinity, - // in a slightly unpredictable manner. - // - // And, of course evaluating to a NaN value is fairly nondeterministic. - // More precisely: when NaN should be returned is knowable, but which NaN? - // So far that's defined by a combination of LLVM and the CPU, not Rust. - // This function, however, allows observing the bitstring of a NaN, - // thus introspection on CTFE. - // - // In order to preserve, at least for the moment, const-to-runtime equivalence, - // reject any of these possible situations from happening. - #[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")] - const fn ct_u32_to_f32(ct: u32) -> f32 { - match f32::classify_bits(ct) { - FpCategory::Subnormal => { - panic!("const-eval error: cannot use f32::from_bits on a subnormal number") - } - FpCategory::Nan => { - panic!("const-eval error: cannot use f32::from_bits on NaN") - } - FpCategory::Infinite | FpCategory::Normal | FpCategory::Zero => { - // SAFETY: It's not a frumious number - unsafe { mem::transmute::(ct) } - } - } - } - - #[inline(always)] // See https://github.com/rust-lang/compiler-builtins/issues/491 - fn rt_u32_to_f32(x: u32) -> f32 { - // SAFETY: `u32` is a plain old datatype so we can always... uh... - // ...look, just pretend you forgot what you just read. - // Stability concerns. - unsafe { mem::transmute(x) } - } - intrinsics::const_eval_select((v,), ct_u32_to_f32, rt_u32_to_f32) + // SAFETY: `u32` is a plain old datatype so we can always transmute from it. + unsafe { mem::transmute(v) } } /// Returns the memory representation of this floating point number as a byte array in diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index a89859be7efe8..85eb152ad1f19 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -528,7 +528,7 @@ impl f64 { #[inline] #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] pub(crate) const fn abs_private(self) -> f64 { - // SAFETY: This transmutation is fine. Probably. For the reasons std is using it. + // SAFETY: This transmutation is fine just like in `to_bits`/`from_bits`. unsafe { mem::transmute::(mem::transmute::(self) & !Self::SIGN_MASK) } } @@ -653,12 +653,14 @@ impl f64 { pub const fn classify(self) -> FpCategory { // A previous implementation tried to only use bitmask-based checks, // using f64::to_bits to transmute the float to its bit repr and match on that. - // Unfortunately, floating point numbers can be much worse than that. - // This also needs to not result in recursive evaluations of f64::to_bits. + // If we only cared about being "technically" correct, that's an entirely legit + // implementation. + // + // Unfortunately, there is hardware out there that does not correctly implement the IEEE + // float semantics Rust relies on: x87 uses a too-large exponent, and some hardware flushes + // subnormals to zero. These are platforms bugs, and Rust will misbehave on such hardware, + // but we can at least try to make things seem as sane as possible by being careful here. // - // On some processors, in some cases, LLVM will "helpfully" lower floating point ops, - // in spite of a request for them using f32 and f64, to things like x87 operations. - // These have an f64's mantissa, but can have a larger than normal exponent. // FIXME(jubilee): Using x87 operations is never necessary in order to function // on x86 processors for Rust-to-Rust calls, so this issue should not happen. // Code generation should be adjusted to use non-C calling conventions, avoiding this. @@ -672,41 +674,18 @@ impl f64 { // as correctness requires avoiding equality tests that may be Subnormal == -0.0 // because it may be wrong under "denormals are zero" and "flush to zero" modes. // Most of std's targets don't use those, but they are used for thumbv7neon. - // So, this does use bitpattern matching for the rest. - - // SAFETY: f64 to u64 is fine. Usually. - // If control flow has gotten this far, the value is definitely in one of the categories - // that f64::partial_classify can correctly analyze. - unsafe { f64::partial_classify(self) } - } - } - - // This doesn't actually return a right answer for NaN on purpose, - // seeing as how it cannot correctly discern between a floating point NaN, - // and some normal floating point numbers truncated from an x87 FPU. - #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] - const unsafe fn partial_classify(self) -> FpCategory { - // SAFETY: The caller is not asking questions for which this will tell lies. - let b = unsafe { mem::transmute::(self) }; - match (b & Self::MAN_MASK, b & Self::EXP_MASK) { - (0, Self::EXP_MASK) => FpCategory::Infinite, - (0, 0) => FpCategory::Zero, - (_, 0) => FpCategory::Subnormal, - _ => FpCategory::Normal, - } - } - - // This operates on bits, and only bits, so it can ignore concerns about weird FPUs. - // FIXME(jubilee): In a just world, this would be the entire impl for classify, - // plus a transmute. We do not live in a just world, but we can make it more so. - #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] - const fn classify_bits(b: u64) -> FpCategory { - match (b & Self::MAN_MASK, b & Self::EXP_MASK) { - (0, Self::EXP_MASK) => FpCategory::Infinite, - (_, Self::EXP_MASK) => FpCategory::Nan, - (0, 0) => FpCategory::Zero, - (_, 0) => FpCategory::Subnormal, - _ => FpCategory::Normal, + // So, this does use bitpattern matching for the rest. On x87, due to the incorrect + // float codegen on this hardware, this doesn't actually return a right answer for NaN + // because it cannot correctly discern between a floating point NaN, and some normal + // floating point numbers truncated from an x87 FPU -- but we took care of NaN above, so + // we are fine. + let b = self.to_bits(); + match (b & Self::MAN_MASK, b & Self::EXP_MASK) { + (0, Self::EXP_MASK) => FpCategory::Infinite, + (0, 0) => FpCategory::Zero, + (_, 0) => FpCategory::Subnormal, + _ => FpCategory::Normal, + } } } @@ -1134,33 +1113,7 @@ impl f64 { #[inline] pub const fn to_bits(self) -> u64 { // SAFETY: `u64` is a plain old datatype so we can always transmute to it. - // ...sorta. - // - // See the SAFETY comment in f64::from_bits for more. - #[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")] - const fn ct_f64_to_u64(ct: f64) -> u64 { - match ct.classify() { - FpCategory::Nan => { - panic!("const-eval error: cannot use f64::to_bits on a NaN") - } - FpCategory::Subnormal => { - panic!("const-eval error: cannot use f64::to_bits on a subnormal number") - } - FpCategory::Infinite | FpCategory::Normal | FpCategory::Zero => { - // SAFETY: We have a normal floating point number. Now we transmute, i.e. do a bitcopy. - unsafe { mem::transmute::(ct) } - } - } - } - - #[inline(always)] // See https://github.com/rust-lang/compiler-builtins/issues/491 - fn rt_f64_to_u64(rt: f64) -> u64 { - // SAFETY: `u64` is a plain old datatype so we can always... uh... - // ...look, just pretend you forgot what you just read. - // Stability concerns. - unsafe { mem::transmute::(rt) } - } - intrinsics::const_eval_select((self,), ct_f64_to_u64, rt_f64_to_u64) + unsafe { mem::transmute(self) } } /// Raw transmutation from `u64`. @@ -1205,58 +1158,8 @@ impl f64 { #[inline] pub const fn from_bits(v: u64) -> Self { // It turns out the safety issues with sNaN were overblown! Hooray! - // SAFETY: `u64` is a plain old datatype so we can always transmute from it - // ...sorta. - // - // It turns out that at runtime, it is possible for a floating point number - // to be subject to floating point modes that alter nonzero subnormal numbers - // to zero on reads and writes, aka "denormals are zero" and "flush to zero". - // This is not a problem usually, but at least one tier2 platform for Rust - // actually exhibits an FTZ behavior by default: thumbv7neon - // aka "the Neon FPU in AArch32 state" - // - // Even with this, not all instructions exhibit the FTZ behaviors on thumbv7neon, - // so this should load the same bits if LLVM emits the "correct" instructions, - // but LLVM sometimes makes interesting choices about float optimization, - // and other FPUs may do similar. Thus, it is wise to indulge luxuriously in caution. - // - // In addition, on x86 targets with SSE or SSE2 disabled and the x87 FPU enabled, - // i.e. not soft-float, the way Rust does parameter passing can actually alter - // a number that is "not infinity" to have the same exponent as infinity, - // in a slightly unpredictable manner. - // - // And, of course evaluating to a NaN value is fairly nondeterministic. - // More precisely: when NaN should be returned is knowable, but which NaN? - // So far that's defined by a combination of LLVM and the CPU, not Rust. - // This function, however, allows observing the bitstring of a NaN, - // thus introspection on CTFE. - // - // In order to preserve, at least for the moment, const-to-runtime equivalence, - // reject any of these possible situations from happening. - #[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")] - const fn ct_u64_to_f64(ct: u64) -> f64 { - match f64::classify_bits(ct) { - FpCategory::Subnormal => { - panic!("const-eval error: cannot use f64::from_bits on a subnormal number") - } - FpCategory::Nan => { - panic!("const-eval error: cannot use f64::from_bits on NaN") - } - FpCategory::Infinite | FpCategory::Normal | FpCategory::Zero => { - // SAFETY: It's not a frumious number - unsafe { mem::transmute::(ct) } - } - } - } - - #[inline(always)] // See https://github.com/rust-lang/compiler-builtins/issues/491 - fn rt_u64_to_f64(rt: u64) -> f64 { - // SAFETY: `u64` is a plain old datatype so we can always... uh... - // ...look, just pretend you forgot what you just read. - // Stability concerns. - unsafe { mem::transmute::(rt) } - } - intrinsics::const_eval_select((v,), ct_u64_to_f64, rt_u64_to_f64) + // SAFETY: `u64` is a plain old datatype so we can always transmute from it. + unsafe { mem::transmute(v) } } /// Returns the memory representation of this floating point number as a byte array in diff --git a/tests/ui/consts/const-float-bits-conv.rs b/tests/ui/consts/const-float-bits-conv.rs index ba8db4c23dc9b..74df901394788 100644 --- a/tests/ui/consts/const-float-bits-conv.rs +++ b/tests/ui/consts/const-float-bits-conv.rs @@ -38,6 +38,17 @@ fn f32() { const_assert!(f32::from_bits(0x44a72000), 1337.0); const_assert!(f32::from_ne_bytes(0x44a72000u32.to_ne_bytes()), 1337.0); const_assert!(f32::from_bits(0xc1640000), -14.25); + + // Check that NaNs roundtrip their bits regardless of signalingness + // 0xA is 0b1010; 0x5 is 0b0101 -- so these two together clobbers all the mantissa bits + // ...actually, let's just check that these break. :D + const MASKED_NAN1: u32 = f32::NAN.to_bits() ^ 0x002A_AAAA; + const MASKED_NAN2: u32 = f32::NAN.to_bits() ^ 0x0055_5555; + + const_assert!(f32::from_bits(MASKED_NAN1).is_nan()); + const_assert!(f32::from_bits(MASKED_NAN1).is_nan()); + const_assert!(f32::from_bits(MASKED_NAN1).to_bits(), MASKED_NAN1); + const_assert!(f32::from_bits(MASKED_NAN2).to_bits(), MASKED_NAN2); } fn f64() { @@ -55,6 +66,17 @@ fn f64() { const_assert!(f64::from_bits(0x4094e40000000000), 1337.0); const_assert!(f64::from_ne_bytes(0x4094e40000000000u64.to_ne_bytes()), 1337.0); const_assert!(f64::from_bits(0xc02c800000000000), -14.25); + + // Check that NaNs roundtrip their bits regardless of signalingness + // 0xA is 0b1010; 0x5 is 0b0101 -- so these two together clobbers all the mantissa bits + // ...actually, let's just check that these break. :D + const MASKED_NAN1: u64 = f64::NAN.to_bits() ^ 0x000A_AAAA_AAAA_AAAA; + const MASKED_NAN2: u64 = f64::NAN.to_bits() ^ 0x0005_5555_5555_5555; + + const_assert!(f64::from_bits(MASKED_NAN1).is_nan()); + const_assert!(f64::from_bits(MASKED_NAN1).is_nan()); + const_assert!(f64::from_bits(MASKED_NAN1).to_bits(), MASKED_NAN1); + const_assert!(f64::from_bits(MASKED_NAN2).to_bits(), MASKED_NAN2); } fn main() { diff --git a/tests/ui/consts/const-float-bits-reject-conv.rs b/tests/ui/consts/const-float-bits-reject-conv.rs deleted file mode 100644 index febb272869a37..0000000000000 --- a/tests/ui/consts/const-float-bits-reject-conv.rs +++ /dev/null @@ -1,68 +0,0 @@ -//@ compile-flags: -Zmir-opt-level=0 -//@ error-pattern: cannot use f32::to_bits on a NaN -#![feature(const_float_bits_conv)] -#![feature(const_float_classify)] - -// Don't promote -const fn nop(x: T) -> T { x } - -macro_rules! const_assert { - ($a:expr) => { - { - const _: () = assert!($a); - assert!(nop($a)); - } - }; - ($a:expr, $b:expr) => { - { - const _: () = assert!($a == $b); - assert_eq!(nop($a), nop($b)); - } - }; -} - -fn f32() { - // Check that NaNs roundtrip their bits regardless of signalingness - // 0xA is 0b1010; 0x5 is 0b0101 -- so these two together clobbers all the mantissa bits - // ...actually, let's just check that these break. :D - const MASKED_NAN1: u32 = f32::NAN.to_bits() ^ 0x002A_AAAA; - //~^ inside - const MASKED_NAN2: u32 = f32::NAN.to_bits() ^ 0x0055_5555; - //~^ inside - - // The rest of the code is dead because the constants already fail to evaluate. - - const_assert!(f32::from_bits(MASKED_NAN1).is_nan()); - const_assert!(f32::from_bits(MASKED_NAN1).is_nan()); - - // LLVM does not guarantee that loads and stores of NaNs preserve their exact bit pattern. - // In practice, this seems to only cause a problem on x86, since the most widely used calling - // convention mandates that floating point values are returned on the x87 FPU stack. See #73328. - // However, during CTFE we still preserve bit patterns (though that is not a guarantee). - const_assert!(f32::from_bits(MASKED_NAN1).to_bits(), MASKED_NAN1); - const_assert!(f32::from_bits(MASKED_NAN2).to_bits(), MASKED_NAN2); -} - -fn f64() { - // Check that NaNs roundtrip their bits regardless of signalingness - // 0xA is 0b1010; 0x5 is 0b0101 -- so these two together clobbers all the mantissa bits - // ...actually, let's just check that these break. :D - const MASKED_NAN1: u64 = f64::NAN.to_bits() ^ 0x000A_AAAA_AAAA_AAAA; - //~^ inside - const MASKED_NAN2: u64 = f64::NAN.to_bits() ^ 0x0005_5555_5555_5555; - //~^ inside - - // The rest of the code is dead because the constants already fail to evaluate. - - const_assert!(f64::from_bits(MASKED_NAN1).is_nan()); - const_assert!(f64::from_bits(MASKED_NAN1).is_nan()); - - // See comment above. - const_assert!(f64::from_bits(MASKED_NAN1).to_bits(), MASKED_NAN1); - const_assert!(f64::from_bits(MASKED_NAN2).to_bits(), MASKED_NAN2); -} - -fn main() { - f32(); - f64(); -} diff --git a/tests/ui/consts/const-float-bits-reject-conv.stderr b/tests/ui/consts/const-float-bits-reject-conv.stderr deleted file mode 100644 index 1511dab12b0e3..0000000000000 --- a/tests/ui/consts/const-float-bits-reject-conv.stderr +++ /dev/null @@ -1,115 +0,0 @@ -error[E0080]: evaluation of constant value failed - --> $SRC_DIR/core/src/num/f32.rs:LL:COL - | - = note: the evaluated program panicked at 'const-eval error: cannot use f32::to_bits on a NaN', $SRC_DIR/core/src/num/f32.rs:LL:COL - | -note: inside `core::f32::::to_bits::ct_f32_to_u32` - --> $SRC_DIR/core/src/num/f32.rs:LL:COL -note: inside `core::f32::::to_bits` - --> $SRC_DIR/core/src/num/f32.rs:LL:COL -note: inside `f32::MASKED_NAN1` - --> $DIR/const-float-bits-reject-conv.rs:28:30 - | -LL | const MASKED_NAN1: u32 = f32::NAN.to_bits() ^ 0x002A_AAAA; - | ^^^^^^^^^^^^^^^^^^ - = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0080]: evaluation of constant value failed - --> $SRC_DIR/core/src/num/f32.rs:LL:COL - | - = note: the evaluated program panicked at 'const-eval error: cannot use f32::to_bits on a NaN', $SRC_DIR/core/src/num/f32.rs:LL:COL - | -note: inside `core::f32::::to_bits::ct_f32_to_u32` - --> $SRC_DIR/core/src/num/f32.rs:LL:COL -note: inside `core::f32::::to_bits` - --> $SRC_DIR/core/src/num/f32.rs:LL:COL -note: inside `f32::MASKED_NAN2` - --> $DIR/const-float-bits-reject-conv.rs:30:30 - | -LL | const MASKED_NAN2: u32 = f32::NAN.to_bits() ^ 0x0055_5555; - | ^^^^^^^^^^^^^^^^^^ - = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info) - -note: erroneous constant encountered - --> $DIR/const-float-bits-reject-conv.rs:35:34 - | -LL | const_assert!(f32::from_bits(MASKED_NAN1).is_nan()); - | ^^^^^^^^^^^ - -note: erroneous constant encountered - --> $DIR/const-float-bits-reject-conv.rs:36:34 - | -LL | const_assert!(f32::from_bits(MASKED_NAN1).is_nan()); - | ^^^^^^^^^^^ - -note: erroneous constant encountered - --> $DIR/const-float-bits-reject-conv.rs:42:34 - | -LL | const_assert!(f32::from_bits(MASKED_NAN1).to_bits(), MASKED_NAN1); - | ^^^^^^^^^^^ - -note: erroneous constant encountered - --> $DIR/const-float-bits-reject-conv.rs:43:34 - | -LL | const_assert!(f32::from_bits(MASKED_NAN2).to_bits(), MASKED_NAN2); - | ^^^^^^^^^^^ - -error[E0080]: evaluation of constant value failed - --> $SRC_DIR/core/src/num/f64.rs:LL:COL - | - = note: the evaluated program panicked at 'const-eval error: cannot use f64::to_bits on a NaN', $SRC_DIR/core/src/num/f64.rs:LL:COL - | -note: inside `core::f64::::to_bits::ct_f64_to_u64` - --> $SRC_DIR/core/src/num/f64.rs:LL:COL -note: inside `core::f64::::to_bits` - --> $SRC_DIR/core/src/num/f64.rs:LL:COL -note: inside `f64::MASKED_NAN1` - --> $DIR/const-float-bits-reject-conv.rs:50:30 - | -LL | const MASKED_NAN1: u64 = f64::NAN.to_bits() ^ 0x000A_AAAA_AAAA_AAAA; - | ^^^^^^^^^^^^^^^^^^ - = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0080]: evaluation of constant value failed - --> $SRC_DIR/core/src/num/f64.rs:LL:COL - | - = note: the evaluated program panicked at 'const-eval error: cannot use f64::to_bits on a NaN', $SRC_DIR/core/src/num/f64.rs:LL:COL - | -note: inside `core::f64::::to_bits::ct_f64_to_u64` - --> $SRC_DIR/core/src/num/f64.rs:LL:COL -note: inside `core::f64::::to_bits` - --> $SRC_DIR/core/src/num/f64.rs:LL:COL -note: inside `f64::MASKED_NAN2` - --> $DIR/const-float-bits-reject-conv.rs:52:30 - | -LL | const MASKED_NAN2: u64 = f64::NAN.to_bits() ^ 0x0005_5555_5555_5555; - | ^^^^^^^^^^^^^^^^^^ - = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info) - -note: erroneous constant encountered - --> $DIR/const-float-bits-reject-conv.rs:57:34 - | -LL | const_assert!(f64::from_bits(MASKED_NAN1).is_nan()); - | ^^^^^^^^^^^ - -note: erroneous constant encountered - --> $DIR/const-float-bits-reject-conv.rs:58:34 - | -LL | const_assert!(f64::from_bits(MASKED_NAN1).is_nan()); - | ^^^^^^^^^^^ - -note: erroneous constant encountered - --> $DIR/const-float-bits-reject-conv.rs:61:34 - | -LL | const_assert!(f64::from_bits(MASKED_NAN1).to_bits(), MASKED_NAN1); - | ^^^^^^^^^^^ - -note: erroneous constant encountered - --> $DIR/const-float-bits-reject-conv.rs:62:34 - | -LL | const_assert!(f64::from_bits(MASKED_NAN2).to_bits(), MASKED_NAN2); - | ^^^^^^^^^^^ - -error: aborting due to 4 previous errors - -For more information about this error, try `rustc --explain E0080`. From 53e1a2ee46cbcf84a61464bf62d9543be2c1ce8b Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 16 Aug 2024 23:08:29 +0200 Subject: [PATCH 2/3] disable problematic float-conv tests in i586 targets also fix typo in const-float-bits-conv --- tests/ui/consts/const-float-bits-conv.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tests/ui/consts/const-float-bits-conv.rs b/tests/ui/consts/const-float-bits-conv.rs index 74df901394788..aad750c4c975b 100644 --- a/tests/ui/consts/const-float-bits-conv.rs +++ b/tests/ui/consts/const-float-bits-conv.rs @@ -23,6 +23,11 @@ macro_rules! const_assert { }; } +fn has_broken_floats() -> bool { + // i586 targets are broken due to . + std::env::var("TARGET").is_ok_and(|v| v.contains("i586")) +} + fn f32() { const_assert!((1f32).to_bits(), 0x3f800000); const_assert!(u32::from_be_bytes(1f32.to_be_bytes()), 0x3f800000); @@ -41,14 +46,15 @@ fn f32() { // Check that NaNs roundtrip their bits regardless of signalingness // 0xA is 0b1010; 0x5 is 0b0101 -- so these two together clobbers all the mantissa bits - // ...actually, let's just check that these break. :D const MASKED_NAN1: u32 = f32::NAN.to_bits() ^ 0x002A_AAAA; const MASKED_NAN2: u32 = f32::NAN.to_bits() ^ 0x0055_5555; const_assert!(f32::from_bits(MASKED_NAN1).is_nan()); - const_assert!(f32::from_bits(MASKED_NAN1).is_nan()); + const_assert!(f32::from_bits(MASKED_NAN2).is_nan()); const_assert!(f32::from_bits(MASKED_NAN1).to_bits(), MASKED_NAN1); - const_assert!(f32::from_bits(MASKED_NAN2).to_bits(), MASKED_NAN2); + if !has_broken_floats() { + const_assert!(f32::from_bits(MASKED_NAN2).to_bits(), MASKED_NAN2); + } } fn f64() { @@ -69,14 +75,15 @@ fn f64() { // Check that NaNs roundtrip their bits regardless of signalingness // 0xA is 0b1010; 0x5 is 0b0101 -- so these two together clobbers all the mantissa bits - // ...actually, let's just check that these break. :D const MASKED_NAN1: u64 = f64::NAN.to_bits() ^ 0x000A_AAAA_AAAA_AAAA; const MASKED_NAN2: u64 = f64::NAN.to_bits() ^ 0x0005_5555_5555_5555; const_assert!(f64::from_bits(MASKED_NAN1).is_nan()); - const_assert!(f64::from_bits(MASKED_NAN1).is_nan()); + const_assert!(f64::from_bits(MASKED_NAN2).is_nan()); const_assert!(f64::from_bits(MASKED_NAN1).to_bits(), MASKED_NAN1); - const_assert!(f64::from_bits(MASKED_NAN2).to_bits(), MASKED_NAN2); + if !has_broken_floats() { + const_assert!(f64::from_bits(MASKED_NAN2).to_bits(), MASKED_NAN2); + } } fn main() { From 5f33085a7f574b95609da59b2d4164238d7782f0 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 17 Aug 2024 10:25:58 +0200 Subject: [PATCH 3/3] more clear NAN names and fix broken_floats logic Co-authored-by: Jubilee <46493976+workingjubilee@users.noreply.github.com> --- tests/ui/consts/const-float-bits-conv.rs | 26 +++++++++++++----------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/tests/ui/consts/const-float-bits-conv.rs b/tests/ui/consts/const-float-bits-conv.rs index aad750c4c975b..45e8ea570ed73 100644 --- a/tests/ui/consts/const-float-bits-conv.rs +++ b/tests/ui/consts/const-float-bits-conv.rs @@ -46,14 +46,15 @@ fn f32() { // Check that NaNs roundtrip their bits regardless of signalingness // 0xA is 0b1010; 0x5 is 0b0101 -- so these two together clobbers all the mantissa bits - const MASKED_NAN1: u32 = f32::NAN.to_bits() ^ 0x002A_AAAA; - const MASKED_NAN2: u32 = f32::NAN.to_bits() ^ 0x0055_5555; + // NOTE: These names assume `f{BITS}::NAN` is a quiet NAN and IEEE754-2008's NaN rules apply! + const QUIET_NAN: u32 = f32::NAN.to_bits() ^ 0x002A_AAAA; + const SIGNALING_NAN: u32 = f32::NAN.to_bits() ^ 0x0055_5555; - const_assert!(f32::from_bits(MASKED_NAN1).is_nan()); - const_assert!(f32::from_bits(MASKED_NAN2).is_nan()); - const_assert!(f32::from_bits(MASKED_NAN1).to_bits(), MASKED_NAN1); + const_assert!(f32::from_bits(QUIET_NAN).is_nan()); + const_assert!(f32::from_bits(SIGNALING_NAN).is_nan()); + const_assert!(f32::from_bits(QUIET_NAN).to_bits(), QUIET_NAN); if !has_broken_floats() { - const_assert!(f32::from_bits(MASKED_NAN2).to_bits(), MASKED_NAN2); + const_assert!(f32::from_bits(SIGNALING_NAN).to_bits(), SIGNALING_NAN); } } @@ -75,14 +76,15 @@ fn f64() { // Check that NaNs roundtrip their bits regardless of signalingness // 0xA is 0b1010; 0x5 is 0b0101 -- so these two together clobbers all the mantissa bits - const MASKED_NAN1: u64 = f64::NAN.to_bits() ^ 0x000A_AAAA_AAAA_AAAA; - const MASKED_NAN2: u64 = f64::NAN.to_bits() ^ 0x0005_5555_5555_5555; + // NOTE: These names assume `f{BITS}::NAN` is a quiet NAN and IEEE754-2008's NaN rules apply! + const QUIET_NAN: u64 = f64::NAN.to_bits() ^ 0x0005_5555_5555_5555; + const SIGNALING_NAN: u64 = f64::NAN.to_bits() ^ 0x000A_AAAA_AAAA_AAAA; - const_assert!(f64::from_bits(MASKED_NAN1).is_nan()); - const_assert!(f64::from_bits(MASKED_NAN2).is_nan()); - const_assert!(f64::from_bits(MASKED_NAN1).to_bits(), MASKED_NAN1); + const_assert!(f64::from_bits(QUIET_NAN).is_nan()); + const_assert!(f64::from_bits(SIGNALING_NAN).is_nan()); + const_assert!(f64::from_bits(QUIET_NAN).to_bits(), QUIET_NAN); if !has_broken_floats() { - const_assert!(f64::from_bits(MASKED_NAN2).to_bits(), MASKED_NAN2); + const_assert!(f64::from_bits(SIGNALING_NAN).to_bits(), SIGNALING_NAN); } }