-
Notifications
You must be signed in to change notification settings - Fork 386
Apply random float error #4156
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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> {} | ||
|
@@ -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" | ||
|
@@ -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" | ||
|
@@ -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)?; | ||
} | ||
|
@@ -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()?; | ||
|
@@ -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()?; | ||
|
@@ -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()?; | ||
|
@@ -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()?; | ||
|
@@ -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) */)?; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @RalfJung Is this really allowed behavior for the algebraic intrinsics? From the documentation for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes it is. Note that This is a closed PR, not a venue for discussion. If you have further questions, please open a thread on the forum. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that the docs even call this out:
Anything beyond "returns an arbitrary random float" is a quality-of-implementation concern, not a stable guarantee unsafe code can rely on. |
||
this.write_immediate(*res, dest)?; | ||
} | ||
|
||
|
@@ -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.) | ||
RalfJung marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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)?; | ||
} | ||
|
||
|
@@ -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)) | ||
} |
Uh oh!
There was an error while loading. Please reload this page.