Skip to content

Commit 51bb1da

Browse files
committed
save LTO import information and check it when trying to reuse build products.
resolves issue 59535.
1 parent a7fc093 commit 51bb1da

File tree

1 file changed

+118
-7
lines changed
  • src/librustc_codegen_llvm/back

1 file changed

+118
-7
lines changed

src/librustc_codegen_llvm/back/lto.rs

Lines changed: 118 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,19 @@ use rustc_data_structures::fx::FxHashMap;
1919
use rustc_codegen_ssa::{RLIB_BYTECODE_EXTENSION, ModuleCodegen, ModuleKind};
2020

2121
use std::ffi::{CStr, CString};
22+
use std::fs::File;
23+
use std::io;
24+
use std::mem;
25+
use std::path::Path;
2226
use std::ptr;
2327
use std::slice;
2428
use std::sync::Arc;
2529

30+
/// We keep track of past LTO imports that were used to produce the current set
31+
/// of compiled object files that we might choose to reuse during this
32+
/// compilation session.
33+
pub const THIN_LTO_IMPORTS_INCR_COMP_FILE_NAME: &str = "thin-lto-past-imports.bin";
34+
2635
pub fn crate_type_allows_lto(crate_type: config::CrateType) -> bool {
2736
match crate_type {
2837
config::CrateType::Executable |
@@ -470,13 +479,26 @@ fn thin_lto(cgcx: &CodegenContext<LlvmCodegenBackend>,
470479

471480
info!("thin LTO data created");
472481

473-
let import_map = if cgcx.incr_comp_session_dir.is_some() {
474-
ThinLTOImports::from_thin_lto_data(data)
482+
let (import_map_path, prev_import_map, mut curr_import_map) =
483+
if let Some(ref incr_comp_session_dir) = cgcx.incr_comp_session_dir
484+
{
485+
let path = incr_comp_session_dir.join(THIN_LTO_IMPORTS_INCR_COMP_FILE_NAME);
486+
let prev = if path.exists() {
487+
// FIXME: can/should we recover from IO error occuring here
488+
// (e.g. by clearing green_modules), rather than panicking from
489+
// unwrap call?
490+
Some(ThinLTOImports::load_from_file(&path).unwrap())
491+
} else {
492+
None
493+
};
494+
let curr = ThinLTOImports::from_thin_lto_data(data);
495+
(Some(path), prev, curr)
475496
} else {
476497
// If we don't compile incrementally, we don't need to load the
477498
// import data from LLVM.
478499
assert!(green_modules.is_empty());
479-
ThinLTOImports::default()
500+
let curr = ThinLTOImports::default();
501+
(None, None, curr)
480502
};
481503
info!("thin LTO import map loaded");
482504

@@ -500,20 +522,35 @@ fn thin_lto(cgcx: &CodegenContext<LlvmCodegenBackend>,
500522
for (module_index, module_name) in shared.module_names.iter().enumerate() {
501523
let module_name = module_name_to_str(module_name);
502524

503-
// If the module hasn't changed and none of the modules it imports
504-
// from has changed, we can re-use the post-ThinLTO version of the
505-
// module.
525+
// If the module hasn't changed, and none of the modules it imported
526+
// from (*) has changed, then we can (1.) re-use the post-ThinLTO
527+
// version of the module, and thus (2.) save those previous ThinLTO
528+
// imports as its current set of imports.
529+
//
530+
// (*): that is, "imported from" at the time it was previously ThinLTO'ed;
531+
// see rust-lang/rust#59535.
532+
//
533+
// If we do *not* re-use the post-ThinLTO version of the module,
534+
// then we should save the computed imports that LLVM reported to us
535+
// during this cycle.
506536
if green_modules.contains_key(module_name) {
507-
let imports_all_green = import_map.modules_imported_by(module_name)
537+
assert!(cgcx.incr_comp_session_dir.is_some());
538+
assert!(prev_import_map.is_some());
539+
540+
let prev_import_map = prev_import_map.as_ref().unwrap();
541+
let prev_imports = prev_import_map.modules_imported_by(module_name);
542+
let imports_all_green = prev_imports
508543
.iter()
509544
.all(|imported_module| green_modules.contains_key(imported_module));
510545

511546
if imports_all_green {
512547
let work_product = green_modules[module_name].clone();
513548
copy_jobs.push(work_product);
514549
info!(" - {}: re-used", module_name);
550+
assert!(cgcx.incr_comp_session_dir.is_some());
515551
cgcx.cgu_reuse_tracker.set_actual_reuse(module_name,
516552
CguReuse::PostLto);
553+
curr_import_map.overwrite_with_past_import_state(module_name, prev_imports);
517554
continue
518555
}
519556
}
@@ -525,6 +562,14 @@ fn thin_lto(cgcx: &CodegenContext<LlvmCodegenBackend>,
525562
}));
526563
}
527564

565+
// Save the accumulated ThinLTO import information
566+
if let Some(path) = import_map_path {
567+
if let Err(err) = curr_import_map.save_to_file(&path) {
568+
let msg = format!("Error while writing ThinLTO import data: {}", err);
569+
return Err(write::llvm_err(&diag_handler, &msg));
570+
}
571+
}
572+
528573
Ok((opt_jobs, copy_jobs))
529574
}
530575
}
@@ -826,10 +871,76 @@ pub struct ThinLTOImports {
826871
}
827872

828873
impl ThinLTOImports {
874+
/// Records `llvm_module_name` as importing `prev_imports` rather than what
875+
/// we have currently computed. Ensures that the previous imports are a
876+
/// superset of what imports LLVM currently computed.
877+
fn overwrite_with_past_import_state(&mut self,
878+
llvm_module_name: &str,
879+
prev_imports: &[String]) {
880+
// There were imports used to previous LTO optimize the module; make
881+
// sure they are a superset of whatever we have in our current state,
882+
// and then store them as our new current state.
883+
let curr_imports_for_mod = self.modules_imported_by(llvm_module_name);
884+
for imported in curr_imports_for_mod {
885+
assert!(prev_imports.contains(imported));
886+
}
887+
// We can avoid doing the insertion entirely if the sets are equivalent,
888+
// and we can determine equivalence cheaply via a length comparison,
889+
// since we have already asserted the past state to be a superset of the
890+
// current state.
891+
if prev_imports.len() != curr_imports_for_mod.len() {
892+
debug!("ThinLTOImports::restore_past_import_state \
893+
mod: {:?} replacing curr state: {:?} with prev state: {:?}",
894+
llvm_module_name, curr_imports_for_mod, prev_imports);
895+
self.imports.insert(llvm_module_name.to_owned(), prev_imports.to_vec());
896+
}
897+
}
898+
829899
fn modules_imported_by(&self, llvm_module_name: &str) -> &[String] {
830900
self.imports.get(llvm_module_name).map(|v| &v[..]).unwrap_or(&[])
831901
}
832902

903+
fn save_to_file(&self, path: &Path) -> io::Result<()> {
904+
use std::io::Write;
905+
let file = File::create(path)?;
906+
let mut writer = io::BufWriter::new(file);
907+
for (importing_module_name, imported_modules) in &self.imports {
908+
writeln!(writer, "{}", importing_module_name)?;
909+
for imported_module in imported_modules {
910+
writeln!(writer, " {}", imported_module)?;
911+
}
912+
writeln!(writer)?;
913+
}
914+
Ok(())
915+
}
916+
917+
fn load_from_file(path: &Path) -> io::Result<ThinLTOImports> {
918+
use std::io::BufRead;
919+
let mut imports = FxHashMap::default();
920+
let mut current_module = None;
921+
let mut current_imports = vec![];
922+
let file = File::open(path)?;
923+
for line in io::BufReader::new(file).lines() {
924+
let line = line?;
925+
if line.is_empty() {
926+
let importing_module = current_module
927+
.take()
928+
.expect("Importing module not set");
929+
imports.insert(importing_module,
930+
mem::replace(&mut current_imports, vec![]));
931+
} else if line.starts_with(" ") {
932+
// Space marks an imported module
933+
assert_ne!(current_module, None);
934+
current_imports.push(line.trim().to_string());
935+
} else {
936+
// Otherwise, beginning of a new module (must be start or follow empty line)
937+
assert_eq!(current_module, None);
938+
current_module = Some(line.trim().to_string());
939+
}
940+
}
941+
Ok(ThinLTOImports { imports })
942+
}
943+
833944
/// Loads the ThinLTO import map from ThinLTOData.
834945
unsafe fn from_thin_lto_data(data: *const llvm::ThinLTOData) -> ThinLTOImports {
835946
unsafe extern "C" fn imported_module_callback(payload: *mut libc::c_void,

0 commit comments

Comments
 (0)