Skip to main content

rustc_middle/
ich.rs

1use std::hash::Hash;
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_hir::def_id::{DefId, LocalDefId};
9use rustc_session::Session;
10use rustc_span::source_map::SourceMap;
11use rustc_span::{BytePos, CachingSourceMapView, DUMMY_SP, Pos, Span};
12
13// Very often, we are hashing something that does not need the `CachingSourceMapView`, so we
14// initialize it lazily.
15enum CachingSourceMap<'a> {
16    Unused(&'a SourceMap),
17    InUse(CachingSourceMapView<'a>),
18}
19
20/// This is the context state available during incr. comp. hashing. It contains
21/// enough information to transform `DefId`s and `HirId`s into stable `DefPath`s (i.e.,
22/// a reference to the `TyCtxt`) and it holds a few caches for speeding up various
23/// things (e.g., each `DefId`/`DefPath` is only hashed once).
24pub struct StableHashState<'a> {
25    untracked: &'a Untracked,
26    // The value of `-Z incremental-ignore-spans`.
27    // This field should only be used by `unstable_opts_incremental_ignore_span`
28    incremental_ignore_spans: bool,
29    caching_source_map: CachingSourceMap<'a>,
30    stable_hash_controls: StableHashControls,
31}
32
33impl<'a> StableHashState<'a> {
34    #[inline]
35    pub fn new(sess: &'a Session, untracked: &'a Untracked) -> Self {
36        let hash_spans_initial = !sess.opts.unstable_opts.incremental_ignore_spans;
37
38        StableHashState {
39            untracked,
40            incremental_ignore_spans: sess.opts.unstable_opts.incremental_ignore_spans,
41            caching_source_map: CachingSourceMap::Unused(sess.source_map()),
42            stable_hash_controls: StableHashControls { hash_spans: hash_spans_initial },
43        }
44    }
45
46    #[inline]
47    pub fn while_hashing_spans<F: FnOnce(&mut Self)>(&mut self, hash_spans: bool, f: F) {
48        let prev_hash_spans = self.stable_hash_controls.hash_spans;
49        self.stable_hash_controls.hash_spans = hash_spans;
50        f(self);
51        self.stable_hash_controls.hash_spans = prev_hash_spans;
52    }
53
54    #[inline]
55    fn source_map(&mut self) -> &mut CachingSourceMapView<'a> {
56        match self.caching_source_map {
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
61            }
62        }
63    }
64
65    #[inline]
66    fn def_span(&self, def_id: LocalDefId) -> Span {
67        self.untracked.source_span.get(def_id).unwrap_or(DUMMY_SP)
68    }
69
70    #[inline]
71    pub fn stable_hash_controls(&self) -> StableHashControls {
72        self.stable_hash_controls
73    }
74}
75
76impl<'a> StableHashCtxt for StableHashState<'a> {
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    #[inline]
89    fn stable_hash_span(&mut self, raw_span: RawSpan, hasher: &mut StableHasher) {
90        const TAG_VALID_SPAN: u8 = 0;
91        const TAG_INVALID_SPAN: u8 = 1;
92        const TAG_RELATIVE_SPAN: u8 = 2;
93
94        #[inline]
95        fn pack_span_location(
96            line_lo: usize,
97            col_lo: BytePos,
98            line_hi: usize,
99            col_hi: BytePos,
100        ) -> u64 {
101            let col_lo_trunc = (col_lo.0 as u64) & 0xFF;
102            let line_lo_trunc = ((line_lo as u64) & 0xFF_FF_FF) << 8;
103            let col_hi_trunc = ((col_hi.0 as u64) & 0xFF) << 32;
104            let line_hi_trunc = ((line_hi as u64) & 0xFF_FF_FF) << 40;
105            col_lo_trunc | line_lo_trunc | col_hi_trunc | line_hi_trunc
106        }
107
108        if !self.stable_hash_controls().hash_spans {
109            return;
110        }
111
112        let span = Span::from_raw_span(raw_span);
113        let span = span.data_untracked();
114        span.ctxt.stable_hash(self, hasher);
115        span.parent.stable_hash(self, hasher);
116
117        if span.is_dummy() {
118            Hash::hash(&TAG_INVALID_SPAN, hasher);
119            return;
120        }
121
122        let parent = span.parent.map(|parent| self.def_span(parent).data_untracked());
123        if let Some(parent) = parent
124            && parent.contains(span)
125        {
126            // This span is enclosed in a definition: only hash the relative position. This catches
127            // a subset of the cases from the `file.contains(parent.lo)`. But we can do this check
128            // cheaply without the expensive `span_data_to_lines_and_cols` query.
129            Hash::hash(&TAG_RELATIVE_SPAN, hasher);
130            (span.lo - parent.lo).to_u32().stable_hash(self, hasher);
131            (span.hi - parent.lo).to_u32().stable_hash(self, hasher);
132            return;
133        }
134
135        // If this is not an empty or invalid span, we want to hash the last position that belongs
136        // to it, as opposed to hashing the first position past it.
137        let Some((file, line_lo, col_lo, line_hi, col_hi)) =
138            self.source_map().span_data_to_lines_and_cols(&span)
139        else {
140            Hash::hash(&TAG_INVALID_SPAN, hasher);
141            return;
142        };
143
144        if let Some(parent) = parent
145            && file.contains(parent.lo)
146        {
147            // This span is relative to another span in the same file,
148            // only hash the relative position.
149            Hash::hash(&TAG_RELATIVE_SPAN, hasher);
150            Hash::hash(&(span.lo.0.wrapping_sub(parent.lo.0)), hasher);
151            Hash::hash(&(span.hi.0.wrapping_sub(parent.lo.0)), hasher);
152            return;
153        }
154
155        Hash::hash(&TAG_VALID_SPAN, hasher);
156        Hash::hash(&file.stable_id, hasher);
157
158        // Hash both the length and the end location (line/column) of a span. If we hash only the
159        // length, for example, then two otherwise equal spans with different end locations will
160        // have the same hash. This can cause a problem during incremental compilation wherein a
161        // previous result for a query that depends on the end location of a span will be
162        // incorrectly reused when the end location of the span it depends on has changed (see
163        // issue #74890). A similar analysis applies if some query depends specifically on the
164        // length of the span, but we only hash the end location. So hash both.
165
166        let col_line = pack_span_location(line_lo, col_lo, line_hi, col_hi);
167        let len = (span.hi - span.lo).0;
168        Hash::hash(&col_line, hasher);
169        Hash::hash(&len, hasher);
170    }
171
172    #[inline]
173    fn def_path_hash(&self, raw_def_id: RawDefId) -> Fingerprint {
174        let def_id = DefId::from_raw_def_id(raw_def_id);
175        if let Some(def_id) = def_id.as_local() {
176            self.untracked.definitions.read().def_path_hash(def_id)
177        } else {
178            self.untracked.cstore.read().def_path_hash(def_id)
179        }
180        .0
181    }
182
183    /// Assert that the provided `StableHashCtxt` is configured with the default
184    /// `StableHashControls`. We should always have bailed out before getting to here with a
185    /// non-default mode. With this check in place, we can avoid the need to maintain separate
186    /// versions of `ExpnData` hashes for each permutation of `StableHashControls` settings.
187    #[inline]
188    fn assert_default_stable_hash_controls(&self, msg: &str) {
189        let stable_hash_controls = self.stable_hash_controls;
190        let StableHashControls { hash_spans } = stable_hash_controls;
191
192        // Note that we require that `hash_spans` be the inverse of the global `-Z
193        // incremental-ignore-spans` option. Normally, this option is disabled, in which case
194        // `hash_spans` must be true.
195        //
196        // Span hashing can also be disabled without `-Z incremental-ignore-spans`. This is the
197        // case for instance when building a hash for name mangling. Such configuration must not be
198        // used for metadata.
199        {
    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!(
200            hash_spans, !self.incremental_ignore_spans,
201            "Attempted hashing of {msg} with non-default StableHashControls: {stable_hash_controls:?}"
202        );
203    }
204
205    #[inline]
206    fn stable_hash_controls(&self) -> StableHashControls {
207        self.stable_hash_controls
208    }
209}