coverage: Data structures for recording branch info during MIR building

This commit is contained in:
Zalathar 2024-02-08 12:41:44 +11:00
parent c921ab1713
commit f9cdaeb6fd
6 changed files with 94 additions and 4 deletions

View File

@ -2,7 +2,7 @@
use rustc_index::IndexVec;
use rustc_macros::HashStable;
use rustc_span::Symbol;
use rustc_span::{Span, Symbol};
use std::fmt::{self, Debug, Formatter};
@ -93,7 +93,7 @@ pub enum CoverageKind {
SpanMarker,
/// Marks its enclosing basic block with an ID that can be referred to by
/// other data in the MIR body.
/// side data in [`BranchInfo`].
///
/// Has no effect during codegen.
BlockMarker { id: BlockMarkerId },
@ -218,3 +218,22 @@ pub struct FunctionCoverageInfo {
pub expressions: IndexVec<ExpressionId, Expression>,
pub mappings: Vec<Mapping>,
}
/// Branch information recorded during THIR-to-MIR lowering, and stored in MIR.
#[derive(Clone, Debug)]
#[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
pub struct BranchInfo {
/// 1 more than the highest-numbered [`CoverageKind::BlockMarker`] that was
/// injected into the MIR body. This makes it possible to allocate per-ID
/// data structures without having to scan the entire body first.
pub num_block_markers: usize,
pub branch_spans: Vec<BranchSpan>,
}
#[derive(Clone, Debug)]
#[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
pub struct BranchSpan {
pub span: Span,
pub true_marker: BlockMarkerId,
pub false_marker: BlockMarkerId,
}

View File

@ -403,6 +403,12 @@ pub struct Body<'tcx> {
pub tainted_by_errors: Option<ErrorGuaranteed>,
/// Branch coverage information collected during MIR building, to be used by
/// the `InstrumentCoverage` pass.
///
/// Only present if branch coverage is enabled and this function is eligible.
pub coverage_branch_info: Option<Box<coverage::BranchInfo>>,
/// Per-function coverage information added by the `InstrumentCoverage`
/// pass, to be used in conjunction with the coverage statements injected
/// into this body's blocks.
@ -450,6 +456,7 @@ impl<'tcx> Body<'tcx> {
is_polymorphic: false,
injection_phase: None,
tainted_by_errors,
coverage_branch_info: None,
function_coverage_info: None,
};
body.is_polymorphic = body.has_non_region_param();
@ -479,6 +486,7 @@ impl<'tcx> Body<'tcx> {
is_polymorphic: false,
injection_phase: None,
tainted_by_errors: None,
coverage_branch_info: None,
function_coverage_info: None,
};
body.is_polymorphic = body.has_non_region_param();

View File

@ -461,6 +461,9 @@ pub fn write_mir_intro<'tcx>(
// Add an empty line before the first block is printed.
writeln!(w)?;
if let Some(branch_info) = &body.coverage_branch_info {
write_coverage_branch_info(branch_info, w)?;
}
if let Some(function_coverage_info) = &body.function_coverage_info {
write_function_coverage_info(function_coverage_info, w)?;
}
@ -468,6 +471,25 @@ pub fn write_mir_intro<'tcx>(
Ok(())
}
fn write_coverage_branch_info(
branch_info: &coverage::BranchInfo,
w: &mut dyn io::Write,
) -> io::Result<()> {
let coverage::BranchInfo { branch_spans, .. } = branch_info;
for coverage::BranchSpan { span, true_marker, false_marker } in branch_spans {
writeln!(
w,
"{INDENT}coverage branch {{ true: {true_marker:?}, false: {false_marker:?} }} => {span:?}",
)?;
}
if !branch_spans.is_empty() {
writeln!(w)?;
}
Ok(())
}
fn write_function_coverage_info(
function_coverage_info: &coverage::FunctionCoverageInfo,
w: &mut dyn io::Write,

View File

@ -0,0 +1,32 @@
use rustc_middle::mir;
use rustc_middle::mir::coverage::BranchSpan;
use rustc_middle::ty::TyCtxt;
use rustc_span::def_id::LocalDefId;
pub(crate) struct BranchInfoBuilder {
num_block_markers: usize,
branch_spans: Vec<BranchSpan>,
}
impl BranchInfoBuilder {
/// Creates a new branch info builder, but only if branch coverage instrumentation
/// 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 { num_block_markers: 0, branch_spans: vec![] })
} else {
None
}
}
pub(crate) fn into_done(self) -> Option<Box<mir::coverage::BranchInfo>> {
let Self { num_block_markers, branch_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 }))
}
}

View File

@ -60,6 +60,7 @@ pub(super) fn build_custom_mir<'tcx>(
tainted_by_errors: None,
injection_phase: None,
pass_count: 0,
coverage_branch_info: None,
function_coverage_info: None,
};

View File

@ -234,6 +234,10 @@ struct Builder<'a, 'tcx> {
// the root (most of them do) and saves us from retracing many sub-paths
// many times, and rechecking many nodes.
lint_level_roots_cache: GrowableBitSet<hir::ItemLocalId>,
/// Collects additional coverage information during MIR building.
/// Only present if branch coverage is enabled and this function is eligible.
coverage_branch_info: Option<coverageinfo::BranchInfoBuilder>,
}
type CaptureMap<'tcx> = SortedIndexMultiMap<usize, hir::HirId, Capture<'tcx>>;
@ -807,6 +811,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
unit_temp: None,
var_debug_info: vec![],
lint_level_roots_cache: GrowableBitSet::new_empty(),
coverage_branch_info: coverageinfo::BranchInfoBuilder::new_if_enabled(tcx, def),
};
assert_eq!(builder.cfg.start_new_block(), START_BLOCK);
@ -826,7 +831,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
}
}
Body::new(
let mut body = Body::new(
MirSource::item(self.def_id.to_def_id()),
self.cfg.basic_blocks,
self.source_scopes,
@ -837,7 +842,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
self.fn_span,
self.coroutine,
None,
)
);
body.coverage_branch_info = self.coverage_branch_info.and_then(|b| b.into_done());
body
}
fn insert_upvar_arg(&mut self) {
@ -1111,6 +1118,7 @@ pub(crate) fn parse_float_into_scalar(
mod block;
mod cfg;
mod coverageinfo;
mod custom;
mod expr;
mod matches;