Rollup merge of #140439 - RalfJung:miri-algebraic-float-nondet, r=oli-obk

miri: algebraic intrinsics: bring back float non-determinism

Fixes https://github.com/rust-lang/miri/issues/4289
Cc ```@bjoernager```
r? ```@oli-obk```
This commit is contained in:
Matthias Krüger 2025-04-30 10:18:28 +02:00 committed by GitHub
commit 254f050eb4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 107 additions and 86 deletions

View File

@ -183,8 +183,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
let res = self.binary_op(op, &a, &b)?; let res = self.binary_op(op, &a, &b)?;
// `binary_op` already called `generate_nan` if needed. // `binary_op` already called `generate_nan` if needed.
let res = M::apply_float_nondet(self, res)?;
// FIXME: Miri should add some non-determinism to the result here to catch any dependences on exact computations. This has previously been done, but the behaviour was removed as part of constification.
self.write_immediate(*res, dest)?; self.write_immediate(*res, dest)?;
} }

View File

@ -276,6 +276,14 @@ pub trait Machine<'tcx>: Sized {
F2::NAN F2::NAN
} }
/// Apply non-determinism to float operations that do not return a precise result.
fn apply_float_nondet(
_ecx: &mut InterpCx<'tcx, Self>,
val: ImmTy<'tcx, Self::Provenance>,
) -> InterpResult<'tcx, ImmTy<'tcx, Self::Provenance>> {
interp_ok(val)
}
/// Determines the result of `min`/`max` on floats when the arguments are equal. /// Determines the result of `min`/`max` on floats when the arguments are equal.
fn equal_float_min_max<F: Float>(_ecx: &InterpCx<'tcx, Self>, a: F, _b: F) -> F { fn equal_float_min_max<F: Float>(_ecx: &InterpCx<'tcx, Self>, a: F, _b: F) -> F {
// By default, we pick the left argument. // By default, we pick the left argument.

View File

@ -7,13 +7,13 @@ use rand::Rng;
use rustc_abi::Size; use rustc_abi::Size;
use rustc_apfloat::{Float, Round}; use rustc_apfloat::{Float, Round};
use rustc_middle::mir; use rustc_middle::mir;
use rustc_middle::ty::{self, FloatTy, ScalarInt}; use rustc_middle::ty::{self, FloatTy};
use rustc_span::{Symbol, sym}; use rustc_span::{Symbol, sym};
use self::atomic::EvalContextExt as _; use self::atomic::EvalContextExt as _;
use self::helpers::{ToHost, ToSoft, check_intrinsic_arg_count}; use self::helpers::{ToHost, ToSoft, check_intrinsic_arg_count};
use self::simd::EvalContextExt as _; use self::simd::EvalContextExt as _;
use crate::math::apply_random_float_error_ulp; use crate::math::apply_random_float_error_to_imm;
use crate::*; use crate::*;
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
@ -473,26 +473,3 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
interp_ok(EmulateItemResult::NeedsReturn) interp_ok(EmulateItemResult::NeedsReturn)
} }
} }
/// Applies a random 16ULP floating point error to `val` and returns the new value.
/// Will fail if `val` is not a floating point number.
fn apply_random_float_error_to_imm<'tcx>(
ecx: &mut MiriInterpCx<'tcx>,
val: ImmTy<'tcx>,
ulp_exponent: u32,
) -> InterpResult<'tcx, ImmTy<'tcx>> {
let scalar = val.to_scalar_int()?;
let res: ScalarInt = match val.layout.ty.kind() {
ty::Float(FloatTy::F16) =>
apply_random_float_error_ulp(ecx, scalar.to_f16(), ulp_exponent).into(),
ty::Float(FloatTy::F32) =>
apply_random_float_error_ulp(ecx, scalar.to_f32(), ulp_exponent).into(),
ty::Float(FloatTy::F64) =>
apply_random_float_error_ulp(ecx, scalar.to_f64(), ulp_exponent).into(),
ty::Float(FloatTy::F128) =>
apply_random_float_error_ulp(ecx, scalar.to_f128(), ulp_exponent).into(),
_ => bug!("intrinsic called with non-float input type"),
};
interp_ok(ImmTy::from_scalar_int(res, val.layout))
}

View File

@ -1198,6 +1198,16 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
ecx.generate_nan(inputs) ecx.generate_nan(inputs)
} }
#[inline(always)]
fn apply_float_nondet(
ecx: &mut InterpCx<'tcx, Self>,
val: ImmTy<'tcx>,
) -> InterpResult<'tcx, ImmTy<'tcx>> {
crate::math::apply_random_float_error_to_imm(
ecx, val, 2 /* log2(4) */
)
}
#[inline(always)] #[inline(always)]
fn equal_float_min_max<F: Float>(ecx: &MiriInterpCx<'tcx>, a: F, b: F) -> F { fn equal_float_min_max<F: Float>(ecx: &MiriInterpCx<'tcx>, a: F, b: F) -> F {
ecx.equal_float_min_max(a, b) ecx.equal_float_min_max(a, b)

View File

@ -1,6 +1,9 @@
use rand::Rng as _; use rand::Rng as _;
use rustc_apfloat::Float as _; use rustc_apfloat::Float as _;
use rustc_apfloat::ieee::IeeeFloat; use rustc_apfloat::ieee::IeeeFloat;
use rustc_middle::ty::{self, FloatTy, ScalarInt};
use crate::*;
/// Disturbes a floating-point result by a relative error in the range (-2^scale, 2^scale). /// Disturbes a floating-point result by a relative error in the range (-2^scale, 2^scale).
/// ///
@ -43,6 +46,29 @@ pub(crate) fn apply_random_float_error_ulp<F: rustc_apfloat::Float>(
apply_random_float_error(ecx, val, err_scale) apply_random_float_error(ecx, val, err_scale)
} }
/// Applies a random 16ULP floating point error to `val` and returns the new value.
/// Will fail if `val` is not a floating point number.
pub(crate) fn apply_random_float_error_to_imm<'tcx>(
ecx: &mut MiriInterpCx<'tcx>,
val: ImmTy<'tcx>,
ulp_exponent: u32,
) -> InterpResult<'tcx, ImmTy<'tcx>> {
let scalar = val.to_scalar_int()?;
let res: ScalarInt = match val.layout.ty.kind() {
ty::Float(FloatTy::F16) =>
apply_random_float_error_ulp(ecx, scalar.to_f16(), ulp_exponent).into(),
ty::Float(FloatTy::F32) =>
apply_random_float_error_ulp(ecx, scalar.to_f32(), ulp_exponent).into(),
ty::Float(FloatTy::F64) =>
apply_random_float_error_ulp(ecx, scalar.to_f64(), ulp_exponent).into(),
ty::Float(FloatTy::F128) =>
apply_random_float_error_ulp(ecx, scalar.to_f128(), ulp_exponent).into(),
_ => bug!("intrinsic called with non-float input type"),
};
interp_ok(ImmTy::from_scalar_int(res, val.layout))
}
pub(crate) fn sqrt<S: rustc_apfloat::ieee::Semantics>(x: IeeeFloat<S>) -> IeeeFloat<S> { pub(crate) fn sqrt<S: rustc_apfloat::ieee::Semantics>(x: IeeeFloat<S>) -> IeeeFloat<S> {
match x.category() { match x.category() {
// preserve zero sign // preserve zero sign

View File

@ -1292,8 +1292,7 @@ fn test_non_determinism() {
} }
} }
// We saw the same thing N times. // We saw the same thing N times.
// FIXME: temporarily disabled as it breaks std tests. panic!("expected non-determinism, got {rounds} times the same result: {first:?}");
//panic!("expected non-determinism, got {rounds} times the same result: {first:?}");
} }
macro_rules! test_operations_f { macro_rules! test_operations_f {
@ -1319,66 +1318,68 @@ fn test_non_determinism() {
} }
pub fn test_operations_f32(a: f32, b: f32) { pub fn test_operations_f32(a: f32, b: f32) {
test_operations_f!(a, b); test_operations_f!(a, b);
ensure_nondet(|| a.log(b)); // FIXME: temporarily disabled as it breaks std tests.
ensure_nondet(|| a.exp()); // ensure_nondet(|| a.log(b));
ensure_nondet(|| 10f32.exp2()); // ensure_nondet(|| a.exp());
ensure_nondet(|| f32::consts::E.ln()); // ensure_nondet(|| 10f32.exp2());
ensure_nondet(|| 1f32.ln_1p()); // ensure_nondet(|| f32::consts::E.ln());
ensure_nondet(|| 10f32.log10()); // ensure_nondet(|| 1f32.ln_1p());
ensure_nondet(|| 8f32.log2()); // ensure_nondet(|| 10f32.log10());
ensure_nondet(|| 27.0f32.cbrt()); // ensure_nondet(|| 8f32.log2());
ensure_nondet(|| 3.0f32.hypot(4.0f32)); // ensure_nondet(|| 27.0f32.cbrt());
ensure_nondet(|| 1f32.sin()); // ensure_nondet(|| 3.0f32.hypot(4.0f32));
ensure_nondet(|| 0f32.cos()); // ensure_nondet(|| 1f32.sin());
// On i686-pc-windows-msvc , these functions are implemented by calling the `f64` version, // ensure_nondet(|| 0f32.cos());
// which means the little rounding errors Miri introduces are discard by the cast down to `f32`. // // On i686-pc-windows-msvc , these functions are implemented by calling the `f64` version,
// Just skip the test for them. // // which means the little rounding errors Miri introduces are discard by the cast down to `f32`.
if !cfg!(all(target_os = "windows", target_env = "msvc", target_arch = "x86")) { // // Just skip the test for them.
ensure_nondet(|| 1.0f32.tan()); // if !cfg!(all(target_os = "windows", target_env = "msvc", target_arch = "x86")) {
ensure_nondet(|| 1.0f32.asin()); // ensure_nondet(|| 1.0f32.tan());
ensure_nondet(|| 5.0f32.acos()); // ensure_nondet(|| 1.0f32.asin());
ensure_nondet(|| 1.0f32.atan()); // ensure_nondet(|| 5.0f32.acos());
ensure_nondet(|| 1.0f32.atan2(2.0f32)); // ensure_nondet(|| 1.0f32.atan());
ensure_nondet(|| 1.0f32.sinh()); // ensure_nondet(|| 1.0f32.atan2(2.0f32));
ensure_nondet(|| 1.0f32.cosh()); // ensure_nondet(|| 1.0f32.sinh());
ensure_nondet(|| 1.0f32.tanh()); // ensure_nondet(|| 1.0f32.cosh());
} // ensure_nondet(|| 1.0f32.tanh());
ensure_nondet(|| 1.0f32.asinh()); // }
ensure_nondet(|| 2.0f32.acosh()); // ensure_nondet(|| 1.0f32.asinh());
ensure_nondet(|| 0.5f32.atanh()); // ensure_nondet(|| 2.0f32.acosh());
ensure_nondet(|| 5.0f32.gamma()); // ensure_nondet(|| 0.5f32.atanh());
ensure_nondet(|| 5.0f32.ln_gamma()); // ensure_nondet(|| 5.0f32.gamma());
ensure_nondet(|| 5.0f32.erf()); // ensure_nondet(|| 5.0f32.ln_gamma());
ensure_nondet(|| 5.0f32.erfc()); // ensure_nondet(|| 5.0f32.erf());
// ensure_nondet(|| 5.0f32.erfc());
} }
pub fn test_operations_f64(a: f64, b: f64) { pub fn test_operations_f64(a: f64, b: f64) {
test_operations_f!(a, b); test_operations_f!(a, b);
ensure_nondet(|| a.log(b)); // FIXME: temporarily disabled as it breaks std tests.
ensure_nondet(|| a.exp()); // ensure_nondet(|| a.log(b));
ensure_nondet(|| 50f64.exp2()); // ensure_nondet(|| a.exp());
ensure_nondet(|| 3f64.ln()); // ensure_nondet(|| 50f64.exp2());
ensure_nondet(|| 1f64.ln_1p()); // ensure_nondet(|| 3f64.ln());
ensure_nondet(|| f64::consts::E.log10()); // ensure_nondet(|| 1f64.ln_1p());
ensure_nondet(|| f64::consts::E.log2()); // ensure_nondet(|| f64::consts::E.log10());
ensure_nondet(|| 27.0f64.cbrt()); // ensure_nondet(|| f64::consts::E.log2());
ensure_nondet(|| 3.0f64.hypot(4.0f64)); // ensure_nondet(|| 27.0f64.cbrt());
ensure_nondet(|| 1f64.sin()); // ensure_nondet(|| 3.0f64.hypot(4.0f64));
ensure_nondet(|| 0f64.cos()); // ensure_nondet(|| 1f64.sin());
ensure_nondet(|| 1.0f64.tan()); // ensure_nondet(|| 0f64.cos());
ensure_nondet(|| 1.0f64.asin()); // ensure_nondet(|| 1.0f64.tan());
ensure_nondet(|| 5.0f64.acos()); // ensure_nondet(|| 1.0f64.asin());
ensure_nondet(|| 1.0f64.atan()); // ensure_nondet(|| 5.0f64.acos());
ensure_nondet(|| 1.0f64.atan2(2.0f64)); // ensure_nondet(|| 1.0f64.atan());
ensure_nondet(|| 1.0f64.sinh()); // ensure_nondet(|| 1.0f64.atan2(2.0f64));
ensure_nondet(|| 1.0f64.cosh()); // ensure_nondet(|| 1.0f64.sinh());
ensure_nondet(|| 1.0f64.tanh()); // ensure_nondet(|| 1.0f64.cosh());
ensure_nondet(|| 1.0f64.asinh()); // ensure_nondet(|| 1.0f64.tanh());
ensure_nondet(|| 3.0f64.acosh()); // ensure_nondet(|| 1.0f64.asinh());
ensure_nondet(|| 0.5f64.atanh()); // ensure_nondet(|| 3.0f64.acosh());
ensure_nondet(|| 5.0f64.gamma()); // ensure_nondet(|| 0.5f64.atanh());
ensure_nondet(|| 5.0f64.ln_gamma()); // ensure_nondet(|| 5.0f64.gamma());
ensure_nondet(|| 5.0f64.erf()); // ensure_nondet(|| 5.0f64.ln_gamma());
ensure_nondet(|| 5.0f64.erfc()); // ensure_nondet(|| 5.0f64.erf());
// ensure_nondet(|| 5.0f64.erfc());
} }
pub fn test_operations_f128(a: f128, b: f128) { pub fn test_operations_f128(a: f128, b: f128) {
test_operations_f!(a, b); test_operations_f!(a, b);