Skip to content

Commit 7d1dc67

Browse files
committed
mcdc-coverage: Add MCDCBlockMarker and MCDCDecisionMarker to the MIR
1 parent 7b321ca commit 7d1dc67

File tree

8 files changed

+131
-17
lines changed

8 files changed

+131
-17
lines changed

compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,10 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> {
9999
.or_insert_with(|| FunctionCoverageCollector::new(instance, function_coverage_info));
100100

101101
match *kind {
102-
CoverageKind::SpanMarker | CoverageKind::BlockMarker { .. } => unreachable!(
102+
CoverageKind::SpanMarker
103+
| CoverageKind::BlockMarker { .. }
104+
| CoverageKind::MCDCBlockMarker { .. }
105+
| CoverageKind::MCDCDecisionMarker { .. } => unreachable!(
103106
"marker statement {kind:?} should have been removed by CleanupPostBorrowck"
104107
),
105108
CoverageKind::CounterIncrement { id } => {

compiler/rustc_middle/src/mir/coverage.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ rustc_index::newtype_index! {
1515
pub struct BlockMarkerId {}
1616
}
1717

18+
rustc_index::newtype_index! {
19+
#[derive(HashStable)]
20+
#[encodable]
21+
#[debug_format = "DecisionMarkerId({})"]
22+
pub struct DecisionMarkerId {}
23+
}
24+
1825
rustc_index::newtype_index! {
1926
/// ID of a coverage counter. Values ascend from 0.
2027
///
@@ -97,6 +104,18 @@ pub enum CoverageKind {
97104
/// Should be erased before codegen (at some point after `InstrumentCoverage`).
98105
BlockMarker { id: BlockMarkerId },
99106

107+
/// Same as BlockMarker, but carries a reference to its parent decision for
108+
/// MCDC coverage.
109+
///
110+
/// Has no effect during codegen.
111+
MCDCBlockMarker { id: BlockMarkerId, decision_id: DecisionMarkerId },
112+
113+
/// Marks the first condition of a decision (boolean expression). All
114+
/// conditions in the same decision will reference this id.
115+
///
116+
/// Has no effect during codegen.
117+
MCDCDecisionMarker { id: DecisionMarkerId },
118+
100119
/// Marks the point in MIR control flow represented by a coverage counter.
101120
///
102121
/// This is eventually lowered to `llvm.instrprof.increment` in LLVM IR.
@@ -122,6 +141,10 @@ impl Debug for CoverageKind {
122141
match self {
123142
SpanMarker => write!(fmt, "SpanMarker"),
124143
BlockMarker { id } => write!(fmt, "BlockMarker({:?})", id.index()),
144+
MCDCBlockMarker { id, decision_id } => {
145+
write!(fmt, "MCDCBlockMarker({:?}, {:?})", id.index(), decision_id.index())
146+
}
147+
MCDCDecisionMarker { id } => write!(fmt, "MCDCDecisionMarker({:?})", id.index()),
125148
CounterIncrement { id } => write!(fmt, "CounterIncrement({:?})", id.index()),
126149
ExpressionUsed { id } => write!(fmt, "ExpressionUsed({:?})", id.index()),
127150
}
@@ -234,12 +257,16 @@ pub struct BranchInfo {
234257
/// data structures without having to scan the entire body first.
235258
pub num_block_markers: usize,
236259
pub branch_spans: Vec<BranchSpan>,
260+
pub decisions: IndexVec<DecisionMarkerId, Span>,
237261
}
238262

239263
#[derive(Clone, Debug)]
240264
#[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
241265
pub struct BranchSpan {
266+
/// Source code region associated to the branch.
242267
pub span: Span,
268+
/// Decision structure the branch is part of. Only used in the MCDC coverage.
269+
pub decision_id: DecisionMarkerId,
243270
pub true_marker: BlockMarkerId,
244271
pub false_marker: BlockMarkerId,
245272
}

compiler/rustc_middle/src/mir/pretty.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ fn write_coverage_branch_info(
477477
) -> io::Result<()> {
478478
let coverage::BranchInfo { branch_spans, .. } = branch_info;
479479

480-
for coverage::BranchSpan { span, true_marker, false_marker } in branch_spans {
480+
for coverage::BranchSpan { span, true_marker, false_marker, .. } in branch_spans {
481481
writeln!(
482482
w,
483483
"{INDENT}coverage branch {{ true: {true_marker:?}, false: {false_marker:?} }} => {span:?}",

compiler/rustc_middle/src/ty/structural_impls.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ TrivialTypeTraversalImpls! {
407407
::rustc_hir::MatchSource,
408408
::rustc_target::asm::InlineAsmRegOrRegClass,
409409
crate::mir::coverage::BlockMarkerId,
410+
crate::mir::coverage::DecisionMarkerId,
410411
crate::mir::coverage::CounterId,
411412
crate::mir::coverage::ExpressionId,
412413
crate::mir::Local,

compiler/rustc_mir_build/src/build/coverageinfo.rs

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ use std::assert_matches::assert_matches;
22
use std::collections::hash_map::Entry;
33

44
use rustc_data_structures::fx::FxHashMap;
5-
use rustc_middle::mir::coverage::{BlockMarkerId, BranchSpan, CoverageKind};
5+
use rustc_index::IndexVec;
6+
use rustc_middle::mir::coverage::{BlockMarkerId, BranchSpan, CoverageKind, DecisionMarkerId};
67
use rustc_middle::mir::{self, BasicBlock, UnOp};
78
use rustc_middle::thir::{ExprId, ExprKind, Thir};
89
use rustc_middle::ty::TyCtxt;
910
use rustc_span::def_id::LocalDefId;
11+
use rustc_span::Span;
1012

1113
use crate::build::Builder;
1214

@@ -16,6 +18,7 @@ pub(crate) struct BranchInfoBuilder {
1618

1719
num_block_markers: usize,
1820
branch_spans: Vec<BranchSpan>,
21+
decisions: IndexVec<DecisionMarkerId, Span>,
1922
}
2023

2124
#[derive(Clone, Copy)]
@@ -32,8 +35,15 @@ impl BranchInfoBuilder {
3235
/// Creates a new branch info builder, but only if branch coverage instrumentation
3336
/// is enabled and `def_id` represents a function that is eligible for coverage.
3437
pub(crate) fn new_if_enabled(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<Self> {
35-
if tcx.sess.instrument_coverage_branch() && tcx.is_eligible_for_coverage(def_id) {
36-
Some(Self { nots: FxHashMap::default(), num_block_markers: 0, branch_spans: vec![] })
38+
if (tcx.sess.instrument_coverage_branch() || tcx.sess.instrument_coverage_mcdc())
39+
&& tcx.is_eligible_for_coverage(def_id)
40+
{
41+
Some(Self {
42+
nots: FxHashMap::default(),
43+
num_block_markers: 0,
44+
branch_spans: vec![],
45+
decisions: IndexVec::new(),
46+
})
3747
} else {
3848
None
3949
}
@@ -86,14 +96,14 @@ impl BranchInfoBuilder {
8696
}
8797

8898
pub(crate) fn into_done(self) -> Option<Box<mir::coverage::BranchInfo>> {
89-
let Self { nots: _, num_block_markers, branch_spans } = self;
99+
let Self { nots: _, num_block_markers, branch_spans, decisions } = self;
90100

91101
if num_block_markers == 0 {
92102
assert!(branch_spans.is_empty());
93103
return None;
94104
}
95105

96-
Some(Box::new(mir::coverage::BranchInfo { num_block_markers, branch_spans }))
106+
Some(Box::new(mir::coverage::BranchInfo { num_block_markers, branch_spans, decisions }))
97107
}
98108
}
99109

@@ -105,6 +115,7 @@ impl Builder<'_, '_> {
105115
mut expr_id: ExprId,
106116
mut then_block: BasicBlock,
107117
mut else_block: BasicBlock,
118+
decision_id: Option<DecisionMarkerId>, // If MCDC is enabled
108119
) {
109120
// Bail out if branch coverage is not enabled for this function.
110121
let Some(branch_info) = self.coverage_branch_info.as_ref() else { return };
@@ -125,9 +136,15 @@ impl Builder<'_, '_> {
125136
let mut inject_branch_marker = |block: BasicBlock| {
126137
let id = branch_info.next_block_marker_id();
127138

139+
let cov_kind = if let Some(decision_id) = decision_id {
140+
CoverageKind::MCDCBlockMarker { id, decision_id }
141+
} else {
142+
CoverageKind::BlockMarker { id }
143+
};
144+
128145
let marker_statement = mir::Statement {
129146
source_info,
130-
kind: mir::StatementKind::Coverage(CoverageKind::BlockMarker { id }),
147+
kind: mir::StatementKind::Coverage(cov_kind),
131148
};
132149
self.cfg.push(block, marker_statement);
133150

@@ -139,8 +156,41 @@ impl Builder<'_, '_> {
139156

140157
branch_info.branch_spans.push(BranchSpan {
141158
span: source_info.span,
159+
// FIXME: Handle case when MCDC is disabled better than just putting 0.
160+
decision_id: decision_id.unwrap_or(DecisionMarkerId::from_u32(0)),
142161
true_marker,
143162
false_marker,
144163
});
145164
}
165+
166+
/// If MCDC coverage is enabled, inject a marker in all the decisions
167+
/// (boolean expressions)
168+
pub(crate) fn visit_coverage_decision(
169+
&mut self,
170+
expr_id: ExprId,
171+
block: BasicBlock,
172+
) -> Option<DecisionMarkerId> {
173+
// Early return if MCDC coverage is not enabled.
174+
if !self.tcx.sess.instrument_coverage_mcdc() {
175+
return None;
176+
}
177+
let Some(branch_info) = self.coverage_branch_info.as_mut() else {
178+
return None;
179+
};
180+
181+
let span = self.thir[expr_id].span;
182+
let decision_id = branch_info.decisions.push(span);
183+
184+
// Inject a decision marker
185+
let source_info = self.source_info(span);
186+
let marker_statement = mir::Statement {
187+
source_info,
188+
kind: mir::StatementKind::Coverage(
189+
CoverageKind::MCDCDecisionMarker { id: decision_id },
190+
),
191+
};
192+
self.cfg.push(block, marker_statement);
193+
194+
Some(decision_id)
195+
}
146196
}

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

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

4650
impl<'a, 'tcx> Builder<'a, 'tcx> {
@@ -58,10 +62,18 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
5862
variable_source_info: SourceInfo,
5963
declare_let_bindings: bool,
6064
) -> BlockAnd<()> {
65+
// Get an ID for the decision if MCDC is enabled.
66+
let decision_id = self.visit_coverage_decision(expr_id, block);
67+
6168
self.then_else_break_inner(
6269
block,
6370
expr_id,
64-
ThenElseArgs { temp_scope_override, variable_source_info, declare_let_bindings },
71+
ThenElseArgs {
72+
temp_scope_override,
73+
variable_source_info,
74+
declare_let_bindings,
75+
decision_id,
76+
},
6577
)
6678
}
6779

@@ -159,7 +171,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
159171

160172
// Record branch coverage info for this condition.
161173
// (Does nothing if branch coverage is not enabled.)
162-
this.visit_coverage_branch_condition(expr_id, then_block, else_block);
174+
this.visit_coverage_branch_condition(
175+
expr_id,
176+
then_block,
177+
else_block,
178+
args.decision_id,
179+
);
163180

164181
let source_info = this.source_info(expr_span);
165182
this.cfg.terminate(block, source_info, term);

compiler/rustc_mir_transform/src/cleanup_post_borrowck.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
//! - [`AscribeUserType`]
66
//! - [`FakeRead`]
77
//! - [`Assign`] statements with a [`Fake`] borrow
8-
//! - [`Coverage`] statements of kind [`BlockMarker`] or [`SpanMarker`]
8+
//! - [`Coverage`] statements of kind [`BlockMarker`], [`SpanMarker`], [`MCDCBlockMarker`], or [`MCDCDecisionMarker`]
99
//!
1010
//! [`AscribeUserType`]: rustc_middle::mir::StatementKind::AscribeUserType
1111
//! [`Assign`]: rustc_middle::mir::StatementKind::Assign
@@ -15,6 +15,8 @@
1515
//! [`Coverage`]: rustc_middle::mir::StatementKind::Coverage
1616
//! [`BlockMarker`]: rustc_middle::mir::coverage::CoverageKind::BlockMarker
1717
//! [`SpanMarker`]: rustc_middle::mir::coverage::CoverageKind::SpanMarker
18+
//! [`MCDCBlockMarker`]: rustc_middle::mir::coverage::CoverageKind::MCDCBlockMarker
19+
//! [`MCDCDecisionMarker`]: rustc_middle::mir::coverage::CoverageKind::MCDCDecisionMarker
1820
1921
use crate::MirPass;
2022
use rustc_middle::mir::coverage::CoverageKind;
@@ -33,7 +35,10 @@ impl<'tcx> MirPass<'tcx> for CleanupPostBorrowck {
3335
| StatementKind::Coverage(
3436
// These kinds of coverage statements are markers inserted during
3537
// MIR building, and are not needed after InstrumentCoverage.
36-
CoverageKind::BlockMarker { .. } | CoverageKind::SpanMarker { .. },
38+
CoverageKind::BlockMarker { .. }
39+
| CoverageKind::SpanMarker { .. }
40+
| CoverageKind::MCDCBlockMarker { .. }
41+
| CoverageKind::MCDCDecisionMarker { .. },
3742
)
3843
| StatementKind::FakeRead(..) => statement.make_nop(),
3944
_ => (),

compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,13 @@ fn filtered_statement_span(statement: &Statement<'_>) -> Option<Span> {
222222
| StatementKind::PlaceMention(..)
223223
| StatementKind::AscribeUserType(_, _) => Some(statement.source_info.span),
224224

225-
// Block markers are used for branch coverage, so ignore them here.
226-
StatementKind::Coverage(CoverageKind::BlockMarker { .. }) => None,
225+
StatementKind::Coverage(
226+
// Block markers are used for branch coverage, so ignore them here.
227+
CoverageKind::BlockMarker {..}
228+
// Ignore MCDC markers as well
229+
| CoverageKind::MCDCBlockMarker{ .. }
230+
| CoverageKind::MCDCDecisionMarker{ .. }
231+
) => None,
227232

228233
// These coverage statements should not exist prior to coverage instrumentation.
229234
StatementKind::Coverage(
@@ -378,16 +383,22 @@ pub(super) fn extract_branch_mappings(
378383
// Fill out the mapping from block marker IDs to their enclosing blocks.
379384
for (bb, data) in mir_body.basic_blocks.iter_enumerated() {
380385
for statement in &data.statements {
381-
if let StatementKind::Coverage(CoverageKind::BlockMarker { id }) = statement.kind {
382-
block_markers[id] = Some(bb);
386+
if let StatementKind::Coverage(kind) = &statement.kind {
387+
match kind {
388+
CoverageKind::BlockMarker { id }
389+
| CoverageKind::MCDCBlockMarker { id, decision_id: _ } => {
390+
block_markers[*id] = Some(bb);
391+
}
392+
_ => (),
393+
}
383394
}
384395
}
385396
}
386397

387398
branch_info
388399
.branch_spans
389400
.iter()
390-
.filter_map(|&BranchSpan { span: raw_span, true_marker, false_marker }| {
401+
.filter_map(|&BranchSpan { span: raw_span, decision_id: _, true_marker, false_marker }| {
391402
// For now, ignore any branch span that was introduced by
392403
// expansion. This makes things like assert macros less noisy.
393404
if !raw_span.ctxt().outer_expn_data().is_root() {

0 commit comments

Comments
 (0)