about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_builtin_macros/src/format.rs6
-rw-r--r--compiler/rustc_builtin_macros/src/lib.rs2
-rw-r--r--compiler/rustc_codegen_llvm/src/declare.rs12
-rw-r--r--compiler/rustc_const_eval/src/lib.rs2
-rw-r--r--compiler/rustc_data_structures/src/sorted_map.rs17
-rw-r--r--compiler/rustc_errors/src/diagnostic.rs5
-rw-r--r--compiler/rustc_errors/src/lib.rs2
-rw-r--r--compiler/rustc_hir_analysis/src/check/expr.rs3
-rw-r--r--compiler/rustc_hir_analysis/src/check/fn_ctxt/suggestions.rs3
-rw-r--r--compiler/rustc_hir_analysis/src/check/op.rs3
-rw-r--r--compiler/rustc_hir_analysis/src/lib.rs2
-rw-r--r--compiler/rustc_lint/src/context.rs2
-rw-r--r--compiler/rustc_lint/src/early.rs1
-rw-r--r--compiler/rustc_lint/src/expect.rs4
-rw-r--r--compiler/rustc_lint/src/levels.rs825
-rw-r--r--compiler/rustc_lint_defs/src/lib.rs31
-rw-r--r--compiler/rustc_middle/src/dep_graph/dep_node.rs38
-rw-r--r--compiler/rustc_middle/src/hir/map/mod.rs52
-rw-r--r--compiler/rustc_middle/src/lint.rs249
-rw-r--r--compiler/rustc_middle/src/query/mod.rs11
-rw-r--r--compiler/rustc_middle/src/ty/context.rs42
-rw-r--r--compiler/rustc_middle/src/ty/query.rs3
-rw-r--r--compiler/rustc_parse/src/parser/diagnostics.rs3
-rw-r--r--compiler/rustc_parse/src/parser/expr.rs2
-rw-r--r--compiler/rustc_parse/src/parser/pat.rs3
-rw-r--r--compiler/rustc_query_impl/src/keys.rs18
-rw-r--r--compiler/rustc_query_system/src/dep_graph/mod.rs6
-rw-r--r--compiler/rustc_session/src/parse.rs14
-rw-r--r--compiler/rustc_target/src/spec/i386_apple_ios.rs3
-rw-r--r--compiler/rustc_target/src/spec/i686_apple_darwin.rs3
-rw-r--r--compiler/rustc_target/src/spec/i686_linux_android.rs3
-rw-r--r--compiler/rustc_target/src/spec/i686_unknown_freebsd.rs3
-rw-r--r--compiler/rustc_target/src/spec/i686_unknown_haiku.rs3
-rw-r--r--compiler/rustc_target/src/spec/i686_unknown_linux_gnu.rs3
-rw-r--r--compiler/rustc_target/src/spec/i686_unknown_linux_musl.rs3
-rw-r--r--compiler/rustc_target/src/spec/i686_unknown_netbsd.rs3
-rw-r--r--compiler/rustc_target/src/spec/i686_unknown_openbsd.rs3
-rw-r--r--compiler/rustc_target/src/spec/i686_wrs_vxworks.rs3
-rw-r--r--compiler/rustc_target/src/spec/linux_kernel_base.rs3
-rw-r--r--compiler/rustc_target/src/spec/mod.rs4
-rw-r--r--compiler/rustc_target/src/spec/x86_64_apple_darwin.rs3
-rw-r--r--compiler/rustc_target/src/spec/x86_64_apple_ios.rs3
-rw-r--r--compiler/rustc_target/src/spec/x86_64_apple_ios_macabi.rs3
-rw-r--r--compiler/rustc_target/src/spec/x86_64_apple_tvos.rs3
-rw-r--r--compiler/rustc_target/src/spec/x86_64_apple_watchos_sim.rs3
-rw-r--r--compiler/rustc_target/src/spec/x86_64_fuchsia.rs3
-rw-r--r--compiler/rustc_target/src/spec/x86_64_linux_android.rs3
-rw-r--r--compiler/rustc_target/src/spec/x86_64_pc_solaris.rs3
-rw-r--r--compiler/rustc_target/src/spec/x86_64_sun_solaris.rs3
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_dragonfly.rs3
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_freebsd.rs3
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_haiku.rs3
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_hermit.rs3
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs3
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_linux_gnux32.rs3
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs3
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_netbsd.rs3
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_none.rs3
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_openbsd.rs3
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_redox.rs3
-rw-r--r--compiler/rustc_target/src/spec/x86_64_wrs_vxworks.rs3
61 files changed, 876 insertions, 585 deletions
diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs
index b15e2d084ef..8b07c110663 100644
--- a/compiler/rustc_builtin_macros/src/format.rs
+++ b/compiler/rustc_builtin_macros/src/format.rs
@@ -159,7 +159,7 @@ pub fn make_format_args(
     append_newline: bool,
 ) -> Result<FormatArgs, ()> {
     let msg = "format argument must be a string literal";
-    let fmt_span = efmt.span;
+    let unexpanded_fmt_span = efmt.span;
     let (fmt_str, fmt_style, fmt_span) = match expr_to_spanned_string(ecx, efmt, msg) {
         Ok(mut fmt) if append_newline => {
             fmt.0 = Symbol::intern(&format!("{}\n", fmt.0));
@@ -174,7 +174,7 @@ pub fn make_format_args(
                 };
                 if !suggested {
                     err.span_suggestion(
-                        fmt_span.shrink_to_lo(),
+                        unexpanded_fmt_span.shrink_to_lo(),
                         "you might be missing a string literal to format with",
                         format!("\"{}\", ", sugg_fmt),
                         Applicability::MaybeIncorrect,
@@ -192,7 +192,7 @@ pub fn make_format_args(
     };
 
     let fmt_str = fmt_str.as_str(); // for the suggestions below
-    let fmt_snippet = ecx.source_map().span_to_snippet(fmt_span).ok();
+    let fmt_snippet = ecx.source_map().span_to_snippet(unexpanded_fmt_span).ok();
     let mut parser = parse::Parser::new(
         fmt_str,
         str_style,
diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs
index f058503064b..c7ea7de8f4e 100644
--- a/compiler/rustc_builtin_macros/src/lib.rs
+++ b/compiler/rustc_builtin_macros/src/lib.rs
@@ -7,7 +7,7 @@
 #![feature(box_patterns)]
 #![feature(decl_macro)]
 #![feature(if_let_guard)]
-#![feature(is_some_with)]
+#![feature(is_some_and)]
 #![feature(is_sorted)]
 #![feature(let_chains)]
 #![feature(proc_macro_internals)]
diff --git a/compiler/rustc_codegen_llvm/src/declare.rs b/compiler/rustc_codegen_llvm/src/declare.rs
index 0f663a26732..f79ef11720d 100644
--- a/compiler/rustc_codegen_llvm/src/declare.rs
+++ b/compiler/rustc_codegen_llvm/src/declare.rs
@@ -32,6 +32,7 @@ fn declare_raw_fn<'ll>(
     name: &str,
     callconv: llvm::CallConv,
     unnamed: llvm::UnnamedAddr,
+    visibility: llvm::Visibility,
     ty: &'ll Type,
 ) -> &'ll Value {
     debug!("declare_raw_fn(name={:?}, ty={:?})", name, ty);
@@ -41,6 +42,7 @@ fn declare_raw_fn<'ll>(
 
     llvm::SetFunctionCallConv(llfn, callconv);
     llvm::SetUnnamedAddress(llfn, unnamed);
+    llvm::set_visibility(llfn, visibility);
 
     let mut attrs = SmallVec::<[_; 4]>::new();
 
@@ -78,7 +80,14 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
         unnamed: llvm::UnnamedAddr,
         fn_type: &'ll Type,
     ) -> &'ll Value {
-        declare_raw_fn(self, name, llvm::CCallConv, unnamed, fn_type)
+        // Declare C ABI functions with the visibility used by C by default.
+        let visibility = if self.tcx.sess.target.default_hidden_visibility {
+            llvm::Visibility::Hidden
+        } else {
+            llvm::Visibility::Default
+        };
+
+        declare_raw_fn(self, name, llvm::CCallConv, unnamed, visibility, fn_type)
     }
 
     /// Declare a Rust function.
@@ -95,6 +104,7 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
             name,
             fn_abi.llvm_cconv(),
             llvm::UnnamedAddr::Global,
+            llvm::Visibility::Default,
             fn_abi.llvm_type(self),
         );
         fn_abi.apply_attrs_llfn(self, llfn);
diff --git a/compiler/rustc_const_eval/src/lib.rs b/compiler/rustc_const_eval/src/lib.rs
index 7c4c7db1035..ebdaf61e439 100644
--- a/compiler/rustc_const_eval/src/lib.rs
+++ b/compiler/rustc_const_eval/src/lib.rs
@@ -20,7 +20,7 @@ Rust MIR: a lowered representation of Rust.
 #![feature(trusted_step)]
 #![feature(try_blocks)]
 #![feature(yeet_expr)]
-#![feature(is_some_with)]
+#![feature(is_some_and)]
 #![recursion_limit = "256"]
 #![allow(rustc::potential_query_instability)]
 
diff --git a/compiler/rustc_data_structures/src/sorted_map.rs b/compiler/rustc_data_structures/src/sorted_map.rs
index 937cb671573..fe257e10205 100644
--- a/compiler/rustc_data_structures/src/sorted_map.rs
+++ b/compiler/rustc_data_structures/src/sorted_map.rs
@@ -96,6 +96,23 @@ impl<K: Ord, V> SortedMap<K, V> {
         }
     }
 
+    /// Gets a mutable reference to the value in the entry, or insert a new one.
+    #[inline]
+    pub fn get_mut_or_insert_default(&mut self, key: K) -> &mut V
+    where
+        K: Eq,
+        V: Default,
+    {
+        let index = match self.lookup_index_for(&key) {
+            Ok(index) => index,
+            Err(index) => {
+                self.data.insert(index, (key, V::default()));
+                index
+            }
+        };
+        unsafe { &mut self.data.get_unchecked_mut(index).1 }
+    }
+
     #[inline]
     pub fn clear(&mut self) {
         self.data.clear();
diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs
index 49dcc2ba021..31e410aaaf0 100644
--- a/compiler/rustc_errors/src/diagnostic.rs
+++ b/compiler/rustc_errors/src/diagnostic.rs
@@ -364,9 +364,10 @@ impl Diagnostic {
             // The lint index inside the attribute is manually transferred here.
             let lint_index = expectation_id.get_lint_index();
             expectation_id.set_lint_index(None);
-            let mut stable_id = *unstable_to_stable
+            let mut stable_id = unstable_to_stable
                 .get(&expectation_id)
-                .expect("each unstable `LintExpectationId` must have a matching stable id");
+                .expect("each unstable `LintExpectationId` must have a matching stable id")
+                .normalize();
 
             stable_id.set_lint_index(lint_index);
             *expectation_id = stable_id;
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index c8711ec6e25..7c312da6279 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -1211,7 +1211,7 @@ impl HandlerInner {
 
         if let Some(expectation_id) = diagnostic.level.get_expectation_id() {
             self.suppressed_expected_diag = true;
-            self.fulfilled_expectations.insert(expectation_id);
+            self.fulfilled_expectations.insert(expectation_id.normalize());
         }
 
         if matches!(diagnostic.level, Warning(_))
diff --git a/compiler/rustc_hir_analysis/src/check/expr.rs b/compiler/rustc_hir_analysis/src/check/expr.rs
index 48a4f40780b..09362eab673 100644
--- a/compiler/rustc_hir_analysis/src/check/expr.rs
+++ b/compiler/rustc_hir_analysis/src/check/expr.rs
@@ -41,6 +41,7 @@ use rustc_middle::ty::adjustment::{Adjust, Adjustment, AllowTwoPhase};
 use rustc_middle::ty::error::TypeError::FieldMisMatch;
 use rustc_middle::ty::subst::SubstsRef;
 use rustc_middle::ty::{self, AdtKind, Ty, TypeVisitable};
+use rustc_session::errors::ExprParenthesesNeeded;
 use rustc_session::parse::feature_err;
 use rustc_span::hygiene::DesugaringKind;
 use rustc_span::lev_distance::find_best_match_for_name;
@@ -394,7 +395,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                         if let Some(sp) =
                             tcx.sess.parse_sess.ambiguous_block_expr_parse.borrow().get(&sp)
                         {
-                            tcx.sess.parse_sess.expr_parentheses_needed(&mut err, *sp);
+                            err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp));
                         }
                         err.emit();
                         oprnd_t = tcx.ty_error();
diff --git a/compiler/rustc_hir_analysis/src/check/fn_ctxt/suggestions.rs b/compiler/rustc_hir_analysis/src/check/fn_ctxt/suggestions.rs
index c32d1309031..05ed3b29972 100644
--- a/compiler/rustc_hir_analysis/src/check/fn_ctxt/suggestions.rs
+++ b/compiler/rustc_hir_analysis/src/check/fn_ctxt/suggestions.rs
@@ -15,6 +15,7 @@ use rustc_infer::infer::{self, TyCtxtInferExt};
 use rustc_infer::traits::{self, StatementAsExpression};
 use rustc_middle::lint::in_external_macro;
 use rustc_middle::ty::{self, Binder, IsSuggestable, ToPredicate, Ty};
+use rustc_session::errors::ExprParenthesesNeeded;
 use rustc_span::symbol::sym;
 use rustc_span::Span;
 use rustc_trait_selection::infer::InferCtxtExt;
@@ -895,7 +896,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         let sp = self.tcx.sess.source_map().start_point(expr.span);
         if let Some(sp) = self.tcx.sess.parse_sess.ambiguous_block_expr_parse.borrow().get(&sp) {
             // `{ 42 } &&x` (#61475) or `{ 42 } && if x { 1 } else { 0 }`
-            self.tcx.sess.parse_sess.expr_parentheses_needed(err, *sp);
+            err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp));
         }
     }
 
diff --git a/compiler/rustc_hir_analysis/src/check/op.rs b/compiler/rustc_hir_analysis/src/check/op.rs
index 4754717c29a..2d7d9020e3e 100644
--- a/compiler/rustc_hir_analysis/src/check/op.rs
+++ b/compiler/rustc_hir_analysis/src/check/op.rs
@@ -13,6 +13,7 @@ use rustc_middle::ty::adjustment::{
 };
 use rustc_middle::ty::print::with_no_trimmed_paths;
 use rustc_middle::ty::{self, DefIdTree, Ty, TyCtxt, TypeFolder, TypeSuperFoldable, TypeVisitable};
+use rustc_session::errors::ExprParenthesesNeeded;
 use rustc_span::source_map::Spanned;
 use rustc_span::symbol::{sym, Ident};
 use rustc_span::Span;
@@ -677,7 +678,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                         // If the previous expression was a block expression, suggest parentheses
                         // (turning this into a binary subtraction operation instead.)
                         // for example, `{2} - 2` -> `({2}) - 2` (see src\test\ui\parser\expr-as-stmt.rs)
-                        self.tcx.sess.parse_sess.expr_parentheses_needed(&mut err, *sp);
+                        err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp));
                     } else {
                         match actual.kind() {
                             Uint(_) if op == hir::UnOp::Neg => {
diff --git a/compiler/rustc_hir_analysis/src/lib.rs b/compiler/rustc_hir_analysis/src/lib.rs
index 1859473166a..d31b9b7ae46 100644
--- a/compiler/rustc_hir_analysis/src/lib.rs
+++ b/compiler/rustc_hir_analysis/src/lib.rs
@@ -70,7 +70,7 @@ This API is completely unstable and subject to change.
 #![feature(once_cell)]
 #![feature(slice_partition_dedup)]
 #![feature(try_blocks)]
-#![feature(is_some_with)]
+#![feature(is_some_and)]
 #![feature(type_alias_impl_trait)]
 #![recursion_limit = "256"]
 
diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs
index 87007728e9d..74e35afc87d 100644
--- a/compiler/rustc_lint/src/context.rs
+++ b/compiler/rustc_lint/src/context.rs
@@ -558,7 +558,7 @@ pub struct LateContext<'tcx> {
 
 /// Context for lint checking of the AST, after expansion, before lowering to HIR.
 pub struct EarlyContext<'a> {
-    pub builder: LintLevelsBuilder<'a>,
+    pub builder: LintLevelsBuilder<'a, crate::levels::TopDown>,
     pub buffered: LintBuffer,
 }
 
diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs
index 18d30e1435b..f7759bec908 100644
--- a/compiler/rustc_lint/src/early.rs
+++ b/compiler/rustc_lint/src/early.rs
@@ -58,6 +58,7 @@ impl<'a, T: EarlyLintPass> EarlyContextAndPass<'a, T> {
         F: FnOnce(&mut Self),
     {
         let is_crate_node = id == ast::CRATE_NODE_ID;
+        debug!(?id);
         let push = self.context.builder.push(attrs, is_crate_node, None);
 
         self.check_id(id);
diff --git a/compiler/rustc_lint/src/expect.rs b/compiler/rustc_lint/src/expect.rs
index af13f453a50..4c3c39734dd 100644
--- a/compiler/rustc_lint/src/expect.rs
+++ b/compiler/rustc_lint/src/expect.rs
@@ -16,8 +16,10 @@ fn check_expectations(tcx: TyCtxt<'_>, tool_filter: Option<Symbol>) {
         return;
     }
 
+    let lint_expectations = tcx.lint_expectations(());
     let fulfilled_expectations = tcx.sess.diagnostic().steal_fulfilled_expectation_ids();
-    let lint_expectations = &tcx.lint_levels(()).lint_expectations;
+
+    tracing::debug!(?lint_expectations, ?fulfilled_expectations);
 
     for (id, expectation) in lint_expectations {
         // This check will always be true, since `lint_expectations` only
diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs
index 82382350823..be1d7d98aa6 100644
--- a/compiler/rustc_lint/src/levels.rs
+++ b/compiler/rustc_lint/src/levels.rs
@@ -5,11 +5,13 @@ use rustc_ast_pretty::pprust;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, DiagnosticMessage, MultiSpan};
 use rustc_hir as hir;
-use rustc_hir::{intravisit, HirId};
+use rustc_hir::intravisit::{self, Visitor};
+use rustc_hir::HirId;
+use rustc_index::vec::IndexVec;
 use rustc_middle::hir::nested_filter;
 use rustc_middle::lint::{
-    struct_lint_level, LevelAndSource, LintExpectation, LintLevelMap, LintLevelSets,
-    LintLevelSource, LintSet, LintStackIndex, COMMAND_LINE,
+    reveal_actual_level, struct_lint_level, LevelAndSource, LintExpectation, LintLevelSource,
+    ShallowLintLevelMap,
 };
 use rustc_middle::ty::query::Providers;
 use rustc_middle::ty::{RegisteredTools, TyCtxt};
@@ -27,47 +29,408 @@ use crate::errors::{
     UnknownToolInScopedLint,
 };
 
-fn lint_levels(tcx: TyCtxt<'_>, (): ()) -> LintLevelMap {
-    let store = unerased_lint_store(tcx);
-    let levels =
-        LintLevelsBuilder::new(tcx.sess, false, &store, &tcx.resolutions(()).registered_tools);
-    let mut builder = LintLevelMapBuilder { levels, tcx };
-    let krate = tcx.hir().krate();
+/// Collection of lint levels for the whole crate.
+/// This is used by AST-based lints, which do not
+/// wait until we have built HIR to be emitted.
+#[derive(Debug)]
+struct LintLevelSets {
+    /// Linked list of specifications.
+    list: IndexVec<LintStackIndex, LintSet>,
+}
+
+rustc_index::newtype_index! {
+    struct LintStackIndex {
+        ENCODABLE = custom, // we don't need encoding
+        const COMMAND_LINE = 0,
+    }
+}
 
-    builder.levels.id_to_set.reserve(krate.owners.len() + 1);
+/// Specifications found at this position in the stack.  This map only represents the lints
+/// found for one set of attributes (like `shallow_lint_levels_on` does).
+///
+/// We store the level specifications as a linked list.
+/// Each `LintSet` represents a set of attributes on the same AST node.
+/// The `parent` forms a linked list that matches the AST tree.
+/// This way, walking the linked list is equivalent to walking the AST bottom-up
+/// to find the specifications for a given lint.
+#[derive(Debug)]
+struct LintSet {
+    // -A,-W,-D flags, a `Symbol` for the flag itself and `Level` for which
+    // flag.
+    specs: FxHashMap<LintId, LevelAndSource>,
+    parent: LintStackIndex,
+}
 
-    let push =
-        builder.levels.push(tcx.hir().attrs(hir::CRATE_HIR_ID), true, Some(hir::CRATE_HIR_ID));
+impl LintLevelSets {
+    fn new() -> Self {
+        LintLevelSets { list: IndexVec::new() }
+    }
+
+    fn get_lint_level(
+        &self,
+        lint: &'static Lint,
+        idx: LintStackIndex,
+        aux: Option<&FxHashMap<LintId, LevelAndSource>>,
+        sess: &Session,
+    ) -> LevelAndSource {
+        let lint = LintId::of(lint);
+        let (level, mut src) = self.raw_lint_id_level(lint, idx, aux);
+        let level = reveal_actual_level(level, &mut src, sess, lint, |id| {
+            self.raw_lint_id_level(id, idx, aux)
+        });
+        (level, src)
+    }
+
+    fn raw_lint_id_level(
+        &self,
+        id: LintId,
+        mut idx: LintStackIndex,
+        aux: Option<&FxHashMap<LintId, LevelAndSource>>,
+    ) -> (Option<Level>, LintLevelSource) {
+        if let Some(specs) = aux {
+            if let Some(&(level, src)) = specs.get(&id) {
+                return (Some(level), src);
+            }
+        }
+        loop {
+            let LintSet { ref specs, parent } = self.list[idx];
+            if let Some(&(level, src)) = specs.get(&id) {
+                return (Some(level), src);
+            }
+            if idx == COMMAND_LINE {
+                return (None, LintLevelSource::Default);
+            }
+            idx = parent;
+        }
+    }
+}
 
-    builder.levels.register_id(hir::CRATE_HIR_ID);
+fn lint_expectations(tcx: TyCtxt<'_>, (): ()) -> Vec<(LintExpectationId, LintExpectation)> {
+    let store = unerased_lint_store(tcx);
+
+    let mut builder = LintLevelsBuilder {
+        sess: tcx.sess,
+        provider: QueryMapExpectationsWrapper {
+            tcx,
+            cur: hir::CRATE_HIR_ID,
+            specs: ShallowLintLevelMap::default(),
+            expectations: Vec::new(),
+            unstable_to_stable_ids: FxHashMap::default(),
+            empty: FxHashMap::default(),
+        },
+        warn_about_weird_lints: false,
+        store,
+        registered_tools: &tcx.resolutions(()).registered_tools,
+    };
+
+    builder.add_command_line();
+    builder.add_id(hir::CRATE_HIR_ID);
     tcx.hir().walk_toplevel_module(&mut builder);
-    builder.levels.pop(push);
 
-    builder.levels.update_unstable_expectation_ids();
-    builder.levels.build_map()
+    tcx.sess.diagnostic().update_unstable_expectation_id(&builder.provider.unstable_to_stable_ids);
+
+    builder.provider.expectations
 }
 
-pub struct LintLevelsBuilder<'s> {
-    sess: &'s Session,
-    lint_expectations: Vec<(LintExpectationId, LintExpectation)>,
-    /// Each expectation has a stable and an unstable identifier. This map
-    /// is used to map from unstable to stable [`LintExpectationId`]s.
-    expectation_id_map: FxHashMap<LintExpectationId, LintExpectationId>,
+#[instrument(level = "trace", skip(tcx), ret)]
+fn shallow_lint_levels_on(tcx: TyCtxt<'_>, owner: hir::OwnerId) -> ShallowLintLevelMap {
+    let store = unerased_lint_store(tcx);
+    let attrs = tcx.hir_attrs(owner);
+
+    let mut levels = LintLevelsBuilder {
+        sess: tcx.sess,
+        provider: LintLevelQueryMap {
+            tcx,
+            cur: owner.into(),
+            specs: ShallowLintLevelMap::default(),
+            empty: FxHashMap::default(),
+            attrs,
+        },
+        warn_about_weird_lints: false,
+        store,
+        registered_tools: &tcx.resolutions(()).registered_tools,
+    };
+
+    if owner == hir::CRATE_OWNER_ID {
+        levels.add_command_line();
+    }
+
+    match attrs.map.range(..) {
+        // There is only something to do if there are attributes at all.
+        [] => {}
+        // Most of the time, there is only one attribute.  Avoid fetching HIR in that case.
+        [(local_id, _)] => levels.add_id(HirId { owner, local_id: *local_id }),
+        // Otherwise, we need to visit the attributes in source code order, so we fetch HIR and do
+        // a standard visit.
+        // FIXME(#102522) Just iterate on attrs once that iteration order matches HIR's.
+        _ => match tcx.hir().expect_owner(owner) {
+            hir::OwnerNode::Item(item) => levels.visit_item(item),
+            hir::OwnerNode::ForeignItem(item) => levels.visit_foreign_item(item),
+            hir::OwnerNode::TraitItem(item) => levels.visit_trait_item(item),
+            hir::OwnerNode::ImplItem(item) => levels.visit_impl_item(item),
+            hir::OwnerNode::Crate(mod_) => {
+                levels.add_id(hir::CRATE_HIR_ID);
+                levels.visit_mod(mod_, mod_.spans.inner_span, hir::CRATE_HIR_ID)
+            }
+        },
+    }
+
+    let specs = levels.provider.specs;
+
+    #[cfg(debug_assertions)]
+    for (_, v) in specs.specs.iter() {
+        debug_assert!(!v.is_empty());
+    }
+
+    specs
+}
+
+pub struct TopDown {
     sets: LintLevelSets,
-    id_to_set: FxHashMap<HirId, LintStackIndex>,
     cur: LintStackIndex,
+}
+
+pub trait LintLevelsProvider {
+    fn current_specs(&self) -> &FxHashMap<LintId, LevelAndSource>;
+    fn insert(&mut self, id: LintId, lvl: LevelAndSource);
+    fn get_lint_level(&self, lint: &'static Lint, sess: &Session) -> LevelAndSource;
+    fn push_expectation(&mut self, _id: LintExpectationId, _expectation: LintExpectation) {}
+}
+
+impl LintLevelsProvider for TopDown {
+    fn current_specs(&self) -> &FxHashMap<LintId, LevelAndSource> {
+        &self.sets.list[self.cur].specs
+    }
+
+    fn insert(&mut self, id: LintId, lvl: LevelAndSource) {
+        self.sets.list[self.cur].specs.insert(id, lvl);
+    }
+
+    fn get_lint_level(&self, lint: &'static Lint, sess: &Session) -> LevelAndSource {
+        self.sets.get_lint_level(lint, self.cur, Some(self.current_specs()), sess)
+    }
+}
+
+struct LintLevelQueryMap<'tcx> {
+    tcx: TyCtxt<'tcx>,
+    cur: HirId,
+    specs: ShallowLintLevelMap,
+    /// Empty hash map to simplify code.
+    empty: FxHashMap<LintId, LevelAndSource>,
+    attrs: &'tcx hir::AttributeMap<'tcx>,
+}
+
+impl LintLevelsProvider for LintLevelQueryMap<'_> {
+    fn current_specs(&self) -> &FxHashMap<LintId, LevelAndSource> {
+        self.specs.specs.get(&self.cur.local_id).unwrap_or(&self.empty)
+    }
+    fn insert(&mut self, id: LintId, lvl: LevelAndSource) {
+        self.specs.specs.get_mut_or_insert_default(self.cur.local_id).insert(id, lvl);
+    }
+    fn get_lint_level(&self, lint: &'static Lint, _: &Session) -> LevelAndSource {
+        self.specs.lint_level_id_at_node(self.tcx, LintId::of(lint), self.cur)
+    }
+}
+
+struct QueryMapExpectationsWrapper<'tcx> {
+    tcx: TyCtxt<'tcx>,
+    cur: HirId,
+    specs: ShallowLintLevelMap,
+    expectations: Vec<(LintExpectationId, LintExpectation)>,
+    unstable_to_stable_ids: FxHashMap<LintExpectationId, LintExpectationId>,
+    /// Empty hash map to simplify code.
+    empty: FxHashMap<LintId, LevelAndSource>,
+}
+
+impl LintLevelsProvider for QueryMapExpectationsWrapper<'_> {
+    fn current_specs(&self) -> &FxHashMap<LintId, LevelAndSource> {
+        self.specs.specs.get(&self.cur.local_id).unwrap_or(&self.empty)
+    }
+    fn insert(&mut self, id: LintId, lvl: LevelAndSource) {
+        let specs = self.specs.specs.get_mut_or_insert_default(self.cur.local_id);
+        specs.clear();
+        specs.insert(id, lvl);
+    }
+    fn get_lint_level(&self, lint: &'static Lint, _: &Session) -> LevelAndSource {
+        self.specs.lint_level_id_at_node(self.tcx, LintId::of(lint), self.cur)
+    }
+    fn push_expectation(&mut self, id: LintExpectationId, expectation: LintExpectation) {
+        let LintExpectationId::Stable { attr_id: Some(attr_id), hir_id, attr_index, .. } = id else { bug!("unstable expectation id should already be mapped") };
+        let key = LintExpectationId::Unstable { attr_id, lint_index: None };
+
+        if !self.unstable_to_stable_ids.contains_key(&key) {
+            self.unstable_to_stable_ids.insert(
+                key,
+                LintExpectationId::Stable { hir_id, attr_index, lint_index: None, attr_id: None },
+            );
+        }
+
+        self.expectations.push((id.normalize(), expectation));
+    }
+}
+
+impl<'tcx> LintLevelsBuilder<'_, LintLevelQueryMap<'tcx>> {
+    fn add_id(&mut self, hir_id: HirId) {
+        self.provider.cur = hir_id;
+        self.add(
+            self.provider.attrs.get(hir_id.local_id),
+            hir_id == hir::CRATE_HIR_ID,
+            Some(hir_id),
+        );
+    }
+}
+
+impl<'tcx> Visitor<'tcx> for LintLevelsBuilder<'_, LintLevelQueryMap<'tcx>> {
+    type NestedFilter = nested_filter::OnlyBodies;
+
+    fn nested_visit_map(&mut self) -> Self::Map {
+        self.provider.tcx.hir()
+    }
+
+    fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) {
+        self.add_id(param.hir_id);
+        intravisit::walk_param(self, param);
+    }
+
+    fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) {
+        self.add_id(it.hir_id());
+        intravisit::walk_item(self, it);
+    }
+
+    fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) {
+        self.add_id(it.hir_id());
+        intravisit::walk_foreign_item(self, it);
+    }
+
+    fn visit_stmt(&mut self, e: &'tcx hir::Stmt<'tcx>) {
+        // We will call `add_id` when we walk
+        // the `StmtKind`. The outer statement itself doesn't
+        // define the lint levels.
+        intravisit::walk_stmt(self, e);
+    }
+
+    fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) {
+        self.add_id(e.hir_id);
+        intravisit::walk_expr(self, e);
+    }
+
+    fn visit_field_def(&mut self, s: &'tcx hir::FieldDef<'tcx>) {
+        self.add_id(s.hir_id);
+        intravisit::walk_field_def(self, s);
+    }
+
+    fn visit_variant(&mut self, v: &'tcx hir::Variant<'tcx>) {
+        self.add_id(v.id);
+        intravisit::walk_variant(self, v);
+    }
+
+    fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) {
+        self.add_id(l.hir_id);
+        intravisit::walk_local(self, l);
+    }
+
+    fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) {
+        self.add_id(a.hir_id);
+        intravisit::walk_arm(self, a);
+    }
+
+    fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) {
+        self.add_id(trait_item.hir_id());
+        intravisit::walk_trait_item(self, trait_item);
+    }
+
+    fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {
+        self.add_id(impl_item.hir_id());
+        intravisit::walk_impl_item(self, impl_item);
+    }
+}
+
+impl<'tcx> LintLevelsBuilder<'_, QueryMapExpectationsWrapper<'tcx>> {
+    fn add_id(&mut self, hir_id: HirId) {
+        self.provider.cur = hir_id;
+        self.add(self.provider.tcx.hir().attrs(hir_id), hir_id == hir::CRATE_HIR_ID, Some(hir_id));
+    }
+}
+
+impl<'tcx> Visitor<'tcx> for LintLevelsBuilder<'_, QueryMapExpectationsWrapper<'tcx>> {
+    type NestedFilter = nested_filter::All;
+
+    fn nested_visit_map(&mut self) -> Self::Map {
+        self.provider.tcx.hir()
+    }
+
+    fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) {
+        self.add_id(param.hir_id);
+        intravisit::walk_param(self, param);
+    }
+
+    fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) {
+        self.add_id(it.hir_id());
+        intravisit::walk_item(self, it);
+    }
+
+    fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) {
+        self.add_id(it.hir_id());
+        intravisit::walk_foreign_item(self, it);
+    }
+
+    fn visit_stmt(&mut self, e: &'tcx hir::Stmt<'tcx>) {
+        // We will call `add_id` when we walk
+        // the `StmtKind`. The outer statement itself doesn't
+        // define the lint levels.
+        intravisit::walk_stmt(self, e);
+    }
+
+    fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) {
+        self.add_id(e.hir_id);
+        intravisit::walk_expr(self, e);
+    }
+
+    fn visit_field_def(&mut self, s: &'tcx hir::FieldDef<'tcx>) {
+        self.add_id(s.hir_id);
+        intravisit::walk_field_def(self, s);
+    }
+
+    fn visit_variant(&mut self, v: &'tcx hir::Variant<'tcx>) {
+        self.add_id(v.id);
+        intravisit::walk_variant(self, v);
+    }
+
+    fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) {
+        self.add_id(l.hir_id);
+        intravisit::walk_local(self, l);
+    }
+
+    fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) {
+        self.add_id(a.hir_id);
+        intravisit::walk_arm(self, a);
+    }
+
+    fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) {
+        self.add_id(trait_item.hir_id());
+        intravisit::walk_trait_item(self, trait_item);
+    }
+
+    fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {
+        self.add_id(impl_item.hir_id());
+        intravisit::walk_impl_item(self, impl_item);
+    }
+}
+
+pub struct LintLevelsBuilder<'s, P> {
+    sess: &'s Session,
+    provider: P,
     warn_about_weird_lints: bool,
     store: &'s LintStore,
     registered_tools: &'s RegisteredTools,
 }
 
-pub struct BuilderPush {
+pub(crate) struct BuilderPush {
     prev: LintStackIndex,
-    pub changed: bool,
 }
 
-impl<'s> LintLevelsBuilder<'s> {
-    pub fn new(
+impl<'s> LintLevelsBuilder<'s, TopDown> {
+    pub(crate) fn new(
         sess: &'s Session,
         warn_about_weird_lints: bool,
         store: &'s LintStore,
@@ -75,20 +438,74 @@ impl<'s> LintLevelsBuilder<'s> {
     ) -> Self {
         let mut builder = LintLevelsBuilder {
             sess,
-            lint_expectations: Default::default(),
-            expectation_id_map: Default::default(),
-            sets: LintLevelSets::new(),
-            cur: COMMAND_LINE,
-            id_to_set: Default::default(),
+            provider: TopDown { sets: LintLevelSets::new(), cur: COMMAND_LINE },
             warn_about_weird_lints,
             store,
             registered_tools,
         };
-        builder.process_command_line(sess, store);
-        assert_eq!(builder.sets.list.len(), 1);
+        builder.process_command_line();
+        assert_eq!(builder.provider.sets.list.len(), 1);
         builder
     }
 
+    fn process_command_line(&mut self) {
+        self.provider.cur = self
+            .provider
+            .sets
+            .list
+            .push(LintSet { specs: FxHashMap::default(), parent: COMMAND_LINE });
+        self.add_command_line();
+    }
+
+    /// Pushes a list of AST lint attributes onto this context.
+    ///
+    /// This function will return a `BuilderPush` object which should be passed
+    /// to `pop` when this scope for the attributes provided is exited.
+    ///
+    /// This function will perform a number of tasks:
+    ///
+    /// * It'll validate all lint-related attributes in `attrs`
+    /// * It'll mark all lint-related attributes as used
+    /// * Lint levels will be updated based on the attributes provided
+    /// * Lint attributes are validated, e.g., a `#[forbid]` can't be switched to
+    ///   `#[allow]`
+    ///
+    /// Don't forget to call `pop`!
+    pub(crate) fn push(
+        &mut self,
+        attrs: &[ast::Attribute],
+        is_crate_node: bool,
+        source_hir_id: Option<HirId>,
+    ) -> BuilderPush {
+        let prev = self.provider.cur;
+        self.provider.cur =
+            self.provider.sets.list.push(LintSet { specs: FxHashMap::default(), parent: prev });
+
+        self.add(attrs, is_crate_node, source_hir_id);
+
+        if self.provider.current_specs().is_empty() {
+            self.provider.sets.list.pop();
+            self.provider.cur = prev;
+        }
+
+        BuilderPush { prev }
+    }
+
+    /// Called after `push` when the scope of a set of attributes are exited.
+    pub(crate) fn pop(&mut self, push: BuilderPush) {
+        self.provider.cur = push.prev;
+        std::mem::forget(push);
+    }
+}
+
+#[cfg(debug_assertions)]
+impl Drop for BuilderPush {
+    fn drop(&mut self) {
+        panic!("Found a `push` without a `pop`.");
+    }
+}
+
+impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
     pub(crate) fn sess(&self) -> &Session {
         self.sess
     }
@@ -98,24 +515,20 @@ impl<'s> LintLevelsBuilder<'s> {
     }
 
     fn current_specs(&self) -> &FxHashMap<LintId, LevelAndSource> {
-        &self.sets.list[self.cur].specs
+        self.provider.current_specs()
     }
 
-    fn current_specs_mut(&mut self) -> &mut FxHashMap<LintId, LevelAndSource> {
-        &mut self.sets.list[self.cur].specs
+    fn insert(&mut self, id: LintId, lvl: LevelAndSource) {
+        self.provider.insert(id, lvl)
     }
 
-    fn process_command_line(&mut self, sess: &Session, store: &LintStore) {
-        self.sets.lint_cap = sess.opts.lint_cap.unwrap_or(Level::Forbid);
-
-        self.cur =
-            self.sets.list.push(LintSet { specs: FxHashMap::default(), parent: COMMAND_LINE });
-        for &(ref lint_name, level) in &sess.opts.lint_opts {
-            store.check_lint_name_cmdline(sess, &lint_name, level, self.registered_tools);
+    fn add_command_line(&mut self) {
+        for &(ref lint_name, level) in &self.sess.opts.lint_opts {
+            self.store.check_lint_name_cmdline(self.sess, &lint_name, level, self.registered_tools);
             let orig_level = level;
             let lint_flag_val = Symbol::intern(lint_name);
 
-            let Ok(ids) = store.find_lints(&lint_name) else {
+            let Ok(ids) = self.store.find_lints(&lint_name) else {
                 // errors handled in check_lint_name_cmdline above
                 continue
             };
@@ -129,7 +542,7 @@ impl<'s> LintLevelsBuilder<'s> {
 
                 if self.check_gated_lint(id, DUMMY_SP) {
                     let src = LintLevelSource::CommandLine(lint_flag_val, orig_level);
-                    self.current_specs_mut().insert(id, (level, src));
+                    self.insert(id, (level, src));
                 }
             }
         }
@@ -138,9 +551,11 @@ impl<'s> LintLevelsBuilder<'s> {
     /// Attempts to insert the `id` to `level_src` map entry. If unsuccessful
     /// (e.g. if a forbid was already inserted on the same scope), then emits a
     /// diagnostic with no change to `specs`.
-    fn insert_spec(&mut self, id: LintId, (level, src): LevelAndSource) {
-        let (old_level, old_src) =
-            self.sets.get_lint_level(id.lint, self.cur, Some(self.current_specs()), &self.sess);
+    fn insert_spec(&mut self, id: LintId, (mut level, src): LevelAndSource) {
+        let (old_level, old_src) = self.provider.get_lint_level(id.lint, &self.sess);
+        if let Level::Expect(id) = &mut level && let LintExpectationId::Stable { .. } = id {
+            *id = id.normalize();
+        }
         // Setting to a non-forbid level is an error if the lint previously had
         // a forbid level. Note that this is not necessarily true even with a
         // `#[forbid(..)]` attribute present, as that is overridden by `--cap-lints`.
@@ -158,7 +573,7 @@ impl<'s> LintLevelsBuilder<'s> {
                 let id_name = id.lint.name_lower();
                 let fcw_warning = match old_src {
                     LintLevelSource::Default => false,
-                    LintLevelSource::Node(symbol, _, _) => self.store.is_lint_group(symbol),
+                    LintLevelSource::Node { name, .. } => self.store.is_lint_group(name),
                     LintLevelSource::CommandLine(symbol, _) => self.store.is_lint_group(symbol),
                 };
                 debug!(
@@ -178,8 +593,8 @@ impl<'s> LintLevelsBuilder<'s> {
                                 id.to_string()
                             ));
                         }
-                        LintLevelSource::Node(_, forbid_source_span, reason) => {
-                            diag.span_label(forbid_source_span, "`forbid` level set here");
+                        LintLevelSource::Node { span, reason, .. } => {
+                            diag.span_label(span, "`forbid` level set here");
                             if let Some(rationale) = reason {
                                 diag.note(rationale.as_str());
                             }
@@ -199,11 +614,8 @@ impl<'s> LintLevelsBuilder<'s> {
                             LintLevelSource::Default => {
                                 OverruledAttributeSub::DefaultSource { id: id.to_string() }
                             }
-                            LintLevelSource::Node(_, forbid_source_span, reason) => {
-                                OverruledAttributeSub::NodeSource {
-                                    span: forbid_source_span,
-                                    reason,
-                                }
+                            LintLevelSource::Node { span, reason, .. } => {
+                                OverruledAttributeSub::NodeSource { span, reason }
                             }
                             LintLevelSource::CommandLine(_, _) => {
                                 OverruledAttributeSub::CommandLineSource
@@ -244,45 +656,21 @@ impl<'s> LintLevelsBuilder<'s> {
 
         match (old_level, level) {
             // If the new level is an expectation store it in `ForceWarn`
-            (Level::ForceWarn(_), Level::Expect(expectation_id)) => self
-                .current_specs_mut()
-                .insert(id, (Level::ForceWarn(Some(expectation_id)), old_src)),
-            // Keep `ForceWarn` level but drop the expectation
-            (Level::ForceWarn(_), _) => {
-                self.current_specs_mut().insert(id, (Level::ForceWarn(None), old_src))
+            (Level::ForceWarn(_), Level::Expect(expectation_id)) => {
+                self.insert(id, (Level::ForceWarn(Some(expectation_id)), old_src))
             }
+            // Keep `ForceWarn` level but drop the expectation
+            (Level::ForceWarn(_), _) => self.insert(id, (Level::ForceWarn(None), old_src)),
             // Set the lint level as normal
-            _ => self.current_specs_mut().insert(id, (level, src)),
+            _ => self.insert(id, (level, src)),
         };
     }
 
-    /// Pushes a list of AST lint attributes onto this context.
-    ///
-    /// This function will return a `BuilderPush` object which should be passed
-    /// to `pop` when this scope for the attributes provided is exited.
-    ///
-    /// This function will perform a number of tasks:
-    ///
-    /// * It'll validate all lint-related attributes in `attrs`
-    /// * It'll mark all lint-related attributes as used
-    /// * Lint levels will be updated based on the attributes provided
-    /// * Lint attributes are validated, e.g., a `#[forbid]` can't be switched to
-    ///   `#[allow]`
-    ///
-    /// Don't forget to call `pop`!
-    pub(crate) fn push(
-        &mut self,
-        attrs: &[ast::Attribute],
-        is_crate_node: bool,
-        source_hir_id: Option<HirId>,
-    ) -> BuilderPush {
-        let prev = self.cur;
-        self.cur = self.sets.list.push(LintSet { specs: FxHashMap::default(), parent: prev });
-
+    fn add(&mut self, attrs: &[ast::Attribute], is_crate_node: bool, source_hir_id: Option<HirId>) {
         let sess = self.sess;
         for (attr_index, attr) in attrs.iter().enumerate() {
             if attr.has_name(sym::automatically_derived) {
-                self.current_specs_mut().insert(
+                self.insert(
                     LintId::of(SINGLE_USE_LIFETIMES),
                     (Level::Allow, LintLevelSource::Default),
                 );
@@ -293,7 +681,17 @@ impl<'s> LintLevelsBuilder<'s> {
                 None => continue,
                 // This is the only lint level with a `LintExpectationId` that can be created from an attribute
                 Some(Level::Expect(unstable_id)) if let Some(hir_id) = source_hir_id => {
-                    let stable_id = self.create_stable_id(unstable_id, hir_id, attr_index);
+                    let LintExpectationId::Unstable { attr_id, lint_index } = unstable_id
+                        else { bug!("stable id Level::from_attr") };
+
+                    let stable_id = LintExpectationId::Stable {
+                        hir_id,
+                        attr_index: attr_index.try_into().unwrap(),
+                        lint_index,
+                        // we pass the previous unstable attr_id such that we can trace the ast id when building a map
+                        // to go from unstable to stable id.
+                        attr_id: Some(attr_id),
+                    };
 
                     Level::Expect(stable_id)
                 }
@@ -408,7 +806,7 @@ impl<'s> LintLevelsBuilder<'s> {
                                 [lint] => *lint == LintId::of(UNFULFILLED_LINT_EXPECTATIONS),
                                 _ => false,
                             };
-                            self.lint_expectations.push((
+                            self.provider.push_expectation(
                                 expect_id,
                                 LintExpectation::new(
                                     reason,
@@ -416,13 +814,19 @@ impl<'s> LintLevelsBuilder<'s> {
                                     is_unfulfilled_lint_expectations,
                                     tool_name,
                                 ),
-                            ));
+                            );
                         }
-                        let src = LintLevelSource::Node(
-                            meta_item.path.segments.last().expect("empty lint name").ident.name,
-                            sp,
+                        let src = LintLevelSource::Node {
+                            name: meta_item
+                                .path
+                                .segments
+                                .last()
+                                .expect("empty lint name")
+                                .ident
+                                .name,
+                            span: sp,
                             reason,
-                        );
+                        };
                         for &id in *ids {
                             if self.check_gated_lint(id, attr.span) {
                                 self.insert_spec(id, (level, src));
@@ -435,31 +839,26 @@ impl<'s> LintLevelsBuilder<'s> {
                             Ok(ids) => {
                                 let complete_name =
                                     &format!("{}::{}", tool_ident.unwrap().name, name);
-                                let src = LintLevelSource::Node(
-                                    Symbol::intern(complete_name),
-                                    sp,
+                                let src = LintLevelSource::Node {
+                                    name: Symbol::intern(complete_name),
+                                    span: sp,
                                     reason,
-                                );
+                                };
                                 for &id in ids {
                                     if self.check_gated_lint(id, attr.span) {
                                         self.insert_spec(id, (level, src));
                                     }
                                 }
                                 if let Level::Expect(expect_id) = level {
-                                    self.lint_expectations.push((
+                                    self.provider.push_expectation(
                                         expect_id,
                                         LintExpectation::new(reason, sp, false, tool_name),
-                                    ));
+                                    );
                                 }
                             }
                             Err((Some(ids), ref new_lint_name)) => {
                                 let lint = builtin::RENAMED_AND_REMOVED_LINTS;
-                                let (lvl, src) = self.sets.get_lint_level(
-                                    lint,
-                                    self.cur,
-                                    Some(self.current_specs()),
-                                    &sess,
-                                );
+                                let (lvl, src) = self.provider.get_lint_level(lint, &sess);
                                 struct_lint_level(
                                     self.sess,
                                     lint,
@@ -481,19 +880,19 @@ impl<'s> LintLevelsBuilder<'s> {
                                     },
                                 );
 
-                                let src = LintLevelSource::Node(
-                                    Symbol::intern(&new_lint_name),
-                                    sp,
+                                let src = LintLevelSource::Node {
+                                    name: Symbol::intern(&new_lint_name),
+                                    span: sp,
                                     reason,
-                                );
+                                };
                                 for id in ids {
                                     self.insert_spec(*id, (level, src));
                                 }
                                 if let Level::Expect(expect_id) = level {
-                                    self.lint_expectations.push((
+                                    self.provider.push_expectation(
                                         expect_id,
                                         LintExpectation::new(reason, sp, false, tool_name),
-                                    ));
+                                    );
                                 }
                             }
                             Err((None, _)) => {
@@ -519,12 +918,7 @@ impl<'s> LintLevelsBuilder<'s> {
 
                     CheckLintNameResult::Warning(msg, renamed) => {
                         let lint = builtin::RENAMED_AND_REMOVED_LINTS;
-                        let (renamed_lint_level, src) = self.sets.get_lint_level(
-                            lint,
-                            self.cur,
-                            Some(self.current_specs()),
-                            &sess,
-                        );
+                        let (renamed_lint_level, src) = self.provider.get_lint_level(lint, &sess);
                         struct_lint_level(
                             self.sess,
                             lint,
@@ -547,12 +941,7 @@ impl<'s> LintLevelsBuilder<'s> {
                     }
                     CheckLintNameResult::NoLint(suggestion) => {
                         let lint = builtin::UNKNOWN_LINTS;
-                        let (level, src) = self.sets.get_lint_level(
-                            lint,
-                            self.cur,
-                            Some(self.current_specs()),
-                            self.sess,
-                        );
+                        let (level, src) = self.provider.get_lint_level(lint, self.sess);
                         let name = if let Some(tool_ident) = tool_ident {
                             format!("{}::{}", tool_ident.name, name)
                         } else {
@@ -588,17 +977,21 @@ impl<'s> LintLevelsBuilder<'s> {
                     if let CheckLintNameResult::Ok(ids) =
                         self.store.check_lint_name(&new_name, None, self.registered_tools)
                     {
-                        let src = LintLevelSource::Node(Symbol::intern(&new_name), sp, reason);
+                        let src = LintLevelSource::Node {
+                            name: Symbol::intern(&new_name),
+                            span: sp,
+                            reason,
+                        };
                         for &id in ids {
                             if self.check_gated_lint(id, attr.span) {
                                 self.insert_spec(id, (level, src));
                             }
                         }
                         if let Level::Expect(expect_id) = level {
-                            self.lint_expectations.push((
+                            self.provider.push_expectation(
                                 expect_id,
                                 LintExpectation::new(reason, sp, false, tool_name),
-                            ));
+                            );
                         }
                     } else {
                         panic!("renamed lint does not exist: {}", new_name);
@@ -613,13 +1006,12 @@ impl<'s> LintLevelsBuilder<'s> {
                     continue;
                 }
 
-                let LintLevelSource::Node(lint_attr_name, lint_attr_span, _) = *src else {
+                let LintLevelSource::Node { name: lint_attr_name, span: lint_attr_span, .. } = *src else {
                     continue
                 };
 
                 let lint = builtin::UNUSED_ATTRIBUTES;
-                let (lint_level, lint_src) =
-                    self.sets.get_lint_level(lint, self.cur, Some(self.current_specs()), self.sess);
+                let (lint_level, lint_src) = self.provider.get_lint_level(lint, &self.sess);
                 struct_lint_level(
                     self.sess,
                     lint,
@@ -637,32 +1029,13 @@ impl<'s> LintLevelsBuilder<'s> {
                 break;
             }
         }
-
-        if self.current_specs().is_empty() {
-            self.sets.list.pop();
-            self.cur = prev;
-        }
-
-        BuilderPush { prev, changed: prev != self.cur }
-    }
-
-    fn create_stable_id(
-        &mut self,
-        unstable_id: LintExpectationId,
-        hir_id: HirId,
-        attr_index: usize,
-    ) -> LintExpectationId {
-        let stable_id =
-            LintExpectationId::Stable { hir_id, attr_index: attr_index as u16, lint_index: None };
-
-        self.expectation_id_map.insert(unstable_id, stable_id);
-
-        stable_id
     }
 
     /// Checks if the lint is gated on a feature that is not enabled.
     ///
     /// Returns `true` if the lint's feature is enabled.
+    // FIXME only emit this once for each attribute, instead of repeating it 4 times for
+    // pre-expansion lints, post-expansion lints, `shallow_lint_levels_on` and `lint_expectations`.
     fn check_gated_lint(&self, lint_id: LintId, span: Span) -> bool {
         if let Some(feature) = lint_id.lint.feature_gate {
             if !self.sess.features_untracked().enabled(feature) {
@@ -689,19 +1062,14 @@ impl<'s> LintLevelsBuilder<'s> {
         true
     }
 
-    /// Called after `push` when the scope of a set of attributes are exited.
-    pub fn pop(&mut self, push: BuilderPush) {
-        self.cur = push.prev;
-    }
-
     /// Find the lint level for a lint.
-    pub fn lint_level(&self, lint: &'static Lint) -> (Level, LintLevelSource) {
-        self.sets.get_lint_level(lint, self.cur, None, self.sess)
+    pub fn lint_level(&self, lint: &'static Lint) -> LevelAndSource {
+        self.provider.get_lint_level(lint, self.sess)
     }
 
     /// Used to emit a lint-related diagnostic based on the current state of
     /// this lint context.
-    pub fn struct_lint(
+    pub(crate) fn struct_lint(
         &self,
         lint: &'static Lint,
         span: Option<MultiSpan>,
@@ -713,141 +1081,8 @@ impl<'s> LintLevelsBuilder<'s> {
         let (level, src) = self.lint_level(lint);
         struct_lint_level(self.sess, lint, level, src, span, msg, decorate)
     }
-
-    /// Registers the ID provided with the current set of lints stored in
-    /// this context.
-    pub fn register_id(&mut self, id: HirId) {
-        self.id_to_set.insert(id, self.cur);
-    }
-
-    fn update_unstable_expectation_ids(&self) {
-        self.sess.diagnostic().update_unstable_expectation_id(&self.expectation_id_map);
-    }
-
-    pub fn build_map(self) -> LintLevelMap {
-        LintLevelMap {
-            sets: self.sets,
-            id_to_set: self.id_to_set,
-            lint_expectations: self.lint_expectations,
-        }
-    }
-}
-
-struct LintLevelMapBuilder<'tcx> {
-    levels: LintLevelsBuilder<'tcx>,
-    tcx: TyCtxt<'tcx>,
-}
-
-impl LintLevelMapBuilder<'_> {
-    fn with_lint_attrs<F>(&mut self, id: hir::HirId, f: F)
-    where
-        F: FnOnce(&mut Self),
-    {
-        let is_crate_hir = id == hir::CRATE_HIR_ID;
-        let attrs = self.tcx.hir().attrs(id);
-        let push = self.levels.push(attrs, is_crate_hir, Some(id));
-
-        if push.changed {
-            self.levels.register_id(id);
-        }
-        f(self);
-        self.levels.pop(push);
-    }
-}
-
-impl<'tcx> intravisit::Visitor<'tcx> for LintLevelMapBuilder<'tcx> {
-    type NestedFilter = nested_filter::All;
-
-    fn nested_visit_map(&mut self) -> Self::Map {
-        self.tcx.hir()
-    }
-
-    fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) {
-        self.with_lint_attrs(param.hir_id, |builder| {
-            intravisit::walk_param(builder, param);
-        });
-    }
-
-    fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) {
-        self.with_lint_attrs(it.hir_id(), |builder| {
-            intravisit::walk_item(builder, it);
-        });
-    }
-
-    fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) {
-        self.with_lint_attrs(it.hir_id(), |builder| {
-            intravisit::walk_foreign_item(builder, it);
-        })
-    }
-
-    fn visit_stmt(&mut self, e: &'tcx hir::Stmt<'tcx>) {
-        // We will call `with_lint_attrs` when we walk
-        // the `StmtKind`. The outer statement itself doesn't
-        // define the lint levels.
-        intravisit::walk_stmt(self, e);
-    }
-
-    fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) {
-        self.with_lint_attrs(e.hir_id, |builder| {
-            intravisit::walk_expr(builder, e);
-        })
-    }
-
-    fn visit_expr_field(&mut self, field: &'tcx hir::ExprField<'tcx>) {
-        self.with_lint_attrs(field.hir_id, |builder| {
-            intravisit::walk_expr_field(builder, field);
-        })
-    }
-
-    fn visit_field_def(&mut self, s: &'tcx hir::FieldDef<'tcx>) {
-        self.with_lint_attrs(s.hir_id, |builder| {
-            intravisit::walk_field_def(builder, s);
-        })
-    }
-
-    fn visit_variant(&mut self, v: &'tcx hir::Variant<'tcx>) {
-        self.with_lint_attrs(v.id, |builder| {
-            intravisit::walk_variant(builder, v);
-        })
-    }
-
-    fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) {
-        self.with_lint_attrs(l.hir_id, |builder| {
-            intravisit::walk_local(builder, l);
-        })
-    }
-
-    fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) {
-        self.with_lint_attrs(a.hir_id, |builder| {
-            intravisit::walk_arm(builder, a);
-        })
-    }
-
-    fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) {
-        self.with_lint_attrs(trait_item.hir_id(), |builder| {
-            intravisit::walk_trait_item(builder, trait_item);
-        });
-    }
-
-    fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {
-        self.with_lint_attrs(impl_item.hir_id(), |builder| {
-            intravisit::walk_impl_item(builder, impl_item);
-        });
-    }
-
-    fn visit_pat_field(&mut self, field: &'tcx hir::PatField<'tcx>) {
-        self.with_lint_attrs(field.hir_id, |builder| {
-            intravisit::walk_pat_field(builder, field);
-        })
-    }
-
-    fn visit_generic_param(&mut self, p: &'tcx hir::GenericParam<'tcx>) {
-        self.with_lint_attrs(p.hir_id, |builder| {
-            intravisit::walk_generic_param(builder, p);
-        });
-    }
 }
 
-pub fn provide(providers: &mut Providers) {
-    providers.lint_levels = lint_levels;
+pub(crate) fn provide(providers: &mut Providers) {
+    *providers = Providers { shallow_lint_levels_on, lint_expectations, ..*providers };
 }
diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs
index 11b2d057a07..cbe7afc8e55 100644
--- a/compiler/rustc_lint_defs/src/lib.rs
+++ b/compiler/rustc_lint_defs/src/lib.rs
@@ -92,7 +92,7 @@ pub enum LintExpectationId {
     /// stable and can be cached. The additional index ensures that nodes with
     /// several expectations can correctly match diagnostics to the individual
     /// expectation.
-    Stable { hir_id: HirId, attr_index: u16, lint_index: Option<u16> },
+    Stable { hir_id: HirId, attr_index: u16, lint_index: Option<u16>, attr_id: Option<AttrId> },
 }
 
 impl LintExpectationId {
@@ -116,13 +116,31 @@ impl LintExpectationId {
 
         *lint_index = new_lint_index
     }
+
+    /// Prepares the id for hashing. Removes references to the ast.
+    /// Should only be called when the id is stable.
+    pub fn normalize(self) -> Self {
+        match self {
+            Self::Stable { hir_id, attr_index, lint_index, .. } => {
+                Self::Stable { hir_id, attr_index, lint_index, attr_id: None }
+            }
+            Self::Unstable { .. } => {
+                unreachable!("`normalize` called when `ExpectationId` is unstable")
+            }
+        }
+    }
 }
 
 impl<HCX: rustc_hir::HashStableContext> HashStable<HCX> for LintExpectationId {
     #[inline]
     fn hash_stable(&self, hcx: &mut HCX, hasher: &mut StableHasher) {
         match self {
-            LintExpectationId::Stable { hir_id, attr_index, lint_index: Some(lint_index) } => {
+            LintExpectationId::Stable {
+                hir_id,
+                attr_index,
+                lint_index: Some(lint_index),
+                attr_id: _,
+            } => {
                 hir_id.hash_stable(hcx, hasher);
                 attr_index.hash_stable(hcx, hasher);
                 lint_index.hash_stable(hcx, hasher);
@@ -142,9 +160,12 @@ impl<HCX: rustc_hir::HashStableContext> ToStableHashKey<HCX> for LintExpectation
     #[inline]
     fn to_stable_hash_key(&self, _: &HCX) -> Self::KeyType {
         match self {
-            LintExpectationId::Stable { hir_id, attr_index, lint_index: Some(lint_index) } => {
-                (*hir_id, *attr_index, *lint_index)
-            }
+            LintExpectationId::Stable {
+                hir_id,
+                attr_index,
+                lint_index: Some(lint_index),
+                attr_id: _,
+            } => (*hir_id, *attr_index, *lint_index),
             _ => {
                 unreachable!("HashStable should only be called for a filled `LintExpectationId`")
             }
diff --git a/compiler/rustc_middle/src/dep_graph/dep_node.rs b/compiler/rustc_middle/src/dep_graph/dep_node.rs
index 40b9d73db94..6b556826918 100644
--- a/compiler/rustc_middle/src/dep_graph/dep_node.rs
+++ b/compiler/rustc_middle/src/dep_graph/dep_node.rs
@@ -62,7 +62,7 @@ use crate::ty::TyCtxt;
 use rustc_data_structures::fingerprint::Fingerprint;
 use rustc_hir::def_id::{CrateNum, DefId, LocalDefId};
 use rustc_hir::definitions::DefPathHash;
-use rustc_hir::{HirId, OwnerId};
+use rustc_hir::{HirId, ItemLocalId, OwnerId};
 use rustc_query_system::dep_graph::FingerprintStyle;
 use rustc_span::symbol::Symbol;
 use std::hash::Hash;
@@ -194,7 +194,7 @@ impl DepNodeExt for DepNode {
         let kind = dep_kind_from_label_string(label)?;
 
         match tcx.fingerprint_style(kind) {
-            FingerprintStyle::Opaque => Err(()),
+            FingerprintStyle::Opaque | FingerprintStyle::HirId => Err(()),
             FingerprintStyle::Unit => Ok(DepNode::new_no_params(tcx, kind)),
             FingerprintStyle::DefPathHash => {
                 Ok(DepNode::from_def_path_hash(tcx, def_path_hash, kind))
@@ -344,7 +344,7 @@ impl<'tcx> DepNodeParams<TyCtxt<'tcx>> for (DefId, DefId) {
 impl<'tcx> DepNodeParams<TyCtxt<'tcx>> for HirId {
     #[inline(always)]
     fn fingerprint_style() -> FingerprintStyle {
-        FingerprintStyle::Opaque
+        FingerprintStyle::HirId
     }
 
     // We actually would not need to specialize the implementation of this
@@ -353,10 +353,36 @@ impl<'tcx> DepNodeParams<TyCtxt<'tcx>> for HirId {
     #[inline(always)]
     fn to_fingerprint(&self, tcx: TyCtxt<'tcx>) -> Fingerprint {
         let HirId { owner, local_id } = *self;
-
         let def_path_hash = tcx.def_path_hash(owner.to_def_id());
-        let local_id = Fingerprint::from_smaller_hash(local_id.as_u32().into());
+        Fingerprint::new(
+            // `owner` is local, so is completely defined by the local hash
+            def_path_hash.local_hash(),
+            local_id.as_u32().into(),
+        )
+    }
 
-        def_path_hash.0.combine(local_id)
+    #[inline(always)]
+    fn to_debug_str(&self, tcx: TyCtxt<'tcx>) -> String {
+        let HirId { owner, local_id } = *self;
+        format!("{}.{}", tcx.def_path_str(owner.to_def_id()), local_id.as_u32())
+    }
+
+    #[inline(always)]
+    fn recover(tcx: TyCtxt<'tcx>, dep_node: &DepNode) -> Option<Self> {
+        if tcx.fingerprint_style(dep_node.kind) == FingerprintStyle::HirId {
+            let (local_hash, local_id) = Fingerprint::from(dep_node.hash).as_value();
+            let def_path_hash = DefPathHash::new(tcx.sess.local_stable_crate_id(), local_hash);
+            let def_id = tcx
+                .def_path_hash_to_def_id(def_path_hash, &mut || {
+                    panic!("Failed to extract HirId: {:?} {}", dep_node.kind, dep_node.hash)
+                })
+                .expect_local();
+            let local_id = local_id
+                .try_into()
+                .unwrap_or_else(|_| panic!("local id should be u32, found {:?}", local_id));
+            Some(HirId { owner: OwnerId { def_id }, local_id: ItemLocalId::from_u32(local_id) })
+        } else {
+            None
+        }
     }
 }
diff --git a/compiler/rustc_middle/src/hir/map/mod.rs b/compiler/rustc_middle/src/hir/map/mod.rs
index 8523b5ca0ec..b78c3f85596 100644
--- a/compiler/rustc_middle/src/hir/map/mod.rs
+++ b/compiler/rustc_middle/src/hir/map/mod.rs
@@ -61,7 +61,7 @@ pub struct ParentHirIterator<'hir> {
 }
 
 impl<'hir> Iterator for ParentHirIterator<'hir> {
-    type Item = (HirId, Node<'hir>);
+    type Item = HirId;
 
     fn next(&mut self) -> Option<Self::Item> {
         if self.current_id == CRATE_HIR_ID {
@@ -77,10 +77,7 @@ impl<'hir> Iterator for ParentHirIterator<'hir> {
             }
 
             self.current_id = parent_id;
-            if let Some(node) = self.map.find(parent_id) {
-                return Some((parent_id, node));
-            }
-            // If this `HirId` doesn't have an entry, skip it and look for its `parent_id`.
+            return Some(parent_id);
         }
     }
 }
@@ -393,8 +390,8 @@ impl<'hir> Map<'hir> {
     }
 
     pub fn enclosing_body_owner(self, hir_id: HirId) -> LocalDefId {
-        for (parent, _) in self.parent_iter(hir_id) {
-            if let Some(body) = self.find(parent).map(associated_body).flatten() {
+        for (_, node) in self.parent_iter(hir_id) {
+            if let Some(body) = associated_body(node) {
                 return self.body_owner_def_id(body);
             }
         }
@@ -635,13 +632,20 @@ impl<'hir> Map<'hir> {
     /// Returns an iterator for the nodes in the ancestor tree of the `current_id`
     /// until the crate root is reached. Prefer this over your own loop using `get_parent_node`.
     #[inline]
-    pub fn parent_iter(self, current_id: HirId) -> ParentHirIterator<'hir> {
+    pub fn parent_id_iter(self, current_id: HirId) -> impl Iterator<Item = HirId> + 'hir {
         ParentHirIterator { current_id, map: self }
     }
 
     /// Returns an iterator for the nodes in the ancestor tree of the `current_id`
     /// until the crate root is reached. Prefer this over your own loop using `get_parent_node`.
     #[inline]
+    pub fn parent_iter(self, current_id: HirId) -> impl Iterator<Item = (HirId, Node<'hir>)> {
+        self.parent_id_iter(current_id).filter_map(move |id| Some((id, self.find(id)?)))
+    }
+
+    /// Returns an iterator for the nodes in the ancestor tree of the `current_id`
+    /// until the crate root is reached. Prefer this over your own loop using `get_parent_node`.
+    #[inline]
     pub fn parent_owner_iter(self, current_id: HirId) -> ParentOwnerIterator<'hir> {
         ParentOwnerIterator { current_id, map: self }
     }
@@ -937,9 +941,19 @@ impl<'hir> Map<'hir> {
 
         let span = match self.find(hir_id)? {
             // Function-like.
-            Node::Item(Item { kind: ItemKind::Fn(sig, ..), .. })
-            | Node::TraitItem(TraitItem { kind: TraitItemKind::Fn(sig, ..), .. })
-            | Node::ImplItem(ImplItem { kind: ImplItemKind::Fn(sig, ..), .. }) => sig.span,
+            Node::Item(Item { kind: ItemKind::Fn(sig, ..), span: outer_span, .. })
+            | Node::TraitItem(TraitItem {
+                kind: TraitItemKind::Fn(sig, ..),
+                span: outer_span,
+                ..
+            })
+            | Node::ImplItem(ImplItem {
+                kind: ImplItemKind::Fn(sig, ..), span: outer_span, ..
+            }) => {
+                // Ensure that the returned span has the item's SyntaxContext, and not the
+                // SyntaxContext of the visibility.
+                sig.span.find_ancestor_in_same_ctxt(*outer_span).unwrap_or(*outer_span)
+            }
             // Constants and Statics.
             Node::Item(Item {
                 kind:
@@ -981,7 +995,11 @@ impl<'hir> Map<'hir> {
             }
             // Other cases.
             Node::Item(item) => match &item.kind {
-                ItemKind::Use(path, _) => path.span,
+                ItemKind::Use(path, _) => {
+                    // Ensure that the returned span has the item's SyntaxContext, and not the
+                    // SyntaxContext of the path.
+                    path.span.find_ancestor_in_same_ctxt(item.span).unwrap_or(item.span)
+                }
                 _ => named_span(item.span, item.ident, item.kind.generics()),
             },
             Node::Variant(variant) => named_span(variant.span, variant.ident, None),
@@ -991,11 +1009,17 @@ impl<'hir> Map<'hir> {
                 _ => named_span(item.span, item.ident, None),
             },
             Node::Ctor(_) => return self.opt_span(self.get_parent_node(hir_id)),
-            Node::Expr(Expr { kind: ExprKind::Closure(Closure { fn_decl_span, .. }), .. }) => {
-                *fn_decl_span
+            Node::Expr(Expr {
+                kind: ExprKind::Closure(Closure { fn_decl_span, .. }),
+                span,
+                ..
+            }) => {
+                // Ensure that the returned span has the item's SyntaxContext.
+                fn_decl_span.find_ancestor_in_same_ctxt(*span).unwrap_or(*span)
             }
             _ => self.span_with_body(hir_id),
         };
+        debug_assert_eq!(span.ctxt(), self.span_with_body(hir_id).ctxt());
         Some(span)
     }
 
diff --git a/compiler/rustc_middle/src/lint.rs b/compiler/rustc_middle/src/lint.rs
index 328b7ad6a49..f3e4f1faeb0 100644
--- a/compiler/rustc_middle/src/lint.rs
+++ b/compiler/rustc_middle/src/lint.rs
@@ -1,20 +1,20 @@
 use std::cmp;
 
 use rustc_data_structures::fx::FxHashMap;
-use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
+use rustc_data_structures::sorted_map::SortedMap;
 use rustc_errors::{Diagnostic, DiagnosticBuilder, DiagnosticId, DiagnosticMessage, MultiSpan};
-use rustc_hir::HirId;
-use rustc_index::vec::IndexVec;
-use rustc_query_system::ich::StableHashingContext;
+use rustc_hir::{HirId, ItemLocalId};
 use rustc_session::lint::{
     builtin::{self, FORBIDDEN_LINT_GROUPS},
-    FutureIncompatibilityReason, Level, Lint, LintExpectationId, LintId,
+    FutureIncompatibilityReason, Level, Lint, LintId,
 };
 use rustc_session::Session;
 use rustc_span::hygiene::MacroKind;
 use rustc_span::source_map::{DesugaringKind, ExpnKind};
 use rustc_span::{symbol, Span, Symbol, DUMMY_SP};
 
+use crate::ty::TyCtxt;
+
 /// How a lint level was set.
 #[derive(Clone, Copy, PartialEq, Eq, HashStable, Debug)]
 pub enum LintLevelSource {
@@ -23,7 +23,12 @@ pub enum LintLevelSource {
     Default,
 
     /// Lint level was set by an attribute.
-    Node(Symbol, Span, Option<Symbol> /* RFC 2383 reason */),
+    Node {
+        name: Symbol,
+        span: Span,
+        /// RFC 2383 reason
+        reason: Option<Symbol>,
+    },
 
     /// Lint level was set by a command-line flag.
     /// The provided `Level` is the level specified on the command line.
@@ -35,7 +40,7 @@ impl LintLevelSource {
     pub fn name(&self) -> Symbol {
         match *self {
             LintLevelSource::Default => symbol::kw::Default,
-            LintLevelSource::Node(name, _, _) => name,
+            LintLevelSource::Node { name, .. } => name,
             LintLevelSource::CommandLine(name, _) => name,
         }
     }
@@ -43,7 +48,7 @@ impl LintLevelSource {
     pub fn span(&self) -> Span {
         match *self {
             LintLevelSource::Default => DUMMY_SP,
-            LintLevelSource::Node(_, span, _) => span,
+            LintLevelSource::Node { span, .. } => span,
             LintLevelSource::CommandLine(_, _) => DUMMY_SP,
         }
     }
@@ -52,145 +57,137 @@ impl LintLevelSource {
 /// A tuple of a lint level and its source.
 pub type LevelAndSource = (Level, LintLevelSource);
 
-#[derive(Debug, HashStable)]
-pub struct LintLevelSets {
-    pub list: IndexVec<LintStackIndex, LintSet>,
-    pub lint_cap: Level,
-}
-
-rustc_index::newtype_index! {
-    #[derive(HashStable)]
-    pub struct LintStackIndex {
-        const COMMAND_LINE = 0,
-    }
-}
-
-#[derive(Debug, HashStable)]
-pub struct LintSet {
-    // -A,-W,-D flags, a `Symbol` for the flag itself and `Level` for which
-    // flag.
-    pub specs: FxHashMap<LintId, LevelAndSource>,
-
-    pub parent: LintStackIndex,
+/// Return type for the `shallow_lint_levels_on` query.
+///
+/// This map represents the set of allowed lints and allowance levels given
+/// by the attributes for *a single HirId*.
+#[derive(Default, Debug, HashStable)]
+pub struct ShallowLintLevelMap {
+    pub specs: SortedMap<ItemLocalId, FxHashMap<LintId, LevelAndSource>>,
 }
 
-impl LintLevelSets {
-    pub fn new() -> Self {
-        LintLevelSets { list: IndexVec::new(), lint_cap: Level::Forbid }
-    }
-
-    pub fn get_lint_level(
-        &self,
-        lint: &'static Lint,
-        idx: LintStackIndex,
-        aux: Option<&FxHashMap<LintId, LevelAndSource>>,
-        sess: &Session,
-    ) -> LevelAndSource {
-        let (level, mut src) = self.get_lint_id_level(LintId::of(lint), idx, aux);
-
-        // If `level` is none then we actually assume the default level for this
-        // lint.
-        let mut level = level.unwrap_or_else(|| lint.default_level(sess.edition()));
-
-        // If we're about to issue a warning, check at the last minute for any
-        // directives against the warnings "lint". If, for example, there's an
-        // `allow(warnings)` in scope then we want to respect that instead.
-        //
-        // We exempt `FORBIDDEN_LINT_GROUPS` from this because it specifically
-        // triggers in cases (like #80988) where you have `forbid(warnings)`,
-        // and so if we turned that into an error, it'd defeat the purpose of the
-        // future compatibility warning.
-        if level == Level::Warn && LintId::of(lint) != LintId::of(FORBIDDEN_LINT_GROUPS) {
-            let (warnings_level, warnings_src) =
-                self.get_lint_id_level(LintId::of(builtin::WARNINGS), idx, aux);
-            if let Some(configured_warning_level) = warnings_level {
-                if configured_warning_level != Level::Warn {
-                    level = configured_warning_level;
-                    src = warnings_src;
-                }
+/// From an initial level and source, verify the effect of special annotations:
+/// `warnings` lint level and lint caps.
+///
+/// The return of this function is suitable for diagnostics.
+pub fn reveal_actual_level(
+    level: Option<Level>,
+    src: &mut LintLevelSource,
+    sess: &Session,
+    lint: LintId,
+    probe_for_lint_level: impl FnOnce(LintId) -> (Option<Level>, LintLevelSource),
+) -> Level {
+    // If `level` is none then we actually assume the default level for this lint.
+    let mut level = level.unwrap_or_else(|| lint.lint.default_level(sess.edition()));
+
+    // If we're about to issue a warning, check at the last minute for any
+    // directives against the warnings "lint". If, for example, there's an
+    // `allow(warnings)` in scope then we want to respect that instead.
+    //
+    // We exempt `FORBIDDEN_LINT_GROUPS` from this because it specifically
+    // triggers in cases (like #80988) where you have `forbid(warnings)`,
+    // and so if we turned that into an error, it'd defeat the purpose of the
+    // future compatibility warning.
+    if level == Level::Warn && lint != LintId::of(FORBIDDEN_LINT_GROUPS) {
+        let (warnings_level, warnings_src) = probe_for_lint_level(LintId::of(builtin::WARNINGS));
+        if let Some(configured_warning_level) = warnings_level {
+            if configured_warning_level != Level::Warn {
+                level = configured_warning_level;
+                *src = warnings_src;
             }
         }
+    }
 
-        // Ensure that we never exceed the `--cap-lints` argument
-        // unless the source is a --force-warn
-        level = if let LintLevelSource::CommandLine(_, Level::ForceWarn(_)) = src {
-            level
-        } else {
-            cmp::min(level, self.lint_cap)
-        };
-
-        if let Some(driver_level) = sess.driver_lint_caps.get(&LintId::of(lint)) {
-            // Ensure that we never exceed driver level.
-            level = cmp::min(*driver_level, level);
-        }
+    // Ensure that we never exceed the `--cap-lints` argument unless the source is a --force-warn
+    level = if let LintLevelSource::CommandLine(_, Level::ForceWarn(_)) = src {
+        level
+    } else {
+        cmp::min(level, sess.opts.lint_cap.unwrap_or(Level::Forbid))
+    };
 
-        (level, src)
+    if let Some(driver_level) = sess.driver_lint_caps.get(&lint) {
+        // Ensure that we never exceed driver level.
+        level = cmp::min(*driver_level, level);
     }
 
-    pub fn get_lint_id_level(
+    level
+}
+
+impl ShallowLintLevelMap {
+    /// Perform a deep probe in the HIR tree looking for the actual level for the lint.
+    /// This lint level is not usable for diagnostics, it needs to be corrected by
+    /// `reveal_actual_level` beforehand.
+    #[instrument(level = "trace", skip(self, tcx), ret)]
+    fn probe_for_lint_level(
         &self,
+        tcx: TyCtxt<'_>,
         id: LintId,
-        mut idx: LintStackIndex,
-        aux: Option<&FxHashMap<LintId, LevelAndSource>>,
+        start: HirId,
     ) -> (Option<Level>, LintLevelSource) {
-        if let Some(specs) = aux {
-            if let Some(&(level, src)) = specs.get(&id) {
-                return (Some(level), src);
-            }
+        if let Some(map) = self.specs.get(&start.local_id)
+            && let Some(&(level, src)) = map.get(&id)
+        {
+            return (Some(level), src);
         }
-        loop {
-            let LintSet { ref specs, parent } = self.list[idx];
-            if let Some(&(level, src)) = specs.get(&id) {
-                return (Some(level), src);
+
+        let mut owner = start.owner;
+        let mut specs = &self.specs;
+
+        for parent in tcx.hir().parent_id_iter(start) {
+            if parent.owner != owner {
+                owner = parent.owner;
+                specs = &tcx.shallow_lint_levels_on(owner).specs;
             }
-            if idx == COMMAND_LINE {
-                return (None, LintLevelSource::Default);
+            if let Some(map) = specs.get(&parent.local_id)
+                && let Some(&(level, src)) = map.get(&id)
+            {
+                return (Some(level), src);
             }
-            idx = parent;
         }
-    }
-}
 
-#[derive(Debug)]
-pub struct LintLevelMap {
-    /// This is a collection of lint expectations as described in RFC 2383, that
-    /// can be fulfilled during this compilation session. This means that at least
-    /// one expected lint is currently registered in the lint store.
-    ///
-    /// The [`LintExpectationId`] is stored as a part of the [`Expect`](Level::Expect)
-    /// lint level.
-    pub lint_expectations: Vec<(LintExpectationId, LintExpectation)>,
-    pub sets: LintLevelSets,
-    pub id_to_set: FxHashMap<HirId, LintStackIndex>,
-}
+        (None, LintLevelSource::Default)
+    }
 
-impl LintLevelMap {
-    /// If the `id` was previously registered with `register_id` when building
-    /// this `LintLevelMap` this returns the corresponding lint level and source
-    /// of the lint level for the lint provided.
-    ///
-    /// If the `id` was not previously registered, returns `None`. If `None` is
-    /// returned then the parent of `id` should be acquired and this function
-    /// should be called again.
-    pub fn level_and_source(
+    /// Fetch and return the user-visible lint level for the given lint at the given HirId.
+    #[instrument(level = "trace", skip(self, tcx), ret)]
+    pub fn lint_level_id_at_node(
         &self,
-        lint: &'static Lint,
-        id: HirId,
-        session: &Session,
-    ) -> Option<LevelAndSource> {
-        self.id_to_set.get(&id).map(|idx| self.sets.get_lint_level(lint, *idx, None, session))
+        tcx: TyCtxt<'_>,
+        lint: LintId,
+        cur: HirId,
+    ) -> (Level, LintLevelSource) {
+        let (level, mut src) = self.probe_for_lint_level(tcx, lint, cur);
+        let level = reveal_actual_level(level, &mut src, tcx.sess, lint, |lint| {
+            self.probe_for_lint_level(tcx, lint, cur)
+        });
+        (level, src)
     }
 }
 
-impl<'a> HashStable<StableHashingContext<'a>> for LintLevelMap {
-    #[inline]
-    fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) {
-        let LintLevelMap { ref sets, ref id_to_set, ref lint_expectations } = *self;
+impl TyCtxt<'_> {
+    /// Fetch and return the user-visible lint level for the given lint at the given HirId.
+    pub fn lint_level_at_node(self, lint: &'static Lint, id: HirId) -> (Level, LintLevelSource) {
+        self.shallow_lint_levels_on(id.owner).lint_level_id_at_node(self, LintId::of(lint), id)
+    }
 
-        id_to_set.hash_stable(hcx, hasher);
-        lint_expectations.hash_stable(hcx, hasher);
+    /// Walks upwards from `id` to find a node which might change lint levels with attributes.
+    /// It stops at `bound` and just returns it if reached.
+    pub fn maybe_lint_level_root_bounded(self, mut id: HirId, bound: HirId) -> HirId {
+        let hir = self.hir();
+        loop {
+            if id == bound {
+                return bound;
+            }
 
-        hcx.while_hashing_spans(true, |hcx| sets.hash_stable(hcx, hasher))
+            if hir.attrs(id).iter().any(|attr| Level::from_attr(attr).is_some()) {
+                return id;
+            }
+            let next = hir.get_parent_node(id);
+            if next == id {
+                bug!("lint traversal reached the root of the crate");
+            }
+            id = next;
+        }
     }
 }
 
@@ -261,11 +258,11 @@ pub fn explain_lint_level_source(
                 ));
             }
         }
-        LintLevelSource::Node(lint_attr_name, src, reason) => {
+        LintLevelSource::Node { name: lint_attr_name, span, reason, .. } => {
             if let Some(rationale) = reason {
                 err.note(rationale.as_str());
             }
-            err.span_note_once(src, "the lint level is defined here");
+            err.span_note_once(span, "the lint level is defined here");
             if lint_attr_name.as_str() != name {
                 let level_str = level.as_str();
                 err.note_once(&format!(
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index fb867f8b46b..cf5b365b27c 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -274,10 +274,15 @@ rustc_queries! {
         separate_provide_extern
     }
 
-    query lint_levels(_: ()) -> LintLevelMap {
+    query shallow_lint_levels_on(key: hir::OwnerId) -> rustc_middle::lint::ShallowLintLevelMap {
+        eval_always // fetches `resolutions`
         arena_cache
-        eval_always
-        desc { "computing the lint levels for items in this crate" }
+        desc { |tcx| "looking up lint levels for `{}`", tcx.def_path_str(key.to_def_id()) }
+    }
+
+    query lint_expectations(_: ()) -> Vec<(LintExpectationId, LintExpectation)> {
+        arena_cache
+        desc { "computing `#[expect]`ed lints in this crate" }
     }
 
     query parent_module_from_def_id(key: LocalDefId) -> LocalDefId {
diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs
index a3489226f62..97646003e73 100644
--- a/compiler/rustc_middle/src/ty/context.rs
+++ b/compiler/rustc_middle/src/ty/context.rs
@@ -4,7 +4,7 @@ use crate::arena::Arena;
 use crate::dep_graph::{DepGraph, DepKindStruct};
 use crate::hir::place::Place as HirPlace;
 use crate::infer::canonical::{Canonical, CanonicalVarInfo, CanonicalVarInfos};
-use crate::lint::{struct_lint_level, LintLevelSource};
+use crate::lint::struct_lint_level;
 use crate::middle::codegen_fn_attrs::CodegenFnAttrs;
 use crate::middle::resolve_lifetime;
 use crate::middle::stability;
@@ -57,7 +57,7 @@ use rustc_serialize::opaque::{FileEncodeResult, FileEncoder};
 use rustc_session::config::{CrateType, OutputFilenames};
 use rustc_session::cstore::CrateStoreDyn;
 use rustc_session::errors::TargetDataLayoutErrorsWrapper;
-use rustc_session::lint::{Level, Lint};
+use rustc_session::lint::Lint;
 use rustc_session::Limit;
 use rustc_session::Session;
 use rustc_span::def_id::{DefPathHash, StableCrateId};
@@ -2812,44 +2812,6 @@ impl<'tcx> TyCtxt<'tcx> {
         iter.intern_with(|xs| self.intern_bound_variable_kinds(xs))
     }
 
-    /// Walks upwards from `id` to find a node which might change lint levels with attributes.
-    /// It stops at `bound` and just returns it if reached.
-    pub fn maybe_lint_level_root_bounded(self, mut id: HirId, bound: HirId) -> HirId {
-        let hir = self.hir();
-        loop {
-            if id == bound {
-                return bound;
-            }
-
-            if hir.attrs(id).iter().any(|attr| Level::from_attr(attr).is_some()) {
-                return id;
-            }
-            let next = hir.get_parent_node(id);
-            if next == id {
-                bug!("lint traversal reached the root of the crate");
-            }
-            id = next;
-        }
-    }
-
-    pub fn lint_level_at_node(
-        self,
-        lint: &'static Lint,
-        mut id: hir::HirId,
-    ) -> (Level, LintLevelSource) {
-        let sets = self.lint_levels(());
-        loop {
-            if let Some(pair) = sets.level_and_source(lint, id, self.sess) {
-                return pair;
-            }
-            let next = self.hir().get_parent_node(id);
-            if next == id {
-                bug!("lint traversal reached the root of the crate");
-            }
-            id = next;
-        }
-    }
-
     /// Emit a lint at `span` from a lint struct (some type that implements `DecorateLint`,
     /// typically generated by `#[derive(LintDiagnostic)]`).
     pub fn emit_spanned_lint(
diff --git a/compiler/rustc_middle/src/ty/query.rs b/compiler/rustc_middle/src/ty/query.rs
index 169da348a29..b6cda34c51f 100644
--- a/compiler/rustc_middle/src/ty/query.rs
+++ b/compiler/rustc_middle/src/ty/query.rs
@@ -1,6 +1,6 @@
 use crate::dep_graph;
 use crate::infer::canonical::{self, Canonical};
-use crate::lint::LintLevelMap;
+use crate::lint::LintExpectation;
 use crate::metadata::ModChild;
 use crate::middle::codegen_fn_attrs::CodegenFnAttrs;
 use crate::middle::exported_symbols::{ExportedSymbol, SymbolExportInfo};
@@ -51,6 +51,7 @@ use rustc_index::{bit_set::FiniteBitSet, vec::IndexVec};
 use rustc_session::config::{EntryFnType, OptLevel, OutputFilenames, SymbolManglingVersion};
 use rustc_session::cstore::{CrateDepKind, CrateSource};
 use rustc_session::cstore::{ExternCrate, ForeignModule, LinkagePreference, NativeLib};
+use rustc_session::lint::LintExpectationId;
 use rustc_session::utils::NativeLibKind;
 use rustc_session::Limits;
 use rustc_span::symbol::Symbol;
diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs
index b512f26335f..f57bd9cec19 100644
--- a/compiler/rustc_parse/src/parser/diagnostics.rs
+++ b/compiler/rustc_parse/src/parser/diagnostics.rs
@@ -33,6 +33,7 @@ use rustc_errors::{
     fluent, Applicability, DiagnosticBuilder, DiagnosticMessage, Handler, MultiSpan, PResult,
 };
 use rustc_errors::{pluralize, Diagnostic, ErrorGuaranteed, IntoDiagnostic};
+use rustc_session::errors::ExprParenthesesNeeded;
 use rustc_span::source_map::Spanned;
 use rustc_span::symbol::{kw, sym, Ident};
 use rustc_span::{Span, SpanSnippetError, DUMMY_SP};
@@ -2049,7 +2050,7 @@ impl<'a> Parser<'a> {
         let mut err = self.struct_span_err(span, &msg);
         let sp = self.sess.source_map().start_point(self.token.span);
         if let Some(sp) = self.sess.ambiguous_block_expr_parse.borrow().get(&sp) {
-            self.sess.expr_parentheses_needed(&mut err, *sp);
+            err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp));
         }
         err.span_label(span, "expected expression");
         err
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index 8b328e593ae..11301f03e48 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -1310,7 +1310,7 @@ impl<'a> Parser<'a> {
                 // If the input is something like `if a { 1 } else { 2 } | if a { 3 } else { 4 }`
                 // then suggest parens around the lhs.
                 if let Some(sp) = self.sess.ambiguous_block_expr_parse.borrow().get(&lo) {
-                    self.sess.expr_parentheses_needed(&mut err, *sp);
+                    err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp));
                 }
                 err
             })
diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs
index 542a1ac5dc6..0250b518243 100644
--- a/compiler/rustc_parse/src/parser/pat.rs
+++ b/compiler/rustc_parse/src/parser/pat.rs
@@ -10,6 +10,7 @@ use rustc_ast::{
 };
 use rustc_ast_pretty::pprust;
 use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder, ErrorGuaranteed, PResult};
+use rustc_session::errors::ExprParenthesesNeeded;
 use rustc_span::source_map::{respan, Span, Spanned};
 use rustc_span::symbol::{kw, sym, Ident};
 
@@ -693,7 +694,7 @@ impl<'a> Parser<'a> {
 
         let sp = self.sess.source_map().start_point(self.token.span);
         if let Some(sp) = self.sess.ambiguous_block_expr_parse.borrow().get(&sp) {
-            self.sess.expr_parentheses_needed(&mut err, *sp);
+            err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp));
         }
 
         Err(err)
diff --git a/compiler/rustc_query_impl/src/keys.rs b/compiler/rustc_query_impl/src/keys.rs
index 47762440e29..cdbf734cdbe 100644
--- a/compiler/rustc_query_impl/src/keys.rs
+++ b/compiler/rustc_query_impl/src/keys.rs
@@ -1,7 +1,7 @@
 //! Defines the set of legal keys that can be used in queries.
 
 use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, LOCAL_CRATE};
-use rustc_hir::hir_id::OwnerId;
+use rustc_hir::hir_id::{HirId, OwnerId};
 use rustc_middle::infer::canonical::Canonical;
 use rustc_middle::mir;
 use rustc_middle::traits;
@@ -557,3 +557,19 @@ impl<'tcx> Key for (Ty<'tcx>, ty::ValTree<'tcx>) {
         DUMMY_SP
     }
 }
+
+impl Key for HirId {
+    #[inline(always)]
+    fn query_crate_is_local(&self) -> bool {
+        true
+    }
+
+    fn default_span(&self, tcx: TyCtxt<'_>) -> Span {
+        tcx.hir().span(*self)
+    }
+
+    #[inline(always)]
+    fn key_as_def_id(&self) -> Option<DefId> {
+        None
+    }
+}
diff --git a/compiler/rustc_query_system/src/dep_graph/mod.rs b/compiler/rustc_query_system/src/dep_graph/mod.rs
index 5003a14b910..da2075fd5aa 100644
--- a/compiler/rustc_query_system/src/dep_graph/mod.rs
+++ b/compiler/rustc_query_system/src/dep_graph/mod.rs
@@ -94,6 +94,8 @@ impl<T: DepContext> HasDepContext for T {
 pub enum FingerprintStyle {
     /// The fingerprint is actually a DefPathHash.
     DefPathHash,
+    /// The fingerprint is actually a HirId.
+    HirId,
     /// Query key was `()` or equivalent, so fingerprint is just zero.
     Unit,
     /// Some opaque hash.
@@ -104,7 +106,9 @@ impl FingerprintStyle {
     #[inline]
     pub fn reconstructible(self) -> bool {
         match self {
-            FingerprintStyle::DefPathHash | FingerprintStyle::Unit => true,
+            FingerprintStyle::DefPathHash | FingerprintStyle::Unit | FingerprintStyle::HirId => {
+                true
+            }
             FingerprintStyle::Opaque => false,
         }
     }
diff --git a/compiler/rustc_session/src/parse.rs b/compiler/rustc_session/src/parse.rs
index d97e1df2a16..2c3d8d5283b 100644
--- a/compiler/rustc_session/src/parse.rs
+++ b/compiler/rustc_session/src/parse.rs
@@ -2,9 +2,7 @@
 //! It also serves as an input to the parser itself.
 
 use crate::config::CheckCfg;
-use crate::errors::{
-    ExprParenthesesNeeded, FeatureDiagnosticForIssue, FeatureDiagnosticHelp, FeatureGateError,
-};
+use crate::errors::{FeatureDiagnosticForIssue, FeatureDiagnosticHelp, FeatureGateError};
 use crate::lint::{
     builtin::UNSTABLE_SYNTAX_PRE_EXPANSION, BufferedEarlyLint, BuiltinLintDiagnostics, Lint, LintId,
 };
@@ -13,8 +11,8 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
 use rustc_data_structures::sync::{Lock, Lrc};
 use rustc_errors::{emitter::SilentEmitter, ColorConfig, Handler};
 use rustc_errors::{
-    fallback_fluent_bundle, AddToDiagnostic, Diagnostic, DiagnosticBuilder, DiagnosticId,
-    DiagnosticMessage, EmissionGuarantee, ErrorGuaranteed, IntoDiagnostic, MultiSpan, StashKey,
+    fallback_fluent_bundle, Diagnostic, DiagnosticBuilder, DiagnosticId, DiagnosticMessage,
+    EmissionGuarantee, ErrorGuaranteed, IntoDiagnostic, MultiSpan, StashKey,
 };
 use rustc_feature::{find_feature_issue, GateIssue, UnstableFeatures};
 use rustc_span::edition::Edition;
@@ -324,12 +322,6 @@ impl ParseSess {
         });
     }
 
-    /// Extend an error with a suggestion to wrap an expression with parentheses to allow the
-    /// parser to continue parsing the following operation as part of the same expression.
-    pub fn expr_parentheses_needed(&self, err: &mut Diagnostic, span: Span) {
-        ExprParenthesesNeeded::surrounding(span).add_to_diagnostic(err);
-    }
-
     pub fn save_proc_macro_span(&self, span: Span) -> usize {
         let mut spans = self.proc_macro_quoted_spans.lock();
         spans.push(span);
diff --git a/compiler/rustc_target/src/spec/i386_apple_ios.rs b/compiler/rustc_target/src/spec/i386_apple_ios.rs
index 8b6266c5800..b85214a9c6b 100644
--- a/compiler/rustc_target/src/spec/i386_apple_ios.rs
+++ b/compiler/rustc_target/src/spec/i386_apple_ios.rs
@@ -14,8 +14,7 @@ pub fn target() -> Target {
         arch: "x86".into(),
         options: TargetOptions {
             max_atomic_width: Some(64),
-            // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-            stack_probes: StackProbeType::Call,
+            stack_probes: StackProbeType::X86,
             ..base
         },
     }
diff --git a/compiler/rustc_target/src/spec/i686_apple_darwin.rs b/compiler/rustc_target/src/spec/i686_apple_darwin.rs
index 5e9ceb844f7..99b9d88e642 100644
--- a/compiler/rustc_target/src/spec/i686_apple_darwin.rs
+++ b/compiler/rustc_target/src/spec/i686_apple_darwin.rs
@@ -7,8 +7,7 @@ pub fn target() -> Target {
     base.max_atomic_width = Some(64);
     base.add_pre_link_args(LinkerFlavor::Gcc, &["-m32"]);
     base.link_env_remove.to_mut().extend(super::apple_base::macos_link_env_remove());
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
     base.frame_pointer = FramePointer::Always;
 
     // Clang automatically chooses a more specific target based on
diff --git a/compiler/rustc_target/src/spec/i686_linux_android.rs b/compiler/rustc_target/src/spec/i686_linux_android.rs
index bdaf5c99069..c7c30c23901 100644
--- a/compiler/rustc_target/src/spec/i686_linux_android.rs
+++ b/compiler/rustc_target/src/spec/i686_linux_android.rs
@@ -11,8 +11,7 @@ pub fn target() -> Target {
     // https://developer.android.com/ndk/guides/abis.html#x86
     base.cpu = "pentiumpro".into();
     base.features = "+mmx,+sse,+sse2,+sse3,+ssse3".into();
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
 
     Target {
         llvm_target: "i686-linux-android".into(),
diff --git a/compiler/rustc_target/src/spec/i686_unknown_freebsd.rs b/compiler/rustc_target/src/spec/i686_unknown_freebsd.rs
index aff284bf2bc..7d201245006 100644
--- a/compiler/rustc_target/src/spec/i686_unknown_freebsd.rs
+++ b/compiler/rustc_target/src/spec/i686_unknown_freebsd.rs
@@ -5,8 +5,7 @@ pub fn target() -> Target {
     base.cpu = "pentium4".into();
     base.max_atomic_width = Some(64);
     base.add_pre_link_args(LinkerFlavor::Gcc, &["-m32", "-Wl,-znotext"]);
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
 
     Target {
         llvm_target: "i686-unknown-freebsd".into(),
diff --git a/compiler/rustc_target/src/spec/i686_unknown_haiku.rs b/compiler/rustc_target/src/spec/i686_unknown_haiku.rs
index 87aa74e406c..357cc547fa0 100644
--- a/compiler/rustc_target/src/spec/i686_unknown_haiku.rs
+++ b/compiler/rustc_target/src/spec/i686_unknown_haiku.rs
@@ -5,8 +5,7 @@ pub fn target() -> Target {
     base.cpu = "pentium4".into();
     base.max_atomic_width = Some(64);
     base.add_pre_link_args(LinkerFlavor::Gcc, &["-m32"]);
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
 
     Target {
         llvm_target: "i686-unknown-haiku".into(),
diff --git a/compiler/rustc_target/src/spec/i686_unknown_linux_gnu.rs b/compiler/rustc_target/src/spec/i686_unknown_linux_gnu.rs
index f62029c9067..bb7b5680298 100644
--- a/compiler/rustc_target/src/spec/i686_unknown_linux_gnu.rs
+++ b/compiler/rustc_target/src/spec/i686_unknown_linux_gnu.rs
@@ -6,8 +6,7 @@ pub fn target() -> Target {
     base.max_atomic_width = Some(64);
     base.supported_sanitizers = SanitizerSet::ADDRESS;
     base.add_pre_link_args(LinkerFlavor::Gcc, &["-m32"]);
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
 
     Target {
         llvm_target: "i686-unknown-linux-gnu".into(),
diff --git a/compiler/rustc_target/src/spec/i686_unknown_linux_musl.rs b/compiler/rustc_target/src/spec/i686_unknown_linux_musl.rs
index d9492804349..f6047919674 100644
--- a/compiler/rustc_target/src/spec/i686_unknown_linux_musl.rs
+++ b/compiler/rustc_target/src/spec/i686_unknown_linux_musl.rs
@@ -5,8 +5,7 @@ pub fn target() -> Target {
     base.cpu = "pentium4".into();
     base.max_atomic_width = Some(64);
     base.add_pre_link_args(LinkerFlavor::Gcc, &["-m32", "-Wl,-melf_i386"]);
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
 
     // The unwinder used by i686-unknown-linux-musl, the LLVM libunwind
     // implementation, apparently relies on frame pointers existing... somehow.
diff --git a/compiler/rustc_target/src/spec/i686_unknown_netbsd.rs b/compiler/rustc_target/src/spec/i686_unknown_netbsd.rs
index 8de698b51f0..0fd2d1231df 100644
--- a/compiler/rustc_target/src/spec/i686_unknown_netbsd.rs
+++ b/compiler/rustc_target/src/spec/i686_unknown_netbsd.rs
@@ -5,8 +5,7 @@ pub fn target() -> Target {
     base.cpu = "pentium4".into();
     base.max_atomic_width = Some(64);
     base.add_pre_link_args(LinkerFlavor::Gcc, &["-m32"]);
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
 
     Target {
         llvm_target: "i686-unknown-netbsdelf".into(),
diff --git a/compiler/rustc_target/src/spec/i686_unknown_openbsd.rs b/compiler/rustc_target/src/spec/i686_unknown_openbsd.rs
index 7f25a1a16c1..2952c043daa 100644
--- a/compiler/rustc_target/src/spec/i686_unknown_openbsd.rs
+++ b/compiler/rustc_target/src/spec/i686_unknown_openbsd.rs
@@ -5,8 +5,7 @@ pub fn target() -> Target {
     base.cpu = "pentium4".into();
     base.max_atomic_width = Some(64);
     base.add_pre_link_args(LinkerFlavor::Gcc, &["-m32", "-fuse-ld=lld"]);
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
 
     Target {
         llvm_target: "i686-unknown-openbsd".into(),
diff --git a/compiler/rustc_target/src/spec/i686_wrs_vxworks.rs b/compiler/rustc_target/src/spec/i686_wrs_vxworks.rs
index f62404e8279..4a0d98efd82 100644
--- a/compiler/rustc_target/src/spec/i686_wrs_vxworks.rs
+++ b/compiler/rustc_target/src/spec/i686_wrs_vxworks.rs
@@ -5,8 +5,7 @@ pub fn target() -> Target {
     base.cpu = "pentium4".into();
     base.max_atomic_width = Some(64);
     base.add_pre_link_args(LinkerFlavor::Gcc, &["-m32"]);
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
 
     Target {
         llvm_target: "i686-unknown-linux-gnu".into(),
diff --git a/compiler/rustc_target/src/spec/linux_kernel_base.rs b/compiler/rustc_target/src/spec/linux_kernel_base.rs
index 0f5d85205f8..f41533a9548 100644
--- a/compiler/rustc_target/src/spec/linux_kernel_base.rs
+++ b/compiler/rustc_target/src/spec/linux_kernel_base.rs
@@ -6,8 +6,7 @@ pub fn opts() -> TargetOptions {
         env: "gnu".into(),
         disable_redzone: true,
         panic_strategy: PanicStrategy::Abort,
-        // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-        stack_probes: StackProbeType::Call,
+        stack_probes: StackProbeType::X86,
         frame_pointer: FramePointer::Always,
         position_independent_executables: true,
         needs_plt: true,
diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs
index 35b4cce50a0..af85e2f4feb 100644
--- a/compiler/rustc_target/src/spec/mod.rs
+++ b/compiler/rustc_target/src/spec/mod.rs
@@ -635,6 +635,10 @@ pub enum StackProbeType {
 }
 
 impl StackProbeType {
+    // LLVM X86 targets (ix86 and x86_64) can use inline-asm stack probes starting with LLVM 16.
+    // Notable past issues were rust#83139 (fixed in 14) and rust#84667 (fixed in 16).
+    const X86: Self = Self::InlineOrCall { min_llvm_version_for_inline: (16, 0, 0) };
+
     fn from_json(json: &Json) -> Result<Self, String> {
         let object = json.as_object().ok_or_else(|| "expected a JSON object")?;
         let kind = object
diff --git a/compiler/rustc_target/src/spec/x86_64_apple_darwin.rs b/compiler/rustc_target/src/spec/x86_64_apple_darwin.rs
index 176c9dd6b76..ad96923320c 100644
--- a/compiler/rustc_target/src/spec/x86_64_apple_darwin.rs
+++ b/compiler/rustc_target/src/spec/x86_64_apple_darwin.rs
@@ -9,8 +9,7 @@ pub fn target() -> Target {
     base.frame_pointer = FramePointer::Always;
     base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
     base.link_env_remove.to_mut().extend(super::apple_base::macos_link_env_remove());
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
     base.supported_sanitizers =
         SanitizerSet::ADDRESS | SanitizerSet::CFI | SanitizerSet::LEAK | SanitizerSet::THREAD;
 
diff --git a/compiler/rustc_target/src/spec/x86_64_apple_ios.rs b/compiler/rustc_target/src/spec/x86_64_apple_ios.rs
index 5e64ed0cff6..e6143025d6d 100644
--- a/compiler/rustc_target/src/spec/x86_64_apple_ios.rs
+++ b/compiler/rustc_target/src/spec/x86_64_apple_ios.rs
@@ -13,8 +13,7 @@ pub fn target() -> Target {
         arch: "x86_64".into(),
         options: TargetOptions {
             max_atomic_width: Some(64),
-            // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-            stack_probes: StackProbeType::Call,
+            stack_probes: StackProbeType::X86,
             ..base
         },
     }
diff --git a/compiler/rustc_target/src/spec/x86_64_apple_ios_macabi.rs b/compiler/rustc_target/src/spec/x86_64_apple_ios_macabi.rs
index 2122bcd37fc..61591dacf45 100644
--- a/compiler/rustc_target/src/spec/x86_64_apple_ios_macabi.rs
+++ b/compiler/rustc_target/src/spec/x86_64_apple_ios_macabi.rs
@@ -15,8 +15,7 @@ pub fn target() -> Target {
         arch: "x86_64".into(),
         options: TargetOptions {
             max_atomic_width: Some(64),
-            // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-            stack_probes: StackProbeType::Call,
+            stack_probes: StackProbeType::X86,
             ..base
         },
     }
diff --git a/compiler/rustc_target/src/spec/x86_64_apple_tvos.rs b/compiler/rustc_target/src/spec/x86_64_apple_tvos.rs
index a848c5a0aff..3d54da0867c 100644
--- a/compiler/rustc_target/src/spec/x86_64_apple_tvos.rs
+++ b/compiler/rustc_target/src/spec/x86_64_apple_tvos.rs
@@ -10,8 +10,7 @@ pub fn target() -> Target {
         arch: "x86_64".into(),
         options: TargetOptions {
             max_atomic_width: Some(64),
-            // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-            stack_probes: StackProbeType::Call,
+            stack_probes: StackProbeType::X86,
             ..base
         },
     }
diff --git a/compiler/rustc_target/src/spec/x86_64_apple_watchos_sim.rs b/compiler/rustc_target/src/spec/x86_64_apple_watchos_sim.rs
index 4dff3c2f209..e499b1985e7 100644
--- a/compiler/rustc_target/src/spec/x86_64_apple_watchos_sim.rs
+++ b/compiler/rustc_target/src/spec/x86_64_apple_watchos_sim.rs
@@ -15,8 +15,7 @@ pub fn target() -> Target {
         arch: "x86_64".into(),
         options: TargetOptions {
             max_atomic_width: Some(64),
-            // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-            stack_probes: StackProbeType::Call,
+            stack_probes: StackProbeType::X86,
             forces_embed_bitcode: true,
             // Taken from a clang build on Xcode 11.4.1.
             // These arguments are not actually invoked - they just have
diff --git a/compiler/rustc_target/src/spec/x86_64_fuchsia.rs b/compiler/rustc_target/src/spec/x86_64_fuchsia.rs
index 4f88fc3500b..532dd6d0742 100644
--- a/compiler/rustc_target/src/spec/x86_64_fuchsia.rs
+++ b/compiler/rustc_target/src/spec/x86_64_fuchsia.rs
@@ -4,8 +4,7 @@ pub fn target() -> Target {
     let mut base = super::fuchsia_base::opts();
     base.cpu = "x86-64".into();
     base.max_atomic_width = Some(64);
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
     base.supported_sanitizers = SanitizerSet::ADDRESS | SanitizerSet::CFI;
 
     Target {
diff --git a/compiler/rustc_target/src/spec/x86_64_linux_android.rs b/compiler/rustc_target/src/spec/x86_64_linux_android.rs
index 6d19cf26574..4db5ec7fd28 100644
--- a/compiler/rustc_target/src/spec/x86_64_linux_android.rs
+++ b/compiler/rustc_target/src/spec/x86_64_linux_android.rs
@@ -7,8 +7,7 @@ pub fn target() -> Target {
     base.features = "+mmx,+sse,+sse2,+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt".into();
     base.max_atomic_width = Some(64);
     base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
 
     Target {
         llvm_target: "x86_64-linux-android".into(),
diff --git a/compiler/rustc_target/src/spec/x86_64_pc_solaris.rs b/compiler/rustc_target/src/spec/x86_64_pc_solaris.rs
index 0550b221fd9..974359a138b 100644
--- a/compiler/rustc_target/src/spec/x86_64_pc_solaris.rs
+++ b/compiler/rustc_target/src/spec/x86_64_pc_solaris.rs
@@ -6,8 +6,7 @@ pub fn target() -> Target {
     base.cpu = "x86-64".into();
     base.vendor = "pc".into();
     base.max_atomic_width = Some(64);
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
     base.supported_sanitizers = SanitizerSet::ADDRESS | SanitizerSet::CFI;
 
     Target {
diff --git a/compiler/rustc_target/src/spec/x86_64_sun_solaris.rs b/compiler/rustc_target/src/spec/x86_64_sun_solaris.rs
index cbe87589a70..a2fe371a2b8 100644
--- a/compiler/rustc_target/src/spec/x86_64_sun_solaris.rs
+++ b/compiler/rustc_target/src/spec/x86_64_sun_solaris.rs
@@ -6,8 +6,7 @@ pub fn target() -> Target {
     base.cpu = "x86-64".into();
     base.vendor = "sun".into();
     base.max_atomic_width = Some(64);
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
 
     Target {
         llvm_target: "x86_64-pc-solaris".into(),
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_dragonfly.rs b/compiler/rustc_target/src/spec/x86_64_unknown_dragonfly.rs
index 746f6478178..989e6432b66 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_dragonfly.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_dragonfly.rs
@@ -5,8 +5,7 @@ pub fn target() -> Target {
     base.cpu = "x86-64".into();
     base.max_atomic_width = Some(64);
     base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
 
     Target {
         llvm_target: "x86_64-unknown-dragonfly".into(),
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_freebsd.rs b/compiler/rustc_target/src/spec/x86_64_unknown_freebsd.rs
index b30784ed692..24b5b4beebc 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_freebsd.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_freebsd.rs
@@ -5,8 +5,7 @@ pub fn target() -> Target {
     base.cpu = "x86-64".into();
     base.max_atomic_width = Some(64);
     base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
     base.supported_sanitizers =
         SanitizerSet::ADDRESS | SanitizerSet::CFI | SanitizerSet::MEMORY | SanitizerSet::THREAD;
 
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_haiku.rs b/compiler/rustc_target/src/spec/x86_64_unknown_haiku.rs
index d6d03362982..e3f14aeeea9 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_haiku.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_haiku.rs
@@ -5,8 +5,7 @@ pub fn target() -> Target {
     base.cpu = "x86-64".into();
     base.max_atomic_width = Some(64);
     base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
     // This option is required to build executables on Haiku x86_64
     base.position_independent_executables = true;
 
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_hermit.rs b/compiler/rustc_target/src/spec/x86_64_unknown_hermit.rs
index d315301615b..fb1af33f80a 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_hermit.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_hermit.rs
@@ -5,8 +5,7 @@ pub fn target() -> Target {
     base.cpu = "x86-64".into();
     base.max_atomic_width = Some(64);
     base.features = "+rdrnd,+rdseed".into();
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
 
     Target {
         llvm_target: "x86_64-unknown-hermit".into(),
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs b/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs
index 956be0353fa..34e20544da6 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs
@@ -5,8 +5,7 @@ pub fn target() -> Target {
     base.cpu = "x86-64".into();
     base.max_atomic_width = Some(64);
     base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
     base.static_position_independent_executables = true;
     base.supported_sanitizers = SanitizerSet::ADDRESS
         | SanitizerSet::CFI
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnux32.rs b/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnux32.rs
index 140882747c2..23a1f5d80f2 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnux32.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnux32.rs
@@ -6,8 +6,7 @@ pub fn target() -> Target {
     base.abi = "x32".into();
     base.max_atomic_width = Some(64);
     base.add_pre_link_args(LinkerFlavor::Gcc, &["-mx32"]);
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
     base.has_thread_local = false;
     // BUG(GabrielMajeri): disabling the PLT on x86_64 Linux with x32 ABI
     // breaks code gen. See LLVM bug 36743
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs b/compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs
index 87e7784d1f9..179f0995456 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs
@@ -5,8 +5,7 @@ pub fn target() -> Target {
     base.cpu = "x86-64".into();
     base.max_atomic_width = Some(64);
     base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
     base.static_position_independent_executables = true;
     base.supported_sanitizers = SanitizerSet::ADDRESS
         | SanitizerSet::CFI
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_netbsd.rs b/compiler/rustc_target/src/spec/x86_64_unknown_netbsd.rs
index d3a67619aa8..ac77dfb6415 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_netbsd.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_netbsd.rs
@@ -5,8 +5,7 @@ pub fn target() -> Target {
     base.cpu = "x86-64".into();
     base.max_atomic_width = Some(64);
     base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
     base.supported_sanitizers = SanitizerSet::ADDRESS
         | SanitizerSet::CFI
         | SanitizerSet::LEAK
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_none.rs b/compiler/rustc_target/src/spec/x86_64_unknown_none.rs
index b9a345127e3..871cdd02078 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_none.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_none.rs
@@ -11,8 +11,7 @@ pub fn target() -> Target {
     let opts = TargetOptions {
         cpu: "x86-64".into(),
         max_atomic_width: Some(64),
-        // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-        stack_probes: StackProbeType::Call,
+        stack_probes: StackProbeType::X86,
         position_independent_executables: true,
         static_position_independent_executables: true,
         relro_level: RelroLevel::Full,
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_openbsd.rs b/compiler/rustc_target/src/spec/x86_64_unknown_openbsd.rs
index f50c6bceec9..b8084d513f7 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_openbsd.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_openbsd.rs
@@ -5,8 +5,7 @@ pub fn target() -> Target {
     base.cpu = "x86-64".into();
     base.max_atomic_width = Some(64);
     base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
 
     Target {
         llvm_target: "x86_64-unknown-openbsd".into(),
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_redox.rs b/compiler/rustc_target/src/spec/x86_64_unknown_redox.rs
index 668ae905417..a2a143f856d 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_redox.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_redox.rs
@@ -5,8 +5,7 @@ pub fn target() -> Target {
     base.cpu = "x86-64".into();
     base.max_atomic_width = Some(64);
     base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
 
     Target {
         llvm_target: "x86_64-unknown-redox".into(),
diff --git a/compiler/rustc_target/src/spec/x86_64_wrs_vxworks.rs b/compiler/rustc_target/src/spec/x86_64_wrs_vxworks.rs
index 1298974952f..187027d3889 100644
--- a/compiler/rustc_target/src/spec/x86_64_wrs_vxworks.rs
+++ b/compiler/rustc_target/src/spec/x86_64_wrs_vxworks.rs
@@ -5,8 +5,7 @@ pub fn target() -> Target {
     base.cpu = "x86-64".into();
     base.max_atomic_width = Some(64);
     base.add_pre_link_args(LinkerFlavor::Gcc, &["-m64"]);
-    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
-    base.stack_probes = StackProbeType::Call;
+    base.stack_probes = StackProbeType::X86;
     base.disable_redzone = true;
 
     Target {