Skip to content

Commit 1b52388

Browse files
committed
Auto merge of #152086 - nnethercote:stable-hashing-cleanups, r=<try>
Stable hashing cleanups
2 parents 1d05e3c + 3327b70 commit 1b52388

File tree

12 files changed

+194
-235
lines changed

12 files changed

+194
-235
lines changed

Cargo.lock

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4594,7 +4594,6 @@ dependencies = [
45944594
"rustc_macros",
45954595
"rustc_metadata",
45964596
"rustc_middle",
4597-
"rustc_query_system",
45984597
"rustc_session",
45994598
"rustc_span",
46004599
"smallvec",

compiler/rustc_data_structures/src/stable_hasher.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,7 @@ where
607607
/// result (for example, using a `Fingerprint` produced while
608608
/// hashing `Span`s when a `Fingerprint` without `Span`s is
609609
/// being requested)
610-
#[derive(Clone, Hash, Eq, PartialEq, Debug)]
610+
#[derive(Clone, Copy, Hash, Eq, PartialEq, Debug)]
611611
pub struct HashingControls {
612612
pub hash_spans: bool,
613613
}

compiler/rustc_hir/src/stable_hash_impls.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ use crate::lints::DelayedLints;
1111
/// Requirements for a `StableHashingContext` to be used in this crate.
1212
/// This is a hack to allow using the `HashStable_Generic` derive macro
1313
/// instead of implementing everything in `rustc_middle`.
14-
pub trait HashStableContext: rustc_ast::HashStableContext + rustc_abi::HashStableContext {
15-
fn hash_attr_id(&mut self, id: &HashIgnoredAttrId, hasher: &mut StableHasher);
16-
}
14+
pub trait HashStableContext: rustc_ast::HashStableContext + rustc_abi::HashStableContext {}
1715

1816
impl<HirCtx: crate::HashStableContext> ToStableHashKey<HirCtx> for BodyId {
1917
type KeyType = (DefPathHash, ItemLocalId);
@@ -104,7 +102,7 @@ impl<HirCtx: crate::HashStableContext> HashStable<HirCtx> for Crate<'_> {
104102
}
105103

106104
impl<HirCtx: crate::HashStableContext> HashStable<HirCtx> for HashIgnoredAttrId {
107-
fn hash_stable(&self, hcx: &mut HirCtx, hasher: &mut StableHasher) {
108-
hcx.hash_attr_id(self, hasher)
105+
fn hash_stable(&self, _hcx: &mut HirCtx, _hasher: &mut StableHasher) {
106+
/* we don't hash HashIgnoredAttrId, we ignore them */
109107
}
110108
}

compiler/rustc_middle/src/ty/impls_ty.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ where
2020
T: HashStable<StableHashingContext<'a>>,
2121
{
2222
fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) {
23+
// Note: this cache makes an *enormous* performance difference on certain benchmarks. E.g.
24+
// without it, compiling `diesel-2.2.10` can be 74% slower, and compiling
25+
// `deeply-nested-multi` can be ~4,000x slower(!)
2326
thread_local! {
2427
static CACHE: RefCell<FxHashMap<(*const (), HashingControls), Fingerprint>> =
2528
RefCell::new(Default::default());
Lines changed: 126 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
1-
use rustc_ast as ast;
1+
use std::hash::Hash;
2+
23
use rustc_data_structures::stable_hasher::{HashStable, HashingControls, StableHasher};
34
use rustc_hir::def_id::{DefId, LocalDefId};
45
use rustc_hir::definitions::DefPathHash;
56
use rustc_session::Session;
67
use rustc_session::cstore::Untracked;
78
use rustc_span::source_map::SourceMap;
8-
use rustc_span::{BytePos, CachingSourceMapView, DUMMY_SP, SourceFile, Span, SpanData, Symbol};
9+
use rustc_span::{CachingSourceMapView, DUMMY_SP, Pos, Span};
910

10-
use crate::ich;
11+
// Very often, we are hashing something that does not need the `CachingSourceMapView`, so we
12+
// initialize it lazily.
13+
#[derive(Clone)]
14+
enum CachingSourceMap<'a> {
15+
Unused(&'a SourceMap),
16+
InUse(CachingSourceMapView<'a>),
17+
}
1118

1219
/// This is the context state available during incr. comp. hashing. It contains
1320
/// enough information to transform `DefId`s and `HirId`s into stable `DefPath`s (i.e.,
@@ -19,10 +26,7 @@ pub struct StableHashingContext<'a> {
1926
// The value of `-Z incremental-ignore-spans`.
2027
// This field should only be used by `unstable_opts_incremental_ignore_span`
2128
incremental_ignore_spans: bool,
22-
// Very often, we are hashing something that does not need the
23-
// `CachingSourceMapView`, so we initialize it lazily.
24-
raw_source_map: &'a SourceMap,
25-
caching_source_map: Option<CachingSourceMapView<'a>>,
29+
caching_source_map: CachingSourceMap<'a>,
2630
hashing_controls: HashingControls,
2731
}
2832

@@ -34,8 +38,7 @@ impl<'a> StableHashingContext<'a> {
3438
StableHashingContext {
3539
untracked,
3640
incremental_ignore_spans: sess.opts.unstable_opts.incremental_ignore_spans,
37-
caching_source_map: None,
38-
raw_source_map: sess.source_map(),
41+
caching_source_map: CachingSourceMap::Unused(sess.source_map()),
3942
hashing_controls: HashingControls { hash_spans: hash_spans_initial },
4043
}
4144
}
@@ -49,81 +52,143 @@ impl<'a> StableHashingContext<'a> {
4952
}
5053

5154
#[inline]
52-
pub fn def_path_hash(&self, def_id: DefId) -> DefPathHash {
53-
if let Some(def_id) = def_id.as_local() {
54-
self.local_def_path_hash(def_id)
55-
} else {
56-
self.untracked.cstore.read().def_path_hash(def_id)
57-
}
58-
}
59-
60-
#[inline]
61-
pub fn local_def_path_hash(&self, def_id: LocalDefId) -> DefPathHash {
62-
self.untracked.definitions.read().def_path_hash(def_id)
63-
}
64-
65-
#[inline]
66-
pub fn source_map(&mut self) -> &mut CachingSourceMapView<'a> {
55+
fn source_map(&mut self) -> &mut CachingSourceMapView<'a> {
6756
match self.caching_source_map {
68-
Some(ref mut sm) => sm,
69-
ref mut none => {
70-
*none = Some(CachingSourceMapView::new(self.raw_source_map));
71-
none.as_mut().unwrap()
57+
CachingSourceMap::InUse(ref mut sm) => sm,
58+
CachingSourceMap::Unused(sm) => {
59+
self.caching_source_map = CachingSourceMap::InUse(CachingSourceMapView::new(sm));
60+
self.source_map() // this recursive call will hit the `InUse` case
7261
}
7362
}
7463
}
7564

7665
#[inline]
77-
pub fn is_ignored_attr(&self, name: Symbol) -> bool {
78-
ich::IGNORED_ATTRIBUTES.contains(&name)
66+
fn def_span(&self, def_id: LocalDefId) -> Span {
67+
self.untracked.source_span.get(def_id).unwrap_or(DUMMY_SP)
7968
}
8069

8170
#[inline]
8271
pub fn hashing_controls(&self) -> HashingControls {
83-
self.hashing_controls.clone()
84-
}
85-
}
86-
87-
impl<'a> HashStable<StableHashingContext<'a>> for ast::NodeId {
88-
#[inline]
89-
fn hash_stable(&self, _: &mut StableHashingContext<'a>, _: &mut StableHasher) {
90-
panic!("Node IDs should not appear in incremental state");
72+
self.hashing_controls
9173
}
9274
}
9375

9476
impl<'a> rustc_span::HashStableContext for StableHashingContext<'a> {
95-
#[inline]
96-
fn hash_spans(&self) -> bool {
97-
self.hashing_controls.hash_spans
98-
}
77+
/// Hashes a span in a stable way. We can't directly hash the span's `BytePos` fields (that
78+
/// would be similar to hashing pointers, since those are just offsets into the `SourceMap`).
79+
/// Instead, we hash the (file name, line, column) triple, which stays the same even if the
80+
/// containing `SourceFile` has moved within the `SourceMap`.
81+
///
82+
/// Also note that we are hashing byte offsets for the column, not unicode codepoint offsets.
83+
/// For the purpose of the hash that's sufficient. Also, hashing filenames is expensive so we
84+
/// avoid doing it twice when the span starts and ends in the same file, which is almost always
85+
/// the case.
86+
///
87+
/// IMPORTANT: changes to this method should be reflected in implementations of `SpanEncoder`.
88+
fn span_hash_stable(&mut self, span: Span, hasher: &mut StableHasher) {
89+
const TAG_VALID_SPAN: u8 = 0;
90+
const TAG_INVALID_SPAN: u8 = 1;
91+
const TAG_RELATIVE_SPAN: u8 = 2;
92+
93+
if !self.hashing_controls().hash_spans {
94+
return;
95+
}
9996

100-
#[inline]
101-
fn unstable_opts_incremental_ignore_spans(&self) -> bool {
102-
self.incremental_ignore_spans
103-
}
97+
let span = span.data_untracked();
98+
span.ctxt.hash_stable(self, hasher);
99+
span.parent.hash_stable(self, hasher);
104100

105-
#[inline]
106-
fn def_path_hash(&self, def_id: DefId) -> DefPathHash {
107-
self.def_path_hash(def_id)
108-
}
101+
if span.is_dummy() {
102+
Hash::hash(&TAG_INVALID_SPAN, hasher);
103+
return;
104+
}
109105

110-
#[inline]
111-
fn def_span(&self, def_id: LocalDefId) -> Span {
112-
self.untracked.source_span.get(def_id).unwrap_or(DUMMY_SP)
106+
let parent = span.parent.map(|parent| self.def_span(parent).data_untracked());
107+
if let Some(parent) = parent
108+
&& parent.contains(span)
109+
{
110+
// This span is enclosed in a definition: only hash the relative position. This catches
111+
// a subset of the cases from the `file.contains(parent.lo)`. But we can do this check
112+
// cheaply without the expensive `span_data_to_lines_and_cols` query.
113+
Hash::hash(&TAG_RELATIVE_SPAN, hasher);
114+
(span.lo - parent.lo).to_u32().hash_stable(self, hasher);
115+
(span.hi - parent.lo).to_u32().hash_stable(self, hasher);
116+
return;
117+
}
118+
119+
// If this is not an empty or invalid span, we want to hash the last position that belongs
120+
// to it, as opposed to hashing the first position past it.
121+
let Some((file, line_lo, col_lo, line_hi, col_hi)) =
122+
self.source_map().span_data_to_lines_and_cols(&span)
123+
else {
124+
Hash::hash(&TAG_INVALID_SPAN, hasher);
125+
return;
126+
};
127+
128+
if let Some(parent) = parent
129+
&& file.contains(parent.lo)
130+
{
131+
// This span is relative to another span in the same file,
132+
// only hash the relative position.
133+
Hash::hash(&TAG_RELATIVE_SPAN, hasher);
134+
Hash::hash(&(span.lo.0.wrapping_sub(parent.lo.0)), hasher);
135+
Hash::hash(&(span.hi.0.wrapping_sub(parent.lo.0)), hasher);
136+
return;
137+
}
138+
139+
Hash::hash(&TAG_VALID_SPAN, hasher);
140+
Hash::hash(&file.stable_id, hasher);
141+
142+
// Hash both the length and the end location (line/column) of a span. If we hash only the
143+
// length, for example, then two otherwise equal spans with different end locations will
144+
// have the same hash. This can cause a problem during incremental compilation wherein a
145+
// previous result for a query that depends on the end location of a span will be
146+
// incorrectly reused when the end location of the span it depends on has changed (see
147+
// issue #74890). A similar analysis applies if some query depends specifically on the
148+
// length of the span, but we only hash the end location. So hash both.
149+
150+
let col_lo_trunc = (col_lo.0 as u64) & 0xFF;
151+
let line_lo_trunc = ((line_lo as u64) & 0xFF_FF_FF) << 8;
152+
let col_hi_trunc = (col_hi.0 as u64) & 0xFF << 32;
153+
let line_hi_trunc = ((line_hi as u64) & 0xFF_FF_FF) << 40;
154+
let col_line = col_lo_trunc | line_lo_trunc | col_hi_trunc | line_hi_trunc;
155+
let len = (span.hi - span.lo).0;
156+
Hash::hash(&col_line, hasher);
157+
Hash::hash(&len, hasher);
113158
}
114159

115160
#[inline]
116-
fn span_data_to_lines_and_cols(
117-
&mut self,
118-
span: &SpanData,
119-
) -> Option<(&SourceFile, usize, BytePos, usize, BytePos)> {
120-
self.source_map().span_data_to_lines_and_cols(span)
161+
fn def_path_hash(&self, def_id: DefId) -> DefPathHash {
162+
if let Some(def_id) = def_id.as_local() {
163+
self.untracked.definitions.read().def_path_hash(def_id)
164+
} else {
165+
self.untracked.cstore.read().def_path_hash(def_id)
166+
}
121167
}
122168

123-
#[inline]
124-
fn hashing_controls(&self) -> HashingControls {
125-
self.hashing_controls.clone()
169+
/// Assert that the provided `HashStableContext` is configured with the default
170+
/// `HashingControls`. We should always have bailed out before getting to here with a
171+
/// non-default mode. With this check in place, we can avoid the need to maintain separate
172+
/// versions of `ExpnData` hashes for each permutation of `HashingControls` settings.
173+
fn assert_default_hashing_controls(&self, msg: &str) {
174+
let hashing_controls = self.hashing_controls;
175+
let HashingControls { hash_spans } = hashing_controls;
176+
177+
// Note that we require that `hash_spans` be the inverse of the global `-Z
178+
// incremental-ignore-spans` option. Normally, this option is disabled, in which case
179+
// `hash_spans` must be true.
180+
//
181+
// Span hashing can also be disabled without `-Z incremental-ignore-spans`. This is the
182+
// case for instance when building a hash for name mangling. Such configuration must not be
183+
// used for metadata.
184+
assert_eq!(
185+
hash_spans, !self.incremental_ignore_spans,
186+
"Attempted hashing of {msg} with non-default HashingControls: {hashing_controls:?}"
187+
);
126188
}
127189
}
128190

191+
impl<'a> rustc_abi::HashStableContext for StableHashingContext<'a> {}
192+
impl<'a> rustc_ast::HashStableContext for StableHashingContext<'a> {}
193+
impl<'a> rustc_hir::HashStableContext for StableHashingContext<'a> {}
129194
impl<'a> rustc_session::HashStableContext for StableHashingContext<'a> {}

compiler/rustc_query_system/src/ich/impls_syntax.rs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@
22
//! from various crates in no particular order.
33
44
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
5-
use rustc_hir::{self as hir, HashIgnoredAttrId};
6-
use rustc_span::SourceFile;
5+
use rustc_span::{SourceFile, Symbol, sym};
76
use smallvec::SmallVec;
7+
use {rustc_ast as ast, rustc_hir as hir};
88

99
use crate::ich::StableHashingContext;
1010

11-
impl<'ctx> rustc_abi::HashStableContext for StableHashingContext<'ctx> {}
12-
impl<'ctx> rustc_ast::HashStableContext for StableHashingContext<'ctx> {}
11+
impl<'a> HashStable<StableHashingContext<'a>> for ast::NodeId {
12+
#[inline]
13+
fn hash_stable(&self, _: &mut StableHashingContext<'a>, _: &mut StableHasher) {
14+
panic!("Node IDs should not appear in incremental state");
15+
}
16+
}
1317

1418
impl<'a> HashStable<StableHashingContext<'a>> for [hir::Attribute] {
1519
fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) {
@@ -24,7 +28,7 @@ impl<'a> HashStable<StableHashingContext<'a>> for [hir::Attribute] {
2428
.filter(|attr| {
2529
attr.is_doc_comment().is_none()
2630
// FIXME(jdonszelmann) have a better way to handle ignored attrs
27-
&& !attr.name().is_some_and(|ident| hcx.is_ignored_attr(ident))
31+
&& !attr.name().is_some_and(|ident| is_ignored_attr(ident))
2832
})
2933
.collect();
3034

@@ -35,10 +39,19 @@ impl<'a> HashStable<StableHashingContext<'a>> for [hir::Attribute] {
3539
}
3640
}
3741

38-
impl<'ctx> rustc_hir::HashStableContext for StableHashingContext<'ctx> {
39-
fn hash_attr_id(&mut self, _id: &HashIgnoredAttrId, _hasher: &mut StableHasher) {
40-
/* we don't hash HashIgnoredAttrId, we ignore them */
41-
}
42+
#[inline]
43+
fn is_ignored_attr(name: Symbol) -> bool {
44+
const IGNORED_ATTRIBUTES: &[Symbol] = &[
45+
sym::cfg_trace, // FIXME should this really be ignored?
46+
sym::rustc_if_this_changed,
47+
sym::rustc_then_this_would_need,
48+
sym::rustc_dirty,
49+
sym::rustc_clean,
50+
sym::rustc_partition_reused,
51+
sym::rustc_partition_codegened,
52+
sym::rustc_expected_cgu_reuse,
53+
];
54+
IGNORED_ATTRIBUTES.contains(&name)
4255
}
4356

4457
impl<'a> HashStable<StableHashingContext<'a>> for SourceFile {
Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,6 @@
11
//! ICH - Incremental Compilation Hash
22
3-
use rustc_span::{Symbol, sym};
4-
53
pub use self::hcx::StableHashingContext;
64

75
mod hcx;
86
mod impls_syntax;
9-
10-
pub const IGNORED_ATTRIBUTES: &[Symbol] = &[
11-
sym::cfg_trace, // FIXME should this really be ignored?
12-
sym::rustc_if_this_changed,
13-
sym::rustc_then_this_would_need,
14-
sym::rustc_dirty,
15-
sym::rustc_clean,
16-
sym::rustc_partition_reused,
17-
sym::rustc_partition_codegened,
18-
sym::rustc_expected_cgu_reuse,
19-
];

compiler/rustc_resolve/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ rustc_index = { path = "../rustc_index" }
2222
rustc_macros = { path = "../rustc_macros" }
2323
rustc_metadata = { path = "../rustc_metadata" }
2424
rustc_middle = { path = "../rustc_middle" }
25-
rustc_query_system = { path = "../rustc_query_system" }
2625
rustc_session = { path = "../rustc_session" }
2726
rustc_span = { path = "../rustc_span" }
2827
smallvec = { version = "1.8.1", features = ["union", "may_dangle"] }

compiler/rustc_resolve/src/lib.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ use rustc_middle::ty::{
7272
self, DelegationFnSig, DelegationInfo, Feed, MainDefinition, RegisteredTools,
7373
ResolverAstLowering, ResolverGlobalCtxt, TyCtxt, TyCtxtFeed, Visibility,
7474
};
75-
use rustc_query_system::ich::StableHashingContext;
7675
use rustc_session::config::CrateType;
7776
use rustc_session::lint::builtin::PRIVATE_MACRO_USE;
7877
use rustc_span::hygiene::{ExpnId, LocalExpnId, MacroKind, SyntaxContext, Transparency};
@@ -1841,10 +1840,6 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
18411840
ResolverOutputs { global_ctxt, ast_lowering }
18421841
}
18431842

1844-
fn create_stable_hashing_context(&self) -> StableHashingContext<'_> {
1845-
StableHashingContext::new(self.tcx.sess, self.tcx.untracked())
1846-
}
1847-
18481843
fn cstore(&self) -> FreezeReadGuard<'_, CStore> {
18491844
CStore::from_tcx(self.tcx)
18501845
}

0 commit comments

Comments
 (0)