Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 28 additions & 9 deletions compiler/rustc_codegen_ssa/src/codegen_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,19 @@ fn process_builtin_attrs(
AttributeKind::LinkSection { name } => codegen_fn_attrs.link_section = Some(*name),
AttributeKind::NoMangle(attr_span) => {
interesting_spans.no_mangle = Some(*attr_span);
if tcx.opt_item_name(did.to_def_id()).is_some() {
codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_MANGLE;
if let Some(name) = tcx.opt_item_name(did.to_def_id()) {
// Don't override #[export_name].

// Also don't override #[link_name]. All places where #[link_name] is allowed
// shouldn't allow #[no_mangle], so #[link_name] shouldn't be a concern here,
// however currently #[no_mangle] is currently merely a warning on foreign
// items rather than a hard error, so we still need to take #[no_mangle] +
// #[link_name] into account.
// FIXME remove this comment once #[no_mangle] on foreign items is a hard error.

if codegen_fn_attrs.symbol_name.is_none() {
codegen_fn_attrs.symbol_name = Some(name);
}
} else {
Comment on lines 94 to 109

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In what way do no_mangle and link_name interact when it looks like they're not allowed to be on the same item?

unsafe extern "C" {
    #[unsafe(no_mangle)]
    safe fn name_in_rust();
}

emits the unused_attributes lint, so I expect it to do nothing there.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The problem is that no_mangle isn't yet a hard error inside extern blocks. Both export_name (and by extension no_mangle) and link_name write to the same symbol_name field in codegen_fn_attrs, so without this check, I think the following would result in wrong_name as name rather than correct_name as #[unsafe(no_mangle)] runs second and overrides the symbol_name set by #[unsafe(link_name)].

extern "C" {
    #[unsafe(link_name = "correct_name")]
    #[unsafe(no_mangle)]
    fn wrong_name();
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

That makes sense. Can you add a comment about this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done

tcx.dcx()
.span_delayed_bug(*attr_span, "no_mangle should be on a named function");
Expand Down Expand Up @@ -402,6 +413,8 @@ fn apply_overrides(tcx: TyCtxt<'_>, did: LocalDefId, codegen_fn_attrs: &mut Code
// get the same symbol name as the *mangled* foreign item they refer to so that's all good.
} else if codegen_fn_attrs.symbol_name.is_some() {
// * This can be overridden with the `#[link_name]` attribute
} else if codegen_fn_attrs.link_ordinal.is_some() {
// * `#[link_ordinal]` and `#[link_name]` are incompatible with each other

@bjorn3 bjorn3 Jul 16, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is a behavior change. We no longer keep the symbol that Rust code refers to unmangled when #[link_ordinal] is used. This matches the behavior on wasm for #[wasm_import_section], but prevents overriding the symbol using a definition with an #[export_name] matching the name we have imported it as in the raw-dylib block. And it is inconsistent with non-ordinal imports through raw-dylib where we could also have used mangled names on our side.

View changes since the review

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think that makes sense, what's the process here? Nominate for t-lang and let them decide whether to accept this change?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ideally, separate refactors from behavior changes. Doing both in the same PR makes things more complicated to manage.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It is non-trivial to keep the old behavior after this refactor. I could open a PR to change the behavior first.

@RalfJung RalfJung Aug 16, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think that would be better, yeah.

And it is inconsistent with non-ordinal imports through raw-dylib where we could also have used mangled names on our side.

It is unclear to me whether you are calling the old or new behavior inconsistent here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ideally, separate refactors from behavior changes.

I think the current PR is small enough to get away with this, no need to split it up here.

However, you can probably simplify much more by lowering into the same attribute during attribute parsing instead. (that would be a more intrusive change where refactors and behavior changes should be split)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It is unclear to me whether you are calling the old or new behavior inconsistent here.

The old behavior is consistent with non-ordinal imports on windows. The new behavior is consistent with imports on wasm, but not with non-ordinal imports on windows.

@RalfJung RalfJung Aug 21, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't understand how this comment explains why we should keep the symbol name unset here.

View changes since the review

} else {
// NOTE: there's one more exception that we cannot apply here. On wasm,
// some items cannot be `no_mangle`.
Expand All @@ -410,7 +423,7 @@ fn apply_overrides(tcx: TyCtxt<'_>, did: LocalDefId, codegen_fn_attrs: &mut Code
// import will *still* be mangled despite this.
//
// if none of the exceptions apply; apply no_mangle
codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_MANGLE;
codegen_fn_attrs.symbol_name = Some(tcx.item_name(did));
}
}
}
Expand Down Expand Up @@ -543,22 +556,24 @@ fn handle_lang_items(
// strippable by the linker.
//
// Additionally weak lang items have predetermined symbol names.
if let Some(lang_item) = lang_item
let link_name_override = if let Some(lang_item) = lang_item
&& let Some(link_name) = lang_item.link_name()
{
codegen_fn_attrs.flags |= CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL;
codegen_fn_attrs.symbol_name = Some(link_name);
}
Some(link_name)
} else {
None
};

// error when using no_mangle on a lang item item
// error when using no_mangle, or export_name on a lang item item
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL)
&& codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NO_MANGLE)
&& codegen_fn_attrs.symbol_name.is_some()
{
let mut err = tcx
.dcx()
.struct_span_err(
interesting_spans.no_mangle.unwrap_or_default(),
"`#[no_mangle]` cannot be used on internal language items",
"`#[no_mangle]` and `#[export_name]` cannot be used on internal language items",
)
.with_note("Rustc requires this item to have a specific mangled name.")
.with_span_label(tcx.def_span(did), "should be the internal language item");
Expand All @@ -574,6 +589,10 @@ fn handle_lang_items(
}
err.emit();
}

if let Some(link_name_override) = link_name_override {
codegen_fn_attrs.symbol_name = Some(link_name_override);
}
}

/// Generate the [`CodegenFnAttrs`] for an item (identified by the [`LocalDefId`]).
Expand Down
52 changes: 18 additions & 34 deletions compiler/rustc_lint/src/foreign_modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,23 +77,11 @@ struct ClashingExternDeclarations {
seen_decls: UnordMap<Symbol, hir::OwnerId>,
}

/// Differentiate between whether the name for an extern decl came from the link_name attribute or
/// just from declaration itself. This is important because we don't want to report clashes on
/// symbol name if they don't actually clash because one or the other links against a symbol with a
/// different name.
enum SymbolName {
/// The name of the symbol + the span of the annotation which introduced the link name.
Link(Symbol, Span),
/// No link name, so just the name of the symbol.
Normal(Symbol),
}

impl SymbolName {
fn get_name(&self) -> Symbol {
match self {
SymbolName::Link(s, _) | SymbolName::Normal(s) => *s,
}
}
struct SymbolName {
/// The name of the symbol that will be linked against.
link_name: Symbol,
/// The span of the annotation which introduced the link name.
span: Option<Span>,
}

impl ClashingExternDeclarations {
Expand Down Expand Up @@ -140,7 +128,7 @@ impl ClashingExternDeclarations {

// Finally, emit the diagnostic.
let this = tcx.item_name(this_fi.owner_id.to_def_id());
let orig = orig.get_name();
let orig = orig.link_name;
let previous_decl_label = get_relevant_span(tcx, existing_did);
let mismatch_label = get_relevant_span(tcx, this_fi.owner_id);
let sub =
Expand Down Expand Up @@ -176,28 +164,24 @@ impl ClashingExternDeclarations {
/// the name specified in a #[link_name = ...] attribute if one was specified, else, just the
/// symbol's name.
fn name_of_extern_decl(tcx: TyCtxt<'_>, fi: hir::OwnerId) -> SymbolName {
if let Some((overridden_link_name, overridden_link_name_span)) =
tcx.codegen_fn_attrs(fi).symbol_name.map(|overridden_link_name| {
// FIXME: Instead of searching through the attributes again to get span
// information, we could have codegen_fn_attrs also give span information back for
// where the attribute was defined. However, until this is found to be a
// bottleneck, this does just fine.
(overridden_link_name, find_attr!(tcx, fi, LinkName {span, ..} => *span).unwrap())
})
{
SymbolName::Link(overridden_link_name, overridden_link_name_span)
} else {
SymbolName::Normal(tcx.item_name(fi.to_def_id()))
// FIXME if symbol_name is not set, this is likely a #[rustc_std_internal_symbol] or EII which
// actually have their name mangled and thus should use the mangled name here.
let link_name =
tcx.codegen_fn_attrs(fi).symbol_name.unwrap_or_else(|| tcx.item_name(fi.to_def_id()));
SymbolName {
link_name,
// FIXME: Instead of searching through the attributes again to get span
// information, we could have codegen_fn_attrs also give span information back for
// where the attribute was defined. However, until this is found to be a
// bottleneck, this does just fine.
span: find_attr!(tcx, fi, LinkName {span, ..} => *span),
}
}

/// We want to ensure that we use spans for both decls that include where the
/// name was defined, whether that was from the link_name attribute or not.
fn get_relevant_span(tcx: TyCtxt<'_>, fi: hir::OwnerId) -> Span {
match name_of_extern_decl(tcx, fi) {
SymbolName::Normal(_) => tcx.def_span(fi),
SymbolName::Link(_, annot_span) => annot_span,
}
if let Some(span) = name_of_extern_decl(tcx, fi).span { span } else { tcx.def_span(fi) }
}

/// Checks whether two types are structurally the same enough that the declarations shouldn't
Expand Down
10 changes: 1 addition & 9 deletions compiler/rustc_middle/src/middle/codegen_fn_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,6 @@ impl<'tcx> TyCtxt<'tcx> {
if let InstanceKind::Shim(ShimKind::Reify(_, _)) = instance_kind
&& attrs.flags.contains(CodegenFnAttrFlags::TRACK_CALLER)
{
if attrs.flags.contains(CodegenFnAttrFlags::NO_MANGLE) {
attrs.to_mut().flags.remove(CodegenFnAttrFlags::NO_MANGLE);
}

if attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL) {
attrs.to_mut().flags.remove(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL);
}
Expand Down Expand Up @@ -203,9 +199,6 @@ bitflags::bitflags! {
/// `#[naked]`: an indicator to LLVM that no function prologue/epilogue
/// should be generated.
const NAKED = 1 << 2;
/// `#[no_mangle]`: an indicator that the function's name should be the same
/// as its symbol.
const NO_MANGLE = 1 << 3;
/// `#[rustc_std_internal_symbol]`: an indicator that this symbol is a
/// "weird symbol" for the standard library in that it has slightly
/// different linkage, visibility, and reachability rules.
Expand Down Expand Up @@ -290,8 +283,7 @@ impl CodegenFnAttrs {
return false;
}

self.flags.contains(CodegenFnAttrFlags::NO_MANGLE)
|| self.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL)
self.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL)
// note: for these we do also set a symbol name so technically also handled by the
// condition below. However, I think that regardless these should be treated as extern.
|| self.flags.contains(CodegenFnAttrFlags::EXTERNALLY_IMPLEMENTABLE_ITEM)
Expand Down
5 changes: 0 additions & 5 deletions compiler/rustc_symbol_mangling/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,11 +221,6 @@ pub fn symbol_name_from_attrs<'tcx>(
// Use provided name
return Some(name.to_string());
}

if attrs.flags.contains(CodegenFnAttrFlags::NO_MANGLE) {
// Don't mangle
return Some(tcx.item_name(def_id).to_string());
}
}

None
Expand Down
1 change: 0 additions & 1 deletion src/tools/miri/src/shims/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let attrs = tcx.codegen_fn_attrs(def_id);
// Skip over items without an explicitly defined symbol name.
if !(attrs.symbol_name.is_some()
|| attrs.flags.contains(CodegenFnAttrFlags::NO_MANGLE)
|| attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL))
{
return interp_ok(());
Expand Down
10 changes: 3 additions & 7 deletions tests/ui/codegen/no-mangle-on-internal-lang-items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@
#![feature(rustc_attrs)]

#[rustc_std_internal_symbol]
#[unsafe(no_mangle)] //~ERROR `#[no_mangle]` cannot be used on internal language items
fn internal_lang_function () {
#[unsafe(no_mangle)] //~ERROR `#[no_mangle]` and `#[export_name]` cannot be used on internal language items
fn internal_lang_function() {}

}

fn main() {

}
fn main() {}
6 changes: 3 additions & 3 deletions tests/ui/codegen/no-mangle-on-internal-lang-items.stderr
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
error: `#[no_mangle]` cannot be used on internal language items
error: `#[no_mangle]` and `#[export_name]` cannot be used on internal language items
--> $DIR/no-mangle-on-internal-lang-items.rs:7:1
|
LL | #[unsafe(no_mangle)]
| ^^^^^^^^^^^^^^^^^^^^
LL | fn internal_lang_function () {
| ---------------------------- should be the internal language item
LL | fn internal_lang_function() {}
| --------------------------- should be the internal language item
|
= note: Rustc requires this item to have a specific mangled name.

Expand Down
4 changes: 2 additions & 2 deletions tests/ui/codegen/no-mangle-on-panic-handler.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Issue an error when the user uses #[no_mangle] on the panic handler
//@ edition:2024

#![crate_type="lib"]
#![crate_type = "lib"]
#![no_std]
#![no_main]

use core::panic::PanicInfo;

#[unsafe(no_mangle)] //~ ERROR `#[no_mangle]` cannot be used on internal language items
#[unsafe(no_mangle)] //~ ERROR `#[no_mangle]` and `#[export_name]` cannot be used on internal language items
#[panic_handler]
pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! {
loop {}
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/codegen/no-mangle-on-panic-handler.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: `#[no_mangle]` cannot be used on internal language items
error: `#[no_mangle]` and `#[export_name]` cannot be used on internal language items
--> $DIR/no-mangle-on-panic-handler.rs:10:1
|
LL | #[unsafe(no_mangle)]
Expand Down
Loading