@@ -449,7 +449,8 @@ impl f32 {
449
449
#[ inline]
450
450
#[ rustc_const_unstable( feature = "const_float_classify" , issue = "72505" ) ]
451
451
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 ) }
453
454
}
454
455
455
456
/// Returns `true` if this value is positive infinity or negative infinity, and
@@ -472,7 +473,10 @@ impl f32 {
472
473
#[ rustc_const_unstable( feature = "const_float_classify" , issue = "72505" ) ]
473
474
#[ inline]
474
475
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 )
476
480
}
477
481
478
482
/// Returns `true` if this number is neither infinite nor `NaN`.
@@ -568,15 +572,76 @@ impl f32 {
568
572
#[ stable( feature = "rust1" , since = "1.0.0" ) ]
569
573
#[ rustc_const_unstable( feature = "const_float_classify" , issue = "72505" ) ]
570
574
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 {
571
620
const EXP_MASK : u32 = 0x7f800000 ;
572
621
const MAN_MASK : u32 = 0x007fffff ;
573
622
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 ) {
576
626
( 0 , 0 ) => FpCategory :: Zero ,
577
627
( _, 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 ) {
578
641
( 0 , EXP_MASK ) => FpCategory :: Infinite ,
579
642
( _, EXP_MASK ) => FpCategory :: Nan ,
643
+ ( 0 , 0 ) => FpCategory :: Zero ,
644
+ ( _, 0 ) => FpCategory :: Subnormal ,
580
645
_ => FpCategory :: Normal ,
581
646
}
582
647
}
@@ -616,7 +681,8 @@ impl f32 {
616
681
pub const fn is_sign_negative ( self ) -> bool {
617
682
// IEEE754 says: isSignMinus(x) is true if and only if x has negative sign. isSignMinus
618
683
// 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 }
620
686
}
621
687
622
688
/// Takes the reciprocal (inverse) of a number, `1/x`.
@@ -831,8 +897,49 @@ impl f32 {
831
897
#[ rustc_const_unstable( feature = "const_float_bits_conv" , issue = "72447" ) ]
832
898
#[ inline]
833
899
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) }
836
943
}
837
944
838
945
/// Raw transmutation from `u32`.
@@ -876,9 +983,51 @@ impl f32 {
876
983
#[ must_use]
877
984
#[ inline]
878
985
pub const fn from_bits ( v : u32 ) -> Self {
879
- // SAFETY: `u32` is a plain old datatype so we can always transmute from it
880
986
// 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) }
882
1031
}
883
1032
884
1033
/// Return the memory representation of this floating point number as a byte array in
0 commit comments