@@ -449,7 +449,8 @@ impl f32 {
449449 #[ inline]
450450 #[ rustc_const_unstable( feature = "const_float_classify" , issue = "72505" ) ]
451451 pub ( crate ) const fn abs_private ( self ) -> f32 {
452- f32:: from_bits ( self . to_bits ( ) & 0x7fff_ffff )
452+ // SAFETY: This transmutation is fine. Probably. For the reasons std is using it.
453+ unsafe { mem:: transmute :: < u32 , f32 > ( mem:: transmute :: < f32 , u32 > ( self ) & 0x7fff_ffff ) }
453454 }
454455
455456 /// Returns `true` if this value is positive infinity or negative infinity, and
@@ -472,7 +473,10 @@ impl f32 {
472473 #[ rustc_const_unstable( feature = "const_float_classify" , issue = "72505" ) ]
473474 #[ inline]
474475 pub const fn is_infinite ( self ) -> bool {
475- self . abs_private ( ) == Self :: INFINITY
476+ // Getting clever with transmutation can result in incorrect answers on some FPUs
477+ // FIXME: alter the Rust <-> Rust calling convention to prevent this problem.
478+ // See https://github.com/rust-lang/rust/issues/72327
479+ ( self == f32:: INFINITY ) | ( self == f32:: NEG_INFINITY )
476480 }
477481
478482 /// Returns `true` if this number is neither infinite nor `NaN`.
@@ -568,15 +572,76 @@ impl f32 {
568572 #[ stable( feature = "rust1" , since = "1.0.0" ) ]
569573 #[ rustc_const_unstable( feature = "const_float_classify" , issue = "72505" ) ]
570574 pub const fn classify ( self ) -> FpCategory {
575+ // A previous implementation tried to only use bitmask-based checks,
576+ // using f32::to_bits to transmute the float to its bit repr and match on that.
577+ // Unfortunately, floating point numbers can be much worse than that.
578+ // This also needs to not result in recursive evaluations of f64::to_bits.
579+ //
580+ // On some processors, in some cases, LLVM will "helpfully" lower floating point ops,
581+ // in spite of a request for them using f32 and f64, to things like x87 operations.
582+ // These have an f64's mantissa, but can have a larger than normal exponent.
583+ // FIXME(jubilee): Using x87 operations is never necessary in order to function
584+ // on x86 processors for Rust-to-Rust calls, so this issue should not happen.
585+ // Code generation should be adjusted to use non-C calling conventions, avoiding this.
586+ //
587+ if self . is_infinite ( ) {
588+ // Thus, a value may compare unequal to infinity, despite having a "full" exponent mask.
589+ FpCategory :: Infinite
590+ } else if self . is_nan ( ) {
591+ // And it may not be NaN, as it can simply be an "overextended" finite value.
592+ FpCategory :: Nan
593+ } else {
594+ // However, std can't simply compare to zero to check for zero, either,
595+ // as correctness requires avoiding equality tests that may be Subnormal == -0.0
596+ // because it may be wrong under "denormals are zero" and "flush to zero" modes.
597+ // Most of std's targets don't use those, but they are used for thumbv7neon.
598+ // So, this does use bitpattern matching for the rest.
599+
600+ // SAFETY: f32 to u32 is fine. Usually.
601+ // If classify has gotten this far, the value is definitely in one of these categories.
602+ unsafe { f32:: partial_classify ( self ) }
603+ }
604+ }
605+
606+ // This doesn't actually return a right answer for NaN on purpose,
607+ // seeing as how it cannot correctly discern between a floating point NaN,
608+ // and some normal floating point numbers truncated from an x87 FPU.
609+ // FIXME(jubilee): This probably could at least answer things correctly for Infinity,
610+ // like the f64 version does, but I need to run more checks on how things go on x86.
611+ // I fear losing mantissa data that would have answered that differently.
612+ //
613+ // # Safety
614+ // This requires making sure you call this function for values it answers correctly on,
615+ // otherwise it returns a wrong answer. This is not important for memory safety per se,
616+ // but getting floats correct is important for not accidentally leaking const eval
617+ // runtime-deviating logic which may or may not be acceptable.
618+ #[ rustc_const_unstable( feature = "const_float_classify" , issue = "72505" ) ]
619+ const unsafe fn partial_classify ( self ) -> FpCategory {
571620 const EXP_MASK : u32 = 0x7f800000 ;
572621 const MAN_MASK : u32 = 0x007fffff ;
573622
574- let bits = self . to_bits ( ) ;
575- match ( bits & MAN_MASK , bits & EXP_MASK ) {
623+ // SAFETY: The caller is not asking questions for which this will tell lies.
624+ let b = unsafe { mem:: transmute :: < f32 , u32 > ( self ) } ;
625+ match ( b & MAN_MASK , b & EXP_MASK ) {
576626 ( 0 , 0 ) => FpCategory :: Zero ,
577627 ( _, 0 ) => FpCategory :: Subnormal ,
628+ _ => FpCategory :: Normal ,
629+ }
630+ }
631+
632+ // This operates on bits, and only bits, so it can ignore concerns about weird FPUs.
633+ // FIXME(jubilee): In a just world, this would be the entire impl for classify,
634+ // plus a transmute. We do not live in a just world, but we can make it more so.
635+ #[ rustc_const_unstable( feature = "const_float_classify" , issue = "72505" ) ]
636+ const fn classify_bits ( b : u32 ) -> FpCategory {
637+ const EXP_MASK : u32 = 0x7f800000 ;
638+ const MAN_MASK : u32 = 0x007fffff ;
639+
640+ match ( b & MAN_MASK , b & EXP_MASK ) {
578641 ( 0 , EXP_MASK ) => FpCategory :: Infinite ,
579642 ( _, EXP_MASK ) => FpCategory :: Nan ,
643+ ( 0 , 0 ) => FpCategory :: Zero ,
644+ ( _, 0 ) => FpCategory :: Subnormal ,
580645 _ => FpCategory :: Normal ,
581646 }
582647 }
@@ -616,7 +681,8 @@ impl f32 {
616681 pub const fn is_sign_negative ( self ) -> bool {
617682 // IEEE754 says: isSignMinus(x) is true if and only if x has negative sign. isSignMinus
618683 // applies to zeros and NaNs as well.
619- self . to_bits ( ) & 0x8000_0000 != 0
684+ // SAFETY: This is just transmuting to get the sign bit, it's fine.
685+ unsafe { mem:: transmute :: < f32 , u32 > ( self ) & 0x8000_0000 != 0 }
620686 }
621687
622688 /// Takes the reciprocal (inverse) of a number, `1/x`.
@@ -831,8 +897,49 @@ impl f32 {
831897 #[ rustc_const_unstable( feature = "const_float_bits_conv" , issue = "72447" ) ]
832898 #[ inline]
833899 pub const fn to_bits ( self ) -> u32 {
834- // SAFETY: `u32` is a plain old datatype so we can always transmute to it
835- unsafe { mem:: transmute ( self ) }
900+ // SAFETY: `u32` is a plain old datatype so we can always transmute to it.
901+ // ...sorta.
902+ //
903+ // It turns out that at runtime, it is possible for a floating point number
904+ // to be subject to a floating point mode that alters nonzero subnormal numbers
905+ // to zero on reads and writes, aka "denormals are zero" and "flush to zero".
906+ // This is not a problem per se, but at least one tier2 platform for Rust
907+ // actually exhibits this behavior by default.
908+ //
909+ // In addition, on x86 targets with SSE or SSE2 disabled and the x87 FPU enabled,
910+ // i.e. not soft-float, the way Rust does parameter passing can actually alter
911+ // a number that is "not infinity" to have the same exponent as infinity,
912+ // in a slightly unpredictable manner.
913+ //
914+ // And, of course evaluating to a NaN value is fairly nondeterministic.
915+ // More precisely: when NaN should be returned is knowable, but which NaN?
916+ // So far that's defined by a combination of LLVM and the CPU, not Rust.
917+ // This function, however, allows observing the bitstring of a NaN,
918+ // thus introspection on CTFE.
919+ //
920+ // In order to preserve, at least for the moment, const-to-runtime equivalence,
921+ // we reject any of these possible situations from happening.
922+ #[ rustc_const_unstable( feature = "const_float_bits_conv" , issue = "72447" ) ]
923+ const fn ct_f32_to_u32 ( ct : f32 ) -> u32 {
924+ match ct. classify ( ) {
925+ FpCategory :: Nan => {
926+ panic ! ( "const-eval error: cannot use f32::to_bits on a NaN" )
927+ }
928+ FpCategory :: Subnormal => {
929+ panic ! ( "const-eval error: cannot use f32::to_bits on a subnormal number" )
930+ }
931+ FpCategory :: Infinite | FpCategory :: Normal | FpCategory :: Zero => {
932+ // SAFETY: We have a normal floating point number. Now we transmute, i.e. do a bitcopy.
933+ unsafe { mem:: transmute :: < f32 , u32 > ( ct) }
934+ }
935+ }
936+ }
937+ // SAFETY: `u32` is a plain old datatype so we can always... uh...
938+ // ...look, just pretend you forgot what you just read.
939+ // Stability concerns.
940+ let rt_f32_to_u32 = |rt| unsafe { mem:: transmute :: < f32 , u32 > ( rt) } ;
941+ // SAFETY: We use internal implementations that either always work or fail at compile time.
942+ unsafe { intrinsics:: const_eval_select ( ( self , ) , ct_f32_to_u32, rt_f32_to_u32) }
836943 }
837944
838945 /// Raw transmutation from `u32`.
@@ -876,9 +983,51 @@ impl f32 {
876983 #[ must_use]
877984 #[ inline]
878985 pub const fn from_bits ( v : u32 ) -> Self {
879- // SAFETY: `u32` is a plain old datatype so we can always transmute from it
880986 // It turns out the safety issues with sNaN were overblown! Hooray!
881- unsafe { mem:: transmute ( v) }
987+ // SAFETY: `u32` is a plain old datatype so we can always transmute from it
988+ // ...sorta.
989+ //
990+ // It turns out that at runtime, it is possible for a floating point number
991+ // to be subject to floating point modes that alter nonzero subnormal numbers
992+ // to zero on reads and writes, aka "denormals are zero" and "flush to zero".
993+ // This is not a problem usually, but at least one tier2 platform for Rust
994+ // actually exhibits this behavior by default: thumbv7neon
995+ // aka "the Neon FPU in AArch32 state"
996+ //
997+ // In addition, on x86 targets with SSE or SSE2 disabled and the x87 FPU enabled,
998+ // i.e. not soft-float, the way Rust does parameter passing can actually alter
999+ // a number that is "not infinity" to have the same exponent as infinity,
1000+ // in a slightly unpredictable manner.
1001+ //
1002+ // And, of course evaluating to a NaN value is fairly nondeterministic.
1003+ // More precisely: when NaN should be returned is knowable, but which NaN?
1004+ // So far that's defined by a combination of LLVM and the CPU, not Rust.
1005+ // This function, however, allows observing the bitstring of a NaN,
1006+ // thus introspection on CTFE.
1007+ //
1008+ // In order to preserve, at least for the moment, const-to-runtime equivalence,
1009+ // reject any of these possible situations from happening.
1010+ #[ rustc_const_unstable( feature = "const_float_bits_conv" , issue = "72447" ) ]
1011+ const fn ct_u32_to_f32 ( ct : u32 ) -> f32 {
1012+ match f32:: classify_bits ( ct) {
1013+ FpCategory :: Subnormal => {
1014+ panic ! ( "const-eval error: cannot use f32::from_bits on a subnormal number" )
1015+ }
1016+ FpCategory :: Nan => {
1017+ panic ! ( "const-eval error: cannot use f32::from_bits on NaN" )
1018+ }
1019+ FpCategory :: Infinite | FpCategory :: Normal | FpCategory :: Zero => {
1020+ // SAFETY: It's not a frumious number
1021+ unsafe { mem:: transmute :: < u32 , f32 > ( ct) }
1022+ }
1023+ }
1024+ }
1025+ // SAFETY: `u32` is a plain old datatype so we can always... uh...
1026+ // ...look, just pretend you forgot what you just read.
1027+ // Stability concerns.
1028+ let rt_u32_to_f32 = |rt| unsafe { mem:: transmute :: < u32 , f32 > ( rt) } ;
1029+ // SAFETY: We use internal implementations that either always work or fail at compile time.
1030+ unsafe { intrinsics:: const_eval_select ( ( v, ) , ct_u32_to_f32, rt_u32_to_f32) }
8821031 }
8831032
8841033 /// Return the memory representation of this floating point number as a byte array in
0 commit comments