Skip to content

Commit 88a4970

Browse files
committed
coverage: Don't rely on the custom traversal to find enclosing loops
1 parent 55b7f8e commit 88a4970

File tree

2 files changed

+81
-60
lines changed

2 files changed

+81
-60
lines changed

compiler/rustc_mir_transform/src/coverage/counters.rs

+13-18
Original file line numberDiff line numberDiff line change
@@ -280,13 +280,12 @@ impl<'a> CountersBuilder<'a> {
280280
//
281281
// The traversal tries to ensure that, when a loop is encountered, all
282282
// nodes within the loop are visited before visiting any nodes outside
283-
// the loop. It also keeps track of which loop(s) the traversal is
284-
// currently inside.
283+
// the loop.
285284
let mut traversal = TraverseCoverageGraphWithLoops::new(self.graph);
286285
while let Some(bcb) = traversal.next() {
287286
let _span = debug_span!("traversal", ?bcb).entered();
288287
if self.bcb_needs_counter.contains(bcb) {
289-
self.make_node_counter_and_out_edge_counters(&traversal, bcb);
288+
self.make_node_counter_and_out_edge_counters(bcb);
290289
}
291290
}
292291

@@ -299,12 +298,8 @@ impl<'a> CountersBuilder<'a> {
299298

300299
/// Make sure the given node has a node counter, and then make sure each of
301300
/// its out-edges has an edge counter (if appropriate).
302-
#[instrument(level = "debug", skip(self, traversal))]
303-
fn make_node_counter_and_out_edge_counters(
304-
&mut self,
305-
traversal: &TraverseCoverageGraphWithLoops<'_>,
306-
from_bcb: BasicCoverageBlock,
307-
) {
301+
#[instrument(level = "debug", skip(self))]
302+
fn make_node_counter_and_out_edge_counters(&mut self, from_bcb: BasicCoverageBlock) {
308303
// First, ensure that this node has a counter of some kind.
309304
// We might also use that counter to compute one of the out-edge counters.
310305
let node_counter = self.get_or_make_node_counter(from_bcb);
@@ -337,8 +332,7 @@ impl<'a> CountersBuilder<'a> {
337332

338333
// If there are out-edges without counters, choose one to be given an expression
339334
// (computed from this node and the other out-edges) instead of a physical counter.
340-
let Some(target_bcb) =
341-
self.choose_out_edge_for_expression(traversal, &candidate_successors)
335+
let Some(target_bcb) = self.choose_out_edge_for_expression(from_bcb, &candidate_successors)
342336
else {
343337
return;
344338
};
@@ -462,12 +456,12 @@ impl<'a> CountersBuilder<'a> {
462456
/// choose one to be given a counter expression instead of a physical counter.
463457
fn choose_out_edge_for_expression(
464458
&self,
465-
traversal: &TraverseCoverageGraphWithLoops<'_>,
459+
from_bcb: BasicCoverageBlock,
466460
candidate_successors: &[BasicCoverageBlock],
467461
) -> Option<BasicCoverageBlock> {
468462
// Try to find a candidate that leads back to the top of a loop,
469463
// because reloop edges tend to be executed more times than loop-exit edges.
470-
if let Some(reloop_target) = self.find_good_reloop_edge(traversal, &candidate_successors) {
464+
if let Some(reloop_target) = self.find_good_reloop_edge(from_bcb, &candidate_successors) {
471465
debug!("Selecting reloop target {reloop_target:?} to get an expression");
472466
return Some(reloop_target);
473467
}
@@ -486,7 +480,7 @@ impl<'a> CountersBuilder<'a> {
486480
/// for them to be able to avoid a physical counter increment.
487481
fn find_good_reloop_edge(
488482
&self,
489-
traversal: &TraverseCoverageGraphWithLoops<'_>,
483+
from_bcb: BasicCoverageBlock,
490484
candidate_successors: &[BasicCoverageBlock],
491485
) -> Option<BasicCoverageBlock> {
492486
// If there are no candidates, avoid iterating over the loop stack.
@@ -495,14 +489,15 @@ impl<'a> CountersBuilder<'a> {
495489
}
496490

497491
// Consider each loop on the current traversal context stack, top-down.
498-
for reloop_bcbs in traversal.reloop_bcbs_per_loop() {
492+
for loop_header_node in self.graph.loop_headers_containing(from_bcb) {
499493
// Try to find a candidate edge that doesn't exit this loop.
500494
for &target_bcb in candidate_successors {
501495
// An edge is a reloop edge if its target dominates any BCB that has
502496
// an edge back to the loop header. (Otherwise it's an exit edge.)
503-
let is_reloop_edge = reloop_bcbs
504-
.iter()
505-
.any(|&reloop_bcb| self.graph.dominates(target_bcb, reloop_bcb));
497+
let is_reloop_edge = self
498+
.graph
499+
.reloop_predecessors(loop_header_node)
500+
.any(|reloop_bcb| self.graph.dominates(target_bcb, reloop_bcb));
506501
if is_reloop_edge {
507502
// We found a good out-edge to be given an expression.
508503
return Some(target_bcb);

compiler/rustc_mir_transform/src/coverage/graph.rs

+68-42
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
use std::cmp::Ordering;
22
use std::collections::VecDeque;
3+
use std::iter;
34
use std::ops::{Index, IndexMut};
45

56
use rustc_data_structures::captures::Captures;
67
use rustc_data_structures::fx::FxHashSet;
7-
use rustc_data_structures::graph::dominators::{self, Dominators};
8+
use rustc_data_structures::graph::dominators::Dominators;
89
use rustc_data_structures::graph::{self, DirectedGraph, StartNode};
910
use rustc_index::IndexVec;
1011
use rustc_index::bit_set::BitSet;
@@ -20,11 +21,17 @@ pub(crate) struct CoverageGraph {
2021
bb_to_bcb: IndexVec<BasicBlock, Option<BasicCoverageBlock>>,
2122
pub(crate) successors: IndexVec<BasicCoverageBlock, Vec<BasicCoverageBlock>>,
2223
pub(crate) predecessors: IndexVec<BasicCoverageBlock, Vec<BasicCoverageBlock>>,
24+
2325
dominators: Option<Dominators<BasicCoverageBlock>>,
2426
/// Allows nodes to be compared in some total order such that _if_
2527
/// `a` dominates `b`, then `a < b`. If neither node dominates the other,
2628
/// their relative order is consistent but arbitrary.
2729
dominator_order_rank: IndexVec<BasicCoverageBlock, u32>,
30+
/// A loop header is a node that dominates one or more of its predecessors.
31+
is_loop_header: BitSet<BasicCoverageBlock>,
32+
/// For each node, the loop header node of its nearest enclosing loop.
33+
/// This forms a linked list that can be traversed to find all enclosing loops.
34+
enclosing_loop_header: IndexVec<BasicCoverageBlock, Option<BasicCoverageBlock>>,
2835
}
2936

3037
impl CoverageGraph {
@@ -66,17 +73,37 @@ impl CoverageGraph {
6673
predecessors,
6774
dominators: None,
6875
dominator_order_rank: IndexVec::from_elem_n(0, num_nodes),
76+
is_loop_header: BitSet::new_empty(num_nodes),
77+
enclosing_loop_header: IndexVec::from_elem_n(None, num_nodes),
6978
};
7079
assert_eq!(num_nodes, this.num_nodes());
7180

72-
this.dominators = Some(dominators::dominators(&this));
81+
// Set the dominators first, because later init steps rely on them.
82+
this.dominators = Some(graph::dominators::dominators(&this));
7383

74-
// The dominator rank of each node is just its index in a reverse-postorder traversal.
84+
// Reverse postorder visits dominating nodes before the nodes they dominate.
7585
let reverse_post_order = graph::iterate::reverse_post_order(&this, this.start_node());
7686
// The coverage graph is created by traversal, so all nodes are reachable.
7787
assert_eq!(reverse_post_order.len(), this.num_nodes());
7888
for (rank, bcb) in (0u32..).zip(reverse_post_order) {
89+
// The dominator rank of each node is just its index in a reverse-postorder traversal.
7990
this.dominator_order_rank[bcb] = rank;
91+
92+
// A node is a loop header if it dominates any of its predecessors.
93+
if this.reloop_predecessors(bcb).next().is_some() {
94+
this.is_loop_header.insert(bcb);
95+
}
96+
97+
// If the immediate dominator is a loop header, that's our enclosing loop.
98+
// Otherwise, inherit the immediate dominator's enclosing loop.
99+
// (Reverse postorder ensures that we already processed the dominator.)
100+
if let Some(dom) = this.dominators().immediate_dominator(bcb) {
101+
this.enclosing_loop_header[bcb] = this
102+
.is_loop_header
103+
.contains(dom)
104+
.then_some(dom)
105+
.or_else(|| this.enclosing_loop_header[dom]);
106+
}
80107
}
81108

82109
// The coverage graph's entry-point node (bcb0) always starts with bb0,
@@ -172,9 +199,14 @@ impl CoverageGraph {
172199
if bb.index() < self.bb_to_bcb.len() { self.bb_to_bcb[bb] } else { None }
173200
}
174201

202+
#[inline(always)]
203+
fn dominators(&self) -> &Dominators<BasicCoverageBlock> {
204+
self.dominators.as_ref().unwrap()
205+
}
206+
175207
#[inline(always)]
176208
pub(crate) fn dominates(&self, dom: BasicCoverageBlock, node: BasicCoverageBlock) -> bool {
177-
self.dominators.as_ref().unwrap().dominates(dom, node)
209+
self.dominators().dominates(dom, node)
178210
}
179211

180212
#[inline(always)]
@@ -214,6 +246,36 @@ impl CoverageGraph {
214246
None
215247
}
216248
}
249+
250+
/// For each loop that contains the given node, yields the "loop header"
251+
/// node representing that loop, from innermost to outermost. If the given
252+
/// node is itself a loop header, it is yielded first.
253+
pub(crate) fn loop_headers_containing(
254+
&self,
255+
bcb: BasicCoverageBlock,
256+
) -> impl Iterator<Item = BasicCoverageBlock> + Captures<'_> {
257+
let self_if_loop_header = self.is_loop_header.contains(bcb).then_some(bcb).into_iter();
258+
259+
let mut curr = Some(bcb);
260+
let strictly_enclosing = iter::from_fn(move || {
261+
let enclosing = self.enclosing_loop_header[curr?];
262+
curr = enclosing;
263+
enclosing
264+
});
265+
266+
self_if_loop_header.chain(strictly_enclosing)
267+
}
268+
269+
/// For the given node, yields the subset of its predecessor nodes that
270+
/// it dominates. If that subset is non-empty, the node is a "loop header",
271+
/// and each of those predecessors represents an in-edge that jumps back to
272+
/// the top of its loop.
273+
pub(crate) fn reloop_predecessors(
274+
&self,
275+
to_bcb: BasicCoverageBlock,
276+
) -> impl Iterator<Item = BasicCoverageBlock> + Captures<'_> {
277+
self.predecessors[to_bcb].iter().copied().filter(move |&pred| self.dominates(to_bcb, pred))
278+
}
217279
}
218280

219281
impl Index<BasicCoverageBlock> for CoverageGraph {
@@ -439,15 +501,12 @@ struct TraversalContext {
439501
pub(crate) struct TraverseCoverageGraphWithLoops<'a> {
440502
basic_coverage_blocks: &'a CoverageGraph,
441503

442-
backedges: IndexVec<BasicCoverageBlock, Vec<BasicCoverageBlock>>,
443504
context_stack: Vec<TraversalContext>,
444505
visited: BitSet<BasicCoverageBlock>,
445506
}
446507

447508
impl<'a> TraverseCoverageGraphWithLoops<'a> {
448509
pub(crate) fn new(basic_coverage_blocks: &'a CoverageGraph) -> Self {
449-
let backedges = find_loop_backedges(basic_coverage_blocks);
450-
451510
let worklist = VecDeque::from([basic_coverage_blocks.start_node()]);
452511
let context_stack = vec![TraversalContext { loop_header: None, worklist }];
453512

@@ -456,17 +515,7 @@ impl<'a> TraverseCoverageGraphWithLoops<'a> {
456515
// of the stack as loops are entered, and popped off of the stack when a loop's worklist is
457516
// exhausted.
458517
let visited = BitSet::new_empty(basic_coverage_blocks.num_nodes());
459-
Self { basic_coverage_blocks, backedges, context_stack, visited }
460-
}
461-
462-
/// For each loop on the loop context stack (top-down), yields a list of BCBs
463-
/// within that loop that have an outgoing edge back to the loop header.
464-
pub(crate) fn reloop_bcbs_per_loop(&self) -> impl Iterator<Item = &[BasicCoverageBlock]> {
465-
self.context_stack
466-
.iter()
467-
.rev()
468-
.filter_map(|context| context.loop_header)
469-
.map(|header_bcb| self.backedges[header_bcb].as_slice())
518+
Self { basic_coverage_blocks, context_stack, visited }
470519
}
471520

472521
pub(crate) fn next(&mut self) -> Option<BasicCoverageBlock> {
@@ -488,7 +537,7 @@ impl<'a> TraverseCoverageGraphWithLoops<'a> {
488537
}
489538
debug!("Visiting {bcb:?}");
490539

491-
if self.backedges[bcb].len() > 0 {
540+
if self.basic_coverage_blocks.is_loop_header.contains(bcb) {
492541
debug!("{bcb:?} is a loop header! Start a new TraversalContext...");
493542
self.context_stack
494543
.push(TraversalContext { loop_header: Some(bcb), worklist: VecDeque::new() });
@@ -566,29 +615,6 @@ impl<'a> TraverseCoverageGraphWithLoops<'a> {
566615
}
567616
}
568617

569-
fn find_loop_backedges(
570-
basic_coverage_blocks: &CoverageGraph,
571-
) -> IndexVec<BasicCoverageBlock, Vec<BasicCoverageBlock>> {
572-
let num_bcbs = basic_coverage_blocks.num_nodes();
573-
let mut backedges = IndexVec::from_elem_n(Vec::<BasicCoverageBlock>::new(), num_bcbs);
574-
575-
// Identify loops by their backedges.
576-
for (bcb, _) in basic_coverage_blocks.iter_enumerated() {
577-
for &successor in &basic_coverage_blocks.successors[bcb] {
578-
if basic_coverage_blocks.dominates(successor, bcb) {
579-
let loop_header = successor;
580-
let backedge_from_bcb = bcb;
581-
debug!(
582-
"Found BCB backedge: {:?} -> loop_header: {:?}",
583-
backedge_from_bcb, loop_header
584-
);
585-
backedges[loop_header].push(backedge_from_bcb);
586-
}
587-
}
588-
}
589-
backedges
590-
}
591-
592618
fn short_circuit_preorder<'a, 'tcx, F, Iter>(
593619
body: &'a mir::Body<'tcx>,
594620
filtered_successors: F,

0 commit comments

Comments
 (0)