@@ -284,7 +284,7 @@ impl f16 {
284284 #[ inline]
285285 #[ rustc_const_unstable( feature = "const_float_classify" , issue = "72505" ) ]
286286 pub ( crate ) const fn abs_private ( self ) -> f16 {
287- // SAFETY: This transmutation is fine. Probably. For the reasons std is using it .
287+ // SAFETY: This transmutation is fine just like in `to_bits`/`from_bits` .
288288 unsafe { mem:: transmute :: < u16 , f16 > ( mem:: transmute :: < f16 , u16 > ( self ) & !Self :: SIGN_MASK ) }
289289 }
290290
@@ -426,15 +426,15 @@ impl f16 {
426426 pub const fn classify ( self ) -> FpCategory {
427427 // A previous implementation for f32/f64 tried to only use bitmask-based checks,
428428 // using `to_bits` to transmute the float to its bit repr and match on that.
429- // Unfortunately, floating point numbers can be much worse than that.
430- // This also needs to not result in recursive evaluations of `to_bits` .
429+ // If we only cared about being "technically" correct, that's an entirely legit
430+ // implementation .
431431 //
432-
433- // Platforms without native support generally convert to `f32` to perform operations,
434- // and most of these platforms correctly round back to `f16` after each operation .
435- // However, some platforms have bugs where they keep the excess `f32` precision (e.g.
436- // WASM, see llvm/llvm-project#96437). This implementation makes a best-effort attempt
437- // to account for that excess precision .
432+ // Unfortunately, there are platforms out there that do not correctly implement the IEEE
433+ // float semantics Rust relies on: some hardware flushes denormals to zero, and some
434+ // platforms convert to `f32` to perform operations without properly rounding back (e.g .
435+ // WASM, see llvm/llvm-project#96437). These are platforms bugs, and Rust will misbehave on
436+ // such platforms, but we can at least try to make things seem as sane as possible by being
437+ // careful here .
438438 if self . is_infinite ( ) {
439439 // Thus, a value may compare unequal to infinity, despite having a "full" exponent mask.
440440 FpCategory :: Infinite
@@ -446,49 +446,20 @@ impl f16 {
446446 // as correctness requires avoiding equality tests that may be Subnormal == -0.0
447447 // because it may be wrong under "denormals are zero" and "flush to zero" modes.
448448 // Most of std's targets don't use those, but they are used for thumbv7neon.
449- // So, this does use bitpattern matching for the rest.
450-
451- // SAFETY: f16 to u16 is fine. Usually.
452- // If classify has gotten this far, the value is definitely in one of these categories.
453- unsafe { f16:: partial_classify ( self ) }
454- }
455- }
456-
457- /// This doesn't actually return a right answer for NaN on purpose,
458- /// seeing as how it cannot correctly discern between a floating point NaN,
459- /// and some normal floating point numbers truncated from an x87 FPU.
460- ///
461- /// # Safety
462- ///
463- /// This requires making sure you call this function for values it answers correctly on,
464- /// otherwise it returns a wrong answer. This is not important for memory safety per se,
465- /// but getting floats correct is important for not accidentally leaking const eval
466- /// runtime-deviating logic which may or may not be acceptable.
467- #[ inline]
468- #[ rustc_const_unstable( feature = "const_float_classify" , issue = "72505" ) ]
469- const unsafe fn partial_classify ( self ) -> FpCategory {
470- // SAFETY: The caller is not asking questions for which this will tell lies.
471- let b = unsafe { mem:: transmute :: < f16 , u16 > ( self ) } ;
472- match ( b & Self :: MAN_MASK , b & Self :: EXP_MASK ) {
473- ( 0 , Self :: EXP_MASK ) => FpCategory :: Infinite ,
474- ( 0 , 0 ) => FpCategory :: Zero ,
475- ( _, 0 ) => FpCategory :: Subnormal ,
476- _ => FpCategory :: Normal ,
477- }
478- }
479-
480- /// This operates on bits, and only bits, so it can ignore concerns about weird FPUs.
481- /// FIXME(jubilee): In a just world, this would be the entire impl for classify,
482- /// plus a transmute. We do not live in a just world, but we can make it more so.
483- #[ inline]
484- #[ rustc_const_unstable( feature = "const_float_classify" , issue = "72505" ) ]
485- const fn classify_bits ( b : u16 ) -> FpCategory {
486- match ( b & Self :: MAN_MASK , b & Self :: EXP_MASK ) {
487- ( 0 , Self :: EXP_MASK ) => FpCategory :: Infinite ,
488- ( _, Self :: EXP_MASK ) => FpCategory :: Nan ,
489- ( 0 , 0 ) => FpCategory :: Zero ,
490- ( _, 0 ) => FpCategory :: Subnormal ,
491- _ => FpCategory :: Normal ,
449+ // So, this does use bitpattern matching for the rest. On x87, due to the incorrect
450+ // float codegen on this hardware, this doesn't actually return a right answer for NaN
451+ // because it cannot correctly discern between a floating point NaN, and some normal
452+ // floating point numbers truncated from an x87 FPU -- but we took care of NaN above, so
453+ // we are fine.
454+ // FIXME(jubilee): This probably could at least answer things correctly for Infinity,
455+ // like the f64 version does, but I need to run more checks on how things go on x86.
456+ // I fear losing mantissa data that would have answered that differently.
457+ let b = self . to_bits ( ) ;
458+ match ( b & Self :: MAN_MASK , b & Self :: EXP_MASK ) {
459+ ( 0 , 0 ) => FpCategory :: Zero ,
460+ ( _, 0 ) => FpCategory :: Subnormal ,
461+ _ => FpCategory :: Normal ,
462+ }
492463 }
493464 }
494465
@@ -952,48 +923,7 @@ impl f16 {
952923 #[ must_use = "this returns the result of the operation, without modifying the original" ]
953924 pub const fn to_bits ( self ) -> u16 {
954925 // SAFETY: `u16` is a plain old datatype so we can always transmute to it.
955- // ...sorta.
956- //
957- // It turns out that at runtime, it is possible for a floating point number
958- // to be subject to a floating point mode that alters nonzero subnormal numbers
959- // to zero on reads and writes, aka "denormals are zero" and "flush to zero".
960- //
961- // And, of course evaluating to a NaN value is fairly nondeterministic.
962- // More precisely: when NaN should be returned is knowable, but which NaN?
963- // So far that's defined by a combination of LLVM and the CPU, not Rust.
964- // This function, however, allows observing the bitstring of a NaN,
965- // thus introspection on CTFE.
966- //
967- // In order to preserve, at least for the moment, const-to-runtime equivalence,
968- // we reject any of these possible situations from happening.
969- #[ inline]
970- #[ rustc_const_unstable( feature = "const_float_bits_conv" , issue = "72447" ) ]
971- const fn ct_f16_to_u16 ( ct : f16 ) -> u16 {
972- // FIXME(f16_f128): we should use `.classify()` like `f32` and `f64`, but we don't yet
973- // want to rely on that on all platforms because it is nondeterministic (e.g. x86 has
974- // convention discrepancies calling intrinsics). So just classify the bits instead.
975-
976- // SAFETY: this is a POD transmutation
977- let bits = unsafe { mem:: transmute :: < f16 , u16 > ( ct) } ;
978- match f16:: classify_bits ( bits) {
979- FpCategory :: Nan => {
980- panic ! ( "const-eval error: cannot use f16::to_bits on a NaN" )
981- }
982- FpCategory :: Subnormal => {
983- panic ! ( "const-eval error: cannot use f16::to_bits on a subnormal number" )
984- }
985- FpCategory :: Infinite | FpCategory :: Normal | FpCategory :: Zero => bits,
986- }
987- }
988-
989- #[ inline( always) ] // See https://github.com/rust-lang/compiler-builtins/issues/491
990- fn rt_f16_to_u16 ( x : f16 ) -> u16 {
991- // SAFETY: `u16` is a plain old datatype so we can always... uh...
992- // ...look, just pretend you forgot what you just read.
993- // Stability concerns.
994- unsafe { mem:: transmute ( x) }
995- }
996- intrinsics:: const_eval_select ( ( self , ) , ct_f16_to_u16, rt_f16_to_u16)
926+ unsafe { mem:: transmute ( self ) }
997927 }
998928
999929 /// Raw transmutation from `u16`.
@@ -1040,49 +970,8 @@ impl f16 {
1040970 #[ rustc_const_unstable( feature = "const_float_bits_conv" , issue = "72447" ) ]
1041971 pub const fn from_bits ( v : u16 ) -> Self {
1042972 // It turns out the safety issues with sNaN were overblown! Hooray!
1043- // SAFETY: `u16` is a plain old datatype so we can always transmute from it
1044- // ...sorta.
1045- //
1046- // It turns out that at runtime, it is possible for a floating point number
1047- // to be subject to floating point modes that alter nonzero subnormal numbers
1048- // to zero on reads and writes, aka "denormals are zero" and "flush to zero".
1049- // This is not a problem usually, but at least one tier2 platform for Rust
1050- // actually exhibits this behavior by default: thumbv7neon
1051- // aka "the Neon FPU in AArch32 state"
1052- //
1053- // And, of course evaluating to a NaN value is fairly nondeterministic.
1054- // More precisely: when NaN should be returned is knowable, but which NaN?
1055- // So far that's defined by a combination of LLVM and the CPU, not Rust.
1056- // This function, however, allows observing the bitstring of a NaN,
1057- // thus introspection on CTFE.
1058- //
1059- // In order to preserve, at least for the moment, const-to-runtime equivalence,
1060- // reject any of these possible situations from happening.
1061- #[ inline]
1062- #[ rustc_const_unstable( feature = "const_float_bits_conv" , issue = "72447" ) ]
1063- const fn ct_u16_to_f16 ( ct : u16 ) -> f16 {
1064- match f16:: classify_bits ( ct) {
1065- FpCategory :: Subnormal => {
1066- panic ! ( "const-eval error: cannot use f16::from_bits on a subnormal number" )
1067- }
1068- FpCategory :: Nan => {
1069- panic ! ( "const-eval error: cannot use f16::from_bits on NaN" )
1070- }
1071- FpCategory :: Infinite | FpCategory :: Normal | FpCategory :: Zero => {
1072- // SAFETY: It's not a frumious number
1073- unsafe { mem:: transmute :: < u16 , f16 > ( ct) }
1074- }
1075- }
1076- }
1077-
1078- #[ inline( always) ] // See https://github.com/rust-lang/compiler-builtins/issues/491
1079- fn rt_u16_to_f16 ( x : u16 ) -> f16 {
1080- // SAFETY: `u16` is a plain old datatype so we can always... uh...
1081- // ...look, just pretend you forgot what you just read.
1082- // Stability concerns.
1083- unsafe { mem:: transmute ( x) }
1084- }
1085- intrinsics:: const_eval_select ( ( v, ) , ct_u16_to_f16, rt_u16_to_f16)
973+ // SAFETY: `u16` is a plain old datatype so we can always transmute from it.
974+ unsafe { mem:: transmute ( v) }
1086975 }
1087976
1088977 /// Returns the memory representation of this floating point number as a byte array in
0 commit comments