From 67fac9bb5a6ebf5c0b5c9921398498510d40ddbf Mon Sep 17 00:00:00 2001 From: Johannes Hostert Date: Sat, 26 Oct 2024 16:35:56 +0200 Subject: [PATCH 1/3] Add benchmark showing effectivity of subtree skipping --- bench-cargo-miri/string-replace/Cargo.lock | 7 +++++++ bench-cargo-miri/string-replace/Cargo.toml | 8 ++++++++ bench-cargo-miri/string-replace/data.json | 1 + bench-cargo-miri/string-replace/src/main.rs | 7 +++++++ 4 files changed, 23 insertions(+) create mode 100644 bench-cargo-miri/string-replace/Cargo.lock create mode 100644 bench-cargo-miri/string-replace/Cargo.toml create mode 100644 bench-cargo-miri/string-replace/data.json create mode 100644 bench-cargo-miri/string-replace/src/main.rs diff --git a/bench-cargo-miri/string-replace/Cargo.lock b/bench-cargo-miri/string-replace/Cargo.lock new file mode 100644 index 0000000000..443115c126 --- /dev/null +++ b/bench-cargo-miri/string-replace/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "string-replace" +version = "0.1.0" diff --git a/bench-cargo-miri/string-replace/Cargo.toml b/bench-cargo-miri/string-replace/Cargo.toml new file mode 100644 index 0000000000..f0785cd693 --- /dev/null +++ b/bench-cargo-miri/string-replace/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "string-replace" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/bench-cargo-miri/string-replace/data.json b/bench-cargo-miri/string-replace/data.json new file mode 100644 index 0000000000..7e074cd695 --- /dev/null +++ b/bench-cargo-miri/string-replace/data.json @@ -0,0 +1 @@ +[{"_id":"6724e6fc58417687afba2b85","index":0,"guid":"5c1bd108-2ee2-40bd-bce8-895c206409df","isActive":true,"balance":"$2,927.88","picture":"http://placehold.it/32x32","age":40,"eyeColor":"green","name":"Wynn Bradshaw","gender":"male","company":"ROTODYNE","email":"wynnbradshaw@rotodyne.com","phone":"+1 (904) 559-3130","address":"287 Bergen Avenue, Sperryville, Alaska, 5392","about":"Adipisicing fugiat aute adipisicing qui esse cillum. Lorem consequat consectetur voluptate id pariatur nostrud incididunt aliquip incididunt laboris aliqua. Magna nulla adipisicing cupidatat ea velit aliquip magna duis duis sunt ipsum. Cillum labore mollit fugiat tempor dolor sit.\r\n","registered":"2017-01-26T01:28:10 -01:00","latitude":46.089504,"longitude":51.763723,"greeting":"Hello, Wynn Bradshaw! You have 6 unread messages.","favoriteFruit":"banana"},{"_id":"6724e6fce8619d86c0389ccf","index":1,"guid":"ced7fbb7-3b1a-419b-9fc7-d47582bbb3ea","isActive":true,"balance":"$1,856.38","picture":"http://placehold.it/32x32","age":21,"eyeColor":"brown","name":"Olsen Larsen","gender":"male","company":"VERBUS","email":"olsenlarsen@verbus.com","phone":"+1 (936) 480-3749","address":"370 Losee Terrace, Churchill, Maine, 4040","about":"Consequat Lorem in laboris fugiat veniam tempor eiusmod eu incididunt enim do et qui. Sit commodo eu excepteur cillum ex tempor commodo ex ex laboris esse. Aute aute nulla dolore dolor do. Irure esse proident nostrud non. Incididunt velit reprehenderit incididunt laboris do. Consequat nulla est id ex veniam tempor. Sit Lorem magna cillum aliquip irure quis sit minim anim.\r\n","registered":"2016-07-12T02:08:39 -02:00","latitude":-12.843628,"longitude":-124.143829,"greeting":"Hello, Olsen Larsen! You have 1 unread messages.","favoriteFruit":"apple"},{"_id":"6724e6fc01b471965ea560cf","index":2,"guid":"21fde9a3-13ba-46be-baed-fb503f668b9e","isActive":false,"balance":"$2,025.88","picture":"http://placehold.it/32x32","age":29,"eyeColor":"green","name":"Ramirez Kinney","gender":"male","company":"QUAREX","email":"ramirezkinney@quarex.com","phone":"+1 (852) 447-2930","address":"986 Cornelia Street, Oberlin, Texas, 362","about":"Minim ea proident quis eiusmod aliquip duis excepteur velit minim aute cupidatat. Esse qui ex aliquip laborum id reprehenderit. Anim dolore commodo deserunt laborum nulla duis. Sint quis anim mollit fugiat sit incididunt reprehenderit occaecat aliqua dolor. Ullamco ipsum eiusmod incididunt proident qui exercitation adipisicing voluptate elit aliquip. Tempor duis aute incididunt adipisicing.\r\n","registered":"2016-02-23T05:34:14 -01:00","latitude":-56.21645,"longitude":44.048129,"greeting":"Hello, Ramirez Kinney! You have 9 unread messages.","favoriteFruit":"banana"},{"_id":"6724e6fc3ea8e4182b9e170f","index":3,"guid":"46b20637-eecc-40db-87d7-03da9bcd1cea","isActive":true,"balance":"$3,399.31","picture":"http://placehold.it/32x32","age":39,"eyeColor":"brown","name":"Hansen Kaufman","gender":"male","company":"EVENTAGE","email":"hansenkaufman@eventage.com","phone":"+1 (827) 483-2303","address":"916 Brighton Court, Sunbury, New Mexico, 3804","about":"Nisi in voluptate aute ullamco ipsum proident fugiat veniam anim reprehenderit. In ad irure dolor labore culpa incididunt veniam mollit Lorem deserunt cupidatat incididunt. Aliquip aliquip proident ut culpa.\r\n","registered":"2023-10-18T07:03:48 -02:00","latitude":-40.239135,"longitude":49.802049,"greeting":"Hello, Hansen Kaufman! You have 10 unread messages.","favoriteFruit":"apple"},{"_id":"6724e6fc721f83a10cf2aa37","index":4,"guid":"3d23743b-1e82-474e-8f7a-855fa46170d1","isActive":false,"balance":"$1,967.87","picture":"http://placehold.it/32x32","age":35,"eyeColor":"green","name":"Imelda Stephens","gender":"female","company":"OHMNET","email":"imeldastephens@ohmnet.com","phone":"+1 (893) 523-2400","address":"391 Wilson Street, Glidden, Kansas, 7226","about":"Officia sunt magna adipisicing id exercitation deserunt deserunt aliquip excepteur Lorem enim fugiat. Nulla culpa ut cupidatat excepteur do deserunt labore id eu laboris ullamco adipisicing ad. Et non nisi adipisicing minim aliquip ea ut qui adipisicing do laboris ex dolore duis.\r\n","registered":"2020-10-20T07:03:38 -02:00","latitude":0.348698,"longitude":-157.961956,"greeting":"Hello, Imelda Stephens! You have 2 unread messages.","favoriteFruit":"strawberry"},{"_id":"6724e6fc7ad7274b9f4c406c","index":5,"guid":"626292b1-ae84-4887-9e29-78e548cd24e6","isActive":true,"balance":"$1,577.44","picture":"http://placehold.it/32x32","age":40,"eyeColor":"brown","name":"Lynne Jarvis","gender":"female","company":"CORECOM","email":"lynnejarvis@corecom.com","phone":"+1 (899) 556-3876","address":"465 National Drive, Davenport, Palau, 9786","about":"Aliquip elit dolore sint quis do laboris exercitation elit aliqua eiusmod. Excepteur ad aliqua eiusmod incididunt tempor laboris officia consectetur sit. Cupidatat voluptate deserunt ut consectetur qui laborum duis elit incididunt occaecat laborum. Mollit aute velit officia amet aute minim fugiat sit laborum Lorem deserunt in. Exercitation eu sunt nulla adipisicing quis ea aute est. Lorem ea cillum ad labore quis minim et est laboris deserunt proident. Amet ut tempor laborum occaecat exercitation ullamco laborum adipisicing fugiat ea voluptate quis fugiat.\r\n","registered":"2018-11-03T03:53:15 -01:00","latitude":89.827087,"longitude":-136.882799,"greeting":"Hello, Lynne Jarvis! You have 3 unread messages.","favoriteFruit":"strawberry"},{"_id":"6724e6fcef1a1db2cf170762","index":6,"guid":"b8777c06-b90f-49a4-8737-96712fc504a3","isActive":false,"balance":"$2,285.03","picture":"http://placehold.it/32x32","age":37,"eyeColor":"green","name":"Price Bolton","gender":"male","company":"IMANT","email":"pricebolton@imant.com","phone":"+1 (825) 424-2873","address":"237 Aberdeen Street, Sattley, Montana, 2918","about":"Non cillum irure fugiat consequat ad ex. Magna magna tempor excepteur irure quis. Duis in laboris ipsum adipisicing culpa magna reprehenderit nisi incididunt est veniam quis. Labore culpa ut culpa veniam est est consectetur ipsum ex esse.\r\n","registered":"2014-04-26T01:20:19 -02:00","latitude":70.349258,"longitude":126.810102,"greeting":"Hello, Price Bolton! You have 10 unread messages.","favoriteFruit":"banana"},{"_id":"6724e6fc8bcb952208c159f9","index":7,"guid":"a4e6c6c8-3fe3-42de-ae28-79c16956d309","isActive":false,"balance":"$1,298.07","picture":"http://placehold.it/32x32","age":28,"eyeColor":"blue","name":"Gretchen Wynn","gender":"female","company":"TERASCAPE","email":"gretchenwynn@terascape.com","phone":"+1 (882) 447-2895","address":"973 Suydam Place, Shindler, Nebraska, 8094","about":"Anim mollit labore magna proident ipsum culpa enim deserunt dolore sunt veniam fugiat. Ad fugiat cupidatat nisi commodo dolore duis commodo nostrud est. Enim proident ullamco non adipisicing magna consequat mollit ad reprehenderit laboris. Ex quis duis anim id non commodo amet sunt est magna officia.\r\n","registered":"2021-08-13T08:51:32 -02:00","latitude":14.551848,"longitude":-27.142242,"greeting":"Hello, Gretchen Wynn! You have 7 unread messages.","favoriteFruit":"apple"},{"_id":"6724e6fcc8243c2dfa47f5d4","index":8,"guid":"27df20d5-c1d8-419b-ad38-bdd1e6094775","isActive":true,"balance":"$3,005.40","picture":"http://placehold.it/32x32","age":33,"eyeColor":"blue","name":"Chen Travis","gender":"male","company":"MEMORA","email":"chentravis@memora.com","phone":"+1 (980) 500-2406","address":"182 Dahlgreen Place, Baker, South Carolina, 9817","about":"Ad nisi consequat aliquip eiusmod aute pariatur est sint magna. Ad magna anim esse qui Lorem nulla veniam dolore eiusmod. Cillum consequat sit aliqua est proident exercitation eiusmod irure. Minim eu laboris ad incididunt enim sunt. Sunt in excepteur aute non tempor irure mollit laboris. Eu et duis ullamco dolor sint occaecat officia culpa ipsum anim anim eu veniam aliquip. Exercitation ipsum dolor sint cillum duis incididunt minim quis irure enim reprehenderit do do incididunt.\r\n","registered":"2019-09-01T02:57:37 -02:00","latitude":25.442301,"longitude":48.381036,"greeting":"Hello, Chen Travis! You have 1 unread messages.","favoriteFruit":"banana"}] \ No newline at end of file diff --git a/bench-cargo-miri/string-replace/src/main.rs b/bench-cargo-miri/string-replace/src/main.rs new file mode 100644 index 0000000000..73bf4a850e --- /dev/null +++ b/bench-cargo-miri/string-replace/src/main.rs @@ -0,0 +1,7 @@ +const TCB_INFO_JSON: &str = include_str!("../data.json"); + +fn main() { + let tcb_json = TCB_INFO_JSON; + let bad_tcb_json = tcb_json.replace("female", "male"); + std::hint::black_box(bad_tcb_json); +} From 04630e0a52351428e258804c72943cc5a5f48295 Mon Sep 17 00:00:00 2001 From: Johannes Hostert Date: Mon, 26 Aug 2024 22:40:17 +0200 Subject: [PATCH 2/3] Properly fix #3846 by resetting parents on lazy node creation This commit supplies a real fix, which makes retags more complicated, at the benefit of making accesses more performant. --- src/borrow_tracker/tree_borrows/mod.rs | 5 + src/borrow_tracker/tree_borrows/perms.rs | 28 ++++ src/borrow_tracker/tree_borrows/tree.rs | 185 ++++++++++++++++++----- 3 files changed, 181 insertions(+), 37 deletions(-) diff --git a/src/borrow_tracker/tree_borrows/mod.rs b/src/borrow_tracker/tree_borrows/mod.rs index 40467aa4bc..cd3822a35f 100644 --- a/src/borrow_tracker/tree_borrows/mod.rs +++ b/src/borrow_tracker/tree_borrows/mod.rs @@ -297,6 +297,11 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; // Record the parent-child pair in the tree. tree_borrows.new_child(orig_tag, new_tag, new_perm.initial_state, range, span)?; + tree_borrows.update_last_accessed_after_retag( + new_tag, + new_perm.initial_state, + new_perm.protector.is_some(), + ); drop(tree_borrows); // Also inform the data race model (but only if any bytes are actually affected). diff --git a/src/borrow_tracker/tree_borrows/perms.rs b/src/borrow_tracker/tree_borrows/perms.rs index 28f9dec7bb..1d0b5dc930 100644 --- a/src/borrow_tracker/tree_borrows/perms.rs +++ b/src/borrow_tracker/tree_borrows/perms.rs @@ -87,6 +87,28 @@ impl PermissionPriv { fn compatible_with_protector(&self) -> bool { !matches!(self, ReservedIM) } + + /// Returns the strongest foreign action this node survives (without change), + /// where `prot` indicates if it is protected. + /// They are ordered as `None` < `Some(Read)` < `Some(Write)`, in order of power. + pub fn strongest_survivable_foreign_action(&self, prot: bool) -> Option { + match self { + // The read will make it conflicted, so it is not invariant under it. + ReservedFrz { conflicted } if prot && !conflicted => None, + // Otherwise, foreign reads do not affect it + ReservedFrz { .. } => Some(AccessKind::Read), + // Famously, ReservedIM survives foreign writes. It is never protected. + ReservedIM => Some(AccessKind::Write), + // Active changes on any foreign access (becomes Frozen/Disabled). + Active => None, + // Frozen survives foreign reads, but not writes. + Frozen => Some(AccessKind::Read), + // Disabled survives foreign reads and writes. It survives them + // even if protected, because a protected `Disabled` is not initialized + // and does therefore not trigger UB. + Disabled => Some(AccessKind::Write), + } + } } /// This module controls how each permission individually reacts to an access. @@ -305,6 +327,12 @@ impl Permission { (Disabled, _) => false, } } + + /// Returns the strongest foreign action this node survives (without change), + /// where `prot` indicates if it is protected. + pub fn strongest_survivable_foreign_action(&self, prot: bool) -> Option { + self.inner.strongest_survivable_foreign_action(prot) + } } impl PermTransition { diff --git a/src/borrow_tracker/tree_borrows/tree.rs b/src/borrow_tracker/tree_borrows/tree.rs index a551b017df..116cdf0d40 100644 --- a/src/borrow_tracker/tree_borrows/tree.rs +++ b/src/borrow_tracker/tree_borrows/tree.rs @@ -51,6 +51,11 @@ pub(super) struct LocationState { /// `Some(Write)` if at least one foreign write access has been applied /// since the previous child access, and `Some(Read)` if at least one /// foreign read and no foreign write have occurred since the last child access. + /// It is an invariant that all children of this node have an access that is + /// at least as strong, if not stronger, where the strength order is: + /// `None` < `Some(Read)` < `Some(Write)`, and `Some(Write)` is strongest. + /// Further, for correctness, it is important that this is `None` when a node + /// is default-created. latest_foreign_access: Option, } @@ -143,30 +148,25 @@ impl LocationState { } } - // Helper to optimize the tree traversal. - // The optimization here consists of observing thanks to the tests - // `foreign_read_is_noop_after_foreign_write` and `all_transitions_idempotent`, - // that there are actually just three possible sequences of events that can occur - // in between two child accesses that produce different results. - // - // Indeed, - // - applying any number of foreign read accesses is the same as applying - // exactly one foreign read, - // - applying any number of foreign read or write accesses is the same - // as applying exactly one foreign write. - // therefore the three sequences of events that can produce different - // outcomes are - // - an empty sequence (`self.latest_foreign_access = None`) - // - a nonempty read-only sequence (`self.latest_foreign_access = Some(Read)`) - // - a nonempty sequence with at least one write (`self.latest_foreign_access = Some(Write)`) - // - // This function not only determines if skipping the propagation right now - // is possible, it also updates the internal state to keep track of whether - // the propagation can be skipped next time. - // It is a performance loss not to call this function when a foreign access occurs. - // FIXME: This optimization is wrong, and is currently disabled (by ignoring the - // result returned here). Since we presumably want an optimization like this, - // we should add it back. See #3864 for more information. + /// Tree traversal optimizations. + /// It turns out that all repeated accesses are idempotent, and further, + /// that foreign write accesses to a node are "stronger" than foreign read + /// accesses, insofar that a foreign read after a foreign write is a no-op. + /// The tests `foreign_read_is_noop_after_foreign_write` and + /// `all_transitions_idempotent` ensure this. + /// + /// Thus, if this access is a foreign access, and we already had such a foreign + /// access happen in the past, without a child access in-between, then we can + /// skip this access, since it is a known no-op. Further, since it is an + /// invariant of `latest_foreign_access` that all children survive stronger accesses, + /// we can not only skip this node, but the entire subtree rooted at this node. + /// + /// This method now checks whether this is the case, and the access to the entire + /// subtree (including this node) can be skipped. + /// For correctness, it is crucial that the information about the last access is + /// updated whenever an update occurs, using `record_new_access` or + /// `reset_last_foreign_access_after_reborrow`. If not, this function can cause + /// subtrees to be skipped that should be updated. fn skip_if_known_noop( &self, access_kind: AccessKind, @@ -207,21 +207,82 @@ impl LocationState { } /// Records a new access, so that future access can potentially be skipped - /// by `skip_if_known_noop`. - /// The invariants for this function are closely coupled to the function above: - /// It MUST be called on child accesses, and on foreign accesses MUST be called - /// when `skip_if_know_noop` returns `Recurse`, and MUST NOT be called otherwise. - /// FIXME: This optimization is wrong, and is currently disabled (by ignoring the - /// result returned here). Since we presumably want an optimization like this, - /// we should add it back. See #3864 for more information. - #[allow(unused)] + /// by `skip_if_known_noop`. It needs to be called after some accesses, in order + /// to record this new access. + /// The rules for when it needs to be called are tightly coupled to `skip_if_known_noop` above: + /// If `skip_if_known_noop` says that a node should be visited, this function MUST be called afterwards. + /// If `skip_if_known_noop` says that a node (and its subtree) should be skipped, this function + /// MUST NOT be called for this node (nor any of its children). fn record_new_access(&mut self, access_kind: AccessKind, rel_pos: AccessRelatedness) { + debug_assert!(matches!( + self.skip_if_known_noop(access_kind, rel_pos), + ContinueTraversal::Recurse + )); if rel_pos.is_foreign() { self.latest_foreign_access = Some(access_kind); } else { self.latest_foreign_access = None; } } + + /// Check if the latest recorded foreign update needs to be updated after a retag. + /// Retags can violate the invariant on `LocationState::latest_foreign_access`, + /// which states that a node's children survive accesses at least as strong as that node itself. + /// While retags act as a read for the offsets specified in the retag, it is also present at the other + /// offsets, but marked as "lazy." Such lazy nodes can, however, still be affected by foreign accesses. + /// This would violate the invariants that children always survive accesses at least as strong as the last + /// recorded foreign access at the parent. + /// To restore the invariant, we need to weaken the information stored in the parent, until it is weak enough + /// to encompass the newly added lazy node. + /// This function compares two accesses, and indicates if the parents needs to be updated. + /// It returns `true` iff `last_recorded` is stronger than `happening_now`, where + /// `None` is weakest and `Some(Write)` is strongest. + pub fn foreign_access_requires_update( + last_recorded: Option, + happening_now: Option, + ) -> bool { + match (last_recorded, happening_now) { + // if the last access here was a child access, we need to do nothing + (None, _) => false, + // if the last access here was a foreign access, but our new child does not + // survive any foreign access, we must let the next foreign access through again + (_, None) => true, + // last access was a read, the child survives reads -> no change needed + (Some(AccessKind::Read), Some(AccessKind::Read)) => false, + // last access was a read, the child survives writes -> no change needed + (Some(AccessKind::Read), Some(AccessKind::Write)) => false, + // last access was a write, the child survives writes -> no change needed + (Some(AccessKind::Write), Some(AccessKind::Write)) => false, + // last access was a write, the child does not survive writes -> reset it to read + (Some(AccessKind::Write), Some(AccessKind::Read)) => true, + } + } + + /// Restores the "children are stronger" invariant of `latest_foreign_access` after a retag. + /// See also `foreign_access_requires_update`. + /// Retags can violate the invariant on `latest_foreign_access`, + /// which states that a node's children survive accesses at least as strong as that node itself. + /// While retags act as a read for the offsets specified in the retag, it is also present at the other + /// offsets, but marked as "lazy." Such lazy nodes can, however, still be affected by foreign accesses. + /// This would violate the invariants that children always survive accesses at least as strong as the last + /// recorded foreign access at the parent. + /// To restore the invariant, we need to weaken the information stored in the parent, until it is weak enough + /// to encompass the newly added lazy node. + /// This function updates the `latest_foreign_access` for this node, by weakening it if necessary. + /// It returns `true` if such a weakening was necessary. If this method returns `true`, then this node's parent + /// also needs to be updated. If it returns `false`, then the invariant is restored globally, since all of `self`'s + /// parents are already weaker than `self` itself. + fn reset_last_foreign_access_after_retag( + &mut self, + strongest_survivable: Option, + ) -> bool { + let needs_update = + Self::foreign_access_requires_update(self.latest_foreign_access, strongest_survivable); + if needs_update { + self.latest_foreign_access = strongest_survivable; + } + needs_update + } } impl fmt::Display for LocationState { @@ -640,6 +701,56 @@ impl<'tcx> Tree { interp_ok(()) } + /// Restores the "children are stronger" invariant of `latest_foreign_access` after a retag. + /// See also `reset_last_foreign_access_after_reborrow`. + /// Retags can violate the invariant on `latest_foreign_access`, + /// which states that a node's children survive accesses at least as strong as that node itself. + /// While retags act as a read for the offsets specified in the retag, it is also present at the other + /// offsets, but marked as "lazy." Such lazy nodes can, however, still be affected by foreign accesses. + /// This would violate the invariants that children always survive accesses at least as strong as the last + /// recorded foreign access at the parent. + /// To restore the invariant, we need to weaken the information stored in all parents, until it is weak enough + /// to encompass the newly added lazy node. + /// This function walks upwards from the newly inserted node, and ensures that all its parents are aware that the subtree + /// rooted at that parent might now survive fewer foreign accesses than it did before this node was inserted. + pub fn update_last_accessed_after_retag( + &mut self, + child_tag: BorTag, + new_perm: Permission, + prot: bool, + ) { + let mut current = self.tag_mapping.get(&child_tag).unwrap(); + let strongest_survivable = new_perm.strongest_survivable_foreign_action(prot); + // We walk the tree upwards, until the invariant is restored + while let Some(next) = self.nodes.get(current).unwrap().parent { + current = next; + // record whether we did any change (if not, the invariant is restored). + let any_change = self.rperms.iter_mut_all().any(|(_, map)| { + match map.get_mut(current) { + // if there is a permission, ensure that it's latest recorded foreign access is not stronger + // than the `strongest_survivable` by the newly inserted child. + // Returns `true` if it changed something, and in that case, we need to continue with the parent. + Some(perm) => perm.reset_last_foreign_access_after_retag(strongest_survivable), + // if there is no permission yet, it is still "default lazy". + // in that case, we would need to compare with that node's original default permission. + // But that depended on whether that node was protected when it was created, which is no + // longer the case now. + // Anyways, since the node is "default", its latest recorded foreign access will default to `None`, + // but we might need to continue weakening the parent. + // So we return `true` here, which is definitely correct, but maybe not fully optimal. + // But in cases where we are imprecise, the iteration stops at the next initialized parent anyway, + // so it is likely not a huge loss in practice. + None => true, + } + }); + if any_change { + continue; + } else { + break; + } + } + } + /// Deallocation requires /// - a pointer that permits write accesses /// - the absence of Strong Protectors anywhere in the allocation @@ -735,9 +846,7 @@ impl<'tcx> Tree { .get() .copied() .unwrap_or_else(|| LocationState::new_uninit(node.default_initial_perm)); - // FIXME: See #3684 - let _would_skip_if_not_for_fixme = old_state.skip_if_known_noop(access_kind, *rel_pos); - ContinueTraversal::Recurse + old_state.skip_if_known_noop(access_kind, *rel_pos) }; let node_app = |perms_range: Range, access_kind: AccessKind, @@ -748,8 +857,10 @@ impl<'tcx> Tree { let old_state = perm.or_insert(LocationState::new_uninit(node.default_initial_perm)); - // FIXME: See #3684 - // old_state.record_new_access(access_kind, rel_pos); + // Call this function now, which ensures it is only called when + // `skip_if_known_noop` returns `Recurse`, due to the contract of + // `traverse_this_parents_children_other`. + old_state.record_new_access(access_kind, rel_pos); let protected = global.borrow().protected_tags.contains_key(&node.tag); let transition = old_state.perform_access(access_kind, rel_pos, protected)?; From 73383fdc4f0a5c1287d8f6c3434bd880b32a5ab2 Mon Sep 17 00:00:00 2001 From: Johannes Hostert Date: Fri, 23 Aug 2024 22:19:05 +0200 Subject: [PATCH 3/3] try optimizing accesses on large trees by ignoring subtrees if the access would mostly be a NOP --- src/borrow_tracker/tree_borrows/perms.rs | 4 ++++ src/borrow_tracker/tree_borrows/tree.rs | 26 +++++++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/borrow_tracker/tree_borrows/perms.rs b/src/borrow_tracker/tree_borrows/perms.rs index 1d0b5dc930..b0b42de968 100644 --- a/src/borrow_tracker/tree_borrows/perms.rs +++ b/src/borrow_tracker/tree_borrows/perms.rs @@ -236,6 +236,10 @@ impl Permission { pub fn is_active(&self) -> bool { self.inner == Active } + /// Check if `self` is the never-allow-writes-again state of a pointer (is `Frozen`). + pub fn is_frozen(&self) -> bool { + self.inner == Frozen + } /// Default initial permission of the root of a new tree at inbounds positions. /// Must *only* be used for the root, this is not in general an "initial" permission! diff --git a/src/borrow_tracker/tree_borrows/tree.rs b/src/borrow_tracker/tree_borrows/tree.rs index 116cdf0d40..0b84c9d274 100644 --- a/src/borrow_tracker/tree_borrows/tree.rs +++ b/src/borrow_tracker/tree_borrows/tree.rs @@ -173,7 +173,7 @@ impl LocationState { rel_pos: AccessRelatedness, ) -> ContinueTraversal { if rel_pos.is_foreign() { - let new_access_noop = match (self.latest_foreign_access, access_kind) { + let mut new_access_noop = match (self.latest_foreign_access, access_kind) { // Previously applied transition makes the new one a guaranteed // noop in the two following cases: // (1) justified by `foreign_read_is_noop_after_foreign_write` @@ -185,6 +185,30 @@ impl LocationState { // need to be applied to this subtree. _ => false, }; + if self.permission.is_disabled() { + // A foreign access to a `Disabled` tag will have almost no observable effect. + // It's a theorem that `Disabled` node have no protected initialized children, + // and so this foreign access will never trigger any protector. + // Further, the children will never be able to read or write again, since they + // have a `Disabled` parents. Even further, all children of `Disabled` are one + // of `ReservedIM`, `Disabled`, or a not-yet-accessed "lazy" permission thing. + // The two former are already invariant under all foreign accesses, and for + // the latter it does not really matter, since they can not be used/initialized + // due to having a protected parent. So this only affects diagnostics, but the + // blocking write will still be identified directly, just at a different tag. + new_access_noop = true; + } + if self.permission.is_frozen() && access_kind == AccessKind::Read { + // A foreign read to a `Frozen` tag will have almost no observable effect. + // It's a theorem that `Frozen` nodes have no active children, so all children + // already survive foreign reads. Foreign reads in general have almost no + // effect, the only further thing they could do is make protected `Reserved` + // nodes become conflicted, i.e. make them reject child writes for the further + // duration of their protector. But such a child write is already rejected + // because this node is frozen. So this only affects diagnostics, but the + // blocking read will still be identified directly, just at a different tag. + new_access_noop = true; + } if new_access_noop { // Abort traversal if the new transition is indeed guaranteed // to be noop.