|
| 1 | +use super::AccessKind; |
| 2 | +use super::tree::AccessRelatedness; |
| 3 | + |
| 4 | +/// To speed up tree traversals, we want to skip traversing subtrees when we know the traversal will have no effect. |
| 5 | +/// This is often the case for foreign accesses, since usually foreign accesses happen several times in a row, but also |
| 6 | +/// foreign accesses are idempotent. In particular, see tests `foreign_read_is_noop_after_foreign_write` and `all_transitions_idempotent`. |
| 7 | +/// Thus, for each node we keep track of the "strongest idempotent foreign access" (SIFA), i.e. which foreign access can be skipped. |
| 8 | +/// Note that for correctness, it is not required that this is the strongest access, just any access it is idempotent under. In particular, setting |
| 9 | +/// it to `None` is always correct, but the point of this optimization is to have it be as strong as possible so that more accesses can be skipped. |
| 10 | +/// This enum represents the kinds of values we store: |
| 11 | +/// - `None` means that the node (and its subtrees) are not (guaranteed to be) idempotent under any foreign access. |
| 12 | +/// - `Read` means that the node (and its subtrees) are idempotent under foreign reads, but not (yet / necessarily) under foreign writes. |
| 13 | +/// - `Write` means that the node (and its subtrees) are idempotent under foreign writes. This also implies that it is idempotent under foreign |
| 14 | +/// reads, since reads are stronger than writes (see test `foreign_read_is_noop_after_foreign_write`). In other words, this node can be skipped |
| 15 | +/// for all foreign accesses. |
| 16 | +/// |
| 17 | +/// Since a traversal does not just visit a node, but instead the entire subtree, the SIFA field for a given node indicates that the access to |
| 18 | +/// *the entire subtree* rooted at that node can be skipped. In order for this to work, we maintain the global invariant that at |
| 19 | +/// each location, the SIFA at each child must be stronger than that at the parent. For normal reads and writes, this is easily accomplished by |
| 20 | +/// tracking each foreign access as it occurs, so that then the next access can be skipped. This also obviously maintains the invariant, because |
| 21 | +/// if a node undergoes a foreign access, then all its children also see this as a foreign access. However, the invariant is broken during retags, |
| 22 | +/// because retags act across the entire allocation, but only emit a read event across a specific range. This means that for all nodes outside that |
| 23 | +/// range, the invariant is potentially broken, since a new child with a weaker SIFA is inserted. Thus, during retags, special care is taken to |
| 24 | +/// "manually" reset the parent's SIFA to be at least as strong as the new child's. This is accomplished with the `ensure_no_stronger_than` method. |
| 25 | +/// |
| 26 | +/// Note that we derive Ord and PartialOrd, so the order in which variants are listed below matters: |
| 27 | +/// None < Read < Write. Do not change that order. See the `test_order` test. |
| 28 | +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] |
| 29 | +pub enum IdempotentForeignAccess { |
| 30 | + #[default] |
| 31 | + None, |
| 32 | + Read, |
| 33 | + Write, |
| 34 | +} |
| 35 | + |
| 36 | +impl IdempotentForeignAccess { |
| 37 | + /// Returns true if a node where the strongest idempotent foreign access is `self` |
| 38 | + /// can skip the access `happening_next`. Note that if this returns |
| 39 | + /// `true`, then the entire subtree will be skipped. |
| 40 | + pub fn can_skip_foreign_access(self, happening_next: IdempotentForeignAccess) -> bool { |
| 41 | + debug_assert!(happening_next.is_foreign()); |
| 42 | + // This ordering is correct. Intuitively, if the last access here was |
| 43 | + // a foreign write, everything can be skipped, since after a foreign write, |
| 44 | + // all further foreign accesses are idempotent |
| 45 | + happening_next <= self |
| 46 | + } |
| 47 | + |
| 48 | + /// Updates `self` to account for a foreign access. |
| 49 | + pub fn record_new(&mut self, just_happened: IdempotentForeignAccess) { |
| 50 | + if just_happened.is_local() { |
| 51 | + // If the access is local, reset it. |
| 52 | + *self = IdempotentForeignAccess::None; |
| 53 | + } else { |
| 54 | + // Otherwise, keep it or stengthen it. |
| 55 | + *self = just_happened.max(*self); |
| 56 | + } |
| 57 | + } |
| 58 | + |
| 59 | + /// Returns true if this access is local. |
| 60 | + pub fn is_local(self) -> bool { |
| 61 | + matches!(self, IdempotentForeignAccess::None) |
| 62 | + } |
| 63 | + |
| 64 | + /// Returns true if this access is foreign, i.e. not local. |
| 65 | + pub fn is_foreign(self) -> bool { |
| 66 | + !self.is_local() |
| 67 | + } |
| 68 | + |
| 69 | + /// Constructs a foreign access from an `AccessKind` |
| 70 | + pub fn from_foreign(acc: AccessKind) -> IdempotentForeignAccess { |
| 71 | + match acc { |
| 72 | + AccessKind::Read => Self::Read, |
| 73 | + AccessKind::Write => Self::Write, |
| 74 | + } |
| 75 | + } |
| 76 | + |
| 77 | + /// Usually, tree traversals have an `AccessKind` and an `AccessRelatedness`. |
| 78 | + /// This methods converts these into the corresponding `IdempotentForeignAccess`, to be used |
| 79 | + /// to e.g. invoke `can_skip_foreign_access`. |
| 80 | + pub fn from_acc_and_rel(acc: AccessKind, rel: AccessRelatedness) -> IdempotentForeignAccess { |
| 81 | + if rel.is_foreign() { Self::from_foreign(acc) } else { Self::None } |
| 82 | + } |
| 83 | + |
| 84 | + /// During retags, the SIFA needs to be weakened to account for children with weaker SIFAs being inserted. |
| 85 | + /// Thus, this method is called from the bottom up on each parent, until it returns false, which means the |
| 86 | + /// "children have stronger SIFAs" invariant is restored. |
| 87 | + pub fn ensure_no_stronger_than(&mut self, strongest_allowed: IdempotentForeignAccess) -> bool { |
| 88 | + if *self > strongest_allowed { |
| 89 | + *self = strongest_allowed; |
| 90 | + true |
| 91 | + } else { |
| 92 | + false |
| 93 | + } |
| 94 | + } |
| 95 | +} |
| 96 | + |
| 97 | +#[cfg(test)] |
| 98 | +mod tests { |
| 99 | + use super::IdempotentForeignAccess; |
| 100 | + |
| 101 | + #[test] |
| 102 | + fn test_order() { |
| 103 | + // The internal logic relies on this order. |
| 104 | + // Do not change. |
| 105 | + assert!(IdempotentForeignAccess::None < IdempotentForeignAccess::Read); |
| 106 | + assert!(IdempotentForeignAccess::Read < IdempotentForeignAccess::Write); |
| 107 | + } |
| 108 | +} |
0 commit comments