1212#![ unstable( feature = "f16" , issue = "116909" ) ]
1313
1414use crate :: convert:: FloatToInt ;
15- #[ cfg( not( test) ) ]
16- use crate :: intrinsics;
1715use crate :: mem;
1816use crate :: num:: FpCategory ;
1917
@@ -426,15 +424,15 @@ impl f16 {
426424 pub const fn classify ( self ) -> FpCategory {
427425 // A previous implementation for f32/f64 tried to only use bitmask-based checks,
428426 // 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` .
427+ // If we only cared about being "technically" correct, that's an entirely legit
428+ // implementation .
431429 //
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 .
430+ // Unfortunately, there are platforms out there that do not correctly implement the IEEE
431+ // float semantics Rust relies on: some hardware flushes denormals to zero, and some
432+ // platforms convert to `f32` to perform operations without properly rounding back (e.g .
433+ // WASM, see llvm/llvm-project#96437). These are platforms bugs, and Rust will misbehave on
434+ // such platforms, but we can at least try to make things seem as sane as possible by being
435+ // careful here .
438436 if self . is_infinite ( ) {
439437 // Thus, a value may compare unequal to infinity, despite having a "full" exponent mask.
440438 FpCategory :: Infinite
@@ -447,45 +445,19 @@ impl f16 {
447445 // because it may be wrong under "denormals are zero" and "flush to zero" modes.
448446 // Most of std's targets don't use those, but they are used for thumbv7neon.
449447 // 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 ,
448+ f16:: partial_classify ( self )
477449 }
478450 }
479451
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 .
452+ /// On x87, due do the incorrect float codegen on this hardware, this doesn't actually return a
453+ /// right answer for NaN because it cannot correctly discern between a floating point NaN, and
454+ /// some normal floating point numbers truncated from an x87 FPU .
483455 #[ inline]
484456 #[ rustc_const_unstable( feature = "const_float_classify" , issue = "72505" ) ]
485- const fn classify_bits ( b : u16 ) -> FpCategory {
457+ const fn partial_classify ( self ) -> FpCategory {
458+ let b = self . to_bits ( ) ;
486459 match ( b & Self :: MAN_MASK , b & Self :: EXP_MASK ) {
487460 ( 0 , Self :: EXP_MASK ) => FpCategory :: Infinite ,
488- ( _, Self :: EXP_MASK ) => FpCategory :: Nan ,
489461 ( 0 , 0 ) => FpCategory :: Zero ,
490462 ( _, 0 ) => FpCategory :: Subnormal ,
491463 _ => FpCategory :: Normal ,
@@ -781,48 +753,7 @@ impl f16 {
781753 #[ must_use = "this returns the result of the operation, without modifying the original" ]
782754 pub const fn to_bits ( self ) -> u16 {
783755 // SAFETY: `u16` is a plain old datatype so we can always transmute to it.
784- // ...sorta.
785- //
786- // It turns out that at runtime, it is possible for a floating point number
787- // to be subject to a floating point mode that alters nonzero subnormal numbers
788- // to zero on reads and writes, aka "denormals are zero" and "flush to zero".
789- //
790- // And, of course evaluating to a NaN value is fairly nondeterministic.
791- // More precisely: when NaN should be returned is knowable, but which NaN?
792- // So far that's defined by a combination of LLVM and the CPU, not Rust.
793- // This function, however, allows observing the bitstring of a NaN,
794- // thus introspection on CTFE.
795- //
796- // In order to preserve, at least for the moment, const-to-runtime equivalence,
797- // we reject any of these possible situations from happening.
798- #[ inline]
799- #[ rustc_const_unstable( feature = "const_float_bits_conv" , issue = "72447" ) ]
800- const fn ct_f16_to_u16 ( ct : f16 ) -> u16 {
801- // FIXME(f16_f128): we should use `.classify()` like `f32` and `f64`, but we don't yet
802- // want to rely on that on all platforms because it is nondeterministic (e.g. x86 has
803- // convention discrepancies calling intrinsics). So just classify the bits instead.
804-
805- // SAFETY: this is a POD transmutation
806- let bits = unsafe { mem:: transmute :: < f16 , u16 > ( ct) } ;
807- match f16:: classify_bits ( bits) {
808- FpCategory :: Nan => {
809- panic ! ( "const-eval error: cannot use f16::to_bits on a NaN" )
810- }
811- FpCategory :: Subnormal => {
812- panic ! ( "const-eval error: cannot use f16::to_bits on a subnormal number" )
813- }
814- FpCategory :: Infinite | FpCategory :: Normal | FpCategory :: Zero => bits,
815- }
816- }
817-
818- #[ inline( always) ] // See https://github.com/rust-lang/compiler-builtins/issues/491
819- fn rt_f16_to_u16 ( x : f16 ) -> u16 {
820- // SAFETY: `u16` is a plain old datatype so we can always... uh...
821- // ...look, just pretend you forgot what you just read.
822- // Stability concerns.
823- unsafe { mem:: transmute ( x) }
824- }
825- intrinsics:: const_eval_select ( ( self , ) , ct_f16_to_u16, rt_f16_to_u16)
756+ unsafe { mem:: transmute ( self ) }
826757 }
827758
828759 /// Raw transmutation from `u16`.
@@ -869,49 +800,8 @@ impl f16 {
869800 #[ rustc_const_unstable( feature = "const_float_bits_conv" , issue = "72447" ) ]
870801 pub const fn from_bits ( v : u16 ) -> Self {
871802 // It turns out the safety issues with sNaN were overblown! Hooray!
872- // SAFETY: `u16` is a plain old datatype so we can always transmute from it
873- // ...sorta.
874- //
875- // It turns out that at runtime, it is possible for a floating point number
876- // to be subject to floating point modes that alter nonzero subnormal numbers
877- // to zero on reads and writes, aka "denormals are zero" and "flush to zero".
878- // This is not a problem usually, but at least one tier2 platform for Rust
879- // actually exhibits this behavior by default: thumbv7neon
880- // aka "the Neon FPU in AArch32 state"
881- //
882- // And, of course evaluating to a NaN value is fairly nondeterministic.
883- // More precisely: when NaN should be returned is knowable, but which NaN?
884- // So far that's defined by a combination of LLVM and the CPU, not Rust.
885- // This function, however, allows observing the bitstring of a NaN,
886- // thus introspection on CTFE.
887- //
888- // In order to preserve, at least for the moment, const-to-runtime equivalence,
889- // reject any of these possible situations from happening.
890- #[ inline]
891- #[ rustc_const_unstable( feature = "const_float_bits_conv" , issue = "72447" ) ]
892- const fn ct_u16_to_f16 ( ct : u16 ) -> f16 {
893- match f16:: classify_bits ( ct) {
894- FpCategory :: Subnormal => {
895- panic ! ( "const-eval error: cannot use f16::from_bits on a subnormal number" )
896- }
897- FpCategory :: Nan => {
898- panic ! ( "const-eval error: cannot use f16::from_bits on NaN" )
899- }
900- FpCategory :: Infinite | FpCategory :: Normal | FpCategory :: Zero => {
901- // SAFETY: It's not a frumious number
902- unsafe { mem:: transmute :: < u16 , f16 > ( ct) }
903- }
904- }
905- }
906-
907- #[ inline( always) ] // See https://github.com/rust-lang/compiler-builtins/issues/491
908- fn rt_u16_to_f16 ( x : u16 ) -> f16 {
909- // SAFETY: `u16` is a plain old datatype so we can always... uh...
910- // ...look, just pretend you forgot what you just read.
911- // Stability concerns.
912- unsafe { mem:: transmute ( x) }
913- }
914- intrinsics:: const_eval_select ( ( v, ) , ct_u16_to_f16, rt_u16_to_f16)
803+ // SAFETY: `u16` is a plain old datatype so we can always transmute from it.
804+ unsafe { mem:: transmute ( v) }
915805 }
916806
917807 /// Returns the memory representation of this floating point number as a byte array in
0 commit comments