@@ -16,15 +16,24 @@ use rustc::hir::def_id::LOCAL_CRATE;
1616use rustc:: middle:: exported_symbols:: SymbolExportLevel ;
1717use rustc:: session:: config:: { self , Lto } ;
1818use rustc:: util:: common:: time_ext;
19- use rustc_data_structures:: fx:: FxHashMap ;
19+ use rustc_data_structures:: fx:: { FxHashSet , FxHashMap } ;
2020use rustc_codegen_ssa:: { RLIB_BYTECODE_EXTENSION , ModuleCodegen , ModuleKind } ;
2121use log:: { info, debug} ;
2222
2323use std:: ffi:: { CStr , CString } ;
24+ use std:: fs:: File ;
25+ use std:: io;
26+ use std:: mem;
27+ use std:: path:: Path ;
2428use std:: ptr;
2529use std:: slice;
2630use std:: sync:: Arc ;
2731
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+
2837pub fn crate_type_allows_lto ( crate_type : config:: CrateType ) -> bool {
2938 match crate_type {
3039 config:: CrateType :: Executable |
@@ -472,13 +481,26 @@ fn thin_lto(cgcx: &CodegenContext<LlvmCodegenBackend>,
472481
473482 info ! ( "thin LTO data created" ) ;
474483
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)
477498 } else {
478499 // If we don't compile incrementally, we don't need to load the
479500 // import data from LLVM.
480501 assert ! ( green_modules. is_empty( ) ) ;
481- ThinLTOImports :: default ( )
502+ let curr = ThinLTOImports :: default ( ) ;
503+ ( None , None , curr)
482504 } ;
483505 info ! ( "thin LTO import map loaded" ) ;
484506
@@ -502,18 +524,36 @@ fn thin_lto(cgcx: &CodegenContext<LlvmCodegenBackend>,
502524 for ( module_index, module_name) in shared. module_names . iter ( ) . enumerate ( ) {
503525 let module_name = module_name_to_str ( module_name) ;
504526
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
510549 . iter ( )
511550 . all ( |imported_module| green_modules. contains_key ( imported_module) ) ;
512551
513- if imports_all_green {
552+ if imports_all_green && equivalent_as_sets ( prev_imports , curr_imports ) {
514553 let work_product = green_modules[ module_name] . clone ( ) ;
515554 copy_jobs. push ( work_product) ;
516555 info ! ( " - {}: re-used" , module_name) ;
556+ assert ! ( cgcx. incr_comp_session_dir. is_some( ) ) ;
517557 cgcx. cgu_reuse_tracker . set_actual_reuse ( module_name,
518558 CguReuse :: PostLto ) ;
519559 continue
@@ -527,10 +567,33 @@ fn thin_lto(cgcx: &CodegenContext<LlvmCodegenBackend>,
527567 } ) ) ;
528568 }
529569
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+
530579 Ok ( ( opt_jobs, copy_jobs) )
531580 }
532581}
533582
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+
534597pub ( crate ) fn run_pass_manager ( cgcx : & CodegenContext < LlvmCodegenBackend > ,
535598 module : & ModuleCodegen < ModuleLlvm > ,
536599 config : & ModuleConfig ,
@@ -832,6 +895,47 @@ impl ThinLTOImports {
832895 self . imports . get ( llvm_module_name) . map ( |v| & v[ ..] ) . unwrap_or ( & [ ] )
833896 }
834897
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+
835939 /// Loads the ThinLTO import map from ThinLTOData.
836940 unsafe fn from_thin_lto_data ( data : * const llvm:: ThinLTOData ) -> ThinLTOImports {
837941 unsafe extern "C" fn imported_module_callback ( payload : * mut libc:: c_void ,
0 commit comments