Skip to content

Commit 76b6bda

Browse files
authored
Rollup merge of rust-lang#58057 - michaelwoerister:stabilize-xlto, r=alexcrichton
Stabilize linker-plugin based LTO (aka cross-language LTO) This PR stabilizes [linker plugin based LTO](rust-lang#49879), also known as "cross-language LTO" because it allows for doing inlining and other optimizations across language boundaries in mixed Rust/C/C++ projects. As described in the tracking issue, it works by making `rustc` emit LLVM bitcode instead of machine code, the same as `clang` does. A linker with the proper plugin (like LLD) can then run (Thin)LTO across all modules. The feature has been implemented over a number of pull requests and there are various [codegen](https://github.com/rust-lang/rust/blob/master/src/test/codegen/no-dllimport-w-cross-lang-lto.rs) and [run](https://github.com/rust-lang/rust/tree/master/src/test/run-make-fulldeps/cross-lang-lto-clang)-[make](https://github.com/rust-lang/rust/tree/master/src/test/run-make-fulldeps/cross-lang-lto-upstream-rlibs) [tests](https://github.com/rust-lang/rust/tree/master/src/test/run-make-fulldeps/cross-lang-lto) that make sure that it keeps working. It also works for building big projects like [Firefox](https://treeherder.mozilla.org/#/jobs?repo=try&revision=2ce2d5ddcea6fbff790503eac406954e469b2f5d). The PR makes the feature available under the `-C linker-plugin-lto` flag. As discussed in the tracking issue it is not cross-language specific and also not LLD specific. `-C linker-plugin-lto` is descriptive of what it does. If someone has a better name, let me know `:)`
2 parents 48ba561 + 3a9d171 commit 76b6bda

File tree

15 files changed

+172
-61
lines changed

15 files changed

+172
-61
lines changed

src/doc/rustc/src/SUMMARY.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@
1313
- [Targets](targets/index.md)
1414
- [Built-in Targets](targets/built-in.md)
1515
- [Custom Targets](targets/custom.md)
16-
- [Contributing to `rustc`](contributing.md)
16+
- [Linker-plugin based LTO](linker-plugin-lto.md)
17+
- [Contributing to `rustc`](contributing.md)
+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Linker-plugin-LTO
2+
3+
The `-C linker-plugin-lto` flag allows for deferring the LTO optimization
4+
to the actual linking step, which in turn allows for performing
5+
interprocedural optimizations across programming language boundaries if
6+
all the object files being linked were created by LLVM based toolchains.
7+
The prime example here would be linking Rust code together with
8+
Clang-compiled C/C++ code.
9+
10+
## Usage
11+
12+
There are two main cases how linker plugin based LTO can be used:
13+
14+
- compiling a Rust `staticlib` that is used as a C ABI dependency
15+
- compiling a Rust binary where `rustc` invokes the linker
16+
17+
In both cases the Rust code has to be compiled with `-C linker-plugin-lto` and
18+
the C/C++ code with `-flto` or `-flto=thin` so that object files are emitted
19+
as LLVM bitcode.
20+
21+
### Rust `staticlib` as dependency in C/C++ program
22+
23+
In this case the Rust compiler just has to make sure that the object files in
24+
the `staticlib` are in the right format. For linking, a linker with the
25+
LLVM plugin must be used (e.g. LLD).
26+
27+
Using `rustc` directly:
28+
29+
```bash
30+
# Compile the Rust staticlib
31+
rustc --crate-type=staticlib -Clinker-plugin-lto -Copt-level=2 ./lib.rs
32+
# Compile the C code with `-flto=thin`
33+
clang -c -O2 -flto=thin -o main.o ./main.c
34+
# Link everything, making sure that we use an appropriate linker
35+
clang -flto=thin -fuse-ld=lld -L . -l"name-of-your-rust-lib" -o main -O2 ./cmain.o
36+
```
37+
38+
Using `cargo`:
39+
40+
```bash
41+
# Compile the Rust staticlib
42+
RUSTFLAGS="-Clinker-plugin-lto" cargo build --release
43+
# Compile the C code with `-flto=thin`
44+
clang -c -O2 -flto=thin -o main.o ./main.c
45+
# Link everything, making sure that we use an appropriate linker
46+
clang -flto=thin -fuse-ld=lld -L . -l"name-of-your-rust-lib" -o main -O2 ./cmain.o
47+
```
48+
49+
### C/C++ code as a dependency in Rust
50+
51+
In this case the linker will be invoked by `rustc`. We again have to make sure
52+
that an appropriate linker is used.
53+
54+
Using `rustc` directly:
55+
56+
```bash
57+
# Compile C code with `-flto`
58+
clang ./clib.c -flto=thin -c -o ./clib.o -O2
59+
# Create a static library from the C code
60+
ar crus ./libxyz.a ./clib.o
61+
62+
# Invoke `rustc` with the additional arguments
63+
rustc -Clinker-plugin-lto -L. -Copt-level=2 -Clinker=clang -Clink-arg=-fuse-ld=lld ./main.rs
64+
```
65+
66+
Using `cargo` directly:
67+
68+
```bash
69+
# Compile C code with `-flto`
70+
clang ./clib.c -flto=thin -c -o ./clib.o -O2
71+
# Create a static library from the C code
72+
ar crus ./libxyz.a ./clib.o
73+
74+
# Set the linking arguments via RUSTFLAGS
75+
RUSTFLAGS="-Clinker-plugin-lto -Clinker=clang -Clink-arg=-fuse-ld=lld" cargo build --release
76+
```
77+
78+
### Explicitly specifying the linker plugin to be used by `rustc`
79+
80+
If one wants to use a linker other than LLD, the LLVM linker plugin has to be
81+
specified explicitly. Otherwise the linker cannot read the object files. The
82+
path to the plugin is passed as an argument to the `-Clinker-plugin-lto`
83+
option:
84+
85+
```bash
86+
rustc -Clinker-plugin-lto="/path/to/LLVMgold.so" -L. -Copt-level=2 ./main.rs
87+
```
88+
89+
90+
## Toolchain Compatibility
91+
92+
In order for this kind of LTO to work, the LLVM linker plugin must be able to
93+
handle the LLVM bitcode produced by both `rustc` and `clang`.
94+
95+
Best results are achieved by using a `rustc` and `clang` that are based on the
96+
exact same version of LLVM. One can use `rustc -vV` in order to view the LLVM
97+
used by a given `rustc` version. Note that the version number given
98+
here is only an approximation as Rust sometimes uses unstable revisions of
99+
LLVM. However, the approximation is usually reliable.
100+
101+
The following table shows known good combinations of toolchain versions.
102+
103+
| | Clang 7 | Clang 8 |
104+
|-----------|-----------|-----------|
105+
| Rust 1.34 |||
106+
| Rust 1.35 || ✓(?) |
107+
108+
Note that the compatibility policy for this feature might change in the future.

src/librustc/session/config.rs

+23-21
Original file line numberDiff line numberDiff line change
@@ -96,18 +96,18 @@ pub enum LtoCli {
9696
}
9797

9898
#[derive(Clone, PartialEq, Hash)]
99-
pub enum CrossLangLto {
99+
pub enum LinkerPluginLto {
100100
LinkerPlugin(PathBuf),
101101
LinkerPluginAuto,
102102
Disabled
103103
}
104104

105-
impl CrossLangLto {
105+
impl LinkerPluginLto {
106106
pub fn enabled(&self) -> bool {
107107
match *self {
108-
CrossLangLto::LinkerPlugin(_) |
109-
CrossLangLto::LinkerPluginAuto => true,
110-
CrossLangLto::Disabled => false,
108+
LinkerPluginLto::LinkerPlugin(_) |
109+
LinkerPluginLto::LinkerPluginAuto => true,
110+
LinkerPluginLto::Disabled => false,
111111
}
112112
}
113113
}
@@ -812,7 +812,7 @@ macro_rules! options {
812812
pub const parse_lto: Option<&str> =
813813
Some("either a boolean (`yes`, `no`, `on`, `off`, etc), `thin`, \
814814
`fat`, or omitted");
815-
pub const parse_cross_lang_lto: Option<&str> =
815+
pub const parse_linker_plugin_lto: Option<&str> =
816816
Some("either a boolean (`yes`, `no`, `on`, `off`, etc), \
817817
or the path to the linker plugin");
818818
pub const parse_merge_functions: Option<&str> =
@@ -821,7 +821,7 @@ macro_rules! options {
821821

822822
#[allow(dead_code)]
823823
mod $mod_set {
824-
use super::{$struct_name, Passes, Sanitizer, LtoCli, CrossLangLto};
824+
use super::{$struct_name, Passes, Sanitizer, LtoCli, LinkerPluginLto};
825825
use rustc_target::spec::{LinkerFlavor, MergeFunctions, PanicStrategy, RelroLevel};
826826
use std::path::PathBuf;
827827
use std::str::FromStr;
@@ -1037,22 +1037,22 @@ macro_rules! options {
10371037
true
10381038
}
10391039

1040-
fn parse_cross_lang_lto(slot: &mut CrossLangLto, v: Option<&str>) -> bool {
1040+
fn parse_linker_plugin_lto(slot: &mut LinkerPluginLto, v: Option<&str>) -> bool {
10411041
if v.is_some() {
10421042
let mut bool_arg = None;
10431043
if parse_opt_bool(&mut bool_arg, v) {
10441044
*slot = if bool_arg.unwrap() {
1045-
CrossLangLto::LinkerPluginAuto
1045+
LinkerPluginLto::LinkerPluginAuto
10461046
} else {
1047-
CrossLangLto::Disabled
1047+
LinkerPluginLto::Disabled
10481048
};
10491049
return true
10501050
}
10511051
}
10521052

10531053
*slot = match v {
1054-
None => CrossLangLto::LinkerPluginAuto,
1055-
Some(path) => CrossLangLto::LinkerPlugin(PathBuf::from(path)),
1054+
None => LinkerPluginLto::LinkerPluginAuto,
1055+
Some(path) => LinkerPluginLto::LinkerPlugin(PathBuf::from(path)),
10561056
};
10571057
true
10581058
}
@@ -1145,6 +1145,10 @@ options! {CodegenOptions, CodegenSetter, basic_codegen_options,
11451145
"allow the linker to link its default libraries"),
11461146
linker_flavor: Option<LinkerFlavor> = (None, parse_linker_flavor, [UNTRACKED],
11471147
"Linker flavor"),
1148+
linker_plugin_lto: LinkerPluginLto = (LinkerPluginLto::Disabled,
1149+
parse_linker_plugin_lto, [TRACKED],
1150+
"generate build artifacts that are compatible with linker-based LTO."),
1151+
11481152
}
11491153

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

25122514
impl_dep_tracking_hash_for_sortable_vec_of!(String);
25132515
impl_dep_tracking_hash_for_sortable_vec_of!(PathBuf);
@@ -2572,7 +2574,7 @@ mod tests {
25722574
use crate::lint;
25732575
use crate::middle::cstore;
25742576
use crate::session::config::{build_configuration, build_session_options_and_crate_config};
2575-
use crate::session::config::{LtoCli, CrossLangLto};
2577+
use crate::session::config::{LtoCli, LinkerPluginLto};
25762578
use crate::session::build_session;
25772579
use crate::session::search_paths::SearchPath;
25782580
use std::collections::{BTreeMap, BTreeSet};
@@ -3105,6 +3107,10 @@ mod tests {
31053107
opts = reference.clone();
31063108
opts.cg.panic = Some(PanicStrategy::Abort);
31073109
assert!(reference.dep_tracking_hash() != opts.dep_tracking_hash());
3110+
3111+
opts = reference.clone();
3112+
opts.cg.linker_plugin_lto = LinkerPluginLto::LinkerPluginAuto;
3113+
assert!(reference.dep_tracking_hash() != opts.dep_tracking_hash());
31083114
}
31093115

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

3234-
opts = reference.clone();
3235-
opts.debugging_opts.cross_lang_lto = CrossLangLto::LinkerPluginAuto;
3236-
assert!(reference.dep_tracking_hash() != opts.dep_tracking_hash());
3237-
32383240
opts = reference.clone();
32393241
opts.debugging_opts.merge_functions = Some(MergeFunctions::Disabled);
32403242
assert!(reference.dep_tracking_hash() != opts.dep_tracking_hash());

src/librustc/session/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1267,7 +1267,7 @@ fn validate_commandline_args_with_session_available(sess: &Session) {
12671267
// bitcode during ThinLTO. Therefore we disallow dynamic linking on MSVC
12681268
// when compiling for LLD ThinLTO. This way we can validly just not generate
12691269
// the `dllimport` attributes and `__imp_` symbols in that case.
1270-
if sess.opts.debugging_opts.cross_lang_lto.enabled() &&
1270+
if sess.opts.cg.linker_plugin_lto.enabled() &&
12711271
sess.opts.cg.prefer_dynamic &&
12721272
sess.target.target.options.is_like_msvc {
12731273
sess.err("Linker plugin based LTO is not supported together with \

src/librustc_codegen_llvm/back/link.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -857,7 +857,7 @@ fn link_args(cmd: &mut dyn Linker,
857857
codegen_results: &CodegenResults) {
858858

859859
// Linker plugins should be specified early in the list of arguments
860-
cmd.cross_lang_lto();
860+
cmd.linker_plugin_lto();
861861

862862
// The default library location, we need this to find the runtime.
863863
// The location of crates will be determined as needed.
@@ -1491,7 +1491,7 @@ fn are_upstream_rust_objects_already_included(sess: &Session) -> bool {
14911491
Lto::Thin => {
14921492
// If we defer LTO to the linker, we haven't run LTO ourselves, so
14931493
// any upstream object files have not been copied yet.
1494-
!sess.opts.debugging_opts.cross_lang_lto.enabled()
1494+
!sess.opts.cg.linker_plugin_lto.enabled()
14951495
}
14961496
Lto::No |
14971497
Lto::ThinLocal => false,

src/librustc_codegen_llvm/back/lto.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ pub(crate) fn run_thin(cgcx: &CodegenContext<LlvmCodegenBackend>,
159159
let symbol_white_list = symbol_white_list.iter()
160160
.map(|c| c.as_ptr())
161161
.collect::<Vec<_>>();
162-
if cgcx.opts.debugging_opts.cross_lang_lto.enabled() {
162+
if cgcx.opts.cg.linker_plugin_lto.enabled() {
163163
unreachable!("We should never reach this case if the LTO step \
164164
is deferred to the linker");
165165
}

src/librustc_codegen_llvm/back/write.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ pub(crate) unsafe fn optimize(cgcx: &CodegenContext<LlvmCodegenBackend>,
366366
let opt_level = config.opt_level.map(|x| to_llvm_opt_settings(x).0)
367367
.unwrap_or(llvm::CodeGenOptLevel::None);
368368
let prepare_for_thin_lto = cgcx.lto == Lto::Thin || cgcx.lto == Lto::ThinLocal ||
369-
(cgcx.lto != Lto::Fat && cgcx.opts.debugging_opts.cross_lang_lto.enabled());
369+
(cgcx.lto != Lto::Fat && cgcx.opts.cg.linker_plugin_lto.enabled());
370370
with_llvm_pmb(llmod, &config, opt_level, prepare_for_thin_lto, &mut |b| {
371371
llvm::LLVMPassManagerBuilderPopulateFunctionPassManager(b, fpm);
372372
llvm::LLVMPassManagerBuilderPopulateModulePassManager(b, mpm);

src/librustc_codegen_llvm/consts.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -275,12 +275,12 @@ impl CodegenCx<'ll, 'tcx> {
275275
self.use_dll_storage_attrs && !self.tcx.is_foreign_item(def_id) &&
276276
// ThinLTO can't handle this workaround in all cases, so we don't
277277
// emit the attrs. Instead we make them unnecessary by disallowing
278-
// dynamic linking when cross-language LTO is enabled.
279-
!self.tcx.sess.opts.debugging_opts.cross_lang_lto.enabled();
278+
// dynamic linking when linker plugin based LTO is enabled.
279+
!self.tcx.sess.opts.cg.linker_plugin_lto.enabled();
280280

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

0 commit comments

Comments
 (0)