Skip to content

Commit b6591fb

Browse files
author
zhuyunxing
committed
coverage. Change structure of MCDCBranchSpan to prepare for matching arms
1 parent d974f49 commit b6591fb

File tree

10 files changed

+524
-355
lines changed

10 files changed

+524
-355
lines changed

compiler/rustc_middle/src/mir/coverage.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -326,8 +326,10 @@ pub struct MCDCBranchSpan {
326326
/// If `None`, this actually represents a normal branch span inserted for
327327
/// code that was too complex for MC/DC.
328328
pub condition_info: Option<ConditionInfo>,
329-
pub true_marker: BlockMarkerId,
330-
pub false_marker: BlockMarkerId,
329+
/// Markers inserted into blocks where the condition is tested.
330+
pub test_markers: Vec<BlockMarkerId>,
331+
/// Markers inserted into blocks which would be executed if the condition is true.
332+
pub true_markers: Vec<BlockMarkerId>,
331333
pub decision_depth: u16,
332334
}
333335

compiler/rustc_middle/src/mir/pretty.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -499,14 +499,14 @@ fn write_coverage_branch_info(
499499
for coverage::MCDCBranchSpan {
500500
span,
501501
condition_info,
502-
true_marker,
503-
false_marker,
502+
test_markers,
503+
true_markers,
504504
decision_depth,
505505
} in mcdc_branch_spans
506506
{
507507
writeln!(
508508
w,
509-
"{INDENT}coverage mcdc branch {{ condition_id: {:?}, true: {true_marker:?}, false: {false_marker:?}, depth: {decision_depth:?} }} => {span:?}",
509+
"{INDENT}coverage mcdc branch {{ condition_id: {:?}, test: {test_markers:?}, true: {true_markers:?}, depth: {decision_depth:?} }} => {span:?}",
510510
condition_info.map(|info| info.condition_id)
511511
)?;
512512
}

compiler/rustc_mir_build/src/build/coverageinfo.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ impl<'tcx> Builder<'_, 'tcx> {
162162
pub(crate) fn visit_coverage_branch_condition(
163163
&mut self,
164164
mut expr_id: ExprId,
165+
test_block: BasicBlock,
165166
mut then_block: BasicBlock,
166167
mut else_block: BasicBlock,
167168
) {
@@ -181,12 +182,12 @@ impl<'tcx> Builder<'_, 'tcx> {
181182

182183
// Separate path for handling branches when MC/DC is enabled.
183184
if let Some(mcdc_info) = branch_info.mcdc_info.as_mut() {
184-
let inject_block_marker = |source_info, block| {
185-
branch_info.markers.inject_block_marker(&mut self.cfg, source_info, block)
186-
};
185+
let inject_block_marker =
186+
|block| branch_info.markers.inject_block_marker(&mut self.cfg, source_info, block);
187187
mcdc_info.visit_evaluated_condition(
188188
self.tcx,
189-
source_info,
189+
source_info.span,
190+
test_block,
190191
then_block,
191192
else_block,
192193
inject_block_marker,

compiler/rustc_mir_build/src/build/coverageinfo/mcdc.rs

+125-110
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::collections::VecDeque;
33
use rustc_middle::mir::coverage::{
44
BlockMarkerId, ConditionId, ConditionInfo, MCDCBranchSpan, MCDCDecisionSpan,
55
};
6-
use rustc_middle::mir::{BasicBlock, SourceInfo};
6+
use rustc_middle::mir::BasicBlock;
77
use rustc_middle::thir::LogicalOp;
88
use rustc_middle::ty::TyCtxt;
99
use rustc_span::Span;
@@ -16,34 +16,24 @@ use crate::errors::MCDCExceedsConditionNumLimit;
1616
/// This limit may be relaxed if the [upstream change](https://github.com/llvm/llvm-project/pull/82448) is merged.
1717
const MAX_CONDITIONS_NUM_IN_DECISION: usize = 6;
1818

19-
#[derive(Default)]
2019
struct MCDCDecisionCtx {
2120
/// To construct condition evaluation tree.
21+
decision_info: MCDCDecisionSpan,
2222
decision_stack: VecDeque<ConditionInfo>,
23-
processing_decision: Option<MCDCDecisionSpan>,
23+
conditions: Vec<MCDCBranchSpan>,
2424
}
2525

26-
struct MCDCState {
27-
decision_ctx_stack: Vec<MCDCDecisionCtx>,
28-
}
29-
30-
impl MCDCState {
31-
fn new() -> Self {
32-
Self { decision_ctx_stack: vec![MCDCDecisionCtx::default()] }
33-
}
34-
35-
/// Decision depth is given as a u16 to reduce the size of the `CoverageKind`,
36-
/// as it is very unlikely that the depth ever reaches 2^16.
37-
#[inline]
38-
fn decision_depth(&self) -> u16 {
39-
match u16::try_from(self.decision_ctx_stack.len())
40-
.expect(
41-
"decision depth did not fit in u16, this is likely to be an instrumentation error",
42-
)
43-
.checked_sub(1)
44-
{
45-
Some(d) => d,
46-
None => bug!("Unexpected empty decision stack"),
26+
impl MCDCDecisionCtx {
27+
fn new(decision_depth: u16) -> Self {
28+
Self {
29+
decision_stack: VecDeque::new(),
30+
decision_info: MCDCDecisionSpan {
31+
span: Span::default(),
32+
conditions_num: 0,
33+
end_markers: vec![],
34+
decision_depth,
35+
},
36+
conditions: vec![],
4737
}
4838
}
4939

@@ -87,34 +77,17 @@ impl MCDCState {
8777
// As the compiler tracks expression in pre-order, we can ensure that condition info of parents are always properly assigned when their children are visited.
8878
// - If the op is AND, the "false_next" of LHS and RHS should be the parent's "false_next". While "true_next" of the LHS is the RHS, the "true next" of RHS is the parent's "true_next".
8979
// - If the op is OR, the "true_next" of LHS and RHS should be the parent's "true_next". While "false_next" of the LHS is the RHS, the "false next" of RHS is the parent's "false_next".
90-
fn record_conditions(&mut self, op: LogicalOp, span: Span) {
91-
let decision_depth = self.decision_depth();
92-
let Some(decision_ctx) = self.decision_ctx_stack.last_mut() else {
93-
bug!("Unexpected empty decision_ctx_stack")
94-
};
95-
let decision = match decision_ctx.processing_decision.as_mut() {
96-
Some(decision) => {
97-
decision.span = decision.span.to(span);
98-
decision
99-
}
100-
None => decision_ctx.processing_decision.insert(MCDCDecisionSpan {
101-
span,
102-
conditions_num: 0,
103-
end_markers: vec![],
104-
decision_depth,
105-
}),
106-
};
107-
108-
let parent_condition = decision_ctx.decision_stack.pop_back().unwrap_or_default();
80+
fn record_conditions(&mut self, op: LogicalOp) {
81+
let parent_condition = self.decision_stack.pop_back().unwrap_or_default();
10982
let lhs_id = if parent_condition.condition_id == ConditionId::NONE {
110-
decision.conditions_num += 1;
111-
ConditionId::from(decision.conditions_num)
83+
self.decision_info.conditions_num += 1;
84+
ConditionId::from(self.decision_info.conditions_num)
11285
} else {
11386
parent_condition.condition_id
11487
};
11588

116-
decision.conditions_num += 1;
117-
let rhs_condition_id = ConditionId::from(decision.conditions_num);
89+
self.decision_info.conditions_num += 1;
90+
let rhs_condition_id = ConditionId::from(self.decision_info.conditions_num);
11891

11992
let (lhs, rhs) = match op {
12093
LogicalOp::And => {
@@ -145,85 +118,118 @@ impl MCDCState {
145118
}
146119
};
147120
// We visit expressions tree in pre-order, so place the left-hand side on the top.
148-
decision_ctx.decision_stack.push_back(rhs);
149-
decision_ctx.decision_stack.push_back(lhs);
121+
self.decision_stack.push_back(rhs);
122+
self.decision_stack.push_back(lhs);
150123
}
151124

152-
fn take_condition(
125+
fn finish_two_way_branch(
153126
&mut self,
127+
span: Span,
128+
test_marker: BlockMarkerId,
154129
true_marker: BlockMarkerId,
155130
false_marker: BlockMarkerId,
156-
) -> (Option<ConditionInfo>, Option<MCDCDecisionSpan>) {
157-
let Some(decision_ctx) = self.decision_ctx_stack.last_mut() else {
158-
bug!("Unexpected empty decision_ctx_stack")
159-
};
160-
let Some(condition_info) = decision_ctx.decision_stack.pop_back() else {
161-
return (None, None);
162-
};
163-
let Some(decision) = decision_ctx.processing_decision.as_mut() else {
164-
bug!("Processing decision should have been created before any conditions are taken");
165-
};
131+
) {
132+
let condition_info = self.decision_stack.pop_back().unwrap_or_default();
166133
if condition_info.true_next_id == ConditionId::NONE {
167-
decision.end_markers.push(true_marker);
134+
self.decision_info.end_markers.push(true_marker);
168135
}
169136
if condition_info.false_next_id == ConditionId::NONE {
170-
decision.end_markers.push(false_marker);
137+
self.decision_info.end_markers.push(false_marker);
171138
}
172139

173-
if decision_ctx.decision_stack.is_empty() {
174-
(Some(condition_info), decision_ctx.processing_decision.take())
175-
} else {
176-
(Some(condition_info), None)
177-
}
140+
self.conditions.push(MCDCBranchSpan {
141+
span,
142+
condition_info: Some(condition_info),
143+
test_markers: vec![test_marker],
144+
true_markers: vec![true_marker],
145+
decision_depth: self.decision_info.decision_depth,
146+
});
147+
// In case this decision had only one condition
148+
self.decision_info.conditions_num = self.decision_info.conditions_num.max(1);
149+
}
150+
151+
fn finished(&self) -> bool {
152+
self.decision_stack.is_empty()
178153
}
179154
}
180155

181156
pub struct MCDCInfoBuilder {
182157
branch_spans: Vec<MCDCBranchSpan>,
183158
decision_spans: Vec<MCDCDecisionSpan>,
184-
state: MCDCState,
159+
decision_ctx_stack: Vec<MCDCDecisionCtx>,
160+
base_depth: u16,
185161
}
186162

187163
impl MCDCInfoBuilder {
188164
pub fn new() -> Self {
189-
Self { branch_spans: vec![], decision_spans: vec![], state: MCDCState::new() }
165+
Self {
166+
branch_spans: vec![],
167+
decision_spans: vec![],
168+
decision_ctx_stack: vec![],
169+
base_depth: 0,
170+
}
190171
}
191172

192-
pub fn visit_evaluated_condition(
173+
fn next_decision_depth(&self) -> u16 {
174+
u16::try_from(self.decision_ctx_stack.len()).expect(
175+
"decision depth did not fit in u16, this is likely to be an instrumentation error",
176+
)
177+
}
178+
179+
fn ensure_decision(&mut self, span: Span) -> &mut MCDCDecisionCtx {
180+
if self.base_depth == self.next_decision_depth() {
181+
let depth = self.next_decision_depth();
182+
self.decision_ctx_stack.push(MCDCDecisionCtx::new(depth));
183+
} else {
184+
assert!(
185+
self.base_depth <= self.next_decision_depth(),
186+
"expected depth shall not skip next decision depth"
187+
);
188+
}
189+
let ctx = self.decision_ctx_stack.last_mut().expect("ensured above");
190+
191+
if ctx.decision_info.span == Span::default() {
192+
ctx.decision_info.span = span;
193+
} else {
194+
ctx.decision_info.span = ctx.decision_info.span.to(span);
195+
}
196+
ctx
197+
}
198+
199+
fn try_finish_decision(&mut self, tcx: TyCtxt<'_>) {
200+
if !self.decision_ctx_stack.last().is_some_and(|decision| decision.finished()) {
201+
return;
202+
}
203+
let MCDCDecisionCtx { decision_info, decision_stack: _, conditions } =
204+
self.decision_ctx_stack.pop().expect("has checked above");
205+
206+
self.append_mcdc_info(tcx, decision_info, conditions);
207+
}
208+
209+
fn append_mcdc_info(
193210
&mut self,
194211
tcx: TyCtxt<'_>,
195-
source_info: SourceInfo,
196-
true_block: BasicBlock,
197-
false_block: BasicBlock,
198-
mut inject_block_marker: impl FnMut(SourceInfo, BasicBlock) -> BlockMarkerId,
212+
decision: MCDCDecisionSpan,
213+
mut conditions: Vec<MCDCBranchSpan>,
199214
) {
200-
let true_marker = inject_block_marker(source_info, true_block);
201-
let false_marker = inject_block_marker(source_info, false_block);
202-
203-
let decision_depth = self.state.decision_depth();
204-
let (mut condition_info, decision_result) =
205-
self.state.take_condition(true_marker, false_marker);
206215
// take_condition() returns Some for decision_result when the decision stack
207216
// is empty, i.e. when all the conditions of the decision were instrumented,
208217
// and the decision is "complete".
209-
if let Some(decision) = decision_result {
210-
match decision.conditions_num {
211-
0 => {
212-
unreachable!("Decision with no condition is not expected");
213-
}
214-
1..=MAX_CONDITIONS_NUM_IN_DECISION => {
215-
self.decision_spans.push(decision);
216-
}
217-
_ => {
218-
// Do not generate mcdc mappings and statements for decisions with too many conditions.
219-
let rebase_idx = self.branch_spans.len() - decision.conditions_num + 1;
220-
for branch in &mut self.branch_spans[rebase_idx..] {
221-
branch.condition_info = None;
222-
}
223-
224-
// ConditionInfo of this branch shall also be reset.
225-
condition_info = None;
226-
218+
match decision.conditions_num {
219+
0 => {
220+
unreachable!("Decision with no condition is not expected");
221+
}
222+
2..=MAX_CONDITIONS_NUM_IN_DECISION => {
223+
self.decision_spans.push(decision);
224+
self.branch_spans.extend(conditions);
225+
}
226+
// MCDC is equivalent to normal branch coverage if number of conditions is less than 1, so ignore these decisions.
227+
// See comment of `MAX_CONDITIONS_NUM_IN_DECISION` for why decisions with oversized conditions are ignored.
228+
_ => {
229+
// Generate normal branch coverage mappings in such cases.
230+
conditions.iter_mut().for_each(|branch| branch.condition_info = None);
231+
self.branch_spans.extend(conditions);
232+
if decision.conditions_num > MAX_CONDITIONS_NUM_IN_DECISION {
227233
tcx.dcx().emit_warn(MCDCExceedsConditionNumLimit {
228234
span: decision.span,
229235
conditions_num: decision.conditions_num,
@@ -232,13 +238,23 @@ impl MCDCInfoBuilder {
232238
}
233239
}
234240
}
235-
self.branch_spans.push(MCDCBranchSpan {
236-
span: source_info.span,
237-
condition_info,
238-
true_marker,
239-
false_marker,
240-
decision_depth,
241-
});
241+
}
242+
243+
pub fn visit_evaluated_condition(
244+
&mut self,
245+
tcx: TyCtxt<'_>,
246+
span: Span,
247+
test_block: BasicBlock,
248+
true_block: BasicBlock,
249+
false_block: BasicBlock,
250+
mut inject_block_marker: impl FnMut(BasicBlock) -> BlockMarkerId,
251+
) {
252+
let test_marker = inject_block_marker(test_block);
253+
let true_marker = inject_block_marker(true_block);
254+
let false_marker = inject_block_marker(false_block);
255+
let decision = self.ensure_decision(span);
256+
decision.finish_two_way_branch(span, test_marker, true_marker, false_marker);
257+
self.try_finish_decision(tcx);
242258
}
243259

244260
pub fn into_done(self) -> (Vec<MCDCDecisionSpan>, Vec<MCDCBranchSpan>) {
@@ -251,25 +267,24 @@ impl Builder<'_, '_> {
251267
if let Some(branch_info) = self.coverage_branch_info.as_mut()
252268
&& let Some(mcdc_info) = branch_info.mcdc_info.as_mut()
253269
{
254-
mcdc_info.state.record_conditions(logical_op, span);
270+
let decision = mcdc_info.ensure_decision(span);
271+
decision.record_conditions(logical_op);
255272
}
256273
}
257274

258275
pub(crate) fn mcdc_increment_depth_if_enabled(&mut self) {
259276
if let Some(branch_info) = self.coverage_branch_info.as_mut()
260277
&& let Some(mcdc_info) = branch_info.mcdc_info.as_mut()
261278
{
262-
mcdc_info.state.decision_ctx_stack.push(MCDCDecisionCtx::default());
279+
mcdc_info.base_depth = mcdc_info.next_decision_depth().max(mcdc_info.base_depth + 1);
263280
};
264281
}
265282

266283
pub(crate) fn mcdc_decrement_depth_if_enabled(&mut self) {
267284
if let Some(branch_info) = self.coverage_branch_info.as_mut()
268285
&& let Some(mcdc_info) = branch_info.mcdc_info.as_mut()
269286
{
270-
if mcdc_info.state.decision_ctx_stack.pop().is_none() {
271-
bug!("Unexpected empty decision stack");
272-
}
287+
mcdc_info.base_depth -= 1;
273288
};
274289
}
275290
}

0 commit comments

Comments
 (0)