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}