Skip to content

Commit 20f298d

Browse files
committed
coverage: Repair instrumented functions that have lost all their counters
If a function has been instrumented for coverage, but MIR optimizations subsequently remove all of its counter-increment statements, then we won't emit LLVM counter-increment intrinsics. LLVM will think the function is not instrumented, and it will disappear from coverage mappings and coverage reports. This new MIR pass detects when that has happened, and re-inserts a dummy counter-increment statement so that LLVM knows to treat the function as instrumented.
1 parent edf8ca2 commit 20f298d

File tree

3 files changed

+62
-3
lines changed

3 files changed

+62
-3
lines changed

compiler/rustc_mir_transform/src/coverage/mod.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
pub mod query;
2-
31
mod counters;
42
mod graph;
3+
pub mod query;
4+
pub(crate) mod repair;
55
mod spans;
6-
76
#[cfg(test)]
87
mod tests;
98

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use rustc_middle::mir::coverage::{CounterId, CoverageKind};
2+
use rustc_middle::mir::{
3+
self, Coverage, MirPass, SourceInfo, Statement, StatementKind, START_BLOCK,
4+
};
5+
use rustc_middle::ty::TyCtxt;
6+
use rustc_span::DUMMY_SP;
7+
8+
/// If a function has been [instrumented for coverage](super::InstrumentCoverage),
9+
/// but MIR optimizations subsequently remove all of its [`CoverageKind::CounterIncrement`]
10+
/// statements (e.g. because bb0 is unreachable), then we won't generate any
11+
/// `llvm.instrprof.increment` intrinsics. LLVM will think the function is not
12+
/// instrumented, and it will disappear from coverage mappings and coverage reports.
13+
///
14+
/// This pass detects when that has happened, and re-inserts a dummy counter-increment
15+
/// statement so that LLVM knows to treat the function as instrumented.
16+
pub struct RepairCoverage;
17+
18+
impl<'tcx> MirPass<'tcx> for RepairCoverage {
19+
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
20+
sess.instrument_coverage()
21+
}
22+
23+
fn run_pass(&self, _tcx: TyCtxt<'tcx>, body: &mut mir::Body<'tcx>) {
24+
// If a function wasn't instrumented for coverage in the first place,
25+
// then there's no need to repair anything.
26+
if body.function_coverage_info.is_none() {
27+
return;
28+
}
29+
30+
// If the body still contains one or more counter-increment statements,
31+
// there's no need to repair anything.
32+
let has_counter = body
33+
.basic_blocks
34+
.iter()
35+
.flat_map(|bb_data| &bb_data.statements)
36+
.filter_map(|statement| match statement.kind {
37+
StatementKind::Coverage(box ref coverage) => Some(coverage),
38+
_ => None,
39+
})
40+
.any(|coverage| matches!(coverage.kind, CoverageKind::CounterIncrement { .. }));
41+
if has_counter {
42+
return;
43+
}
44+
45+
debug!(
46+
"all counters were removed after instrumentation; restoring one counter in {:?}",
47+
body.source.def_id()
48+
);
49+
50+
let statement = Statement {
51+
source_info: SourceInfo::outermost(DUMMY_SP),
52+
kind: StatementKind::Coverage(Box::new(Coverage {
53+
kind: CoverageKind::CounterIncrement { id: CounterId::START },
54+
})),
55+
};
56+
body[START_BLOCK].statements.insert(0, statement);
57+
}
58+
}

compiler/rustc_mir_transform/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,8 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
585585
&large_enums::EnumSizeOpt { discrepancy: 128 },
586586
// Some cleanup necessary at least for LLVM and potentially other codegen backends.
587587
&add_call_guards::CriticalCallEdges,
588+
// If opts removed all coverage counters, ensure there is at least one present.
589+
&coverage::repair::RepairCoverage,
588590
// Cleanup for human readability, off by default.
589591
&prettify::ReorderBasicBlocks,
590592
&prettify::ReorderLocals,

0 commit comments

Comments
 (0)