Skip to main content

rustc_middle/
ich.rs

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