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