From 1e6e3fb8a292cbb9e44f831f170c8cedbd799972 Mon Sep 17 00:00:00 2001 From: zhuyunxing Date: Tue, 9 Jul 2024 15:35:41 +0800 Subject: [PATCH] coverage. Group MCDC decisions and conditions until instrument mappings and support multiple branch markers --- compiler/rustc_middle/src/mir/coverage.rs | 21 +- compiler/rustc_middle/src/mir/pretty.rs | 29 +-- .../rustc_mir_build/src/build/coverageinfo.rs | 11 +- .../src/build/coverageinfo/mcdc.rs | 27 +-- .../src/coverage/counters.rs | 13 +- .../src/coverage/mappings.rs | 153 ++++++++------- .../rustc_mir_transform/src/coverage/mod.rs | 182 ++++++++++++------ 7 files changed, 263 insertions(+), 173 deletions(-) diff --git a/compiler/rustc_middle/src/mir/coverage.rs b/compiler/rustc_middle/src/mir/coverage.rs index 3a3cd1f1e017e..95508d8a2e574 100644 --- a/compiler/rustc_middle/src/mir/coverage.rs +++ b/compiler/rustc_middle/src/mir/coverage.rs @@ -297,8 +297,10 @@ pub struct CoverageInfoHi { /// data structures without having to scan the entire body first. pub num_block_markers: usize, pub branch_spans: Vec, - pub mcdc_branch_spans: Vec, - pub mcdc_decision_spans: Vec, + /// Branch spans generated by mcdc. Because of some limits mcdc builder give up generating + /// decisions including them so that they are handled as normal branch spans. + pub mcdc_degraded_spans: Vec, + pub mcdc_spans: Vec<(MCDCDecisionSpan, Vec)>, } #[derive(Clone, Debug)] @@ -331,12 +333,12 @@ impl Default for ConditionInfo { #[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)] pub struct MCDCBranchSpan { pub span: Span, - /// If `None`, this actually represents a normal branch span inserted for - /// code that was too complex for MC/DC. - pub condition_info: Option, - pub true_marker: BlockMarkerId, - pub false_marker: BlockMarkerId, - pub decision_depth: u16, + pub condition_info: ConditionInfo, + // For boolean decisions and most pattern matching decisions there is only + // one true marker and one false marker in each branch. But for matching decisions + // with `|` there can be several. + pub true_markers: Vec, + pub false_markers: Vec, } #[derive(Copy, Clone, Debug)] @@ -350,13 +352,12 @@ pub struct DecisionInfo { #[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)] pub struct MCDCDecisionSpan { pub span: Span, - pub num_conditions: usize, pub end_markers: Vec, pub decision_depth: u16, } impl MCDCDecisionSpan { pub fn new(span: Span) -> Self { - Self { span, num_conditions: 0, end_markers: Vec::new(), decision_depth: 0 } + Self { span, end_markers: Vec::new(), decision_depth: 0 } } } diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index 223249952dc74..86effaf25d59b 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -490,8 +490,8 @@ fn write_coverage_info_hi( let coverage::CoverageInfoHi { num_block_markers: _, branch_spans, - mcdc_branch_spans, - mcdc_decision_spans, + mcdc_degraded_spans, + mcdc_spans, } = coverage_info_hi; // Only add an extra trailing newline if we printed at least one thing. @@ -505,29 +505,30 @@ fn write_coverage_info_hi( did_print = true; } - for coverage::MCDCBranchSpan { - span, - condition_info, - true_marker, - false_marker, - decision_depth, - } in mcdc_branch_spans - { + for coverage::MCDCBranchSpan { span, true_markers, false_markers, .. } in mcdc_degraded_spans { writeln!( w, - "{INDENT}coverage mcdc branch {{ condition_id: {:?}, true: {true_marker:?}, false: {false_marker:?}, depth: {decision_depth:?} }} => {span:?}", - condition_info.map(|info| info.condition_id) + "{INDENT}coverage branch {{ true: {true_markers:?}, false: {false_markers:?} }} => {span:?}", )?; did_print = true; } - for coverage::MCDCDecisionSpan { span, num_conditions, end_markers, decision_depth } in - mcdc_decision_spans + for (coverage::MCDCDecisionSpan { span, end_markers, decision_depth }, conditions) in mcdc_spans { + let num_conditions = conditions.len(); writeln!( w, "{INDENT}coverage mcdc decision {{ num_conditions: {num_conditions:?}, end: {end_markers:?}, depth: {decision_depth:?} }} => {span:?}" )?; + for coverage::MCDCBranchSpan { span, condition_info, true_markers, false_markers } in + conditions + { + writeln!( + w, + "{INDENT}coverage mcdc branch {{ condition_id: {:?}, true: {true_markers:?}, false: {false_markers:?} }} => {span:?}", + condition_info.condition_id + )?; + } did_print = true; } diff --git a/compiler/rustc_mir_build/src/build/coverageinfo.rs b/compiler/rustc_mir_build/src/build/coverageinfo.rs index 6f70a952bf291..89a4a714bc35f 100644 --- a/compiler/rustc_mir_build/src/build/coverageinfo.rs +++ b/compiler/rustc_mir_build/src/build/coverageinfo.rs @@ -175,21 +175,16 @@ impl CoverageInfoBuilder { let branch_spans = branch_info.map(|branch_info| branch_info.branch_spans).unwrap_or_default(); - let (mut mcdc_branch_spans, mcdc_spans) = + let (mcdc_degraded_spans, mcdc_spans) = mcdc_info.map(MCDCInfoBuilder::into_done).unwrap_or_default(); - let mut mcdc_decision_spans = Vec::with_capacity(mcdc_spans.len()); - for (decision, conditions) in mcdc_spans { - mcdc_branch_spans.extend(conditions); - mcdc_decision_spans.push(decision); - } // For simplicity, always return an info struct (without Option), even // if there's nothing interesting in it. Box::new(CoverageInfoHi { num_block_markers, branch_spans, - mcdc_branch_spans, - mcdc_decision_spans, + mcdc_degraded_spans, + mcdc_spans, }) } } diff --git a/compiler/rustc_mir_build/src/build/coverageinfo/mcdc.rs b/compiler/rustc_mir_build/src/build/coverageinfo/mcdc.rs index 29d0c9c3a6ab1..d3a621c049911 100644 --- a/compiler/rustc_mir_build/src/build/coverageinfo/mcdc.rs +++ b/compiler/rustc_mir_build/src/build/coverageinfo/mcdc.rs @@ -31,6 +31,7 @@ struct BooleanDecisionCtx { /// To construct condition evaluation tree. decision_stack: VecDeque, conditions: Vec, + condition_id_counter: usize, } impl BooleanDecisionCtx { @@ -40,9 +41,15 @@ impl BooleanDecisionCtx { decision_info: MCDCDecisionSpan::new(Span::default()), decision_stack: VecDeque::new(), conditions: vec![], + condition_id_counter: 0, } } + fn next_condition_id(&mut self) -> ConditionId { + self.condition_id_counter += 1; + ConditionId::from_usize(self.condition_id_counter) + } + // At first we assign ConditionIds for each sub expression. // If the sub expression is composite, re-assign its ConditionId to its LHS and generate a new ConditionId for its RHS. // @@ -86,14 +93,12 @@ impl BooleanDecisionCtx { fn record_conditions(&mut self, op: LogicalOp) { let parent_condition = self.decision_stack.pop_back().unwrap_or_default(); let lhs_id = if parent_condition.condition_id == ConditionId::NONE { - self.decision_info.num_conditions += 1; - ConditionId::from(self.decision_info.num_conditions) + ConditionId::from(self.next_condition_id()) } else { parent_condition.condition_id }; - self.decision_info.num_conditions += 1; - let rhs_condition_id = ConditionId::from(self.decision_info.num_conditions); + let rhs_condition_id = self.next_condition_id(); let (lhs, rhs) = match op { LogicalOp::And => { @@ -144,13 +149,10 @@ impl BooleanDecisionCtx { self.conditions.push(MCDCBranchSpan { span, - condition_info: Some(condition_info), - true_marker, - false_marker, - decision_depth: 0, + condition_info, + true_markers: vec![true_marker], + false_markers: vec![false_marker], }); - // In case this decision had only one condition - self.decision_info.num_conditions = self.decision_info.num_conditions.max(1); } fn is_finished(&self) -> bool { @@ -244,7 +246,6 @@ struct MCDCTargetInfo { impl MCDCTargetInfo { fn set_depth(&mut self, depth: u16) { self.decision.decision_depth = depth; - self.conditions.iter_mut().for_each(|branch| branch.decision_depth = depth); } } @@ -338,7 +339,9 @@ impl MCDCInfoBuilder { } fn append_normal_branches(&mut self, mut branches: Vec) { - branches.iter_mut().for_each(|branch| branch.condition_info = None); + branches + .iter_mut() + .for_each(|branch| branch.condition_info.condition_id = ConditionId::NONE); self.normal_branch_spans.extend(branches); } diff --git a/compiler/rustc_mir_transform/src/coverage/counters.rs b/compiler/rustc_mir_transform/src/coverage/counters.rs index a8b0f4a8d6df4..f068494e23405 100644 --- a/compiler/rustc_mir_transform/src/coverage/counters.rs +++ b/compiler/rustc_mir_transform/src/coverage/counters.rs @@ -101,7 +101,12 @@ impl CoverageCounters { BcbCounter::Counter { id } } - fn make_expression(&mut self, lhs: BcbCounter, op: Op, rhs: BcbCounter) -> BcbCounter { + pub(super) fn make_expression( + &mut self, + lhs: BcbCounter, + op: Op, + rhs: BcbCounter, + ) -> BcbCounter { let new_expr = BcbExpression { lhs, op, rhs }; *self .expressions_memo @@ -159,7 +164,11 @@ impl CoverageCounters { /// Variant of `make_expression` that makes `lhs` optional and assumes [`Op::Add`]. /// /// This is useful when using [`Iterator::fold`] to build an arbitrary-length sum. - fn make_sum_expression(&mut self, lhs: Option, rhs: BcbCounter) -> BcbCounter { + pub(super) fn make_sum_expression( + &mut self, + lhs: Option, + rhs: BcbCounter, + ) -> BcbCounter { let Some(lhs) = lhs else { return rhs }; self.make_expression(lhs, Op::Add, rhs) } diff --git a/compiler/rustc_mir_transform/src/coverage/mappings.rs b/compiler/rustc_mir_transform/src/coverage/mappings.rs index 25297245172ac..0eed1aa7fd9a3 100644 --- a/compiler/rustc_mir_transform/src/coverage/mappings.rs +++ b/compiler/rustc_mir_transform/src/coverage/mappings.rs @@ -36,12 +36,9 @@ pub(super) struct BranchPair { #[derive(Debug)] pub(super) struct MCDCBranch { pub(super) span: Span, - pub(super) true_bcb: BasicCoverageBlock, - pub(super) false_bcb: BasicCoverageBlock, - /// If `None`, this actually represents a normal branch mapping inserted - /// for code that was too complex for MC/DC. - pub(super) condition_info: Option, - pub(super) decision_depth: u16, + pub(super) true_bcbs: Vec, + pub(super) false_bcbs: Vec, + pub(super) condition_info: ConditionInfo, } /// Associates an MC/DC decision with its join BCBs. @@ -50,7 +47,6 @@ pub(super) struct MCDCDecision { pub(super) span: Span, pub(super) end_bcbs: BTreeSet, pub(super) bitmap_idx: u32, - pub(super) num_conditions: u16, pub(super) decision_depth: u16, } @@ -59,8 +55,8 @@ pub(super) struct ExtractedMappings { pub(super) code_mappings: Vec, pub(super) branch_pairs: Vec, pub(super) mcdc_bitmap_bytes: u32, - pub(super) mcdc_branches: Vec, - pub(super) mcdc_decisions: Vec, + pub(super) mcdc_degraded_branches: Vec, + pub(super) mcdc_mappings: Vec<(MCDCDecision, Vec)>, } /// Extracts coverage-relevant spans from MIR, and associates them with @@ -74,8 +70,8 @@ pub(super) fn extract_all_mapping_info_from_mir<'tcx>( let mut code_mappings = vec![]; let mut branch_pairs = vec![]; let mut mcdc_bitmap_bytes = 0; - let mut mcdc_branches = vec![]; - let mut mcdc_decisions = vec![]; + let mut mcdc_degraded_branches = vec![]; + let mut mcdc_mappings = vec![]; if hir_info.is_async_fn || tcx.sess.coverage_no_mir_spans() { // An async function desugars into a function that returns a future, @@ -101,16 +97,16 @@ pub(super) fn extract_all_mapping_info_from_mir<'tcx>( hir_info.body_span, basic_coverage_blocks, &mut mcdc_bitmap_bytes, - &mut mcdc_branches, - &mut mcdc_decisions, + &mut mcdc_degraded_branches, + &mut mcdc_mappings, ); ExtractedMappings { code_mappings, branch_pairs, mcdc_bitmap_bytes, - mcdc_branches, - mcdc_decisions, + mcdc_degraded_branches, + mcdc_mappings, } } @@ -124,8 +120,8 @@ impl ExtractedMappings { code_mappings, branch_pairs, mcdc_bitmap_bytes: _, - mcdc_branches, - mcdc_decisions, + mcdc_degraded_branches, + mcdc_mappings, } = self; // Identify which BCBs have one or more mappings. @@ -141,16 +137,18 @@ impl ExtractedMappings { insert(true_bcb); insert(false_bcb); } - for &MCDCBranch { true_bcb, false_bcb, .. } in mcdc_branches { - insert(true_bcb); - insert(false_bcb); + for MCDCBranch { true_bcbs, false_bcbs, .. } in mcdc_degraded_branches + .iter() + .chain(mcdc_mappings.iter().map(|(_, branches)| branches.into_iter()).flatten()) + { + true_bcbs.into_iter().chain(false_bcbs.into_iter()).copied().for_each(&mut insert); } // MC/DC decisions refer to BCBs, but don't require those BCBs to have counters. if bcbs_with_counter_mappings.is_empty() { debug_assert!( - mcdc_decisions.is_empty(), - "A function with no counter mappings shouldn't have any decisions: {mcdc_decisions:?}", + mcdc_mappings.is_empty(), + "A function with no counter mappings shouldn't have any decisions: {mcdc_mappings:?}", ); } @@ -221,8 +219,8 @@ pub(super) fn extract_mcdc_mappings( body_span: Span, basic_coverage_blocks: &CoverageGraph, mcdc_bitmap_bytes: &mut u32, - mcdc_branches: &mut impl Extend, - mcdc_decisions: &mut impl Extend, + mcdc_degraded_branches: &mut impl Extend, + mcdc_mappings: &mut impl Extend<(MCDCDecision, Vec)>, ) { let Some(coverage_info_hi) = mir_body.coverage_info_hi.as_deref() else { return }; @@ -231,57 +229,70 @@ pub(super) fn extract_mcdc_mappings( let bcb_from_marker = |marker: BlockMarkerId| basic_coverage_blocks.bcb_from_bb(block_markers[marker]?); - let check_branch_bcb = - |raw_span: Span, true_marker: BlockMarkerId, false_marker: BlockMarkerId| { - // For now, ignore any branch span that was introduced by - // expansion. This makes things like assert macros less noisy. - if !raw_span.ctxt().outer_expn_data().is_root() { - return None; - } - let span = unexpand_into_body_span(raw_span, body_span)?; - - let true_bcb = bcb_from_marker(true_marker)?; - let false_bcb = bcb_from_marker(false_marker)?; - Some((span, true_bcb, false_bcb)) - }; + let check_branch_bcb = |raw_span: Span, + true_markers: &[BlockMarkerId], + false_markers: &[BlockMarkerId]| { + // For now, ignore any branch span that was introduced by + // expansion. This makes things like assert macros less noisy. + if !raw_span.ctxt().outer_expn_data().is_root() { + return None; + } + let span = unexpand_into_body_span(raw_span, body_span)?; + + let true_bcbs = + true_markers.into_iter().copied().map(&bcb_from_marker).collect::>>()?; + let false_bcbs = + false_markers.into_iter().copied().map(&bcb_from_marker).collect::>>()?; + + Some((span, true_bcbs, false_bcbs)) + }; + + let extract_condition_mapping = |&mir::coverage::MCDCBranchSpan { + span: raw_span, + condition_info, + ref true_markers, + ref false_markers, + }| { + let (span, true_bcbs, false_bcbs) = + check_branch_bcb(raw_span, true_markers, false_markers)?; + Some(MCDCBranch { span, true_bcbs, false_bcbs, condition_info }) + }; + + mcdc_degraded_branches + .extend(coverage_info_hi.mcdc_degraded_spans.iter().filter_map(extract_condition_mapping)); + + mcdc_mappings.extend(coverage_info_hi.mcdc_spans.iter().filter_map(|(decision, branches)| { + if branches.len() == 0 { + return None; + } + let span = unexpand_into_body_span(decision.span, body_span)?; - mcdc_branches.extend(coverage_info_hi.mcdc_branch_spans.iter().filter_map( - |&mir::coverage::MCDCBranchSpan { - span: raw_span, - condition_info, - true_marker, - false_marker, - decision_depth, - }| { - let (span, true_bcb, false_bcb) = - check_branch_bcb(raw_span, true_marker, false_marker)?; - Some(MCDCBranch { span, true_bcb, false_bcb, condition_info, decision_depth }) - }, - )); - - mcdc_decisions.extend(coverage_info_hi.mcdc_decision_spans.iter().filter_map( - |decision: &mir::coverage::MCDCDecisionSpan| { - let span = unexpand_into_body_span(decision.span, body_span)?; - - let end_bcbs = decision - .end_markers - .iter() - .map(|&marker| bcb_from_marker(marker)) - .collect::>()?; + let end_bcbs = decision + .end_markers + .iter() + .map(|&marker| bcb_from_marker(marker)) + .collect::>()?; + let branch_mappings: Vec<_> = + branches.into_iter().filter_map(extract_condition_mapping).collect(); + if branch_mappings.len() == branches.len() { // Each decision containing N conditions needs 2^N bits of space in // the bitmap, rounded up to a whole number of bytes. // The decision's "bitmap index" points to its first byte in the bitmap. let bitmap_idx = *mcdc_bitmap_bytes; - *mcdc_bitmap_bytes += (1_u32 << decision.num_conditions).div_ceil(8); - - Some(MCDCDecision { - span, - end_bcbs, - bitmap_idx, - num_conditions: decision.num_conditions as u16, - decision_depth: decision.decision_depth, - }) - }, - )); + *mcdc_bitmap_bytes += (1_u32 << branch_mappings.len()).div_ceil(8); + + Some(( + MCDCDecision { + span, + end_bcbs, + bitmap_idx, // Assigned in `coverage::create_mappings` + decision_depth: decision.decision_depth, + }, + branch_mappings, + )) + } else { + None + } + })); } diff --git a/compiler/rustc_mir_transform/src/coverage/mod.rs b/compiler/rustc_mir_transform/src/coverage/mod.rs index 2efca40d18047..118cea8a7b5fb 100644 --- a/compiler/rustc_mir_transform/src/coverage/mod.rs +++ b/compiler/rustc_mir_transform/src/coverage/mod.rs @@ -76,7 +76,7 @@ fn instrument_function_for_coverage<'tcx>(tcx: TyCtxt<'tcx>, mir_body: &mut mir: //////////////////////////////////////////////////// // Extract coverage spans and other mapping info from MIR. - let extracted_mappings = mappings::extract_all_mapping_info_from_mir( + let mut extracted_mappings = mappings::extract_all_mapping_info_from_mir( tcx, mir_body, &hir_info, @@ -96,10 +96,17 @@ fn instrument_function_for_coverage<'tcx>(tcx: TyCtxt<'tcx>, mir_body: &mut mir: } let bcb_has_counter_mappings = |bcb| bcbs_with_counter_mappings.contains(bcb); - let coverage_counters = + let mut coverage_counters = CoverageCounters::make_bcb_counters(&basic_coverage_blocks, bcb_has_counter_mappings); - let mappings = create_mappings(tcx, &hir_info, &extracted_mappings, &coverage_counters); + let mappings = create_mappings( + mir_body, + tcx, + &hir_info, + &mut extracted_mappings, + &basic_coverage_blocks, + &mut coverage_counters, + ); if mappings.is_empty() { // No spans could be converted into valid mappings, so skip this function. debug!("no spans could be converted into valid mappings; skipping"); @@ -116,9 +123,9 @@ fn instrument_function_for_coverage<'tcx>(tcx: TyCtxt<'tcx>, mir_body: &mut mir: inject_mcdc_statements(mir_body, &basic_coverage_blocks, &extracted_mappings); let mcdc_num_condition_bitmaps = extracted_mappings - .mcdc_decisions + .mcdc_mappings .iter() - .map(|&mappings::MCDCDecision { decision_depth, .. }| decision_depth) + .map(|&(mappings::MCDCDecision { decision_depth, .. }, _)| decision_depth) .max() .map_or(0, |max| usize::from(max) + 1); @@ -138,10 +145,12 @@ fn instrument_function_for_coverage<'tcx>(tcx: TyCtxt<'tcx>, mir_body: &mut mir: /// Precondition: All BCBs corresponding to those spans have been given /// coverage counters. fn create_mappings<'tcx>( + mir_body: &mut mir::Body<'tcx>, tcx: TyCtxt<'tcx>, hir_info: &ExtractedHirInfo, - extracted_mappings: &ExtractedMappings, - coverage_counters: &CoverageCounters, + extracted_mappings: &mut ExtractedMappings, + basic_coverage_blocks: &CoverageGraph, + coverage_counters: &mut CoverageCounters, ) -> Vec { let source_map = tcx.sess.source_map(); let body_span = hir_info.body_span; @@ -166,8 +175,8 @@ fn create_mappings<'tcx>( code_mappings, branch_pairs, mcdc_bitmap_bytes: _, - mcdc_branches, - mcdc_decisions, + mcdc_degraded_branches, + mcdc_mappings, } = extracted_mappings; let mut mappings = Vec::new(); @@ -189,27 +198,94 @@ fn create_mappings<'tcx>( Some(Mapping { kind, code_region }) }, )); + let mut term_for_bcbs = |bcbs: &[BasicCoverageBlock]| { + let counters = bcbs + .into_iter() + .copied() + .map(|bcb| { + coverage_counters.bcb_counter(bcb).expect("all BCBs with spans were given counters") + }) + .collect::>(); + let counter = counters + .into_iter() + .fold(None, |acc, val| Some(coverage_counters.make_sum_expression(acc, val))) + .expect("counters must be non-empty"); + // Since the counter is a sum of multiple counters, it should be marked used in + // all associated bcbs in case it were markered as `Zero`. + if bcbs.len() > 1 + && let counters::BcbCounter::Expression { id } = counter + { + for &bcb in bcbs { + inject_statement( + mir_body, + CoverageKind::ExpressionUsed { id }, + basic_coverage_blocks[bcb].leader_bb(), + ); + } + } + counter.as_term() + }; - mappings.extend(mcdc_branches.iter().filter_map( - |&mappings::MCDCBranch { span, true_bcb, false_bcb, condition_info, decision_depth: _ }| { + // MCDC branch mappings are appended with their decisions in case decisions were ignored. + mappings.extend(mcdc_degraded_branches.iter().filter_map( + |&mappings::MCDCBranch { span, ref true_bcbs, ref false_bcbs, condition_info: _ }| { let code_region = region_for_span(span)?; - let true_term = term_for_bcb(true_bcb); - let false_term = term_for_bcb(false_bcb); - let kind = match condition_info { - Some(mcdc_params) => MappingKind::MCDCBranch { true_term, false_term, mcdc_params }, - None => MappingKind::Branch { true_term, false_term }, - }; - Some(Mapping { kind, code_region }) + let true_term = term_for_bcbs(true_bcbs); + let false_term = term_for_bcbs(false_bcbs); + Some(Mapping { kind: MappingKind::Branch { true_term, false_term }, code_region }) }, )); - mappings.extend(mcdc_decisions.iter().filter_map( - |&mappings::MCDCDecision { span, bitmap_idx, num_conditions, .. }| { - let code_region = region_for_span(span)?; - let kind = MappingKind::MCDCDecision(DecisionInfo { bitmap_idx, num_conditions }); - Some(Mapping { kind, code_region }) - }, - )); + for (decision, branches) in mcdc_mappings { + let num_conditions = branches.len() as u16; + let conditions = branches + .into_iter() + .filter_map( + |&mut mappings::MCDCBranch { + span, + ref true_bcbs, + ref false_bcbs, + condition_info, + }| { + let code_region = region_for_span(span)?; + let true_term = term_for_bcbs(true_bcbs); + let false_term = term_for_bcbs(false_bcbs); + Some(Mapping { + kind: MappingKind::MCDCBranch { + true_term, + false_term, + mcdc_params: condition_info, + }, + code_region, + }) + }, + ) + .collect::>(); + + if conditions.len() == num_conditions as usize + && let Some(code_region) = region_for_span(decision.span) + { + let kind = MappingKind::MCDCDecision(DecisionInfo { + bitmap_idx: decision.bitmap_idx, + num_conditions, + }); + mappings.extend( + std::iter::once(Mapping { kind, code_region }).chain(conditions.into_iter()), + ); + } else { + mappings.extend(conditions.into_iter().map(|mapping| { + let MappingKind::MCDCBranch { true_term, false_term, mcdc_params: _ } = + mapping.kind + else { + unreachable!("all mappings here are MCDCBranch as shown above"); + }; + Mapping { + kind: MappingKind::Branch { true_term, false_term }, + code_region: mapping.code_region, + } + })) + } + } mappings } @@ -273,43 +349,37 @@ fn inject_mcdc_statements<'tcx>( basic_coverage_blocks: &CoverageGraph, extracted_mappings: &ExtractedMappings, ) { - // Inject test vector update first because `inject_statement` always insert new statement at head. - for &mappings::MCDCDecision { - span: _, - ref end_bcbs, - bitmap_idx, - num_conditions: _, - decision_depth, - } in &extracted_mappings.mcdc_decisions - { - for end in end_bcbs { - let end_bb = basic_coverage_blocks[*end].leader_bb(); + for (decision, conditions) in &extracted_mappings.mcdc_mappings { + // Inject test vector update first because `inject_statement` always insert new statement at head. + for &end in &decision.end_bcbs { + let end_bb = basic_coverage_blocks[end].leader_bb(); inject_statement( mir_body, - CoverageKind::TestVectorBitmapUpdate { bitmap_idx, decision_depth }, + CoverageKind::TestVectorBitmapUpdate { + bitmap_idx: decision.bitmap_idx, + decision_depth: decision.decision_depth, + }, end_bb, ); } - } - - for &mappings::MCDCBranch { span: _, true_bcb, false_bcb, condition_info, decision_depth } in - &extracted_mappings.mcdc_branches - { - let Some(condition_info) = condition_info else { continue }; - let id = condition_info.condition_id; - let true_bb = basic_coverage_blocks[true_bcb].leader_bb(); - inject_statement( - mir_body, - CoverageKind::CondBitmapUpdate { id, value: true, decision_depth }, - true_bb, - ); - let false_bb = basic_coverage_blocks[false_bcb].leader_bb(); - inject_statement( - mir_body, - CoverageKind::CondBitmapUpdate { id, value: false, decision_depth }, - false_bb, - ); + for &mappings::MCDCBranch { span: _, ref true_bcbs, false_bcbs: _, condition_info } in + conditions + { + let id = condition_info.condition_id; + for &bcb in true_bcbs { + let bb = basic_coverage_blocks[bcb].leader_bb(); + inject_statement( + mir_body, + CoverageKind::CondBitmapUpdate { + id, + value: true, + decision_depth: decision.decision_depth, + }, + bb, + ); + } + } } }