Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply random float error #4156

Merged
merged 2 commits into from
Feb 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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