Skip to content

Commit 7a58674

Browse files
committed
Auto merge of rust-lang#124255 - RenjiSann:renji/mcdc-nested-expressions, r=Zalathar
MCDC coverage: support nested decision coverage rust-lang#123409 provided the initial MCDC coverage implementation. As referenced in rust-lang#124144, it does not currently support "nested" decisions, like the following example : ```rust fn nested_if_in_condition(a: bool, b: bool, c: bool) { if a && if b || c { true } else { false } { say("yes"); } else { say("no"); } } ``` Note that there is an if-expression (`if b || c ...`) embedded inside a boolean expression in the decision of an outer if-expression. This PR proposes a workaround for this cases, by introducing a Decision context stack, and by handing several `temporary condition bitmaps` instead of just one. When instrumenting boolean expressions, if the current node is a leaf condition (i.e. not a `||`/`&&` logical operator nor a `!` not operator), we insert a new decision context, such that if there are more boolean expressions inside the condition, they are handled as separate expressions. On the codegen LLVM side, we allocate as many `temp_cond_bitmap`s as necessary to handle the maximum encountered decision depth.
2 parents 9084601 + eb422d5 commit 7a58674

File tree

12 files changed

+699
-70
lines changed

12 files changed

+699
-70
lines changed

compiler/rustc_codegen_llvm/src/builder.rs

+20-11
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use rustc_target::abi::{self, call::FnAbi, Align, Size, WrappingRange};
2727
use rustc_target::spec::{HasTargetSpec, SanitizerSet, Target};
2828
use smallvec::SmallVec;
2929
use std::borrow::Cow;
30+
use std::ffi::CString;
3031
use std::iter;
3132
use std::ops::Deref;
3233
use std::ptr;
@@ -1709,7 +1710,8 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> {
17091710
fn_name: &'ll Value,
17101711
hash: &'ll Value,
17111712
bitmap_bytes: &'ll Value,
1712-
) -> &'ll Value {
1713+
max_decision_depth: u32,
1714+
) -> Vec<&'ll Value> {
17131715
debug!("mcdc_parameters() with args ({:?}, {:?}, {:?})", fn_name, hash, bitmap_bytes);
17141716

17151717
assert!(llvm_util::get_version() >= (18, 0, 0), "MCDC intrinsics require LLVM 18 or later");
@@ -1722,6 +1724,8 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> {
17221724
let args = &[fn_name, hash, bitmap_bytes];
17231725
let args = self.check_call("call", llty, llfn, args);
17241726

1727+
let mut cond_bitmaps = vec![];
1728+
17251729
unsafe {
17261730
let _ = llvm::LLVMRustBuildCall(
17271731
self.llbuilder,
@@ -1733,17 +1737,22 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> {
17331737
0 as c_uint,
17341738
);
17351739
// Create condition bitmap named `mcdc.addr`.
1736-
let mut bx = Builder::with_cx(self.cx);
1737-
bx.position_at_start(llvm::LLVMGetFirstBasicBlock(self.llfn()));
1738-
let cond_bitmap = {
1739-
let alloca =
1740-
llvm::LLVMBuildAlloca(bx.llbuilder, bx.cx.type_i32(), c"mcdc.addr".as_ptr());
1741-
llvm::LLVMSetAlignment(alloca, 4);
1742-
alloca
1743-
};
1744-
bx.store(self.const_i32(0), cond_bitmap, self.tcx().data_layout.i32_align.abi);
1745-
cond_bitmap
1740+
for i in 0..=max_decision_depth {
1741+
let mut bx = Builder::with_cx(self.cx);
1742+
bx.position_at_start(llvm::LLVMGetFirstBasicBlock(self.llfn()));
1743+
1744+
let name = CString::new(format!("mcdc.addr.{i}")).unwrap();
1745+
let cond_bitmap = {
1746+
let alloca =
1747+
llvm::LLVMBuildAlloca(bx.llbuilder, bx.cx.type_i32(), name.as_ptr());
1748+
llvm::LLVMSetAlignment(alloca, 4);
1749+
alloca
1750+
};
1751+
bx.store(self.const_i32(0), cond_bitmap, self.tcx().data_layout.i32_align.abi);
1752+
cond_bitmaps.push(cond_bitmap);
1753+
}
17461754
}
1755+
cond_bitmaps
17471756
}
17481757

17491758
pub(crate) fn mcdc_tvbitmap_update(

compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs

+21-9
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub struct CrateCoverageContext<'ll, 'tcx> {
3030
pub(crate) function_coverage_map:
3131
RefCell<FxIndexMap<Instance<'tcx>, FunctionCoverageCollector<'tcx>>>,
3232
pub(crate) pgo_func_name_var_map: RefCell<FxHashMap<Instance<'tcx>, &'ll llvm::Value>>,
33-
pub(crate) mcdc_condition_bitmap_map: RefCell<FxHashMap<Instance<'tcx>, &'ll llvm::Value>>,
33+
pub(crate) mcdc_condition_bitmap_map: RefCell<FxHashMap<Instance<'tcx>, Vec<&'ll llvm::Value>>>,
3434
}
3535

3636
impl<'ll, 'tcx> CrateCoverageContext<'ll, 'tcx> {
@@ -49,9 +49,20 @@ impl<'ll, 'tcx> CrateCoverageContext<'ll, 'tcx> {
4949
}
5050

5151
/// LLVM use a temp value to record evaluated mcdc test vector of each decision, which is called condition bitmap.
52-
/// This value is named `mcdc.addr` (same as clang) and is a 32-bit integer.
53-
fn try_get_mcdc_condition_bitmap(&self, instance: &Instance<'tcx>) -> Option<&'ll llvm::Value> {
54-
self.mcdc_condition_bitmap_map.borrow().get(instance).copied()
52+
/// In order to handle nested decisions, several condition bitmaps can be
53+
/// allocated for a function body.
54+
/// These values are named `mcdc.addr.{i}` and are a 32-bit integers.
55+
/// They respectively hold the condition bitmaps for decisions with a depth of `i`.
56+
fn try_get_mcdc_condition_bitmap(
57+
&self,
58+
instance: &Instance<'tcx>,
59+
decision_depth: u16,
60+
) -> Option<&'ll llvm::Value> {
61+
self.mcdc_condition_bitmap_map
62+
.borrow()
63+
.get(instance)
64+
.and_then(|bitmap_map| bitmap_map.get(decision_depth as usize))
65+
.copied() // Dereference Option<&&Value> to Option<&Value>
5566
}
5667
}
5768

@@ -143,26 +154,26 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> {
143154
CoverageKind::ExpressionUsed { id } => {
144155
func_coverage.mark_expression_id_seen(id);
145156
}
146-
CoverageKind::CondBitmapUpdate { id, value, .. } => {
157+
CoverageKind::CondBitmapUpdate { id, value, decision_depth } => {
147158
drop(coverage_map);
148159
assert_ne!(
149160
id.as_u32(),
150161
0,
151162
"ConditionId of evaluated conditions should never be zero"
152163
);
153164
let cond_bitmap = coverage_context
154-
.try_get_mcdc_condition_bitmap(&instance)
165+
.try_get_mcdc_condition_bitmap(&instance, decision_depth)
155166
.expect("mcdc cond bitmap should have been allocated for updating");
156167
let cond_loc = bx.const_i32(id.as_u32() as i32 - 1);
157168
let bool_value = bx.const_bool(value);
158169
let fn_name = bx.get_pgo_func_name_var(instance);
159170
let hash = bx.const_u64(function_coverage_info.function_source_hash);
160171
bx.mcdc_condbitmap_update(fn_name, hash, cond_loc, cond_bitmap, bool_value);
161172
}
162-
CoverageKind::TestVectorBitmapUpdate { bitmap_idx } => {
173+
CoverageKind::TestVectorBitmapUpdate { bitmap_idx, decision_depth } => {
163174
drop(coverage_map);
164175
let cond_bitmap = coverage_context
165-
.try_get_mcdc_condition_bitmap(&instance)
176+
.try_get_mcdc_condition_bitmap(&instance, decision_depth)
166177
.expect("mcdc cond bitmap should have been allocated for merging into the global bitmap");
167178
let bitmap_bytes = bx.tcx().coverage_ids_info(instance.def).mcdc_bitmap_bytes;
168179
assert!(bitmap_idx < bitmap_bytes, "bitmap index of the decision out of range");
@@ -195,7 +206,8 @@ fn ensure_mcdc_parameters<'ll, 'tcx>(
195206
let fn_name = bx.get_pgo_func_name_var(instance);
196207
let hash = bx.const_u64(function_coverage_info.function_source_hash);
197208
let bitmap_bytes = bx.const_u32(function_coverage_info.mcdc_bitmap_bytes);
198-
let cond_bitmap = bx.mcdc_parameters(fn_name, hash, bitmap_bytes);
209+
let max_decision_depth = function_coverage_info.mcdc_max_decision_depth;
210+
let cond_bitmap = bx.mcdc_parameters(fn_name, hash, bitmap_bytes, max_decision_depth as u32);
199211
bx.coverage_context()
200212
.expect("already checked above")
201213
.mcdc_condition_bitmap_map

compiler/rustc_middle/src/mir/coverage.rs

+17-6
Original file line numberDiff line numberDiff line change
@@ -132,15 +132,15 @@ pub enum CoverageKind {
132132
///
133133
/// If this statement does not survive MIR optimizations, the condition would never be
134134
/// taken as evaluated.
135-
CondBitmapUpdate { id: ConditionId, value: bool },
135+
CondBitmapUpdate { id: ConditionId, value: bool, decision_depth: u16 },
136136

137137
/// Marks the point in MIR control flow represented by a evaluated decision.
138138
///
139139
/// This is eventually lowered to `llvm.instrprof.mcdc.tvbitmap.update` in LLVM IR.
140140
///
141141
/// If this statement does not survive MIR optimizations, the decision would never be
142142
/// taken as evaluated.
143-
TestVectorBitmapUpdate { bitmap_idx: u32 },
143+
TestVectorBitmapUpdate { bitmap_idx: u32, decision_depth: u16 },
144144
}
145145

146146
impl Debug for CoverageKind {
@@ -151,11 +151,17 @@ impl Debug for CoverageKind {
151151
BlockMarker { id } => write!(fmt, "BlockMarker({:?})", id.index()),
152152
CounterIncrement { id } => write!(fmt, "CounterIncrement({:?})", id.index()),
153153
ExpressionUsed { id } => write!(fmt, "ExpressionUsed({:?})", id.index()),
154-
CondBitmapUpdate { id, value } => {
155-
write!(fmt, "CondBitmapUpdate({:?}, {:?})", id.index(), value)
154+
CondBitmapUpdate { id, value, decision_depth } => {
155+
write!(
156+
fmt,
157+
"CondBitmapUpdate({:?}, {:?}, depth={:?})",
158+
id.index(),
159+
value,
160+
decision_depth
161+
)
156162
}
157-
TestVectorBitmapUpdate { bitmap_idx } => {
158-
write!(fmt, "TestVectorUpdate({:?})", bitmap_idx)
163+
TestVectorBitmapUpdate { bitmap_idx, decision_depth } => {
164+
write!(fmt, "TestVectorUpdate({:?}, depth={:?})", bitmap_idx, decision_depth)
159165
}
160166
}
161167
}
@@ -269,6 +275,9 @@ pub struct FunctionCoverageInfo {
269275
pub mcdc_bitmap_bytes: u32,
270276
pub expressions: IndexVec<ExpressionId, Expression>,
271277
pub mappings: Vec<Mapping>,
278+
/// The depth of the deepest decision is used to know how many
279+
/// temp condbitmaps should be allocated for the function.
280+
pub mcdc_max_decision_depth: u16,
272281
}
273282

274283
/// Branch information recorded during THIR-to-MIR lowering, and stored in MIR.
@@ -319,6 +328,7 @@ pub struct MCDCBranchSpan {
319328
pub condition_info: Option<ConditionInfo>,
320329
pub true_marker: BlockMarkerId,
321330
pub false_marker: BlockMarkerId,
331+
pub decision_depth: u16,
322332
}
323333

324334
#[derive(Copy, Clone, Debug)]
@@ -334,4 +344,5 @@ pub struct MCDCDecisionSpan {
334344
pub span: Span,
335345
pub conditions_num: usize,
336346
pub end_markers: Vec<BlockMarkerId>,
347+
pub decision_depth: u16,
337348
}

compiler/rustc_middle/src/mir/pretty.rs

+12-5
Original file line numberDiff line numberDiff line change
@@ -496,20 +496,27 @@ fn write_coverage_branch_info(
496496
)?;
497497
}
498498

499-
for coverage::MCDCBranchSpan { span, condition_info, true_marker, false_marker } in
500-
mcdc_branch_spans
499+
for coverage::MCDCBranchSpan {
500+
span,
501+
condition_info,
502+
true_marker,
503+
false_marker,
504+
decision_depth,
505+
} in mcdc_branch_spans
501506
{
502507
writeln!(
503508
w,
504-
"{INDENT}coverage mcdc branch {{ condition_id: {:?}, true: {true_marker:?}, false: {false_marker:?} }} => {span:?}",
509+
"{INDENT}coverage mcdc branch {{ condition_id: {:?}, true: {true_marker:?}, false: {false_marker:?}, depth: {decision_depth:?} }} => {span:?}",
505510
condition_info.map(|info| info.condition_id)
506511
)?;
507512
}
508513

509-
for coverage::MCDCDecisionSpan { span, conditions_num, end_markers } in mcdc_decision_spans {
514+
for coverage::MCDCDecisionSpan { span, conditions_num, end_markers, decision_depth } in
515+
mcdc_decision_spans
516+
{
510517
writeln!(
511518
w,
512-
"{INDENT}coverage mcdc decision {{ conditions_num: {conditions_num:?}, end: {end_markers:?} }} => {span:?}"
519+
"{INDENT}coverage mcdc decision {{ conditions_num: {conditions_num:?}, end: {end_markers:?}, depth: {decision_depth:?} }} => {span:?}"
513520
)?;
514521
}
515522

compiler/rustc_mir_build/src/build/coverageinfo.rs

+60-15
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,14 @@ impl BranchInfoBuilder {
101101
tcx: TyCtxt<'_>,
102102
true_marker: BlockMarkerId,
103103
false_marker: BlockMarkerId,
104-
) -> Option<ConditionInfo> {
104+
) -> Option<(u16, ConditionInfo)> {
105105
let mcdc_state = self.mcdc_state.as_mut()?;
106+
let decision_depth = mcdc_state.decision_depth();
106107
let (mut condition_info, decision_result) =
107108
mcdc_state.take_condition(true_marker, false_marker);
109+
// take_condition() returns Some for decision_result when the decision stack
110+
// is empty, i.e. when all the conditions of the decision were instrumented,
111+
// and the decision is "complete".
108112
if let Some(decision) = decision_result {
109113
match decision.conditions_num {
110114
0 => {
@@ -131,7 +135,7 @@ impl BranchInfoBuilder {
131135
}
132136
}
133137
}
134-
condition_info
138+
condition_info.map(|cond_info| (decision_depth, cond_info))
135139
}
136140

137141
fn add_two_way_branch<'tcx>(
@@ -199,17 +203,32 @@ impl BranchInfoBuilder {
199203
/// This limit may be relaxed if the [upstream change](https://github.com/llvm/llvm-project/pull/82448) is merged.
200204
const MAX_CONDITIONS_NUM_IN_DECISION: usize = 6;
201205

202-
struct MCDCState {
206+
#[derive(Default)]
207+
struct MCDCDecisionCtx {
203208
/// To construct condition evaluation tree.
204209
decision_stack: VecDeque<ConditionInfo>,
205210
processing_decision: Option<MCDCDecisionSpan>,
206211
}
207212

213+
struct MCDCState {
214+
decision_ctx_stack: Vec<MCDCDecisionCtx>,
215+
}
216+
208217
impl MCDCState {
209218
fn new_if_enabled(tcx: TyCtxt<'_>) -> Option<Self> {
210219
tcx.sess
211220
.instrument_coverage_mcdc()
212-
.then(|| Self { decision_stack: VecDeque::new(), processing_decision: None })
221+
.then(|| Self { decision_ctx_stack: vec![MCDCDecisionCtx::default()] })
222+
}
223+
224+
/// Decision depth is given as a u16 to reduce the size of the `CoverageKind`,
225+
/// as it is very unlikely that the depth ever reaches 2^16.
226+
#[inline]
227+
fn decision_depth(&self) -> u16 {
228+
u16::try_from(
229+
self.decision_ctx_stack.len().checked_sub(1).expect("Unexpected empty decision stack"),
230+
)
231+
.expect("decision depth did not fit in u16, this is likely to be an instrumentation error")
213232
}
214233

215234
// At first we assign ConditionIds for each sub expression.
@@ -253,19 +272,23 @@ impl MCDCState {
253272
// - 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".
254273
// - 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".
255274
fn record_conditions(&mut self, op: LogicalOp, span: Span) {
256-
let decision = match self.processing_decision.as_mut() {
275+
let decision_depth = self.decision_depth();
276+
let decision_ctx =
277+
self.decision_ctx_stack.last_mut().expect("Unexpected empty decision_ctx_stack");
278+
let decision = match decision_ctx.processing_decision.as_mut() {
257279
Some(decision) => {
258280
decision.span = decision.span.to(span);
259281
decision
260282
}
261-
None => self.processing_decision.insert(MCDCDecisionSpan {
283+
None => decision_ctx.processing_decision.insert(MCDCDecisionSpan {
262284
span,
263285
conditions_num: 0,
264286
end_markers: vec![],
287+
decision_depth,
265288
}),
266289
};
267290

268-
let parent_condition = self.decision_stack.pop_back().unwrap_or_default();
291+
let parent_condition = decision_ctx.decision_stack.pop_back().unwrap_or_default();
269292
let lhs_id = if parent_condition.condition_id == ConditionId::NONE {
270293
decision.conditions_num += 1;
271294
ConditionId::from(decision.conditions_num)
@@ -305,19 +328,21 @@ impl MCDCState {
305328
}
306329
};
307330
// 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);
331+
decision_ctx.decision_stack.push_back(rhs);
332+
decision_ctx.decision_stack.push_back(lhs);
310333
}
311334

312335
fn take_condition(
313336
&mut self,
314337
true_marker: BlockMarkerId,
315338
false_marker: BlockMarkerId,
316339
) -> (Option<ConditionInfo>, Option<MCDCDecisionSpan>) {
317-
let Some(condition_info) = self.decision_stack.pop_back() else {
340+
let decision_ctx =
341+
self.decision_ctx_stack.last_mut().expect("Unexpected empty decision_ctx_stack");
342+
let Some(condition_info) = decision_ctx.decision_stack.pop_back() else {
318343
return (None, None);
319344
};
320-
let Some(decision) = self.processing_decision.as_mut() else {
345+
let Some(decision) = decision_ctx.processing_decision.as_mut() else {
321346
bug!("Processing decision should have been created before any conditions are taken");
322347
};
323348
if condition_info.true_next_id == ConditionId::NONE {
@@ -327,8 +352,8 @@ impl MCDCState {
327352
decision.end_markers.push(false_marker);
328353
}
329354

330-
if self.decision_stack.is_empty() {
331-
(Some(condition_info), self.processing_decision.take())
355+
if decision_ctx.decision_stack.is_empty() {
356+
(Some(condition_info), decision_ctx.processing_decision.take())
332357
} else {
333358
(Some(condition_info), None)
334359
}
@@ -364,13 +389,17 @@ impl Builder<'_, '_> {
364389
|block| branch_info.inject_block_marker(&mut self.cfg, source_info, block);
365390
let true_marker = inject_block_marker(then_block);
366391
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);
392+
let (decision_depth, condition_info) = branch_info
393+
.fetch_mcdc_condition_info(self.tcx, true_marker, false_marker)
394+
.map_or((0, None), |(decision_depth, condition_info)| {
395+
(decision_depth, Some(condition_info))
396+
});
369397
branch_info.mcdc_branch_spans.push(MCDCBranchSpan {
370398
span: source_info.span,
371399
condition_info,
372400
true_marker,
373401
false_marker,
402+
decision_depth,
374403
});
375404
return;
376405
}
@@ -385,4 +414,20 @@ impl Builder<'_, '_> {
385414
mcdc_state.record_conditions(logical_op, span);
386415
}
387416
}
417+
418+
pub(crate) fn mcdc_increment_depth_if_enabled(&mut self) {
419+
if let Some(branch_info) = self.coverage_branch_info.as_mut()
420+
&& let Some(mcdc_state) = branch_info.mcdc_state.as_mut()
421+
{
422+
mcdc_state.decision_ctx_stack.push(MCDCDecisionCtx::default());
423+
};
424+
}
425+
426+
pub(crate) fn mcdc_decrement_depth_if_enabled(&mut self) {
427+
if let Some(branch_info) = self.coverage_branch_info.as_mut()
428+
&& let Some(mcdc_state) = branch_info.mcdc_state.as_mut()
429+
{
430+
mcdc_state.decision_ctx_stack.pop().expect("Unexpected empty decision stack");
431+
};
432+
}
388433
}

0 commit comments

Comments
 (0)