about summary refs log tree commit diff
path: root/src/tools/miri/tests/pass
diff options
context:
space:
mode:
authorRalf Jung <post@ralfj.de>2023-10-08 12:03:01 +0200
committerRalf Jung <post@ralfj.de>2023-10-08 20:35:46 +0200
commit6796c5765d7b52271cd09ab977cb617ee3971a3a (patch)
tree01c142e4e604df2dab1724493b5d230552d61c81 /src/tools/miri/tests/pass
parentd087c6fae215484bf47ba6dda1bc836ef4c07ac5 (diff)
downloadrust-6796c5765d7b52271cd09ab977cb617ee3971a3a.tar.gz
rust-6796c5765d7b52271cd09ab977cb617ee3971a3a.zip
miri: make NaN generation non-deterministic
Diffstat (limited to 'src/tools/miri/tests/pass')
-rw-r--r--src/tools/miri/tests/pass/float_nan.rs316
1 files changed, 316 insertions, 0 deletions
diff --git a/src/tools/miri/tests/pass/float_nan.rs b/src/tools/miri/tests/pass/float_nan.rs
new file mode 100644
index 00000000000..8fa567aa106
--- /dev/null
+++ b/src/tools/miri/tests/pass/float_nan.rs
@@ -0,0 +1,316 @@
+use std::collections::HashSet;
+use std::fmt;
+use std::hash::Hash;
+use std::hint::black_box;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+enum Sign {
+    Neg = 1,
+    Pos = 0,
+}
+use Sign::*;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+enum NaNKind {
+    Quiet = 1,
+    Signaling = 0,
+}
+use NaNKind::*;
+
+#[track_caller]
+fn check_all_outcomes<T: Eq + Hash + fmt::Display>(expected: HashSet<T>, generate: impl Fn() -> T) {
+    let mut seen = HashSet::new();
+    // Let's give it 8x as many tries as we are expecting values.
+    let tries = expected.len() * 8;
+    for _ in 0..tries {
+        let val = generate();
+        assert!(expected.contains(&val), "got an unexpected value: {val}");
+        seen.insert(val);
+    }
+    // Let's see if we saw them all.
+    for val in expected {
+        if !seen.contains(&val) {
+            panic!("did not get value that should be possible: {val}");
+        }
+    }
+}
+
+// -- f32 support
+#[repr(C)]
+#[derive(Copy, Clone, Eq, PartialEq, Hash)]
+struct F32(u32);
+
+impl From<f32> for F32 {
+    fn from(x: f32) -> Self {
+        F32(x.to_bits())
+    }
+}
+
+/// Returns a value that is `ones` many 1-bits.
+fn u32_ones(ones: u32) -> u32 {
+    assert!(ones <= 32);
+    if ones == 0 {
+        // `>>` by 32 doesn't actually shift. So inconsistent :(
+        return 0;
+    }
+    u32::MAX >> (32 - ones)
+}
+
+const F32_SIGN_BIT: u32 = 32 - 1; // position of the sign bit
+const F32_EXP: u32 = 8; // 8 bits of exponent
+const F32_MANTISSA: u32 = F32_SIGN_BIT - F32_EXP;
+const F32_NAN_PAYLOAD: u32 = F32_MANTISSA - 1;
+
+impl fmt::Display for F32 {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        // Alaways show raw bits.
+        write!(f, "0x{:08x} ", self.0)?;
+        // Also show nice version.
+        let val = self.0;
+        let sign = val >> F32_SIGN_BIT;
+        let val = val & u32_ones(F32_SIGN_BIT); // mask away sign
+        let exp = val >> F32_MANTISSA;
+        let mantissa = val & u32_ones(F32_MANTISSA);
+        if exp == u32_ones(F32_EXP) {
+            // A NaN! Special printing.
+            let sign = if sign != 0 { Neg } else { Pos };
+            let quiet = if (mantissa >> F32_NAN_PAYLOAD) != 0 { Quiet } else { Signaling };
+            let payload = mantissa & u32_ones(F32_NAN_PAYLOAD);
+            write!(f, "(NaN: {:?}, {:?}, payload = {:#x})", sign, quiet, payload)
+        } else {
+            // Normal float value.
+            write!(f, "({})", f32::from_bits(self.0))
+        }
+    }
+}
+
+impl F32 {
+    fn nan(sign: Sign, kind: NaNKind, payload: u32) -> Self {
+        // Either the quiet bit must be set of the payload must be non-0;
+        // otherwise this is not a NaN but an infinity.
+        assert!(kind == Quiet || payload != 0);
+        // Payload must fit in 22 bits.
+        assert!(payload < (1 << F32_NAN_PAYLOAD));
+        // Concatenate the bits (with a 22bit payload).
+        // Pattern: [negative] ++ [1]^8 ++ [quiet] ++ [payload]
+        let val = ((sign as u32) << F32_SIGN_BIT)
+            | (u32_ones(F32_EXP) << F32_MANTISSA)
+            | ((kind as u32) << F32_NAN_PAYLOAD)
+            | payload;
+        // Sanity check.
+        assert!(f32::from_bits(val).is_nan());
+        // Done!
+        F32(val)
+    }
+
+    fn as_f32(self) -> f32 {
+        black_box(f32::from_bits(self.0))
+    }
+}
+
+// -- f64 support
+#[repr(C)]
+#[derive(Copy, Clone, Eq, PartialEq, Hash)]
+struct F64(u64);
+
+impl From<f64> for F64 {
+    fn from(x: f64) -> Self {
+        F64(x.to_bits())
+    }
+}
+
+/// Returns a value that is `ones` many 1-bits.
+fn u64_ones(ones: u32) -> u64 {
+    assert!(ones <= 64);
+    if ones == 0 {
+        // `>>` by 32 doesn't actually shift. So inconsistent :(
+        return 0;
+    }
+    u64::MAX >> (64 - ones)
+}
+
+const F64_SIGN_BIT: u32 = 64 - 1; // position of the sign bit
+const F64_EXP: u32 = 11; // 11 bits of exponent
+const F64_MANTISSA: u32 = F64_SIGN_BIT - F64_EXP;
+const F64_NAN_PAYLOAD: u32 = F64_MANTISSA - 1;
+
+impl fmt::Display for F64 {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        // Alaways show raw bits.
+        write!(f, "0x{:08x} ", self.0)?;
+        // Also show nice version.
+        let val = self.0;
+        let sign = val >> F64_SIGN_BIT;
+        let val = val & u64_ones(F64_SIGN_BIT); // mask away sign
+        let exp = val >> F64_MANTISSA;
+        let mantissa = val & u64_ones(F64_MANTISSA);
+        if exp == u64_ones(F64_EXP) {
+            // A NaN! Special printing.
+            let sign = if sign != 0 { Neg } else { Pos };
+            let quiet = if (mantissa >> F64_NAN_PAYLOAD) != 0 { Quiet } else { Signaling };
+            let payload = mantissa & u64_ones(F64_NAN_PAYLOAD);
+            write!(f, "(NaN: {:?}, {:?}, payload = {:#x})", sign, quiet, payload)
+        } else {
+            // Normal float value.
+            write!(f, "({})", f64::from_bits(self.0))
+        }
+    }
+}
+
+impl F64 {
+    fn nan(sign: Sign, kind: NaNKind, payload: u64) -> Self {
+        // Either the quiet bit must be set of the payload must be non-0;
+        // otherwise this is not a NaN but an infinity.
+        assert!(kind == Quiet || payload != 0);
+        // Payload must fit in 52 bits.
+        assert!(payload < (1 << F64_NAN_PAYLOAD));
+        // Concatenate the bits (with a 52bit payload).
+        // Pattern: [negative] ++ [1]^11 ++ [quiet] ++ [payload]
+        let val = ((sign as u64) << F64_SIGN_BIT)
+            | (u64_ones(F64_EXP) << F64_MANTISSA)
+            | ((kind as u64) << F64_NAN_PAYLOAD)
+            | payload;
+        // Sanity check.
+        assert!(f64::from_bits(val).is_nan());
+        // Done!
+        F64(val)
+    }
+
+    fn as_f64(self) -> f64 {
+        black_box(f64::from_bits(self.0))
+    }
+}
+
+// -- actual tests
+
+fn test_f32() {
+    // Freshly generated NaNs can have either sign.
+    check_all_outcomes(
+        HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
+        || F32::from(0.0 / black_box(0.0)),
+    );
+    // When there are NaN inputs, their payload can be propagated, with any sign.
+    let all1_payload = u32_ones(22);
+    let all1 = F32::nan(Pos, Quiet, all1_payload).as_f32();
+    check_all_outcomes(
+        HashSet::from_iter([
+            F32::nan(Pos, Quiet, 0),
+            F32::nan(Neg, Quiet, 0),
+            F32::nan(Pos, Quiet, all1_payload),
+            F32::nan(Neg, Quiet, all1_payload),
+        ]),
+        || F32::from(0.0 + all1),
+    );
+    // When there are two NaN inputs, the output can be either one, or the preferred NaN.
+    let just1 = F32::nan(Neg, Quiet, 1).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::nan(Pos, Quiet, all1_payload),
+            F32::nan(Neg, Quiet, all1_payload),
+        ]),
+        || F32::from(just1 - all1),
+    );
+    // When there are *signaling* NaN inputs, they might be quieted or not.
+    let all1_snan = F32::nan(Pos, Signaling, all1_payload).as_f32();
+    check_all_outcomes(
+        HashSet::from_iter([
+            F32::nan(Pos, Quiet, 0),
+            F32::nan(Neg, Quiet, 0),
+            F32::nan(Pos, Quiet, all1_payload),
+            F32::nan(Neg, Quiet, all1_payload),
+            F32::nan(Pos, Signaling, all1_payload),
+            F32::nan(Neg, Signaling, all1_payload),
+        ]),
+        || F32::from(0.0 * all1_snan),
+    );
+    // Mix signaling and non-signaling NaN.
+    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::nan(Pos, Quiet, all1_payload),
+            F32::nan(Neg, Quiet, all1_payload),
+            F32::nan(Pos, Signaling, all1_payload),
+            F32::nan(Neg, Signaling, all1_payload),
+        ]),
+        || F32::from(just1 % all1_snan),
+    );
+}
+
+fn test_f64() {
+    // Freshly generated NaNs can have either sign.
+    check_all_outcomes(
+        HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
+        || F64::from(0.0 / black_box(0.0)),
+    );
+    // When there are NaN inputs, their payload can be propagated, with any sign.
+    let all1_payload = u64_ones(51);
+    let all1 = F64::nan(Pos, Quiet, all1_payload).as_f64();
+    check_all_outcomes(
+        HashSet::from_iter([
+            F64::nan(Pos, Quiet, 0),
+            F64::nan(Neg, Quiet, 0),
+            F64::nan(Pos, Quiet, all1_payload),
+            F64::nan(Neg, Quiet, all1_payload),
+        ]),
+        || F64::from(0.0 + all1),
+    );
+    // When there are two NaN inputs, the output can be either one, or the preferred NaN.
+    let just1 = F64::nan(Neg, Quiet, 1).as_f64();
+    check_all_outcomes(
+        HashSet::from_iter([
+            F64::nan(Pos, Quiet, 0),
+            F64::nan(Neg, Quiet, 0),
+            F64::nan(Pos, Quiet, 1),
+            F64::nan(Neg, Quiet, 1),
+            F64::nan(Pos, Quiet, all1_payload),
+            F64::nan(Neg, Quiet, all1_payload),
+        ]),
+        || F64::from(just1 - all1),
+    );
+    // When there are *signaling* NaN inputs, they might be quieted or not.
+    let all1_snan = F64::nan(Pos, Signaling, all1_payload).as_f64();
+    check_all_outcomes(
+        HashSet::from_iter([
+            F64::nan(Pos, Quiet, 0),
+            F64::nan(Neg, Quiet, 0),
+            F64::nan(Pos, Quiet, all1_payload),
+            F64::nan(Neg, Quiet, all1_payload),
+            F64::nan(Pos, Signaling, all1_payload),
+            F64::nan(Neg, Signaling, all1_payload),
+        ]),
+        || F64::from(0.0 * all1_snan),
+    );
+    // Mix signaling and non-signaling NaN.
+    check_all_outcomes(
+        HashSet::from_iter([
+            F64::nan(Pos, Quiet, 0),
+            F64::nan(Neg, Quiet, 0),
+            F64::nan(Pos, Quiet, 1),
+            F64::nan(Neg, Quiet, 1),
+            F64::nan(Pos, Quiet, all1_payload),
+            F64::nan(Neg, Quiet, all1_payload),
+            F64::nan(Pos, Signaling, all1_payload),
+            F64::nan(Neg, Signaling, all1_payload),
+        ]),
+        || F64::from(just1 % all1_snan),
+    );
+}
+
+fn main() {
+    // Check our constants against std, just to be sure.
+    // We add 1 since our numbers are the number of bits stored
+    // to represent the value, and std has the precision of the value,
+    // which is one more due to the implicit leading 1.
+    assert_eq!(F32_MANTISSA + 1, f32::MANTISSA_DIGITS);
+    assert_eq!(F64_MANTISSA + 1, f64::MANTISSA_DIGITS);
+
+    test_f32();
+    test_f64();
+}