1
1
use std:: cmp:: Ordering ;
2
2
use std:: collections:: VecDeque ;
3
- use std:: iter;
4
3
use std:: ops:: { Index , IndexMut } ;
4
+ use std:: { iter, mem, slice} ;
5
5
6
6
use rustc_data_structures:: captures:: Captures ;
7
7
use rustc_data_structures:: fx:: FxHashSet ;
@@ -127,10 +127,10 @@ impl CoverageGraph {
127
127
let mut bcbs = IndexVec :: < BasicCoverageBlock , _ > :: with_capacity ( num_basic_blocks) ;
128
128
let mut bb_to_bcb = IndexVec :: from_elem_n ( None , num_basic_blocks) ;
129
129
130
- let mut add_basic_coverage_block = |basic_blocks : & mut Vec < BasicBlock > | {
130
+ let mut flush_chain_into_new_bcb = |current_chain : & mut Vec < BasicBlock > | {
131
131
// Take the accumulated list of blocks, leaving the vector empty
132
132
// to be used by subsequent BCBs.
133
- let basic_blocks = std :: mem:: take ( basic_blocks ) ;
133
+ let basic_blocks = mem:: take ( current_chain ) ;
134
134
135
135
let bcb = bcbs. next_index ( ) ;
136
136
for & bb in basic_blocks. iter ( ) {
@@ -141,48 +141,41 @@ impl CoverageGraph {
141
141
bcb_filtered_successors ( mir_body[ bb] . terminator ( ) ) . is_out_summable ( )
142
142
} ) ;
143
143
let bcb_data = BasicCoverageBlockData { basic_blocks, is_out_summable } ;
144
- debug ! ( "adding bcb{ }: {:?}" , bcb . index ( ) , bcb_data ) ;
144
+ debug ! ( "adding {bcb:? }: {bcb_data :?}" ) ;
145
145
bcbs. push ( bcb_data) ;
146
146
} ;
147
147
148
- // Walk the MIR CFG using a Preorder traversal, which starts from `START_BLOCK` and follows
149
- // each block terminator's `successors()`. Coverage spans must map to actual source code,
150
- // so compiler generated blocks and paths can be ignored. To that end, the CFG traversal
151
- // intentionally omits unwind paths.
152
- // FIXME(#78544): MIR InstrumentCoverage: Improve coverage of `#[should_panic]` tests and
153
- // `catch_unwind()` handlers.
148
+ // Traverse the MIR control-flow graph, accumulating chains of blocks
149
+ // that can be combined into a single node in the coverage graph.
150
+ // A depth-first search ensures that if two nodes can be chained
151
+ // together, they will be adjacent in the traversal order.
154
152
155
153
// Accumulates a chain of blocks that will be combined into one BCB.
156
- let mut basic_blocks = Vec :: new ( ) ;
154
+ let mut current_chain = vec ! [ ] ;
157
155
158
- let filtered_successors = |bb| bcb_filtered_successors ( mir_body[ bb ] . terminator ( ) ) ;
159
- for bb in short_circuit_preorder ( mir_body , filtered_successors )
156
+ let subgraph = CoverageRelevantSubgraph :: new ( & mir_body. basic_blocks ) ;
157
+ for bb in graph :: depth_first_search ( subgraph , mir :: START_BLOCK )
160
158
. filter ( |& bb| mir_body[ bb] . terminator ( ) . kind != TerminatorKind :: Unreachable )
161
159
{
162
- // If the previous block can't be chained into `bb`, flush the accumulated
163
- // blocks into a new BCB, then start building the next chain.
164
- if let Some ( & prev) = basic_blocks. last ( )
165
- && ( !filtered_successors ( prev) . is_chainable ( ) || {
166
- // If `bb` has multiple predecessor blocks, or `prev` isn't
167
- // one of its predecessors, we can't chain and must flush.
168
- let predecessors = & mir_body. basic_blocks . predecessors ( ) [ bb] ;
169
- predecessors. len ( ) > 1 || !predecessors. contains ( & prev)
170
- } )
171
- {
172
- debug ! (
173
- terminator_kind = ?mir_body[ prev] . terminator( ) . kind,
174
- predecessors = ?& mir_body. basic_blocks. predecessors( ) [ bb] ,
175
- "can't chain from {prev:?} to {bb:?}"
176
- ) ;
177
- add_basic_coverage_block ( & mut basic_blocks) ;
160
+ if let Some ( & prev) = current_chain. last ( ) {
161
+ // Adding a block to a non-empty chain is allowed if the
162
+ // previous block permits chaining, and the current block has
163
+ // `prev` as its sole predecessor.
164
+ let can_chain = subgraph. coverage_successors ( prev) . is_out_chainable ( )
165
+ && mir_body. basic_blocks . predecessors ( ) [ bb] . as_slice ( ) == & [ prev] ;
166
+ if !can_chain {
167
+ // The current block can't be added to the existing chain, so
168
+ // flush that chain into a new BCB, and start a new chain.
169
+ flush_chain_into_new_bcb ( & mut current_chain) ;
170
+ }
178
171
}
179
172
180
- basic_blocks . push ( bb) ;
173
+ current_chain . push ( bb) ;
181
174
}
182
175
183
- if !basic_blocks . is_empty ( ) {
176
+ if !current_chain . is_empty ( ) {
184
177
debug ! ( "flushing accumulated blocks into one last BCB" ) ;
185
- add_basic_coverage_block ( & mut basic_blocks ) ;
178
+ flush_chain_into_new_bcb ( & mut current_chain ) ;
186
179
}
187
180
188
181
( bcbs, bb_to_bcb)
@@ -389,34 +382,28 @@ impl BasicCoverageBlockData {
389
382
/// indicates whether that block can potentially be combined into the same BCB
390
383
/// as its sole successor.
391
384
#[ derive( Clone , Copy , Debug ) ]
392
- enum CoverageSuccessors < ' a > {
393
- /// The terminator has exactly one straight-line successor, so its block can
394
- /// potentially be combined into the same BCB as that successor.
395
- Chainable ( BasicBlock ) ,
396
- /// The block cannot be combined into the same BCB as its successor(s).
397
- NotChainable ( & ' a [ BasicBlock ] ) ,
398
- /// Yield terminators are not chainable, and their execution count can also
399
- /// differ from the execution count of their out-edge.
400
- Yield ( BasicBlock ) ,
385
+ struct CoverageSuccessors < ' a > {
386
+ /// Coverage-relevant successors of the corresponding terminator.
387
+ /// There might be 0, 1, or multiple targets.
388
+ targets : & ' a [ BasicBlock ] ,
389
+ /// `Yield` terminators are not chainable, because their sole out-edge is
390
+ /// only followed if/when the generator is resumed after the yield.
391
+ is_yield : bool ,
401
392
}
402
393
403
394
impl CoverageSuccessors < ' _ > {
404
- fn is_chainable ( & self ) -> bool {
405
- match self {
406
- Self :: Chainable ( _ ) => true ,
407
- Self :: NotChainable ( _ ) => false ,
408
- Self :: Yield ( _ ) => false ,
409
- }
395
+ /// If `false`, this terminator cannot be chained into another block when
396
+ /// building the coverage graph.
397
+ fn is_out_chainable ( & self ) -> bool {
398
+ // If a terminator is out-summable and has exactly one out-edge, then
399
+ // it is eligible to be chained into its successor block.
400
+ self . is_out_summable ( ) && self . targets . len ( ) == 1
410
401
}
411
402
412
403
/// Returns true if the terminator itself is assumed to have the same
413
404
/// execution count as the sum of its out-edges (assuming no panics).
414
405
fn is_out_summable ( & self ) -> bool {
415
- match self {
416
- Self :: Chainable ( _) => true ,
417
- Self :: NotChainable ( _) => true ,
418
- Self :: Yield ( _) => false ,
419
- }
406
+ !self . is_yield && !self . targets . is_empty ( )
420
407
}
421
408
}
422
409
@@ -425,12 +412,7 @@ impl IntoIterator for CoverageSuccessors<'_> {
425
412
type IntoIter = impl DoubleEndedIterator < Item = Self :: Item > ;
426
413
427
414
fn into_iter ( self ) -> Self :: IntoIter {
428
- match self {
429
- Self :: Chainable ( bb) | Self :: Yield ( bb) => {
430
- Some ( bb) . into_iter ( ) . chain ( ( & [ ] ) . iter ( ) . copied ( ) )
431
- }
432
- Self :: NotChainable ( bbs) => None . into_iter ( ) . chain ( bbs. iter ( ) . copied ( ) ) ,
433
- }
415
+ self . targets . iter ( ) . copied ( )
434
416
}
435
417
}
436
418
@@ -440,48 +422,44 @@ impl IntoIterator for CoverageSuccessors<'_> {
440
422
// `catch_unwind()` handlers.
441
423
fn bcb_filtered_successors < ' a , ' tcx > ( terminator : & ' a Terminator < ' tcx > ) -> CoverageSuccessors < ' a > {
442
424
use TerminatorKind :: * ;
443
- match terminator. kind {
425
+ let mut is_yield = false ;
426
+ let targets = match & terminator. kind {
444
427
// A switch terminator can have many coverage-relevant successors.
445
- // (If there is exactly one successor, we still treat it as not chainable.)
446
- SwitchInt { ref targets, .. } => CoverageSuccessors :: NotChainable ( targets. all_targets ( ) ) ,
428
+ SwitchInt { targets, .. } => targets. all_targets ( ) ,
447
429
448
430
// A yield terminator has exactly 1 successor, but should not be chained,
449
431
// because its resume edge has a different execution count.
450
- Yield { resume, .. } => CoverageSuccessors :: Yield ( resume) ,
432
+ Yield { resume, .. } => {
433
+ is_yield = true ;
434
+ slice:: from_ref ( resume)
435
+ }
451
436
452
437
// These terminators have exactly one coverage-relevant successor,
453
438
// and can be chained into it.
454
439
Assert { target, .. }
455
440
| Drop { target, .. }
456
441
| FalseEdge { real_target : target, .. }
457
442
| FalseUnwind { real_target : target, .. }
458
- | Goto { target } => CoverageSuccessors :: Chainable ( target) ,
443
+ | Goto { target } => slice :: from_ref ( target) ,
459
444
460
445
// A call terminator can normally be chained, except when it has no
461
446
// successor because it is known to diverge.
462
- Call { target : maybe_target, .. } => match maybe_target {
463
- Some ( target) => CoverageSuccessors :: Chainable ( target) ,
464
- None => CoverageSuccessors :: NotChainable ( & [ ] ) ,
465
- } ,
447
+ Call { target : maybe_target, .. } => maybe_target. as_slice ( ) ,
466
448
467
449
// An inline asm terminator can normally be chained, except when it
468
450
// diverges or uses asm goto.
469
- InlineAsm { ref targets, .. } => {
470
- if let [ target] = targets[ ..] {
471
- CoverageSuccessors :: Chainable ( target)
472
- } else {
473
- CoverageSuccessors :: NotChainable ( targets)
474
- }
475
- }
451
+ InlineAsm { targets, .. } => & targets,
476
452
477
453
// These terminators have no coverage-relevant successors.
478
454
CoroutineDrop
479
455
| Return
480
456
| TailCall { .. }
481
457
| Unreachable
482
458
| UnwindResume
483
- | UnwindTerminate ( _) => CoverageSuccessors :: NotChainable ( & [ ] ) ,
484
- }
459
+ | UnwindTerminate ( _) => & [ ] ,
460
+ } ;
461
+
462
+ CoverageSuccessors { targets, is_yield }
485
463
}
486
464
487
465
/// Maintains separate worklists for each loop in the BasicCoverageBlock CFG, plus one for the
@@ -616,28 +594,31 @@ impl<'a> TraverseCoverageGraphWithLoops<'a> {
616
594
}
617
595
}
618
596
619
- fn short_circuit_preorder < ' a , ' tcx , F , Iter > (
620
- body : & ' a mir:: Body < ' tcx > ,
621
- filtered_successors : F ,
622
- ) -> impl Iterator < Item = BasicBlock > + Captures < ' a > + Captures < ' tcx >
623
- where
624
- F : Fn ( BasicBlock ) -> Iter ,
625
- Iter : IntoIterator < Item = BasicBlock > ,
626
- {
627
- let mut visited = BitSet :: new_empty ( body. basic_blocks . len ( ) ) ;
628
- let mut worklist = vec ! [ mir:: START_BLOCK ] ;
629
-
630
- std:: iter:: from_fn ( move || {
631
- while let Some ( bb) = worklist. pop ( ) {
632
- if !visited. insert ( bb) {
633
- continue ;
634
- }
635
-
636
- worklist. extend ( filtered_successors ( bb) ) ;
597
+ /// Wrapper around a [`mir::BasicBlocks`] graph that restricts each node's
598
+ /// successors to only the ones considered "relevant" when building a coverage
599
+ /// graph.
600
+ #[ derive( Clone , Copy ) ]
601
+ struct CoverageRelevantSubgraph < ' a , ' tcx > {
602
+ basic_blocks : & ' a mir:: BasicBlocks < ' tcx > ,
603
+ }
604
+ impl < ' a , ' tcx > CoverageRelevantSubgraph < ' a , ' tcx > {
605
+ fn new ( basic_blocks : & ' a mir:: BasicBlocks < ' tcx > ) -> Self {
606
+ Self { basic_blocks }
607
+ }
637
608
638
- return Some ( bb) ;
639
- }
609
+ fn coverage_successors ( & self , bb : BasicBlock ) -> CoverageSuccessors < ' _ > {
610
+ bcb_filtered_successors ( self . basic_blocks [ bb] . terminator ( ) )
611
+ }
612
+ }
613
+ impl < ' a , ' tcx > graph:: DirectedGraph for CoverageRelevantSubgraph < ' a , ' tcx > {
614
+ type Node = BasicBlock ;
640
615
641
- None
642
- } )
616
+ fn num_nodes ( & self ) -> usize {
617
+ self . basic_blocks . num_nodes ( )
618
+ }
619
+ }
620
+ impl < ' a , ' tcx > graph:: Successors for CoverageRelevantSubgraph < ' a , ' tcx > {
621
+ fn successors ( & self , bb : Self :: Node ) -> impl Iterator < Item = Self :: Node > {
622
+ self . coverage_successors ( bb) . into_iter ( )
623
+ }
643
624
}
0 commit comments