Auto merge of #95971 - workingjubilee:no-weird-fp-in-const, r=oli-obk
No "weird" floats in const fn {from,to}_bits I suspect this code is subtly incorrect and that we don't even e.g. use x87-style floats in CTFE, so I don't have to guard against that case. A future PR will be hopefully removing them from concern entirely, anyways. But at the moment I wanted to get this rolling because small questions like that one seem best answered by review. r? `@oli-obk` cc `@eddyb` `@thomcc`
This commit is contained in:
commit
1e9aa8a96b
5 changed files with 479 additions and 48 deletions
|
@ -449,7 +449,8 @@ impl f32 {
|
|||
#[inline]
|
||||
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
|
||||
pub(crate) const fn abs_private(self) -> f32 {
|
||||
f32::from_bits(self.to_bits() & 0x7fff_ffff)
|
||||
// SAFETY: This transmutation is fine. Probably. For the reasons std is using it.
|
||||
unsafe { mem::transmute::<u32, f32>(mem::transmute::<f32, u32>(self) & 0x7fff_ffff) }
|
||||
}
|
||||
|
||||
/// Returns `true` if this value is positive infinity or negative infinity, and
|
||||
|
@ -472,7 +473,10 @@ impl f32 {
|
|||
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
|
||||
#[inline]
|
||||
pub const fn is_infinite(self) -> bool {
|
||||
self.abs_private() == Self::INFINITY
|
||||
// Getting clever with transmutation can result in incorrect answers on some FPUs
|
||||
// FIXME: alter the Rust <-> Rust calling convention to prevent this problem.
|
||||
// See https://github.com/rust-lang/rust/issues/72327
|
||||
(self == f32::INFINITY) | (self == f32::NEG_INFINITY)
|
||||
}
|
||||
|
||||
/// Returns `true` if this number is neither infinite nor `NaN`.
|
||||
|
@ -568,15 +572,76 @@ impl f32 {
|
|||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
|
||||
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.
|
||||
//
|
||||
// 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.
|
||||
FpCategory::Infinite
|
||||
} else if self.is_nan() {
|
||||
// And it may not be NaN, as it can simply be an "overextended" finite value.
|
||||
FpCategory::Nan
|
||||
} else {
|
||||
// However, std can't simply compare to zero to check for zero, either,
|
||||
// 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 {
|
||||
const EXP_MASK: u32 = 0x7f800000;
|
||||
const MAN_MASK: u32 = 0x007fffff;
|
||||
|
||||
let bits = self.to_bits();
|
||||
match (bits & MAN_MASK, bits & EXP_MASK) {
|
||||
// SAFETY: The caller is not asking questions for which this will tell lies.
|
||||
let b = unsafe { mem::transmute::<f32, u32>(self) };
|
||||
match (b & MAN_MASK, b & 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 {
|
||||
const EXP_MASK: u32 = 0x7f800000;
|
||||
const MAN_MASK: u32 = 0x007fffff;
|
||||
|
||||
match (b & MAN_MASK, b & EXP_MASK) {
|
||||
(0, EXP_MASK) => FpCategory::Infinite,
|
||||
(_, EXP_MASK) => FpCategory::Nan,
|
||||
(0, 0) => FpCategory::Zero,
|
||||
(_, 0) => FpCategory::Subnormal,
|
||||
_ => FpCategory::Normal,
|
||||
}
|
||||
}
|
||||
|
@ -616,7 +681,8 @@ impl f32 {
|
|||
pub const fn is_sign_negative(self) -> bool {
|
||||
// IEEE754 says: isSignMinus(x) is true if and only if x has negative sign. isSignMinus
|
||||
// applies to zeros and NaNs as well.
|
||||
self.to_bits() & 0x8000_0000 != 0
|
||||
// SAFETY: This is just transmuting to get the sign bit, it's fine.
|
||||
unsafe { mem::transmute::<f32, u32>(self) & 0x8000_0000 != 0 }
|
||||
}
|
||||
|
||||
/// Takes the reciprocal (inverse) of a number, `1/x`.
|
||||
|
@ -831,8 +897,49 @@ impl f32 {
|
|||
#[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")]
|
||||
#[inline]
|
||||
pub const fn to_bits(self) -> u32 {
|
||||
// SAFETY: `u32` is a plain old datatype so we can always transmute to it
|
||||
unsafe { mem::transmute(self) }
|
||||
// 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::<f32, u32>(ct) }
|
||||
}
|
||||
}
|
||||
}
|
||||
// SAFETY: `u32` is a plain old datatype so we can always... uh...
|
||||
// ...look, just pretend you forgot what you just read.
|
||||
// Stability concerns.
|
||||
let rt_f32_to_u32 = |rt| unsafe { mem::transmute::<f32, u32>(rt) };
|
||||
// SAFETY: We use internal implementations that either always work or fail at compile time.
|
||||
unsafe { intrinsics::const_eval_select((self,), ct_f32_to_u32, rt_f32_to_u32) }
|
||||
}
|
||||
|
||||
/// Raw transmutation from `u32`.
|
||||
|
@ -876,9 +983,51 @@ impl f32 {
|
|||
#[must_use]
|
||||
#[inline]
|
||||
pub const fn from_bits(v: u32) -> Self {
|
||||
// SAFETY: `u32` is a plain old datatype so we can always transmute from it
|
||||
// It turns out the safety issues with sNaN were overblown! Hooray!
|
||||
unsafe { mem::transmute(v) }
|
||||
// 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::<u32, f32>(ct) }
|
||||
}
|
||||
}
|
||||
}
|
||||
// SAFETY: `u32` is a plain old datatype so we can always... uh...
|
||||
// ...look, just pretend you forgot what you just read.
|
||||
// Stability concerns.
|
||||
let rt_u32_to_f32 = |rt| unsafe { mem::transmute::<u32, f32>(rt) };
|
||||
// SAFETY: We use internal implementations that either always work or fail at compile time.
|
||||
unsafe { intrinsics::const_eval_select((v,), ct_u32_to_f32, rt_u32_to_f32) }
|
||||
}
|
||||
|
||||
/// Return the memory representation of this floating point number as a byte array in
|
||||
|
|
|
@ -448,7 +448,10 @@ impl f64 {
|
|||
#[inline]
|
||||
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
|
||||
pub(crate) const fn abs_private(self) -> f64 {
|
||||
f64::from_bits(self.to_bits() & 0x7fff_ffff_ffff_ffff)
|
||||
// SAFETY: This transmutation is fine. Probably. For the reasons std is using it.
|
||||
unsafe {
|
||||
mem::transmute::<u64, f64>(mem::transmute::<f64, u64>(self) & 0x7fff_ffff_ffff_ffff)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this value is positive infinity or negative infinity, and
|
||||
|
@ -471,7 +474,10 @@ impl f64 {
|
|||
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
|
||||
#[inline]
|
||||
pub const fn is_infinite(self) -> bool {
|
||||
self.abs_private() == Self::INFINITY
|
||||
// Getting clever with transmutation can result in incorrect answers on some FPUs
|
||||
// FIXME: alter the Rust <-> Rust calling convention to prevent this problem.
|
||||
// See https://github.com/rust-lang/rust/issues/72327
|
||||
(self == f64::INFINITY) | (self == f64::NEG_INFINITY)
|
||||
}
|
||||
|
||||
/// Returns `true` if this number is neither infinite nor `NaN`.
|
||||
|
@ -567,15 +573,67 @@ impl f64 {
|
|||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
|
||||
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.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Thus, a value may compare unequal to infinity, despite having a "full" exponent mask.
|
||||
// And it may not be NaN, as it can simply be an "overextended" finite value.
|
||||
if self.is_nan() {
|
||||
FpCategory::Nan
|
||||
} else {
|
||||
// However, std can't simply compare to zero to check for zero, either,
|
||||
// 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 {
|
||||
const EXP_MASK: u64 = 0x7ff0000000000000;
|
||||
const MAN_MASK: u64 = 0x000fffffffffffff;
|
||||
|
||||
let bits = self.to_bits();
|
||||
match (bits & MAN_MASK, bits & EXP_MASK) {
|
||||
// SAFETY: The caller is not asking questions for which this will tell lies.
|
||||
let b = unsafe { mem::transmute::<f64, u64>(self) };
|
||||
match (b & MAN_MASK, b & EXP_MASK) {
|
||||
(0, 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 {
|
||||
const EXP_MASK: u64 = 0x7ff0000000000000;
|
||||
const MAN_MASK: u64 = 0x000fffffffffffff;
|
||||
|
||||
match (b & MAN_MASK, b & EXP_MASK) {
|
||||
(0, EXP_MASK) => FpCategory::Infinite,
|
||||
(_, EXP_MASK) => FpCategory::Nan,
|
||||
(0, 0) => FpCategory::Zero,
|
||||
(_, 0) => FpCategory::Subnormal,
|
||||
_ => FpCategory::Normal,
|
||||
}
|
||||
}
|
||||
|
@ -622,7 +680,10 @@ impl f64 {
|
|||
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
|
||||
#[inline]
|
||||
pub const fn is_sign_negative(self) -> bool {
|
||||
self.to_bits() & 0x8000_0000_0000_0000 != 0
|
||||
// IEEE754 says: isSignMinus(x) is true if and only if x has negative sign. isSignMinus
|
||||
// applies to zeros and NaNs as well.
|
||||
// SAFETY: This is just transmuting to get the sign bit, it's fine.
|
||||
unsafe { mem::transmute::<f64, u64>(self) & 0x8000_0000_0000_0000 != 0 }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
|
@ -847,8 +908,31 @@ impl f64 {
|
|||
#[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")]
|
||||
#[inline]
|
||||
pub const fn to_bits(self) -> u64 {
|
||||
// SAFETY: `u64` is a plain old datatype so we can always transmute to it
|
||||
unsafe { mem::transmute(self) }
|
||||
// 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::<f64, u64>(ct) }
|
||||
}
|
||||
}
|
||||
}
|
||||
// SAFETY: `u64` is a plain old datatype so we can always... uh...
|
||||
// ...look, just pretend you forgot what you just read.
|
||||
// Stability concerns.
|
||||
let rt_f64_to_u64 = |rt| unsafe { mem::transmute::<f64, u64>(rt) };
|
||||
// SAFETY: We use internal implementations that either always work or fail at compile time.
|
||||
unsafe { intrinsics::const_eval_select((self,), ct_f64_to_u64, rt_f64_to_u64) }
|
||||
}
|
||||
|
||||
/// Raw transmutation from `u64`.
|
||||
|
@ -892,9 +976,56 @@ impl f64 {
|
|||
#[must_use]
|
||||
#[inline]
|
||||
pub const fn from_bits(v: u64) -> Self {
|
||||
// SAFETY: `u64` is a plain old datatype so we can always transmute from it
|
||||
// It turns out the safety issues with sNaN were overblown! Hooray!
|
||||
unsafe { mem::transmute(v) }
|
||||
// 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::<u64, f64>(ct) }
|
||||
}
|
||||
}
|
||||
}
|
||||
// SAFETY: `u64` is a plain old datatype so we can always... uh...
|
||||
// ...look, just pretend you forgot what you just read.
|
||||
// Stability concerns.
|
||||
let rt_u64_to_f64 = |rt| unsafe { mem::transmute::<u64, f64>(rt) };
|
||||
// SAFETY: We use internal implementations that either always work or fail at compile time.
|
||||
unsafe { intrinsics::const_eval_select((v,), ct_u64_to_f64, rt_u64_to_f64) }
|
||||
}
|
||||
|
||||
/// Return the memory representation of this floating point number as a byte array in
|
||||
|
|
|
@ -37,22 +37,6 @@ 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
|
||||
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());
|
||||
|
||||
// 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.
|
||||
if !cfg!(target_arch = "x86") {
|
||||
const_assert!(f32::from_bits(MASKED_NAN1).to_bits(), MASKED_NAN1);
|
||||
const_assert!(f32::from_bits(MASKED_NAN2).to_bits(), MASKED_NAN2);
|
||||
}
|
||||
}
|
||||
|
||||
fn f64() {
|
||||
|
@ -70,20 +54,6 @@ 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
|
||||
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());
|
||||
|
||||
// See comment above.
|
||||
if !cfg!(target_arch = "x86") {
|
||||
const_assert!(f64::from_bits(MASKED_NAN1).to_bits(), MASKED_NAN1);
|
||||
const_assert!(f64::from_bits(MASKED_NAN2).to_bits(), MASKED_NAN2);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
|
62
src/test/ui/consts/const-float-bits-reject-conv.rs
Normal file
62
src/test/ui/consts/const-float-bits-reject-conv.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
// compile-flags: -Zmir-opt-level=0
|
||||
#![feature(const_float_bits_conv)]
|
||||
#![feature(const_float_classify)]
|
||||
|
||||
// Don't promote
|
||||
const fn nop<T>(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;
|
||||
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());
|
||||
|
||||
// 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.
|
||||
if !cfg!(target_arch = "x86") {
|
||||
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;
|
||||
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());
|
||||
|
||||
// See comment above.
|
||||
if !cfg!(target_arch = "x86") {
|
||||
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();
|
||||
}
|
119
src/test/ui/consts/const-float-bits-reject-conv.stderr
Normal file
119
src/test/ui/consts/const-float-bits-reject-conv.stderr
Normal file
|
@ -0,0 +1,119 @@
|
|||
error[E0080]: evaluation of constant value failed
|
||||
--> $SRC_DIR/core/src/num/f32.rs:LL:COL
|
||||
|
|
||||
LL | panic!("const-eval error: cannot use f32::to_bits on a NaN")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
| |
|
||||
| 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
|
||||
| inside `core::f32::<impl f32>::to_bits::ct_f32_to_u32` at $SRC_DIR/core/src/panic.rs:LL:COL
|
||||
...
|
||||
LL | unsafe { intrinsics::const_eval_select((self,), ct_f32_to_u32, rt_f32_to_u32) }
|
||||
| -------------------------------------------------------------------- inside `core::f32::<impl f32>::to_bits` at $SRC_DIR/core/src/num/f32.rs:LL:COL
|
||||
|
|
||||
::: $SRC_DIR/core/src/ops/function.rs:LL:COL
|
||||
|
|
||||
LL | extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
|
||||
| ------------------------------------------------------------------ inside `<fn(f32) -> u32 {core::f32::<impl f32>::to_bits::ct_f32_to_u32} as FnOnce<(f32,)>>::call_once - shim(fn(f32) -> u32 {core::f32::<impl f32>::to_bits::ct_f32_to_u32})` at $SRC_DIR/core/src/ops/function.rs:LL:COL
|
||||
|
|
||||
::: $SRC_DIR/core/src/intrinsics.rs:LL:COL
|
||||
|
|
||||
LL | called_in_const.call_once(arg)
|
||||
| ------------------------------ inside `const_eval_select::<(f32,), fn(f32) -> u32 {core::f32::<impl f32>::to_bits::ct_f32_to_u32}, [closure@core::f32::<impl f32>::to_bits::{closure#0}], u32>` at $SRC_DIR/core/src/intrinsics.rs:LL:COL
|
||||
|
|
||||
::: $DIR/const-float-bits-reject-conv.rs:27:30
|
||||
|
|
||||
LL | const MASKED_NAN1: u32 = f32::NAN.to_bits() ^ 0x002A_AAAA;
|
||||
| ------------------ inside `f32::MASKED_NAN1` at $DIR/const-float-bits-reject-conv.rs:27:30
|
||||
|
|
||||
= note: this error originates in the macro `$crate::panic::panic_2021` (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
|
||||
|
|
||||
LL | panic!("const-eval error: cannot use f32::to_bits on a NaN")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
| |
|
||||
| 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
|
||||
| inside `core::f32::<impl f32>::to_bits::ct_f32_to_u32` at $SRC_DIR/core/src/panic.rs:LL:COL
|
||||
...
|
||||
LL | unsafe { intrinsics::const_eval_select((self,), ct_f32_to_u32, rt_f32_to_u32) }
|
||||
| -------------------------------------------------------------------- inside `core::f32::<impl f32>::to_bits` at $SRC_DIR/core/src/num/f32.rs:LL:COL
|
||||
|
|
||||
::: $SRC_DIR/core/src/ops/function.rs:LL:COL
|
||||
|
|
||||
LL | extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
|
||||
| ------------------------------------------------------------------ inside `<fn(f32) -> u32 {core::f32::<impl f32>::to_bits::ct_f32_to_u32} as FnOnce<(f32,)>>::call_once - shim(fn(f32) -> u32 {core::f32::<impl f32>::to_bits::ct_f32_to_u32})` at $SRC_DIR/core/src/ops/function.rs:LL:COL
|
||||
|
|
||||
::: $SRC_DIR/core/src/intrinsics.rs:LL:COL
|
||||
|
|
||||
LL | called_in_const.call_once(arg)
|
||||
| ------------------------------ inside `const_eval_select::<(f32,), fn(f32) -> u32 {core::f32::<impl f32>::to_bits::ct_f32_to_u32}, [closure@core::f32::<impl f32>::to_bits::{closure#0}], u32>` at $SRC_DIR/core/src/intrinsics.rs:LL:COL
|
||||
|
|
||||
::: $DIR/const-float-bits-reject-conv.rs:28:30
|
||||
|
|
||||
LL | const MASKED_NAN2: u32 = f32::NAN.to_bits() ^ 0x0055_5555;
|
||||
| ------------------ inside `f32::MASKED_NAN2` at $DIR/const-float-bits-reject-conv.rs:28:30
|
||||
|
|
||||
= note: this error originates in the macro `$crate::panic::panic_2021` (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
|
||||
|
|
||||
LL | panic!("const-eval error: cannot use f64::to_bits on a NaN")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
| |
|
||||
| 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
|
||||
| inside `core::f64::<impl f64>::to_bits::ct_f64_to_u64` at $SRC_DIR/core/src/panic.rs:LL:COL
|
||||
...
|
||||
LL | unsafe { intrinsics::const_eval_select((self,), ct_f64_to_u64, rt_f64_to_u64) }
|
||||
| -------------------------------------------------------------------- inside `core::f64::<impl f64>::to_bits` at $SRC_DIR/core/src/num/f64.rs:LL:COL
|
||||
|
|
||||
::: $SRC_DIR/core/src/ops/function.rs:LL:COL
|
||||
|
|
||||
LL | extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
|
||||
| ------------------------------------------------------------------ inside `<fn(f64) -> u64 {core::f64::<impl f64>::to_bits::ct_f64_to_u64} as FnOnce<(f64,)>>::call_once - shim(fn(f64) -> u64 {core::f64::<impl f64>::to_bits::ct_f64_to_u64})` at $SRC_DIR/core/src/ops/function.rs:LL:COL
|
||||
|
|
||||
::: $SRC_DIR/core/src/intrinsics.rs:LL:COL
|
||||
|
|
||||
LL | called_in_const.call_once(arg)
|
||||
| ------------------------------ inside `const_eval_select::<(f64,), fn(f64) -> u64 {core::f64::<impl f64>::to_bits::ct_f64_to_u64}, [closure@core::f64::<impl f64>::to_bits::{closure#0}], u64>` at $SRC_DIR/core/src/intrinsics.rs:LL:COL
|
||||
|
|
||||
::: $DIR/const-float-bits-reject-conv.rs:46:30
|
||||
|
|
||||
LL | const MASKED_NAN1: u64 = f64::NAN.to_bits() ^ 0x000A_AAAA_AAAA_AAAA;
|
||||
| ------------------ inside `f64::MASKED_NAN1` at $DIR/const-float-bits-reject-conv.rs:46:30
|
||||
|
|
||||
= note: this error originates in the macro `$crate::panic::panic_2021` (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
|
||||
|
|
||||
LL | panic!("const-eval error: cannot use f64::to_bits on a NaN")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
| |
|
||||
| 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
|
||||
| inside `core::f64::<impl f64>::to_bits::ct_f64_to_u64` at $SRC_DIR/core/src/panic.rs:LL:COL
|
||||
...
|
||||
LL | unsafe { intrinsics::const_eval_select((self,), ct_f64_to_u64, rt_f64_to_u64) }
|
||||
| -------------------------------------------------------------------- inside `core::f64::<impl f64>::to_bits` at $SRC_DIR/core/src/num/f64.rs:LL:COL
|
||||
|
|
||||
::: $SRC_DIR/core/src/ops/function.rs:LL:COL
|
||||
|
|
||||
LL | extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
|
||||
| ------------------------------------------------------------------ inside `<fn(f64) -> u64 {core::f64::<impl f64>::to_bits::ct_f64_to_u64} as FnOnce<(f64,)>>::call_once - shim(fn(f64) -> u64 {core::f64::<impl f64>::to_bits::ct_f64_to_u64})` at $SRC_DIR/core/src/ops/function.rs:LL:COL
|
||||
|
|
||||
::: $SRC_DIR/core/src/intrinsics.rs:LL:COL
|
||||
|
|
||||
LL | called_in_const.call_once(arg)
|
||||
| ------------------------------ inside `const_eval_select::<(f64,), fn(f64) -> u64 {core::f64::<impl f64>::to_bits::ct_f64_to_u64}, [closure@core::f64::<impl f64>::to_bits::{closure#0}], u64>` at $SRC_DIR/core/src/intrinsics.rs:LL:COL
|
||||
|
|
||||
::: $DIR/const-float-bits-reject-conv.rs:47:30
|
||||
|
|
||||
LL | const MASKED_NAN2: u64 = f64::NAN.to_bits() ^ 0x0005_5555_5555_5555;
|
||||
| ------------------ inside `f64::MASKED_NAN2` at $DIR/const-float-bits-reject-conv.rs:47:30
|
||||
|
|
||||
= note: this error originates in the macro `$crate::panic::panic_2021` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: aborting due to 4 previous errors
|
||||
|
||||
For more information about this error, try `rustc --explain E0080`.
|
Loading…
Add table
Reference in a new issue