Skip to content

Commit ac76206

Browse files
committed
Auto merge of rust-lang#44841 - alexcrichton:thinlto, r=michaelwoerister
rustc: Implement ThinLTO This commit is an implementation of LLVM's ThinLTO for consumption in rustc itself. Currently today LTO works by merging all relevant LLVM modules into one and then running optimization passes. "Thin" LTO operates differently by having more sharded work and allowing parallelism opportunities between optimizing codegen units. Further down the road Thin LTO also allows *incremental* LTO which should enable even faster release builds without compromising on the performance we have today. This commit uses a `-Z thinlto` flag to gate whether ThinLTO is enabled. It then also implements two forms of ThinLTO: * In one mode we'll *only* perform ThinLTO over the codegen units produced in a single compilation. That is, we won't load upstream rlibs, but we'll instead just perform ThinLTO amongst all codegen units produced by the compiler for the local crate. This is intended to emulate a desired end point where we have codegen units turned on by default for all crates and ThinLTO allows us to do this without performance loss. * In anther mode, like full LTO today, we'll optimize all upstream dependencies in "thin" mode. Unlike today, however, this LTO step is fully parallelized so should finish much more quickly. There's a good bit of comments about what the implementation is doing and where it came from, but the tl;dr; is that currently most of the support here is copied from upstream LLVM. This code duplication is done for a number of reasons: * Controlling parallelism means we can use the existing jobserver support to avoid overloading machines. * We will likely want a slightly different form of incremental caching which integrates with our own incremental strategy, but this is yet to be determined. * This buys us some flexibility about when/where we run ThinLTO, as well as having it tailored to fit our needs for the time being. * Finally this allows us to reuse some artifacts such as our `TargetMachine` creation, where all our options we used today aren't necessarily supported by upstream LLVM yet. My hope is that we can get some experience with this copy/paste in tree and then eventually upstream some work to LLVM itself to avoid the duplication while still ensuring our needs are met. Otherwise I fear that maintaining these bindings may be quite costly over the years with LLVM updates!
2 parents 05f8ddc + 4ca1b19 commit ac76206

File tree

24 files changed

+1289
-183
lines changed

24 files changed

+1289
-183
lines changed

src/librustc/session/config.rs

+6-14
Original file line numberDiff line numberDiff line change
@@ -409,9 +409,7 @@ impl_stable_hash_for!(struct self::OutputFilenames {
409409
outputs
410410
});
411411

412-
/// Codegen unit names generated by the numbered naming scheme will contain this
413-
/// marker right before the index of the codegen unit.
414-
pub const NUMBERED_CODEGEN_UNIT_MARKER: &'static str = ".cgu-";
412+
pub const RUST_CGU_EXT: &str = "rust-cgu";
415413

416414
impl OutputFilenames {
417415
pub fn path(&self, flavor: OutputType) -> PathBuf {
@@ -442,22 +440,14 @@ impl OutputFilenames {
442440
let mut extension = String::new();
443441

444442
if let Some(codegen_unit_name) = codegen_unit_name {
445-
if codegen_unit_name.contains(NUMBERED_CODEGEN_UNIT_MARKER) {
446-
// If we use the numbered naming scheme for modules, we don't want
447-
// the files to look like <crate-name><extra>.<crate-name>.<index>.<ext>
448-
// but simply <crate-name><extra>.<index>.<ext>
449-
let marker_offset = codegen_unit_name.rfind(NUMBERED_CODEGEN_UNIT_MARKER)
450-
.unwrap();
451-
let index_offset = marker_offset + NUMBERED_CODEGEN_UNIT_MARKER.len();
452-
extension.push_str(&codegen_unit_name[index_offset .. ]);
453-
} else {
454-
extension.push_str(codegen_unit_name);
455-
};
443+
extension.push_str(codegen_unit_name);
456444
}
457445

458446
if !ext.is_empty() {
459447
if !extension.is_empty() {
460448
extension.push_str(".");
449+
extension.push_str(RUST_CGU_EXT);
450+
extension.push_str(".");
461451
}
462452

463453
extension.push_str(ext);
@@ -1105,6 +1095,8 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
11051095
"run the non-lexical lifetimes MIR pass"),
11061096
trans_time_graph: bool = (false, parse_bool, [UNTRACKED],
11071097
"generate a graphical HTML report of time spent in trans and LLVM"),
1098+
thinlto: bool = (false, parse_bool, [TRACKED],
1099+
"enable ThinLTO when possible"),
11081100
}
11091101

11101102
pub fn default_lib_output() -> CrateType {

src/librustc_llvm/build.rs

+1
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ fn main() {
115115
"linker",
116116
"asmparser",
117117
"mcjit",
118+
"lto",
118119
"interpreter",
119120
"instrumentation"];
120121

src/librustc_llvm/ffi.rs

+56
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,20 @@ pub enum PassKind {
345345
Module,
346346
}
347347

348+
/// LLVMRustThinLTOData
349+
pub enum ThinLTOData {}
350+
351+
/// LLVMRustThinLTOBuffer
352+
pub enum ThinLTOBuffer {}
353+
354+
/// LLVMRustThinLTOModule
355+
#[repr(C)]
356+
pub struct ThinLTOModule {
357+
pub identifier: *const c_char,
358+
pub data: *const u8,
359+
pub len: usize,
360+
}
361+
348362
// Opaque pointer types
349363
#[allow(missing_copy_implementations)]
350364
pub enum Module_opaque {}
@@ -1271,6 +1285,9 @@ extern "C" {
12711285
PM: PassManagerRef,
12721286
Internalize: Bool,
12731287
RunInliner: Bool);
1288+
pub fn LLVMRustPassManagerBuilderPopulateThinLTOPassManager(
1289+
PMB: PassManagerBuilderRef,
1290+
PM: PassManagerRef) -> bool;
12741291

12751292
// Stuff that's in rustllvm/ because it's not upstream yet.
12761293

@@ -1685,4 +1702,43 @@ extern "C" {
16851702
pub fn LLVMRustModuleBufferLen(p: *const ModuleBuffer) -> usize;
16861703
pub fn LLVMRustModuleBufferFree(p: *mut ModuleBuffer);
16871704
pub fn LLVMRustModuleCost(M: ModuleRef) -> u64;
1705+
1706+
pub fn LLVMRustThinLTOAvailable() -> bool;
1707+
pub fn LLVMRustWriteThinBitcodeToFile(PMR: PassManagerRef,
1708+
M: ModuleRef,
1709+
BC: *const c_char) -> bool;
1710+
pub fn LLVMRustThinLTOBufferCreate(M: ModuleRef) -> *mut ThinLTOBuffer;
1711+
pub fn LLVMRustThinLTOBufferFree(M: *mut ThinLTOBuffer);
1712+
pub fn LLVMRustThinLTOBufferPtr(M: *const ThinLTOBuffer) -> *const c_char;
1713+
pub fn LLVMRustThinLTOBufferLen(M: *const ThinLTOBuffer) -> size_t;
1714+
pub fn LLVMRustCreateThinLTOData(
1715+
Modules: *const ThinLTOModule,
1716+
NumModules: c_uint,
1717+
PreservedSymbols: *const *const c_char,
1718+
PreservedSymbolsLen: c_uint,
1719+
) -> *mut ThinLTOData;
1720+
pub fn LLVMRustPrepareThinLTORename(
1721+
Data: *const ThinLTOData,
1722+
Module: ModuleRef,
1723+
) -> bool;
1724+
pub fn LLVMRustPrepareThinLTOResolveWeak(
1725+
Data: *const ThinLTOData,
1726+
Module: ModuleRef,
1727+
) -> bool;
1728+
pub fn LLVMRustPrepareThinLTOInternalize(
1729+
Data: *const ThinLTOData,
1730+
Module: ModuleRef,
1731+
) -> bool;
1732+
pub fn LLVMRustPrepareThinLTOImport(
1733+
Data: *const ThinLTOData,
1734+
Module: ModuleRef,
1735+
) -> bool;
1736+
pub fn LLVMRustFreeThinLTOData(Data: *mut ThinLTOData);
1737+
pub fn LLVMRustParseBitcodeForThinLTO(
1738+
Context: ContextRef,
1739+
Data: *const u8,
1740+
len: usize,
1741+
Identifier: *const c_char,
1742+
) -> ModuleRef;
1743+
pub fn LLVMGetModuleIdentifier(M: ModuleRef, size: *mut usize) -> *const c_char;
16881744
}

src/librustc_trans/back/link.rs

+30-13
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use super::rpath::RPathConfig;
1616
use super::rpath;
1717
use metadata::METADATA_FILENAME;
1818
use rustc::session::config::{self, NoDebugInfo, OutputFilenames, OutputType, PrintRequest};
19+
use rustc::session::config::RUST_CGU_EXT;
1920
use rustc::session::filesearch;
2021
use rustc::session::search_paths::PathKind;
2122
use rustc::session::Session;
@@ -45,13 +46,9 @@ use syntax::attr;
4546
/// The LLVM module name containing crate-metadata. This includes a `.` on
4647
/// purpose, so it cannot clash with the name of a user-defined module.
4748
pub const METADATA_MODULE_NAME: &'static str = "crate.metadata";
48-
/// The name of the crate-metadata object file the compiler generates. Must
49-
/// match up with `METADATA_MODULE_NAME`.
50-
pub const METADATA_OBJ_NAME: &'static str = "crate.metadata.o";
5149

5250
// same as for metadata above, but for allocator shim
5351
pub const ALLOCATOR_MODULE_NAME: &'static str = "crate.allocator";
54-
pub const ALLOCATOR_OBJ_NAME: &'static str = "crate.allocator.o";
5552

5653
pub use rustc_trans_utils::link::{find_crate_name, filename_for_input, default_output_for_target,
5754
invalid_output_for_target, build_link_meta, out_filename,
@@ -129,6 +126,14 @@ fn command_path(sess: &Session) -> OsString {
129126
env::join_paths(new_path).unwrap()
130127
}
131128

129+
fn metadata_obj(outputs: &OutputFilenames) -> PathBuf {
130+
outputs.temp_path(OutputType::Object, Some(METADATA_MODULE_NAME))
131+
}
132+
133+
fn allocator_obj(outputs: &OutputFilenames) -> PathBuf {
134+
outputs.temp_path(OutputType::Object, Some(ALLOCATOR_MODULE_NAME))
135+
}
136+
132137
pub fn remove(sess: &Session, path: &Path) {
133138
match fs::remove_file(path) {
134139
Ok(..) => {}
@@ -174,9 +179,9 @@ pub fn link_binary(sess: &Session,
174179
remove(sess, &obj.object);
175180
}
176181
}
177-
remove(sess, &outputs.with_extension(METADATA_OBJ_NAME));
182+
remove(sess, &metadata_obj(outputs));
178183
if trans.allocator_module.is_some() {
179-
remove(sess, &outputs.with_extension(ALLOCATOR_OBJ_NAME));
184+
remove(sess, &allocator_obj(outputs));
180185
}
181186
}
182187

@@ -478,7 +483,7 @@ fn link_rlib<'a>(sess: &'a Session,
478483

479484
RlibFlavor::StaticlibBase => {
480485
if trans.allocator_module.is_some() {
481-
ab.add_file(&outputs.with_extension(ALLOCATOR_OBJ_NAME));
486+
ab.add_file(&allocator_obj(outputs));
482487
}
483488
}
484489
}
@@ -908,11 +913,11 @@ fn link_args(cmd: &mut Linker,
908913
// object file, so we link that in here.
909914
if crate_type == config::CrateTypeDylib ||
910915
crate_type == config::CrateTypeProcMacro {
911-
cmd.add_object(&outputs.with_extension(METADATA_OBJ_NAME));
916+
cmd.add_object(&metadata_obj(outputs));
912917
}
913918

914919
if trans.allocator_module.is_some() {
915-
cmd.add_object(&outputs.with_extension(ALLOCATOR_OBJ_NAME));
920+
cmd.add_object(&allocator_obj(outputs));
916921
}
917922

918923
// Try to strip as much out of the generated object by removing unused
@@ -1265,11 +1270,23 @@ fn add_upstream_rust_crates(cmd: &mut Linker,
12651270
let canonical = f.replace("-", "_");
12661271
let canonical_name = name.replace("-", "_");
12671272

1273+
// Look for `.rust-cgu.o` at the end of the filename to conclude
1274+
// that this is a Rust-related object file.
1275+
fn looks_like_rust(s: &str) -> bool {
1276+
let path = Path::new(s);
1277+
let ext = path.extension().and_then(|s| s.to_str());
1278+
if ext != Some(OutputType::Object.extension()) {
1279+
return false
1280+
}
1281+
let ext2 = path.file_stem()
1282+
.and_then(|s| Path::new(s).extension())
1283+
.and_then(|s| s.to_str());
1284+
ext2 == Some(RUST_CGU_EXT)
1285+
}
1286+
12681287
let is_rust_object =
1269-
canonical.starts_with(&canonical_name) && {
1270-
let num = &f[name.len()..f.len() - 2];
1271-
num.len() > 0 && num[1..].parse::<u32>().is_ok()
1272-
};
1288+
canonical.starts_with(&canonical_name) &&
1289+
looks_like_rust(&f);
12731290

12741291
// If we've been requested to skip all native object files
12751292
// (those not generated by the rust compiler) then we can skip

0 commit comments

Comments
 (0)