rustc_middle/mir/interpret/allocation/
provenance_map.rs

1//! Store the provenance for each byte in the range, with a more efficient
2//! representation for the common case where PTR_SIZE consecutive bytes have the same provenance.
3
4use std::cmp;
5
6use rustc_abi::{HasDataLayout, Size};
7use rustc_data_structures::sorted_map::SortedMap;
8use rustc_macros::HashStable;
9use rustc_serialize::{Decodable, Decoder, Encodable, Encoder};
10use tracing::trace;
11
12use super::{AllocError, AllocRange, AllocResult, CtfeProvenance, Provenance, alloc_range};
13
14/// Stores the provenance information of pointers stored in memory.
15#[derive(Clone, PartialEq, Eq, Hash, Debug)]
16#[derive(HashStable)]
17pub struct ProvenanceMap<Prov = CtfeProvenance> {
18    /// `Provenance` in this map applies from the given offset for an entire pointer-size worth of
19    /// bytes. Two entries in this map are always at least a pointer size apart.
20    ptrs: SortedMap<Size, Prov>,
21    /// Provenance in this map only applies to the given single byte.
22    /// This map is disjoint from the previous. It will always be empty when
23    /// `Prov::OFFSET_IS_ADDR` is false.
24    bytes: Option<Box<SortedMap<Size, Prov>>>,
25}
26
27// These impls are generic over `Prov` since `CtfeProvenance` is only decodable/encodable
28// for some particular `D`/`S`.
29impl<D: Decoder, Prov: Provenance + Decodable<D>> Decodable<D> for ProvenanceMap<Prov> {
30    fn decode(d: &mut D) -> Self {
31        assert!(!Prov::OFFSET_IS_ADDR); // only `CtfeProvenance` is ever serialized
32        Self { ptrs: Decodable::decode(d), bytes: None }
33    }
34}
35impl<S: Encoder, Prov: Provenance + Encodable<S>> Encodable<S> for ProvenanceMap<Prov> {
36    fn encode(&self, s: &mut S) {
37        let Self { ptrs, bytes } = self;
38        assert!(!Prov::OFFSET_IS_ADDR); // only `CtfeProvenance` is ever serialized
39        debug_assert!(bytes.is_none()); // without `OFFSET_IS_ADDR`, this is always empty
40        ptrs.encode(s)
41    }
42}
43
44impl<Prov> ProvenanceMap<Prov> {
45    pub fn new() -> Self {
46        ProvenanceMap { ptrs: SortedMap::new(), bytes: None }
47    }
48
49    /// The caller must guarantee that the given provenance list is already sorted
50    /// by address and contain no duplicates.
51    pub fn from_presorted_ptrs(r: Vec<(Size, Prov)>) -> Self {
52        ProvenanceMap { ptrs: SortedMap::from_presorted_elements(r), bytes: None }
53    }
54}
55
56impl ProvenanceMap {
57    /// Give access to the ptr-sized provenances (which can also be thought of as relocations, and
58    /// indeed that is how codegen treats them).
59    ///
60    /// Only exposed with `CtfeProvenance` provenance, since it panics if there is bytewise provenance.
61    #[inline]
62    pub fn ptrs(&self) -> &SortedMap<Size, CtfeProvenance> {
63        debug_assert!(self.bytes.is_none()); // `CtfeProvenance::OFFSET_IS_ADDR` is false so this cannot fail
64        &self.ptrs
65    }
66}
67
68impl<Prov: Provenance> ProvenanceMap<Prov> {
69    /// Returns all ptr-sized provenance in the given range.
70    /// If the range has length 0, returns provenance that crosses the edge between `start-1` and
71    /// `start`.
72    pub(super) fn range_get_ptrs(
73        &self,
74        range: AllocRange,
75        cx: &impl HasDataLayout,
76    ) -> &[(Size, Prov)] {
77        // We have to go back `pointer_size - 1` bytes, as that one would still overlap with
78        // the beginning of this range.
79        let adjusted_start = Size::from_bytes(
80            range.start.bytes().saturating_sub(cx.data_layout().pointer_size.bytes() - 1),
81        );
82        self.ptrs.range(adjusted_start..range.end())
83    }
84
85    /// Returns all byte-wise provenance in the given range.
86    fn range_get_bytes(&self, range: AllocRange) -> &[(Size, Prov)] {
87        if let Some(bytes) = self.bytes.as_ref() {
88            bytes.range(range.start..range.end())
89        } else {
90            &[]
91        }
92    }
93
94    /// Get the provenance of a single byte.
95    pub fn get(&self, offset: Size, cx: &impl HasDataLayout) -> Option<Prov> {
96        let prov = self.range_get_ptrs(alloc_range(offset, Size::from_bytes(1)), cx);
97        debug_assert!(prov.len() <= 1);
98        if let Some(entry) = prov.first() {
99            // If it overlaps with this byte, it is on this byte.
100            debug_assert!(self.bytes.as_ref().is_none_or(|b| !b.contains_key(&offset)));
101            Some(entry.1)
102        } else {
103            // Look up per-byte provenance.
104            self.bytes.as_ref().and_then(|b| b.get(&offset).copied())
105        }
106    }
107
108    /// Check if here is ptr-sized provenance at the given index.
109    /// Does not mean anything for bytewise provenance! But can be useful as an optimization.
110    pub fn get_ptr(&self, offset: Size) -> Option<Prov> {
111        self.ptrs.get(&offset).copied()
112    }
113
114    /// Returns whether this allocation has provenance overlapping with the given range.
115    ///
116    /// Note: this function exists to allow `range_get_provenance` to be private, in order to somewhat
117    /// limit access to provenance outside of the `Allocation` abstraction.
118    ///
119    pub fn range_empty(&self, range: AllocRange, cx: &impl HasDataLayout) -> bool {
120        self.range_get_ptrs(range, cx).is_empty() && self.range_get_bytes(range).is_empty()
121    }
122
123    /// Yields all the provenances stored in this map.
124    pub fn provenances(&self) -> impl Iterator<Item = Prov> + '_ {
125        let bytes = self.bytes.iter().flat_map(|b| b.values());
126        self.ptrs.values().chain(bytes).copied()
127    }
128
129    pub fn insert_ptr(&mut self, offset: Size, prov: Prov, cx: &impl HasDataLayout) {
130        debug_assert!(self.range_empty(alloc_range(offset, cx.data_layout().pointer_size), cx));
131        self.ptrs.insert(offset, prov);
132    }
133
134    /// Removes all provenance inside the given range.
135    /// If there is provenance overlapping with the edges, might result in an error.
136    pub fn clear(&mut self, range: AllocRange, cx: &impl HasDataLayout) -> AllocResult {
137        let start = range.start;
138        let end = range.end();
139        // Clear the bytewise part -- this is easy.
140        if Prov::OFFSET_IS_ADDR {
141            if let Some(bytes) = self.bytes.as_mut() {
142                bytes.remove_range(start..end);
143            }
144        } else {
145            debug_assert!(self.bytes.is_none());
146        }
147
148        // For the ptr-sized part, find the first (inclusive) and last (exclusive) byte of
149        // provenance that overlaps with the given range.
150        let (first, last) = {
151            // Find all provenance overlapping the given range.
152            let provenance = self.range_get_ptrs(range, cx);
153            if provenance.is_empty() {
154                // No provenance in this range, we are done.
155                return Ok(());
156            }
157
158            (
159                provenance.first().unwrap().0,
160                provenance.last().unwrap().0 + cx.data_layout().pointer_size,
161            )
162        };
163
164        // We need to handle clearing the provenance from parts of a pointer.
165        if first < start {
166            if !Prov::OFFSET_IS_ADDR {
167                // We can't split up the provenance into less than a pointer.
168                return Err(AllocError::OverwritePartialPointer(first));
169            }
170            // Insert the remaining part in the bytewise provenance.
171            let prov = self.ptrs[&first];
172            let bytes = self.bytes.get_or_insert_with(Box::default);
173            for offset in first..start {
174                bytes.insert(offset, prov);
175            }
176        }
177        if last > end {
178            let begin_of_last = last - cx.data_layout().pointer_size;
179            if !Prov::OFFSET_IS_ADDR {
180                // We can't split up the provenance into less than a pointer.
181                return Err(AllocError::OverwritePartialPointer(begin_of_last));
182            }
183            // Insert the remaining part in the bytewise provenance.
184            let prov = self.ptrs[&begin_of_last];
185            let bytes = self.bytes.get_or_insert_with(Box::default);
186            for offset in end..last {
187                bytes.insert(offset, prov);
188            }
189        }
190
191        // Forget all the provenance.
192        // Since provenance do not overlap, we know that removing until `last` (exclusive) is fine,
193        // i.e., this will not remove any other provenance just after the ones we care about.
194        self.ptrs.remove_range(first..last);
195
196        Ok(())
197    }
198
199    /// Overwrites all provenance in the allocation with wildcard provenance.
200    ///
201    /// Provided for usage in Miri and panics otherwise.
202    pub fn write_wildcards(&mut self, alloc_size: usize) {
203        assert!(
204            Prov::OFFSET_IS_ADDR,
205            "writing wildcard provenance is not supported when `OFFSET_IS_ADDR` is false"
206        );
207        let wildcard = Prov::WILDCARD.unwrap();
208
209        // Remove all pointer provenances, then write wildcards into the whole byte range.
210        self.ptrs.clear();
211        let last = Size::from_bytes(alloc_size);
212        let bytes = self.bytes.get_or_insert_with(Box::default);
213        for offset in Size::ZERO..last {
214            bytes.insert(offset, wildcard);
215        }
216    }
217}
218
219/// A partial, owned list of provenance to transfer into another allocation.
220///
221/// Offsets are already adjusted to the destination allocation.
222pub struct ProvenanceCopy<Prov> {
223    dest_ptrs: Option<Box<[(Size, Prov)]>>,
224    dest_bytes: Option<Box<[(Size, Prov)]>>,
225}
226
227impl<Prov: Provenance> ProvenanceMap<Prov> {
228    pub fn prepare_copy(
229        &self,
230        src: AllocRange,
231        dest: Size,
232        count: u64,
233        cx: &impl HasDataLayout,
234    ) -> AllocResult<ProvenanceCopy<Prov>> {
235        let shift_offset = move |idx, offset| {
236            // compute offset for current repetition
237            let dest_offset = dest + src.size * idx; // `Size` operations
238            // shift offsets from source allocation to destination allocation
239            (offset - src.start) + dest_offset // `Size` operations
240        };
241        let ptr_size = cx.data_layout().pointer_size;
242
243        // # Pointer-sized provenances
244        // Get the provenances that are entirely within this range.
245        // (Different from `range_get_ptrs` which asks if they overlap the range.)
246        // Only makes sense if we are copying at least one pointer worth of bytes.
247        let mut dest_ptrs_box = None;
248        if src.size >= ptr_size {
249            let adjusted_end = Size::from_bytes(src.end().bytes() - (ptr_size.bytes() - 1));
250            let ptrs = self.ptrs.range(src.start..adjusted_end);
251            // If `count` is large, this is rather wasteful -- we are allocating a big array here, which
252            // is mostly filled with redundant information since it's just N copies of the same `Prov`s
253            // at slightly adjusted offsets. The reason we do this is so that in `mark_provenance_range`
254            // we can use `insert_presorted`. That wouldn't work with an `Iterator` that just produces
255            // the right sequence of provenance for all N copies.
256            // Basically, this large array would have to be created anyway in the target allocation.
257            let mut dest_ptrs = Vec::with_capacity(ptrs.len() * (count as usize));
258            for i in 0..count {
259                dest_ptrs
260                    .extend(ptrs.iter().map(|&(offset, reloc)| (shift_offset(i, offset), reloc)));
261            }
262            debug_assert_eq!(dest_ptrs.len(), dest_ptrs.capacity());
263            dest_ptrs_box = Some(dest_ptrs.into_boxed_slice());
264        };
265
266        // # Byte-sized provenances
267        // This includes the existing bytewise provenance in the range, and ptr provenance
268        // that overlaps with the begin/end of the range.
269        let mut dest_bytes_box = None;
270        let begin_overlap = self.range_get_ptrs(alloc_range(src.start, Size::ZERO), cx).first();
271        let end_overlap = self.range_get_ptrs(alloc_range(src.end(), Size::ZERO), cx).first();
272        if !Prov::OFFSET_IS_ADDR {
273            // There can't be any bytewise provenance, and we cannot split up the begin/end overlap.
274            if let Some(entry) = begin_overlap {
275                return Err(AllocError::ReadPartialPointer(entry.0));
276            }
277            if let Some(entry) = end_overlap {
278                return Err(AllocError::ReadPartialPointer(entry.0));
279            }
280            debug_assert!(self.bytes.is_none());
281        } else {
282            let mut bytes = Vec::new();
283            // First, if there is a part of a pointer at the start, add that.
284            if let Some(entry) = begin_overlap {
285                trace!("start overlapping entry: {entry:?}");
286                // For really small copies, make sure we don't run off the end of the `src` range.
287                let entry_end = cmp::min(entry.0 + ptr_size, src.end());
288                for offset in src.start..entry_end {
289                    bytes.push((offset, entry.1));
290                }
291            } else {
292                trace!("no start overlapping entry");
293            }
294            // Then the main part, bytewise provenance from `self.bytes`.
295            if let Some(all_bytes) = self.bytes.as_ref() {
296                bytes.extend(all_bytes.range(src.start..src.end()));
297            }
298            // And finally possibly parts of a pointer at the end.
299            if let Some(entry) = end_overlap {
300                trace!("end overlapping entry: {entry:?}");
301                // For really small copies, make sure we don't start before `src` does.
302                let entry_start = cmp::max(entry.0, src.start);
303                for offset in entry_start..src.end() {
304                    if bytes.last().is_none_or(|bytes_entry| bytes_entry.0 < offset) {
305                        // The last entry, if it exists, has a lower offset than us.
306                        bytes.push((offset, entry.1));
307                    } else {
308                        // There already is an entry for this offset in there! This can happen when the
309                        // start and end range checks actually end up hitting the same pointer, so we
310                        // already added this in the "pointer at the start" part above.
311                        assert!(entry.0 <= src.start);
312                    }
313                }
314            } else {
315                trace!("no end overlapping entry");
316            }
317            trace!("byte provenances: {bytes:?}");
318
319            // And again a buffer for the new list on the target side.
320            let mut dest_bytes = Vec::with_capacity(bytes.len() * (count as usize));
321            for i in 0..count {
322                dest_bytes
323                    .extend(bytes.iter().map(|&(offset, reloc)| (shift_offset(i, offset), reloc)));
324            }
325            debug_assert_eq!(dest_bytes.len(), dest_bytes.capacity());
326            dest_bytes_box = Some(dest_bytes.into_boxed_slice());
327        }
328
329        Ok(ProvenanceCopy { dest_ptrs: dest_ptrs_box, dest_bytes: dest_bytes_box })
330    }
331
332    /// Applies a provenance copy.
333    /// The affected range, as defined in the parameters to `prepare_copy` is expected
334    /// to be clear of provenance.
335    pub fn apply_copy(&mut self, copy: ProvenanceCopy<Prov>) {
336        if let Some(dest_ptrs) = copy.dest_ptrs {
337            self.ptrs.insert_presorted(dest_ptrs.into());
338        }
339        if Prov::OFFSET_IS_ADDR {
340            if let Some(dest_bytes) = copy.dest_bytes
341                && !dest_bytes.is_empty()
342            {
343                self.bytes.get_or_insert_with(Box::default).insert_presorted(dest_bytes.into());
344            }
345        } else {
346            debug_assert!(copy.dest_bytes.is_none());
347        }
348    }
349}