Skip to content

Commit 16274ea

Browse files
committed
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
1 parent ea1da92 commit 16274ea

File tree

12 files changed

+162
-14
lines changed

12 files changed

+162
-14
lines changed

compiler/rustc_codegen_llvm/src/back/write.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -827,7 +827,7 @@ pub(crate) unsafe fn codegen(
827827
"LLVM_module_codegen_make_bitcode",
828828
&*module.name,
829829
);
830-
ThinBuffer::new(llmod, config.emit_thin_lto, false)
830+
ThinBuffer::new(llmod, cgcx.lto != Lto::Fat, false)
831831
};
832832
let data = thin.data();
833833
let _timer = cgcx

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

+30
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

+6
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,12 @@ impl Rustc {
171171
self
172172
}
173173

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

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ pub use cargo::cargo;
6363
pub use clang::{clang, Clang};
6464
pub use htmldocck::htmldocck;
6565
pub use llvm::{
66-
llvm_ar, llvm_bcanalyzer, llvm_dis, llvm_dwarfdump, llvm_filecheck, llvm_nm, llvm_objcopy,
66+
llvm_ar, llvm_bcanalyzer, llvm_as, llvm_dis, llvm_dwarfdump, llvm_filecheck, llvm_nm, llvm_objcopy,
6767
llvm_objdump, llvm_profdata, llvm_readobj, LlvmAr, LlvmBcanalyzer, LlvmDis, LlvmDwarfdump,
6868
LlvmFilecheck, LlvmNm, LlvmObjcopy, LlvmObjdump, LlvmProfdata, LlvmReadobj,
6969
};

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

+20-7
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,24 @@ 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+
3139
rustc()
40+
.lto(lto)
3241
.linker_plugin_lto("on")
3342
.output(static_lib_name("rustlib-xlto"))
3443
.opt_level("2")
3544
.codegen_units(1)
3645
.input("rustlib.rs")
3746
.run();
3847
clang()
39-
.lto("thin")
48+
.lto(clang_lto)
4049
.use_ld("lld")
4150
.arg("-lrustlib-xlto")
4251
.out_exe("cmain")
@@ -57,9 +66,10 @@ fn main() {
5766
.input("cmain")
5867
.run()
5968
.assert_stdout_contains_regex(RUST_NEVER_INLINED_PATTERN);
60-
clang().input("clib.c").lto("thin").arg("-c").out_exe("clib.o").arg("-O2").run();
69+
clang().input("clib.c").lto(clang_lto).arg("-c").out_exe("clib.o").arg("-O2").run();
6170
llvm_ar().obj_to_ar().output_input(static_lib_name("xyz"), "clib.o").run();
6271
rustc()
72+
.lto(lto)
6373
.linker_plugin_lto("on")
6474
.opt_level("2")
6575
.linker(&env_var("CLANG"))
@@ -72,9 +82,12 @@ fn main() {
7282
.input("rsmain")
7383
.run()
7484
.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);
85+
86+
let dump = llvm_objdump().disassemble().input("rsmain").run();
87+
if !fat_lto {
88+
dump.assert_stdout_contains_regex(C_NEVER_INLINED_PATTERN);
89+
} else {
90+
// fat lto inlines this anyway
91+
dump.assert_stdout_not_contains_regex(C_NEVER_INLINED_PATTERN);
92+
}
8093
}
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#![feature(no_core, lang_items)]
2+
#![no_core]
3+
#![crate_type = "rlib"]
4+
5+
#[lang = "sized"]
6+
trait Sized {}
7+
8+
pub fn foo() {}
+11
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+
}
+25
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+
}

tests/run-make/issue-84395-lto-embed-bitcode/rmake.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
//! `-lto-embed-bitcode=optimized` to lld when linking rust code via `linker-plugin-lto` doesn't
66
//! produce the expected result.
77
//!
8-
//! See PR <https://github.com/rust-lang/rust/pull/98162> which initially introduced this test.
8+
//! -Clto=fat needs to explicitly requested to produce the correct, raw bitcode format for lld.
99
1010
//@ needs-force-clang-based-tests
1111

@@ -14,11 +14,11 @@ use run_make_support::{env_var, llvm_dis, llvm_objcopy, rustc};
1414
fn main() {
1515
rustc()
1616
.input("test.rs")
17-
.arg("-Clink-arg=-fuse-ld=lld")
18-
.arg("-Clinker-plugin-lto")
17+
.lto("fat")
18+
.linker_plugin_lto("on")
19+
.link_arg("-fuse-ld=lld")
1920
.arg(format!("-Clinker={}", env_var("CLANG")))
20-
.arg("-Clink-arg=-Wl,--plugin-opt=-lto-embed-bitcode=optimized")
21-
.arg("-Zemit-thin-lto=no")
21+
.link_arg("-Wl,--plugin-opt=-lto-embed-bitcode=optimized")
2222
.run();
2323

2424
llvm_objcopy().dump_section(".llvmbc", "test.bc").arg("test").run();
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#![feature(no_core, lang_items)]
2+
#![no_core]
3+
#![crate_type = "cdylib"]
4+
5+
#[lang = "sized"]
6+
trait Sized {}
7+
8+
extern "C" {
9+
fn ir_callee();
10+
}
11+
12+
#[no_mangle]
13+
extern "C" fn rs_foo() {
14+
unsafe {
15+
ir_callee();
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Check that -C lto=fat with -C linker-plugin-lto actually works and can inline functions.
2+
// A library is created from LLVM IR, defining a single function. Then a dylib is compiled,
3+
// linking to the library and calling the function from the library.
4+
// The function from the library should end up inlined and disappear from the output.
5+
6+
//@ only-x86_64-unknown-linux-gnu
7+
//@ needs-rust-lld
8+
9+
use run_make_support::{dynamic_lib_name, llvm_as, llvm_objdump, rustc};
10+
11+
fn main() {
12+
llvm_as().input("ir.ll").run();
13+
rustc()
14+
.input("main.rs")
15+
.opt_level("3")
16+
.lto("fat")
17+
.linker_plugin_lto("on")
18+
.link_arg("ir.bc")
19+
.arg("-Zlinker-features=+lld")
20+
.run();
21+
22+
llvm_objdump()
23+
.input(dynamic_lib_name("main"))
24+
.arg("--disassemble-symbols=rs_foo")
25+
.run()
26+
// The called function should be inlined.
27+
// Check that we have a ret (to detect tail
28+
// calls with a jmp) and no call.
29+
.assert_stdout_contains("foo")
30+
.assert_stdout_contains("ret")
31+
.assert_stdout_not_contains("call");
32+
}

0 commit comments

Comments
 (0)