Skip to content

Conversation

@folkertdev
Copy link
Contributor

fixes #148307

Emit .private_extern on macos when the naked function uses Linkage::Internal. Failing to do so can cause issues with LTO.

The documentation on this directive is kind of sparse, but I believe this is at least not incorrect, and does fix the issue.

r? @Amanieu
cc @bjorn3

@folkertdev folkertdev added the A-naked Area: `#[naked]`, prologue and epilogue-free, functions, https://git.io/vAzzS label Oct 31, 2025
@rustbot
Copy link
Collaborator

rustbot commented Oct 31, 2025

Some changes occurred in compiler/rustc_codegen_ssa

cc @WaffleLapkin

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Oct 31, 2025
@bjorn3
Copy link
Member

bjorn3 commented Nov 2, 2025

Is this really only an issue on macOS? I would have assumes this is an issue on all platforms.

@folkertdev
Copy link
Contributor Author

The reproduction given in the issue builds successfully for me on linux.

I did just notice that we do already have

match item_data.visibility {
Visibility::Default | Visibility::Protected => {}
Visibility::Hidden => writeln!(begin, ".private_extern {asm_name}").unwrap(),
}

That clearly did not actually trigger in this case. Is that right?

@bjorn3
Copy link
Member

bjorn3 commented Nov 2, 2025

Yeah, the issue here I believe is that rustc correctly computes the symbol visibility for the naked function as private, but then when ThinLTO decides to inline the caller of the naked function, it is unable to change the naked function to be public (+ add a .llvm.<somehash> suffix to the symbol name to prevent conflicts) like it would do for regular functions. So we have to mark all naked functions as visible within the same DSO rather than just within the same object file, aka use external linkage + hidden visibility on ELF and equivalent with other object file formats.

@folkertdev
Copy link
Contributor Author

aka use external linkage + hidden visibility on ELF and equivalent with other object file formats.

Hmm, we do emit the following:

// If the function is #[naked] or contains any other attribute that requires exactly-once
// instantiation:
// We emit an unused_attributes lint for this case, which should be kept in sync if possible.
let codegen_fn_attrs = tcx.codegen_instance_attrs(instance.def);
if codegen_fn_attrs.contains_extern_indicator()
|| codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NAKED)
{
return InstantiationMode::GloballyShared { may_conflict: false };
}

but apparently that is not enough.

Is compiler/rustc_monomorphize/src/partitioning.rs the right place to fix this?

@bjorn3
Copy link
Member

bjorn3 commented Nov 2, 2025

That gets overridden at

// If we got here, we did not find any uses from other CGUs, so
// it's fine to make this monomorphization internal.
data.linkage = Linkage::Internal;
data.visibility = Visibility::Default;

@folkertdev folkertdev force-pushed the naked-macos-private-extern branch from 502098d to d96ae8f Compare November 2, 2025 20:59
@rustbot
Copy link
Collaborator

rustbot commented Nov 2, 2025

This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

Comment on lines -184 to +185
// write nothing
// LTO can fail when internal linkage is used.
emit_fatal("naked functions may not have internal linkage")
Copy link
Contributor Author

@folkertdev folkertdev Nov 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this too strict? We could allow it, but I think this is only reachable now with an explicit linkage attribute (which is unstable).

@Amanieu
Copy link
Member

Amanieu commented Nov 4, 2025

I think the issue with inlining is bigger than that and we effectively have to always use global visibility for naked functions. Consider dylib crates where the naked function is private but is called by an inlinable function. If that function gets inlined into another crate then the naked function needs to have global visibility so that it can be referenced from another dylib.

@bjorn3
Copy link
Member

bjorn3 commented Nov 4, 2025

Consider dylib crates where the naked function is private but is called by an inlinable function. If that function gets inlined into another crate then the naked function needs to have global visibility so that it can be referenced from another dylib.

If rustc considers the caller to be cross-crate codegenable, it would already mark the callee as public be it a regular function be it a naked function. The problematic case is where the codegen backend decides to inline a function into another codegen unit when rustc didn't consider it cross-crate codegenable. This can only happen within a single dylib through ThinLTO, not between dylibs. And it is only an issue for naked functions as ThinLTO can't make the function public itself as it would do for regular functions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-naked Area: `#[naked]`, prologue and epilogue-free, functions, https://git.io/vAzzS S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unable to link code containing naked functions on ARM64 macOS with ThinLTO enabled

4 participants