Skip to content

Commit d80f319

Browse files
committed
add -Zmiri-many-seeds flag to the driver itself
1 parent bba6f0a commit d80f319

File tree

5 files changed

+172
-94
lines changed

5 files changed

+172
-94
lines changed

src/tools/miri/src/bin/miri.rs

Lines changed: 157 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,17 @@ extern crate rustc_span;
2626

2727
use std::env::{self, VarError};
2828
use std::num::NonZero;
29+
use std::ops::Range;
2930
use std::path::PathBuf;
3031
use std::str::FromStr;
32+
use std::sync::Arc;
33+
use std::sync::atomic::{AtomicBool, Ordering};
3134

32-
use miri::{BacktraceStyle, BorrowTrackerMethod, ProvenanceMode, RetagFields, ValidationMode};
35+
use miri::{
36+
BacktraceStyle, BorrowTrackerMethod, MiriConfig, ProvenanceMode, RetagFields, ValidationMode,
37+
};
3338
use rustc_abi::ExternAbi;
39+
use rustc_data_structures::sync;
3440
use rustc_data_structures::sync::Lrc;
3541
use rustc_driver::Compilation;
3642
use rustc_hir::def_id::LOCAL_CRATE;
@@ -52,7 +58,64 @@ use rustc_span::def_id::DefId;
5258
use tracing::debug;
5359

5460
struct MiriCompilerCalls {
55-
miri_config: miri::MiriConfig,
61+
miri_config: Option<MiriConfig>,
62+
many_seeds: Option<Range<u32>>,
63+
}
64+
65+
impl MiriCompilerCalls {
66+
fn new(miri_config: MiriConfig, many_seeds: Option<Range<u32>>) -> Self {
67+
Self { miri_config: Some(miri_config), many_seeds }
68+
}
69+
}
70+
71+
fn entry_fn(tcx: TyCtxt<'_>) -> (DefId, EntryFnType) {
72+
if let Some(entry_def) = tcx.entry_fn(()) {
73+
return entry_def;
74+
}
75+
// Look for a symbol in the local crate named `miri_start`, and treat that as the entry point.
76+
let sym = tcx.exported_symbols(LOCAL_CRATE).iter().find_map(|(sym, _)| {
77+
if sym.symbol_name_for_local_instance(tcx).name == "miri_start" { Some(sym) } else { None }
78+
});
79+
if let Some(ExportedSymbol::NonGeneric(id)) = sym {
80+
let start_def_id = id.expect_local();
81+
let start_span = tcx.def_span(start_def_id);
82+
83+
let expected_sig = ty::Binder::dummy(tcx.mk_fn_sig(
84+
[tcx.types.isize, Ty::new_imm_ptr(tcx, Ty::new_imm_ptr(tcx, tcx.types.u8))],
85+
tcx.types.isize,
86+
false,
87+
hir::Safety::Safe,
88+
ExternAbi::Rust,
89+
));
90+
91+
let correct_func_sig = check_function_signature(
92+
tcx,
93+
ObligationCause::new(start_span, start_def_id, ObligationCauseCode::Misc),
94+
*id,
95+
expected_sig,
96+
)
97+
.is_ok();
98+
99+
if correct_func_sig {
100+
(*id, EntryFnType::Start)
101+
} else {
102+
tcx.dcx().fatal(
103+
"`miri_start` must have the following signature:\n\
104+
fn miri_start(argc: isize, argv: *const *const u8) -> isize",
105+
);
106+
}
107+
} else {
108+
tcx.dcx().fatal(
109+
"Miri can only run programs that have a main function.\n\
110+
Alternatively, you can export a `miri_start` function:\n\
111+
\n\
112+
#[cfg(miri)]\n\
113+
#[no_mangle]\n\
114+
fn miri_start(argc: isize, argv: *const *const u8) -> isize {\
115+
\n // Call the actual start function that your project implements, based on your target's conventions.\n\
116+
}"
117+
);
118+
}
56119
}
57120

58121
impl rustc_driver::Callbacks for MiriCompilerCalls {
@@ -87,7 +150,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
87150
}
88151

89152
let (entry_def_id, entry_type) = entry_fn(tcx);
90-
let mut config = self.miri_config.clone();
153+
let mut config = self.miri_config.take().expect("after_analysis must only be called once");
91154

92155
// Add filename to `miri` arguments.
93156
config.args.insert(0, tcx.sess.io.input.filestem().to_string());
@@ -111,12 +174,31 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
111174
optimizations is usually marginal at best.");
112175
}
113176

114-
if let Some(return_code) = miri::eval_entry(tcx, entry_def_id, entry_type, config) {
115-
std::process::exit(i32::try_from(return_code).expect("Return value was too large!"));
177+
if let Some(many_seeds) = self.many_seeds.take() {
178+
assert!(config.seed.is_none());
179+
sync::par_for_each_in(many_seeds, |seed| {
180+
let mut config = config.clone();
181+
config.seed = Some(seed.into());
182+
eprintln!("Trying seed: {seed}");
183+
let return_code = miri::eval_entry(tcx, entry_def_id, entry_type, config)
184+
.unwrap_or(rustc_driver::EXIT_FAILURE);
185+
if return_code != rustc_driver::EXIT_SUCCESS {
186+
eprintln!("FAILING SEED: {seed}");
187+
tcx.dcx().abort_if_errors(); // exits with a different error message
188+
std::process::exit(return_code);
189+
}
190+
});
191+
std::process::exit(rustc_driver::EXIT_SUCCESS);
192+
} else {
193+
let return_code = miri::eval_entry(tcx, entry_def_id, entry_type, config)
194+
.unwrap_or_else(|| {
195+
tcx.dcx().abort_if_errors();
196+
rustc_driver::EXIT_FAILURE
197+
});
198+
std::process::exit(return_code);
116199
}
117-
tcx.dcx().abort_if_errors();
118200

119-
Compilation::Stop
201+
// Unreachable.
120202
}
121203
}
122204

@@ -241,21 +323,28 @@ fn rustc_logger_config() -> rustc_log::LoggerConfig {
241323
cfg
242324
}
243325

326+
/// The global logger can only be set once per process, so track
327+
/// whether that already happened.
328+
static LOGGER_INITED: AtomicBool = AtomicBool::new(false);
329+
244330
fn init_early_loggers(early_dcx: &EarlyDiagCtxt) {
245331
// Now for rustc. We only initialize `rustc` if the env var is set (so the user asked for it).
246332
// If it is not set, we avoid initializing now so that we can initialize later with our custom
247333
// settings, and *not* log anything for what happens before `miri` gets started.
248334
if env::var_os("RUSTC_LOG").is_some() {
249335
rustc_driver::init_logger(early_dcx, rustc_logger_config());
336+
assert!(!LOGGER_INITED.swap(true, Ordering::AcqRel));
250337
}
251338
}
252339

253340
fn init_late_loggers(early_dcx: &EarlyDiagCtxt, tcx: TyCtxt<'_>) {
254-
// If `RUSTC_LOG` is not set, then `init_early_loggers` did not call
255-
// `rustc_driver::init_logger`, so we have to do this now.
256-
if env::var_os("RUSTC_LOG").is_none() {
341+
// If the logger is not yet initialized, initialize it.
342+
if !LOGGER_INITED.swap(true, Ordering::AcqRel) {
257343
rustc_driver::init_logger(early_dcx, rustc_logger_config());
258344
}
345+
// There's a little race condition here in many-seeds mode, where we don't wait for the thread
346+
// that is doing the initializing. But if you want to debug things with extended logging you
347+
// probably won't use many-seeds mode anyway.
259348

260349
// If `MIRI_BACKTRACE` is set and `RUSTC_CTFE_BACKTRACE` is not, set `RUSTC_CTFE_BACKTRACE`.
261350
// 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<'_>) {
270359
}
271360

272361
/// Execute a compiler with the given CLI arguments and callbacks.
273-
fn run_compiler(
274-
mut args: Vec<String>,
275-
target_crate: bool,
362+
fn run_compiler_and_exit(
363+
args: &[String],
276364
callbacks: &mut (dyn rustc_driver::Callbacks + Send),
277-
using_internal_features: std::sync::Arc<std::sync::atomic::AtomicBool>,
365+
using_internal_features: Arc<std::sync::atomic::AtomicBool>,
278366
) -> ! {
279-
// Don't insert `MIRI_DEFAULT_ARGS`, in particular, `--cfg=miri`, if we are building
280-
// a "host" crate. That may cause procedural macros (and probably build scripts) to
281-
// depend on Miri-only symbols, such as `miri_resolve_frame`:
282-
// https://github.com/rust-lang/miri/issues/1760
283-
if target_crate {
284-
// Some options have different defaults in Miri than in plain rustc; apply those by making
285-
// them the first arguments after the binary name (but later arguments can overwrite them).
286-
args.splice(1..1, miri::MIRI_DEFAULT_ARGS.iter().map(ToString::to_string));
287-
}
288-
289367
// Invoke compiler, and handle return code.
290368
let exit_code = rustc_driver::catch_with_exit_code(move || {
291-
rustc_driver::RunCompiler::new(&args, callbacks)
369+
rustc_driver::RunCompiler::new(args, callbacks)
292370
.set_using_internal_features(using_internal_features)
293371
.run();
294372
Ok(())
@@ -311,6 +389,18 @@ fn parse_rate(input: &str) -> Result<f64, &'static str> {
311389
}
312390
}
313391

392+
/// Parses a seed range
393+
///
394+
/// This function is used for the `-Zmiri-many-seeds` flag. It expects the range in the form
395+
/// `<from>..<to>`. `<from>` is inclusive, `<to>` is exclusive. `<from>` can be omitted,
396+
/// in which case it is assumed to be `0`.
397+
fn parse_range(val: &str) -> Result<Range<u32>, &'static str> {
398+
let (from, to) = val.split_once("..").ok_or("expected `from..to`")?;
399+
let from: u32 = if from.is_empty() { 0 } else { from.parse().map_err(|_| "invalid `from`")? };
400+
let to: u32 = to.parse().map_err(|_| "invalid `to`")?;
401+
Ok(from..to)
402+
}
403+
314404
#[cfg(any(target_os = "linux", target_os = "macos"))]
315405
fn jemalloc_magic() {
316406
// These magic runes are copied from
@@ -349,56 +439,6 @@ fn jemalloc_magic() {
349439
}
350440
}
351441

352-
fn entry_fn(tcx: TyCtxt<'_>) -> (DefId, EntryFnType) {
353-
if let Some(entry_def) = tcx.entry_fn(()) {
354-
return entry_def;
355-
}
356-
// Look for a symbol in the local crate named `miri_start`, and treat that as the entry point.
357-
let sym = tcx.exported_symbols(LOCAL_CRATE).iter().find_map(|(sym, _)| {
358-
if sym.symbol_name_for_local_instance(tcx).name == "miri_start" { Some(sym) } else { None }
359-
});
360-
if let Some(ExportedSymbol::NonGeneric(id)) = sym {
361-
let start_def_id = id.expect_local();
362-
let start_span = tcx.def_span(start_def_id);
363-
364-
let expected_sig = ty::Binder::dummy(tcx.mk_fn_sig(
365-
[tcx.types.isize, Ty::new_imm_ptr(tcx, Ty::new_imm_ptr(tcx, tcx.types.u8))],
366-
tcx.types.isize,
367-
false,
368-
hir::Safety::Safe,
369-
ExternAbi::Rust,
370-
));
371-
372-
let correct_func_sig = check_function_signature(
373-
tcx,
374-
ObligationCause::new(start_span, start_def_id, ObligationCauseCode::Misc),
375-
*id,
376-
expected_sig,
377-
)
378-
.is_ok();
379-
380-
if correct_func_sig {
381-
(*id, EntryFnType::Start)
382-
} else {
383-
tcx.dcx().fatal(
384-
"`miri_start` must have the following signature:\n\
385-
fn miri_start(argc: isize, argv: *const *const u8) -> isize",
386-
);
387-
}
388-
} else {
389-
tcx.dcx().fatal(
390-
"Miri can only run programs that have a main function.\n\
391-
Alternatively, you can export a `miri_start` function:\n\
392-
\n\
393-
#[cfg(miri)]\n\
394-
#[no_mangle]\n\
395-
fn miri_start(argc: isize, argv: *const *const u8) -> isize {\
396-
\n // Call the actual start function that your project implements, based on your target's conventions.\n\
397-
}"
398-
);
399-
}
400-
}
401-
402442
fn main() {
403443
#[cfg(any(target_os = "linux", target_os = "macos"))]
404444
jemalloc_magic();
@@ -431,10 +471,21 @@ fn main() {
431471
panic!("invalid `MIRI_BE_RUSTC` value: {crate_kind:?}")
432472
};
433473

434-
// We cannot use `rustc_driver::main` as we need to adjust the CLI arguments.
435-
run_compiler(
436-
args,
437-
target_crate,
474+
let mut args = args;
475+
// Don't insert `MIRI_DEFAULT_ARGS`, in particular, `--cfg=miri`, if we are building
476+
// a "host" crate. That may cause procedural macros (and probably build scripts) to
477+
// depend on Miri-only symbols, such as `miri_resolve_frame`:
478+
// https://github.com/rust-lang/miri/issues/1760
479+
if target_crate {
480+
// Splice in the default arguments after the program name.
481+
// Some options have different defaults in Miri than in plain rustc; apply those by making
482+
// them the first arguments after the binary name (but later arguments can overwrite them).
483+
args.splice(1..1, miri::MIRI_DEFAULT_ARGS.iter().map(ToString::to_string));
484+
}
485+
486+
// We cannot use `rustc_driver::main` as we want it to use `args` as the CLI arguments.
487+
run_compiler_and_exit(
488+
&args,
438489
&mut MiriBeRustCompilerCalls { target_crate },
439490
using_internal_features,
440491
)
@@ -448,7 +499,8 @@ fn main() {
448499
init_early_loggers(&early_dcx);
449500

450501
// Parse our arguments and split them across `rustc` and `miri`.
451-
let mut miri_config = miri::MiriConfig::default();
502+
let mut many_seeds: Option<Range<u32>> = None;
503+
let mut miri_config = MiriConfig::default();
452504
miri_config.env = env_snapshot;
453505

454506
let mut rustc_args = vec![];
@@ -463,6 +515,8 @@ fn main() {
463515
if rustc_args.is_empty() {
464516
// Very first arg: binary name.
465517
rustc_args.push(arg);
518+
// Also add the default arguments.
519+
rustc_args.extend(miri::MIRI_DEFAULT_ARGS.iter().map(ToString::to_string));
466520
} else if after_dashdash {
467521
// Everything that comes after `--` is forwarded to the interpreted crate.
468522
miri_config.args.push(arg);
@@ -544,13 +598,19 @@ fn main() {
544598
_ => show_error!("`-Zmiri-retag-fields` can only be `all`, `none`, or `scalar`"),
545599
};
546600
} else if let Some(param) = arg.strip_prefix("-Zmiri-seed=") {
547-
if miri_config.seed.is_some() {
548-
show_error!("Cannot specify -Zmiri-seed multiple times!");
549-
}
550601
let seed = param.parse::<u64>().unwrap_or_else(|_| {
551602
show_error!("-Zmiri-seed must be an integer that fits into u64")
552603
});
553604
miri_config.seed = Some(seed);
605+
} else if let Some(param) = arg.strip_prefix("-Zmiri-many-seeds=") {
606+
let range = parse_range(param).unwrap_or_else(|err| {
607+
show_error!(
608+
"-Zmiri-many-seeds requires a range in the form `from..to` or `..to`: {err}"
609+
)
610+
});
611+
many_seeds = Some(range);
612+
} else if arg == "-Zmiri-many-seeds" {
613+
many_seeds = Some(0..64);
554614
} else if let Some(_param) = arg.strip_prefix("-Zmiri-env-exclude=") {
555615
show_error!(
556616
"`-Zmiri-env-exclude` has been removed; unset env vars before starting Miri instead"
@@ -665,13 +725,23 @@ fn main() {
665725
"Tree Borrows does not support integer-to-pointer casts, and is hence not compatible with permissive provenance"
666726
);
667727
}
728+
// You can set either one seed or many.
729+
if many_seeds.is_some() && miri_config.seed.is_some() {
730+
show_error!("Only one of `-Zmiri-seed` and `-Zmiri-many-seeds can be set");
731+
}
732+
if many_seeds.is_some() && !rustc_args.iter().any(|arg| arg.starts_with("-Zthreads=")) {
733+
// Ensure we have parallelism for many-seeds mode.
734+
rustc_args.push(format!(
735+
"-Zthreads={}",
736+
std::thread::available_parallelism().map_or(1, |n| n.get())
737+
));
738+
}
668739

669740
debug!("rustc arguments: {:?}", rustc_args);
670741
debug!("crate arguments: {:?}", miri_config.args);
671-
run_compiler(
672-
rustc_args,
673-
/* target_crate: */ true,
674-
&mut MiriCompilerCalls { miri_config },
742+
run_compiler_and_exit(
743+
&rustc_args,
744+
&mut MiriCompilerCalls::new(miri_config, many_seeds),
675745
using_internal_features,
676746
)
677747
}

src/tools/miri/src/diagnostics.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::*;
1212
/// Details of premature program termination.
1313
pub enum TerminationInfo {
1414
Exit {
15-
code: i64,
15+
code: i32,
1616
leak_check: bool,
1717
},
1818
Abort(String),
@@ -214,7 +214,7 @@ pub fn prune_stacktrace<'tcx>(
214214
pub fn report_error<'tcx>(
215215
ecx: &InterpCx<'tcx, MiriMachine<'tcx>>,
216216
e: InterpErrorInfo<'tcx>,
217-
) -> Option<(i64, bool)> {
217+
) -> Option<(i32, bool)> {
218218
use InterpErrorKind::*;
219219
use UndefinedBehaviorInfo::*;
220220

0 commit comments

Comments
 (0)