Skip to content

Commit 20cdc51

Browse files
committed
mcdc-coverage: Add doc comments, move too many conditions error
1 parent 6e7e2c5 commit 20cdc51

File tree

3 files changed

+109
-35
lines changed

3 files changed

+109
-35
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,125 @@
1-
use rustc_macros::Diagnostic;
21
use rustc_middle::{
3-
mir::{
4-
self,
5-
coverage::{CoverageKind, DecisionSpan},
6-
Statement,
7-
},
2+
mir::{self, coverage::CoverageKind, Statement},
83
ty::TyCtxt,
94
};
10-
use rustc_span::Span;
11-
12-
#[derive(Diagnostic)]
13-
#[diag(mir_transform_mcdc_too_many_conditions)]
14-
pub(crate) struct MCDCTooManyConditions {
15-
#[primary_span]
16-
pub span: Span,
17-
pub num_conditions: u32,
18-
pub max_conditions: u32,
19-
}
5+
6+
use crate::coverage::graph::CoverageGraph;
7+
use crate::errors::MCDCTooManyConditions;
208

219
const MAX_COND_DECISION: u32 = 6;
2210

11+
2312
/// If MCDC coverage is enabled, add MCDC instrumentation to the function.
13+
///
14+
/// Assume a decision to be the following:
15+
///
16+
/// if (A && B) || C { then(); } else { otherwise(); }
17+
///
18+
/// The corresponding BDD (Binary Decision Diagram) will look like so:
19+
///
20+
/// ```
21+
/// │
22+
/// ┌▼┐
23+
/// │A│
24+
/// ┌──┴─┴──┐
25+
/// │t f│
26+
/// │ │
27+
/// ┌▼┐ ┌▼┐
28+
/// │B├─────►C├───┐
29+
/// └┬┘ f └┬┘ f│
30+
/// │t │t │
31+
/// ┌──▼───┐ │ ┌─▼─────────┐
32+
/// │then()◄───┘ │otherwise()│
33+
/// └──────┘ └───────────┘
34+
/// ```
35+
///
36+
/// The coverage graph is similar, up to unwinding mechanics. The goal is to
37+
/// instrument each edge of the BDD to update two bitmaps.
38+
///
39+
/// The first bitmap (CBM) is updated upon the evaluation of each contidion.
40+
/// It tracks the state of a condition at a given instant.
41+
/// is given an index, such that when the decision is taken, CBM represents the
42+
/// state of all conditions that were evaluated (1 for true, 0 for
43+
/// false/skipped).
44+
///
45+
/// The second bitmap (TVBM) is the test vector bitmap. It tracks all the test
46+
/// vectors that were executed during the program's life. It is updated before
47+
/// branching to `then()` or `otherwise()` by using CBM as an integer n and
48+
/// setting the nth integer of TVBM to 1.
49+
/// Basically, we do `TVBM |= 1 << CBM`.
50+
///
51+
/// Note: This technique is very sub-optimal, as it implies that TVBM to have a
52+
/// size of 2^n bits, (n being the number of conditions in the decision) to
53+
/// account for every combination, even though only a subset of theses
54+
/// combinations are actually achievable because of logical operators
55+
/// short-circuit semantics.
56+
/// An improvement to this instrumentation is being implemented in Clang and
57+
/// shall be ported to Rustc in the future:
58+
/// https://discourse.llvm.org/t/rfc-coverage-new-algorithm-and-file-format-for-mc-dc/76798
59+
///
60+
/// In the meantime, to follow the original implementation of Clang, we use this
61+
/// suboptimal technique, while limiting the size of the decisions we can
62+
/// instrument to 6 conditions.
63+
///
2464
/// Will do the following things :
2565
/// 1. Add an instruction in the first basic block for the codegen to call
2666
/// the `instrprof.mcdc.parameters` instrinsic.
27-
pub fn instrument_function_mcdc<'tcx>(tcx: TyCtxt<'tcx>, mir_body: &mut mir::Body<'tcx>) {
67+
/// 2. Before each decision, add an instruction to reset CBM.
68+
/// 3. Add an isntruction to update CBM upon condition evaluation.
69+
/// 4. Add an instruction to update TVBM with the CBM value before jumping
70+
/// to the `then` or `else` block.
71+
/// 5. Build mappings for the reporting tools to get the results and transpose
72+
/// it to the source code.
73+
pub fn instrument_function_mcdc<'tcx>(
74+
tcx: TyCtxt<'tcx>,
75+
mir_body: &mut mir::Body<'tcx>,
76+
coverage_graph: &CoverageGraph,
77+
) {
78+
let _ = coverage_graph;
2879
if !tcx.sess.instrument_coverage_mcdc() {
2980
return;
3081
}
82+
debug!("Called instrument_function_mcdc()");
3183

3284
let Some(branch_info) = mir_body.coverage_branch_info.as_deref() else {
3385
return;
3486
};
3587

36-
// Compute the total sum of needed bytes for the current body.
37-
let needed_bytes: u32 = branch_info
38-
.decision_spans
39-
.iter()
40-
.filter_map(|DecisionSpan { span, num_conditions }| {
41-
if *num_conditions > MAX_COND_DECISION {
42-
tcx.dcx().emit_warn(MCDCTooManyConditions {
43-
span: *span,
44-
num_conditions: *num_conditions,
45-
max_conditions: MAX_COND_DECISION,
46-
});
47-
None
48-
} else {
49-
Some(1 << num_conditions)
50-
}
51-
})
52-
.sum();
88+
let mut needed_bytes = 0;
89+
let mut bitmap_indexes = vec![];
90+
91+
for dec_span in branch_info.decision_spans.iter() {
92+
// Skip uninstrumentable conditions.
93+
if dec_span.num_conditions > MAX_COND_DECISION {
94+
tcx.dcx().emit_warn(MCDCTooManyConditions {
95+
span: dec_span.span,
96+
num_conditions: dec_span.num_conditions,
97+
max_conditions: MAX_COND_DECISION,
98+
});
99+
continue;
100+
}
101+
bitmap_indexes.push(needed_bytes);
102+
needed_bytes += 1 << dec_span.num_conditions;
103+
}
53104

105+
if needed_bytes == 0 {
106+
// No decision to instrument
107+
return;
108+
}
109+
110+
// In the first BB, specify that we need to allocate bitmaps.
54111
let entry_bb = &mut mir_body.basic_blocks_mut()[mir::START_BLOCK];
55112
let mcdc_parameters_statement = Statement {
56113
source_info: entry_bb.terminator().source_info,
57114
kind: mir::StatementKind::Coverage(CoverageKind::MCDCBitmapRequire { needed_bytes }),
58115
};
59116
entry_bb.statements.insert(0, mcdc_parameters_statement);
117+
118+
// For each decision:
119+
// - Find its 'root' basic block
120+
// - Insert a 'reset CDM' instruction
121+
// - for each branch, find the root condition, give it an index and
122+
// call a condbitmapUpdate on it
123+
// - Get the Output markers, and insert goto blocks before to put a
124+
// tvbitmapupdate on it.
60125
}

compiler/rustc_mir_transform/src/coverage/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ fn instrument_function_for_coverage<'tcx>(tcx: TyCtxt<'tcx>, mir_body: &mut mir:
103103

104104
////////////////////////////////////////////////////
105105
// Add the MCDC instrumentation. This is a no-op if MCDC coverage is disabled.
106-
mcdc::instrument_function_mcdc(tcx, mir_body);
106+
mcdc::instrument_function_mcdc(tcx, mir_body, &basic_coverage_blocks);
107107

108108
mir_body.function_coverage_info = Some(Box::new(FunctionCoverageInfo {
109109
function_source_hash: hir_info.function_source_hash,

compiler/rustc_mir_transform/src/errors.rs

+9
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ pub(crate) enum ConstMutate {
3232
},
3333
}
3434

35+
#[derive(Diagnostic)]
36+
#[diag(mir_transform_mcdc_too_many_conditions)]
37+
pub(crate) struct MCDCTooManyConditions {
38+
#[primary_span]
39+
pub span: Span,
40+
pub num_conditions: u32,
41+
pub max_conditions: u32,
42+
}
43+
3544
#[derive(Diagnostic)]
3645
#[diag(mir_transform_unaligned_packed_ref, code = E0793)]
3746
#[note]

0 commit comments

Comments
 (0)