Skip to content

Commit 16b2506

Browse files
author
zhuyunxing
committed
coverage. Split mcdc builder to a sub module of coverageinfo
1 parent 27e634e commit 16b2506

File tree

2 files changed

+284
-274
lines changed

2 files changed

+284
-274
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
1+
mod mcdc;
12
use std::assert_matches::assert_matches;
23
use std::collections::hash_map::Entry;
3-
use std::collections::VecDeque;
44

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

12+
use crate::build::coverageinfo::mcdc::MCDCInfoBuilder;
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.
@@ -159,240 +155,6 @@ impl BranchInfoBuilder {
159155
}
160156
}
161157

162-
/// The MCDC bitmap scales exponentially (2^n) based on the number of conditions seen,
163-
/// So llvm sets a maximum value prevents the bitmap footprint from growing too large without the user's knowledge.
164-
/// This limit may be relaxed if the [upstream change](https://github.com/llvm/llvm-project/pull/82448) is merged.
165-
const MAX_CONDITIONS_NUM_IN_DECISION: usize = 6;
166-
167-
#[derive(Default)]
168-
struct MCDCDecisionCtx {
169-
/// To construct condition evaluation tree.
170-
decision_stack: VecDeque<ConditionInfo>,
171-
processing_decision: Option<MCDCDecisionSpan>,
172-
}
173-
174-
struct MCDCState {
175-
decision_ctx_stack: Vec<MCDCDecisionCtx>,
176-
}
177-
178-
impl MCDCState {
179-
fn new() -> Self {
180-
Self { decision_ctx_stack: vec![MCDCDecisionCtx::default()] }
181-
}
182-
183-
/// Decision depth is given as a u16 to reduce the size of the `CoverageKind`,
184-
/// as it is very unlikely that the depth ever reaches 2^16.
185-
#[inline]
186-
fn decision_depth(&self) -> u16 {
187-
match u16::try_from(self.decision_ctx_stack.len())
188-
.expect(
189-
"decision depth did not fit in u16, this is likely to be an instrumentation error",
190-
)
191-
.checked_sub(1)
192-
{
193-
Some(d) => d,
194-
None => bug!("Unexpected empty decision stack"),
195-
}
196-
}
197-
198-
// At first we assign ConditionIds for each sub expression.
199-
// If the sub expression is composite, re-assign its ConditionId to its LHS and generate a new ConditionId for its RHS.
200-
//
201-
// Example: "x = (A && B) || (C && D) || (D && F)"
202-
//
203-
// Visit Depth1:
204-
// (A && B) || (C && D) || (D && F)
205-
// ^-------LHS--------^ ^-RHS--^
206-
// ID=1 ID=2
207-
//
208-
// Visit LHS-Depth2:
209-
// (A && B) || (C && D)
210-
// ^-LHS--^ ^-RHS--^
211-
// ID=1 ID=3
212-
//
213-
// Visit LHS-Depth3:
214-
// (A && B)
215-
// LHS RHS
216-
// ID=1 ID=4
217-
//
218-
// Visit RHS-Depth3:
219-
// (C && D)
220-
// LHS RHS
221-
// ID=3 ID=5
222-
//
223-
// Visit RHS-Depth2: (D && F)
224-
// LHS RHS
225-
// ID=2 ID=6
226-
//
227-
// Visit Depth1:
228-
// (A && B) || (C && D) || (D && F)
229-
// ID=1 ID=4 ID=3 ID=5 ID=2 ID=6
230-
//
231-
// A node ID of '0' always means MC/DC isn't being tracked.
232-
//
233-
// If a "next" node ID is '0', it means it's the end of the test vector.
234-
//
235-
// 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.
236-
// - 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".
237-
// - 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".
238-
fn record_conditions(&mut self, op: LogicalOp, span: Span) {
239-
let decision_depth = self.decision_depth();
240-
let decision_ctx =
241-
self.decision_ctx_stack.last_mut().expect("Unexpected empty decision_ctx_stack");
242-
let decision = match decision_ctx.processing_decision.as_mut() {
243-
Some(decision) => {
244-
decision.span = decision.span.to(span);
245-
decision
246-
}
247-
None => decision_ctx.processing_decision.insert(MCDCDecisionSpan {
248-
span,
249-
conditions_num: 0,
250-
end_markers: vec![],
251-
decision_depth,
252-
}),
253-
};
254-
255-
let parent_condition = decision_ctx.decision_stack.pop_back().unwrap_or_default();
256-
let lhs_id = if parent_condition.condition_id == ConditionId::NONE {
257-
decision.conditions_num += 1;
258-
ConditionId::from(decision.conditions_num)
259-
} else {
260-
parent_condition.condition_id
261-
};
262-
263-
decision.conditions_num += 1;
264-
let rhs_condition_id = ConditionId::from(decision.conditions_num);
265-
266-
let (lhs, rhs) = match op {
267-
LogicalOp::And => {
268-
let lhs = ConditionInfo {
269-
condition_id: lhs_id,
270-
true_next_id: rhs_condition_id,
271-
false_next_id: parent_condition.false_next_id,
272-
};
273-
let rhs = ConditionInfo {
274-
condition_id: rhs_condition_id,
275-
true_next_id: parent_condition.true_next_id,
276-
false_next_id: parent_condition.false_next_id,
277-
};
278-
(lhs, rhs)
279-
}
280-
LogicalOp::Or => {
281-
let lhs = ConditionInfo {
282-
condition_id: lhs_id,
283-
true_next_id: parent_condition.true_next_id,
284-
false_next_id: rhs_condition_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-
};
294-
// We visit expressions tree in pre-order, so place the left-hand side on the top.
295-
decision_ctx.decision_stack.push_back(rhs);
296-
decision_ctx.decision_stack.push_back(lhs);
297-
}
298-
299-
fn take_condition(
300-
&mut self,
301-
true_marker: BlockMarkerId,
302-
false_marker: BlockMarkerId,
303-
) -> (Option<ConditionInfo>, Option<MCDCDecisionSpan>) {
304-
let Some(decision_ctx) = self.decision_ctx_stack.last_mut() else {
305-
bug!("Unexpected empty decision_ctx_stack")
306-
};
307-
let Some(condition_info) = decision_ctx.decision_stack.pop_back() else {
308-
return (None, None);
309-
};
310-
let Some(decision) = decision_ctx.processing_decision.as_mut() else {
311-
bug!("Processing decision should have been created before any conditions are taken");
312-
};
313-
if condition_info.true_next_id == ConditionId::NONE {
314-
decision.end_markers.push(true_marker);
315-
}
316-
if condition_info.false_next_id == ConditionId::NONE {
317-
decision.end_markers.push(false_marker);
318-
}
319-
320-
if decision_ctx.decision_stack.is_empty() {
321-
(Some(condition_info), decision_ctx.processing_decision.take())
322-
} else {
323-
(Some(condition_info), None)
324-
}
325-
}
326-
}
327-
328-
struct MCDCInfoBuilder {
329-
branch_spans: Vec<MCDCBranchSpan>,
330-
decision_spans: Vec<MCDCDecisionSpan>,
331-
state: MCDCState,
332-
}
333-
334-
impl MCDCInfoBuilder {
335-
fn new() -> Self {
336-
Self { branch_spans: vec![], decision_spans: vec![], state: MCDCState::new() }
337-
}
338-
339-
fn visit_evaluated_condition(
340-
&mut self,
341-
tcx: TyCtxt<'_>,
342-
source_info: SourceInfo,
343-
true_block: BasicBlock,
344-
false_block: BasicBlock,
345-
mut inject_block_marker: impl FnMut(SourceInfo, BasicBlock) -> BlockMarkerId,
346-
) {
347-
let true_marker = inject_block_marker(source_info, true_block);
348-
let false_marker = inject_block_marker(source_info, false_block);
349-
350-
let decision_depth = self.state.decision_depth();
351-
let (mut condition_info, decision_result) =
352-
self.state.take_condition(true_marker, false_marker);
353-
// take_condition() returns Some for decision_result when the decision stack
354-
// is empty, i.e. when all the conditions of the decision were instrumented,
355-
// and the decision is "complete".
356-
if let Some(decision) = decision_result {
357-
match decision.conditions_num {
358-
0 => {
359-
unreachable!("Decision with no condition is not expected");
360-
}
361-
1..=MAX_CONDITIONS_NUM_IN_DECISION => {
362-
self.decision_spans.push(decision);
363-
}
364-
_ => {
365-
// Do not generate mcdc mappings and statements for decisions with too many conditions.
366-
let rebase_idx = self.branch_spans.len() - decision.conditions_num + 1;
367-
for branch in &mut self.branch_spans[rebase_idx..] {
368-
branch.condition_info = None;
369-
}
370-
371-
// ConditionInfo of this branch shall also be reset.
372-
condition_info = None;
373-
374-
tcx.dcx().emit_warn(MCDCExceedsConditionNumLimit {
375-
span: decision.span,
376-
conditions_num: decision.conditions_num,
377-
max_conditions_num: MAX_CONDITIONS_NUM_IN_DECISION,
378-
});
379-
}
380-
}
381-
}
382-
self.branch_spans.push(MCDCBranchSpan {
383-
span: source_info.span,
384-
condition_info,
385-
true_marker,
386-
false_marker,
387-
decision_depth,
388-
});
389-
}
390-
391-
fn into_done(self) -> (Vec<MCDCDecisionSpan>, Vec<MCDCBranchSpan>) {
392-
(self.decision_spans, self.branch_spans)
393-
}
394-
}
395-
396158
impl Builder<'_, '_> {
397159
/// If branch coverage is enabled, inject marker statements into `then_block`
398160
/// and `else_block`, and record their IDs in the table of branch spans.
@@ -433,36 +195,4 @@ impl Builder<'_, '_> {
433195

434196
branch_info.add_two_way_branch(&mut self.cfg, source_info, then_block, else_block);
435197
}
436-
437-
pub(crate) fn visit_coverage_branch_operation(&mut self, logical_op: LogicalOp, span: Span) {
438-
if let Some(mcdc_info) = self
439-
.coverage_branch_info
440-
.as_mut()
441-
.and_then(|branch_info| branch_info.mcdc_info.as_mut())
442-
{
443-
mcdc_info.state.record_conditions(logical_op, span);
444-
}
445-
}
446-
447-
pub(crate) fn mcdc_increment_depth_if_enabled(&mut self) {
448-
if let Some(mcdc_info) = self
449-
.coverage_branch_info
450-
.as_mut()
451-
.and_then(|branch_info| branch_info.mcdc_info.as_mut())
452-
{
453-
mcdc_info.state.decision_ctx_stack.push(MCDCDecisionCtx::default());
454-
};
455-
}
456-
457-
pub(crate) fn mcdc_decrement_depth_if_enabled(&mut self) {
458-
if let Some(mcdc_info) = self
459-
.coverage_branch_info
460-
.as_mut()
461-
.and_then(|branch_info| branch_info.mcdc_info.as_mut())
462-
{
463-
if mcdc_info.state.decision_ctx_stack.pop().is_none() {
464-
bug!("Unexpected empty decision stack");
465-
}
466-
};
467-
}
468198
}

0 commit comments

Comments
 (0)