Skip to content

Commit e454c45

Browse files
committed
Auto merge of #132091 - Zalathar:graph-loops, r=saethlin
coverage: Don't rely on the custom traversal to find enclosing loops This opens up the possibility of modifying or removing the custom graph traversal used in coverage counter creation, without losing access to the heuristics that care about a node's enclosing loops. Actually changing the traversal is left for future work, because this PR on its own doesn't change the emitted coverage mappings at all.
2 parents de7cef7 + 19b2142 commit e454c45

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)