Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stabilize linker-plugin based LTO (aka cross-language LTO) #58057

Merged
merged 3 commits into from Feb 13, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/doc/rustc/src/SUMMARY.md
Expand Up @@ -13,4 +13,5 @@
- [Targets](targets/index.md)
- [Built-in Targets](targets/built-in.md)
- [Custom Targets](targets/custom.md)
- [Contributing to `rustc`](contributing.md)
- [Linker-plugin based LTO](linker-plugin-lto.md)
- [Contributing to `rustc`](contributing.md)
108 changes: 108 additions & 0 deletions src/doc/rustc/src/linker-plugin-lto.md
@@ -0,0 +1,108 @@
# Linker-plugin-LTO

The `-C linker-plugin-lto` flag allows for deferring the LTO optimization
to the actual linking step, which in turn allows for performing
interprocedural optimizations across programming language boundaries if
all the object files being linked were created by LLVM based toolchains.
The prime example here would be linking Rust code together with
Clang-compiled C/C++ code.

## Usage

There are two main cases how linker plugin based LTO can be used:

- compiling a Rust `staticlib` that is used as a C ABI dependency
- compiling a Rust binary where `rustc` invokes the linker

In both cases the Rust code has to be compiled with `-C linker-plugin-lto` and
the C/C++ code with `-flto` or `-flto=thin` so that object files are emitted
as LLVM bitcode.

### Rust `staticlib` as dependency in C/C++ program

In this case the Rust compiler just has to make sure that the object files in
the `staticlib` are in the right format. For linking, a linker with the
LLVM plugin must be used (e.g. LLD).

Using `rustc` directly:

```bash
# Compile the Rust staticlib
rustc --crate-type=staticlib -Clinker-plugin-lto -Copt-level=2 ./lib.rs
# Compile the C code with `-flto=thin`
clang -c -O2 -flto=thin -o main.o ./main.c
# Link everything, making sure that we use an appropriate linker
clang -flto=thin -fuse-ld=lld -L . -l"name-of-your-rust-lib" -o main -O2 ./cmain.o
```

Using `cargo`:

```bash
# Compile the Rust staticlib
RUSTFLAGS="-Clinker-plugin-lto" cargo build --release
# Compile the C code with `-flto=thin`
clang -c -O2 -flto=thin -o main.o ./main.c
# Link everything, making sure that we use an appropriate linker
clang -flto=thin -fuse-ld=lld -L . -l"name-of-your-rust-lib" -o main -O2 ./cmain.o
```

### C/C++ code as a dependency in Rust

In this case the linker will be invoked by `rustc`. We again have to make sure
that an appropriate linker is used.

Using `rustc` directly:

```bash
# Compile C code with `-flto`
clang ./clib.c -flto=thin -c -o ./clib.o -O2
# Create a static library from the C code
ar crus ./libxyz.a ./clib.o

# Invoke `rustc` with the additional arguments
rustc -Clinker-plugin-lto -L. -Copt-level=2 -Clinker=clang -Clink-arg=-fuse-ld=lld ./main.rs
```

Using `cargo` directly:

```bash
# Compile C code with `-flto`
clang ./clib.c -flto=thin -c -o ./clib.o -O2
# Create a static library from the C code
ar crus ./libxyz.a ./clib.o

# Set the linking arguments via RUSTFLAGS
RUSTFLAGS="-Clinker-plugin-lto -Clinker=clang -Clink-arg=-fuse-ld=lld" cargo build --release
```

### Explicitly specifying the linker plugin to be used by `rustc`

If one wants to use a linker other than LLD, the LLVM linker plugin has to be
specified explicitly. Otherwise the linker cannot read the object files. The
path to the plugin is passed as an argument to the `-Clinker-plugin-lto`
option:

```bash
rustc -Clinker-plugin-lto="/path/to/LLVMgold.so" -L. -Copt-level=2 ./main.rs
```


## Toolchain Compatibility

In order for this kind of LTO to work, the LLVM linker plugin must be able to
handle the LLVM bitcode produced by both `rustc` and `clang`.

Best results are achieved by using a `rustc` and `clang` that are based on the
exact same version of LLVM. One can use `rustc -vV` in order to view the LLVM
used by a given `rustc` version. Note that the version number given
here is only an approximation as Rust sometimes uses unstable revisions of
LLVM. However, the approximation is usually reliable.

The following table shows known good combinations of toolchain versions.

| | Clang 7 | Clang 8 |
|-----------|-----------|-----------|
| Rust 1.34 | ✗ | ✓ |
| Rust 1.35 | ✗ | ✓(?) |

Note that the compatibility policy for this feature might change in the future.
44 changes: 23 additions & 21 deletions src/librustc/session/config.rs
Expand Up @@ -96,18 +96,18 @@ pub enum LtoCli {
}

#[derive(Clone, PartialEq, Hash)]
pub enum CrossLangLto {
pub enum LinkerPluginLto {
LinkerPlugin(PathBuf),
LinkerPluginAuto,
Disabled
}

impl CrossLangLto {
impl LinkerPluginLto {
pub fn enabled(&self) -> bool {
match *self {
CrossLangLto::LinkerPlugin(_) |
CrossLangLto::LinkerPluginAuto => true,
CrossLangLto::Disabled => false,
LinkerPluginLto::LinkerPlugin(_) |
LinkerPluginLto::LinkerPluginAuto => true,
LinkerPluginLto::Disabled => false,
}
}
}
Expand Down Expand Up @@ -812,7 +812,7 @@ macro_rules! options {
pub const parse_lto: Option<&str> =
Some("either a boolean (`yes`, `no`, `on`, `off`, etc), `thin`, \
`fat`, or omitted");
pub const parse_cross_lang_lto: Option<&str> =
pub const parse_linker_plugin_lto: Option<&str> =
Some("either a boolean (`yes`, `no`, `on`, `off`, etc), \
or the path to the linker plugin");
pub const parse_merge_functions: Option<&str> =
Expand All @@ -821,7 +821,7 @@ macro_rules! options {

#[allow(dead_code)]
mod $mod_set {
use super::{$struct_name, Passes, Sanitizer, LtoCli, CrossLangLto};
use super::{$struct_name, Passes, Sanitizer, LtoCli, LinkerPluginLto};
use rustc_target::spec::{LinkerFlavor, MergeFunctions, PanicStrategy, RelroLevel};
use std::path::PathBuf;
use std::str::FromStr;
Expand Down Expand Up @@ -1037,22 +1037,22 @@ macro_rules! options {
true
}

fn parse_cross_lang_lto(slot: &mut CrossLangLto, v: Option<&str>) -> bool {
fn parse_linker_plugin_lto(slot: &mut LinkerPluginLto, v: Option<&str>) -> bool {
if v.is_some() {
let mut bool_arg = None;
if parse_opt_bool(&mut bool_arg, v) {
*slot = if bool_arg.unwrap() {
CrossLangLto::LinkerPluginAuto
LinkerPluginLto::LinkerPluginAuto
} else {
CrossLangLto::Disabled
LinkerPluginLto::Disabled
};
return true
}
}

*slot = match v {
None => CrossLangLto::LinkerPluginAuto,
Some(path) => CrossLangLto::LinkerPlugin(PathBuf::from(path)),
None => LinkerPluginLto::LinkerPluginAuto,
Some(path) => LinkerPluginLto::LinkerPlugin(PathBuf::from(path)),
};
true
}
Expand Down Expand Up @@ -1145,6 +1145,10 @@ options! {CodegenOptions, CodegenSetter, basic_codegen_options,
"allow the linker to link its default libraries"),
linker_flavor: Option<LinkerFlavor> = (None, parse_linker_flavor, [UNTRACKED],
"Linker flavor"),
linker_plugin_lto: LinkerPluginLto = (LinkerPluginLto::Disabled,
parse_linker_plugin_lto, [TRACKED],
"generate build artifacts that are compatible with linker-based LTO."),

}

options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
Expand Down Expand Up @@ -1383,8 +1387,6 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
"make the current crate share its generic instantiations"),
chalk: bool = (false, parse_bool, [TRACKED],
"enable the experimental Chalk-based trait solving engine"),
cross_lang_lto: CrossLangLto = (CrossLangLto::Disabled, parse_cross_lang_lto, [TRACKED],
"generate build artifacts that are compatible with linker-based LTO."),
no_parallel_llvm: bool = (false, parse_bool, [UNTRACKED],
"don't run LLVM in parallel (while keeping codegen-units and ThinLTO)"),
no_leak_check: bool = (false, parse_bool, [UNTRACKED],
Expand Down Expand Up @@ -2440,7 +2442,7 @@ mod dep_tracking {
use std::path::PathBuf;
use std::collections::hash_map::DefaultHasher;
use super::{CrateType, DebugInfo, ErrorOutputType, OptLevel, OutputTypes,
Passes, Sanitizer, LtoCli, CrossLangLto};
Passes, Sanitizer, LtoCli, LinkerPluginLto};
use syntax::feature_gate::UnstableFeatures;
use rustc_target::spec::{MergeFunctions, PanicStrategy, RelroLevel, TargetTriple};
use syntax::edition::Edition;
Expand Down Expand Up @@ -2507,7 +2509,7 @@ mod dep_tracking {
impl_dep_tracking_hash_via_hash!(Option<Sanitizer>);
impl_dep_tracking_hash_via_hash!(TargetTriple);
impl_dep_tracking_hash_via_hash!(Edition);
impl_dep_tracking_hash_via_hash!(CrossLangLto);
impl_dep_tracking_hash_via_hash!(LinkerPluginLto);

impl_dep_tracking_hash_for_sortable_vec_of!(String);
impl_dep_tracking_hash_for_sortable_vec_of!(PathBuf);
Expand Down Expand Up @@ -2572,7 +2574,7 @@ mod tests {
use crate::lint;
use crate::middle::cstore;
use crate::session::config::{build_configuration, build_session_options_and_crate_config};
use crate::session::config::{LtoCli, CrossLangLto};
use crate::session::config::{LtoCli, LinkerPluginLto};
use crate::session::build_session;
use crate::session::search_paths::SearchPath;
use std::collections::{BTreeMap, BTreeSet};
Expand Down Expand Up @@ -3105,6 +3107,10 @@ mod tests {
opts = reference.clone();
opts.cg.panic = Some(PanicStrategy::Abort);
assert!(reference.dep_tracking_hash() != opts.dep_tracking_hash());

opts = reference.clone();
opts.cg.linker_plugin_lto = LinkerPluginLto::LinkerPluginAuto;
assert!(reference.dep_tracking_hash() != opts.dep_tracking_hash());
}

#[test]
Expand Down Expand Up @@ -3231,10 +3237,6 @@ mod tests {
opts.debugging_opts.relro_level = Some(RelroLevel::Full);
assert!(reference.dep_tracking_hash() != opts.dep_tracking_hash());

opts = reference.clone();
opts.debugging_opts.cross_lang_lto = CrossLangLto::LinkerPluginAuto;
assert!(reference.dep_tracking_hash() != opts.dep_tracking_hash());

opts = reference.clone();
opts.debugging_opts.merge_functions = Some(MergeFunctions::Disabled);
assert!(reference.dep_tracking_hash() != opts.dep_tracking_hash());
Expand Down
2 changes: 1 addition & 1 deletion src/librustc/session/mod.rs
Expand Up @@ -1267,7 +1267,7 @@ fn validate_commandline_args_with_session_available(sess: &Session) {
// bitcode during ThinLTO. Therefore we disallow dynamic linking on MSVC
// when compiling for LLD ThinLTO. This way we can validly just not generate
// the `dllimport` attributes and `__imp_` symbols in that case.
if sess.opts.debugging_opts.cross_lang_lto.enabled() &&
if sess.opts.cg.linker_plugin_lto.enabled() &&
sess.opts.cg.prefer_dynamic &&
sess.target.target.options.is_like_msvc {
sess.err("Linker plugin based LTO is not supported together with \
Expand Down
4 changes: 2 additions & 2 deletions src/librustc_codegen_llvm/back/link.rs
Expand Up @@ -857,7 +857,7 @@ fn link_args(cmd: &mut dyn Linker,
codegen_results: &CodegenResults) {

// Linker plugins should be specified early in the list of arguments
cmd.cross_lang_lto();
cmd.linker_plugin_lto();

// The default library location, we need this to find the runtime.
// The location of crates will be determined as needed.
Expand Down Expand Up @@ -1491,7 +1491,7 @@ fn are_upstream_rust_objects_already_included(sess: &Session) -> bool {
Lto::Thin => {
// If we defer LTO to the linker, we haven't run LTO ourselves, so
// any upstream object files have not been copied yet.
!sess.opts.debugging_opts.cross_lang_lto.enabled()
!sess.opts.cg.linker_plugin_lto.enabled()
}
Lto::No |
Lto::ThinLocal => false,
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_codegen_llvm/back/lto.rs
Expand Up @@ -159,7 +159,7 @@ pub(crate) fn run_thin(cgcx: &CodegenContext<LlvmCodegenBackend>,
let symbol_white_list = symbol_white_list.iter()
.map(|c| c.as_ptr())
.collect::<Vec<_>>();
if cgcx.opts.debugging_opts.cross_lang_lto.enabled() {
if cgcx.opts.cg.linker_plugin_lto.enabled() {
unreachable!("We should never reach this case if the LTO step \
is deferred to the linker");
}
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_codegen_llvm/back/write.rs
Expand Up @@ -366,7 +366,7 @@ pub(crate) unsafe fn optimize(cgcx: &CodegenContext<LlvmCodegenBackend>,
let opt_level = config.opt_level.map(|x| to_llvm_opt_settings(x).0)
.unwrap_or(llvm::CodeGenOptLevel::None);
let prepare_for_thin_lto = cgcx.lto == Lto::Thin || cgcx.lto == Lto::ThinLocal ||
(cgcx.lto != Lto::Fat && cgcx.opts.debugging_opts.cross_lang_lto.enabled());
(cgcx.lto != Lto::Fat && cgcx.opts.cg.linker_plugin_lto.enabled());
with_llvm_pmb(llmod, &config, opt_level, prepare_for_thin_lto, &mut |b| {
llvm::LLVMPassManagerBuilderPopulateFunctionPassManager(b, fpm);
llvm::LLVMPassManagerBuilderPopulateModulePassManager(b, mpm);
Expand Down
6 changes: 3 additions & 3 deletions src/librustc_codegen_llvm/consts.rs
Expand Up @@ -275,12 +275,12 @@ impl CodegenCx<'ll, 'tcx> {
self.use_dll_storage_attrs && !self.tcx.is_foreign_item(def_id) &&
// ThinLTO can't handle this workaround in all cases, so we don't
// emit the attrs. Instead we make them unnecessary by disallowing
// dynamic linking when cross-language LTO is enabled.
!self.tcx.sess.opts.debugging_opts.cross_lang_lto.enabled();
// dynamic linking when linker plugin based LTO is enabled.
!self.tcx.sess.opts.cg.linker_plugin_lto.enabled();

// If this assertion triggers, there's something wrong with commandline
// argument validation.
debug_assert!(!(self.tcx.sess.opts.debugging_opts.cross_lang_lto.enabled() &&
debug_assert!(!(self.tcx.sess.opts.cg.linker_plugin_lto.enabled() &&
self.tcx.sess.target.target.options.is_like_msvc &&
self.tcx.sess.opts.cg.prefer_dynamic));

Expand Down
26 changes: 13 additions & 13 deletions src/librustc_codegen_ssa/back/linker.rs
Expand Up @@ -13,7 +13,7 @@ use rustc::hir::def_id::{LOCAL_CRATE, CrateNum};
use rustc::middle::dependency_format::Linkage;
use rustc::session::Session;
use rustc::session::config::{self, CrateType, OptLevel, DebugInfo,
CrossLangLto, Lto};
LinkerPluginLto, Lto};
use rustc::ty::TyCtxt;
use rustc_target::spec::{LinkerFlavor, LldFlavor};
use serialize::{json, Encoder};
Expand Down Expand Up @@ -127,7 +127,7 @@ pub trait Linker {
fn subsystem(&mut self, subsystem: &str);
fn group_start(&mut self);
fn group_end(&mut self);
fn cross_lang_lto(&mut self);
fn linker_plugin_lto(&mut self);
// Should have been finalize(self), but we don't support self-by-value on trait objects (yet?).
fn finalize(&mut self) -> Command;
}
Expand Down Expand Up @@ -183,7 +183,7 @@ impl<'a> GccLinker<'a> {
}
}

fn push_cross_lang_lto_args(&mut self, plugin_path: Option<&OsStr>) {
fn push_linker_plugin_lto_args(&mut self, plugin_path: Option<&OsStr>) {
if let Some(plugin_path) = plugin_path {
let mut arg = OsString::from("-plugin=");
arg.push(plugin_path);
Expand Down Expand Up @@ -454,16 +454,16 @@ impl<'a> Linker for GccLinker<'a> {
}
}

fn cross_lang_lto(&mut self) {
match self.sess.opts.debugging_opts.cross_lang_lto {
CrossLangLto::Disabled => {
fn linker_plugin_lto(&mut self) {
match self.sess.opts.cg.linker_plugin_lto {
LinkerPluginLto::Disabled => {
// Nothing to do
}
CrossLangLto::LinkerPluginAuto => {
self.push_cross_lang_lto_args(None);
LinkerPluginLto::LinkerPluginAuto => {
self.push_linker_plugin_lto_args(None);
}
CrossLangLto::LinkerPlugin(ref path) => {
self.push_cross_lang_lto_args(Some(path.as_os_str()));
LinkerPluginLto::LinkerPlugin(ref path) => {
self.push_linker_plugin_lto_args(Some(path.as_os_str()));
}
}
}
Expand Down Expand Up @@ -697,7 +697,7 @@ impl<'a> Linker for MsvcLinker<'a> {
fn group_start(&mut self) {}
fn group_end(&mut self) {}

fn cross_lang_lto(&mut self) {
fn linker_plugin_lto(&mut self) {
// Do nothing
}
}
Expand Down Expand Up @@ -865,7 +865,7 @@ impl<'a> Linker for EmLinker<'a> {
fn group_start(&mut self) {}
fn group_end(&mut self) {}

fn cross_lang_lto(&mut self) {
fn linker_plugin_lto(&mut self) {
// Do nothing
}
}
Expand Down Expand Up @@ -1047,7 +1047,7 @@ impl<'a> Linker for WasmLd<'a> {
fn group_start(&mut self) {}
fn group_end(&mut self) {}

fn cross_lang_lto(&mut self) {
fn linker_plugin_lto(&mut self) {
// Do nothing for now
}
}
Expand Down