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}