Make Tree Borrows Provenance GC no longer produce stack overflows

This commit is contained in:
Johannes Hostert 2024-08-21 21:08:09 +02:00
parent 13b02e3d86
commit b5d77d849e
No known key found for this signature in database
GPG Key ID: 0BA6032B5A38D049
3 changed files with 74 additions and 47 deletions

View File

@ -20,7 +20,7 @@ doctest = false # and no doc tests
[dependencies] [dependencies]
getrandom = { version = "0.2", features = ["std"] } getrandom = { version = "0.2", features = ["std"] }
rand = "0.8" rand = "0.8"
smallvec = "1.7" smallvec = { version = "1.7", features = ["drain_filter"] }
aes = { version = "0.8.3", features = ["hazmat"] } aes = { version = "0.8.3", features = ["hazmat"] }
measureme = "11" measureme = "11"
ctrlc = "3.2.5" ctrlc = "3.2.5"

View File

@ -10,7 +10,7 @@
//! and the relative position of the access; //! and the relative position of the access;
//! - idempotency properties asserted in `perms.rs` (for optimizations) //! - idempotency properties asserted in `perms.rs` (for optimizations)
use std::fmt; use std::{fmt, mem};
use smallvec::SmallVec; use smallvec::SmallVec;
@ -699,8 +699,7 @@ pub fn perform_access(
/// Integration with the BorTag garbage collector /// Integration with the BorTag garbage collector
impl Tree { impl Tree {
pub fn remove_unreachable_tags(&mut self, live_tags: &FxHashSet<BorTag>) { pub fn remove_unreachable_tags(&mut self, live_tags: &FxHashSet<BorTag>) {
let root_is_needed = self.keep_only_needed(self.root, live_tags); // root can't be removed self.remove_useless_children(self.root, live_tags);
assert!(root_is_needed);
// Right after the GC runs is a good moment to check if we can // Right after the GC runs is a good moment to check if we can
// merge some adjacent ranges that were made equal by the removal of some // merge some adjacent ranges that were made equal by the removal of some
// tags (this does not necessarily mean that they have identical internal representations, // tags (this does not necessarily mean that they have identical internal representations,
@ -708,9 +707,16 @@ pub fn remove_unreachable_tags(&mut self, live_tags: &FxHashSet<BorTag>) {
self.rperms.merge_adjacent_thorough(); self.rperms.merge_adjacent_thorough();
} }
/// Checks if a node is useless and should be GC'ed.
/// A node is useless if it has no children and also the tag is no longer live.
fn is_useless(&self, idx: UniIndex, live: &FxHashSet<BorTag>) -> bool {
let node = self.nodes.get(idx).unwrap();
node.children.is_empty() && !live.contains(&node.tag)
}
/// Traverses the entire tree looking for useless tags. /// Traverses the entire tree looking for useless tags.
/// Returns true iff the tag it was called on is still live or has live children, /// Removes from the tree all useless child nodes of root.
/// and removes from the tree all tags that have no live children. /// It will not delete the root itself.
/// ///
/// NOTE: This leaves in the middle of the tree tags that are unreachable but have /// NOTE: This leaves in the middle of the tree tags that are unreachable but have
/// reachable children. There is a potential for compacting the tree by reassigning /// reachable children. There is a potential for compacting the tree by reassigning
@ -721,42 +727,60 @@ pub fn remove_unreachable_tags(&mut self, live_tags: &FxHashSet<BorTag>) {
/// `child: Reserved`. This tree can exist. If we blindly delete `parent` and reassign /// `child: Reserved`. This tree can exist. If we blindly delete `parent` and reassign
/// `child` to be a direct child of `root` then Writes to `child` are now permitted /// `child` to be a direct child of `root` then Writes to `child` are now permitted
/// whereas they were not when `parent` was still there. /// whereas they were not when `parent` was still there.
fn keep_only_needed(&mut self, idx: UniIndex, live: &FxHashSet<BorTag>) -> bool { fn remove_useless_children(&mut self, root: UniIndex, live: &FxHashSet<BorTag>) {
let node = self.nodes.get(idx).unwrap(); // To avoid stack overflows, we roll our own stack.
// FIXME: this function does a lot of cloning, a 2-pass approach is possibly // Each element in the stack consists of the current tag, and the number of the
// more efficient. It could consist of // next child to be processed.
// 1. traverse the Tree, collect all useless tags in a Vec
// 2. traverse the Vec, remove all tags previously selected // The other functions are written using the `TreeVisitorStack`, but that does not work here
// Bench it. // since we need to 1) do a post-traversal and 2) remove nodes from the tree.
let children: SmallVec<_> = node // Since we do a post-traversal (by deleting nodes only after handling all children),
.children // we also need to be a bit smarter than "pop node, push all children."
.clone() let mut stack = vec![(root, 0)];
.into_iter() while let Some((tag, nth_child)) = stack.last_mut() {
.filter(|child| self.keep_only_needed(*child, live)) let node = self.nodes.get(*tag).unwrap();
.collect(); if *nth_child < node.children.len() {
let no_children = children.is_empty(); // Visit the child by pushing it to the stack.
let node = self.nodes.get_mut(idx).unwrap(); // Also increase `nth_child` so that when we come back to the `tag` node, we
node.children = children; // look at the next child.
if !live.contains(&node.tag) && no_children { let next_child = node.children[*nth_child];
// All of the children and this node are unreachable, delete this tag *nth_child += 1;
// from the tree (the children have already been deleted by recursive stack.push((next_child, 0));
// calls). continue;
// Due to the API of UniMap we must absolutely call } else {
// `UniValMap::remove` for the key of this tag on *all* maps that used it // We have processed all children of `node`, so now it is time to process `node` itself.
// (which are `self.nodes` and every range of `self.rperms`) // First, get the current children of `node`. To appease the borrow checker,
// before we can safely apply `UniValMap::forget` to truly remove // we have to temporarily move the list out of the node, and then put the
// the tag from the mapping. // list of remaining children back in.
let tag = node.tag; let mut children_of_node =
self.nodes.remove(idx); mem::take(&mut self.nodes.get_mut(*tag).unwrap().children);
for (_perms_range, perms) in self.rperms.iter_mut_all() { // Remove all useless children, and save them for later.
perms.remove(idx); // The closure needs `&self` and the loop below needs `&mut self`, so we need to `collect`
// in to a temporary list.
let to_remove: Vec<_> =
children_of_node.drain_filter(|x| self.is_useless(*x, live)).collect();
// Put back the now-filtered vector.
self.nodes.get_mut(*tag).unwrap().children = children_of_node;
// Now, all that is left is unregistering the children saved in `to_remove`.
for idx in to_remove {
// Note: In the rest of this comment, "this node" refers to `idx`.
// This node has no more children (if there were any, they have already been removed).
// It is also unreachable as determined by the GC, so we can remove it everywhere.
// Due to the API of UniMap we must make sure to call
// `UniValMap::remove` for the key of this node on *all* maps that used it
// (which are `self.nodes` and every range of `self.rperms`)
// before we can safely apply `UniKeyMap::remove` to truly remove
// this tag from the `tag_mapping`.
let node = self.nodes.remove(idx).unwrap();
for (_perms_range, perms) in self.rperms.iter_mut_all() {
perms.remove(idx);
}
self.tag_mapping.remove(&node.tag);
}
// We are done, the parent can continue.
stack.pop();
continue;
} }
self.tag_mapping.remove(&tag);
// The tag has been deleted, inform the caller
false
} else {
// The tag is still live or has live children, it must be kept
true
} }
} }
} }

View File

@ -12,7 +12,7 @@
#![allow(dead_code)] #![allow(dead_code)]
use std::hash::Hash; use std::{hash::Hash, mem};
use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::fx::FxHashMap;
@ -187,13 +187,16 @@ pub fn get_mut(&mut self, idx: UniIndex) -> Option<&mut V> {
self.data.get_mut(idx.idx as usize).and_then(Option::as_mut) self.data.get_mut(idx.idx as usize).and_then(Option::as_mut)
} }
/// Delete any value associated with this index. Ok even if the index /// Delete any value associated with this index.
/// has no associated value. /// Returns None if the value was not present, otherwise
pub fn remove(&mut self, idx: UniIndex) { /// returns the previously stored value.
pub fn remove(&mut self, idx: UniIndex) -> Option<V> {
if idx.idx as usize >= self.data.len() { if idx.idx as usize >= self.data.len() {
return; return None;
} }
self.data[idx.idx as usize] = None; let mut res = None;
mem::swap(&mut res, &mut self.data[idx.idx as usize]);
res
} }
} }