about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_const_eval/src/interpret/cast.rs23
-rw-r--r--compiler/rustc_const_eval/src/interpret/machine.rs9
-rw-r--r--compiler/rustc_const_eval/src/interpret/operator.rs4
-rw-r--r--src/tools/miri/src/machine.rs5
-rw-r--r--src/tools/miri/src/operator.rs36
-rw-r--r--src/tools/miri/tests/pass/float_nan.rs90
6 files changed, 150 insertions, 17 deletions
diff --git a/compiler/rustc_const_eval/src/interpret/cast.rs b/compiler/rustc_const_eval/src/interpret/cast.rs
index b9f88cf6352..b9557eaf6ab 100644
--- a/compiler/rustc_const_eval/src/interpret/cast.rs
+++ b/compiler/rustc_const_eval/src/interpret/cast.rs
@@ -311,6 +311,21 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         F: Float + Into<Scalar<M::Provenance>> + FloatConvert<Single> + FloatConvert<Double>,
     {
         use rustc_type_ir::sty::TyKind::*;
+
+        fn adjust_nan<
+            'mir,
+            'tcx: 'mir,
+            M: Machine<'mir, 'tcx>,
+            F1: rustc_apfloat::Float + FloatConvert<F2>,
+            F2: rustc_apfloat::Float,
+        >(
+            ecx: &InterpCx<'mir, 'tcx, M>,
+            f1: F1,
+            f2: F2,
+        ) -> F2 {
+            if f2.is_nan() { M::generate_nan(ecx, &[f1]) } else { f2 }
+        }
+
         match *dest_ty.kind() {
             // float -> uint
             Uint(t) => {
@@ -330,9 +345,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 Scalar::from_int(v, size)
             }
             // float -> f32
-            Float(FloatTy::F32) => Scalar::from_f32(f.convert(&mut false).value),
+            Float(FloatTy::F32) => {
+                Scalar::from_f32(adjust_nan(self, f, f.convert(&mut false).value))
+            }
             // float -> f64
-            Float(FloatTy::F64) => Scalar::from_f64(f.convert(&mut false).value),
+            Float(FloatTy::F64) => {
+                Scalar::from_f64(adjust_nan(self, f, f.convert(&mut false).value))
+            }
             // That's it.
             _ => span_bug!(self.cur_span(), "invalid float to {} cast", dest_ty),
         }
diff --git a/compiler/rustc_const_eval/src/interpret/machine.rs b/compiler/rustc_const_eval/src/interpret/machine.rs
index b172fd9f517..b615ced6c76 100644
--- a/compiler/rustc_const_eval/src/interpret/machine.rs
+++ b/compiler/rustc_const_eval/src/interpret/machine.rs
@@ -6,7 +6,7 @@ use std::borrow::{Borrow, Cow};
 use std::fmt::Debug;
 use std::hash::Hash;
 
-use rustc_apfloat::Float;
+use rustc_apfloat::{Float, FloatConvert};
 use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece};
 use rustc_middle::mir;
 use rustc_middle::ty::layout::TyAndLayout;
@@ -243,9 +243,12 @@ pub trait Machine<'mir, 'tcx: 'mir>: Sized {
 
     /// Generate the NaN returned by a float operation, given the list of inputs.
     /// (This is all inputs, not just NaN inputs!)
-    fn generate_nan<F: Float>(_ecx: &InterpCx<'mir, 'tcx, Self>, _inputs: &[F]) -> F {
+    fn generate_nan<F1: Float + FloatConvert<F2>, F2: Float>(
+        _ecx: &InterpCx<'mir, 'tcx, Self>,
+        _inputs: &[F1],
+    ) -> F2 {
         // By default we always return the preferred NaN.
-        F::NAN
+        F2::NAN
     }
 
     /// Called before writing the specified `local` of the `frame`.
diff --git a/compiler/rustc_const_eval/src/interpret/operator.rs b/compiler/rustc_const_eval/src/interpret/operator.rs
index a685b499d0e..53e1756d897 100644
--- a/compiler/rustc_const_eval/src/interpret/operator.rs
+++ b/compiler/rustc_const_eval/src/interpret/operator.rs
@@ -1,4 +1,4 @@
-use rustc_apfloat::Float;
+use rustc_apfloat::{Float, FloatConvert};
 use rustc_middle::mir;
 use rustc_middle::mir::interpret::{InterpResult, Scalar};
 use rustc_middle::ty::layout::TyAndLayout;
@@ -104,7 +104,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         (ImmTy::from_bool(res, *self.tcx), false)
     }
 
-    fn binary_float_op<F: Float + Into<Scalar<M::Provenance>>>(
+    fn binary_float_op<F: Float + FloatConvert<F> + Into<Scalar<M::Provenance>>>(
         &self,
         bin_op: mir::BinOp,
         layout: TyAndLayout<'tcx>,
diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs
index d7177a4a1d2..3de27460860 100644
--- a/src/tools/miri/src/machine.rs
+++ b/src/tools/miri/src/machine.rs
@@ -1002,7 +1002,10 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
     }
 
     #[inline(always)]
-    fn generate_nan<F: rustc_apfloat::Float>(ecx: &InterpCx<'mir, 'tcx, Self>, inputs: &[F]) -> F {
+    fn generate_nan<F1: rustc_apfloat::Float + rustc_apfloat::FloatConvert<F2>, F2: rustc_apfloat::Float>(
+        ecx: &InterpCx<'mir, 'tcx, Self>,
+        inputs: &[F1],
+    ) -> F2 {
         ecx.generate_nan(inputs)
     }
 
diff --git a/src/tools/miri/src/operator.rs b/src/tools/miri/src/operator.rs
index d655d1b0465..e5a437f95f0 100644
--- a/src/tools/miri/src/operator.rs
+++ b/src/tools/miri/src/operator.rs
@@ -3,7 +3,7 @@ use std::iter;
 use log::trace;
 
 use rand::{seq::IteratorRandom, Rng};
-use rustc_apfloat::Float;
+use rustc_apfloat::{Float, FloatConvert};
 use rustc_middle::mir;
 use rustc_target::abi::Size;
 
@@ -78,17 +78,35 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         })
     }
 
-    fn generate_nan<F: Float>(&self, inputs: &[F]) -> F {
+    fn generate_nan<F1: Float + FloatConvert<F2>, F2: Float>(&self, inputs: &[F1]) -> F2 {
+        /// Make the given NaN a signaling NaN.
+        /// Returns `None` if this would not result in a NaN.
+        fn make_signaling<F: Float>(f: F) -> Option<F> {
+            // The quiet/signaling bit is the leftmost bit in the mantissa.
+            // That's position `PRECISION-1`, since `PRECISION` includes the fixed leading 1 bit,
+            // and then we subtract 1 more since this is 0-indexed.
+            let quiet_bit_mask = 1 << (F::PRECISION - 2);
+            // Unset the bit. Double-check that this wasn't the last bit set in the payload.
+            // (which would turn the NaN into an infinity).
+            let f = F::from_bits(f.to_bits() & !quiet_bit_mask);
+            if f.is_nan() { Some(f) } else { None }
+        }
+
         let this = self.eval_context_ref();
         let mut rand = this.machine.rng.borrow_mut();
-        // Assemble an iterator of possible NaNs: preferred, unchanged propagation, quieting propagation.
-        let preferred_nan = F::qnan(Some(0));
+        // Assemble an iterator of possible NaNs: preferred, quieting propagation, unchanged propagation.
+        // On some targets there are more possibilities; for now we just generate those options that
+        // are possible everywhere.
+        let preferred_nan = F2::qnan(Some(0));
         let nans = iter::once(preferred_nan)
-            .chain(inputs.iter().filter(|f| f.is_nan()).copied())
-            .chain(inputs.iter().filter(|f| f.is_signaling()).map(|f| {
-                // Make it quiet, by setting the bit. We assume that `preferred_nan`
-                // only has bits set that all quiet NaNs need to have set.
-                F::from_bits(f.to_bits() | preferred_nan.to_bits())
+            .chain(inputs.iter().filter(|f| f.is_nan()).map(|&f| {
+                // Regular apfloat cast is quieting.
+                f.convert(&mut false).value
+            }))
+            .chain(inputs.iter().filter(|f| f.is_signaling()).filter_map(|&f| {
+                let f: F2 = f.convert(&mut false).value;
+                // We have to de-quiet this again for unchanged propagation.
+                make_signaling(f)
             }));
         // Pick one of the NaNs.
         let nan = nans.choose(&mut *rand).unwrap();
diff --git a/src/tools/miri/tests/pass/float_nan.rs b/src/tools/miri/tests/pass/float_nan.rs
index cb9cc42c601..698aa447e26 100644
--- a/src/tools/miri/tests/pass/float_nan.rs
+++ b/src/tools/miri/tests/pass/float_nan.rs
@@ -311,6 +311,95 @@ fn test_f64() {
     );
 }
 
+fn test_casts() {
+    let all1_payload_32 = u32_ones(22);
+    let all1_payload_64 = u64_ones(51);
+    let left1_payload_64 = (all1_payload_32 as u64) << (51 - 22);
+
+    // 64-to-32
+    check_all_outcomes(
+        HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
+        || F32::from(F64::nan(Pos, Quiet, 0).as_f64() as f32),
+    );
+    // The preferred payload is always a possibility.
+    check_all_outcomes(
+        HashSet::from_iter([
+            F32::nan(Pos, Quiet, 0),
+            F32::nan(Neg, Quiet, 0),
+            F32::nan(Pos, Quiet, all1_payload_32),
+            F32::nan(Neg, Quiet, all1_payload_32),
+        ]),
+        || F32::from(F64::nan(Pos, Quiet, all1_payload_64).as_f64() as f32),
+    );
+    // If the input is signaling, then the output *may* also be signaling.
+    check_all_outcomes(
+        HashSet::from_iter([
+            F32::nan(Pos, Quiet, 0),
+            F32::nan(Neg, Quiet, 0),
+            F32::nan(Pos, Quiet, all1_payload_32),
+            F32::nan(Neg, Quiet, all1_payload_32),
+            F32::nan(Pos, Signaling, all1_payload_32),
+            F32::nan(Neg, Signaling, all1_payload_32),
+        ]),
+        || F32::from(F64::nan(Pos, Signaling, all1_payload_64).as_f64() as f32),
+    );
+    // Check that the low bits are gone (not the high bits).
+    check_all_outcomes(
+        HashSet::from_iter([
+            F32::nan(Pos, Quiet, 0),
+            F32::nan(Neg, Quiet, 0),
+        ]),
+        || F32::from(F64::nan(Pos, Quiet, 1).as_f64() as f32),
+    );
+    check_all_outcomes(
+        HashSet::from_iter([
+            F32::nan(Pos, Quiet, 0),
+            F32::nan(Neg, Quiet, 0),
+            F32::nan(Pos, Quiet, 1),
+            F32::nan(Neg, Quiet, 1),
+        ]),
+        || F32::from(F64::nan(Pos, Quiet, 1 << (51-22)).as_f64() as f32),
+    );
+    check_all_outcomes(
+        HashSet::from_iter([
+            F32::nan(Pos, Quiet, 0),
+            F32::nan(Neg, Quiet, 0),
+            // The `1` payload becomes `0`, and the `0` payload cannot be signaling,
+            // so these are the only options.
+        ]),
+        || F32::from(F64::nan(Pos, Signaling, 1).as_f64() as f32),
+    );
+
+    // 32-to-64
+    check_all_outcomes(
+        HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
+        || F64::from(F32::nan(Pos, Quiet, 0).as_f32() as f64),
+    );
+    // The preferred payload is always a possibility.
+    // Also checks that 0s are added on the right.
+    check_all_outcomes(
+        HashSet::from_iter([
+            F64::nan(Pos, Quiet, 0),
+            F64::nan(Neg, Quiet, 0),
+            F64::nan(Pos, Quiet, left1_payload_64),
+            F64::nan(Neg, Quiet, left1_payload_64),
+        ]),
+        || F64::from(F32::nan(Pos, Quiet, all1_payload_32).as_f32() as f64),
+    );
+    // If the input is signaling, then the output *may* also be signaling.
+    check_all_outcomes(
+        HashSet::from_iter([
+            F64::nan(Pos, Quiet, 0),
+            F64::nan(Neg, Quiet, 0),
+            F64::nan(Pos, Quiet, left1_payload_64),
+            F64::nan(Neg, Quiet, left1_payload_64),
+            F64::nan(Pos, Signaling, left1_payload_64),
+            F64::nan(Neg, Signaling, left1_payload_64),
+        ]),
+        || F64::from(F32::nan(Pos, Signaling, all1_payload_32).as_f32() as f64),
+    );
+}
+
 fn main() {
     // Check our constants against std, just to be sure.
     // We add 1 since our numbers are the number of bits stored
@@ -321,4 +410,5 @@ fn main() {
 
     test_f32();
     test_f64();
+    test_casts();
 }