Skip to content

Commit a3d737a

Browse files
author
zhuyunxing
committed
coverage. Split mcdc code into a sub module of coverageingo
1 parent e59f2c5 commit a3d737a

File tree

2 files changed

+288
-247
lines changed

2 files changed

+288
-247
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,24 @@
1+
mod mcdc;
2+
use mcdc::MCDCInfoBuilder;
13
use std::assert_matches::assert_matches;
24
use std::collections::hash_map::Entry;
3-
use std::collections::VecDeque;
45

56
use rustc_data_structures::fx::FxHashMap;
6-
use rustc_middle::mir::coverage::{
7-
BlockMarkerId, BranchSpan, ConditionId, ConditionInfo, CoverageKind, MCDCBranchSpan,
8-
MCDCDecisionSpan,
9-
};
7+
use rustc_middle::mir::coverage::{BlockMarkerId, BranchSpan, CoverageKind};
108
use rustc_middle::mir::{self, BasicBlock, SourceInfo, UnOp};
11-
use rustc_middle::thir::{ExprId, ExprKind, LogicalOp, Thir};
9+
use rustc_middle::thir::{ExprId, ExprKind, Thir};
1210
use rustc_middle::ty::TyCtxt;
1311
use rustc_span::def_id::LocalDefId;
14-
use rustc_span::Span;
1512

1613
use crate::build::{Builder, CFG};
17-
use crate::errors::MCDCExceedsConditionNumLimit;
1814

1915
pub(crate) struct BranchInfoBuilder {
2016
/// Maps condition expressions to their enclosing `!`, for better instrumentation.
2117
nots: FxHashMap<ExprId, NotInfo>,
22-
23-
num_block_markers: usize,
18+
block_marker_generator: BlockMarkerGenerator,
2419
branch_spans: Vec<BranchSpan>,
2520

26-
mcdc_branch_spans: Vec<MCDCBranchSpan>,
27-
mcdc_decision_spans: Vec<MCDCDecisionSpan>,
28-
mcdc_state: Option<MCDCState>,
21+
mcdc_builder: Option<MCDCInfoBuilder>,
2922
}
3023

3124
#[derive(Clone, Copy)]
@@ -38,18 +31,46 @@ struct NotInfo {
3831
is_flipped: bool,
3932
}
4033

34+
#[derive(Default)]
35+
struct BlockMarkerGenerator {
36+
num_block_markers: usize,
37+
}
38+
39+
impl BlockMarkerGenerator {
40+
fn next_block_marker_id(&mut self) -> BlockMarkerId {
41+
let id = BlockMarkerId::from_usize(self.num_block_markers);
42+
self.num_block_markers += 1;
43+
id
44+
}
45+
46+
fn inject_block_marker(
47+
&mut self,
48+
cfg: &mut CFG<'_>,
49+
source_info: SourceInfo,
50+
block: BasicBlock,
51+
) -> BlockMarkerId {
52+
let id = self.next_block_marker_id();
53+
54+
let marker_statement = mir::Statement {
55+
source_info,
56+
kind: mir::StatementKind::Coverage(CoverageKind::BlockMarker { id }),
57+
};
58+
cfg.push(block, marker_statement);
59+
60+
id
61+
}
62+
}
63+
4164
impl BranchInfoBuilder {
4265
/// Creates a new branch info builder, but only if branch coverage instrumentation
4366
/// is enabled and `def_id` represents a function that is eligible for coverage.
4467
pub(crate) fn new_if_enabled(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<Self> {
4568
if tcx.sess.instrument_coverage_branch() && tcx.is_eligible_for_coverage(def_id) {
4669
Some(Self {
4770
nots: FxHashMap::default(),
48-
num_block_markers: 0,
71+
block_marker_generator: BlockMarkerGenerator::default(),
4972
branch_spans: vec![],
50-
mcdc_branch_spans: vec![],
51-
mcdc_decision_spans: vec![],
52-
mcdc_state: MCDCState::new_if_enabled(tcx),
73+
mcdc_builder: tcx.sess.instrument_coverage_mcdc().then(MCDCInfoBuilder::new),
5374
})
5475
} else {
5576
None
@@ -96,245 +117,46 @@ impl BranchInfoBuilder {
96117
}
97118
}
98119

99-
fn fetch_mcdc_condition_info(
100-
&mut self,
101-
tcx: TyCtxt<'_>,
102-
true_marker: BlockMarkerId,
103-
false_marker: BlockMarkerId,
104-
) -> Option<ConditionInfo> {
105-
let mcdc_state = self.mcdc_state.as_mut()?;
106-
let (mut condition_info, decision_result) =
107-
mcdc_state.take_condition(true_marker, false_marker);
108-
if let Some(decision) = decision_result {
109-
match decision.conditions_num {
110-
0 => {
111-
unreachable!("Decision with no condition is not expected");
112-
}
113-
1..=MAX_CONDITIONS_NUM_IN_DECISION => {
114-
self.mcdc_decision_spans.push(decision);
115-
}
116-
_ => {
117-
// 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..] {
120-
branch.condition_info = None;
121-
}
122-
123-
// ConditionInfo of this branch shall also be reset.
124-
condition_info = None;
125-
126-
tcx.dcx().emit_warn(MCDCExceedsConditionNumLimit {
127-
span: decision.span,
128-
conditions_num: decision.conditions_num,
129-
max_conditions_num: MAX_CONDITIONS_NUM_IN_DECISION,
130-
});
131-
}
132-
}
133-
}
134-
condition_info
135-
}
136-
137120
fn add_two_way_branch<'tcx>(
138121
&mut self,
139122
cfg: &mut CFG<'tcx>,
140123
source_info: SourceInfo,
141124
true_block: BasicBlock,
142125
false_block: BasicBlock,
143126
) {
144-
let true_marker = self.inject_block_marker(cfg, source_info, true_block);
145-
let false_marker = self.inject_block_marker(cfg, source_info, false_block);
127+
let true_marker =
128+
self.block_marker_generator.inject_block_marker(cfg, source_info, true_block);
129+
let false_marker =
130+
self.block_marker_generator.inject_block_marker(cfg, source_info, false_block);
146131

147132
self.branch_spans.push(BranchSpan { span: source_info.span, true_marker, false_marker });
148133
}
149134

150-
fn next_block_marker_id(&mut self) -> BlockMarkerId {
151-
let id = BlockMarkerId::from_usize(self.num_block_markers);
152-
self.num_block_markers += 1;
153-
id
154-
}
155-
156-
fn inject_block_marker(
157-
&mut self,
158-
cfg: &mut CFG<'_>,
159-
source_info: SourceInfo,
160-
block: BasicBlock,
161-
) -> BlockMarkerId {
162-
let id = self.next_block_marker_id();
163-
164-
let marker_statement = mir::Statement {
165-
source_info,
166-
kind: mir::StatementKind::Coverage(CoverageKind::BlockMarker { id }),
167-
};
168-
cfg.push(block, marker_statement);
169-
170-
id
171-
}
172-
173135
pub(crate) fn into_done(self) -> Option<Box<mir::coverage::BranchInfo>> {
174136
let Self {
175137
nots: _,
176-
num_block_markers,
138+
block_marker_generator: BlockMarkerGenerator { num_block_markers },
177139
branch_spans,
178-
mcdc_branch_spans,
179-
mcdc_decision_spans,
180-
mcdc_state: _,
140+
mcdc_builder,
181141
} = self;
182142

183143
if num_block_markers == 0 {
184144
assert!(branch_spans.is_empty());
185145
return None;
186146
}
187147

148+
let (mcdc_decision_spans, mcdc_branch_spans) =
149+
mcdc_builder.map(MCDCInfoBuilder::into_done).unwrap_or_default();
150+
188151
Some(Box::new(mir::coverage::BranchInfo {
189152
num_block_markers,
190153
branch_spans,
191-
mcdc_branch_spans,
192154
mcdc_decision_spans,
155+
mcdc_branch_spans,
193156
}))
194157
}
195158
}
196159

197-
/// The MCDC bitmap scales exponentially (2^n) based on the number of conditions seen,
198-
/// So llvm sets a maximum value prevents the bitmap footprint from growing too large without the user's knowledge.
199-
/// This limit may be relaxed if the [upstream change](https://github.com/llvm/llvm-project/pull/82448) is merged.
200-
const MAX_CONDITIONS_NUM_IN_DECISION: usize = 6;
201-
202-
struct MCDCState {
203-
/// To construct condition evaluation tree.
204-
decision_stack: VecDeque<ConditionInfo>,
205-
processing_decision: Option<MCDCDecisionSpan>,
206-
}
207-
208-
impl MCDCState {
209-
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 })
213-
}
214-
215-
// At first we assign ConditionIds for each sub expression.
216-
// If the sub expression is composite, re-assign its ConditionId to its LHS and generate a new ConditionId for its RHS.
217-
//
218-
// Example: "x = (A && B) || (C && D) || (D && F)"
219-
//
220-
// Visit Depth1:
221-
// (A && B) || (C && D) || (D && F)
222-
// ^-------LHS--------^ ^-RHS--^
223-
// ID=1 ID=2
224-
//
225-
// Visit LHS-Depth2:
226-
// (A && B) || (C && D)
227-
// ^-LHS--^ ^-RHS--^
228-
// ID=1 ID=3
229-
//
230-
// Visit LHS-Depth3:
231-
// (A && B)
232-
// LHS RHS
233-
// ID=1 ID=4
234-
//
235-
// Visit RHS-Depth3:
236-
// (C && D)
237-
// LHS RHS
238-
// ID=3 ID=5
239-
//
240-
// Visit RHS-Depth2: (D && F)
241-
// LHS RHS
242-
// ID=2 ID=6
243-
//
244-
// Visit Depth1:
245-
// (A && B) || (C && D) || (D && F)
246-
// ID=1 ID=4 ID=3 ID=5 ID=2 ID=6
247-
//
248-
// A node ID of '0' always means MC/DC isn't being tracked.
249-
//
250-
// If a "next" node ID is '0', it means it's the end of the test vector.
251-
//
252-
// 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.
253-
// - 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".
254-
// - 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".
255-
fn record_conditions(&mut self, op: LogicalOp, span: Span) {
256-
let decision = match self.processing_decision.as_mut() {
257-
Some(decision) => {
258-
decision.span = decision.span.to(span);
259-
decision
260-
}
261-
None => self.processing_decision.insert(MCDCDecisionSpan {
262-
span,
263-
conditions_num: 0,
264-
end_markers: vec![],
265-
}),
266-
};
267-
268-
let parent_condition = self.decision_stack.pop_back().unwrap_or_default();
269-
let lhs_id = if parent_condition.condition_id == ConditionId::NONE {
270-
decision.conditions_num += 1;
271-
ConditionId::from(decision.conditions_num)
272-
} else {
273-
parent_condition.condition_id
274-
};
275-
276-
decision.conditions_num += 1;
277-
let rhs_condition_id = ConditionId::from(decision.conditions_num);
278-
279-
let (lhs, rhs) = match op {
280-
LogicalOp::And => {
281-
let lhs = ConditionInfo {
282-
condition_id: lhs_id,
283-
true_next_id: rhs_condition_id,
284-
false_next_id: parent_condition.false_next_id,
285-
};
286-
let rhs = ConditionInfo {
287-
condition_id: rhs_condition_id,
288-
true_next_id: parent_condition.true_next_id,
289-
false_next_id: parent_condition.false_next_id,
290-
};
291-
(lhs, rhs)
292-
}
293-
LogicalOp::Or => {
294-
let lhs = ConditionInfo {
295-
condition_id: lhs_id,
296-
true_next_id: parent_condition.true_next_id,
297-
false_next_id: rhs_condition_id,
298-
};
299-
let rhs = ConditionInfo {
300-
condition_id: rhs_condition_id,
301-
true_next_id: parent_condition.true_next_id,
302-
false_next_id: parent_condition.false_next_id,
303-
};
304-
(lhs, rhs)
305-
}
306-
};
307-
// We visit expressions tree in pre-order, so place the left-hand side on the top.
308-
self.decision_stack.push_back(rhs);
309-
self.decision_stack.push_back(lhs);
310-
}
311-
312-
fn take_condition(
313-
&mut self,
314-
true_marker: BlockMarkerId,
315-
false_marker: BlockMarkerId,
316-
) -> (Option<ConditionInfo>, Option<MCDCDecisionSpan>) {
317-
let Some(condition_info) = self.decision_stack.pop_back() else {
318-
return (None, None);
319-
};
320-
let Some(decision) = self.processing_decision.as_mut() else {
321-
bug!("Processing decision should have been created before any conditions are taken");
322-
};
323-
if condition_info.true_next_id == ConditionId::NONE {
324-
decision.end_markers.push(true_marker);
325-
}
326-
if condition_info.false_next_id == ConditionId::NONE {
327-
decision.end_markers.push(false_marker);
328-
}
329-
330-
if self.decision_stack.is_empty() {
331-
(Some(condition_info), self.processing_decision.take())
332-
} else {
333-
(Some(condition_info), None)
334-
}
335-
}
336-
}
337-
338160
impl Builder<'_, '_> {
339161
/// If branch coverage is enabled, inject marker statements into `then_block`
340162
/// and `else_block`, and record their IDs in the table of branch spans.
@@ -359,30 +181,18 @@ impl Builder<'_, '_> {
359181
let source_info = SourceInfo { span: self.thir[expr_id].span, scope: self.source_scope };
360182

361183
// Separate path for handling branches when MC/DC is enabled.
362-
if branch_info.mcdc_state.is_some() {
363-
let mut inject_block_marker =
364-
|block| branch_info.inject_block_marker(&mut self.cfg, source_info, block);
365-
let true_marker = inject_block_marker(then_block);
366-
let false_marker = inject_block_marker(else_block);
367-
let condition_info =
368-
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-
});
184+
if let Some(mcdc_builder) = branch_info.mcdc_builder.as_mut() {
185+
mcdc_builder.visit_evaluated_condition(
186+
self.tcx,
187+
&mut self.cfg,
188+
source_info,
189+
then_block,
190+
else_block,
191+
&mut branch_info.block_marker_generator,
192+
);
375193
return;
376194
}
377195

378196
branch_info.add_two_way_branch(&mut self.cfg, source_info, then_block, else_block);
379197
}
380-
381-
pub(crate) fn visit_coverage_branch_operation(&mut self, logical_op: LogicalOp, span: Span) {
382-
if let Some(branch_info) = self.coverage_branch_info.as_mut()
383-
&& let Some(mcdc_state) = branch_info.mcdc_state.as_mut()
384-
{
385-
mcdc_state.record_conditions(logical_op, span);
386-
}
387-
}
388198
}

0 commit comments

Comments
 (0)