Merge pull request #4555 from RalfJung/float-err

add a flag to always apply the maximum float error
This commit is contained in:
Ralf Jung 2025-09-02 15:01:48 +00:00 committed by GitHub
commit 80c3ba803f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 116 additions and 72 deletions

View file

@ -319,6 +319,8 @@ environment variable. We first document the most relevant and most commonly used
Can be used without a value; in that case the range defaults to `0..64`.
* `-Zmiri-many-seeds-keep-going` tells Miri to really try all the seeds in the given range, even if
a failing seed has already been found. This is useful to determine which fraction of seeds fails.
* `-Zmiri-max-extra-rounding-error` tells Miri to always apply the maximum error to float operations
that do not have a guaranteed precision. The sign of the error is still non-deterministic.
* `-Zmiri-no-extra-rounding-error` stops Miri from adding extra rounding errors to float operations
that do not have a guaranteed precision.
* `-Zmiri-num-cpus` states the number of available CPUs to be reported by miri. By default, the

View file

@ -556,7 +556,9 @@ fn main() {
} else if arg == "-Zmiri-deterministic-floats" {
miri_config.float_nondet = false;
} else if arg == "-Zmiri-no-extra-rounding-error" {
miri_config.float_rounding_error = false;
miri_config.float_rounding_error = miri::FloatRoundingErrorMode::None;
} else if arg == "-Zmiri-max-extra-rounding-error" {
miri_config.float_rounding_error = miri::FloatRoundingErrorMode::Max;
} else if arg == "-Zmiri-strict-provenance" {
miri_config.provenance_mode = ProvenanceMode::Strict;
} else if arg == "-Zmiri-permissive-provenance" {

View file

@ -32,65 +32,6 @@ pub enum MiriEntryFnType {
/// will hang the program.
const MAIN_THREAD_YIELDS_AT_SHUTDOWN: u32 = 256;
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum AlignmentCheck {
/// Do not check alignment.
None,
/// Check alignment "symbolically", i.e., using only the requested alignment for an allocation and not its real base address.
Symbolic,
/// Check alignment on the actual physical integer address.
Int,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum RejectOpWith {
/// Isolated op is rejected with an abort of the machine.
Abort,
/// If not Abort, miri returns an error for an isolated op.
/// Following options determine if user should be warned about such error.
/// Do not print warning about rejected isolated op.
NoWarning,
/// Print a warning about rejected isolated op, with backtrace.
Warning,
/// Print a warning about rejected isolated op, without backtrace.
WarningWithoutBacktrace,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum IsolatedOp {
/// Reject an op requiring communication with the host. By
/// default, miri rejects the op with an abort. If not, it returns
/// an error code, and prints a warning about it. Warning levels
/// are controlled by `RejectOpWith` enum.
Reject(RejectOpWith),
/// Execute op requiring communication with the host, i.e. disable isolation.
Allow,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum BacktraceStyle {
/// Prints a terser backtrace which ideally only contains relevant information.
Short,
/// Prints a backtrace with all possible information.
Full,
/// Prints only the frame that the error occurs in.
Off,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ValidationMode {
/// Do not perform any kind of validation.
No,
/// Validate the interior of the value, but not things behind references.
Shallow,
/// Fully recursively validate references.
Deep,
}
/// Configuration needed to spawn a Miri instance.
#[derive(Clone)]
pub struct MiriConfig {
@ -171,7 +112,7 @@ pub struct MiriConfig {
/// Whether floating-point operations can behave non-deterministically.
pub float_nondet: bool,
/// Whether floating-point operations can have a non-deterministic rounding error.
pub float_rounding_error: bool,
pub float_rounding_error: FloatRoundingErrorMode,
}
impl Default for MiriConfig {
@ -213,7 +154,7 @@ impl Default for MiriConfig {
fixed_scheduling: false,
force_intrinsic_fallback: false,
float_nondet: true,
float_rounding_error: true,
float_rounding_error: FloatRoundingErrorMode::Random,
}
}
}

View file

@ -138,15 +138,14 @@ pub use crate::data_structures::mono_hash_map::MonoHashMap;
pub use crate::diagnostics::{
EvalContextExt as _, NonHaltingDiagnostic, TerminationInfo, report_error,
};
pub use crate::eval::{
AlignmentCheck, BacktraceStyle, IsolatedOp, MiriConfig, MiriEntryFnType, RejectOpWith,
ValidationMode, create_ecx, eval_entry,
};
pub use crate::eval::{MiriConfig, MiriEntryFnType, create_ecx, eval_entry};
pub use crate::helpers::{AccessKind, EvalContextExt as _, ToU64 as _, ToUsize as _};
pub use crate::intrinsics::EvalContextExt as _;
pub use crate::machine::{
AllocExtra, DynMachineCallback, FrameExtra, MachineCallback, MemoryKind, MiriInterpCx,
MiriInterpCxExt, MiriMachine, MiriMemoryKind, PrimitiveLayouts, Provenance, ProvenanceExtra,
AlignmentCheck, AllocExtra, BacktraceStyle, DynMachineCallback, FloatRoundingErrorMode,
FrameExtra, IsolatedOp, MachineCallback, MemoryKind, MiriInterpCx, MiriInterpCxExt,
MiriMachine, MiriMemoryKind, PrimitiveLayouts, Provenance, ProvenanceExtra, RejectOpWith,
ValidationMode,
};
pub use crate::operator::EvalContextExt as _;
pub use crate::provenance_gc::{EvalContextExt as _, LiveAllocs, VisitProvenance, VisitWith};

View file

@ -49,6 +49,75 @@ pub const SIGRTMAX: i32 = 42;
/// base address for each evaluation would produce unbounded memory usage.
const ADDRS_PER_ANON_GLOBAL: usize = 32;
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum AlignmentCheck {
/// Do not check alignment.
None,
/// Check alignment "symbolically", i.e., using only the requested alignment for an allocation and not its real base address.
Symbolic,
/// Check alignment on the actual physical integer address.
Int,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum RejectOpWith {
/// Isolated op is rejected with an abort of the machine.
Abort,
/// If not Abort, miri returns an error for an isolated op.
/// Following options determine if user should be warned about such error.
/// Do not print warning about rejected isolated op.
NoWarning,
/// Print a warning about rejected isolated op, with backtrace.
Warning,
/// Print a warning about rejected isolated op, without backtrace.
WarningWithoutBacktrace,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum IsolatedOp {
/// Reject an op requiring communication with the host. By
/// default, miri rejects the op with an abort. If not, it returns
/// an error code, and prints a warning about it. Warning levels
/// are controlled by `RejectOpWith` enum.
Reject(RejectOpWith),
/// Execute op requiring communication with the host, i.e. disable isolation.
Allow,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum BacktraceStyle {
/// Prints a terser backtrace which ideally only contains relevant information.
Short,
/// Prints a backtrace with all possible information.
Full,
/// Prints only the frame that the error occurs in.
Off,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ValidationMode {
/// Do not perform any kind of validation.
No,
/// Validate the interior of the value, but not things behind references.
Shallow,
/// Fully recursively validate references.
Deep,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum FloatRoundingErrorMode {
/// Apply a random error (the default).
Random,
/// Don't apply any error.
None,
/// Always apply the maximum error (with a random sign).
Max,
}
/// Extra data stored with each stack frame
pub struct FrameExtra<'tcx> {
/// Extra data for the Borrow Tracker.
@ -599,7 +668,7 @@ pub struct MiriMachine<'tcx> {
/// Whether floating-point operations can behave non-deterministically.
pub float_nondet: bool,
/// Whether floating-point operations can have a non-deterministic rounding error.
pub float_rounding_error: bool,
pub float_rounding_error: FloatRoundingErrorMode,
}
impl<'tcx> MiriMachine<'tcx> {

View file

@ -15,7 +15,9 @@ pub(crate) fn apply_random_float_error<F: rustc_apfloat::Float>(
val: F,
err_scale: i32,
) -> F {
if !ecx.machine.float_nondet || !ecx.machine.float_rounding_error {
if !ecx.machine.float_nondet
|| matches!(ecx.machine.float_rounding_error, FloatRoundingErrorMode::None)
{
return val;
}
@ -24,7 +26,12 @@ pub(crate) fn apply_random_float_error<F: rustc_apfloat::Float>(
// (When read as binary, the position of the first `1` determines the exponent,
// and the remaining bits fill the mantissa. `PREC` is one plus the size of the mantissa,
// so this all works out.)
let r = F::from_u128(rng.random_range(0..(1 << F::PRECISION))).value;
let r = F::from_u128(match ecx.machine.float_rounding_error {
FloatRoundingErrorMode::Random => rng.random_range(0..(1 << F::PRECISION)),
FloatRoundingErrorMode::Max => (1 << F::PRECISION) - 1, // force max error
FloatRoundingErrorMode::None => unreachable!(),
})
.value;
// Multiply this with 2^(scale - PREC). The result is between 0 and
// 2^PREC * 2^(scale - PREC) = 2^scale.
let err = r.scalbn(err_scale.strict_sub(F::PRECISION.try_into().unwrap()));

View file

@ -9,7 +9,8 @@ use std::time::{Duration, Instant};
// We are expecting to sleep for 10ms. How long of a sleep we are accepting?
// Even with 1000ms we still see this test fail on macOS runners.
const MAX_SLEEP_TIME_MS: u64 = 2000;
// On a aarch64-pc-windows-msvc runner, we saw 2.7s!
const MAX_SLEEP_TIME_MS: u64 = 4000;
// Check if Rust barriers are working.

View file

@ -0,0 +1,23 @@
//! Check that the flags to control the extra rounding error work.
//@revisions: random max none
//@[max]compile-flags: -Zmiri-max-extra-rounding-error
//@[none]compile-flags: -Zmiri-no-extra-rounding-error
#![feature(cfg_select)]
use std::collections::HashSet;
use std::hint::black_box;
fn main() {
let expected = cfg_select! {
random => 13, // FIXME: why is it 13?
max => 2,
none => 1,
};
// Call `sin(0.5)` a bunch of times and see how many different values we get.
let mut values = HashSet::new();
for _ in 0..(expected * 16) {
let val = black_box(0.5f64).sin();
values.insert(val.to_bits());
}
assert_eq!(values.len(), expected);
}