Skip to content

Commit 5ff7c05

Browse files
committed
coverage: Record branch information during MIR building
1 parent a09ec47 commit 5ff7c05

File tree

2 files changed

+112
-6
lines changed

2 files changed

+112
-6
lines changed

compiler/rustc_mir_build/src/build/coverageinfo.rs

+80-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
use rustc_middle::mir;
2-
use rustc_middle::mir::coverage::BranchSpan;
1+
use rustc_middle::mir::coverage::{BlockMarkerId, BranchSpan, CoverageKind};
2+
use rustc_middle::mir::{self, BasicBlock, Statement, StatementKind, UnOp};
3+
use rustc_middle::thir::{ExprId, ExprKind};
34
use rustc_middle::ty::TyCtxt;
45
use rustc_span::def_id::LocalDefId;
56

7+
use crate::build::Builder;
8+
69
pub(crate) struct HirBranchInfoBuilder {
710
num_block_markers: usize,
811
branch_spans: Vec<BranchSpan>,
@@ -17,6 +20,12 @@ impl HirBranchInfoBuilder {
1720
}
1821
}
1922

23+
fn next_block_marker_id(&mut self) -> BlockMarkerId {
24+
let id = BlockMarkerId::from_usize(self.num_block_markers);
25+
self.num_block_markers += 1;
26+
id
27+
}
28+
2029
pub(crate) fn into_done(self) -> Option<Box<mir::coverage::HirBranchInfo>> {
2130
let Self { num_block_markers, branch_spans } = self;
2231

@@ -28,3 +37,72 @@ impl HirBranchInfoBuilder {
2837
Some(Box::new(mir::coverage::HirBranchInfo { num_block_markers, branch_spans }))
2938
}
3039
}
40+
41+
impl<'a, 'tcx> Builder<'a, 'tcx> {
42+
/// If branch coverage is enabled, inject marker statements into `then_block`
43+
/// and `else_block`, and record their IDs in the table of branch spans.
44+
pub(crate) fn coverage_record_branch(
45+
&mut self,
46+
cond_expr_id: ExprId,
47+
enclosing_not_expr_id: Option<ExprId>,
48+
then_block: BasicBlock,
49+
else_block: BasicBlock,
50+
) {
51+
// If branch coverage instrumentation isn't enabled, do nothing.
52+
if self.coverage_branch_info.is_none() {
53+
return;
54+
}
55+
56+
// If the condition is nested in an odd number of `!` expressions, we
57+
// need to reverse the meaning of the then/else block markers, so that
58+
// they correspond to the outermost `!` expression being true/false.
59+
let swap_arm_markers = {
60+
let mut curr = enclosing_not_expr_id.unwrap_or(cond_expr_id);
61+
let mut swap_arm_markers = false;
62+
63+
while curr != cond_expr_id {
64+
match self.thir[curr].kind {
65+
ExprKind::Scope { value, .. } => curr = value,
66+
ExprKind::Unary { op: UnOp::Not, arg } => {
67+
swap_arm_markers = !swap_arm_markers;
68+
curr = arg;
69+
}
70+
_ => unreachable!("ensured by `Builder::then_else_break_inner`"),
71+
}
72+
}
73+
swap_arm_markers
74+
};
75+
76+
let cond_source_info =
77+
self.source_info(self.thir[enclosing_not_expr_id.unwrap_or(cond_expr_id)].span);
78+
79+
let branch_info_builder =
80+
self.coverage_branch_info.as_mut().expect("confirmed present above");
81+
82+
let mut inject_branch_marker = |block: BasicBlock| {
83+
let id = branch_info_builder.next_block_marker_id();
84+
85+
let marker_statement = Statement {
86+
source_info: cond_source_info,
87+
kind: StatementKind::Coverage(Box::new(mir::Coverage {
88+
kind: CoverageKind::BlockMarker { id },
89+
})),
90+
};
91+
self.cfg.push(block, marker_statement);
92+
93+
id
94+
};
95+
96+
let mut true_marker = inject_branch_marker(then_block);
97+
let mut false_marker = inject_branch_marker(else_block);
98+
if swap_arm_markers {
99+
std::mem::swap(&mut true_marker, &mut false_marker);
100+
}
101+
102+
branch_info_builder.branch_spans.push(BranchSpan {
103+
span: cond_source_info.span,
104+
true_marker,
105+
false_marker,
106+
});
107+
}
108+
}

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

+32-4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use std::mem;
3434
/// to recursive invocations.
3535
#[derive(Clone, Copy)]
3636
struct ThenElseArgs {
37+
enclosing_not_expr_id: Option<ExprId>,
3738
/// Used as the temp scope for lowering `expr`. If absent (for match guards),
3839
/// `self.local_scope()` is used.
3940
temp_scope_override: Option<region::Scope>,
@@ -61,7 +62,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
6162
self.then_else_break_inner(
6263
block,
6364
expr_id,
64-
ThenElseArgs { temp_scope_override, variable_source_info, declare_let_bindings },
65+
ThenElseArgs {
66+
enclosing_not_expr_id: None,
67+
temp_scope_override,
68+
variable_source_info,
69+
declare_let_bindings,
70+
},
6571
)
6672
}
6773

@@ -77,6 +83,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
7783

7884
match expr.kind {
7985
ExprKind::LogicalOp { op: LogicalOp::And, lhs, rhs } => {
86+
let args = ThenElseArgs { enclosing_not_expr_id: None, ..args };
8087
let lhs_then_block = unpack!(this.then_else_break_inner(block, lhs, args));
8188
let rhs_then_block = unpack!(this.then_else_break_inner(lhs_then_block, rhs, args));
8289
rhs_then_block.unit()
@@ -88,13 +95,21 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
8895
this.then_else_break_inner(
8996
block,
9097
lhs,
91-
ThenElseArgs { declare_let_bindings: true, ..args },
98+
ThenElseArgs {
99+
enclosing_not_expr_id: None,
100+
declare_let_bindings: true,
101+
..args
102+
},
92103
)
93104
});
94105
let rhs_success_block = unpack!(this.then_else_break_inner(
95106
failure_block,
96107
rhs,
97-
ThenElseArgs { declare_let_bindings: true, ..args },
108+
ThenElseArgs {
109+
enclosing_not_expr_id: None,
110+
declare_let_bindings: true,
111+
..args
112+
},
98113
));
99114

100115
// Make the LHS and RHS success arms converge to a common block.
@@ -117,7 +132,11 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
117132
this.then_else_break_inner(
118133
block,
119134
arg,
120-
ThenElseArgs { declare_let_bindings: true, ..args },
135+
ThenElseArgs {
136+
enclosing_not_expr_id: args.enclosing_not_expr_id.or(Some(expr_id)),
137+
declare_let_bindings: true,
138+
..args
139+
},
121140
)
122141
});
123142
this.break_for_else(success_block, args.variable_source_info);
@@ -150,6 +169,15 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
150169
let else_block = this.cfg.start_new_block();
151170
let term = TerminatorKind::if_(operand, then_block, else_block);
152171

172+
// Record branch coverage info for this condition.
173+
// This is a no-op if branch coverage is not enabled.
174+
this.coverage_record_branch(
175+
expr_id,
176+
args.enclosing_not_expr_id,
177+
then_block,
178+
else_block,
179+
);
180+
153181
let source_info = this.source_info(expr_span);
154182
this.cfg.terminate(block, source_info, term);
155183
this.break_for_else(else_block, source_info);

0 commit comments

Comments
 (0)