Skip to content

Commit ceeb5ad

Browse files
committed
Auto merge of #93718 - thomcc:used-macho, r=pnkfelix
Only compile #[used] as llvm.compiler.used for ELF targets This returns `#[used]` to how it worked prior to the LLVM 13 update. The intention is not that this is a stable promise. I'll add tests later today. The tests will test things that we don't actually promise, though. It's a deliberately small patch, mostly comments. And assuming it's reviewed and lands in time, IMO it should at least be considered for uplifting to beta (so that it can be in 1.59), as the change broke many crates in the ecosystem, even if they are relying on behavior that is not guaranteed. # Background LLVM has two ways of preventing removal of an unused variable: `llvm.compiler.used`, which must be present in object files, but allows the linker to remove the value, and `llvm.used` which is supposed to apply to the linker as well, if possible. Prior to LLVM 13, `llvm.used` and `llvm.compiler.used` were the same on ELF targets, although they were different elsewhere. Prior to our update to LLVM 13, we compiled `#[used]` using `llvm.used` unconditionally, even though we only ever promised behavior like `llvm.compiler.used`. In LLVM 13, ELF targets gained some support for preventing linker removal of `llvm.used` via the SHF_RETAIN section flag. This has some compatibility issues though: Concretely: some older versions `ld.gold` (specifically ones prior to v2.36, released in Jan 2021) had a bug where it would fail to place a `#[used] #[link_section = ".init_array"]` static in between `__init_array_start`/`__init_array_end`, leading to code that does this failing to run a static constructor. This is technically not a thing we guarantee will work, is a common use case, and is needed in `libstd` (for example, to get access to `std::env::args()` even if Rust does not control `main`, such as when in a `cdylib` crate). As a result, when updating to LLVM 13, we unconditionally switched to using `llvm.compiler.used`, which mirror the guarantees we make for `#[used]` and doesn't require the latest ld.gold. Unfortunately, this happened to break quite a bit of things in the ecosystem, as non-ELF targets had come to rely on `#[used]` being slightly stronger. In particular, there are cases where it will even break static constructors on these targets[^initinit] (and in fact, breaks way more use cases, as Mach-O uses special sections as an interface to the OS/linker/loader in many places). As a result, we only switch to `llvm.compiler.used` on ELF[^elfish] targets. The rationale here is: 1. It is (hopefully) identical to the semantics we used prior to the LLVM13 update as prior to that update we unconditionally used `llvm.used`, but on ELF `llvm.used` was the same as `llvm.compiler.used`. 2. It seems to be how Clang compiles this, and given that they have similar (but stronger) compatibility promises, that makes sense. [^initinit]: For Mach-O targets: It is not always guaranteed that `__DATA,__mod_init_func` is a GC root if it does not have the `S_MOD_INIT_FUNC_POINTERS` flag which we cannot add. In most cases, when ld64 transformed this section into `__DATA_CONST,__mod_init_func` it gets applied, but it's not clear that that is intentional (let alone guaranteed), and the logic is complex enough that it probably happens sometimes, and people in the wild report it occurring. [^elfish]: Actually, there's not a great way to tell if it's ELF, so I've approximated it. This is pretty ad-hoc and hacky! We probably should have a firmer set of guarantees here, but this change should relax the pressure on coming up with that considerably, returning it to previous levels. --- Unsure who should review so leaving it open, but for sure CC `@nikic`
2 parents 039a6ad + a64b2a9 commit ceeb5ad

File tree

5 files changed

+61
-4
lines changed

5 files changed

+61
-4
lines changed

compiler/rustc_codegen_llvm/src/consts.rs

+12-2
Original file line numberDiff line numberDiff line change
@@ -535,10 +535,20 @@ impl<'ll> StaticMethods for CodegenCx<'ll, '_> {
535535

536536
// The semantics of #[used] in Rust only require the symbol to make it into the
537537
// object file. It is explicitly allowed for the linker to strip the symbol if it
538-
// is dead. As such, use llvm.compiler.used instead of llvm.used.
538+
// is dead, which means we are allowed use `llvm.compiler.used` instead of
539+
// `llvm.used` here.
540+
//
539541
// Additionally, https://reviews.llvm.org/D97448 in LLVM 13 started emitting unique
540542
// sections with SHF_GNU_RETAIN flag for llvm.used symbols, which may trigger bugs
541-
// in some versions of the gold linker.
543+
// in the handling of `.init_array` (the static constructor list) in versions of
544+
// the gold linker (prior to the one released with binutils 2.36).
545+
//
546+
// That said, we only ever emit these when compiling for ELF targets, unless
547+
// `#[used(compiler)]` is explicitly requested. This is to avoid similar breakage
548+
// on other targets, in particular MachO targets have *their* static constructor
549+
// lists broken if `llvm.compiler.used` is emitted rather than llvm.used. However,
550+
// that check happens when assigning the `CodegenFnAttrFlags` in `rustc_typeck`,
551+
// so we don't need to take care of it here.
542552
self.add_compiler_used_global(g);
543553
}
544554
if attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER) {

compiler/rustc_codegen_ssa/src/traits/statics.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ pub trait StaticMethods: BackendTypes {
1313
/// Same as add_used_global(), but only prevent the compiler from potentially removing an
1414
/// otherwise unused symbol. The linker is still permitted to drop it.
1515
///
16-
/// This corresponds to the semantics of the `#[used]` attribute.
16+
/// This corresponds to the documented semantics of the `#[used]` attribute, although
17+
/// on some targets (non-ELF), we may use `add_used_global` for `#[used]` statics
18+
/// instead.
1719
fn add_compiler_used_global(&self, global: Self::Value);
1820
}
1921

compiler/rustc_typeck/src/collect.rs

+31-1
Original file line numberDiff line numberDiff line change
@@ -2813,7 +2813,37 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: DefId) -> CodegenFnAttrs {
28132813
)
28142814
.emit();
28152815
}
2816-
None => codegen_fn_attrs.flags |= CodegenFnAttrFlags::USED,
2816+
None => {
2817+
// Unfortunately, unconditionally using `llvm.used` causes
2818+
// issues in handling `.init_array` with the gold linker,
2819+
// but using `llvm.compiler.used` caused a nontrival amount
2820+
// of unintentional ecosystem breakage -- particularly on
2821+
// Mach-O targets.
2822+
//
2823+
// As a result, we emit `llvm.compiler.used` only on ELF
2824+
// targets. This is somewhat ad-hoc, but actually follows
2825+
// our pre-LLVM 13 behavior (prior to the ecosystem
2826+
// breakage), and seems to match `clang`'s behavior as well
2827+
// (both before and after LLVM 13), possibly because they
2828+
// have similar compatibility concerns to us. See
2829+
// https://github.com/rust-lang/rust/issues/47384#issuecomment-1019080146
2830+
// and following comments for some discussion of this, as
2831+
// well as the comments in `rustc_codegen_llvm` where these
2832+
// flags are handled.
2833+
//
2834+
// Anyway, to be clear: this is still up in the air
2835+
// somewhat, and is subject to change in the future (which
2836+
// is a good thing, because this would ideally be a bit
2837+
// more firmed up).
2838+
let is_like_elf = !(tcx.sess.target.is_like_osx
2839+
|| tcx.sess.target.is_like_windows
2840+
|| tcx.sess.target.is_like_wasm);
2841+
codegen_fn_attrs.flags = if is_like_elf {
2842+
CodegenFnAttrFlags::USED
2843+
} else {
2844+
CodegenFnAttrFlags::USED_LINKER
2845+
};
2846+
}
28172847
}
28182848
} else if attr.has_name(sym::cmse_nonsecure_entry) {
28192849
if !matches!(tcx.fn_sig(did).abi(), abi::Abi::C { .. }) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-include ../tools.mk
2+
3+
# only-macos
4+
#
5+
# This checks that `#[used]` passes through to the linker on
6+
# darwin. This is subject to change in the future, see
7+
# https://github.com/rust-lang/rust/pull/93718 for discussion
8+
9+
all:
10+
$(RUSTC) -Copt-level=3 dylib_used.rs
11+
nm $(TMPDIR)/libdylib_used.dylib | $(CGREP) VERY_IMPORTANT_SYMBOL
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#![crate_type = "cdylib"]
2+
3+
#[used]
4+
static VERY_IMPORTANT_SYMBOL: u32 = 12345;

0 commit comments

Comments
 (0)