Skip to content

Commit b5d77d8

Browse files
Make Tree Borrows Provenance GC no longer produce stack overflows
1 parent 13b02e3 commit b5d77d8

File tree

3 files changed

+74
-47
lines changed

3 files changed

+74
-47
lines changed

Diff for: src/tools/miri/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ doctest = false # and no doc tests
2020
[dependencies]
2121
getrandom = { version = "0.2", features = ["std"] }
2222
rand = "0.8"
23-
smallvec = "1.7"
23+
smallvec = { version = "1.7", features = ["drain_filter"] }
2424
aes = { version = "0.8.3", features = ["hazmat"] }
2525
measureme = "11"
2626
ctrlc = "3.2.5"

Diff for: src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs

+64-40
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
//! and the relative position of the access;
1111
//! - idempotency properties asserted in `perms.rs` (for optimizations)
1212
13-
use std::fmt;
13+
use std::{fmt, mem};
1414

1515
use smallvec::SmallVec;
1616

@@ -699,18 +699,24 @@ impl<'tcx> Tree {
699699
/// Integration with the BorTag garbage collector
700700
impl Tree {
701701
pub fn remove_unreachable_tags(&mut self, live_tags: &FxHashSet<BorTag>) {
702-
let root_is_needed = self.keep_only_needed(self.root, live_tags); // root can't be removed
703-
assert!(root_is_needed);
702+
self.remove_useless_children(self.root, live_tags);
704703
// Right after the GC runs is a good moment to check if we can
705704
// merge some adjacent ranges that were made equal by the removal of some
706705
// tags (this does not necessarily mean that they have identical internal representations,
707706
// see the `PartialEq` impl for `UniValMap`)
708707
self.rperms.merge_adjacent_thorough();
709708
}
710709

710+
/// Checks if a node is useless and should be GC'ed.
711+
/// A node is useless if it has no children and also the tag is no longer live.
712+
fn is_useless(&self, idx: UniIndex, live: &FxHashSet<BorTag>) -> bool {
713+
let node = self.nodes.get(idx).unwrap();
714+
node.children.is_empty() && !live.contains(&node.tag)
715+
}
716+
711717
/// Traverses the entire tree looking for useless tags.
712-
/// Returns true iff the tag it was called on is still live or has live children,
713-
/// and removes from the tree all tags that have no live children.
718+
/// Removes from the tree all useless child nodes of root.
719+
/// It will not delete the root itself.
714720
///
715721
/// NOTE: This leaves in the middle of the tree tags that are unreachable but have
716722
/// reachable children. There is a potential for compacting the tree by reassigning
@@ -721,42 +727,60 @@ impl Tree {
721727
/// `child: Reserved`. This tree can exist. If we blindly delete `parent` and reassign
722728
/// `child` to be a direct child of `root` then Writes to `child` are now permitted
723729
/// whereas they were not when `parent` was still there.
724-
fn keep_only_needed(&mut self, idx: UniIndex, live: &FxHashSet<BorTag>) -> bool {
725-
let node = self.nodes.get(idx).unwrap();
726-
// FIXME: this function does a lot of cloning, a 2-pass approach is possibly
727-
// more efficient. It could consist of
728-
// 1. traverse the Tree, collect all useless tags in a Vec
729-
// 2. traverse the Vec, remove all tags previously selected
730-
// Bench it.
731-
let children: SmallVec<_> = node
732-
.children
733-
.clone()
734-
.into_iter()
735-
.filter(|child| self.keep_only_needed(*child, live))
736-
.collect();
737-
let no_children = children.is_empty();
738-
let node = self.nodes.get_mut(idx).unwrap();
739-
node.children = children;
740-
if !live.contains(&node.tag) && no_children {
741-
// All of the children and this node are unreachable, delete this tag
742-
// from the tree (the children have already been deleted by recursive
743-
// calls).
744-
// Due to the API of UniMap we must absolutely call
745-
// `UniValMap::remove` for the key of this tag on *all* maps that used it
746-
// (which are `self.nodes` and every range of `self.rperms`)
747-
// before we can safely apply `UniValMap::forget` to truly remove
748-
// the tag from the mapping.
749-
let tag = node.tag;
750-
self.nodes.remove(idx);
751-
for (_perms_range, perms) in self.rperms.iter_mut_all() {
752-
perms.remove(idx);
730+
fn remove_useless_children(&mut self, root: UniIndex, live: &FxHashSet<BorTag>) {
731+
// To avoid stack overflows, we roll our own stack.
732+
// Each element in the stack consists of the current tag, and the number of the
733+
// next child to be processed.
734+
735+
// The other functions are written using the `TreeVisitorStack`, but that does not work here
736+
// since we need to 1) do a post-traversal and 2) remove nodes from the tree.
737+
// Since we do a post-traversal (by deleting nodes only after handling all children),
738+
// we also need to be a bit smarter than "pop node, push all children."
739+
let mut stack = vec![(root, 0)];
740+
while let Some((tag, nth_child)) = stack.last_mut() {
741+
let node = self.nodes.get(*tag).unwrap();
742+
if *nth_child < node.children.len() {
743+
// Visit the child by pushing it to the stack.
744+
// Also increase `nth_child` so that when we come back to the `tag` node, we
745+
// look at the next child.
746+
let next_child = node.children[*nth_child];
747+
*nth_child += 1;
748+
stack.push((next_child, 0));
749+
continue;
750+
} else {
751+
// We have processed all children of `node`, so now it is time to process `node` itself.
752+
// First, get the current children of `node`. To appease the borrow checker,
753+
// we have to temporarily move the list out of the node, and then put the
754+
// list of remaining children back in.
755+
let mut children_of_node =
756+
mem::take(&mut self.nodes.get_mut(*tag).unwrap().children);
757+
// Remove all useless children, and save them for later.
758+
// The closure needs `&self` and the loop below needs `&mut self`, so we need to `collect`
759+
// in to a temporary list.
760+
let to_remove: Vec<_> =
761+
children_of_node.drain_filter(|x| self.is_useless(*x, live)).collect();
762+
// Put back the now-filtered vector.
763+
self.nodes.get_mut(*tag).unwrap().children = children_of_node;
764+
// Now, all that is left is unregistering the children saved in `to_remove`.
765+
for idx in to_remove {
766+
// Note: In the rest of this comment, "this node" refers to `idx`.
767+
// This node has no more children (if there were any, they have already been removed).
768+
// It is also unreachable as determined by the GC, so we can remove it everywhere.
769+
// Due to the API of UniMap we must make sure to call
770+
// `UniValMap::remove` for the key of this node on *all* maps that used it
771+
// (which are `self.nodes` and every range of `self.rperms`)
772+
// before we can safely apply `UniKeyMap::remove` to truly remove
773+
// this tag from the `tag_mapping`.
774+
let node = self.nodes.remove(idx).unwrap();
775+
for (_perms_range, perms) in self.rperms.iter_mut_all() {
776+
perms.remove(idx);
777+
}
778+
self.tag_mapping.remove(&node.tag);
779+
}
780+
// We are done, the parent can continue.
781+
stack.pop();
782+
continue;
753783
}
754-
self.tag_mapping.remove(&tag);
755-
// The tag has been deleted, inform the caller
756-
false
757-
} else {
758-
// The tag is still live or has live children, it must be kept
759-
true
760784
}
761785
}
762786
}

Diff for: src/tools/miri/src/borrow_tracker/tree_borrows/unimap.rs

+9-6
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
1313
#![allow(dead_code)]
1414

15-
use std::hash::Hash;
15+
use std::{hash::Hash, mem};
1616

1717
use rustc_data_structures::fx::FxHashMap;
1818

@@ -187,13 +187,16 @@ impl<V> UniValMap<V> {
187187
self.data.get_mut(idx.idx as usize).and_then(Option::as_mut)
188188
}
189189

190-
/// Delete any value associated with this index. Ok even if the index
191-
/// has no associated value.
192-
pub fn remove(&mut self, idx: UniIndex) {
190+
/// Delete any value associated with this index.
191+
/// Returns None if the value was not present, otherwise
192+
/// returns the previously stored value.
193+
pub fn remove(&mut self, idx: UniIndex) -> Option<V> {
193194
if idx.idx as usize >= self.data.len() {
194-
return;
195+
return None;
195196
}
196-
self.data[idx.idx as usize] = None;
197+
let mut res = None;
198+
mem::swap(&mut res, &mut self.data[idx.idx as usize]);
199+
res
197200
}
198201
}
199202

0 commit comments

Comments
 (0)