@@ -26,11 +26,17 @@ extern crate rustc_span;
26
26
27
27
use std:: env:: { self , VarError } ;
28
28
use std:: num:: NonZero ;
29
+ use std:: ops:: Range ;
29
30
use std:: path:: PathBuf ;
30
31
use std:: str:: FromStr ;
32
+ use std:: sync:: Arc ;
33
+ use std:: sync:: atomic:: { AtomicBool , Ordering } ;
31
34
32
- use miri:: { BacktraceStyle , BorrowTrackerMethod , ProvenanceMode , RetagFields , ValidationMode } ;
35
+ use miri:: {
36
+ BacktraceStyle , BorrowTrackerMethod , MiriConfig , ProvenanceMode , RetagFields , ValidationMode ,
37
+ } ;
33
38
use rustc_abi:: ExternAbi ;
39
+ use rustc_data_structures:: sync;
34
40
use rustc_data_structures:: sync:: Lrc ;
35
41
use rustc_driver:: Compilation ;
36
42
use rustc_hir:: def_id:: LOCAL_CRATE ;
@@ -52,7 +58,64 @@ use rustc_span::def_id::DefId;
52
58
use tracing:: debug;
53
59
54
60
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
+ }
56
119
}
57
120
58
121
impl rustc_driver:: Callbacks for MiriCompilerCalls {
@@ -87,7 +150,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
87
150
}
88
151
89
152
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" ) ;
91
154
92
155
// Add filename to `miri` arguments.
93
156
config. args . insert ( 0 , tcx. sess . io . input . filestem ( ) . to_string ( ) ) ;
@@ -111,12 +174,31 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
111
174
optimizations is usually marginal at best.") ;
112
175
}
113
176
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) ;
116
199
}
117
- tcx. dcx ( ) . abort_if_errors ( ) ;
118
200
119
- Compilation :: Stop
201
+ // Unreachable.
120
202
}
121
203
}
122
204
@@ -241,21 +323,28 @@ fn rustc_logger_config() -> rustc_log::LoggerConfig {
241
323
cfg
242
324
}
243
325
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
+
244
330
fn init_early_loggers ( early_dcx : & EarlyDiagCtxt ) {
245
331
// Now for rustc. We only initialize `rustc` if the env var is set (so the user asked for it).
246
332
// If it is not set, we avoid initializing now so that we can initialize later with our custom
247
333
// settings, and *not* log anything for what happens before `miri` gets started.
248
334
if env:: var_os ( "RUSTC_LOG" ) . is_some ( ) {
249
335
rustc_driver:: init_logger ( early_dcx, rustc_logger_config ( ) ) ;
336
+ assert ! ( !LOGGER_INITED . swap( true , Ordering :: AcqRel ) ) ;
250
337
}
251
338
}
252
339
253
340
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 ) {
257
343
rustc_driver:: init_logger ( early_dcx, rustc_logger_config ( ) ) ;
258
344
}
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.
259
348
260
349
// If `MIRI_BACKTRACE` is set and `RUSTC_CTFE_BACKTRACE` is not, set `RUSTC_CTFE_BACKTRACE`.
261
350
// 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<'_>) {
270
359
}
271
360
272
361
/// 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 ] ,
276
364
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 > ,
278
366
) -> ! {
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
-
289
367
// Invoke compiler, and handle return code.
290
368
let exit_code = rustc_driver:: catch_with_exit_code ( move || {
291
- rustc_driver:: RunCompiler :: new ( & args, callbacks)
369
+ rustc_driver:: RunCompiler :: new ( args, callbacks)
292
370
. set_using_internal_features ( using_internal_features)
293
371
. run ( ) ;
294
372
Ok ( ( ) )
@@ -311,6 +389,18 @@ fn parse_rate(input: &str) -> Result<f64, &'static str> {
311
389
}
312
390
}
313
391
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
+
314
404
#[ cfg( any( target_os = "linux" , target_os = "macos" ) ) ]
315
405
fn jemalloc_magic ( ) {
316
406
// These magic runes are copied from
@@ -349,56 +439,6 @@ fn jemalloc_magic() {
349
439
}
350
440
}
351
441
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
-
402
442
fn main ( ) {
403
443
#[ cfg( any( target_os = "linux" , target_os = "macos" ) ) ]
404
444
jemalloc_magic ( ) ;
@@ -431,10 +471,21 @@ fn main() {
431
471
panic ! ( "invalid `MIRI_BE_RUSTC` value: {crate_kind:?}" )
432
472
} ;
433
473
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,
438
489
& mut MiriBeRustCompilerCalls { target_crate } ,
439
490
using_internal_features,
440
491
)
@@ -448,7 +499,8 @@ fn main() {
448
499
init_early_loggers ( & early_dcx) ;
449
500
450
501
// 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 ( ) ;
452
504
miri_config. env = env_snapshot;
453
505
454
506
let mut rustc_args = vec ! [ ] ;
@@ -463,6 +515,8 @@ fn main() {
463
515
if rustc_args. is_empty ( ) {
464
516
// Very first arg: binary name.
465
517
rustc_args. push ( arg) ;
518
+ // Also add the default arguments.
519
+ rustc_args. extend ( miri:: MIRI_DEFAULT_ARGS . iter ( ) . map ( ToString :: to_string) ) ;
466
520
} else if after_dashdash {
467
521
// Everything that comes after `--` is forwarded to the interpreted crate.
468
522
miri_config. args . push ( arg) ;
@@ -544,13 +598,19 @@ fn main() {
544
598
_ => show_error ! ( "`-Zmiri-retag-fields` can only be `all`, `none`, or `scalar`" ) ,
545
599
} ;
546
600
} 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
- }
550
601
let seed = param. parse :: < u64 > ( ) . unwrap_or_else ( |_| {
551
602
show_error ! ( "-Zmiri-seed must be an integer that fits into u64" )
552
603
} ) ;
553
604
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 ) ;
554
614
} else if let Some ( _param) = arg. strip_prefix ( "-Zmiri-env-exclude=" ) {
555
615
show_error ! (
556
616
"`-Zmiri-env-exclude` has been removed; unset env vars before starting Miri instead"
@@ -665,13 +725,23 @@ fn main() {
665
725
"Tree Borrows does not support integer-to-pointer casts, and is hence not compatible with permissive provenance"
666
726
) ;
667
727
}
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
+ }
668
739
669
740
debug ! ( "rustc arguments: {:?}" , rustc_args) ;
670
741
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) ,
675
745
using_internal_features,
676
746
)
677
747
}
0 commit comments