Skip to content

Commit 8bde2ac

Browse files
committed
rustc: Add -C lto=val option
This commit primarily adds the ability to control what kind of LTO happens when rustc performs LTO, namely allowing values to be specified to the `-C lto` option, such as `-C lto=thin` and `-C lto=fat`. (where "fat" is the previous kind of LTO, throw everything in one giant module) Along the way this also refactors a number of fields which store information about whether LTO/ThinLTO are enabled to unify them all into one field through which everything is dispatched, hopefully removing a number of special cases throughout. This is intended to help mitigate rust-lang#47409 but will require a backport as well, and this would unfortunately need to be an otherwise insta-stable option.
1 parent 4e3901d commit 8bde2ac

File tree

9 files changed

+221
-115
lines changed

9 files changed

+221
-115
lines changed

src/librustc/session/config.rs

+45-11
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,26 @@ pub enum OptLevel {
7272
SizeMin, // -Oz
7373
}
7474

75+
#[derive(Clone, Copy, PartialEq, Hash)]
76+
pub enum Lto {
77+
/// Don't do any LTO whatsoever
78+
No,
79+
80+
/// Do a full crate graph LTO. The flavor is determined by the compiler
81+
/// (currently the default is "fat").
82+
Yes,
83+
84+
/// Do a full crate graph LTO with ThinLTO
85+
Thin,
86+
87+
/// Do a local graph LTO with ThinLTO (only relevant for multiple codegen
88+
/// units).
89+
ThinLocal,
90+
91+
/// Do a full crate graph LTO with "fat" LTO
92+
Fat,
93+
}
94+
7595
#[derive(Clone, Copy, PartialEq, Hash)]
7696
pub enum DebugInfoLevel {
7797
NoDebugInfo,
@@ -389,7 +409,7 @@ top_level_options!(
389409
// commands like `--emit llvm-ir` which they're often incompatible with
390410
// if we otherwise use the defaults of rustc.
391411
cli_forced_codegen_units: Option<usize> [UNTRACKED],
392-
cli_forced_thinlto: Option<bool> [UNTRACKED],
412+
cli_forced_thinlto_off: bool [UNTRACKED],
393413
}
394414
);
395415

@@ -590,7 +610,7 @@ pub fn basic_options() -> Options {
590610
debug_assertions: true,
591611
actually_rustdoc: false,
592612
cli_forced_codegen_units: None,
593-
cli_forced_thinlto: None,
613+
cli_forced_thinlto_off: false,
594614
}
595615
}
596616

@@ -780,11 +800,13 @@ macro_rules! options {
780800
Some("crate=integer");
781801
pub const parse_unpretty: Option<&'static str> =
782802
Some("`string` or `string=string`");
803+
pub const parse_lto: Option<&'static str> =
804+
Some("one of `thin`, `fat`, or omitted");
783805
}
784806

785807
#[allow(dead_code)]
786808
mod $mod_set {
787-
use super::{$struct_name, Passes, SomePasses, AllPasses, Sanitizer};
809+
use super::{$struct_name, Passes, SomePasses, AllPasses, Sanitizer, Lto};
788810
use rustc_back::{LinkerFlavor, PanicStrategy, RelroLevel};
789811
use std::path::PathBuf;
790812

@@ -978,6 +1000,16 @@ macro_rules! options {
9781000
_ => false,
9791001
}
9801002
}
1003+
1004+
fn parse_lto(slot: &mut Lto, v: Option<&str>) -> bool {
1005+
*slot = match v {
1006+
None => Lto::Yes,
1007+
Some("thin") => Lto::Thin,
1008+
Some("fat") => Lto::Fat,
1009+
Some(_) => return false,
1010+
};
1011+
true
1012+
}
9811013
}
9821014
) }
9831015

@@ -994,7 +1026,7 @@ options! {CodegenOptions, CodegenSetter, basic_codegen_options,
9941026
"extra arguments to append to the linker invocation (space separated)"),
9951027
link_dead_code: bool = (false, parse_bool, [UNTRACKED],
9961028
"don't let linker strip dead code (turning it on can be used for code coverage)"),
997-
lto: bool = (false, parse_bool, [TRACKED],
1029+
lto: Lto = (Lto::No, parse_lto, [TRACKED],
9981030
"perform LLVM link-time optimizations"),
9991031
target_cpu: Option<String> = (None, parse_opt_string, [TRACKED],
10001032
"select target processor (rustc --print target-cpus for details)"),
@@ -1677,7 +1709,7 @@ pub fn build_session_options_and_crate_config(matches: &getopts::Matches)
16771709

16781710
let mut cg = build_codegen_options(matches, error_format);
16791711
let mut codegen_units = cg.codegen_units;
1680-
let mut thinlto = None;
1712+
let mut disable_thinlto = false;
16811713

16821714
// Issue #30063: if user requests llvm-related output to one
16831715
// particular path, disable codegen-units.
@@ -1699,12 +1731,12 @@ pub fn build_session_options_and_crate_config(matches: &getopts::Matches)
16991731
}
17001732
early_warn(error_format, "resetting to default -C codegen-units=1");
17011733
codegen_units = Some(1);
1702-
thinlto = Some(false);
1734+
disable_thinlto = true;
17031735
}
17041736
}
17051737
_ => {
17061738
codegen_units = Some(1);
1707-
thinlto = Some(false);
1739+
disable_thinlto = true;
17081740
}
17091741
}
17101742
}
@@ -1734,7 +1766,7 @@ pub fn build_session_options_and_crate_config(matches: &getopts::Matches)
17341766
(&None, &None) => None,
17351767
}.map(|m| PathBuf::from(m));
17361768

1737-
if cg.lto && incremental.is_some() {
1769+
if cg.lto != Lto::No && incremental.is_some() {
17381770
early_error(error_format, "can't perform LTO when compiling incrementally");
17391771
}
17401772

@@ -1934,7 +1966,7 @@ pub fn build_session_options_and_crate_config(matches: &getopts::Matches)
19341966
debug_assertions,
19351967
actually_rustdoc: false,
19361968
cli_forced_codegen_units: codegen_units,
1937-
cli_forced_thinlto: thinlto,
1969+
cli_forced_thinlto_off: disable_thinlto,
19381970
},
19391971
cfg)
19401972
}
@@ -2052,7 +2084,7 @@ mod dep_tracking {
20522084
use std::hash::Hash;
20532085
use std::path::PathBuf;
20542086
use std::collections::hash_map::DefaultHasher;
2055-
use super::{Passes, CrateType, OptLevel, DebugInfoLevel,
2087+
use super::{Passes, CrateType, OptLevel, DebugInfoLevel, Lto,
20562088
OutputTypes, Externs, ErrorOutputType, Sanitizer};
20572089
use syntax::feature_gate::UnstableFeatures;
20582090
use rustc_back::{PanicStrategy, RelroLevel};
@@ -2107,6 +2139,7 @@ mod dep_tracking {
21072139
impl_dep_tracking_hash_via_hash!(RelroLevel);
21082140
impl_dep_tracking_hash_via_hash!(Passes);
21092141
impl_dep_tracking_hash_via_hash!(OptLevel);
2142+
impl_dep_tracking_hash_via_hash!(Lto);
21102143
impl_dep_tracking_hash_via_hash!(DebugInfoLevel);
21112144
impl_dep_tracking_hash_via_hash!(UnstableFeatures);
21122145
impl_dep_tracking_hash_via_hash!(Externs);
@@ -2180,6 +2213,7 @@ mod tests {
21802213
use lint;
21812214
use middle::cstore;
21822215
use session::config::{build_configuration, build_session_options_and_crate_config};
2216+
use session::config::Lto;
21832217
use session::build_session;
21842218
use std::collections::{BTreeMap, BTreeSet};
21852219
use std::iter::FromIterator;
@@ -2656,7 +2690,7 @@ mod tests {
26562690

26572691
// Make sure changing a [TRACKED] option changes the hash
26582692
opts = reference.clone();
2659-
opts.cg.lto = true;
2693+
opts.cg.lto = Lto::Fat;
26602694
assert!(reference.dep_tracking_hash() != opts.dep_tracking_hash());
26612695

26622696
opts = reference.clone();

src/librustc/session/mod.rs

+58-34
Original file line numberDiff line numberDiff line change
@@ -498,9 +498,65 @@ impl Session {
498498
self.use_mir()
499499
}
500500

501-
pub fn lto(&self) -> bool {
502-
self.opts.cg.lto || self.target.target.options.requires_lto
501+
/// Calculates the flavor of LTO to use for this compilation.
502+
pub fn lto(&self) -> config::Lto {
503+
// If our target has codegen requirements ignore the command line
504+
if self.target.target.options.requires_lto {
505+
return config::Lto::Fat
506+
}
507+
508+
// If the user specified something, return that. If they only said `-C
509+
// lto` and we've for whatever reason forced off ThinLTO via the CLI,
510+
// then ensure we can't use a ThinLTO.
511+
match self.opts.cg.lto {
512+
config::Lto::No => {}
513+
config::Lto::Yes if self.opts.cli_forced_thinlto_off => {
514+
return config::Lto::Fat
515+
}
516+
other => return other,
517+
}
518+
519+
// Ok at this point the target doesn't require anything and the user
520+
// hasn't asked for anything. Our next decision is whether or not
521+
// we enable "auto" ThinLTO where we use multiple codegen units and
522+
// then do ThinLTO over those codegen units. The logic below will
523+
// either return `No` or `ThinLocal`.
524+
525+
// If processing command line options determined that we're incompatible
526+
// with ThinLTO (e.g. `-C lto --emit llvm-ir`) then return that option.
527+
if self.opts.cli_forced_thinlto_off {
528+
return config::Lto::No
529+
}
530+
531+
// If `-Z thinlto` specified process that, but note that this is mostly
532+
// a deprecated option now that `-C lto=thin` exists.
533+
if let Some(enabled) = self.opts.debugging_opts.thinlto {
534+
if enabled {
535+
return config::Lto::ThinLocal
536+
} else {
537+
return config::Lto::No
538+
}
539+
}
540+
541+
// If there's only one codegen unit and LTO isn't enabled then there's
542+
// no need for ThinLTO so just return false.
543+
if self.codegen_units() == 1 {
544+
return config::Lto::No
545+
}
546+
547+
// Right now ThinLTO isn't compatible with incremental compilation.
548+
if self.opts.incremental.is_some() {
549+
return config::Lto::No
550+
}
551+
552+
// Now we're in "defaults" territory. By default we enable ThinLTO for
553+
// optimized compiles (anything greater than O0).
554+
match self.opts.optimize {
555+
config::OptLevel::No => config::Lto::No,
556+
_ => config::Lto::ThinLocal,
557+
}
503558
}
559+
504560
/// Returns the panic strategy for this compile session. If the user explicitly selected one
505561
/// using '-C panic', use that, otherwise use the panic strategy defined by the target.
506562
pub fn panic_strategy(&self) -> PanicStrategy {
@@ -804,38 +860,6 @@ impl Session {
804860
// scientific.
805861
16
806862
}
807-
808-
/// Returns whether ThinLTO is enabled for this compilation
809-
pub fn thinlto(&self) -> bool {
810-
// If processing command line options determined that we're incompatible
811-
// with ThinLTO (e.g. `-C lto --emit llvm-ir`) then return that option.
812-
if let Some(enabled) = self.opts.cli_forced_thinlto {
813-
return enabled
814-
}
815-
816-
// If explicitly specified, use that with the next highest priority
817-
if let Some(enabled) = self.opts.debugging_opts.thinlto {
818-
return enabled
819-
}
820-
821-
// If there's only one codegen unit and LTO isn't enabled then there's
822-
// no need for ThinLTO so just return false.
823-
if self.codegen_units() == 1 && !self.lto() {
824-
return false
825-
}
826-
827-
// Right now ThinLTO isn't compatible with incremental compilation.
828-
if self.opts.incremental.is_some() {
829-
return false
830-
}
831-
832-
// Now we're in "defaults" territory. By default we enable ThinLTO for
833-
// optimized compiles (anything greater than O0).
834-
match self.opts.optimize {
835-
config::OptLevel::No => false,
836-
_ => true,
837-
}
838-
}
839863
}
840864

841865
pub fn build_session(sopts: config::Options,

src/librustc_trans/back/link.rs

+17-5
Original file line numberDiff line numberDiff line change
@@ -16,7 +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;
19+
use rustc::session::config::{RUST_CGU_EXT, Lto};
2020
use rustc::session::filesearch;
2121
use rustc::session::search_paths::PathKind;
2222
use rustc::session::Session;
@@ -503,7 +503,8 @@ fn link_staticlib(sess: &Session,
503503
});
504504
ab.add_rlib(path,
505505
&name.as_str(),
506-
sess.lto() && !ignored_for_lto(sess, &trans.crate_info, cnum),
506+
is_full_lto_enabled(sess) &&
507+
!ignored_for_lto(sess, &trans.crate_info, cnum),
507508
skip_object_files).unwrap();
508509

509510
all_native_libs.extend(trans.crate_info.native_libraries[&cnum].iter().cloned());
@@ -1211,7 +1212,8 @@ fn add_upstream_rust_crates(cmd: &mut Linker,
12111212
lib.kind == NativeLibraryKind::NativeStatic && !relevant_lib(sess, lib)
12121213
});
12131214

1214-
if (!sess.lto() || ignored_for_lto(sess, &trans.crate_info, cnum)) &&
1215+
if (!is_full_lto_enabled(sess) ||
1216+
ignored_for_lto(sess, &trans.crate_info, cnum)) &&
12151217
crate_type != config::CrateTypeDylib &&
12161218
!skip_native {
12171219
cmd.link_rlib(&fix_windows_verbatim_for_gcc(cratepath));
@@ -1264,7 +1266,7 @@ fn add_upstream_rust_crates(cmd: &mut Linker,
12641266
// file, then we don't need the object file as it's part of the
12651267
// LTO module. Note that `#![no_builtins]` is excluded from LTO,
12661268
// though, so we let that object file slide.
1267-
let skip_because_lto = sess.lto() &&
1269+
let skip_because_lto = is_full_lto_enabled(sess) &&
12681270
is_rust_object &&
12691271
(sess.target.target.options.no_builtins ||
12701272
!trans.crate_info.is_no_builtins.contains(&cnum));
@@ -1301,7 +1303,7 @@ fn add_upstream_rust_crates(cmd: &mut Linker,
13011303
fn add_dynamic_crate(cmd: &mut Linker, sess: &Session, cratepath: &Path) {
13021304
// If we're performing LTO, then it should have been previously required
13031305
// that all upstream rust dependencies were available in an rlib format.
1304-
assert!(!sess.lto());
1306+
assert!(!is_full_lto_enabled(sess));
13051307

13061308
// Just need to tell the linker about where the library lives and
13071309
// what its name is
@@ -1409,3 +1411,13 @@ fn link_binaryen(sess: &Session,
14091411
e));
14101412
}
14111413
}
1414+
1415+
fn is_full_lto_enabled(sess: &Session) -> bool {
1416+
match sess.lto() {
1417+
Lto::Yes |
1418+
Lto::Thin |
1419+
Lto::Fat => true,
1420+
Lto::No |
1421+
Lto::ThinLocal => false,
1422+
}
1423+
}

0 commit comments

Comments
 (0)