Skip to content

Commit ce542b3

Browse files
authored
Unrolled build for rust-lang#116654
Rollup merge of rust-lang#116654 - Zalathar:reloop-traversal, r=oli-obk coverage: Clarify loop-edge detection and graph traversal This is a collection of improvements to two semi-related pieces of code: - The code in `counters` that detects which graph edges don't exit a loop, and would therefore be good candidates to have their coverage computed as an expression rather than having a physical counter. - The code in `graph` that traverses the coverage BCB graph in a particular order, and tracks loops and loop edges along the way (which is relevant to the above). I was originally only planning to make the `graph` changes, but there was going to be a lot of indentation churn in `counters` anyway, and once I started looking I noticed a lot of opportunities for simplification. --- `@rustbot` label +A-code-coverage
2 parents 19149d1 + d99ab97 commit ce542b3

File tree

3 files changed

+130
-164
lines changed

3 files changed

+130
-164
lines changed

compiler/rustc_mir_transform/src/coverage/counters.rs

+48-83
Original file line numberDiff line numberDiff line change
@@ -245,13 +245,13 @@ impl<'a> MakeBcbCounters<'a> {
245245
// the loop. The `traversal` state includes a `context_stack`, providing a way to know if
246246
// the current BCB is in one or more nested loops or not.
247247
let mut traversal = TraverseCoverageGraphWithLoops::new(&self.basic_coverage_blocks);
248-
while let Some(bcb) = traversal.next(self.basic_coverage_blocks) {
248+
while let Some(bcb) = traversal.next() {
249249
if bcb_has_coverage_spans(bcb) {
250250
debug!("{:?} has at least one coverage span. Get or make its counter", bcb);
251251
let branching_counter_operand = self.get_or_make_counter_operand(bcb)?;
252252

253253
if self.bcb_needs_branch_counters(bcb) {
254-
self.make_branch_counters(&mut traversal, bcb, branching_counter_operand)?;
254+
self.make_branch_counters(&traversal, bcb, branching_counter_operand)?;
255255
}
256256
} else {
257257
debug!(
@@ -274,7 +274,7 @@ impl<'a> MakeBcbCounters<'a> {
274274

275275
fn make_branch_counters(
276276
&mut self,
277-
traversal: &mut TraverseCoverageGraphWithLoops,
277+
traversal: &TraverseCoverageGraphWithLoops<'_>,
278278
branching_bcb: BasicCoverageBlock,
279279
branching_counter_operand: Operand,
280280
) -> Result<(), Error> {
@@ -507,21 +507,14 @@ impl<'a> MakeBcbCounters<'a> {
507507
/// found, select any branch.
508508
fn choose_preferred_expression_branch(
509509
&self,
510-
traversal: &TraverseCoverageGraphWithLoops,
510+
traversal: &TraverseCoverageGraphWithLoops<'_>,
511511
branches: &[BcbBranch],
512512
) -> BcbBranch {
513-
let branch_needs_a_counter = |branch: &BcbBranch| self.branch_has_no_counter(branch);
514-
515-
let some_reloop_branch = self.find_some_reloop_branch(traversal, &branches);
516-
if let Some(reloop_branch_without_counter) =
517-
some_reloop_branch.filter(branch_needs_a_counter)
518-
{
519-
debug!(
520-
"Selecting reloop_branch={:?} that still needs a counter, to get the \
521-
`Expression`",
522-
reloop_branch_without_counter
523-
);
524-
reloop_branch_without_counter
513+
let good_reloop_branch = self.find_good_reloop_branch(traversal, &branches);
514+
if let Some(reloop_branch) = good_reloop_branch {
515+
assert!(self.branch_has_no_counter(&reloop_branch));
516+
debug!("Selecting reloop branch {reloop_branch:?} to get an expression");
517+
reloop_branch
525518
} else {
526519
let &branch_without_counter =
527520
branches.iter().find(|&branch| self.branch_has_no_counter(branch)).expect(
@@ -538,75 +531,52 @@ impl<'a> MakeBcbCounters<'a> {
538531
}
539532
}
540533

541-
/// At most, one of the branches (or its edge, from the branching_bcb, if the branch has
542-
/// multiple incoming edges) can have a counter computed by expression.
543-
///
544-
/// If at least one of the branches leads outside of a loop (`found_loop_exit` is
545-
/// true), and at least one other branch does not exit the loop (the first of which
546-
/// is captured in `some_reloop_branch`), it's likely any reloop branch will be
547-
/// executed far more often than loop exit branch, making the reloop branch a better
548-
/// candidate for an expression.
549-
fn find_some_reloop_branch(
534+
/// Tries to find a branch that leads back to the top of a loop, and that
535+
/// doesn't already have a counter. Such branches are good candidates to
536+
/// be given an expression (instead of a physical counter), because they
537+
/// will tend to be executed more times than a loop-exit branch.
538+
fn find_good_reloop_branch(
550539
&self,
551-
traversal: &TraverseCoverageGraphWithLoops,
540+
traversal: &TraverseCoverageGraphWithLoops<'_>,
552541
branches: &[BcbBranch],
553542
) -> Option<BcbBranch> {
554-
let branch_needs_a_counter = |branch: &BcbBranch| self.branch_has_no_counter(branch);
555-
556-
let mut some_reloop_branch: Option<BcbBranch> = None;
557-
for context in traversal.context_stack.iter().rev() {
558-
if let Some((backedge_from_bcbs, _)) = &context.loop_backedges {
559-
let mut found_loop_exit = false;
560-
for &branch in branches.iter() {
561-
if backedge_from_bcbs.iter().any(|&backedge_from_bcb| {
562-
self.bcb_dominates(branch.target_bcb, backedge_from_bcb)
563-
}) {
564-
if let Some(reloop_branch) = some_reloop_branch {
565-
if self.branch_has_no_counter(&reloop_branch) {
566-
// we already found a candidate reloop_branch that still
567-
// needs a counter
568-
continue;
569-
}
570-
}
571-
// The path from branch leads back to the top of the loop. Set this
572-
// branch as the `reloop_branch`. If this branch already has a
573-
// counter, and we find another reloop branch that doesn't have a
574-
// counter yet, that branch will be selected as the `reloop_branch`
575-
// instead.
576-
some_reloop_branch = Some(branch);
577-
} else {
578-
// The path from branch leads outside this loop
579-
found_loop_exit = true;
580-
}
581-
if found_loop_exit
582-
&& some_reloop_branch.filter(branch_needs_a_counter).is_some()
583-
{
584-
// Found both a branch that exits the loop and a branch that returns
585-
// to the top of the loop (`reloop_branch`), and the `reloop_branch`
586-
// doesn't already have a counter.
587-
break;
543+
// Consider each loop on the current traversal context stack, top-down.
544+
for reloop_bcbs in traversal.reloop_bcbs_per_loop() {
545+
let mut all_branches_exit_this_loop = true;
546+
547+
// Try to find a branch that doesn't exit this loop and doesn't
548+
// already have a counter.
549+
for &branch in branches {
550+
// A branch is a reloop branch if it dominates any BCB that has
551+
// an edge back to the loop header. (Other branches are exits.)
552+
let is_reloop_branch = reloop_bcbs.iter().any(|&reloop_bcb| {
553+
self.basic_coverage_blocks.dominates(branch.target_bcb, reloop_bcb)
554+
});
555+
556+
if is_reloop_branch {
557+
all_branches_exit_this_loop = false;
558+
if self.branch_has_no_counter(&branch) {
559+
// We found a good branch to be given an expression.
560+
return Some(branch);
588561
}
562+
// Keep looking for another reloop branch without a counter.
563+
} else {
564+
// This branch exits the loop.
589565
}
590-
if !found_loop_exit {
591-
debug!(
592-
"No branches exit the loop, so any branch without an existing \
593-
counter can have the `Expression`."
594-
);
595-
break;
596-
}
597-
if some_reloop_branch.is_some() {
598-
debug!(
599-
"Found a branch that exits the loop and a branch the loops back to \
600-
the top of the loop (`reloop_branch`). The `reloop_branch` will \
601-
get the `Expression`, as long as it still needs a counter."
602-
);
603-
break;
604-
}
605-
// else all branches exited this loop context, so run the same checks with
606-
// the outer loop(s)
607566
}
567+
568+
if !all_branches_exit_this_loop {
569+
// We found one or more reloop branches, but all of them already
570+
// have counters. Let the caller choose one of the exit branches.
571+
debug!("All reloop branches had counters; skip checking the other loops");
572+
return None;
573+
}
574+
575+
// All of the branches exit this loop, so keep looking for a good
576+
// reloop branch for one of the outer loops.
608577
}
609-
some_reloop_branch
578+
579+
None
610580
}
611581

612582
#[inline]
@@ -652,9 +622,4 @@ impl<'a> MakeBcbCounters<'a> {
652622
fn bcb_has_one_path_to_target(&self, bcb: BasicCoverageBlock) -> bool {
653623
self.bcb_predecessors(bcb).len() <= 1
654624
}
655-
656-
#[inline]
657-
fn bcb_dominates(&self, dom: BasicCoverageBlock, node: BasicCoverageBlock) -> bool {
658-
self.basic_coverage_blocks.dominates(dom, node)
659-
}
660625
}

compiler/rustc_mir_transform/src/coverage/graph.rs

+81-80
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use rustc_index::{IndexSlice, IndexVec};
66
use rustc_middle::mir::{self, BasicBlock, TerminatorKind};
77

88
use std::cmp::Ordering;
9+
use std::collections::VecDeque;
910
use std::ops::{Index, IndexMut};
1011

1112
/// A coverage-specific simplification of the MIR control flow graph (CFG). The `CoverageGraph`s
@@ -385,57 +386,72 @@ fn bcb_filtered_successors<'a, 'tcx>(
385386
/// ensures a loop is completely traversed before processing Blocks after the end of the loop.
386387
#[derive(Debug)]
387388
pub(super) struct TraversalContext {
388-
/// From one or more backedges returning to a loop header.
389-
pub loop_backedges: Option<(Vec<BasicCoverageBlock>, BasicCoverageBlock)>,
390-
391-
/// worklist, to be traversed, of CoverageGraph in the loop with the given loop
392-
/// backedges, such that the loop is the inner inner-most loop containing these
393-
/// CoverageGraph
394-
pub worklist: Vec<BasicCoverageBlock>,
389+
/// BCB with one or more incoming loop backedges, indicating which loop
390+
/// this context is for.
391+
///
392+
/// If `None`, this is the non-loop context for the function as a whole.
393+
loop_header: Option<BasicCoverageBlock>,
394+
395+
/// Worklist of BCBs to be processed in this context.
396+
worklist: VecDeque<BasicCoverageBlock>,
395397
}
396398

397-
pub(super) struct TraverseCoverageGraphWithLoops {
398-
pub backedges: IndexVec<BasicCoverageBlock, Vec<BasicCoverageBlock>>,
399-
pub context_stack: Vec<TraversalContext>,
399+
pub(super) struct TraverseCoverageGraphWithLoops<'a> {
400+
basic_coverage_blocks: &'a CoverageGraph,
401+
402+
backedges: IndexVec<BasicCoverageBlock, Vec<BasicCoverageBlock>>,
403+
context_stack: Vec<TraversalContext>,
400404
visited: BitSet<BasicCoverageBlock>,
401405
}
402406

403-
impl TraverseCoverageGraphWithLoops {
404-
pub fn new(basic_coverage_blocks: &CoverageGraph) -> Self {
405-
let start_bcb = basic_coverage_blocks.start_node();
407+
impl<'a> TraverseCoverageGraphWithLoops<'a> {
408+
pub(super) fn new(basic_coverage_blocks: &'a CoverageGraph) -> Self {
406409
let backedges = find_loop_backedges(basic_coverage_blocks);
407-
let context_stack =
408-
vec![TraversalContext { loop_backedges: None, worklist: vec![start_bcb] }];
410+
411+
let worklist = VecDeque::from([basic_coverage_blocks.start_node()]);
412+
let context_stack = vec![TraversalContext { loop_header: None, worklist }];
413+
409414
// `context_stack` starts with a `TraversalContext` for the main function context (beginning
410415
// with the `start` BasicCoverageBlock of the function). New worklists are pushed to the top
411416
// of the stack as loops are entered, and popped off of the stack when a loop's worklist is
412417
// exhausted.
413418
let visited = BitSet::new_empty(basic_coverage_blocks.num_nodes());
414-
Self { backedges, context_stack, visited }
419+
Self { basic_coverage_blocks, backedges, context_stack, visited }
415420
}
416421

417-
pub fn next(&mut self, basic_coverage_blocks: &CoverageGraph) -> Option<BasicCoverageBlock> {
422+
/// For each loop on the loop context stack (top-down), yields a list of BCBs
423+
/// within that loop that have an outgoing edge back to the loop header.
424+
pub(super) fn reloop_bcbs_per_loop(&self) -> impl Iterator<Item = &[BasicCoverageBlock]> {
425+
self.context_stack
426+
.iter()
427+
.rev()
428+
.filter_map(|context| context.loop_header)
429+
.map(|header_bcb| self.backedges[header_bcb].as_slice())
430+
}
431+
432+
pub(super) fn next(&mut self) -> Option<BasicCoverageBlock> {
418433
debug!(
419434
"TraverseCoverageGraphWithLoops::next - context_stack: {:?}",
420435
self.context_stack.iter().rev().collect::<Vec<_>>()
421436
);
422437

423438
while let Some(context) = self.context_stack.last_mut() {
424-
if let Some(next_bcb) = context.worklist.pop() {
425-
if !self.visited.insert(next_bcb) {
426-
debug!("Already visited: {:?}", next_bcb);
439+
if let Some(bcb) = context.worklist.pop_front() {
440+
if !self.visited.insert(bcb) {
441+
debug!("Already visited: {bcb:?}");
427442
continue;
428443
}
429-
debug!("Visiting {:?}", next_bcb);
430-
if self.backedges[next_bcb].len() > 0 {
431-
debug!("{:?} is a loop header! Start a new TraversalContext...", next_bcb);
444+
debug!("Visiting {bcb:?}");
445+
446+
if self.backedges[bcb].len() > 0 {
447+
debug!("{bcb:?} is a loop header! Start a new TraversalContext...");
432448
self.context_stack.push(TraversalContext {
433-
loop_backedges: Some((self.backedges[next_bcb].clone(), next_bcb)),
434-
worklist: Vec::new(),
449+
loop_header: Some(bcb),
450+
worklist: VecDeque::new(),
435451
});
436452
}
437-
self.extend_worklist(basic_coverage_blocks, next_bcb);
438-
return Some(next_bcb);
453+
self.add_successors_to_worklists(bcb);
454+
return Some(bcb);
439455
} else {
440456
// Strip contexts with empty worklists from the top of the stack
441457
self.context_stack.pop();
@@ -445,13 +461,10 @@ impl TraverseCoverageGraphWithLoops {
445461
None
446462
}
447463

448-
pub fn extend_worklist(
449-
&mut self,
450-
basic_coverage_blocks: &CoverageGraph,
451-
bcb: BasicCoverageBlock,
452-
) {
453-
let successors = &basic_coverage_blocks.successors[bcb];
464+
pub fn add_successors_to_worklists(&mut self, bcb: BasicCoverageBlock) {
465+
let successors = &self.basic_coverage_blocks.successors[bcb];
454466
debug!("{:?} has {} successors:", bcb, successors.len());
467+
455468
for &successor in successors {
456469
if successor == bcb {
457470
debug!(
@@ -460,56 +473,44 @@ impl TraverseCoverageGraphWithLoops {
460473
bcb
461474
);
462475
// Don't re-add this successor to the worklist. We are already processing it.
476+
// FIXME: This claims to skip just the self-successor, but it actually skips
477+
// all other successors as well. Does that matter?
463478
break;
464479
}
465-
for context in self.context_stack.iter_mut().rev() {
466-
// Add successors of the current BCB to the appropriate context. Successors that
467-
// stay within a loop are added to the BCBs context worklist. Successors that
468-
// exit the loop (they are not dominated by the loop header) must be reachable
469-
// from other BCBs outside the loop, and they will be added to a different
470-
// worklist.
471-
//
472-
// Branching blocks (with more than one successor) must be processed before
473-
// blocks with only one successor, to prevent unnecessarily complicating
474-
// `Expression`s by creating a Counter in a `BasicCoverageBlock` that the
475-
// branching block would have given an `Expression` (or vice versa).
476-
let (some_successor_to_add, some_loop_header) =
477-
if let Some((_, loop_header)) = context.loop_backedges {
478-
if basic_coverage_blocks.dominates(loop_header, successor) {
479-
(Some(successor), Some(loop_header))
480-
} else {
481-
(None, None)
482-
}
483-
} else {
484-
(Some(successor), None)
485-
};
486-
if let Some(successor_to_add) = some_successor_to_add {
487-
if basic_coverage_blocks.successors[successor_to_add].len() > 1 {
488-
debug!(
489-
"{:?} successor is branching. Prioritize it at the beginning of \
490-
the {}",
491-
successor_to_add,
492-
if let Some(loop_header) = some_loop_header {
493-
format!("worklist for the loop headed by {loop_header:?}")
494-
} else {
495-
String::from("non-loop worklist")
496-
},
497-
);
498-
context.worklist.insert(0, successor_to_add);
499-
} else {
500-
debug!(
501-
"{:?} successor is non-branching. Defer it to the end of the {}",
502-
successor_to_add,
503-
if let Some(loop_header) = some_loop_header {
504-
format!("worklist for the loop headed by {loop_header:?}")
505-
} else {
506-
String::from("non-loop worklist")
507-
},
508-
);
509-
context.worklist.push(successor_to_add);
480+
481+
// Add successors of the current BCB to the appropriate context. Successors that
482+
// stay within a loop are added to the BCBs context worklist. Successors that
483+
// exit the loop (they are not dominated by the loop header) must be reachable
484+
// from other BCBs outside the loop, and they will be added to a different
485+
// worklist.
486+
//
487+
// Branching blocks (with more than one successor) must be processed before
488+
// blocks with only one successor, to prevent unnecessarily complicating
489+
// `Expression`s by creating a Counter in a `BasicCoverageBlock` that the
490+
// branching block would have given an `Expression` (or vice versa).
491+
492+
let context = self
493+
.context_stack
494+
.iter_mut()
495+
.rev()
496+
.find(|context| match context.loop_header {
497+
Some(loop_header) => {
498+
self.basic_coverage_blocks.dominates(loop_header, successor)
510499
}
511-
break;
512-
}
500+
None => true,
501+
})
502+
.unwrap_or_else(|| bug!("should always fall back to the root non-loop context"));
503+
debug!("adding to worklist for {:?}", context.loop_header);
504+
505+
// FIXME: The code below had debug messages claiming to add items to a
506+
// particular end of the worklist, but was confused about which end was
507+
// which. The existing behaviour has been preserved for now, but it's
508+
// unclear what the intended behaviour was.
509+
510+
if self.basic_coverage_blocks.successors[successor].len() > 1 {
511+
context.worklist.push_back(successor);
512+
} else {
513+
context.worklist.push_front(successor);
513514
}
514515
}
515516
}

0 commit comments

Comments
 (0)