diff --git a/compiler/rustc_mir_build/src/build/coverageinfo/mcdc.rs b/compiler/rustc_mir_build/src/build/coverageinfo/mcdc.rs index 46b4806dad9bf..27c08856679c8 100644 --- a/compiler/rustc_mir_build/src/build/coverageinfo/mcdc.rs +++ b/compiler/rustc_mir_build/src/build/coverageinfo/mcdc.rs @@ -15,11 +15,6 @@ use rustc_span::Span; use crate::build::Builder; use crate::errors::{MCDCExceedsConditionLimit, MCDCExceedsDecisionDepth}; -/// 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_IN_DECISION: usize = 6; - /// MCDC allocates an i32 variable on stack for each depth. Ignore decisions nested too much to prevent it /// consuming excessive memory. const MAX_DECISION_DEPTH: u16 = 0x3FFF; @@ -356,6 +351,7 @@ pub(crate) struct MCDCInfoBuilder { mcdc_targets: FxIndexMap, state: MCDCState, decision_id_gen: DecisionIdGen, + required_num_test_vectors: usize, } impl MCDCInfoBuilder { @@ -365,6 +361,7 @@ impl MCDCInfoBuilder { mcdc_targets: FxIndexMap::default(), state: MCDCState::new(), decision_id_gen: DecisionIdGen::default(), + required_num_test_vectors: 0, } } @@ -396,27 +393,30 @@ impl MCDCInfoBuilder { conditions: Vec, ) -> Option<&mut MCDCTargetInfo> { let num_conditions = conditions.len(); - match num_conditions { - 0 => { - unreachable!("Decision with no condition is not expected"); - } - // Ignore decisions with only one condition given that mcdc for them is completely equivalent to branch coverage. - 2..=MAX_CONDITIONS_IN_DECISION => { - let info = MCDCTargetInfo::new(decision, conditions); - Some(self.mcdc_targets.entry(id).or_insert(info)) - } - _ => { - self.append_normal_branches(conditions); - if num_conditions > MAX_CONDITIONS_IN_DECISION { - tcx.dcx().emit_warn(MCDCExceedsConditionLimit { - span: decision.span, - num_conditions, - max_conditions: MAX_CONDITIONS_IN_DECISION, - }); - } - None - } + let max_conditions = tcx.sess.coverage_mcdc_max_conditions_per_decision(); + let max_test_vectors = tcx.sess.coverage_mcdc_max_test_vectors(); + // Ignore decisions with only one condition given that mcdc for them is completely equivalent to branch coverage. + if num_conditions <= 1 { + return None; + } + if num_conditions > max_conditions { + self.append_normal_branches(conditions); + tcx.dcx().emit_warn(MCDCExceedsConditionLimit { + span: decision.span, + num_conditions, + max_conditions, + }); + return None; } + + let info = MCDCTargetInfo::new(decision, conditions); + let expected_num_tv = info.decision.num_test_vectors + self.required_num_test_vectors; + if expected_num_tv > max_test_vectors { + self.append_normal_branches(info.conditions); + return None; + } + + Some(self.mcdc_targets.entry(id).or_insert(info)) } fn normalize_depth_from(&mut self, tcx: TyCtxt<'_>, id: DecisionId) { @@ -499,8 +499,13 @@ impl MCDCInfoBuilder { pub(crate) fn into_done( self, ) -> (Vec, Vec<(MCDCDecisionSpan, Vec)>) { - let MCDCInfoBuilder { normal_branch_spans, mcdc_targets, state: _, decision_id_gen: _ } = - self; + let MCDCInfoBuilder { + normal_branch_spans, + mcdc_targets, + state: _, + decision_id_gen: _, + required_num_test_vectors: _, + } = self; let mcdc_spans = mcdc_targets .into_values() diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 24143808ef492..0d1d4188af55f 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -146,7 +146,7 @@ pub enum InstrumentCoverage { } /// Individual flag values controlled by `-Z coverage-options`. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct CoverageOptions { pub level: CoverageLevel, @@ -157,6 +157,33 @@ pub struct CoverageOptions { /// For internal debugging only. If other code changes would make it hard /// to keep supporting this flag, remove it. pub no_mir_spans: bool, + + /// Limit for number of conditions in a decision. By default the limit is + /// `32767` and user defined limit shall not be larger than it. + pub mcdc_max_conditions: usize, + + /// Limit for number of test vectors of all decisions in a function. + /// Each test vectors takes up 1 bit memory. By default the limit is + /// `2,147,483,646` and user defined limit shall not be larger than it. + pub mcdc_max_test_vectors: usize, +} + +impl Default for CoverageOptions { + fn default() -> Self { + Self { + level: CoverageLevel::default(), + no_mir_spans: false, + mcdc_max_conditions: Self::MCDC_MAX_CONDITIONS_IN_DECISION, + mcdc_max_test_vectors: Self::MCDC_MAX_TEST_VECTORS, + } + } +} + +impl CoverageOptions { + // Maxium number of conditions in a decision specified by LLVM. + pub const MCDC_MAX_CONDITIONS_IN_DECISION: usize = 0x7fff; + // Maxium number of test vectors in a function specified by LLVM. + pub const MCDC_MAX_TEST_VECTORS: usize = 0x7fffffff; } /// Controls whether branch coverage or MC/DC coverage is enabled. diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 145af50117cbf..9da1e104971f5 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -965,7 +965,23 @@ mod parse { "condition" => slot.level = CoverageLevel::Condition, "mcdc" => slot.level = CoverageLevel::Mcdc, "no-mir-spans" => slot.no_mir_spans = true, - _ => return false, + _ => { + if let Some(max_conditions) = option + .strip_prefix("mcdc-max-conditions=") + .and_then(|num| num.parse::().ok()) + { + slot.mcdc_max_conditions = + max_conditions.min(CoverageOptions::MCDC_MAX_CONDITIONS_IN_DECISION); + } else if let Some(max_test_vectors) = option + .strip_prefix("mcdc-test-vectors=") + .and_then(|num| num.parse::().ok()) + { + slot.mcdc_max_test_vectors = + max_test_vectors.min(CoverageOptions::MCDC_MAX_TEST_VECTORS); + } else { + return false; + } + } } } true diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 89d029fa6e0ed..a6af15b14e00b 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -368,6 +368,14 @@ impl Session { self.opts.unstable_opts.coverage_options.no_mir_spans } + pub fn coverage_mcdc_max_conditions_per_decision(&self) -> usize { + self.opts.unstable_opts.coverage_options.mcdc_max_conditions + } + + pub fn coverage_mcdc_max_test_vectors(&self) -> usize { + self.opts.unstable_opts.coverage_options.mcdc_max_test_vectors + } + pub fn is_sanitizer_cfi_enabled(&self) -> bool { self.opts.unstable_opts.sanitizer.contains(SanitizerSet::CFI) }