Merge branch 'configure-lto' of https://github.com/alexcrichton/rust into rollup

This commit is contained in:
Alex Crichton 2018-01-25 10:51:20 -08:00
commit e61c609320
9 changed files with 222 additions and 116 deletions

View File

@ -72,6 +72,26 @@ pub enum OptLevel {
SizeMin, // -Oz
}
#[derive(Clone, Copy, PartialEq, Hash)]
pub enum Lto {
/// Don't do any LTO whatsoever
No,
/// Do a full crate graph LTO. The flavor is determined by the compiler
/// (currently the default is "fat").
Yes,
/// Do a full crate graph LTO with ThinLTO
Thin,
/// Do a local graph LTO with ThinLTO (only relevant for multiple codegen
/// units).
ThinLocal,
/// Do a full crate graph LTO with "fat" LTO
Fat,
}
#[derive(Clone, Copy, PartialEq, Hash)]
pub enum DebugInfoLevel {
NoDebugInfo,
@ -389,7 +409,7 @@ top_level_options!(
// commands like `--emit llvm-ir` which they're often incompatible with
// if we otherwise use the defaults of rustc.
cli_forced_codegen_units: Option<usize> [UNTRACKED],
cli_forced_thinlto: Option<bool> [UNTRACKED],
cli_forced_thinlto_off: bool [UNTRACKED],
}
);
@ -590,7 +610,7 @@ pub fn basic_options() -> Options {
debug_assertions: true,
actually_rustdoc: false,
cli_forced_codegen_units: None,
cli_forced_thinlto: None,
cli_forced_thinlto_off: false,
}
}
@ -780,11 +800,13 @@ macro_rules! options {
Some("crate=integer");
pub const parse_unpretty: Option<&'static str> =
Some("`string` or `string=string`");
pub const parse_lto: Option<&'static str> =
Some("one of `thin`, `fat`, or omitted");
}
#[allow(dead_code)]
mod $mod_set {
use super::{$struct_name, Passes, SomePasses, AllPasses, Sanitizer};
use super::{$struct_name, Passes, SomePasses, AllPasses, Sanitizer, Lto};
use rustc_back::{LinkerFlavor, PanicStrategy, RelroLevel};
use std::path::PathBuf;
@ -978,6 +1000,16 @@ macro_rules! options {
_ => false,
}
}
fn parse_lto(slot: &mut Lto, v: Option<&str>) -> bool {
*slot = match v {
None => Lto::Yes,
Some("thin") => Lto::Thin,
Some("fat") => Lto::Fat,
Some(_) => return false,
};
true
}
}
) }
@ -994,7 +1026,7 @@ options! {CodegenOptions, CodegenSetter, basic_codegen_options,
"extra arguments to append to the linker invocation (space separated)"),
link_dead_code: bool = (false, parse_bool, [UNTRACKED],
"don't let linker strip dead code (turning it on can be used for code coverage)"),
lto: bool = (false, parse_bool, [TRACKED],
lto: Lto = (Lto::No, parse_lto, [TRACKED],
"perform LLVM link-time optimizations"),
target_cpu: Option<String> = (None, parse_opt_string, [TRACKED],
"select target processor (rustc --print target-cpus for details)"),
@ -1677,7 +1709,7 @@ pub fn build_session_options_and_crate_config(matches: &getopts::Matches)
let mut cg = build_codegen_options(matches, error_format);
let mut codegen_units = cg.codegen_units;
let mut thinlto = None;
let mut disable_thinlto = false;
// Issue #30063: if user requests llvm-related output to one
// particular path, disable codegen-units.
@ -1699,12 +1731,12 @@ pub fn build_session_options_and_crate_config(matches: &getopts::Matches)
}
early_warn(error_format, "resetting to default -C codegen-units=1");
codegen_units = Some(1);
thinlto = Some(false);
disable_thinlto = true;
}
}
_ => {
codegen_units = Some(1);
thinlto = Some(false);
disable_thinlto = true;
}
}
}
@ -1734,7 +1766,7 @@ pub fn build_session_options_and_crate_config(matches: &getopts::Matches)
(&None, &None) => None,
}.map(|m| PathBuf::from(m));
if cg.lto && incremental.is_some() {
if cg.lto != Lto::No && incremental.is_some() {
early_error(error_format, "can't perform LTO when compiling incrementally");
}
@ -1934,7 +1966,7 @@ pub fn build_session_options_and_crate_config(matches: &getopts::Matches)
debug_assertions,
actually_rustdoc: false,
cli_forced_codegen_units: codegen_units,
cli_forced_thinlto: thinlto,
cli_forced_thinlto_off: disable_thinlto,
},
cfg)
}
@ -2052,7 +2084,7 @@ mod dep_tracking {
use std::hash::Hash;
use std::path::PathBuf;
use std::collections::hash_map::DefaultHasher;
use super::{Passes, CrateType, OptLevel, DebugInfoLevel,
use super::{Passes, CrateType, OptLevel, DebugInfoLevel, Lto,
OutputTypes, Externs, ErrorOutputType, Sanitizer};
use syntax::feature_gate::UnstableFeatures;
use rustc_back::{PanicStrategy, RelroLevel};
@ -2107,6 +2139,7 @@ mod dep_tracking {
impl_dep_tracking_hash_via_hash!(RelroLevel);
impl_dep_tracking_hash_via_hash!(Passes);
impl_dep_tracking_hash_via_hash!(OptLevel);
impl_dep_tracking_hash_via_hash!(Lto);
impl_dep_tracking_hash_via_hash!(DebugInfoLevel);
impl_dep_tracking_hash_via_hash!(UnstableFeatures);
impl_dep_tracking_hash_via_hash!(Externs);
@ -2180,6 +2213,7 @@ mod tests {
use lint;
use middle::cstore;
use session::config::{build_configuration, build_session_options_and_crate_config};
use session::config::Lto;
use session::build_session;
use std::collections::{BTreeMap, BTreeSet};
use std::iter::FromIterator;
@ -2656,7 +2690,7 @@ mod tests {
// Make sure changing a [TRACKED] option changes the hash
opts = reference.clone();
opts.cg.lto = true;
opts.cg.lto = Lto::Fat;
assert!(reference.dep_tracking_hash() != opts.dep_tracking_hash());
opts = reference.clone();

View File

@ -498,9 +498,65 @@ impl Session {
self.use_mir()
}
pub fn lto(&self) -> bool {
self.opts.cg.lto || self.target.target.options.requires_lto
/// Calculates the flavor of LTO to use for this compilation.
pub fn lto(&self) -> config::Lto {
// If our target has codegen requirements ignore the command line
if self.target.target.options.requires_lto {
return config::Lto::Fat
}
// If the user specified something, return that. If they only said `-C
// lto` and we've for whatever reason forced off ThinLTO via the CLI,
// then ensure we can't use a ThinLTO.
match self.opts.cg.lto {
config::Lto::No => {}
config::Lto::Yes if self.opts.cli_forced_thinlto_off => {
return config::Lto::Fat
}
other => return other,
}
// Ok at this point the target doesn't require anything and the user
// hasn't asked for anything. Our next decision is whether or not
// we enable "auto" ThinLTO where we use multiple codegen units and
// then do ThinLTO over those codegen units. The logic below will
// either return `No` or `ThinLocal`.
// If processing command line options determined that we're incompatible
// with ThinLTO (e.g. `-C lto --emit llvm-ir`) then return that option.
if self.opts.cli_forced_thinlto_off {
return config::Lto::No
}
// If `-Z thinlto` specified process that, but note that this is mostly
// a deprecated option now that `-C lto=thin` exists.
if let Some(enabled) = self.opts.debugging_opts.thinlto {
if enabled {
return config::Lto::ThinLocal
} else {
return config::Lto::No
}
}
// If there's only one codegen unit and LTO isn't enabled then there's
// no need for ThinLTO so just return false.
if self.codegen_units() == 1 {
return config::Lto::No
}
// Right now ThinLTO isn't compatible with incremental compilation.
if self.opts.incremental.is_some() {
return config::Lto::No
}
// Now we're in "defaults" territory. By default we enable ThinLTO for
// optimized compiles (anything greater than O0).
match self.opts.optimize {
config::OptLevel::No => config::Lto::No,
_ => config::Lto::ThinLocal,
}
}
/// Returns the panic strategy for this compile session. If the user explicitly selected one
/// using '-C panic', use that, otherwise use the panic strategy defined by the target.
pub fn panic_strategy(&self) -> PanicStrategy {
@ -804,38 +860,6 @@ impl Session {
// scientific.
16
}
/// Returns whether ThinLTO is enabled for this compilation
pub fn thinlto(&self) -> bool {
// If processing command line options determined that we're incompatible
// with ThinLTO (e.g. `-C lto --emit llvm-ir`) then return that option.
if let Some(enabled) = self.opts.cli_forced_thinlto {
return enabled
}
// If explicitly specified, use that with the next highest priority
if let Some(enabled) = self.opts.debugging_opts.thinlto {
return enabled
}
// If there's only one codegen unit and LTO isn't enabled then there's
// no need for ThinLTO so just return false.
if self.codegen_units() == 1 && !self.lto() {
return false
}
// Right now ThinLTO isn't compatible with incremental compilation.
if self.opts.incremental.is_some() {
return false
}
// Now we're in "defaults" territory. By default we enable ThinLTO for
// optimized compiles (anything greater than O0).
match self.opts.optimize {
config::OptLevel::No => false,
_ => true,
}
}
}
pub fn build_session(sopts: config::Options,

View File

@ -16,7 +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::config::{RUST_CGU_EXT, Lto};
use rustc::session::filesearch;
use rustc::session::search_paths::PathKind;
use rustc::session::Session;
@ -503,7 +503,8 @@ fn link_staticlib(sess: &Session,
});
ab.add_rlib(path,
&name.as_str(),
sess.lto() && !ignored_for_lto(sess, &trans.crate_info, cnum),
is_full_lto_enabled(sess) &&
!ignored_for_lto(sess, &trans.crate_info, cnum),
skip_object_files).unwrap();
all_native_libs.extend(trans.crate_info.native_libraries[&cnum].iter().cloned());
@ -1211,7 +1212,8 @@ fn add_upstream_rust_crates(cmd: &mut Linker,
lib.kind == NativeLibraryKind::NativeStatic && !relevant_lib(sess, lib)
});
if (!sess.lto() || ignored_for_lto(sess, &trans.crate_info, cnum)) &&
if (!is_full_lto_enabled(sess) ||
ignored_for_lto(sess, &trans.crate_info, cnum)) &&
crate_type != config::CrateTypeDylib &&
!skip_native {
cmd.link_rlib(&fix_windows_verbatim_for_gcc(cratepath));
@ -1264,7 +1266,7 @@ fn add_upstream_rust_crates(cmd: &mut Linker,
// file, then we don't need the object file as it's part of the
// LTO module. Note that `#![no_builtins]` is excluded from LTO,
// though, so we let that object file slide.
let skip_because_lto = sess.lto() &&
let skip_because_lto = is_full_lto_enabled(sess) &&
is_rust_object &&
(sess.target.target.options.no_builtins ||
!trans.crate_info.is_no_builtins.contains(&cnum));
@ -1301,7 +1303,7 @@ fn add_upstream_rust_crates(cmd: &mut Linker,
fn add_dynamic_crate(cmd: &mut Linker, sess: &Session, cratepath: &Path) {
// If we're performing LTO, then it should have been previously required
// that all upstream rust dependencies were available in an rlib format.
assert!(!sess.lto());
assert!(!is_full_lto_enabled(sess));
// Just need to tell the linker about where the library lives and
// what its name is
@ -1409,3 +1411,13 @@ fn link_binaryen(sess: &Session,
e));
}
}
fn is_full_lto_enabled(sess: &Session) -> bool {
match sess.lto() {
Lto::Yes |
Lto::Thin |
Lto::Fat => true,
Lto::No |
Lto::ThinLocal => false,
}
}

View File

@ -18,7 +18,7 @@ use llvm::{ModuleRef, TargetMachineRef, True, False};
use llvm;
use rustc::hir::def_id::LOCAL_CRATE;
use rustc::middle::exported_symbols::SymbolExportLevel;
use rustc::session::config;
use rustc::session::config::{self, Lto};
use rustc::util::common::time;
use time_graph::Timeline;
use {ModuleTranslation, ModuleLlvm, ModuleKind, ModuleSource};
@ -95,25 +95,22 @@ impl LtoModuleTranslation {
}
}
pub enum LTOMode {
WholeCrateGraph,
JustThisCrate,
}
pub(crate) fn run(cgcx: &CodegenContext,
modules: Vec<ModuleTranslation>,
mode: LTOMode,
timeline: &mut Timeline)
modules: Vec<ModuleTranslation>,
timeline: &mut Timeline)
-> Result<Vec<LtoModuleTranslation>, FatalError>
{
let diag_handler = cgcx.create_diag_handler();
let export_threshold = match mode {
LTOMode::WholeCrateGraph => {
let export_threshold = match cgcx.lto {
// We're just doing LTO for our one crate
Lto::ThinLocal => SymbolExportLevel::Rust,
// We're doing LTO for the entire crate graph
Lto::Yes | Lto::Fat | Lto::Thin => {
symbol_export::crates_export_threshold(&cgcx.crate_types)
}
LTOMode::JustThisCrate => {
SymbolExportLevel::Rust
}
Lto::No => panic!("didn't request LTO but we're doing LTO"),
};
let symbol_filter = &|&(ref name, _, level): &(String, _, SymbolExportLevel)| {
@ -140,7 +137,7 @@ pub(crate) fn run(cgcx: &CodegenContext,
// We save off all the bytecode and LLVM module ids for later processing
// with either fat or thin LTO
let mut upstream_modules = Vec::new();
if let LTOMode::WholeCrateGraph = mode {
if cgcx.lto != Lto::ThinLocal {
if cgcx.opts.cg.prefer_dynamic {
diag_handler.struct_err("cannot prefer dynamic linking when performing LTO")
.note("only 'staticlib', 'bin', and 'cdylib' outputs are \
@ -186,13 +183,16 @@ pub(crate) fn run(cgcx: &CodegenContext,
}
let arr = symbol_white_list.iter().map(|c| c.as_ptr()).collect::<Vec<_>>();
match mode {
LTOMode::WholeCrateGraph if !cgcx.thinlto => {
match cgcx.lto {
Lto::Yes | // `-C lto` == fat LTO by default
Lto::Fat => {
fat_lto(cgcx, &diag_handler, modules, upstream_modules, &arr, timeline)
}
_ => {
Lto::Thin |
Lto::ThinLocal => {
thin_lto(&diag_handler, modules, upstream_modules, &arr, timeline)
}
Lto::No => unreachable!(),
}
}

View File

@ -20,7 +20,7 @@ use rustc_incremental::{save_trans_partition, in_incr_comp_dir};
use rustc::dep_graph::{DepGraph, WorkProductFileKind};
use rustc::middle::cstore::{LinkMeta, EncodedMetadata};
use rustc::session::config::{self, OutputFilenames, OutputType, Passes, SomePasses,
AllPasses, Sanitizer};
AllPasses, Sanitizer, Lto};
use rustc::session::Session;
use rustc::util::nodemap::FxHashMap;
use rustc_back::LinkerFlavor;
@ -329,8 +329,7 @@ struct AssemblerCommand {
pub struct CodegenContext {
// Resouces needed when running LTO
pub time_passes: bool,
pub lto: bool,
pub thinlto: bool,
pub lto: Lto,
pub no_landing_pads: bool,
pub save_temps: bool,
pub fewer_names: bool,
@ -589,12 +588,7 @@ fn generate_lto_work(cgcx: &CodegenContext,
TRANS_WORK_PACKAGE_KIND,
"generate lto")
}).unwrap_or(Timeline::noop());
let mode = if cgcx.lto {
lto::LTOMode::WholeCrateGraph
} else {
lto::LTOMode::JustThisCrate
};
let lto_modules = lto::run(cgcx, modules, mode, &mut timeline)
let lto_modules = lto::run(cgcx, modules, &mut timeline)
.unwrap_or_else(|e| panic!(e));
lto_modules.into_iter().map(|module| {
@ -1296,28 +1290,51 @@ fn execute_work_item(cgcx: &CodegenContext,
unsafe {
optimize(cgcx, &diag_handler, &mtrans, config, timeline)?;
let lto = cgcx.lto;
let auto_thin_lto =
cgcx.thinlto &&
cgcx.total_cgus > 1 &&
mtrans.kind != ModuleKind::Allocator;
// If we're a metadata module we never participate in LTO.
// After we've done the initial round of optimizations we need to
// decide whether to synchronously codegen this module or ship it
// back to the coordinator thread for further LTO processing (which
// has to wait for all the initial modules to be optimized).
//
// If LTO was explicitly requested on the command line, we always
// LTO everything else.
//
// If LTO *wasn't* explicitly requested and we're not a metdata
// module, then we may automatically do ThinLTO if we've got
// multiple codegen units. Note, however, that the allocator module
// doesn't participate here automatically because of linker
// shenanigans later on.
if mtrans.kind == ModuleKind::Metadata || (!lto && !auto_thin_lto) {
// Here we dispatch based on the `cgcx.lto` and kind of module we're
// translating...
let needs_lto = match cgcx.lto {
Lto::No => false,
// Here we've got a full crate graph LTO requested. We ignore
// this, however, if the crate type is only an rlib as there's
// no full crate graph to process, that'll happen later.
//
// This use case currently comes up primarily for targets that
// require LTO so the request for LTO is always unconditionally
// passed down to the backend, but we don't actually want to do
// anything about it yet until we've got a final product.
Lto::Yes | Lto::Fat | Lto::Thin => {
cgcx.crate_types.len() != 1 ||
cgcx.crate_types[0] != config::CrateTypeRlib
}
// When we're automatically doing ThinLTO for multi-codegen-unit
// builds we don't actually want to LTO the allocator modules if
// it shows up. This is due to various linker shenanigans that
// we'll encounter later.
//
// Additionally here's where we also factor in the current LLVM
// version. If it doesn't support ThinLTO we skip this.
Lto::ThinLocal => {
mtrans.kind != ModuleKind::Allocator &&
llvm::LLVMRustThinLTOAvailable()
}
};
// Metadata modules never participate in LTO regardless of the lto
// settings.
let needs_lto = needs_lto && mtrans.kind != ModuleKind::Metadata;
if needs_lto {
Ok(WorkItemResult::NeedsLTO(mtrans))
} else {
let module = codegen(cgcx, &diag_handler, mtrans, config, timeline)?;
Ok(WorkItemResult::Compiled(module))
} else {
Ok(WorkItemResult::NeedsLTO(mtrans))
}
}
}
@ -1393,10 +1410,6 @@ fn start_executing_work(tcx: TyCtxt,
each_linked_rlib_for_lto.push((cnum, path.to_path_buf()));
}));
let crate_types = sess.crate_types.borrow();
let only_rlib = crate_types.len() == 1 &&
crate_types[0] == config::CrateTypeRlib;
let wasm_import_memory =
attr::contains_name(&tcx.hir.krate().attrs, "wasm_import_memory");
@ -1415,18 +1428,7 @@ fn start_executing_work(tcx: TyCtxt,
let cgcx = CodegenContext {
crate_types: sess.crate_types.borrow().clone(),
each_linked_rlib_for_lto,
// If we're only building an rlibc then allow the LTO flag to be passed
// but don't actually do anything, the full LTO will happen later
lto: sess.lto() && !only_rlib,
// Enable ThinLTO if requested, but only if the target we're compiling
// for doesn't require full LTO. Some targets require one LLVM module
// (they effectively don't have a linker) so it's up to us to use LTO to
// link everything together.
thinlto: sess.thinlto() &&
!sess.target.target.options.requires_lto &&
unsafe { llvm::LLVMRustThinLTOAvailable() },
lto: sess.lto(),
no_landing_pads: sess.no_landing_pads(),
fewer_names: sess.fewer_names(),
save_temps: sess.opts.cg.save_temps,

View File

@ -16,11 +16,11 @@ all:
$(RUSTC) -C extra-filename=foo dummy.rs 2>&1
#Option taking no argument
$(RUSTC) -C lto= dummy.rs 2>&1 | \
$(CGREP) 'codegen option `lto` takes no value'
$(CGREP) 'codegen option `lto` - one of `thin`, `fat`, or'
$(RUSTC) -C lto=1 dummy.rs 2>&1 | \
$(CGREP) 'codegen option `lto` takes no value'
$(CGREP) 'codegen option `lto` - one of `thin`, `fat`, or'
$(RUSTC) -C lto=foo dummy.rs 2>&1 | \
$(CGREP) 'codegen option `lto` takes no value'
$(CGREP) 'codegen option `lto` - one of `thin`, `fat`, or'
$(RUSTC) -C lto dummy.rs
# Should not link dead code...

View File

@ -0,0 +1,17 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// compile-flags: -Clto=fat
// no-prefer-dynamic
fn main() {
println!("hello!");
}

View File

@ -0,0 +1,17 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// compile-flags: -Clto=thin
// no-prefer-dynamic
// min-llvm-version 4.0
fn main() {
println!("hello!");
}

View File

@ -8,7 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// compile-flags: -Z thinlto -C codegen-units=8 -O -C lto
// compile-flags: -C codegen-units=8 -O -C lto=thin
// aux-build:thin-lto-inlines-aux.rs
// min-llvm-version 4.0
// no-prefer-dynamic