Skip to content

Commit fe03b46

Browse files
committed
Auto merge of #113609 - nnethercote:maybe_lint_level_root_bounded-cache, r=cjgillot
Add a cache for `maybe_lint_level_root_bounded` `maybe_lint_level_root_bounded` is called many times and traces node sub-paths many times. This PR adds a cache that lets many of these tracings be skipped, avoiding lots of calls to functions like `Map::attrs` and `Map::parent_id`. r? `@cjgillot`
2 parents 7d60819 + 667d75e commit fe03b46

File tree

3 files changed

+71
-32
lines changed

3 files changed

+71
-32
lines changed

compiler/rustc_middle/src/lint.rs

-20
Original file line numberDiff line numberDiff line change
@@ -169,26 +169,6 @@ impl TyCtxt<'_> {
169169
pub fn lint_level_at_node(self, lint: &'static Lint, id: HirId) -> (Level, LintLevelSource) {
170170
self.shallow_lint_levels_on(id.owner).lint_level_id_at_node(self, LintId::of(lint), id)
171171
}
172-
173-
/// Walks upwards from `id` to find a node which might change lint levels with attributes.
174-
/// It stops at `bound` and just returns it if reached.
175-
pub fn maybe_lint_level_root_bounded(self, mut id: HirId, bound: HirId) -> HirId {
176-
let hir = self.hir();
177-
loop {
178-
if id == bound {
179-
return bound;
180-
}
181-
182-
if hir.attrs(id).iter().any(|attr| Level::from_attr(attr).is_some()) {
183-
return id;
184-
}
185-
let next = hir.parent_id(id);
186-
if next == id {
187-
bug!("lint traversal reached the root of the crate");
188-
}
189-
id = next;
190-
}
191-
}
192172
}
193173

194174
/// This struct represents a lint expectation and holds all required information

compiler/rustc_mir_build/src/build/mod.rs

+10
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use rustc_hir as hir;
1010
use rustc_hir::def::DefKind;
1111
use rustc_hir::def_id::{DefId, LocalDefId};
1212
use rustc_hir::{GeneratorKind, Node};
13+
use rustc_index::bit_set::GrowableBitSet;
1314
use rustc_index::{Idx, IndexSlice, IndexVec};
1415
use rustc_infer::infer::{InferCtxt, TyCtxtInferExt};
1516
use rustc_middle::hir::place::PlaceBase as HirPlaceBase;
@@ -215,6 +216,14 @@ struct Builder<'a, 'tcx> {
215216
unit_temp: Option<Place<'tcx>>,
216217

217218
var_debug_info: Vec<VarDebugInfo<'tcx>>,
219+
220+
// A cache for `maybe_lint_level_roots_bounded`. That function is called
221+
// repeatedly, and each time it effectively traces a path through a tree
222+
// structure from a node towards the root, doing an attribute check on each
223+
// node along the way. This cache records which nodes trace all the way to
224+
// the root (most of them do) and saves us from retracing many sub-paths
225+
// many times, and rechecking many nodes.
226+
lint_level_roots_cache: GrowableBitSet<hir::ItemLocalId>,
218227
}
219228

220229
type CaptureMap<'tcx> = SortedIndexMultiMap<usize, hir::HirId, Capture<'tcx>>;
@@ -725,6 +734,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
725734
var_indices: Default::default(),
726735
unit_temp: None,
727736
var_debug_info: vec![],
737+
lint_level_roots_cache: GrowableBitSet::new_empty(),
728738
};
729739

730740
assert_eq!(builder.cfg.start_new_block(), START_BLOCK);

compiler/rustc_mir_build/src/build/scope.rs

+61-12
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ use rustc_index::{IndexSlice, IndexVec};
9090
use rustc_middle::middle::region;
9191
use rustc_middle::mir::*;
9292
use rustc_middle::thir::{Expr, LintLevel};
93-
9493
use rustc_middle::ty::Ty;
94+
use rustc_session::lint::Level;
9595
use rustc_span::{Span, DUMMY_SP};
9696

9797
#[derive(Debug)]
@@ -760,20 +760,25 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
760760
) {
761761
let (current_root, parent_root) =
762762
if self.tcx.sess.opts.unstable_opts.maximal_hir_to_mir_coverage {
763-
// Some consumers of rustc need to map MIR locations back to HIR nodes. Currently the
764-
// the only part of rustc that tracks MIR -> HIR is the `SourceScopeLocalData::lint_root`
765-
// field that tracks lint levels for MIR locations. Normally the number of source scopes
766-
// is limited to the set of nodes with lint annotations. The -Zmaximal-hir-to-mir-coverage
767-
// flag changes this behavior to maximize the number of source scopes, increasing the
768-
// granularity of the MIR->HIR mapping.
763+
// Some consumers of rustc need to map MIR locations back to HIR nodes. Currently
764+
// the the only part of rustc that tracks MIR -> HIR is the
765+
// `SourceScopeLocalData::lint_root` field that tracks lint levels for MIR
766+
// locations. Normally the number of source scopes is limited to the set of nodes
767+
// with lint annotations. The -Zmaximal-hir-to-mir-coverage flag changes this
768+
// behavior to maximize the number of source scopes, increasing the granularity of
769+
// the MIR->HIR mapping.
769770
(current_id, parent_id)
770771
} else {
771-
// Use `maybe_lint_level_root_bounded` with `self.hir_id` as a bound
772-
// to avoid adding Hir dependencies on our parents.
773-
// We estimate the true lint roots here to avoid creating a lot of source scopes.
772+
// Use `maybe_lint_level_root_bounded` to avoid adding Hir dependencies on our
773+
// parents. We estimate the true lint roots here to avoid creating a lot of source
774+
// scopes.
774775
(
775-
self.tcx.maybe_lint_level_root_bounded(current_id, self.hir_id),
776-
self.tcx.maybe_lint_level_root_bounded(parent_id, self.hir_id),
776+
self.maybe_lint_level_root_bounded(current_id),
777+
if parent_id == self.hir_id {
778+
parent_id // this is very common
779+
} else {
780+
self.maybe_lint_level_root_bounded(parent_id)
781+
},
777782
)
778783
};
779784

@@ -783,6 +788,50 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
783788
}
784789
}
785790

791+
/// Walks upwards from `orig_id` to find a node which might change lint levels with attributes.
792+
/// It stops at `self.hir_id` and just returns it if reached.
793+
fn maybe_lint_level_root_bounded(&mut self, orig_id: HirId) -> HirId {
794+
// This assertion lets us just store `ItemLocalId` in the cache, rather
795+
// than the full `HirId`.
796+
assert_eq!(orig_id.owner, self.hir_id.owner);
797+
798+
let mut id = orig_id;
799+
let hir = self.tcx.hir();
800+
loop {
801+
if id == self.hir_id {
802+
// This is a moderately common case, mostly hit for previously unseen nodes.
803+
break;
804+
}
805+
806+
if hir.attrs(id).iter().any(|attr| Level::from_attr(attr).is_some()) {
807+
// This is a rare case. It's for a node path that doesn't reach the root due to an
808+
// intervening lint level attribute. This result doesn't get cached.
809+
return id;
810+
}
811+
812+
let next = hir.parent_id(id);
813+
if next == id {
814+
bug!("lint traversal reached the root of the crate");
815+
}
816+
id = next;
817+
818+
// This lookup is just an optimization; it can be removed without affecting
819+
// functionality. It might seem strange to see this at the end of this loop, but the
820+
// `orig_id` passed in to this function is almost always previously unseen, for which a
821+
// lookup will be a miss. So we only do lookups for nodes up the parent chain, where
822+
// cache lookups have a very high hit rate.
823+
if self.lint_level_roots_cache.contains(id.local_id) {
824+
break;
825+
}
826+
}
827+
828+
// `orig_id` traced to `self_id`; record this fact. If `orig_id` is a leaf node it will
829+
// rarely (never?) subsequently be searched for, but it's hard to know if that is the case.
830+
// The performance wins from the cache all come from caching non-leaf nodes.
831+
self.lint_level_roots_cache.insert(orig_id.local_id);
832+
self.hir_id
833+
}
834+
786835
/// Creates a new source scope, nested in the current one.
787836
pub(crate) fn new_source_scope(
788837
&mut self,

0 commit comments

Comments
 (0)