Rollup merge of #132389 - Zalathar:graph-tweaks, r=jieyouxu

coverage: Simplify parts of coverage graph creation

This is a combination of three semi-related simplifications to how coverage graphs are created, grouped into one PR to avoid conflicts.

There are no observable changes to the output of any of the coverage tests.
This commit is contained in:
Stuart Cook 2024-11-08 18:51:29 +11:00 committed by GitHub
commit 1b55244aa2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,7 +1,7 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::iter;
use std::ops::{Index, IndexMut}; use std::ops::{Index, IndexMut};
use std::{iter, mem, slice};
use rustc_data_structures::captures::Captures; use rustc_data_structures::captures::Captures;
use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::fx::FxHashSet;
@ -127,10 +127,10 @@ fn compute_basic_coverage_blocks(
let mut bcbs = IndexVec::<BasicCoverageBlock, _>::with_capacity(num_basic_blocks); let mut bcbs = IndexVec::<BasicCoverageBlock, _>::with_capacity(num_basic_blocks);
let mut bb_to_bcb = IndexVec::from_elem_n(None, num_basic_blocks); let mut bb_to_bcb = IndexVec::from_elem_n(None, num_basic_blocks);
let mut add_basic_coverage_block = |basic_blocks: &mut Vec<BasicBlock>| { let mut flush_chain_into_new_bcb = |current_chain: &mut Vec<BasicBlock>| {
// Take the accumulated list of blocks, leaving the vector empty // Take the accumulated list of blocks, leaving the vector empty
// to be used by subsequent BCBs. // to be used by subsequent BCBs.
let basic_blocks = std::mem::take(basic_blocks); let basic_blocks = mem::take(current_chain);
let bcb = bcbs.next_index(); let bcb = bcbs.next_index();
for &bb in basic_blocks.iter() { for &bb in basic_blocks.iter() {
@ -141,48 +141,41 @@ fn compute_basic_coverage_blocks(
bcb_filtered_successors(mir_body[bb].terminator()).is_out_summable() bcb_filtered_successors(mir_body[bb].terminator()).is_out_summable()
}); });
let bcb_data = BasicCoverageBlockData { basic_blocks, is_out_summable }; let bcb_data = BasicCoverageBlockData { basic_blocks, is_out_summable };
debug!("adding bcb{}: {:?}", bcb.index(), bcb_data); debug!("adding {bcb:?}: {bcb_data:?}");
bcbs.push(bcb_data); bcbs.push(bcb_data);
}; };
// Walk the MIR CFG using a Preorder traversal, which starts from `START_BLOCK` and follows // Traverse the MIR control-flow graph, accumulating chains of blocks
// each block terminator's `successors()`. Coverage spans must map to actual source code, // that can be combined into a single node in the coverage graph.
// so compiler generated blocks and paths can be ignored. To that end, the CFG traversal // A depth-first search ensures that if two nodes can be chained
// intentionally omits unwind paths. // together, they will be adjacent in the traversal order.
// FIXME(#78544): MIR InstrumentCoverage: Improve coverage of `#[should_panic]` tests and
// `catch_unwind()` handlers.
// Accumulates a chain of blocks that will be combined into one BCB. // Accumulates a chain of blocks that will be combined into one BCB.
let mut basic_blocks = Vec::new(); let mut current_chain = vec![];
let filtered_successors = |bb| bcb_filtered_successors(mir_body[bb].terminator()); let subgraph = CoverageRelevantSubgraph::new(&mir_body.basic_blocks);
for bb in short_circuit_preorder(mir_body, filtered_successors) for bb in graph::depth_first_search(subgraph, mir::START_BLOCK)
.filter(|&bb| mir_body[bb].terminator().kind != TerminatorKind::Unreachable) .filter(|&bb| mir_body[bb].terminator().kind != TerminatorKind::Unreachable)
{ {
// If the previous block can't be chained into `bb`, flush the accumulated if let Some(&prev) = current_chain.last() {
// blocks into a new BCB, then start building the next chain. // Adding a block to a non-empty chain is allowed if the
if let Some(&prev) = basic_blocks.last() // previous block permits chaining, and the current block has
&& (!filtered_successors(prev).is_chainable() || { // `prev` as its sole predecessor.
// If `bb` has multiple predecessor blocks, or `prev` isn't let can_chain = subgraph.coverage_successors(prev).is_out_chainable()
// one of its predecessors, we can't chain and must flush. && mir_body.basic_blocks.predecessors()[bb].as_slice() == &[prev];
let predecessors = &mir_body.basic_blocks.predecessors()[bb]; if !can_chain {
predecessors.len() > 1 || !predecessors.contains(&prev) // The current block can't be added to the existing chain, so
}) // flush that chain into a new BCB, and start a new chain.
{ flush_chain_into_new_bcb(&mut current_chain);
debug!( }
terminator_kind = ?mir_body[prev].terminator().kind,
predecessors = ?&mir_body.basic_blocks.predecessors()[bb],
"can't chain from {prev:?} to {bb:?}"
);
add_basic_coverage_block(&mut basic_blocks);
} }
basic_blocks.push(bb); current_chain.push(bb);
} }
if !basic_blocks.is_empty() { if !current_chain.is_empty() {
debug!("flushing accumulated blocks into one last BCB"); debug!("flushing accumulated blocks into one last BCB");
add_basic_coverage_block(&mut basic_blocks); flush_chain_into_new_bcb(&mut current_chain);
} }
(bcbs, bb_to_bcb) (bcbs, bb_to_bcb)
@ -389,34 +382,28 @@ pub(crate) fn last_bb(&self) -> BasicBlock {
/// indicates whether that block can potentially be combined into the same BCB /// indicates whether that block can potentially be combined into the same BCB
/// as its sole successor. /// as its sole successor.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
enum CoverageSuccessors<'a> { struct CoverageSuccessors<'a> {
/// The terminator has exactly one straight-line successor, so its block can /// Coverage-relevant successors of the corresponding terminator.
/// potentially be combined into the same BCB as that successor. /// There might be 0, 1, or multiple targets.
Chainable(BasicBlock), targets: &'a [BasicBlock],
/// The block cannot be combined into the same BCB as its successor(s). /// `Yield` terminators are not chainable, because their sole out-edge is
NotChainable(&'a [BasicBlock]), /// only followed if/when the generator is resumed after the yield.
/// Yield terminators are not chainable, and their execution count can also is_yield: bool,
/// differ from the execution count of their out-edge.
Yield(BasicBlock),
} }
impl CoverageSuccessors<'_> { impl CoverageSuccessors<'_> {
fn is_chainable(&self) -> bool { /// If `false`, this terminator cannot be chained into another block when
match self { /// building the coverage graph.
Self::Chainable(_) => true, fn is_out_chainable(&self) -> bool {
Self::NotChainable(_) => false, // If a terminator is out-summable and has exactly one out-edge, then
Self::Yield(_) => false, // it is eligible to be chained into its successor block.
} self.is_out_summable() && self.targets.len() == 1
} }
/// Returns true if the terminator itself is assumed to have the same /// Returns true if the terminator itself is assumed to have the same
/// execution count as the sum of its out-edges (assuming no panics). /// execution count as the sum of its out-edges (assuming no panics).
fn is_out_summable(&self) -> bool { fn is_out_summable(&self) -> bool {
match self { !self.is_yield && !self.targets.is_empty()
Self::Chainable(_) => true,
Self::NotChainable(_) => true,
Self::Yield(_) => false,
}
} }
} }
@ -425,12 +412,7 @@ impl IntoIterator for CoverageSuccessors<'_> {
type IntoIter = impl DoubleEndedIterator<Item = Self::Item>; type IntoIter = impl DoubleEndedIterator<Item = Self::Item>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
match self { self.targets.iter().copied()
Self::Chainable(bb) | Self::Yield(bb) => {
Some(bb).into_iter().chain((&[]).iter().copied())
}
Self::NotChainable(bbs) => None.into_iter().chain(bbs.iter().copied()),
}
} }
} }
@ -440,14 +422,17 @@ fn into_iter(self) -> Self::IntoIter {
// `catch_unwind()` handlers. // `catch_unwind()` handlers.
fn bcb_filtered_successors<'a, 'tcx>(terminator: &'a Terminator<'tcx>) -> CoverageSuccessors<'a> { fn bcb_filtered_successors<'a, 'tcx>(terminator: &'a Terminator<'tcx>) -> CoverageSuccessors<'a> {
use TerminatorKind::*; use TerminatorKind::*;
match terminator.kind { let mut is_yield = false;
let targets = match &terminator.kind {
// A switch terminator can have many coverage-relevant successors. // A switch terminator can have many coverage-relevant successors.
// (If there is exactly one successor, we still treat it as not chainable.) SwitchInt { targets, .. } => targets.all_targets(),
SwitchInt { ref targets, .. } => CoverageSuccessors::NotChainable(targets.all_targets()),
// A yield terminator has exactly 1 successor, but should not be chained, // A yield terminator has exactly 1 successor, but should not be chained,
// because its resume edge has a different execution count. // because its resume edge has a different execution count.
Yield { resume, .. } => CoverageSuccessors::Yield(resume), Yield { resume, .. } => {
is_yield = true;
slice::from_ref(resume)
}
// These terminators have exactly one coverage-relevant successor, // These terminators have exactly one coverage-relevant successor,
// and can be chained into it. // and can be chained into it.
@ -455,24 +440,15 @@ fn bcb_filtered_successors<'a, 'tcx>(terminator: &'a Terminator<'tcx>) -> Covera
| Drop { target, .. } | Drop { target, .. }
| FalseEdge { real_target: target, .. } | FalseEdge { real_target: target, .. }
| FalseUnwind { real_target: target, .. } | FalseUnwind { real_target: target, .. }
| Goto { target } => CoverageSuccessors::Chainable(target), | Goto { target } => slice::from_ref(target),
// A call terminator can normally be chained, except when it has no // A call terminator can normally be chained, except when it has no
// successor because it is known to diverge. // successor because it is known to diverge.
Call { target: maybe_target, .. } => match maybe_target { Call { target: maybe_target, .. } => maybe_target.as_slice(),
Some(target) => CoverageSuccessors::Chainable(target),
None => CoverageSuccessors::NotChainable(&[]),
},
// An inline asm terminator can normally be chained, except when it // An inline asm terminator can normally be chained, except when it
// diverges or uses asm goto. // diverges or uses asm goto.
InlineAsm { ref targets, .. } => { InlineAsm { targets, .. } => &targets,
if let [target] = targets[..] {
CoverageSuccessors::Chainable(target)
} else {
CoverageSuccessors::NotChainable(targets)
}
}
// These terminators have no coverage-relevant successors. // These terminators have no coverage-relevant successors.
CoroutineDrop CoroutineDrop
@ -480,8 +456,10 @@ fn bcb_filtered_successors<'a, 'tcx>(terminator: &'a Terminator<'tcx>) -> Covera
| TailCall { .. } | TailCall { .. }
| Unreachable | Unreachable
| UnwindResume | UnwindResume
| UnwindTerminate(_) => CoverageSuccessors::NotChainable(&[]), | UnwindTerminate(_) => &[],
} };
CoverageSuccessors { targets, is_yield }
} }
/// Maintains separate worklists for each loop in the BasicCoverageBlock CFG, plus one for the /// Maintains separate worklists for each loop in the BasicCoverageBlock CFG, plus one for the
@ -616,28 +594,31 @@ pub(crate) fn unvisited(&self) -> Vec<BasicCoverageBlock> {
} }
} }
fn short_circuit_preorder<'a, 'tcx, F, Iter>( /// Wrapper around a [`mir::BasicBlocks`] graph that restricts each node's
body: &'a mir::Body<'tcx>, /// successors to only the ones considered "relevant" when building a coverage
filtered_successors: F, /// graph.
) -> impl Iterator<Item = BasicBlock> + Captures<'a> + Captures<'tcx> #[derive(Clone, Copy)]
where struct CoverageRelevantSubgraph<'a, 'tcx> {
F: Fn(BasicBlock) -> Iter, basic_blocks: &'a mir::BasicBlocks<'tcx>,
Iter: IntoIterator<Item = BasicBlock>, }
{ impl<'a, 'tcx> CoverageRelevantSubgraph<'a, 'tcx> {
let mut visited = BitSet::new_empty(body.basic_blocks.len()); fn new(basic_blocks: &'a mir::BasicBlocks<'tcx>) -> Self {
let mut worklist = vec![mir::START_BLOCK]; Self { basic_blocks }
}
std::iter::from_fn(move || {
while let Some(bb) = worklist.pop() { fn coverage_successors(&self, bb: BasicBlock) -> CoverageSuccessors<'_> {
if !visited.insert(bb) { bcb_filtered_successors(self.basic_blocks[bb].terminator())
continue; }
} }
impl<'a, 'tcx> graph::DirectedGraph for CoverageRelevantSubgraph<'a, 'tcx> {
worklist.extend(filtered_successors(bb)); type Node = BasicBlock;
return Some(bb); fn num_nodes(&self) -> usize {
} self.basic_blocks.num_nodes()
}
None }
}) impl<'a, 'tcx> graph::Successors for CoverageRelevantSubgraph<'a, 'tcx> {
fn successors(&self, bb: Self::Node) -> impl Iterator<Item = Self::Node> {
self.coverage_successors(bb).into_iter()
}
} }