Skip to main content

rustc_middle/
ich.rs

1use std::hash::Hasher;
2
3use rustc_crate_store::Untracked;
4use rustc_data_structures::fingerprint::Fingerprint;
5use rustc_data_structures::stable_hash::{
6    RawDefId, RawSpan, StableHash, StableHashControls, StableHashCtxt, StableHasher,
7};
8use rustc_data_structures::sync::AppendOnlyIndexVec;
9use rustc_hir::def_id::{DefId, LocalDefId};
10use rustc_session::Session;
11use rustc_span::source_map::SourceMap;
12use rustc_span::{BytePos, CachingSourceMapView, DUMMY_SP, Pos, Span, SpanData};
13
14// Very often, we are hashing something that does not need the `CachingSourceMapView`, so we
15// initialize it lazily.
16enum CachingSourceMap<'a> {
17    Unused(&'a SourceMap),
18    InUse(CachingSourceMapView<'a>),
19}
20
21/// This is the context state available during incr. comp. hashing. It contains
22/// enough information to transform `DefId`s and `HirId`s into stable `DefPath`s (i.e.,
23/// a reference to the `TyCtxt`) and it holds a few caches for speeding up various
24/// things (e.g., each `DefId`/`DefPath` is only hashed once).
25pub struct StableHashState<'a> {
26    untracked: &'a Untracked,
27    // The value of `-Z incremental-ignore-spans`.
28    // This field should only be used by `unstable_opts_incremental_ignore_span`
29    incremental_ignore_spans: bool,
30    caching_source_map: CachingSourceMap<'a>,
31    stable_hash_controls: StableHashControls,
32    source_span_cache: SourceSpanCache,
33}
34
35impl<'a> StableHashState<'a> {
36    #[inline]
37    pub fn new(sess: &'a Session, untracked: &'a Untracked) -> Self {
38        let hash_spans_initial = !sess.opts.unstable_opts.incremental_ignore_spans;
39
40        StableHashState {
41            untracked,
42            incremental_ignore_spans: sess.opts.unstable_opts.incremental_ignore_spans,
43            caching_source_map: CachingSourceMap::Unused(sess.source_map()),
44            stable_hash_controls: StableHashControls { hash_spans: hash_spans_initial },
45            source_span_cache: SourceSpanCache::default(),
46        }
47    }
48
49    #[inline]
50    pub fn while_hashing_spans<F: FnOnce(&mut Self)>(&mut self, hash_spans: bool, f: F) {
51        let prev_hash_spans = self.stable_hash_controls.hash_spans;
52        self.stable_hash_controls.hash_spans = hash_spans;
53        f(self);
54        self.stable_hash_controls.hash_spans = prev_hash_spans;
55    }
56
57    #[inline]
58    fn source_map(&mut self) -> &mut CachingSourceMapView<'a> {
59        match self.caching_source_map {
60            CachingSourceMap::InUse(ref mut sm) => sm,
61            CachingSourceMap::Unused(sm) => {
62                self.caching_source_map = CachingSourceMap::InUse(CachingSourceMapView::new(sm));
63                self.source_map() // this recursive call will hit the `InUse` case
64            }
65        }
66    }
67
68    #[inline]
69    pub fn stable_hash_controls(&self) -> StableHashControls {
70        self.stable_hash_controls
71    }
72}
73
74impl<'a> StableHashCtxt for StableHashState<'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    /// To avoid stable-hashing inconsistencies between stage1 and stage2 compilers, take care
86    /// not to rely on any `Hash` impls that were provided or derived by the standard library.
87    /// Instead, delegate to `StableHash` impls, or call `Hasher::write_*` methods directly.
88    ///
89    /// IMPORTANT: changes to this method should be reflected in implementations of `SpanEncoder`.
90    #[inline]
91    fn stable_hash_span(&mut self, raw_span: RawSpan, hasher: &mut StableHasher) {
92        const TAG_VALID_SPAN: u8 = 0;
93        const TAG_INVALID_SPAN: u8 = 1;
94        const TAG_RELATIVE_SPAN: u8 = 2;
95
96        #[inline]
97        fn pack_span_location(
98            line_lo: usize,
99            col_lo: BytePos,
100            line_hi: usize,
101            col_hi: BytePos,
102        ) -> u64 {
103            let col_lo_trunc = (col_lo.0 as u64) & 0xFF;
104            let line_lo_trunc = ((line_lo as u64) & 0xFF_FF_FF) << 8;
105            let col_hi_trunc = ((col_hi.0 as u64) & 0xFF) << 32;
106            let line_hi_trunc = ((line_hi as u64) & 0xFF_FF_FF) << 40;
107            col_lo_trunc | line_lo_trunc | col_hi_trunc | line_hi_trunc
108        }
109
110        if !self.stable_hash_controls().hash_spans {
111            return;
112        }
113
114        let span = Span::from_raw_span(raw_span);
115        let span = span.data_untracked();
116        span.ctxt.stable_hash(self, hasher);
117        span.parent.stable_hash(self, hasher);
118
119        if span.is_dummy() {
120            hasher.write_u8(TAG_INVALID_SPAN);
121            return;
122        }
123
124        let parent = span
125            .parent
126            .map(|parent| self.source_span_cache.lookup(parent, &self.untracked.source_span));
127        if let Some(parent) = parent
128            && parent.contains(span)
129        {
130            // This span is enclosed in a definition: only hash the relative position. This catches
131            // a subset of the cases from the `file.contains(parent.lo)`. But we can do this check
132            // cheaply without the expensive `span_data_to_lines_and_cols` query.
133            hasher.write_u8(TAG_RELATIVE_SPAN);
134            hasher.write_u32((span.lo - parent.lo).to_u32());
135            hasher.write_u32((span.hi - parent.lo).to_u32());
136            return;
137        }
138
139        // If this is not an empty or invalid span, we want to hash the last position that belongs
140        // to it, as opposed to hashing the first position past it.
141        let Some((file, line_lo, col_lo, line_hi, col_hi)) =
142            self.source_map().span_data_to_lines_and_cols(&span)
143        else {
144            hasher.write_u8(TAG_INVALID_SPAN);
145            return;
146        };
147        let file_stable_id = file.stable_id;
148
149        if let Some(parent) = parent
150            && file.contains(parent.lo)
151        {
152            // This span is relative to another span in the same file,
153            // only hash the relative position.
154            hasher.write_u8(TAG_RELATIVE_SPAN);
155            hasher.write_u32(span.lo.0.wrapping_sub(parent.lo.0));
156            hasher.write_u32(span.hi.0.wrapping_sub(parent.lo.0));
157            return;
158        }
159
160        hasher.write_u8(TAG_VALID_SPAN);
161        file_stable_id.stable_hash(self, hasher);
162
163        // Hash both the length and the end location (line/column) of a span. If we hash only the
164        // length, for example, then two otherwise equal spans with different end locations will
165        // have the same hash. This can cause a problem during incremental compilation wherein a
166        // previous result for a query that depends on the end location of a span will be
167        // incorrectly reused when the end location of the span it depends on has changed (see
168        // issue #74890). A similar analysis applies if some query depends specifically on the
169        // length of the span, but we only hash the end location. So hash both.
170
171        let col_line = pack_span_location(line_lo, col_lo, line_hi, col_hi);
172        let len = (span.hi - span.lo).0;
173        hasher.write_u64(col_line);
174        hasher.write_u32(len);
175    }
176
177    #[inline]
178    fn def_path_hash(&self, raw_def_id: RawDefId) -> Fingerprint {
179        let def_id = DefId::from_raw_def_id(raw_def_id);
180        if let Some(def_id) = def_id.as_local() {
181            self.untracked.definitions.read().def_path_hash(def_id)
182        } else {
183            self.untracked.cstore.read().def_path_hash(def_id)
184        }
185        .0
186    }
187
188    /// Assert that the provided `StableHashCtxt` is configured with the default
189    /// `StableHashControls`. We should always have bailed out before getting to here with a
190    /// non-default mode. With this check in place, we can avoid the need to maintain separate
191    /// versions of `ExpnData` hashes for each permutation of `StableHashControls` settings.
192    #[inline]
193    fn assert_default_stable_hash_controls(&self, msg: &str) {
194        let stable_hash_controls = self.stable_hash_controls;
195        let StableHashControls { hash_spans } = stable_hash_controls;
196
197        // Note that we require that `hash_spans` be the inverse of the global `-Z
198        // incremental-ignore-spans` option. Normally, this option is disabled, in which case
199        // `hash_spans` must be true.
200        //
201        // Span hashing can also be disabled without `-Z incremental-ignore-spans`. This is the
202        // case for instance when building a hash for name mangling. Such configuration must not be
203        // used for metadata.
204        {
    match (&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 StableHashControls: {1:?}",
                            msg, stable_hash_controls)));
            }
        }
    }
};assert_eq!(
205            hash_spans, !self.incremental_ignore_spans,
206            "Attempted hashing of {msg} with non-default StableHashControls: {stable_hash_controls:?}"
207        );
208    }
209
210    #[inline]
211    fn stable_hash_controls(&self) -> StableHashControls {
212        self.stable_hash_controls
213    }
214}
215
216/// A single-entry cache for a `SpanData`, to optimize a particular scenario: when iterating
217/// through child spans (e.g. during encoding/decoding/hashing), many of those spans will have the
218/// same parent span. In the case where that parent span is interned (e.g. the parent is a const
219/// literal item exceeding MAX_LEN bytes of source code), this cache avoids having to fetch the
220/// parent's `SpanData` from the interner (which involves TLS, locking, etc.) over and over. A
221/// single entry is enough for an extremely high hit rate.
222#[derive(#[automatically_derived]impl ::core::default::Default for SourceSpanCache {
    #[inline]
    fn default() -> Self {
        SourceSpanCache(::core::default::Default::default())
    }
}Default)]
223pub(crate) struct SourceSpanCache(Option<(LocalDefId, SpanData)>);
224
225impl SourceSpanCache {
226    #[inline]
227    pub(crate) fn lookup(
228        &mut self,
229        def_id: LocalDefId,
230        source_span: &AppendOnlyIndexVec<LocalDefId, Span>,
231    ) -> SpanData {
232        if let Some(cache) = self.0
233            && cache.0 == def_id
234        {
235            cache.1
236        } else {
237            let span_data = source_span.get(def_id).unwrap_or(DUMMY_SP).data_untracked();
238            self.0 = Some((def_id, span_data));
239            span_data
240        }
241    }
242}