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}