Skip to content

Commit d8c09c1

Browse files
Rollup merge of #136840 - Flakebi:linker-plugin-lto-fat, r=dianqk
Fix linker-plugin-lto only doing thin lto When rust provides LLVM bitcode files to lld and the bitcode contains function summaries as used for thin lto, lld defaults to using thin lto. This prevents some optimizations that are only applied for fat lto. We solve this by not creating function summaries when fat lto is enabled. The bitcode for the module is just directly written out. An alternative solution would be to set the `ThinLTO=0` module flag to signal lld to do fat lto. The code in clang that sets this flag is here: https://github.com/llvm/llvm-project/blob/560149b5e3c891c64899e9912e29467a69dc3a4c/clang/lib/CodeGen/BackendUtil.cpp#L1150 The code in LLVM that queries the flag and defaults to thin lto if not set is here: https://github.com/llvm/llvm-project/blob/e258bca9505f35e0a22cb213a305eea9b76d11ea/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp#L4441-L4446 try-job: x86_64-gnu-debug try-job: aarch64-gnu-debug
2 parents cc0a5b7 + 7a127fb commit d8c09c1

File tree

11 files changed

+189
-25
lines changed

11 files changed

+189
-25
lines changed

compiler/rustc_codegen_ssa/src/back/write.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,9 @@ impl ModuleConfig {
214214
false
215215
),
216216
emit_obj,
217-
emit_thin_lto: sess.opts.unstable_opts.emit_thin_lto,
217+
// thin lto summaries prevent fat lto, so do not emit them if fat
218+
// lto is requested. See PR #136840 for background information.
219+
emit_thin_lto: sess.opts.unstable_opts.emit_thin_lto && sess.lto() != Lto::Fat,
218220
emit_thin_lto_summary: if_regular!(
219221
sess.opts.output_types.contains_key(&OutputType::ThinLinkBitcode),
220222
false

src/tools/run-make-support/src/external_deps/llvm.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ pub fn llvm_pdbutil() -> LlvmPdbutil {
6060
LlvmPdbutil::new()
6161
}
6262

63+
/// Construct a new `llvm-as` invocation. This assumes that `llvm-as` is available
64+
/// at `$LLVM_BIN_DIR/llvm-as`.
65+
pub fn llvm_as() -> LlvmAs {
66+
LlvmAs::new()
67+
}
68+
6369
/// Construct a new `llvm-dis` invocation. This assumes that `llvm-dis` is available
6470
/// at `$LLVM_BIN_DIR/llvm-dis`.
6571
pub fn llvm_dis() -> LlvmDis {
@@ -135,6 +141,13 @@ pub struct LlvmPdbutil {
135141
cmd: Command,
136142
}
137143

144+
/// A `llvm-as` invocation builder.
145+
#[derive(Debug)]
146+
#[must_use]
147+
pub struct LlvmAs {
148+
cmd: Command,
149+
}
150+
138151
/// A `llvm-dis` invocation builder.
139152
#[derive(Debug)]
140153
#[must_use]
@@ -158,6 +171,7 @@ crate::macros::impl_common_helpers!(LlvmNm);
158171
crate::macros::impl_common_helpers!(LlvmBcanalyzer);
159172
crate::macros::impl_common_helpers!(LlvmDwarfdump);
160173
crate::macros::impl_common_helpers!(LlvmPdbutil);
174+
crate::macros::impl_common_helpers!(LlvmAs);
161175
crate::macros::impl_common_helpers!(LlvmDis);
162176
crate::macros::impl_common_helpers!(LlvmObjcopy);
163177

@@ -441,6 +455,22 @@ impl LlvmObjcopy {
441455
}
442456
}
443457

458+
impl LlvmAs {
459+
/// Construct a new `llvm-as` invocation. This assumes that `llvm-as` is available
460+
/// at `$LLVM_BIN_DIR/llvm-as`.
461+
pub fn new() -> Self {
462+
let llvm_as = llvm_bin_dir().join("llvm-as");
463+
let cmd = Command::new(llvm_as);
464+
Self { cmd }
465+
}
466+
467+
/// Provide an input file.
468+
pub fn input<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
469+
self.cmd.arg(path.as_ref());
470+
self
471+
}
472+
}
473+
444474
impl LlvmDis {
445475
/// Construct a new `llvm-dis` invocation. This assumes that `llvm-dis` is available
446476
/// at `$LLVM_BIN_DIR/llvm-dis`.

src/tools/run-make-support/src/external_deps/rustc.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ impl Rustc {
173173
self
174174
}
175175

176+
/// This flag enables LTO in the specified form.
177+
pub fn lto(&mut self, option: &str) -> &mut Self {
178+
self.cmd.arg(format!("-Clto={option}"));
179+
self
180+
}
181+
176182
/// This flag defers LTO optimizations to the linker.
177183
pub fn linker_plugin_lto(&mut self, option: &str) -> &mut Self {
178184
self.cmd.arg(format!("-Clinker-plugin-lto={option}"));

src/tools/run-make-support/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,9 @@ pub use crate::external_deps::clang::{Clang, clang};
6363
pub use crate::external_deps::htmldocck::htmldocck;
6464
pub use crate::external_deps::llvm::{
6565
self, LlvmAr, LlvmBcanalyzer, LlvmDis, LlvmDwarfdump, LlvmFilecheck, LlvmNm, LlvmObjcopy,
66-
LlvmObjdump, LlvmProfdata, LlvmReadobj, llvm_ar, llvm_bcanalyzer, llvm_dis, llvm_dwarfdump,
67-
llvm_filecheck, llvm_nm, llvm_objcopy, llvm_objdump, llvm_profdata, llvm_readobj,
66+
LlvmObjdump, LlvmProfdata, LlvmReadobj, llvm_ar, llvm_as, llvm_bcanalyzer, llvm_dis,
67+
llvm_dwarfdump, llvm_filecheck, llvm_nm, llvm_objcopy, llvm_objdump, llvm_profdata,
68+
llvm_readobj,
6869
};
6970
pub use crate::external_deps::python::python_command;
7071
pub use crate::external_deps::rustc::{self, Rustc, bare_rustc, rustc, rustc_path};

tests/run-make/cross-lang-lto-clang/rmake.rs

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,53 +28,68 @@ static C_NEVER_INLINED_PATTERN: &'static str = "bl.*<c_never_inlined>";
2828
static C_NEVER_INLINED_PATTERN: &'static str = "call.*c_never_inlined";
2929

3030
fn main() {
31+
test_lto(false);
32+
test_lto(true);
33+
}
34+
35+
fn test_lto(fat_lto: bool) {
36+
let lto = if fat_lto { "fat" } else { "thin" };
37+
let clang_lto = if fat_lto { "full" } else { "thin" };
38+
println!("Running {lto} lto");
39+
3140
rustc()
41+
.lto(lto)
3242
.linker_plugin_lto("on")
3343
.output(static_lib_name("rustlib-xlto"))
3444
.opt_level("2")
3545
.codegen_units(1)
3646
.input("rustlib.rs")
3747
.run();
3848
clang()
39-
.lto("thin")
49+
.lto(clang_lto)
4050
.use_ld("lld")
4151
.arg("-lrustlib-xlto")
4252
.out_exe("cmain")
4353
.input("cmain.c")
4454
.arg("-O3")
4555
.run();
56+
57+
let dump = llvm_objdump().disassemble().input("cmain").run();
4658
// Make sure we don't find a call instruction to the function we expect to
4759
// always be inlined.
48-
llvm_objdump()
49-
.disassemble()
50-
.input("cmain")
51-
.run()
52-
.assert_stdout_not_contains_regex(RUST_ALWAYS_INLINED_PATTERN);
60+
dump.assert_stdout_not_contains_regex(RUST_ALWAYS_INLINED_PATTERN);
5361
// As a sanity check, make sure we do find a call instruction to a
5462
// non-inlined function
55-
llvm_objdump()
56-
.disassemble()
57-
.input("cmain")
58-
.run()
59-
.assert_stdout_contains_regex(RUST_NEVER_INLINED_PATTERN);
60-
clang().input("clib.c").lto("thin").arg("-c").out_exe("clib.o").arg("-O2").run();
63+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
64+
dump.assert_stdout_contains_regex(RUST_NEVER_INLINED_PATTERN);
65+
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
66+
{
67+
if fat_lto {
68+
// fat lto inlines this anyway
69+
dump.assert_stdout_not_contains_regex(RUST_NEVER_INLINED_PATTERN);
70+
} else {
71+
dump.assert_stdout_contains_regex(RUST_NEVER_INLINED_PATTERN);
72+
}
73+
}
74+
75+
clang().input("clib.c").lto(clang_lto).arg("-c").out_exe("clib.o").arg("-O2").run();
6176
llvm_ar().obj_to_ar().output_input(static_lib_name("xyz"), "clib.o").run();
6277
rustc()
78+
.lto(lto)
6379
.linker_plugin_lto("on")
6480
.opt_level("2")
6581
.linker(&env_var("CLANG"))
6682
.link_arg("-fuse-ld=lld")
6783
.input("main.rs")
6884
.output("rsmain")
6985
.run();
70-
llvm_objdump()
71-
.disassemble()
72-
.input("rsmain")
73-
.run()
74-
.assert_stdout_not_contains_regex(C_ALWAYS_INLINED_PATTERN);
75-
llvm_objdump()
76-
.disassemble()
77-
.input("rsmain")
78-
.run()
79-
.assert_stdout_contains_regex(C_NEVER_INLINED_PATTERN);
86+
87+
let dump = llvm_objdump().disassemble().input("rsmain").run();
88+
dump.assert_stdout_not_contains_regex(C_ALWAYS_INLINED_PATTERN);
89+
if fat_lto {
90+
// fat lto inlines this anyway
91+
dump.assert_stdout_not_contains_regex(C_NEVER_INLINED_PATTERN);
92+
} else {
93+
dump.assert_stdout_contains_regex(C_NEVER_INLINED_PATTERN);
94+
}
8095
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#![allow(internal_features)]
2+
#![feature(no_core, lang_items)]
3+
#![no_core]
4+
#![crate_type = "rlib"]
5+
6+
#[lang = "pointee_sized"]
7+
trait PointeeSized {}
8+
#[lang = "meta_sized"]
9+
trait MetaSized: PointeeSized {}
10+
#[lang = "sized"]
11+
trait Sized: MetaSized {}
12+
13+
pub fn foo() {}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#![allow(internal_features)]
2+
#![feature(no_core, lang_items)]
3+
#![no_core]
4+
#![crate_type = "cdylib"]
5+
6+
extern crate lib;
7+
8+
#[unsafe(no_mangle)]
9+
pub fn bar() {
10+
lib::foo();
11+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Compile a library with lto=fat, then compile a binary with lto=thin
2+
// and check that lto is applied with the library.
3+
// The goal is to mimic the standard library being build with lto=fat
4+
// and allowing users to build with lto=thin.
5+
6+
//@ only-x86_64-unknown-linux-gnu
7+
8+
use run_make_support::{dynamic_lib_name, llvm_objdump, rustc};
9+
10+
fn main() {
11+
rustc().input("lib.rs").opt_level("3").lto("fat").run();
12+
rustc().input("main.rs").panic("abort").opt_level("3").lto("thin").run();
13+
14+
llvm_objdump()
15+
.input(dynamic_lib_name("main"))
16+
.arg("--disassemble-symbols=bar")
17+
.run()
18+
// The called function should be inlined.
19+
// Check that we have a ret (to detect tail
20+
// calls with a jmp) and no call.
21+
.assert_stdout_contains("bar")
22+
.assert_stdout_contains("ret")
23+
.assert_stdout_not_contains("foo")
24+
.assert_stdout_not_contains("call");
25+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
2+
target triple = "x86_64-unknown-linux-gnu"
3+
4+
define void @ir_callee() {
5+
ret void
6+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#![allow(internal_features)]
2+
#![feature(no_core, lang_items)]
3+
#![no_core]
4+
#![crate_type = "cdylib"]
5+
6+
#[lang = "pointee_sized"]
7+
trait PointeeSized {}
8+
#[lang = "meta_sized"]
9+
trait MetaSized: PointeeSized {}
10+
#[lang = "sized"]
11+
trait Sized: MetaSized {}
12+
13+
extern "C" {
14+
fn ir_callee();
15+
}
16+
17+
#[no_mangle]
18+
extern "C" fn rs_foo() {
19+
unsafe {
20+
ir_callee();
21+
}
22+
}

0 commit comments

Comments
 (0)