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 < BasicCoverageBlock > > ,
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 < 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
+
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 < 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
-
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
}
0 commit comments