about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/miri/src/bin/miri.rs244
-rw-r--r--src/tools/miri/src/diagnostics.rs4
-rw-r--r--src/tools/miri/src/eval.rs11
-rw-r--r--src/tools/miri/src/shims/foreign_items.rs2
-rw-r--r--src/tools/miri/src/shims/windows/foreign_items.rs5
5 files changed, 172 insertions, 94 deletions
diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs
index 5248c9d186b..ca3704e0655 100644
--- a/src/tools/miri/src/bin/miri.rs
+++ b/src/tools/miri/src/bin/miri.rs
@@ -26,11 +26,17 @@ extern crate rustc_span;
 
 use std::env::{self, VarError};
 use std::num::NonZero;
+use std::ops::Range;
 use std::path::PathBuf;
 use std::str::FromStr;
+use std::sync::Arc;
+use std::sync::atomic::{AtomicBool, Ordering};
 
-use miri::{BacktraceStyle, BorrowTrackerMethod, ProvenanceMode, RetagFields, ValidationMode};
+use miri::{
+    BacktraceStyle, BorrowTrackerMethod, MiriConfig, ProvenanceMode, RetagFields, ValidationMode,
+};
 use rustc_abi::ExternAbi;
+use rustc_data_structures::sync;
 use rustc_data_structures::sync::Lrc;
 use rustc_driver::Compilation;
 use rustc_hir::def_id::LOCAL_CRATE;
@@ -52,7 +58,64 @@ use rustc_span::def_id::DefId;
 use tracing::debug;
 
 struct MiriCompilerCalls {
-    miri_config: miri::MiriConfig,
+    miri_config: Option<MiriConfig>,
+    many_seeds: Option<Range<u32>>,
+}
+
+impl MiriCompilerCalls {
+    fn new(miri_config: MiriConfig, many_seeds: Option<Range<u32>>) -> Self {
+        Self { miri_config: Some(miri_config), many_seeds }
+    }
+}
+
+fn entry_fn(tcx: TyCtxt<'_>) -> (DefId, EntryFnType) {
+    if let Some(entry_def) = tcx.entry_fn(()) {
+        return entry_def;
+    }
+    // Look for a symbol in the local crate named `miri_start`, and treat that as the entry point.
+    let sym = tcx.exported_symbols(LOCAL_CRATE).iter().find_map(|(sym, _)| {
+        if sym.symbol_name_for_local_instance(tcx).name == "miri_start" { Some(sym) } else { None }
+    });
+    if let Some(ExportedSymbol::NonGeneric(id)) = sym {
+        let start_def_id = id.expect_local();
+        let start_span = tcx.def_span(start_def_id);
+
+        let expected_sig = ty::Binder::dummy(tcx.mk_fn_sig(
+            [tcx.types.isize, Ty::new_imm_ptr(tcx, Ty::new_imm_ptr(tcx, tcx.types.u8))],
+            tcx.types.isize,
+            false,
+            hir::Safety::Safe,
+            ExternAbi::Rust,
+        ));
+
+        let correct_func_sig = check_function_signature(
+            tcx,
+            ObligationCause::new(start_span, start_def_id, ObligationCauseCode::Misc),
+            *id,
+            expected_sig,
+        )
+        .is_ok();
+
+        if correct_func_sig {
+            (*id, EntryFnType::Start)
+        } else {
+            tcx.dcx().fatal(
+                "`miri_start` must have the following signature:\n\
+                        fn miri_start(argc: isize, argv: *const *const u8) -> isize",
+            );
+        }
+    } else {
+        tcx.dcx().fatal(
+            "Miri can only run programs that have a main function.\n\
+            Alternatively, you can export a `miri_start` function:\n\
+            \n\
+            #[cfg(miri)]\n\
+            #[no_mangle]\n\
+            fn miri_start(argc: isize, argv: *const *const u8) -> isize {\
+            \n    // Call the actual start function that your project implements, based on your target's conventions.\n\
+            }"
+        );
+    }
 }
 
 impl rustc_driver::Callbacks for MiriCompilerCalls {
@@ -87,7 +150,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
         }
 
         let (entry_def_id, entry_type) = entry_fn(tcx);
-        let mut config = self.miri_config.clone();
+        let mut config = self.miri_config.take().expect("after_analysis must only be called once");
 
         // Add filename to `miri` arguments.
         config.args.insert(0, tcx.sess.io.input.filestem().to_string());
@@ -111,12 +174,31 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
                     optimizations is usually marginal at best.");
         }
 
-        if let Some(return_code) = miri::eval_entry(tcx, entry_def_id, entry_type, config) {
-            std::process::exit(i32::try_from(return_code).expect("Return value was too large!"));
+        if let Some(many_seeds) = self.many_seeds.take() {
+            assert!(config.seed.is_none());
+            sync::par_for_each_in(many_seeds, |seed| {
+                let mut config = config.clone();
+                config.seed = Some(seed.into());
+                eprintln!("Trying seed: {seed}");
+                let return_code = miri::eval_entry(tcx, entry_def_id, entry_type, config)
+                    .unwrap_or(rustc_driver::EXIT_FAILURE);
+                if return_code != rustc_driver::EXIT_SUCCESS {
+                    eprintln!("FAILING SEED: {seed}");
+                    tcx.dcx().abort_if_errors(); // exits with a different error message
+                    std::process::exit(return_code);
+                }
+            });
+            std::process::exit(rustc_driver::EXIT_SUCCESS);
+        } else {
+            let return_code = miri::eval_entry(tcx, entry_def_id, entry_type, config)
+                .unwrap_or_else(|| {
+                    tcx.dcx().abort_if_errors();
+                    rustc_driver::EXIT_FAILURE
+                });
+            std::process::exit(return_code);
         }
-        tcx.dcx().abort_if_errors();
 
-        Compilation::Stop
+        // Unreachable.
     }
 }
 
@@ -241,21 +323,28 @@ fn rustc_logger_config() -> rustc_log::LoggerConfig {
     cfg
 }
 
+/// The global logger can only be set once per process, so track
+/// whether that already happened.
+static LOGGER_INITED: AtomicBool = AtomicBool::new(false);
+
 fn init_early_loggers(early_dcx: &EarlyDiagCtxt) {
     // Now for rustc. We only initialize `rustc` if the env var is set (so the user asked for it).
     // If it is not set, we avoid initializing now so that we can initialize later with our custom
     // settings, and *not* log anything for what happens before `miri` gets started.
     if env::var_os("RUSTC_LOG").is_some() {
         rustc_driver::init_logger(early_dcx, rustc_logger_config());
+        assert!(!LOGGER_INITED.swap(true, Ordering::AcqRel));
     }
 }
 
 fn init_late_loggers(early_dcx: &EarlyDiagCtxt, tcx: TyCtxt<'_>) {
-    // If `RUSTC_LOG` is not set, then `init_early_loggers` did not call
-    // `rustc_driver::init_logger`, so we have to do this now.
-    if env::var_os("RUSTC_LOG").is_none() {
+    // If the logger is not yet initialized, initialize it.
+    if !LOGGER_INITED.swap(true, Ordering::AcqRel) {
         rustc_driver::init_logger(early_dcx, rustc_logger_config());
     }
+    // There's a little race condition here in many-seeds mode, where we don't wait for the thread
+    // that is doing the initializing. But if you want to debug things with extended logging you
+    // probably won't use many-seeds mode anyway.
 
     // If `MIRI_BACKTRACE` is set and `RUSTC_CTFE_BACKTRACE` is not, set `RUSTC_CTFE_BACKTRACE`.
     // Do this late, so we ideally only apply this to Miri's errors.
@@ -270,25 +359,14 @@ fn init_late_loggers(early_dcx: &EarlyDiagCtxt, tcx: TyCtxt<'_>) {
 }
 
 /// Execute a compiler with the given CLI arguments and callbacks.
-fn run_compiler(
-    mut args: Vec<String>,
-    target_crate: bool,
+fn run_compiler_and_exit(
+    args: &[String],
     callbacks: &mut (dyn rustc_driver::Callbacks + Send),
-    using_internal_features: std::sync::Arc<std::sync::atomic::AtomicBool>,
+    using_internal_features: Arc<std::sync::atomic::AtomicBool>,
 ) -> ! {
-    // Don't insert `MIRI_DEFAULT_ARGS`, in particular, `--cfg=miri`, if we are building
-    // a "host" crate. That may cause procedural macros (and probably build scripts) to
-    // depend on Miri-only symbols, such as `miri_resolve_frame`:
-    // https://github.com/rust-lang/miri/issues/1760
-    if target_crate {
-        // Some options have different defaults in Miri than in plain rustc; apply those by making
-        // them the first arguments after the binary name (but later arguments can overwrite them).
-        args.splice(1..1, miri::MIRI_DEFAULT_ARGS.iter().map(ToString::to_string));
-    }
-
     // Invoke compiler, and handle return code.
     let exit_code = rustc_driver::catch_with_exit_code(move || {
-        rustc_driver::RunCompiler::new(&args, callbacks)
+        rustc_driver::RunCompiler::new(args, callbacks)
             .set_using_internal_features(using_internal_features)
             .run();
         Ok(())
@@ -311,6 +389,18 @@ fn parse_rate(input: &str) -> Result<f64, &'static str> {
     }
 }
 
+/// Parses a seed range
+///
+/// This function is used for the `-Zmiri-many-seeds` flag. It expects the range in the form
+/// `<from>..<to>`. `<from>` is inclusive, `<to>` is exclusive. `<from>` can be omitted,
+/// in which case it is assumed to be `0`.
+fn parse_range(val: &str) -> Result<Range<u32>, &'static str> {
+    let (from, to) = val.split_once("..").ok_or("expected `from..to`")?;
+    let from: u32 = if from.is_empty() { 0 } else { from.parse().map_err(|_| "invalid `from`")? };
+    let to: u32 = to.parse().map_err(|_| "invalid `to`")?;
+    Ok(from..to)
+}
+
 #[cfg(any(target_os = "linux", target_os = "macos"))]
 fn jemalloc_magic() {
     // These magic runes are copied from
@@ -349,56 +439,6 @@ fn jemalloc_magic() {
     }
 }
 
-fn entry_fn(tcx: TyCtxt<'_>) -> (DefId, EntryFnType) {
-    if let Some(entry_def) = tcx.entry_fn(()) {
-        return entry_def;
-    }
-    // Look for a symbol in the local crate named `miri_start`, and treat that as the entry point.
-    let sym = tcx.exported_symbols(LOCAL_CRATE).iter().find_map(|(sym, _)| {
-        if sym.symbol_name_for_local_instance(tcx).name == "miri_start" { Some(sym) } else { None }
-    });
-    if let Some(ExportedSymbol::NonGeneric(id)) = sym {
-        let start_def_id = id.expect_local();
-        let start_span = tcx.def_span(start_def_id);
-
-        let expected_sig = ty::Binder::dummy(tcx.mk_fn_sig(
-            [tcx.types.isize, Ty::new_imm_ptr(tcx, Ty::new_imm_ptr(tcx, tcx.types.u8))],
-            tcx.types.isize,
-            false,
-            hir::Safety::Safe,
-            ExternAbi::Rust,
-        ));
-
-        let correct_func_sig = check_function_signature(
-            tcx,
-            ObligationCause::new(start_span, start_def_id, ObligationCauseCode::Misc),
-            *id,
-            expected_sig,
-        )
-        .is_ok();
-
-        if correct_func_sig {
-            (*id, EntryFnType::Start)
-        } else {
-            tcx.dcx().fatal(
-                "`miri_start` must have the following signature:\n\
-                        fn miri_start(argc: isize, argv: *const *const u8) -> isize",
-            );
-        }
-    } else {
-        tcx.dcx().fatal(
-            "Miri can only run programs that have a main function.\n\
-            Alternatively, you can export a `miri_start` function:\n\
-            \n\
-            #[cfg(miri)]\n\
-            #[no_mangle]\n\
-            fn miri_start(argc: isize, argv: *const *const u8) -> isize {\
-            \n    // Call the actual start function that your project implements, based on your target's conventions.\n\
-            }"
-        );
-    }
-}
-
 fn main() {
     #[cfg(any(target_os = "linux", target_os = "macos"))]
     jemalloc_magic();
@@ -431,10 +471,21 @@ fn main() {
             panic!("invalid `MIRI_BE_RUSTC` value: {crate_kind:?}")
         };
 
-        // We cannot use `rustc_driver::main` as we need to adjust the CLI arguments.
-        run_compiler(
-            args,
-            target_crate,
+        let mut args = args;
+        // Don't insert `MIRI_DEFAULT_ARGS`, in particular, `--cfg=miri`, if we are building
+        // a "host" crate. That may cause procedural macros (and probably build scripts) to
+        // depend on Miri-only symbols, such as `miri_resolve_frame`:
+        // https://github.com/rust-lang/miri/issues/1760
+        if target_crate {
+            // Splice in the default arguments after the program name.
+            // Some options have different defaults in Miri than in plain rustc; apply those by making
+            // them the first arguments after the binary name (but later arguments can overwrite them).
+            args.splice(1..1, miri::MIRI_DEFAULT_ARGS.iter().map(ToString::to_string));
+        }
+
+        // We cannot use `rustc_driver::main` as we want it to use `args` as the CLI arguments.
+        run_compiler_and_exit(
+            &args,
             &mut MiriBeRustCompilerCalls { target_crate },
             using_internal_features,
         )
@@ -448,7 +499,8 @@ fn main() {
     init_early_loggers(&early_dcx);
 
     // Parse our arguments and split them across `rustc` and `miri`.
-    let mut miri_config = miri::MiriConfig::default();
+    let mut many_seeds: Option<Range<u32>> = None;
+    let mut miri_config = MiriConfig::default();
     miri_config.env = env_snapshot;
 
     let mut rustc_args = vec![];
@@ -463,6 +515,8 @@ fn main() {
         if rustc_args.is_empty() {
             // Very first arg: binary name.
             rustc_args.push(arg);
+            // Also add the default arguments.
+            rustc_args.extend(miri::MIRI_DEFAULT_ARGS.iter().map(ToString::to_string));
         } else if after_dashdash {
             // Everything that comes after `--` is forwarded to the interpreted crate.
             miri_config.args.push(arg);
@@ -544,13 +598,19 @@ fn main() {
                 _ => show_error!("`-Zmiri-retag-fields` can only be `all`, `none`, or `scalar`"),
             };
         } else if let Some(param) = arg.strip_prefix("-Zmiri-seed=") {
-            if miri_config.seed.is_some() {
-                show_error!("Cannot specify -Zmiri-seed multiple times!");
-            }
             let seed = param.parse::<u64>().unwrap_or_else(|_| {
                 show_error!("-Zmiri-seed must be an integer that fits into u64")
             });
             miri_config.seed = Some(seed);
+        } else if let Some(param) = arg.strip_prefix("-Zmiri-many-seeds=") {
+            let range = parse_range(param).unwrap_or_else(|err| {
+                show_error!(
+                    "-Zmiri-many-seeds requires a range in the form `from..to` or `..to`: {err}"
+                )
+            });
+            many_seeds = Some(range);
+        } else if arg == "-Zmiri-many-seeds" {
+            many_seeds = Some(0..64);
         } else if let Some(_param) = arg.strip_prefix("-Zmiri-env-exclude=") {
             show_error!(
                 "`-Zmiri-env-exclude` has been removed; unset env vars before starting Miri instead"
@@ -665,13 +725,23 @@ fn main() {
             "Tree Borrows does not support integer-to-pointer casts, and is hence not compatible with permissive provenance"
         );
     }
+    // You can set either one seed or many.
+    if many_seeds.is_some() && miri_config.seed.is_some() {
+        show_error!("Only one of `-Zmiri-seed` and `-Zmiri-many-seeds can be set");
+    }
+    if many_seeds.is_some() && !rustc_args.iter().any(|arg| arg.starts_with("-Zthreads=")) {
+        // Ensure we have parallelism for many-seeds mode.
+        rustc_args.push(format!(
+            "-Zthreads={}",
+            std::thread::available_parallelism().map_or(1, |n| n.get())
+        ));
+    }
 
     debug!("rustc arguments: {:?}", rustc_args);
     debug!("crate arguments: {:?}", miri_config.args);
-    run_compiler(
-        rustc_args,
-        /* target_crate: */ true,
-        &mut MiriCompilerCalls { miri_config },
+    run_compiler_and_exit(
+        &rustc_args,
+        &mut MiriCompilerCalls::new(miri_config, many_seeds),
         using_internal_features,
     )
 }
diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs
index 6b5646d5473..1a12d4139c7 100644
--- a/src/tools/miri/src/diagnostics.rs
+++ b/src/tools/miri/src/diagnostics.rs
@@ -12,7 +12,7 @@ use crate::*;
 /// Details of premature program termination.
 pub enum TerminationInfo {
     Exit {
-        code: i64,
+        code: i32,
         leak_check: bool,
     },
     Abort(String),
@@ -214,7 +214,7 @@ pub fn prune_stacktrace<'tcx>(
 pub fn report_error<'tcx>(
     ecx: &InterpCx<'tcx, MiriMachine<'tcx>>,
     e: InterpErrorInfo<'tcx>,
-) -> Option<(i64, bool)> {
+) -> Option<(i32, bool)> {
     use InterpErrorKind::*;
     use UndefinedBehaviorInfo::*;
 
diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs
index 1df1d08802a..eaf4b30c660 100644
--- a/src/tools/miri/src/eval.rs
+++ b/src/tools/miri/src/eval.rs
@@ -249,6 +249,13 @@ impl<'tcx> MainThreadState<'tcx> {
                 // Figure out exit code.
                 let ret_place = this.machine.main_fn_ret_place.clone().unwrap();
                 let exit_code = this.read_target_isize(&ret_place)?;
+                // Rust uses `isize` but the underlying type of an exit code is `i32`.
+                // Do a saturating cast.
+                let exit_code = i32::try_from(exit_code).unwrap_or(if exit_code >= 0 {
+                    i32::MAX
+                } else {
+                    i32::MIN
+                });
                 // Deal with our thread-local memory. We do *not* want to actually free it, instead we consider TLS
                 // to be like a global `static`, so that all memory reached by it is considered to "not leak".
                 this.terminate_active_thread(TlsAllocAction::Leak)?;
@@ -421,7 +428,7 @@ pub fn create_ecx<'tcx>(
 }
 
 /// Evaluates the entry function specified by `entry_id`.
-/// Returns `Some(return_code)` if program executed completed.
+/// Returns `Some(return_code)` if program execution completed.
 /// Returns `None` if an evaluation error occurred.
 #[expect(clippy::needless_lifetimes)]
 pub fn eval_entry<'tcx>(
@@ -429,7 +436,7 @@ pub fn eval_entry<'tcx>(
     entry_id: DefId,
     entry_type: EntryFnType,
     config: MiriConfig,
-) -> Option<i64> {
+) -> Option<i32> {
     // Copy setting before we move `config`.
     let ignore_leaks = config.ignore_leaks;
 
diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs
index 8c8850ba7e0..6c8ccc83985 100644
--- a/src/tools/miri/src/shims/foreign_items.rs
+++ b/src/tools/miri/src/shims/foreign_items.rs
@@ -428,7 +428,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
             "exit" => {
                 let [code] = this.check_shim(abi, Conv::C, link_name, args)?;
                 let code = this.read_scalar(code)?.to_i32()?;
-                throw_machine_stop!(TerminationInfo::Exit { code: code.into(), leak_check: false });
+                throw_machine_stop!(TerminationInfo::Exit { code, leak_check: false });
             }
             "abort" => {
                 let [] = this.check_shim(abi, Conv::C, link_name, args)?;
diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs
index fe4d2158ff9..0bf56c3d005 100644
--- a/src/tools/miri/src/shims/windows/foreign_items.rs
+++ b/src/tools/miri/src/shims/windows/foreign_items.rs
@@ -552,8 +552,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Miscellaneous
             "ExitProcess" => {
                 let [code] = this.check_shim(abi, sys_conv, link_name, args)?;
-                let code = this.read_scalar(code)?.to_u32()?;
-                throw_machine_stop!(TerminationInfo::Exit { code: code.into(), leak_check: false });
+                // Windows technically uses u32, but we unify everything to a Unix-style i32.
+                let code = this.read_scalar(code)?.to_i32()?;
+                throw_machine_stop!(TerminationInfo::Exit { code, leak_check: false });
             }
             "SystemFunction036" => {
                 // used by getrandom 0.1