Skip to content

Commit 19b2142

Browse files
committed
coverage: Don't rely on the custom traversal to find enclosing loops
1 parent b188577 commit 19b2142

File tree

2 files changed

+85
-63
lines changed

2 files changed

+85
-63
lines changed

Diff for: 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);

Diff for: compiler/rustc_mir_transform/src/coverage/graph.rs

+72-45
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,38 @@ 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.
75-
let reverse_post_order = graph::iterate::reverse_post_order(&this, this.start_node());
84+
// Iterate over all nodes, such that dominating nodes are visited before
85+
// the nodes they dominate. Either preorder or reverse postorder is fine.
86+
let dominator_order = graph::iterate::reverse_post_order(&this, this.start_node());
7687
// The coverage graph is created by traversal, so all nodes are reachable.
77-
assert_eq!(reverse_post_order.len(), this.num_nodes());
78-
for (rank, bcb) in (0u32..).zip(reverse_post_order) {
88+
assert_eq!(dominator_order.len(), this.num_nodes());
89+
for (rank, bcb) in (0u32..).zip(dominator_order) {
90+
// The dominator rank of each node is its index in a dominator-order traversal.
7991
this.dominator_order_rank[bcb] = rank;
92+
93+
// A node is a loop header if it dominates any of its predecessors.
94+
if this.reloop_predecessors(bcb).next().is_some() {
95+
this.is_loop_header.insert(bcb);
96+
}
97+
98+
// If the immediate dominator is a loop header, that's our enclosing loop.
99+
// Otherwise, inherit the immediate dominator's enclosing loop.
100+
// (Dominator order ensures that we already processed the dominator.)
101+
if let Some(dom) = this.dominators().immediate_dominator(bcb) {
102+
this.enclosing_loop_header[bcb] = this
103+
.is_loop_header
104+
.contains(dom)
105+
.then_some(dom)
106+
.or_else(|| this.enclosing_loop_header[dom]);
107+
}
80108
}
81109

82110
// The coverage graph's entry-point node (bcb0) always starts with bb0,
@@ -172,9 +200,14 @@ impl CoverageGraph {
172200
if bb.index() < self.bb_to_bcb.len() { self.bb_to_bcb[bb] } else { None }
173201
}
174202

203+
#[inline(always)]
204+
fn dominators(&self) -> &Dominators<BasicCoverageBlock> {
205+
self.dominators.as_ref().unwrap()
206+
}
207+
175208
#[inline(always)]
176209
pub(crate) fn dominates(&self, dom: BasicCoverageBlock, node: BasicCoverageBlock) -> bool {
177-
self.dominators.as_ref().unwrap().dominates(dom, node)
210+
self.dominators().dominates(dom, node)
178211
}
179212

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

219282
impl Index<BasicCoverageBlock> for CoverageGraph {
@@ -439,15 +502,12 @@ struct TraversalContext {
439502
pub(crate) struct TraverseCoverageGraphWithLoops<'a> {
440503
basic_coverage_blocks: &'a CoverageGraph,
441504

442-
backedges: IndexVec<BasicCoverageBlock, Vec<BasicCoverageBlock>>,
443505
context_stack: Vec<TraversalContext>,
444506
visited: BitSet<BasicCoverageBlock>,
445507
}
446508

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

@@ -456,17 +516,7 @@ impl<'a> TraverseCoverageGraphWithLoops<'a> {
456516
// of the stack as loops are entered, and popped off of the stack when a loop's worklist is
457517
// exhausted.
458518
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())
519+
Self { basic_coverage_blocks, context_stack, visited }
470520
}
471521

472522
pub(crate) fn next(&mut self) -> Option<BasicCoverageBlock> {
@@ -488,7 +538,7 @@ impl<'a> TraverseCoverageGraphWithLoops<'a> {
488538
}
489539
debug!("Visiting {bcb:?}");
490540

491-
if self.backedges[bcb].len() > 0 {
541+
if self.basic_coverage_blocks.is_loop_header.contains(bcb) {
492542
debug!("{bcb:?} is a loop header! Start a new TraversalContext...");
493543
self.context_stack
494544
.push(TraversalContext { loop_header: Some(bcb), worklist: VecDeque::new() });
@@ -566,29 +616,6 @@ impl<'a> TraverseCoverageGraphWithLoops<'a> {
566616
}
567617
}
568618

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-
592619
fn short_circuit_preorder<'a, 'tcx, F, Iter>(
593620
body: &'a mir::Body<'tcx>,
594621
filtered_successors: F,

0 commit comments

Comments
 (0)