Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 289ada5

Browse files
committed
Auto merge of rust-lang#85385 - richkadel:simpler-simplify-with-coverage, r=wesleywiser
Reland - Report coverage `0` of dead blocks Fixes: rust-lang#84018 With `-Z instrument-coverage`, coverage reporting of dead blocks (for example, blocks dropped because a conditional branch is dropped, based on const evaluation) is now supported. Note, this PR relands an earlier, reverted PR that failed when compiling generators. The prior issues with generators has been resolved and a new test was added to prevent future regressions. Check out the resulting changes to test coverage of dead blocks in the test coverage reports in this PR. r? `@tmandry` fyi: `@wesleywiser`
2 parents c4c2ab5 + f4f76e6 commit 289ada5

24 files changed

+216
-65
lines changed

compiler/rustc_codegen_ssa/src/coverageinfo/map.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub struct Expression {
2828
/// only whitespace or comments). According to LLVM Code Coverage Mapping documentation, "A count
2929
/// for a gap area is only used as the line execution count if there are no other regions on a
3030
/// line."
31+
#[derive(Debug)]
3132
pub struct FunctionCoverage<'tcx> {
3233
instance: Instance<'tcx>,
3334
source_hash: u64,
@@ -113,6 +114,14 @@ impl<'tcx> FunctionCoverage<'tcx> {
113114
expression_id, lhs, op, rhs, region
114115
);
115116
let expression_index = self.expression_index(u32::from(expression_id));
117+
debug_assert!(
118+
expression_index.as_usize() < self.expressions.len(),
119+
"expression_index {} is out of range for expressions.len() = {}
120+
for {:?}",
121+
expression_index.as_usize(),
122+
self.expressions.len(),
123+
self,
124+
);
116125
if let Some(previous_expression) = self.expressions[expression_index].replace(Expression {
117126
lhs,
118127
op,

compiler/rustc_mir/src/transform/const_goto.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ impl<'tcx> MirPass<'tcx> for ConstGoto {
4747
// if we applied optimizations, we potentially have some cfg to cleanup to
4848
// make it easier for further passes
4949
if should_simplify {
50-
simplify_cfg(body);
50+
simplify_cfg(tcx, body);
5151
simplify_locals(body, tcx);
5252
}
5353
}

compiler/rustc_mir/src/transform/deduplicate_blocks.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ impl<'tcx> MirPass<'tcx> for DeduplicateBlocks {
2626
if has_opts_to_apply {
2727
let mut opt_applier = OptApplier { tcx, duplicates };
2828
opt_applier.visit_body(body);
29-
simplify_cfg(body);
29+
simplify_cfg(tcx, body);
3030
}
3131
}
3232
}

compiler/rustc_mir/src/transform/early_otherwise_branch.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ impl<'tcx> MirPass<'tcx> for EarlyOtherwiseBranch {
164164
// Since this optimization adds new basic blocks and invalidates others,
165165
// clean up the cfg to make it nicer for other passes
166166
if should_cleanup {
167-
simplify_cfg(body);
167+
simplify_cfg(tcx, body);
168168
}
169169
}
170170
}

compiler/rustc_mir/src/transform/generator.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -964,7 +964,7 @@ fn create_generator_drop_shim<'tcx>(
964964

965965
// Make sure we remove dead blocks to remove
966966
// unrelated code from the resume part of the function
967-
simplify::remove_dead_blocks(&mut body);
967+
simplify::remove_dead_blocks(tcx, &mut body);
968968

969969
dump_mir(tcx, None, "generator_drop", &0, &body, |_, _| Ok(()));
970970

@@ -1137,7 +1137,7 @@ fn create_generator_resume_function<'tcx>(
11371137

11381138
// Make sure we remove dead blocks to remove
11391139
// unrelated code from the drop part of the function
1140-
simplify::remove_dead_blocks(body);
1140+
simplify::remove_dead_blocks(tcx, body);
11411141

11421142
dump_mir(tcx, None, "generator_resume", &0, body, |_, _| Ok(()));
11431143
}

compiler/rustc_mir/src/transform/inline.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ impl<'tcx> MirPass<'tcx> for Inline {
5757
if inline(tcx, body) {
5858
debug!("running simplify cfg on {:?}", body.source);
5959
CfgSimplifier::new(body).simplify();
60-
remove_dead_blocks(body);
60+
remove_dead_blocks(tcx, body);
6161
}
6262
}
6363
}

compiler/rustc_mir/src/transform/match_branches.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ impl<'tcx> MirPass<'tcx> for MatchBranchSimplification {
167167
}
168168

169169
if should_cleanup {
170-
simplify_cfg(body);
170+
simplify_cfg(tcx, body);
171171
}
172172
}
173173
}

compiler/rustc_mir/src/transform/multiple_return_terminators.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,6 @@ impl<'tcx> MirPass<'tcx> for MultipleReturnTerminators {
3838
}
3939
}
4040

41-
simplify::remove_dead_blocks(body)
41+
simplify::remove_dead_blocks(tcx, body)
4242
}
4343
}

compiler/rustc_mir/src/transform/remove_unneeded_drops.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ impl<'tcx> MirPass<'tcx> for RemoveUnneededDrops {
3636
// if we applied optimizations, we potentially have some cfg to cleanup to
3737
// make it easier for further passes
3838
if should_simplify {
39-
simplify_cfg(body);
39+
simplify_cfg(tcx, body);
4040
}
4141
}
4242
}

compiler/rustc_mir/src/transform/simplify.rs

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
3030
use crate::transform::MirPass;
3131
use rustc_index::vec::{Idx, IndexVec};
32+
use rustc_middle::mir::coverage::*;
3233
use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor};
3334
use rustc_middle::mir::*;
3435
use rustc_middle::ty::TyCtxt;
@@ -46,9 +47,9 @@ impl SimplifyCfg {
4647
}
4748
}
4849

49-
pub fn simplify_cfg(body: &mut Body<'_>) {
50+
pub fn simplify_cfg(tcx: TyCtxt<'tcx>, body: &mut Body<'_>) {
5051
CfgSimplifier::new(body).simplify();
51-
remove_dead_blocks(body);
52+
remove_dead_blocks(tcx, body);
5253

5354
// FIXME: Should probably be moved into some kind of pass manager
5455
body.basic_blocks_mut().raw.shrink_to_fit();
@@ -59,9 +60,9 @@ impl<'tcx> MirPass<'tcx> for SimplifyCfg {
5960
Cow::Borrowed(&self.label)
6061
}
6162

62-
fn run_pass(&self, _tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
63+
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
6364
debug!("SimplifyCfg({:?}) - simplifying {:?}", self.label, body.source);
64-
simplify_cfg(body);
65+
simplify_cfg(tcx, body);
6566
}
6667
}
6768

@@ -286,7 +287,7 @@ impl<'a, 'tcx> CfgSimplifier<'a, 'tcx> {
286287
}
287288
}
288289

289-
pub fn remove_dead_blocks(body: &mut Body<'_>) {
290+
pub fn remove_dead_blocks(tcx: TyCtxt<'tcx>, body: &mut Body<'_>) {
290291
let reachable = traversal::reachable_as_bitset(body);
291292
let num_blocks = body.basic_blocks().len();
292293
if num_blocks == reachable.count() {
@@ -306,6 +307,11 @@ pub fn remove_dead_blocks(body: &mut Body<'_>) {
306307
}
307308
used_blocks += 1;
308309
}
310+
311+
if tcx.sess.instrument_coverage() {
312+
save_unreachable_coverage(basic_blocks, used_blocks);
313+
}
314+
309315
basic_blocks.raw.truncate(used_blocks);
310316

311317
for block in basic_blocks {
@@ -315,6 +321,75 @@ pub fn remove_dead_blocks(body: &mut Body<'_>) {
315321
}
316322
}
317323

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+
318393
pub struct SimplifyLocals;
319394

320395
impl<'tcx> MirPass<'tcx> for SimplifyLocals {

0 commit comments

Comments
 (0)