From 9003a3bd4a3a2e5a73df8e344c3f3c8b9d74f353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Tue, 29 Jul 2025 20:51:46 +0200 Subject: [PATCH] make no_mangle explicit on foreign items --- .../rustc_codegen_ssa/src/codegen_attrs.rs | 29 +++++++++++++++ .../src/middle/codegen_fn_attrs.rs | 4 +++ compiler/rustc_passes/src/check_attr.rs | 36 +++++++++---------- compiler/rustc_passes/src/dead.rs | 20 ++++++----- compiler/rustc_passes/src/reachable.rs | 8 ++++- compiler/rustc_symbol_mangling/src/lib.rs | 29 ++------------- 6 files changed, 72 insertions(+), 54 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs index 78edc69b237af..7e1921aff2f36 100644 --- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs +++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs @@ -445,6 +445,35 @@ fn apply_overrides(tcx: TyCtxt<'_>, did: LocalDefId, codegen_fn_attrs: &mut Code if tcx.should_inherit_track_caller(did) { codegen_fn_attrs.flags |= CodegenFnAttrFlags::TRACK_CALLER; } + + // Foreign items by default use no mangling for their symbol name. + if tcx.is_foreign_item(did) { + // There's a few exceptions to this rule though: + if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL) { + // * `#[rustc_std_internal_symbol]` mangles the symbol name in a special way + // both for exports and imports through foreign items. This is handled further, + // during symbol mangling logic/ + } else if codegen_fn_attrs.link_name.is_some() { + // * This can be overridden with the `#[link_name]` attribute + } else if tcx.sess.target.is_like_wasm + && tcx.wasm_import_module_map(LOCAL_CRATE).contains_key(&did.into()) + { + // * On the wasm32 targets there is a bug (or feature) in LLD [1] where the + // same-named symbol when imported from different wasm modules will get + // hooked up incorrectly. As a result foreign symbols, on the wasm target, + // with a wasm import module, get mangled. Additionally our codegen will + // deduplicate symbols based purely on the symbol name, but for wasm this + // isn't quite right because the same-named symbol on wasm can come from + // different modules. For these reasons if `#[link(wasm_import_module)]` + // is present we mangle everything on wasm because the demangled form will + // show up in the `wasm-import-name` custom attribute in LLVM IR. + // + // [1]: https://bugs.llvm.org/show_bug.cgi?id=44316 + } else { + // if none of the exceptions apply; apply no_mangle + codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_MANGLE; + } + } } fn check_result( diff --git a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs index 34a29acdc85a3..f62092d6b57d8 100644 --- a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs +++ b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs @@ -192,6 +192,10 @@ impl CodegenFnAttrs { /// * `#[export_name(...)]` is present /// * `#[linkage]` is present /// + /// Note that this returns true for foreign items. + /// However, in some places that care about `contains_extern_indicator`, foreign items + /// (in an `extern` block) should explicitly be ignored. + /// /// Keep this in sync with the logic for the unused_attributes for `#[inline]` lint. pub fn contains_extern_indicator(&self) -> bool { self.flags.contains(CodegenFnAttrFlags::NO_MANGLE) diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 0b329cc38b070..272fd7b7df3b3 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -565,7 +565,24 @@ impl<'tcx> CheckAttrVisitor<'tcx> { match target { Target::Fn | Target::Closure - | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => {} + | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => { + // `#[inline]` is ignored if the symbol must be codegened upstream because it's exported. + if let Some(did) = hir_id.as_owner() + && self.tcx.def_kind(did).has_codegen_attrs() + && kind != &InlineAttr::Never + { + let attrs = self.tcx.codegen_fn_attrs(did); + // Not checking naked as `#[inline]` is forbidden for naked functions anyways. + if attrs.contains_extern_indicator() { + self.tcx.emit_node_span_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr_span, + errors::InlineIgnoredForExported {}, + ); + } + } + } Target::Method(MethodKind::Trait { body: false }) | Target::ForeignFn => { self.tcx.emit_node_span_lint( UNUSED_ATTRIBUTES, @@ -592,23 +609,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { self.dcx().emit_err(errors::InlineNotFnOrClosure { attr_span, defn_span }); } } - - // `#[inline]` is ignored if the symbol must be codegened upstream because it's exported. - if let Some(did) = hir_id.as_owner() - && self.tcx.def_kind(did).has_codegen_attrs() - && kind != &InlineAttr::Never - { - let attrs = self.tcx.codegen_fn_attrs(did); - // Not checking naked as `#[inline]` is forbidden for naked functions anyways. - if attrs.contains_extern_indicator() { - self.tcx.emit_node_span_lint( - UNUSED_ATTRIBUTES, - hir_id, - attr_span, - errors::InlineIgnoredForExported {}, - ); - } - } } /// Checks that `#[coverage(..)]` is applied to a function/closure/method, diff --git a/compiler/rustc_passes/src/dead.rs b/compiler/rustc_passes/src/dead.rs index a90d1af87ca19..4be3ad2562027 100644 --- a/compiler/rustc_passes/src/dead.rs +++ b/compiler/rustc_passes/src/dead.rs @@ -694,15 +694,17 @@ fn has_allow_dead_code_or_lang_attr( } fn has_used_like_attr(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { - tcx.def_kind(def_id).has_codegen_attrs() && { - let cg_attrs = tcx.codegen_fn_attrs(def_id); - - // #[used], #[no_mangle], #[export_name], etc also keeps the item alive - // forcefully, e.g., for placing it in a specific section. - cg_attrs.contains_extern_indicator() - || cg_attrs.flags.contains(CodegenFnAttrFlags::USED_COMPILER) - || cg_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER) - } + tcx.def_kind(def_id).has_codegen_attrs() + && { + let cg_attrs = tcx.codegen_fn_attrs(def_id); + + // #[used], #[no_mangle], #[export_name], etc also keeps the item alive + // forcefully, e.g., for placing it in a specific section. + cg_attrs.contains_extern_indicator() + || cg_attrs.flags.contains(CodegenFnAttrFlags::USED_COMPILER) + || cg_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER) + } + && !tcx.is_foreign_item(def_id) } if has_allow_expect_dead_code(tcx, def_id) { diff --git a/compiler/rustc_passes/src/reachable.rs b/compiler/rustc_passes/src/reachable.rs index 2f78c22c7483b..f3af2229f4292 100644 --- a/compiler/rustc_passes/src/reachable.rs +++ b/compiler/rustc_passes/src/reachable.rs @@ -183,7 +183,8 @@ impl<'tcx> ReachableContext<'tcx> { } else { CodegenFnAttrs::EMPTY }; - let is_extern = codegen_attrs.contains_extern_indicator(); + let is_extern = + codegen_attrs.contains_extern_indicator() && !self.tcx.is_foreign_item(search_item); if is_extern { self.reachable_symbols.insert(search_item); } @@ -417,12 +418,17 @@ fn check_item<'tcx>( } fn has_custom_linkage(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { + if tcx.is_foreign_item(def_id) { + return false; + } + // Anything which has custom linkage gets thrown on the worklist no // matter where it is in the crate, along with "special std symbols" // which are currently akin to allocator symbols. if !tcx.def_kind(def_id).has_codegen_attrs() { return false; } + let codegen_attrs = tcx.codegen_fn_attrs(def_id); codegen_attrs.contains_extern_indicator() // FIXME(nbdd0121): `#[used]` are marked as reachable here so it's picked up by diff --git a/compiler/rustc_symbol_mangling/src/lib.rs b/compiler/rustc_symbol_mangling/src/lib.rs index 6bcb7f6e093c4..d0549b91680a1 100644 --- a/compiler/rustc_symbol_mangling/src/lib.rs +++ b/compiler/rustc_symbol_mangling/src/lib.rs @@ -218,32 +218,9 @@ fn compute_symbol_name<'tcx>( } } - // Foreign items by default use no mangling for their symbol name. There's a - // few exceptions to this rule though: - // - // * This can be overridden with the `#[link_name]` attribute - // - // * On the wasm32 targets there is a bug (or feature) in LLD [1] where the - // same-named symbol when imported from different wasm modules will get - // hooked up incorrectly. As a result foreign symbols, on the wasm target, - // with a wasm import module, get mangled. Additionally our codegen will - // deduplicate symbols based purely on the symbol name, but for wasm this - // isn't quite right because the same-named symbol on wasm can come from - // different modules. For these reasons if `#[link(wasm_import_module)]` - // is present we mangle everything on wasm because the demangled form will - // show up in the `wasm-import-name` custom attribute in LLVM IR. - // - // * `#[rustc_std_internal_symbol]` mangles the symbol name in a special way - // both for exports and imports through foreign items. This is handled above. - // [1]: https://bugs.llvm.org/show_bug.cgi?id=44316 - if tcx.is_foreign_item(def_id) - && (!tcx.sess.target.is_like_wasm - || !tcx.wasm_import_module_map(def_id.krate).contains_key(&def_id)) - { - if let Some(name) = attrs.link_name { - return name.to_string(); - } - return tcx.item_name(def_id).to_string(); + if let Some(name) = attrs.link_name { + // Use provided name + return name.to_string(); } if let Some(name) = attrs.export_name {