Skip to content

Commit e13d0a2

Browse files
committed
coverage: Record branch information during MIR building
1 parent bb040e6 commit e13d0a2

File tree

2 files changed

+135
-4
lines changed

2 files changed

+135
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,93 @@
1-
use rustc_middle::mir;
2-
use rustc_middle::mir::coverage::BranchSpan;
1+
use std::assert_matches::assert_matches;
2+
use std::collections::hash_map::Entry;
3+
4+
use rustc_data_structures::fx::FxHashMap;
5+
use rustc_middle::mir::coverage::{BlockMarkerId, BranchSpan, CoverageKind};
6+
use rustc_middle::mir::{self, BasicBlock, UnOp};
7+
use rustc_middle::thir::{ExprId, ExprKind, Thir};
38
use rustc_middle::ty::TyCtxt;
49
use rustc_span::def_id::LocalDefId;
510

11+
use crate::build::Builder;
12+
613
pub(crate) struct HirBranchInfoBuilder {
14+
inversions: FxHashMap<ExprId, Inversion>,
15+
716
num_block_markers: usize,
817
branch_spans: Vec<BranchSpan>,
918
}
1019

20+
#[derive(Clone, Copy)]
21+
struct Inversion {
22+
/// When visiting the associated expression as a branch condition, treat this
23+
/// enclosing `!` as the branch condition instead.
24+
enclosing_not: ExprId,
25+
/// True if the associated expression is nested within an odd number of `!`
26+
/// expressions relative to `enclosing_not` (inclusive of `enclosing_not`).
27+
is_inverted: bool,
28+
}
29+
1130
impl HirBranchInfoBuilder {
1231
pub(crate) fn new_if_enabled_and_eligible(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<Self> {
1332
if tcx.sess.instrument_coverage_branch() && tcx.is_eligible_for_coverage(def_id) {
14-
Some(Self { num_block_markers: 0, branch_spans: vec![] })
33+
Some(Self {
34+
inversions: FxHashMap::default(),
35+
num_block_markers: 0,
36+
branch_spans: vec![],
37+
})
1538
} else {
1639
None
1740
}
1841
}
1942

43+
/// Unary `!` expressions inside an `if` condition are lowered by lowering
44+
/// their argument instead, and then reversing the then/else arms of that `if`.
45+
///
46+
/// That's awkward for branch coverage instrumentation, so to work around that
47+
/// we pre-emptively visit any affected `!` expressions, and record extra
48+
/// information that [`Builder::visit_coverage_branch_condition`] can use to
49+
/// synthesize branch instrumentation for the enclosing `!`.
50+
pub(crate) fn visit_unary_not(&mut self, thir: &Thir<'_>, unary_not: ExprId) {
51+
assert_matches!(thir[unary_not].kind, ExprKind::Unary { op: UnOp::Not, .. });
52+
53+
self.visit_inverted(
54+
thir,
55+
unary_not,
56+
// Set `is_inverted: false` for the `!` itself, so that its enclosed
57+
// expression will have `is_inverted: true`.
58+
Inversion { enclosing_not: unary_not, is_inverted: false },
59+
);
60+
}
61+
62+
fn visit_inverted(&mut self, thir: &Thir<'_>, expr_id: ExprId, inversion: Inversion) {
63+
match self.inversions.entry(expr_id) {
64+
// This expression has already been marked by an enclosing `!`.
65+
Entry::Occupied(_) => return,
66+
Entry::Vacant(entry) => entry.insert(inversion),
67+
};
68+
69+
match thir[expr_id].kind {
70+
ExprKind::Unary { op: UnOp::Not, arg } => {
71+
// Flip the `is_inverted` flag for the contents of this `!`.
72+
let inversion = Inversion { is_inverted: !inversion.is_inverted, ..inversion };
73+
self.visit_inverted(thir, arg, inversion);
74+
}
75+
ExprKind::Scope { value, .. } => self.visit_inverted(thir, value, inversion),
76+
ExprKind::Use { source } => self.visit_inverted(thir, source, inversion),
77+
// All other expressions (including `&&` and `||`) don't need any
78+
// special handling of their contents, so stop visiting.
79+
_ => {}
80+
}
81+
}
82+
83+
fn next_block_marker_id(&mut self) -> BlockMarkerId {
84+
let id = BlockMarkerId::from_usize(self.num_block_markers);
85+
self.num_block_markers += 1;
86+
id
87+
}
88+
2089
pub(crate) fn into_done(self) -> Option<Box<mir::coverage::HirBranchInfo>> {
21-
let Self { num_block_markers, branch_spans } = self;
90+
let Self { inversions: _, num_block_markers, branch_spans } = self;
2291

2392
if num_block_markers == 0 {
2493
assert!(branch_spans.is_empty());
@@ -28,3 +97,54 @@ impl HirBranchInfoBuilder {
2897
Some(Box::new(mir::coverage::HirBranchInfo { num_block_markers, branch_spans }))
2998
}
3099
}
100+
101+
impl Builder<'_, '_> {
102+
/// If branch coverage is enabled, inject marker statements into `then_block`
103+
/// and `else_block`, and record their IDs in the table of branch spans.
104+
pub(crate) fn visit_coverage_branch_condition(
105+
&mut self,
106+
expr_id: ExprId,
107+
then_block: BasicBlock,
108+
else_block: BasicBlock,
109+
) {
110+
// Bail out if branch coverage is not enabled for this function.
111+
let Some(branch_info) = self.coverage_branch_info.as_ref() else { return };
112+
113+
// If this condition expression is nested within one or more `!` expressions,
114+
// replace it with the enclosing `!` collected by `visit_unary_not`.
115+
let (expr_id, is_inverted) = match branch_info.inversions.get(&expr_id) {
116+
Some(&Inversion { enclosing_not, is_inverted }) => (enclosing_not, is_inverted),
117+
None => (expr_id, false),
118+
};
119+
let source_info = self.source_info(self.thir[expr_id].span);
120+
121+
// Now that we have `source_info`, we can upgrade to a &mut reference.
122+
let branch_info = self.coverage_branch_info.as_mut().expect("upgrading & to &mut");
123+
124+
let mut inject_branch_marker = |block: BasicBlock| {
125+
let id = branch_info.next_block_marker_id();
126+
127+
let marker_statement = mir::Statement {
128+
source_info,
129+
kind: mir::StatementKind::Coverage(Box::new(mir::Coverage {
130+
kind: CoverageKind::BlockMarker { id },
131+
})),
132+
};
133+
self.cfg.push(block, marker_statement);
134+
135+
id
136+
};
137+
138+
let mut true_marker = inject_branch_marker(then_block);
139+
let mut false_marker = inject_branch_marker(else_block);
140+
if is_inverted {
141+
std::mem::swap(&mut true_marker, &mut false_marker);
142+
}
143+
144+
branch_info.branch_spans.push(BranchSpan {
145+
span: source_info.span,
146+
true_marker,
147+
false_marker,
148+
});
149+
}
150+
}

compiler/rustc_mir_build/src/build/matches/mod.rs

+11
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
106106
success_block.unit()
107107
}
108108
ExprKind::Unary { op: UnOp::Not, arg } => {
109+
// Improve branch coverage instrumentation by noting conditions
110+
// nested within one or more `!` expressions.
111+
// (Skipped if branch coverage is not enabled.)
112+
if let Some(branch_info) = this.coverage_branch_info.as_mut() {
113+
branch_info.visit_unary_not(this.thir, expr_id);
114+
}
115+
109116
let local_scope = this.local_scope();
110117
let (success_block, failure_block) =
111118
this.in_if_then_scope(local_scope, expr_span, |this| {
@@ -150,6 +157,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
150157
let else_block = this.cfg.start_new_block();
151158
let term = TerminatorKind::if_(operand, then_block, else_block);
152159

160+
// Record branch coverage info for this condition.
161+
// (Does nothing if branch coverage is not enabled.)
162+
this.visit_coverage_branch_condition(expr_id, then_block, else_block);
163+
153164
let source_info = this.source_info(expr_span);
154165
this.cfg.terminate(block, source_info, term);
155166
this.break_for_else(else_block, source_info);

0 commit comments

Comments
 (0)