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:560149b5e3/clang/lib/CodeGen/BackendUtil.cpp (L1150)The code in LLVM that queries the flag and defaults to thin lto if not set is here:e258bca950/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp (L4441-L4446)
This commit is contained in:
@@ -215,7 +215,9 @@ impl ModuleConfig {
|
|||||||
false
|
false
|
||||||
),
|
),
|
||||||
emit_obj,
|
emit_obj,
|
||||||
emit_thin_lto: sess.opts.unstable_opts.emit_thin_lto,
|
// thin lto summaries prevent fat lto, so do not emit them if fat
|
||||||
|
// lto is requested. See PR #136840 for background information.
|
||||||
|
emit_thin_lto: sess.opts.unstable_opts.emit_thin_lto && sess.lto() != Lto::Fat,
|
||||||
emit_thin_lto_summary: if_regular!(
|
emit_thin_lto_summary: if_regular!(
|
||||||
sess.opts.output_types.contains_key(&OutputType::ThinLinkBitcode),
|
sess.opts.output_types.contains_key(&OutputType::ThinLinkBitcode),
|
||||||
false
|
false
|
||||||
|
|||||||
@@ -60,6 +60,12 @@ pub fn llvm_pdbutil() -> LlvmPdbutil {
|
|||||||
LlvmPdbutil::new()
|
LlvmPdbutil::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Construct a new `llvm-as` invocation. This assumes that `llvm-as` is available
|
||||||
|
/// at `$LLVM_BIN_DIR/llvm-as`.
|
||||||
|
pub fn llvm_as() -> LlvmAs {
|
||||||
|
LlvmAs::new()
|
||||||
|
}
|
||||||
|
|
||||||
/// Construct a new `llvm-dis` invocation. This assumes that `llvm-dis` is available
|
/// Construct a new `llvm-dis` invocation. This assumes that `llvm-dis` is available
|
||||||
/// at `$LLVM_BIN_DIR/llvm-dis`.
|
/// at `$LLVM_BIN_DIR/llvm-dis`.
|
||||||
pub fn llvm_dis() -> LlvmDis {
|
pub fn llvm_dis() -> LlvmDis {
|
||||||
@@ -135,6 +141,13 @@ pub struct LlvmPdbutil {
|
|||||||
cmd: Command,
|
cmd: Command,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A `llvm-as` invocation builder.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[must_use]
|
||||||
|
pub struct LlvmAs {
|
||||||
|
cmd: Command,
|
||||||
|
}
|
||||||
|
|
||||||
/// A `llvm-dis` invocation builder.
|
/// A `llvm-dis` invocation builder.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@@ -158,6 +171,7 @@ crate::macros::impl_common_helpers!(LlvmNm);
|
|||||||
crate::macros::impl_common_helpers!(LlvmBcanalyzer);
|
crate::macros::impl_common_helpers!(LlvmBcanalyzer);
|
||||||
crate::macros::impl_common_helpers!(LlvmDwarfdump);
|
crate::macros::impl_common_helpers!(LlvmDwarfdump);
|
||||||
crate::macros::impl_common_helpers!(LlvmPdbutil);
|
crate::macros::impl_common_helpers!(LlvmPdbutil);
|
||||||
|
crate::macros::impl_common_helpers!(LlvmAs);
|
||||||
crate::macros::impl_common_helpers!(LlvmDis);
|
crate::macros::impl_common_helpers!(LlvmDis);
|
||||||
crate::macros::impl_common_helpers!(LlvmObjcopy);
|
crate::macros::impl_common_helpers!(LlvmObjcopy);
|
||||||
|
|
||||||
@@ -441,6 +455,22 @@ impl LlvmObjcopy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LlvmAs {
|
||||||
|
/// Construct a new `llvm-as` invocation. This assumes that `llvm-as` is available
|
||||||
|
/// at `$LLVM_BIN_DIR/llvm-as`.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let llvm_as = llvm_bin_dir().join("llvm-as");
|
||||||
|
let cmd = Command::new(llvm_as);
|
||||||
|
Self { cmd }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provide an input file.
|
||||||
|
pub fn input<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
|
||||||
|
self.cmd.arg(path.as_ref());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl LlvmDis {
|
impl LlvmDis {
|
||||||
/// Construct a new `llvm-dis` invocation. This assumes that `llvm-dis` is available
|
/// Construct a new `llvm-dis` invocation. This assumes that `llvm-dis` is available
|
||||||
/// at `$LLVM_BIN_DIR/llvm-dis`.
|
/// at `$LLVM_BIN_DIR/llvm-dis`.
|
||||||
|
|||||||
@@ -173,6 +173,12 @@ impl Rustc {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This flag enables LTO in the specified form.
|
||||||
|
pub fn lto(&mut self, option: &str) -> &mut Self {
|
||||||
|
self.cmd.arg(format!("-Clto={option}"));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// This flag defers LTO optimizations to the linker.
|
/// This flag defers LTO optimizations to the linker.
|
||||||
pub fn linker_plugin_lto(&mut self, option: &str) -> &mut Self {
|
pub fn linker_plugin_lto(&mut self, option: &str) -> &mut Self {
|
||||||
self.cmd.arg(format!("-Clinker-plugin-lto={option}"));
|
self.cmd.arg(format!("-Clinker-plugin-lto={option}"));
|
||||||
|
|||||||
@@ -63,8 +63,9 @@ pub use crate::external_deps::clang::{Clang, clang};
|
|||||||
pub use crate::external_deps::htmldocck::htmldocck;
|
pub use crate::external_deps::htmldocck::htmldocck;
|
||||||
pub use crate::external_deps::llvm::{
|
pub use crate::external_deps::llvm::{
|
||||||
self, LlvmAr, LlvmBcanalyzer, LlvmDis, LlvmDwarfdump, LlvmFilecheck, LlvmNm, LlvmObjcopy,
|
self, LlvmAr, LlvmBcanalyzer, LlvmDis, LlvmDwarfdump, LlvmFilecheck, LlvmNm, LlvmObjcopy,
|
||||||
LlvmObjdump, LlvmProfdata, LlvmReadobj, llvm_ar, llvm_bcanalyzer, llvm_dis, llvm_dwarfdump,
|
LlvmObjdump, LlvmProfdata, LlvmReadobj, llvm_ar, llvm_as, llvm_bcanalyzer, llvm_dis,
|
||||||
llvm_filecheck, llvm_nm, llvm_objcopy, llvm_objdump, llvm_profdata, llvm_readobj,
|
llvm_dwarfdump, llvm_filecheck, llvm_nm, llvm_objcopy, llvm_objdump, llvm_profdata,
|
||||||
|
llvm_readobj,
|
||||||
};
|
};
|
||||||
pub use crate::external_deps::python::python_command;
|
pub use crate::external_deps::python::python_command;
|
||||||
pub use crate::external_deps::rustc::{self, Rustc, bare_rustc, rustc, rustc_path};
|
pub use crate::external_deps::rustc::{self, Rustc, bare_rustc, rustc, rustc_path};
|
||||||
|
|||||||
@@ -28,7 +28,17 @@ static C_NEVER_INLINED_PATTERN: &'static str = "bl.*<c_never_inlined>";
|
|||||||
static C_NEVER_INLINED_PATTERN: &'static str = "call.*c_never_inlined";
|
static C_NEVER_INLINED_PATTERN: &'static str = "call.*c_never_inlined";
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
test_lto(false);
|
||||||
|
test_lto(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_lto(fat_lto: bool) {
|
||||||
|
let lto = if fat_lto { "fat" } else { "thin" };
|
||||||
|
let clang_lto = if fat_lto { "full" } else { "thin" };
|
||||||
|
println!("Running {lto} lto");
|
||||||
|
|
||||||
rustc()
|
rustc()
|
||||||
|
.lto(lto)
|
||||||
.linker_plugin_lto("on")
|
.linker_plugin_lto("on")
|
||||||
.output(static_lib_name("rustlib-xlto"))
|
.output(static_lib_name("rustlib-xlto"))
|
||||||
.opt_level("2")
|
.opt_level("2")
|
||||||
@@ -36,30 +46,36 @@ fn main() {
|
|||||||
.input("rustlib.rs")
|
.input("rustlib.rs")
|
||||||
.run();
|
.run();
|
||||||
clang()
|
clang()
|
||||||
.lto("thin")
|
.lto(clang_lto)
|
||||||
.use_ld("lld")
|
.use_ld("lld")
|
||||||
.arg("-lrustlib-xlto")
|
.arg("-lrustlib-xlto")
|
||||||
.out_exe("cmain")
|
.out_exe("cmain")
|
||||||
.input("cmain.c")
|
.input("cmain.c")
|
||||||
.arg("-O3")
|
.arg("-O3")
|
||||||
.run();
|
.run();
|
||||||
|
|
||||||
|
let dump = llvm_objdump().disassemble().input("cmain").run();
|
||||||
// Make sure we don't find a call instruction to the function we expect to
|
// Make sure we don't find a call instruction to the function we expect to
|
||||||
// always be inlined.
|
// always be inlined.
|
||||||
llvm_objdump()
|
dump.assert_stdout_not_contains_regex(RUST_ALWAYS_INLINED_PATTERN);
|
||||||
.disassemble()
|
|
||||||
.input("cmain")
|
|
||||||
.run()
|
|
||||||
.assert_stdout_not_contains_regex(RUST_ALWAYS_INLINED_PATTERN);
|
|
||||||
// As a sanity check, make sure we do find a call instruction to a
|
// As a sanity check, make sure we do find a call instruction to a
|
||||||
// non-inlined function
|
// non-inlined function
|
||||||
llvm_objdump()
|
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||||
.disassemble()
|
dump.assert_stdout_contains_regex(RUST_NEVER_INLINED_PATTERN);
|
||||||
.input("cmain")
|
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||||
.run()
|
{
|
||||||
.assert_stdout_contains_regex(RUST_NEVER_INLINED_PATTERN);
|
if fat_lto {
|
||||||
clang().input("clib.c").lto("thin").arg("-c").out_exe("clib.o").arg("-O2").run();
|
// fat lto inlines this anyway
|
||||||
|
dump.assert_stdout_not_contains_regex(RUST_NEVER_INLINED_PATTERN);
|
||||||
|
} else {
|
||||||
|
dump.assert_stdout_contains_regex(RUST_NEVER_INLINED_PATTERN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clang().input("clib.c").lto(clang_lto).arg("-c").out_exe("clib.o").arg("-O2").run();
|
||||||
llvm_ar().obj_to_ar().output_input(static_lib_name("xyz"), "clib.o").run();
|
llvm_ar().obj_to_ar().output_input(static_lib_name("xyz"), "clib.o").run();
|
||||||
rustc()
|
rustc()
|
||||||
|
.lto(lto)
|
||||||
.linker_plugin_lto("on")
|
.linker_plugin_lto("on")
|
||||||
.opt_level("2")
|
.opt_level("2")
|
||||||
.linker(&env_var("CLANG"))
|
.linker(&env_var("CLANG"))
|
||||||
@@ -67,14 +83,13 @@ fn main() {
|
|||||||
.input("main.rs")
|
.input("main.rs")
|
||||||
.output("rsmain")
|
.output("rsmain")
|
||||||
.run();
|
.run();
|
||||||
llvm_objdump()
|
|
||||||
.disassemble()
|
let dump = llvm_objdump().disassemble().input("rsmain").run();
|
||||||
.input("rsmain")
|
dump.assert_stdout_not_contains_regex(C_ALWAYS_INLINED_PATTERN);
|
||||||
.run()
|
if fat_lto {
|
||||||
.assert_stdout_not_contains_regex(C_ALWAYS_INLINED_PATTERN);
|
// fat lto inlines this anyway
|
||||||
llvm_objdump()
|
dump.assert_stdout_not_contains_regex(C_NEVER_INLINED_PATTERN);
|
||||||
.disassemble()
|
} else {
|
||||||
.input("rsmain")
|
dump.assert_stdout_contains_regex(C_NEVER_INLINED_PATTERN);
|
||||||
.run()
|
}
|
||||||
.assert_stdout_contains_regex(C_NEVER_INLINED_PATTERN);
|
|
||||||
}
|
}
|
||||||
|
|||||||
13
tests/run-make/fat-then-thin-lto/lib.rs
Normal file
13
tests/run-make/fat-then-thin-lto/lib.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#![allow(internal_features)]
|
||||||
|
#![feature(no_core, lang_items)]
|
||||||
|
#![no_core]
|
||||||
|
#![crate_type = "rlib"]
|
||||||
|
|
||||||
|
#[lang = "pointee_sized"]
|
||||||
|
trait PointeeSized {}
|
||||||
|
#[lang = "meta_sized"]
|
||||||
|
trait MetaSized: PointeeSized {}
|
||||||
|
#[lang = "sized"]
|
||||||
|
trait Sized: MetaSized {}
|
||||||
|
|
||||||
|
pub fn foo() {}
|
||||||
11
tests/run-make/fat-then-thin-lto/main.rs
Normal file
11
tests/run-make/fat-then-thin-lto/main.rs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#![allow(internal_features)]
|
||||||
|
#![feature(no_core, lang_items)]
|
||||||
|
#![no_core]
|
||||||
|
#![crate_type = "cdylib"]
|
||||||
|
|
||||||
|
extern crate lib;
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub fn bar() {
|
||||||
|
lib::foo();
|
||||||
|
}
|
||||||
25
tests/run-make/fat-then-thin-lto/rmake.rs
Normal file
25
tests/run-make/fat-then-thin-lto/rmake.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// Compile a library with lto=fat, then compile a binary with lto=thin
|
||||||
|
// and check that lto is applied with the library.
|
||||||
|
// The goal is to mimic the standard library being build with lto=fat
|
||||||
|
// and allowing users to build with lto=thin.
|
||||||
|
|
||||||
|
//@ only-x86_64-unknown-linux-gnu
|
||||||
|
|
||||||
|
use run_make_support::{dynamic_lib_name, llvm_objdump, rustc};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
rustc().input("lib.rs").opt_level("3").lto("fat").run();
|
||||||
|
rustc().input("main.rs").panic("abort").opt_level("3").lto("thin").run();
|
||||||
|
|
||||||
|
llvm_objdump()
|
||||||
|
.input(dynamic_lib_name("main"))
|
||||||
|
.arg("--disassemble-symbols=bar")
|
||||||
|
.run()
|
||||||
|
// The called function should be inlined.
|
||||||
|
// Check that we have a ret (to detect tail
|
||||||
|
// calls with a jmp) and no call.
|
||||||
|
.assert_stdout_contains("bar")
|
||||||
|
.assert_stdout_contains("ret")
|
||||||
|
.assert_stdout_not_contains("foo")
|
||||||
|
.assert_stdout_not_contains("call");
|
||||||
|
}
|
||||||
6
tests/run-make/linker-plugin-lto-fat/ir.ll
Normal file
6
tests/run-make/linker-plugin-lto-fat/ir.ll
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||||
|
target triple = "x86_64-unknown-linux-gnu"
|
||||||
|
|
||||||
|
define void @ir_callee() {
|
||||||
|
ret void
|
||||||
|
}
|
||||||
22
tests/run-make/linker-plugin-lto-fat/main.rs
Normal file
22
tests/run-make/linker-plugin-lto-fat/main.rs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#![allow(internal_features)]
|
||||||
|
#![feature(no_core, lang_items)]
|
||||||
|
#![no_core]
|
||||||
|
#![crate_type = "cdylib"]
|
||||||
|
|
||||||
|
#[lang = "pointee_sized"]
|
||||||
|
trait PointeeSized {}
|
||||||
|
#[lang = "meta_sized"]
|
||||||
|
trait MetaSized: PointeeSized {}
|
||||||
|
#[lang = "sized"]
|
||||||
|
trait Sized: MetaSized {}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
fn ir_callee();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
extern "C" fn rs_foo() {
|
||||||
|
unsafe {
|
||||||
|
ir_callee();
|
||||||
|
}
|
||||||
|
}
|
||||||
33
tests/run-make/linker-plugin-lto-fat/rmake.rs
Normal file
33
tests/run-make/linker-plugin-lto-fat/rmake.rs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// Check that -C lto=fat with -C linker-plugin-lto actually works and can inline functions.
|
||||||
|
// A library is created from LLVM IR, defining a single function. Then a dylib is compiled,
|
||||||
|
// linking to the library and calling the function from the library.
|
||||||
|
// The function from the library should end up inlined and disappear from the output.
|
||||||
|
|
||||||
|
//@ only-x86_64-unknown-linux-gnu
|
||||||
|
//@ needs-rust-lld
|
||||||
|
|
||||||
|
use run_make_support::{dynamic_lib_name, llvm_as, llvm_objdump, rustc};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
llvm_as().input("ir.ll").run();
|
||||||
|
rustc()
|
||||||
|
.input("main.rs")
|
||||||
|
.opt_level("3")
|
||||||
|
.lto("fat")
|
||||||
|
.linker_plugin_lto("on")
|
||||||
|
.link_arg("ir.bc")
|
||||||
|
.arg("-Zunstable-options")
|
||||||
|
.arg("-Clinker-features=+lld")
|
||||||
|
.run();
|
||||||
|
|
||||||
|
llvm_objdump()
|
||||||
|
.input(dynamic_lib_name("main"))
|
||||||
|
.arg("--disassemble-symbols=rs_foo")
|
||||||
|
.run()
|
||||||
|
// The called function should be inlined.
|
||||||
|
// Check that we have a ret (to detect tail
|
||||||
|
// calls with a jmp) and no call.
|
||||||
|
.assert_stdout_contains("foo")
|
||||||
|
.assert_stdout_contains("ret")
|
||||||
|
.assert_stdout_not_contains("call");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user