diff --git a/compiler/rustc_codegen_gcc/src/builder.rs b/compiler/rustc_codegen_gcc/src/builder.rs
index 43cc46cfe682f..adf8f986ddd9b 100644
--- a/compiler/rustc_codegen_gcc/src/builder.rs
+++ b/compiler/rustc_codegen_gcc/src/builder.rs
@@ -25,7 +25,7 @@ use rustc_middle::ty::layout::{
     FnAbiError, FnAbiOfHelpers, FnAbiRequest, HasParamEnv, HasTyCtxt, LayoutError, LayoutOfHelpers,
     TyAndLayout,
 };
-use rustc_middle::ty::{ParamEnv, Ty, TyCtxt, Instance};
+use rustc_middle::ty::{Instance, ParamEnv, Ty, TyCtxt};
 use rustc_span::def_id::DefId;
 use rustc_span::Span;
 use rustc_target::abi::{
@@ -1736,6 +1736,41 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> {
     ) {
         unimplemented!();
     }
+
+    fn mcdc_parameters(
+        &mut self,
+        _fn_name: Self::Value,
+        _hash: Self::Value,
+        _bitmap_bytes: Self::Value,
+    ) -> Self::Value {
+        unimplemented!();
+    }
+
+    fn mcdc_tvbitmap_update(
+        &mut self,
+        _fn_name: Self::Value,
+        _hash: Self::Value,
+        _bitmap_bytes: Self::Value,
+        _bitmap_index: Self::Value,
+        _mcdc_temp: Self::Value,
+    ) {
+        unimplemented!();
+    }
+
+    fn mcdc_condbitmap_update(
+        &mut self,
+        _fn_name: Self::Value,
+        _hash: Self::Value,
+        _cond_loc: Self::Value,
+        _mcdc_temp: Self::Value,
+        _bool_value: Self::Value,
+    ) {
+        unimplemented!();
+    }
+
+    fn mcdc_condbitmap_reset(&mut self, _mcdc_temp: Self::Value) {
+        unimplemented!();
+    }
 }
 
 impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> {
diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs
index 1a32958d3627b..b06a181e955e6 100644
--- a/compiler/rustc_codegen_llvm/src/builder.rs
+++ b/compiler/rustc_codegen_llvm/src/builder.rs
@@ -17,7 +17,7 @@ use rustc_data_structures::small_c_str::SmallCStr;
 use rustc_hir::def_id::DefId;
 use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs;
 use rustc_middle::ty::layout::{
-    FnAbiError, FnAbiOfHelpers, FnAbiRequest, LayoutError, LayoutOfHelpers, TyAndLayout,
+    FnAbiError, FnAbiOfHelpers, FnAbiRequest, HasTyCtxt, LayoutError, LayoutOfHelpers, TyAndLayout,
 };
 use rustc_middle::ty::{self, Instance, Ty, TyCtxt};
 use rustc_session::config::OptLevel;
@@ -1238,6 +1238,133 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
         }
     }
 
+    fn mcdc_parameters(
+        &mut self,
+        fn_name: Self::Value,
+        hash: Self::Value,
+        bitmap_bytes: Self::Value,
+    ) -> &'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");
+
+        let llfn = unsafe { llvm::LLVMRustGetInstrProfMCDCParametersIntrinsic(self.cx().llmod) };
+        let llty = self.cx.type_func(
+            &[self.cx.type_ptr(), self.cx.type_i64(), self.cx.type_i32()],
+            self.cx.type_void(),
+        );
+        let args = &[fn_name, hash, bitmap_bytes];
+        let args = self.check_call("call", llty, llfn, args);
+
+        unsafe {
+            let _ = llvm::LLVMRustBuildCall(
+                self.llbuilder,
+                llty,
+                llfn,
+                args.as_ptr() as *const &llvm::Value,
+                args.len() as c_uint,
+                [].as_ptr(),
+                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
+        }
+    }
+
+    fn mcdc_tvbitmap_update(
+        &mut self,
+        fn_name: Self::Value,
+        hash: Self::Value,
+        bitmap_bytes: Self::Value,
+        bitmap_index: Self::Value,
+        mcdc_temp: Self::Value,
+    ) {
+        debug!(
+            "mcdc_tvbitmap_update() with args ({:?}, {:?}, {:?}, {:?}, {:?})",
+            fn_name, hash, bitmap_bytes, bitmap_index, mcdc_temp
+        );
+        assert!(llvm_util::get_version() >= (18, 0, 0), "MCDC intrinsics require LLVM 18 or later");
+
+        let llfn =
+            unsafe { llvm::LLVMRustGetInstrProfMCDCTVBitmapUpdateIntrinsic(self.cx().llmod) };
+        let llty = self.cx.type_func(
+            &[
+                self.cx.type_ptr(),
+                self.cx.type_i64(),
+                self.cx.type_i32(),
+                self.cx.type_i32(),
+                self.cx.type_ptr(),
+            ],
+            self.cx.type_void(),
+        );
+        let args = &[fn_name, hash, bitmap_bytes, bitmap_index, mcdc_temp];
+        let args = self.check_call("call", llty, llfn, args);
+        unsafe {
+            let _ = llvm::LLVMRustBuildCall(
+                self.llbuilder,
+                llty,
+                llfn,
+                args.as_ptr() as *const &llvm::Value,
+                args.len() as c_uint,
+                [].as_ptr(),
+                0 as c_uint,
+            );
+        }
+    }
+
+    fn mcdc_condbitmap_update(
+        &mut self,
+        fn_name: Self::Value,
+        hash: Self::Value,
+        cond_loc: Self::Value,
+        mcdc_temp: Self::Value,
+        bool_value: Self::Value,
+    ) {
+        debug!(
+            "mcdc_condbitmap_update() with args ({:?}, {:?}, {:?}, {:?}, {:?})",
+            fn_name, hash, cond_loc, mcdc_temp, bool_value
+        );
+        assert!(llvm_util::get_version() >= (18, 0, 0), "MCDC intrinsics require LLVM 18 or later");
+        let llfn = unsafe { llvm::LLVMRustGetInstrProfMCDCCondBitmapIntrinsic(self.cx().llmod) };
+        let llty = self.cx.type_func(
+            &[
+                self.cx.type_ptr(),
+                self.cx.type_i64(),
+                self.cx.type_i32(),
+                self.cx.type_ptr(),
+                self.cx.type_i1(),
+            ],
+            self.cx.type_void(),
+        );
+        let args = &[fn_name, hash, cond_loc, mcdc_temp, bool_value];
+        self.check_call("call", llty, llfn, args);
+        unsafe {
+            let _ = llvm::LLVMRustBuildCall(
+                self.llbuilder,
+                llty,
+                llfn,
+                args.as_ptr() as *const &llvm::Value,
+                args.len() as c_uint,
+                [].as_ptr(),
+                0 as c_uint,
+            );
+        }
+    }
+
+    fn mcdc_condbitmap_reset(&mut self, mcdc_temp: Self::Value) {
+        let i32_align = self.tcx().data_layout.i32_align.abi;
+        self.store(self.const_i32(0), mcdc_temp, i32_align);
+    }
+
     fn call(
         &mut self,
         llty: &'ll Type,
diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs
index 2af28146a51ea..12a846a49ec16 100644
--- a/compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs
+++ b/compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs
@@ -1,4 +1,6 @@
-use rustc_middle::mir::coverage::{CodeRegion, CounterId, CovTerm, ExpressionId, MappingKind};
+use rustc_middle::mir::coverage::{
+    CodeRegion, ConditionInfo, CounterId, CovTerm, DecisionInfo, ExpressionId, MappingKind,
+};
 
 /// Must match the layout of `LLVMRustCounterKind`.
 #[derive(Copy, Clone, Debug)]
@@ -99,6 +101,86 @@ pub enum RegionKind {
     /// associated with two counters, each representing the number of times the
     /// expression evaluates to true or false.
     BranchRegion = 4,
+
+    /// A DecisionRegion represents a top-level boolean expression and is
+    /// associated with a variable length bitmap index and condition number.
+    MCDCDecisionRegion = 5,
+
+    /// A Branch Region can be extended to include IDs to facilitate MC/DC.
+    MCDCBranchRegion = 6,
+}
+
+pub mod mcdc {
+    use rustc_middle::mir::coverage::{ConditionInfo, DecisionInfo};
+
+    /// Must match the layout of `LLVMRustMCDCDecisionParameters`.
+    #[repr(C)]
+    #[derive(Clone, Copy, Debug, Default)]
+    pub struct DecisionParameters {
+        bitmap_idx: u32,
+        conditions_num: u16,
+    }
+
+    // ConditionId in llvm is `unsigned int` at 18 while `int16_t` at [19](https://github.com/llvm/llvm-project/pull/81257)
+    type LLVMConditionId = i16;
+
+    /// Must match the layout of `LLVMRustMCDCBranchParameters`.
+    #[repr(C)]
+    #[derive(Clone, Copy, Debug, Default)]
+    pub struct BranchParameters {
+        condition_id: LLVMConditionId,
+        condition_ids: [LLVMConditionId; 2],
+    }
+
+    #[repr(C)]
+    #[derive(Clone, Copy, Debug)]
+    pub enum ParameterTag {
+        None = 0,
+        Decision = 1,
+        Branch = 2,
+    }
+    /// Same layout with `LLVMRustMCDCParameters`
+    #[repr(C)]
+    #[derive(Clone, Copy, Debug)]
+    pub struct Parameters {
+        tag: ParameterTag,
+        decision_params: DecisionParameters,
+        branch_params: BranchParameters,
+    }
+
+    impl Parameters {
+        pub fn none() -> Self {
+            Self {
+                tag: ParameterTag::None,
+                decision_params: Default::default(),
+                branch_params: Default::default(),
+            }
+        }
+        pub fn decision(decision_params: DecisionParameters) -> Self {
+            Self { tag: ParameterTag::Decision, decision_params, branch_params: Default::default() }
+        }
+        pub fn branch(branch_params: BranchParameters) -> Self {
+            Self { tag: ParameterTag::Branch, decision_params: Default::default(), branch_params }
+        }
+    }
+
+    impl From<ConditionInfo> for BranchParameters {
+        fn from(value: ConditionInfo) -> Self {
+            Self {
+                condition_id: value.condition_id.as_u32() as LLVMConditionId,
+                condition_ids: [
+                    value.false_next_id.as_u32() as LLVMConditionId,
+                    value.true_next_id.as_u32() as LLVMConditionId,
+                ],
+            }
+        }
+    }
+
+    impl From<DecisionInfo> for DecisionParameters {
+        fn from(value: DecisionInfo) -> Self {
+            Self { bitmap_idx: value.bitmap_idx, conditions_num: value.conditions_num }
+        }
+    }
 }
 
 /// This struct provides LLVM's representation of a "CoverageMappingRegion", encoded into the
@@ -122,6 +204,7 @@ pub struct CounterMappingRegion {
     /// for the false branch of the region.
     false_counter: Counter,
 
+    mcdc_params: mcdc::Parameters,
     /// An indirect reference to the source filename. In the LLVM Coverage Mapping Format, the
     /// file_id is an index into a function-specific `virtual_file_mapping` array of indexes
     /// that, in turn, are used to look up the filename for this region.
@@ -173,6 +256,26 @@ impl CounterMappingRegion {
                 end_line,
                 end_col,
             ),
+            MappingKind::MCDCBranch { true_term, false_term, mcdc_params } => {
+                Self::mcdc_branch_region(
+                    Counter::from_term(true_term),
+                    Counter::from_term(false_term),
+                    mcdc_params,
+                    local_file_id,
+                    start_line,
+                    start_col,
+                    end_line,
+                    end_col,
+                )
+            }
+            MappingKind::MCDCDecision(decision_info) => Self::decision_region(
+                decision_info,
+                local_file_id,
+                start_line,
+                start_col,
+                end_line,
+                end_col,
+            ),
         }
     }
 
@@ -187,6 +290,7 @@ impl CounterMappingRegion {
         Self {
             counter,
             false_counter: Counter::ZERO,
+            mcdc_params: mcdc::Parameters::none(),
             file_id,
             expanded_file_id: 0,
             start_line,
@@ -209,6 +313,7 @@ impl CounterMappingRegion {
         Self {
             counter,
             false_counter,
+            mcdc_params: mcdc::Parameters::none(),
             file_id,
             expanded_file_id: 0,
             start_line,
@@ -219,6 +324,54 @@ impl CounterMappingRegion {
         }
     }
 
+    pub(crate) fn mcdc_branch_region(
+        counter: Counter,
+        false_counter: Counter,
+        condition_info: ConditionInfo,
+        file_id: u32,
+        start_line: u32,
+        start_col: u32,
+        end_line: u32,
+        end_col: u32,
+    ) -> Self {
+        Self {
+            counter,
+            false_counter,
+            mcdc_params: mcdc::Parameters::branch(condition_info.into()),
+            file_id,
+            expanded_file_id: 0,
+            start_line,
+            start_col,
+            end_line,
+            end_col,
+            kind: RegionKind::MCDCBranchRegion,
+        }
+    }
+
+    pub(crate) fn decision_region(
+        decision_info: DecisionInfo,
+        file_id: u32,
+        start_line: u32,
+        start_col: u32,
+        end_line: u32,
+        end_col: u32,
+    ) -> Self {
+        let mcdc_params = mcdc::Parameters::decision(decision_info.into());
+
+        Self {
+            counter: Counter::ZERO,
+            false_counter: Counter::ZERO,
+            mcdc_params,
+            file_id,
+            expanded_file_id: 0,
+            start_line,
+            start_col,
+            end_line,
+            end_col,
+            kind: RegionKind::MCDCDecisionRegion,
+        }
+    }
+
     // This function might be used in the future; the LLVM API is still evolving, as is coverage
     // support.
     #[allow(dead_code)]
@@ -233,6 +386,7 @@ impl CounterMappingRegion {
         Self {
             counter: Counter::ZERO,
             false_counter: Counter::ZERO,
+            mcdc_params: mcdc::Parameters::none(),
             file_id,
             expanded_file_id,
             start_line,
@@ -256,6 +410,7 @@ impl CounterMappingRegion {
         Self {
             counter: Counter::ZERO,
             false_counter: Counter::ZERO,
+            mcdc_params: mcdc::Parameters::none(),
             file_id,
             expanded_file_id: 0,
             start_line,
@@ -280,6 +435,7 @@ impl CounterMappingRegion {
         Self {
             counter,
             false_counter: Counter::ZERO,
+            mcdc_params: mcdc::Parameters::none(),
             file_id,
             expanded_file_id: 0,
             start_line,
diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs
index d85d9411f03b6..291e53c2904f0 100644
--- a/compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs
+++ b/compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs
@@ -1,5 +1,4 @@
 use crate::coverageinfo::ffi::{Counter, CounterExpression, ExprKind};
-
 use rustc_data_structures::captures::Captures;
 use rustc_data_structures::fx::FxIndexSet;
 use rustc_index::bit_set::BitSet;
@@ -74,6 +73,7 @@ impl<'tcx> FunctionCoverageCollector<'tcx> {
         Self {
             function_coverage_info,
             is_used,
+
             counters_seen: BitSet::new_empty(num_counters),
             expressions_seen,
         }
diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs
index 140566e8da9bb..d9d940a84ae4e 100644
--- a/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs
+++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs
@@ -13,7 +13,7 @@ use rustc_codegen_ssa::traits::{
 use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
 use rustc_llvm::RustString;
 use rustc_middle::bug;
-use rustc_middle::mir::coverage::CoverageKind;
+use rustc_middle::mir::coverage::{CoverageKind, FunctionCoverageInfo};
 use rustc_middle::ty::layout::HasTyCtxt;
 use rustc_middle::ty::Instance;
 use rustc_target::abi::Align;
@@ -30,6 +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>>,
 }
 
 impl<'ll, 'tcx> CrateCoverageContext<'ll, 'tcx> {
@@ -37,6 +38,7 @@ impl<'ll, 'tcx> CrateCoverageContext<'ll, 'tcx> {
         Self {
             function_coverage_map: Default::default(),
             pgo_func_name_var_map: Default::default(),
+            mcdc_condition_bitmap_map: Default::default(),
         }
     }
 
@@ -45,6 +47,12 @@ impl<'ll, 'tcx> CrateCoverageContext<'ll, 'tcx> {
     ) -> FxIndexMap<Instance<'tcx>, FunctionCoverageCollector<'tcx>> {
         self.function_coverage_map.replace(FxIndexMap::default())
     }
+
+    /// 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()
+    }
 }
 
 // These methods used to be part of trait `CoverageInfoMethods`, which no longer
@@ -90,6 +98,10 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> {
             return;
         };
 
+        if function_coverage_info.mcdc_bitmap_bytes > 0 {
+            ensure_mcdc_parameters(bx, instance, function_coverage_info);
+        }
+
         let Some(coverage_context) = bx.coverage_context() else { return };
         let mut coverage_map = coverage_context.function_coverage_map.borrow_mut();
         let func_coverage = coverage_map
@@ -131,10 +143,67 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> {
             CoverageKind::ExpressionUsed { id } => {
                 func_coverage.mark_expression_id_seen(id);
             }
+            CoverageKind::CondBitmapUpdate { id, value, .. } => {
+                drop(coverage_map);
+                assert_ne!(
+                    id.as_u32(),
+                    0,
+                    "ConditionId of evaluated conditions should never be zero"
+                );
+                let cond_bitmap = coverage_context
+                    .try_get_mcdc_condition_bitmap(&instance)
+                    .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);
+                let fn_name = bx.get_pgo_func_name_var(instance);
+                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 } => {
+                drop(coverage_map);
+                let cond_bitmap = coverage_context
+                                    .try_get_mcdc_condition_bitmap(&instance)
+                                    .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");
+                assert!(
+                    bitmap_bytes <= function_coverage_info.mcdc_bitmap_bytes,
+                    "bitmap length disagreement: query says {bitmap_bytes} but function info only has {}",
+                    function_coverage_info.mcdc_bitmap_bytes
+                );
+
+                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(bitmap_bytes);
+                let bitmap_index = bx.const_u32(bitmap_idx);
+                bx.mcdc_tvbitmap_update(fn_name, hash, bitmap_bytes, bitmap_index, cond_bitmap);
+                bx.mcdc_condbitmap_reset(cond_bitmap);
+            }
         }
     }
 }
 
+fn ensure_mcdc_parameters<'ll, 'tcx>(
+    bx: &mut Builder<'_, 'll, 'tcx>,
+    instance: Instance<'tcx>,
+    function_coverage_info: &FunctionCoverageInfo,
+) {
+    let Some(cx) = bx.coverage_context() else { return };
+    if cx.mcdc_condition_bitmap_map.borrow().contains_key(&instance) {
+        return;
+    }
+
+    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);
+    bx.coverage_context()
+        .expect("already checked above")
+        .mcdc_condition_bitmap_map
+        .borrow_mut()
+        .insert(instance, cond_bitmap);
+}
+
 /// Calls llvm::createPGOFuncNameVar() with the given function instance's
 /// mangled function name. The LLVM API returns an llvm::GlobalVariable
 /// containing the function name, with the specific variable name and linkage
diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs
index 284bc74d5c434..986119a4ff46b 100644
--- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs
+++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs
@@ -1631,6 +1631,10 @@ extern "C" {
 
     // Miscellaneous instructions
     pub fn LLVMRustGetInstrProfIncrementIntrinsic(M: &Module) -> &Value;
+    pub fn LLVMRustGetInstrProfMCDCParametersIntrinsic(M: &Module) -> &Value;
+    pub fn LLVMRustGetInstrProfMCDCTVBitmapUpdateIntrinsic(M: &Module) -> &Value;
+    pub fn LLVMRustGetInstrProfMCDCCondBitmapIntrinsic(M: &Module) -> &Value;
+
     pub fn LLVMRustBuildCall<'a>(
         B: &Builder<'a>,
         Ty: &'a Type,
diff --git a/compiler/rustc_codegen_ssa/src/traits/builder.rs b/compiler/rustc_codegen_ssa/src/traits/builder.rs
index 6c8dcc5b69001..842526a6b759e 100644
--- a/compiler/rustc_codegen_ssa/src/traits/builder.rs
+++ b/compiler/rustc_codegen_ssa/src/traits/builder.rs
@@ -382,6 +382,33 @@ pub trait BuilderMethods<'a, 'tcx>:
         index: Self::Value,
     );
 
+    fn mcdc_parameters(
+        &mut self,
+        fn_name: Self::Value,
+        hash: Self::Value,
+        bitmap_bytes: Self::Value,
+    ) -> Self::Value;
+
+    fn mcdc_condbitmap_update(
+        &mut self,
+        fn_name: Self::Value,
+        hash: Self::Value,
+        cond_loc: Self::Value,
+        mcdc_temp: Self::Value,
+        bool_value: Self::Value,
+    );
+
+    fn mcdc_tvbitmap_update(
+        &mut self,
+        fn_name: Self::Value,
+        hash: Self::Value,
+        bitmap_bytes: Self::Value,
+        bitmap_index: Self::Value,
+        mcdc_temp: Self::Value,
+    );
+
+    fn mcdc_condbitmap_reset(&mut self, mcdc_temp: Self::Value);
+
     fn call(
         &mut self,
         llty: Self::Type,
diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs
index d2fb65b5d4f8e..3de1b98aa4e56 100644
--- a/compiler/rustc_interface/src/tests.rs
+++ b/compiler/rustc_interface/src/tests.rs
@@ -759,7 +759,7 @@ fn test_unstable_options_tracking_hash() {
     );
     tracked!(codegen_backend, Some("abc".to_string()));
     tracked!(collapse_macro_debuginfo, CollapseMacroDebuginfo::Yes);
-    tracked!(coverage_options, CoverageOptions { branch: true });
+    tracked!(coverage_options, CoverageOptions { branch: true, mcdc: true });
     tracked!(crate_attr, vec!["abc".to_string()]);
     tracked!(cross_crate_inline_threshold, InliningThreshold::Always);
     tracked!(debug_info_for_profiling, true);
diff --git a/compiler/rustc_llvm/llvm-wrapper/CoverageMappingWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/CoverageMappingWrapper.cpp
index 0998b463a886c..e842f47f48c23 100644
--- a/compiler/rustc_llvm/llvm-wrapper/CoverageMappingWrapper.cpp
+++ b/compiler/rustc_llvm/llvm-wrapper/CoverageMappingWrapper.cpp
@@ -4,8 +4,6 @@
 #include "llvm/ProfileData/Coverage/CoverageMappingWriter.h"
 #include "llvm/ProfileData/InstrProf.h"
 
-#include <iostream>
-
 using namespace llvm;
 
 // FFI equivalent of enum `llvm::coverage::Counter::CounterKind`
@@ -43,6 +41,8 @@ enum class LLVMRustCounterMappingRegionKind {
   SkippedRegion = 2,
   GapRegion = 3,
   BranchRegion = 4,
+  MCDCDecisionRegion = 5,
+  MCDCBranchRegion = 6
 };
 
 static coverage::CounterMappingRegion::RegionKind
@@ -58,15 +58,102 @@ fromRust(LLVMRustCounterMappingRegionKind Kind) {
     return coverage::CounterMappingRegion::GapRegion;
   case LLVMRustCounterMappingRegionKind::BranchRegion:
     return coverage::CounterMappingRegion::BranchRegion;
+#if LLVM_VERSION_GE(18, 0)
+  case LLVMRustCounterMappingRegionKind::MCDCDecisionRegion:
+    return coverage::CounterMappingRegion::MCDCDecisionRegion;
+  case LLVMRustCounterMappingRegionKind::MCDCBranchRegion:
+    return coverage::CounterMappingRegion::MCDCBranchRegion;
+#else
+  case LLVMRustCounterMappingRegionKind::MCDCDecisionRegion:
+    break;
+  case LLVMRustCounterMappingRegionKind::MCDCBranchRegion:
+    break;
+#endif
   }
   report_fatal_error("Bad LLVMRustCounterMappingRegionKind!");
 }
 
+enum LLVMRustMCDCParametersTag {
+  None = 0,
+  Decision = 1,
+  Branch = 2,
+};
+
+struct LLVMRustMCDCDecisionParameters {
+  uint32_t BitmapIdx;
+  uint16_t NumConditions;
+};
+
+struct LLVMRustMCDCBranchParameters {
+  int16_t ConditionID;
+  int16_t ConditionIDs[2];
+};
+
+struct LLVMRustMCDCParameters {
+  LLVMRustMCDCParametersTag Tag;
+  LLVMRustMCDCDecisionParameters DecisionParameters;
+  LLVMRustMCDCBranchParameters BranchParameters;
+};
+
+// LLVM representations for `MCDCParameters` evolved from LLVM 18 to 19.
+// Look at representations in 18
+// https://github.com/rust-lang/llvm-project/blob/66a2881a/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h#L253-L263
+// and representations in 19
+// https://github.com/llvm/llvm-project/blob/843cc474faefad1d639f4c44c1cf3ad7dbda76c8/llvm/include/llvm/ProfileData/Coverage/MCDCTypes.h
+#if LLVM_VERSION_GE(18, 0) && LLVM_VERSION_LT(19, 0)
+static coverage::CounterMappingRegion::MCDCParameters
+fromRust(LLVMRustMCDCParameters Params) {
+  auto parameter = coverage::CounterMappingRegion::MCDCParameters{};
+  switch (Params.Tag) {
+  case LLVMRustMCDCParametersTag::None:
+    return parameter;
+  case LLVMRustMCDCParametersTag::Decision:
+    parameter.BitmapIdx =
+        static_cast<unsigned>(Params.DecisionParameters.BitmapIdx),
+    parameter.NumConditions =
+        static_cast<unsigned>(Params.DecisionParameters.NumConditions);
+    return parameter;
+  case LLVMRustMCDCParametersTag::Branch:
+    parameter.ID = static_cast<coverage::CounterMappingRegion::MCDCConditionID>(
+        Params.BranchParameters.ConditionID),
+    parameter.FalseID =
+        static_cast<coverage::CounterMappingRegion::MCDCConditionID>(
+            Params.BranchParameters.ConditionIDs[0]),
+    parameter.TrueID =
+        static_cast<coverage::CounterMappingRegion::MCDCConditionID>(
+            Params.BranchParameters.ConditionIDs[1]);
+    return parameter;
+  }
+  report_fatal_error("Bad LLVMRustMCDCParametersTag!");
+}
+#elif LLVM_VERSION_GE(19, 0)
+static coverage::mcdc::Parameters fromRust(LLVMRustMCDCParameters Params) {
+  switch (Params.Tag) {
+  case LLVMRustMCDCParametersTag::None:
+    return std::monostate();
+  case LLVMRustMCDCParametersTag::Decision:
+    return coverage::mcdc::DecisionParameters(
+        Params.DecisionParameters.BitmapIdx,
+        Params.DecisionParameters.NumConditions);
+  case LLVMRustMCDCParametersTag::Branch:
+    return coverage::mcdc::BranchParameters(
+        static_cast<coverage::mcdc::ConditionID>(
+            Params.BranchParameters.ConditionID),
+        {static_cast<coverage::mcdc::ConditionID>(
+             Params.BranchParameters.ConditionIDs[0]),
+         static_cast<coverage::mcdc::ConditionID>(
+             Params.BranchParameters.ConditionIDs[1])});
+  }
+  report_fatal_error("Bad LLVMRustMCDCParametersTag!");
+}
+#endif
+
 // FFI equivalent of struct `llvm::coverage::CounterMappingRegion`
 // https://github.com/rust-lang/llvm-project/blob/ea6fa9c2/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h#L211-L304
 struct LLVMRustCounterMappingRegion {
   LLVMRustCounter Count;
   LLVMRustCounter FalseCount;
+  LLVMRustMCDCParameters MCDCParameters;
   uint32_t FileID;
   uint32_t ExpandedFileID;
   uint32_t LineStart;
@@ -135,7 +222,8 @@ extern "C" void LLVMRustCoverageWriteMappingToBuffer(
     MappingRegions.emplace_back(
         fromRust(Region.Count), fromRust(Region.FalseCount),
 #if LLVM_VERSION_GE(18, 0) && LLVM_VERSION_LT(19, 0)
-        coverage::CounterMappingRegion::MCDCParameters{},
+        // LLVM 19 may move this argument to last.
+        fromRust(Region.MCDCParameters),
 #endif
         Region.FileID, Region.ExpandedFileID, // File IDs, then region info.
         Region.LineStart, Region.ColumnStart, Region.LineEnd, Region.ColumnEnd,
diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp
index 8ec1f5a99e7ca..b912a1ff244ad 100644
--- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp
+++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp
@@ -23,6 +23,7 @@
 #include "llvm/Bitcode/BitcodeWriter.h"
 #include "llvm/Support/Signals.h"
 
+#include <cassert>
 #include <iostream>
 
 // for raw `write` in the bad-alloc handler
@@ -1528,6 +1529,36 @@ extern "C" LLVMValueRef LLVMRustGetInstrProfIncrementIntrinsic(LLVMModuleRef M)
               (llvm::Intrinsic::ID)llvm::Intrinsic::instrprof_increment));
 }
 
+extern "C" LLVMValueRef LLVMRustGetInstrProfMCDCParametersIntrinsic(LLVMModuleRef M) {
+  assert(LLVM_VERSION_GE(18, 0));
+#if LLVM_VERSION_GE(18, 0)
+  return wrap(llvm::Intrinsic::getDeclaration(unwrap(M),
+              (llvm::Intrinsic::ID)llvm::Intrinsic::instrprof_mcdc_parameters));
+#else // Just make the wrapper can be compiled
+  return LLVMRustGetInstrProfIncrementIntrinsic(M);
+#endif
+}
+
+extern "C" LLVMValueRef LLVMRustGetInstrProfMCDCTVBitmapUpdateIntrinsic(LLVMModuleRef M) {
+  assert(LLVM_VERSION_GE(18, 0));
+#if LLVM_VERSION_GE(18, 0)
+  return wrap(llvm::Intrinsic::getDeclaration(
+      unwrap(M), llvm::Intrinsic::instrprof_mcdc_tvbitmap_update));
+#else // Just make the wrapper can be compiled
+  return LLVMRustGetInstrProfIncrementIntrinsic(M);
+#endif
+}
+
+extern "C" LLVMValueRef LLVMRustGetInstrProfMCDCCondBitmapIntrinsic(LLVMModuleRef M) {
+  assert(LLVM_VERSION_GE(18, 0));
+#if LLVM_VERSION_GE(18, 0)
+  return wrap(llvm::Intrinsic::getDeclaration(
+      unwrap(M), llvm::Intrinsic::instrprof_mcdc_condbitmap_update));
+#else // Just make the wrapper can be compiled
+  return LLVMRustGetInstrProfIncrementIntrinsic(M);
+#endif
+}
+
 extern "C" LLVMValueRef LLVMRustBuildMemCpy(LLVMBuilderRef B,
                                             LLVMValueRef Dst, unsigned DstAlign,
                                             LLVMValueRef Src, unsigned SrcAlign,
diff --git a/compiler/rustc_middle/src/mir/coverage.rs b/compiler/rustc_middle/src/mir/coverage.rs
index 582a180668816..d900aac0dc03b 100644
--- a/compiler/rustc_middle/src/mir/coverage.rs
+++ b/compiler/rustc_middle/src/mir/coverage.rs
@@ -51,6 +51,23 @@ rustc_index::newtype_index! {
     pub struct ExpressionId {}
 }
 
+rustc_index::newtype_index! {
+    /// ID of a mcdc condition. Used by llvm to check mcdc coverage.
+    ///
+    /// Note that LLVM handles condition IDs as `int16_t`, so there is no need
+    /// to use a larger representation on the Rust side.
+    #[derive(HashStable)]
+    #[encodable]
+    #[orderable]
+    #[max = 0xFFFF]
+    #[debug_format = "ConditionId({})"]
+    pub struct ConditionId {}
+}
+
+impl ConditionId {
+    pub const NONE: Self = Self::from_u32(0);
+}
+
 /// Enum that can hold a constant zero value, the ID of an physical coverage
 /// counter, or the ID of a coverage-counter expression.
 ///
@@ -106,6 +123,22 @@ pub enum CoverageKind {
     /// mappings. Intermediate expressions with no direct mappings are
     /// retained/zeroed based on whether they are transitively used.)
     ExpressionUsed { id: ExpressionId },
+
+    /// Marks the point in MIR control flow represented by a evaluated condition.
+    ///
+    /// This is eventually lowered to `llvm.instrprof.mcdc.condbitmap.update` in LLVM IR.
+    ///
+    /// If this statement does not survive MIR optimizations, the condition would never be
+    /// taken as evaluated.
+    CondBitmapUpdate { id: ConditionId, value: bool },
+
+    /// Marks the point in MIR control flow represented by a evaluated decision.
+    ///
+    /// This is eventually lowered to `llvm.instrprof.mcdc.tvbitmap.update` in LLVM IR.
+    ///
+    /// If this statement does not survive MIR optimizations, the decision would never be
+    /// taken as evaluated.
+    TestVectorBitmapUpdate { bitmap_idx: u32 },
 }
 
 impl Debug for CoverageKind {
@@ -116,6 +149,12 @@ 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)
+            }
+            TestVectorBitmapUpdate { bitmap_idx } => {
+                write!(fmt, "TestVectorUpdate({:?})", bitmap_idx)
+            }
         }
     }
 }
@@ -172,16 +211,23 @@ pub enum MappingKind {
     Code(CovTerm),
     /// Associates a branch region with separate counters for true and false.
     Branch { true_term: CovTerm, false_term: CovTerm },
+    /// Associates a branch region with separate counters for true and false.
+    MCDCBranch { true_term: CovTerm, false_term: CovTerm, mcdc_params: ConditionInfo },
+    /// Associates a decision region with a bitmap and number of conditions.
+    MCDCDecision(DecisionInfo),
 }
 
 impl MappingKind {
     /// Iterator over all coverage terms in this mapping kind.
     pub fn terms(&self) -> impl Iterator<Item = CovTerm> {
-        let one = |a| std::iter::once(a).chain(None);
-        let two = |a, b| std::iter::once(a).chain(Some(b));
+        let zero = || None.into_iter().chain(None);
+        let one = |a| Some(a).into_iter().chain(None);
+        let two = |a, b| Some(a).into_iter().chain(Some(b));
         match *self {
             Self::Code(term) => one(term),
             Self::Branch { true_term, false_term } => two(true_term, false_term),
+            Self::MCDCBranch { true_term, false_term, .. } => two(true_term, false_term),
+            Self::MCDCDecision(_) => zero(),
         }
     }
 
@@ -193,6 +239,12 @@ impl MappingKind {
             Self::Branch { true_term, false_term } => {
                 Self::Branch { true_term: map_fn(true_term), false_term: map_fn(false_term) }
             }
+            Self::MCDCBranch { true_term, false_term, mcdc_params } => Self::MCDCBranch {
+                true_term: map_fn(true_term),
+                false_term: map_fn(false_term),
+                mcdc_params,
+            },
+            Self::MCDCDecision(param) => Self::MCDCDecision(param),
         }
     }
 }
@@ -212,7 +264,7 @@ pub struct Mapping {
 pub struct FunctionCoverageInfo {
     pub function_source_hash: u64,
     pub num_counters: usize,
-
+    pub mcdc_bitmap_bytes: u32,
     pub expressions: IndexVec<ExpressionId, Expression>,
     pub mappings: Vec<Mapping>,
 }
@@ -226,6 +278,8 @@ pub struct BranchInfo {
     /// data structures without having to scan the entire body first.
     pub num_block_markers: usize,
     pub branch_spans: Vec<BranchSpan>,
+    pub mcdc_branch_spans: Vec<MCDCBranchSpan>,
+    pub mcdc_decision_spans: Vec<MCDCDecisionSpan>,
 }
 
 #[derive(Clone, Debug)]
@@ -235,3 +289,45 @@ pub struct BranchSpan {
     pub true_marker: BlockMarkerId,
     pub false_marker: BlockMarkerId,
 }
+
+#[derive(Copy, Clone, Debug)]
+#[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
+pub struct ConditionInfo {
+    pub condition_id: ConditionId,
+    pub true_next_id: ConditionId,
+    pub false_next_id: ConditionId,
+}
+
+impl Default for ConditionInfo {
+    fn default() -> Self {
+        Self {
+            condition_id: ConditionId::NONE,
+            true_next_id: ConditionId::NONE,
+            false_next_id: ConditionId::NONE,
+        }
+    }
+}
+
+#[derive(Clone, Debug)]
+#[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
+pub struct MCDCBranchSpan {
+    pub span: Span,
+    pub condition_info: ConditionInfo,
+    pub true_marker: BlockMarkerId,
+    pub false_marker: BlockMarkerId,
+}
+
+#[derive(Copy, Clone, Debug)]
+#[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
+pub struct DecisionInfo {
+    pub bitmap_idx: u32,
+    pub conditions_num: u16,
+}
+
+#[derive(Clone, Debug)]
+#[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
+pub struct MCDCDecisionSpan {
+    pub span: Span,
+    pub conditions_num: usize,
+    pub end_markers: Vec<BlockMarkerId>,
+}
diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs
index 41df2e3b5875a..c18b1d350e8a7 100644
--- a/compiler/rustc_middle/src/mir/pretty.rs
+++ b/compiler/rustc_middle/src/mir/pretty.rs
@@ -475,7 +475,8 @@ fn write_coverage_branch_info(
     branch_info: &coverage::BranchInfo,
     w: &mut dyn io::Write,
 ) -> io::Result<()> {
-    let coverage::BranchInfo { branch_spans, .. } = branch_info;
+    let coverage::BranchInfo { branch_spans, mcdc_branch_spans, mcdc_decision_spans, .. } =
+        branch_info;
 
     for coverage::BranchSpan { span, true_marker, false_marker } in branch_spans {
         writeln!(
@@ -483,7 +484,26 @@ fn write_coverage_branch_info(
             "{INDENT}coverage branch {{ true: {true_marker:?}, false: {false_marker:?} }} => {span:?}",
         )?;
     }
-    if !branch_spans.is_empty() {
+
+    for coverage::MCDCBranchSpan { span, condition_info, true_marker, false_marker } in
+        mcdc_branch_spans
+    {
+        writeln!(
+            w,
+            "{INDENT}coverage mcdc branch {{ condition_id: {:?}, true: {true_marker:?}, false: {false_marker:?} }} => {span:?}",
+            condition_info.condition_id
+        )?;
+    }
+
+    for coverage::MCDCDecisionSpan { span, conditions_num, end_markers } in mcdc_decision_spans {
+        writeln!(
+            w,
+            "{INDENT}coverage mcdc decision {{ conditions_num: {conditions_num:?}, end: {end_markers:?} }} => {span:?}"
+        )?;
+    }
+
+    if !branch_spans.is_empty() || !mcdc_branch_spans.is_empty() || !mcdc_decision_spans.is_empty()
+    {
         writeln!(w)?;
     }
 
diff --git a/compiler/rustc_middle/src/mir/query.rs b/compiler/rustc_middle/src/mir/query.rs
index 0f62212743086..c6d7cfd325ea8 100644
--- a/compiler/rustc_middle/src/mir/query.rs
+++ b/compiler/rustc_middle/src/mir/query.rs
@@ -361,4 +361,8 @@ pub struct CoverageIdsInfo {
     /// InstrumentCoverage MIR pass, if the highest-numbered counter increments
     /// were removed by MIR optimizations.
     pub max_counter_id: mir::coverage::CounterId,
+
+    /// Coverage codegen for mcdc needs to know the size of the global bitmap so that it can
+    /// set the `bytemap-bytes` argument of the `llvm.instrprof.mcdc.tvbitmap.update` intrinsic.
+    pub mcdc_bitmap_bytes: u32,
 }
diff --git a/compiler/rustc_middle/src/ty/structural_impls.rs b/compiler/rustc_middle/src/ty/structural_impls.rs
index 8231c0214cb71..11584320f2852 100644
--- a/compiler/rustc_middle/src/ty/structural_impls.rs
+++ b/compiler/rustc_middle/src/ty/structural_impls.rs
@@ -409,6 +409,7 @@ TrivialTypeTraversalImpls! {
     crate::mir::coverage::BlockMarkerId,
     crate::mir::coverage::CounterId,
     crate::mir::coverage::ExpressionId,
+    crate::mir::coverage::ConditionId,
     crate::mir::Local,
     crate::mir::Promoted,
     crate::traits::Reveal,
diff --git a/compiler/rustc_mir_build/messages.ftl b/compiler/rustc_mir_build/messages.ftl
index 1de691f32a70c..34440c60cf378 100644
--- a/compiler/rustc_mir_build/messages.ftl
+++ b/compiler/rustc_mir_build/messages.ftl
@@ -97,6 +97,8 @@ mir_build_deref_raw_pointer_requires_unsafe_unsafe_op_in_unsafe_fn_allowed =
     .note = raw pointers may be null, dangling or unaligned; they can violate aliasing rules and cause data races: all of these are undefined behavior
     .label = dereference of raw pointer
 
+mir_build_exceeds_mcdc_condition_num_limit =  Conditions number of the decision ({$conditions_num}) exceeds limit ({$max_conditions_num}). MCDC analysis will not count this expression.
+
 mir_build_extern_static_requires_unsafe =
     use of extern static is unsafe and requires unsafe block
     .note = extern statics are not controlled by the Rust type system: invalid data, aliasing violations or data races will cause undefined behavior
diff --git a/compiler/rustc_mir_build/src/build/coverageinfo.rs b/compiler/rustc_mir_build/src/build/coverageinfo.rs
index ab0043906b19f..57f809ef7cf8a 100644
--- a/compiler/rustc_mir_build/src/build/coverageinfo.rs
+++ b/compiler/rustc_mir_build/src/build/coverageinfo.rs
@@ -1,14 +1,20 @@
 use std::assert_matches::assert_matches;
 use std::collections::hash_map::Entry;
+use std::collections::VecDeque;
 
 use rustc_data_structures::fx::FxHashMap;
-use rustc_middle::mir::coverage::{BlockMarkerId, BranchSpan, CoverageKind};
+use rustc_middle::mir::coverage::{
+    BlockMarkerId, BranchSpan, ConditionId, ConditionInfo, CoverageKind, MCDCBranchSpan,
+    MCDCDecisionSpan,
+};
 use rustc_middle::mir::{self, BasicBlock, UnOp};
-use rustc_middle::thir::{ExprId, ExprKind, Thir};
+use rustc_middle::thir::{ExprId, ExprKind, LogicalOp, Thir};
 use rustc_middle::ty::TyCtxt;
 use rustc_span::def_id::LocalDefId;
+use rustc_span::Span;
 
 use crate::build::Builder;
+use crate::errors::MCDCExceedsConditionNumLimit;
 
 pub(crate) struct BranchInfoBuilder {
     /// Maps condition expressions to their enclosing `!`, for better instrumentation.
@@ -16,6 +22,9 @@ pub(crate) struct BranchInfoBuilder {
 
     num_block_markers: usize,
     branch_spans: Vec<BranchSpan>,
+    mcdc_branch_spans: Vec<MCDCBranchSpan>,
+    mcdc_decision_spans: Vec<MCDCDecisionSpan>,
+    mcdc_state: Option<MCDCState>,
 }
 
 #[derive(Clone, Copy)]
@@ -33,7 +42,14 @@ impl BranchInfoBuilder {
     /// is enabled and `def_id` represents a function that is eligible for coverage.
     pub(crate) fn new_if_enabled(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<Self> {
         if tcx.sess.instrument_coverage_branch() && tcx.is_eligible_for_coverage(def_id) {
-            Some(Self { nots: FxHashMap::default(), num_block_markers: 0, branch_spans: vec![] })
+            Some(Self {
+                nots: FxHashMap::default(),
+                num_block_markers: 0,
+                branch_spans: vec![],
+                mcdc_branch_spans: vec![],
+                mcdc_decision_spans: vec![],
+                mcdc_state: MCDCState::new_if_enabled(tcx),
+            })
         } else {
             None
         }
@@ -79,6 +95,55 @@ impl BranchInfoBuilder {
         }
     }
 
+    fn record_conditions_operation(&mut self, logical_op: LogicalOp, span: Span) {
+        if let Some(mcdc_state) = self.mcdc_state.as_mut() {
+            mcdc_state.record_conditions(logical_op, span);
+        }
+    }
+
+    fn fetch_condition_info(
+        &mut self,
+        tcx: TyCtxt<'_>,
+        true_marker: BlockMarkerId,
+        false_marker: BlockMarkerId,
+    ) -> Option<ConditionInfo> {
+        let mcdc_state = self.mcdc_state.as_mut()?;
+        let (mut condition_info, decision_result) =
+            mcdc_state.take_condition(true_marker, false_marker);
+        if let Some(decision) = decision_result {
+            match decision.conditions_num {
+                0 => {
+                    unreachable!("Decision with no condition is not expected");
+                }
+                1..=MAX_CONDITIONS_NUM_IN_DECISION => {
+                    self.mcdc_decision_spans.push(decision);
+                }
+                _ => {
+                    // Do not generate mcdc mappings and statements for decisions with too many conditions.
+                    let rebase_idx = self.mcdc_branch_spans.len() - decision.conditions_num + 1;
+                    let to_normal_branches = self.mcdc_branch_spans.split_off(rebase_idx);
+                    self.branch_spans.extend(to_normal_branches.into_iter().map(
+                        |MCDCBranchSpan { span, true_marker, false_marker, .. }| BranchSpan {
+                            span,
+                            true_marker,
+                            false_marker,
+                        },
+                    ));
+
+                    // ConditionInfo of this branch shall also be reset.
+                    condition_info = None;
+
+                    tcx.dcx().emit_warn(MCDCExceedsConditionNumLimit {
+                        span: decision.span,
+                        conditions_num: decision.conditions_num,
+                        max_conditions_num: MAX_CONDITIONS_NUM_IN_DECISION,
+                    });
+                }
+            }
+        }
+        condition_info
+    }
+
     fn next_block_marker_id(&mut self) -> BlockMarkerId {
         let id = BlockMarkerId::from_usize(self.num_block_markers);
         self.num_block_markers += 1;
@@ -86,14 +151,167 @@ impl BranchInfoBuilder {
     }
 
     pub(crate) fn into_done(self) -> Option<Box<mir::coverage::BranchInfo>> {
-        let Self { nots: _, num_block_markers, branch_spans } = self;
+        let Self {
+            nots: _,
+            num_block_markers,
+            branch_spans,
+            mcdc_branch_spans,
+            mcdc_decision_spans,
+            ..
+        } = self;
 
         if num_block_markers == 0 {
             assert!(branch_spans.is_empty());
             return None;
         }
 
-        Some(Box::new(mir::coverage::BranchInfo { num_block_markers, branch_spans }))
+        Some(Box::new(mir::coverage::BranchInfo {
+            num_block_markers,
+            branch_spans,
+            mcdc_branch_spans,
+            mcdc_decision_spans,
+        }))
+    }
+}
+
+/// The MCDC bitmap scales exponentially (2^n) based on the number of conditions seen,
+/// So llvm sets a maximum value prevents the bitmap footprint from growing too large without the user's knowledge.
+/// 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 {
+    /// To construct condition evaluation tree.
+    decision_stack: VecDeque<ConditionInfo>,
+    processing_decision: Option<MCDCDecisionSpan>,
+}
+
+impl MCDCState {
+    fn new_if_enabled(tcx: TyCtxt<'_>) -> Option<Self> {
+        tcx.sess
+            .instrument_coverage_mcdc()
+            .then(|| Self { decision_stack: VecDeque::new(), processing_decision: None })
+    }
+
+    // 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.
+    //
+    // Example: "x = (A && B) || (C && D) || (D && F)"
+    //
+    //      Visit Depth1:
+    //              (A && B) || (C && D) || (D && F)
+    //              ^-------LHS--------^    ^-RHS--^
+    //                      ID=1              ID=2
+    //
+    //      Visit LHS-Depth2:
+    //              (A && B) || (C && D)
+    //              ^-LHS--^    ^-RHS--^
+    //                ID=1        ID=3
+    //
+    //      Visit LHS-Depth3:
+    //               (A && B)
+    //               LHS   RHS
+    //               ID=1  ID=4
+    //
+    //      Visit RHS-Depth3:
+    //                         (C && D)
+    //                         LHS   RHS
+    //                         ID=3  ID=5
+    //
+    //      Visit RHS-Depth2:              (D && F)
+    //                                     LHS   RHS
+    //                                     ID=2  ID=6
+    //
+    //      Visit Depth1:
+    //              (A && B)  || (C && D)  || (D && F)
+    //              ID=1  ID=4   ID=3  ID=5   ID=2  ID=6
+    //
+    // A node ID of '0' always means MC/DC isn't being tracked.
+    //
+    // If a "next" node ID is '0', it means it's the end of the test vector.
+    //
+    // As the compiler tracks expression in pre-order, we can ensure that condition info of parents are always properly assigned when their children are visited.
+    // - 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() {
+            Some(decision) => {
+                decision.span = decision.span.to(span);
+                decision
+            }
+            None => self.processing_decision.insert(MCDCDecisionSpan {
+                span,
+                conditions_num: 0,
+                end_markers: vec![],
+            }),
+        };
+
+        let parent_condition = self.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)
+        } else {
+            parent_condition.condition_id
+        };
+
+        decision.conditions_num += 1;
+        let rhs_condition_id = ConditionId::from(decision.conditions_num);
+
+        let (lhs, rhs) = match op {
+            LogicalOp::And => {
+                let lhs = ConditionInfo {
+                    condition_id: lhs_id,
+                    true_next_id: rhs_condition_id,
+                    false_next_id: parent_condition.false_next_id,
+                };
+                let rhs = ConditionInfo {
+                    condition_id: rhs_condition_id,
+                    true_next_id: parent_condition.true_next_id,
+                    false_next_id: parent_condition.false_next_id,
+                };
+                (lhs, rhs)
+            }
+            LogicalOp::Or => {
+                let lhs = ConditionInfo {
+                    condition_id: lhs_id,
+                    true_next_id: parent_condition.true_next_id,
+                    false_next_id: rhs_condition_id,
+                };
+                let rhs = ConditionInfo {
+                    condition_id: rhs_condition_id,
+                    true_next_id: parent_condition.true_next_id,
+                    false_next_id: parent_condition.false_next_id,
+                };
+                (lhs, rhs)
+            }
+        };
+        // 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);
+    }
+
+    fn take_condition(
+        &mut self,
+        true_marker: BlockMarkerId,
+        false_marker: BlockMarkerId,
+    ) -> (Option<ConditionInfo>, Option<MCDCDecisionSpan>) {
+        let Some(condition_info) = self.decision_stack.pop_back() else {
+            return (None, None);
+        };
+        let Some(decision) = self.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 {
+            decision.end_markers.push(true_marker);
+        }
+        if condition_info.false_next_id == ConditionId::NONE {
+            decision.end_markers.push(false_marker);
+        }
+
+        if self.decision_stack.is_empty() {
+            (Some(condition_info), self.processing_decision.take())
+        } else {
+            (Some(condition_info), None)
+        }
     }
 }
 
@@ -137,10 +355,27 @@ impl Builder<'_, '_> {
         let true_marker = inject_branch_marker(then_block);
         let false_marker = inject_branch_marker(else_block);
 
-        branch_info.branch_spans.push(BranchSpan {
-            span: source_info.span,
-            true_marker,
-            false_marker,
-        });
+        if let Some(condition_info) =
+            branch_info.fetch_condition_info(self.tcx, true_marker, false_marker)
+        {
+            branch_info.mcdc_branch_spans.push(MCDCBranchSpan {
+                span: source_info.span,
+                condition_info,
+                true_marker,
+                false_marker,
+            });
+        } else {
+            branch_info.branch_spans.push(BranchSpan {
+                span: source_info.span,
+                true_marker,
+                false_marker,
+            });
+        }
+    }
+
+    pub(crate) fn visit_coverage_branch_operation(&mut self, logical_op: LogicalOp, span: Span) {
+        if let Some(branch_info) = self.coverage_branch_info.as_mut() {
+            branch_info.record_conditions_operation(logical_op, span);
+        }
     }
 }
diff --git a/compiler/rustc_mir_build/src/build/expr/into.rs b/compiler/rustc_mir_build/src/build/expr/into.rs
index c8360b6a5fdd2..1bf83c90ea782 100644
--- a/compiler/rustc_mir_build/src/build/expr/into.rs
+++ b/compiler/rustc_mir_build/src/build/expr/into.rs
@@ -148,6 +148,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
             ExprKind::LogicalOp { op, lhs, rhs } => {
                 let condition_scope = this.local_scope();
                 let source_info = this.source_info(expr.span);
+
                 // We first evaluate the left-hand side of the predicate ...
                 let (then_block, else_block) =
                     this.in_if_then_scope(condition_scope, expr.span, |this| {
diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs
index 367c391b45a49..82c3107634bc1 100644
--- a/compiler/rustc_mir_build/src/build/matches/mod.rs
+++ b/compiler/rustc_mir_build/src/build/matches/mod.rs
@@ -77,11 +77,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
 
         match expr.kind {
             ExprKind::LogicalOp { op: LogicalOp::And, lhs, rhs } => {
+                this.visit_coverage_branch_operation(LogicalOp::And, expr_span);
                 let lhs_then_block = unpack!(this.then_else_break_inner(block, lhs, args));
                 let rhs_then_block = unpack!(this.then_else_break_inner(lhs_then_block, rhs, args));
                 rhs_then_block.unit()
             }
             ExprKind::LogicalOp { op: LogicalOp::Or, lhs, rhs } => {
+                this.visit_coverage_branch_operation(LogicalOp::Or, expr_span);
                 let local_scope = this.local_scope();
                 let (lhs_success_block, failure_block) =
                     this.in_if_then_scope(local_scope, expr_span, |this| {
diff --git a/compiler/rustc_mir_build/src/errors.rs b/compiler/rustc_mir_build/src/errors.rs
index 26f10fdd333cb..9ddfb12bf7643 100644
--- a/compiler/rustc_mir_build/src/errors.rs
+++ b/compiler/rustc_mir_build/src/errors.rs
@@ -818,6 +818,15 @@ pub struct NontrivialStructuralMatch<'tcx> {
     pub non_sm_ty: Ty<'tcx>,
 }
 
+#[derive(Diagnostic)]
+#[diag(mir_build_exceeds_mcdc_condition_num_limit)]
+pub(crate) struct MCDCExceedsConditionNumLimit {
+    #[primary_span]
+    pub span: Span,
+    pub conditions_num: usize,
+    pub max_conditions_num: usize,
+}
+
 #[derive(Diagnostic)]
 #[diag(mir_build_pattern_not_covered, code = E0005)]
 pub(crate) struct PatternNotCovered<'s, 'tcx> {
diff --git a/compiler/rustc_mir_transform/src/coverage/mod.rs b/compiler/rustc_mir_transform/src/coverage/mod.rs
index d382d2c03c24a..8736e7308957b 100644
--- a/compiler/rustc_mir_transform/src/coverage/mod.rs
+++ b/compiler/rustc_mir_transform/src/coverage/mod.rs
@@ -77,7 +77,6 @@ fn instrument_function_for_coverage<'tcx>(tcx: TyCtxt<'tcx>, mir_body: &mut mir:
         return;
     };
 
-    ////////////////////////////////////////////////////
     // Create an optimized mix of `Counter`s and `Expression`s for the `CoverageGraph`. Ensure
     // every coverage span has a `Counter` or `Expression` assigned to its `BasicCoverageBlock`
     // and all `Expression` dependencies (operands) are also generated, for any other
@@ -100,9 +99,12 @@ fn instrument_function_for_coverage<'tcx>(tcx: TyCtxt<'tcx>, mir_body: &mut mir:
         &coverage_counters,
     );
 
+    inject_mcdc_statements(mir_body, &basic_coverage_blocks, &coverage_spans);
+
     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,
     }));
@@ -136,20 +138,33 @@ fn create_mappings<'tcx>(
             .as_term()
     };
 
-    coverage_spans
-        .all_bcb_mappings()
-        .filter_map(|&BcbMapping { kind: bcb_mapping_kind, span }| {
-            let kind = match bcb_mapping_kind {
+    let mut mappings = Vec::new();
+
+    mappings.extend(coverage_spans.all_bcb_mappings().filter_map(
+        |BcbMapping { kind: bcb_mapping_kind, span }| {
+            let kind = match *bcb_mapping_kind {
                 BcbMappingKind::Code(bcb) => MappingKind::Code(term_for_bcb(bcb)),
                 BcbMappingKind::Branch { true_bcb, false_bcb } => MappingKind::Branch {
                     true_term: term_for_bcb(true_bcb),
                     false_term: term_for_bcb(false_bcb),
                 },
+                BcbMappingKind::MCDCBranch { true_bcb, false_bcb, condition_info } => {
+                    MappingKind::MCDCBranch {
+                        true_term: term_for_bcb(true_bcb),
+                        false_term: term_for_bcb(false_bcb),
+                        mcdc_params: condition_info,
+                    }
+                }
+                BcbMappingKind::MCDCDecision { bitmap_idx, conditions_num, .. } => {
+                    MappingKind::MCDCDecision(DecisionInfo { bitmap_idx, conditions_num })
+                }
             };
-            let code_region = make_code_region(source_map, file_name, span, body_span)?;
+            let code_region = make_code_region(source_map, file_name, *span, body_span)?;
             Some(Mapping { kind, code_region })
-        })
-        .collect::<Vec<_>>()
+        },
+    ));
+
+    mappings
 }
 
 /// For each BCB node or BCB edge that has an associated coverage counter,
@@ -204,6 +219,55 @@ fn inject_coverage_statements<'tcx>(
     }
 }
 
+/// For each conditions inject statements to update condition bitmap after it has been evaluated.
+/// For each decision inject statements to update test vector bitmap after it has been evaluated.
+fn inject_mcdc_statements<'tcx>(
+    mir_body: &mut mir::Body<'tcx>,
+    basic_coverage_blocks: &CoverageGraph,
+    coverage_spans: &CoverageSpans,
+) {
+    if coverage_spans.test_vector_bitmap_bytes() == 0 {
+        return;
+    }
+
+    // Inject test vector update first because `inject_statement` always insert new statement at head.
+    for (end_bcbs, bitmap_idx) in
+        coverage_spans.all_bcb_mappings().filter_map(|mapping| match &mapping.kind {
+            BcbMappingKind::MCDCDecision { end_bcbs, bitmap_idx, .. } => {
+                Some((end_bcbs, *bitmap_idx))
+            }
+            _ => 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);
+        }
+    }
+
+    for (true_bcb, false_bcb, condition_id) in
+        coverage_spans.all_bcb_mappings().filter_map(|mapping| match mapping.kind {
+            BcbMappingKind::MCDCBranch { true_bcb, false_bcb, condition_info } => {
+                Some((true_bcb, false_bcb, condition_info.condition_id))
+            }
+            _ => None,
+        })
+    {
+        let true_bb = basic_coverage_blocks[true_bcb].leader_bb();
+        inject_statement(
+            mir_body,
+            CoverageKind::CondBitmapUpdate { id: condition_id, value: true },
+            true_bb,
+        );
+        let false_bb = basic_coverage_blocks[false_bcb].leader_bb();
+        inject_statement(
+            mir_body,
+            CoverageKind::CondBitmapUpdate { id: condition_id, value: false },
+            false_bb,
+        );
+    }
+}
+
 /// Given two basic blocks that have a control-flow edge between them, creates
 /// and returns a new block that sits between those blocks.
 fn inject_edge_counter_basic_block(
diff --git a/compiler/rustc_mir_transform/src/coverage/query.rs b/compiler/rustc_mir_transform/src/coverage/query.rs
index 65715253647a8..f77ee63d02c20 100644
--- a/compiler/rustc_mir_transform/src/coverage/query.rs
+++ b/compiler/rustc_mir_transform/src/coverage/query.rs
@@ -61,7 +61,17 @@ fn coverage_ids_info<'tcx>(
         .max()
         .unwrap_or(CounterId::ZERO);
 
-    CoverageIdsInfo { max_counter_id }
+    let mcdc_bitmap_bytes = mir_body
+        .coverage_branch_info
+        .as_deref()
+        .map(|info| {
+            info.mcdc_decision_spans
+                .iter()
+                .fold(0, |acc, decision| acc + (1_u32 << decision.conditions_num).div_ceil(8))
+        })
+        .unwrap_or_default();
+
+    CoverageIdsInfo { max_counter_id, mcdc_bitmap_bytes }
 }
 
 fn all_coverage_in_mir_body<'a, 'tcx>(
diff --git a/compiler/rustc_mir_transform/src/coverage/spans.rs b/compiler/rustc_mir_transform/src/coverage/spans.rs
index 672de1fbe60fd..a96afed82f105 100644
--- a/compiler/rustc_mir_transform/src/coverage/spans.rs
+++ b/compiler/rustc_mir_transform/src/coverage/spans.rs
@@ -1,6 +1,9 @@
+use std::collections::BTreeSet;
+
 use rustc_data_structures::graph::WithNumNodes;
 use rustc_index::bit_set::BitSet;
 use rustc_middle::mir;
+use rustc_middle::mir::coverage::ConditionInfo;
 use rustc_span::{BytePos, Span};
 
 use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph, START_BCB};
@@ -9,12 +12,20 @@ use crate::coverage::ExtractedHirInfo;
 
 mod from_mir;
 
-#[derive(Clone, Copy, Debug)]
+#[derive(Clone, Debug)]
 pub(super) enum BcbMappingKind {
     /// Associates an ordinary executable code span with its corresponding BCB.
     Code(BasicCoverageBlock),
     /// Associates a branch span with BCBs for its true and false arms.
     Branch { true_bcb: BasicCoverageBlock, false_bcb: BasicCoverageBlock },
+    /// Associates a mcdc branch span with condition info besides fields for normal branch.
+    MCDCBranch {
+        true_bcb: BasicCoverageBlock,
+        false_bcb: BasicCoverageBlock,
+        condition_info: ConditionInfo,
+    },
+    /// Associates a mcdc decision with its join BCB.
+    MCDCDecision { end_bcbs: BTreeSet<BasicCoverageBlock>, bitmap_idx: u32, conditions_num: u16 },
 }
 
 #[derive(Debug)]
@@ -26,6 +37,7 @@ pub(super) struct BcbMapping {
 pub(super) struct CoverageSpans {
     bcb_has_mappings: BitSet<BasicCoverageBlock>,
     mappings: Vec<BcbMapping>,
+    test_vector_bitmap_bytes: u32,
 }
 
 impl CoverageSpans {
@@ -36,6 +48,10 @@ impl CoverageSpans {
     pub(super) fn all_bcb_mappings(&self) -> impl Iterator<Item = &BcbMapping> {
         self.mappings.iter()
     }
+
+    pub(super) fn test_vector_bitmap_bytes(&self) -> u32 {
+        self.test_vector_bitmap_bytes
+    }
 }
 
 /// Extracts coverage-relevant spans from MIR, and associates them with
@@ -85,17 +101,26 @@ pub(super) fn generate_coverage_spans(
     let mut insert = |bcb| {
         bcb_has_mappings.insert(bcb);
     };
-    for &BcbMapping { kind, span: _ } in &mappings {
-        match kind {
+    let mut test_vector_bitmap_bytes = 0;
+    for BcbMapping { kind, span: _ } in &mappings {
+        match *kind {
             BcbMappingKind::Code(bcb) => insert(bcb),
-            BcbMappingKind::Branch { true_bcb, false_bcb } => {
+            BcbMappingKind::Branch { true_bcb, false_bcb }
+            | BcbMappingKind::MCDCBranch { true_bcb, false_bcb, .. } => {
                 insert(true_bcb);
                 insert(false_bcb);
             }
+            BcbMappingKind::MCDCDecision { bitmap_idx, conditions_num, .. } => {
+                // `bcb_has_mappings` is used for inject coverage counters
+                // but they are not needed for decision BCBs.
+                // While the length of test vector bitmap should be calculated here.
+                test_vector_bitmap_bytes = test_vector_bitmap_bytes
+                    .max(bitmap_idx + (1_u32 << conditions_num as u32).div_ceil(8));
+            }
         }
     }
 
-    Some(CoverageSpans { bcb_has_mappings, mappings })
+    Some(CoverageSpans { bcb_has_mappings, mappings, test_vector_bitmap_bytes })
 }
 
 #[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 adb0c9f1929d9..b9919a2ae884e 100644
--- a/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs
+++ b/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs
@@ -1,7 +1,9 @@
 use rustc_data_structures::captures::Captures;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_index::IndexVec;
-use rustc_middle::mir::coverage::{BlockMarkerId, BranchSpan, CoverageKind};
+use rustc_middle::mir::coverage::{
+    BlockMarkerId, BranchSpan, CoverageKind, MCDCBranchSpan, MCDCDecisionSpan,
+};
 use rustc_middle::mir::{
     self, AggregateKind, BasicBlock, FakeReadCause, Rvalue, Statement, StatementKind, Terminator,
     TerminatorKind,
@@ -227,7 +229,10 @@ fn filtered_statement_span(statement: &Statement<'_>) -> Option<Span> {
 
         // These coverage statements should not exist prior to coverage instrumentation.
         StatementKind::Coverage(
-            CoverageKind::CounterIncrement { .. } | CoverageKind::ExpressionUsed { .. },
+            CoverageKind::CounterIncrement { .. }
+            | CoverageKind::ExpressionUsed { .. }
+            | CoverageKind::CondBitmapUpdate { .. }
+            | CoverageKind::TestVectorBitmapUpdate { .. },
         ) => bug!(
             "Unexpected coverage statement found during coverage instrumentation: {statement:?}"
         ),
@@ -384,10 +389,11 @@ pub(super) fn extract_branch_mappings(
         }
     }
 
-    branch_info
-        .branch_spans
-        .iter()
-        .filter_map(|&BranchSpan { span: raw_span, true_marker, false_marker }| {
+    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() {
@@ -395,13 +401,56 @@ pub(super) fn extract_branch_mappings(
             }
             let (span, _) = unexpand_into_body_span_with_visible_macro(raw_span, body_span)?;
 
-            let bcb_from_marker =
-                |marker: BlockMarkerId| basic_coverage_blocks.bcb_from_bb(block_markers[marker]?);
-
             let true_bcb = bcb_from_marker(true_marker)?;
             let false_bcb = bcb_from_marker(false_marker)?;
+            Some((span, true_bcb, false_bcb))
+        };
 
-            Some(BcbMapping { kind: BcbMappingKind::Branch { true_bcb, false_bcb }, span })
+    let branch_filter_map = |&BranchSpan { span: raw_span, true_marker, false_marker }| {
+        check_branch_bcb(raw_span, true_marker, false_marker).map(|(span, true_bcb, false_bcb)| {
+            BcbMapping { kind: BcbMappingKind::Branch { true_bcb, false_bcb }, span }
         })
+    };
+
+    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 mut next_bitmap_idx = 0;
+
+    let decision_filter_map = |decision: &MCDCDecisionSpan| {
+        let (span, _) = unexpand_into_body_span_with_visible_macro(decision.span, body_span)?;
+
+        let end_bcbs = decision
+            .end_markers
+            .iter()
+            .map(|&marker| bcb_from_marker(marker))
+            .collect::<Option<_>>()?;
+
+        let bitmap_idx = next_bitmap_idx;
+        next_bitmap_idx += (1_u32 << decision.conditions_num).div_ceil(8);
+
+        Some(BcbMapping {
+            kind: BcbMappingKind::MCDCDecision {
+                end_bcbs,
+                bitmap_idx,
+                conditions_num: decision.conditions_num as u16,
+            },
+            span,
+        })
+    };
+
+    branch_info
+        .branch_spans
+        .iter()
+        .filter_map(branch_filter_map)
+        .chain(branch_info.mcdc_branch_spans.iter().filter_map(mcdc_branch_filter_map))
+        .chain(branch_info.mcdc_decision_spans.iter().filter_map(decision_filter_map))
         .collect::<Vec<_>>()
 }
diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
index a4919b25fe35e..05e19f7783f84 100644
--- a/compiler/rustc_session/src/config.rs
+++ b/compiler/rustc_session/src/config.rs
@@ -148,6 +148,8 @@ pub enum InstrumentCoverage {
 pub struct CoverageOptions {
     /// Add branch coverage instrumentation.
     pub branch: bool,
+    /// Add mcdc coverage instrumentation.
+    pub mcdc: bool,
 }
 
 /// Settings for `-Z instrument-xray` flag.
diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs
index 5e7c246509771..fff5ac05ef0b2 100644
--- a/compiler/rustc_session/src/options.rs
+++ b/compiler/rustc_session/src/options.rs
@@ -396,7 +396,7 @@ mod desc {
     pub const parse_optimization_fuel: &str = "crate=integer";
     pub const parse_dump_mono_stats: &str = "`markdown` (default) or `json`";
     pub const parse_instrument_coverage: &str = parse_bool;
-    pub const parse_coverage_options: &str = "`branch` or `no-branch`";
+    pub const parse_coverage_options: &str = "either  `no-branch`, `branch` or `mcdc`";
     pub const parse_instrument_xray: &str = "either a boolean (`yes`, `no`, `on`, `off`, etc), or a comma separated list of settings: `always` or `never` (mutually exclusive), `ignore-loops`, `instruction-threshold=N`, `skip-entry`, `skip-exit`";
     pub const parse_unpretty: &str = "`string` or `string=string`";
     pub const parse_treat_err_as_bug: &str = "either no value or a non-negative number";
@@ -944,17 +944,19 @@ mod parse {
         let Some(v) = v else { return true };
 
         for option in v.split(',') {
-            let (option, enabled) = match option.strip_prefix("no-") {
-                Some(without_no) => (without_no, false),
-                None => (option, true),
-            };
-            let slot = match option {
-                "branch" => &mut slot.branch,
+            match option {
+                "no-branch" => {
+                    slot.branch = false;
+                    slot.mcdc = false;
+                }
+                "branch" => slot.branch = true,
+                "mcdc" => {
+                    slot.branch = true;
+                    slot.mcdc = true;
+                }
                 _ => return false,
-            };
-            *slot = enabled;
+            }
         }
-
         true
     }
 
diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs
index 22ca8a3cf3ec2..7729befbe7ad8 100644
--- a/compiler/rustc_session/src/session.rs
+++ b/compiler/rustc_session/src/session.rs
@@ -352,6 +352,10 @@ impl Session {
         self.instrument_coverage() && self.opts.unstable_opts.coverage_options.branch
     }
 
+    pub fn instrument_coverage_mcdc(&self) -> bool {
+        self.instrument_coverage() && self.opts.unstable_opts.coverage_options.mcdc
+    }
+
     pub fn is_sanitizer_cfi_enabled(&self) -> bool {
         self.opts.unstable_opts.sanitizer.contains(SanitizerSet::CFI)
     }
diff --git a/src/doc/rustc/src/instrument-coverage.md b/src/doc/rustc/src/instrument-coverage.md
index 185a3ba5dbd41..bbd81e7437c67 100644
--- a/src/doc/rustc/src/instrument-coverage.md
+++ b/src/doc/rustc/src/instrument-coverage.md
@@ -351,8 +351,8 @@ $ llvm-cov report \
 This unstable option provides finer control over some aspects of coverage
 instrumentation. Pass one or more of the following values, separated by commas.
 
-- `branch` or `no-branch`
-  - Enables or disables branch coverage instrumentation.
+- Either `no-branch`, `branch` or `mcdc`
+  - `branch` enables branch coverage instrumentation and `mcdc` further enables modified condition/decision coverage instrumentation. `no-branch` disables branch coverage instrumentation, which is same as do not pass `branch` or `mcdc`.
 
 ## Other references
 
diff --git a/src/doc/unstable-book/src/compiler-flags/coverage-options.md b/src/doc/unstable-book/src/compiler-flags/coverage-options.md
index 450573cc6c746..5e192d9aca13e 100644
--- a/src/doc/unstable-book/src/compiler-flags/coverage-options.md
+++ b/src/doc/unstable-book/src/compiler-flags/coverage-options.md
@@ -5,4 +5,4 @@ This option controls details of the coverage instrumentation performed by
 
 Multiple options can be passed, separated by commas. Valid options are:
 
-- `branch` or `no-branch`: Enables or disables branch coverage instrumentation.
+- `no-branch`, `branch` or `mcdc`: `branch` enables branch coverage instrumentation and `mcdc` further enables modified condition/decision coverage instrumentation. `no-branch` disables branch coverage instrumentation as well as mcdc instrumentation, which is same as do not pass `branch` or `mcdc`.
diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs
index 689fdc5dfebc0..0e244b9369747 100644
--- a/src/tools/compiletest/src/runtest.rs
+++ b/src/tools/compiletest/src/runtest.rs
@@ -752,6 +752,19 @@ impl<'test> TestCx<'test> {
             Lazy::new(|| Regex::new(r"(?m:^)(?<prefix>(?:  \|)+  Branch \()[0-9]+:").unwrap());
         let coverage = BRANCH_LINE_NUMBER_RE.replace_all(&coverage, "${prefix}LL:");
 
+        // `  |---> MC/DC Decision Region (1:`     => `  |---> MC/DC Decision Region (LL:`
+        static MCDC_DECISION_LINE_NUMBER_RE: Lazy<Regex> = Lazy::new(|| {
+            Regex::new(r"(?m:^)(?<prefix>(?:  \|)+---> MC/DC Decision Region \()[0-9]+:(?<middle>[0-9]+\) to \()[0-9]+:").unwrap()
+        });
+        let coverage =
+            MCDC_DECISION_LINE_NUMBER_RE.replace_all(&coverage, "${prefix}LL:${middle}LL:");
+
+        // `  |     Condition C1 --> (1:`     => `  |     Condition C1 --> (LL:`
+        static MCDC_CONDITION_LINE_NUMBER_RE: Lazy<Regex> = Lazy::new(|| {
+            Regex::new(r"(?m:^)(?<prefix>(?:  \|)+     Condition C[0-9]+ --> \()[0-9]+:").unwrap()
+        });
+        let coverage = MCDC_CONDITION_LINE_NUMBER_RE.replace_all(&coverage, "${prefix}LL:");
+
         coverage.into_owned()
     }
 
diff --git a/src/tools/coverage-dump/src/covfun.rs b/src/tools/coverage-dump/src/covfun.rs
index 49e3a6ed58383..b308c8de14fe3 100644
--- a/src/tools/coverage-dump/src/covfun.rs
+++ b/src/tools/coverage-dump/src/covfun.rs
@@ -70,7 +70,8 @@ pub(crate) fn dump_covfun_mappings(
                     }
                     // If the mapping is a branch region, print both of its arms
                     // in resolved form (even if they aren't expressions).
-                    MappingKind::Branch { r#true, r#false } => {
+                    MappingKind::Branch { r#true, r#false }
+                    | MappingKind::MCDCBranch { r#true, r#false, .. } => {
                         println!("    true  = {}", expression_resolver.format_term(r#true));
                         println!("    false = {}", expression_resolver.format_term(r#false));
                     }
@@ -164,6 +165,26 @@ impl<'a> Parser<'a> {
                     let r#false = self.read_simple_term()?;
                     Ok(MappingKind::Branch { r#true, r#false })
                 }
+                5 => {
+                    let bitmap_idx = self.read_uleb128_u32()?;
+                    let conditions_num = self.read_uleb128_u32()?;
+                    Ok(MappingKind::MCDCDecision { bitmap_idx, conditions_num })
+                }
+                6 => {
+                    let r#true = self.read_simple_term()?;
+                    let r#false = self.read_simple_term()?;
+                    let condition_id = self.read_uleb128_u32()?;
+                    let true_next_id = self.read_uleb128_u32()?;
+                    let false_next_id = self.read_uleb128_u32()?;
+                    Ok(MappingKind::MCDCBranch {
+                        r#true,
+                        r#false,
+                        condition_id,
+                        true_next_id,
+                        false_next_id,
+                    })
+                }
+
                 _ => Err(anyhow!("unknown mapping kind: {raw_mapping_kind:#x}")),
             }
         }
@@ -224,7 +245,28 @@ enum MappingKind {
     // Using raw identifiers here makes the dump output a little bit nicer
     // (via the derived Debug), at the expense of making this tool's source
     // code a little bit uglier.
-    Branch { r#true: CovTerm, r#false: CovTerm },
+    Branch {
+        r#true: CovTerm,
+        r#false: CovTerm,
+    },
+    MCDCBranch {
+        r#true: CovTerm,
+        r#false: CovTerm,
+        // These attributes are printed in Debug but not used directly.
+        #[allow(dead_code)]
+        condition_id: u32,
+        #[allow(dead_code)]
+        true_next_id: u32,
+        #[allow(dead_code)]
+        false_next_id: u32,
+    },
+    MCDCDecision {
+        // These attributes are printed in Debug but not used directly.
+        #[allow(dead_code)]
+        bitmap_idx: u32,
+        #[allow(dead_code)]
+        conditions_num: u32,
+    },
 }
 
 struct MappingRegion {
diff --git a/tests/coverage/mcdc_if.cov-map b/tests/coverage/mcdc_if.cov-map
new file mode 100644
index 0000000000000..35a265684d2f6
--- /dev/null
+++ b/tests/coverage/mcdc_if.cov-map
@@ -0,0 +1,218 @@
+Function name: mcdc_if::mcdc_check_a
+Raw bytes (64): 0x[01, 01, 04, 01, 05, 09, 02, 0d, 0f, 09, 02, 08, 01, 0f, 01, 01, 09, 28, 00, 02, 01, 08, 00, 0e, 30, 05, 02, 01, 02, 00, 00, 08, 00, 09, 05, 00, 0d, 00, 0e, 30, 0d, 09, 02, 00, 00, 00, 0d, 00, 0e, 0d, 00, 0f, 02, 06, 0f, 02, 0c, 02, 06, 0b, 03, 01, 00, 02]
+Number of files: 1
+- file 0 => global file 1
+Number of expressions: 4
+- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
+- expression 1 operands: lhs = Counter(2), rhs = Expression(0, Sub)
+- expression 2 operands: lhs = Counter(3), rhs = Expression(3, Add)
+- expression 3 operands: lhs = Counter(2), rhs = Expression(0, Sub)
+Number of file 0 mappings: 8
+- Code(Counter(0)) at (prev + 15, 1) to (start + 1, 9)
+- MCDCDecision { bitmap_idx: 0, conditions_num: 2 } at (prev + 1, 8) to (start + 0, 14)
+- 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)
+- Code(Counter(1)) at (prev + 0, 13) to (start + 0, 14)
+- 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, 14)
+    true  = c3
+    false = c2
+- Code(Counter(3)) at (prev + 0, 15) to (start + 2, 6)
+- Code(Expression(3, Add)) at (prev + 2, 12) to (start + 2, 6)
+    = (c2 + (c0 - c1))
+- Code(Expression(2, Add)) at (prev + 3, 1) to (start + 0, 2)
+    = (c3 + (c2 + (c0 - c1)))
+
+Function name: mcdc_if::mcdc_check_b
+Raw bytes (64): 0x[01, 01, 04, 01, 05, 09, 02, 0d, 0f, 09, 02, 08, 01, 17, 01, 01, 09, 28, 00, 02, 01, 08, 00, 0e, 30, 05, 02, 01, 02, 00, 00, 08, 00, 09, 05, 00, 0d, 00, 0e, 30, 0d, 09, 02, 00, 00, 00, 0d, 00, 0e, 0d, 00, 0f, 02, 06, 0f, 02, 0c, 02, 06, 0b, 03, 01, 00, 02]
+Number of files: 1
+- file 0 => global file 1
+Number of expressions: 4
+- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
+- expression 1 operands: lhs = Counter(2), rhs = Expression(0, Sub)
+- expression 2 operands: lhs = Counter(3), rhs = Expression(3, Add)
+- expression 3 operands: lhs = Counter(2), rhs = Expression(0, Sub)
+Number of file 0 mappings: 8
+- Code(Counter(0)) at (prev + 23, 1) to (start + 1, 9)
+- MCDCDecision { bitmap_idx: 0, conditions_num: 2 } at (prev + 1, 8) to (start + 0, 14)
+- 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)
+- Code(Counter(1)) at (prev + 0, 13) to (start + 0, 14)
+- 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, 14)
+    true  = c3
+    false = c2
+- Code(Counter(3)) at (prev + 0, 15) to (start + 2, 6)
+- Code(Expression(3, Add)) at (prev + 2, 12) to (start + 2, 6)
+    = (c2 + (c0 - c1))
+- Code(Expression(2, Add)) at (prev + 3, 1) to (start + 0, 2)
+    = (c3 + (c2 + (c0 - c1)))
+
+Function name: mcdc_if::mcdc_check_both
+Raw bytes (64): 0x[01, 01, 04, 01, 05, 09, 02, 0d, 0f, 09, 02, 08, 01, 1f, 01, 01, 09, 28, 00, 02, 01, 08, 00, 0e, 30, 05, 02, 01, 02, 00, 00, 08, 00, 09, 05, 00, 0d, 00, 0e, 30, 0d, 09, 02, 00, 00, 00, 0d, 00, 0e, 0d, 00, 0f, 02, 06, 0f, 02, 0c, 02, 06, 0b, 03, 01, 00, 02]
+Number of files: 1
+- file 0 => global file 1
+Number of expressions: 4
+- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
+- expression 1 operands: lhs = Counter(2), rhs = Expression(0, Sub)
+- expression 2 operands: lhs = Counter(3), rhs = Expression(3, Add)
+- expression 3 operands: lhs = Counter(2), rhs = Expression(0, Sub)
+Number of file 0 mappings: 8
+- Code(Counter(0)) at (prev + 31, 1) to (start + 1, 9)
+- MCDCDecision { bitmap_idx: 0, conditions_num: 2 } at (prev + 1, 8) to (start + 0, 14)
+- 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)
+- Code(Counter(1)) at (prev + 0, 13) to (start + 0, 14)
+- 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, 14)
+    true  = c3
+    false = c2
+- Code(Counter(3)) at (prev + 0, 15) to (start + 2, 6)
+- Code(Expression(3, Add)) at (prev + 2, 12) to (start + 2, 6)
+    = (c2 + (c0 - c1))
+- Code(Expression(2, Add)) at (prev + 3, 1) to (start + 0, 2)
+    = (c3 + (c2 + (c0 - c1)))
+
+Function name: mcdc_if::mcdc_check_neither
+Raw bytes (64): 0x[01, 01, 04, 01, 05, 09, 02, 0d, 0f, 09, 02, 08, 01, 07, 01, 01, 09, 28, 00, 02, 01, 08, 00, 0e, 30, 05, 02, 01, 02, 00, 00, 08, 00, 09, 05, 00, 0d, 00, 0e, 30, 0d, 09, 02, 00, 00, 00, 0d, 00, 0e, 0d, 00, 0f, 02, 06, 0f, 02, 0c, 02, 06, 0b, 03, 01, 00, 02]
+Number of files: 1
+- file 0 => global file 1
+Number of expressions: 4
+- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
+- expression 1 operands: lhs = Counter(2), rhs = Expression(0, Sub)
+- expression 2 operands: lhs = Counter(3), rhs = Expression(3, Add)
+- expression 3 operands: lhs = Counter(2), rhs = Expression(0, Sub)
+Number of file 0 mappings: 8
+- Code(Counter(0)) at (prev + 7, 1) to (start + 1, 9)
+- MCDCDecision { bitmap_idx: 0, conditions_num: 2 } at (prev + 1, 8) to (start + 0, 14)
+- 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)
+- Code(Counter(1)) at (prev + 0, 13) to (start + 0, 14)
+- 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, 14)
+    true  = c3
+    false = c2
+- Code(Counter(3)) at (prev + 0, 15) to (start + 2, 6)
+- Code(Expression(3, Add)) at (prev + 2, 12) to (start + 2, 6)
+    = (c2 + (c0 - c1))
+- Code(Expression(2, Add)) at (prev + 3, 1) to (start + 0, 2)
+    = (c3 + (c2 + (c0 - c1)))
+
+Function name: mcdc_if::mcdc_check_not_tree_decision
+Raw bytes (87): 0x[01, 01, 08, 01, 05, 02, 09, 05, 09, 0d, 1e, 02, 09, 11, 1b, 0d, 1e, 02, 09, 0a, 01, 31, 01, 03, 0a, 28, 00, 03, 03, 08, 00, 15, 30, 05, 02, 01, 02, 03, 00, 09, 00, 0a, 02, 00, 0e, 00, 0f, 30, 09, 1e, 03, 02, 00, 00, 0e, 00, 0f, 0b, 00, 14, 00, 15, 30, 11, 0d, 02, 00, 00, 00, 14, 00, 15, 11, 00, 16, 02, 06, 1b, 02, 0c, 02, 06, 17, 03, 01, 00, 02]
+Number of files: 1
+- file 0 => global file 1
+Number of expressions: 8
+- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
+- expression 1 operands: lhs = Expression(0, Sub), rhs = Counter(2)
+- expression 2 operands: lhs = Counter(1), rhs = Counter(2)
+- expression 3 operands: lhs = Counter(3), rhs = Expression(7, Sub)
+- expression 4 operands: lhs = Expression(0, Sub), rhs = Counter(2)
+- expression 5 operands: lhs = Counter(4), rhs = Expression(6, Add)
+- expression 6 operands: lhs = Counter(3), rhs = Expression(7, Sub)
+- expression 7 operands: lhs = Expression(0, Sub), rhs = Counter(2)
+Number of file 0 mappings: 10
+- Code(Counter(0)) at (prev + 49, 1) to (start + 3, 10)
+- MCDCDecision { bitmap_idx: 0, conditions_num: 3 } at (prev + 3, 8) to (start + 0, 21)
+- MCDCBranch { true: Counter(1), false: Expression(0, Sub), condition_id: 1, true_next_id: 2, false_next_id: 3 } at (prev + 0, 9) to (start + 0, 10)
+    true  = c1
+    false = (c0 - c1)
+- Code(Expression(0, Sub)) at (prev + 0, 14) to (start + 0, 15)
+    = (c0 - c1)
+- MCDCBranch { true: Counter(2), false: Expression(7, Sub), condition_id: 3, true_next_id: 2, false_next_id: 0 } at (prev + 0, 14) to (start + 0, 15)
+    true  = c2
+    false = ((c0 - c1) - c2)
+- Code(Expression(2, Add)) at (prev + 0, 20) to (start + 0, 21)
+    = (c1 + c2)
+- MCDCBranch { true: Counter(4), false: Counter(3), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 20) to (start + 0, 21)
+    true  = c4
+    false = c3
+- Code(Counter(4)) at (prev + 0, 22) to (start + 2, 6)
+- Code(Expression(6, Add)) at (prev + 2, 12) to (start + 2, 6)
+    = (c3 + ((c0 - c1) - c2))
+- Code(Expression(5, Add)) at (prev + 3, 1) to (start + 0, 2)
+    = (c4 + (c3 + ((c0 - c1) - c2)))
+
+Function name: mcdc_if::mcdc_check_tree_decision
+Raw bytes (87): 0x[01, 01, 08, 01, 05, 05, 0d, 05, 0d, 0d, 11, 09, 02, 1b, 1f, 0d, 11, 09, 02, 0a, 01, 27, 01, 03, 09, 28, 00, 03, 03, 08, 00, 15, 30, 05, 02, 01, 02, 00, 00, 08, 00, 09, 05, 00, 0e, 00, 0f, 30, 0d, 0a, 02, 00, 03, 00, 0e, 00, 0f, 0a, 00, 13, 00, 14, 30, 11, 09, 03, 00, 00, 00, 13, 00, 14, 1b, 00, 16, 02, 06, 1f, 02, 0c, 02, 06, 17, 03, 01, 00, 02]
+Number of files: 1
+- file 0 => global file 1
+Number of expressions: 8
+- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
+- expression 1 operands: lhs = Counter(1), rhs = Counter(3)
+- expression 2 operands: lhs = Counter(1), rhs = Counter(3)
+- expression 3 operands: lhs = Counter(3), rhs = Counter(4)
+- expression 4 operands: lhs = Counter(2), rhs = Expression(0, Sub)
+- expression 5 operands: lhs = Expression(6, Add), rhs = Expression(7, Add)
+- expression 6 operands: lhs = Counter(3), rhs = Counter(4)
+- expression 7 operands: lhs = Counter(2), rhs = Expression(0, Sub)
+Number of file 0 mappings: 10
+- Code(Counter(0)) at (prev + 39, 1) to (start + 3, 9)
+- MCDCDecision { bitmap_idx: 0, conditions_num: 3 } at (prev + 3, 8) to (start + 0, 21)
+- 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)
+- Code(Counter(1)) at (prev + 0, 14) to (start + 0, 15)
+- MCDCBranch { true: Counter(3), false: Expression(2, Sub), condition_id: 2, true_next_id: 0, false_next_id: 3 } at (prev + 0, 14) to (start + 0, 15)
+    true  = c3
+    false = (c1 - c3)
+- Code(Expression(2, Sub)) at (prev + 0, 19) to (start + 0, 20)
+    = (c1 - c3)
+- MCDCBranch { true: Counter(4), false: Counter(2), condition_id: 3, true_next_id: 0, false_next_id: 0 } at (prev + 0, 19) to (start + 0, 20)
+    true  = c4
+    false = c2
+- Code(Expression(6, Add)) at (prev + 0, 22) to (start + 2, 6)
+    = (c3 + c4)
+- Code(Expression(7, Add)) at (prev + 2, 12) to (start + 2, 6)
+    = (c2 + (c0 - c1))
+- Code(Expression(5, Add)) at (prev + 3, 1) to (start + 0, 2)
+    = ((c3 + c4) + (c2 + (c0 - c1)))
+
+Function name: mcdc_if::mcdc_nested_if
+Raw bytes (124): 0x[01, 01, 0d, 01, 05, 02, 09, 05, 09, 1b, 15, 05, 09, 1b, 15, 05, 09, 11, 15, 02, 09, 2b, 32, 0d, 2f, 11, 15, 02, 09, 0e, 01, 3b, 01, 01, 09, 28, 00, 02, 01, 08, 00, 0e, 30, 05, 02, 01, 00, 02, 00, 08, 00, 09, 02, 00, 0d, 00, 0e, 30, 09, 32, 02, 00, 00, 00, 0d, 00, 0e, 1b, 01, 09, 01, 0d, 28, 01, 02, 01, 0c, 00, 12, 30, 16, 15, 01, 02, 00, 00, 0c, 00, 0d, 16, 00, 11, 00, 12, 30, 0d, 11, 02, 00, 00, 00, 11, 00, 12, 0d, 00, 13, 02, 0a, 2f, 02, 0a, 00, 0b, 32, 01, 0c, 02, 06, 27, 03, 01, 00, 02]
+Number of files: 1
+- file 0 => global file 1
+Number of expressions: 13
+- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
+- expression 1 operands: lhs = Expression(0, Sub), rhs = Counter(2)
+- expression 2 operands: lhs = Counter(1), rhs = Counter(2)
+- expression 3 operands: lhs = Expression(6, Add), rhs = Counter(5)
+- expression 4 operands: lhs = Counter(1), rhs = Counter(2)
+- expression 5 operands: lhs = Expression(6, Add), rhs = Counter(5)
+- expression 6 operands: lhs = Counter(1), rhs = Counter(2)
+- expression 7 operands: lhs = Counter(4), rhs = Counter(5)
+- expression 8 operands: lhs = Expression(0, Sub), rhs = Counter(2)
+- expression 9 operands: lhs = Expression(10, Add), rhs = Expression(12, Sub)
+- expression 10 operands: lhs = Counter(3), rhs = Expression(11, Add)
+- expression 11 operands: lhs = Counter(4), rhs = Counter(5)
+- expression 12 operands: lhs = Expression(0, Sub), rhs = Counter(2)
+Number of file 0 mappings: 14
+- Code(Counter(0)) at (prev + 59, 1) to (start + 1, 9)
+- MCDCDecision { bitmap_idx: 0, conditions_num: 2 } at (prev + 1, 8) to (start + 0, 14)
+- MCDCBranch { true: Counter(1), false: Expression(0, Sub), condition_id: 1, true_next_id: 0, false_next_id: 2 } at (prev + 0, 8) to (start + 0, 9)
+    true  = c1
+    false = (c0 - c1)
+- Code(Expression(0, Sub)) at (prev + 0, 13) to (start + 0, 14)
+    = (c0 - c1)
+- MCDCBranch { true: Counter(2), false: Expression(12, Sub), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 13) to (start + 0, 14)
+    true  = c2
+    false = ((c0 - c1) - c2)
+- Code(Expression(6, Add)) at (prev + 1, 9) to (start + 1, 13)
+    = (c1 + c2)
+- MCDCDecision { bitmap_idx: 1, conditions_num: 2 } at (prev + 1, 12) to (start + 0, 18)
+- MCDCBranch { true: Expression(5, Sub), false: Counter(5), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 12) to (start + 0, 13)
+    true  = ((c1 + c2) - c5)
+    false = c5
+- Code(Expression(5, Sub)) at (prev + 0, 17) to (start + 0, 18)
+    = ((c1 + c2) - c5)
+- MCDCBranch { true: Counter(3), false: Counter(4), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 17) to (start + 0, 18)
+    true  = c3
+    false = c4
+- Code(Counter(3)) at (prev + 0, 19) to (start + 2, 10)
+- Code(Expression(11, Add)) at (prev + 2, 10) to (start + 0, 11)
+    = (c4 + c5)
+- Code(Expression(12, Sub)) at (prev + 1, 12) to (start + 2, 6)
+    = ((c0 - c1) - c2)
+- Code(Expression(9, Add)) at (prev + 3, 1) to (start + 0, 2)
+    = ((c3 + (c4 + c5)) + ((c0 - c1) - c2))
+
diff --git a/tests/coverage/mcdc_if.coverage b/tests/coverage/mcdc_if.coverage
new file mode 100644
index 0000000000000..c2ed311a5bc2c
--- /dev/null
+++ b/tests/coverage/mcdc_if.coverage
@@ -0,0 +1,262 @@
+   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|      2|fn mcdc_check_neither(a: bool, b: bool) {
+   LL|      2|    if a && b {
+                          ^0
+  ------------------
+  |---> MC/DC Decision Region (LL:8) to (LL:14)
+  |
+  |  Number of Conditions: 2
+  |     Condition C1 --> (LL:8)
+  |     Condition C2 --> (LL:13)
+  |
+  |  Executed MC/DC Test Vectors:
+  |
+  |     C1, C2    Result
+  |  1 { F,  -  = F      }
+  |
+  |  C1-Pair: not covered
+  |  C2-Pair: not covered
+  |  MC/DC Coverage for Decision: 0.00%
+  |
+  ------------------
+   LL|      0|        say("a and b");
+   LL|      2|    } else {
+   LL|      2|        say("not both");
+   LL|      2|    }
+   LL|      2|}
+   LL|       |
+   LL|      2|fn mcdc_check_a(a: bool, b: bool) {
+   LL|      2|    if a && b {
+                          ^1
+  ------------------
+  |---> MC/DC Decision Region (LL:8) to (LL:14)
+  |
+  |  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,  T  = T      }
+  |
+  |  C1-Pair: covered: (1,2)
+  |  C2-Pair: not covered
+  |  MC/DC Coverage for Decision: 50.00%
+  |
+  ------------------
+   LL|      1|        say("a and b");
+   LL|      1|    } else {
+   LL|      1|        say("not both");
+   LL|      1|    }
+   LL|      2|}
+   LL|       |
+   LL|      2|fn mcdc_check_b(a: bool, b: bool) {
+   LL|      2|    if a && b {
+  ------------------
+  |---> MC/DC Decision Region (LL:8) to (LL:14)
+  |
+  |  Number of Conditions: 2
+  |     Condition C1 --> (LL:8)
+  |     Condition C2 --> (LL:13)
+  |
+  |  Executed MC/DC Test Vectors:
+  |
+  |     C1, C2    Result
+  |  1 { T,  F  = F      }
+  |  2 { T,  T  = T      }
+  |
+  |  C1-Pair: not covered
+  |  C2-Pair: covered: (1,2)
+  |  MC/DC Coverage for Decision: 50.00%
+  |
+  ------------------
+   LL|      1|        say("a and b");
+   LL|      1|    } else {
+   LL|      1|        say("not both");
+   LL|      1|    }
+   LL|      2|}
+   LL|       |
+   LL|      3|fn mcdc_check_both(a: bool, b: bool) {
+   LL|      3|    if a && b {
+                          ^2
+  ------------------
+  |---> MC/DC Decision Region (LL:8) to (LL:14)
+  |
+  |  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("a and b");
+   LL|      2|    } else {
+   LL|      2|        say("not both");
+   LL|      2|    }
+   LL|      3|}
+   LL|       |
+   LL|      4|fn mcdc_check_tree_decision(a: bool, b: bool, c: bool) {
+   LL|      4|    // This expression is intentionally written in a way
+   LL|      4|    // where 100% branch coverage indicates 100% mcdc coverage.
+   LL|      4|    if a && (b || c) {
+                           ^3   ^2
+  ------------------
+  |---> MC/DC Decision Region (LL:8) to (LL:21)
+  |
+  |  Number of Conditions: 3
+  |     Condition C1 --> (LL:8)
+  |     Condition C2 --> (LL:14)
+  |     Condition C3 --> (LL:19)
+  |
+  |  Executed MC/DC Test Vectors:
+  |
+  |     C1, C2, C3    Result
+  |  1 { F,  -,  -  = F      }
+  |  2 { T,  F,  F  = F      }
+  |  3 { T,  T,  -  = T      }
+  |  4 { T,  F,  T  = T      }
+  |
+  |  C1-Pair: covered: (1,3)
+  |  C2-Pair: covered: (2,3)
+  |  C3-Pair: covered: (2,4)
+  |  MC/DC Coverage for Decision: 100.00%
+  |
+  ------------------
+   LL|      2|        say("pass");
+   LL|      2|    } else {
+   LL|      2|        say("reject");
+   LL|      2|    }
+   LL|      4|}
+   LL|       |
+   LL|      4|fn mcdc_check_not_tree_decision(a: bool, b: bool, c: bool) {
+   LL|      4|    // Contradict to `mcdc_check_tree_decision`,
+   LL|      4|    // 100% branch coverage of this expression does not mean indicates 100% mcdc coverage.
+   LL|      4|    if (a || b) && c {
+                           ^1
+  ------------------
+  |---> MC/DC Decision Region (LL:8) to (LL:21)
+  |
+  |  Number of Conditions: 3
+  |     Condition C1 --> (LL:9)
+  |     Condition C2 --> (LL:14)
+  |     Condition C3 --> (LL:20)
+  |
+  |  Executed MC/DC Test Vectors:
+  |
+  |     C1, C2, C3    Result
+  |  1 { T,  -,  F  = F      }
+  |  2 { T,  -,  T  = T      }
+  |  3 { F,  T,  T  = T      }
+  |
+  |  C1-Pair: not covered
+  |  C2-Pair: not covered
+  |  C3-Pair: covered: (1,2)
+  |  MC/DC Coverage for Decision: 33.33%
+  |
+  ------------------
+   LL|      2|        say("pass");
+   LL|      2|    } else {
+   LL|      2|        say("reject");
+   LL|      2|    }
+   LL|      4|}
+   LL|       |
+   LL|      3|fn mcdc_nested_if(a: bool, b: bool, c: bool) {
+   LL|      3|    if a || b {
+                          ^0
+  ------------------
+  |---> MC/DC Decision Region (LL:8) to (LL:14)
+  |
+  |  Number of Conditions: 2
+  |     Condition C1 --> (LL:8)
+  |     Condition C2 --> (LL:13)
+  |
+  |  Executed MC/DC Test Vectors:
+  |
+  |     C1, C2    Result
+  |  1 { T,  -  = T      }
+  |
+  |  C1-Pair: not covered
+  |  C2-Pair: not covered
+  |  MC/DC Coverage for Decision: 0.00%
+  |
+  ------------------
+   LL|      3|        say("a or b");
+   LL|      3|        if b && c {
+                              ^2
+  ------------------
+  |---> MC/DC Decision Region (LL:12) to (LL:18)
+  |
+  |  Number of Conditions: 2
+  |     Condition C1 --> (LL:12)
+  |     Condition C2 --> (LL:17)
+  |
+  |  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("b and c");
+   LL|      2|        }
+   LL|      0|    } else {
+   LL|      0|        say("neither a nor b");
+   LL|      0|    }
+   LL|      3|}
+   LL|       |
+   LL|       |#[coverage(off)]
+   LL|       |fn main() {
+   LL|       |    mcdc_check_neither(false, false);
+   LL|       |    mcdc_check_neither(false, true);
+   LL|       |
+   LL|       |    mcdc_check_a(true, true);
+   LL|       |    mcdc_check_a(false, true);
+   LL|       |
+   LL|       |    mcdc_check_b(true, true);
+   LL|       |    mcdc_check_b(true, false);
+   LL|       |
+   LL|       |    mcdc_check_both(false, true);
+   LL|       |    mcdc_check_both(true, true);
+   LL|       |    mcdc_check_both(true, false);
+   LL|       |
+   LL|       |    mcdc_check_tree_decision(false, true, true);
+   LL|       |    mcdc_check_tree_decision(true, true, false);
+   LL|       |    mcdc_check_tree_decision(true, false, false);
+   LL|       |    mcdc_check_tree_decision(true, false, true);
+   LL|       |
+   LL|       |    mcdc_check_not_tree_decision(false, true, true);
+   LL|       |    mcdc_check_not_tree_decision(true, true, false);
+   LL|       |    mcdc_check_not_tree_decision(true, false, false);
+   LL|       |    mcdc_check_not_tree_decision(true, false, true);
+   LL|       |
+   LL|       |    mcdc_nested_if(true, false, true);
+   LL|       |    mcdc_nested_if(true, true, true);
+   LL|       |    mcdc_nested_if(true, true, false);
+   LL|       |}
+   LL|       |
+   LL|       |#[coverage(off)]
+   LL|       |fn say(message: &str) {
+   LL|       |    core::hint::black_box(message);
+   LL|       |}
+
diff --git a/tests/coverage/mcdc_if.rs b/tests/coverage/mcdc_if.rs
new file mode 100644
index 0000000000000..a85843721c6ce
--- /dev/null
+++ b/tests/coverage/mcdc_if.rs
@@ -0,0 +1,103 @@
+#![feature(coverage_attribute)]
+//@ edition: 2021
+//@ min-llvm-version: 18
+//@ compile-flags: -Zcoverage-options=mcdc
+//@ llvm-cov-flags: --show-mcdc
+
+fn mcdc_check_neither(a: bool, b: bool) {
+    if a && b {
+        say("a and b");
+    } else {
+        say("not both");
+    }
+}
+
+fn mcdc_check_a(a: bool, b: bool) {
+    if a && b {
+        say("a and b");
+    } else {
+        say("not both");
+    }
+}
+
+fn mcdc_check_b(a: bool, b: bool) {
+    if a && b {
+        say("a and b");
+    } else {
+        say("not both");
+    }
+}
+
+fn mcdc_check_both(a: bool, b: bool) {
+    if a && b {
+        say("a and b");
+    } else {
+        say("not both");
+    }
+}
+
+fn mcdc_check_tree_decision(a: bool, b: bool, c: bool) {
+    // This expression is intentionally written in a way
+    // where 100% branch coverage indicates 100% mcdc coverage.
+    if a && (b || c) {
+        say("pass");
+    } else {
+        say("reject");
+    }
+}
+
+fn mcdc_check_not_tree_decision(a: bool, b: bool, c: bool) {
+    // Contradict to `mcdc_check_tree_decision`,
+    // 100% branch coverage of this expression does not mean indicates 100% mcdc coverage.
+    if (a || b) && c {
+        say("pass");
+    } else {
+        say("reject");
+    }
+}
+
+fn mcdc_nested_if(a: bool, b: bool, c: bool) {
+    if a || b {
+        say("a or b");
+        if b && c {
+            say("b and c");
+        }
+    } else {
+        say("neither a nor b");
+    }
+}
+
+#[coverage(off)]
+fn main() {
+    mcdc_check_neither(false, false);
+    mcdc_check_neither(false, true);
+
+    mcdc_check_a(true, true);
+    mcdc_check_a(false, true);
+
+    mcdc_check_b(true, true);
+    mcdc_check_b(true, false);
+
+    mcdc_check_both(false, true);
+    mcdc_check_both(true, true);
+    mcdc_check_both(true, false);
+
+    mcdc_check_tree_decision(false, true, true);
+    mcdc_check_tree_decision(true, true, false);
+    mcdc_check_tree_decision(true, false, false);
+    mcdc_check_tree_decision(true, false, true);
+
+    mcdc_check_not_tree_decision(false, true, true);
+    mcdc_check_not_tree_decision(true, true, false);
+    mcdc_check_not_tree_decision(true, false, false);
+    mcdc_check_not_tree_decision(true, false, true);
+
+    mcdc_nested_if(true, false, true);
+    mcdc_nested_if(true, true, true);
+    mcdc_nested_if(true, true, false);
+}
+
+#[coverage(off)]
+fn say(message: &str) {
+    core::hint::black_box(message);
+}
diff --git a/tests/coverage/mcdc_while.cov-map b/tests/coverage/mcdc_while.cov-map
new file mode 100644
index 0000000000000..e5e6c792f5604
--- /dev/null
+++ b/tests/coverage/mcdc_while.cov-map
@@ -0,0 +1,129 @@
+Function name: mcdc_while::mcdc_while_100_percent
+Raw bytes (103): 0x[01, 01, 10, 01, 2f, 05, 09, 03, 0d, 0d, 05, 3a, 3e, 0d, 05, 03, 0d, 37, 09, 3a, 3e, 0d, 05, 03, 0d, 05, 09, 37, 09, 3a, 3e, 0d, 05, 03, 0d, 0a, 01, 1a, 01, 01, 12, 28, 00, 03, 03, 0b, 00, 36, 03, 00, 0c, 00, 17, 30, 0d, 3e, 01, 03, 02, 00, 0c, 00, 17, 0d, 00, 1b, 00, 26, 30, 05, 3a, 03, 00, 02, 00, 1b, 00, 26, 37, 00, 2b, 00, 36, 30, 09, 32, 02, 00, 00, 00, 2b, 00, 36, 2f, 01, 09, 00, 0f, 32, 02, 01, 00, 02]
+Number of files: 1
+- file 0 => global file 1
+Number of expressions: 16
+- expression 0 operands: lhs = Counter(0), rhs = Expression(11, Add)
+- expression 1 operands: lhs = Counter(1), rhs = Counter(2)
+- expression 2 operands: lhs = Expression(0, Add), rhs = Counter(3)
+- expression 3 operands: lhs = Counter(3), rhs = Counter(1)
+- expression 4 operands: lhs = Expression(14, Sub), rhs = Expression(15, Sub)
+- expression 5 operands: lhs = Counter(3), rhs = Counter(1)
+- expression 6 operands: lhs = Expression(0, Add), rhs = Counter(3)
+- expression 7 operands: lhs = Expression(13, Add), rhs = Counter(2)
+- expression 8 operands: lhs = Expression(14, Sub), rhs = Expression(15, Sub)
+- expression 9 operands: lhs = Counter(3), rhs = Counter(1)
+- expression 10 operands: lhs = Expression(0, Add), rhs = Counter(3)
+- expression 11 operands: lhs = Counter(1), rhs = Counter(2)
+- expression 12 operands: lhs = Expression(13, Add), rhs = Counter(2)
+- expression 13 operands: lhs = Expression(14, Sub), rhs = Expression(15, Sub)
+- expression 14 operands: lhs = Counter(3), rhs = Counter(1)
+- expression 15 operands: lhs = Expression(0, Add), rhs = Counter(3)
+Number of file 0 mappings: 10
+- Code(Counter(0)) at (prev + 26, 1) to (start + 1, 18)
+- MCDCDecision { bitmap_idx: 0, conditions_num: 3 } at (prev + 3, 11) to (start + 0, 54)
+- Code(Expression(0, Add)) at (prev + 0, 12) to (start + 0, 23)
+    = (c0 + (c1 + c2))
+- MCDCBranch { true: Counter(3), false: Expression(15, Sub), condition_id: 1, true_next_id: 3, false_next_id: 2 } at (prev + 0, 12) to (start + 0, 23)
+    true  = c3
+    false = ((c0 + (c1 + c2)) - c3)
+- Code(Counter(3)) at (prev + 0, 27) to (start + 0, 38)
+- MCDCBranch { true: Counter(1), false: Expression(14, Sub), condition_id: 3, true_next_id: 0, false_next_id: 2 } at (prev + 0, 27) to (start + 0, 38)
+    true  = c1
+    false = (c3 - c1)
+- Code(Expression(13, Add)) at (prev + 0, 43) to (start + 0, 54)
+    = ((c3 - c1) + ((c0 + (c1 + c2)) - c3))
+- MCDCBranch { true: Counter(2), false: Expression(12, Sub), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 43) to (start + 0, 54)
+    true  = c2
+    false = (((c3 - c1) + ((c0 + (c1 + c2)) - c3)) - c2)
+- Code(Expression(11, Add)) at (prev + 1, 9) to (start + 0, 15)
+    = (c1 + c2)
+- Code(Expression(12, Sub)) at (prev + 2, 1) to (start + 0, 2)
+    = (((c3 - c1) + ((c0 + (c1 + c2)) - c3)) - c2)
+
+Function name: mcdc_while::mcdc_while_33_percent
+Raw bytes (103): 0x[01, 01, 10, 01, 2f, 05, 09, 03, 0d, 0d, 05, 3a, 3e, 0d, 05, 03, 0d, 37, 09, 3a, 3e, 0d, 05, 03, 0d, 05, 09, 37, 09, 3a, 3e, 0d, 05, 03, 0d, 0a, 01, 07, 01, 02, 12, 28, 00, 03, 04, 0b, 00, 36, 03, 00, 0c, 00, 17, 30, 0d, 3e, 01, 03, 02, 00, 0c, 00, 17, 0d, 00, 1b, 00, 26, 30, 05, 3a, 03, 00, 02, 00, 1b, 00, 26, 37, 00, 2b, 00, 36, 30, 09, 32, 02, 00, 00, 00, 2b, 00, 36, 2f, 01, 09, 00, 0f, 32, 02, 01, 00, 02]
+Number of files: 1
+- file 0 => global file 1
+Number of expressions: 16
+- expression 0 operands: lhs = Counter(0), rhs = Expression(11, Add)
+- expression 1 operands: lhs = Counter(1), rhs = Counter(2)
+- expression 2 operands: lhs = Expression(0, Add), rhs = Counter(3)
+- expression 3 operands: lhs = Counter(3), rhs = Counter(1)
+- expression 4 operands: lhs = Expression(14, Sub), rhs = Expression(15, Sub)
+- expression 5 operands: lhs = Counter(3), rhs = Counter(1)
+- expression 6 operands: lhs = Expression(0, Add), rhs = Counter(3)
+- expression 7 operands: lhs = Expression(13, Add), rhs = Counter(2)
+- expression 8 operands: lhs = Expression(14, Sub), rhs = Expression(15, Sub)
+- expression 9 operands: lhs = Counter(3), rhs = Counter(1)
+- expression 10 operands: lhs = Expression(0, Add), rhs = Counter(3)
+- expression 11 operands: lhs = Counter(1), rhs = Counter(2)
+- expression 12 operands: lhs = Expression(13, Add), rhs = Counter(2)
+- expression 13 operands: lhs = Expression(14, Sub), rhs = Expression(15, Sub)
+- expression 14 operands: lhs = Counter(3), rhs = Counter(1)
+- expression 15 operands: lhs = Expression(0, Add), rhs = Counter(3)
+Number of file 0 mappings: 10
+- Code(Counter(0)) at (prev + 7, 1) to (start + 2, 18)
+- MCDCDecision { bitmap_idx: 0, conditions_num: 3 } at (prev + 4, 11) to (start + 0, 54)
+- Code(Expression(0, Add)) at (prev + 0, 12) to (start + 0, 23)
+    = (c0 + (c1 + c2))
+- MCDCBranch { true: Counter(3), false: Expression(15, Sub), condition_id: 1, true_next_id: 3, false_next_id: 2 } at (prev + 0, 12) to (start + 0, 23)
+    true  = c3
+    false = ((c0 + (c1 + c2)) - c3)
+- Code(Counter(3)) at (prev + 0, 27) to (start + 0, 38)
+- MCDCBranch { true: Counter(1), false: Expression(14, Sub), condition_id: 3, true_next_id: 0, false_next_id: 2 } at (prev + 0, 27) to (start + 0, 38)
+    true  = c1
+    false = (c3 - c1)
+- Code(Expression(13, Add)) at (prev + 0, 43) to (start + 0, 54)
+    = ((c3 - c1) + ((c0 + (c1 + c2)) - c3))
+- MCDCBranch { true: Counter(2), false: Expression(12, Sub), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 43) to (start + 0, 54)
+    true  = c2
+    false = (((c3 - c1) + ((c0 + (c1 + c2)) - c3)) - c2)
+- Code(Expression(11, Add)) at (prev + 1, 9) to (start + 0, 15)
+    = (c1 + c2)
+- Code(Expression(12, Sub)) at (prev + 2, 1) to (start + 0, 2)
+    = (((c3 - c1) + ((c0 + (c1 + c2)) - c3)) - c2)
+
+Function name: mcdc_while::mcdc_while_66_percent
+Raw bytes (103): 0x[01, 01, 10, 01, 2f, 05, 09, 03, 0d, 0d, 05, 3a, 3e, 0d, 05, 03, 0d, 37, 09, 3a, 3e, 0d, 05, 03, 0d, 05, 09, 37, 09, 3a, 3e, 0d, 05, 03, 0d, 0a, 01, 10, 01, 03, 12, 28, 00, 03, 05, 0b, 00, 36, 03, 00, 0c, 00, 17, 30, 0d, 3e, 01, 03, 02, 00, 0c, 00, 17, 0d, 00, 1b, 00, 26, 30, 05, 3a, 03, 00, 02, 00, 1b, 00, 26, 37, 00, 2b, 00, 36, 30, 09, 32, 02, 00, 00, 00, 2b, 00, 36, 2f, 01, 09, 00, 0f, 32, 02, 01, 00, 02]
+Number of files: 1
+- file 0 => global file 1
+Number of expressions: 16
+- expression 0 operands: lhs = Counter(0), rhs = Expression(11, Add)
+- expression 1 operands: lhs = Counter(1), rhs = Counter(2)
+- expression 2 operands: lhs = Expression(0, Add), rhs = Counter(3)
+- expression 3 operands: lhs = Counter(3), rhs = Counter(1)
+- expression 4 operands: lhs = Expression(14, Sub), rhs = Expression(15, Sub)
+- expression 5 operands: lhs = Counter(3), rhs = Counter(1)
+- expression 6 operands: lhs = Expression(0, Add), rhs = Counter(3)
+- expression 7 operands: lhs = Expression(13, Add), rhs = Counter(2)
+- expression 8 operands: lhs = Expression(14, Sub), rhs = Expression(15, Sub)
+- expression 9 operands: lhs = Counter(3), rhs = Counter(1)
+- expression 10 operands: lhs = Expression(0, Add), rhs = Counter(3)
+- expression 11 operands: lhs = Counter(1), rhs = Counter(2)
+- expression 12 operands: lhs = Expression(13, Add), rhs = Counter(2)
+- expression 13 operands: lhs = Expression(14, Sub), rhs = Expression(15, Sub)
+- expression 14 operands: lhs = Counter(3), rhs = Counter(1)
+- expression 15 operands: lhs = Expression(0, Add), rhs = Counter(3)
+Number of file 0 mappings: 10
+- Code(Counter(0)) at (prev + 16, 1) to (start + 3, 18)
+- MCDCDecision { bitmap_idx: 0, conditions_num: 3 } at (prev + 5, 11) to (start + 0, 54)
+- Code(Expression(0, Add)) at (prev + 0, 12) to (start + 0, 23)
+    = (c0 + (c1 + c2))
+- MCDCBranch { true: Counter(3), false: Expression(15, Sub), condition_id: 1, true_next_id: 3, false_next_id: 2 } at (prev + 0, 12) to (start + 0, 23)
+    true  = c3
+    false = ((c0 + (c1 + c2)) - c3)
+- Code(Counter(3)) at (prev + 0, 27) to (start + 0, 38)
+- MCDCBranch { true: Counter(1), false: Expression(14, Sub), condition_id: 3, true_next_id: 0, false_next_id: 2 } at (prev + 0, 27) to (start + 0, 38)
+    true  = c1
+    false = (c3 - c1)
+- Code(Expression(13, Add)) at (prev + 0, 43) to (start + 0, 54)
+    = ((c3 - c1) + ((c0 + (c1 + c2)) - c3))
+- MCDCBranch { true: Counter(2), false: Expression(12, Sub), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 43) to (start + 0, 54)
+    true  = c2
+    false = (((c3 - c1) + ((c0 + (c1 + c2)) - c3)) - c2)
+- Code(Expression(11, Add)) at (prev + 1, 9) to (start + 0, 15)
+    = (c1 + c2)
+- Code(Expression(12, Sub)) at (prev + 2, 1) to (start + 0, 2)
+    = (((c3 - c1) + ((c0 + (c1 + c2)) - c3)) - c2)
+
diff --git a/tests/coverage/mcdc_while.coverage b/tests/coverage/mcdc_while.coverage
new file mode 100644
index 0000000000000..f431c6be78c04
--- /dev/null
+++ b/tests/coverage/mcdc_while.coverage
@@ -0,0 +1,113 @@
+   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|      1|fn mcdc_while_33_percent() {
+   LL|      1|    let values = [(false, true, true), (true, false, true), (true, false, false)];
+   LL|      1|    let mut i = 0;
+   LL|       |
+   LL|      3|    while (values[i].0 && values[i].1) || values[i].2 {
+                                        ^2
+  ------------------
+  |---> MC/DC Decision Region (LL:11) to (LL:54)
+  |
+  |  Number of Conditions: 3
+  |     Condition C1 --> (LL:12)
+  |     Condition C2 --> (LL:27)
+  |     Condition C3 --> (LL:43)
+  |
+  |  Executed MC/DC Test Vectors:
+  |
+  |     C1, C2, C3    Result
+  |  1 { T,  F,  F  = F      }
+  |  2 { F,  -,  T  = T      }
+  |  3 { T,  F,  T  = T      }
+  |
+  |  C1-Pair: not covered
+  |  C2-Pair: not covered
+  |  C3-Pair: covered: (1,3)
+  |  MC/DC Coverage for Decision: 33.33%
+  |
+  ------------------
+   LL|      2|        i += 1
+   LL|       |    }
+   LL|      1|}
+   LL|       |
+   LL|      1|fn mcdc_while_66_percent() {
+   LL|      1|    let values =
+   LL|      1|        [(false, true, true), (true, false, true), (true, true, true), (false, false, false)];
+   LL|      1|    let mut i = 0;
+   LL|       |
+   LL|      4|    while (values[i].0 && values[i].1) || values[i].2 {
+                                        ^2              ^3
+  ------------------
+  |---> MC/DC Decision Region (LL:11) to (LL:54)
+  |
+  |  Number of Conditions: 3
+  |     Condition C1 --> (LL:12)
+  |     Condition C2 --> (LL:27)
+  |     Condition C3 --> (LL:43)
+  |
+  |  Executed MC/DC Test Vectors:
+  |
+  |     C1, C2, C3    Result
+  |  1 { F,  -,  F  = F      }
+  |  2 { F,  -,  T  = T      }
+  |  3 { T,  F,  T  = T      }
+  |  4 { T,  T,  -  = T      }
+  |
+  |  C1-Pair: covered: (1,4)
+  |  C2-Pair: not covered
+  |  C3-Pair: covered: (1,2)
+  |  MC/DC Coverage for Decision: 66.67%
+  |
+  ------------------
+   LL|      3|        i += 1
+   LL|       |    }
+   LL|      1|}
+   LL|       |
+   LL|      2|fn mcdc_while_100_percent(values: &[(bool, bool, bool)]) {
+   LL|      2|    let mut i = 0;
+   LL|       |
+   LL|      4|    while (values[i].0 && values[i].1) || values[i].2 {
+                                        ^2              ^3
+  ------------------
+  |---> MC/DC Decision Region (LL:11) to (LL:54)
+  |
+  |  Number of Conditions: 3
+  |     Condition C1 --> (LL:12)
+  |     Condition C2 --> (LL:27)
+  |     Condition C3 --> (LL:43)
+  |
+  |  Executed MC/DC Test Vectors:
+  |
+  |     C1, C2, C3    Result
+  |  1 { F,  -,  F  = F      }
+  |  2 { T,  F,  F  = F      }
+  |  3 { F,  -,  T  = T      }
+  |  4 { T,  T,  -  = T      }
+  |
+  |  C1-Pair: covered: (1,4)
+  |  C2-Pair: covered: (2,4)
+  |  C3-Pair: covered: (1,3)
+  |  MC/DC Coverage for Decision: 100.00%
+  |
+  ------------------
+   LL|      2|        i += 1
+   LL|       |    }
+   LL|      2|}
+   LL|       |
+   LL|       |#[coverage(off)]
+   LL|       |fn main() {
+   LL|       |    mcdc_while_33_percent();
+   LL|       |
+   LL|       |    mcdc_while_66_percent();
+   LL|       |
+   LL|       |    let values_1 = [(true, true, true), (true, false, false)];
+   LL|       |    let values_2 = [(false, true, true), (false, false, false)];
+   LL|       |    mcdc_while_100_percent(&values_1);
+   LL|       |    mcdc_while_100_percent(&values_2);
+   LL|       |}
+
diff --git a/tests/coverage/mcdc_while.rs b/tests/coverage/mcdc_while.rs
new file mode 100644
index 0000000000000..0009a2569d786
--- /dev/null
+++ b/tests/coverage/mcdc_while.rs
@@ -0,0 +1,44 @@
+#![feature(coverage_attribute)]
+//@ edition: 2021
+//@ min-llvm-version: 18
+//@ compile-flags: -Zcoverage-options=mcdc
+//@ llvm-cov-flags: --show-mcdc
+
+fn mcdc_while_33_percent() {
+    let values = [(false, true, true), (true, false, true), (true, false, false)];
+    let mut i = 0;
+
+    while (values[i].0 && values[i].1) || values[i].2 {
+        i += 1
+    }
+}
+
+fn mcdc_while_66_percent() {
+    let values =
+        [(false, true, true), (true, false, true), (true, true, true), (false, false, false)];
+    let mut i = 0;
+
+    while (values[i].0 && values[i].1) || values[i].2 {
+        i += 1
+    }
+}
+
+fn mcdc_while_100_percent(values: &[(bool, bool, bool)]) {
+    let mut i = 0;
+
+    while (values[i].0 && values[i].1) || values[i].2 {
+        i += 1
+    }
+}
+
+#[coverage(off)]
+fn main() {
+    mcdc_while_33_percent();
+
+    mcdc_while_66_percent();
+
+    let values_1 = [(true, true, true), (true, false, false)];
+    let values_2 = [(false, true, true), (false, false, false)];
+    mcdc_while_100_percent(&values_1);
+    mcdc_while_100_percent(&values_2);
+}
diff --git a/tests/ui/instrument-coverage/coverage-options.bad.stderr b/tests/ui/instrument-coverage/coverage-options.bad.stderr
index ca82dc5bdb93d..f6e5421f87845 100644
--- a/tests/ui/instrument-coverage/coverage-options.bad.stderr
+++ b/tests/ui/instrument-coverage/coverage-options.bad.stderr
@@ -1,2 +1,2 @@
-error: incorrect value `bad` for unstable option `coverage-options` - `branch` or `no-branch` was expected
+error: incorrect value `bad` for unstable option `coverage-options` - either  `no-branch`, `branch` or `mcdc` was expected
 
diff --git a/tests/ui/instrument-coverage/coverage-options.rs b/tests/ui/instrument-coverage/coverage-options.rs
index a62e0554f7690..50c01ed29b5ec 100644
--- a/tests/ui/instrument-coverage/coverage-options.rs
+++ b/tests/ui/instrument-coverage/coverage-options.rs
@@ -8,7 +8,13 @@
 //@ [no-branch] check-pass
 //@ [no-branch] compile-flags: -Zcoverage-options=no-branch
 
+//@ [mcdc] check-pass
+//@ [mcdc] compile-flags: -Zcoverage-options=mcdc
+
 //@ [bad] check-fail
 //@ [bad] compile-flags: -Zcoverage-options=bad
 
+//@ [conflict] check-fail
+//@ [conflict] compile-flags: -Zcoverage-options=no-branch,mcdc
+
 fn main() {}