summary refs log tree commit diff
path: root/tests
diff options
context:
space:
mode:
authorSamuel Tardieu <sam@rfc1149.net>2025-07-31 13:12:45 +0200
committerGitHub <noreply@github.com>2025-07-31 13:12:45 +0200
commitd8c09c10e5e86559cd49e1081482d2b700ba5ccf (patch)
treed42f502c7e5c9b4f7cac82f1017e774ca0cc1824 /tests
parentcc0a5b73053c62a3df5f84b3ee85079c9b65fa87 (diff)
parent7a127fba657ed627cc4877f2871abaf7d9819caa (diff)
downloadrust-d8c09c10e5e86559cd49e1081482d2b700ba5ccf.tar.gz
rust-d8c09c10e5e86559cd49e1081482d2b700ba5ccf.zip
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
Diffstat (limited to 'tests')
-rw-r--r--tests/run-make/cross-lang-lto-clang/rmake.rs59
-rw-r--r--tests/run-make/fat-then-thin-lto/lib.rs13
-rw-r--r--tests/run-make/fat-then-thin-lto/main.rs11
-rw-r--r--tests/run-make/fat-then-thin-lto/rmake.rs25
-rw-r--r--tests/run-make/linker-plugin-lto-fat/ir.ll6
-rw-r--r--tests/run-make/linker-plugin-lto-fat/main.rs22
-rw-r--r--tests/run-make/linker-plugin-lto-fat/rmake.rs33
7 files changed, 147 insertions, 22 deletions
diff --git a/tests/run-make/cross-lang-lto-clang/rmake.rs b/tests/run-make/cross-lang-lto-clang/rmake.rs
index 3fed6ea2066..f209318abbc 100644
--- a/tests/run-make/cross-lang-lto-clang/rmake.rs
+++ b/tests/run-make/cross-lang-lto-clang/rmake.rs
@@ -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";
 
 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()
+        .lto(lto)
         .linker_plugin_lto("on")
         .output(static_lib_name("rustlib-xlto"))
         .opt_level("2")
@@ -36,30 +46,36 @@ fn main() {
         .input("rustlib.rs")
         .run();
     clang()
-        .lto("thin")
+        .lto(clang_lto)
         .use_ld("lld")
         .arg("-lrustlib-xlto")
         .out_exe("cmain")
         .input("cmain.c")
         .arg("-O3")
         .run();
+
+    let dump = llvm_objdump().disassemble().input("cmain").run();
     // Make sure we don't find a call instruction to the function we expect to
     // always be inlined.
-    llvm_objdump()
-        .disassemble()
-        .input("cmain")
-        .run()
-        .assert_stdout_not_contains_regex(RUST_ALWAYS_INLINED_PATTERN);
+    dump.assert_stdout_not_contains_regex(RUST_ALWAYS_INLINED_PATTERN);
     // As a sanity check, make sure we do find a call instruction to a
     // non-inlined function
-    llvm_objdump()
-        .disassemble()
-        .input("cmain")
-        .run()
-        .assert_stdout_contains_regex(RUST_NEVER_INLINED_PATTERN);
-    clang().input("clib.c").lto("thin").arg("-c").out_exe("clib.o").arg("-O2").run();
+    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+    dump.assert_stdout_contains_regex(RUST_NEVER_INLINED_PATTERN);
+    #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
+    {
+        if fat_lto {
+            // 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();
     rustc()
+        .lto(lto)
         .linker_plugin_lto("on")
         .opt_level("2")
         .linker(&env_var("CLANG"))
@@ -67,14 +83,13 @@ fn main() {
         .input("main.rs")
         .output("rsmain")
         .run();
-    llvm_objdump()
-        .disassemble()
-        .input("rsmain")
-        .run()
-        .assert_stdout_not_contains_regex(C_ALWAYS_INLINED_PATTERN);
-    llvm_objdump()
-        .disassemble()
-        .input("rsmain")
-        .run()
-        .assert_stdout_contains_regex(C_NEVER_INLINED_PATTERN);
+
+    let dump = llvm_objdump().disassemble().input("rsmain").run();
+    dump.assert_stdout_not_contains_regex(C_ALWAYS_INLINED_PATTERN);
+    if fat_lto {
+        // fat lto inlines this anyway
+        dump.assert_stdout_not_contains_regex(C_NEVER_INLINED_PATTERN);
+    } else {
+        dump.assert_stdout_contains_regex(C_NEVER_INLINED_PATTERN);
+    }
 }
diff --git a/tests/run-make/fat-then-thin-lto/lib.rs b/tests/run-make/fat-then-thin-lto/lib.rs
new file mode 100644
index 00000000000..c675dcb6e8a
--- /dev/null
+++ b/tests/run-make/fat-then-thin-lto/lib.rs
@@ -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() {}
diff --git a/tests/run-make/fat-then-thin-lto/main.rs b/tests/run-make/fat-then-thin-lto/main.rs
new file mode 100644
index 00000000000..a3f2e18158b
--- /dev/null
+++ b/tests/run-make/fat-then-thin-lto/main.rs
@@ -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();
+}
diff --git a/tests/run-make/fat-then-thin-lto/rmake.rs b/tests/run-make/fat-then-thin-lto/rmake.rs
new file mode 100644
index 00000000000..ef4f26689d4
--- /dev/null
+++ b/tests/run-make/fat-then-thin-lto/rmake.rs
@@ -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");
+}
diff --git a/tests/run-make/linker-plugin-lto-fat/ir.ll b/tests/run-make/linker-plugin-lto-fat/ir.ll
new file mode 100644
index 00000000000..fa3dbdd4e08
--- /dev/null
+++ b/tests/run-make/linker-plugin-lto-fat/ir.ll
@@ -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
+}
diff --git a/tests/run-make/linker-plugin-lto-fat/main.rs b/tests/run-make/linker-plugin-lto-fat/main.rs
new file mode 100644
index 00000000000..ad2a90bc801
--- /dev/null
+++ b/tests/run-make/linker-plugin-lto-fat/main.rs
@@ -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();
+    }
+}
diff --git a/tests/run-make/linker-plugin-lto-fat/rmake.rs b/tests/run-make/linker-plugin-lto-fat/rmake.rs
new file mode 100644
index 00000000000..ff5b647a594
--- /dev/null
+++ b/tests/run-make/linker-plugin-lto-fat/rmake.rs
@@ -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");
+}