Skip to content

Commit 8b10bda

Browse files
committed
Auto merge of rust-lang#3833 - JoJoDeveloping:tb-fix-stack-overflow, r=RalfJung
Make Tree Borrows Provenance GC no longer produce stack overflows Most functions operating on Tree Borrows' trees are carefully written to not cause stack overflows due to too much recursion. The one exception is [`Tree::keep_only_needed`](https://github.com/rust-lang/miri/blob/94f5588fafcc7d59fce60ca8f7af0208e6f618d4/src/borrow_tracker/tree_borrows/tree.rs#L724), which just uses regular recursion. This function is part of the provenance GC, so it is called regularly for every allocation in the program. Tests show that this is a problem in practice. For example, the test `fill::horizontal_line` in crate `tiny-skia` (version 0.11.4) is such a test. This PR changes this, this test no now longer crashes. Instead, it succeeds (after a _long_ time).
2 parents 695a4f9 + b5d77d8 commit 8b10bda

File tree

3 files changed

+74
-47
lines changed

3 files changed

+74
-47
lines changed

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"

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
}

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)