@@ -623,6 +623,23 @@ impl f32 {
623623 }
624624 }
625625
626+ // This operates on bits, and only bits, so it can ignore concerns about weird FPUs.
627+ // FIXME(jubilee): In a just world, this would be the entire impl for classify,
628+ // plus a transmute. We do not live in a just world, but we can make it more so.
629+ #[ rustc_const_unstable( feature = "const_float_classify" , issue = "72505" ) ]
630+ const fn classify_bits ( b : u32 ) -> FpCategory {
631+ const EXP_MASK : u32 = 0x7f800000 ;
632+ const MAN_MASK : u32 = 0x007fffff ;
633+
634+ match ( b & MAN_MASK , b & EXP_MASK ) {
635+ ( 0 , EXP_MASK ) => FpCategory :: Infinite ,
636+ ( _, EXP_MASK ) => FpCategory :: Nan ,
637+ ( 0 , 0 ) => FpCategory :: Zero ,
638+ ( _, 0 ) => FpCategory :: Subnormal ,
639+ _ => FpCategory :: Normal ,
640+ }
641+ }
642+
626643 /// Returns `true` if `self` has a positive sign, including `+0.0`, `NaN`s with
627644 /// positive sign bit and positive infinity.
628645 ///
@@ -874,8 +891,59 @@ impl f32 {
874891 #[ rustc_const_unstable( feature = "const_float_bits_conv" , issue = "72447" ) ]
875892 #[ inline]
876893 pub const fn to_bits ( self ) -> u32 {
877- // SAFETY: `u32` is a plain old datatype so we can always transmute to it
878- unsafe { mem:: transmute ( self ) }
894+ // SAFETY: `u32` is a plain old datatype so we can always transmute to it.
895+ // ...sorta.
896+ //
897+ // It turns out that at runtime, it is possible for a floating point number
898+ // to be subject to a floating point mode that alters nonzero subnormal numbers
899+ // to zero on reads and writes, aka "denormals are zero" and "flush to zero".
900+ // This is not a problem per se, but at least one tier2 platform for Rust
901+ // actually exhibits this behavior by default.
902+ //
903+ // In addition, on x86 targets with SSE or SSE2 disabled and the x87 FPU enabled,
904+ // i.e. not soft-float, the way Rust does parameter passing can actually alter
905+ // a number that is "not infinity" to have the same exponent as infinity,
906+ // in a slightly unpredictable manner.
907+ //
908+ // And, of course evaluating to a NaN value is fairly nondeterministic.
909+ // More precisely: when NaN should be returned is knowable, but which NaN?
910+ // So far that's defined by a combination of LLVM and the CPU, not Rust.
911+ // This function, however, allows observing the bitstring of a NaN,
912+ // thus introspection on CTFE.
913+ //
914+ // In order to preserve, at least for the moment, const-to-runtime equivalence,
915+ // we reject any of these possible situations from happening.
916+ #[ rustc_const_unstable( feature = "const_float_bits_conv" , issue = "72447" ) ]
917+ const fn ct_f32_to_u32 ( ct : f32 ) -> u32 {
918+ match ct. classify ( ) {
919+ FpCategory :: Nan => {
920+ panic ! ( "const-eval error: cannot use f32::to_bits on a NaN" )
921+ }
922+ FpCategory :: Subnormal => {
923+ panic ! ( "const-eval error: cannot use f32::to_bits on a subnormal number" )
924+ }
925+ FpCategory :: Infinite =>
926+ // SAFETY: Infinity per se is fine
927+ unsafe { mem:: transmute :: < f32 , u32 > ( ct) } ,
928+ FpCategory :: Zero | FpCategory :: Normal => {
929+ // SAFETY: We have a normal floating point number. Now we transmute, i.e. do a bitcopy.
930+ let bits: u32 = unsafe { mem:: transmute :: < f32 , u32 > ( ct) } ;
931+ // Let's doublecheck to make sure it wasn't a weird float by truncating it.
932+ if bits >> 23 & 0xFF == 0xFF {
933+ panic ! (
934+ "const-eval error: an unusually large x87 floating point value should not leak into const eval"
935+ )
936+ } ;
937+ bits
938+ }
939+ }
940+ }
941+ // SAFETY: `u32` is a plain old datatype so we can always... uh...
942+ // ...look, just pretend you forgot what you just read.
943+ // Stability concerns.
944+ let rt_f32_to_u32 = |rt| unsafe { mem:: transmute :: < f32 , u32 > ( rt) } ;
945+ // SAFETY: We use internal implementations that either always work or fail at compile time.
946+ unsafe { intrinsics:: const_eval_select ( ( self , ) , ct_f32_to_u32, rt_f32_to_u32) }
879947 }
880948
881949 /// Raw transmutation from `u32`.
@@ -919,9 +987,49 @@ impl f32 {
919987 #[ must_use]
920988 #[ inline]
921989 pub const fn from_bits ( v : u32 ) -> Self {
922- // SAFETY: `u32` is a plain old datatype so we can always transmute from it
923990 // It turns out the safety issues with sNaN were overblown! Hooray!
924- unsafe { mem:: transmute :: < u32 , f32 > ( v) }
991+ // SAFETY: `u32` is a plain old datatype so we can always transmute from it
992+ // ...sorta.
993+ //
994+ // It turns out that at runtime, it is possible for a floating point number
995+ // to be subject to a floating point mode that alters nonzero subnormal numbers
996+ // to zero on reads and writes, aka "denormals are zero" and "flush to zero".
997+ // This is not a problem usually, but at least one tier2 platform for Rust
998+ // actually exhibits this behavior by default: thumbv7neon
999+ // aka "the Neon FPU in AArch32 state"
1000+ //
1001+ // In addition, on x86 targets with SSE or SSE2 disabled and the x87 FPU enabled,
1002+ // i.e. not soft-float, the way Rust does parameter passing can actually alter
1003+ // a number that is "not infinity" to have the same exponent as infinity,
1004+ // in a slightly unpredictable manner.
1005+ //
1006+ // And, of course evaluating to a NaN value is fairly nondeterministic.
1007+ // More precisely: when NaN should be returned is knowable, but which NaN?
1008+ // So far that's defined by a combination of LLVM and the CPU, not Rust.
1009+ // This function, however, allows observing the bitstring of a NaN,
1010+ // thus introspection on CTFE.
1011+ //
1012+ // In order to preserve, at least for the moment, const-to-runtime equivalence,
1013+ // reject any of these possible situations from happening.
1014+ #[ rustc_const_unstable( feature = "const_float_bits_conv" , issue = "72447" ) ]
1015+ const fn ct_u32_to_f32 ( ct : u32 ) -> f32 {
1016+ match f32:: classify_bits ( ct) {
1017+ FpCategory :: Subnormal => {
1018+ panic ! ( "const-eval error: cannot use f32::from_bits on a subnormal number" ) ;
1019+ }
1020+ FpCategory :: Nan => {
1021+ panic ! ( "const-eval error: cannot use f32::from_bits on NaN" ) ;
1022+ }
1023+ // SAFETY: It's not a frumious number
1024+ _ => unsafe { mem:: transmute :: < u32 , f32 > ( ct) } ,
1025+ }
1026+ }
1027+ // SAFETY: `u32` is a plain old datatype so we can always... uh...
1028+ // ...look, just pretend you forgot what you just read.
1029+ // Stability concerns.
1030+ let rt_u32_to_f32 = |rt| unsafe { mem:: transmute :: < u32 , f32 > ( rt) } ;
1031+ // SAFETY: We use internal implementations that either always work or fail at compile time.
1032+ unsafe { intrinsics:: const_eval_select ( ( v, ) , ct_u32_to_f32, rt_u32_to_f32) }
9251033 }
9261034
9271035 /// Return the memory representation of this floating point number as a byte array in
0 commit comments