Skip to content

Commit 2081b6b

Browse files
committed
doc comment suggestions
1 parent a054ef1 commit 2081b6b

File tree

1 file changed

+57
-25
lines changed
  • src/borrow_tracker/tree_borrows

1 file changed

+57
-25
lines changed

src/borrow_tracker/tree_borrows/tree.rs

+57-25
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,19 @@ use crate::*;
2929
/// Data for a single *location*.
3030
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3131
pub(super) struct LocationState {
32-
/// This pointer's current permission
33-
permission: Permission,
34-
/// A location is initialized when it is child accessed for the first time,
35-
/// and it then stays initialized forever.
36-
/// Before initialization we still apply some preemptive transitions on
37-
/// `permission` to know what to do in case it ever gets initialized,
38-
/// but these can never cause any immediate UB. There can however be UB
39-
/// the moment we attempt to initialize (i.e. child-access) because some
40-
/// foreign access done between the creation and the initialization is
41-
/// incompatible with child accesses.
32+
/// A location is initialized when it is child-accessed for the first time (and the initial
33+
/// retag initializes the location for the range covered by the type), and it then stays
34+
/// initialized forever.
35+
/// For initialized locations, "permission" is the current permission. However, for
36+
/// uninitialized locations, we still need to track the "future initial permission": this will
37+
/// start out to be `default_initial_perm`, but foreign accesses need to be taken into account.
38+
/// Crucially however, while transitions to `Disabled` would usually be UB if this location is
39+
/// protected, that is *not* the case for uninitialized locations. Instead we just have a latent
40+
/// "future initial permission" of `Disabled`, causing UB only if an access is ever actually
41+
/// performed.
4242
initialized: bool,
43+
/// This pointer's current permission / future initial permission.
44+
permission: Permission,
4345
/// Strongest foreign access whose effects have already been applied to
4446
/// this node and all its children since the last child access.
4547
/// This is `None` if the most recent access is a child access,
@@ -84,25 +86,50 @@ impl LocationState {
8486
let old_perm = self.permission;
8587
let transition = Permission::perform_access(access_kind, rel_pos, old_perm, protected)
8688
.ok_or(TransitionError::ChildAccessForbidden(old_perm))?;
87-
if protected
88-
// Can't trigger Protector on uninitialized locations
89-
&& self.initialized
90-
&& transition.produces_disabled()
91-
{
89+
// Why do only initialized locations cause protector errors?
90+
// Consider two mutable references `x`, `y` into disjoint parts of
91+
// the same allocation. A priori, these may actually both be used to
92+
// access the entire allocation, as long as only reads occur. However,
93+
// a write to `y` needs to somehow record that `x` can no longer be used
94+
// on that location at all. For these uninitialized locations (i.e., locations
95+
// that haven't been accessed with `x` yet), we track the "future initial state":
96+
// it defaults to whatever the initial state of the tag is,
97+
// but the access to `y` moves that "future initial state" of `x` to `Disabled`.
98+
// However, usually a `Reserved -> Disabled` transition would be UB due to the protector!
99+
// So clearly protectors shouldn't fire for such "future initial state" transitions.
100+
//
101+
// See the test `two_mut_protected_same_alloc` in `tests/pass/tree_borrows/tree-borrows.rs`
102+
// for an example of safe code that would be UB if we forgot to check `self.initialized`.
103+
if protected && self.initialized && transition.produces_disabled() {
92104
return Err(TransitionError::ProtectedDisabled(old_perm));
93105
}
94106
self.permission = transition.applied(old_perm).unwrap();
95107
self.initialized |= !rel_pos.is_foreign();
96108
Ok(transition)
97109
}
98110

99-
// Optimize the tree traversal.
111+
// Helper to optimize the tree traversal.
100112
// The optimization here consists of observing thanks to the tests
101-
// `foreign_read_is_noop_after_write` and `all_transitions_idempotent`
102-
// that if we apply twice in a row the effects of a foreign access
103-
// we can skip some branches.
104-
// "two foreign accesses in a row" occurs when `perm.latest_foreign_access` is `Some(_)`
105-
// AND the `rel_pos` of the current access corresponds to a foreign access.
113+
// `foreign_read_is_noop_after_write` and `all_transitions_idempotent`,
114+
// that there are actually just three possible sequences of events that can occur
115+
// in between two child accesses that produce different results.
116+
//
117+
// Indeed,
118+
// - applying any number of foreign read accesses is the same as applying
119+
// exactly one foreign read,
120+
// - applying any number of foreign read or write accesses is the same
121+
// as applying exactly one foreign write.
122+
// therefore the three sequences of events that can produce different
123+
// outcomes are
124+
// - an empty sequence (`self.latest_foreign_access = None`)
125+
// - a nonempty read-only sequence (`self.latest_foreign_access = Some(Read)`)
126+
// - a nonempty sequence with at least one write (`self.latest_foreign_access = Some(Write)`)
127+
//
128+
// This function not only determines if skipping the propagation right now
129+
// is possible, it also updates the internal state to keep track of whether
130+
// the propagation can be skipped next time.
131+
// It is a performance loss not to call this function when a foreign access occurs.
132+
// It is unsound not to call this function when a child access occurs.
106133
fn skip_if_known_noop(
107134
&mut self,
108135
access_kind: AccessKind,
@@ -124,19 +151,24 @@ impl LocationState {
124151
if new_access_noop {
125152
// Abort traversal if the new transition is indeed guaranteed
126153
// to be noop.
127-
return ContinueTraversal::SkipChildren;
154+
// No need to update `self.latest_foreign_access`,
155+
// the type of the current streak among nonempty read-only
156+
// or nonempty with at least one write has not changed.
157+
ContinueTraversal::SkipChildren
128158
} else {
129159
// Otherwise propagate this time, and also record the
130160
// access that just occurred so that we can skip the propagation
131161
// next time.
132162
self.latest_foreign_access = Some(access_kind);
163+
ContinueTraversal::Recurse
133164
}
134165
} else {
135-
// A child access occurred, this breaks the streak of "two foreign
136-
// accesses in a row" and we reset this field.
166+
// A child access occurred, this breaks the streak of foreign
167+
// accesses in a row and the sequence since the previous child access
168+
// is now empty.
137169
self.latest_foreign_access = None;
170+
ContinueTraversal::Recurse
138171
}
139-
ContinueTraversal::Recurse
140172
}
141173
}
142174

0 commit comments

Comments
 (0)