miri/borrow_tracker/tree_borrows/wildcard.rs
1use std::cmp::max;
2use std::fmt::Debug;
3
4use super::Tree;
5use super::tree::{AccessRelatedness, Node};
6use super::unimap::{UniIndex, UniValMap};
7use crate::BorTag;
8use crate::borrow_tracker::AccessKind;
9#[cfg(feature = "expensive-consistency-checks")]
10use crate::borrow_tracker::GlobalState;
11
12/// Represents the maximum access level that is possible.
13///
14/// Note that we derive Ord and PartialOrd, so the order in which variants are listed below matters:
15/// None < Read < Write. Do not change that order.
16#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
17pub enum WildcardAccessLevel {
18 #[default]
19 None,
20 Read,
21 Write,
22}
23impl WildcardAccessLevel {
24 /// Weather this access kind is allowed at this level.
25 pub fn allows(self, kind: AccessKind) -> bool {
26 let required_level = match kind {
27 AccessKind::Read => Self::Read,
28 AccessKind::Write => Self::Write,
29 };
30 required_level <= self
31 }
32}
33
34/// Where the access happened relative to the current node.
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub enum WildcardAccessRelatedness {
37 /// The access definitively happened through a local node.
38 LocalAccess,
39 /// The access definitively happened through a foreign node.
40 ForeignAccess,
41 /// We do not know if the access is foreign or local.
42 EitherAccess,
43}
44impl WildcardAccessRelatedness {
45 pub fn to_relatedness(self) -> Option<AccessRelatedness> {
46 match self {
47 Self::LocalAccess => Some(AccessRelatedness::LocalAccess),
48 Self::ForeignAccess => Some(AccessRelatedness::ForeignAccess),
49 Self::EitherAccess => None,
50 }
51 }
52}
53
54/// State per location per node keeping track of where relative to this
55/// node exposed nodes are and what access permissions they have.
56///
57/// Designed to be completely determined by its parent, siblings and
58/// direct children's max_local_access/max_foreign_access.
59#[derive(Clone, Default, PartialEq, Eq)]
60pub struct WildcardState {
61 /// How many of this node's direct children have `max_local_access()==Write`.
62 child_writes: u16,
63 /// How many of this node's direct children have `max_local_access()>=Read`.
64 child_reads: u16,
65 /// The maximum access level that could happen from an exposed node
66 /// that is foreign to this node.
67 ///
68 /// This is calculated as the `max()` of the parent's `max_foreign_access`,
69 /// `exposed_as` and the siblings' `max_local_access()`.
70 max_foreign_access: WildcardAccessLevel,
71 /// At what access level this node itself is exposed.
72 exposed_as: WildcardAccessLevel,
73}
74impl Debug for WildcardState {
75 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76 f.debug_struct("WildcardState")
77 .field("child_r/w", &(self.child_reads, self.child_writes))
78 .field("foreign", &self.max_foreign_access)
79 .field("exposed_as", &self.exposed_as)
80 .finish()
81 }
82}
83impl WildcardState {
84 /// The maximum access level that could happen from an exposed
85 /// node that is local to this node.
86 fn max_local_access(&self) -> WildcardAccessLevel {
87 use WildcardAccessLevel::*;
88 max(
89 self.exposed_as,
90 if self.child_writes > 0 {
91 Write
92 } else if self.child_reads > 0 {
93 Read
94 } else {
95 None
96 },
97 )
98 }
99
100 /// From where relative to the node with this wildcard info a read or write access could happen.
101 /// If `only_foreign` is true then we treat `LocalAccess` as impossible. This means we return
102 /// `None` if only a `LocalAccess` is possible, and we treat `EitherAccess` as a
103 /// `ForeignAccess`.
104 pub fn access_relatedness(
105 &self,
106 kind: AccessKind,
107 only_foreign: bool,
108 ) -> Option<WildcardAccessRelatedness> {
109 let rel = match kind {
110 AccessKind::Read => self.read_access_relatedness(),
111 AccessKind::Write => self.write_access_relatedness(),
112 };
113 if only_foreign {
114 use WildcardAccessRelatedness as E;
115 match rel {
116 Some(E::EitherAccess | E::ForeignAccess) => Some(E::ForeignAccess),
117 Some(E::LocalAccess) | None => None,
118 }
119 } else {
120 rel
121 }
122 }
123
124 /// From where relative to the node with this wildcard info a read access could happen.
125 fn read_access_relatedness(&self) -> Option<WildcardAccessRelatedness> {
126 let has_foreign = self.max_foreign_access >= WildcardAccessLevel::Read;
127 let has_local = self.max_local_access() >= WildcardAccessLevel::Read;
128 use WildcardAccessRelatedness as E;
129 match (has_foreign, has_local) {
130 (true, true) => Some(E::EitherAccess),
131 (true, false) => Some(E::ForeignAccess),
132 (false, true) => Some(E::LocalAccess),
133 (false, false) => None,
134 }
135 }
136
137 /// From where relative to the node with this wildcard info a write access could happen.
138 fn write_access_relatedness(&self) -> Option<WildcardAccessRelatedness> {
139 let has_foreign = self.max_foreign_access == WildcardAccessLevel::Write;
140 let has_local = self.max_local_access() == WildcardAccessLevel::Write;
141 use WildcardAccessRelatedness as E;
142 match (has_foreign, has_local) {
143 (true, true) => Some(E::EitherAccess),
144 (true, false) => Some(E::ForeignAccess),
145 (false, true) => Some(E::LocalAccess),
146 (false, false) => None,
147 }
148 }
149
150 /// Gets the access tracking information for a new child node of a parent with this
151 /// wildcard info.
152 /// The new node doesn't have any child reads/writes, but calculates `max_foreign_access`
153 /// from its parent.
154 pub fn for_new_child(&self) -> Self {
155 Self {
156 max_foreign_access: max(self.max_foreign_access, self.max_local_access()),
157 ..Default::default()
158 }
159 }
160 /// Crates the initial `WildcardState` for a wildcard root.
161 /// This has `max_foreign_access==Write` as it actually is the child of *some* exposed node
162 /// through which we can receive foreign accesses.
163 ///
164 /// This is different from the main root which has `max_foreign_access==None`, since there
165 /// cannot be a foreign access to the root of the allocation.
166 pub fn for_wildcard_root() -> Self {
167 Self { max_foreign_access: WildcardAccessLevel::Write, ..Default::default() }
168 }
169
170 /// Pushes the nodes of `children` onto the stack who's `max_foreign_access`
171 /// needs to be updated.
172 ///
173 /// * `children`: A list of nodes with the same parent. `children` doesn't
174 /// necessarily have to contain all children of parent, but can just be
175 /// a subset.
176 ///
177 /// * `child_reads`, `child_writes`: How many of `children` have `max_local_access()`
178 /// of at least `read`/`write`
179 ///
180 /// * `new_foreign_access`, `old_foreign_access`:
181 /// The max possible access level that is foreign to all `children`
182 /// (i.e., it is not local to *any* of them).
183 /// This can be calculated as the max of the parent's `exposed_as()`, `max_foreign_access`
184 /// and of all `max_local_access()` of any nodes with the same parent that are
185 /// not listed in `children`.
186 ///
187 /// This access level changed from `old` to `new`, which is why we need to
188 /// update `children`.
189 fn push_relevant_children(
190 stack: &mut Vec<(UniIndex, WildcardAccessLevel)>,
191 new_foreign_access: WildcardAccessLevel,
192 old_foreign_access: WildcardAccessLevel,
193 child_reads: u16,
194 child_writes: u16,
195 children: impl Iterator<Item = UniIndex>,
196
197 wildcard_accesses: &UniValMap<WildcardState>,
198 ) {
199 use WildcardAccessLevel::*;
200
201 // Nothing changed so we don't need to update anything.
202 if new_foreign_access == old_foreign_access {
203 return;
204 }
205
206 // We need to consider that the children's `max_local_access()` affect each
207 // other's `max_foreign_access`, but do not affect their own `max_foreign_access`.
208
209 // The new `max_foreign_acces` for children with `max_local_access()==Write`.
210 let write_foreign_access = max(
211 new_foreign_access,
212 if child_writes > 1 {
213 // There exists at least one more child with exposed write access.
214 // This means that a foreign write through that node is possible.
215 Write
216 } else if child_reads > 1 {
217 // There exists at least one more child with exposed read access,
218 // but no other with write access.
219 // This means that a foreign read but no write through that node
220 // is possible.
221 Read
222 } else {
223 // There are no other nodes with read or write access.
224 // This means no foreign writes through other children are possible.
225 None
226 },
227 );
228
229 // The new `max_foreign_acces` for children with `max_local_access()==Read`.
230 let read_foreign_access = max(
231 new_foreign_access,
232 if child_writes > 0 {
233 // There exists at least one child with write access (and it's not this one).
234 Write
235 } else if child_reads > 1 {
236 // There exists at least one more child with exposed read access,
237 // but no other with write access.
238 Read
239 } else {
240 // There are no other nodes with read or write access,
241 None
242 },
243 );
244
245 // The new `max_foreign_acces` for children with `max_local_access()==None`.
246 let none_foreign_access = max(
247 new_foreign_access,
248 if child_writes > 0 {
249 // There exists at least one child with write access (and it's not this one).
250 Write
251 } else if child_reads > 0 {
252 // There exists at least one child with read access (and it's not this one),
253 // but none with write access.
254 Read
255 } else {
256 // No children are exposed as read or write.
257 None
258 },
259 );
260
261 stack.extend(children.filter_map(|child| {
262 let state = wildcard_accesses.get(child).cloned().unwrap_or_default();
263
264 let new_foreign_access = match state.max_local_access() {
265 Write => write_foreign_access,
266 Read => read_foreign_access,
267 None => none_foreign_access,
268 };
269
270 if new_foreign_access != state.max_foreign_access {
271 Some((child, new_foreign_access))
272 } else {
273 Option::None
274 }
275 }));
276 }
277
278 /// Update the tracking information of a tree, to reflect that the node specified by `id` is
279 /// now exposed with `new_exposed_as`.
280 ///
281 /// Propagates the Willard access information over the tree. This needs to be called every
282 /// time the access level of an exposed node changes, to keep the state in sync with
283 /// the rest of the tree.
284 pub fn update_exposure(
285 id: UniIndex,
286 new_exposed_as: WildcardAccessLevel,
287 nodes: &UniValMap<Node>,
288 wildcard_accesses: &mut UniValMap<WildcardState>,
289 ) {
290 let mut entry = wildcard_accesses.entry(id);
291 let src_state = entry.or_insert(Default::default());
292 let old_exposed_as = src_state.exposed_as;
293
294 // If the exposure doesn't change, then we don't need to update anything.
295 if old_exposed_as == new_exposed_as {
296 return;
297 }
298
299 let src_old_local_access = src_state.max_local_access();
300
301 src_state.exposed_as = new_exposed_as;
302
303 let src_new_local_access = src_state.max_local_access();
304
305 // Stack of nodes for which the max_foreign_access field needs to be updated.
306 // Will be filled with the children of this node and its parents children before
307 // we begin downwards traversal.
308 let mut stack: Vec<(UniIndex, WildcardAccessLevel)> = Vec::new();
309
310 // Add the direct children of this node to the stack.
311 {
312 let node = nodes.get(id).unwrap();
313 Self::push_relevant_children(
314 &mut stack,
315 // new_foreign_access
316 max(src_state.max_foreign_access, new_exposed_as),
317 // old_foreign_access
318 max(src_state.max_foreign_access, old_exposed_as),
319 // Consider all children.
320 src_state.child_reads,
321 src_state.child_writes,
322 node.children.iter().copied(),
323 wildcard_accesses,
324 );
325 }
326 // We need to propagate the tracking info up the tree, for this we traverse
327 // up the parents.
328 // We can skip propagating info to the parent and siblings of a node if its
329 // access didn't change.
330 {
331 // The child from which we came.
332 let mut child = id;
333 // This is the `max_local_access()` of the child we came from, before
334 // this update...
335 let mut old_child_access = src_old_local_access;
336 // and after this update.
337 let mut new_child_access = src_new_local_access;
338 while let Some(parent_id) = nodes.get(child).unwrap().parent {
339 let parent_node = nodes.get(parent_id).unwrap();
340 let mut entry = wildcard_accesses.entry(parent_id);
341 let parent_state = entry.or_insert(Default::default());
342
343 let old_parent_local_access = parent_state.max_local_access();
344 use WildcardAccessLevel::*;
345 // Updating this node's tracking state for its children.
346 match (old_child_access, new_child_access) {
347 (None | Read, Write) => parent_state.child_writes += 1,
348 (Write, None | Read) => parent_state.child_writes -= 1,
349 _ => {}
350 }
351 match (old_child_access, new_child_access) {
352 (None, Read | Write) => parent_state.child_reads += 1,
353 (Read | Write, None) => parent_state.child_reads -= 1,
354 _ => {}
355 }
356
357 let new_parent_local_access = parent_state.max_local_access();
358
359 {
360 // We need to update the `max_foreign_access` of `child`'s
361 // siblings. For this we can reuse the `push_relevant_children`
362 // function.
363 //
364 // We pass it just the siblings without child itself. Since
365 // `child`'s `max_local_access()` is foreign to all of its
366 // siblings we can pass it as part of the foreign access.
367
368 let parent_access =
369 max(parent_state.exposed_as, parent_state.max_foreign_access);
370 // This is how many of `child`'s siblings have read/write local access.
371 // If `child` itself has access, then we need to subtract its access from the count.
372 let sibling_reads =
373 parent_state.child_reads - if new_child_access >= Read { 1 } else { 0 };
374 let sibling_writes =
375 parent_state.child_writes - if new_child_access >= Write { 1 } else { 0 };
376 Self::push_relevant_children(
377 &mut stack,
378 // new_foreign_access
379 max(parent_access, new_child_access),
380 // old_foreign_access
381 max(parent_access, old_child_access),
382 // Consider only siblings of child.
383 sibling_reads,
384 sibling_writes,
385 parent_node.children.iter().copied().filter(|id| child != *id),
386 wildcard_accesses,
387 );
388 }
389 if old_parent_local_access == new_parent_local_access {
390 // We didn't change `max_local_access()` for parent, so we don't need to propagate further upwards.
391 break;
392 }
393
394 old_child_access = old_parent_local_access;
395 new_child_access = new_parent_local_access;
396 child = parent_id;
397 }
398 }
399 // Traverses down the tree to update max_foreign_access fields of children and cousins who need to be updated.
400 while let Some((id, new_access)) = stack.pop() {
401 let node = nodes.get(id).unwrap();
402 let mut entry = wildcard_accesses.entry(id);
403 let state = entry.or_insert(Default::default());
404
405 let old_access = state.max_foreign_access;
406 state.max_foreign_access = new_access;
407
408 Self::push_relevant_children(
409 &mut stack,
410 // new_foreign_access
411 max(state.exposed_as, new_access),
412 // old_foreign_access
413 max(state.exposed_as, old_access),
414 // Consider all children.
415 state.child_reads,
416 state.child_writes,
417 node.children.iter().copied(),
418 wildcard_accesses,
419 );
420 }
421 }
422}
423
424impl Tree {
425 /// Marks the tag as exposed & updates the wildcard tracking data structure
426 /// to represent its access level.
427 /// Also takes as an argument whether the tag is protected or not.
428 pub fn expose_tag(&mut self, tag: BorTag, protected: bool) {
429 let id = self.tag_mapping.get(&tag).unwrap();
430 let node = self.nodes.get_mut(id).unwrap();
431 node.is_exposed = true;
432 let node = self.nodes.get(id).unwrap();
433
434 // When the first tag gets exposed then we initialize the
435 // wildcard state for every node and location in the tree.
436 for (_, loc) in self.locations.iter_mut_all() {
437 let perm = loc
438 .perms
439 .get(id)
440 .map(|p| p.permission())
441 .unwrap_or_else(|| node.default_location_state().permission());
442
443 let access_type = perm.strongest_allowed_local_access(protected);
444 WildcardState::update_exposure(
445 id,
446 access_type,
447 &self.nodes,
448 &mut loc.wildcard_accesses,
449 );
450 }
451 }
452
453 /// This updates the wildcard tracking data structure to reflect the release of
454 /// the protector on `tag`.
455 pub(super) fn update_exposure_for_protector_release(&mut self, tag: BorTag) {
456 let idx = self.tag_mapping.get(&tag).unwrap();
457
458 // We check if the node is already exposed, as we don't want to expose any
459 // nodes which aren't already exposed.
460
461 if self.nodes.get(idx).unwrap().is_exposed {
462 // Updates the exposure to the new permission on every location.
463 self.expose_tag(tag, /* protected */ false);
464 }
465 }
466}
467
468#[cfg(feature = "expensive-consistency-checks")]
469impl Tree {
470 /// Checks that the wildcard tracking data structure is internally consistent and
471 /// has the correct `exposed_as` values.
472 pub fn verify_wildcard_consistency(&self, global: &GlobalState) {
473 // We rely on the fact that `roots` is ordered according to tag from low to high.
474 assert!(self.roots.is_sorted_by_key(|idx| self.nodes.get(*idx).unwrap().tag));
475 let main_root_idx = self.roots[0];
476
477 let protected_tags = &global.borrow().protected_tags;
478 for (_, loc) in self.locations.iter_all() {
479 let wildcard_accesses = &loc.wildcard_accesses;
480 let perms = &loc.perms;
481 // Checks if accesses is empty.
482 if wildcard_accesses.is_empty() {
483 return;
484 }
485 for (id, node) in self.nodes.iter() {
486 let state = wildcard_accesses.get(id).unwrap();
487
488 let expected_exposed_as = if node.is_exposed {
489 let perm =
490 perms.get(id).copied().unwrap_or_else(|| node.default_location_state());
491
492 perm.permission()
493 .strongest_allowed_local_access(protected_tags.contains_key(&node.tag))
494 } else {
495 WildcardAccessLevel::None
496 };
497
498 // The foreign wildcard accesses possible at a node are determined by which
499 // accesses can originate from their siblings, their parent, and from above
500 // their parent.
501 let expected_max_foreign_access = if let Some(parent) = node.parent {
502 let parent_node = self.nodes.get(parent).unwrap();
503 let parent_state = wildcard_accesses.get(parent).unwrap();
504
505 let max_sibling_access = parent_node
506 .children
507 .iter()
508 .copied()
509 .filter(|child| *child != id)
510 .map(|child| {
511 let state = wildcard_accesses.get(child).unwrap();
512 state.max_local_access()
513 })
514 .fold(WildcardAccessLevel::None, max);
515
516 max_sibling_access
517 .max(parent_state.max_foreign_access)
518 .max(parent_state.exposed_as)
519 } else {
520 if main_root_idx == id {
521 // There can never be a foreign access to the root of the allocation.
522 // So its foreign access level is always `None`.
523 WildcardAccessLevel::None
524 } else {
525 // For wildcard roots any access on a different subtree can be foreign
526 // to it. So a wildcard root has the maximum possible foreign access
527 // level.
528 WildcardAccessLevel::Write
529 }
530 };
531
532 // Count how many children can be the source of wildcard reads or writes
533 // (either directly, or via their children).
534 let child_accesses = node.children.iter().copied().map(|child| {
535 let state = wildcard_accesses.get(child).unwrap();
536 state.max_local_access()
537 });
538 let expected_child_reads =
539 child_accesses.clone().filter(|a| *a >= WildcardAccessLevel::Read).count();
540 let expected_child_writes =
541 child_accesses.filter(|a| *a >= WildcardAccessLevel::Write).count();
542
543 assert_eq!(
544 expected_exposed_as, state.exposed_as,
545 "tag {:?} (id:{id:?}) should be exposed as {expected_exposed_as:?} but is exposed as {:?}",
546 node.tag, state.exposed_as
547 );
548 assert_eq!(
549 expected_max_foreign_access, state.max_foreign_access,
550 "expected {:?}'s (id:{id:?}) max_foreign_access to be {:?} instead of {:?}",
551 node.tag, expected_max_foreign_access, state.max_foreign_access
552 );
553 let child_reads: usize = state.child_reads.into();
554 assert_eq!(
555 expected_child_reads, child_reads,
556 "expected {:?}'s (id:{id:?}) child_reads to be {} instead of {}",
557 node.tag, expected_child_reads, child_reads
558 );
559 let child_writes: usize = state.child_writes.into();
560 assert_eq!(
561 expected_child_writes, child_writes,
562 "expected {:?}'s (id:{id:?}) child_writes to be {} instead of {}",
563 node.tag, expected_child_writes, child_writes
564 );
565 }
566 }
567 }
568}