1use std::hash::Hash;
23use rustc_data_structures::stable_hasher::{HashStable, HashingControls, StableHasher};
4use rustc_hir::def_id::{DefId, LocalDefId};
5use rustc_hir::definitions::DefPathHash;
6use rustc_session::Session;
7use rustc_session::cstore::Untracked;
8use rustc_span::source_map::SourceMap;
9use rustc_span::{CachingSourceMapView, DUMMY_SP, HashStableContext, Pos, Span};
1011// Very often, we are hashing something that does not need the `CachingSourceMapView`, so we
12// initialize it lazily.
13enum CachingSourceMap<'a> {
14 Unused(&'a SourceMap),
15 InUse(CachingSourceMapView<'a>),
16}
1718/// This is the context state available during incr. comp. hashing. It contains
19/// enough information to transform `DefId`s and `HirId`s into stable `DefPath`s (i.e.,
20/// a reference to the `TyCtxt`) and it holds a few caches for speeding up various
21/// things (e.g., each `DefId`/`DefPath` is only hashed once).
22pub struct StableHashingContext<'a> {
23 untracked: &'a Untracked,
24// The value of `-Z incremental-ignore-spans`.
25 // This field should only be used by `unstable_opts_incremental_ignore_span`
26incremental_ignore_spans: bool,
27 caching_source_map: CachingSourceMap<'a>,
28 hashing_controls: HashingControls,
29}
3031impl<'a> StableHashingContext<'a> {
32#[inline]
33pub fn new(sess: &'a Session, untracked: &'a Untracked) -> Self {
34let hash_spans_initial = !sess.opts.unstable_opts.incremental_ignore_spans;
3536StableHashingContext {
37untracked,
38 incremental_ignore_spans: sess.opts.unstable_opts.incremental_ignore_spans,
39 caching_source_map: CachingSourceMap::Unused(sess.source_map()),
40 hashing_controls: HashingControls { hash_spans: hash_spans_initial },
41 }
42 }
4344#[inline]
45pub fn while_hashing_spans<F: FnOnce(&mut Self)>(&mut self, hash_spans: bool, f: F) {
46let prev_hash_spans = self.hashing_controls.hash_spans;
47self.hashing_controls.hash_spans = hash_spans;
48f(self);
49self.hashing_controls.hash_spans = prev_hash_spans;
50 }
5152#[inline]
53fn source_map(&mut self) -> &mut CachingSourceMapView<'a> {
54match self.caching_source_map {
55 CachingSourceMap::InUse(ref mut sm) => sm,
56 CachingSourceMap::Unused(sm) => {
57self.caching_source_map = CachingSourceMap::InUse(CachingSourceMapView::new(sm));
58self.source_map() // this recursive call will hit the `InUse` case
59}
60 }
61 }
6263#[inline]
64fn def_span(&self, def_id: LocalDefId) -> Span {
65self.untracked.source_span.get(def_id).unwrap_or(DUMMY_SP)
66 }
6768#[inline]
69pub fn hashing_controls(&self) -> HashingControls {
70self.hashing_controls
71 }
72}
7374impl<'a> HashStableContextfor StableHashingContext<'a> {
75/// Hashes a span in a stable way. We can't directly hash the span's `BytePos` fields (that
76 /// would be similar to hashing pointers, since those are just offsets into the `SourceMap`).
77 /// Instead, we hash the (file name, line, column) triple, which stays the same even if the
78 /// containing `SourceFile` has moved within the `SourceMap`.
79 ///
80 /// Also note that we are hashing byte offsets for the column, not unicode codepoint offsets.
81 /// For the purpose of the hash that's sufficient. Also, hashing filenames is expensive so we
82 /// avoid doing it twice when the span starts and ends in the same file, which is almost always
83 /// the case.
84 ///
85 /// IMPORTANT: changes to this method should be reflected in implementations of `SpanEncoder`.
86#[inline]
87fn span_hash_stable(&mut self, span: Span, hasher: &mut StableHasher) {
88const TAG_VALID_SPAN: u8 = 0;
89const TAG_INVALID_SPAN: u8 = 1;
90const TAG_RELATIVE_SPAN: u8 = 2;
9192if !self.hashing_controls().hash_spans {
93return;
94 }
9596let span = span.data_untracked();
97span.ctxt.hash_stable(self, hasher);
98span.parent.hash_stable(self, hasher);
99100if span.is_dummy() {
101 Hash::hash(&TAG_INVALID_SPAN, hasher);
102return;
103 }
104105let parent = span.parent.map(|parent| self.def_span(parent).data_untracked());
106if let Some(parent) = parent107 && parent.contains(span)
108 {
109// This span is enclosed in a definition: only hash the relative position. This catches
110 // a subset of the cases from the `file.contains(parent.lo)`. But we can do this check
111 // cheaply without the expensive `span_data_to_lines_and_cols` query.
112Hash::hash(&TAG_RELATIVE_SPAN, hasher);
113 (span.lo - parent.lo).to_u32().hash_stable(self, hasher);
114 (span.hi - parent.lo).to_u32().hash_stable(self, hasher);
115return;
116 }
117118// If this is not an empty or invalid span, we want to hash the last position that belongs
119 // to it, as opposed to hashing the first position past it.
120let Some((file, line_lo, col_lo, line_hi, col_hi)) =
121self.source_map().span_data_to_lines_and_cols(&span)
122else {
123 Hash::hash(&TAG_INVALID_SPAN, hasher);
124return;
125 };
126127if let Some(parent) = parent128 && file.contains(parent.lo)
129 {
130// This span is relative to another span in the same file,
131 // only hash the relative position.
132Hash::hash(&TAG_RELATIVE_SPAN, hasher);
133 Hash::hash(&(span.lo.0.wrapping_sub(parent.lo.0)), hasher);
134 Hash::hash(&(span.hi.0.wrapping_sub(parent.lo.0)), hasher);
135return;
136 }
137138 Hash::hash(&TAG_VALID_SPAN, hasher);
139 Hash::hash(&file.stable_id, hasher);
140141// Hash both the length and the end location (line/column) of a span. If we hash only the
142 // length, for example, then two otherwise equal spans with different end locations will
143 // have the same hash. This can cause a problem during incremental compilation wherein a
144 // previous result for a query that depends on the end location of a span will be
145 // incorrectly reused when the end location of the span it depends on has changed (see
146 // issue #74890). A similar analysis applies if some query depends specifically on the
147 // length of the span, but we only hash the end location. So hash both.
148149let col_lo_trunc = (col_lo.0 as u64) & 0xFF;
150let line_lo_trunc = ((line_loas u64) & 0xFF_FF_FF) << 8;
151let col_hi_trunc = (col_hi.0 as u64) & 0xFF << 32;
152let line_hi_trunc = ((line_hias u64) & 0xFF_FF_FF) << 40;
153let col_line = col_lo_trunc | line_lo_trunc | col_hi_trunc | line_hi_trunc;
154let len = (span.hi - span.lo).0;
155 Hash::hash(&col_line, hasher);
156 Hash::hash(&len, hasher);
157 }
158159#[inline]
160fn def_path_hash(&self, def_id: DefId) -> DefPathHash {
161if let Some(def_id) = def_id.as_local() {
162self.untracked.definitions.read().def_path_hash(def_id)
163 } else {
164self.untracked.cstore.read().def_path_hash(def_id)
165 }
166 }
167168/// Assert that the provided `HashStableContext` is configured with the default
169 /// `HashingControls`. We should always have bailed out before getting to here with a
170 /// non-default mode. With this check in place, we can avoid the need to maintain separate
171 /// versions of `ExpnData` hashes for each permutation of `HashingControls` settings.
172#[inline]
173fn assert_default_hashing_controls(&self, msg: &str) {
174let hashing_controls = self.hashing_controls;
175let HashingControls { hash_spans } = hashing_controls;
176177// 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.
184match (&hash_spans, &!self.incremental_ignore_spans) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
let kind = ::core::panicking::AssertKind::Eq;
::core::panicking::assert_failed(kind, &*left_val, &*right_val,
::core::option::Option::Some(format_args!("Attempted hashing of {0} with non-default HashingControls: {1:?}",
msg, hashing_controls)));
}
}
};assert_eq!(
185 hash_spans, !self.incremental_ignore_spans,
186"Attempted hashing of {msg} with non-default HashingControls: {hashing_controls:?}"
187);
188 }
189}