Skip to content

Commit 5a5027a

Browse files
Move float non determinism helpers to math.rs
1 parent 1079c5e commit 5a5027a

File tree

2 files changed

+145
-164
lines changed

2 files changed

+145
-164
lines changed

src/tools/miri/src/intrinsics/mod.rs

Lines changed: 32 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,17 @@
33
mod atomic;
44
mod simd;
55

6-
use std::ops::Neg;
7-
86
use rand::Rng;
97
use rustc_abi::Size;
10-
use rustc_apfloat::ieee::{IeeeFloat, Semantics};
118
use rustc_apfloat::{self, Float, Round};
129
use rustc_middle::mir;
13-
use rustc_middle::ty::{self, FloatTy, ScalarInt};
10+
use rustc_middle::ty::{self, FloatTy};
1411
use rustc_span::{Symbol, sym};
1512

1613
use self::atomic::EvalContextExt as _;
1714
use self::helpers::{ToHost, ToSoft, check_intrinsic_arg_count};
1815
use self::simd::EvalContextExt as _;
19-
use crate::math::{IeeeExt, apply_random_float_error_ulp};
16+
use crate::math::apply_random_float_error_ulp;
2017
use crate::*;
2118

2219
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
@@ -191,7 +188,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
191188
let [f] = check_intrinsic_arg_count(args)?;
192189
let f = this.read_scalar(f)?.to_f32()?;
193190

194-
let res = fixed_float_value(this, intrinsic_name, &[f]).unwrap_or_else(|| {
191+
let res = math::fixed_float_value(this, intrinsic_name, &[f]).unwrap_or_else(|| {
195192
// Using host floats (but it's fine, these operations do not have
196193
// guaranteed precision).
197194
let host = f.to_host();
@@ -209,15 +206,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
209206

210207
// Apply a relative error of 4ULP to introduce some non-determinism
211208
// simulating imprecise implementations and optimizations.
212-
let res = apply_random_float_error_ulp(
209+
let res = math::apply_random_float_error_ulp(
213210
this,
214211
res,
215212
2, // log2(4)
216213
);
217214

218215
// Clamp the result to the guaranteed range of this function according to the C standard,
219216
// if any.
220-
clamp_float_value(intrinsic_name, res)
217+
math::clamp_float_value(intrinsic_name, res)
221218
});
222219
let res = this.adjust_nan(res, &[f]);
223220
this.write_scalar(res, dest)?;
@@ -235,7 +232,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
235232
let [f] = check_intrinsic_arg_count(args)?;
236233
let f = this.read_scalar(f)?.to_f64()?;
237234

238-
let res = fixed_float_value(this, intrinsic_name, &[f]).unwrap_or_else(|| {
235+
let res = math::fixed_float_value(this, intrinsic_name, &[f]).unwrap_or_else(|| {
239236
// Using host floats (but it's fine, these operations do not have
240237
// guaranteed precision).
241238
let host = f.to_host();
@@ -253,15 +250,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
253250

254251
// Apply a relative error of 4ULP to introduce some non-determinism
255252
// simulating imprecise implementations and optimizations.
256-
let res = apply_random_float_error_ulp(
253+
let res = math::apply_random_float_error_ulp(
257254
this,
258255
res,
259256
2, // log2(4)
260257
);
261258

262259
// Clamp the result to the guaranteed range of this function according to the C standard,
263260
// if any.
264-
clamp_float_value(intrinsic_name, res)
261+
math::clamp_float_value(intrinsic_name, res)
265262
});
266263
let res = this.adjust_nan(res, &[f]);
267264
this.write_scalar(res, dest)?;
@@ -312,16 +309,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
312309
let f1 = this.read_scalar(f1)?.to_f32()?;
313310
let f2 = this.read_scalar(f2)?.to_f32()?;
314311

315-
let res = fixed_float_value(this, intrinsic_name, &[f1, f2]).unwrap_or_else(|| {
316-
// Using host floats (but it's fine, this operation does not have guaranteed precision).
317-
let res = f1.to_host().powf(f2.to_host()).to_soft();
312+
let res =
313+
math::fixed_float_value(this, intrinsic_name, &[f1, f2]).unwrap_or_else(|| {
314+
// Using host floats (but it's fine, this operation does not have guaranteed precision).
315+
let res = f1.to_host().powf(f2.to_host()).to_soft();
318316

319-
// Apply a relative error of 4ULP to introduce some non-determinism
320-
// simulating imprecise implementations and optimizations.
321-
apply_random_float_error_ulp(
322-
this, res, 2, // log2(4)
323-
)
324-
});
317+
// Apply a relative error of 4ULP to introduce some non-determinism
318+
// simulating imprecise implementations and optimizations.
319+
math::apply_random_float_error_ulp(
320+
this, res, 2, // log2(4)
321+
)
322+
});
325323
let res = this.adjust_nan(res, &[f1, f2]);
326324
this.write_scalar(res, dest)?;
327325
}
@@ -330,16 +328,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
330328
let f1 = this.read_scalar(f1)?.to_f64()?;
331329
let f2 = this.read_scalar(f2)?.to_f64()?;
332330

333-
let res = fixed_float_value(this, intrinsic_name, &[f1, f2]).unwrap_or_else(|| {
334-
// Using host floats (but it's fine, this operation does not have guaranteed precision).
335-
let res = f1.to_host().powf(f2.to_host()).to_soft();
331+
let res =
332+
math::fixed_float_value(this, intrinsic_name, &[f1, f2]).unwrap_or_else(|| {
333+
// Using host floats (but it's fine, this operation does not have guaranteed precision).
334+
let res = f1.to_host().powf(f2.to_host()).to_soft();
336335

337-
// Apply a relative error of 4ULP to introduce some non-determinism
338-
// simulating imprecise implementations and optimizations.
339-
apply_random_float_error_ulp(
340-
this, res, 2, // log2(4)
341-
)
342-
});
336+
// Apply a relative error of 4ULP to introduce some non-determinism
337+
// simulating imprecise implementations and optimizations.
338+
math::apply_random_float_error_ulp(
339+
this, res, 2, // log2(4)
340+
)
341+
});
343342
let res = this.adjust_nan(res, &[f1, f2]);
344343
this.write_scalar(res, dest)?;
345344
}
@@ -349,7 +348,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
349348
let f = this.read_scalar(f)?.to_f32()?;
350349
let i = this.read_scalar(i)?.to_i32()?;
351350

352-
let res = fixed_powi_float_value(this, f, i).unwrap_or_else(|| {
351+
let res = math::fixed_powi_float_value(this, f, i).unwrap_or_else(|| {
353352
// Using host floats (but it's fine, this operation does not have guaranteed precision).
354353
let res = f.to_host().powi(i).to_soft();
355354

@@ -367,13 +366,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
367366
let f = this.read_scalar(f)?.to_f64()?;
368367
let i = this.read_scalar(i)?.to_i32()?;
369368

370-
let res = fixed_powi_float_value(this, f, i).unwrap_or_else(|| {
369+
let res = math::fixed_powi_float_value(this, f, i).unwrap_or_else(|| {
371370
// Using host floats (but it's fine, this operation does not have guaranteed precision).
372371
let res = f.to_host().powi(i).to_soft();
373372

374373
// Apply a relative error of 4ULP to introduce some non-determinism
375374
// simulating imprecise implementations and optimizations.
376-
apply_random_float_error_ulp(
375+
math::apply_random_float_error_ulp(
377376
this, res, 2, // log2(4)
378377
)
379378
});
@@ -430,7 +429,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
430429
}
431430
// Apply a relative error of 4ULP to simulate non-deterministic precision loss
432431
// due to optimizations.
433-
let res = apply_random_float_error_to_imm(this, res, 2 /* log2(4) */)?;
432+
let res = math::apply_random_float_error_to_imm(this, res, 2 /* log2(4) */)?;
434433
this.write_immediate(*res, dest)?;
435434
}
436435

@@ -467,133 +466,3 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
467466
interp_ok(EmulateItemResult::NeedsReturn)
468467
}
469468
}
470-
471-
/// Applies a random ULP floating point error to `val` and returns the new value.
472-
/// So if you want an X ULP error, `ulp_exponent` should be log2(X).
473-
///
474-
/// Will fail if `val` is not a floating point number.
475-
fn apply_random_float_error_to_imm<'tcx>(
476-
ecx: &mut MiriInterpCx<'tcx>,
477-
val: ImmTy<'tcx>,
478-
ulp_exponent: u32,
479-
) -> InterpResult<'tcx, ImmTy<'tcx>> {
480-
let scalar = val.to_scalar_int()?;
481-
let res: ScalarInt = match val.layout.ty.kind() {
482-
ty::Float(FloatTy::F16) =>
483-
apply_random_float_error_ulp(ecx, scalar.to_f16(), ulp_exponent).into(),
484-
ty::Float(FloatTy::F32) =>
485-
apply_random_float_error_ulp(ecx, scalar.to_f32(), ulp_exponent).into(),
486-
ty::Float(FloatTy::F64) =>
487-
apply_random_float_error_ulp(ecx, scalar.to_f64(), ulp_exponent).into(),
488-
ty::Float(FloatTy::F128) =>
489-
apply_random_float_error_ulp(ecx, scalar.to_f128(), ulp_exponent).into(),
490-
_ => bug!("intrinsic called with non-float input type"),
491-
};
492-
493-
interp_ok(ImmTy::from_scalar_int(res, val.layout))
494-
}
495-
496-
/// For the intrinsics:
497-
/// - sinf32, sinf64
498-
/// - cosf32, cosf64
499-
/// - expf32, expf64, exp2f32, exp2f64
500-
/// - logf32, logf64, log2f32, log2f64, log10f32, log10f64
501-
/// - powf32, powf64
502-
///
503-
/// # Return
504-
///
505-
/// Returns `Some(output)` if the `intrinsic` results in a defined fixed `output` specified in the C standard
506-
/// (specifically, C23 annex F.10) when given `args` as arguments. Outputs that are unaffected by a relative error
507-
/// (such as INF and zero) are not handled here, they are assumed to be handled by the underlying
508-
/// implementation. Returns `None` if no specific value is guaranteed.
509-
///
510-
/// # Note
511-
///
512-
/// For `powf*` operations of the form:
513-
///
514-
/// - `(SNaN)^(±0)`
515-
/// - `1^(SNaN)`
516-
///
517-
/// The result is implementation-defined:
518-
/// - musl returns for both `1.0`
519-
/// - glibc returns for both `NaN`
520-
///
521-
/// This discrepancy exists because SNaN handling is not consistently defined across platforms,
522-
/// and the C standard leaves behavior for SNaNs unspecified.
523-
///
524-
/// Miri chooses to adhere to both implementations and returns either one of them non-deterministically.
525-
fn fixed_float_value<S: Semantics>(
526-
ecx: &mut MiriInterpCx<'_>,
527-
intrinsic_name: &str,
528-
args: &[IeeeFloat<S>],
529-
) -> Option<IeeeFloat<S>> {
530-
let one = IeeeFloat::<S>::one();
531-
Some(match (intrinsic_name, args) {
532-
// cos(+- 0) = 1
533-
("cosf32" | "cosf64", [input]) if input.is_zero() => one,
534-
535-
// e^0 = 1
536-
("expf32" | "expf64" | "exp2f32" | "exp2f64", [input]) if input.is_zero() => one,
537-
538-
// (-1)^(±INF) = 1
539-
("powf32" | "powf64", [base, exp]) if *base == -one && exp.is_infinite() => one,
540-
541-
// 1^y = 1 for any y, even a NaN
542-
("powf32" | "powf64", [base, exp]) if *base == one => {
543-
let rng = ecx.machine.rng.get_mut();
544-
// SNaN exponents get special treatment: they might return 1, or a NaN.
545-
let return_nan = exp.is_signaling() && ecx.machine.float_nondet && rng.random();
546-
// Handle both the musl and glibc cases non-deterministically.
547-
if return_nan { ecx.generate_nan(args) } else { one }
548-
}
549-
550-
// x^(±0) = 1 for any x, even a NaN
551-
("powf32" | "powf64", [base, exp]) if exp.is_zero() => {
552-
let rng = ecx.machine.rng.get_mut();
553-
// SNaN bases get special treatment: they might return 1, or a NaN.
554-
let return_nan = base.is_signaling() && ecx.machine.float_nondet && rng.random();
555-
// Handle both the musl and glibc cases non-deterministically.
556-
if return_nan { ecx.generate_nan(args) } else { one }
557-
}
558-
559-
// There are a lot of cases for fixed outputs according to the C Standard, but these are
560-
// mainly INF or zero which are not affected by the applied error.
561-
_ => return None,
562-
})
563-
}
564-
565-
/// Returns `Some(output)` if `powi` (called `pown` in C) results in a fixed value specified in the
566-
/// C standard (specifically, C23 annex F.10.4.6) when doing `base^exp`. Otherwise, returns `None`.
567-
fn fixed_powi_float_value<S: Semantics>(
568-
ecx: &mut MiriInterpCx<'_>,
569-
base: IeeeFloat<S>,
570-
exp: i32,
571-
) -> Option<IeeeFloat<S>> {
572-
Some(match exp {
573-
0 => {
574-
let one = IeeeFloat::<S>::one();
575-
let rng = ecx.machine.rng.get_mut();
576-
let return_nan = ecx.machine.float_nondet && rng.random() && base.is_signaling();
577-
// For SNaN treatment, we are consistent with `powf`above.
578-
// (We wouldn't have two, unlike powf all implementations seem to agree for powi,
579-
// but for now we are maximally conservative.)
580-
if return_nan { ecx.generate_nan(&[base]) } else { one }
581-
}
582-
583-
_ => return None,
584-
})
585-
}
586-
587-
/// Given an floating-point operation and a floating-point value, clamps the result to the output
588-
/// range of the given operation.
589-
fn clamp_float_value<S: Semantics>(intrinsic_name: &str, val: IeeeFloat<S>) -> IeeeFloat<S> {
590-
match intrinsic_name {
591-
// sin and cos: [-1, 1]
592-
"sinf32" | "cosf32" | "sinf64" | "cosf64" =>
593-
val.clamp(IeeeFloat::<S>::one().neg(), IeeeFloat::<S>::one()),
594-
// exp: [0, +INF]
595-
"expf32" | "exp2f32" | "expf64" | "exp2f64" =>
596-
IeeeFloat::<S>::maximum(val, IeeeFloat::<S>::ZERO),
597-
_ => val,
598-
}
599-
}

0 commit comments

Comments
 (0)