From dae48ea4bb28857153bf1705694022e7e0b411ce Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 27 Dec 2023 20:31:28 +0100 Subject: [PATCH 1/5] fast_math: detect non-finite results --- src/tools/miri/src/shims/intrinsics/mod.rs | 6 +++++- .../tests/fail/{ => intrinsics}/fast_math_both.rs | 0 .../fail/{ => intrinsics}/fast_math_both.stderr | 0 .../fail/{ => intrinsics}/fast_math_first.rs | 0 .../fail/{ => intrinsics}/fast_math_first.stderr | 0 .../tests/fail/intrinsics/fast_math_result.rs | 7 +++++++ .../tests/fail/intrinsics/fast_math_result.stderr | 15 +++++++++++++++ .../fail/{ => intrinsics}/fast_math_second.rs | 0 .../fail/{ => intrinsics}/fast_math_second.stderr | 0 9 files changed, 27 insertions(+), 1 deletion(-) rename src/tools/miri/tests/fail/{ => intrinsics}/fast_math_both.rs (100%) rename src/tools/miri/tests/fail/{ => intrinsics}/fast_math_both.stderr (100%) rename src/tools/miri/tests/fail/{ => intrinsics}/fast_math_first.rs (100%) rename src/tools/miri/tests/fail/{ => intrinsics}/fast_math_first.stderr (100%) create mode 100644 src/tools/miri/tests/fail/intrinsics/fast_math_result.rs create mode 100644 src/tools/miri/tests/fail/intrinsics/fast_math_result.stderr rename src/tools/miri/tests/fail/{ => intrinsics}/fast_math_second.rs (100%) rename src/tools/miri/tests/fail/{ => intrinsics}/fast_math_second.stderr (100%) diff --git a/src/tools/miri/src/shims/intrinsics/mod.rs b/src/tools/miri/src/shims/intrinsics/mod.rs index 66918db995d03..625ae3ef39e5e 100644 --- a/src/tools/miri/src/shims/intrinsics/mod.rs +++ b/src/tools/miri/src/shims/intrinsics/mod.rs @@ -268,7 +268,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ), _ => {} } - this.binop_ignore_overflow(op, &a, &b, dest)?; + let res = this.wrapping_binary_op(op, &a, &b)?; + if !float_finite(&res)? { + throw_ub_format!("`{intrinsic_name}` intrinsic produced non-finite value as result"); + } + this.write_immediate(*res, dest)?; } #[rustfmt::skip] diff --git a/src/tools/miri/tests/fail/fast_math_both.rs b/src/tools/miri/tests/fail/intrinsics/fast_math_both.rs similarity index 100% rename from src/tools/miri/tests/fail/fast_math_both.rs rename to src/tools/miri/tests/fail/intrinsics/fast_math_both.rs diff --git a/src/tools/miri/tests/fail/fast_math_both.stderr b/src/tools/miri/tests/fail/intrinsics/fast_math_both.stderr similarity index 100% rename from src/tools/miri/tests/fail/fast_math_both.stderr rename to src/tools/miri/tests/fail/intrinsics/fast_math_both.stderr diff --git a/src/tools/miri/tests/fail/fast_math_first.rs b/src/tools/miri/tests/fail/intrinsics/fast_math_first.rs similarity index 100% rename from src/tools/miri/tests/fail/fast_math_first.rs rename to src/tools/miri/tests/fail/intrinsics/fast_math_first.rs diff --git a/src/tools/miri/tests/fail/fast_math_first.stderr b/src/tools/miri/tests/fail/intrinsics/fast_math_first.stderr similarity index 100% rename from src/tools/miri/tests/fail/fast_math_first.stderr rename to src/tools/miri/tests/fail/intrinsics/fast_math_first.stderr diff --git a/src/tools/miri/tests/fail/intrinsics/fast_math_result.rs b/src/tools/miri/tests/fail/intrinsics/fast_math_result.rs new file mode 100644 index 0000000000000..4a143da057556 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/fast_math_result.rs @@ -0,0 +1,7 @@ +#![feature(core_intrinsics)] + +fn main() { + unsafe { + let _x: f32 = core::intrinsics::fdiv_fast(1.0, 0.0); //~ ERROR: `fdiv_fast` intrinsic produced non-finite value as result + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/fast_math_result.stderr b/src/tools/miri/tests/fail/intrinsics/fast_math_result.stderr new file mode 100644 index 0000000000000..5b24d2250266d --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/fast_math_result.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `fdiv_fast` intrinsic produced non-finite value as result + --> $DIR/fast_math_result.rs:LL:CC + | +LL | let _x: f32 = core::intrinsics::fdiv_fast(1.0, 0.0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `fdiv_fast` intrinsic produced non-finite value as result + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/fast_math_result.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/fast_math_second.rs b/src/tools/miri/tests/fail/intrinsics/fast_math_second.rs similarity index 100% rename from src/tools/miri/tests/fail/fast_math_second.rs rename to src/tools/miri/tests/fail/intrinsics/fast_math_second.rs diff --git a/src/tools/miri/tests/fail/fast_math_second.stderr b/src/tools/miri/tests/fail/intrinsics/fast_math_second.stderr similarity index 100% rename from src/tools/miri/tests/fail/fast_math_second.stderr rename to src/tools/miri/tests/fail/intrinsics/fast_math_second.stderr From 86198a15d7f67ad61a7988d881e9af68a0bbf361 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 28 Dec 2023 08:41:51 +0100 Subject: [PATCH 2/5] make float intrinsics return non-deterministic NaN --- src/tools/miri/src/helpers.rs | 45 ++++++ src/tools/miri/src/operator.rs | 4 + src/tools/miri/src/shims/intrinsics/mod.rs | 166 +++++++++++---------- src/tools/miri/tests/pass/float_nan.rs | 98 ++++++++++++ 4 files changed, 238 insertions(+), 75 deletions(-) diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index d2fd51b099ad7..98f646da6b69f 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -6,6 +6,7 @@ use std::time::Duration; use log::trace; use rustc_apfloat::ieee::{Double, Single}; +use rustc_apfloat::Float; use rustc_hir::def::{DefKind, Namespace}; use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX}; use rustc_index::IndexVec; @@ -117,6 +118,50 @@ fn try_resolve_did(tcx: TyCtxt<'_>, path: &[&str], namespace: Option) } } +/// Convert a softfloat type to its corresponding hostfloat type. +pub trait ToHost { + type HostFloat; + fn to_host(self) -> Self::HostFloat; +} + +/// Convert a hostfloat type to its corresponding softfloat type. +pub trait ToSoft { + type SoftFloat; + fn to_soft(self) -> Self::SoftFloat; +} + +impl ToHost for rustc_apfloat::ieee::Double { + type HostFloat = f64; + + fn to_host(self) -> Self::HostFloat { + f64::from_bits(self.to_bits().try_into().unwrap()) + } +} + +impl ToSoft for f64 { + type SoftFloat = rustc_apfloat::ieee::Double; + + fn to_soft(self) -> Self::SoftFloat { + Float::from_bits(self.to_bits().into()) + } +} + +impl ToHost for rustc_apfloat::ieee::Single { + type HostFloat = f32; + + fn to_host(self) -> Self::HostFloat { + f32::from_bits(self.to_bits().try_into().unwrap()) + } +} + +impl ToSoft for f32 { + type SoftFloat = rustc_apfloat::ieee::Single; + + fn to_soft(self) -> Self::SoftFloat { + Float::from_bits(self.to_bits().into()) + } +} + impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { /// Checks if the given crate/module exists. diff --git a/src/tools/miri/src/operator.rs b/src/tools/miri/src/operator.rs index e5a437f95f0ea..140764446969a 100644 --- a/src/tools/miri/src/operator.rs +++ b/src/tools/miri/src/operator.rs @@ -118,4 +118,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { nan } } + + fn adjust_nan, F2: Float>(&self, f: F2, inputs: &[F1]) -> F2 { + if f.is_nan() { self.generate_nan(inputs) } else { f } + } } diff --git a/src/tools/miri/src/shims/intrinsics/mod.rs b/src/tools/miri/src/shims/intrinsics/mod.rs index 625ae3ef39e5e..cc81ef6e6c916 100644 --- a/src/tools/miri/src/shims/intrinsics/mod.rs +++ b/src/tools/miri/src/shims/intrinsics/mod.rs @@ -15,7 +15,7 @@ use rustc_target::abi::Size; use crate::*; use atomic::EvalContextExt as _; -use helpers::check_arg_count; +use helpers::{check_arg_count, ToHost, ToSoft}; use simd::EvalContextExt as _; impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} @@ -146,12 +146,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let [f] = check_arg_count(args)?; let f = this.read_scalar(f)?.to_f32()?; // Can be implemented in soft-floats. + // This is a "bitwise" operation, so there's no NaN non-determinism. this.write_scalar(Scalar::from_f32(f.abs()), dest)?; } "fabsf64" => { let [f] = check_arg_count(args)?; let f = this.read_scalar(f)?.to_f64()?; // Can be implemented in soft-floats. + // This is a "bitwise" operation, so there's no NaN non-determinism. this.write_scalar(Scalar::from_f64(f.abs()), dest)?; } #[rustfmt::skip] @@ -170,25 +172,28 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { | "rintf32" => { let [f] = check_arg_count(args)?; + let f = this.read_scalar(f)?.to_f32()?; // FIXME: Using host floats. - let f = f32::from_bits(this.read_scalar(f)?.to_u32()?); - let f = match intrinsic_name { - "sinf32" => f.sin(), - "cosf32" => f.cos(), - "sqrtf32" => f.sqrt(), - "expf32" => f.exp(), - "exp2f32" => f.exp2(), - "logf32" => f.ln(), - "log10f32" => f.log10(), - "log2f32" => f.log2(), - "floorf32" => f.floor(), - "ceilf32" => f.ceil(), - "truncf32" => f.trunc(), - "roundf32" => f.round(), - "rintf32" => f.round_ties_even(), + let f_host = f.to_host(); + let res = match intrinsic_name { + "sinf32" => f_host.sin(), + "cosf32" => f_host.cos(), + "sqrtf32" => f_host.sqrt(), + "expf32" => f_host.exp(), + "exp2f32" => f_host.exp2(), + "logf32" => f_host.ln(), + "log10f32" => f_host.log10(), + "log2f32" => f_host.log2(), + "floorf32" => f_host.floor(), + "ceilf32" => f_host.ceil(), + "truncf32" => f_host.trunc(), + "roundf32" => f_host.round(), + "rintf32" => f_host.round_ties_even(), _ => bug!(), }; - this.write_scalar(Scalar::from_u32(f.to_bits()), dest)?; + let res = res.to_soft(); + let res = this.adjust_nan(res, &[f]); + this.write_scalar(res, dest)?; } #[rustfmt::skip] @@ -207,25 +212,28 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { | "rintf64" => { let [f] = check_arg_count(args)?; + let f = this.read_scalar(f)?.to_f64()?; // FIXME: Using host floats. - let f = f64::from_bits(this.read_scalar(f)?.to_u64()?); - let f = match intrinsic_name { - "sinf64" => f.sin(), - "cosf64" => f.cos(), - "sqrtf64" => f.sqrt(), - "expf64" => f.exp(), - "exp2f64" => f.exp2(), - "logf64" => f.ln(), - "log10f64" => f.log10(), - "log2f64" => f.log2(), - "floorf64" => f.floor(), - "ceilf64" => f.ceil(), - "truncf64" => f.trunc(), - "roundf64" => f.round(), - "rintf64" => f.round_ties_even(), + let f_host = f.to_host(); + let res = match intrinsic_name { + "sinf64" => f_host.sin(), + "cosf64" => f_host.cos(), + "sqrtf64" => f_host.sqrt(), + "expf64" => f_host.exp(), + "exp2f64" => f_host.exp2(), + "logf64" => f_host.ln(), + "log10f64" => f_host.log10(), + "log2f64" => f_host.log2(), + "floorf64" => f_host.floor(), + "ceilf64" => f_host.ceil(), + "truncf64" => f_host.trunc(), + "roundf64" => f_host.round(), + "rintf64" => f_host.round_ties_even(), _ => bug!(), }; - this.write_scalar(Scalar::from_u64(f.to_bits()), dest)?; + let res = res.to_soft(); + let res = this.adjust_nan(res, &[f]); + this.write_scalar(res, dest)?; } #[rustfmt::skip] @@ -272,6 +280,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { if !float_finite(&res)? { throw_ub_format!("`{intrinsic_name}` intrinsic produced non-finite value as result"); } + // This cannot be a NaN so we also don't have to apply any non-determinism. + // (Also, `wrapping_binary_op` already called `generate_nan` if needed.) this.write_immediate(*res, dest)?; } @@ -284,9 +294,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let a = this.read_scalar(a)?.to_f32()?; let b = this.read_scalar(b)?.to_f32()?; let res = match intrinsic_name { - "minnumf32" => a.min(b), - "maxnumf32" => a.max(b), - "copysignf32" => a.copy_sign(b), + "minnumf32" => this.adjust_nan(a.min(b), &[a, b]), + "maxnumf32" => this.adjust_nan(a.max(b), &[a, b]), + "copysignf32" => a.copy_sign(b), // bitwise, no NaN adjustments _ => bug!(), }; this.write_scalar(Scalar::from_f32(res), dest)?; @@ -301,68 +311,74 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let a = this.read_scalar(a)?.to_f64()?; let b = this.read_scalar(b)?.to_f64()?; let res = match intrinsic_name { - "minnumf64" => a.min(b), - "maxnumf64" => a.max(b), - "copysignf64" => a.copy_sign(b), + "minnumf64" => this.adjust_nan(a.min(b), &[a, b]), + "maxnumf64" => this.adjust_nan(a.max(b), &[a, b]), + "copysignf64" => a.copy_sign(b), // bitwise, no NaN adjustments _ => bug!(), }; this.write_scalar(Scalar::from_f64(res), dest)?; } - "powf32" => { - let [f, f2] = check_arg_count(args)?; - // FIXME: Using host floats. - let f = f32::from_bits(this.read_scalar(f)?.to_u32()?); - let f2 = f32::from_bits(this.read_scalar(f2)?.to_u32()?); - let res = f.powf(f2); - this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?; - } - - "powf64" => { - let [f, f2] = check_arg_count(args)?; - // FIXME: Using host floats. - let f = f64::from_bits(this.read_scalar(f)?.to_u64()?); - let f2 = f64::from_bits(this.read_scalar(f2)?.to_u64()?); - let res = f.powf(f2); - this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?; - } - "fmaf32" => { let [a, b, c] = check_arg_count(args)?; + let a = this.read_scalar(a)?.to_f32()?; + let b = this.read_scalar(b)?.to_f32()?; + let c = this.read_scalar(c)?.to_f32()?; // FIXME: Using host floats, to work around https://github.com/rust-lang/rustc_apfloat/issues/11 - let a = f32::from_bits(this.read_scalar(a)?.to_u32()?); - let b = f32::from_bits(this.read_scalar(b)?.to_u32()?); - let c = f32::from_bits(this.read_scalar(c)?.to_u32()?); - let res = a.mul_add(b, c); - this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?; + let res = a.to_host().mul_add(b.to_host(), c.to_host()).to_soft(); + let res = this.adjust_nan(res, &[a, b, c]); + this.write_scalar(res, dest)?; } "fmaf64" => { let [a, b, c] = check_arg_count(args)?; + let a = this.read_scalar(a)?.to_f64()?; + let b = this.read_scalar(b)?.to_f64()?; + let c = this.read_scalar(c)?.to_f64()?; // FIXME: Using host floats, to work around https://github.com/rust-lang/rustc_apfloat/issues/11 - let a = f64::from_bits(this.read_scalar(a)?.to_u64()?); - let b = f64::from_bits(this.read_scalar(b)?.to_u64()?); - let c = f64::from_bits(this.read_scalar(c)?.to_u64()?); - let res = a.mul_add(b, c); - this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?; + let res = a.to_host().mul_add(b.to_host(), c.to_host()).to_soft(); + let res = this.adjust_nan(res, &[a, b, c]); + this.write_scalar(res, dest)?; + } + + "powf32" => { + let [f1, f2] = check_arg_count(args)?; + let f1 = this.read_scalar(f1)?.to_f32()?; + let f2 = this.read_scalar(f2)?.to_f32()?; + // FIXME: Using host floats. + let res = f1.to_host().powf(f2.to_host()).to_soft(); + let res = this.adjust_nan(res, &[f1, f2]); + this.write_scalar(res, dest)?; + } + + "powf64" => { + let [f1, f2] = check_arg_count(args)?; + let f1 = this.read_scalar(f1)?.to_f64()?; + let f2 = this.read_scalar(f2)?.to_f64()?; + // FIXME: Using host floats. + let res = f1.to_host().powf(f2.to_host()).to_soft(); + let res = this.adjust_nan(res, &[f1, f2]); + this.write_scalar(res, dest)?; } "powif32" => { let [f, i] = check_arg_count(args)?; - // FIXME: Using host floats. - let f = f32::from_bits(this.read_scalar(f)?.to_u32()?); + let f = this.read_scalar(f)?.to_f32()?; let i = this.read_scalar(i)?.to_i32()?; - let res = f.powi(i); - this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?; + // FIXME: Using host floats. + let res = f.to_host().powi(i).to_soft(); + let res = this.adjust_nan(res, &[f]); + this.write_scalar(res, dest)?; } "powif64" => { let [f, i] = check_arg_count(args)?; - // FIXME: Using host floats. - let f = f64::from_bits(this.read_scalar(f)?.to_u64()?); + let f = this.read_scalar(f)?.to_f64()?; let i = this.read_scalar(i)?.to_i32()?; - let res = f.powi(i); - this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?; + // FIXME: Using host floats. + let res = f.to_host().powi(i).to_soft(); + let res = this.adjust_nan(res, &[f]); + this.write_scalar(res, dest)?; } "float_to_int_unchecked" => { diff --git a/src/tools/miri/tests/pass/float_nan.rs b/src/tools/miri/tests/pass/float_nan.rs index 6ea034e2cda9d..99151e5df7c0d 100644 --- a/src/tools/miri/tests/pass/float_nan.rs +++ b/src/tools/miri/tests/pass/float_nan.rs @@ -249,6 +249,55 @@ fn test_f32() { check_all_outcomes(HashSet::from_iter([F32::nan(Neg, Signaling, all1_payload)]), || { F32::from(-all1_snan) }); + + // Intrinsics + let nan = F32::nan(Neg, Quiet, 0).as_f32(); + check_all_outcomes( + HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]), + || F32::from(f32::min(nan, nan)), + ); + check_all_outcomes( + HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]), + || F32::from(nan.sin()), + ); + 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, 2), + F32::nan(Neg, Quiet, 2), + 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.mul_add(F32::nan(Neg, Quiet, 2).as_f32(), all1_snan)), + ); + check_all_outcomes( + HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]), + || F32::from(nan.powf(nan)), + ); + check_all_outcomes( + HashSet::from_iter([1.0f32.into()]), + || F32::from(1.0f32.powf(nan)), // special `pow` rule + ); + 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, Signaling, 1), + F32::nan(Neg, Signaling, 1), + ]), + || F32::from(1.0f32.powf(F32::nan(Pos, Signaling, 1).as_f32())), // unspecified `pow` case + ); + check_all_outcomes( + HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]), + || F32::from(nan.powi(1)), + ); } fn test_f64() { @@ -309,6 +358,55 @@ fn test_f64() { ]), || F64::from(just1 % all1_snan), ); + + // Intrinsics + let nan = F64::nan(Neg, Quiet, 0).as_f64(); + check_all_outcomes( + HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]), + || F64::from(f64::min(nan, nan)), + ); + check_all_outcomes( + HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]), + || F64::from(nan.sin()), + ); + 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, 2), + F64::nan(Neg, Quiet, 2), + 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.mul_add(F64::nan(Neg, Quiet, 2).as_f64(), all1_snan)), + ); + check_all_outcomes( + HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]), + || F64::from(nan.powf(nan)), + ); + check_all_outcomes( + HashSet::from_iter([1.0f64.into()]), + || F64::from(1.0f64.powf(nan)), // special `pow` rule + ); + 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, Signaling, 1), + F64::nan(Neg, Signaling, 1), + ]), + || F64::from(1.0f64.powf(F64::nan(Pos, Signaling, 1).as_f64())), // unspecified `pow` case + ); + check_all_outcomes( + HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]), + || F64::from(nan.powi(1)), + ); } fn test_casts() { From 0f98c0e6102cadfc8c0b759d09c7f1b73b07564c Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 28 Dec 2023 11:07:56 +0100 Subject: [PATCH 3/5] add NaN-nondet to libm functions --- src/tools/miri/src/shims/foreign_items.rs | 108 ++++++++++++---------- src/tools/miri/tests/pass/float_nan.rs | 40 ++++++++ 2 files changed, 101 insertions(+), 47 deletions(-) diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index 8ddfc05dd300a..e7b2a6823ed6e 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -23,6 +23,7 @@ use rustc_target::{ use super::backtrace::EvalContextExt as _; use crate::*; +use helpers::{ToHost, ToSoft}; /// Type of dynamic symbols (for `dlsym` et al) #[derive(Debug, Copy, Clone)] @@ -886,23 +887,26 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { | "tgammaf" => { let [f] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let f = this.read_scalar(f)?.to_f32()?; // FIXME: Using host floats. - let f = f32::from_bits(this.read_scalar(f)?.to_u32()?); + let f_host = f.to_host(); let res = match link_name.as_str() { - "cbrtf" => f.cbrt(), - "coshf" => f.cosh(), - "sinhf" => f.sinh(), - "tanf" => f.tan(), - "tanhf" => f.tanh(), - "acosf" => f.acos(), - "asinf" => f.asin(), - "atanf" => f.atan(), - "log1pf" => f.ln_1p(), - "expm1f" => f.exp_m1(), - "tgammaf" => f.gamma(), + "cbrtf" => f_host.cbrt(), + "coshf" => f_host.cosh(), + "sinhf" => f_host.sinh(), + "tanf" => f_host.tan(), + "tanhf" => f_host.tanh(), + "acosf" => f_host.acos(), + "asinf" => f_host.asin(), + "atanf" => f_host.atan(), + "log1pf" => f_host.ln_1p(), + "expm1f" => f_host.exp_m1(), + "tgammaf" => f_host.gamma(), _ => bug!(), }; - this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?; + let res = res.to_soft(); + let res = this.adjust_nan(res, &[f]); + this.write_scalar(res, dest)?; } #[rustfmt::skip] | "_hypotf" @@ -911,19 +915,20 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { | "fdimf" => { let [f1, f2] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let f1 = this.read_scalar(f1)?.to_f32()?; + let f2 = this.read_scalar(f2)?.to_f32()?; // underscore case for windows, here and below // (see https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/floating-point-primitives?view=vs-2019) // FIXME: Using host floats. - let f1 = f32::from_bits(this.read_scalar(f1)?.to_u32()?); - let f2 = f32::from_bits(this.read_scalar(f2)?.to_u32()?); let res = match link_name.as_str() { - "_hypotf" | "hypotf" => f1.hypot(f2), - "atan2f" => f1.atan2(f2), + "_hypotf" | "hypotf" => f1.to_host().hypot(f2.to_host()).to_soft(), + "atan2f" => f1.to_host().atan2(f2.to_host()).to_soft(), #[allow(deprecated)] - "fdimf" => f1.abs_sub(f2), + "fdimf" => f1.to_host().abs_sub(f2.to_host()).to_soft(), _ => bug!(), }; - this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?; + let res = this.adjust_nan(res, &[f1, f2]); + this.write_scalar(res, dest)?; } #[rustfmt::skip] | "cbrt" @@ -939,23 +944,26 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { | "tgamma" => { let [f] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let f = this.read_scalar(f)?.to_f64()?; // FIXME: Using host floats. - let f = f64::from_bits(this.read_scalar(f)?.to_u64()?); + let f_host = f.to_host(); let res = match link_name.as_str() { - "cbrt" => f.cbrt(), - "cosh" => f.cosh(), - "sinh" => f.sinh(), - "tan" => f.tan(), - "tanh" => f.tanh(), - "acos" => f.acos(), - "asin" => f.asin(), - "atan" => f.atan(), - "log1p" => f.ln_1p(), - "expm1" => f.exp_m1(), - "tgamma" => f.gamma(), + "cbrt" => f_host.cbrt(), + "cosh" => f_host.cosh(), + "sinh" => f_host.sinh(), + "tan" => f_host.tan(), + "tanh" => f_host.tanh(), + "acos" => f_host.acos(), + "asin" => f_host.asin(), + "atan" => f_host.atan(), + "log1p" => f_host.ln_1p(), + "expm1" => f_host.exp_m1(), + "tgamma" => f_host.gamma(), _ => bug!(), }; - this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?; + let res = res.to_soft(); + let res = this.adjust_nan(res, &[f]); + this.write_scalar(res, dest)?; } #[rustfmt::skip] | "_hypot" @@ -964,17 +972,20 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { | "fdim" => { let [f1, f2] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let f1 = this.read_scalar(f1)?.to_f64()?; + let f2 = this.read_scalar(f2)?.to_f64()?; + // underscore case for windows, here and below + // (see https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/floating-point-primitives?view=vs-2019) // FIXME: Using host floats. - let f1 = f64::from_bits(this.read_scalar(f1)?.to_u64()?); - let f2 = f64::from_bits(this.read_scalar(f2)?.to_u64()?); let res = match link_name.as_str() { - "_hypot" | "hypot" => f1.hypot(f2), - "atan2" => f1.atan2(f2), + "_hypot" | "hypot" => f1.to_host().hypot(f2.to_host()).to_soft(), + "atan2" => f1.to_host().atan2(f2.to_host()).to_soft(), #[allow(deprecated)] - "fdim" => f1.abs_sub(f2), + "fdim" => f1.to_host().abs_sub(f2.to_host()).to_soft(), _ => bug!(), }; - this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?; + let res = this.adjust_nan(res, &[f1, f2]); + this.write_scalar(res, dest)?; } #[rustfmt::skip] | "_ldexp" @@ -987,27 +998,30 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let exp = this.read_scalar(exp)?.to_i32()?; let res = x.scalbn(exp); - this.write_scalar(Scalar::from_f64(res), dest)?; + let res = this.adjust_nan(res, &[x]); + this.write_scalar(res, dest)?; } "lgammaf_r" => { let [x, signp] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - // FIXME: Using host floats. - let x = f32::from_bits(this.read_scalar(x)?.to_u32()?); + let x = this.read_scalar(x)?.to_f32()?; let signp = this.deref_pointer(signp)?; - let (res, sign) = x.ln_gamma(); + // FIXME: Using host floats. + let (res, sign) = x.to_host().ln_gamma(); this.write_int(sign, &signp)?; - this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?; + let res = this.adjust_nan(res.to_soft(), &[x]); + this.write_scalar(res, dest)?; } "lgamma_r" => { let [x, signp] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - // FIXME: Using host floats. - let x = f64::from_bits(this.read_scalar(x)?.to_u64()?); + let x = this.read_scalar(x)?.to_f64()?; let signp = this.deref_pointer(signp)?; - let (res, sign) = x.ln_gamma(); + // FIXME: Using host floats. + let (res, sign) = x.to_host().ln_gamma(); this.write_int(sign, &signp)?; - this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?; + let res = this.adjust_nan(res.to_soft(), &[x]); + this.write_scalar(res, dest)?; } // LLVM intrinsics diff --git a/src/tools/miri/tests/pass/float_nan.rs b/src/tools/miri/tests/pass/float_nan.rs index 99151e5df7c0d..5e717bdca0072 100644 --- a/src/tools/miri/tests/pass/float_nan.rs +++ b/src/tools/miri/tests/pass/float_nan.rs @@ -1,8 +1,16 @@ +#![feature(float_gamma)] use std::collections::HashSet; use std::fmt; use std::hash::Hash; use std::hint::black_box; +fn ldexp(a: f64, b: i32) -> f64 { + extern "C" { + fn ldexp(x: f64, n: i32) -> f64; + } + unsafe { ldexp(a, b) } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum Sign { Neg = 1, @@ -298,6 +306,20 @@ fn test_f32() { HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]), || F32::from(nan.powi(1)), ); + + // libm functions + check_all_outcomes( + HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]), + || F32::from(nan.sinh()), + ); + check_all_outcomes( + HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]), + || F32::from(nan.atan2(nan)), + ); + check_all_outcomes( + HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]), + || F32::from(nan.ln_gamma().0), + ); } fn test_f64() { @@ -407,6 +429,24 @@ fn test_f64() { HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]), || F64::from(nan.powi(1)), ); + + // libm functions + check_all_outcomes( + HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]), + || F64::from(nan.sinh()), + ); + check_all_outcomes( + HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]), + || F64::from(nan.atan2(nan)), + ); + check_all_outcomes( + HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]), + || F64::from(ldexp(nan, 1)), + ); + check_all_outcomes( + HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]), + || F64::from(nan.ln_gamma().0), + ); } fn test_casts() { From ee42d1eb9f5983f8f281e87989617caba45007b6 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 28 Dec 2023 12:14:01 +0100 Subject: [PATCH 4/5] NaN non-determinism for SIMD intrinsics --- src/tools/miri/src/shims/intrinsics/simd.rs | 162 +++++++++++--------- src/tools/miri/tests/pass/float_nan.rs | 58 ++++++- 2 files changed, 145 insertions(+), 75 deletions(-) diff --git a/src/tools/miri/src/shims/intrinsics/simd.rs b/src/tools/miri/src/shims/intrinsics/simd.rs index 2c8493d8aad1a..d749182ed5e71 100644 --- a/src/tools/miri/src/shims/intrinsics/simd.rs +++ b/src/tools/miri/src/shims/intrinsics/simd.rs @@ -5,10 +5,17 @@ use rustc_span::{sym, Symbol}; use rustc_target::abi::{Endian, HasDataLayout}; use crate::helpers::{ - bool_to_simd_element, check_arg_count, round_to_next_multiple_of, simd_element_to_bool, + bool_to_simd_element, check_arg_count, round_to_next_multiple_of, simd_element_to_bool, ToHost, + ToSoft, }; use crate::*; +#[derive(Copy, Clone)] +pub(crate) enum MinMax { + Min, + Max, +} + impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { /// Calls the simd intrinsic `intrinsic`; the `simd_` prefix has already been removed. @@ -67,13 +74,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let op = this.read_immediate(&this.project_index(&op, i)?)?; let dest = this.project_index(&dest, i)?; let val = match which { - Op::MirOp(mir_op) => this.wrapping_unary_op(mir_op, &op)?.to_scalar(), + Op::MirOp(mir_op) => { + // This already does NaN adjustments + this.wrapping_unary_op(mir_op, &op)?.to_scalar() + } Op::Abs => { // Works for f32 and f64. let ty::Float(float_ty) = op.layout.ty.kind() else { span_bug!(this.cur_span(), "{} operand is not a float", intrinsic_name) }; let op = op.to_scalar(); + // "Bitwise" operation, no NaN adjustments match float_ty { FloatTy::F32 => Scalar::from_f32(op.to_f32()?.abs()), FloatTy::F64 => Scalar::from_f64(op.to_f64()?.abs()), @@ -86,14 +97,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // FIXME using host floats match float_ty { FloatTy::F32 => { - let f = f32::from_bits(op.to_scalar().to_u32()?); - let res = f.sqrt(); - Scalar::from_u32(res.to_bits()) + let f = op.to_scalar().to_f32()?; + let res = f.to_host().sqrt().to_soft(); + let res = this.adjust_nan(res, &[f]); + Scalar::from(res) } FloatTy::F64 => { - let f = f64::from_bits(op.to_scalar().to_u64()?); - let res = f.sqrt(); - Scalar::from_u64(res.to_bits()) + let f = op.to_scalar().to_f64()?; + let res = f.to_host().sqrt().to_soft(); + let res = this.adjust_nan(res, &[f]); + Scalar::from(res) } } } @@ -105,11 +118,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { FloatTy::F32 => { let f = op.to_scalar().to_f32()?; let res = f.round_to_integral(rounding).value; + let res = this.adjust_nan(res, &[f]); Scalar::from_f32(res) } FloatTy::F64 => { let f = op.to_scalar().to_f64()?; let res = f.round_to_integral(rounding).value; + let res = this.adjust_nan(res, &[f]); Scalar::from_f64(res) } } @@ -157,8 +172,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { enum Op { MirOp(BinOp), SaturatingOp(BinOp), - FMax, - FMin, + FMinMax(MinMax), WrappingOffset, } let which = match intrinsic_name { @@ -178,8 +192,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { "le" => Op::MirOp(BinOp::Le), "gt" => Op::MirOp(BinOp::Gt), "ge" => Op::MirOp(BinOp::Ge), - "fmax" => Op::FMax, - "fmin" => Op::FMin, + "fmax" => Op::FMinMax(MinMax::Max), + "fmin" => Op::FMinMax(MinMax::Min), "saturating_add" => Op::SaturatingOp(BinOp::Add), "saturating_sub" => Op::SaturatingOp(BinOp::Sub), "arith_offset" => Op::WrappingOffset, @@ -192,6 +206,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let dest = this.project_index(&dest, i)?; let val = match which { Op::MirOp(mir_op) => { + // This does NaN adjustments. let (val, overflowed) = this.overflowing_binary_op(mir_op, &left, &right)?; if matches!(mir_op, BinOp::Shl | BinOp::Shr) { // Shifts have extra UB as SIMD operations that the MIR binop does not have. @@ -225,11 +240,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let offset_ptr = ptr.wrapping_signed_offset(offset_bytes, this); Scalar::from_maybe_pointer(offset_ptr, this) } - Op::FMax => { - fmax_op(&left, &right)? - } - Op::FMin => { - fmin_op(&left, &right)? + Op::FMinMax(op) => { + this.fminmax_op(op, &left, &right)? } }; this.write_scalar(val, &dest)?; @@ -259,18 +271,20 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { }; let val = match float_ty { FloatTy::F32 => { - let a = f32::from_bits(a.to_u32()?); - let b = f32::from_bits(b.to_u32()?); - let c = f32::from_bits(c.to_u32()?); - let res = a.mul_add(b, c); - Scalar::from_u32(res.to_bits()) + let a = a.to_f32()?; + let b = b.to_f32()?; + let c = c.to_f32()?; + let res = a.to_host().mul_add(b.to_host(), c.to_host()).to_soft(); + let res = this.adjust_nan(res, &[a, b, c]); + Scalar::from(res) } FloatTy::F64 => { - let a = f64::from_bits(a.to_u64()?); - let b = f64::from_bits(b.to_u64()?); - let c = f64::from_bits(c.to_u64()?); - let res = a.mul_add(b, c); - Scalar::from_u64(res.to_bits()) + let a = a.to_f64()?; + let b = b.to_f64()?; + let c = c.to_f64()?; + let res = a.to_host().mul_add(b.to_host(), c.to_host()).to_soft(); + let res = this.adjust_nan(res, &[a, b, c]); + Scalar::from(res) } }; this.write_scalar(val, &dest)?; @@ -295,8 +309,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { enum Op { MirOp(BinOp), MirOpBool(BinOp), - Max, - Min, + MinMax(MinMax), } let which = match intrinsic_name { "reduce_and" => Op::MirOp(BinOp::BitAnd), @@ -304,8 +317,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { "reduce_xor" => Op::MirOp(BinOp::BitXor), "reduce_any" => Op::MirOpBool(BinOp::BitOr), "reduce_all" => Op::MirOpBool(BinOp::BitAnd), - "reduce_max" => Op::Max, - "reduce_min" => Op::Min, + "reduce_max" => Op::MinMax(MinMax::Max), + "reduce_min" => Op::MinMax(MinMax::Min), _ => unreachable!(), }; @@ -325,24 +338,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let op = imm_from_bool(simd_element_to_bool(op)?); this.wrapping_binary_op(mir_op, &res, &op)? } - Op::Max => { - if matches!(res.layout.ty.kind(), ty::Float(_)) { - ImmTy::from_scalar(fmax_op(&res, &op)?, res.layout) - } else { - // Just boring integers, so NaNs to worry about - if this.wrapping_binary_op(BinOp::Ge, &res, &op)?.to_scalar().to_bool()? { - res - } else { - op - } - } - } - Op::Min => { + Op::MinMax(mmop) => { if matches!(res.layout.ty.kind(), ty::Float(_)) { - ImmTy::from_scalar(fmin_op(&res, &op)?, res.layout) + ImmTy::from_scalar(this.fminmax_op(mmop, &res, &op)?, res.layout) } else { // Just boring integers, so NaNs to worry about - if this.wrapping_binary_op(BinOp::Le, &res, &op)?.to_scalar().to_bool()? { + let mirop = match mmop { + MinMax::Min => BinOp::Le, + MinMax::Max => BinOp::Ge, + }; + if this.wrapping_binary_op(mirop, &res, &op)?.to_scalar().to_bool()? { res } else { op @@ -709,6 +714,43 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } Ok(()) } + + fn fminmax_op( + &self, + op: MinMax, + left: &ImmTy<'tcx, Provenance>, + right: &ImmTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_ref(); + assert_eq!(left.layout.ty, right.layout.ty); + let ty::Float(float_ty) = left.layout.ty.kind() else { + bug!("fmax operand is not a float") + }; + let left = left.to_scalar(); + let right = right.to_scalar(); + Ok(match float_ty { + FloatTy::F32 => { + let left = left.to_f32()?; + let right = right.to_f32()?; + let res = match op { + MinMax::Min => left.min(right), + MinMax::Max => left.max(right), + }; + let res = this.adjust_nan(res, &[left, right]); + Scalar::from_f32(res) + } + FloatTy::F64 => { + let left = left.to_f64()?; + let right = right.to_f64()?; + let res = match op { + MinMax::Min => left.min(right), + MinMax::Max => left.max(right), + }; + let res = this.adjust_nan(res, &[left, right]); + Scalar::from_f64(res) + } + }) + } } fn simd_bitmask_index(idx: u32, vec_len: u32, endianness: Endian) -> u32 { @@ -719,31 +761,3 @@ fn simd_bitmask_index(idx: u32, vec_len: u32, endianness: Endian) -> u32 { Endian::Big => vec_len - 1 - idx, // reverse order of bits } } - -fn fmax_op<'tcx>( - left: &ImmTy<'tcx, Provenance>, - right: &ImmTy<'tcx, Provenance>, -) -> InterpResult<'tcx, Scalar> { - assert_eq!(left.layout.ty, right.layout.ty); - let ty::Float(float_ty) = left.layout.ty.kind() else { bug!("fmax operand is not a float") }; - let left = left.to_scalar(); - let right = right.to_scalar(); - Ok(match float_ty { - FloatTy::F32 => Scalar::from_f32(left.to_f32()?.max(right.to_f32()?)), - FloatTy::F64 => Scalar::from_f64(left.to_f64()?.max(right.to_f64()?)), - }) -} - -fn fmin_op<'tcx>( - left: &ImmTy<'tcx, Provenance>, - right: &ImmTy<'tcx, Provenance>, -) -> InterpResult<'tcx, Scalar> { - assert_eq!(left.layout.ty, right.layout.ty); - let ty::Float(float_ty) = left.layout.ty.kind() else { bug!("fmin operand is not a float") }; - let left = left.to_scalar(); - let right = right.to_scalar(); - Ok(match float_ty { - FloatTy::F32 => Scalar::from_f32(left.to_f32()?.min(right.to_f32()?)), - FloatTy::F64 => Scalar::from_f64(left.to_f64()?.min(right.to_f64()?)), - }) -} diff --git a/src/tools/miri/tests/pass/float_nan.rs b/src/tools/miri/tests/pass/float_nan.rs index 5e717bdca0072..fff103a776f45 100644 --- a/src/tools/miri/tests/pass/float_nan.rs +++ b/src/tools/miri/tests/pass/float_nan.rs @@ -1,4 +1,4 @@ -#![feature(float_gamma)] +#![feature(float_gamma, portable_simd, core_intrinsics, platform_intrinsics)] use std::collections::HashSet; use std::fmt; use std::hash::Hash; @@ -535,6 +535,61 @@ fn test_casts() { ); } +fn test_simd() { + use std::intrinsics::simd::*; + use std::simd::*; + + extern "platform-intrinsic" { + fn simd_fsqrt(x: T) -> T; + fn simd_ceil(x: T) -> T; + fn simd_fma(x: T, y: T, z: T) -> T; + } + + let nan = F32::nan(Neg, Quiet, 0).as_f32(); + check_all_outcomes( + HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]), + || F32::from(unsafe { simd_div(f32x4::splat(0.0), f32x4::splat(0.0)) }[0]), + ); + check_all_outcomes( + HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]), + || F32::from(unsafe { simd_fmin(f32x4::splat(nan), f32x4::splat(nan)) }[0]), + ); + check_all_outcomes( + HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]), + || F32::from(unsafe { simd_fmax(f32x4::splat(nan), f32x4::splat(nan)) }[0]), + ); + check_all_outcomes( + HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]), + || { + F32::from( + unsafe { simd_fma(f32x4::splat(nan), f32x4::splat(nan), f32x4::splat(nan)) }[0], + ) + }, + ); + check_all_outcomes( + HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]), + || F32::from(unsafe { simd_reduce_add_ordered::<_, f32>(f32x4::splat(nan), nan) }), + ); + check_all_outcomes( + HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]), + || F32::from(unsafe { simd_reduce_max::<_, f32>(f32x4::splat(nan)) }), + ); + check_all_outcomes( + HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]), + || F32::from(unsafe { simd_fsqrt(f32x4::splat(nan)) }[0]), + ); + check_all_outcomes( + HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]), + || F32::from(unsafe { simd_ceil(f32x4::splat(nan)) }[0]), + ); + + // Casts + check_all_outcomes( + HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]), + || F64::from(unsafe { simd_cast::(f32x4::splat(nan)) }[0]), + ); +} + fn main() { // Check our constants against std, just to be sure. // We add 1 since our numbers are the number of bits stored @@ -546,4 +601,5 @@ fn main() { test_f32(); test_f64(); test_casts(); + test_simd(); } From 771e47929f55febce4125c34f2641089f01ece52 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 28 Dec 2023 12:56:07 +0100 Subject: [PATCH 5/5] don't test unspecified case --- src/tools/miri/tests/pass/float_nan.rs | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/src/tools/miri/tests/pass/float_nan.rs b/src/tools/miri/tests/pass/float_nan.rs index fff103a776f45..207ce70fb20e3 100644 --- a/src/tools/miri/tests/pass/float_nan.rs +++ b/src/tools/miri/tests/pass/float_nan.rs @@ -291,17 +291,6 @@ fn test_f32() { HashSet::from_iter([1.0f32.into()]), || F32::from(1.0f32.powf(nan)), // special `pow` rule ); - 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, Signaling, 1), - F32::nan(Neg, Signaling, 1), - ]), - || F32::from(1.0f32.powf(F32::nan(Pos, Signaling, 1).as_f32())), // unspecified `pow` case - ); check_all_outcomes( HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]), || F32::from(nan.powi(1)), @@ -414,17 +403,6 @@ fn test_f64() { HashSet::from_iter([1.0f64.into()]), || F64::from(1.0f64.powf(nan)), // special `pow` rule ); - 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, Signaling, 1), - F64::nan(Neg, Signaling, 1), - ]), - || F64::from(1.0f64.powf(F64::nan(Pos, Signaling, 1).as_f64())), // unspecified `pow` case - ); check_all_outcomes( HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]), || F64::from(nan.powi(1)),