Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support mcdc analysis for pattern matching #124278

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,13 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> {
bx.mcdc_tvbitmap_update(fn_name, hash, bitmap_index, cond_bitmap);
bx.mcdc_condbitmap_reset(cond_bitmap);
}
CoverageKind::CondBitmapReset { decision_depth } => {
drop(coverage_map);
let cond_bitmap = bx.coverage_cx()
.try_get_mcdc_condition_bitmap(&instance, decision_depth)
.expect("mcdc cond bitmap should have been allocated for merging into the global bitmap");
bx.mcdc_condbitmap_reset(cond_bitmap);
}
}
}
}
Expand Down
132 changes: 126 additions & 6 deletions compiler/rustc_middle/src/mir/coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ rustc_index::newtype_index! {
pub struct ExpressionId {}
}

rustc_index::newtype_index! {
/// ID of a mcdc decision. Used to identify decision in a function.
#[derive(HashStable)]
#[encodable]
#[orderable]
#[debug_format = "DecisionId({})"]
pub struct DecisionId {}
}

rustc_index::newtype_index! {
/// ID of a mcdc condition. Used by llvm to check mcdc coverage.
///
Expand Down Expand Up @@ -131,6 +140,11 @@ pub enum CoverageKind {
/// This is eventually lowered to instruments updating mcdc temp variables.
CondBitmapUpdate { index: u32, decision_depth: u16 },

/// Marks the point in MIR control flow where a condition bitmap is reset.
///
/// This is eventually lowered to instruments set the mcdc temp variable to zero.
CondBitmapReset { decision_depth: u16 },

/// 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.
Expand All @@ -148,6 +162,9 @@ impl Debug for CoverageKind {
CondBitmapUpdate { index, decision_depth } => {
write!(fmt, "CondBitmapUpdate(index={:?}, depth={:?})", index, decision_depth)
}
CondBitmapReset { decision_depth } => {
write!(fmt, "CondBitmapReset(depth={:?})", decision_depth)
}
TestVectorBitmapUpdate { bitmap_idx, decision_depth } => {
write!(fmt, "TestVectorUpdate({:?}, depth={:?})", bitmap_idx, decision_depth)
}
Expand Down Expand Up @@ -271,7 +288,7 @@ pub struct CoverageInfoHi {
pub branch_spans: Vec<BranchSpan>,
/// Branch spans generated by mcdc. Because of some limits mcdc builder give up generating
/// decisions including them so that they are handled as normal branch spans.
pub mcdc_degraded_branch_spans: Vec<MCDCBranchSpan>,
pub mcdc_degraded_spans: Vec<MCDCBranchSpan>,
pub mcdc_spans: Vec<(MCDCDecisionSpan, Vec<MCDCBranchSpan>)>,
}

Expand All @@ -283,7 +300,7 @@ pub struct BranchSpan {
pub false_marker: BlockMarkerId,
}

#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
pub struct ConditionInfo {
pub condition_id: ConditionId,
Expand All @@ -296,8 +313,11 @@ pub struct ConditionInfo {
pub struct MCDCBranchSpan {
pub span: Span,
pub condition_info: ConditionInfo,
pub true_marker: BlockMarkerId,
pub false_marker: BlockMarkerId,
// For boolean decisions and most pattern matching decisions there is only
// one true marker and one false marker in each branch. But for matching decisions
// with `|` there can be several.
pub true_markers: Vec<BlockMarkerId>,
pub false_markers: Vec<BlockMarkerId>,
}

#[derive(Copy, Clone, Debug)]
Expand All @@ -311,7 +331,107 @@ pub struct DecisionInfo {
#[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
pub struct MCDCDecisionSpan {
pub span: Span,
pub end_markers: Vec<BlockMarkerId>,
// Blocks where update test vectors of the decision.
pub update_end_markers: Vec<BlockMarkerId>,
// Block where discard written condition bitmap of the decision.
pub discard_end_markers: Vec<BlockMarkerId>,
pub decision_depth: u16,
pub num_conditions: usize,
}

impl MCDCDecisionSpan {
pub fn new(span: Span) -> Self {
Self {
span,
update_end_markers: Vec::new(),
discard_end_markers: Vec::new(),
decision_depth: 0,
}
}
}

/// Identify subcandidates in a candidate.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SubcandidateId(usize);

impl SubcandidateId {
pub const ROOT: SubcandidateId = SubcandidateId(0);
pub fn is_root(&self) -> bool {
*self == Self::ROOT
}

pub fn next_subcandidate_id(&self) -> Self {
Self(self.0 + 1)
}
}

/// Identify MatchPair in a candidate.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MatchPairId(usize);

impl MatchPairId {
pub const INVALID: MatchPairId = MatchPairId(0);
pub const START: MatchPairId = MatchPairId(1);
pub fn next_match_pair_id(&self) -> Self {
Self(self.0 + 1)
}
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct CandidateCovId {
pub decision_id: DecisionId,
pub subcandidate_id: SubcandidateId,
}

impl Default for CandidateCovId {
fn default() -> Self {
Self { decision_id: DecisionId::MAX, subcandidate_id: SubcandidateId(usize::MAX) }
}
}

impl CandidateCovId {
/// Return `true` is this `CandidateCovId` is assigned properly through coverage mechanism
/// and can be mapped as a decision.
pub fn is_valid(self) -> bool {
self != Self::default()
}

pub fn new_match_info(
&mut self,
match_id: MatchPairId,
span: Span,
fully_matched: bool,
) -> MatchCoverageInfo {
let key = MatchKey {
decision_id: self.decision_id,
match_id,
subcandidate_id: self.subcandidate_id,
};
MatchCoverageInfo { key, span, fully_matched }
}
}

/// Key for matched patterns.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct MatchKey {
pub decision_id: DecisionId,
pub match_id: MatchPairId,
pub subcandidate_id: SubcandidateId,
}

impl Default for MatchKey {
fn default() -> Self {
Self {
decision_id: DecisionId::MAX,
match_id: MatchPairId(0),
subcandidate_id: SubcandidateId(0),
}
}
}

/// Information about matched patterns.
#[derive(Clone, Copy, Debug)]
pub struct MatchCoverageInfo {
pub key: MatchKey,
pub span: Span,
pub fully_matched: bool,
}
21 changes: 12 additions & 9 deletions compiler/rustc_middle/src/mir/pretty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ fn write_coverage_info_hi(
let coverage::CoverageInfoHi {
num_block_markers: _,
branch_spans,
mcdc_degraded_branch_spans,
mcdc_degraded_spans,
mcdc_spans,
} = coverage_info_hi;

Expand All @@ -553,32 +553,35 @@ fn write_coverage_info_hi(
did_print = true;
}

for coverage::MCDCBranchSpan { span, true_marker, false_marker, .. } in
mcdc_degraded_branch_spans
{
for coverage::MCDCBranchSpan { span, true_markers, false_markers, .. } in mcdc_degraded_spans {
writeln!(
w,
"{INDENT}coverage branch {{ true: {true_marker:?}, false: {false_marker:?} }} => {span:?}",
"{INDENT}coverage branch {{ true: {true_markers:?}, false: {false_markers:?} }} => {span:?}",
)?;
did_print = true;
}

for (
coverage::MCDCDecisionSpan { span, end_markers, decision_depth, num_conditions: _ },
coverage::MCDCDecisionSpan {
span,
update_end_markers: end_markers,
discard_end_markers: discard_markers,
decision_depth,
},
conditions,
) in mcdc_spans
{
let num_conditions = conditions.len();
writeln!(
w,
"{INDENT}coverage mcdc decision {{ num_conditions: {num_conditions:?}, end: {end_markers:?}, depth: {decision_depth:?} }} => {span:?}"
"{INDENT}coverage mcdc decision {{ num_conditions: {num_conditions:?}, end: {end_markers:?}, discard_markers: {discard_markers:?} depth: {decision_depth:?} }} => {span:?}"
)?;
for coverage::MCDCBranchSpan { span, condition_info, true_marker, false_marker } in
for coverage::MCDCBranchSpan { span, condition_info, true_markers, false_markers } in
conditions
{
writeln!(
w,
"{INDENT}coverage mcdc branch {{ condition_id: {:?}, true: {true_marker:?}, false: {false_marker:?} }} => {span:?}",
"{INDENT}coverage mcdc branch {{ condition_id: {:?}, true: {true_markers:?}, false: {false_markers:?} }} => {span:?}",
condition_info.condition_id
)?;
}
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_mir_build/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ mir_build_deref_raw_pointer_requires_unsafe_unsafe_op_in_unsafe_fn_allowed =

mir_build_exceeds_mcdc_condition_limit = number of conditions in decision ({$num_conditions}) exceeds limit ({$max_conditions}), so MC/DC analysis will not count this expression

mir_build_exceeds_mcdc_decision_depth = number of decisions evaluated simultaneously exceeds limit ({$max_decision_depth}). 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
Expand Down
8 changes: 4 additions & 4 deletions compiler/rustc_mir_build/src/build/coverageinfo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,10 @@ impl CoverageInfoBuilder {
// Separate path for handling branches when MC/DC is enabled.
if let Some(mcdc_info) = self.mcdc_info.as_mut() {
let inject_block_marker =
|source_info, block| self.markers.inject_block_marker(cfg, source_info, block);
|block| self.markers.inject_block_marker(cfg, source_info, block);
mcdc_info.visit_evaluated_condition(
tcx,
source_info,
source_info.span,
true_block,
false_block,
inject_block_marker,
Expand Down Expand Up @@ -175,15 +175,15 @@ impl CoverageInfoBuilder {
let branch_spans =
branch_info.map(|branch_info| branch_info.branch_spans).unwrap_or_default();

let (mcdc_spans, mcdc_degraded_branch_spans) =
let (mcdc_degraded_spans, mcdc_spans) =
mcdc_info.map(MCDCInfoBuilder::into_done).unwrap_or_default();

// For simplicity, always return an info struct (without Option), even
// if there's nothing interesting in it.
Box::new(CoverageInfoHi {
num_block_markers,
branch_spans,
mcdc_degraded_branch_spans,
mcdc_degraded_spans,
mcdc_spans,
})
}
Expand Down
Loading
Loading