Skip to content

Commit

Permalink
Fix linker-plugin-lto only doing thin lto
Browse files Browse the repository at this point in the history
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.

Set the `ThinLTO=0` module flag to signal lld to do fat lto.

The analogous 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
  • Loading branch information
Flakebi committed Feb 19, 2025
1 parent c03c38d commit 2d33f69
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 2 deletions.
7 changes: 6 additions & 1 deletion compiler/rustc_codegen_llvm/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ use rustc_middle::ty::layout::{
};
use rustc_middle::ty::{self, Instance, Ty, TyCtxt};
use rustc_middle::{bug, span_bug};
use rustc_session::Session;
use rustc_session::config::{
BranchProtection, CFGuard, CFProtection, CrateType, DebugInfo, FunctionReturn, PAuthKey, PacRet,
};
use rustc_session::{Session, config};
use rustc_span::source_map::Spanned;
use rustc_span::{DUMMY_SP, Span};
use rustc_target::spec::{HasTargetSpec, RelocModel, SmallDataThresholdSupport, Target, TlsModel};
Expand Down Expand Up @@ -290,6 +290,11 @@ pub(crate) unsafe fn create_module<'ll>(
);
}

// If fat lto is requested, lld still defaults to thin lto. Set ThinLTO=0 to force fat lto in lld.
if sess.lto() == config::Lto::Fat {
llvm::add_module_flag_u32(llmod, llvm::ModuleFlagMergeBehavior::Override, "ThinLTO", 0);
}

// Add "kcfi" module flag if KCFI is enabled. (See https://reviews.llvm.org/D119296.)
if sess.is_sanitizer_kcfi_enabled() {
llvm::add_module_flag_u32(llmod, llvm::ModuleFlagMergeBehavior::Override, "kcfi", 1);
Expand Down
30 changes: 30 additions & 0 deletions src/tools/run-make-support/src/external_deps/llvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ pub fn llvm_pdbutil() -> LlvmPdbutil {
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
/// at `$LLVM_BIN_DIR/llvm-dis`.
pub fn llvm_dis() -> LlvmDis {
Expand Down Expand Up @@ -135,6 +141,13 @@ pub struct LlvmPdbutil {
cmd: Command,
}

/// A `llvm-as` invocation builder.
#[derive(Debug)]
#[must_use]
pub struct LlvmAs {
cmd: Command,
}

/// A `llvm-dis` invocation builder.
#[derive(Debug)]
#[must_use]
Expand All @@ -158,6 +171,7 @@ crate::macros::impl_common_helpers!(LlvmNm);
crate::macros::impl_common_helpers!(LlvmBcanalyzer);
crate::macros::impl_common_helpers!(LlvmDwarfdump);
crate::macros::impl_common_helpers!(LlvmPdbutil);
crate::macros::impl_common_helpers!(LlvmAs);
crate::macros::impl_common_helpers!(LlvmDis);
crate::macros::impl_common_helpers!(LlvmObjcopy);

Expand Down Expand Up @@ -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 {
/// Construct a new `llvm-dis` invocation. This assumes that `llvm-dis` is available
/// at `$LLVM_BIN_DIR/llvm-dis`.
Expand Down
2 changes: 1 addition & 1 deletion src/tools/run-make-support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ pub use cargo::cargo;
pub use clang::{clang, Clang};
pub use htmldocck::htmldocck;
pub use llvm::{
llvm_ar, llvm_bcanalyzer, llvm_dis, llvm_dwarfdump, llvm_filecheck, llvm_nm, llvm_objcopy,
llvm_ar, llvm_bcanalyzer, llvm_as, llvm_dis, llvm_dwarfdump, llvm_filecheck, llvm_nm, llvm_objcopy,
llvm_objdump, llvm_profdata, llvm_readobj, LlvmAr, LlvmBcanalyzer, LlvmDis, LlvmDwarfdump,
LlvmFilecheck, LlvmNm, LlvmObjcopy, LlvmObjdump, LlvmProfdata, LlvmReadobj,
};
Expand Down
8 changes: 8 additions & 0 deletions tests/run-make/fat-then-thin-lto/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#![feature(no_core, lang_items)]
#![no_core]
#![crate_type = "rlib"]

#[lang = "sized"]
trait Sized {}

pub fn foo() {}
11 changes: 11 additions & 0 deletions tests/run-make/fat-then-thin-lto/main.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 25 additions & 0 deletions tests/run-make/fat-then-thin-lto/rmake.rs
Original file line number Diff line number Diff line change
@@ -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").arg("-Clto=fat").run();
rustc().input("main.rs").panic("abort").opt_level("3").arg("-Clto=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 changes: 6 additions & 0 deletions tests/run-make/linker-plugin-lto-fat/ir.ll
Original file line number Diff line number Diff line change
@@ -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
}
17 changes: 17 additions & 0 deletions tests/run-make/linker-plugin-lto-fat/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#![feature(no_core, lang_items)]
#![no_core]
#![crate_type = "cdylib"]

#[lang = "sized"]
trait Sized {}

extern "C" {
fn ir_callee();
}

#[no_mangle]
extern "C" fn rs_foo() {
unsafe {
ir_callee();
}
}
28 changes: 28 additions & 0 deletions tests/run-make/linker-plugin-lto-fat/rmake.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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

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")
.args(&["-Clto=fat", "-Clinker-plugin-lto", "-Clink-arg=ir.bc"])
.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");
}

0 comments on commit 2d33f69

Please sign in to comment.