11use rustc_data_structures:: captures:: Captures ;
2+ use rustc_data_structures:: fx:: FxHashSet ;
23use rustc_data_structures:: graph:: dominators:: { self , Dominators } ;
34use rustc_data_structures:: graph:: { self , GraphSuccessors , WithNumNodes , WithStartNode } ;
45use 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
89use std:: cmp:: Ordering ;
910use 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 >
546542where
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 }
0 commit comments