Skip to content

Commit ee97f7f

Browse files
bjorn3Kobzol
authored andcommitted
Allow LTO for dylibs
* Use more accurate symbol export list for LTO * Add missing export for the oom strategy symbol Co-authored-by: Jakub Beránek <berykubik@gmail.com>
1 parent c27948d commit ee97f7f

File tree

12 files changed

+127
-27
lines changed

12 files changed

+127
-27
lines changed

compiler/rustc_codegen_llvm/src/back/lto.rs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ pub const THIN_LTO_KEYS_INCR_COMP_FILE_NAME: &str = "thin-lto-past-keys.bin";
3232

3333
pub fn crate_type_allows_lto(crate_type: CrateType) -> bool {
3434
match crate_type {
35-
CrateType::Executable | CrateType::Staticlib | CrateType::Cdylib => true,
36-
CrateType::Dylib | CrateType::Rlib | CrateType::ProcMacro => false,
35+
CrateType::Executable | CrateType::Dylib | CrateType::Staticlib | CrateType::Cdylib => true,
36+
CrateType::Rlib | CrateType::ProcMacro => false,
3737
}
3838
}
3939

@@ -73,17 +73,6 @@ fn prepare_lto(
7373
// with either fat or thin LTO
7474
let mut upstream_modules = Vec::new();
7575
if cgcx.lto != Lto::ThinLocal {
76-
if cgcx.opts.cg.prefer_dynamic {
77-
diag_handler
78-
.struct_err("cannot prefer dynamic linking when performing LTO")
79-
.note(
80-
"only 'staticlib', 'bin', and 'cdylib' outputs are \
81-
supported with LTO",
82-
)
83-
.emit();
84-
return Err(FatalError);
85-
}
86-
8776
// Make sure we actually can run LTO
8877
for crate_type in cgcx.crate_types.iter() {
8978
if !crate_type_allows_lto(*crate_type) {
@@ -92,9 +81,25 @@ fn prepare_lto(
9281
static library outputs",
9382
);
9483
return Err(e);
84+
} else if *crate_type == CrateType::Dylib {
85+
if !cgcx.opts.unstable_opts.dylib_lto {
86+
return Err(diag_handler
87+
.fatal("lto cannot be used for `dylib` crate type without `-Zdylib-lto`"));
88+
}
9589
}
9690
}
9791

92+
if cgcx.opts.cg.prefer_dynamic && !cgcx.opts.unstable_opts.dylib_lto {
93+
diag_handler
94+
.struct_err("cannot prefer dynamic linking when performing LTO")
95+
.note(
96+
"only 'staticlib', 'bin', and 'cdylib' outputs are \
97+
supported with LTO",
98+
)
99+
.emit();
100+
return Err(FatalError);
101+
}
102+
98103
for &(cnum, ref path) in cgcx.each_linked_rlib_for_lto.iter() {
99104
let exported_symbols =
100105
cgcx.exported_symbols.as_ref().expect("needs exported symbols for LTO");

compiler/rustc_codegen_ssa/src/back/link.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use rustc_metadata::find_native_static_library;
1111
use rustc_metadata::fs::{emit_metadata, METADATA_FILENAME};
1212
use rustc_middle::middle::dependency_format::Linkage;
1313
use rustc_middle::middle::exported_symbols::SymbolExportKind;
14-
use rustc_session::config::{self, CFGuard, CrateType, DebugInfo, LdImpl, Strip};
14+
use rustc_session::config::{self, CFGuard, CrateType, DebugInfo, LdImpl, Lto, Strip};
1515
use rustc_session::config::{OutputFilenames, OutputType, PrintRequest, SplitDwarfKind};
1616
use rustc_session::cstore::DllImport;
1717
use rustc_session::output::{check_file_is_writeable, invalid_output_for_target, out_filename};
@@ -37,6 +37,7 @@ use cc::windows_registry;
3737
use regex::Regex;
3838
use tempfile::Builder as TempFileBuilder;
3939

40+
use itertools::Itertools;
4041
use std::borrow::Borrow;
4142
use std::cell::OnceCell;
4243
use std::collections::BTreeSet;
@@ -206,11 +207,26 @@ pub fn link_binary<'a>(
206207
}
207208

208209
pub fn each_linked_rlib(
210+
sess: &Session,
209211
info: &CrateInfo,
210212
f: &mut dyn FnMut(CrateNum, &Path),
211213
) -> Result<(), String> {
212214
let crates = info.used_crates.iter();
213215
let mut fmts = None;
216+
217+
let lto_active = matches!(sess.lto(), Lto::Fat | Lto::Thin);
218+
if lto_active {
219+
for combination in info.dependency_formats.iter().combinations(2) {
220+
let (ty1, list1) = combination[0];
221+
let (ty2, list2) = combination[1];
222+
if list1 != list2 {
223+
return Err(format!(
224+
"{ty1:?} and {ty2:?} do not have equivalent dependency formats (`{list1:?}` vs `{list2:?}`)"
225+
));
226+
}
227+
}
228+
}
229+
214230
for (ty, list) in info.dependency_formats.iter() {
215231
match ty {
216232
CrateType::Executable
@@ -220,6 +236,10 @@ pub fn each_linked_rlib(
220236
fmts = Some(list);
221237
break;
222238
}
239+
CrateType::Dylib if lto_active => {
240+
fmts = Some(list);
241+
break;
242+
}
223243
_ => {}
224244
}
225245
}
@@ -501,7 +521,7 @@ fn link_staticlib<'a>(
501521
)?;
502522
let mut all_native_libs = vec![];
503523

504-
let res = each_linked_rlib(&codegen_results.crate_info, &mut |cnum, path| {
524+
let res = each_linked_rlib(sess, &codegen_results.crate_info, &mut |cnum, path| {
505525
let name = codegen_results.crate_info.crate_name[&cnum];
506526
let native_libs = &codegen_results.crate_info.native_libraries[&cnum];
507527

compiler/rustc_codegen_ssa/src/back/symbol_export.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use rustc_middle::ty::query::{ExternProviders, Providers};
1313
use rustc_middle::ty::subst::{GenericArgKind, SubstsRef};
1414
use rustc_middle::ty::Instance;
1515
use rustc_middle::ty::{self, SymbolName, TyCtxt};
16-
use rustc_session::config::CrateType;
16+
use rustc_session::config::{CrateType, OomStrategy};
1717
use rustc_target::spec::SanitizerSet;
1818

1919
pub fn threshold(tcx: TyCtxt<'_>) -> SymbolExportLevel {
@@ -206,6 +206,15 @@ fn exported_symbols_provider_local<'tcx>(
206206
},
207207
));
208208
}
209+
210+
symbols.push((
211+
ExportedSymbol::NoDefId(SymbolName::new(tcx, OomStrategy::SYMBOL)),
212+
SymbolExportInfo {
213+
level: SymbolExportLevel::Rust,
214+
kind: SymbolExportKind::Text,
215+
used: false,
216+
},
217+
));
209218
}
210219

211220
if tcx.sess.instrument_coverage() || tcx.sess.opts.cg.profile_generate.enabled() {

compiler/rustc_codegen_ssa/src/back/write.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,6 +1006,14 @@ fn start_executing_work<B: ExtraBackendMethods>(
10061006
let coordinator_send = tx_to_llvm_workers;
10071007
let sess = tcx.sess;
10081008

1009+
let mut each_linked_rlib_for_lto = Vec::new();
1010+
drop(link::each_linked_rlib(sess, crate_info, &mut |cnum, path| {
1011+
if link::ignored_for_lto(sess, crate_info, cnum) {
1012+
return;
1013+
}
1014+
each_linked_rlib_for_lto.push((cnum, path.to_path_buf()));
1015+
}));
1016+
10091017
// Compute the set of symbols we need to retain when doing LTO (if we need to)
10101018
let exported_symbols = {
10111019
let mut exported_symbols = FxHashMap::default();
@@ -1027,7 +1035,7 @@ fn start_executing_work<B: ExtraBackendMethods>(
10271035
}
10281036
Lto::Fat | Lto::Thin => {
10291037
exported_symbols.insert(LOCAL_CRATE, copy_symbols(LOCAL_CRATE));
1030-
for &cnum in tcx.crates(()).iter() {
1038+
for &(cnum, ref _path) in &each_linked_rlib_for_lto {
10311039
exported_symbols.insert(cnum, copy_symbols(cnum));
10321040
}
10331041
Some(Arc::new(exported_symbols))
@@ -1047,14 +1055,6 @@ fn start_executing_work<B: ExtraBackendMethods>(
10471055
})
10481056
.expect("failed to spawn helper thread");
10491057

1050-
let mut each_linked_rlib_for_lto = Vec::new();
1051-
drop(link::each_linked_rlib(crate_info, &mut |cnum, path| {
1052-
if link::ignored_for_lto(sess, crate_info, cnum) {
1053-
return;
1054-
}
1055-
each_linked_rlib_for_lto.push((cnum, path.to_path_buf()));
1056-
}));
1057-
10581058
let ol =
10591059
if tcx.sess.opts.unstable_opts.no_codegen || !tcx.sess.opts.output_types.should_codegen() {
10601060
// If we know that we won’t be doing codegen, create target machines without optimisation.

compiler/rustc_interface/src/tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,7 @@ fn test_unstable_options_tracking_hash() {
654654
untracked!(dump_mir_dir, String::from("abc"));
655655
untracked!(dump_mir_exclude_pass_number, true);
656656
untracked!(dump_mir_graphviz, true);
657+
untracked!(dylib_lto, true);
657658
untracked!(emit_stack_sizes, true);
658659
untracked!(future_incompat_test, true);
659660
untracked!(hir_stats, true);

compiler/rustc_session/src/options.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,6 +1296,8 @@ options! {
12961296
an additional `.html` file showing the computed coverage spans."),
12971297
dwarf_version: Option<u32> = (None, parse_opt_number, [TRACKED],
12981298
"version of DWARF debug information to emit (default: 2 or 4, depending on platform)"),
1299+
dylib_lto: bool = (false, parse_bool, [UNTRACKED],
1300+
"enables LTO for dylib crate type"),
12991301
emit_stack_sizes: bool = (false, parse_bool, [UNTRACKED],
13001302
"emit a section containing stack size metadata (default: no)"),
13011303
emit_thin_lto: bool = (true, parse_bool, [TRACKED],

config.toml.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,11 @@ changelog-seen = 2
638638
# If an explicit setting is given, it will be used for all parts of the codebase.
639639
#new-symbol-mangling = true|false (see comment)
640640

641+
# Select LTO mode that will be used for compiling rustc. By default, thin local LTO
642+
# (LTO within a single crate) is used (like for any Rust crate). You can also select
643+
# "thin" or "fat" to apply Thin/Fat LTO to the `rustc_driver` dylib.
644+
#lto = "thin-local"
645+
641646
# =============================================================================
642647
# Options for specific targets
643648
#

src/bootstrap/compile.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use serde::Deserialize;
2121
use crate::builder::Cargo;
2222
use crate::builder::{Builder, Kind, RunConfig, ShouldRun, Step};
2323
use crate::cache::{Interned, INTERNER};
24-
use crate::config::{LlvmLibunwind, TargetSelection};
24+
use crate::config::{LlvmLibunwind, RustcLto, TargetSelection};
2525
use crate::dist;
2626
use crate::native;
2727
use crate::tool::SourceType;
@@ -701,6 +701,28 @@ impl Step for Rustc {
701701
));
702702
}
703703

704+
// cfg(bootstrap): remove if condition once the bootstrap compiler supports dylib LTO
705+
if compiler.stage != 0 {
706+
match builder.config.rust_lto {
707+
RustcLto::Thin | RustcLto::Fat => {
708+
// Since using LTO for optimizing dylibs is currently experimental,
709+
// we need to pass -Zdylib-lto.
710+
cargo.rustflag("-Zdylib-lto");
711+
// Cargo by default passes `-Cembed-bitcode=no` and doesn't pass `-Clto` when
712+
// compiling dylibs (and their dependencies), even when LTO is enabled for the
713+
// crate. Therefore, we need to override `-Clto` and `-Cembed-bitcode` here.
714+
let lto_type = match builder.config.rust_lto {
715+
RustcLto::Thin => "thin",
716+
RustcLto::Fat => "fat",
717+
_ => unreachable!(),
718+
};
719+
cargo.rustflag(&format!("-Clto={}", lto_type));
720+
cargo.rustflag("-Cembed-bitcode=yes");
721+
}
722+
RustcLto::ThinLocal => { /* Do nothing, this is the default */ }
723+
}
724+
}
725+
704726
builder.info(&format!(
705727
"Building stage{} compiler artifacts ({} -> {})",
706728
compiler.stage, &compiler.host, target

src/bootstrap/config.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ pub struct Config {
158158
pub rust_new_symbol_mangling: Option<bool>,
159159
pub rust_profile_use: Option<String>,
160160
pub rust_profile_generate: Option<String>,
161+
pub rust_lto: RustcLto,
161162
pub llvm_profile_use: Option<String>,
162163
pub llvm_profile_generate: bool,
163164
pub llvm_libunwind_default: Option<LlvmLibunwind>,
@@ -317,6 +318,28 @@ impl SplitDebuginfo {
317318
}
318319
}
319320

321+
/// LTO mode used for compiling rustc itself.
322+
#[derive(Default, Clone)]
323+
pub enum RustcLto {
324+
#[default]
325+
ThinLocal,
326+
Thin,
327+
Fat,
328+
}
329+
330+
impl std::str::FromStr for RustcLto {
331+
type Err = String;
332+
333+
fn from_str(s: &str) -> Result<Self, Self::Err> {
334+
match s {
335+
"thin-local" => Ok(RustcLto::ThinLocal),
336+
"thin" => Ok(RustcLto::Thin),
337+
"fat" => Ok(RustcLto::Fat),
338+
_ => Err(format!("Invalid value for rustc LTO: {}", s)),
339+
}
340+
}
341+
}
342+
320343
#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
321344
pub struct TargetSelection {
322345
pub triple: Interned<String>,
@@ -724,6 +747,7 @@ define_config! {
724747
profile_use: Option<String> = "profile-use",
725748
// ignored; this is set from an env var set by bootstrap.py
726749
download_rustc: Option<StringOrBool> = "download-rustc",
750+
lto: Option<String> = "lto",
727751
}
728752
}
729753

@@ -1153,6 +1177,12 @@ impl Config {
11531177
config.rust_profile_use = flags.rust_profile_use.or(rust.profile_use);
11541178
config.rust_profile_generate = flags.rust_profile_generate.or(rust.profile_generate);
11551179
config.download_rustc_commit = download_ci_rustc_commit(&config, rust.download_rustc);
1180+
1181+
config.rust_lto = rust
1182+
.lto
1183+
.as_deref()
1184+
.map(|value| RustcLto::from_str(value).unwrap())
1185+
.unwrap_or_default();
11561186
} else {
11571187
config.rust_profile_use = flags.rust_profile_use;
11581188
config.rust_profile_generate = flags.rust_profile_generate;

src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ ENV RUST_CONFIGURE_ARGS \
7878
--set llvm.thin-lto=true \
7979
--set llvm.ninja=false \
8080
--set rust.jemalloc \
81-
--set rust.use-lld=true
81+
--set rust.use-lld=true \
82+
--set rust.lto=thin
8283
ENV SCRIPT ../src/ci/pgo.sh python3 ../x.py dist \
8384
--host $HOSTS --target $HOSTS \
8485
--include-default-paths \
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
## `dylib-lto`
2+
3+
This option enables using LTO for the `dylib` crate type. This is currently only used for compiling
4+
`rustc` itself (more specifically, the `librustc_driver` dylib).

src/test/rustdoc-ui/z-help.stdout

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
-Z dump-mir-graphviz=val -- in addition to `.mir` files, create graphviz `.dot` files (and with `-Z instrument-coverage`, also create a `.dot` file for the MIR-derived coverage graph) (default: no)
3636
-Z dump-mir-spanview=val -- in addition to `.mir` files, create `.html` files to view spans for all `statement`s (including terminators), only `terminator` spans, or computed `block` spans (one span encompassing a block's terminator and all statements). If `-Z instrument-coverage` is also enabled, create an additional `.html` file showing the computed coverage spans.
3737
-Z dwarf-version=val -- version of DWARF debug information to emit (default: 2 or 4, depending on platform)
38+
-Z dylib-lto=val -- enables LTO for dylib crate type
3839
-Z emit-stack-sizes=val -- emit a section containing stack size metadata (default: no)
3940
-Z emit-thin-lto=val -- emit the bc module with thin LTO info (default: yes)
4041
-Z export-executable-symbols=val -- export symbols from executables, as if they were dynamic libraries

0 commit comments

Comments
 (0)