Skip to content

Commit 0255b0b

Browse files
author
zhuyunxing
committed
coverage. Implement branch coverage in mcdc for pattern match
1 parent 9cf10bc commit 0255b0b

File tree

9 files changed

+359
-76
lines changed

9 files changed

+359
-76
lines changed

compiler/rustc_middle/src/mir/coverage.rs

+32-2
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,7 @@ pub struct BranchInfo {
280280
/// data structures without having to scan the entire body first.
281281
pub num_block_markers: usize,
282282
pub branch_spans: Vec<BranchSpan>,
283-
pub mcdc_branch_spans: Vec<MCDCBranchSpan>,
284-
pub mcdc_decision_spans: Vec<MCDCDecisionSpan>,
283+
pub mcdc_info: Option<MCDCBranchInfo>,
285284
}
286285

287286
#[derive(Clone, Debug)]
@@ -321,6 +320,20 @@ pub struct MCDCBranchSpan {
321320
pub false_marker: BlockMarkerId,
322321
}
323322

323+
#[derive(Clone, Debug)]
324+
#[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
325+
pub struct MCDCMatchArmSpan {
326+
pub span: Span,
327+
pub condition_info: Option<ConditionInfo>,
328+
// Mark the blocks which test the pattern.
329+
pub test_markers: Vec<BlockMarkerId>,
330+
// The blocks switched into if this pattern was matched.
331+
pub true_markers: Vec<BlockMarkerId>,
332+
// The blocks of `true_markers` of the prior bindings.
333+
// Eg. in `A | B` pattern A is prior to pattern B.
334+
pub prior_binding_markers: Vec<BlockMarkerId>,
335+
}
336+
324337
#[derive(Copy, Clone, Debug)]
325338
#[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
326339
pub struct DecisionInfo {
@@ -335,3 +348,20 @@ pub struct MCDCDecisionSpan {
335348
pub conditions_num: usize,
336349
pub end_markers: Vec<BlockMarkerId>,
337350
}
351+
352+
/// Branch information recorded during THIR-to-MIR lowering, and stored in MIR.
353+
#[derive(Clone, Debug)]
354+
#[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
355+
pub struct MCDCBranchInfo {
356+
pub branch_spans: Vec<MCDCBranchSpan>,
357+
pub match_arm_spans: Vec<MCDCMatchArmSpan>,
358+
pub decision_spans: Vec<MCDCDecisionSpan>,
359+
}
360+
361+
impl MCDCBranchInfo {
362+
pub fn calc_bitmap_bytes(&self) -> u32 {
363+
self.decision_spans
364+
.iter()
365+
.fold(0, |acc, decision| acc + (1_u32 << decision.conditions_num).div_ceil(8))
366+
}
367+
}

compiler/rustc_middle/src/mir/pretty.rs

+39-17
Original file line numberDiff line numberDiff line change
@@ -475,35 +475,57 @@ fn write_coverage_branch_info(
475475
branch_info: &coverage::BranchInfo,
476476
w: &mut dyn io::Write,
477477
) -> io::Result<()> {
478-
let coverage::BranchInfo { branch_spans, mcdc_branch_spans, mcdc_decision_spans, .. } =
479-
branch_info;
478+
let coverage::BranchInfo { branch_spans, mcdc_info, .. } = branch_info;
480479

480+
let mut printed = false;
481481
for coverage::BranchSpan { span, true_marker, false_marker } in branch_spans {
482482
writeln!(
483483
w,
484484
"{INDENT}coverage branch {{ true: {true_marker:?}, false: {false_marker:?} }} => {span:?}",
485485
)?;
486486
}
487487

488-
for coverage::MCDCBranchSpan { span, condition_info, true_marker, false_marker } in
489-
mcdc_branch_spans
488+
printed |= !branch_spans.is_empty();
489+
490+
if let Some(coverage::MCDCBranchInfo { branch_spans, decision_spans, match_arm_spans }) =
491+
mcdc_info.as_ref()
490492
{
491-
writeln!(
492-
w,
493-
"{INDENT}coverage mcdc branch {{ condition_id: {:?}, true: {true_marker:?}, false: {false_marker:?} }} => {span:?}",
494-
condition_info.map(|info| info.condition_id)
495-
)?;
496-
}
493+
for coverage::MCDCBranchSpan { span, condition_info, true_marker, false_marker } in
494+
branch_spans
495+
{
496+
writeln!(
497+
w,
498+
"{INDENT}coverage mcdc branch {{ condition_id: {:?}, true: {true_marker:?}, false: {false_marker:?} }} => {span:?}",
499+
condition_info.map(|info| info.condition_id)
500+
)?;
501+
}
497502

498-
for coverage::MCDCDecisionSpan { span, conditions_num, end_markers } in mcdc_decision_spans {
499-
writeln!(
500-
w,
501-
"{INDENT}coverage mcdc decision {{ conditions_num: {conditions_num:?}, end: {end_markers:?} }} => {span:?}"
502-
)?;
503+
for coverage::MCDCMatchArmSpan {
504+
span,
505+
condition_info,
506+
test_markers,
507+
true_markers,
508+
prior_binding_markers,
509+
} in match_arm_spans
510+
{
511+
writeln!(
512+
w,
513+
"{INDENT}coverage mcdc match arm {{ condition_id: {:?}, test: {test_markers:?}, true: {true_markers:?}, prior: {prior_binding_markers:?} }} => {span:?}",
514+
condition_info.map(|info| info.condition_id)
515+
)?;
516+
}
517+
518+
for coverage::MCDCDecisionSpan { span, conditions_num, end_markers } in decision_spans {
519+
writeln!(
520+
w,
521+
"{INDENT}coverage mcdc decision {{ conditions_num: {conditions_num:?}, end: {end_markers:?} }} => {span:?}"
522+
)?;
523+
}
524+
printed |=
525+
!branch_spans.is_empty() || !decision_spans.is_empty() || !match_arm_spans.is_empty();
503526
}
504527

505-
if !branch_spans.is_empty() || !mcdc_branch_spans.is_empty() || !mcdc_decision_spans.is_empty()
506-
{
528+
if printed {
507529
writeln!(w)?;
508530
}
509531

compiler/rustc_mir_build/src/build/coverageinfo.rs

+97-37
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
use std::assert_matches::assert_matches;
22
use std::collections::hash_map::Entry;
3-
use std::collections::VecDeque;
3+
use std::collections::{BTreeMap, VecDeque};
44

55
use rustc_data_structures::fx::FxHashMap;
66
use rustc_middle::mir::coverage::{
7-
BlockMarkerId, BranchSpan, ConditionId, ConditionInfo, CoverageKind, MCDCBranchSpan,
8-
MCDCDecisionSpan,
7+
BlockMarkerId, BranchSpan, ConditionId, ConditionInfo, CoverageKind, MCDCBranchInfo,
8+
MCDCBranchSpan, MCDCDecisionSpan, MCDCMatchArmSpan,
99
};
1010
use rustc_middle::mir::{self, BasicBlock, SourceInfo, UnOp};
1111
use rustc_middle::thir::{ExprId, ExprKind, LogicalOp, Thir};
@@ -23,9 +23,7 @@ pub(crate) struct BranchInfoBuilder {
2323
num_block_markers: usize,
2424
branch_spans: Vec<BranchSpan>,
2525

26-
mcdc_branch_spans: Vec<MCDCBranchSpan>,
27-
mcdc_decision_spans: Vec<MCDCDecisionSpan>,
28-
mcdc_state: Option<MCDCState>,
26+
mcdc_builder: Option<MCDCBuilder>,
2927
}
3028

3129
#[derive(Clone, Copy)]
@@ -47,9 +45,7 @@ impl BranchInfoBuilder {
4745
nots: FxHashMap::default(),
4846
num_block_markers: 0,
4947
branch_spans: vec![],
50-
mcdc_branch_spans: vec![],
51-
mcdc_decision_spans: vec![],
52-
mcdc_state: MCDCState::new_if_enabled(tcx),
48+
mcdc_builder: MCDCBuilder::new_if_enabled(tcx),
5349
})
5450
} else {
5551
None
@@ -102,21 +98,21 @@ impl BranchInfoBuilder {
10298
true_marker: BlockMarkerId,
10399
false_marker: BlockMarkerId,
104100
) -> Option<ConditionInfo> {
105-
let mcdc_state = self.mcdc_state.as_mut()?;
101+
let mcdc_builder = self.mcdc_builder.as_mut()?;
106102
let (mut condition_info, decision_result) =
107-
mcdc_state.take_condition(true_marker, false_marker);
103+
mcdc_builder.take_condition(true_marker, false_marker);
108104
if let Some(decision) = decision_result {
109105
match decision.conditions_num {
110106
0 => {
111107
unreachable!("Decision with no condition is not expected");
112108
}
113109
1..=MAX_CONDITIONS_NUM_IN_DECISION => {
114-
self.mcdc_decision_spans.push(decision);
110+
mcdc_builder.decision_spans.push(decision);
115111
}
116112
_ => {
117113
// Do not generate mcdc mappings and statements for decisions with too many conditions.
118-
let rebase_idx = self.mcdc_branch_spans.len() - decision.conditions_num + 1;
119-
for branch in &mut self.mcdc_branch_spans[rebase_idx..] {
114+
let rebase_idx = mcdc_builder.branch_spans.len() - decision.conditions_num + 1;
115+
for branch in &mut mcdc_builder.branch_spans[rebase_idx..] {
120116
branch.condition_info = None;
121117
}
122118

@@ -171,14 +167,7 @@ impl BranchInfoBuilder {
171167
}
172168

173169
pub(crate) fn into_done(self) -> Option<Box<mir::coverage::BranchInfo>> {
174-
let Self {
175-
nots: _,
176-
num_block_markers,
177-
branch_spans,
178-
mcdc_branch_spans,
179-
mcdc_decision_spans,
180-
mcdc_state: _,
181-
} = self;
170+
let Self { nots: _, num_block_markers, branch_spans, mcdc_builder } = self;
182171

183172
if num_block_markers == 0 {
184173
assert!(branch_spans.is_empty());
@@ -188,8 +177,7 @@ impl BranchInfoBuilder {
188177
Some(Box::new(mir::coverage::BranchInfo {
189178
num_block_markers,
190179
branch_spans,
191-
mcdc_branch_spans,
192-
mcdc_decision_spans,
180+
mcdc_info: mcdc_builder.map(MCDCBuilder::into_done),
193181
}))
194182
}
195183
}
@@ -199,17 +187,25 @@ impl BranchInfoBuilder {
199187
/// This limit may be relaxed if the [upstream change](https://github.com/llvm/llvm-project/pull/82448) is merged.
200188
const MAX_CONDITIONS_NUM_IN_DECISION: usize = 6;
201189

202-
struct MCDCState {
190+
struct MCDCBuilder {
203191
/// To construct condition evaluation tree.
204192
decision_stack: VecDeque<ConditionInfo>,
205193
processing_decision: Option<MCDCDecisionSpan>,
194+
195+
match_arm_records: BTreeMap<Span, MCDCMatchArmSpan>,
196+
branch_spans: Vec<MCDCBranchSpan>,
197+
decision_spans: Vec<MCDCDecisionSpan>,
206198
}
207199

208-
impl MCDCState {
200+
impl MCDCBuilder {
209201
fn new_if_enabled(tcx: TyCtxt<'_>) -> Option<Self> {
210-
tcx.sess
211-
.instrument_coverage_mcdc()
212-
.then(|| Self { decision_stack: VecDeque::new(), processing_decision: None })
202+
tcx.sess.instrument_coverage_mcdc().then(|| Self {
203+
decision_stack: VecDeque::new(),
204+
processing_decision: None,
205+
match_arm_records: BTreeMap::new(),
206+
branch_spans: vec![],
207+
decision_spans: vec![],
208+
})
213209
}
214210

215211
// At first we assign ConditionIds for each sub expression.
@@ -333,6 +329,23 @@ impl MCDCState {
333329
(Some(condition_info), None)
334330
}
335331
}
332+
333+
fn into_done(self) -> MCDCBranchInfo {
334+
let Self { match_arm_records, branch_spans, decision_spans, .. } = self;
335+
336+
let mut match_arm_spans = match_arm_records.into_values().collect::<Vec<_>>();
337+
match_arm_spans.sort_by(|a, b| {
338+
if a.span.contains(b.span) { std::cmp::Ordering::Less } else { a.span.cmp(&b.span) }
339+
});
340+
for idx in 0..match_arm_spans.len().saturating_sub(1) {
341+
if match_arm_spans[idx].span.contains(match_arm_spans[idx + 1].span) {
342+
let refined_span = match_arm_spans[idx].span.until(match_arm_spans[idx + 1].span);
343+
match_arm_spans[idx].span = refined_span;
344+
}
345+
}
346+
347+
MCDCBranchInfo { branch_spans, match_arm_spans, decision_spans }
348+
}
336349
}
337350

338351
impl Builder<'_, '_> {
@@ -359,19 +372,21 @@ impl Builder<'_, '_> {
359372
let source_info = SourceInfo { span: self.thir[expr_id].span, scope: self.source_scope };
360373

361374
// Separate path for handling branches when MC/DC is enabled.
362-
if branch_info.mcdc_state.is_some() {
375+
if branch_info.mcdc_builder.is_some() {
363376
let mut inject_block_marker =
364377
|block| branch_info.inject_block_marker(&mut self.cfg, source_info, block);
365378
let true_marker = inject_block_marker(then_block);
366379
let false_marker = inject_block_marker(else_block);
367380
let condition_info =
368381
branch_info.fetch_mcdc_condition_info(self.tcx, true_marker, false_marker);
369-
branch_info.mcdc_branch_spans.push(MCDCBranchSpan {
370-
span: source_info.span,
371-
condition_info,
372-
true_marker,
373-
false_marker,
374-
});
382+
branch_info.mcdc_builder.as_mut().expect("checked at condition").branch_spans.push(
383+
MCDCBranchSpan {
384+
span: source_info.span,
385+
condition_info,
386+
true_marker,
387+
false_marker,
388+
},
389+
);
375390
return;
376391
}
377392

@@ -380,9 +395,54 @@ impl Builder<'_, '_> {
380395

381396
pub(crate) fn visit_coverage_branch_operation(&mut self, logical_op: LogicalOp, span: Span) {
382397
if let Some(branch_info) = self.coverage_branch_info.as_mut()
383-
&& let Some(mcdc_state) = branch_info.mcdc_state.as_mut()
398+
&& let Some(mcdc_state) = branch_info.mcdc_builder.as_mut()
384399
{
385400
mcdc_state.record_conditions(logical_op, span);
386401
}
387402
}
403+
404+
pub(crate) fn visit_coverage_mcdc_match_arms(
405+
&mut self,
406+
(test_span, test_block): (Span, BasicBlock),
407+
targets: impl Iterator<Item = (Span, BasicBlock)>,
408+
) {
409+
let Some(branch_info) = self.coverage_branch_info.as_mut() else { return };
410+
// Only enabled in mcdc
411+
if branch_info.mcdc_builder.is_none() {
412+
return;
413+
}
414+
415+
// May inject redundant block markers, it's ok because we will transform them into basic coverage block in coverage pass.
416+
let mut gen_marker_id = |span, block| {
417+
branch_info.inject_block_marker(
418+
&mut self.cfg,
419+
SourceInfo { span, scope: self.source_scope },
420+
block,
421+
)
422+
};
423+
424+
let test_block_id = gen_marker_id(test_span, test_block);
425+
426+
let mut targets =
427+
targets.map(|(span, block)| (span, gen_marker_id(span, block))).collect::<Vec<_>>();
428+
429+
let branch_records =
430+
&mut branch_info.mcdc_builder.as_mut().expect("checked at entry").match_arm_records;
431+
432+
while let Some((span, matched_block)) = targets.pop() {
433+
// By here we do not know the span of the target yet, so just record the
434+
// basic blocks and insert BlockMarkerId later.
435+
let record = branch_records.entry(span).or_insert_with(|| MCDCMatchArmSpan {
436+
span,
437+
condition_info: None,
438+
test_markers: vec![],
439+
true_markers: vec![],
440+
prior_binding_markers: vec![],
441+
});
442+
record.test_markers.push(test_block_id);
443+
record.true_markers.push(matched_block);
444+
445+
record.prior_binding_markers.extend(targets.iter().map(|(_, blk)| *blk));
446+
}
447+
}
388448
}

0 commit comments

Comments
 (0)