Auto merge of #119508 - Zalathar:graph, r=compiler-errors · rust-lang/rust@73252d5 (original) (raw)
1
1
`use rustc_data_structures::captures::Captures;
`
``
2
`+
use rustc_data_structures::fx::FxHashSet;
`
2
3
`use rustc_data_structures::graph::dominators::{self, Dominators};
`
3
4
`use rustc_data_structures::graph::{self, GraphSuccessors, WithNumNodes, WithStartNode};
`
4
5
`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};
`
7
8
``
8
9
`use std::cmp::Ordering;
`
9
10
`use std::collections::VecDeque;
`
`@@ -30,23 +31,16 @@ impl CoverageGraph {
`
30
31
`` // SwitchInt
to have multiple targets to the same destination BasicBlock
, so
``
31
32
`// de-duplication is required. This is done without reordering the successors.
`
32
33
``
33
``
`-
let mut seen = IndexVec::from_elem(false, &bcbs);
`
34
34
`let successors = IndexVec::from_fn_n(
`
35
35
` |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()
`
42
40
`.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<_>>()
`
50
44
`},
`
51
45
` bcbs.len(),
`
52
46
`);
`
`@@ -80,112 +74,66 @@ impl CoverageGraph {
`
80
74
`IndexVec<BasicBlock, Option>,
`
81
75
`) {
`
82
76
`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);
`
84
78
`let mut bb_to_bcb = IndexVec::from_elem_n(None, num_basic_blocks);
`
85
79
``
``
80
`+
let mut add_basic_coverage_block = |basic_blocks: &mut Vec| {
`
``
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
+
86
94
`` // Walk the MIR CFG using a Preorder traversal, which starts from START_BLOCK
and follows
``
87
95
`` // each block terminator's successors()
. Coverage spans must map to actual source code,
``
88
96
`// so compiler generated blocks and paths can be ignored. To that end, the CFG traversal
`
89
97
`// intentionally omits unwind paths.
`
90
98
`` // FIXME(#78544): MIR InstrumentCoverage: Improve coverage of #[should_panic]
tests and
``
91
99
`` // catch_unwind()
handlers.
``
92
100
``
``
101
`+
// Accumulates a chain of blocks that will be combined into one BCB.
`
93
102
`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);
`
119
103
``
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);
`
163
124
`}
`
``
125
+
``
126
`+
basic_blocks.push(bb);
`
164
127
`}
`
165
128
``
166
129
`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);
`
170
132
`}
`
171
133
``
172
134
`(bcbs, bb_to_bcb)
`
173
135
`}
`
174
136
``
175
``
`-
fn add_basic_coverage_block(
`
176
``
`-
bcbs: &mut IndexVec<BasicCoverageBlock, BasicCoverageBlockData>,
`
177
``
`-
bb_to_bcb: &mut IndexSlice<BasicBlock, Option>,
`
178
``
`-
basic_blocks: Vec,
`
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
``
-
189
137
`#[inline(always)]
`
190
138
`pub fn iter_enumerated(
`
191
139
`&self,
`
`@@ -346,28 +294,76 @@ impl BasicCoverageBlockData {
`
346
294
`}
`
347
295
`}
`
348
296
``
``
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
+
349
330
`// 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.
`
351
332
`` // FIXME(#78544): MIR InstrumentCoverage: Improve coverage of #[should_panic]
tests and
``
352
333
`` // 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
`+
}
`
371
367
`}
`
372
368
``
373
369
`/// 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>(
`
544
540
`filtered_successors: F,
`
545
541
`) -> impl Iterator<Item = BasicBlock> + Captures<'a> + Captures<'tcx>
`
546
542
`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>,
`
549
545
`{
`
550
546
`let mut visited = BitSet::new_empty(body.basic_blocks.len());
`
551
547
`let mut worklist = vec![mir::START_BLOCK];
`
`@@ -556,7 +552,7 @@ where
`
556
552
`continue;
`
557
553
`}
`
558
554
``
559
``
`-
worklist.extend(filtered_successors(body, bb));
`
``
555
`+
worklist.extend(filtered_successors(bb));
`
560
556
``
561
557
`return Some(bb);
`
562
558
`}
`