Skip to content

Commit f831496

Browse files
committed
mcdc-coverage: Refactor Decision markers creation, add outcome markers creation
1 parent a30b4bb commit f831496

File tree

5 files changed

+132
-24
lines changed

5 files changed

+132
-24
lines changed

compiler/rustc_mir_build/messages.ftl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,8 @@ mir_build_lower_range_bound_must_be_less_than_or_equal_to_upper =
200200
201201
mir_build_lower_range_bound_must_be_less_than_upper = lower range bound must be less than upper
202202
203+
mir_build_mcdc_nested_decision = Unsupported MCDC Decision: Nested decision. Expression will not be corevered.
204+
203205
mir_build_more_information = for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
204206
205207
mir_build_moved = value is moved into `{$name}` here

compiler/rustc_mir_build/src/build/coverageinfo.rs

Lines changed: 110 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,24 @@ use rustc_span::def_id::LocalDefId;
1313
use rustc_span::Span;
1414

1515
use crate::build::Builder;
16+
use crate::errors::MCDCNestedDecision;
1617

1718
pub(crate) struct BranchInfoBuilder {
1819
/// Maps condition expressions to their enclosing `!`, for better instrumentation.
1920
nots: FxHashMap<ExprId, NotInfo>,
2021

2122
num_block_markers: usize,
2223
branch_spans: Vec<BranchSpan>,
24+
25+
// MCDC decision stuff
26+
/// ID of the current decision.
27+
/// Do not use directly. Use the function instead, as it will hide
28+
/// the decision in the scope of nested decisions.
29+
current_decision_id: Option<DecisionMarkerId>,
30+
/// Track the nesting level of decision to avoid MCDC instrumentation of
31+
/// nested decisions.
32+
nested_decision_level: u32,
33+
/// Vector for storing all the decisions with their span
2334
decisions: IndexVec<DecisionMarkerId, Span>,
2435
}
2536

@@ -44,6 +55,8 @@ impl BranchInfoBuilder {
4455
nots: FxHashMap::default(),
4556
num_block_markers: 0,
4657
branch_spans: vec![],
58+
current_decision_id: None,
59+
nested_decision_level: 0,
4760
decisions: IndexVec::new(),
4861
})
4962
} else {
@@ -98,7 +111,7 @@ impl BranchInfoBuilder {
98111
}
99112

100113
pub(crate) fn into_done(self) -> Option<Box<mir::coverage::BranchInfo>> {
101-
let Self { nots: _, num_block_markers, branch_spans, decisions } = self;
114+
let Self { nots: _, num_block_markers, branch_spans, decisions, .. } = self;
102115

103116
if num_block_markers == 0 {
104117
assert!(branch_spans.is_empty());
@@ -122,6 +135,32 @@ impl BranchInfoBuilder {
122135
decision_spans,
123136
}))
124137
}
138+
139+
/// Increase the nested decision level and return true if the
140+
/// decision can be instrumented (not in a nested condition).
141+
pub fn enter_decision(&mut self, span: Span) -> bool {
142+
self.nested_decision_level += 1;
143+
let can_mcdc = !self.in_nested_condition();
144+
145+
if can_mcdc {
146+
self.current_decision_id = Some(self.decisions.push(span));
147+
}
148+
149+
can_mcdc
150+
}
151+
152+
pub fn exit_decision(&mut self) {
153+
self.nested_decision_level -= 1;
154+
}
155+
156+
/// Return true if the current decision is located inside another decision.
157+
pub fn in_nested_condition(&self) -> bool {
158+
self.nested_decision_level > 1
159+
}
160+
161+
pub fn current_decision_id(&self) -> Option<DecisionMarkerId> {
162+
if self.in_nested_condition() { None } else { self.current_decision_id }
163+
}
125164
}
126165

127166
impl Builder<'_, '_> {
@@ -132,7 +171,6 @@ impl Builder<'_, '_> {
132171
mut expr_id: ExprId,
133172
mut then_block: BasicBlock,
134173
mut else_block: BasicBlock,
135-
decision_id: Option<DecisionMarkerId>, // If MCDC is enabled
136174
) {
137175
// Bail out if branch coverage is not enabled for this function.
138176
let Some(branch_info) = self.coverage_branch_info.as_ref() else { return };
@@ -153,7 +191,7 @@ impl Builder<'_, '_> {
153191
let mut inject_branch_marker = |block: BasicBlock| {
154192
let id = branch_info.next_block_marker_id();
155193

156-
let cov_kind = if let Some(decision_id) = decision_id {
194+
let cov_kind = if let Some(decision_id) = branch_info.current_decision_id() {
157195
CoverageKind::MCDCBlockMarker { id, decision_id }
158196
} else {
159197
CoverageKind::BlockMarker { id }
@@ -171,30 +209,35 @@ impl Builder<'_, '_> {
171209

172210
branch_info.branch_spans.push(BranchSpan {
173211
span: source_info.span,
174-
// FIXME: Handle case when MCDC is disabled better than just putting 0.
175-
decision_id: decision_id.unwrap_or(DecisionMarkerId::from_u32(0)),
212+
// FIXME(dprn): Handle case when MCDC is disabled better than just putting 0.
213+
decision_id: branch_info.current_decision_id.unwrap_or(DecisionMarkerId::from_u32(0)),
176214
true_marker,
177215
false_marker,
178216
});
179217
}
180218

181-
/// If MCDC coverage is enabled, inject a marker in all the decisions
182-
/// (boolean expressions)
183-
pub(crate) fn visit_coverage_decision(
184-
&mut self,
185-
expr_id: ExprId,
186-
block: BasicBlock,
187-
) -> Option<DecisionMarkerId> {
219+
/// If MCDC coverage is enabled, inject a decision entry marker in the given decision.
220+
/// return true
221+
pub(crate) fn begin_mcdc_decision_coverage(&mut self, expr_id: ExprId, block: BasicBlock) {
188222
// Early return if MCDC coverage is not enabled.
189223
if !self.tcx.sess.instrument_coverage_mcdc() {
190-
return None;
224+
return;
191225
}
192226
let Some(branch_info) = self.coverage_branch_info.as_mut() else {
193-
return None;
227+
return;
194228
};
195229

196230
let span = self.thir[expr_id].span;
197-
let decision_id = branch_info.decisions.push(span);
231+
232+
// enter_decision returns false if it detects nested decisions.
233+
if !branch_info.enter_decision(span) {
234+
// FIXME(dprn): do WARNING for nested decision.
235+
debug!("MCDC: Unsupported nested decision");
236+
self.tcx.dcx().emit_warn(MCDCNestedDecision { span });
237+
return;
238+
}
239+
240+
let decision_id = branch_info.current_decision_id().expect("Should have returned.");
198241

199242
// Inject a decision marker
200243
let source_info = self.source_info(span);
@@ -205,7 +248,58 @@ impl Builder<'_, '_> {
205248
}),
206249
};
207250
self.cfg.push(block, marker_statement);
251+
}
252+
253+
/// If MCDC is enabled, and function is instrumented,
254+
pub(crate) fn end_mcdc_decision_coverage(&mut self) {
255+
// Early return if MCDC coverage is not enabled.
256+
if !self.tcx.sess.instrument_coverage_mcdc() {
257+
return;
258+
}
259+
let Some(branch_info) = self.coverage_branch_info.as_mut() else {
260+
return;
261+
};
262+
263+
// Exit decision now so we can drop &mut to branch info
264+
branch_info.exit_decision();
265+
}
266+
267+
/// If MCDC is enabled and the current decision is being instrumented,
268+
/// inject an `MCDCDecisionOutputMarker` to the given basic block.
269+
/// `outcome` should be true for the then block and false for the else block.
270+
pub(crate) fn mcdc_decision_outcome_block(
271+
&mut self,
272+
bb: BasicBlock,
273+
outcome: bool,
274+
) -> BasicBlock {
275+
let Some(branch_info) = self.coverage_branch_info.as_mut() else {
276+
// Coverage instrumentation is not enabled.
277+
return bb;
278+
};
279+
let Some(decision_id) = branch_info.current_decision_id() else {
280+
// Decision is not instrumented
281+
return bb;
282+
};
283+
284+
let span = branch_info.decisions[decision_id];
285+
let source_info = self.source_info(span);
286+
let marker_statement = mir::Statement {
287+
source_info,
288+
kind: mir::StatementKind::Coverage(CoverageKind::MCDCDecisionOutputMarker {
289+
id: decision_id,
290+
outcome,
291+
}),
292+
};
293+
294+
// Insert statements at the beginning of the following basic block
295+
self.cfg.block_data_mut(bb).statements.insert(0, marker_statement);
296+
297+
// Create a new block to return
298+
let new_bb = self.cfg.start_new_block();
299+
300+
// Set bb -> new_bb
301+
self.cfg.goto(bb, source_info, new_bb);
208302

209-
Some(decision_id)
303+
new_bb
210304
}
211305
}

compiler/rustc_mir_build/src/build/expr/into.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
7676
this.source_info(then_span)
7777
};
7878

79+
// If MCDC is enabled AND we are not in a nested decision,
80+
// Mark the decision's root, true and false outcomes.
81+
this.begin_mcdc_decision_coverage(expr_id, block);
82+
7983
// Lower the condition, and have it branch into `then` and `else` blocks.
8084
let (then_block, else_block) =
8185
this.in_if_then_scope(condition_scope, then_span, |this| {
@@ -87,10 +91,19 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
8791
true, // Declare `let` bindings normally
8892
));
8993

94+
// If MCDC is enabled, inject an outcome marker.
95+
let then_blk = this.mcdc_decision_outcome_block(then_blk, true);
96+
9097
// Lower the `then` arm into its block.
9198
this.expr_into_dest(destination, then_blk, then)
9299
});
93100

101+
// If MCDC is enabled and decision was instrumented, exit the
102+
// decision scope and inject an MCDC decision output marker
103+
// in the then and else blocks.
104+
let else_block = this.mcdc_decision_outcome_block(else_block, false);
105+
this.end_mcdc_decision_coverage();
106+
94107
// Pack `(then_block, else_block)` into `BlockAnd<BasicBlock>`.
95108
then_block.and(else_block)
96109
},

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ use rustc_data_structures::{
1616
};
1717
use rustc_hir::{BindingAnnotation, ByRef};
1818
use rustc_middle::middle::region;
19-
use rustc_middle::mir::coverage::DecisionMarkerId;
2019
use rustc_middle::mir::{self, *};
2120
use rustc_middle::thir::{self, *};
2221
use rustc_middle::ty::{self, CanonicalUserTypeAnnotation, Ty};
@@ -42,9 +41,6 @@ struct ThenElseArgs {
4241
/// Forwarded to [`Builder::lower_let_expr`] when lowering [`ExprKind::Let`].
4342
/// When false (for match guards), `let` bindings won't be declared.
4443
declare_let_bindings: bool,
45-
46-
/// Id of the parent decision id MCDC is enabled
47-
decision_id: Option<DecisionMarkerId>,
4844
}
4945

5046
impl<'a, 'tcx> Builder<'a, 'tcx> {
@@ -62,8 +58,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
6258
variable_source_info: SourceInfo,
6359
declare_let_bindings: bool,
6460
) -> BlockAnd<()> {
65-
// Get an ID for the decision if MCDC is enabled.
66-
let decision_id = self.visit_coverage_decision(expr_id, block);
6761

6862
self.then_else_break_inner(
6963
block,
@@ -72,7 +66,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
7266
temp_scope_override,
7367
variable_source_info,
7468
declare_let_bindings,
75-
decision_id,
7669
},
7770
)
7871
}
@@ -175,7 +168,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
175168
expr_id,
176169
then_block,
177170
else_block,
178-
args.decision_id,
179171
);
180172

181173
let source_info = this.source_info(expr_span);

compiler/rustc_mir_build/src/errors.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,13 @@ pub(crate) struct PatternNotCovered<'s, 'tcx> {
850850
pub misc_suggestion: Option<MiscPatternSuggestion>,
851851
}
852852

853+
#[derive(Diagnostic)]
854+
#[diag(mir_build_mcdc_nested_decision)]
855+
pub(crate) struct MCDCNestedDecision {
856+
#[primary_span]
857+
pub span: Span,
858+
}
859+
853860
#[derive(Subdiagnostic)]
854861
#[note(mir_build_inform_irrefutable)]
855862
#[note(mir_build_more_information)]

0 commit comments

Comments
 (0)