rustc_span/
caching_source_map_view.rs

1use std::ops::Range;
2use std::sync::Arc;
3
4use crate::source_map::SourceMap;
5use crate::{BytePos, Pos, RelativeBytePos, SourceFile, SpanData, StableSourceFileId};
6
7#[derive(Clone)]
8struct CacheEntry {
9    time_stamp: usize,
10    line_number: usize,
11    // The line's byte position range in the `SourceMap`. This range will fail to contain a valid
12    // position in certain edge cases. Spans often start/end one past something, and when that
13    // something is the last character of a file (this can happen when a file doesn't end in a
14    // newline, for example), we'd still like for the position to be considered within the last
15    // line. However, it isn't according to the exclusive upper bound of this range. We cannot
16    // change the upper bound to be inclusive, because for most lines, the upper bound is the same
17    // as the lower bound of the next line, so there would be an ambiguity.
18    //
19    // Since the containment aspect of this range is only used to see whether or not the cache
20    // entry contains a position, the only ramification of the above is that we will get cache
21    // misses for these rare positions. A line lookup for the position via `SourceMap::lookup_line`
22    // after a cache miss will produce the last line number, as desired.
23    line: Range<BytePos>,
24    file: Arc<SourceFile>,
25    file_index: usize,
26}
27
28impl CacheEntry {
29    #[inline]
30    fn update(
31        &mut self,
32        new_file_and_idx: Option<(Arc<SourceFile>, usize)>,
33        pos: BytePos,
34        time_stamp: usize,
35    ) {
36        if let Some((file, file_idx)) = new_file_and_idx {
37            self.file = file;
38            self.file_index = file_idx;
39        }
40
41        let pos = self.file.relative_position(pos);
42        let line_index = self.file.lookup_line(pos).unwrap();
43        let line_bounds = self.file.line_bounds(line_index);
44        self.line_number = line_index + 1;
45        self.line = line_bounds;
46        self.touch(time_stamp);
47    }
48
49    #[inline]
50    fn touch(&mut self, time_stamp: usize) {
51        self.time_stamp = time_stamp;
52    }
53}
54
55#[derive(Clone)]
56pub struct CachingSourceMapView<'sm> {
57    source_map: &'sm SourceMap,
58    line_cache: [CacheEntry; 3],
59    time_stamp: usize,
60}
61
62impl<'sm> CachingSourceMapView<'sm> {
63    pub fn new(source_map: &'sm SourceMap) -> CachingSourceMapView<'sm> {
64        let files = source_map.files();
65        let first_file = Arc::clone(&files[0]);
66        let entry = CacheEntry {
67            time_stamp: 0,
68            line_number: 0,
69            line: BytePos(0)..BytePos(0),
70            file: first_file,
71            file_index: 0,
72        };
73
74        CachingSourceMapView {
75            source_map,
76            line_cache: [entry.clone(), entry.clone(), entry],
77            time_stamp: 0,
78        }
79    }
80
81    pub fn byte_pos_to_line_and_col(
82        &mut self,
83        pos: BytePos,
84    ) -> Option<(Arc<SourceFile>, usize, RelativeBytePos)> {
85        self.time_stamp += 1;
86
87        // Check if the position is in one of the cached lines
88        let cache_idx = self.cache_entry_index(pos);
89        if cache_idx != -1 {
90            let cache_entry = &mut self.line_cache[cache_idx as usize];
91            cache_entry.touch(self.time_stamp);
92
93            let col = RelativeBytePos(pos.to_u32() - cache_entry.line.start.to_u32());
94            return Some((Arc::clone(&cache_entry.file), cache_entry.line_number, col));
95        }
96
97        // No cache hit ...
98        let oldest = self.oldest_cache_entry_index();
99
100        // If the entry doesn't point to the correct file, get the new file and index.
101        let new_file_and_idx = if !file_contains(&self.line_cache[oldest].file, pos) {
102            Some(self.file_for_position(pos)?)
103        } else {
104            None
105        };
106
107        let cache_entry = &mut self.line_cache[oldest];
108        cache_entry.update(new_file_and_idx, pos, self.time_stamp);
109
110        let col = RelativeBytePos(pos.to_u32() - cache_entry.line.start.to_u32());
111        Some((Arc::clone(&cache_entry.file), cache_entry.line_number, col))
112    }
113
114    pub fn span_data_to_lines_and_cols(
115        &mut self,
116        span_data: &SpanData,
117    ) -> Option<(StableSourceFileId, usize, BytePos, usize, BytePos)> {
118        self.time_stamp += 1;
119
120        // Check if lo and hi are in the cached lines.
121        let lo_cache_idx: isize = self.cache_entry_index(span_data.lo);
122        let hi_cache_idx = self.cache_entry_index(span_data.hi);
123
124        if lo_cache_idx != -1 && hi_cache_idx != -1 {
125            // Cache hit for span lo and hi. Check if they belong to the same file.
126            let lo_file_index = self.line_cache[lo_cache_idx as usize].file_index;
127            let hi_file_index = self.line_cache[hi_cache_idx as usize].file_index;
128
129            if lo_file_index != hi_file_index {
130                return None;
131            }
132
133            self.line_cache[lo_cache_idx as usize].touch(self.time_stamp);
134            self.line_cache[hi_cache_idx as usize].touch(self.time_stamp);
135
136            let lo = &self.line_cache[lo_cache_idx as usize];
137            let hi = &self.line_cache[hi_cache_idx as usize];
138            return Some((
139                lo.file.stable_id,
140                lo.line_number,
141                span_data.lo - lo.line.start,
142                hi.line_number,
143                span_data.hi - hi.line.start,
144            ));
145        }
146
147        // No cache hit or cache hit for only one of span lo and hi.
148        let oldest = if lo_cache_idx != -1 || hi_cache_idx != -1 {
149            let avoid_idx = if lo_cache_idx != -1 { lo_cache_idx } else { hi_cache_idx };
150            self.oldest_cache_entry_index_avoid(avoid_idx as usize)
151        } else {
152            self.oldest_cache_entry_index()
153        };
154
155        // If the entry doesn't point to the correct file, get the new file and index.
156        // Return early if the file containing beginning of span doesn't contain end of span.
157        let new_file_and_idx = if !file_contains(&self.line_cache[oldest].file, span_data.lo) {
158            let new_file_and_idx = self.file_for_position(span_data.lo)?;
159            if !file_contains(&new_file_and_idx.0, span_data.hi) {
160                return None;
161            }
162
163            Some(new_file_and_idx)
164        } else {
165            let file = &self.line_cache[oldest].file;
166            if !file_contains(file, span_data.hi) {
167                return None;
168            }
169
170            None
171        };
172
173        // Update the cache entries.
174        let (lo_idx, hi_idx) = match (lo_cache_idx, hi_cache_idx) {
175            // Oldest cache entry is for span_data.lo line.
176            (-1, -1) => {
177                let lo = &mut self.line_cache[oldest];
178                lo.update(new_file_and_idx, span_data.lo, self.time_stamp);
179
180                if !lo.line.contains(&span_data.hi) {
181                    let new_file_and_idx = Some((Arc::clone(&lo.file), lo.file_index));
182                    let next_oldest = self.oldest_cache_entry_index_avoid(oldest);
183                    let hi = &mut self.line_cache[next_oldest];
184                    hi.update(new_file_and_idx, span_data.hi, self.time_stamp);
185                    (oldest, next_oldest)
186                } else {
187                    (oldest, oldest)
188                }
189            }
190            // Oldest cache entry is for span_data.lo line.
191            (-1, _) => {
192                let lo = &mut self.line_cache[oldest];
193                lo.update(new_file_and_idx, span_data.lo, self.time_stamp);
194                let hi = &mut self.line_cache[hi_cache_idx as usize];
195                hi.touch(self.time_stamp);
196                (oldest, hi_cache_idx as usize)
197            }
198            // Oldest cache entry is for span_data.hi line.
199            (_, -1) => {
200                let hi = &mut self.line_cache[oldest];
201                hi.update(new_file_and_idx, span_data.hi, self.time_stamp);
202                let lo = &mut self.line_cache[lo_cache_idx as usize];
203                lo.touch(self.time_stamp);
204                (lo_cache_idx as usize, oldest)
205            }
206            _ => {
207                panic!(
208                    "the case of neither value being equal to -1 was handled above and the function returns."
209                );
210            }
211        };
212
213        let lo = &self.line_cache[lo_idx];
214        let hi = &self.line_cache[hi_idx];
215
216        // Span lo and hi may equal line end when last line doesn't
217        // end in newline, hence the inclusive upper bounds below.
218        assert!(span_data.lo >= lo.line.start);
219        assert!(span_data.lo <= lo.line.end);
220        assert!(span_data.hi >= hi.line.start);
221        assert!(span_data.hi <= hi.line.end);
222        assert!(lo.file.contains(span_data.lo));
223        assert!(lo.file.contains(span_data.hi));
224        assert_eq!(lo.file_index, hi.file_index);
225
226        Some((
227            lo.file.stable_id,
228            lo.line_number,
229            span_data.lo - lo.line.start,
230            hi.line_number,
231            span_data.hi - hi.line.start,
232        ))
233    }
234
235    fn cache_entry_index(&self, pos: BytePos) -> isize {
236        for (idx, cache_entry) in self.line_cache.iter().enumerate() {
237            if cache_entry.line.contains(&pos) {
238                return idx as isize;
239            }
240        }
241
242        -1
243    }
244
245    fn oldest_cache_entry_index(&self) -> usize {
246        let mut oldest = 0;
247
248        for idx in 1..self.line_cache.len() {
249            if self.line_cache[idx].time_stamp < self.line_cache[oldest].time_stamp {
250                oldest = idx;
251            }
252        }
253
254        oldest
255    }
256
257    fn oldest_cache_entry_index_avoid(&self, avoid_idx: usize) -> usize {
258        let mut oldest = if avoid_idx != 0 { 0 } else { 1 };
259
260        for idx in 0..self.line_cache.len() {
261            if idx != avoid_idx
262                && self.line_cache[idx].time_stamp < self.line_cache[oldest].time_stamp
263            {
264                oldest = idx;
265            }
266        }
267
268        oldest
269    }
270
271    fn file_for_position(&self, pos: BytePos) -> Option<(Arc<SourceFile>, usize)> {
272        if !self.source_map.files().is_empty() {
273            let file_idx = self.source_map.lookup_source_file_idx(pos);
274            let file = &self.source_map.files()[file_idx];
275
276            if file_contains(file, pos) {
277                return Some((Arc::clone(file), file_idx));
278            }
279        }
280
281        None
282    }
283}
284
285#[inline]
286fn file_contains(file: &SourceFile, pos: BytePos) -> bool {
287    // `SourceMap::lookup_source_file_idx` and `SourceFile::contains` both consider the position
288    // one past the end of a file to belong to it. Normally, that's what we want. But for the
289    // purposes of converting a byte position to a line and column number, we can't come up with a
290    // line and column number if the file is empty, because an empty file doesn't contain any
291    // lines. So for our purposes, we don't consider empty files to contain any byte position.
292    file.contains(pos) && !file.is_empty()
293}