Repository navigation
Incremental compilation fails when a generic function uses a private symbol #53929
Description
The way that incremental compilation works breaks code that uses private symbols. For example, the following code will fail to compile when using incremental compilation (e.g. the default cargo build), but will build successfully when incremental compilation is disabled (e.g. the default cargo build --release):
// I have reproduced this issue for the following targets:
// mips-unknown-linux-gnu
// x86_64-unknown-linux-gnu
// x86_64-apple-darwin
// aarch64-apple-ios
fn foo<T>() {
// See m:<mangling> at https://llvm.org/docs/LangRef.html#data-layout
// This is only reproducible when using ELF, Mips, or Mach-O mangling.
#[cfg_attr(all(target_os = "linux", not(target_arch = "mips")),
export_name = "\x01.L_this_is_a_private_symbol")]
#[cfg_attr(any(target_os = "macos", target_os = "ios"),
export_name = "\x01L_this_is_a_private_symbol")]
#[cfg_attr(target_arch = "mips", export_name = "\x01$_this_is_a_private_symbol")]
static X: usize = 0;
// Use the static symbol in a way that prevents the optimizer from
// optimizing it away.
unsafe { std::ptr::read_volatile(&X as *const _); }
}
fn main() {
foo::<usize>();
}Output (for both cargo build and cargo build --release):
$ cargo build
Compiling zzz v0.1.0 (file:///private/tmp/zzz)
LLVM ERROR: unsupported relocation of undefined symbol 'L_this_is_a_private_symbol'
error: Could not compile `zzz`.
To learn more, run the command again with --verbose.
$ cargo build --release
Compiling zzz v0.1.0 (file:///private/tmp/zzz)
Finished release [optimized] target(s) in 0.26s
I have reproduced this issue for the following targets:
mips-unknown-linux-gnux86_64-unknown-linux-gnux86_64-apple-darwinaarch64-apple-ios
I'm interested in fixing this issue, and have been trying to better understand librustc_mir/monomorphize/partitioning.rs (which I assume is where the problem is). I'll update this issue if I've made any progress, but I would appreciate any guidance anyone might be able to offer.
These private symbols are useful for low-level FFI work (e.g. the linker on macOS/iOS will deduplicate selector names only if they have a private symbol name).
Activity
mjbshaw commentedon Sep 3, 2018
I forgot to mention what the cause of the error is:
The private symbol is partitioned into a different codegen unit than the generic function, which means the generic function is attempting to reference a different module's private symbol, which obviously fails.
I believe the correct fix would be to modify the partitioning code so that private symbols and functions that reference them are always partitioned together into a codegen unit.
michaelwoerister commentedon Sep 4, 2018
Yes, the partitioning code does not really handle setting explicit linkage very well yet.
Incremental compilation will create two compilation units for each Rust module, one for the generic code and one for the non-generic code. So I think we would need to treat private symbols the same as inline functions: instantiate them in every CGU they are accessed in. However, that could have surprising. Consider the following example:
You'd think that these access the same
FOObut the two functions would actually each access a private copy. That's very much not what one would expect. So anything that accesses something with private or internal linkage would have to be forced into the same CGU. It's doable but it would be a bit of work.mjbshaw commentedon Sep 4, 2018
Yeah, as much as I would love to be able to inline a static (so it gets duplicated across codegen units), I too think that would be too surprising. I think Rust would need some kind of annotation that allowed developers to opt-in to that kind of behavior on a per-static basis.
For this particular issue at hand, I think a minimal solution would be acceptable. That is, a private static nested inside of a generic function should always go in the same codegen unit as the monomorphisations of that generic function. This minimal solution would suffice for my personal needs.
Extending that further, a slightly better solution (but I'm guessing more work) would be to make sure that private statics outside of the function (but within the same module) get put into the same codegen units as the functions that use them.
The "nuclear" solution is to make sure that all code (even separate modules) that uses private statics gets put into the same codegen unit. That sounds like the most work to me and I don't think it's currently worth it.
The implication of neither duplicating private statics nor merging code that uses them into a single codegen unit is that these functions cannot be inlined, or else linking will fail. The user must explicitly annotate them with
#[inline(never)]. I think that's acceptable for the time being.michaelwoerister commentedon Sep 4, 2018
One thing we could experiment with -- and which would make sure that things always work as long as they are in the same module -- is to make incremental compilation not split generic and non-generic code into different CGUs. That might make compilation slower (because the cache is hit less often during incr. comp.) but it might also make compilation faster (because less per-CGU work has to be duplicated, such as instantiating inline functions). That would be a simple change to make and we could test it on perf.rust-lang.org before merging.
michaelwoerister commentedon Sep 9, 2018
I tried this out in #53963 but unfortunately the performance hit was too big.
pnkfelix commentedon Nov 17, 2020
discussed in today's meeting; wg-incr-comp thinks this might be fixed...
wesleywiser commentedon Nov 29, 2020
This does not seem to be fixed.
cargo buildnow reports a slightly different error:nvzqz commentedon Dec 16, 2020
I would like to use a
\x01-prefixed static across crates with inlining. What's the best course of action right now to accomplish this?In 1.48 I'm hitting
LLVM ERROR: unsupported relocation of undefined symbollike the original post. However, I'm not using a generic function.My use case is creating
\x01L_OBJC_SELECTOR_REFERENCES_XXand\x01L_OBJC_METH_VAR_NAME_XXsymbols that get picked up by the Objective-C runtime at program start.Use static selector for `ObjCObject::class`
madsmtm commentedon Jun 22, 2022
Yeah as @nvzqz noted, this problem is not specific to generics nor incremental compilation, it can also happen across crate boundaries if
#[inline]is specified. The following code:Fails with
unsupported relocation of undefined symbol(macOS/iOS) orUndefined temporary symbol(Linux).madsmtm commentedon Jun 22, 2022
I think the way to fix this is probably not in the partitioning code, it fundamentally can't do this across crates!
Instead, we would have to find a way to either:
jbatez commentedon May 17, 2025
At least in the macOS linker case, I don't think there's a way to access private symbols in one unit from another. My understanding is that's the whole point of private symbols. I tried several different variations of LLVM IR and always get
unsupported relocation of undefined symbolat link time.In-case anyone's curious, I also tried using non-private external symbols for the objc selector use-case (e.g.
OBJC_SELECTOR_REFERENCES_XXwithout the\x01L_prefix). If you don't specify the link section, everything compiles fine. But as soon as you specify thesection = "__DATA,__objc_selrefs,literal_pointers", you start gettingld: symbol(s) not found for architecture x86_64errors if the symbol's referenced outside the unit where it's defined.An annotation that enables duplicating inlined statics sounds straightforward, but the use-cases would be extremely limited since how Rust splits codegen units is more-or-less an implementation detail. I can't think of any use cases other than Objective-C binding. Once it's that specific, it starts to make me wonder if there should just be a
#[objc_selector_ref = "selector:name:here:"]syntax built-in to the language instead.jbatez commentedon May 17, 2025
Turns out you can alias a private symbol with a public symbol (e.g.
@FOO = alias ptr, ptr @"\01L_OBJC_SELECTOR_REFERENCES_"in LLVM IR). But that doesn't solve theld: symbol(s) not found for architecture x86_64problem once the symbol's defined withsection = "__DATA,__objc_selrefs,literal_pointers".jbatez commentedon May 18, 2025
I have a branch with an experimental
#[define_in_every_cgu_used]attribute that can be applied to statics.master...jbatez:rust:define_in_every_cgu_used
When rustc would otherwise generate an external reference to a static with this attribute, it generates a complete definition instead.
@madsmtm I tried adding this to the objc2 statics defined in
macro_rules! __statics_seland it madeunstable-static-sel-inlinedwork as desired.bjorn3 commentedon May 18, 2025
#[define_in_every_cgu_used]is unimplementable for cg_clif. It always puts inline asm and regular functions and statics in separate object files as inline asm is compiled using an external assembler, while regular functions and statics are emitted by cg_clif itself.Edit: Never mind. this specific case doesn't involve inline asm. We did have to be careful to deny references to those statics from inline asm though.
madsmtm commentedon May 18, 2025
Very interesting @jbatez! I don't currently have the time to dive into the details myself, but I'm supportive of whatever can be done here to support the Objective-C selector use-case.
jbatez commentedon May 18, 2025
@bjorn3 thank you for taking a look. I think disallowing access to these statics from inline asm is an acceptable caveat.
jbatez commentedon May 21, 2025
I've created a tracking issue for the proposed attribute.
#141339
@bjorn3 it sounds like you've actually done some work to implement this in the Cranelift backend. Could you point me at that so I can implement it in my branch?
bjorn3 commentedon May 21, 2025
I haven't implemented anything in cg_clif. I just realized that I misinterpreted the issue here.
jbatez commentedon Aug 16, 2025
A general
define_in_every_cgu_usedattribute raised too many questions about what a static with multiple definitions would mean. The current plan is a narrow solution specifically for Objective-C classes and selectors. Tacking issue: #145496bjorn3 commentedon Oct 8, 2026
IMO the code in the issue description is just invalid as it tries to override symbol linkage behind the back of the compiler.
#[linkage = "internal"]was also removed in #163405. Also the\x01prefix working is an accident, not a stable part of the rust language. For the specific case of objc selectors, there is now #145496.