Skip to content

Commit 660d1e2

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 660d1e2

File tree

11 files changed

+157
-9
lines changed

11 files changed

+157
-9
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 && config.emit_thin_lto, 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+
}
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)