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}