about summary refs log tree commit diff
diff options
context:
space:
mode:
authorSasha Pourcelot <sasha.pourcelot@protonmail.com>2025-08-12 20:22:45 +0200
committerSasha Pourcelot <sasha.pourcelot@protonmail.com>2025-08-13 21:01:37 +0200
commitd435197afcb019a692aa1faf8b7169e167ac1de8 (patch)
tree95e56a3b1cde6aa054b53a2fbc515dba1c5e9b71
parent350d0ef0ec0493e6d21cfb265cb8211a0e74d766 (diff)
downloadrust-d435197afcb019a692aa1faf8b7169e167ac1de8.tar.gz
rust-d435197afcb019a692aa1faf8b7169e167ac1de8.zip
Port the `#[linkage]` attribute to the new attribute system
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/link_attrs.rs76
-rw-r--r--compiler/rustc_attr_parsing/src/context.rs3
-rw-r--r--compiler/rustc_codegen_cranelift/src/constant.rs8
-rw-r--r--compiler/rustc_codegen_cranelift/src/driver/aot.rs5
-rw-r--r--compiler/rustc_codegen_cranelift/src/linkage.rs3
-rw-r--r--compiler/rustc_codegen_gcc/src/base.rs2
-rw-r--r--compiler/rustc_codegen_gcc/src/consts.rs2
-rw-r--r--compiler/rustc_codegen_gcc/src/mono_item.rs3
-rw-r--r--compiler/rustc_codegen_llvm/src/base.rs3
-rw-r--r--compiler/rustc_codegen_llvm/src/consts.rs3
-rw-r--r--compiler/rustc_codegen_llvm/src/mono_item.rs3
-rw-r--r--compiler/rustc_codegen_ssa/src/back/symbol_export.rs3
-rw-r--r--compiler/rustc_codegen_ssa/src/codegen_attrs.rs77
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/naked_asm.rs4
-rw-r--r--compiler/rustc_codegen_ssa/src/mono_item.rs3
-rw-r--r--compiler/rustc_codegen_ssa/src/traits/declare.rs3
-rw-r--r--compiler/rustc_hir/src/attrs/data_structures.rs21
-rw-r--r--compiler/rustc_hir/src/attrs/encode_cross_crate.rs1
-rw-r--r--compiler/rustc_hir/src/hir.rs1
-rw-r--r--compiler/rustc_middle/src/middle/codegen_fn_attrs.rs3
-rw-r--r--compiler/rustc_middle/src/mir/mono.rs18
-rw-r--r--compiler/rustc_monomorphize/src/partitioning.rs4
-rw-r--r--compiler/rustc_passes/src/check_attr.rs8
-rw-r--r--compiler/rustc_span/src/symbol.rs8
-rw-r--r--tests/ui/attributes/malformed-attrs.stderr37
-rw-r--r--tests/ui/linkage-attr/linkage3.rs2
-rw-r--r--tests/ui/linkage-attr/linkage3.stderr27
27 files changed, 207 insertions, 124 deletions
diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs
index d406c30b83e..e4ced2e37c5 100644
--- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs
@@ -1,6 +1,6 @@
 use rustc_feature::{AttributeTemplate, template};
-use rustc_hir::attrs::AttributeKind;
 use rustc_hir::attrs::AttributeKind::{LinkName, LinkOrdinal, LinkSection};
+use rustc_hir::attrs::{AttributeKind, Linkage};
 use rustc_span::{Span, Symbol, sym};
 
 use crate::attributes::{
@@ -129,3 +129,77 @@ impl<S: Stage> SingleAttributeParser<S> for LinkOrdinalParser {
         Some(LinkOrdinal { ordinal, span: cx.attr_span })
     }
 }
+
+pub(crate) struct LinkageParser;
+
+impl<S: Stage> SingleAttributeParser<S> for LinkageParser {
+    const PATH: &[Symbol] = &[sym::linkage];
+
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
+
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+
+    const TEMPLATE: AttributeTemplate = template!(NameValueStr: [
+        "available_externally",
+        "common",
+        "extern_weak",
+        "external",
+        "internal",
+        "linkonce",
+        "linkonce_odr",
+        "weak",
+        "weak_odr",
+    ]);
+
+    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
+        let Some(name_value) = args.name_value() else {
+            cx.expected_name_value(cx.attr_span, Some(sym::linkage));
+            return None;
+        };
+
+        let Some(value) = name_value.value_as_str() else {
+            cx.expected_string_literal(name_value.value_span, Some(name_value.value_as_lit()));
+            return None;
+        };
+
+        // Use the names from src/llvm/docs/LangRef.rst here. Most types are only
+        // applicable to variable declarations and may not really make sense for
+        // Rust code in the first place but allow them anyway and trust that the
+        // user knows what they're doing. Who knows, unanticipated use cases may pop
+        // up in the future.
+        //
+        // ghost, dllimport, dllexport and linkonce_odr_autohide are not supported
+        // and don't have to be, LLVM treats them as no-ops.
+        let linkage = match value {
+            sym::available_externally => Linkage::AvailableExternally,
+            sym::common => Linkage::Common,
+            sym::extern_weak => Linkage::ExternalWeak,
+            sym::external => Linkage::External,
+            sym::internal => Linkage::Internal,
+            sym::linkonce => Linkage::LinkOnceAny,
+            sym::linkonce_odr => Linkage::LinkOnceODR,
+            sym::weak => Linkage::WeakAny,
+            sym::weak_odr => Linkage::WeakODR,
+
+            _ => {
+                cx.expected_specific_argument(
+                    name_value.value_span,
+                    vec![
+                        "available_externally",
+                        "common",
+                        "extern_weak",
+                        "external",
+                        "internal",
+                        "linkonce",
+                        "linkonce_odr",
+                        "weak",
+                        "weak_odr",
+                    ],
+                );
+                return None;
+            }
+        };
+
+        Some(AttributeKind::Linkage(linkage, cx.attr_span))
+    }
+}
diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs
index 1420753a44e..6045a8b28a2 100644
--- a/compiler/rustc_attr_parsing/src/context.rs
+++ b/compiler/rustc_attr_parsing/src/context.rs
@@ -27,7 +27,7 @@ use crate::attributes::dummy::DummyParser;
 use crate::attributes::inline::{InlineParser, RustcForceInlineParser};
 use crate::attributes::link_attrs::{
     ExportStableParser, FfiConstParser, FfiPureParser, LinkNameParser, LinkOrdinalParser,
-    LinkSectionParser, StdInternalSymbolParser,
+    LinkSectionParser, LinkageParser, StdInternalSymbolParser,
 };
 use crate::attributes::lint_helpers::{
     AsPtrParser, AutomaticallyDerivedParser, PassByValueParser, PubTransparentParser,
@@ -167,6 +167,7 @@ attribute_parsers!(
         Single<LinkNameParser>,
         Single<LinkOrdinalParser>,
         Single<LinkSectionParser>,
+        Single<LinkageParser>,
         Single<MustUseParser>,
         Single<OptimizeParser>,
         Single<PathAttributeParser>,
diff --git a/compiler/rustc_codegen_cranelift/src/constant.rs b/compiler/rustc_codegen_cranelift/src/constant.rs
index bec546badc9..a56466750e7 100644
--- a/compiler/rustc_codegen_cranelift/src/constant.rs
+++ b/compiler/rustc_codegen_cranelift/src/constant.rs
@@ -281,8 +281,8 @@ fn data_id_for_static(
             .abi
             .bytes();
 
-        let linkage = if import_linkage == rustc_middle::mir::mono::Linkage::ExternalWeak
-            || import_linkage == rustc_middle::mir::mono::Linkage::WeakAny
+        let linkage = if import_linkage == rustc_hir::attrs::Linkage::ExternalWeak
+            || import_linkage == rustc_hir::attrs::Linkage::WeakAny
         {
             Linkage::Preemptible
         } else {
@@ -332,8 +332,8 @@ fn data_id_for_static(
 
     let linkage = if definition {
         crate::linkage::get_static_linkage(tcx, def_id)
-    } else if attrs.linkage == Some(rustc_middle::mir::mono::Linkage::ExternalWeak)
-        || attrs.linkage == Some(rustc_middle::mir::mono::Linkage::WeakAny)
+    } else if attrs.linkage == Some(rustc_hir::attrs::Linkage::ExternalWeak)
+        || attrs.linkage == Some(rustc_hir::attrs::Linkage::WeakAny)
     {
         Linkage::Preemptible
     } else {
diff --git a/compiler/rustc_codegen_cranelift/src/driver/aot.rs b/compiler/rustc_codegen_cranelift/src/driver/aot.rs
index 8ec3599b63d..7e77781dc2f 100644
--- a/compiler/rustc_codegen_cranelift/src/driver/aot.rs
+++ b/compiler/rustc_codegen_cranelift/src/driver/aot.rs
@@ -18,12 +18,11 @@ use rustc_codegen_ssa::{
 use rustc_data_structures::profiling::SelfProfilerRef;
 use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
 use rustc_data_structures::sync::{IntoDynSyncSend, par_map};
+use rustc_hir::attrs::Linkage as RLinkage;
 use rustc_metadata::fs::copy_to_stdout;
 use rustc_middle::dep_graph::{WorkProduct, WorkProductId};
 use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
-use rustc_middle::mir::mono::{
-    CodegenUnit, Linkage as RLinkage, MonoItem, MonoItemData, Visibility,
-};
+use rustc_middle::mir::mono::{CodegenUnit, MonoItem, MonoItemData, Visibility};
 use rustc_session::Session;
 use rustc_session::config::{DebugInfo, OutFileName, OutputFilenames, OutputType};
 
diff --git a/compiler/rustc_codegen_cranelift/src/linkage.rs b/compiler/rustc_codegen_cranelift/src/linkage.rs
index ca853aac158..d76ab9d0109 100644
--- a/compiler/rustc_codegen_cranelift/src/linkage.rs
+++ b/compiler/rustc_codegen_cranelift/src/linkage.rs
@@ -1,4 +1,5 @@
-use rustc_middle::mir::mono::{Linkage as RLinkage, MonoItem, Visibility};
+use rustc_hir::attrs::Linkage as RLinkage;
+use rustc_middle::mir::mono::{MonoItem, Visibility};
 
 use crate::prelude::*;
 
diff --git a/compiler/rustc_codegen_gcc/src/base.rs b/compiler/rustc_codegen_gcc/src/base.rs
index c105916bbb2..e9d72e457a0 100644
--- a/compiler/rustc_codegen_gcc/src/base.rs
+++ b/compiler/rustc_codegen_gcc/src/base.rs
@@ -8,8 +8,8 @@ use rustc_codegen_ssa::ModuleCodegen;
 use rustc_codegen_ssa::base::maybe_create_entry_wrapper;
 use rustc_codegen_ssa::mono_item::MonoItemExt;
 use rustc_codegen_ssa::traits::DebugInfoCodegenMethods;
+use rustc_hir::attrs::Linkage;
 use rustc_middle::dep_graph;
-use rustc_middle::mir::mono::Linkage;
 #[cfg(feature = "master")]
 use rustc_middle::mir::mono::Visibility;
 use rustc_middle::ty::TyCtxt;
diff --git a/compiler/rustc_codegen_gcc/src/consts.rs b/compiler/rustc_codegen_gcc/src/consts.rs
index 873f1f1951c..619277eba8b 100644
--- a/compiler/rustc_codegen_gcc/src/consts.rs
+++ b/compiler/rustc_codegen_gcc/src/consts.rs
@@ -5,13 +5,13 @@ use rustc_abi::{self as abi, Align, HasDataLayout, Primitive, Size, WrappingRang
 use rustc_codegen_ssa::traits::{
     BaseTypeCodegenMethods, ConstCodegenMethods, StaticCodegenMethods,
 };
+use rustc_hir::attrs::Linkage;
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::LOCAL_CRATE;
 use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs};
 use rustc_middle::mir::interpret::{
     self, ConstAllocation, ErrorHandled, Scalar as InterpScalar, read_target_uint,
 };
-use rustc_middle::mir::mono::Linkage;
 use rustc_middle::ty::layout::LayoutOf;
 use rustc_middle::ty::{self, Instance};
 use rustc_middle::{bug, span_bug};
diff --git a/compiler/rustc_codegen_gcc/src/mono_item.rs b/compiler/rustc_codegen_gcc/src/mono_item.rs
index ff188c437da..35d44d21bcb 100644
--- a/compiler/rustc_codegen_gcc/src/mono_item.rs
+++ b/compiler/rustc_codegen_gcc/src/mono_item.rs
@@ -1,11 +1,12 @@
 #[cfg(feature = "master")]
 use gccjit::{FnAttribute, VarAttribute};
 use rustc_codegen_ssa::traits::PreDefineCodegenMethods;
+use rustc_hir::attrs::Linkage;
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::{DefId, LOCAL_CRATE};
 use rustc_middle::bug;
 use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
-use rustc_middle::mir::mono::{Linkage, Visibility};
+use rustc_middle::mir::mono::Visibility;
 use rustc_middle::ty::layout::{FnAbiOf, HasTypingEnv, LayoutOf};
 use rustc_middle::ty::{self, Instance, TypeVisitableExt};
 
diff --git a/compiler/rustc_codegen_llvm/src/base.rs b/compiler/rustc_codegen_llvm/src/base.rs
index 5dda836988c..9cc5d8dbc21 100644
--- a/compiler/rustc_codegen_llvm/src/base.rs
+++ b/compiler/rustc_codegen_llvm/src/base.rs
@@ -18,9 +18,10 @@ use rustc_codegen_ssa::base::maybe_create_entry_wrapper;
 use rustc_codegen_ssa::mono_item::MonoItemExt;
 use rustc_codegen_ssa::traits::*;
 use rustc_data_structures::small_c_str::SmallCStr;
+use rustc_hir::attrs::Linkage;
 use rustc_middle::dep_graph;
 use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs;
-use rustc_middle::mir::mono::{Linkage, Visibility};
+use rustc_middle::mir::mono::Visibility;
 use rustc_middle::ty::TyCtxt;
 use rustc_session::config::DebugInfo;
 use rustc_span::Symbol;
diff --git a/compiler/rustc_codegen_llvm/src/consts.rs b/compiler/rustc_codegen_llvm/src/consts.rs
index 6b06daf3477..9ec7b0f80ae 100644
--- a/compiler/rustc_codegen_llvm/src/consts.rs
+++ b/compiler/rustc_codegen_llvm/src/consts.rs
@@ -4,6 +4,7 @@ use rustc_abi::{Align, HasDataLayout, Primitive, Scalar, Size, WrappingRange};
 use rustc_codegen_ssa::common;
 use rustc_codegen_ssa::traits::*;
 use rustc_hir::LangItem;
+use rustc_hir::attrs::Linkage;
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::{DefId, LOCAL_CRATE};
 use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs};
@@ -11,7 +12,7 @@ use rustc_middle::mir::interpret::{
     Allocation, ConstAllocation, ErrorHandled, InitChunk, Pointer, Scalar as InterpScalar,
     read_target_uint,
 };
-use rustc_middle::mir::mono::{Linkage, MonoItem};
+use rustc_middle::mir::mono::MonoItem;
 use rustc_middle::ty::layout::{HasTypingEnv, LayoutOf};
 use rustc_middle::ty::{self, Instance};
 use rustc_middle::{bug, span_bug};
diff --git a/compiler/rustc_codegen_llvm/src/mono_item.rs b/compiler/rustc_codegen_llvm/src/mono_item.rs
index f9edaded60d..5075befae8a 100644
--- a/compiler/rustc_codegen_llvm/src/mono_item.rs
+++ b/compiler/rustc_codegen_llvm/src/mono_item.rs
@@ -1,8 +1,9 @@
 use rustc_codegen_ssa::traits::*;
+use rustc_hir::attrs::Linkage;
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::{DefId, LOCAL_CRATE};
 use rustc_middle::bug;
-use rustc_middle::mir::mono::{Linkage, Visibility};
+use rustc_middle::mir::mono::Visibility;
 use rustc_middle::ty::layout::{FnAbiOf, HasTypingEnv, LayoutOf};
 use rustc_middle::ty::{self, Instance, TypeVisitableExt};
 use rustc_session::config::CrateType;
diff --git a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs
index 0494666bda9..77096822fdc 100644
--- a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs
+++ b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs
@@ -306,7 +306,8 @@ fn exported_generic_symbols_provider_local<'tcx>(
     let mut symbols: Vec<_> = vec![];
 
     if tcx.local_crate_exports_generics() {
-        use rustc_middle::mir::mono::{Linkage, MonoItem, Visibility};
+        use rustc_hir::attrs::Linkage;
+        use rustc_middle::mir::mono::{MonoItem, Visibility};
         use rustc_middle::ty::InstanceKind;
 
         // Normally, we require that shared monomorphizations are not hidden,
diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
index 287787eb3d1..a36a772bc97 100644
--- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
+++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
@@ -11,7 +11,6 @@ use rustc_hir::{self as hir, Attribute, LangItem, find_attr, lang_items};
 use rustc_middle::middle::codegen_fn_attrs::{
     CodegenFnAttrFlags, CodegenFnAttrs, PatchableFunctionEntry,
 };
-use rustc_middle::mir::mono::Linkage;
 use rustc_middle::query::Providers;
 use rustc_middle::span_bug;
 use rustc_middle::ty::{self as ty, TyCtxt};
@@ -26,31 +25,6 @@ use crate::target_features::{
     check_target_feature_trait_unsafe, check_tied_features, from_target_feature_attr,
 };
 
-fn linkage_by_name(tcx: TyCtxt<'_>, def_id: LocalDefId, name: &str) -> Linkage {
-    use rustc_middle::mir::mono::Linkage::*;
-
-    // Use the names from src/llvm/docs/LangRef.rst here. Most types are only
-    // applicable to variable declarations and may not really make sense for
-    // Rust code in the first place but allow them anyway and trust that the
-    // user knows what they're doing. Who knows, unanticipated use cases may pop
-    // up in the future.
-    //
-    // ghost, dllimport, dllexport and linkonce_odr_autohide are not supported
-    // and don't have to be, LLVM treats them as no-ops.
-    match name {
-        "available_externally" => AvailableExternally,
-        "common" => Common,
-        "extern_weak" => ExternalWeak,
-        "external" => External,
-        "internal" => Internal,
-        "linkonce" => LinkOnceAny,
-        "linkonce_odr" => LinkOnceODR,
-        "weak" => WeakAny,
-        "weak_odr" => WeakODR,
-        _ => tcx.dcx().span_fatal(tcx.def_span(def_id), "invalid linkage specified"),
-    }
-}
-
 /// In some cases, attributes are only valid on functions, but it's the `check_attr`
 /// pass that checks that they aren't used anywhere else, rather than this module.
 /// In these cases, we bail from performing further checks that are only meaningful for
@@ -103,13 +77,6 @@ fn parse_instruction_set_attr(tcx: TyCtxt<'_>, attr: &Attribute) -> Option<Instr
     }
 }
 
-// FIXME(jdonszelmann): remove when linkage becomes a parsed attr
-fn parse_linkage_attr(tcx: TyCtxt<'_>, did: LocalDefId, attr: &Attribute) -> Option<Linkage> {
-    let val = attr.value_str()?;
-    let linkage = linkage_by_name(tcx, did, val.as_str());
-    Some(linkage)
-}
-
 // FIXME(jdonszelmann): remove when no_sanitize becomes a parsed attr
 fn parse_no_sanitize_attr(tcx: TyCtxt<'_>, attr: &Attribute) -> Option<SanitizerSet> {
     let list = attr.meta_item_list()?;
@@ -332,6 +299,28 @@ fn process_builtin_attrs(
                 AttributeKind::StdInternalSymbol(_) => {
                     codegen_fn_attrs.flags |= CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL
                 }
+                AttributeKind::Linkage(linkage, _) => {
+                    let linkage = Some(*linkage);
+
+                    if tcx.is_foreign_item(did) {
+                        codegen_fn_attrs.import_linkage = linkage;
+
+                        if tcx.is_mutable_static(did.into()) {
+                            let mut diag = tcx.dcx().struct_span_err(
+                                attr.span(),
+                                "extern mutable statics are not allowed with `#[linkage]`",
+                            );
+                            diag.note(
+                                "marking the extern static mutable would allow changing which \
+                                symbol the static references rather than make the target of the \
+                                symbol mutable",
+                            );
+                            diag.emit();
+                        }
+                    } else {
+                        codegen_fn_attrs.linkage = linkage;
+                    }
+                }
                 _ => {}
             }
         }
@@ -349,28 +338,6 @@ fn process_builtin_attrs(
                 codegen_fn_attrs.flags |= CodegenFnAttrFlags::ALLOCATOR_ZEROED
             }
             sym::thread_local => codegen_fn_attrs.flags |= CodegenFnAttrFlags::THREAD_LOCAL,
-            sym::linkage => {
-                let linkage = parse_linkage_attr(tcx, did, attr);
-
-                if tcx.is_foreign_item(did) {
-                    codegen_fn_attrs.import_linkage = linkage;
-
-                    if tcx.is_mutable_static(did.into()) {
-                        let mut diag = tcx.dcx().struct_span_err(
-                            attr.span(),
-                            "extern mutable statics are not allowed with `#[linkage]`",
-                        );
-                        diag.note(
-                            "marking the extern static mutable would allow changing which \
-                            symbol the static references rather than make the target of the \
-                            symbol mutable",
-                        );
-                        diag.emit();
-                    }
-                } else {
-                    codegen_fn_attrs.linkage = linkage;
-                }
-            }
             sym::no_sanitize => {
                 interesting_spans.no_sanitize = Some(attr.span());
                 codegen_fn_attrs.no_sanitize |=
diff --git a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs
index 2a9b5c9019b..31784cabf4a 100644
--- a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs
@@ -1,6 +1,6 @@
 use rustc_abi::{BackendRepr, Float, Integer, Primitive, RegKind};
-use rustc_hir::attrs::InstructionSetAttr;
-use rustc_middle::mir::mono::{Linkage, MonoItemData, Visibility};
+use rustc_hir::attrs::{InstructionSetAttr, Linkage};
+use rustc_middle::mir::mono::{MonoItemData, Visibility};
 use rustc_middle::mir::{InlineAsmOperand, START_BLOCK};
 use rustc_middle::ty::layout::{FnAbiOf, LayoutOf, TyAndLayout};
 use rustc_middle::ty::{Instance, Ty, TyCtxt, TypeVisitableExt};
diff --git a/compiler/rustc_codegen_ssa/src/mono_item.rs b/compiler/rustc_codegen_ssa/src/mono_item.rs
index b9040c330fb..8f03dc1e6b5 100644
--- a/compiler/rustc_codegen_ssa/src/mono_item.rs
+++ b/compiler/rustc_codegen_ssa/src/mono_item.rs
@@ -1,5 +1,6 @@
+use rustc_hir::attrs::Linkage;
 use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
-use rustc_middle::mir::mono::{Linkage, MonoItem, MonoItemData, Visibility};
+use rustc_middle::mir::mono::{MonoItem, MonoItemData, Visibility};
 use rustc_middle::ty::layout::HasTyCtxt;
 use tracing::debug;
 
diff --git a/compiler/rustc_codegen_ssa/src/traits/declare.rs b/compiler/rustc_codegen_ssa/src/traits/declare.rs
index 9f735546558..8d5f0a5b939 100644
--- a/compiler/rustc_codegen_ssa/src/traits/declare.rs
+++ b/compiler/rustc_codegen_ssa/src/traits/declare.rs
@@ -1,5 +1,6 @@
+use rustc_hir::attrs::Linkage;
 use rustc_hir::def_id::DefId;
-use rustc_middle::mir::mono::{Linkage, Visibility};
+use rustc_middle::mir::mono::Visibility;
 use rustc_middle::ty::Instance;
 
 pub trait PreDefineCodegenMethods<'tcx> {
diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs
index e02edf5fe24..510fc832978 100644
--- a/compiler/rustc_hir/src/attrs/data_structures.rs
+++ b/compiler/rustc_hir/src/attrs/data_structures.rs
@@ -187,6 +187,24 @@ pub enum CfgEntry {
     Version(Option<RustcVersion>, Span),
 }
 
+/// Possible values for the `#[linkage]` attribute, allowing to specify the
+/// linkage type for a `MonoItem`.
+///
+/// See <https://llvm.org/docs/LangRef.html#linkage-types> for more details about these variants.
+#[derive(Encodable, Decodable, Clone, Copy, Debug, PartialEq, Eq, Hash)]
+#[derive(HashStable_Generic, PrintAttribute)]
+pub enum Linkage {
+    AvailableExternally,
+    Common,
+    ExternalWeak,
+    External,
+    Internal,
+    LinkOnceAny,
+    LinkOnceODR,
+    WeakAny,
+    WeakODR,
+}
+
 /// Represents parsed *built-in* inert attributes.
 ///
 /// ## Overview
@@ -360,6 +378,9 @@ pub enum AttributeKind {
     /// Represents [`#[link_section]`](https://doc.rust-lang.org/reference/abi.html#the-link_section-attribute)
     LinkSection { name: Symbol, span: Span },
 
+    /// Represents `#[linkage]`.
+    Linkage(Linkage, Span),
+
     /// Represents `#[loop_match]`.
     LoopMatch(Span),
 
diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs
index 7ce624dcc55..84a975523f2 100644
--- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs
+++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs
@@ -46,6 +46,7 @@ impl AttributeKind {
             LinkName { .. } => Yes, // Needed for rustdoc
             LinkOrdinal { .. } => No,
             LinkSection { .. } => Yes, // Needed for rustdoc
+            Linkage(..) => No,
             LoopMatch(..) => No,
             MacroEscape(..) => No,
             MacroTransparency(..) => Yes,
diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs
index b27c223527e..260cfc1052b 100644
--- a/compiler/rustc_hir/src/hir.rs
+++ b/compiler/rustc_hir/src/hir.rs
@@ -1311,6 +1311,7 @@ impl AttributeExt for Attribute {
             Attribute::Parsed(AttributeKind::ShouldPanic { span, .. }) => *span,
             Attribute::Parsed(AttributeKind::AutomaticallyDerived(span)) => *span,
             Attribute::Parsed(AttributeKind::AllowInternalUnsafe(span)) => *span,
+            Attribute::Parsed(AttributeKind::Linkage(_, span)) => *span,
             a => panic!("can't get the span of an arbitrary parsed attribute: {a:?}"),
         }
     }
diff --git a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs
index 52341df0740..2852c4cbd34 100644
--- a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs
+++ b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs
@@ -2,13 +2,12 @@ use std::borrow::Cow;
 
 use rustc_abi::Align;
 use rustc_ast::expand::autodiff_attrs::AutoDiffAttrs;
-use rustc_hir::attrs::{InlineAttr, InstructionSetAttr, OptimizeAttr};
+use rustc_hir::attrs::{InlineAttr, InstructionSetAttr, Linkage, OptimizeAttr};
 use rustc_hir::def_id::DefId;
 use rustc_macros::{HashStable, TyDecodable, TyEncodable};
 use rustc_span::Symbol;
 use rustc_target::spec::SanitizerSet;
 
-use crate::mir::mono::Linkage;
 use crate::ty::{InstanceKind, TyCtxt};
 
 impl<'tcx> TyCtxt<'tcx> {
diff --git a/compiler/rustc_middle/src/mir/mono.rs b/compiler/rustc_middle/src/mir/mono.rs
index 0d98e055d95..440771b3d68 100644
--- a/compiler/rustc_middle/src/mir/mono.rs
+++ b/compiler/rustc_middle/src/mir/mono.rs
@@ -10,7 +10,7 @@ use rustc_data_structures::stable_hasher::{HashStable, StableHasher, ToStableHas
 use rustc_data_structures::unord::UnordMap;
 use rustc_hashes::Hash128;
 use rustc_hir::ItemId;
-use rustc_hir::attrs::InlineAttr;
+use rustc_hir::attrs::{InlineAttr, Linkage};
 use rustc_hir::def_id::{CrateNum, DefId, DefIdSet, LOCAL_CRATE};
 use rustc_macros::{HashStable, TyDecodable, TyEncodable};
 use rustc_query_system::ich::StableHashingContext;
@@ -368,22 +368,6 @@ pub struct MonoItemData {
     pub size_estimate: usize,
 }
 
-/// Specifies the linkage type for a `MonoItem`.
-///
-/// See <https://llvm.org/docs/LangRef.html#linkage-types> for more details about these variants.
-#[derive(Copy, Clone, PartialEq, Debug, TyEncodable, TyDecodable, HashStable)]
-pub enum Linkage {
-    External,
-    AvailableExternally,
-    LinkOnceAny,
-    LinkOnceODR,
-    WeakAny,
-    WeakODR,
-    Internal,
-    ExternalWeak,
-    Common,
-}
-
 /// Specifies the symbol visibility with regards to dynamic linking.
 ///
 /// Visibility doesn't have any effect when linkage is internal.
diff --git a/compiler/rustc_monomorphize/src/partitioning.rs b/compiler/rustc_monomorphize/src/partitioning.rs
index d76b27d9970..c80d058c503 100644
--- a/compiler/rustc_monomorphize/src/partitioning.rs
+++ b/compiler/rustc_monomorphize/src/partitioning.rs
@@ -104,7 +104,7 @@ use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
 use rustc_data_structures::sync;
 use rustc_data_structures::unord::{UnordMap, UnordSet};
 use rustc_hir::LangItem;
-use rustc_hir::attrs::InlineAttr;
+use rustc_hir::attrs::{InlineAttr, Linkage};
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::{DefId, DefIdSet, LOCAL_CRATE};
 use rustc_hir::definitions::DefPathDataName;
@@ -112,7 +112,7 @@ use rustc_middle::bug;
 use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
 use rustc_middle::middle::exported_symbols::{SymbolExportInfo, SymbolExportLevel};
 use rustc_middle::mir::mono::{
-    CodegenUnit, CodegenUnitNameBuilder, InstantiationMode, Linkage, MonoItem, MonoItemData,
+    CodegenUnit, CodegenUnitNameBuilder, InstantiationMode, MonoItem, MonoItemData,
     MonoItemPartitions, Visibility,
 };
 use rustc_middle::ty::print::{characteristic_def_id_of_type, with_no_trimmed_paths};
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index 165f8fe1995..df1db65c06e 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -332,6 +332,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                 &Attribute::Parsed(AttributeKind::Coroutine(attr_span)) => {
                     self.check_coroutine(attr_span, target)
                 }
+                &Attribute::Parsed(AttributeKind::Linkage(_, attr_span)) => {
+                    self.check_linkage(attr_span, span, target);
+                }
                 Attribute::Unparsed(attr_item) => {
                     style = Some(attr_item.style);
                     match attr.path().as_slice() {
@@ -395,7 +398,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                         [sym::autodiff_forward, ..] | [sym::autodiff_reverse, ..] => {
                             self.check_autodiff(hir_id, attr, span, target)
                         }
-                        [sym::linkage, ..] => self.check_linkage(attr, span, target),
                         [
                             // ok
                             sym::allow
@@ -2707,7 +2709,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
         }
     }
 
-    fn check_linkage(&self, attr: &Attribute, span: Span, target: Target) {
+    fn check_linkage(&self, attr_span: Span, span: Span, target: Target) {
         match target {
             Target::Fn
             | Target::Method(..)
@@ -2715,7 +2717,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             | Target::ForeignStatic
             | Target::ForeignFn => {}
             _ => {
-                self.dcx().emit_err(errors::Linkage { attr_span: attr.span(), span });
+                self.dcx().emit_err(errors::Linkage { attr_span, span });
             }
         }
     }
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index acbed7a9eed..416ce27367e 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -545,6 +545,7 @@ symbols! {
         autodiff_forward,
         autodiff_reverse,
         automatically_derived,
+        available_externally,
         avx,
         avx10_target_feature,
         avx512_target_feature,
@@ -676,6 +677,7 @@ symbols! {
         cold_path,
         collapse_debuginfo,
         column,
+        common,
         compare_bytes,
         compare_exchange,
         compare_exchange_weak,
@@ -956,6 +958,7 @@ symbols! {
         extern_prelude,
         extern_system_varargs,
         extern_types,
+        extern_weak,
         external,
         external_doc,
         f,
@@ -1213,6 +1216,7 @@ symbols! {
         instruction_set,
         integer_: "integer", // underscore to avoid clashing with the function `sym::integer` below
         integral,
+        internal,
         internal_features,
         into_async_iter_into_iter,
         into_future,
@@ -1287,6 +1291,8 @@ symbols! {
         linkage,
         linker,
         linker_messages,
+        linkonce,
+        linkonce_odr,
         lint_reasons,
         literal,
         load,
@@ -2360,6 +2366,8 @@ symbols! {
         wasm_abi,
         wasm_import_module,
         wasm_target_feature,
+        weak,
+        weak_odr,
         where_clause_attrs,
         while_let,
         width,
diff --git a/tests/ui/attributes/malformed-attrs.stderr b/tests/ui/attributes/malformed-attrs.stderr
index 705050e9a7d..aa4891459aa 100644
--- a/tests/ui/attributes/malformed-attrs.stderr
+++ b/tests/ui/attributes/malformed-attrs.stderr
@@ -90,25 +90,6 @@ error: malformed `cfi_encoding` attribute input
 LL | #[cfi_encoding]
    | ^^^^^^^^^^^^^^^ help: must be of the form: `#[cfi_encoding = "encoding"]`
 
-error: malformed `linkage` attribute input
-  --> $DIR/malformed-attrs.rs:170:5
-   |
-LL |     #[linkage]
-   |     ^^^^^^^^^^
-   |
-   = note: for more information, visit <https://doc.rust-lang.org/reference/linkage.html>
-help: the following are the possible correct uses
-   |
-LL |     #[linkage = "available_externally"]
-   |               ++++++++++++++++++++++++
-LL |     #[linkage = "common"]
-   |               ++++++++++
-LL |     #[linkage = "extern_weak"]
-   |               +++++++++++++++
-LL |     #[linkage = "external"]
-   |               ++++++++++++
-   = and 5 other candidates
-
 error: malformed `allow` attribute input
   --> $DIR/malformed-attrs.rs:175:1
    |
@@ -659,6 +640,24 @@ LL |     #[unsafe(ffi_const = 1)]
    |     |                  didn't expect any arguments here
    |     help: must be of the form: `#[ffi_const]`
 
+error[E0539]: malformed `linkage` attribute input
+  --> $DIR/malformed-attrs.rs:170:5
+   |
+LL |     #[linkage]
+   |     ^^^^^^^^^^ expected this to be of the form `linkage = "..."`
+   |
+help: try changing it to one of the following valid forms of the attribute
+   |
+LL |     #[linkage = "available_externally"]
+   |               ++++++++++++++++++++++++
+LL |     #[linkage = "common"]
+   |               ++++++++++
+LL |     #[linkage = "extern_weak"]
+   |               +++++++++++++++
+LL |     #[linkage = "external"]
+   |               ++++++++++++
+   = and 5 other candidates
+
 error[E0565]: malformed `automatically_derived` attribute input
   --> $DIR/malformed-attrs.rs:188:1
    |
diff --git a/tests/ui/linkage-attr/linkage3.rs b/tests/ui/linkage-attr/linkage3.rs
index f95e5eecc48..74a6fd6600b 100644
--- a/tests/ui/linkage-attr/linkage3.rs
+++ b/tests/ui/linkage-attr/linkage3.rs
@@ -5,7 +5,7 @@
 extern "C" {
     #[linkage = "foo"]
     static foo: *const i32;
-//~^ ERROR: invalid linkage specified
+//~^^ ERROR: malformed `linkage` attribute input [E0539]
 }
 
 fn main() {
diff --git a/tests/ui/linkage-attr/linkage3.stderr b/tests/ui/linkage-attr/linkage3.stderr
index 5f7b7ef227c..f1215f09aea 100644
--- a/tests/ui/linkage-attr/linkage3.stderr
+++ b/tests/ui/linkage-attr/linkage3.stderr
@@ -1,8 +1,27 @@
-error: invalid linkage specified
-  --> $DIR/linkage3.rs:7:5
+error[E0539]: malformed `linkage` attribute input
+  --> $DIR/linkage3.rs:6:5
    |
-LL |     static foo: *const i32;
-   |     ^^^^^^^^^^^^^^^^^^^^^^
+LL |     #[linkage = "foo"]
+   |     ^^^^^^^^^^^^-----^
+   |                 |
+   |                 valid arguments are `available_externally`, `common`, `extern_weak`, `external`, `internal`, `linkonce`, `linkonce_odr`, `weak` or `weak_odr`
+   |
+help: try changing it to one of the following valid forms of the attribute
+   |
+LL -     #[linkage = "foo"]
+LL +     #[linkage = "available_externally"]
+   |
+LL -     #[linkage = "foo"]
+LL +     #[linkage = "common"]
+   |
+LL -     #[linkage = "foo"]
+LL +     #[linkage = "extern_weak"]
+   |
+LL -     #[linkage = "foo"]
+LL +     #[linkage = "external"]
+   |
+   = and 5 other candidates
 
 error: aborting due to 1 previous error
 
+For more information about this error, try `rustc --explain E0539`.