@@ -8,6 +8,7 @@ use crate::compile::execute::{CargoProcess, Processor};
88use crate :: toolchain:: Toolchain ;
99use crate :: utils:: wait_for_future;
1010use anyhow:: { bail, Context } ;
11+ use database:: selector:: CompileTestCase ;
1112use log:: debug;
1213use std:: collections:: { HashMap , HashSet } ;
1314use std:: fmt:: { Display , Formatter } ;
@@ -243,6 +244,7 @@ impl Benchmark {
243244 toolchain : & Toolchain ,
244245 iterations : Option < usize > ,
245246 targets : & [ Target ] ,
247+ already_computed : & hashbrown:: HashSet < CompileTestCase > ,
246248 ) -> anyhow:: Result < ( ) > {
247249 if self . config . disabled {
248250 eprintln ! ( "Skipping {}: disabled" , self . name) ;
@@ -273,19 +275,65 @@ impl Benchmark {
273275 return Ok ( ( ) ) ;
274276 }
275277
276- eprintln ! ( "Preparing {}" , self . name) ;
277- let mut target_dirs: Vec < ( ( CodegenBackend , Profile , Target ) , TempDir ) > = vec ! [ ] ;
278+ struct BenchmarkDir {
279+ dir : TempDir ,
280+ scenarios : Vec < Scenario > ,
281+ profile : Profile ,
282+ backend : CodegenBackend ,
283+ target : Target ,
284+ }
285+
286+ // Materialize the test cases that we want to benchmark
287+ // We need to handle scenarios a bit specially, because they share the target directory
288+ let mut benchmark_dirs: Vec < BenchmarkDir > = vec ! [ ] ;
289+
278290 for backend in backends {
279291 for profile in & profiles {
280292 for target in targets {
281- target_dirs. push ( (
282- ( * backend, * profile, * target) ,
283- self . make_temp_dir ( & self . path ) ?,
284- ) ) ;
293+ // Do we have any scenarios left to compute?
294+ let remaining_scenarios = scenarios
295+ . iter ( )
296+ . filter ( |scenario| {
297+ self . should_run_scenario (
298+ scenario,
299+ profile,
300+ backend,
301+ target,
302+ already_computed,
303+ )
304+ } )
305+ . copied ( )
306+ . collect :: < Vec < Scenario > > ( ) ;
307+ if remaining_scenarios. is_empty ( ) {
308+ continue ;
309+ }
310+
311+ let temp_dir = self . make_temp_dir ( & self . path ) ?;
312+ benchmark_dirs. push ( BenchmarkDir {
313+ dir : temp_dir,
314+ scenarios : remaining_scenarios,
315+ profile : * profile,
316+ backend : * backend,
317+ target : * target,
318+ } ) ;
285319 }
286320 }
287321 }
288322
323+ if benchmark_dirs. is_empty ( ) {
324+ eprintln ! (
325+ "Skipping {}: all test cases were previously computed" ,
326+ self . name
327+ ) ;
328+ return Ok ( ( ) ) ;
329+ }
330+
331+ eprintln ! (
332+ "Preparing {} (test cases: {})" ,
333+ self . name,
334+ benchmark_dirs. len( )
335+ ) ;
336+
289337 // In parallel (but with a limit to the number of CPUs), prepare all
290338 // profiles. This is done in parallel vs. sequentially because:
291339 // * We don't record any measurements during this phase, so the
@@ -319,18 +367,18 @@ impl Benchmark {
319367 . get ( ) ,
320368 )
321369 . context ( "jobserver::new" ) ?;
322- let mut threads = Vec :: with_capacity ( target_dirs . len ( ) ) ;
323- for ( ( backend , profile , target ) , prep_dir ) in & target_dirs {
370+ let mut threads = Vec :: with_capacity ( benchmark_dirs . len ( ) ) ;
371+ for benchmark_dir in & benchmark_dirs {
324372 let server = server. clone ( ) ;
325373 let thread = s. spawn :: < _ , anyhow:: Result < ( ) > > ( move || {
326374 wait_for_future ( async move {
327375 let server = server. clone ( ) ;
328376 self . mk_cargo_process (
329377 toolchain,
330- prep_dir . path ( ) ,
331- * profile,
332- * backend,
333- * target,
378+ benchmark_dir . dir . path ( ) ,
379+ benchmark_dir . profile ,
380+ benchmark_dir . backend ,
381+ benchmark_dir . target ,
334382 )
335383 . jobserver ( server)
336384 . run_rustc ( false )
@@ -365,10 +413,11 @@ impl Benchmark {
365413 let mut timing_dirs: Vec < ManuallyDrop < TempDir > > = vec ! [ ] ;
366414
367415 let benchmark_start = std:: time:: Instant :: now ( ) ;
368- for ( ( backend, profile, target) , prep_dir) in & target_dirs {
369- let backend = * backend;
370- let profile = * profile;
371- let target = * target;
416+ for benchmark_dir in & benchmark_dirs {
417+ let backend = benchmark_dir. backend ;
418+ let profile = benchmark_dir. profile ;
419+ let target = benchmark_dir. target ;
420+ let scenarios = & benchmark_dir. scenarios ;
372421 eprintln ! (
373422 "Running {}: {:?} + {:?} + {:?} + {:?}" ,
374423 self . name, profile, scenarios, backend, target,
@@ -388,7 +437,7 @@ impl Benchmark {
388437 }
389438 log:: debug!( "Benchmark iteration {}/{}" , i + 1 , iterations) ;
390439 // Don't delete the directory on error.
391- let timing_dir = ManuallyDrop :: new ( self . make_temp_dir ( prep_dir . path ( ) ) ?) ;
440+ let timing_dir = ManuallyDrop :: new ( self . make_temp_dir ( benchmark_dir . dir . path ( ) ) ?) ;
392441 let cwd = timing_dir. path ( ) ;
393442
394443 // A full non-incremental build.
@@ -458,6 +507,67 @@ impl Benchmark {
458507
459508 Ok ( ( ) )
460509 }
510+
511+ /// Return true if the given `scenario` should be computed.
512+ fn should_run_scenario (
513+ & self ,
514+ scenario : & Scenario ,
515+ profile : & Profile ,
516+ backend : & CodegenBackend ,
517+ target : & Target ,
518+ already_computed : & hashbrown:: HashSet < CompileTestCase > ,
519+ ) -> bool {
520+ let benchmark = database:: Benchmark :: from ( self . name . 0 . as_str ( ) ) ;
521+ let profile = match profile {
522+ Profile :: Check => database:: Profile :: Check ,
523+ Profile :: Debug => database:: Profile :: Debug ,
524+ Profile :: Doc => database:: Profile :: Doc ,
525+ Profile :: DocJson => database:: Profile :: DocJson ,
526+ Profile :: Opt => database:: Profile :: Opt ,
527+ Profile :: Clippy => database:: Profile :: Clippy ,
528+ } ;
529+ let backend = match backend {
530+ CodegenBackend :: Llvm => database:: CodegenBackend :: Llvm ,
531+ CodegenBackend :: Cranelift => database:: CodegenBackend :: Cranelift ,
532+ } ;
533+ let target = match target {
534+ Target :: X86_64UnknownLinuxGnu => database:: Target :: X86_64UnknownLinuxGnu ,
535+ } ;
536+
537+ match scenario {
538+ // For these scenarios, we can simply check if they were benchmarked or not
539+ Scenario :: Full | Scenario :: IncrFull | Scenario :: IncrUnchanged => {
540+ let test_case = CompileTestCase {
541+ benchmark,
542+ profile,
543+ backend,
544+ target,
545+ scenario : match scenario {
546+ Scenario :: Full => database:: Scenario :: Empty ,
547+ Scenario :: IncrFull => database:: Scenario :: IncrementalEmpty ,
548+ Scenario :: IncrUnchanged => database:: Scenario :: IncrementalFresh ,
549+ Scenario :: IncrPatched => unreachable ! ( ) ,
550+ } ,
551+ } ;
552+ !already_computed. contains ( & test_case)
553+ }
554+ // For incr-patched, it is a bit more complicated.
555+ // If there is at least a single uncomputed `IncrPatched`, we need to rerun
556+ // all of them, because they stack on top of one another.
557+ // Note that we don't need to explicitly include `IncrFull` if `IncrPatched`
558+ // is selected, as the benchmark code will always run `IncrFull` before `IncrPatched`.
559+ Scenario :: IncrPatched => self . patches . iter ( ) . any ( |patch| {
560+ let test_case = CompileTestCase {
561+ benchmark,
562+ profile,
563+ scenario : database:: Scenario :: IncrementalPatch ( patch. name ) ,
564+ backend,
565+ target,
566+ } ;
567+ !already_computed. contains ( & test_case)
568+ } ) ,
569+ }
570+ }
461571}
462572
463573/// Directory containing compile-time benchmarks.
0 commit comments