Skip to main content

miri/borrow_tracker/tree_borrows/
wildcard.rs

1use std::fmt::Debug;
2
3use super::Tree;
4use super::tree::{AccessRelatedness, Node};
5use super::unimap::{UniIndex, UniValMap};
6use crate::BorTag;
7use crate::borrow_tracker::AccessKind;
8#[cfg(feature = "expensive-consistency-checks")]
9use crate::borrow_tracker::GlobalState;
10
11/// Represents the maximum access level that is possible.
12///
13/// Note that we derive Ord and PartialOrd, so the order in which variants are listed below matters:
14/// None < Read < Write. Do not change that order.
15#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
16pub enum WildcardAccessLevel {
17    #[default]
18    None,
19    Read,
20    Write,
21}
22impl WildcardAccessLevel {
23    /// Weather this access kind is allowed at this level.
24    pub fn allows(self, kind: AccessKind) -> bool {
25        let required_level = match kind {
26            AccessKind::Read => Self::Read,
27            AccessKind::Write => Self::Write,
28        };
29        required_level <= self
30    }
31}
32
33/// Where the access happened relative to the current node.
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub enum WildcardAccessRelatedness {
36    /// The access definitively happened through a local node.
37    LocalAccess,
38    /// The access definitively happened through a foreign node.
39    ForeignAccess,
40    /// We do not know if the access is foreign or local.
41    EitherAccess,
42}
43impl WildcardAccessRelatedness {
44    pub fn to_relatedness(self) -> Option<AccessRelatedness> {
45        match self {
46            Self::LocalAccess => Some(AccessRelatedness::LocalAccess),
47            Self::ForeignAccess => Some(AccessRelatedness::ForeignAccess),
48            Self::EitherAccess => None,
49        }
50    }
51}
52
53/// Caches information about where in the tree exposed nodes with permission to do reads/ rites are
54/// located. [`ExposedCache`] stores this information a single location (or rather, a range of
55/// homogeneous locations) for all nodes in an allocation.
56///
57/// Nodes not in this map have a default [`ExposedCacheNode`], i.e. they have no exposed children.
58/// In particular, this map remains empty (and thus consumes no memory) until the first
59/// node in the tree gets exposed.
60#[derive(Clone, Debug, Default, PartialEq, Eq)]
61pub struct ExposedCache(UniValMap<ExposedCacheNode>);
62
63/// State per location per node keeping track of where relative to this
64/// node exposed nodes are and what access permissions they have.
65#[derive(Clone, Default, Debug, PartialEq, Eq)]
66struct ExposedCacheNode {
67    /// How many local nodes (in this subtree) are exposed with write permissions.
68    local_writes: u16,
69    /// How many local nodes (in this subtree) are exposed with read permissions.
70    local_reads: u16,
71}
72
73impl ExposedCache {
74    /// Returns the relatedness of a wildcard access to a node.
75    ///
76    /// This function only considers a single subtree. If the current subtree does not contain
77    /// any valid exposed nodes then the function return `None`.
78    ///
79    /// * `root`: The root of the subtree the node belongs to.
80    /// * `id`: The id of the node.
81    /// * `kind`: The kind of the wildcard access.
82    /// * `is_wildcard_tree`: This nodes belongs to a wildcard subtree.
83    ///   This means we always treat foreign accesses as possible.
84    /// * `only_foreign`: Assume the access cannot come from a local node.
85    pub fn access_relatedness(
86        &self,
87        root: UniIndex,
88        id: UniIndex,
89        kind: AccessKind,
90        is_wildcard_tree: bool,
91        only_foreign: bool,
92    ) -> Option<WildcardAccessRelatedness> {
93        // All nodes in the tree are local to the root, so we can use the root to get the total
94        // number of valid exposed nodes in the tree.
95        let root = self.0.get(root).cloned().unwrap_or_default();
96        let node = self.0.get(id).cloned().unwrap_or_default();
97
98        let (total_num, local_num) = match kind {
99            AccessKind::Read => (root.local_reads, node.local_reads),
100            AccessKind::Write => (root.local_writes, node.local_writes),
101        };
102
103        // If this is a wildcard tree then an access can always be foreign as
104        // it could come from another tree.
105        // We can represent this by adding 1 to the total which means there
106        // always exists a foreign exposed node.
107        // (We cannot bake this into the root's count as then if `node == root` it would
108        // affect both `total` and `local`.)
109        let total_num = total_num + u16::from(is_wildcard_tree);
110
111        use WildcardAccessRelatedness::*;
112        let relatedness = if total_num == 0 {
113            // we return None if the tree does not contain any valid exposed nodes.
114            None
115        } else {
116            Some(if total_num == local_num {
117                // If all valid exposed nodes are local to this node then the access is local.
118                LocalAccess
119            } else if local_num == 0 {
120                // If the node does not have any exposed nodes as children then the access is foreign.
121                ForeignAccess
122            } else {
123                // If some but not all of the valid exposed nodes are local then we cannot determine the correct relatedness.
124                EitherAccess
125            })
126        };
127
128        if only_foreign {
129            // This is definitely not a local access; clamp the result accordingly.
130            match relatedness {
131                Some(LocalAccess) => None,
132                Some(ForeignAccess) => Some(ForeignAccess),
133                Some(EitherAccess) => Some(ForeignAccess),
134                None => None,
135            }
136        } else {
137            relatedness
138        }
139    }
140    /// Update the tracking information of a tree, to reflect that the node specified by `id` is
141    /// now exposed with `new_exposed_as` permission.
142    ///
143    /// Propagates the Willard access information over the tree. This needs to be called every
144    /// time the access level of an exposed node changes, to keep the state in sync with
145    /// the rest of the tree.
146    ///
147    /// * `from`: The previous access level of the exposed node.
148    ///   Set to `None` if the node was not exposed before.
149    /// * `to`: The new access level.
150    pub fn update_exposure(
151        &mut self,
152        nodes: &UniValMap<Node>,
153        id: UniIndex,
154        from: WildcardAccessLevel,
155        to: WildcardAccessLevel,
156    ) {
157        // If the exposure doesn't change, then we don't need to update anything.
158        if from == to {
159            return;
160        }
161
162        // Update the counts of this node and all its ancestors.
163        let mut next_id = Some(id);
164        while let Some(id) = next_id {
165            let node = nodes.get(id).unwrap();
166            let mut state = self.0.entry(id);
167            let state = state.or_insert(Default::default());
168
169            use WildcardAccessLevel::*;
170            match (from, to) {
171                (None | Read, Write) => state.local_writes += 1,
172                (Write, None | Read) => state.local_writes -= 1,
173                _ => {}
174            }
175            match (from, to) {
176                (None, Read | Write) => state.local_reads += 1,
177                (Read | Write, None) => state.local_reads -= 1,
178                _ => {}
179            }
180            next_id = node.parent;
181        }
182    }
183    /// Removes a node from the datastructure.
184    ///
185    /// The caller needs to ensure that the node does not have any children.
186    pub fn remove(&mut self, idx: UniIndex) {
187        self.0.remove(idx);
188    }
189}
190
191impl Tree {
192    /// Marks the tag as exposed & updates the wildcard tracking data structure
193    /// to represent its access level.
194    /// Also takes as an argument whether the tag is protected or not.
195    pub fn expose_tag(&mut self, tag: BorTag, protected: bool) {
196        let id = self.tag_mapping.get(&tag).unwrap();
197        let node = self.nodes.get_mut(id).unwrap();
198        if !node.is_exposed {
199            node.is_exposed = true;
200            let node = self.nodes.get(id).unwrap();
201
202            for (_, loc) in self.locations.iter_mut_all() {
203                let perm = loc
204                    .perms
205                    .get(id)
206                    .map(|p| p.permission())
207                    .unwrap_or_else(|| node.default_location_state().permission());
208
209                let access_level = perm.strongest_allowed_local_access(protected);
210                // An unexposed node gets treated as access level `None`. Therefore,
211                // the initial exposure transitions from `None` to the node's actual
212                // `access_level`.
213                loc.exposed_cache.update_exposure(
214                    &self.nodes,
215                    id,
216                    WildcardAccessLevel::None,
217                    access_level,
218                );
219            }
220        }
221    }
222
223    /// This updates the wildcard tracking data structure to reflect the release of
224    /// the protector on `tag`.
225    pub(super) fn update_exposure_for_protector_release(&mut self, tag: BorTag) {
226        let idx = self.tag_mapping.get(&tag).unwrap();
227
228        // We check if the node is already exposed, as we don't want to expose any
229        // nodes which aren't already exposed.
230        let node = self.nodes.get(idx).unwrap();
231        if node.is_exposed {
232            for (_, loc) in self.locations.iter_mut_all() {
233                let perm = loc
234                    .perms
235                    .get(idx)
236                    .map(|p| p.permission())
237                    .unwrap_or_else(|| node.default_location_state().permission());
238                // We are transitioning from protected to unprotected.
239                let old_access_type = perm.strongest_allowed_local_access(/*protected*/ true);
240                let access_type = perm.strongest_allowed_local_access(/*protected*/ false);
241                loc.exposed_cache.update_exposure(&self.nodes, idx, old_access_type, access_type);
242            }
243        }
244    }
245}
246
247#[cfg(feature = "expensive-consistency-checks")]
248impl Tree {
249    /// Checks that the wildcard tracking data structure is internally consistent and
250    /// has the correct `exposed_as` values.
251    pub fn verify_wildcard_consistency(&self, global: &GlobalState) {
252        // We rely on the fact that `roots` is ordered according to tag from low to high.
253        assert!(self.roots.is_sorted_by_key(|idx| self.nodes.get(*idx).unwrap().tag));
254
255        let protected_tags = &global.borrow().protected_tags;
256        for (_, loc) in self.locations.iter_all() {
257            let exposed_cache = &loc.exposed_cache;
258            let perms = &loc.perms;
259            for (id, node) in self.nodes.iter() {
260                let state = exposed_cache.0.get(id).cloned().unwrap_or_default();
261
262                let exposed_as = if node.is_exposed {
263                    let perm =
264                        perms.get(id).copied().unwrap_or_else(|| node.default_location_state());
265
266                    perm.permission()
267                        .strongest_allowed_local_access(protected_tags.contains_key(&node.tag))
268                } else {
269                    WildcardAccessLevel::None
270                };
271
272                let (child_reads, child_writes) = node
273                    .children
274                    .iter()
275                    .copied()
276                    .map(|id| exposed_cache.0.get(id).cloned().unwrap_or_default())
277                    .fold((0, 0), |acc, wc| (acc.0 + wc.local_reads, acc.1 + wc.local_writes));
278                let expected_reads =
279                    child_reads + u16::from(exposed_as >= WildcardAccessLevel::Read);
280                let expected_writes =
281                    child_writes + u16::from(exposed_as >= WildcardAccessLevel::Write);
282                assert_eq!(
283                    state.local_reads, expected_reads,
284                    "expected {:?}'s (id:{id:?}) local_reads to be {expected_reads:?} instead of {:?} (child_reads: {child_reads:?}, exposed_as: {exposed_as:?})",
285                    node.tag, state.local_reads
286                );
287                assert_eq!(
288                    state.local_writes, expected_writes,
289                    "expected {:?}'s (id:{id:?}) local_writes to be {expected_writes:?} instead of {:?} (child_writes: {child_writes:?}, exposed_as: {exposed_as:?})",
290                    node.tag, state.local_writes
291                );
292            }
293        }
294    }
295}