Skip to content

Commit 5763627

Browse files
committed
save LTO import information and check it when trying to reuse build products.
adopts simple strategy devised with assistance from mw: Instead of accumulating (and acting upon) LTO import information over an unbounded number of prior compilations, just see if the current import set matches the previous import set. if they don't match, then you cannot reuse the PostLTO build product for that module. In either case (of a match or a non-match), we can (and must) unconditionally emit the current import set as the recorded information in the incremental compilation cache, ready to be loaded during the next compiler run for use in the same check described above. resolves issue 59535.
1 parent 19b3813 commit 5763627

File tree

1 file changed

+114
-10
lines changed
  • src/librustc_codegen_llvm/back

1 file changed

+114
-10
lines changed

Diff for: src/librustc_codegen_llvm/back/lto.rs

+114-10
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,24 @@ use rustc::hir::def_id::LOCAL_CRATE;
1616
use rustc::middle::exported_symbols::SymbolExportLevel;
1717
use rustc::session::config::{self, Lto};
1818
use rustc::util::common::time_ext;
19-
use rustc_data_structures::fx::FxHashMap;
19+
use rustc_data_structures::fx::{FxHashSet, FxHashMap};
2020
use rustc_codegen_ssa::{RLIB_BYTECODE_EXTENSION, ModuleCodegen, ModuleKind};
2121
use log::{info, debug};
2222

2323
use std::ffi::{CStr, CString};
24+
use std::fs::File;
25+
use std::io;
26+
use std::mem;
27+
use std::path::Path;
2428
use std::ptr;
2529
use std::slice;
2630
use 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+
2837
pub 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+
534597
pub(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

Comments
 (0)