Skip to content

Commit b3841ed

Browse files
committed
Test that compiler-builtins is partitioned right
1 parent 27cf264 commit b3841ed

File tree

6 files changed

+116
-1
lines changed

6 files changed

+116
-1
lines changed

tests/run-make/compiler-builtins/rmake.rs renamed to tests/run-make/compiler-builtins-linkage/rmake.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
//! The compiler_builtins library is special. It can call functions in core, but it must not
1+
//! The compiler_builtins library is special. When linkers are passed multiple libraries, they
2+
//! expect undefined symbols mentioned by libraries on the left to be defined in libraries to the
3+
//! right. Since calls to compiler_builtins may be inserted during codegen, it is placed all the way
4+
//! to the right. Therefore, compiler_builtins can call functions in core but it must not
25
//! require linkage against a build of core. If it ever does, building the standard library *may*
36
//! result in linker errors, depending on whether the linker in use applies optimizations first or
47
//! resolves symbols first. So the portable and safe approach is to forbid such a linkage
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
name = "scratch"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
path = "lib.rs"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#![no_std]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
//! The compiler_builtins library is special. It exists to export a number of intrinsics which may
2+
//! also be provided by libgcc or compiler-rt, and when an intrinsic is provided by another
3+
//! library, we want that definition to override the one in compiler_builtins because we expect
4+
//! that those implementations are more optimized than compiler_builtins. To make sure that an
5+
//! attempt to override a compiler_builtins intrinsic does not result in a multiple definitions
6+
//! linker error, the compiler has special CGU partitioning logic for compiler_builtins that
7+
//! ensures every intrinsic gets its own CGU.
8+
//!
9+
//! This test is slightly overfit to the current compiler_builtins CGU naming strategy; it doesn't
10+
//! distinguish between "multiple intrinsics are in one object file!" which would be very bad, and
11+
//! "This object file has an intrinsic and also some of its helper functions!" which would be okay.
12+
//!
13+
//! This test ensures that the compiler_builtins rlib has only one intrinsic in each object file.
14+
15+
// wasm and nvptx targets don't produce rlib files that object can parse.
16+
//@ ignore-wasm
17+
//@ ignore-nvptx64
18+
19+
#![deny(warnings)]
20+
21+
use std::str;
22+
23+
use run_make_support::object::read::Object;
24+
use run_make_support::object::read::archive::ArchiveFile;
25+
use run_make_support::object::{ObjectSection, SectionKind};
26+
use run_make_support::rfs::{read, read_dir};
27+
use run_make_support::{cargo, object, path, target};
28+
29+
fn main() {
30+
println!("Testing compiler_builtins CGU partitioning for {}", target());
31+
32+
// CGU partitioning has some special cases for codegen-units=1, so we also test 2 CGUs.
33+
for cgus in [1, 2] {
34+
for profile in ["debug", "release"] {
35+
run_test(profile, cgus);
36+
}
37+
}
38+
}
39+
40+
fn run_test(profile: &str, cgus: usize) {
41+
println!("Testing with profile {profile} and -Ccodegen-units={cgus}");
42+
43+
let target_dir = path("target");
44+
45+
let mut cmd = cargo();
46+
cmd.args(&[
47+
"build",
48+
"--manifest-path",
49+
"Cargo.toml",
50+
"-Zbuild-std=core",
51+
"--target",
52+
&target(),
53+
])
54+
.env("RUSTFLAGS", &format!("-Ccodegen-units={cgus}"))
55+
.env("CARGO_TARGET_DIR", &target_dir)
56+
.env("RUSTC_BOOTSTRAP", "1")
57+
// Visual Studio 2022 requires that the LIB env var be set so it can
58+
// find the Windows SDK.
59+
.env("LIB", std::env::var("LIB").unwrap_or_default());
60+
if profile == "release" {
61+
cmd.arg("--release");
62+
}
63+
cmd.run();
64+
65+
let rlibs_path = target_dir.join(target()).join(profile).join("deps");
66+
let compiler_builtins_rlib = read_dir(rlibs_path)
67+
.find_map(|e| {
68+
let path = e.unwrap().path();
69+
let file_name = path.file_name().unwrap().to_str().unwrap();
70+
if file_name.starts_with("libcompiler_builtins") && file_name.ends_with(".rlib") {
71+
Some(path)
72+
} else {
73+
None
74+
}
75+
})
76+
.unwrap();
77+
78+
// rlib files are archives, where the archive members are our CGUs, and we also have one called
79+
// lib.rmeta which is the encoded metadata. Each of the CGUs is an object file.
80+
let data = read(compiler_builtins_rlib);
81+
82+
let archive = ArchiveFile::parse(&*data).unwrap();
83+
for member in archive.members() {
84+
let member = member.unwrap();
85+
if member.name() == b"lib.rmeta" {
86+
continue;
87+
}
88+
let data = member.data(&*data).unwrap();
89+
let object = object::File::parse(&*data).unwrap();
90+
91+
let mut text_sections_count = 0;
92+
println!("Inspecting object {}", str::from_utf8(&member.name()).unwrap());
93+
for section in object
94+
.sections()
95+
.filter(|section| section.kind() == SectionKind::Text && section.size() > 0)
96+
{
97+
println!("section: {:?}", section.name().unwrap());
98+
text_sections_count += 1;
99+
}
100+
// Assert that we have 0 or 1 text sections. Occasionally, we will encounter an object file
101+
// that just contains a static. Which is weird, but valid.
102+
assert!(text_sections_count <= 1);
103+
}
104+
}

0 commit comments

Comments
 (0)