Skip to content

Commit

Permalink
Auto merge of #44841 - alexcrichton:thinlto, r=michaelwoerister
Browse files Browse the repository at this point in the history
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!
  • Loading branch information
bors committed Oct 7, 2017
2 parents 05f8ddc + 4ca1b19 commit ac76206
Show file tree
Hide file tree
Showing 24 changed files with 1,289 additions and 183 deletions.
20 changes: 6 additions & 14 deletions src/librustc/session/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -409,9 +409,7 @@ impl_stable_hash_for!(struct self::OutputFilenames {
outputs
});

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

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

if let Some(codegen_unit_name) = codegen_unit_name {
if codegen_unit_name.contains(NUMBERED_CODEGEN_UNIT_MARKER) {
// If we use the numbered naming scheme for modules, we don't want
// the files to look like <crate-name><extra>.<crate-name>.<index>.<ext>
// but simply <crate-name><extra>.<index>.<ext>
let marker_offset = codegen_unit_name.rfind(NUMBERED_CODEGEN_UNIT_MARKER)
.unwrap();
let index_offset = marker_offset + NUMBERED_CODEGEN_UNIT_MARKER.len();
extension.push_str(&codegen_unit_name[index_offset .. ]);
} else {
extension.push_str(codegen_unit_name);
};
extension.push_str(codegen_unit_name);
}

if !ext.is_empty() {
if !extension.is_empty() {
extension.push_str(".");
extension.push_str(RUST_CGU_EXT);
extension.push_str(".");
}

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

pub fn default_lib_output() -> CrateType {
Expand Down
1 change: 1 addition & 0 deletions src/librustc_llvm/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ fn main() {
"linker",
"asmparser",
"mcjit",
"lto",
"interpreter",
"instrumentation"];

Expand Down
56 changes: 56 additions & 0 deletions src/librustc_llvm/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,20 @@ pub enum PassKind {
Module,
}

/// LLVMRustThinLTOData
pub enum ThinLTOData {}

/// LLVMRustThinLTOBuffer
pub enum ThinLTOBuffer {}

/// LLVMRustThinLTOModule
#[repr(C)]
pub struct ThinLTOModule {
pub identifier: *const c_char,
pub data: *const u8,
pub len: usize,
}

// Opaque pointer types
#[allow(missing_copy_implementations)]
pub enum Module_opaque {}
Expand Down Expand Up @@ -1271,6 +1285,9 @@ extern "C" {
PM: PassManagerRef,
Internalize: Bool,
RunInliner: Bool);
pub fn LLVMRustPassManagerBuilderPopulateThinLTOPassManager(
PMB: PassManagerBuilderRef,
PM: PassManagerRef) -> bool;

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

Expand Down Expand Up @@ -1685,4 +1702,43 @@ extern "C" {
pub fn LLVMRustModuleBufferLen(p: *const ModuleBuffer) -> usize;
pub fn LLVMRustModuleBufferFree(p: *mut ModuleBuffer);
pub fn LLVMRustModuleCost(M: ModuleRef) -> u64;

pub fn LLVMRustThinLTOAvailable() -> bool;
pub fn LLVMRustWriteThinBitcodeToFile(PMR: PassManagerRef,
M: ModuleRef,
BC: *const c_char) -> bool;
pub fn LLVMRustThinLTOBufferCreate(M: ModuleRef) -> *mut ThinLTOBuffer;
pub fn LLVMRustThinLTOBufferFree(M: *mut ThinLTOBuffer);
pub fn LLVMRustThinLTOBufferPtr(M: *const ThinLTOBuffer) -> *const c_char;
pub fn LLVMRustThinLTOBufferLen(M: *const ThinLTOBuffer) -> size_t;
pub fn LLVMRustCreateThinLTOData(
Modules: *const ThinLTOModule,
NumModules: c_uint,
PreservedSymbols: *const *const c_char,
PreservedSymbolsLen: c_uint,
) -> *mut ThinLTOData;
pub fn LLVMRustPrepareThinLTORename(
Data: *const ThinLTOData,
Module: ModuleRef,
) -> bool;
pub fn LLVMRustPrepareThinLTOResolveWeak(
Data: *const ThinLTOData,
Module: ModuleRef,
) -> bool;
pub fn LLVMRustPrepareThinLTOInternalize(
Data: *const ThinLTOData,
Module: ModuleRef,
) -> bool;
pub fn LLVMRustPrepareThinLTOImport(
Data: *const ThinLTOData,
Module: ModuleRef,
) -> bool;
pub fn LLVMRustFreeThinLTOData(Data: *mut ThinLTOData);
pub fn LLVMRustParseBitcodeForThinLTO(
Context: ContextRef,
Data: *const u8,
len: usize,
Identifier: *const c_char,
) -> ModuleRef;
pub fn LLVMGetModuleIdentifier(M: ModuleRef, size: *mut usize) -> *const c_char;
}
43 changes: 30 additions & 13 deletions src/librustc_trans/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use super::rpath::RPathConfig;
use super::rpath;
use metadata::METADATA_FILENAME;
use rustc::session::config::{self, NoDebugInfo, OutputFilenames, OutputType, PrintRequest};
use rustc::session::config::RUST_CGU_EXT;
use rustc::session::filesearch;
use rustc::session::search_paths::PathKind;
use rustc::session::Session;
Expand Down Expand Up @@ -45,13 +46,9 @@ use syntax::attr;
/// The LLVM module name containing crate-metadata. This includes a `.` on
/// purpose, so it cannot clash with the name of a user-defined module.
pub const METADATA_MODULE_NAME: &'static str = "crate.metadata";
/// The name of the crate-metadata object file the compiler generates. Must
/// match up with `METADATA_MODULE_NAME`.
pub const METADATA_OBJ_NAME: &'static str = "crate.metadata.o";

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

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

fn metadata_obj(outputs: &OutputFilenames) -> PathBuf {
outputs.temp_path(OutputType::Object, Some(METADATA_MODULE_NAME))
}

fn allocator_obj(outputs: &OutputFilenames) -> PathBuf {
outputs.temp_path(OutputType::Object, Some(ALLOCATOR_MODULE_NAME))
}

pub fn remove(sess: &Session, path: &Path) {
match fs::remove_file(path) {
Ok(..) => {}
Expand Down Expand Up @@ -174,9 +179,9 @@ pub fn link_binary(sess: &Session,
remove(sess, &obj.object);
}
}
remove(sess, &outputs.with_extension(METADATA_OBJ_NAME));
remove(sess, &metadata_obj(outputs));
if trans.allocator_module.is_some() {
remove(sess, &outputs.with_extension(ALLOCATOR_OBJ_NAME));
remove(sess, &allocator_obj(outputs));
}
}

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

RlibFlavor::StaticlibBase => {
if trans.allocator_module.is_some() {
ab.add_file(&outputs.with_extension(ALLOCATOR_OBJ_NAME));
ab.add_file(&allocator_obj(outputs));
}
}
}
Expand Down Expand Up @@ -908,11 +913,11 @@ fn link_args(cmd: &mut Linker,
// object file, so we link that in here.
if crate_type == config::CrateTypeDylib ||
crate_type == config::CrateTypeProcMacro {
cmd.add_object(&outputs.with_extension(METADATA_OBJ_NAME));
cmd.add_object(&metadata_obj(outputs));
}

if trans.allocator_module.is_some() {
cmd.add_object(&outputs.with_extension(ALLOCATOR_OBJ_NAME));
cmd.add_object(&allocator_obj(outputs));
}

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

// Look for `.rust-cgu.o` at the end of the filename to conclude
// that this is a Rust-related object file.
fn looks_like_rust(s: &str) -> bool {
let path = Path::new(s);
let ext = path.extension().and_then(|s| s.to_str());
if ext != Some(OutputType::Object.extension()) {
return false
}
let ext2 = path.file_stem()
.and_then(|s| Path::new(s).extension())
.and_then(|s| s.to_str());
ext2 == Some(RUST_CGU_EXT)
}

let is_rust_object =
canonical.starts_with(&canonical_name) && {
let num = &f[name.len()..f.len() - 2];
num.len() > 0 && num[1..].parse::<u32>().is_ok()
};
canonical.starts_with(&canonical_name) &&
looks_like_rust(&f);

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

0 comments on commit ac76206

Please sign in to comment.