about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-04-29 11:54:49 +0000
committerbors <bors@rust-lang.org>2024-04-29 11:54:49 +0000
commit7a5867425959b4b5d69334fa6f02150dc2a5d128 (patch)
tree7074df6418d7e361b71a600181cba27e7eb1f763
parent90846015cc99aa90290c1f4ee95c571fff6901ef (diff)
parenteb422d5c7e6adccf83533ddb5a9836f6575bcd35 (diff)
downloadrust-7a5867425959b4b5d69334fa6f02150dc2a5d128.tar.gz
rust-7a5867425959b4b5d69334fa6f02150dc2a5d128.zip
Auto merge of #124255 - RenjiSann:renji/mcdc-nested-expressions, r=Zalathar
MCDC coverage: support nested decision coverage

#123409 provided the initial MCDC coverage implementation.

As referenced in #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.
-rw-r--r--compiler/rustc_codegen_llvm/src/builder.rs31
-rw-r--r--compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs30
-rw-r--r--compiler/rustc_middle/src/mir/coverage.rs23
-rw-r--r--compiler/rustc_middle/src/mir/pretty.rs17
-rw-r--r--compiler/rustc_mir_build/src/build/coverageinfo.rs75
-rw-r--r--compiler/rustc_mir_build/src/build/matches/mod.rs6
-rw-r--r--compiler/rustc_mir_transform/src/coverage/mod.rs46
-rw-r--r--compiler/rustc_mir_transform/src/coverage/spans.rs8
-rw-r--r--compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs27
-rw-r--r--tests/coverage/mcdc_nested_if.cov-map201
-rw-r--r--tests/coverage/mcdc_nested_if.coverage235
-rw-r--r--tests/coverage/mcdc_nested_if.rs70
12 files changed, 699 insertions, 70 deletions
diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs
index f7546039540..5c8f358d03a 100644
--- a/compiler/rustc_codegen_llvm/src/builder.rs
+++ b/compiler/rustc_codegen_llvm/src/builder.rs
@@ -27,6 +27,7 @@ use rustc_target::abi::{self, call::FnAbi, Align, Size, WrappingRange};
 use rustc_target::spec::{HasTargetSpec, SanitizerSet, Target};
 use smallvec::SmallVec;
 use std::borrow::Cow;
+use std::ffi::CString;
 use std::iter;
 use std::ops::Deref;
 use std::ptr;
@@ -1709,7 +1710,8 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> {
         fn_name: &'ll Value,
         hash: &'ll Value,
         bitmap_bytes: &'ll Value,
-    ) -> &'ll Value {
+        max_decision_depth: u32,
+    ) -> Vec<&'ll Value> {
         debug!("mcdc_parameters() with args ({:?}, {:?}, {:?})", fn_name, hash, bitmap_bytes);
 
         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> {
         let args = &[fn_name, hash, bitmap_bytes];
         let args = self.check_call("call", llty, llfn, args);
 
+        let mut cond_bitmaps = vec![];
+
         unsafe {
             let _ = llvm::LLVMRustBuildCall(
                 self.llbuilder,
@@ -1733,17 +1737,22 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> {
                 0 as c_uint,
             );
             // Create condition bitmap named `mcdc.addr`.
-            let mut bx = Builder::with_cx(self.cx);
-            bx.position_at_start(llvm::LLVMGetFirstBasicBlock(self.llfn()));
-            let cond_bitmap = {
-                let alloca =
-                    llvm::LLVMBuildAlloca(bx.llbuilder, bx.cx.type_i32(), c"mcdc.addr".as_ptr());
-                llvm::LLVMSetAlignment(alloca, 4);
-                alloca
-            };
-            bx.store(self.const_i32(0), cond_bitmap, self.tcx().data_layout.i32_align.abi);
-            cond_bitmap
+            for i in 0..=max_decision_depth {
+                let mut bx = Builder::with_cx(self.cx);
+                bx.position_at_start(llvm::LLVMGetFirstBasicBlock(self.llfn()));
+
+                let name = CString::new(format!("mcdc.addr.{i}")).unwrap();
+                let cond_bitmap = {
+                    let alloca =
+                        llvm::LLVMBuildAlloca(bx.llbuilder, bx.cx.type_i32(), name.as_ptr());
+                    llvm::LLVMSetAlignment(alloca, 4);
+                    alloca
+                };
+                bx.store(self.const_i32(0), cond_bitmap, self.tcx().data_layout.i32_align.abi);
+                cond_bitmaps.push(cond_bitmap);
+            }
         }
+        cond_bitmaps
     }
 
     pub(crate) fn mcdc_tvbitmap_update(
diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs
index 085ce15d81f..679c6e1a2ff 100644
--- a/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs
+++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs
@@ -30,7 +30,7 @@ pub struct CrateCoverageContext<'ll, 'tcx> {
     pub(crate) function_coverage_map:
         RefCell<FxIndexMap<Instance<'tcx>, FunctionCoverageCollector<'tcx>>>,
     pub(crate) pgo_func_name_var_map: RefCell<FxHashMap<Instance<'tcx>, &'ll llvm::Value>>,
-    pub(crate) mcdc_condition_bitmap_map: RefCell<FxHashMap<Instance<'tcx>, &'ll llvm::Value>>,
+    pub(crate) mcdc_condition_bitmap_map: RefCell<FxHashMap<Instance<'tcx>, Vec<&'ll llvm::Value>>>,
 }
 
 impl<'ll, 'tcx> CrateCoverageContext<'ll, 'tcx> {
@@ -49,9 +49,20 @@ impl<'ll, 'tcx> CrateCoverageContext<'ll, 'tcx> {
     }
 
     /// LLVM use a temp value to record evaluated mcdc test vector of each decision, which is called condition bitmap.
-    /// This value is named `mcdc.addr` (same as clang) and is a 32-bit integer.
-    fn try_get_mcdc_condition_bitmap(&self, instance: &Instance<'tcx>) -> Option<&'ll llvm::Value> {
-        self.mcdc_condition_bitmap_map.borrow().get(instance).copied()
+    /// In order to handle nested decisions, several condition bitmaps can be
+    /// allocated for a function body.
+    /// These values are named `mcdc.addr.{i}` and are a 32-bit integers.
+    /// They respectively hold the condition bitmaps for decisions with a depth of `i`.
+    fn try_get_mcdc_condition_bitmap(
+        &self,
+        instance: &Instance<'tcx>,
+        decision_depth: u16,
+    ) -> Option<&'ll llvm::Value> {
+        self.mcdc_condition_bitmap_map
+            .borrow()
+            .get(instance)
+            .and_then(|bitmap_map| bitmap_map.get(decision_depth as usize))
+            .copied() // Dereference Option<&&Value> to Option<&Value>
     }
 }
 
@@ -143,7 +154,7 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> {
             CoverageKind::ExpressionUsed { id } => {
                 func_coverage.mark_expression_id_seen(id);
             }
-            CoverageKind::CondBitmapUpdate { id, value, .. } => {
+            CoverageKind::CondBitmapUpdate { id, value, decision_depth } => {
                 drop(coverage_map);
                 assert_ne!(
                     id.as_u32(),
@@ -151,7 +162,7 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> {
                     "ConditionId of evaluated conditions should never be zero"
                 );
                 let cond_bitmap = coverage_context
-                    .try_get_mcdc_condition_bitmap(&instance)
+                    .try_get_mcdc_condition_bitmap(&instance, decision_depth)
                     .expect("mcdc cond bitmap should have been allocated for updating");
                 let cond_loc = bx.const_i32(id.as_u32() as i32 - 1);
                 let bool_value = bx.const_bool(value);
@@ -159,10 +170,10 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> {
                 let hash = bx.const_u64(function_coverage_info.function_source_hash);
                 bx.mcdc_condbitmap_update(fn_name, hash, cond_loc, cond_bitmap, bool_value);
             }
-            CoverageKind::TestVectorBitmapUpdate { bitmap_idx } => {
+            CoverageKind::TestVectorBitmapUpdate { bitmap_idx, decision_depth } => {
                 drop(coverage_map);
                 let cond_bitmap = coverage_context
-                                    .try_get_mcdc_condition_bitmap(&instance)
+                                    .try_get_mcdc_condition_bitmap(&instance, decision_depth)
                                     .expect("mcdc cond bitmap should have been allocated for merging into the global bitmap");
                 let bitmap_bytes = bx.tcx().coverage_ids_info(instance.def).mcdc_bitmap_bytes;
                 assert!(bitmap_idx < bitmap_bytes, "bitmap index of the decision out of range");
@@ -195,7 +206,8 @@ fn ensure_mcdc_parameters<'ll, 'tcx>(
     let fn_name = bx.get_pgo_func_name_var(instance);
     let hash = bx.const_u64(function_coverage_info.function_source_hash);
     let bitmap_bytes = bx.const_u32(function_coverage_info.mcdc_bitmap_bytes);
-    let cond_bitmap = bx.mcdc_parameters(fn_name, hash, bitmap_bytes);
+    let max_decision_depth = function_coverage_info.mcdc_max_decision_depth;
+    let cond_bitmap = bx.mcdc_parameters(fn_name, hash, bitmap_bytes, max_decision_depth as u32);
     bx.coverage_context()
         .expect("already checked above")
         .mcdc_condition_bitmap_map
diff --git a/compiler/rustc_middle/src/mir/coverage.rs b/compiler/rustc_middle/src/mir/coverage.rs
index 04011fd4194..a3fa69160a7 100644
--- a/compiler/rustc_middle/src/mir/coverage.rs
+++ b/compiler/rustc_middle/src/mir/coverage.rs
@@ -132,7 +132,7 @@ pub enum CoverageKind {
     ///
     /// If this statement does not survive MIR optimizations, the condition would never be
     /// taken as evaluated.
-    CondBitmapUpdate { id: ConditionId, value: bool },
+    CondBitmapUpdate { id: ConditionId, value: bool, decision_depth: u16 },
 
     /// Marks the point in MIR control flow represented by a evaluated decision.
     ///
@@ -140,7 +140,7 @@ pub enum CoverageKind {
     ///
     /// If this statement does not survive MIR optimizations, the decision would never be
     /// taken as evaluated.
-    TestVectorBitmapUpdate { bitmap_idx: u32 },
+    TestVectorBitmapUpdate { bitmap_idx: u32, decision_depth: u16 },
 }
 
 impl Debug for CoverageKind {
@@ -151,11 +151,17 @@ impl Debug for CoverageKind {
             BlockMarker { id } => write!(fmt, "BlockMarker({:?})", id.index()),
             CounterIncrement { id } => write!(fmt, "CounterIncrement({:?})", id.index()),
             ExpressionUsed { id } => write!(fmt, "ExpressionUsed({:?})", id.index()),
-            CondBitmapUpdate { id, value } => {
-                write!(fmt, "CondBitmapUpdate({:?}, {:?})", id.index(), value)
+            CondBitmapUpdate { id, value, decision_depth } => {
+                write!(
+                    fmt,
+                    "CondBitmapUpdate({:?}, {:?}, depth={:?})",
+                    id.index(),
+                    value,
+                    decision_depth
+                )
             }
-            TestVectorBitmapUpdate { bitmap_idx } => {
-                write!(fmt, "TestVectorUpdate({:?})", bitmap_idx)
+            TestVectorBitmapUpdate { bitmap_idx, decision_depth } => {
+                write!(fmt, "TestVectorUpdate({:?}, depth={:?})", bitmap_idx, decision_depth)
             }
         }
     }
@@ -269,6 +275,9 @@ pub struct FunctionCoverageInfo {
     pub mcdc_bitmap_bytes: u32,
     pub expressions: IndexVec<ExpressionId, Expression>,
     pub mappings: Vec<Mapping>,
+    /// The depth of the deepest decision is used to know how many
+    /// temp condbitmaps should be allocated for the function.
+    pub mcdc_max_decision_depth: u16,
 }
 
 /// Branch information recorded during THIR-to-MIR lowering, and stored in MIR.
@@ -319,6 +328,7 @@ pub struct MCDCBranchSpan {
     pub condition_info: Option<ConditionInfo>,
     pub true_marker: BlockMarkerId,
     pub false_marker: BlockMarkerId,
+    pub decision_depth: u16,
 }
 
 #[derive(Copy, Clone, Debug)]
@@ -334,4 +344,5 @@ pub struct MCDCDecisionSpan {
     pub span: Span,
     pub conditions_num: usize,
     pub end_markers: Vec<BlockMarkerId>,
+    pub decision_depth: u16,
 }
diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs
index a350eb05226..5aaa1c30cad 100644
--- a/compiler/rustc_middle/src/mir/pretty.rs
+++ b/compiler/rustc_middle/src/mir/pretty.rs
@@ -496,20 +496,27 @@ fn write_coverage_branch_info(
         )?;
     }
 
-    for coverage::MCDCBranchSpan { span, condition_info, true_marker, false_marker } in
-        mcdc_branch_spans
+    for coverage::MCDCBranchSpan {
+        span,
+        condition_info,
+        true_marker,
+        false_marker,
+        decision_depth,
+    } in mcdc_branch_spans
     {
         writeln!(
             w,
-            "{INDENT}coverage mcdc branch {{ condition_id: {:?}, true: {true_marker:?}, false: {false_marker:?} }} => {span:?}",
+            "{INDENT}coverage mcdc branch {{ condition_id: {:?}, true: {true_marker:?}, false: {false_marker:?}, depth: {decision_depth:?} }} => {span:?}",
             condition_info.map(|info| info.condition_id)
         )?;
     }
 
-    for coverage::MCDCDecisionSpan { span, conditions_num, end_markers } in mcdc_decision_spans {
+    for coverage::MCDCDecisionSpan { span, conditions_num, end_markers, decision_depth } in
+        mcdc_decision_spans
+    {
         writeln!(
             w,
-            "{INDENT}coverage mcdc decision {{ conditions_num: {conditions_num:?}, end: {end_markers:?} }} => {span:?}"
+            "{INDENT}coverage mcdc decision {{ conditions_num: {conditions_num:?}, end: {end_markers:?}, depth: {decision_depth:?} }} => {span:?}"
         )?;
     }
 
diff --git a/compiler/rustc_mir_build/src/build/coverageinfo.rs b/compiler/rustc_mir_build/src/build/coverageinfo.rs
index 9e9ccd3dc2d..a1c20fcfd83 100644
--- a/compiler/rustc_mir_build/src/build/coverageinfo.rs
+++ b/compiler/rustc_mir_build/src/build/coverageinfo.rs
@@ -101,10 +101,14 @@ impl BranchInfoBuilder {
         tcx: TyCtxt<'_>,
         true_marker: BlockMarkerId,
         false_marker: BlockMarkerId,
-    ) -> Option<ConditionInfo> {
+    ) -> Option<(u16, ConditionInfo)> {
         let mcdc_state = self.mcdc_state.as_mut()?;
+        let decision_depth = mcdc_state.decision_depth();
         let (mut condition_info, decision_result) =
             mcdc_state.take_condition(true_marker, false_marker);
+        // take_condition() returns Some for decision_result when the decision stack
+        // is empty, i.e. when all the conditions of the decision were instrumented,
+        // and the decision is "complete".
         if let Some(decision) = decision_result {
             match decision.conditions_num {
                 0 => {
@@ -131,7 +135,7 @@ impl BranchInfoBuilder {
                 }
             }
         }
-        condition_info
+        condition_info.map(|cond_info| (decision_depth, cond_info))
     }
 
     fn add_two_way_branch<'tcx>(
@@ -199,17 +203,32 @@ impl BranchInfoBuilder {
 /// This limit may be relaxed if the [upstream change](https://github.com/llvm/llvm-project/pull/82448) is merged.
 const MAX_CONDITIONS_NUM_IN_DECISION: usize = 6;
 
-struct MCDCState {
+#[derive(Default)]
+struct MCDCDecisionCtx {
     /// To construct condition evaluation tree.
     decision_stack: VecDeque<ConditionInfo>,
     processing_decision: Option<MCDCDecisionSpan>,
 }
 
+struct MCDCState {
+    decision_ctx_stack: Vec<MCDCDecisionCtx>,
+}
+
 impl MCDCState {
     fn new_if_enabled(tcx: TyCtxt<'_>) -> Option<Self> {
         tcx.sess
             .instrument_coverage_mcdc()
-            .then(|| Self { decision_stack: VecDeque::new(), processing_decision: None })
+            .then(|| Self { decision_ctx_stack: vec![MCDCDecisionCtx::default()] })
+    }
+
+    /// Decision depth is given as a u16 to reduce the size of the `CoverageKind`,
+    /// as it is very unlikely that the depth ever reaches 2^16.
+    #[inline]
+    fn decision_depth(&self) -> u16 {
+        u16::try_from(
+            self.decision_ctx_stack.len().checked_sub(1).expect("Unexpected empty decision stack"),
+        )
+        .expect("decision depth did not fit in u16, this is likely to be an instrumentation error")
     }
 
     // At first we assign ConditionIds for each sub expression.
@@ -253,19 +272,23 @@ impl MCDCState {
     // - 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".
     // - 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".
     fn record_conditions(&mut self, op: LogicalOp, span: Span) {
-        let decision = match self.processing_decision.as_mut() {
+        let decision_depth = self.decision_depth();
+        let decision_ctx =
+            self.decision_ctx_stack.last_mut().expect("Unexpected empty decision_ctx_stack");
+        let decision = match decision_ctx.processing_decision.as_mut() {
             Some(decision) => {
                 decision.span = decision.span.to(span);
                 decision
             }
-            None => self.processing_decision.insert(MCDCDecisionSpan {
+            None => decision_ctx.processing_decision.insert(MCDCDecisionSpan {
                 span,
                 conditions_num: 0,
                 end_markers: vec![],
+                decision_depth,
             }),
         };
 
-        let parent_condition = self.decision_stack.pop_back().unwrap_or_default();
+        let parent_condition = decision_ctx.decision_stack.pop_back().unwrap_or_default();
         let lhs_id = if parent_condition.condition_id == ConditionId::NONE {
             decision.conditions_num += 1;
             ConditionId::from(decision.conditions_num)
@@ -305,8 +328,8 @@ impl MCDCState {
             }
         };
         // We visit expressions tree in pre-order, so place the left-hand side on the top.
-        self.decision_stack.push_back(rhs);
-        self.decision_stack.push_back(lhs);
+        decision_ctx.decision_stack.push_back(rhs);
+        decision_ctx.decision_stack.push_back(lhs);
     }
 
     fn take_condition(
@@ -314,10 +337,12 @@ impl MCDCState {
         true_marker: BlockMarkerId,
         false_marker: BlockMarkerId,
     ) -> (Option<ConditionInfo>, Option<MCDCDecisionSpan>) {
-        let Some(condition_info) = self.decision_stack.pop_back() else {
+        let decision_ctx =
+            self.decision_ctx_stack.last_mut().expect("Unexpected empty decision_ctx_stack");
+        let Some(condition_info) = decision_ctx.decision_stack.pop_back() else {
             return (None, None);
         };
-        let Some(decision) = self.processing_decision.as_mut() else {
+        let Some(decision) = decision_ctx.processing_decision.as_mut() else {
             bug!("Processing decision should have been created before any conditions are taken");
         };
         if condition_info.true_next_id == ConditionId::NONE {
@@ -327,8 +352,8 @@ impl MCDCState {
             decision.end_markers.push(false_marker);
         }
 
-        if self.decision_stack.is_empty() {
-            (Some(condition_info), self.processing_decision.take())
+        if decision_ctx.decision_stack.is_empty() {
+            (Some(condition_info), decision_ctx.processing_decision.take())
         } else {
             (Some(condition_info), None)
         }
@@ -364,13 +389,17 @@ impl Builder<'_, '_> {
                 |block| branch_info.inject_block_marker(&mut self.cfg, source_info, block);
             let true_marker = inject_block_marker(then_block);
             let false_marker = inject_block_marker(else_block);
-            let condition_info =
-                branch_info.fetch_mcdc_condition_info(self.tcx, true_marker, false_marker);
+            let (decision_depth, condition_info) = branch_info
+                .fetch_mcdc_condition_info(self.tcx, true_marker, false_marker)
+                .map_or((0, None), |(decision_depth, condition_info)| {
+                    (decision_depth, Some(condition_info))
+                });
             branch_info.mcdc_branch_spans.push(MCDCBranchSpan {
                 span: source_info.span,
                 condition_info,
                 true_marker,
                 false_marker,
+                decision_depth,
             });
             return;
         }
@@ -385,4 +414,20 @@ impl Builder<'_, '_> {
             mcdc_state.record_conditions(logical_op, span);
         }
     }
+
+    pub(crate) fn mcdc_increment_depth_if_enabled(&mut self) {
+        if let Some(branch_info) = self.coverage_branch_info.as_mut()
+            && let Some(mcdc_state) = branch_info.mcdc_state.as_mut()
+        {
+            mcdc_state.decision_ctx_stack.push(MCDCDecisionCtx::default());
+        };
+    }
+
+    pub(crate) fn mcdc_decrement_depth_if_enabled(&mut self) {
+        if let Some(branch_info) = self.coverage_branch_info.as_mut()
+            && let Some(mcdc_state) = branch_info.mcdc_state.as_mut()
+        {
+            mcdc_state.decision_ctx_stack.pop().expect("Unexpected empty decision stack");
+        };
+    }
 }
diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs
index d36c51f4472..64053f2672c 100644
--- a/compiler/rustc_mir_build/src/build/matches/mod.rs
+++ b/compiler/rustc_mir_build/src/build/matches/mod.rs
@@ -148,8 +148,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 let mut block = block;
                 let temp_scope = args.temp_scope_override.unwrap_or_else(|| this.local_scope());
                 let mutability = Mutability::Mut;
+
+                // Increment the decision depth, in case we encounter boolean expressions
+                // further down.
+                this.mcdc_increment_depth_if_enabled();
                 let place =
                     unpack!(block = this.as_temp(block, Some(temp_scope), expr_id, mutability));
+                this.mcdc_decrement_depth_if_enabled();
+
                 let operand = Operand::Move(Place::from(place));
 
                 let then_block = this.cfg.start_new_block();
diff --git a/compiler/rustc_mir_transform/src/coverage/mod.rs b/compiler/rustc_mir_transform/src/coverage/mod.rs
index 0b15c52c281..159c099fac5 100644
--- a/compiler/rustc_mir_transform/src/coverage/mod.rs
+++ b/compiler/rustc_mir_transform/src/coverage/mod.rs
@@ -102,12 +102,23 @@ fn instrument_function_for_coverage<'tcx>(tcx: TyCtxt<'tcx>, mir_body: &mut mir:
 
     inject_mcdc_statements(mir_body, &basic_coverage_blocks, &coverage_spans);
 
+    let mcdc_max_decision_depth = coverage_spans
+        .mappings
+        .iter()
+        .filter_map(|bcb_mapping| match bcb_mapping.kind {
+            BcbMappingKind::MCDCDecision { decision_depth, .. } => Some(decision_depth),
+            _ => None,
+        })
+        .max()
+        .unwrap_or(0);
+
     mir_body.function_coverage_info = Some(Box::new(FunctionCoverageInfo {
         function_source_hash: hir_info.function_source_hash,
         num_counters: coverage_counters.num_counters(),
         mcdc_bitmap_bytes: coverage_spans.test_vector_bitmap_bytes(),
         expressions: coverage_counters.into_expressions(),
         mappings,
+        mcdc_max_decision_depth,
     }));
 }
 
@@ -145,16 +156,17 @@ fn create_mappings<'tcx>(
         |BcbMapping { kind: bcb_mapping_kind, span }| {
             let kind = match *bcb_mapping_kind {
                 BcbMappingKind::Code(bcb) => MappingKind::Code(term_for_bcb(bcb)),
-                BcbMappingKind::MCDCBranch { true_bcb, false_bcb, condition_info: None } => {
-                    MappingKind::Branch {
-                        true_term: term_for_bcb(true_bcb),
-                        false_term: term_for_bcb(false_bcb),
-                    }
-                }
+                BcbMappingKind::MCDCBranch {
+                    true_bcb, false_bcb, condition_info: None, ..
+                } => MappingKind::Branch {
+                    true_term: term_for_bcb(true_bcb),
+                    false_term: term_for_bcb(false_bcb),
+                },
                 BcbMappingKind::MCDCBranch {
                     true_bcb,
                     false_bcb,
                     condition_info: Some(mcdc_params),
+                    ..
                 } => MappingKind::MCDCBranch {
                     true_term: term_for_bcb(true_bcb),
                     false_term: term_for_bcb(false_bcb),
@@ -246,24 +258,28 @@ fn inject_mcdc_statements<'tcx>(
     }
 
     // Inject test vector update first because `inject_statement` always insert new statement at head.
-    for (end_bcbs, bitmap_idx) in
+    for (end_bcbs, bitmap_idx, decision_depth) in
         coverage_spans.mappings.iter().filter_map(|mapping| match &mapping.kind {
-            BcbMappingKind::MCDCDecision { end_bcbs, bitmap_idx, .. } => {
-                Some((end_bcbs, *bitmap_idx))
+            BcbMappingKind::MCDCDecision { end_bcbs, bitmap_idx, decision_depth, .. } => {
+                Some((end_bcbs, *bitmap_idx, *decision_depth))
             }
             _ => None,
         })
     {
         for end in end_bcbs {
             let end_bb = basic_coverage_blocks[*end].leader_bb();
-            inject_statement(mir_body, CoverageKind::TestVectorBitmapUpdate { bitmap_idx }, end_bb);
+            inject_statement(
+                mir_body,
+                CoverageKind::TestVectorBitmapUpdate { bitmap_idx, decision_depth },
+                end_bb,
+            );
         }
     }
 
-    for (true_bcb, false_bcb, condition_id) in
+    for (true_bcb, false_bcb, condition_id, decision_depth) in
         coverage_spans.mappings.iter().filter_map(|mapping| match mapping.kind {
-            BcbMappingKind::MCDCBranch { true_bcb, false_bcb, condition_info } => {
-                Some((true_bcb, false_bcb, condition_info?.condition_id))
+            BcbMappingKind::MCDCBranch { true_bcb, false_bcb, condition_info, decision_depth } => {
+                Some((true_bcb, false_bcb, condition_info?.condition_id, decision_depth))
             }
             _ => None,
         })
@@ -271,13 +287,13 @@ fn inject_mcdc_statements<'tcx>(
         let true_bb = basic_coverage_blocks[true_bcb].leader_bb();
         inject_statement(
             mir_body,
-            CoverageKind::CondBitmapUpdate { id: condition_id, value: true },
+            CoverageKind::CondBitmapUpdate { id: condition_id, value: true, decision_depth },
             true_bb,
         );
         let false_bb = basic_coverage_blocks[false_bcb].leader_bb();
         inject_statement(
             mir_body,
-            CoverageKind::CondBitmapUpdate { id: condition_id, value: false },
+            CoverageKind::CondBitmapUpdate { id: condition_id, value: false, decision_depth },
             false_bb,
         );
     }
diff --git a/compiler/rustc_mir_transform/src/coverage/spans.rs b/compiler/rustc_mir_transform/src/coverage/spans.rs
index 88f18b72085..b64b1212cec 100644
--- a/compiler/rustc_mir_transform/src/coverage/spans.rs
+++ b/compiler/rustc_mir_transform/src/coverage/spans.rs
@@ -26,9 +26,15 @@ pub(super) enum BcbMappingKind {
         /// If `None`, this actually represents a normal branch mapping inserted
         /// for code that was too complex for MC/DC.
         condition_info: Option<ConditionInfo>,
+        decision_depth: u16,
     },
     /// Associates a mcdc decision with its join BCB.
-    MCDCDecision { end_bcbs: BTreeSet<BasicCoverageBlock>, bitmap_idx: u32, conditions_num: u16 },
+    MCDCDecision {
+        end_bcbs: BTreeSet<BasicCoverageBlock>,
+        bitmap_idx: u32,
+        conditions_num: u16,
+        decision_depth: u16,
+    },
 }
 
 #[derive(Debug)]
diff --git a/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs b/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs
index 64f21d74b1c..142dfc17fb8 100644
--- a/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs
+++ b/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs
@@ -453,15 +453,25 @@ pub(super) fn extract_mcdc_mappings(
             Some((span, true_bcb, false_bcb))
         };
 
-    let mcdc_branch_filter_map =
-        |&MCDCBranchSpan { span: raw_span, true_marker, false_marker, condition_info }| {
-            check_branch_bcb(raw_span, true_marker, false_marker).map(
-                |(span, true_bcb, false_bcb)| BcbMapping {
-                    kind: BcbMappingKind::MCDCBranch { true_bcb, false_bcb, condition_info },
-                    span,
+    let mcdc_branch_filter_map = |&MCDCBranchSpan {
+                                      span: raw_span,
+                                      true_marker,
+                                      false_marker,
+                                      condition_info,
+                                      decision_depth,
+                                  }| {
+        check_branch_bcb(raw_span, true_marker, false_marker).map(|(span, true_bcb, false_bcb)| {
+            BcbMapping {
+                kind: BcbMappingKind::MCDCBranch {
+                    true_bcb,
+                    false_bcb,
+                    condition_info,
+                    decision_depth,
                 },
-            )
-        };
+                span,
+            }
+        })
+    };
 
     let mut next_bitmap_idx = 0;
 
@@ -482,6 +492,7 @@ pub(super) fn extract_mcdc_mappings(
                 end_bcbs,
                 bitmap_idx,
                 conditions_num: decision.conditions_num as u16,
+                decision_depth: decision.decision_depth,
             },
             span,
         })
diff --git a/tests/coverage/mcdc_nested_if.cov-map b/tests/coverage/mcdc_nested_if.cov-map
new file mode 100644
index 00000000000..2f35ffad8a9
--- /dev/null
+++ b/tests/coverage/mcdc_nested_if.cov-map
@@ -0,0 +1,201 @@
+Function name: mcdc_nested_if::doubly_nested_if_in_condition
+Raw bytes (168): 0x[01, 01, 0e, 01, 05, 05, 11, 05, 11, 26, 19, 05, 11, 19, 1d, 19, 1d, 1d, 22, 26, 19, 05, 11, 11, 15, 09, 02, 0d, 37, 09, 02, 14, 01, 0f, 01, 01, 09, 28, 02, 02, 01, 08, 00, 4e, 30, 05, 02, 01, 02, 00, 00, 08, 00, 09, 30, 0d, 09, 02, 00, 00, 00, 0d, 00, 4e, 05, 00, 10, 00, 11, 28, 01, 02, 00, 10, 00, 36, 30, 11, 26, 01, 00, 02, 00, 10, 00, 11, 30, 15, 21, 02, 00, 00, 00, 15, 00, 36, 26, 00, 18, 00, 19, 28, 00, 02, 00, 18, 00, 1e, 30, 19, 22, 01, 02, 00, 00, 18, 00, 19, 19, 00, 1d, 00, 1e, 30, 1a, 1d, 02, 00, 00, 00, 1d, 00, 1e, 1a, 00, 21, 00, 25, 1f, 00, 2f, 00, 34, 2b, 00, 39, 00, 3e, 21, 00, 48, 00, 4c, 0d, 00, 4f, 02, 06, 37, 02, 0c, 02, 06, 33, 03, 01, 00, 02]
+Number of files: 1
+- file 0 => global file 1
+Number of expressions: 14
+- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
+- expression 1 operands: lhs = Counter(1), rhs = Counter(4)
+- expression 2 operands: lhs = Counter(1), rhs = Counter(4)
+- expression 3 operands: lhs = Expression(9, Sub), rhs = Counter(6)
+- expression 4 operands: lhs = Counter(1), rhs = Counter(4)
+- expression 5 operands: lhs = Counter(6), rhs = Counter(7)
+- expression 6 operands: lhs = Counter(6), rhs = Counter(7)
+- expression 7 operands: lhs = Counter(7), rhs = Expression(8, Sub)
+- expression 8 operands: lhs = Expression(9, Sub), rhs = Counter(6)
+- expression 9 operands: lhs = Counter(1), rhs = Counter(4)
+- expression 10 operands: lhs = Counter(4), rhs = Counter(5)
+- expression 11 operands: lhs = Counter(2), rhs = Expression(0, Sub)
+- expression 12 operands: lhs = Counter(3), rhs = Expression(13, Add)
+- expression 13 operands: lhs = Counter(2), rhs = Expression(0, Sub)
+Number of file 0 mappings: 20
+- Code(Counter(0)) at (prev + 15, 1) to (start + 1, 9)
+- MCDCDecision { bitmap_idx: 2, conditions_num: 2 } at (prev + 1, 8) to (start + 0, 78)
+- MCDCBranch { true: Counter(1), false: Expression(0, Sub), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 8) to (start + 0, 9)
+    true  = c1
+    false = (c0 - c1)
+- MCDCBranch { true: Counter(3), false: Counter(2), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 13) to (start + 0, 78)
+    true  = c3
+    false = c2
+- Code(Counter(1)) at (prev + 0, 16) to (start + 0, 17)
+- MCDCDecision { bitmap_idx: 1, conditions_num: 2 } at (prev + 0, 16) to (start + 0, 54)
+- MCDCBranch { true: Counter(4), false: Expression(9, Sub), condition_id: 1, true_next_id: 0, false_next_id: 2 } at (prev + 0, 16) to (start + 0, 17)
+    true  = c4
+    false = (c1 - c4)
+- MCDCBranch { true: Counter(5), false: Counter(8), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 21) to (start + 0, 54)
+    true  = c5
+    false = c8
+- Code(Expression(9, Sub)) at (prev + 0, 24) to (start + 0, 25)
+    = (c1 - c4)
+- MCDCDecision { bitmap_idx: 0, conditions_num: 2 } at (prev + 0, 24) to (start + 0, 30)
+- MCDCBranch { true: Counter(6), false: Expression(8, Sub), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 24) to (start + 0, 25)
+    true  = c6
+    false = ((c1 - c4) - c6)
+- Code(Counter(6)) at (prev + 0, 29) to (start + 0, 30)
+- MCDCBranch { true: Expression(6, Sub), false: Counter(7), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 29) to (start + 0, 30)
+    true  = (c6 - c7)
+    false = c7
+- Code(Expression(6, Sub)) at (prev + 0, 33) to (start + 0, 37)
+    = (c6 - c7)
+- Code(Expression(7, Add)) at (prev + 0, 47) to (start + 0, 52)
+    = (c7 + ((c1 - c4) - c6))
+- Code(Expression(10, Add)) at (prev + 0, 57) to (start + 0, 62)
+    = (c4 + c5)
+- Code(Counter(8)) at (prev + 0, 72) to (start + 0, 76)
+- Code(Counter(3)) at (prev + 0, 79) to (start + 2, 6)
+- Code(Expression(13, Add)) at (prev + 2, 12) to (start + 2, 6)
+    = (c2 + (c0 - c1))
+- Code(Expression(12, Add)) at (prev + 3, 1) to (start + 0, 2)
+    = (c3 + (c2 + (c0 - c1)))
+
+Function name: mcdc_nested_if::nested_if_in_condition
+Raw bytes (120): 0x[01, 01, 0b, 01, 05, 05, 11, 05, 11, 1e, 15, 05, 11, 11, 15, 1e, 15, 05, 11, 09, 02, 0d, 2b, 09, 02, 0e, 01, 07, 01, 01, 09, 28, 01, 02, 01, 08, 00, 2e, 30, 05, 02, 01, 02, 00, 00, 08, 00, 09, 30, 0d, 09, 02, 00, 00, 00, 0d, 00, 2e, 05, 00, 10, 00, 11, 28, 00, 02, 00, 10, 00, 16, 30, 11, 1e, 01, 00, 02, 00, 10, 00, 11, 1e, 00, 15, 00, 16, 30, 15, 1a, 02, 00, 00, 00, 15, 00, 16, 17, 00, 19, 00, 1d, 1a, 00, 27, 00, 2c, 0d, 00, 2f, 02, 06, 2b, 02, 0c, 02, 06, 27, 03, 01, 00, 02]
+Number of files: 1
+- file 0 => global file 1
+Number of expressions: 11
+- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
+- expression 1 operands: lhs = Counter(1), rhs = Counter(4)
+- expression 2 operands: lhs = Counter(1), rhs = Counter(4)
+- expression 3 operands: lhs = Expression(7, Sub), rhs = Counter(5)
+- expression 4 operands: lhs = Counter(1), rhs = Counter(4)
+- expression 5 operands: lhs = Counter(4), rhs = Counter(5)
+- expression 6 operands: lhs = Expression(7, Sub), rhs = Counter(5)
+- expression 7 operands: lhs = Counter(1), rhs = Counter(4)
+- expression 8 operands: lhs = Counter(2), rhs = Expression(0, Sub)
+- expression 9 operands: lhs = Counter(3), rhs = Expression(10, Add)
+- expression 10 operands: lhs = Counter(2), rhs = Expression(0, Sub)
+Number of file 0 mappings: 14
+- Code(Counter(0)) at (prev + 7, 1) to (start + 1, 9)
+- MCDCDecision { bitmap_idx: 1, conditions_num: 2 } at (prev + 1, 8) to (start + 0, 46)
+- MCDCBranch { true: Counter(1), false: Expression(0, Sub), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 8) to (start + 0, 9)
+    true  = c1
+    false = (c0 - c1)
+- MCDCBranch { true: Counter(3), false: Counter(2), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 13) to (start + 0, 46)
+    true  = c3
+    false = c2
+- Code(Counter(1)) at (prev + 0, 16) to (start + 0, 17)
+- MCDCDecision { bitmap_idx: 0, conditions_num: 2 } at (prev + 0, 16) to (start + 0, 22)
+- MCDCBranch { true: Counter(4), false: Expression(7, Sub), condition_id: 1, true_next_id: 0, false_next_id: 2 } at (prev + 0, 16) to (start + 0, 17)
+    true  = c4
+    false = (c1 - c4)
+- Code(Expression(7, Sub)) at (prev + 0, 21) to (start + 0, 22)
+    = (c1 - c4)
+- MCDCBranch { true: Counter(5), false: Expression(6, Sub), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 21) to (start + 0, 22)
+    true  = c5
+    false = ((c1 - c4) - c5)
+- Code(Expression(5, Add)) at (prev + 0, 25) to (start + 0, 29)
+    = (c4 + c5)
+- Code(Expression(6, Sub)) at (prev + 0, 39) to (start + 0, 44)
+    = ((c1 - c4) - c5)
+- Code(Counter(3)) at (prev + 0, 47) to (start + 2, 6)
+- Code(Expression(10, Add)) at (prev + 2, 12) to (start + 2, 6)
+    = (c2 + (c0 - c1))
+- Code(Expression(9, Add)) at (prev + 3, 1) to (start + 0, 2)
+    = (c3 + (c2 + (c0 - c1)))
+
+Function name: mcdc_nested_if::nested_in_then_block_in_condition
+Raw bytes (176): 0x[01, 01, 12, 01, 05, 05, 11, 05, 11, 3a, 15, 05, 11, 11, 15, 33, 19, 11, 15, 19, 1d, 19, 1d, 1d, 2e, 33, 19, 11, 15, 3a, 15, 05, 11, 09, 02, 0d, 47, 09, 02, 14, 01, 22, 01, 01, 09, 28, 02, 02, 01, 08, 00, 4b, 30, 05, 02, 01, 02, 00, 00, 08, 00, 09, 30, 0d, 09, 02, 00, 00, 00, 0d, 00, 4b, 05, 00, 10, 00, 11, 28, 00, 02, 00, 10, 00, 16, 30, 11, 3a, 01, 00, 02, 00, 10, 00, 11, 3a, 00, 15, 00, 16, 30, 15, 36, 02, 00, 00, 00, 15, 00, 16, 33, 00, 1c, 00, 1d, 28, 01, 02, 00, 1c, 00, 22, 30, 19, 2e, 01, 02, 00, 00, 1c, 00, 1d, 19, 00, 21, 00, 22, 30, 26, 1d, 02, 00, 00, 00, 21, 00, 22, 26, 00, 25, 00, 29, 2b, 00, 33, 00, 38, 36, 00, 44, 00, 49, 0d, 00, 4c, 02, 06, 47, 02, 0c, 02, 06, 43, 03, 01, 00, 02]
+Number of files: 1
+- file 0 => global file 1
+Number of expressions: 18
+- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
+- expression 1 operands: lhs = Counter(1), rhs = Counter(4)
+- expression 2 operands: lhs = Counter(1), rhs = Counter(4)
+- expression 3 operands: lhs = Expression(14, Sub), rhs = Counter(5)
+- expression 4 operands: lhs = Counter(1), rhs = Counter(4)
+- expression 5 operands: lhs = Counter(4), rhs = Counter(5)
+- expression 6 operands: lhs = Expression(12, Add), rhs = Counter(6)
+- expression 7 operands: lhs = Counter(4), rhs = Counter(5)
+- expression 8 operands: lhs = Counter(6), rhs = Counter(7)
+- expression 9 operands: lhs = Counter(6), rhs = Counter(7)
+- expression 10 operands: lhs = Counter(7), rhs = Expression(11, Sub)
+- expression 11 operands: lhs = Expression(12, Add), rhs = Counter(6)
+- expression 12 operands: lhs = Counter(4), rhs = Counter(5)
+- expression 13 operands: lhs = Expression(14, Sub), rhs = Counter(5)
+- expression 14 operands: lhs = Counter(1), rhs = Counter(4)
+- expression 15 operands: lhs = Counter(2), rhs = Expression(0, Sub)
+- expression 16 operands: lhs = Counter(3), rhs = Expression(17, Add)
+- expression 17 operands: lhs = Counter(2), rhs = Expression(0, Sub)
+Number of file 0 mappings: 20
+- Code(Counter(0)) at (prev + 34, 1) to (start + 1, 9)
+- MCDCDecision { bitmap_idx: 2, conditions_num: 2 } at (prev + 1, 8) to (start + 0, 75)
+- MCDCBranch { true: Counter(1), false: Expression(0, Sub), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 8) to (start + 0, 9)
+    true  = c1
+    false = (c0 - c1)
+- MCDCBranch { true: Counter(3), false: Counter(2), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 13) to (start + 0, 75)
+    true  = c3
+    false = c2
+- Code(Counter(1)) at (prev + 0, 16) to (start + 0, 17)
+- MCDCDecision { bitmap_idx: 0, conditions_num: 2 } at (prev + 0, 16) to (start + 0, 22)
+- MCDCBranch { true: Counter(4), false: Expression(14, Sub), condition_id: 1, true_next_id: 0, false_next_id: 2 } at (prev + 0, 16) to (start + 0, 17)
+    true  = c4
+    false = (c1 - c4)
+- Code(Expression(14, Sub)) at (prev + 0, 21) to (start + 0, 22)
+    = (c1 - c4)
+- MCDCBranch { true: Counter(5), false: Expression(13, Sub), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 21) to (start + 0, 22)
+    true  = c5
+    false = ((c1 - c4) - c5)
+- Code(Expression(12, Add)) at (prev + 0, 28) to (start + 0, 29)
+    = (c4 + c5)
+- MCDCDecision { bitmap_idx: 1, conditions_num: 2 } at (prev + 0, 28) to (start + 0, 34)
+- MCDCBranch { true: Counter(6), false: Expression(11, Sub), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 28) to (start + 0, 29)
+    true  = c6
+    false = ((c4 + c5) - c6)
+- Code(Counter(6)) at (prev + 0, 33) to (start + 0, 34)
+- MCDCBranch { true: Expression(9, Sub), false: Counter(7), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 33) to (start + 0, 34)
+    true  = (c6 - c7)
+    false = c7
+- Code(Expression(9, Sub)) at (prev + 0, 37) to (start + 0, 41)
+    = (c6 - c7)
+- Code(Expression(10, Add)) at (prev + 0, 51) to (start + 0, 56)
+    = (c7 + ((c4 + c5) - c6))
+- Code(Expression(13, Sub)) at (prev + 0, 68) to (start + 0, 73)
+    = ((c1 - c4) - c5)
+- Code(Counter(3)) at (prev + 0, 76) to (start + 2, 6)
+- Code(Expression(17, Add)) at (prev + 2, 12) to (start + 2, 6)
+    = (c2 + (c0 - c1))
+- Code(Expression(16, Add)) at (prev + 3, 1) to (start + 0, 2)
+    = (c3 + (c2 + (c0 - c1)))
+
+Function name: mcdc_nested_if::nested_single_condition_decision
+Raw bytes (85): 0x[01, 01, 06, 01, 05, 05, 11, 05, 11, 09, 02, 0d, 17, 09, 02, 0b, 01, 17, 01, 04, 09, 28, 00, 02, 04, 08, 00, 29, 30, 05, 02, 01, 02, 00, 00, 08, 00, 09, 30, 0d, 09, 02, 00, 00, 00, 0d, 00, 29, 05, 00, 10, 00, 11, 20, 11, 0a, 00, 10, 00, 11, 11, 00, 14, 00, 19, 0a, 00, 23, 00, 27, 0d, 00, 2a, 02, 06, 17, 02, 0c, 02, 06, 13, 03, 01, 00, 02]
+Number of files: 1
+- file 0 => global file 1
+Number of expressions: 6
+- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
+- expression 1 operands: lhs = Counter(1), rhs = Counter(4)
+- expression 2 operands: lhs = Counter(1), rhs = Counter(4)
+- expression 3 operands: lhs = Counter(2), rhs = Expression(0, Sub)
+- expression 4 operands: lhs = Counter(3), rhs = Expression(5, Add)
+- expression 5 operands: lhs = Counter(2), rhs = Expression(0, Sub)
+Number of file 0 mappings: 11
+- Code(Counter(0)) at (prev + 23, 1) to (start + 4, 9)
+- MCDCDecision { bitmap_idx: 0, conditions_num: 2 } at (prev + 4, 8) to (start + 0, 41)
+- MCDCBranch { true: Counter(1), false: Expression(0, Sub), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 8) to (start + 0, 9)
+    true  = c1
+    false = (c0 - c1)
+- MCDCBranch { true: Counter(3), false: Counter(2), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 13) to (start + 0, 41)
+    true  = c3
+    false = c2
+- Code(Counter(1)) at (prev + 0, 16) to (start + 0, 17)
+- Branch { true: Counter(4), false: Expression(2, Sub) } at (prev + 0, 16) to (start + 0, 17)
+    true  = c4
+    false = (c1 - c4)
+- Code(Counter(4)) at (prev + 0, 20) to (start + 0, 25)
+- Code(Expression(2, Sub)) at (prev + 0, 35) to (start + 0, 39)
+    = (c1 - c4)
+- Code(Counter(3)) at (prev + 0, 42) to (start + 2, 6)
+- Code(Expression(5, Add)) at (prev + 2, 12) to (start + 2, 6)
+    = (c2 + (c0 - c1))
+- Code(Expression(4, Add)) at (prev + 3, 1) to (start + 0, 2)
+    = (c3 + (c2 + (c0 - c1)))
+
diff --git a/tests/coverage/mcdc_nested_if.coverage b/tests/coverage/mcdc_nested_if.coverage
new file mode 100644
index 00000000000..19529cd6aa4
--- /dev/null
+++ b/tests/coverage/mcdc_nested_if.coverage
@@ -0,0 +1,235 @@
+   LL|       |#![feature(coverage_attribute)]
+   LL|       |//@ edition: 2021
+   LL|       |//@ min-llvm-version: 18
+   LL|       |//@ compile-flags: -Zcoverage-options=mcdc
+   LL|       |//@ llvm-cov-flags: --show-mcdc
+   LL|       |
+   LL|      4|fn nested_if_in_condition(a: bool, b: bool, c: bool) {
+   LL|      4|    if a && if b || c { true } else { false } {
+                             ^3   ^2  ^2            ^1
+  ------------------
+  |---> MC/DC Decision Region (LL:8) to (LL:46)
+  |
+  |  Number of Conditions: 2
+  |     Condition C1 --> (LL:8)
+  |     Condition C2 --> (LL:13)
+  |
+  |  Executed MC/DC Test Vectors:
+  |
+  |     C1, C2    Result
+  |  1 { F,  -  = F      }
+  |  2 { T,  F  = F      }
+  |  3 { T,  T  = T      }
+  |
+  |  C1-Pair: covered: (1,3)
+  |  C2-Pair: covered: (2,3)
+  |  MC/DC Coverage for Decision: 100.00%
+  |
+  |---> MC/DC Decision Region (LL:16) to (LL:22)
+  |
+  |  Number of Conditions: 2
+  |     Condition C1 --> (LL:16)
+  |     Condition C2 --> (LL:21)
+  |
+  |  Executed MC/DC Test Vectors:
+  |
+  |     C1, C2    Result
+  |  1 { F,  F  = F      }
+  |  2 { T,  -  = T      }
+  |  3 { F,  T  = T      }
+  |
+  |  C1-Pair: covered: (1,2)
+  |  C2-Pair: covered: (1,3)
+  |  MC/DC Coverage for Decision: 100.00%
+  |
+  ------------------
+   LL|      2|        say("yes");
+   LL|      2|    } else {
+   LL|      2|        say("no");
+   LL|      2|    }
+   LL|      4|}
+   LL|       |
+   LL|      4|fn doubly_nested_if_in_condition(a: bool, b: bool, c: bool, d: bool) {
+   LL|      4|    if a && if b || if c && d { true } else { false } { false } else { true } {
+                             ^3      ^2   ^1  ^1            ^1        ^2             ^1
+  ------------------
+  |---> MC/DC Decision Region (LL:8) to (LL:78)
+  |
+  |  Number of Conditions: 2
+  |     Condition C1 --> (LL:8)
+  |     Condition C2 --> (LL:13)
+  |
+  |  Executed MC/DC Test Vectors:
+  |
+  |     C1, C2    Result
+  |  1 { F,  -  = F      }
+  |  2 { T,  F  = F      }
+  |  3 { T,  T  = T      }
+  |
+  |  C1-Pair: covered: (1,3)
+  |  C2-Pair: covered: (2,3)
+  |  MC/DC Coverage for Decision: 100.00%
+  |
+  |---> MC/DC Decision Region (LL:16) to (LL:54)
+  |
+  |  Number of Conditions: 2
+  |     Condition C1 --> (LL:16)
+  |     Condition C2 --> (LL:21)
+  |
+  |  Executed MC/DC Test Vectors:
+  |
+  |     C1, C2    Result
+  |  1 { F,  F  = F      }
+  |  2 { T,  -  = T      }
+  |  3 { F,  T  = T      }
+  |
+  |  C1-Pair: covered: (1,2)
+  |  C2-Pair: covered: (1,3)
+  |  MC/DC Coverage for Decision: 100.00%
+  |
+  |---> MC/DC Decision Region (LL:24) to (LL:30)
+  |
+  |  Number of Conditions: 2
+  |     Condition C1 --> (LL:24)
+  |     Condition C2 --> (LL:29)
+  |
+  |  Executed MC/DC Test Vectors:
+  |
+  |     C1, C2    Result
+  |  1 { F,  -  = F      }
+  |  2 { T,  T  = T      }
+  |
+  |  C1-Pair: covered: (1,2)
+  |  C2-Pair: not covered
+  |  MC/DC Coverage for Decision: 50.00%
+  |
+  ------------------
+   LL|      1|        say("yes");
+   LL|      3|    } else {
+   LL|      3|        say("no");
+   LL|      3|    }
+   LL|      4|}
+   LL|       |
+   LL|      3|fn nested_single_condition_decision(a: bool, b: bool) {
+   LL|      3|    // Decision with only 1 decision should not be instrumented by MCDC because
+   LL|      3|    // branch-coverage is equivalent to MCDC coverage in this case, and we don't
+   LL|      3|    // want to waste bitmap space for this.
+   LL|      3|    if a && if b { false } else { true } {
+                             ^2  ^1             ^1
+  ------------------
+  |---> MC/DC Decision Region (LL:8) to (LL:41)
+  |
+  |  Number of Conditions: 2
+  |     Condition C1 --> (LL:8)
+  |     Condition C2 --> (LL:13)
+  |
+  |  Executed MC/DC Test Vectors:
+  |
+  |     C1, C2    Result
+  |  1 { F,  -  = F      }
+  |  2 { T,  F  = F      }
+  |  3 { T,  T  = T      }
+  |
+  |  C1-Pair: covered: (1,3)
+  |  C2-Pair: covered: (2,3)
+  |  MC/DC Coverage for Decision: 100.00%
+  |
+  ------------------
+   LL|      1|        say("yes");
+   LL|      2|    } else {
+   LL|      2|        say("no");
+   LL|      2|    }
+   LL|      3|}
+   LL|       |
+   LL|      7|fn nested_in_then_block_in_condition(a: bool, b: bool, c: bool, d: bool, e: bool) {
+   LL|      7|    if a && if b || c { if d && e { true } else { false } } else { false } {
+                             ^6   ^5     ^5   ^2  ^1            ^4               ^1
+  ------------------
+  |---> MC/DC Decision Region (LL:8) to (LL:75)
+  |
+  |  Number of Conditions: 2
+  |     Condition C1 --> (LL:8)
+  |     Condition C2 --> (LL:13)
+  |
+  |  Executed MC/DC Test Vectors:
+  |
+  |     C1, C2    Result
+  |  1 { F,  -  = F      }
+  |  2 { T,  F  = F      }
+  |  3 { T,  T  = T      }
+  |
+  |  C1-Pair: covered: (1,3)
+  |  C2-Pair: covered: (2,3)
+  |  MC/DC Coverage for Decision: 100.00%
+  |
+  |---> MC/DC Decision Region (LL:16) to (LL:22)
+  |
+  |  Number of Conditions: 2
+  |     Condition C1 --> (LL:16)
+  |     Condition C2 --> (LL:21)
+  |
+  |  Executed MC/DC Test Vectors:
+  |
+  |     C1, C2    Result
+  |  1 { F,  F  = F      }
+  |  2 { T,  -  = T      }
+  |  3 { F,  T  = T      }
+  |
+  |  C1-Pair: covered: (1,2)
+  |  C2-Pair: covered: (1,3)
+  |  MC/DC Coverage for Decision: 100.00%
+  |
+  |---> MC/DC Decision Region (LL:28) to (LL:34)
+  |
+  |  Number of Conditions: 2
+  |     Condition C1 --> (LL:28)
+  |     Condition C2 --> (LL:33)
+  |
+  |  Executed MC/DC Test Vectors:
+  |
+  |     C1, C2    Result
+  |  1 { F,  -  = F      }
+  |  2 { T,  F  = F      }
+  |  3 { T,  T  = T      }
+  |
+  |  C1-Pair: covered: (1,3)
+  |  C2-Pair: covered: (2,3)
+  |  MC/DC Coverage for Decision: 100.00%
+  |
+  ------------------
+   LL|      1|        say("yes");
+   LL|      6|    } else {
+   LL|      6|        say("no");
+   LL|      6|    }
+   LL|      7|}
+   LL|       |
+   LL|       |#[coverage(off)]
+   LL|       |fn main() {
+   LL|       |    nested_if_in_condition(true, false, false);
+   LL|       |    nested_if_in_condition(true, true, true);
+   LL|       |    nested_if_in_condition(true, false, true);
+   LL|       |    nested_if_in_condition(false, true, true);
+   LL|       |
+   LL|       |    doubly_nested_if_in_condition(true, false, false, true);
+   LL|       |    doubly_nested_if_in_condition(true, true, true, true);
+   LL|       |    doubly_nested_if_in_condition(true, false, true, true);
+   LL|       |    doubly_nested_if_in_condition(false, true, true, true);
+   LL|       |
+   LL|       |    nested_single_condition_decision(true, true);
+   LL|       |    nested_single_condition_decision(true, false);
+   LL|       |    nested_single_condition_decision(false, false);
+   LL|       |
+   LL|       |    nested_in_then_block_in_condition(false, false, false, false, false);
+   LL|       |    nested_in_then_block_in_condition(true, false, false, false, false);
+   LL|       |    nested_in_then_block_in_condition(true, true, false, false, false);
+   LL|       |    nested_in_then_block_in_condition(true, false, true, false, false);
+   LL|       |    nested_in_then_block_in_condition(true, false, true, true, false);
+   LL|       |    nested_in_then_block_in_condition(true, false, true, false, true);
+   LL|       |    nested_in_then_block_in_condition(true, false, true, true, true);
+   LL|       |}
+   LL|       |
+   LL|       |#[coverage(off)]
+   LL|       |fn say(message: &str) {
+   LL|       |    core::hint::black_box(message);
+   LL|       |}
+
diff --git a/tests/coverage/mcdc_nested_if.rs b/tests/coverage/mcdc_nested_if.rs
new file mode 100644
index 00000000000..3d869771f75
--- /dev/null
+++ b/tests/coverage/mcdc_nested_if.rs
@@ -0,0 +1,70 @@
+#![feature(coverage_attribute)]
+//@ edition: 2021
+//@ min-llvm-version: 18
+//@ compile-flags: -Zcoverage-options=mcdc
+//@ llvm-cov-flags: --show-mcdc
+
+fn nested_if_in_condition(a: bool, b: bool, c: bool) {
+    if a && if b || c { true } else { false } {
+        say("yes");
+    } else {
+        say("no");
+    }
+}
+
+fn doubly_nested_if_in_condition(a: bool, b: bool, c: bool, d: bool) {
+    if a && if b || if c && d { true } else { false } { false } else { true } {
+        say("yes");
+    } else {
+        say("no");
+    }
+}
+
+fn nested_single_condition_decision(a: bool, b: bool) {
+    // Decision with only 1 decision should not be instrumented by MCDC because
+    // branch-coverage is equivalent to MCDC coverage in this case, and we don't
+    // want to waste bitmap space for this.
+    if a && if b { false } else { true } {
+        say("yes");
+    } else {
+        say("no");
+    }
+}
+
+fn nested_in_then_block_in_condition(a: bool, b: bool, c: bool, d: bool, e: bool) {
+    if a && if b || c { if d && e { true } else { false } } else { false } {
+        say("yes");
+    } else {
+        say("no");
+    }
+}
+
+#[coverage(off)]
+fn main() {
+    nested_if_in_condition(true, false, false);
+    nested_if_in_condition(true, true, true);
+    nested_if_in_condition(true, false, true);
+    nested_if_in_condition(false, true, true);
+
+    doubly_nested_if_in_condition(true, false, false, true);
+    doubly_nested_if_in_condition(true, true, true, true);
+    doubly_nested_if_in_condition(true, false, true, true);
+    doubly_nested_if_in_condition(false, true, true, true);
+
+    nested_single_condition_decision(true, true);
+    nested_single_condition_decision(true, false);
+    nested_single_condition_decision(false, false);
+
+    nested_in_then_block_in_condition(false, false, false, false, false);
+    nested_in_then_block_in_condition(true, false, false, false, false);
+    nested_in_then_block_in_condition(true, true, false, false, false);
+    nested_in_then_block_in_condition(true, false, true, false, false);
+    nested_in_then_block_in_condition(true, false, true, true, false);
+    nested_in_then_block_in_condition(true, false, true, false, true);
+    nested_in_then_block_in_condition(true, false, true, true, true);
+}
+
+#[coverage(off)]
+fn say(message: &str) {
+    core::hint::black_box(message);
+}