Skip to content

Commit 826e72f

Browse files
authored
Merge pull request #4156 from LorrensP-2158466/apply-random-float-error
Apply random float error
2 parents aec4fff + 5c9eaa3 commit 826e72f

File tree

4 files changed

+316
-74
lines changed

4 files changed

+316
-74
lines changed

src/intrinsics/mod.rs

+89-24
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ use rand::Rng;
77
use rustc_abi::Size;
88
use rustc_apfloat::{Float, Round};
99
use rustc_middle::mir;
10-
use rustc_middle::ty::{self, FloatTy};
10+
use rustc_middle::ty::{self, FloatTy, ScalarInt};
1111
use rustc_span::{Symbol, sym};
1212

1313
use self::atomic::EvalContextExt as _;
1414
use self::helpers::{ToHost, ToSoft, check_intrinsic_arg_count};
1515
use self::simd::EvalContextExt as _;
16+
use crate::math::apply_random_float_error_ulp;
1617
use crate::*;
1718

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

210+
"sqrtf32" => {
211+
let [f] = check_intrinsic_arg_count(args)?;
212+
let f = this.read_scalar(f)?.to_f32()?;
213+
// Sqrt is specified to be fully precise.
214+
let res = math::sqrt(f);
215+
let res = this.adjust_nan(res, &[f]);
216+
this.write_scalar(res, dest)?;
217+
}
218+
"sqrtf64" => {
219+
let [f] = check_intrinsic_arg_count(args)?;
220+
let f = this.read_scalar(f)?.to_f64()?;
221+
// Sqrt is specified to be fully precise.
222+
let res = math::sqrt(f);
223+
let res = this.adjust_nan(res, &[f]);
224+
this.write_scalar(res, dest)?;
225+
}
226+
209227
#[rustfmt::skip]
210228
| "sinf32"
211229
| "cosf32"
212-
| "sqrtf32"
213230
| "expf32"
214231
| "exp2f32"
215232
| "logf32"
@@ -218,26 +235,33 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
218235
=> {
219236
let [f] = check_intrinsic_arg_count(args)?;
220237
let f = this.read_scalar(f)?.to_f32()?;
221-
// Using host floats except for sqrt (but it's fine, these operations do not have
238+
// Using host floats (but it's fine, these operations do not have
222239
// guaranteed precision).
240+
let host = f.to_host();
223241
let res = match intrinsic_name {
224-
"sinf32" => f.to_host().sin().to_soft(),
225-
"cosf32" => f.to_host().cos().to_soft(),
226-
"sqrtf32" => math::sqrt(f),
227-
"expf32" => f.to_host().exp().to_soft(),
228-
"exp2f32" => f.to_host().exp2().to_soft(),
229-
"logf32" => f.to_host().ln().to_soft(),
230-
"log10f32" => f.to_host().log10().to_soft(),
231-
"log2f32" => f.to_host().log2().to_soft(),
242+
"sinf32" => host.sin(),
243+
"cosf32" => host.cos(),
244+
"expf32" => host.exp(),
245+
"exp2f32" => host.exp2(),
246+
"logf32" => host.ln(),
247+
"log10f32" => host.log10(),
248+
"log2f32" => host.log2(),
232249
_ => bug!(),
233250
};
251+
let res = res.to_soft();
252+
// Apply a relative error of 16ULP to introduce some non-determinism
253+
// simulating imprecise implementations and optimizations.
254+
let res = apply_random_float_error_ulp(
255+
this,
256+
res,
257+
4, // log2(16)
258+
);
234259
let res = this.adjust_nan(res, &[f]);
235260
this.write_scalar(res, dest)?;
236261
}
237262
#[rustfmt::skip]
238263
| "sinf64"
239264
| "cosf64"
240-
| "sqrtf64"
241265
| "expf64"
242266
| "exp2f64"
243267
| "logf64"
@@ -246,19 +270,27 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
246270
=> {
247271
let [f] = check_intrinsic_arg_count(args)?;
248272
let f = this.read_scalar(f)?.to_f64()?;
249-
// Using host floats except for sqrt (but it's fine, these operations do not have
273+
// Using host floats (but it's fine, these operations do not have
250274
// guaranteed precision).
275+
let host = f.to_host();
251276
let res = match intrinsic_name {
252-
"sinf64" => f.to_host().sin().to_soft(),
253-
"cosf64" => f.to_host().cos().to_soft(),
254-
"sqrtf64" => math::sqrt(f),
255-
"expf64" => f.to_host().exp().to_soft(),
256-
"exp2f64" => f.to_host().exp2().to_soft(),
257-
"logf64" => f.to_host().ln().to_soft(),
258-
"log10f64" => f.to_host().log10().to_soft(),
259-
"log2f64" => f.to_host().log2().to_soft(),
277+
"sinf64" => host.sin(),
278+
"cosf64" => host.cos(),
279+
"expf64" => host.exp(),
280+
"exp2f64" => host.exp2(),
281+
"logf64" => host.ln(),
282+
"log10f64" => host.log10(),
283+
"log2f64" => host.log2(),
260284
_ => bug!(),
261285
};
286+
let res = res.to_soft();
287+
// Apply a relative error of 16ULP to introduce some non-determinism
288+
// simulating imprecise implementations and optimizations.
289+
let res = apply_random_float_error_ulp(
290+
this,
291+
res,
292+
4, // log2(16)
293+
);
262294
let res = this.adjust_nan(res, &[f]);
263295
this.write_scalar(res, dest)?;
264296
}
@@ -316,6 +348,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
316348
}
317349

318350
"powf32" => {
351+
// FIXME: apply random relative error but without altering behaviour of powf
319352
let [f1, f2] = check_intrinsic_arg_count(args)?;
320353
let f1 = this.read_scalar(f1)?.to_f32()?;
321354
let f2 = this.read_scalar(f2)?.to_f32()?;
@@ -325,6 +358,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
325358
this.write_scalar(res, dest)?;
326359
}
327360
"powf64" => {
361+
// FIXME: apply random relative error but without altering behaviour of powf
328362
let [f1, f2] = check_intrinsic_arg_count(args)?;
329363
let f1 = this.read_scalar(f1)?.to_f64()?;
330364
let f2 = this.read_scalar(f2)?.to_f64()?;
@@ -335,6 +369,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
335369
}
336370

337371
"powif32" => {
372+
// FIXME: apply random relative error but without altering behaviour of powi
338373
let [f, i] = check_intrinsic_arg_count(args)?;
339374
let f = this.read_scalar(f)?.to_f32()?;
340375
let i = this.read_scalar(i)?.to_i32()?;
@@ -344,6 +379,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
344379
this.write_scalar(res, dest)?;
345380
}
346381
"powif64" => {
382+
// FIXME: apply random relative error but without altering behaviour of powi
347383
let [f, i] = check_intrinsic_arg_count(args)?;
348384
let f = this.read_scalar(f)?.to_f64()?;
349385
let i = this.read_scalar(i)?.to_i32()?;
@@ -372,7 +408,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
372408
_ => bug!(),
373409
};
374410
let res = this.binary_op(op, &a, &b)?;
375-
// `binary_op` already called `generate_nan` if necessary.
411+
// `binary_op` already called `generate_nan` if needed.
412+
// Apply a relative error of 16ULP to simulate non-deterministic precision loss
413+
// due to optimizations.
414+
let res = apply_random_float_error_to_imm(this, res, 4 /* log2(16) */)?;
376415
this.write_immediate(*res, dest)?;
377416
}
378417

@@ -418,11 +457,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
418457
_ => {}
419458
}
420459
let res = this.binary_op(op, &a, &b)?;
460+
// This cannot be a NaN so we also don't have to apply any non-determinism.
461+
// (Also, `binary_op` already called `generate_nan` if needed.)
421462
if !float_finite(&res)? {
422463
throw_ub_format!("`{intrinsic_name}` intrinsic produced non-finite value as result");
423464
}
424-
// This cannot be a NaN so we also don't have to apply any non-determinism.
425-
// (Also, `binary_op` already called `generate_nan` if needed.)
465+
// Apply a relative error of 16ULP to simulate non-deterministic precision loss
466+
// due to optimizations.
467+
let res = apply_random_float_error_to_imm(this, res, 4 /* log2(16) */)?;
426468
this.write_immediate(*res, dest)?;
427469
}
428470

@@ -455,3 +497,26 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
455497
interp_ok(EmulateItemResult::NeedsReturn)
456498
}
457499
}
500+
501+
/// Applies a random 16ULP floating point error to `val` and returns the new value.
502+
/// Will fail if `val` is not a floating point number.
503+
fn apply_random_float_error_to_imm<'tcx>(
504+
ecx: &mut MiriInterpCx<'tcx>,
505+
val: ImmTy<'tcx>,
506+
ulp_exponent: u32,
507+
) -> InterpResult<'tcx, ImmTy<'tcx>> {
508+
let scalar = val.to_scalar_int()?;
509+
let res: ScalarInt = match val.layout.ty.kind() {
510+
ty::Float(FloatTy::F16) =>
511+
apply_random_float_error_ulp(ecx, scalar.to_f16(), ulp_exponent).into(),
512+
ty::Float(FloatTy::F32) =>
513+
apply_random_float_error_ulp(ecx, scalar.to_f32(), ulp_exponent).into(),
514+
ty::Float(FloatTy::F64) =>
515+
apply_random_float_error_ulp(ecx, scalar.to_f64(), ulp_exponent).into(),
516+
ty::Float(FloatTy::F128) =>
517+
apply_random_float_error_ulp(ecx, scalar.to_f128(), ulp_exponent).into(),
518+
_ => bug!("intrinsic called with non-float input type"),
519+
};
520+
521+
interp_ok(ImmTy::from_scalar_int(res, val.layout))
522+
}

src/math.rs

+16
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,22 @@ pub(crate) fn apply_random_float_error<F: rustc_apfloat::Float>(
2727
(val * (F::from_u128(1).value + err).value).value
2828
}
2929

30+
/// [`apply_random_float_error`] gives instructions to apply a 2^N ULP error.
31+
/// This function implements these instructions such that applying a 2^N ULP error is less error prone.
32+
/// So for a 2^N ULP error, you would pass N as the `ulp_exponent` argument.
33+
pub(crate) fn apply_random_float_error_ulp<F: rustc_apfloat::Float>(
34+
ecx: &mut crate::MiriInterpCx<'_>,
35+
val: F,
36+
ulp_exponent: u32,
37+
) -> F {
38+
let n = i32::try_from(ulp_exponent)
39+
.expect("`err_scale_for_ulp`: exponent is too large to create an error scale");
40+
// we know this fits
41+
let prec = i32::try_from(F::PRECISION).unwrap();
42+
let err_scale = -(prec - n - 1);
43+
apply_random_float_error(ecx, val, err_scale)
44+
}
45+
3046
pub(crate) fn sqrt<S: rustc_apfloat::ieee::Semantics>(x: IeeeFloat<S>) -> IeeeFloat<S> {
3147
match x.category() {
3248
// preserve zero sign

src/shims/foreign_items.rs

+38-4
Original file line numberDiff line numberDiff line change
@@ -765,7 +765,13 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
765765
"erfcf" => f_host.erfc(),
766766
_ => bug!(),
767767
};
768-
let res = res.to_soft();
768+
// Apply a relative error of 16ULP to introduce some non-determinism
769+
// simulating imprecise implementations and optimizations.
770+
let res = math::apply_random_float_error_ulp(
771+
this,
772+
res.to_soft(),
773+
4, // log2(16)
774+
);
769775
let res = this.adjust_nan(res, &[f]);
770776
this.write_scalar(res, dest)?;
771777
}
@@ -788,6 +794,13 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
788794
"fdimf" => f1.to_host().abs_sub(f2.to_host()).to_soft(),
789795
_ => bug!(),
790796
};
797+
// Apply a relative error of 16ULP to introduce some non-determinism
798+
// simulating imprecise implementations and optimizations.
799+
let res = math::apply_random_float_error_ulp(
800+
this,
801+
res,
802+
4, // log2(16)
803+
);
791804
let res = this.adjust_nan(res, &[f1, f2]);
792805
this.write_scalar(res, dest)?;
793806
}
@@ -826,7 +839,13 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
826839
"erfc" => f_host.erfc(),
827840
_ => bug!(),
828841
};
829-
let res = res.to_soft();
842+
// Apply a relative error of 16ULP to introduce some non-determinism
843+
// simulating imprecise implementations and optimizations.
844+
let res = math::apply_random_float_error_ulp(
845+
this,
846+
res.to_soft(),
847+
4, // log2(16)
848+
);
830849
let res = this.adjust_nan(res, &[f]);
831850
this.write_scalar(res, dest)?;
832851
}
@@ -849,6 +868,13 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
849868
"fdim" => f1.to_host().abs_sub(f2.to_host()).to_soft(),
850869
_ => bug!(),
851870
};
871+
// Apply a relative error of 16ULP to introduce some non-determinism
872+
// simulating imprecise implementations and optimizations.
873+
let res = math::apply_random_float_error_ulp(
874+
this,
875+
res,
876+
4, // log2(16)
877+
);
852878
let res = this.adjust_nan(res, &[f1, f2]);
853879
this.write_scalar(res, dest)?;
854880
}
@@ -874,7 +900,11 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
874900
// Using host floats (but it's fine, these operations do not have guaranteed precision).
875901
let (res, sign) = x.to_host().ln_gamma();
876902
this.write_int(sign, &signp)?;
877-
let res = this.adjust_nan(res.to_soft(), &[x]);
903+
// Apply a relative error of 16ULP to introduce some non-determinism
904+
// simulating imprecise implementations and optimizations.
905+
let res =
906+
math::apply_random_float_error_ulp(this, res.to_soft(), 4 /* log2(16) */);
907+
let res = this.adjust_nan(res, &[x]);
878908
this.write_scalar(res, dest)?;
879909
}
880910
"lgamma_r" => {
@@ -885,7 +915,11 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
885915
// Using host floats (but it's fine, these operations do not have guaranteed precision).
886916
let (res, sign) = x.to_host().ln_gamma();
887917
this.write_int(sign, &signp)?;
888-
let res = this.adjust_nan(res.to_soft(), &[x]);
918+
// Apply a relative error of 16ULP to introduce some non-determinism
919+
// simulating imprecise implementations and optimizations.
920+
let res =
921+
math::apply_random_float_error_ulp(this, res.to_soft(), 4 /* log2(16) */);
922+
let res = this.adjust_nan(res, &[x]);
889923
this.write_scalar(res, dest)?;
890924
}
891925

0 commit comments

Comments
 (0)