about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJubilee <workingjubilee@gmail.com>2024-09-11 15:53:21 -0700
committerGitHub <noreply@github.com>2024-09-11 15:53:21 -0700
commit312b597a7ecf6eff2a1fe481d90a41965b438ada (patch)
tree7ce513d1074ac584079bd634db52996867706276
parentabf0ac5ba0458faff91d9a7f96c0ed1bf5e1b6f5 (diff)
parente556c136f3a4e5ff36afa248a6dd0e2cc9ddaced (diff)
downloadrust-312b597a7ecf6eff2a1fe481d90a41965b438ada.tar.gz
rust-312b597a7ecf6eff2a1fe481d90a41965b438ada.zip
Rollup merge of #129835 - RalfJung:float-tests, r=workingjubilee
enable const-float-classify test, and test_next_up/down on 32bit x86

The  test_next_up/down tests have been disabled on all 32bit x86 targets, which goes too far -- they should definitely work on our (tier 1) i686 target, it is only without SSE that we might run into trouble due to https://github.com/rust-lang/rust/issues/114479. However, I cannot reproduce that trouble any more -- maybe that got fixed by https://github.com/rust-lang/rust/pull/123351?

The  const-float-classify test relied on const traits "because we can", and got disabled when const traits got removed. That's an unfortunate reduction in test coverage of our float functionality, so let's restore the test in a way that does not rely on const traits.

The const-float tests are actually testing runtime behavior as well, and I don't think that runtime behavior is covered anywhere else. Probably they shouldn't be called "const-float", but we don't have a `tests/ui/float` folder... should I create one and move them there? Are there any other ui tests that should be moved there?

I also removed some FIXME referring to not use x87 for Rust-to-Rust-calls -- that has happened in #123351 so this got fixed indeed. Does that mean we can simplify all that float code again? I am not sure how to test it. Is running the test suite with an i586 target enough?

Cc ```@tgross35``` ```@workingjubilee```
-rw-r--r--library/core/src/num/f16.rs1
-rw-r--r--library/core/src/num/f32.rs5
-rw-r--r--library/core/src/num/f64.rs5
-rw-r--r--library/std/src/f32/tests.rs13
-rw-r--r--library/std/src/f64/tests.rs13
-rw-r--r--tests/ui/consts/const-float-bits-conv.rs161
-rw-r--r--tests/ui/consts/const-float-classify.rs76
-rw-r--r--tests/ui/consts/const-float-classify.stderr11
-rw-r--r--tests/ui/float/classify-runtime-const.rs82
-rw-r--r--tests/ui/float/conv-bits-runtime-const.rs168
-rw-r--r--tests/ui/numbers-arithmetic/issue-105626.rs1
11 files changed, 254 insertions, 282 deletions
diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs
index 9252e8c6015..da92da1086d 100644
--- a/library/core/src/num/f16.rs
+++ b/library/core/src/num/f16.rs
@@ -435,6 +435,7 @@ impl f16 {
         // 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.
+        // see also https://github.com/rust-lang/rust/issues/114479
         if self.is_infinite() {
             // Thus, a value may compare unequal to infinity, despite having a "full" exponent mask.
             FpCategory::Infinite
diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs
index 2bc89722497..885f7608a33 100644
--- a/library/core/src/num/f32.rs
+++ b/library/core/src/num/f32.rs
@@ -662,10 +662,7 @@ impl f32 {
         // 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.
-        //
-        // 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.
+        // see also https://github.com/rust-lang/rust/issues/114479
         if self.is_infinite() {
             // A value may compare unequal to infinity, despite having a "full" exponent mask.
             FpCategory::Infinite
diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs
index b3f5be9fc8a..28cc231ccc7 100644
--- a/library/core/src/num/f64.rs
+++ b/library/core/src/num/f64.rs
@@ -660,10 +660,7 @@ impl f64 {
         // 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.
-        //
-        // 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.
+        // see also https://github.com/rust-lang/rust/issues/114479
         //
         // 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.
diff --git a/library/std/src/f32/tests.rs b/library/std/src/f32/tests.rs
index 3a4c1c120a4..99cfcfb231d 100644
--- a/library/std/src/f32/tests.rs
+++ b/library/std/src/f32/tests.rs
@@ -2,31 +2,24 @@ use crate::f32::consts;
 use crate::num::{FpCategory as Fp, *};
 
 /// Smallest number
-#[allow(dead_code)] // unused on x86
 const TINY_BITS: u32 = 0x1;
 
 /// Next smallest number
-#[allow(dead_code)] // unused on x86
 const TINY_UP_BITS: u32 = 0x2;
 
 /// Exponent = 0b11...10, Sifnificand 0b1111..10. Min val > 0
-#[allow(dead_code)] // unused on x86
 const MAX_DOWN_BITS: u32 = 0x7f7f_fffe;
 
 /// Zeroed exponent, full significant
-#[allow(dead_code)] // unused on x86
 const LARGEST_SUBNORMAL_BITS: u32 = 0x007f_ffff;
 
 /// Exponent = 0b1, zeroed significand
-#[allow(dead_code)] // unused on x86
 const SMALLEST_NORMAL_BITS: u32 = 0x0080_0000;
 
 /// First pattern over the mantissa
-#[allow(dead_code)] // unused on x86
 const NAN_MASK1: u32 = 0x002a_aaaa;
 
 /// Second pattern over the mantissa
-#[allow(dead_code)] // unused on x86
 const NAN_MASK2: u32 = 0x0055_5555;
 
 #[allow(unused_macros)]
@@ -353,9 +346,6 @@ fn test_is_sign_negative() {
     assert!((-f32::NAN).is_sign_negative());
 }
 
-// Ignore test on x87 floating point, these platforms do not guarantee NaN
-// payloads are preserved and flush denormals to zero, failing the tests.
-#[cfg(not(target_arch = "x86"))]
 #[test]
 fn test_next_up() {
     let tiny = f32::from_bits(TINY_BITS);
@@ -386,9 +376,6 @@ fn test_next_up() {
     assert_f32_biteq!(nan2.next_up(), nan2);
 }
 
-// Ignore test on x87 floating point, these platforms do not guarantee NaN
-// payloads are preserved and flush denormals to zero, failing the tests.
-#[cfg(not(target_arch = "x86"))]
 #[test]
 fn test_next_down() {
     let tiny = f32::from_bits(TINY_BITS);
diff --git a/library/std/src/f64/tests.rs b/library/std/src/f64/tests.rs
index bac8405f973..3fac2efe0d7 100644
--- a/library/std/src/f64/tests.rs
+++ b/library/std/src/f64/tests.rs
@@ -2,31 +2,24 @@ use crate::f64::consts;
 use crate::num::{FpCategory as Fp, *};
 
 /// Smallest number
-#[allow(dead_code)] // unused on x86
 const TINY_BITS: u64 = 0x1;
 
 /// Next smallest number
-#[allow(dead_code)] // unused on x86
 const TINY_UP_BITS: u64 = 0x2;
 
 /// Exponent = 0b11...10, Sifnificand 0b1111..10. Min val > 0
-#[allow(dead_code)] // unused on x86
 const MAX_DOWN_BITS: u64 = 0x7fef_ffff_ffff_fffe;
 
 /// Zeroed exponent, full significant
-#[allow(dead_code)] // unused on x86
 const LARGEST_SUBNORMAL_BITS: u64 = 0x000f_ffff_ffff_ffff;
 
 /// Exponent = 0b1, zeroed significand
-#[allow(dead_code)] // unused on x86
 const SMALLEST_NORMAL_BITS: u64 = 0x0010_0000_0000_0000;
 
 /// First pattern over the mantissa
-#[allow(dead_code)] // unused on x86
 const NAN_MASK1: u64 = 0x000a_aaaa_aaaa_aaaa;
 
 /// Second pattern over the mantissa
-#[allow(dead_code)] // unused on x86
 const NAN_MASK2: u64 = 0x0005_5555_5555_5555;
 
 #[allow(unused_macros)]
@@ -343,9 +336,6 @@ fn test_is_sign_negative() {
     assert!((-f64::NAN).is_sign_negative());
 }
 
-// Ignore test on x87 floating point, these platforms do not guarantee NaN
-// payloads are preserved and flush denormals to zero, failing the tests.
-#[cfg(not(target_arch = "x86"))]
 #[test]
 fn test_next_up() {
     let tiny = f64::from_bits(TINY_BITS);
@@ -375,9 +365,6 @@ fn test_next_up() {
     assert_f64_biteq!(nan2.next_up(), nan2);
 }
 
-// Ignore test on x87 floating point, these platforms do not guarantee NaN
-// payloads are preserved and flush denormals to zero, failing the tests.
-#[cfg(not(target_arch = "x86"))]
 #[test]
 fn test_next_down() {
     let tiny = f64::from_bits(TINY_BITS);
diff --git a/tests/ui/consts/const-float-bits-conv.rs b/tests/ui/consts/const-float-bits-conv.rs
deleted file mode 100644
index 869498d1076..00000000000
--- a/tests/ui/consts/const-float-bits-conv.rs
+++ /dev/null
@@ -1,161 +0,0 @@
-//@ compile-flags: -Zmir-opt-level=0
-//@ run-pass
-
-#![feature(const_float_classify)]
-#![feature(f16, f16_const)]
-#![feature(f128, f128_const)]
-#![allow(unused_macro_rules)]
-// 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 has_broken_floats() -> bool {
-    // i586 targets are broken due to <https://github.com/rust-lang/rust/issues/114479>.
-    std::env::var("TARGET").is_ok_and(|v| v.contains("i586"))
-}
-
-#[cfg(target_arch = "x86_64")]
-fn f16(){
-    const_assert!((1f16).to_bits(), 0x3c00);
-    const_assert!(u16::from_be_bytes(1f16.to_be_bytes()), 0x3c00);
-    const_assert!((12.5f16).to_bits(), 0x4a40);
-    const_assert!(u16::from_le_bytes(12.5f16.to_le_bytes()), 0x4a40);
-    const_assert!((1337f16).to_bits(), 0x6539);
-    const_assert!(u16::from_ne_bytes(1337f16.to_ne_bytes()), 0x6539);
-    const_assert!((-14.25f16).to_bits(), 0xcb20);
-    const_assert!(f16::from_bits(0x3c00), 1.0);
-    const_assert!(f16::from_be_bytes(0x3c00u16.to_be_bytes()), 1.0);
-    const_assert!(f16::from_bits(0x4a40), 12.5);
-    const_assert!(f16::from_le_bytes(0x4a40u16.to_le_bytes()), 12.5);
-    const_assert!(f16::from_bits(0x5be0), 252.0);
-    const_assert!(f16::from_ne_bytes(0x5be0u16.to_ne_bytes()), 252.0);
-    const_assert!(f16::from_bits(0xcb20), -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
-    // NOTE: These names assume `f{BITS}::NAN` is a quiet NAN and IEEE754-2008's NaN rules apply!
-    const QUIET_NAN: u16 = f16::NAN.to_bits() ^ 0x0155;
-    const SIGNALING_NAN: u16 = f16::NAN.to_bits() ^ 0x02AA;
-
-    const_assert!(f16::from_bits(QUIET_NAN).is_nan());
-    const_assert!(f16::from_bits(SIGNALING_NAN).is_nan());
-    const_assert!(f16::from_bits(QUIET_NAN).to_bits(), QUIET_NAN);
-    if !has_broken_floats() {
-        const_assert!(f16::from_bits(SIGNALING_NAN).to_bits(), SIGNALING_NAN);
-    }
-}
-
-fn f32() {
-    const_assert!((1f32).to_bits(), 0x3f800000);
-    const_assert!(u32::from_be_bytes(1f32.to_be_bytes()), 0x3f800000);
-    const_assert!((12.5f32).to_bits(), 0x41480000);
-    const_assert!(u32::from_le_bytes(12.5f32.to_le_bytes()), 0x41480000);
-    const_assert!((1337f32).to_bits(), 0x44a72000);
-    const_assert!(u32::from_ne_bytes(1337f32.to_ne_bytes()), 0x44a72000);
-    const_assert!((-14.25f32).to_bits(), 0xc1640000);
-    const_assert!(f32::from_bits(0x3f800000), 1.0);
-    const_assert!(f32::from_be_bytes(0x3f800000u32.to_be_bytes()), 1.0);
-    const_assert!(f32::from_bits(0x41480000), 12.5);
-    const_assert!(f32::from_le_bytes(0x41480000u32.to_le_bytes()), 12.5);
-    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
-    // 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(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(SIGNALING_NAN).to_bits(), SIGNALING_NAN);
-    }
-}
-
-fn f64() {
-    const_assert!((1f64).to_bits(), 0x3ff0000000000000);
-    const_assert!(u64::from_be_bytes(1f64.to_be_bytes()), 0x3ff0000000000000);
-    const_assert!((12.5f64).to_bits(), 0x4029000000000000);
-    const_assert!(u64::from_le_bytes(12.5f64.to_le_bytes()), 0x4029000000000000);
-    const_assert!((1337f64).to_bits(), 0x4094e40000000000);
-    const_assert!(u64::from_ne_bytes(1337f64.to_ne_bytes()), 0x4094e40000000000);
-    const_assert!((-14.25f64).to_bits(), 0xc02c800000000000);
-    const_assert!(f64::from_bits(0x3ff0000000000000), 1.0);
-    const_assert!(f64::from_be_bytes(0x3ff0000000000000u64.to_be_bytes()), 1.0);
-    const_assert!(f64::from_bits(0x4029000000000000), 12.5);
-    const_assert!(f64::from_le_bytes(0x4029000000000000u64.to_le_bytes()), 12.5);
-    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
-    // 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(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(SIGNALING_NAN).to_bits(), SIGNALING_NAN);
-    }
-}
-
-#[cfg(target_arch = "x86_64")]
-fn f128() {
-    const_assert!((1f128).to_bits(), 0x3fff0000000000000000000000000000);
-    const_assert!(u128::from_be_bytes(1f128.to_be_bytes()), 0x3fff0000000000000000000000000000);
-    const_assert!((12.5f128).to_bits(), 0x40029000000000000000000000000000);
-    const_assert!(u128::from_le_bytes(12.5f128.to_le_bytes()), 0x40029000000000000000000000000000);
-    const_assert!((1337f128).to_bits(), 0x40094e40000000000000000000000000);
-    const_assert!(u128::from_ne_bytes(1337f128.to_ne_bytes()), 0x40094e40000000000000000000000000);
-    const_assert!((-14.25f128).to_bits(), 0xc002c800000000000000000000000000);
-    const_assert!(f128::from_bits(0x3fff0000000000000000000000000000), 1.0);
-    const_assert!(f128::from_be_bytes(0x3fff0000000000000000000000000000u128.to_be_bytes()), 1.0);
-    const_assert!(f128::from_bits(0x40029000000000000000000000000000), 12.5);
-    const_assert!(f128::from_le_bytes(0x40029000000000000000000000000000u128.to_le_bytes()), 12.5);
-    const_assert!(f128::from_bits(0x40094e40000000000000000000000000), 1337.0);
-    assert_eq!(f128::from_ne_bytes(0x40094e40000000000000000000000000u128.to_ne_bytes()), 1337.0);
-    const_assert!(f128::from_bits(0xc002c800000000000000000000000000), -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
-    // NOTE: These names assume `f{BITS}::NAN` is a quiet NAN and IEEE754-2008's NaN rules apply!
-    const QUIET_NAN: u128 = f128::NAN.to_bits() | 0x0000_AAAA_AAAA_AAAA_AAAA_AAAA_AAAA_AAAA;
-    const SIGNALING_NAN: u128 = f128::NAN.to_bits() ^ 0x0000_5555_5555_5555_5555_5555_5555_5555;
-
-    const_assert!(f128::from_bits(QUIET_NAN).is_nan());
-    const_assert!(f128::from_bits(SIGNALING_NAN).is_nan());
-    const_assert!(f128::from_bits(QUIET_NAN).to_bits(), QUIET_NAN);
-    if !has_broken_floats() {
-        const_assert!(f128::from_bits(SIGNALING_NAN).to_bits(), SIGNALING_NAN);
-    }
-}
-
-fn main() {
-    #[cfg(target_arch = "x86_64")]
-    {
-        f16();
-        f128();
-    }
-    f32();
-    f64();
-}
diff --git a/tests/ui/consts/const-float-classify.rs b/tests/ui/consts/const-float-classify.rs
deleted file mode 100644
index 6e5097f7f2b..00000000000
--- a/tests/ui/consts/const-float-classify.rs
+++ /dev/null
@@ -1,76 +0,0 @@
-//@ compile-flags: -Zmir-opt-level=0 -Znext-solver
-//@ known-bug: #110395
-// FIXME(effects) run-pass
-
-#![feature(const_float_classify)]
-#![feature(const_trait_impl, effects)]
-#![allow(incomplete_features)]
-
-// Don't promote
-const fn nop<T>(x: T) -> T { x }
-
-impl const PartialEq<NonDet> for bool {
-    fn eq(&self, _: &NonDet) -> bool {
-        true
-    }
-}
-
-macro_rules! const_assert {
-    ($a:expr, $b:expr) => {
-        {
-            const _: () = assert!($a == $b);
-            assert!(nop($a) == nop($b));
-        }
-    };
-}
-
-macro_rules! suite {
-    ( $( $tt:tt )* ) => {
-        fn f32() {
-            suite_inner!(f32 $($tt)*);
-        }
-
-        fn f64() {
-            suite_inner!(f64 $($tt)*);
-        }
-    }
-
-}
-
-macro_rules! suite_inner {
-    (
-        $ty:ident [$( $fn:ident ),*]
-        $val:expr => [$($out:ident),*]
-
-        $( $tail:tt )*
-    ) => {
-        $( const_assert!($ty::$fn($val), $out); )*
-        suite_inner!($ty [$($fn),*] $($tail)*)
-    };
-
-    ( $ty:ident [$( $fn:ident ),*]) => {};
-}
-
-#[derive(Debug)]
-struct NonDet;
-
-// The result of the `is_sign` methods are not checked for correctness, since LLVM does not
-// guarantee anything about the signedness of NaNs. See
-// https://github.com/rust-lang/rust/issues/55131.
-
-suite! {
-                   [is_nan, is_infinite, is_finite, is_normal, is_sign_positive, is_sign_negative]
-     -0.0 / 0.0 => [  true,       false,     false,     false,           NonDet,           NonDet]
-      0.0 / 0.0 => [  true,       false,     false,     false,           NonDet,           NonDet]
-            1.0 => [ false,       false,      true,      true,             true,            false]
-           -1.0 => [ false,       false,      true,      true,            false,             true]
-            0.0 => [ false,       false,      true,     false,             true,            false]
-           -0.0 => [ false,       false,      true,     false,            false,             true]
-      1.0 / 0.0 => [ false,        true,     false,     false,             true,            false]
-     -1.0 / 0.0 => [ false,        true,     false,     false,            false,             true]
-}
-
-fn main() {
-    f32();
-    f64();
-}
diff --git a/tests/ui/consts/const-float-classify.stderr b/tests/ui/consts/const-float-classify.stderr
deleted file mode 100644
index a35de8ad0ea..00000000000
--- a/tests/ui/consts/const-float-classify.stderr
+++ /dev/null
@@ -1,11 +0,0 @@
-error: const `impl` for trait `PartialEq` which is not marked with `#[const_trait]`
-  --> $DIR/const-float-classify.rs:12:12
-   |
-LL | impl const PartialEq<NonDet> for bool {
-   |            ^^^^^^^^^^^^^^^^^
-   |
-   = note: marking a trait with `#[const_trait]` ensures all default method bodies are `const`
-   = note: adding a non-const method body in the future would be a breaking change
-
-error: aborting due to 1 previous error
-
diff --git a/tests/ui/float/classify-runtime-const.rs b/tests/ui/float/classify-runtime-const.rs
new file mode 100644
index 00000000000..59a232c255e
--- /dev/null
+++ b/tests/ui/float/classify-runtime-const.rs
@@ -0,0 +1,82 @@
+//@ compile-flags: -Zmir-opt-level=0 -Znext-solver
+//@ run-pass
+// ignore-tidy-linelength
+
+// This tests the float classification functions, for regular runtime code and for const evaluation.
+
+#![feature(f16_const)]
+#![feature(f128_const)]
+#![feature(const_float_classify)]
+
+use std::hint::black_box;
+use std::num::FpCategory::*;
+
+macro_rules! both_assert {
+    ($a:expr, NonDet) => {
+        {
+            // Compute `a`, but do not compare with anything as the result is non-deterministic.
+            const _: () = { let _val = $a; };
+            // `black_box` prevents promotion, and MIR opts are disabled above, so this is truly
+            // going through LLVM.
+            let _val = black_box($a);
+        }
+    };
+    ($a:expr, $b:ident) => {
+        {
+            const _: () = assert!(matches!($a, $b));
+            assert!(black_box($a) == black_box($b));
+        }
+    };
+}
+
+macro_rules! suite {
+    ( $tyname:ident: $( $tt:tt )* ) => {
+        fn f32() {
+            type $tyname = f32;
+            suite_inner!(f32 $($tt)*);
+        }
+
+        fn f64() {
+            type $tyname = f64;
+            suite_inner!(f64 $($tt)*);
+        }
+    }
+}
+
+macro_rules! suite_inner {
+    (
+        $ty:ident [$( $fn:ident ),*]
+        $val:expr => [$($out:ident),*]
+
+        $( $tail:tt )*
+    ) => {
+        $( both_assert!($ty::$fn($val), $out); )*
+        suite_inner!($ty [$($fn),*] $($tail)*)
+    };
+
+    ( $ty:ident [$( $fn:ident ),*]) => {};
+}
+
+// The result of the `is_sign` methods are not checked for correctness, since we do not
+// guarantee anything about the signedness of NaNs. See
+// https://rust-lang.github.io/rfcs/3514-float-semantics.html.
+
+suite! { T: // type alias for the type we are testing
+                   [ classify, is_nan, is_infinite, is_finite, is_normal, is_sign_positive, is_sign_negative]
+     -0.0 / 0.0 => [      Nan,   true,       false,     false,     false,           NonDet,           NonDet]
+      0.0 / 0.0 => [      Nan,   true,       false,     false,     false,           NonDet,           NonDet]
+            1.0 => [   Normal,  false,       false,      true,      true,             true,            false]
+           -1.0 => [   Normal,  false,       false,      true,      true,            false,             true]
+            0.0 => [     Zero,  false,       false,      true,     false,             true,            false]
+           -0.0 => [     Zero,  false,       false,      true,     false,            false,             true]
+      1.0 / 0.0 => [ Infinite,  false,        true,     false,     false,             true,            false]
+     -1.0 / 0.0 => [ Infinite,  false,        true,     false,     false,            false,             true]
+   1.0 / T::MAX => [Subnormal,  false,       false,      true,     false,             true,            false]
+  -1.0 / T::MAX => [Subnormal,  false,       false,      true,     false,            false,             true]
+}
+
+fn main() {
+    f32();
+    f64();
+    // FIXME(f16_f128): also test f16 and f128
+}
diff --git a/tests/ui/float/conv-bits-runtime-const.rs b/tests/ui/float/conv-bits-runtime-const.rs
new file mode 100644
index 00000000000..e85a889d2c2
--- /dev/null
+++ b/tests/ui/float/conv-bits-runtime-const.rs
@@ -0,0 +1,168 @@
+//@ compile-flags: -Zmir-opt-level=0
+//@ run-pass
+
+// This tests the float classification functions, for regular runtime code and for const evaluation.
+
+#![feature(const_float_classify)]
+#![feature(f16)]
+#![feature(f128)]
+#![feature(f16_const)]
+#![feature(f128_const)]
+#![allow(unused_macro_rules)]
+
+use std::hint::black_box;
+
+macro_rules! both_assert {
+    ($a:expr) => {
+        {
+            const _: () = assert!($a);
+            // `black_box` prevents promotion, and MIR opts are disabled above, so this is truly
+            // going through LLVM.
+            assert!(black_box($a));
+        }
+    };
+    ($a:expr, $b:expr) => {
+        {
+            const _: () = assert!($a == $b);
+            assert_eq!(black_box($a), black_box($b));
+        }
+    };
+}
+
+fn has_broken_floats() -> bool {
+    // i586 targets are broken due to <https://github.com/rust-lang/rust/issues/114479>.
+    cfg!(all(target_arch = "x86", not(target_feature = "sse2")))
+}
+
+#[cfg(target_arch = "x86_64")]
+fn f16(){
+    both_assert!((1f16).to_bits(), 0x3c00);
+    both_assert!(u16::from_be_bytes(1f16.to_be_bytes()), 0x3c00);
+    both_assert!((12.5f16).to_bits(), 0x4a40);
+    both_assert!(u16::from_le_bytes(12.5f16.to_le_bytes()), 0x4a40);
+    both_assert!((1337f16).to_bits(), 0x6539);
+    both_assert!(u16::from_ne_bytes(1337f16.to_ne_bytes()), 0x6539);
+    both_assert!((-14.25f16).to_bits(), 0xcb20);
+    both_assert!(f16::from_bits(0x3c00), 1.0);
+    both_assert!(f16::from_be_bytes(0x3c00u16.to_be_bytes()), 1.0);
+    both_assert!(f16::from_bits(0x4a40), 12.5);
+    both_assert!(f16::from_le_bytes(0x4a40u16.to_le_bytes()), 12.5);
+    both_assert!(f16::from_bits(0x5be0), 252.0);
+    both_assert!(f16::from_ne_bytes(0x5be0u16.to_ne_bytes()), 252.0);
+    both_assert!(f16::from_bits(0xcb20), -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
+    // NOTE: These names assume `f{BITS}::NAN` is a quiet NAN and IEEE754-2008's NaN rules apply!
+    const QUIET_NAN: u16 = f16::NAN.to_bits() ^ 0x0155;
+    const SIGNALING_NAN: u16 = f16::NAN.to_bits() ^ 0x02AA;
+
+    both_assert!(f16::from_bits(QUIET_NAN).is_nan());
+    both_assert!(f16::from_bits(SIGNALING_NAN).is_nan());
+    both_assert!(f16::from_bits(QUIET_NAN).to_bits(), QUIET_NAN);
+    if !has_broken_floats() {
+        both_assert!(f16::from_bits(SIGNALING_NAN).to_bits(), SIGNALING_NAN);
+    }
+}
+
+fn f32() {
+    both_assert!((1f32).to_bits(), 0x3f800000);
+    both_assert!(u32::from_be_bytes(1f32.to_be_bytes()), 0x3f800000);
+    both_assert!((12.5f32).to_bits(), 0x41480000);
+    both_assert!(u32::from_le_bytes(12.5f32.to_le_bytes()), 0x41480000);
+    both_assert!((1337f32).to_bits(), 0x44a72000);
+    both_assert!(u32::from_ne_bytes(1337f32.to_ne_bytes()), 0x44a72000);
+    both_assert!((-14.25f32).to_bits(), 0xc1640000);
+    both_assert!(f32::from_bits(0x3f800000), 1.0);
+    both_assert!(f32::from_be_bytes(0x3f800000u32.to_be_bytes()), 1.0);
+    both_assert!(f32::from_bits(0x41480000), 12.5);
+    both_assert!(f32::from_le_bytes(0x41480000u32.to_le_bytes()), 12.5);
+    both_assert!(f32::from_bits(0x44a72000), 1337.0);
+    both_assert!(f32::from_ne_bytes(0x44a72000u32.to_ne_bytes()), 1337.0);
+    both_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
+    // 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;
+
+    both_assert!(f32::from_bits(QUIET_NAN).is_nan());
+    both_assert!(f32::from_bits(SIGNALING_NAN).is_nan());
+    both_assert!(f32::from_bits(QUIET_NAN).to_bits(), QUIET_NAN);
+    if !has_broken_floats() {
+        both_assert!(f32::from_bits(SIGNALING_NAN).to_bits(), SIGNALING_NAN);
+    }
+}
+
+fn f64() {
+    both_assert!((1f64).to_bits(), 0x3ff0000000000000);
+    both_assert!(u64::from_be_bytes(1f64.to_be_bytes()), 0x3ff0000000000000);
+    both_assert!((12.5f64).to_bits(), 0x4029000000000000);
+    both_assert!(u64::from_le_bytes(12.5f64.to_le_bytes()), 0x4029000000000000);
+    both_assert!((1337f64).to_bits(), 0x4094e40000000000);
+    both_assert!(u64::from_ne_bytes(1337f64.to_ne_bytes()), 0x4094e40000000000);
+    both_assert!((-14.25f64).to_bits(), 0xc02c800000000000);
+    both_assert!(f64::from_bits(0x3ff0000000000000), 1.0);
+    both_assert!(f64::from_be_bytes(0x3ff0000000000000u64.to_be_bytes()), 1.0);
+    both_assert!(f64::from_bits(0x4029000000000000), 12.5);
+    both_assert!(f64::from_le_bytes(0x4029000000000000u64.to_le_bytes()), 12.5);
+    both_assert!(f64::from_bits(0x4094e40000000000), 1337.0);
+    both_assert!(f64::from_ne_bytes(0x4094e40000000000u64.to_ne_bytes()), 1337.0);
+    both_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
+    // 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;
+
+    both_assert!(f64::from_bits(QUIET_NAN).is_nan());
+    both_assert!(f64::from_bits(SIGNALING_NAN).is_nan());
+    both_assert!(f64::from_bits(QUIET_NAN).to_bits(), QUIET_NAN);
+    if !has_broken_floats() {
+        both_assert!(f64::from_bits(SIGNALING_NAN).to_bits(), SIGNALING_NAN);
+    }
+}
+
+#[cfg(target_arch = "x86_64")]
+fn f128() {
+    both_assert!((1f128).to_bits(), 0x3fff0000000000000000000000000000);
+    both_assert!(u128::from_be_bytes(1f128.to_be_bytes()), 0x3fff0000000000000000000000000000);
+    both_assert!((12.5f128).to_bits(), 0x40029000000000000000000000000000);
+    both_assert!(u128::from_le_bytes(12.5f128.to_le_bytes()), 0x40029000000000000000000000000000);
+    both_assert!((1337f128).to_bits(), 0x40094e40000000000000000000000000);
+    both_assert!(u128::from_ne_bytes(1337f128.to_ne_bytes()), 0x40094e40000000000000000000000000);
+    both_assert!((-14.25f128).to_bits(), 0xc002c800000000000000000000000000);
+    both_assert!(f128::from_bits(0x3fff0000000000000000000000000000), 1.0);
+    both_assert!(f128::from_be_bytes(0x3fff0000000000000000000000000000u128.to_be_bytes()), 1.0);
+    both_assert!(f128::from_bits(0x40029000000000000000000000000000), 12.5);
+    both_assert!(f128::from_le_bytes(0x40029000000000000000000000000000u128.to_le_bytes()), 12.5);
+    both_assert!(f128::from_bits(0x40094e40000000000000000000000000), 1337.0);
+    assert_eq!(f128::from_ne_bytes(0x40094e40000000000000000000000000u128.to_ne_bytes()), 1337.0);
+    both_assert!(f128::from_bits(0xc002c800000000000000000000000000), -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
+    // NOTE: These names assume `f{BITS}::NAN` is a quiet NAN and IEEE754-2008's NaN rules apply!
+    const QUIET_NAN: u128 = f128::NAN.to_bits() | 0x0000_AAAA_AAAA_AAAA_AAAA_AAAA_AAAA_AAAA;
+    const SIGNALING_NAN: u128 = f128::NAN.to_bits() ^ 0x0000_5555_5555_5555_5555_5555_5555_5555;
+
+    both_assert!(f128::from_bits(QUIET_NAN).is_nan());
+    both_assert!(f128::from_bits(SIGNALING_NAN).is_nan());
+    both_assert!(f128::from_bits(QUIET_NAN).to_bits(), QUIET_NAN);
+    if !has_broken_floats() {
+        both_assert!(f128::from_bits(SIGNALING_NAN).to_bits(), SIGNALING_NAN);
+    }
+}
+
+fn main() {
+    f32();
+    f64();
+
+    #[cfg(target_arch = "x86_64")]
+    {
+        f16();
+        f128();
+    }
+}
diff --git a/tests/ui/numbers-arithmetic/issue-105626.rs b/tests/ui/numbers-arithmetic/issue-105626.rs
index f942cf1283d..8d4a7c308e7 100644
--- a/tests/ui/numbers-arithmetic/issue-105626.rs
+++ b/tests/ui/numbers-arithmetic/issue-105626.rs
@@ -11,6 +11,7 @@ fn main() {
     assert_ne!((n as f64) as f32, n as f32);
 
     // FIXME: these assertions fail if only x87 is enabled
+    // see also https://github.com/rust-lang/rust/issues/114479
     assert_eq!(n as i64 as f32, r);
     assert_eq!(n as u64 as f32, r);
 }