Skip to content

Commit 4cd5271

Browse files
committed
avoid letting compiler-builtins' weak math definitions shadow system libm
1 parent bba5310 commit 4cd5271

4 files changed

Lines changed: 273 additions & 0 deletions

File tree

compiler/rustc_codegen_ssa/src/back/link.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// ignore-tidy-file-filelength
12
mod raw_dylib;
23

34
use std::collections::BTreeSet;
@@ -751,6 +752,72 @@ fn link_rlib<'a>(
751752
///
752753
/// There's no need to include metadata in a static archive, so ensure to not link in the metadata
753754
/// object file (and also don't prepare the archive with a metadata file).
755+
// Weak f32/f64 math symbols from c-b's `full_availability` module; keep in sync with
756+
// `compiler-builtins/src/math/mod.rs`.
757+
const COMPILER_BUILTINS_LIBM_SYMBOLS: &[&str] = &[
758+
"cbrtf",
759+
"ceilf",
760+
"copysignf",
761+
"fabsf",
762+
"fdimf",
763+
"floorf",
764+
"fmaf",
765+
"fmaxf",
766+
"fminf",
767+
"fmodf",
768+
"rintf",
769+
"roundf",
770+
"sqrtf",
771+
"truncf",
772+
"cbrt",
773+
"ceil",
774+
"copysign",
775+
"fabs",
776+
"fdim",
777+
"floor",
778+
"fma",
779+
"fmax",
780+
"fmin",
781+
"fmod",
782+
"rint",
783+
"round",
784+
"sqrt",
785+
"trunc",
786+
];
787+
788+
fn compiler_builtins_libm_members(rlib_path: &Path) -> FxHashSet<String> {
789+
let Ok(file) = File::open(rlib_path) else { return FxHashSet::default() };
790+
let Ok(mmap) = (unsafe { Mmap::map(file) }) else { return FxHashSet::default() };
791+
let Ok(archive) = object::read::archive::ArchiveFile::parse(&*mmap) else {
792+
return FxHashSet::default();
793+
};
794+
let Some(symbols) = archive.symbols().ok().flatten() else { return FxHashSet::default() };
795+
796+
let mut members = FxHashSet::default();
797+
for symbol in symbols {
798+
let Ok(symbol) = symbol else { continue };
799+
if !COMPILER_BUILTINS_LIBM_SYMBOLS.iter().any(|&name| name.as_bytes() == symbol.name()) {
800+
continue;
801+
}
802+
if let Ok(member) = archive.member(symbol.offset())
803+
&& let Ok(name) = str::from_utf8(member.name())
804+
{
805+
members.insert(name.to_string());
806+
}
807+
}
808+
members
809+
}
810+
811+
// Whether a system libm (`-lm`) is among the native libraries recorded for the final link.
812+
fn links_libm(crate_info: &CrateInfo, sess: &Session) -> bool {
813+
crate_info
814+
.native_libraries
815+
.values()
816+
.chain(std::iter::once(&crate_info.used_libraries))
817+
.flatten()
818+
.any(|lib| lib.name.as_str() == "m" && lib.kind.is_dllimport() && relevant_lib(sess, lib))
819+
}
820+
754821
fn link_staticlib(
755822
sess: &Session,
756823
archive_builder_builder: &dyn ArchiveBuilderBuilder,
@@ -773,7 +840,15 @@ fn link_staticlib(
773840
);
774841
let mut all_native_libs = vec![];
775842

843+
// With `-lm` recorded, drop c-b's weak math definitions so libm's strong ones win (#142119).
844+
let links_libm = links_libm(crate_info, sess);
776845
let res = each_linked_rlib(crate_info, Some(CrateType::StaticLib), &mut |cnum, path| {
846+
debug_assert!(
847+
!links_libm
848+
|| crate_info.compiler_builtins != Some(cnum)
849+
|| ignored_for_lto(sess, crate_info, cnum),
850+
"compiler-builtins must not participate in LTO for the omit-libm skip to hold"
851+
);
777852
let lto = are_upstream_rust_objects_already_included(sess)
778853
&& !ignored_for_lto(sess, crate_info, cnum);
779854

@@ -792,6 +867,11 @@ fn link_staticlib(
792867
.enumerate()
793868
.filter_map(|(i, _)| bundled_filenames.get(i).copied().flatten())
794869
.collect();
870+
let cb_libm_members = if links_libm && crate_info.compiler_builtins == Some(cnum) {
871+
compiler_builtins_libm_members(path)
872+
} else {
873+
FxHashSet::default()
874+
};
795875
ab.add_archive(
796876
path,
797877
AddArchiveKind::Rlib(rmeta_link_cache, &|fname: &str, entry_kind| {
@@ -810,6 +890,9 @@ fn link_staticlib(
810890
return true;
811891
}
812892

893+
if cb_libm_members.contains(fname) {
894+
return true;
895+
}
813896
false
814897
}),
815898
)
@@ -3036,6 +3119,12 @@ fn linker_with_args(
30363119
cmd.link_arg(std::path::absolute(&*sess.target_tlib_path.dir).unwrap());
30373120
}
30383121

3122+
// Emit `-lm` ahead of the rlibs on glibc so its strong definitions win over c-b's weak ones;
3123+
// every other native dylib stays after the rlibs below.
3124+
if sess.target.env == Env::Gnu && links_libm(crate_info, sess) {
3125+
cmd.link_dylib_by_name("m", false, true);
3126+
}
3127+
30393128
// Upstream rust crates and their non-dynamic native libraries.
30403129
add_upstream_rust_crates(
30413130
cmd,
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#![no_std]
2+
#![crate_type = "staticlib"]
3+
4+
use core::panic::PanicInfo;
5+
6+
#[panic_handler]
7+
fn panic(_: &PanicInfo) -> ! {
8+
loop {}
9+
}
10+
11+
// Declaring `ceilf`/`sqrtf` pulls compiler-builtins' weak definitions into the archive, while
12+
// `#[link(name = "m")]` makes the staticlib record system libm (`-lm`) as a dependency. When
13+
// libm is recorded, rustc must skip those weak definitions so that a later link against `-lm`
14+
// resolves the strong libm versions (rust-lang/rust#142119).
15+
#[link(name = "m")]
16+
extern "C" {
17+
fn ceilf(x: f32) -> f32;
18+
fn sqrtf(x: f32) -> f32;
19+
}
20+
21+
#[no_mangle]
22+
pub extern "C" fn use_mathf(x: f32) -> f32 {
23+
unsafe { ceilf(x) + sqrtf(x) }
24+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#![no_std]
2+
#![crate_type = "staticlib"]
3+
4+
use core::panic::PanicInfo;
5+
6+
#[panic_handler]
7+
fn panic(_: &PanicInfo) -> ! {
8+
loop {}
9+
}
10+
11+
// No `#[link(name = "m")]`: without a system libm recorded, compiler-builtins' weak f32/f64 math
12+
// definitions are the only provider of `ceilf`/`sqrtf`, so they must be kept.
13+
extern "C" {
14+
fn ceilf(x: f32) -> f32;
15+
fn sqrtf(x: f32) -> f32;
16+
}
17+
18+
#[no_mangle]
19+
pub extern "C" fn use_mathf(x: f32) -> f32 {
20+
unsafe { ceilf(x) + sqrtf(x) }
21+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// compiler-builtins weakly defines the f32/f64 math symbols (its `full_availability` list) so
2+
// `no_std` targets without a system libm still work. A staticlib that records `-lm` must omit
3+
// them so libm's strong definitions win at the final link (#142119); the no-`-lm` control keeps
4+
// its weak fallbacks. `only-gnu` because only gnu libm (glibc) provides these symbols.
5+
6+
//@ only-gnu
7+
//@ ignore-cross-compile
8+
9+
use std::collections::HashSet;
10+
11+
use run_make_support::object::read::archive::ArchiveFile;
12+
use run_make_support::object::read::elf::{FileHeader as _, SectionHeader as _, Sym as _};
13+
use run_make_support::object::{Endianness, elf};
14+
use run_make_support::{rfs, rustc, static_lib_name};
15+
16+
// Keep in sync with `COMPILER_BUILTINS_LIBM_SYMBOLS` in rustc_codegen_ssa/src/back/link.rs.
17+
const LIBM_SYMBOLS: &[&str] = &[
18+
"cbrtf",
19+
"ceilf",
20+
"copysignf",
21+
"fabsf",
22+
"fdimf",
23+
"floorf",
24+
"fmaf",
25+
"fmaxf",
26+
"fminf",
27+
"fmodf",
28+
"rintf",
29+
"roundf",
30+
"sqrtf",
31+
"truncf",
32+
"cbrt",
33+
"ceil",
34+
"copysign",
35+
"fabs",
36+
"fdim",
37+
"floor",
38+
"fma",
39+
"fmax",
40+
"fmin",
41+
"fmod",
42+
"rint",
43+
"round",
44+
"sqrt",
45+
"trunc",
46+
];
47+
48+
fn main() {
49+
rustc().input("no_libm.rs").crate_type("staticlib").panic("abort").run();
50+
let no_libm = static_lib_name("no_libm");
51+
let no_libm_defined = defined_global_symbols(&no_libm);
52+
for name in LIBM_SYMBOLS {
53+
assert!(
54+
no_libm_defined.contains(*name),
55+
"expected weak fallback `{name}` to be kept without `-lm` in `{}`",
56+
no_libm
57+
);
58+
}
59+
60+
rustc().input("lib.rs").crate_type("staticlib").panic("abort").run();
61+
let with_libm = static_lib_name("lib");
62+
let with_libm_defined = defined_global_symbols(&with_libm);
63+
for name in LIBM_SYMBOLS {
64+
assert!(
65+
!with_libm_defined.contains(*name),
66+
"weak definition `{name}` should be omitted when `-lm` is recorded in `{with_libm}`"
67+
);
68+
}
69+
70+
// Not a blanket strip: the f16/f128 and integer fallbacks survive. `ceilf16` also guards
71+
// against matching `ceilf` as a prefix of `ceilf16`.
72+
assert!(
73+
with_libm_defined.contains("ceilf16"),
74+
"compiler-builtins' f16 fallback must survive the omit in `{with_libm}`"
75+
);
76+
assert!(
77+
with_libm_defined.contains("__floatdidf"),
78+
"compiler-builtins' integer fallback must survive the omit in `{with_libm}`"
79+
);
80+
}
81+
82+
/// Every defined global/weak symbol in the archive. The omitted symbols can still appear as
83+
/// undefined references (e.g. `use_mathf` calling `ceilf`), so filter `SHN_UNDEF` rather than
84+
/// checking mere presence.
85+
fn defined_global_symbols(archive_path: &str) -> HashSet<String> {
86+
let archive_data = rfs::read(archive_path);
87+
let archive = ArchiveFile::parse(archive_data.as_slice()).unwrap();
88+
let mut defined = HashSet::new();
89+
90+
for member in archive.members() {
91+
let member = member.unwrap();
92+
let data = member.data(archive_data.as_slice()).unwrap();
93+
94+
if let Ok(header) = elf::FileHeader64::<Endianness>::parse(data) {
95+
collect_elf_defined(header, data, &mut defined);
96+
} else if let Ok(header) = elf::FileHeader32::<Endianness>::parse(data) {
97+
collect_elf_defined(header, data, &mut defined);
98+
}
99+
}
100+
101+
defined
102+
}
103+
104+
fn collect_elf_defined<
105+
Elf: run_make_support::object::read::elf::FileHeader<Endian = Endianness>,
106+
>(
107+
header: &Elf,
108+
data: &[u8],
109+
defined: &mut HashSet<String>,
110+
) {
111+
let Ok(endian) = header.endian() else { return };
112+
let Ok(sections) = header.sections(endian, data) else { return };
113+
114+
for (si, section) in sections.enumerate() {
115+
if section.sh_type(endian) != elf::SHT_SYMTAB {
116+
continue;
117+
}
118+
let Ok(symbols) = run_make_support::object::read::elf::SymbolTable::parse(
119+
endian, data, &sections, si, section,
120+
) else {
121+
continue;
122+
};
123+
let strtab = symbols.strings();
124+
125+
for symbol in symbols.symbols() {
126+
let bind = symbol.st_bind();
127+
if bind != elf::STB_GLOBAL && bind != elf::STB_WEAK {
128+
continue;
129+
}
130+
if symbol.st_shndx(endian) == elf::SHN_UNDEF {
131+
continue;
132+
}
133+
let Ok(name_bytes) = symbol.name(endian, strtab) else { continue };
134+
if let Ok(name) = str::from_utf8(name_bytes) {
135+
defined.insert(name.to_string());
136+
}
137+
}
138+
}
139+
}

0 commit comments

Comments
 (0)