diff --git a/compiler/rustc_attr/src/builtin.rs b/compiler/rustc_attr/src/builtin.rs
index 5a79cf68f113e..3d4bd222715c3 100644
--- a/compiler/rustc_attr/src/builtin.rs
+++ b/compiler/rustc_attr/src/builtin.rs
@@ -868,177 +868,180 @@ impl IntType {
 /// structure layout, `packed` to remove padding, and `transparent` to delegate representation
 /// concerns to the only non-ZST field.
 pub fn find_repr_attrs(sess: &Session, attr: &Attribute) -> Vec<ReprAttr> {
-    use ReprAttr::*;
+    if attr.has_name(sym::repr) { parse_repr_attr(sess, attr) } else { Vec::new() }
+}
 
+pub fn parse_repr_attr(sess: &Session, attr: &Attribute) -> Vec<ReprAttr> {
+    assert!(attr.has_name(sym::repr), "expected `#[repr(..)]`, found: {:?}", attr);
+    use ReprAttr::*;
     let mut acc = Vec::new();
     let diagnostic = &sess.parse_sess.span_diagnostic;
-    if attr.has_name(sym::repr) {
-        if let Some(items) = attr.meta_item_list() {
-            for item in items {
-                let mut recognised = false;
-                if item.is_word() {
-                    let hint = match item.name_or_empty() {
-                        sym::C => Some(ReprC),
-                        sym::packed => Some(ReprPacked(1)),
-                        sym::simd => Some(ReprSimd),
-                        sym::transparent => Some(ReprTransparent),
-                        sym::no_niche => Some(ReprNoNiche),
-                        sym::align => {
-                            let mut err = struct_span_err!(
-                                diagnostic,
-                                item.span(),
-                                E0589,
-                                "invalid `repr(align)` attribute: `align` needs an argument"
-                            );
-                            err.span_suggestion(
-                                item.span(),
-                                "supply an argument here",
-                                "align(...)".to_string(),
-                                Applicability::HasPlaceholders,
-                            );
-                            err.emit();
-                            recognised = true;
-                            None
-                        }
-                        name => int_type_of_word(name).map(ReprInt),
-                    };
 
-                    if let Some(h) = hint {
+    if let Some(items) = attr.meta_item_list() {
+        for item in items {
+            let mut recognised = false;
+            if item.is_word() {
+                let hint = match item.name_or_empty() {
+                    sym::C => Some(ReprC),
+                    sym::packed => Some(ReprPacked(1)),
+                    sym::simd => Some(ReprSimd),
+                    sym::transparent => Some(ReprTransparent),
+                    sym::no_niche => Some(ReprNoNiche),
+                    sym::align => {
+                        let mut err = struct_span_err!(
+                            diagnostic,
+                            item.span(),
+                            E0589,
+                            "invalid `repr(align)` attribute: `align` needs an argument"
+                        );
+                        err.span_suggestion(
+                            item.span(),
+                            "supply an argument here",
+                            "align(...)".to_string(),
+                            Applicability::HasPlaceholders,
+                        );
+                        err.emit();
                         recognised = true;
-                        acc.push(h);
+                        None
                     }
-                } else if let Some((name, value)) = item.name_value_literal() {
-                    let mut literal_error = None;
-                    if name == sym::align {
-                        recognised = true;
-                        match parse_alignment(&value.kind) {
-                            Ok(literal) => acc.push(ReprAlign(literal)),
-                            Err(message) => literal_error = Some(message),
-                        };
-                    } else if name == sym::packed {
-                        recognised = true;
-                        match parse_alignment(&value.kind) {
-                            Ok(literal) => acc.push(ReprPacked(literal)),
-                            Err(message) => literal_error = Some(message),
-                        };
-                    } else if matches!(name, sym::C | sym::simd | sym::transparent | sym::no_niche)
-                        || int_type_of_word(name).is_some()
-                    {
-                        recognised = true;
-                        struct_span_err!(
+                    name => int_type_of_word(name).map(ReprInt),
+                };
+
+                if let Some(h) = hint {
+                    recognised = true;
+                    acc.push(h);
+                }
+            } else if let Some((name, value)) = item.name_value_literal() {
+                let mut literal_error = None;
+                if name == sym::align {
+                    recognised = true;
+                    match parse_alignment(&value.kind) {
+                        Ok(literal) => acc.push(ReprAlign(literal)),
+                        Err(message) => literal_error = Some(message),
+                    };
+                } else if name == sym::packed {
+                    recognised = true;
+                    match parse_alignment(&value.kind) {
+                        Ok(literal) => acc.push(ReprPacked(literal)),
+                        Err(message) => literal_error = Some(message),
+                    };
+                } else if matches!(name, sym::C | sym::simd | sym::transparent | sym::no_niche)
+                    || int_type_of_word(name).is_some()
+                {
+                    recognised = true;
+                    struct_span_err!(
                                 diagnostic,
                                 item.span(),
                                 E0552,
                                 "invalid representation hint: `{}` does not take a parenthesized argument list",
                                 name.to_ident_string(),
                             ).emit();
-                    }
-                    if let Some(literal_error) = literal_error {
-                        struct_span_err!(
+                }
+                if let Some(literal_error) = literal_error {
+                    struct_span_err!(
+                        diagnostic,
+                        item.span(),
+                        E0589,
+                        "invalid `repr({})` attribute: {}",
+                        name.to_ident_string(),
+                        literal_error
+                    )
+                    .emit();
+                }
+            } else if let Some(meta_item) = item.meta_item() {
+                if let MetaItemKind::NameValue(ref value) = meta_item.kind {
+                    if meta_item.has_name(sym::align) || meta_item.has_name(sym::packed) {
+                        let name = meta_item.name_or_empty().to_ident_string();
+                        recognised = true;
+                        let mut err = struct_span_err!(
                             diagnostic,
                             item.span(),
-                            E0589,
-                            "invalid `repr({})` attribute: {}",
-                            name.to_ident_string(),
-                            literal_error
-                        )
-                        .emit();
-                    }
-                } else if let Some(meta_item) = item.meta_item() {
-                    if let MetaItemKind::NameValue(ref value) = meta_item.kind {
-                        if meta_item.has_name(sym::align) || meta_item.has_name(sym::packed) {
-                            let name = meta_item.name_or_empty().to_ident_string();
-                            recognised = true;
-                            let mut err = struct_span_err!(
-                                diagnostic,
-                                item.span(),
-                                E0693,
-                                "incorrect `repr({})` attribute format",
-                                name,
-                            );
-                            match value.kind {
-                                ast::LitKind::Int(int, ast::LitIntType::Unsuffixed) => {
-                                    err.span_suggestion(
-                                        item.span(),
-                                        "use parentheses instead",
-                                        format!("{}({})", name, int),
-                                        Applicability::MachineApplicable,
-                                    );
-                                }
-                                ast::LitKind::Str(s, _) => {
-                                    err.span_suggestion(
-                                        item.span(),
-                                        "use parentheses instead",
-                                        format!("{}({})", name, s),
-                                        Applicability::MachineApplicable,
-                                    );
-                                }
-                                _ => {}
+                            E0693,
+                            "incorrect `repr({})` attribute format",
+                            name,
+                        );
+                        match value.kind {
+                            ast::LitKind::Int(int, ast::LitIntType::Unsuffixed) => {
+                                err.span_suggestion(
+                                    item.span(),
+                                    "use parentheses instead",
+                                    format!("{}({})", name, int),
+                                    Applicability::MachineApplicable,
+                                );
                             }
-                            err.emit();
-                        } else {
-                            if matches!(
-                                meta_item.name_or_empty(),
-                                sym::C | sym::simd | sym::transparent | sym::no_niche
-                            ) || int_type_of_word(meta_item.name_or_empty()).is_some()
-                            {
-                                recognised = true;
-                                struct_span_err!(
-                                    diagnostic,
-                                    meta_item.span,
-                                    E0552,
-                                    "invalid representation hint: `{}` does not take a value",
-                                    meta_item.name_or_empty().to_ident_string(),
-                                )
-                                .emit();
+                            ast::LitKind::Str(s, _) => {
+                                err.span_suggestion(
+                                    item.span(),
+                                    "use parentheses instead",
+                                    format!("{}({})", name, s),
+                                    Applicability::MachineApplicable,
+                                );
                             }
+                            _ => {}
                         }
-                    } else if let MetaItemKind::List(_) = meta_item.kind {
-                        if meta_item.has_name(sym::align) {
+                        err.emit();
+                    } else {
+                        if matches!(
+                            meta_item.name_or_empty(),
+                            sym::C | sym::simd | sym::transparent | sym::no_niche
+                        ) || int_type_of_word(meta_item.name_or_empty()).is_some()
+                        {
                             recognised = true;
                             struct_span_err!(
                                 diagnostic,
                                 meta_item.span,
-                                E0693,
-                                "incorrect `repr(align)` attribute format: \
-                                 `align` takes exactly one argument in parentheses"
+                                E0552,
+                                "invalid representation hint: `{}` does not take a value",
+                                meta_item.name_or_empty().to_ident_string(),
                             )
                             .emit();
-                        } else if meta_item.has_name(sym::packed) {
-                            recognised = true;
-                            struct_span_err!(
-                                diagnostic,
-                                meta_item.span,
-                                E0552,
-                                "incorrect `repr(packed)` attribute format: \
+                        }
+                    }
+                } else if let MetaItemKind::List(_) = meta_item.kind {
+                    if meta_item.has_name(sym::align) {
+                        recognised = true;
+                        struct_span_err!(
+                            diagnostic,
+                            meta_item.span,
+                            E0693,
+                            "incorrect `repr(align)` attribute format: \
+                                 `align` takes exactly one argument in parentheses"
+                        )
+                        .emit();
+                    } else if meta_item.has_name(sym::packed) {
+                        recognised = true;
+                        struct_span_err!(
+                            diagnostic,
+                            meta_item.span,
+                            E0552,
+                            "incorrect `repr(packed)` attribute format: \
                                  `packed` takes exactly one parenthesized argument, \
                                  or no parentheses at all"
-                            )
-                            .emit();
-                        } else if matches!(
-                            meta_item.name_or_empty(),
-                            sym::C | sym::simd | sym::transparent | sym::no_niche
-                        ) || int_type_of_word(meta_item.name_or_empty()).is_some()
-                        {
-                            recognised = true;
-                            struct_span_err!(
+                        )
+                        .emit();
+                    } else if matches!(
+                        meta_item.name_or_empty(),
+                        sym::C | sym::simd | sym::transparent | sym::no_niche
+                    ) || int_type_of_word(meta_item.name_or_empty()).is_some()
+                    {
+                        recognised = true;
+                        struct_span_err!(
                                 diagnostic,
                                 meta_item.span,
                                 E0552,
                                 "invalid representation hint: `{}` does not take a parenthesized argument list",
                                 meta_item.name_or_empty().to_ident_string(),
                             ).emit();
-                        }
                     }
                 }
-                if !recognised {
-                    // Not a word we recognize. This will be caught and reported by
-                    // the `check_mod_attrs` pass, but this pass doesn't always run
-                    // (e.g. if we only pretty-print the source), so we have to gate
-                    // the `delay_span_bug` call as follows:
-                    if sess.opts.pretty.map_or(true, |pp| pp.needs_analysis()) {
-                        diagnostic.delay_span_bug(item.span(), "unrecognized representation hint");
-                    }
+            }
+            if !recognised {
+                // Not a word we recognize. This will be caught and reported by
+                // the `check_mod_attrs` pass, but this pass doesn't always run
+                // (e.g. if we only pretty-print the source), so we have to gate
+                // the `delay_span_bug` call as follows:
+                if sess.opts.pretty.map_or(true, |pp| pp.needs_analysis()) {
+                    diagnostic.delay_span_bug(item.span(), "unrecognized representation hint");
                 }
             }
         }
diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs
index c098ce36f022f..9394d60134f8c 100644
--- a/compiler/rustc_codegen_llvm/src/attributes.rs
+++ b/compiler/rustc_codegen_llvm/src/attributes.rs
@@ -6,6 +6,7 @@ use rustc_hir::def_id::DefId;
 use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
 use rustc_middle::ty::{self, TyCtxt};
 use rustc_session::config::OptLevel;
+use rustc_span::symbol::sym;
 use rustc_target::spec::abi::Abi;
 use rustc_target::spec::{FramePointer, SanitizerSet, StackProbeType, StackProtector};
 use smallvec::SmallVec;
@@ -329,9 +330,7 @@ pub fn from_fn_attrs<'ll, 'tcx>(
     ) {
         let span = cx
             .tcx
-            .get_attrs(instance.def_id())
-            .iter()
-            .find(|a| a.has_name(rustc_span::sym::target_feature))
+            .get_attr(instance.def_id(), sym::target_feature)
             .map_or_else(|| cx.tcx.def_span(instance.def_id()), |a| a.span);
         let msg = format!(
             "the target features {} must all be either enabled or disabled together",
diff --git a/compiler/rustc_const_eval/src/transform/check_consts/check.rs b/compiler/rustc_const_eval/src/transform/check_consts/check.rs
index 7e2a50444db06..8d3bbefb37186 100644
--- a/compiler/rustc_const_eval/src/transform/check_consts/check.rs
+++ b/compiler/rustc_const_eval/src/transform/check_consts/check.rs
@@ -312,11 +312,7 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
 
             Status::Unstable(gate) if self.tcx.features().enabled(gate) => {
                 let unstable_in_stable = self.ccx.is_const_stable_const_fn()
-                    && !super::rustc_allow_const_fn_unstable(
-                        self.tcx,
-                        self.def_id().to_def_id(),
-                        gate,
-                    );
+                    && !super::rustc_allow_const_fn_unstable(self.tcx, self.def_id(), gate);
                 if unstable_in_stable {
                     emit_unstable_in_stable_error(self.ccx, span, gate);
                 }
@@ -713,7 +709,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
         match &terminator.kind {
             TerminatorKind::Call { func, args, fn_span, from_hir_call, .. } => {
                 let ConstCx { tcx, body, param_env, .. } = *self.ccx;
-                let caller = self.def_id().to_def_id();
+                let caller = self.def_id();
 
                 let fn_ty = func.ty(body, tcx);
 
@@ -797,7 +793,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
                             // trait.
                             let callee_trait = tcx.trait_of_item(callee);
                             if callee_trait.is_some()
-                                && tcx.has_attr(caller, sym::default_method_body_is_const)
+                                && tcx.has_attr(caller.to_def_id(), sym::default_method_body_is_const)
                                 && callee_trait == tcx.trait_of_item(caller)
                                 // Can only call methods when it's `<Self as TheTrait>::f`.
                                 && tcx.types.self_param == substs.type_at(0)
diff --git a/compiler/rustc_const_eval/src/transform/check_consts/mod.rs b/compiler/rustc_const_eval/src/transform/check_consts/mod.rs
index 25ba97ee60567..23e2afae79183 100644
--- a/compiler/rustc_const_eval/src/transform/check_consts/mod.rs
+++ b/compiler/rustc_const_eval/src/transform/check_consts/mod.rs
@@ -66,8 +66,12 @@ impl<'mir, 'tcx> ConstCx<'mir, 'tcx> {
     }
 }
 
-pub fn rustc_allow_const_fn_unstable(tcx: TyCtxt<'_>, def_id: DefId, feature_gate: Symbol) -> bool {
-    let attrs = tcx.get_attrs(def_id);
+pub fn rustc_allow_const_fn_unstable(
+    tcx: TyCtxt<'_>,
+    def_id: LocalDefId,
+    feature_gate: Symbol,
+) -> bool {
+    let attrs = tcx.hir().attrs(tcx.hir().local_def_id_to_hir_id(def_id));
     attr::rustc_allow_const_fn_unstable(&tcx.sess, attrs).any(|name| name == feature_gate)
 }
 
diff --git a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
index ba248a3b6cbe5..122471b208d80 100644
--- a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
+++ b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
@@ -1,5 +1,6 @@
 //! Concrete error types for all operations which may be invalid in a certain const context.
 
+use hir::def_id::LocalDefId;
 use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder, ErrorGuaranteed};
 use rustc_hir as hir;
 use rustc_hir::def_id::DefId;
@@ -95,7 +96,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallIndirect {
 /// A function call where the callee is not marked as `const`.
 #[derive(Debug, Clone, Copy)]
 pub struct FnCallNonConst<'tcx> {
-    pub caller: DefId,
+    pub caller: LocalDefId,
     pub callee: DefId,
     pub substs: SubstsRef<'tcx>,
     pub span: Span,
@@ -117,13 +118,8 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
             match self_ty.kind() {
                 Param(param_ty) => {
                     debug!(?param_ty);
-                    if let Some(generics) = caller
-                        .as_local()
-                        .map(|id| tcx.hir().local_def_id_to_hir_id(id))
-                        .map(|id| tcx.hir().get(id))
-                        .as_ref()
-                        .and_then(|node| node.generics())
-                    {
+                    let caller_hir_id = tcx.hir().local_def_id_to_hir_id(caller);
+                    if let Some(generics) = tcx.hir().get(caller_hir_id).generics() {
                         let constraint = with_no_trimmed_paths!(format!(
                             "~const {}",
                             trait_ref.print_only_trait_path()
diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs
index c5f42aa7af724..097493e8985fb 100644
--- a/compiler/rustc_feature/src/builtin_attrs.rs
+++ b/compiler/rustc_feature/src/builtin_attrs.rs
@@ -147,6 +147,16 @@ pub enum AttributeDuplicates {
     FutureWarnPreceding,
 }
 
+/// A conveniece macro to deal with `$($expr)?`.
+macro_rules! or_default {
+    ($default:expr,) => {
+        $default
+    };
+    ($default:expr, $next:expr) => {
+        $next
+    };
+}
+
 /// A convenience macro for constructing attribute templates.
 /// E.g., `template!(Word, List: "description")` means that the attribute
 /// supports forms `#[attr]` and `#[attr(description)]`.
@@ -168,9 +178,10 @@ macro_rules! template {
 }
 
 macro_rules! ungated {
-    ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr $(,)?) => {
+    ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr $(, @only_local: $only_local:expr)? $(,)?) => {
         BuiltinAttribute {
             name: sym::$attr,
+            only_local: or_default!(false, $($only_local)?),
             type_: $typ,
             template: $tpl,
             gate: Ungated,
@@ -180,18 +191,20 @@ macro_rules! ungated {
 }
 
 macro_rules! gated {
-    ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr, $gate:ident, $msg:expr $(,)?) => {
+    ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr $(, @only_local: $only_local:expr)?, $gate:ident, $msg:expr $(,)?) => {
         BuiltinAttribute {
             name: sym::$attr,
+            only_local: or_default!(false, $($only_local)?),
             type_: $typ,
             template: $tpl,
             duplicates: $duplicates,
             gate: Gated(Stability::Unstable, sym::$gate, $msg, cfg_fn!($gate)),
         }
     };
-    ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr, $msg:expr $(,)?) => {
+    ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr $(, @only_local: $only_local:expr)?, $msg:expr $(,)?) => {
         BuiltinAttribute {
             name: sym::$attr,
+            only_local: or_default!(false, $($only_local)?),
             type_: $typ,
             template: $tpl,
             duplicates: $duplicates,
@@ -201,12 +214,13 @@ macro_rules! gated {
 }
 
 macro_rules! rustc_attr {
-    (TEST, $attr:ident, $typ:expr, $tpl:expr, $duplicate:expr $(,)?) => {
+    (TEST, $attr:ident, $typ:expr, $tpl:expr, $duplicate:expr $(, @only_local: $only_local:expr)? $(,)?) => {
         rustc_attr!(
             $attr,
             $typ,
             $tpl,
             $duplicate,
+            $(@only_local: $only_local,)?
             concat!(
                 "the `#[",
                 stringify!($attr),
@@ -215,9 +229,10 @@ macro_rules! rustc_attr {
             ),
         )
     };
-    ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr, $msg:expr $(,)?) => {
+    ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr $(, @only_local: $only_local:expr)?, $msg:expr $(,)?) => {
         BuiltinAttribute {
             name: sym::$attr,
+            only_local: or_default!(false, $($only_local)?),
             type_: $typ,
             template: $tpl,
             duplicates: $duplicates,
@@ -237,6 +252,10 @@ const INTERNAL_UNSTABLE: &str = "this is an internal attribute that will never b
 
 pub struct BuiltinAttribute {
     pub name: Symbol,
+    /// Whether this attribute is only used in the local crate.
+    ///
+    /// If so, it is not encoded in the crate metadata.
+    pub only_local: bool,
     pub type_: AttributeType,
     pub template: AttributeTemplate,
     pub duplicates: AttributeDuplicates,
@@ -295,7 +314,7 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
     ungated!(must_use, Normal, template!(Word, NameValueStr: "reason"), FutureWarnFollowing),
     gated!(
         must_not_suspend, Normal, template!(Word, NameValueStr: "reason"), WarnFollowing,
-        must_not_suspend, experimental!(must_not_suspend)
+        experimental!(must_not_suspend)
     ),
     ungated!(
         deprecated, Normal,
@@ -324,8 +343,8 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
     ungated!(repr, Normal, template!(List: "C"), DuplicatesOk),
     ungated!(export_name, Normal, template!(NameValueStr: "name"), FutureWarnPreceding),
     ungated!(link_section, Normal, template!(NameValueStr: "name"), FutureWarnPreceding),
-    ungated!(no_mangle, Normal, template!(Word), WarnFollowing),
-    ungated!(used, Normal, template!(Word, List: "compiler|linker"), WarnFollowing),
+    ungated!(no_mangle, Normal, template!(Word), WarnFollowing, @only_local: true),
+    ungated!(used, Normal, template!(Word, List: "compiler|linker"), WarnFollowing, @only_local: true),
 
     // Limits:
     ungated!(recursion_limit, CrateLevel, template!(NameValueStr: "N"), FutureWarnFollowing),
@@ -358,8 +377,8 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
     ungated!(panic_handler, Normal, template!(Word), WarnFollowing), // RFC 2070
 
     // Code generation:
-    ungated!(inline, Normal, template!(Word, List: "always|never"), FutureWarnFollowing),
-    ungated!(cold, Normal, template!(Word), WarnFollowing),
+    ungated!(inline, Normal, template!(Word, List: "always|never"), FutureWarnFollowing, @only_local: true),
+    ungated!(cold, Normal, template!(Word), WarnFollowing, @only_local: true),
     ungated!(no_builtins, CrateLevel, template!(Word), WarnFollowing),
     ungated!(target_feature, Normal, template!(List: r#"enable = "name""#), DuplicatesOk),
     ungated!(track_caller, Normal, template!(Word), WarnFollowing),
@@ -385,7 +404,7 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
     ),
 
     // Linking:
-    gated!(naked, Normal, template!(Word), WarnFollowing, naked_functions, experimental!(naked)),
+    gated!(naked, Normal, template!(Word), WarnFollowing, @only_local: true, naked_functions, experimental!(naked)),
     gated!(
         link_ordinal, Normal, template!(List: "ordinal"), ErrorPreceding, raw_dylib,
         experimental!(link_ordinal)
@@ -394,6 +413,7 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
     // Plugins:
     BuiltinAttribute {
         name: sym::plugin,
+        only_local: false,
         type_: CrateLevel,
         template: template!(List: "name"),
         duplicates: DuplicatesOk,
@@ -475,7 +495,7 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
     ),
     // DuplicatesOk since it has its own validation
     ungated!(
-        stable, Normal, template!(List: r#"feature = "name", since = "version""#), DuplicatesOk
+        stable, Normal, template!(List: r#"feature = "name", since = "version""#), DuplicatesOk,
     ),
     ungated!(
         unstable, Normal,
@@ -546,11 +566,11 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
     // ==========================================================================
 
     gated!(
-        linkage, Normal, template!(NameValueStr: "external|internal|..."), ErrorPreceding,
+        linkage, Normal, template!(NameValueStr: "external|internal|..."), ErrorPreceding, @only_local: true,
         "the `linkage` attribute is experimental and not portable across platforms",
     ),
     rustc_attr!(
-        rustc_std_internal_symbol, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE
+        rustc_std_internal_symbol, Normal, template!(Word), WarnFollowing, @only_local: true, INTERNAL_UNSTABLE
     ),
 
     // ==========================================================================
@@ -633,7 +653,7 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
     // Internal attributes, Misc:
     // ==========================================================================
     gated!(
-        lang, Normal, template!(NameValueStr: "name"), DuplicatesOk, lang_items,
+        lang, Normal, template!(NameValueStr: "name"), DuplicatesOk, @only_local: true, lang_items,
         "language items are subject to change",
     ),
     rustc_attr!(
@@ -642,11 +662,11 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
         "#[rustc_pass_by_value] is used to mark types that must be passed by value instead of reference."
     ),
     rustc_attr!(
-        rustc_coherence_is_core, AttributeType::CrateLevel, template!(Word), ErrorFollowing,
+        rustc_coherence_is_core, AttributeType::CrateLevel, template!(Word), ErrorFollowing, @only_local: true,
         "#![rustc_coherence_is_core] allows inherent methods on builtin types, only intended to be used in `core`."
     ),
     rustc_attr!(
-        rustc_allow_incoherent_impl, AttributeType::Normal, template!(Word), ErrorFollowing,
+        rustc_allow_incoherent_impl, AttributeType::Normal, template!(Word), ErrorFollowing, @only_local: true,
         "#[rustc_allow_incoherent_impl] has to be added to all impl items of an incoherent inherent impl."
     ),
     rustc_attr!(
@@ -656,6 +676,8 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
     ),
     BuiltinAttribute {
         name: sym::rustc_diagnostic_item,
+        // FIXME: This can be `true` once we always use `tcx.is_diagnostic_item`.
+        only_local: false,
         type_: Normal,
         template: template!(NameValueStr: "name"),
         duplicates: ErrorFollowing,
@@ -676,7 +698,7 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
         "unboxed_closures are still evolving",
     ),
     rustc_attr!(
-        rustc_inherit_overflow_checks, Normal, template!(Word), WarnFollowing,
+        rustc_inherit_overflow_checks, Normal, template!(Word), WarnFollowing, @only_local: true,
         "the `#[rustc_inherit_overflow_checks]` attribute is just used to control \
         overflow checking behavior of several libcore functions that are inlined \
         across crates and will never be stable",
@@ -778,6 +800,10 @@ pub fn is_builtin_attr_name(name: Symbol) -> bool {
     BUILTIN_ATTRIBUTE_MAP.get(&name).is_some()
 }
 
+pub fn is_builtin_only_local(name: Symbol) -> bool {
+    BUILTIN_ATTRIBUTE_MAP.get(&name).map_or(false, |attr| attr.only_local)
+}
+
 pub static BUILTIN_ATTRIBUTE_MAP: SyncLazy<FxHashMap<Symbol, &BuiltinAttribute>> =
     SyncLazy::new(|| {
         let mut map = FxHashMap::default();
diff --git a/compiler/rustc_feature/src/lib.rs b/compiler/rustc_feature/src/lib.rs
index 940c4ecdcc23a..26e0538b0eb4e 100644
--- a/compiler/rustc_feature/src/lib.rs
+++ b/compiler/rustc_feature/src/lib.rs
@@ -149,7 +149,8 @@ pub use accepted::ACCEPTED_FEATURES;
 pub use active::{Features, ACTIVE_FEATURES, INCOMPATIBLE_FEATURES};
 pub use builtin_attrs::AttributeDuplicates;
 pub use builtin_attrs::{
-    deprecated_attributes, find_gated_cfg, is_builtin_attr_name, AttributeGate, AttributeTemplate,
-    AttributeType, BuiltinAttribute, GatedCfg, BUILTIN_ATTRIBUTES, BUILTIN_ATTRIBUTE_MAP,
+    deprecated_attributes, find_gated_cfg, is_builtin_attr_name, is_builtin_only_local,
+    AttributeGate, AttributeTemplate, AttributeType, BuiltinAttribute, GatedCfg,
+    BUILTIN_ATTRIBUTES, BUILTIN_ATTRIBUTE_MAP,
 };
 pub use removed::{REMOVED_FEATURES, STABLE_REMOVED_FEATURES};
diff --git a/compiler/rustc_incremental/src/persist/dirty_clean.rs b/compiler/rustc_incremental/src/persist/dirty_clean.rs
index aaf24636598b9..424164d8760c4 100644
--- a/compiler/rustc_incremental/src/persist/dirty_clean.rs
+++ b/compiler/rustc_incremental/src/persist/dirty_clean.rs
@@ -183,10 +183,7 @@ pub struct DirtyCleanVisitor<'tcx> {
 impl<'tcx> DirtyCleanVisitor<'tcx> {
     /// Possibly "deserialize" the attribute into a clean/dirty assertion
     fn assertion_maybe(&mut self, item_id: LocalDefId, attr: &Attribute) -> Option<Assertion> {
-        if !attr.has_name(sym::rustc_clean) {
-            // skip: not rustc_clean/dirty
-            return None;
-        }
+        assert!(attr.has_name(sym::rustc_clean));
         if !check_config(self.tcx, attr) {
             // skip: not the correct `cfg=`
             return None;
@@ -384,7 +381,7 @@ impl<'tcx> DirtyCleanVisitor<'tcx> {
     fn check_item(&mut self, item_id: LocalDefId) {
         let item_span = self.tcx.def_span(item_id.to_def_id());
         let def_path_hash = self.tcx.def_path_hash(item_id.to_def_id());
-        for attr in self.tcx.get_attrs(item_id.to_def_id()).iter() {
+        for attr in self.tcx.get_attrs(item_id.to_def_id(), sym::rustc_clean) {
             let Some(assertion) = self.assertion_maybe(item_id, attr) else {
                 continue;
             };
diff --git a/compiler/rustc_interface/src/queries.rs b/compiler/rustc_interface/src/queries.rs
index 22ab62ac372f2..136f0443fa0a3 100644
--- a/compiler/rustc_interface/src/queries.rs
+++ b/compiler/rustc_interface/src/queries.rs
@@ -258,10 +258,7 @@ impl<'tcx> Queries<'tcx> {
     /// an error.
     fn check_for_rustc_errors_attr(tcx: TyCtxt<'_>) {
         let Some((def_id, _)) = tcx.entry_fn(()) else { return };
-
-        let attrs = &*tcx.get_attrs(def_id);
-        let attrs = attrs.iter().filter(|attr| attr.has_name(sym::rustc_error));
-        for attr in attrs {
+        for attr in tcx.get_attrs(def_id, sym::rustc_error) {
             match attr.meta_item_list() {
                 // Check if there is a `#[rustc_error(delay_span_bug_from_inside_query)]`.
                 Some(list)
diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs
index 524fb6556b995..3fbe40a4253a2 100644
--- a/compiler/rustc_lint/src/builtin.rs
+++ b/compiler/rustc_lint/src/builtin.rs
@@ -551,7 +551,7 @@ impl MissingDoc {
             }
         }
 
-        let attrs = cx.tcx.get_attrs(def_id.to_def_id());
+        let attrs = cx.tcx.hir().attrs(cx.tcx.hir().local_def_id_to_hir_id(def_id));
         let has_doc = attrs.iter().any(has_doc);
         if !has_doc {
             cx.struct_span_lint(
@@ -2737,11 +2737,7 @@ impl ClashingExternDeclarations {
                 // bottleneck, this does just fine.
                 (
                     overridden_link_name,
-                    tcx.get_attrs(fi.def_id.to_def_id())
-                        .iter()
-                        .find(|at| at.has_name(sym::link_name))
-                        .unwrap()
-                        .span,
+                    tcx.get_attr(fi.def_id.to_def_id(), sym::link_name).unwrap().span,
                 )
             })
         {
diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs
index dfce30171ff2b..62d427fcd0238 100644
--- a/compiler/rustc_lint/src/types.rs
+++ b/compiler/rustc_lint/src/types.rs
@@ -668,7 +668,7 @@ enum FfiResult<'tcx> {
 }
 
 crate fn nonnull_optimization_guaranteed<'tcx>(tcx: TyCtxt<'tcx>, def: ty::AdtDef<'tcx>) -> bool {
-    tcx.get_attrs(def.did()).iter().any(|a| a.has_name(sym::rustc_nonnull_optimization_guaranteed))
+    tcx.has_attr(def.did(), sym::rustc_nonnull_optimization_guaranteed)
 }
 
 /// `repr(transparent)` structs can have a single non-ZST field, this function returns that
diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs
index 494bdaa1e2b9c..8cae95f46dc37 100644
--- a/compiler/rustc_lint/src/unused.rs
+++ b/compiler/rustc_lint/src/unused.rs
@@ -303,26 +303,25 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
             descr_pre_path: &str,
             descr_post_path: &str,
         ) -> bool {
-            for attr in cx.tcx.get_attrs(def_id).iter() {
-                if attr.has_name(sym::must_use) {
-                    cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| {
-                        let msg = format!(
-                            "unused {}`{}`{} that must be used",
-                            descr_pre_path,
-                            cx.tcx.def_path_str(def_id),
-                            descr_post_path
-                        );
-                        let mut err = lint.build(&msg);
-                        // check for #[must_use = "..."]
-                        if let Some(note) = attr.value_str() {
-                            err.note(note.as_str());
-                        }
-                        err.emit();
-                    });
-                    return true;
-                }
+            if let Some(attr) = cx.tcx.get_attr(def_id, sym::must_use) {
+                cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| {
+                    let msg = format!(
+                        "unused {}`{}`{} that must be used",
+                        descr_pre_path,
+                        cx.tcx.def_path_str(def_id),
+                        descr_post_path
+                    );
+                    let mut err = lint.build(&msg);
+                    // check for #[must_use = "..."]
+                    if let Some(note) = attr.value_str() {
+                        err.note(note.as_str());
+                    }
+                    err.emit();
+                });
+                true
+            } else {
+                false
             }
-            false
         }
     }
 }
diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs
index 81388a0bf585d..1de7dae3c25cb 100644
--- a/compiler/rustc_metadata/src/rmeta/encoder.rs
+++ b/compiler/rustc_metadata/src/rmeta/encoder.rs
@@ -985,11 +985,17 @@ fn should_encode_generics(def_kind: DefKind) -> bool {
 }
 
 impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
-    fn encode_attrs(&mut self, def_id: DefId) {
-        let attrs = self.tcx.get_attrs(def_id);
-        record!(self.tables.attributes[def_id] <- attrs);
-        if attrs.iter().any(|attr| attr.may_have_doc_links()) {
-            self.tables.may_have_doc_links.set(def_id.index, ());
+    fn encode_attrs(&mut self, def_id: LocalDefId) {
+        let mut attrs = self
+            .tcx
+            .hir()
+            .attrs(self.tcx.hir().local_def_id_to_hir_id(def_id))
+            .iter()
+            .filter(|attr| !rustc_feature::is_builtin_only_local(attr.name_or_empty()));
+
+        record!(self.tables.attributes[def_id.to_def_id()] <- attrs.clone());
+        if attrs.any(|attr| attr.may_have_doc_links()) {
+            self.tables.may_have_doc_links.set(def_id.local_def_index, ());
         }
     }
 
@@ -1005,7 +1011,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
             let Some(def_kind) = def_kind else { continue };
             self.tables.opt_def_kind.set(def_id.index, def_kind);
             record!(self.tables.def_span[def_id] <- tcx.def_span(def_id));
-            self.encode_attrs(def_id);
+            self.encode_attrs(local_id);
             record!(self.tables.expn_that_defined[def_id] <- self.tcx.expn_that_defined(def_id));
             if def_kind.has_codegen_attrs() {
                 record!(self.tables.codegen_fn_attrs[def_id] <- self.tcx.codegen_fn_attrs(def_id));
@@ -1670,7 +1676,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
 
             self.tables.opt_def_kind.set(LOCAL_CRATE.as_def_id().index, DefKind::Mod);
             record!(self.tables.def_span[LOCAL_CRATE.as_def_id()] <- tcx.def_span(LOCAL_CRATE.as_def_id()));
-            self.encode_attrs(LOCAL_CRATE.as_def_id());
+            self.encode_attrs(LOCAL_CRATE.as_def_id().expect_local());
             record!(self.tables.visibility[LOCAL_CRATE.as_def_id()] <- tcx.visibility(LOCAL_CRATE.as_def_id()));
             if let Some(stability) = stability {
                 record!(self.tables.lookup_stability[LOCAL_CRATE.as_def_id()] <- stability);
@@ -1711,7 +1717,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
                 let def_id = id.to_def_id();
                 self.tables.opt_def_kind.set(def_id.index, DefKind::Macro(macro_kind));
                 record!(self.tables.kind[def_id] <- EntryKind::ProcMacro(macro_kind));
-                self.encode_attrs(def_id);
+                self.encode_attrs(id);
                 record!(self.tables.def_keys[def_id] <- def_key);
                 record!(self.tables.def_ident_span[def_id] <- span);
                 record!(self.tables.def_span[def_id] <- span);
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index 629a550b77596..ec268fc2343d8 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -1072,6 +1072,9 @@ rustc_queries! {
         desc { |tcx| "checking whether `{}` is `doc(hidden)`", tcx.def_path_str(def_id) }
     }
 
+    /// Returns the attributes on the item at `def_id`.
+    ///
+    /// Do not use this directly, use `tcx.get_attrs` instead.
     query item_attrs(def_id: DefId) -> &'tcx [ast::Attribute] {
         desc { |tcx| "collecting attributes of `{}`", tcx.def_path_str(def_id) }
         separate_provide_extern
diff --git a/compiler/rustc_middle/src/ty/adt.rs b/compiler/rustc_middle/src/ty/adt.rs
index 2e4c16e39eb96..bf7cb610a9097 100644
--- a/compiler/rustc_middle/src/ty/adt.rs
+++ b/compiler/rustc_middle/src/ty/adt.rs
@@ -230,8 +230,7 @@ impl AdtDefData {
             flags |= AdtFlags::HAS_CTOR;
         }
 
-        let attrs = tcx.get_attrs(did);
-        if tcx.sess.contains_name(&attrs, sym::fundamental) {
+        if tcx.has_attr(did, sym::fundamental) {
             flags |= AdtFlags::IS_FUNDAMENTAL;
         }
         if Some(did) == tcx.lang_items().phantom_data() {
diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs
index 9d3d509eb216b..36f1045efef01 100644
--- a/compiler/rustc_middle/src/ty/context.rs
+++ b/compiler/rustc_middle/src/ty/context.rs
@@ -1148,9 +1148,8 @@ impl<'tcx> TyCtxt<'tcx> {
     /// `rustc_layout_scalar_valid_range` attribute.
     // FIXME(eddyb) this is an awkward spot for this method, maybe move it?
     pub fn layout_scalar_valid_range(self, def_id: DefId) -> (Bound<u128>, Bound<u128>) {
-        let attrs = self.get_attrs(def_id);
         let get = |name| {
-            let Some(attr) = attrs.iter().find(|a| a.has_name(name)) else {
+            let Some(attr) = self.get_attr(def_id, name) else {
                 return Bound::Unbounded;
             };
             debug!("layout_scalar_valid_range: attr={:?}", attr);
diff --git a/compiler/rustc_middle/src/ty/error.rs b/compiler/rustc_middle/src/ty/error.rs
index da0934b67c5df..a0fe632f11a07 100644
--- a/compiler/rustc_middle/src/ty/error.rs
+++ b/compiler/rustc_middle/src/ty/error.rs
@@ -568,11 +568,8 @@ impl<T> Trait<T> for X {
                 }
             }
             TargetFeatureCast(def_id) => {
-                let attrs = self.get_attrs(*def_id);
-                let target_spans = attrs
-                    .iter()
-                    .filter(|attr| attr.has_name(sym::target_feature))
-                    .map(|attr| attr.span);
+                let target_spans =
+                    self.get_attrs(*def_id, sym::target_feature).map(|attr| attr.span);
                 diag.note(
                     "functions with `#[target_feature]` can only be coerced to `unsafe` function pointers"
                 );
diff --git a/compiler/rustc_middle/src/ty/instance.rs b/compiler/rustc_middle/src/ty/instance.rs
index 7cf7f8973475d..2180078133301 100644
--- a/compiler/rustc_middle/src/ty/instance.rs
+++ b/compiler/rustc_middle/src/ty/instance.rs
@@ -8,6 +8,7 @@ use rustc_hir::def_id::{CrateNum, DefId};
 use rustc_hir::lang_items::LangItem;
 use rustc_macros::HashStable;
 use rustc_middle::ty::normalize_erasing_regions::NormalizationError;
+use rustc_span::Symbol;
 
 use std::fmt;
 
@@ -185,8 +186,8 @@ impl<'tcx> InstanceDef<'tcx> {
     }
 
     #[inline]
-    pub fn attrs(&self, tcx: TyCtxt<'tcx>) -> ty::Attributes<'tcx> {
-        tcx.get_attrs(self.def_id())
+    pub fn get_attrs(&self, tcx: TyCtxt<'tcx>, attr: Symbol) -> ty::Attributes<'tcx> {
+        tcx.get_attrs(self.def_id(), attr)
     }
 
     /// Returns `true` if the LLVM version of this instance is unconditionally
diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs
index d847068b5bfb3..0e30998388d31 100644
--- a/compiler/rustc_middle/src/ty/mod.rs
+++ b/compiler/rustc_middle/src/ty/mod.rs
@@ -14,12 +14,6 @@ pub use self::AssocItemContainer::*;
 pub use self::BorrowKind::*;
 pub use self::IntVarValue::*;
 pub use self::Variance::*;
-pub use adt::*;
-pub use assoc::*;
-pub use generics::*;
-use rustc_data_structures::fingerprint::Fingerprint;
-pub use vtable::*;
-
 use crate::metadata::ModChild;
 use crate::middle::privacy::AccessLevels;
 use crate::mir::{Body, GeneratorLayout};
@@ -28,8 +22,12 @@ use crate::ty;
 use crate::ty::fast_reject::SimplifiedType;
 use crate::ty::subst::{GenericArg, InternalSubsts, Subst, SubstsRef};
 use crate::ty::util::Discr;
+pub use adt::*;
+pub use assoc::*;
+pub use generics::*;
 use rustc_ast as ast;
 use rustc_attr as attr;
+use rustc_data_structures::fingerprint::Fingerprint;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
 use rustc_data_structures::intern::{Interned, WithStableHash};
 use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
@@ -44,6 +42,7 @@ use rustc_session::cstore::CrateStoreDyn;
 use rustc_span::symbol::{kw, sym, Ident, Symbol};
 use rustc_span::Span;
 use rustc_target::abi::Align;
+pub use vtable::*;
 
 use std::fmt::Debug;
 use std::hash::Hash;
@@ -1818,8 +1817,8 @@ impl ReprOptions {
             field_shuffle_seed ^= user_seed;
         }
 
-        for attr in tcx.get_attrs(did).iter() {
-            for r in attr::find_repr_attrs(&tcx.sess, attr) {
+        for attr in tcx.get_attrs(did, sym::repr) {
+            for r in attr::parse_repr_attr(&tcx.sess, attr) {
                 flags.insert(match r {
                     attr::ReprC => ReprFlags::IS_C,
                     attr::ReprPacked(pack) => {
@@ -1941,8 +1940,7 @@ impl<'tcx> FieldDef {
     }
 }
 
-pub type Attributes<'tcx> = &'tcx [ast::Attribute];
-
+pub type Attributes<'tcx> = impl Iterator<Item = &'tcx ast::Attribute>;
 #[derive(Debug, PartialEq, Eq)]
 pub enum ImplOverlapKind {
     /// These impls are always allowed to overlap.
@@ -2186,8 +2184,8 @@ impl<'tcx> TyCtxt<'tcx> {
         }
     }
 
-    /// Gets the attributes of a definition.
-    pub fn get_attrs(self, did: DefId) -> Attributes<'tcx> {
+    // FIXME(@lcnr): Remove this function.
+    pub fn get_attrs_unchecked(self, did: DefId) -> &'tcx [ast::Attribute] {
         if let Some(did) = did.as_local() {
             self.hir().attrs(self.hir().local_def_id_to_hir_id(did))
         } else {
@@ -2195,9 +2193,29 @@ impl<'tcx> TyCtxt<'tcx> {
         }
     }
 
+    /// Gets all attributes with the given name.
+    pub fn get_attrs(self, did: DefId, attr: Symbol) -> ty::Attributes<'tcx> {
+        let filter_fn = move |a: &&ast::Attribute| a.has_name(attr);
+        if let Some(did) = did.as_local() {
+            self.hir().attrs(self.hir().local_def_id_to_hir_id(did)).iter().filter(filter_fn)
+        } else if cfg!(debug_assertions) && rustc_feature::is_builtin_only_local(attr) {
+            bug!("tried to access the `only_local` attribute `{}` from an extern crate", attr);
+        } else {
+            self.item_attrs(did).iter().filter(filter_fn)
+        }
+    }
+
+    pub fn get_attr(self, did: DefId, attr: Symbol) -> Option<&'tcx ast::Attribute> {
+        self.get_attrs(did, attr).next()
+    }
+
     /// Determines whether an item is annotated with an attribute.
     pub fn has_attr(self, did: DefId, attr: Symbol) -> bool {
-        self.sess.contains_name(&self.get_attrs(did), attr)
+        if cfg!(debug_assertions) && !did.is_local() && rustc_feature::is_builtin_only_local(attr) {
+            bug!("tried to access the `only_local` attribute `{}` from an extern crate", attr);
+        } else {
+            self.get_attrs(did, attr).next().is_some()
+        }
     }
 
     /// Returns `true` if this is an `auto trait`.
diff --git a/compiler/rustc_middle/src/ty/util.rs b/compiler/rustc_middle/src/ty/util.rs
index 1c8af13ce9ca1..b5f8726cdc5b1 100644
--- a/compiler/rustc_middle/src/ty/util.rs
+++ b/compiler/rustc_middle/src/ty/util.rs
@@ -1163,9 +1163,8 @@ pub fn normalize_opaque_types<'tcx>(
 
 /// Determines whether an item is annotated with `doc(hidden)`.
 pub fn is_doc_hidden(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
-    tcx.get_attrs(def_id)
-        .iter()
-        .filter_map(|attr| if attr.has_name(sym::doc) { attr.meta_item_list() } else { None })
+    tcx.get_attrs(def_id, sym::doc)
+        .filter_map(|attr| attr.meta_item_list())
         .any(|items| items.iter().any(|item| item.has_name(sym::hidden)))
 }
 
diff --git a/compiler/rustc_mir_build/src/build/mod.rs b/compiler/rustc_mir_build/src/build/mod.rs
index ce57e5fe846eb..c63c511e09cb4 100644
--- a/compiler/rustc_mir_build/src/build/mod.rs
+++ b/compiler/rustc_mir_build/src/build/mod.rs
@@ -679,7 +679,6 @@ where
     } else {
         None
     };
-    debug!("fn_id {:?} has attrs {:?}", fn_def, tcx.get_attrs(fn_def.did.to_def_id()));
 
     let mut body = builder.finish();
     body.spread_arg = spread_arg;
diff --git a/compiler/rustc_mir_dataflow/src/framework/engine.rs b/compiler/rustc_mir_dataflow/src/framework/engine.rs
index 88ed0128a1fd3..50efb4c1dc42a 100644
--- a/compiler/rustc_mir_dataflow/src/framework/engine.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/engine.rs
@@ -333,14 +333,11 @@ struct RustcMirAttrs {
 
 impl RustcMirAttrs {
     fn parse(tcx: TyCtxt<'_>, def_id: DefId) -> Result<Self, ()> {
-        let attrs = tcx.get_attrs(def_id);
-
         let mut result = Ok(());
         let mut ret = RustcMirAttrs::default();
 
-        let rustc_mir_attrs = attrs
-            .iter()
-            .filter(|attr| attr.has_name(sym::rustc_mir))
+        let rustc_mir_attrs = tcx
+            .get_attrs(def_id, sym::rustc_mir)
             .flat_map(|attr| attr.meta_item_list().into_iter().flat_map(|v| v.into_iter()));
 
         for attr in rustc_mir_attrs {
diff --git a/compiler/rustc_mir_dataflow/src/lib.rs b/compiler/rustc_mir_dataflow/src/lib.rs
index d0837bcf754e4..c1124a533bf2b 100644
--- a/compiler/rustc_mir_dataflow/src/lib.rs
+++ b/compiler/rustc_mir_dataflow/src/lib.rs
@@ -14,9 +14,9 @@ extern crate tracing;
 #[macro_use]
 extern crate rustc_middle;
 
-use rustc_ast::{self as ast, MetaItem};
-use rustc_middle::ty;
-use rustc_session::Session;
+use rustc_ast::MetaItem;
+use rustc_hir::def_id::DefId;
+use rustc_middle::ty::{self, TyCtxt};
 use rustc_span::symbol::{sym, Symbol};
 
 pub use self::drop_flag_effects::{
@@ -49,19 +49,13 @@ pub struct MoveDataParamEnv<'tcx> {
     pub param_env: ty::ParamEnv<'tcx>,
 }
 
-pub fn has_rustc_mir_with(
-    _sess: &Session,
-    attrs: &[ast::Attribute],
-    name: Symbol,
-) -> Option<MetaItem> {
-    for attr in attrs {
-        if attr.has_name(sym::rustc_mir) {
-            let items = attr.meta_item_list();
-            for item in items.iter().flat_map(|l| l.iter()) {
-                match item.meta_item() {
-                    Some(mi) if mi.has_name(name) => return Some(mi.clone()),
-                    _ => continue,
-                }
+pub fn has_rustc_mir_with(tcx: TyCtxt<'_>, def_id: DefId, name: Symbol) -> Option<MetaItem> {
+    for attr in tcx.get_attrs(def_id, sym::rustc_mir) {
+        let items = attr.meta_item_list();
+        for item in items.iter().flat_map(|l| l.iter()) {
+            match item.meta_item() {
+                Some(mi) if mi.has_name(name) => return Some(mi.clone()),
+                _ => continue,
             }
         }
     }
diff --git a/compiler/rustc_mir_dataflow/src/rustc_peek.rs b/compiler/rustc_mir_dataflow/src/rustc_peek.rs
index 51ab5b43bff0a..cc9ee1016c6db 100644
--- a/compiler/rustc_mir_dataflow/src/rustc_peek.rs
+++ b/compiler/rustc_mir_dataflow/src/rustc_peek.rs
@@ -1,4 +1,3 @@
-use rustc_ast::ast;
 use rustc_span::symbol::sym;
 use rustc_span::Span;
 use rustc_target::spec::abi::Abi;
@@ -31,43 +30,41 @@ impl<'tcx> MirPass<'tcx> for SanityCheck {
             debug!("running rustc_peek::SanityCheck on {}", tcx.def_path_str(def_id));
         }
 
-        let attributes = tcx.get_attrs(def_id);
         let param_env = tcx.param_env(def_id);
         let move_data = MoveData::gather_moves(body, tcx, param_env).unwrap();
         let mdpe = MoveDataParamEnv { move_data, param_env };
-        let sess = &tcx.sess;
 
-        if has_rustc_mir_with(sess, &attributes, sym::rustc_peek_maybe_init).is_some() {
+        if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_maybe_init).is_some() {
             let flow_inits = MaybeInitializedPlaces::new(tcx, body, &mdpe)
                 .into_engine(tcx, body)
                 .iterate_to_fixpoint();
 
-            sanity_check_via_rustc_peek(tcx, body, &attributes, &flow_inits);
+            sanity_check_via_rustc_peek(tcx, body, &flow_inits);
         }
 
-        if has_rustc_mir_with(sess, &attributes, sym::rustc_peek_maybe_uninit).is_some() {
+        if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_maybe_uninit).is_some() {
             let flow_uninits = MaybeUninitializedPlaces::new(tcx, body, &mdpe)
                 .into_engine(tcx, body)
                 .iterate_to_fixpoint();
 
-            sanity_check_via_rustc_peek(tcx, body, &attributes, &flow_uninits);
+            sanity_check_via_rustc_peek(tcx, body, &flow_uninits);
         }
 
-        if has_rustc_mir_with(sess, &attributes, sym::rustc_peek_definite_init).is_some() {
+        if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_definite_init).is_some() {
             let flow_def_inits = DefinitelyInitializedPlaces::new(tcx, body, &mdpe)
                 .into_engine(tcx, body)
                 .iterate_to_fixpoint();
 
-            sanity_check_via_rustc_peek(tcx, body, &attributes, &flow_def_inits);
+            sanity_check_via_rustc_peek(tcx, body, &flow_def_inits);
         }
 
-        if has_rustc_mir_with(sess, &attributes, sym::rustc_peek_liveness).is_some() {
+        if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_liveness).is_some() {
             let flow_liveness = MaybeLiveLocals.into_engine(tcx, body).iterate_to_fixpoint();
 
-            sanity_check_via_rustc_peek(tcx, body, &attributes, &flow_liveness);
+            sanity_check_via_rustc_peek(tcx, body, &flow_liveness);
         }
 
-        if has_rustc_mir_with(sess, &attributes, sym::stop_after_dataflow).is_some() {
+        if has_rustc_mir_with(tcx, def_id, sym::stop_after_dataflow).is_some() {
             tcx.sess.fatal("stop_after_dataflow ended compilation");
         }
     }
@@ -92,7 +89,6 @@ impl<'tcx> MirPass<'tcx> for SanityCheck {
 pub fn sanity_check_via_rustc_peek<'tcx, A>(
     tcx: TyCtxt<'tcx>,
     body: &Body<'tcx>,
-    _attributes: &[ast::Attribute],
     results: &Results<'tcx, A>,
 ) where
     A: RustcPeekAt<'tcx>,
diff --git a/compiler/rustc_monomorphize/src/polymorphize.rs b/compiler/rustc_monomorphize/src/polymorphize.rs
index cf13c856a71c1..3cfd935d8b0ae 100644
--- a/compiler/rustc_monomorphize/src/polymorphize.rs
+++ b/compiler/rustc_monomorphize/src/polymorphize.rs
@@ -197,7 +197,7 @@ fn emit_unused_generic_params_error<'tcx>(
     unused_parameters: &FiniteBitSet<u32>,
 ) {
     let base_def_id = tcx.typeck_root_def_id(def_id);
-    if !tcx.get_attrs(base_def_id).iter().any(|a| a.has_name(sym::rustc_polymorphize_error)) {
+    if !tcx.has_attr(base_def_id, sym::rustc_polymorphize_error) {
         return;
     }
 
diff --git a/compiler/rustc_passes/src/check_const.rs b/compiler/rustc_passes/src/check_const.rs
index 9e352fa5cc673..15e24299075b9 100644
--- a/compiler/rustc_passes/src/check_const.rs
+++ b/compiler/rustc_passes/src/check_const.rs
@@ -170,7 +170,7 @@ impl<'tcx> CheckConstVisitor<'tcx> {
 
             // If `def_id` is `None`, we don't need to consider stability attributes.
             let def_id = match def_id {
-                Some(x) => x.to_def_id(),
+                Some(x) => x,
                 None => return true,
             };
 
@@ -182,14 +182,16 @@ impl<'tcx> CheckConstVisitor<'tcx> {
 
             // If this crate is not using stability attributes, or this function is not claiming to be a
             // stable `const fn`, that is all that is required.
-            if !tcx.features().staged_api || tcx.has_attr(def_id, sym::rustc_const_unstable) {
+            if !tcx.features().staged_api
+                || tcx.has_attr(def_id.to_def_id(), sym::rustc_const_unstable)
+            {
                 return true;
             }
 
             // However, we cannot allow stable `const fn`s to use unstable features without an explicit
             // opt-in via `rustc_allow_const_fn_unstable`.
-            attr::rustc_allow_const_fn_unstable(&tcx.sess, &tcx.get_attrs(def_id))
-                .any(|name| name == feature_gate)
+            let attrs = tcx.hir().attrs(tcx.hir().local_def_id_to_hir_id(def_id));
+            attr::rustc_allow_const_fn_unstable(&tcx.sess, attrs).any(|name| name == feature_gate)
         };
 
         match required_gates {
diff --git a/compiler/rustc_passes/src/diagnostic_items.rs b/compiler/rustc_passes/src/diagnostic_items.rs
index 9cbb7917e9a32..ed694eb0e327a 100644
--- a/compiler/rustc_passes/src/diagnostic_items.rs
+++ b/compiler/rustc_passes/src/diagnostic_items.rs
@@ -83,7 +83,7 @@ fn collect_item(tcx: TyCtxt<'_>, items: &mut DiagnosticItems, name: Symbol, item
     }
 }
 
-/// Extract the first `rustc_diagnostic_item = "$name"` out of a list of attributes.p
+/// Extract the first `rustc_diagnostic_item = "$name"` out of a list of attributes.
 fn extract(attrs: &[ast::Attribute]) -> Option<Symbol> {
     attrs.iter().find_map(|attr| {
         if attr.has_name(sym::rustc_diagnostic_item) { attr.value_str() } else { None }
diff --git a/compiler/rustc_passes/src/layout_test.rs b/compiler/rustc_passes/src/layout_test.rs
index 00e8eb5eb2b38..728aaab6137ab 100644
--- a/compiler/rustc_passes/src/layout_test.rs
+++ b/compiler/rustc_passes/src/layout_test.rs
@@ -27,10 +27,8 @@ impl<'tcx> ItemLikeVisitor<'tcx> for LayoutTest<'tcx> {
             | ItemKind::Enum(..)
             | ItemKind::Struct(..)
             | ItemKind::Union(..) => {
-                for attr in self.tcx.get_attrs(item.def_id.to_def_id()).iter() {
-                    if attr.has_name(sym::rustc_layout) {
-                        self.dump_layout_of(item.def_id, item, attr);
-                    }
+                for attr in self.tcx.get_attrs(item.def_id.to_def_id(), sym::rustc_layout) {
+                    self.dump_layout_of(item.def_id, item, attr);
                 }
             }
             _ => {}
diff --git a/compiler/rustc_passes/src/stability.rs b/compiler/rustc_passes/src/stability.rs
index 10dc587be6e48..e1bc248971ae2 100644
--- a/compiler/rustc_passes/src/stability.rs
+++ b/compiler/rustc_passes/src/stability.rs
@@ -110,7 +110,7 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> {
     ) where
         F: FnOnce(&mut Self),
     {
-        let attrs = self.tcx.get_attrs(def_id.to_def_id());
+        let attrs = self.tcx.hir().attrs(self.tcx.hir().local_def_id_to_hir_id(def_id));
         debug!("annotate(id = {:?}, attrs = {:?})", def_id, attrs);
 
         let depr = attr::find_deprecation(&self.tcx.sess, attrs);
diff --git a/compiler/rustc_resolve/src/late/lifetimes.rs b/compiler/rustc_resolve/src/late/lifetimes.rs
index 030c27af4447d..ae4455531ab5b 100644
--- a/compiler/rustc_resolve/src/late/lifetimes.rs
+++ b/compiler/rustc_resolve/src/late/lifetimes.rs
@@ -2002,12 +2002,7 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
                         let parent_def_id = self.tcx.parent(def_id);
                         if let Some(def_id) = parent_def_id.as_local() {
                             // lifetimes in `derive` expansions don't count (Issue #53738)
-                            if self
-                                .tcx
-                                .get_attrs(def_id.to_def_id())
-                                .iter()
-                                .any(|attr| attr.has_name(sym::automatically_derived))
-                            {
+                            if self.tcx.has_attr(def_id.to_def_id(), sym::automatically_derived) {
                                 continue;
                             }
 
diff --git a/compiler/rustc_symbol_mangling/src/test.rs b/compiler/rustc_symbol_mangling/src/test.rs
index 37d1cffa2a52b..7249ce04c155e 100644
--- a/compiler/rustc_symbol_mangling/src/test.rs
+++ b/compiler/rustc_symbol_mangling/src/test.rs
@@ -49,27 +49,26 @@ struct SymbolNamesTest<'tcx> {
 impl SymbolNamesTest<'_> {
     fn process_attrs(&mut self, def_id: LocalDefId) {
         let tcx = self.tcx;
-        for attr in tcx.get_attrs(def_id.to_def_id()).iter() {
-            if attr.has_name(SYMBOL_NAME) {
-                let def_id = def_id.to_def_id();
-                let instance = Instance::new(
-                    def_id,
-                    tcx.erase_regions(InternalSubsts::identity_for_item(tcx, def_id)),
-                );
-                let mangled = tcx.symbol_name(instance);
-                tcx.sess.span_err(attr.span, &format!("symbol-name({})", mangled));
-                if let Ok(demangling) = rustc_demangle::try_demangle(mangled.name) {
-                    tcx.sess.span_err(attr.span, &format!("demangling({})", demangling));
-                    tcx.sess.span_err(attr.span, &format!("demangling-alt({:#})", demangling));
-                }
-            } else if attr.has_name(DEF_PATH) {
-                let path = with_no_trimmed_paths!(tcx.def_path_str(def_id.to_def_id()));
-                tcx.sess.span_err(attr.span, &format!("def-path({})", path));
+        // The formatting of `tag({})` is chosen so that tests can elect
+        // to test the entirety of the string, if they choose, or else just
+        // some subset.
+        for attr in tcx.get_attrs(def_id.to_def_id(), SYMBOL_NAME) {
+            let def_id = def_id.to_def_id();
+            let instance = Instance::new(
+                def_id,
+                tcx.erase_regions(InternalSubsts::identity_for_item(tcx, def_id)),
+            );
+            let mangled = tcx.symbol_name(instance);
+            tcx.sess.span_err(attr.span, &format!("symbol-name({})", mangled));
+            if let Ok(demangling) = rustc_demangle::try_demangle(mangled.name) {
+                tcx.sess.span_err(attr.span, &format!("demangling({})", demangling));
+                tcx.sess.span_err(attr.span, &format!("demangling-alt({:#})", demangling));
             }
+        }
 
-            // (*) The formatting of `tag({})` is chosen so that tests can elect
-            // to test the entirety of the string, if they choose, or else just
-            // some subset.
+        for attr in tcx.get_attrs(def_id.to_def_id(), DEF_PATH) {
+            let path = with_no_trimmed_paths!(tcx.def_path_str(def_id.to_def_id()));
+            tcx.sess.span_err(attr.span, &format!("def-path({})", path));
         }
     }
 }
diff --git a/compiler/rustc_trait_selection/src/traits/on_unimplemented.rs b/compiler/rustc_trait_selection/src/traits/on_unimplemented.rs
index c266eec25aa6b..7d41819819536 100644
--- a/compiler/rustc_trait_selection/src/traits/on_unimplemented.rs
+++ b/compiler/rustc_trait_selection/src/traits/on_unimplemented.rs
@@ -175,9 +175,7 @@ impl<'tcx> OnUnimplementedDirective {
     }
 
     pub fn of_item(tcx: TyCtxt<'tcx>, item_def_id: DefId) -> Result<Option<Self>, ErrorGuaranteed> {
-        let attrs = tcx.get_attrs(item_def_id);
-
-        let Some(attr) = tcx.sess.find_by_name(&attrs, sym::rustc_on_unimplemented) else {
+        let Some(attr) = tcx.get_attr(item_def_id, sym::rustc_on_unimplemented) else {
             return Ok(None);
         };
 
diff --git a/compiler/rustc_trait_selection/src/traits/select/mod.rs b/compiler/rustc_trait_selection/src/traits/select/mod.rs
index 6584d33032a46..3d6bcc93c978c 100644
--- a/compiler/rustc_trait_selection/src/traits/select/mod.rs
+++ b/compiler/rustc_trait_selection/src/traits/select/mod.rs
@@ -1156,9 +1156,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
         if let ImplCandidate(def_id) = candidate {
             if let ty::ImplPolarity::Reservation = tcx.impl_polarity(def_id) {
                 if let Some(intercrate_ambiguity_clauses) = &mut self.intercrate_ambiguity_causes {
-                    let attrs = tcx.get_attrs(def_id);
-                    let attr = tcx.sess.find_by_name(&attrs, sym::rustc_reservation_impl);
-                    let value = attr.and_then(|a| a.value_str());
+                    let value = tcx
+                        .get_attr(def_id, sym::rustc_reservation_impl)
+                        .and_then(|a| a.value_str());
                     if let Some(value) = value {
                         debug!(
                             "filter_reservation_impls: \
diff --git a/compiler/rustc_typeck/src/check/check.rs b/compiler/rustc_typeck/src/check/check.rs
index 4627b58c9bcd3..35cc2da15bd69 100644
--- a/compiler/rustc_typeck/src/check/check.rs
+++ b/compiler/rustc_typeck/src/check/check.rs
@@ -1056,9 +1056,7 @@ fn check_impl_items_against_trait<'tcx>(
         if let Some(missing_items) = must_implement_one_of {
             let impl_span = tcx.sess.source_map().guess_head_span(full_impl_span);
             let attr_span = tcx
-                .get_attrs(impl_trait_ref.def_id)
-                .iter()
-                .find(|attr| attr.has_name(sym::rustc_must_implement_one_of))
+                .get_attr(impl_trait_ref.def_id, sym::rustc_must_implement_one_of)
                 .map(|attr| attr.span);
 
             missing_items_must_implement_one_of_err(tcx, impl_span, missing_items, attr_span);
@@ -1158,20 +1156,20 @@ pub fn check_simd(tcx: TyCtxt<'_>, sp: Span, def_id: LocalDefId) {
 pub(super) fn check_packed(tcx: TyCtxt<'_>, sp: Span, def: ty::AdtDef<'_>) {
     let repr = def.repr();
     if repr.packed() {
-        for attr in tcx.get_attrs(def.did()).iter() {
-            for r in attr::find_repr_attrs(&tcx.sess, attr) {
+        for attr in tcx.get_attrs(def.did(), sym::repr) {
+            for r in attr::parse_repr_attr(&tcx.sess, attr) {
                 if let attr::ReprPacked(pack) = r
-                    && let Some(repr_pack) = repr.pack
-                    && pack as u64 != repr_pack.bytes()
-                {
-                            struct_span_err!(
-                                tcx.sess,
-                                sp,
-                                E0634,
-                                "type has conflicting packed representation hints"
-                            )
-                            .emit();
-                }
+                && let Some(repr_pack) = repr.pack
+                && pack as u64 != repr_pack.bytes()
+            {
+                        struct_span_err!(
+                            tcx.sess,
+                            sp,
+                            E0634,
+                            "type has conflicting packed representation hints"
+                        )
+                        .emit();
+            }
             }
         }
         if repr.align.is_some() {
@@ -1321,8 +1319,7 @@ fn check_enum<'tcx>(
     def.destructor(tcx); // force the destructor to be evaluated
 
     if vs.is_empty() {
-        let attributes = tcx.get_attrs(def_id.to_def_id());
-        if let Some(attr) = tcx.sess.find_by_name(&attributes, sym::repr) {
+        if let Some(attr) = tcx.get_attr(def_id.to_def_id(), sym::repr) {
             struct_span_err!(
                 tcx.sess,
                 attr.span,
diff --git a/compiler/rustc_typeck/src/check/demand.rs b/compiler/rustc_typeck/src/check/demand.rs
index bfd2c32fd7e3c..ae4cebe866bdb 100644
--- a/compiler/rustc_typeck/src/check/demand.rs
+++ b/compiler/rustc_typeck/src/check/demand.rs
@@ -407,8 +407,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             self.has_only_self_parameter(m)
                 && self
                     .tcx
-                    .get_attrs(m.def_id)
-                    .iter()
                     // This special internal attribute is used to permit
                     // "identity-like" conversion methods to be suggested here.
                     //
@@ -419,7 +417,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     //
                     // FIXME? Other potential candidate methods: `as_ref` and
                     // `as_mut`?
-                    .any(|a| a.has_name(sym::rustc_conversion_suggestion))
+                    .has_attr(m.def_id, sym::rustc_conversion_suggestion)
         });
 
         methods
diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs
index 15edc11a4974d..be389f0e3f515 100644
--- a/compiler/rustc_typeck/src/check/generator_interior.rs
+++ b/compiler/rustc_typeck/src/check/generator_interior.rs
@@ -609,44 +609,43 @@ fn check_must_not_suspend_def(
     hir_id: HirId,
     data: SuspendCheckData<'_, '_>,
 ) -> bool {
-    for attr in tcx.get_attrs(def_id).iter() {
-        if attr.has_name(sym::must_not_suspend) {
-            tcx.struct_span_lint_hir(
-                rustc_session::lint::builtin::MUST_NOT_SUSPEND,
-                hir_id,
-                data.source_span,
-                |lint| {
-                    let msg = format!(
-                        "{}`{}`{} held across a suspend point, but should not be",
-                        data.descr_pre,
-                        tcx.def_path_str(def_id),
-                        data.descr_post,
-                    );
-                    let mut err = lint.build(&msg);
+    if let Some(attr) = tcx.get_attr(def_id, sym::must_not_suspend) {
+        tcx.struct_span_lint_hir(
+            rustc_session::lint::builtin::MUST_NOT_SUSPEND,
+            hir_id,
+            data.source_span,
+            |lint| {
+                let msg = format!(
+                    "{}`{}`{} held across a suspend point, but should not be",
+                    data.descr_pre,
+                    tcx.def_path_str(def_id),
+                    data.descr_post,
+                );
+                let mut err = lint.build(&msg);
 
-                    // add span pointing to the offending yield/await
-                    err.span_label(data.yield_span, "the value is held across this suspend point");
+                // add span pointing to the offending yield/await
+                err.span_label(data.yield_span, "the value is held across this suspend point");
 
-                    // Add optional reason note
-                    if let Some(note) = attr.value_str() {
-                        // FIXME(guswynn): consider formatting this better
-                        err.span_note(data.source_span, note.as_str());
-                    }
+                // Add optional reason note
+                if let Some(note) = attr.value_str() {
+                    // FIXME(guswynn): consider formatting this better
+                    err.span_note(data.source_span, note.as_str());
+                }
 
-                    // Add some quick suggestions on what to do
-                    // FIXME: can `drop` work as a suggestion here as well?
-                    err.span_help(
-                        data.source_span,
-                        "consider using a block (`{ ... }`) \
-                        to shrink the value's scope, ending before the suspend point",
-                    );
+                // Add some quick suggestions on what to do
+                // FIXME: can `drop` work as a suggestion here as well?
+                err.span_help(
+                    data.source_span,
+                    "consider using a block (`{ ... }`) \
+                    to shrink the value's scope, ending before the suspend point",
+                );
 
-                    err.emit();
-                },
-            );
+                err.emit();
+            },
+        );
 
-            return true;
-        }
+        true
+    } else {
+        false
     }
-    false
 }
diff --git a/compiler/rustc_typeck/src/check_unused.rs b/compiler/rustc_typeck/src/check_unused.rs
index 1310467aeb968..00f0d1e6f02a9 100644
--- a/compiler/rustc_typeck/src/check_unused.rs
+++ b/compiler/rustc_typeck/src/check_unused.rs
@@ -128,7 +128,8 @@ fn unused_crates_lint(tcx: TyCtxt<'_>) {
                 tcx.struct_span_lint_hir(lint, id, span, |lint| {
                     // Removal suggestion span needs to include attributes (Issue #54400)
                     let span_with_attrs = tcx
-                        .get_attrs(extern_crate.def_id)
+                        .hir()
+                        .attrs(id)
                         .iter()
                         .map(|attr| attr.span)
                         .fold(span, |acc, attr_span| acc.to(attr_span));
@@ -166,13 +167,13 @@ fn unused_crates_lint(tcx: TyCtxt<'_>) {
             continue;
         }
 
+        let id = tcx.hir().local_def_id_to_hir_id(def_id);
         // If the extern crate has any attributes, they may have funky
         // semantics we can't faithfully represent using `use` (most
         // notably `#[macro_use]`). Ignore it.
-        if !tcx.get_attrs(extern_crate.def_id).is_empty() {
+        if !tcx.hir().attrs(id).is_empty() {
             continue;
         }
-        let id = tcx.hir().local_def_id_to_hir_id(def_id);
         tcx.struct_span_lint_hir(lint, id, extern_crate.span, |lint| {
             // Otherwise, we can convert it into a `use` of some kind.
             let base_replacement = match extern_crate.orig_name {
diff --git a/compiler/rustc_typeck/src/collect.rs b/compiler/rustc_typeck/src/collect.rs
index 2e0e026631b51..cda817dee1e1e 100644
--- a/compiler/rustc_typeck/src/collect.rs
+++ b/compiler/rustc_typeck/src/collect.rs
@@ -1200,9 +1200,7 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: DefId) -> ty::TraitDef {
         ty::trait_def::TraitSpecializationKind::None
     };
     let must_implement_one_of = tcx
-        .get_attrs(def_id)
-        .iter()
-        .find(|attr| attr.has_name(sym::rustc_must_implement_one_of))
+        .get_attr(def_id, sym::rustc_must_implement_one_of)
         // Check that there are at least 2 arguments of `#[rustc_must_implement_one_of]`
         // and that they are all identifiers
         .and_then(|attr| match attr.meta_item_list() {
diff --git a/compiler/rustc_typeck/src/lib.rs b/compiler/rustc_typeck/src/lib.rs
index fe285820ba6df..67c6e791bfe58 100644
--- a/compiler/rustc_typeck/src/lib.rs
+++ b/compiler/rustc_typeck/src/lib.rs
@@ -298,17 +298,12 @@ fn check_main_fn_ty(tcx: TyCtxt<'_>, main_def_id: DefId) {
         error = true;
     }
 
-    for attr in tcx.get_attrs(main_def_id) {
-        if attr.has_name(sym::track_caller) {
-            tcx.sess
-                .struct_span_err(
-                    attr.span,
-                    "`main` function is not allowed to be `#[track_caller]`",
-                )
-                .span_label(main_span, "`main` function is not allowed to be `#[track_caller]`")
-                .emit();
-            error = true;
-        }
+    for attr in tcx.get_attrs(main_def_id, sym::track_caller) {
+        tcx.sess
+            .struct_span_err(attr.span, "`main` function is not allowed to be `#[track_caller]`")
+            .span_label(main_span, "`main` function is not allowed to be `#[track_caller]`")
+            .emit();
+        error = true;
     }
 
     if error {
diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs
index 261eb39bf723d..9a579cb531164 100644
--- a/src/librustdoc/clean/inline.rs
+++ b/src/librustdoc/clean/inline.rs
@@ -22,7 +22,7 @@ use crate::clean::{
 use crate::core::DocContext;
 use crate::formats::item_type::ItemType;
 
-type Attrs<'hir> = rustc_middle::ty::Attributes<'hir>;
+type Attrs<'hir> = &'hir [ast::Attribute];
 
 /// Attempt to inline a definition into this AST.
 ///
@@ -155,7 +155,7 @@ crate fn try_inline_glob(
 }
 
 crate fn load_attrs<'hir>(cx: &DocContext<'hir>, did: DefId) -> Attrs<'hir> {
-    cx.tcx.get_attrs(did)
+    cx.tcx.get_attrs_unchecked(did)
 }
 
 /// Record an external fully qualified name in the external_paths cache.
@@ -691,7 +691,7 @@ crate fn record_extern_trait(cx: &mut DocContext<'_>, did: DefId) {
 
     let trait_ = clean::TraitWithExtraInfo {
         trait_,
-        is_notable: clean::utils::has_doc_flag(cx.tcx.get_attrs(did), sym::notable_trait),
+        is_notable: clean::utils::has_doc_flag(cx.tcx, did, sym::notable_trait),
     };
     cx.external_traits.borrow_mut().insert(did, trait_);
     cx.active_extern_traits.remove(&did);
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index 2b65b8f910c70..456d860f12559 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -211,8 +211,8 @@ impl ExternalCrate {
         // Failing that, see if there's an attribute specifying where to find this
         // external crate
         let did = self.crate_num.as_def_id();
-        tcx.get_attrs(did)
-            .lists(sym::doc)
+        tcx.get_attrs(did, sym::doc)
+            .flat_map(|attr| attr.meta_item_list().unwrap_or_default())
             .filter(|a| a.has_name(sym::html_root_url))
             .filter_map(|a| a.value_str())
             .map(to_remote)
@@ -226,11 +226,13 @@ impl ExternalCrate {
 
         let as_keyword = |res: Res<!>| {
             if let Res::Def(DefKind::Mod, def_id) = res {
-                let attrs = tcx.get_attrs(def_id);
                 let mut keyword = None;
-                for attr in attrs.lists(sym::doc) {
-                    if attr.has_name(sym::keyword) {
-                        if let Some(v) = attr.value_str() {
+                let meta_items = tcx
+                    .get_attrs(def_id, sym::doc)
+                    .flat_map(|attr| attr.meta_item_list().unwrap_or_default());
+                for meta in meta_items {
+                    if meta.has_name(sym::keyword) {
+                        if let Some(v) = meta.value_str() {
                             keyword = Some(v);
                             break;
                         }
@@ -288,11 +290,13 @@ impl ExternalCrate {
         // rendering by delegating everything to a hash map.
         let as_primitive = |res: Res<!>| {
             if let Res::Def(DefKind::Mod, def_id) = res {
-                let attrs = tcx.get_attrs(def_id);
                 let mut prim = None;
-                for attr in attrs.lists(sym::doc) {
-                    if let Some(v) = attr.value_str() {
-                        if attr.has_name(sym::primitive) {
+                let meta_items = tcx
+                    .get_attrs(def_id, sym::doc)
+                    .flat_map(|attr| attr.meta_item_list().unwrap_or_default());
+                for meta in meta_items {
+                    if let Some(v) = meta.value_str() {
+                        if meta.has_name(sym::primitive) {
                             prim = PrimitiveType::from_symbol(v);
                             if prim.is_some() {
                                 break;
@@ -413,7 +417,10 @@ impl Item {
     }
 
     crate fn inner_docs(&self, tcx: TyCtxt<'_>) -> bool {
-        self.item_id.as_def_id().map(|did| tcx.get_attrs(did).inner_docs()).unwrap_or(false)
+        self.item_id
+            .as_def_id()
+            .map(|did| tcx.get_attrs_unchecked(did).inner_docs())
+            .unwrap_or(false)
     }
 
     crate fn span(&self, tcx: TyCtxt<'_>) -> Span {
@@ -464,7 +471,7 @@ impl Item {
         kind: ItemKind,
         cx: &mut DocContext<'_>,
     ) -> Item {
-        let ast_attrs = cx.tcx.get_attrs(def_id);
+        let ast_attrs = cx.tcx.get_attrs_unchecked(def_id);
 
         Self::from_def_id_and_attrs_and_parts(
             def_id,
diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs
index 3a2f24d719c93..c67b92df6431f 100644
--- a/src/librustdoc/clean/utils.rs
+++ b/src/librustdoc/clean/utils.rs
@@ -474,10 +474,9 @@ crate fn find_nearest_parent_module(tcx: TyCtxt<'_>, def_id: DefId) -> Option<De
 ///
 /// This function exists because it runs on `hir::Attributes` whereas the other is a
 /// `clean::Attributes` method.
-crate fn has_doc_flag(attrs: ty::Attributes<'_>, flag: Symbol) -> bool {
-    attrs.iter().any(|attr| {
-        attr.has_name(sym::doc)
-            && attr.meta_item_list().map_or(false, |l| rustc_attr::list_contains_name(&l, flag))
+crate fn has_doc_flag(tcx: TyCtxt<'_>, did: DefId, flag: Symbol) -> bool {
+    tcx.get_attrs(did, sym::doc).any(|attr| {
+        attr.meta_item_list().map_or(false, |l| rustc_attr::list_contains_name(&l, flag))
     })
 }
 
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index fbb3d3e45845b..4951cd83af207 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -323,7 +323,7 @@ fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[cl
 
             clean::ImportItem(ref import) => {
                 let (stab, stab_tags) = if let Some(import_def_id) = import.source.did {
-                    let ast_attrs = cx.tcx().get_attrs(import_def_id);
+                    let ast_attrs = cx.tcx().get_attrs_unchecked(import_def_id);
                     let import_attrs = Box::new(clean::Attributes::from_ast(ast_attrs, None));
 
                     // Just need an item with the correct def_id and attrs
diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs
index d245c3750ec08..3b7ca7dc3c519 100644
--- a/src/librustdoc/passes/collect_trait_impls.rs
+++ b/src/librustdoc/passes/collect_trait_impls.rs
@@ -53,9 +53,7 @@ crate fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> Crate
             while let Some(did) = parent {
                 attr_buf.extend(
                     cx.tcx
-                        .get_attrs(did)
-                        .iter()
-                        .filter(|attr| attr.has_name(sym::doc))
+                        .get_attrs(did, sym::doc)
                         .filter(|attr| {
                             if let Some([attr]) = attr.meta_item_list().as_deref() {
                                 attr.has_name(sym::cfg)
diff --git a/src/tools/clippy/clippy_lints/src/derivable_impls.rs b/src/tools/clippy/clippy_lints/src/derivable_impls.rs
index 14098340745b1..34a5f8444dea0 100644
--- a/src/tools/clippy/clippy_lints/src/derivable_impls.rs
+++ b/src/tools/clippy/clippy_lints/src/derivable_impls.rs
@@ -1,5 +1,5 @@
 use clippy_utils::diagnostics::span_lint_and_help;
-use clippy_utils::{is_automatically_derived, is_default_equivalent, peel_blocks};
+use clippy_utils::{is_default_equivalent, peel_blocks};
 use rustc_hir::{
     def::{DefKind, Res},
     Body, Expr, ExprKind, GenericArg, Impl, ImplItemKind, Item, ItemKind, Node, PathSegment, QPath, TyKind,
@@ -71,8 +71,7 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
                 self_ty,
                 ..
             }) = item.kind;
-            if let attrs = cx.tcx.hir().attrs(item.hir_id());
-            if !is_automatically_derived(attrs);
+            if !cx.tcx.has_attr(item.def_id.to_def_id(), sym::automatically_derived);
             if !item.span.from_expansion();
             if let Some(def_id) = trait_ref.trait_def_id();
             if cx.tcx.is_diagnostic_item(sym::Default, def_id);
@@ -81,6 +80,7 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
             if let ImplItemKind::Fn(_, b) = &impl_item.kind;
             if let Body { value: func_expr, .. } = cx.tcx.hir().body(*b);
             if let Some(adt_def) = cx.tcx.type_of(item.def_id).ty_adt_def();
+            if let attrs = cx.tcx.hir().attrs(item.hir_id());
             if !attrs.iter().any(|attr| attr.doc_str().is_some());
             if let child_attrs = cx.tcx.hir().attrs(impl_item_hir);
             if !child_attrs.iter().any(|attr| attr.doc_str().is_some());
diff --git a/src/tools/clippy/clippy_lints/src/derive.rs b/src/tools/clippy/clippy_lints/src/derive.rs
index 557e101494e3a..545bc7d210332 100644
--- a/src/tools/clippy/clippy_lints/src/derive.rs
+++ b/src/tools/clippy/clippy_lints/src/derive.rs
@@ -1,7 +1,7 @@
 use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_then};
 use clippy_utils::paths;
 use clippy_utils::ty::{implements_trait, is_copy};
-use clippy_utils::{is_automatically_derived, is_lint_allowed, match_def_path};
+use clippy_utils::{is_lint_allowed, match_def_path};
 use if_chain::if_chain;
 use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor};
 use rustc_hir::{
@@ -171,8 +171,8 @@ impl<'tcx> LateLintPass<'tcx> for Derive {
         }) = item.kind
         {
             let ty = cx.tcx.type_of(item.def_id);
-            let attrs = cx.tcx.hir().attrs(item.hir_id());
-            let is_automatically_derived = is_automatically_derived(attrs);
+            let is_automatically_derived =
+                cx.tcx.has_attr(item.def_id.to_def_id(), sym::automatically_derived);
 
             check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived);
             check_ord_partial_ord(cx, item.span, trait_ref, ty, is_automatically_derived);
@@ -201,7 +201,7 @@ fn check_hash_peq<'tcx>(
         then {
             // Look for the PartialEq implementations for `ty`
             cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| {
-                let peq_is_automatically_derived = is_automatically_derived(cx.tcx.get_attrs(impl_id));
+                let peq_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
 
                 if peq_is_automatically_derived == hash_is_automatically_derived {
                     return;
@@ -255,7 +255,7 @@ fn check_ord_partial_ord<'tcx>(
         then {
             // Look for the PartialOrd implementations for `ty`
             cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| {
-                let partial_ord_is_automatically_derived = is_automatically_derived(cx.tcx.get_attrs(impl_id));
+                let partial_ord_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
 
                 if partial_ord_is_automatically_derived == ord_is_automatically_derived {
                     return;
diff --git a/src/tools/clippy/clippy_lints/src/functions/must_use.rs b/src/tools/clippy/clippy_lints/src/functions/must_use.rs
index 38e943d2eb872..6672a6cb0b58f 100644
--- a/src/tools/clippy/clippy_lints/src/functions/must_use.rs
+++ b/src/tools/clippy/clippy_lints/src/functions/must_use.rs
@@ -13,13 +13,13 @@ use clippy_utils::attrs::is_proc_macro;
 use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
 use clippy_utils::source::snippet_opt;
 use clippy_utils::ty::is_must_use_ty;
-use clippy_utils::{match_def_path, must_use_attr, return_ty, trait_ref_of_method};
+use clippy_utils::{match_def_path, return_ty, trait_ref_of_method};
 
 use super::{DOUBLE_MUST_USE, MUST_USE_CANDIDATE, MUST_USE_UNIT};
 
 pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
     let attrs = cx.tcx.hir().attrs(item.hir_id());
-    let attr = must_use_attr(attrs);
+    let attr = cx.tcx.get_attr(item.def_id.to_def_id(), sym::must_use);
     if let hir::ItemKind::Fn(ref sig, _generics, ref body_id) = item.kind {
         let is_public = cx.access_levels.is_exported(item.def_id);
         let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
@@ -44,7 +44,7 @@ pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Imp
         let is_public = cx.access_levels.is_exported(item.def_id);
         let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
         let attrs = cx.tcx.hir().attrs(item.hir_id());
-        let attr = must_use_attr(attrs);
+        let attr = cx.tcx.get_attr(item.def_id.to_def_id(), sym::must_use);
         if let Some(attr) = attr {
             check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr);
         } else if is_public && !is_proc_macro(cx.sess(), attrs) && trait_ref_of_method(cx, item.def_id).is_none() {
@@ -67,7 +67,7 @@ pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Tr
         let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
 
         let attrs = cx.tcx.hir().attrs(item.hir_id());
-        let attr = must_use_attr(attrs);
+        let attr = cx.tcx.get_attr(item.def_id.to_def_id(), sym::must_use);
         if let Some(attr) = attr {
             check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr);
         } else if let hir::TraitFn::Provided(eid) = *eid {
diff --git a/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs b/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs
index b8d620d817130..09164690700ef 100644
--- a/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs
+++ b/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs
@@ -1,4 +1,3 @@
-use clippy_utils::attrs::is_doc_hidden;
 use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::source::snippet_opt;
 use clippy_utils::{is_lint_allowed, meets_msrv, msrvs};
@@ -161,7 +160,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustiveEnum {
                 let id = cx.tcx.hir().local_def_id(v.id);
                 (matches!(v.data, hir::VariantData::Unit(_))
                     && v.ident.as_str().starts_with('_')
-                    && is_doc_hidden(cx.tcx.get_attrs(id.to_def_id())))
+                    && cx.tcx.is_doc_hidden(id.to_def_id()))
                 .then(|| (id, v.span))
             });
             if let Some((id, span)) = iter.next()
diff --git a/src/tools/clippy/clippy_lints/src/matches/match_wild_enum.rs b/src/tools/clippy/clippy_lints/src/matches/match_wild_enum.rs
index 93bf0dc62e076..fc45ccee18528 100644
--- a/src/tools/clippy/clippy_lints/src/matches/match_wild_enum.rs
+++ b/src/tools/clippy/clippy_lints/src/matches/match_wild_enum.rs
@@ -193,6 +193,5 @@ impl<'a> CommonPrefixSearcher<'a> {
 }
 
 fn is_hidden(cx: &LateContext<'_>, variant_def: &VariantDef) -> bool {
-    let attrs = cx.tcx.get_attrs(variant_def.def_id);
-    clippy_utils::attrs::is_doc_hidden(attrs) || clippy_utils::attrs::is_unstable(attrs)
+    cx.tcx.is_doc_hidden(variant_def.def_id) || cx.tcx.has_attr(variant_def.def_id, sym::unstable)
 }
diff --git a/src/tools/clippy/clippy_lints/src/new_without_default.rs b/src/tools/clippy/clippy_lints/src/new_without_default.rs
index 2f733f221d572..2bdccb425071b 100644
--- a/src/tools/clippy/clippy_lints/src/new_without_default.rs
+++ b/src/tools/clippy/clippy_lints/src/new_without_default.rs
@@ -85,7 +85,7 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
                             // can't be implemented for unsafe new
                             return;
                         }
-                        if clippy_utils::is_doc_hidden(cx.tcx.hir().attrs(id)) {
+                        if cx.tcx.is_doc_hidden(impl_item.def_id) {
                             // shouldn't be implemented when it is hidden in docs
                             return;
                         }
diff --git a/src/tools/clippy/clippy_lints/src/partialeq_ne_impl.rs b/src/tools/clippy/clippy_lints/src/partialeq_ne_impl.rs
index 1469cb434c00c..09ac514d014eb 100644
--- a/src/tools/clippy/clippy_lints/src/partialeq_ne_impl.rs
+++ b/src/tools/clippy/clippy_lints/src/partialeq_ne_impl.rs
@@ -1,5 +1,4 @@
 use clippy_utils::diagnostics::span_lint_hir;
-use clippy_utils::is_automatically_derived;
 use if_chain::if_chain;
 use rustc_hir::{Impl, Item, ItemKind};
 use rustc_lint::{LateContext, LateLintPass};
@@ -37,8 +36,7 @@ impl<'tcx> LateLintPass<'tcx> for PartialEqNeImpl {
     fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
         if_chain! {
             if let ItemKind::Impl(Impl { of_trait: Some(ref trait_ref), items: impl_items, .. }) = item.kind;
-            let attrs = cx.tcx.hir().attrs(item.hir_id());
-            if !is_automatically_derived(attrs);
+            if !cx.tcx.has_attr(item.def_id.to_def_id(), sym::automatically_derived);
             if let Some(eq_trait) = cx.tcx.lang_items().eq_trait();
             if trait_ref.path.res.def_id() == eq_trait;
             then {
diff --git a/src/tools/clippy/clippy_lints/src/significant_drop_in_scrutinee.rs b/src/tools/clippy/clippy_lints/src/significant_drop_in_scrutinee.rs
index 94ae0c8f5a659..f300acf0fb2ba 100644
--- a/src/tools/clippy/clippy_lints/src/significant_drop_in_scrutinee.rs
+++ b/src/tools/clippy/clippy_lints/src/significant_drop_in_scrutinee.rs
@@ -290,7 +290,7 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
 
     fn has_sig_drop_attr(&mut self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
         if let Some(adt) = ty.ty_adt_def() {
-            if get_attr(cx.sess(), cx.tcx.get_attrs(adt.did()), "has_significant_drop").count() > 0 {
+            if get_attr(cx.sess(), cx.tcx.get_attrs_unchecked(adt.did()), "has_significant_drop").count() > 0 {
                 return true;
             }
         }
diff --git a/src/tools/clippy/clippy_utils/src/attrs.rs b/src/tools/clippy/clippy_utils/src/attrs.rs
index 7f448175e3267..904b1a05ccc30 100644
--- a/src/tools/clippy/clippy_utils/src/attrs.rs
+++ b/src/tools/clippy/clippy_utils/src/attrs.rs
@@ -1,6 +1,7 @@
-use rustc_ast::{ast, attr};
+use rustc_ast::ast;
 use rustc_errors::Applicability;
 use rustc_session::Session;
+use rustc_ast::attr;
 use rustc_span::sym;
 use std::str::FromStr;
 
@@ -158,7 +159,3 @@ pub fn is_doc_hidden(attrs: &[ast::Attribute]) -> bool {
         .any(|l| attr::list_contains_name(&l, sym::hidden))
 }
 
-/// Return true if the attributes contain `#[unstable]`
-pub fn is_unstable(attrs: &[ast::Attribute]) -> bool {
-    attrs.iter().any(|attr| attr.has_name(sym::unstable))
-}
diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs
index 7d46952d9718b..98a073d122e31 100644
--- a/src/tools/clippy/clippy_utils/src/lib.rs
+++ b/src/tools/clippy/clippy_utils/src/lib.rs
@@ -66,7 +66,7 @@ use std::lazy::SyncOnceCell;
 use std::sync::{Mutex, MutexGuard};
 
 use if_chain::if_chain;
-use rustc_ast::ast::{self, Attribute, LitKind};
+use rustc_ast::ast::{self, LitKind};
 use rustc_data_structures::fx::FxHashMap;
 use rustc_data_structures::unhash::UnhashMap;
 use rustc_hir as hir;
@@ -1472,12 +1472,6 @@ pub fn recurse_or_patterns<'tcx, F: FnMut(&'tcx Pat<'tcx>)>(pat: &'tcx Pat<'tcx>
     }
 }
 
-/// Checks for the `#[automatically_derived]` attribute all `#[derive]`d
-/// implementations have.
-pub fn is_automatically_derived(attrs: &[ast::Attribute]) -> bool {
-    has_attr(attrs, sym::automatically_derived)
-}
-
 pub fn is_self(slf: &Param<'_>) -> bool {
     if let PatKind::Binding(.., name, _) = slf.pat.kind {
         name.name == kw::SelfLower
@@ -1724,11 +1718,6 @@ pub fn get_async_fn_body<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'_>) -> Option<&'t
     None
 }
 
-// Finds the `#[must_use]` attribute, if any
-pub fn must_use_attr(attrs: &[Attribute]) -> Option<&Attribute> {
-    attrs.iter().find(|a| a.has_name(sym::must_use))
-}
-
 // check if expr is calling method or function with #[must_use] attribute
 pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
     let did = match expr.kind {
@@ -1745,7 +1734,7 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
         _ => None,
     };
 
-    did.map_or(false, |did| must_use_attr(cx.tcx.get_attrs(did)).is_some())
+    did.map_or(false, |did| cx.tcx.has_attr(did, sym::must_use))
 }
 
 /// Checks if an expression represents the identity function
diff --git a/src/tools/clippy/clippy_utils/src/ty.rs b/src/tools/clippy/clippy_utils/src/ty.rs
index 901e3e5390c5d..7f14a306d0e52 100644
--- a/src/tools/clippy/clippy_utils/src/ty.rs
+++ b/src/tools/clippy/clippy_utils/src/ty.rs
@@ -22,7 +22,7 @@ use rustc_trait_selection::infer::InferCtxtExt;
 use rustc_trait_selection::traits::query::normalize::AtExt;
 use std::iter;
 
-use crate::{match_def_path, must_use_attr, path_res, paths};
+use crate::{match_def_path, path_res, paths};
 
 // Checks if the given type implements copy.
 pub fn is_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
@@ -178,18 +178,18 @@ pub fn has_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
 // Returns whether the type has #[must_use] attribute
 pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
     match ty.kind() {
-        ty::Adt(adt, _) => must_use_attr(cx.tcx.get_attrs(adt.did())).is_some(),
-        ty::Foreign(ref did) => must_use_attr(cx.tcx.get_attrs(*did)).is_some(),
+        ty::Adt(adt, _) => cx.tcx.has_attr(adt.did(), sym::must_use),
+        ty::Foreign(did) => cx.tcx.has_attr(*did, sym::must_use),
         ty::Slice(ty) | ty::Array(ty, _) | ty::RawPtr(ty::TypeAndMut { ty, .. }) | ty::Ref(_, ty, _) => {
             // for the Array case we don't need to care for the len == 0 case
             // because we don't want to lint functions returning empty arrays
             is_must_use_ty(cx, *ty)
         },
         ty::Tuple(substs) => substs.iter().any(|ty| is_must_use_ty(cx, ty)),
-        ty::Opaque(ref def_id, _) => {
+        ty::Opaque(def_id, _) => {
             for (predicate, _) in cx.tcx.explicit_item_bounds(*def_id) {
                 if let ty::PredicateKind::Trait(trait_predicate) = predicate.kind().skip_binder() {
-                    if must_use_attr(cx.tcx.get_attrs(trait_predicate.trait_ref.def_id)).is_some() {
+                    if cx.tcx.has_attr(trait_predicate.trait_ref.def_id, sym::must_use) {
                         return true;
                     }
                 }
@@ -199,7 +199,7 @@ pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
         ty::Dynamic(binder, _) => {
             for predicate in binder.iter() {
                 if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() {
-                    if must_use_attr(cx.tcx.get_attrs(trait_ref.def_id)).is_some() {
+                    if cx.tcx.has_attr(trait_ref.def_id, sym::must_use) {
                         return true;
                     }
                 }