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(); + } + } + } + } +}