Skip to content

Commit e6b2828

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 e3b83df commit e6b2828

File tree

3 files changed

+63
-3
lines changed

3 files changed

+63
-3
lines changed

compiler/rustc_mir_transform/src/coverage/mod.rs

+2-3
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

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_increment = 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_increment {
42+
return;
43+
}
44+
45+
debug!(
46+
"all counter-increments 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

+3
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,9 @@ 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 MIR optimizations removed all coverage-increment statements
589+
// from an instrumented function, add another one to avoid problems.
590+
&coverage::repair::RepairCoverage,
588591
// Cleanup for human readability, off by default.
589592
&prettify::ReorderBasicBlocks,
590593
&prettify::ReorderLocals,

0 commit comments

Comments
 (0)