From 2155b4885284b79eab7f0349ba7127d041e8da29 Mon Sep 17 00:00:00 2001 From: Paul Kirth Date: Mon, 1 Jun 2026 19:01:31 -0700 Subject: [PATCH] Emit the direct-access-external-data module flag in LLVM When compiling with ThinLTO, the missing module flag will cause the compiler to drop the `dso_local` from external data, leading to GOT slots being used. This breaks Rust usage in the Fuchsia kernel, and for embedded code that cannot use GOT indirection whenever ThinLTO is at play due to incorrect setting of the relocation model in the LLVM module when they are merged. Handling the relocation model will be done separately, as this module flag is a pre-requisite of for that change to affect direct-access-external-data. This patch ensures we emit the right module flag for the option. This matches clang's behavior. See https://github.com/llvm/llvm-project/blob/dbab3f71775f6edd11546385aeddb9d0b053a2b5/clang/lib/CodeGen/CodeGenModule.cpp#L1664 --- compiler/rustc_codegen_llvm/src/context.rs | 9 + .../direct-access-external-data.rs | 7 + .../lto-direct-access-external-data/dep.rs | 25 +++ .../lto-direct-access-external-data/lib.rs | 74 +++++++ .../lto-direct-access-external-data/rmake.rs | 182 ++++++++++++++++++ 5 files changed, 297 insertions(+) create mode 100644 tests/run-make/lto-direct-access-external-data/dep.rs create mode 100644 tests/run-make/lto-direct-access-external-data/lib.rs create mode 100644 tests/run-make/lto-direct-access-external-data/rmake.rs diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 621f2cd3f9fc7..46cf00f35db6e 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -517,6 +517,15 @@ pub(crate) unsafe fn create_module<'ll>( ); } + if let Some(direct_access_external_data) = sess.direct_access_external_data() { + llvm::add_module_flag_u32( + llmod, + llvm::ModuleFlagMergeBehavior::Max, + "direct-access-external-data", + direct_access_external_data as u32, + ); + } + match (sess.opts.unstable_opts.small_data_threshold, sess.target.small_data_threshold_support()) { // Set up the small-data optimization limit for architectures that use diff --git a/tests/codegen-llvm/direct-access-external-data.rs b/tests/codegen-llvm/direct-access-external-data.rs index a151bb6012e1e..092d8a692c7f2 100644 --- a/tests/codegen-llvm/direct-access-external-data.rs +++ b/tests/codegen-llvm/direct-access-external-data.rs @@ -47,3 +47,10 @@ pub fn refer() { core::hint::black_box(EXTERNAL); core::hint::black_box(WEAK); } + +// DIRECT: !{{[0-9]+}} = !{i32 7, !"direct-access-external-data", i32 1} + +// INDIRECT: !{{[0-9]+}} = !{i32 7, !"direct-access-external-data", i32 0} + +// DEFAULT-NOT: direct-access-external-data +// PIE-NOT: direct-access-external-data diff --git a/tests/run-make/lto-direct-access-external-data/dep.rs b/tests/run-make/lto-direct-access-external-data/dep.rs new file mode 100644 index 0000000000000..02ca2d23ddb00 --- /dev/null +++ b/tests/run-make/lto-direct-access-external-data/dep.rs @@ -0,0 +1,25 @@ +#![feature(no_core, lang_items)] +#![no_std] +#![no_core] +#![crate_type = "lib"] + +#[lang = "pointee_sized"] +trait PointeeSized {} +#[lang = "meta_sized"] +trait MetaSized: PointeeSized {} +#[lang = "sized"] +trait Sized: MetaSized {} + +#[lang = "copy"] +pub trait Copy {} + +impl Copy for i32 {} + +unsafe extern "C" { + pub safe static VAR: i32; +} + +#[no_mangle] +pub fn refer_dep() -> i32 { + unsafe { VAR } +} diff --git a/tests/run-make/lto-direct-access-external-data/lib.rs b/tests/run-make/lto-direct-access-external-data/lib.rs new file mode 100644 index 0000000000000..9ffe850080e51 --- /dev/null +++ b/tests/run-make/lto-direct-access-external-data/lib.rs @@ -0,0 +1,74 @@ +#![feature(no_core, lang_items)] +#![no_std] +#![no_core] + +extern crate dep; + +unsafe extern "C" { + pub safe static VAR: i32; +} + +pub mod mod_0 { + use super::VAR; + #[no_mangle] + pub fn refer_0() -> i32 { + VAR + } +} + +pub mod mod_1 { + use super::VAR; + #[no_mangle] + pub fn refer_1() -> i32 { + VAR + } +} + +#[no_mangle] +pub fn call_dep() -> i32 { + dep::refer_dep() +} + +// DEFAULT: @VAR = {{.*}}dso_local{{.*}}global i32 +// DEFAULT-NOT: direct-access-external-data +// DEFAULT-NOT: PIE Level + +// PIE: @VAR = external +// PIE-NOT: dso_local +// PIE-SAME: global i32 +// PIE-NOT: direct-access-external-data +// PIE: !{{[0-9]+}} = !{i32 7, !"PIE Level", i32 2} + +// DIRECT: @VAR = {{.*}}dso_local{{.*}}global i32 +// DIRECT-DAG: !{{[0-9]+}} = !{i32 7, !"direct-access-external-data", i32 1} +// DIRECT-DAG: !{{[0-9]+}} = !{i32 7, !"PIE Level", i32 2} + +// INDIRECT: @VAR = external +// INDIRECT-NOT: dso_local +// INDIRECT-SAME: global i32 +// INDIRECT: !{{[0-9]+}} = !{i32 7, !"direct-access-external-data", i32 0} +// INDIRECT-NOT: PIE Level + +// FIXME: Under normal circumstances PIE Level should be set consistently, so +// GOT relocations don't get emitted with direct-access-external-data enabled +// for all versions of Full/Thin LTO. +// DEP-NO-PIE-NOT: PIE Level + +// Demonstrate incorrect GOT relocation under direct-access-external-data for ThinLTO. +// Even with direct-access enabled, the missing PIE Level on the dependency's module +// causes LLVM to fall back to GOT indirection. +// DIRECT-RELOC-THIN: R_X86_64_GOTPCREL{{.*}}VAR + +// For Full LTO we expect direct PC-relative access since the merged module +// correctly inherits the main module's PIE Level. +// DIRECT-RELOC-FAT-NOT: R_X86_64_GOTPCREL{{.*}}VAR +// DIRECT-RELOC-FAT: R_X86_64_PC32{{.*}}VAR + +// For indirect cases, we always expect GOT indirection. +// INDIRECT-RELOC: R_X86_64_GOTPCREL{{.*}}VAR +// INDIRECT-RELOC-NOT: R_X86_64_PC32{{.*}}VAR + +// Default PIE without direct-access enabled should use GOT indirection. +// This is correct and is not changed by setting PIE Level consistently. +// PIE-RELOC: R_X86_64_GOTPCREL{{.*}}VAR +// PIE-RELOC-NOT: R_X86_64_PC32{{.*}}VAR diff --git a/tests/run-make/lto-direct-access-external-data/rmake.rs b/tests/run-make/lto-direct-access-external-data/rmake.rs new file mode 100644 index 0000000000000..8ecad8284accc --- /dev/null +++ b/tests/run-make/lto-direct-access-external-data/rmake.rs @@ -0,0 +1,182 @@ +//@ needs-llvm-components: x86 +//@ ignore-loongarch64 (handles dso_local differently) +//@ ignore-powerpc64 (handles dso_local differently) +//@ ignore-apple (handles dso_local differently) + +// Test the various interleavings of LTO and relocation model. +// We know that in some cases the -Zdirect-access-external-data option will not +// result in the correct code generation due to the module having +// inconsistently set PIE Level. Test those cases so that the result is clear +// when future patches fix the problem. + +use run_make_support::{ + cwd, has_extension, llvm_dis, llvm_filecheck, llvm_readobj, rfs, rustc, shallow_find_files, +}; + +struct TestCase { + lto: &'static str, + reloc: &'static str, + direct_access: Option<&'static str>, + expected_prefix: &'static str, + dep_prefixes: &'static [&'static str], +} + +fn main() { + let test_cases = [ + TestCase { + lto: "thin", + reloc: "static", + direct_access: None, + expected_prefix: "DEFAULT", + dep_prefixes: &[], + }, + TestCase { + lto: "fat", + reloc: "static", + direct_access: None, + expected_prefix: "DEFAULT", + dep_prefixes: &[], + }, + TestCase { + lto: "thin", + reloc: "pie", + direct_access: None, + expected_prefix: "PIE", + dep_prefixes: &["DEP-NO-PIE"], + }, + TestCase { + lto: "fat", + reloc: "pie", + direct_access: None, + expected_prefix: "PIE", + dep_prefixes: &[], + }, + TestCase { + lto: "thin", + reloc: "pie", + direct_access: Some("yes"), + expected_prefix: "DIRECT", + dep_prefixes: &["DEP-NO-PIE"], + }, + TestCase { + lto: "fat", + reloc: "pie", + direct_access: Some("yes"), + expected_prefix: "DIRECT", + dep_prefixes: &[], + }, + TestCase { + lto: "thin", + reloc: "static", + direct_access: Some("no"), + expected_prefix: "INDIRECT", + dep_prefixes: &[], + }, + TestCase { + lto: "fat", + reloc: "static", + direct_access: Some("no"), + expected_prefix: "INDIRECT", + dep_prefixes: &[], + }, + ]; + + for case in test_cases { + // Remove all output files that are not source Rust code for cleanup. + for file in shallow_find_files(cwd(), |path| !has_extension(path, "rs")) { + rfs::remove_file(file); + } + + let mut cmd = rustc(); + cmd.input("dep.rs") + .target("x86_64-unknown-linux-gnu") + .crate_type("rlib") + .crate_name("dep") + .arg("-Cpanic=abort"); + + if let Some(da) = case.direct_access { + cmd.arg(format!("-Zdirect-access-external-data={da}")); + } + cmd.run(); + + cmd = rustc(); + cmd.input("lib.rs") + .target("x86_64-unknown-linux-gnu") + .crate_type("staticlib") + .crate_name("lto_direct_access_test") + .extern_("dep", "libdep.rlib") + .arg("-Cpanic=abort") + .arg("-Csave-temps") + .arg(format!("-Clto={}", case.lto)) + .arg(format!("-Crelocation-model={}", case.reloc)) + .arg("-Ccodegen-units=2"); + + if let Some(da) = case.direct_access { + cmd.arg(format!("-Zdirect-access-external-data={da}")); + } + cmd.run(); + + let suffix = if case.lto == "thin" { + ".rcgu.thin-lto-after-pm.bc" + } else { + ".rcgu.lto.after-restriction.bc" + }; + + let bc_files = shallow_find_files(".", |path| { + let name = path.file_name().unwrap().to_str().unwrap(); + name.starts_with("lto_direct_access_test.lto_direct_access_test.") + && name.ends_with(suffix) + }); + assert!(!bc_files.is_empty(), "No expected bitcode files were generated"); + + for bc_file in &bc_files { + llvm_dis().input(bc_file).run(); + let ll_file = bc_file.with_extension("ll"); + llvm_filecheck() + .input_file(&ll_file) + .patterns("lib.rs") + .check_prefix(case.expected_prefix) + .run(); + } + + let relocs = llvm_readobj().arg("--relocations").input("liblto_direct_access_test.a").run(); + let relocs_out = "relocs.txt"; + rfs::write(relocs_out, relocs.stdout_utf8()); + + let reloc_prefix = match case.expected_prefix { + "DIRECT" => { + if case.lto == "thin" { + Some("DIRECT-RELOC-THIN") + } else { + Some("DIRECT-RELOC-FAT") + } + } + "PIE" => Some("PIE-RELOC"), + "INDIRECT" => Some("INDIRECT-RELOC"), + _ => None, + }; + + if let Some(prefix) = reloc_prefix { + llvm_filecheck().input_file(relocs_out).patterns("lib.rs").check_prefix(prefix).run(); + } + + if !case.dep_prefixes.is_empty() { + let dep_bc_files = shallow_find_files(".", |path| { + let name = path.file_name().unwrap().to_str().unwrap(); + name.contains("dep") && name.ends_with(suffix) + }); + assert!(!dep_bc_files.is_empty(), "dep bitcode files not found"); + for dep_bc in &dep_bc_files { + llvm_dis().input(dep_bc).run(); + let dep_ll = dep_bc.with_extension("ll"); + for dep_cp in case.dep_prefixes { + llvm_filecheck() + .input_file(&dep_ll) + .patterns("lib.rs") + .check_prefix(dep_cp) + .run(); + } + } + } + } +}