@@ -623,6 +623,23 @@ impl f32 {
623
623
}
624
624
}
625
625
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
+
626
643
/// Returns `true` if `self` has a positive sign, including `+0.0`, `NaN`s with
627
644
/// positive sign bit and positive infinity.
628
645
///
@@ -874,8 +891,59 @@ impl f32 {
874
891
#[ rustc_const_unstable( feature = "const_float_bits_conv" , issue = "72447" ) ]
875
892
#[ inline]
876
893
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) }
879
947
}
880
948
881
949
/// Raw transmutation from `u32`.
@@ -919,9 +987,49 @@ impl f32 {
919
987
#[ must_use]
920
988
#[ inline]
921
989
pub const fn from_bits ( v : u32 ) -> Self {
922
- // SAFETY: `u32` is a plain old datatype so we can always transmute from it
923
990
// 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) }
925
1033
}
926
1034
927
1035
/// Return the memory representation of this floating point number as a byte array in
0 commit comments