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

coverage: Prepare for improved branch coverage #124217

Merged
merged 8 commits into from
Apr 23, 2024
4 changes: 3 additions & 1 deletion compiler/rustc_middle/src/mir/coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,9 @@ impl Default for ConditionInfo {
#[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
pub struct MCDCBranchSpan {
pub span: Span,
pub condition_info: ConditionInfo,
/// If `None`, this actually represents a normal branch span inserted for
/// code that was too complex for MC/DC.
pub condition_info: Option<ConditionInfo>,
pub true_marker: BlockMarkerId,
pub false_marker: BlockMarkerId,
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_middle/src/mir/pretty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ fn write_coverage_branch_info(
writeln!(
w,
"{INDENT}coverage mcdc branch {{ condition_id: {:?}, true: {true_marker:?}, false: {false_marker:?} }} => {span:?}",
condition_info.condition_id
condition_info.map(|info| info.condition_id)
)?;
}

Expand Down
103 changes: 55 additions & 48 deletions compiler/rustc_mir_build/src/build/coverageinfo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ use rustc_middle::mir::coverage::{
BlockMarkerId, BranchSpan, ConditionId, ConditionInfo, CoverageKind, MCDCBranchSpan,
MCDCDecisionSpan,
};
use rustc_middle::mir::{self, BasicBlock, UnOp};
use rustc_middle::mir::{self, BasicBlock, SourceInfo, UnOp};
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::build::{Builder, CFG};
use crate::errors::MCDCExceedsConditionNumLimit;

pub(crate) struct BranchInfoBuilder {
Expand All @@ -22,6 +22,7 @@ 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>,
Expand Down Expand Up @@ -95,13 +96,7 @@ 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(
fn fetch_mcdc_condition_info(
&mut self,
tcx: TyCtxt<'_>,
true_marker: BlockMarkerId,
Expand All @@ -121,14 +116,9 @@ impl BranchInfoBuilder {
_ => {
// 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,
},
));
for branch in &mut self.mcdc_branch_spans[rebase_idx..] {
branch.condition_info = None;
}
Comment on lines +119 to +121
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the MC/DC code gives up on full MC/DC instrumentation, instead of creating a BranchSpan, it now creates an MCDCBranchSpan with condition_info: None.

That span will eventually get turned back into an ordinary branch region, in the coverage MIR pass. This change will let me improve how ordinary branch coverage is handled during MIR building, without having to also worry about how it interacts with MC/DC.


// ConditionInfo of this branch shall also be reset.
condition_info = None;
Expand All @@ -144,20 +134,50 @@ impl BranchInfoBuilder {
condition_info
}

fn add_two_way_branch<'tcx>(
&mut self,
cfg: &mut CFG<'tcx>,
source_info: SourceInfo,
true_block: BasicBlock,
false_block: BasicBlock,
) {
let true_marker = self.inject_block_marker(cfg, source_info, true_block);
let false_marker = self.inject_block_marker(cfg, source_info, false_block);

self.branch_spans.push(BranchSpan { span: source_info.span, true_marker, false_marker });
}

fn next_block_marker_id(&mut self) -> BlockMarkerId {
let id = BlockMarkerId::from_usize(self.num_block_markers);
self.num_block_markers += 1;
id
}

fn inject_block_marker(
&mut self,
cfg: &mut CFG<'_>,
source_info: SourceInfo,
block: BasicBlock,
) -> BlockMarkerId {
let id = self.next_block_marker_id();

let marker_statement = mir::Statement {
source_info,
kind: mir::StatementKind::Coverage(CoverageKind::BlockMarker { id }),
};
cfg.push(block, marker_statement);

id
}

pub(crate) fn into_done(self) -> Option<Box<mir::coverage::BranchInfo>> {
let Self {
nots: _,
num_block_markers,
branch_spans,
mcdc_branch_spans,
mcdc_decision_spans,
..
mcdc_state: _,
} = self;

if num_block_markers == 0 {
Expand Down Expand Up @@ -325,7 +345,7 @@ impl Builder<'_, '_> {
mut else_block: BasicBlock,
) {
// Bail out if branch coverage is not enabled for this function.
let Some(branch_info) = self.coverage_branch_info.as_ref() else { return };
let Some(branch_info) = self.coverage_branch_info.as_mut() else { return };

// If this condition expression is nested within one or more `!` expressions,
// replace it with the enclosing `!` collected by `visit_unary_not`.
Expand All @@ -335,47 +355,34 @@ impl Builder<'_, '_> {
std::mem::swap(&mut then_block, &mut else_block);
}
}
let source_info = self.source_info(self.thir[expr_id].span);

// Now that we have `source_info`, we can upgrade to a &mut reference.
let branch_info = self.coverage_branch_info.as_mut().expect("upgrading & to &mut");

let mut inject_branch_marker = |block: BasicBlock| {
let id = branch_info.next_block_marker_id();

let marker_statement = mir::Statement {
source_info,
kind: mir::StatementKind::Coverage(CoverageKind::BlockMarker { id }),
};
self.cfg.push(block, marker_statement);

id
};

let true_marker = inject_branch_marker(then_block);
let false_marker = inject_branch_marker(else_block);
let source_info = SourceInfo { span: self.thir[expr_id].span, scope: self.source_scope };

if let Some(condition_info) =
branch_info.fetch_condition_info(self.tcx, true_marker, false_marker)
{
// Separate path for handling branches when MC/DC is enabled.
if branch_info.mcdc_state.is_some() {
let mut inject_block_marker =
|block| branch_info.inject_block_marker(&mut self.cfg, source_info, block);
let true_marker = inject_block_marker(then_block);
let false_marker = inject_block_marker(else_block);
let condition_info =
branch_info.fetch_mcdc_condition_info(self.tcx, true_marker, false_marker);
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,
});
return;
}

branch_info.add_two_way_branch(&mut self.cfg, source_info, then_block, else_block);
}

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);
if let Some(branch_info) = self.coverage_branch_info.as_mut()
&& let Some(mcdc_state) = branch_info.mcdc_state.as_mut()
{
mcdc_state.record_conditions(logical_op, span);
}
}
}
38 changes: 26 additions & 12 deletions compiler/rustc_mir_transform/src/coverage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ mod tests;

use self::counters::{CounterIncrementSite, CoverageCounters};
use self::graph::{BasicCoverageBlock, CoverageGraph};
use self::spans::{BcbMapping, BcbMappingKind, CoverageSpans};
use self::spans::{BcbBranchPair, BcbMapping, BcbMappingKind, CoverageSpans};

use crate::MirPass;

Expand Down Expand Up @@ -141,21 +141,25 @@ fn create_mappings<'tcx>(

let mut mappings = Vec::new();

mappings.extend(coverage_spans.all_bcb_mappings().filter_map(
mappings.extend(coverage_spans.mappings.iter().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 {
BcbMappingKind::MCDCBranch { true_bcb, false_bcb, condition_info: None } => {
MappingKind::Branch {
true_term: term_for_bcb(true_bcb),
false_term: term_for_bcb(false_bcb),
mcdc_params: condition_info,
}
}
BcbMappingKind::MCDCBranch {
true_bcb,
false_bcb,
condition_info: Some(mcdc_params),
} => MappingKind::MCDCBranch {
true_term: term_for_bcb(true_bcb),
false_term: term_for_bcb(false_bcb),
mcdc_params,
},
BcbMappingKind::MCDCDecision { bitmap_idx, conditions_num, .. } => {
MappingKind::MCDCDecision(DecisionInfo { bitmap_idx, conditions_num })
}
Expand All @@ -165,6 +169,16 @@ fn create_mappings<'tcx>(
},
));

mappings.extend(coverage_spans.branch_pairs.iter().filter_map(
|&BcbBranchPair { span, true_bcb, false_bcb }| {
let true_term = term_for_bcb(true_bcb);
let false_term = term_for_bcb(false_bcb);
let kind = MappingKind::Branch { true_term, false_term };
let code_region = make_code_region(source_map, file_name, span, body_span)?;
Some(Mapping { kind, code_region })
},
));

mappings
}

Expand Down Expand Up @@ -233,7 +247,7 @@ fn inject_mcdc_statements<'tcx>(

// Inject test vector update first because `inject_statement` always insert new statement at head.
for (end_bcbs, bitmap_idx) in
coverage_spans.all_bcb_mappings().filter_map(|mapping| match &mapping.kind {
coverage_spans.mappings.iter().filter_map(|mapping| match &mapping.kind {
BcbMappingKind::MCDCDecision { end_bcbs, bitmap_idx, .. } => {
Some((end_bcbs, *bitmap_idx))
}
Expand All @@ -247,9 +261,9 @@ fn inject_mcdc_statements<'tcx>(
}

for (true_bcb, false_bcb, condition_id) in
coverage_spans.all_bcb_mappings().filter_map(|mapping| match mapping.kind {
coverage_spans.mappings.iter().filter_map(|mapping| match mapping.kind {
BcbMappingKind::MCDCBranch { true_bcb, false_bcb, condition_info } => {
Some((true_bcb, false_bcb, condition_info.condition_id))
Some((true_bcb, false_bcb, condition_info?.condition_id))
}
_ => None,
})
Expand Down
47 changes: 34 additions & 13 deletions compiler/rustc_mir_transform/src/coverage/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@ mod from_mir;
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 },

// Ordinary branch mappings are stored separately, so they don't have a
// variant in this enum.
//
/// Associates a mcdc branch span with condition info besides fields for normal branch.
MCDCBranch {
true_bcb: BasicCoverageBlock,
false_bcb: BasicCoverageBlock,
condition_info: ConditionInfo,
/// If `None`, this actually represents a normal branch mapping inserted
/// for code that was too complex for MC/DC.
condition_info: Option<ConditionInfo>,
},
/// Associates a mcdc decision with its join BCB.
MCDCDecision { end_bcbs: BTreeSet<BasicCoverageBlock>, bitmap_idx: u32, conditions_num: u16 },
Expand All @@ -33,9 +37,20 @@ pub(super) struct BcbMapping {
pub(super) span: Span,
}

/// This is separate from [`BcbMappingKind`] to help prepare for larger changes
/// that will be needed for improved branch coverage in the future.
/// (See <https://github.com/rust-lang/rust/pull/124217>.)
#[derive(Debug)]
pub(super) struct BcbBranchPair {
pub(super) span: Span,
pub(super) true_bcb: BasicCoverageBlock,
pub(super) false_bcb: BasicCoverageBlock,
}

pub(super) struct CoverageSpans {
bcb_has_mappings: BitSet<BasicCoverageBlock>,
mappings: Vec<BcbMapping>,
pub(super) mappings: Vec<BcbMapping>,
pub(super) branch_pairs: Vec<BcbBranchPair>,
test_vector_bitmap_bytes: u32,
}

Expand All @@ -44,10 +59,6 @@ impl CoverageSpans {
self.bcb_has_mappings.contains(bcb)
}

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
}
Expand All @@ -63,6 +74,7 @@ pub(super) fn generate_coverage_spans(
basic_coverage_blocks: &CoverageGraph,
) -> Option<CoverageSpans> {
let mut mappings = vec![];
let mut branch_pairs = vec![];

if hir_info.is_async_fn {
// An async function desugars into a function that returns a future,
Expand All @@ -84,14 +96,20 @@ pub(super) fn generate_coverage_spans(
BcbMapping { kind: BcbMappingKind::Code(bcb), span }
}));

mappings.extend(from_mir::extract_branch_mappings(
branch_pairs.extend(from_mir::extract_branch_pairs(
mir_body,
hir_info,
basic_coverage_blocks,
));

mappings.extend(from_mir::extract_mcdc_mappings(
mir_body,
hir_info.body_span,
basic_coverage_blocks,
));
}

if mappings.is_empty() {
if mappings.is_empty() && branch_pairs.is_empty() {
return None;
}

Expand All @@ -104,8 +122,7 @@ pub(super) fn generate_coverage_spans(
for BcbMapping { kind, span: _ } in &mappings {
match *kind {
BcbMappingKind::Code(bcb) => insert(bcb),
BcbMappingKind::Branch { true_bcb, false_bcb }
| BcbMappingKind::MCDCBranch { true_bcb, false_bcb, .. } => {
BcbMappingKind::MCDCBranch { true_bcb, false_bcb, .. } => {
insert(true_bcb);
insert(false_bcb);
}
Expand All @@ -118,8 +135,12 @@ pub(super) fn generate_coverage_spans(
}
}
}
for &BcbBranchPair { true_bcb, false_bcb, .. } in &branch_pairs {
insert(true_bcb);
insert(false_bcb);
}

Some(CoverageSpans { bcb_has_mappings, mappings, test_vector_bitmap_bytes })
Some(CoverageSpans { bcb_has_mappings, mappings, branch_pairs, test_vector_bitmap_bytes })
}

#[derive(Debug)]
Expand Down
Loading
Loading