-
Notifications
You must be signed in to change notification settings - Fork 349
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
04630e0
commit a7f5fac
Showing
5 changed files
with
220 additions
and
171 deletions.
There are no files selected for viewing
94 changes: 94 additions & 0 deletions
94
src/borrow_tracker/tree_borrows/foreign_access_skipping.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
use super::AccessKind; | ||
use super::tree::AccessRelatedness; | ||
|
||
/// To speed up tree traversals, we want to skip traversing subtrees when we know the traversal will have no effect. | ||
/// This is often the case for foreign accesses, since usually foreign accesses happen several times in a row, but also | ||
/// foreign accesses are idempotent. In particular, see tests `foreign_read_is_noop_after_foreign_write` and `all_transitions_idempotent`. | ||
/// Thus, for each node we keep track of the "strongest idempotent foreign access," (SIFA) i.e. which foreign access can be skipped. | ||
/// This enum represents the kinds of values we store: | ||
/// `LocalAccess` means that the last access was local, and thus the next foreign access needs to happen, it can not be skipped. | ||
/// `ForeignRead` means that we are idempotent under foreign reads, but not (yet) under foreign writes. | ||
/// `ForeignWrite` means that we are idempotent under foreign writes. In other words, this node can be skipped for all foreign accesses. | ||
/// Due to the subtree property of traversals, this also means that all our children can be skipped, which makes this optimization | ||
/// worthwhile. In order for this to work, the invariant for multiple nodes in a tree is that at each location, the SIFA at each | ||
/// child must be stronger than that at the parent. However, note that this is quite invariant, due to retags inserting nodes across | ||
/// the entire range, which can violate this invariant without explicitly causing a local access which would reset things. | ||
/// So this needs to be done manually, thus `ensure_no_stronger_than`, and also the test `permission_sifa_is_correct` in `perms.rs` | ||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] | ||
pub enum LastForeignAccess { | ||
#[default] | ||
LocalAccess, | ||
ForeignRead, | ||
ForeignWrite, | ||
} | ||
|
||
impl LastForeignAccess { | ||
/// Returns true if a node where the strongest idempotent foreign access is `self` | ||
/// can skip the access `happening_next`. Note that if this returns | ||
/// `true`, then the entire subtree will be skipped. | ||
pub fn can_skip_foreign_access(self, happening_next: LastForeignAccess) -> bool { | ||
debug_assert!(happening_next.is_foreign()); | ||
// This ordering is correct. Intuitively, if the last access here was | ||
// a foreign write, everything can be skipped, since after a foreign write, | ||
// all further foreign accesses are idempotent | ||
happening_next <= self | ||
} | ||
|
||
/// Updates `self` to account for a foreign access. | ||
pub fn record_new(&mut self, just_happened: LastForeignAccess) { | ||
if just_happened.is_local() { | ||
// If the access is local, reset it. | ||
*self = LastForeignAccess::LocalAccess; | ||
} else { | ||
// Otherwise, keep it or stengthen it. | ||
*self = just_happened.max(*self); | ||
} | ||
} | ||
|
||
/// Returns true if this access is local. | ||
pub fn is_local(self) -> bool { | ||
matches!(self, LastForeignAccess::LocalAccess) | ||
} | ||
|
||
/// Returns true if this access is foreign, i.e. not local. | ||
pub fn is_foreign(self) -> bool { | ||
!self.is_local() | ||
} | ||
|
||
/// Constructs a foreign access from an `AccessKind` | ||
pub fn foreign(acc: AccessKind) -> LastForeignAccess { | ||
match acc { | ||
AccessKind::Read => Self::ForeignRead, | ||
AccessKind::Write => Self::ForeignWrite, | ||
} | ||
} | ||
|
||
/// Usually, tree traversals have an `AccessKind` and an `AccessRelatedness`. | ||
/// This methods converts these into the corresponding `LastForeignAccess`, to be used | ||
/// to e.g. invoke `can_skip_foreign_access`. | ||
pub fn of_acc_and_rel(acc: AccessKind, rel: AccessRelatedness) -> LastForeignAccess { | ||
if rel.is_foreign() { Self::foreign(acc) } else { Self::LocalAccess } | ||
} | ||
|
||
/// During retags, the SIFA needs to be weakened to account for children with weaker SIFAs being inserted. | ||
/// Thus, this method is called from the bottom up on each parent, until it returns false, which means the | ||
/// "children have stronger SIFAs" invariant is restored. | ||
pub fn ensure_no_stronger_than(&mut self, strongest_allowed: LastForeignAccess) -> bool { | ||
if *self > strongest_allowed { | ||
*self = strongest_allowed; | ||
true | ||
} else { | ||
false | ||
} | ||
} | ||
} | ||
|
||
mod tests { | ||
use super::LastForeignAccess; | ||
|
||
#[test] | ||
fn test_order() { | ||
assert!(LastForeignAccess::LocalAccess < LastForeignAccess::ForeignRead); | ||
assert!(LastForeignAccess::ForeignRead < LastForeignAccess::ForeignWrite); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.