Skip to content

Commit 6e3dd69

Browse files
authored
Rollup merge of #98868 - tmiasko:unreachable-coverage, r=wesleywiser
Fix unreachable coverage generation for inlined functions To generate a function coverage we need at least one coverage counter, so a coverage from unreachable blocks is retained only when some live counters remain. The previous implementation incorrectly retained unreachable coverage, because it didn't account for the fact that those live counters can belong to another function due to inlining. Fixes #98833.
2 parents ad31d5c + 9473141 commit 6e3dd69

File tree

6 files changed

+111
-43
lines changed

6 files changed

+111
-43
lines changed

compiler/rustc_codegen_ssa/src/mir/coverageinfo.rs

+2-5
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,8 @@ use super::FunctionCx;
99
impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
1010
pub fn codegen_coverage(&self, bx: &mut Bx, coverage: Coverage, scope: SourceScope) {
1111
// Determine the instance that coverage data was originally generated for.
12-
let scope_data = &self.mir.source_scopes[scope];
13-
let instance = if let Some((inlined_instance, _)) = scope_data.inlined {
14-
self.monomorphize(inlined_instance)
15-
} else if let Some(inlined_scope) = scope_data.inlined_parent_scope {
16-
self.monomorphize(self.mir.source_scopes[inlined_scope].inlined.unwrap().0)
12+
let instance = if let Some(inlined) = scope.inlined_instance(&self.mir.source_scopes) {
13+
self.monomorphize(inlined)
1714
} else {
1815
self.instance
1916
};

compiler/rustc_middle/src/mir/mod.rs

+16
Original file line numberDiff line numberDiff line change
@@ -1662,6 +1662,22 @@ impl SourceScope {
16621662
ClearCrossCrate::Clear => None,
16631663
}
16641664
}
1665+
1666+
/// The instance this source scope was inlined from, if any.
1667+
#[inline]
1668+
pub fn inlined_instance<'tcx>(
1669+
self,
1670+
source_scopes: &IndexVec<SourceScope, SourceScopeData<'tcx>>,
1671+
) -> Option<ty::Instance<'tcx>> {
1672+
let scope_data = &source_scopes[self];
1673+
if let Some((inlined_instance, _)) = scope_data.inlined {
1674+
Some(inlined_instance)
1675+
} else if let Some(inlined_scope) = scope_data.inlined_parent_scope {
1676+
Some(source_scopes[inlined_scope].inlined.unwrap().0)
1677+
} else {
1678+
None
1679+
}
1680+
}
16651681
}
16661682

16671683
#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)]

compiler/rustc_mir_transform/src/simplify.rs

+43-35
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
//! return.
2929
3030
use crate::MirPass;
31+
use rustc_data_structures::fx::FxHashSet;
3132
use rustc_index::vec::{Idx, IndexVec};
3233
use rustc_middle::mir::coverage::*;
3334
use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor};
@@ -267,7 +268,8 @@ pub fn remove_dead_blocks<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
267268
return;
268269
}
269270

270-
let basic_blocks = body.basic_blocks_mut();
271+
let basic_blocks = body.basic_blocks.as_mut();
272+
let source_scopes = &body.source_scopes;
271273
let mut replacements: Vec<_> = (0..num_blocks).map(BasicBlock::new).collect();
272274
let mut used_blocks = 0;
273275
for alive_index in reachable.iter() {
@@ -282,7 +284,7 @@ pub fn remove_dead_blocks<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
282284
}
283285

284286
if tcx.sess.instrument_coverage() {
285-
save_unreachable_coverage(basic_blocks, used_blocks);
287+
save_unreachable_coverage(basic_blocks, source_scopes, used_blocks);
286288
}
287289

288290
basic_blocks.raw.truncate(used_blocks);
@@ -311,56 +313,62 @@ pub fn remove_dead_blocks<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
311313
/// `Unreachable` coverage statements. These are non-executable statements whose
312314
/// code regions are still recorded in the coverage map, representing regions
313315
/// with `0` executions.
316+
///
317+
/// If there are no live `Counter` `Coverage` statements remaining, we remove
318+
/// dead `Coverage` statements along with the dead blocks. Since at least one
319+
/// counter per function is required by LLVM (and necessary, to add the
320+
/// `function_hash` to the counter's call to the LLVM intrinsic
321+
/// `instrprof.increment()`).
322+
///
323+
/// The `generator::StateTransform` MIR pass and MIR inlining can create
324+
/// atypical conditions, where all live `Counter`s are dropped from the MIR.
325+
///
326+
/// With MIR inlining we can have coverage counters belonging to different
327+
/// instances in a single body, so the strategy described above is applied to
328+
/// coverage counters from each instance individually.
314329
fn save_unreachable_coverage(
315330
basic_blocks: &mut IndexVec<BasicBlock, BasicBlockData<'_>>,
331+
source_scopes: &IndexVec<SourceScope, SourceScopeData<'_>>,
316332
first_dead_block: usize,
317333
) {
318-
let has_live_counters = basic_blocks.raw[0..first_dead_block].iter().any(|live_block| {
319-
live_block.statements.iter().any(|statement| {
320-
if let StatementKind::Coverage(coverage) = &statement.kind {
321-
matches!(coverage.kind, CoverageKind::Counter { .. })
322-
} else {
323-
false
324-
}
325-
})
326-
});
327-
if !has_live_counters {
328-
// If there are no live `Counter` `Coverage` statements anymore, don't
329-
// move dead coverage to the `START_BLOCK`. Just allow the dead
330-
// `Coverage` statements to be dropped with the dead blocks.
331-
//
332-
// The `generator::StateTransform` MIR pass can create atypical
333-
// conditions, where all live `Counter`s are dropped from the MIR.
334-
//
335-
// At least one Counter per function is required by LLVM (and necessary,
336-
// to add the `function_hash` to the counter's call to the LLVM
337-
// intrinsic `instrprof.increment()`).
334+
// Identify instances that still have some live coverage counters left.
335+
let mut live = FxHashSet::default();
336+
for basic_block in &basic_blocks.raw[0..first_dead_block] {
337+
for statement in &basic_block.statements {
338+
let StatementKind::Coverage(coverage) = &statement.kind else { continue };
339+
let CoverageKind::Counter { .. } = coverage.kind else { continue };
340+
let instance = statement.source_info.scope.inlined_instance(source_scopes);
341+
live.insert(instance);
342+
}
343+
}
344+
345+
if live.is_empty() {
338346
return;
339347
}
340348

341-
// Retain coverage info for dead blocks, so coverage reports will still
342-
// report `0` executions for the uncovered code regions.
343-
let mut dropped_coverage = Vec::new();
344-
for dead_block in basic_blocks.raw[first_dead_block..].iter() {
345-
for statement in dead_block.statements.iter() {
346-
if let StatementKind::Coverage(coverage) = &statement.kind {
347-
if let Some(code_region) = &coverage.code_region {
348-
dropped_coverage.push((statement.source_info, code_region.clone()));
349-
}
349+
// Retain coverage for instances that still have some live counters left.
350+
let mut retained_coverage = Vec::new();
351+
for dead_block in &basic_blocks.raw[first_dead_block..] {
352+
for statement in &dead_block.statements {
353+
let StatementKind::Coverage(coverage) = &statement.kind else { continue };
354+
let Some(code_region) = &coverage.code_region else { continue };
355+
let instance = statement.source_info.scope.inlined_instance(source_scopes);
356+
if live.contains(&instance) {
357+
retained_coverage.push((statement.source_info, code_region.clone()));
350358
}
351359
}
352360
}
353361

354362
let start_block = &mut basic_blocks[START_BLOCK];
355-
for (source_info, code_region) in dropped_coverage {
356-
start_block.statements.push(Statement {
363+
start_block.statements.extend(retained_coverage.into_iter().map(
364+
|(source_info, code_region)| Statement {
357365
source_info,
358366
kind: StatementKind::Coverage(Box::new(Coverage {
359367
kind: CoverageKind::Unreachable,
360368
code_region: Some(code_region),
361369
})),
362-
})
363-
}
370+
},
371+
));
364372
}
365373

366374
pub struct SimplifyLocals;

compiler/rustc_monomorphize/src/partitioning/mod.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ mod merging;
9898
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
9999
use rustc_data_structures::sync;
100100
use rustc_hir::def_id::DefIdSet;
101+
use rustc_middle::mir;
101102
use rustc_middle::mir::mono::MonoItem;
102103
use rustc_middle::mir::mono::{CodegenUnit, Linkage};
103104
use rustc_middle::ty::print::with_no_trimmed_paths;
@@ -479,9 +480,14 @@ fn codegened_and_inlined_items<'tcx>(tcx: TyCtxt<'tcx>, (): ()) -> &'tcx DefIdSe
479480
if !visited.insert(did) {
480481
continue;
481482
}
482-
for scope in &tcx.instance_mir(instance.def).source_scopes {
483-
if let Some((ref inlined, _)) = scope.inlined {
484-
result.insert(inlined.def_id());
483+
let body = tcx.instance_mir(instance.def);
484+
for block in body.basic_blocks() {
485+
for statement in &block.statements {
486+
let mir::StatementKind::Coverage(_) = statement.kind else { continue };
487+
let scope = statement.source_info.scope;
488+
if let Some(inlined) = scope.inlined_instance(&body.source_scopes) {
489+
result.insert(inlined.def_id());
490+
}
485491
}
486492
}
487493
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
1| |// Regression test for issue #98833.
2+
2| |// compile-flags: -Zinline-mir
3+
3| |
4+
4| 1|fn main() {
5+
5| 1| println!("{}", live::<false>());
6+
6| 1|}
7+
7| |
8+
8| |#[inline]
9+
9| 1|fn live<const B: bool>() -> u32 {
10+
10| 1| if B {
11+
11| 0| dead()
12+
12| | } else {
13+
13| 1| 0
14+
14| | }
15+
15| 1|}
16+
16| |
17+
17| |#[inline]
18+
18| 0|fn dead() -> u32 {
19+
19| 0| 42
20+
20| 0|}
21+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Regression test for issue #98833.
2+
// compile-flags: -Zinline-mir
3+
4+
fn main() {
5+
println!("{}", live::<false>());
6+
}
7+
8+
#[inline]
9+
fn live<const B: bool>() -> u32 {
10+
if B {
11+
dead()
12+
} else {
13+
0
14+
}
15+
}
16+
17+
#[inline]
18+
fn dead() -> u32 {
19+
42
20+
}

0 commit comments

Comments
 (0)