Skip to content

Commit 6eabf03

Browse files
committed
coverage: Make yank_to_spantree_root iterative instead of recursive
This avoids having to worry about stack space when traversing very long spantree paths, e.g. when instrumenting a long sequence of if/else statements.
1 parent f1300c8 commit 6eabf03

File tree

2 files changed

+36
-19
lines changed

2 files changed

+36
-19
lines changed

compiler/rustc_mir_transform/src/coverage/counters/node_flow.rs

+35-19
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ struct SpantreeBuilder<'a, Node: Idx> {
159159
/// A supernode without a span edge is the root of its component of the
160160
/// spantree. Nodes that aren't supernodes cannot have a spantree edge.
161161
span_edges: IndexVec<Node, Option<SpantreeEdge<Node>>>,
162+
/// Shared path buffer recycled by all calls to `yank_to_spantree_root`.
163+
yank_buffer: Vec<Node>,
162164
/// An in-progress counter expression for each node. Each expression is
163165
/// initially empty, and will be filled in as relevant nodes are visited.
164166
counter_exprs: IndexVec<Node, CounterExprVec<Node>>,
@@ -171,6 +173,7 @@ impl<'a, Node: Idx> SpantreeBuilder<'a, Node> {
171173
graph,
172174
is_unvisited: DenseBitSet::new_filled(num_nodes),
173175
span_edges: IndexVec::from_fn_n(|_| None, num_nodes),
176+
yank_buffer: vec![],
174177
counter_exprs: IndexVec::from_fn_n(|_| SmallVec::new(), num_nodes),
175178
}
176179
}
@@ -192,24 +195,37 @@ impl<'a, Node: Idx> SpantreeBuilder<'a, Node> {
192195
fn yank_to_spantree_root(&mut self, this: Node) {
193196
debug_assert!(self.graph.is_supernode(this));
194197

195-
// Temporarily remove this supernode (any any spantree-children) from its
196-
// spantree component, by disconnecting the edge to its spantree-parent.
197-
let Some(SpantreeEdge { is_reversed, claiming_node, span_parent }) =
198-
self.span_edges[this].take()
199-
else {
200-
// This supernode has no spantree-parent edge, so it is already the
201-
// root of its spantree component.
202-
return;
203-
};
204-
205-
// Recursively make our immediate spantree-parent the root of what's
206-
// left of its component, so that only one more edge rotation is needed.
207-
self.yank_to_spantree_root(span_parent);
208-
209-
// Recreate the removed edge, but in the opposite direction.
210-
// Now `this` is the root of its spantree component.
211-
self.span_edges[span_parent] =
212-
Some(SpantreeEdge { is_reversed: !is_reversed, claiming_node, span_parent: this });
198+
// The rotation is done iteratively, by first traversing from `this` to
199+
// its root and storing the path in a buffer, and then traversing the
200+
// path buffer backwards to reverse all the edges.
201+
202+
// Recycle the same path buffer for all calls to this method.
203+
let path_buf = &mut self.yank_buffer;
204+
path_buf.clear();
205+
path_buf.push(this);
206+
207+
// Traverse the spantree until we reach a supernode that has no
208+
// span-parent, which must be the root.
209+
let mut curr = this;
210+
while let &Some(SpantreeEdge { span_parent, .. }) = &self.span_edges[curr] {
211+
path_buf.push(span_parent);
212+
curr = span_parent;
213+
}
214+
215+
// For each spantree edge `a -> b` in the path that was just traversed,
216+
// reverse it to become `a <- b`, while preserving `claiming_node`.
217+
for &[a, b] in path_buf.array_windows::<2>().rev() {
218+
let SpantreeEdge { is_reversed, claiming_node, span_parent } = self.span_edges[a]
219+
.take()
220+
.expect("all nodes in the path (except the last) have a `span_parent`");
221+
debug_assert_eq!(span_parent, b);
222+
debug_assert!(self.span_edges[b].is_none());
223+
self.span_edges[b] =
224+
Some(SpantreeEdge { is_reversed: !is_reversed, claiming_node, span_parent: a });
225+
}
226+
227+
// The result of the rotation is that `this` is now a spantree root.
228+
debug_assert!(self.span_edges[this].is_none());
213229
}
214230

215231
/// Must be called exactly once for each node in the balanced-flow graph.
@@ -273,7 +289,7 @@ impl<'a, Node: Idx> SpantreeBuilder<'a, Node> {
273289
/// Asserts that all nodes have been visited, and returns the computed
274290
/// counter expressions (made up of physical counters) for each node.
275291
fn finish(self) -> IndexVec<Node, CounterExprVec<Node>> {
276-
let Self { graph, is_unvisited, span_edges, counter_exprs } = self;
292+
let Self { graph, is_unvisited, span_edges, yank_buffer: _, counter_exprs } = self;
277293
assert!(is_unvisited.is_empty(), "some nodes were never visited: {is_unvisited:?}");
278294
debug_assert!(
279295
span_edges

compiler/rustc_mir_transform/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// tidy-alphabetical-start
2+
#![feature(array_windows)]
23
#![feature(assert_matches)]
34
#![feature(box_patterns)]
45
#![feature(const_type_name)]

0 commit comments

Comments
 (0)