rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.89k stars 12.78k forks source link

macOS: Crate symbols get discarded when crate appears unused #133491

Open nvzqz opened 6 hours ago

nvzqz commented 6 hours ago
I tried this code: Within the base crate, the [`#[divan::bench]`](https://docs.rs/divan/latest/divan/attr.bench.html) macro is used for benchmarking crate internals. Its generated code translates to something similar to: ```rs #[used] #[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func,mod_init_funcs")] #[cfg_attr(target_os = "linux", link_section = ".init_array")] #[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")] static CONSTRUCTOR: extern "C" fn() = constructor; extern "C" fn constructor() { // Register benchmark... } ``` This crate has a benchmark executable with only: ```rs fn main() { divan::main(); } ```

I expected to see this happen: run the add benchmark and output the results. This runs correctly on Linux and Windows, but not macOS.

Instead, this happened: divan::main() cannot find the benchmark because the pre-main constructor function never ran.

In order to make the benchmarks visible to the executable, the base crate needs to have one of its items appear to be used, such as black_box-ing any item from the crate.

Note that this also happens with code like:

#[used(linker)]
#[link_section = "__DATA,__my_values,regular,no_dead_strip"]
static VALUE: u32 = 42;

This issue occurs even when doing extern crate my_crate or use my_crate as _, which is a known workaround for similar issues.

Meta

rustc --version --verbose:

rustc 1.82.0 (f6e511eec 2024-10-15)
binary: rustc
commit-hash: f6e511eec7342f59a25f7c0534f1dbea00d01b14
commit-date: 2024-10-15
host: aarch64-apple-darwin
release: 1.82.0
LLVM version: 19.1.1
workingjubilee commented 6 hours ago

hey @madsmtm What do you think we have wrought upon ourselves today?

bjorn3 commented 6 hours ago

Fair chance this is because of linkers only pulling in object files from archives when a symbol in them is referenced unless --whole-archive is used. In principle we should be generating a symbols.o to ensure that all symbols are actually referenced, but maybe something is going wrong with that? Can you try adding #[no_mangle] to CONSTRUCTOR and/or constructor just to see if that makes any difference?

workingjubilee commented 6 hours ago

I'd be surprised if no_mangle worked but used(linker) didn't?

nvzqz commented 6 hours ago

It appears that #[no_mangle] does not fix this.

bjorn3 commented 5 hours ago

Does -Clink-dead-code=yes work? That disables --gc-sections (as well as a couple of other things)

nvzqz commented 5 hours ago

RUSTFLAGS='-Clink-dead-code=yes' cargo bench also does not make the benchmark appear. Nor does it make the symbol in the __DATA,__my_values,regular,no_dead_strip section appear in the final binary.

madsmtm commented 3 hours ago

I think the issue is that the symbols.o trick introduced in https://github.com/rust-lang/rust/pull/95604 just doesn't works at all with ld64.

If I use RUSTFLAGS="-Clinker=rust-lld" cargo bench, the code works as expected.

madsmtm commented 2 hours ago

And RUSTFLAGS=-Clink-arg=-all_load cargo bench does also work.

I'll see if I can figure out a way to make the symbols.o trick work.