Skip to content

Commit 73252d5

Browse files
committed
Auto merge of #119508 - Zalathar:graph, r=compiler-errors
coverage: Simplify building the coverage graph with `CoverageSuccessors` This is a collection of simplifications to the code that builds the *basic coverage block* graph, which is a simplified view of the MIR control-flow graph that ignores panics and merges straight-line sequences of blocks into a single BCB node. The biggest change is to how we determine the coverage-relevant successors of a block. Previously we would call `Terminator::successors` and apply some ad-hoc postprocessing, but with this PR we instead have our own `match` on the terminator kind that produces a coverage-specific enum `CoverageSuccessors`. That enum also includes information about whether a block has exactly one successor that it can be chained into as part of a single BCB.
2 parents 9567c3e + 5eae945 commit 73252d5

File tree

2 files changed

+121
-124
lines changed

2 files changed

+121
-124
lines changed

compiler/rustc_mir_transform/src/coverage/graph.rs

+120-124
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use rustc_data_structures::captures::Captures;
2+
use rustc_data_structures::fx::FxHashSet;
23
use rustc_data_structures::graph::dominators::{self, Dominators};
34
use rustc_data_structures::graph::{self, GraphSuccessors, WithNumNodes, WithStartNode};
45
use rustc_index::bit_set::BitSet;
5-
use rustc_index::{IndexSlice, IndexVec};
6-
use rustc_middle::mir::{self, BasicBlock, TerminatorKind};
6+
use rustc_index::IndexVec;
7+
use rustc_middle::mir::{self, BasicBlock, Terminator, TerminatorKind};
78

89
use std::cmp::Ordering;
910
use std::collections::VecDeque;
@@ -30,23 +31,16 @@ impl CoverageGraph {
3031
// `SwitchInt` to have multiple targets to the same destination `BasicBlock`, so
3132
// de-duplication is required. This is done without reordering the successors.
3233

33-
let mut seen = IndexVec::from_elem(false, &bcbs);
3434
let successors = IndexVec::from_fn_n(
3535
|bcb| {
36-
for b in seen.iter_mut() {
37-
*b = false;
38-
}
39-
let bcb_data = &bcbs[bcb];
40-
let mut bcb_successors = Vec::new();
41-
for successor in bcb_filtered_successors(mir_body, bcb_data.last_bb())
36+
let mut seen_bcbs = FxHashSet::default();
37+
let terminator = mir_body[bcbs[bcb].last_bb()].terminator();
38+
bcb_filtered_successors(terminator)
39+
.into_iter()
4240
.filter_map(|successor_bb| bb_to_bcb[successor_bb])
43-
{
44-
if !seen[successor] {
45-
seen[successor] = true;
46-
bcb_successors.push(successor);
47-
}
48-
}
49-
bcb_successors
41+
// Remove duplicate successor BCBs, keeping only the first.
42+
.filter(|&successor_bcb| seen_bcbs.insert(successor_bcb))
43+
.collect::<Vec<_>>()
5044
},
5145
bcbs.len(),
5246
);
@@ -80,112 +74,66 @@ impl CoverageGraph {
8074
IndexVec<BasicBlock, Option<BasicCoverageBlock>>,
8175
) {
8276
let num_basic_blocks = mir_body.basic_blocks.len();
83-
let mut bcbs = IndexVec::with_capacity(num_basic_blocks);
77+
let mut bcbs = IndexVec::<BasicCoverageBlock, _>::with_capacity(num_basic_blocks);
8478
let mut bb_to_bcb = IndexVec::from_elem_n(None, num_basic_blocks);
8579

80+
let mut add_basic_coverage_block = |basic_blocks: &mut Vec<BasicBlock>| {
81+
// Take the accumulated list of blocks, leaving the vector empty
82+
// to be used by subsequent BCBs.
83+
let basic_blocks = std::mem::take(basic_blocks);
84+
85+
let bcb = bcbs.next_index();
86+
for &bb in basic_blocks.iter() {
87+
bb_to_bcb[bb] = Some(bcb);
88+
}
89+
let bcb_data = BasicCoverageBlockData::from(basic_blocks);
90+
debug!("adding bcb{}: {:?}", bcb.index(), bcb_data);
91+
bcbs.push(bcb_data);
92+
};
93+
8694
// Walk the MIR CFG using a Preorder traversal, which starts from `START_BLOCK` and follows
8795
// each block terminator's `successors()`. Coverage spans must map to actual source code,
8896
// so compiler generated blocks and paths can be ignored. To that end, the CFG traversal
8997
// intentionally omits unwind paths.
9098
// FIXME(#78544): MIR InstrumentCoverage: Improve coverage of `#[should_panic]` tests and
9199
// `catch_unwind()` handlers.
92100

101+
// Accumulates a chain of blocks that will be combined into one BCB.
93102
let mut basic_blocks = Vec::new();
94-
for bb in short_circuit_preorder(mir_body, bcb_filtered_successors) {
95-
if let Some(last) = basic_blocks.last() {
96-
let predecessors = &mir_body.basic_blocks.predecessors()[bb];
97-
if predecessors.len() > 1 || !predecessors.contains(last) {
98-
// The `bb` has more than one _incoming_ edge, and should start its own
99-
// `BasicCoverageBlockData`. (Note, the `basic_blocks` vector does not yet
100-
// include `bb`; it contains a sequence of one or more sequential basic_blocks
101-
// with no intermediate branches in or out. Save these as a new
102-
// `BasicCoverageBlockData` before starting the new one.)
103-
Self::add_basic_coverage_block(
104-
&mut bcbs,
105-
&mut bb_to_bcb,
106-
basic_blocks.split_off(0),
107-
);
108-
debug!(
109-
" because {}",
110-
if predecessors.len() > 1 {
111-
"predecessors.len() > 1".to_owned()
112-
} else {
113-
format!("bb {} is not in predecessors: {:?}", bb.index(), predecessors)
114-
}
115-
);
116-
}
117-
}
118-
basic_blocks.push(bb);
119103

120-
let term = mir_body[bb].terminator();
121-
122-
match term.kind {
123-
TerminatorKind::Return { .. }
124-
| TerminatorKind::UnwindTerminate(_)
125-
| TerminatorKind::Yield { .. }
126-
| TerminatorKind::SwitchInt { .. } => {
127-
// The `bb` has more than one _outgoing_ edge, or exits the function. Save the
128-
// current sequence of `basic_blocks` gathered to this point, as a new
129-
// `BasicCoverageBlockData`.
130-
Self::add_basic_coverage_block(
131-
&mut bcbs,
132-
&mut bb_to_bcb,
133-
basic_blocks.split_off(0),
134-
);
135-
debug!(" because term.kind = {:?}", term.kind);
136-
// Note that this condition is based on `TerminatorKind`, even though it
137-
// theoretically boils down to `successors().len() != 1`; that is, either zero
138-
// (e.g., `Return`, `Terminate`) or multiple successors (e.g., `SwitchInt`), but
139-
// since the BCB CFG ignores things like unwind branches (which exist in the
140-
// `Terminator`s `successors()` list) checking the number of successors won't
141-
// work.
142-
}
143-
144-
// The following `TerminatorKind`s are either not expected outside an unwind branch,
145-
// or they should not (under normal circumstances) branch. Coverage graphs are
146-
// simplified by assuring coverage results are accurate for program executions that
147-
// don't panic.
148-
//
149-
// Programs that panic and unwind may record slightly inaccurate coverage results
150-
// for a coverage region containing the `Terminator` that began the panic. This
151-
// is as intended. (See Issue #78544 for a possible future option to support
152-
// coverage in test programs that panic.)
153-
TerminatorKind::Goto { .. }
154-
| TerminatorKind::UnwindResume
155-
| TerminatorKind::Unreachable
156-
| TerminatorKind::Drop { .. }
157-
| TerminatorKind::Call { .. }
158-
| TerminatorKind::CoroutineDrop
159-
| TerminatorKind::Assert { .. }
160-
| TerminatorKind::FalseEdge { .. }
161-
| TerminatorKind::FalseUnwind { .. }
162-
| TerminatorKind::InlineAsm { .. } => {}
104+
let filtered_successors = |bb| bcb_filtered_successors(mir_body[bb].terminator());
105+
for bb in short_circuit_preorder(mir_body, filtered_successors)
106+
.filter(|&bb| mir_body[bb].terminator().kind != TerminatorKind::Unreachable)
107+
{
108+
// If the previous block can't be chained into `bb`, flush the accumulated
109+
// blocks into a new BCB, then start building the next chain.
110+
if let Some(&prev) = basic_blocks.last()
111+
&& (!filtered_successors(prev).is_chainable() || {
112+
// If `bb` has multiple predecessor blocks, or `prev` isn't
113+
// one of its predecessors, we can't chain and must flush.
114+
let predecessors = &mir_body.basic_blocks.predecessors()[bb];
115+
predecessors.len() > 1 || !predecessors.contains(&prev)
116+
})
117+
{
118+
debug!(
119+
terminator_kind = ?mir_body[prev].terminator().kind,
120+
predecessors = ?&mir_body.basic_blocks.predecessors()[bb],
121+
"can't chain from {prev:?} to {bb:?}"
122+
);
123+
add_basic_coverage_block(&mut basic_blocks);
163124
}
125+
126+
basic_blocks.push(bb);
164127
}
165128

166129
if !basic_blocks.is_empty() {
167-
// process any remaining basic_blocks into a final `BasicCoverageBlockData`
168-
Self::add_basic_coverage_block(&mut bcbs, &mut bb_to_bcb, basic_blocks.split_off(0));
169-
debug!(" because the end of the MIR CFG was reached while traversing");
130+
debug!("flushing accumulated blocks into one last BCB");
131+
add_basic_coverage_block(&mut basic_blocks);
170132
}
171133

172134
(bcbs, bb_to_bcb)
173135
}
174136

175-
fn add_basic_coverage_block(
176-
bcbs: &mut IndexVec<BasicCoverageBlock, BasicCoverageBlockData>,
177-
bb_to_bcb: &mut IndexSlice<BasicBlock, Option<BasicCoverageBlock>>,
178-
basic_blocks: Vec<BasicBlock>,
179-
) {
180-
let bcb = bcbs.next_index();
181-
for &bb in basic_blocks.iter() {
182-
bb_to_bcb[bb] = Some(bcb);
183-
}
184-
let bcb_data = BasicCoverageBlockData::from(basic_blocks);
185-
debug!("adding bcb{}: {:?}", bcb.index(), bcb_data);
186-
bcbs.push(bcb_data);
187-
}
188-
189137
#[inline(always)]
190138
pub fn iter_enumerated(
191139
&self,
@@ -346,28 +294,76 @@ impl BasicCoverageBlockData {
346294
}
347295
}
348296

297+
/// Holds the coverage-relevant successors of a basic block's terminator, and
298+
/// indicates whether that block can potentially be combined into the same BCB
299+
/// as its sole successor.
300+
#[derive(Clone, Copy, Debug)]
301+
enum CoverageSuccessors<'a> {
302+
/// The terminator has exactly one straight-line successor, so its block can
303+
/// potentially be combined into the same BCB as that successor.
304+
Chainable(BasicBlock),
305+
/// The block cannot be combined into the same BCB as its successor(s).
306+
NotChainable(&'a [BasicBlock]),
307+
}
308+
309+
impl CoverageSuccessors<'_> {
310+
fn is_chainable(&self) -> bool {
311+
match self {
312+
Self::Chainable(_) => true,
313+
Self::NotChainable(_) => false,
314+
}
315+
}
316+
}
317+
318+
impl IntoIterator for CoverageSuccessors<'_> {
319+
type Item = BasicBlock;
320+
type IntoIter = impl DoubleEndedIterator<Item = Self::Item>;
321+
322+
fn into_iter(self) -> Self::IntoIter {
323+
match self {
324+
Self::Chainable(bb) => Some(bb).into_iter().chain((&[]).iter().copied()),
325+
Self::NotChainable(bbs) => None.into_iter().chain(bbs.iter().copied()),
326+
}
327+
}
328+
}
329+
349330
// Returns the subset of a block's successors that are relevant to the coverage
350-
// graph, i.e. those that do not represent unwinds or unreachable branches.
331+
// graph, i.e. those that do not represent unwinds or false edges.
351332
// FIXME(#78544): MIR InstrumentCoverage: Improve coverage of `#[should_panic]` tests and
352333
// `catch_unwind()` handlers.
353-
fn bcb_filtered_successors<'a, 'tcx>(
354-
body: &'a mir::Body<'tcx>,
355-
bb: BasicBlock,
356-
) -> impl Iterator<Item = BasicBlock> + Captures<'a> + Captures<'tcx> {
357-
let terminator = body[bb].terminator();
358-
359-
let take_n_successors = match terminator.kind {
360-
// SwitchInt successors are never unwinds, so all of them should be traversed.
361-
TerminatorKind::SwitchInt { .. } => usize::MAX,
362-
// For all other kinds, return only the first successor (if any), ignoring any
363-
// unwind successors.
364-
_ => 1,
365-
};
366-
367-
terminator
368-
.successors()
369-
.take(take_n_successors)
370-
.filter(move |&successor| body[successor].terminator().kind != TerminatorKind::Unreachable)
334+
fn bcb_filtered_successors<'a, 'tcx>(terminator: &'a Terminator<'tcx>) -> CoverageSuccessors<'a> {
335+
use TerminatorKind::*;
336+
match terminator.kind {
337+
// A switch terminator can have many coverage-relevant successors.
338+
// (If there is exactly one successor, we still treat it as not chainable.)
339+
SwitchInt { ref targets, .. } => CoverageSuccessors::NotChainable(targets.all_targets()),
340+
341+
// A yield terminator has exactly 1 successor, but should not be chained,
342+
// because its resume edge has a different execution count.
343+
Yield { ref resume, .. } => CoverageSuccessors::NotChainable(std::slice::from_ref(resume)),
344+
345+
// These terminators have exactly one coverage-relevant successor,
346+
// and can be chained into it.
347+
Assert { target, .. }
348+
| Drop { target, .. }
349+
| FalseEdge { real_target: target, .. }
350+
| FalseUnwind { real_target: target, .. }
351+
| Goto { target } => CoverageSuccessors::Chainable(target),
352+
353+
// These terminators can normally be chained, except when they have no
354+
// successor because they are known to diverge.
355+
Call { target: maybe_target, .. } | InlineAsm { destination: maybe_target, .. } => {
356+
match maybe_target {
357+
Some(target) => CoverageSuccessors::Chainable(target),
358+
None => CoverageSuccessors::NotChainable(&[]),
359+
}
360+
}
361+
362+
// These terminators have no coverage-relevant successors.
363+
CoroutineDrop | Return | Unreachable | UnwindResume | UnwindTerminate(_) => {
364+
CoverageSuccessors::NotChainable(&[])
365+
}
366+
}
371367
}
372368

373369
/// Maintains separate worklists for each loop in the BasicCoverageBlock CFG, plus one for the
@@ -544,8 +540,8 @@ fn short_circuit_preorder<'a, 'tcx, F, Iter>(
544540
filtered_successors: F,
545541
) -> impl Iterator<Item = BasicBlock> + Captures<'a> + Captures<'tcx>
546542
where
547-
F: Fn(&'a mir::Body<'tcx>, BasicBlock) -> Iter,
548-
Iter: Iterator<Item = BasicBlock>,
543+
F: Fn(BasicBlock) -> Iter,
544+
Iter: IntoIterator<Item = BasicBlock>,
549545
{
550546
let mut visited = BitSet::new_empty(body.basic_blocks.len());
551547
let mut worklist = vec![mir::START_BLOCK];
@@ -556,7 +552,7 @@ where
556552
continue;
557553
}
558554

559-
worklist.extend(filtered_successors(body, bb));
555+
worklist.extend(filtered_successors(bb));
560556

561557
return Some(bb);
562558
}

compiler/rustc_mir_transform/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#![feature(box_patterns)]
55
#![feature(cow_is_borrowed)]
66
#![feature(decl_macro)]
7+
#![feature(impl_trait_in_assoc_type)]
78
#![feature(is_sorted)]
89
#![feature(let_chains)]
910
#![feature(map_try_insert)]

0 commit comments

Comments
 (0)