@@ -26,11 +26,17 @@ extern crate rustc_span;
2626
2727use std:: env:: { self , VarError } ;
2828use std:: num:: NonZero ;
29+ use std:: ops:: Range ;
2930use std:: path:: PathBuf ;
3031use 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+ } ;
3338use rustc_abi:: ExternAbi ;
39+ use rustc_data_structures:: sync;
3440use rustc_data_structures:: sync:: Lrc ;
3541use rustc_driver:: Compilation ;
3642use rustc_hir:: def_id:: LOCAL_CRATE ;
@@ -52,7 +58,64 @@ use rustc_span::def_id::DefId;
5258use tracing:: debug;
5359
5460struct 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
58121impl 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+
244330fn 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
253340fn 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" ) ) ]
315405fn 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-
402442fn 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}
0 commit comments