diff --git a/tests/run-make/compiler-builtins/Cargo.toml b/tests/run-make/compiler-builtins-linkage/Cargo.toml similarity index 100% rename from tests/run-make/compiler-builtins/Cargo.toml rename to tests/run-make/compiler-builtins-linkage/Cargo.toml diff --git a/tests/run-make/compiler-builtins/lib.rs b/tests/run-make/compiler-builtins-linkage/lib.rs similarity index 100% rename from tests/run-make/compiler-builtins/lib.rs rename to tests/run-make/compiler-builtins-linkage/lib.rs diff --git a/tests/run-make/compiler-builtins/rmake.rs b/tests/run-make/compiler-builtins-linkage/rmake.rs similarity index 92% rename from tests/run-make/compiler-builtins/rmake.rs rename to tests/run-make/compiler-builtins-linkage/rmake.rs index 10093db2258df..e45b38003ed23 100644 --- a/tests/run-make/compiler-builtins/rmake.rs +++ b/tests/run-make/compiler-builtins-linkage/rmake.rs @@ -1,4 +1,7 @@ -//! The compiler_builtins library is special. It can call functions in core, but it must not +//! The compiler_builtins library is special. When linkers are passed multiple libraries, they +//! expect undefined symbols mentioned by libraries on the left to be defined in libraries to the +//! right. Since calls to compiler_builtins may be inserted during codegen, it is placed all the way +//! to the right. Therefore, compiler_builtins can call functions in core but it must not //! require linkage against a build of core. If it ever does, building the standard library *may* //! result in linker errors, depending on whether the linker in use applies optimizations first or //! resolves symbols first. So the portable and safe approach is to forbid such a linkage diff --git a/tests/run-make/compiler-builtins-partitioning/Cargo.toml b/tests/run-make/compiler-builtins-partitioning/Cargo.toml new file mode 100644 index 0000000000000..869210c4a683c --- /dev/null +++ b/tests/run-make/compiler-builtins-partitioning/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "scratch" +version = "0.1.0" +edition = "2021" + +[lib] +path = "lib.rs" diff --git a/tests/run-make/compiler-builtins-partitioning/lib.rs b/tests/run-make/compiler-builtins-partitioning/lib.rs new file mode 100644 index 0000000000000..0c9ac1ac8e4bd --- /dev/null +++ b/tests/run-make/compiler-builtins-partitioning/lib.rs @@ -0,0 +1 @@ +#![no_std] diff --git a/tests/run-make/compiler-builtins-partitioning/rmake.rs b/tests/run-make/compiler-builtins-partitioning/rmake.rs new file mode 100644 index 0000000000000..d8f5e77494997 --- /dev/null +++ b/tests/run-make/compiler-builtins-partitioning/rmake.rs @@ -0,0 +1,105 @@ +//! The compiler_builtins library is special. It exists to export a number of intrinsics which may +//! also be provided by libgcc or compiler-rt, and when an intrinsic is provided by another +//! library, we want that definition to override the one in compiler_builtins because we expect +//! that those implementations are more optimized than compiler_builtins. To make sure that an +//! attempt to override a compiler_builtins intrinsic does not result in a multiple definitions +//! linker error, the compiler has special CGU partitioning logic for compiler_builtins that +//! ensures every intrinsic gets its own CGU. +//! +//! This test is slightly overfit to the current compiler_builtins CGU naming strategy; it doesn't +//! distinguish between "multiple intrinsics are in one object file!" which would be very bad, and +//! "This object file has an intrinsic and also some of its helper functions!" which would be okay. +//! +//! This test ensures that the compiler_builtins rlib has only one intrinsic in each object file. + +// wasm and nvptx targets don't produce rlib files that object can parse. +//@ ignore-wasm +//@ ignore-nvptx64 + +#![deny(warnings)] + +use std::str; + +use run_make_support::object::read::Object; +use run_make_support::object::read::archive::ArchiveFile; +use run_make_support::object::{ObjectSymbol, SymbolKind}; +use run_make_support::rfs::{read, read_dir}; +use run_make_support::{cargo, object, path, target}; + +fn main() { + println!("Testing compiler_builtins CGU partitioning for {}", target()); + + // CGU partitioning has some special cases for codegen-units=1, so we also test 2 CGUs. + for cgus in [1, 2] { + for profile in ["debug", "release"] { + run_test(profile, cgus); + } + } +} + +fn run_test(profile: &str, cgus: usize) { + println!("Testing with profile {profile} and -Ccodegen-units={cgus}"); + + let target_dir = path("target"); + + let mut cmd = cargo(); + cmd.args(&[ + "build", + "--manifest-path", + "Cargo.toml", + "-Zbuild-std=core", + "--target", + &target(), + ]) + .env("RUSTFLAGS", &format!("-Ccodegen-units={cgus}")) + .env("CARGO_TARGET_DIR", &target_dir) + .env("RUSTC_BOOTSTRAP", "1") + // Visual Studio 2022 requires that the LIB env var be set so it can + // find the Windows SDK. + .env("LIB", std::env::var("LIB").unwrap_or_default()); + if profile == "release" { + cmd.arg("--release"); + } + cmd.run(); + + let rlibs_path = target_dir.join(target()).join(profile).join("deps"); + let compiler_builtins_rlib = read_dir(rlibs_path) + .find_map(|e| { + let path = e.unwrap().path(); + let file_name = path.file_name().unwrap().to_str().unwrap(); + if file_name.starts_with("libcompiler_builtins") && file_name.ends_with(".rlib") { + Some(path) + } else { + None + } + }) + .unwrap(); + + // rlib files are archives, where the archive members are our CGUs, and we also have one called + // lib.rmeta which is the encoded metadata. Each of the CGUs is an object file. + let data = read(compiler_builtins_rlib); + + let archive = ArchiveFile::parse(&*data).unwrap(); + for member in archive.members() { + let member = member.unwrap(); + if member.name() == b"lib.rmeta" { + continue; + } + let data = member.data(&*data).unwrap(); + let object = object::File::parse(&*data).unwrap(); + + let mut global_text_symbols = 0; + println!("Inspecting object {}", str::from_utf8(&member.name()).unwrap()); + for symbol in object + .symbols() + .filter(|symbol| matches!(symbol.kind(), SymbolKind::Text)) + .filter(|symbol| symbol.is_definition() && symbol.is_global()) + { + println!("symbol: {:?}", symbol.name().unwrap()); + global_text_symbols += 1; + } + // Assert that this object/CGU does not define multiple global text symbols. + // We permit the 0 case because some CGUs may only be assigned a static. + assert!(global_text_symbols <= 1); + } +}