@@ -16,15 +16,24 @@ use rustc::hir::def_id::LOCAL_CRATE;
16
16
use rustc:: middle:: exported_symbols:: SymbolExportLevel ;
17
17
use rustc:: session:: config:: { self , Lto } ;
18
18
use rustc:: util:: common:: time_ext;
19
- use rustc_data_structures:: fx:: FxHashMap ;
19
+ use rustc_data_structures:: fx:: { FxHashSet , FxHashMap } ;
20
20
use rustc_codegen_ssa:: { RLIB_BYTECODE_EXTENSION , ModuleCodegen , ModuleKind } ;
21
21
use log:: { info, debug} ;
22
22
23
23
use std:: ffi:: { CStr , CString } ;
24
+ use std:: fs:: File ;
25
+ use std:: io;
26
+ use std:: mem;
27
+ use std:: path:: Path ;
24
28
use std:: ptr;
25
29
use std:: slice;
26
30
use std:: sync:: Arc ;
27
31
32
+ /// We keep track of past LTO imports that were used to produce the current set
33
+ /// of compiled object files that we might choose to reuse during this
34
+ /// compilation session.
35
+ pub const THIN_LTO_IMPORTS_INCR_COMP_FILE_NAME : & str = "thin-lto-past-imports.bin" ;
36
+
28
37
pub fn crate_type_allows_lto ( crate_type : config:: CrateType ) -> bool {
29
38
match crate_type {
30
39
config:: CrateType :: Executable |
@@ -472,13 +481,26 @@ fn thin_lto(cgcx: &CodegenContext<LlvmCodegenBackend>,
472
481
473
482
info ! ( "thin LTO data created" ) ;
474
483
475
- let import_map = if cgcx. incr_comp_session_dir . is_some ( ) {
476
- ThinLTOImports :: from_thin_lto_data ( data)
484
+ let ( import_map_path, prev_import_map, curr_import_map) =
485
+ if let Some ( ref incr_comp_session_dir) = cgcx. incr_comp_session_dir
486
+ {
487
+ let path = incr_comp_session_dir. join ( THIN_LTO_IMPORTS_INCR_COMP_FILE_NAME ) ;
488
+ // If previous imports have been deleted, or we get an IO error
489
+ // reading the file storing them, then we'll just use `None` as the
490
+ // prev_import_map, which will force the code to be recompiled.
491
+ let prev = if path. exists ( ) {
492
+ ThinLTOImports :: load_from_file ( & path) . ok ( )
493
+ } else {
494
+ None
495
+ } ;
496
+ let curr = ThinLTOImports :: from_thin_lto_data ( data) ;
497
+ ( Some ( path) , prev, curr)
477
498
} else {
478
499
// If we don't compile incrementally, we don't need to load the
479
500
// import data from LLVM.
480
501
assert ! ( green_modules. is_empty( ) ) ;
481
- ThinLTOImports :: default ( )
502
+ let curr = ThinLTOImports :: default ( ) ;
503
+ ( None , None , curr)
482
504
} ;
483
505
info ! ( "thin LTO import map loaded" ) ;
484
506
@@ -502,18 +524,36 @@ fn thin_lto(cgcx: &CodegenContext<LlvmCodegenBackend>,
502
524
for ( module_index, module_name) in shared. module_names . iter ( ) . enumerate ( ) {
503
525
let module_name = module_name_to_str ( module_name) ;
504
526
505
- // If the module hasn't changed and none of the modules it imports
506
- // from has changed, we can re-use the post-ThinLTO version of the
507
- // module.
508
- if green_modules. contains_key ( module_name) {
509
- let imports_all_green = import_map. modules_imported_by ( module_name)
527
+ // If (1.) the module hasn't changed, and (2.) none of the modules
528
+ // it imports from has changed, *and* (3.) the import-set itself has
529
+ // not changed from the previous compile when it was last
530
+ // ThinLTO'ed, then we can re-use the post-ThinLTO version of the
531
+ // module. Otherwise, freshly perform LTO optimization.
532
+ //
533
+ // This strategy means we can always save the computed imports as
534
+ // canon: when we reuse the post-ThinLTO version, condition (3.)
535
+ // ensures that the curent import set is the same as the previous
536
+ // one. (And of course, when we don't reuse the post-ThinLTO
537
+ // version, the current import set *is* the correct one, since we
538
+ // are doing the ThinLTO in this current compilation cycle.)
539
+ //
540
+ // See rust-lang/rust#59535.
541
+ if let ( Some ( prev_import_map) , true ) =
542
+ ( prev_import_map. as_ref ( ) , green_modules. contains_key ( module_name) )
543
+ {
544
+ assert ! ( cgcx. incr_comp_session_dir. is_some( ) ) ;
545
+
546
+ let prev_imports = prev_import_map. modules_imported_by ( module_name) ;
547
+ let curr_imports = curr_import_map. modules_imported_by ( module_name) ;
548
+ let imports_all_green = curr_imports
510
549
. iter ( )
511
550
. all ( |imported_module| green_modules. contains_key ( imported_module) ) ;
512
551
513
- if imports_all_green {
552
+ if imports_all_green && equivalent_as_sets ( prev_imports , curr_imports ) {
514
553
let work_product = green_modules[ module_name] . clone ( ) ;
515
554
copy_jobs. push ( work_product) ;
516
555
info ! ( " - {}: re-used" , module_name) ;
556
+ assert ! ( cgcx. incr_comp_session_dir. is_some( ) ) ;
517
557
cgcx. cgu_reuse_tracker . set_actual_reuse ( module_name,
518
558
CguReuse :: PostLto ) ;
519
559
continue
@@ -527,10 +567,33 @@ fn thin_lto(cgcx: &CodegenContext<LlvmCodegenBackend>,
527
567
} ) ) ;
528
568
}
529
569
570
+ // Save the curent ThinLTO import information for the next compilation
571
+ // session, overwriting the previous serialized imports (if any).
572
+ if let Some ( path) = import_map_path {
573
+ if let Err ( err) = curr_import_map. save_to_file ( & path) {
574
+ let msg = format ! ( "Error while writing ThinLTO import data: {}" , err) ;
575
+ return Err ( write:: llvm_err ( & diag_handler, & msg) ) ;
576
+ }
577
+ }
578
+
530
579
Ok ( ( opt_jobs, copy_jobs) )
531
580
}
532
581
}
533
582
583
+ /// Given two slices, each with no repeat elements. returns true if and only if
584
+ /// the two slices have the same contents when considered as sets (i.e. when
585
+ /// element order is disregarded).
586
+ fn equivalent_as_sets ( a : & [ String ] , b : & [ String ] ) -> bool {
587
+ // cheap path: unequal lengths means cannot possibly be set equivalent.
588
+ if a. len ( ) != b. len ( ) { return false ; }
589
+ // fast path: before building new things, check if inputs are equivalent as is.
590
+ if a == b { return true ; }
591
+ // slow path: general set comparison.
592
+ let a: FxHashSet < & str > = a. iter ( ) . map ( |s| s. as_str ( ) ) . collect ( ) ;
593
+ let b: FxHashSet < & str > = b. iter ( ) . map ( |s| s. as_str ( ) ) . collect ( ) ;
594
+ a == b
595
+ }
596
+
534
597
pub ( crate ) fn run_pass_manager ( cgcx : & CodegenContext < LlvmCodegenBackend > ,
535
598
module : & ModuleCodegen < ModuleLlvm > ,
536
599
config : & ModuleConfig ,
@@ -832,6 +895,47 @@ impl ThinLTOImports {
832
895
self . imports . get ( llvm_module_name) . map ( |v| & v[ ..] ) . unwrap_or ( & [ ] )
833
896
}
834
897
898
+ fn save_to_file ( & self , path : & Path ) -> io:: Result < ( ) > {
899
+ use std:: io:: Write ;
900
+ let file = File :: create ( path) ?;
901
+ let mut writer = io:: BufWriter :: new ( file) ;
902
+ for ( importing_module_name, imported_modules) in & self . imports {
903
+ writeln ! ( writer, "{}" , importing_module_name) ?;
904
+ for imported_module in imported_modules {
905
+ writeln ! ( writer, " {}" , imported_module) ?;
906
+ }
907
+ writeln ! ( writer) ?;
908
+ }
909
+ Ok ( ( ) )
910
+ }
911
+
912
+ fn load_from_file ( path : & Path ) -> io:: Result < ThinLTOImports > {
913
+ use std:: io:: BufRead ;
914
+ let mut imports = FxHashMap :: default ( ) ;
915
+ let mut current_module = None ;
916
+ let mut current_imports = vec ! [ ] ;
917
+ let file = File :: open ( path) ?;
918
+ for line in io:: BufReader :: new ( file) . lines ( ) {
919
+ let line = line?;
920
+ if line. is_empty ( ) {
921
+ let importing_module = current_module
922
+ . take ( )
923
+ . expect ( "Importing module not set" ) ;
924
+ imports. insert ( importing_module,
925
+ mem:: replace ( & mut current_imports, vec ! [ ] ) ) ;
926
+ } else if line. starts_with ( " " ) {
927
+ // Space marks an imported module
928
+ assert_ne ! ( current_module, None ) ;
929
+ current_imports. push ( line. trim ( ) . to_string ( ) ) ;
930
+ } else {
931
+ // Otherwise, beginning of a new module (must be start or follow empty line)
932
+ assert_eq ! ( current_module, None ) ;
933
+ current_module = Some ( line. trim ( ) . to_string ( ) ) ;
934
+ }
935
+ }
936
+ Ok ( ThinLTOImports { imports } )
937
+ }
938
+
835
939
/// Loads the ThinLTO import map from ThinLTOData.
836
940
unsafe fn from_thin_lto_data ( data : * const llvm:: ThinLTOData ) -> ThinLTOImports {
837
941
unsafe extern "C" fn imported_module_callback ( payload : * mut libc:: c_void ,
0 commit comments