Skip to content

Commit

Permalink
Merge pull request #4156 from LorrensP-2158466/apply-random-float-error
Browse files Browse the repository at this point in the history
Apply random float error
  • Loading branch information
RalfJung authored Feb 16, 2025
2 parents aec4fff + 5c9eaa3 commit 826e72f
Show file tree
Hide file tree
Showing 4 changed files with 316 additions and 74 deletions.
113 changes: 89 additions & 24 deletions src/intrinsics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ use rand::Rng;
use rustc_abi::Size;
use rustc_apfloat::{Float, Round};
use rustc_middle::mir;
use rustc_middle::ty::{self, FloatTy};
use rustc_middle::ty::{self, FloatTy, ScalarInt};
use rustc_span::{Symbol, sym};

use self::atomic::EvalContextExt as _;
use self::helpers::{ToHost, ToSoft, check_intrinsic_arg_count};
use self::simd::EvalContextExt as _;
use crate::math::apply_random_float_error_ulp;
use crate::*;

impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
Expand Down Expand Up @@ -206,10 +207,26 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
this.write_scalar(res, dest)?;
}

"sqrtf32" => {
let [f] = check_intrinsic_arg_count(args)?;
let f = this.read_scalar(f)?.to_f32()?;
// Sqrt is specified to be fully precise.
let res = math::sqrt(f);
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
"sqrtf64" => {
let [f] = check_intrinsic_arg_count(args)?;
let f = this.read_scalar(f)?.to_f64()?;
// Sqrt is specified to be fully precise.
let res = math::sqrt(f);
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}

#[rustfmt::skip]
| "sinf32"
| "cosf32"
| "sqrtf32"
| "expf32"
| "exp2f32"
| "logf32"
Expand All @@ -218,26 +235,33 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
=> {
let [f] = check_intrinsic_arg_count(args)?;
let f = this.read_scalar(f)?.to_f32()?;
// Using host floats except for sqrt (but it's fine, these operations do not have
// Using host floats (but it's fine, these operations do not have
// guaranteed precision).
let host = f.to_host();
let res = match intrinsic_name {
"sinf32" => f.to_host().sin().to_soft(),
"cosf32" => f.to_host().cos().to_soft(),
"sqrtf32" => math::sqrt(f),
"expf32" => f.to_host().exp().to_soft(),
"exp2f32" => f.to_host().exp2().to_soft(),
"logf32" => f.to_host().ln().to_soft(),
"log10f32" => f.to_host().log10().to_soft(),
"log2f32" => f.to_host().log2().to_soft(),
"sinf32" => host.sin(),
"cosf32" => host.cos(),
"expf32" => host.exp(),
"exp2f32" => host.exp2(),
"logf32" => host.ln(),
"log10f32" => host.log10(),
"log2f32" => host.log2(),
_ => bug!(),
};
let res = res.to_soft();
// Apply a relative error of 16ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res = apply_random_float_error_ulp(
this,
res,
4, // log2(16)
);
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
#[rustfmt::skip]
| "sinf64"
| "cosf64"
| "sqrtf64"
| "expf64"
| "exp2f64"
| "logf64"
Expand All @@ -246,19 +270,27 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
=> {
let [f] = check_intrinsic_arg_count(args)?;
let f = this.read_scalar(f)?.to_f64()?;
// Using host floats except for sqrt (but it's fine, these operations do not have
// Using host floats (but it's fine, these operations do not have
// guaranteed precision).
let host = f.to_host();
let res = match intrinsic_name {
"sinf64" => f.to_host().sin().to_soft(),
"cosf64" => f.to_host().cos().to_soft(),
"sqrtf64" => math::sqrt(f),
"expf64" => f.to_host().exp().to_soft(),
"exp2f64" => f.to_host().exp2().to_soft(),
"logf64" => f.to_host().ln().to_soft(),
"log10f64" => f.to_host().log10().to_soft(),
"log2f64" => f.to_host().log2().to_soft(),
"sinf64" => host.sin(),
"cosf64" => host.cos(),
"expf64" => host.exp(),
"exp2f64" => host.exp2(),
"logf64" => host.ln(),
"log10f64" => host.log10(),
"log2f64" => host.log2(),
_ => bug!(),
};
let res = res.to_soft();
// Apply a relative error of 16ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res = apply_random_float_error_ulp(
this,
res,
4, // log2(16)
);
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
Expand Down Expand Up @@ -316,6 +348,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
}

"powf32" => {
// FIXME: apply random relative error but without altering behaviour of powf
let [f1, f2] = check_intrinsic_arg_count(args)?;
let f1 = this.read_scalar(f1)?.to_f32()?;
let f2 = this.read_scalar(f2)?.to_f32()?;
Expand All @@ -325,6 +358,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
this.write_scalar(res, dest)?;
}
"powf64" => {
// FIXME: apply random relative error but without altering behaviour of powf
let [f1, f2] = check_intrinsic_arg_count(args)?;
let f1 = this.read_scalar(f1)?.to_f64()?;
let f2 = this.read_scalar(f2)?.to_f64()?;
Expand All @@ -335,6 +369,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
}

"powif32" => {
// FIXME: apply random relative error but without altering behaviour of powi
let [f, i] = check_intrinsic_arg_count(args)?;
let f = this.read_scalar(f)?.to_f32()?;
let i = this.read_scalar(i)?.to_i32()?;
Expand All @@ -344,6 +379,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
this.write_scalar(res, dest)?;
}
"powif64" => {
// FIXME: apply random relative error but without altering behaviour of powi
let [f, i] = check_intrinsic_arg_count(args)?;
let f = this.read_scalar(f)?.to_f64()?;
let i = this.read_scalar(i)?.to_i32()?;
Expand Down Expand Up @@ -372,7 +408,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
_ => bug!(),
};
let res = this.binary_op(op, &a, &b)?;
// `binary_op` already called `generate_nan` if necessary.
// `binary_op` already called `generate_nan` if needed.
// Apply a relative error of 16ULP to simulate non-deterministic precision loss
// due to optimizations.
let res = apply_random_float_error_to_imm(this, res, 4 /* log2(16) */)?;
this.write_immediate(*res, dest)?;
}

Expand Down Expand Up @@ -418,11 +457,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
_ => {}
}
let res = this.binary_op(op, &a, &b)?;
// This cannot be a NaN so we also don't have to apply any non-determinism.
// (Also, `binary_op` already called `generate_nan` if needed.)
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, `binary_op` already called `generate_nan` if needed.)
// Apply a relative error of 16ULP to simulate non-deterministic precision loss
// due to optimizations.
let res = apply_random_float_error_to_imm(this, res, 4 /* log2(16) */)?;
this.write_immediate(*res, dest)?;
}

Expand Down Expand Up @@ -455,3 +497,26 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
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))
}
16 changes: 16 additions & 0 deletions src/math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@ pub(crate) fn apply_random_float_error<F: rustc_apfloat::Float>(
(val * (F::from_u128(1).value + err).value).value
}

/// [`apply_random_float_error`] gives instructions to apply a 2^N ULP error.
/// This function implements these instructions such that applying a 2^N ULP error is less error prone.
/// So for a 2^N ULP error, you would pass N as the `ulp_exponent` argument.
pub(crate) fn apply_random_float_error_ulp<F: rustc_apfloat::Float>(
ecx: &mut crate::MiriInterpCx<'_>,
val: F,
ulp_exponent: u32,
) -> F {
let n = i32::try_from(ulp_exponent)
.expect("`err_scale_for_ulp`: exponent is too large to create an error scale");
// we know this fits
let prec = i32::try_from(F::PRECISION).unwrap();
let err_scale = -(prec - n - 1);
apply_random_float_error(ecx, val, err_scale)
}

pub(crate) fn sqrt<S: rustc_apfloat::ieee::Semantics>(x: IeeeFloat<S>) -> IeeeFloat<S> {
match x.category() {
// preserve zero sign
Expand Down
42 changes: 38 additions & 4 deletions src/shims/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -765,7 +765,13 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
"erfcf" => f_host.erfc(),
_ => bug!(),
};
let res = res.to_soft();
// Apply a relative error of 16ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res = math::apply_random_float_error_ulp(
this,
res.to_soft(),
4, // log2(16)
);
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
Expand All @@ -788,6 +794,13 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
"fdimf" => f1.to_host().abs_sub(f2.to_host()).to_soft(),
_ => bug!(),
};
// Apply a relative error of 16ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res = math::apply_random_float_error_ulp(
this,
res,
4, // log2(16)
);
let res = this.adjust_nan(res, &[f1, f2]);
this.write_scalar(res, dest)?;
}
Expand Down Expand Up @@ -826,7 +839,13 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
"erfc" => f_host.erfc(),
_ => bug!(),
};
let res = res.to_soft();
// Apply a relative error of 16ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res = math::apply_random_float_error_ulp(
this,
res.to_soft(),
4, // log2(16)
);
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
Expand All @@ -849,6 +868,13 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
"fdim" => f1.to_host().abs_sub(f2.to_host()).to_soft(),
_ => bug!(),
};
// Apply a relative error of 16ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res = math::apply_random_float_error_ulp(
this,
res,
4, // log2(16)
);
let res = this.adjust_nan(res, &[f1, f2]);
this.write_scalar(res, dest)?;
}
Expand All @@ -874,7 +900,11 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
// Using host floats (but it's fine, these operations do not have guaranteed precision).
let (res, sign) = x.to_host().ln_gamma();
this.write_int(sign, &signp)?;
let res = this.adjust_nan(res.to_soft(), &[x]);
// Apply a relative error of 16ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res =
math::apply_random_float_error_ulp(this, res.to_soft(), 4 /* log2(16) */);
let res = this.adjust_nan(res, &[x]);
this.write_scalar(res, dest)?;
}
"lgamma_r" => {
Expand All @@ -885,7 +915,11 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
// Using host floats (but it's fine, these operations do not have guaranteed precision).
let (res, sign) = x.to_host().ln_gamma();
this.write_int(sign, &signp)?;
let res = this.adjust_nan(res.to_soft(), &[x]);
// Apply a relative error of 16ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res =
math::apply_random_float_error_ulp(this, res.to_soft(), 4 /* log2(16) */);
let res = this.adjust_nan(res, &[x]);
this.write_scalar(res, dest)?;
}

Expand Down
Loading

0 comments on commit 826e72f

Please sign in to comment.