29
29
30
30
use crate :: transform:: MirPass ;
31
31
use rustc_index:: vec:: { Idx , IndexVec } ;
32
+ use rustc_middle:: mir:: coverage:: * ;
32
33
use rustc_middle:: mir:: visit:: { MutVisitor , MutatingUseContext , PlaceContext , Visitor } ;
33
34
use rustc_middle:: mir:: * ;
34
35
use rustc_middle:: ty:: TyCtxt ;
@@ -46,9 +47,9 @@ impl SimplifyCfg {
46
47
}
47
48
}
48
49
49
- pub fn simplify_cfg ( body : & mut Body < ' _ > ) {
50
+ pub fn simplify_cfg ( tcx : TyCtxt < ' tcx > , body : & mut Body < ' _ > ) {
50
51
CfgSimplifier :: new ( body) . simplify ( ) ;
51
- remove_dead_blocks ( body) ;
52
+ remove_dead_blocks ( tcx , body) ;
52
53
53
54
// FIXME: Should probably be moved into some kind of pass manager
54
55
body. basic_blocks_mut ( ) . raw . shrink_to_fit ( ) ;
@@ -59,9 +60,9 @@ impl<'tcx> MirPass<'tcx> for SimplifyCfg {
59
60
Cow :: Borrowed ( & self . label )
60
61
}
61
62
62
- fn run_pass ( & self , _tcx : TyCtxt < ' tcx > , body : & mut Body < ' tcx > ) {
63
+ fn run_pass ( & self , tcx : TyCtxt < ' tcx > , body : & mut Body < ' tcx > ) {
63
64
debug ! ( "SimplifyCfg({:?}) - simplifying {:?}" , self . label, body. source) ;
64
- simplify_cfg ( body) ;
65
+ simplify_cfg ( tcx , body) ;
65
66
}
66
67
}
67
68
@@ -286,7 +287,7 @@ impl<'a, 'tcx> CfgSimplifier<'a, 'tcx> {
286
287
}
287
288
}
288
289
289
- pub fn remove_dead_blocks ( body : & mut Body < ' _ > ) {
290
+ pub fn remove_dead_blocks ( tcx : TyCtxt < ' tcx > , body : & mut Body < ' _ > ) {
290
291
let reachable = traversal:: reachable_as_bitset ( body) ;
291
292
let num_blocks = body. basic_blocks ( ) . len ( ) ;
292
293
if num_blocks == reachable. count ( ) {
@@ -306,6 +307,11 @@ pub fn remove_dead_blocks(body: &mut Body<'_>) {
306
307
}
307
308
used_blocks += 1 ;
308
309
}
310
+
311
+ if tcx. sess . instrument_coverage ( ) {
312
+ save_unreachable_coverage ( basic_blocks, used_blocks) ;
313
+ }
314
+
309
315
basic_blocks. raw . truncate ( used_blocks) ;
310
316
311
317
for block in basic_blocks {
@@ -315,6 +321,75 @@ pub fn remove_dead_blocks(body: &mut Body<'_>) {
315
321
}
316
322
}
317
323
324
+ /// Some MIR transforms can determine at compile time that a sequences of
325
+ /// statements will never be executed, so they can be dropped from the MIR.
326
+ /// For example, an `if` or `else` block that is guaranteed to never be executed
327
+ /// because its condition can be evaluated at compile time, such as by const
328
+ /// evaluation: `if false { ... }`.
329
+ ///
330
+ /// Those statements are bypassed by redirecting paths in the CFG around the
331
+ /// `dead blocks`; but with `-Z instrument-coverage`, the dead blocks usually
332
+ /// include `Coverage` statements representing the Rust source code regions to
333
+ /// be counted at runtime. Without these `Coverage` statements, the regions are
334
+ /// lost, and the Rust source code will show no coverage information.
335
+ ///
336
+ /// What we want to show in a coverage report is the dead code with coverage
337
+ /// counts of `0`. To do this, we need to save the code regions, by injecting
338
+ /// `Unreachable` coverage statements. These are non-executable statements whose
339
+ /// code regions are still recorded in the coverage map, representing regions
340
+ /// with `0` executions.
341
+ fn save_unreachable_coverage (
342
+ basic_blocks : & mut IndexVec < BasicBlock , BasicBlockData < ' _ > > ,
343
+ first_dead_block : usize ,
344
+ ) {
345
+ let has_live_counters = basic_blocks. raw [ 0 ..first_dead_block] . iter ( ) . any ( |live_block| {
346
+ live_block. statements . iter ( ) . any ( |statement| {
347
+ if let StatementKind :: Coverage ( coverage) = & statement. kind {
348
+ matches ! ( coverage. kind, CoverageKind :: Counter { .. } )
349
+ } else {
350
+ false
351
+ }
352
+ } )
353
+ } ) ;
354
+ if !has_live_counters {
355
+ // If there are no live `Counter` `Coverage` statements anymore, don't
356
+ // move dead coverage to the `START_BLOCK`. Just allow the dead
357
+ // `Coverage` statements to be dropped with the dead blocks.
358
+ //
359
+ // The `generator::StateTransform` MIR pass can create atypical
360
+ // conditions, where all live `Counter`s are dropped from the MIR.
361
+ //
362
+ // At least one Counter per function is required by LLVM (and necessary,
363
+ // to add the `function_hash` to the counter's call to the LLVM
364
+ // intrinsic `instrprof.increment()`).
365
+ return ;
366
+ }
367
+
368
+ // Retain coverage info for dead blocks, so coverage reports will still
369
+ // report `0` executions for the uncovered code regions.
370
+ let mut dropped_coverage = Vec :: new ( ) ;
371
+ for dead_block in basic_blocks. raw [ first_dead_block..] . iter ( ) {
372
+ for statement in dead_block. statements . iter ( ) {
373
+ if let StatementKind :: Coverage ( coverage) = & statement. kind {
374
+ if let Some ( code_region) = & coverage. code_region {
375
+ dropped_coverage. push ( ( statement. source_info , code_region. clone ( ) ) ) ;
376
+ }
377
+ }
378
+ }
379
+ }
380
+
381
+ let start_block = & mut basic_blocks[ START_BLOCK ] ;
382
+ for ( source_info, code_region) in dropped_coverage {
383
+ start_block. statements . push ( Statement {
384
+ source_info,
385
+ kind : StatementKind :: Coverage ( box Coverage {
386
+ kind : CoverageKind :: Unreachable ,
387
+ code_region : Some ( code_region) ,
388
+ } ) ,
389
+ } )
390
+ }
391
+ }
392
+
318
393
pub struct SimplifyLocals ;
319
394
320
395
impl < ' tcx > MirPass < ' tcx > for SimplifyLocals {
0 commit comments