1
Fork 0

Rollup merge of #119842 - Zalathar:kind, r=oli-obk

coverage: Add enums to accommodate other kinds of coverage mappings

Extracted from  #118305.

LLVM supports several different kinds of coverage mapping regions, but currently we only ever emit ordinary “code” regions.  This PR performs the plumbing required to add other kinds of regions as enum variants, but does not add any specific variants other than `Code`.

The main motivation for this change is branch coverage, but it will also allow separate experimentation with gap regions and skipped regions, which might help in producing more accurate and useful coverage reports.

---

``@rustbot`` label +A-code-coverage
This commit is contained in:
Matthias Krüger 2024-01-11 19:42:51 +01:00 committed by GitHub
commit 8294356a5d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 123 additions and 74 deletions

View file

@ -1,4 +1,4 @@
use rustc_middle::mir::coverage::{CounterId, CovTerm, ExpressionId}; use rustc_middle::mir::coverage::{CodeRegion, CounterId, CovTerm, ExpressionId, MappingKind};
/// Must match the layout of `LLVMRustCounterKind`. /// Must match the layout of `LLVMRustCounterKind`.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
@ -149,6 +149,24 @@ pub struct CounterMappingRegion {
} }
impl CounterMappingRegion { impl CounterMappingRegion {
pub(crate) fn from_mapping(
mapping_kind: &MappingKind,
local_file_id: u32,
code_region: &CodeRegion,
) -> Self {
let &CodeRegion { file_name: _, start_line, start_col, end_line, end_col } = code_region;
match *mapping_kind {
MappingKind::Code(term) => Self::code_region(
Counter::from_term(term),
local_file_id,
start_line,
start_col,
end_line,
end_col,
),
}
}
pub(crate) fn code_region( pub(crate) fn code_region(
counter: Counter, counter: Counter,
file_id: u32, file_id: u32,

View file

@ -4,7 +4,8 @@ use rustc_data_structures::captures::Captures;
use rustc_data_structures::fx::FxIndexSet; use rustc_data_structures::fx::FxIndexSet;
use rustc_index::bit_set::BitSet; use rustc_index::bit_set::BitSet;
use rustc_middle::mir::coverage::{ use rustc_middle::mir::coverage::{
CodeRegion, CounterId, CovTerm, Expression, ExpressionId, FunctionCoverageInfo, Mapping, Op, CodeRegion, CounterId, CovTerm, Expression, ExpressionId, FunctionCoverageInfo, Mapping,
MappingKind, Op,
}; };
use rustc_middle::ty::Instance; use rustc_middle::ty::Instance;
use rustc_span::Symbol; use rustc_span::Symbol;
@ -64,8 +65,8 @@ impl<'tcx> FunctionCoverageCollector<'tcx> {
// For each expression ID that is directly used by one or more mappings, // For each expression ID that is directly used by one or more mappings,
// mark it as not-yet-seen. This indicates that we expect to see a // mark it as not-yet-seen. This indicates that we expect to see a
// corresponding `ExpressionUsed` statement during MIR traversal. // corresponding `ExpressionUsed` statement during MIR traversal.
for Mapping { term, .. } in &function_coverage_info.mappings { for term in function_coverage_info.mappings.iter().flat_map(|m| m.kind.terms()) {
if let &CovTerm::Expression(id) = term { if let CovTerm::Expression(id) = term {
expressions_seen.remove(id); expressions_seen.remove(id);
} }
} }
@ -221,20 +222,21 @@ impl<'tcx> FunctionCoverage<'tcx> {
/// that will be used by `mapgen` when preparing for FFI. /// that will be used by `mapgen` when preparing for FFI.
pub(crate) fn counter_regions( pub(crate) fn counter_regions(
&self, &self,
) -> impl Iterator<Item = (Counter, &CodeRegion)> + ExactSizeIterator { ) -> impl Iterator<Item = (MappingKind, &CodeRegion)> + ExactSizeIterator {
self.function_coverage_info.mappings.iter().map(move |mapping| { self.function_coverage_info.mappings.iter().map(move |mapping| {
let &Mapping { term, ref code_region } = mapping; let Mapping { kind, code_region } = mapping;
let counter = self.counter_for_term(term); let kind =
(counter, code_region) kind.map_terms(|term| if self.is_zero_term(term) { CovTerm::Zero } else { term });
(kind, code_region)
}) })
} }
fn counter_for_term(&self, term: CovTerm) -> Counter { fn counter_for_term(&self, term: CovTerm) -> Counter {
if is_zero_term(&self.counters_seen, &self.zero_expressions, term) { if self.is_zero_term(term) { Counter::ZERO } else { Counter::from_term(term) }
Counter::ZERO }
} else {
Counter::from_term(term) fn is_zero_term(&self, term: CovTerm) -> bool {
} is_zero_term(&self.counters_seen, &self.zero_expressions, term)
} }
} }

View file

@ -12,7 +12,6 @@ use rustc_hir::def_id::DefId;
use rustc_index::IndexVec; use rustc_index::IndexVec;
use rustc_middle::bug; use rustc_middle::bug;
use rustc_middle::mir; use rustc_middle::mir;
use rustc_middle::mir::coverage::CodeRegion;
use rustc_middle::ty::{self, TyCtxt}; use rustc_middle::ty::{self, TyCtxt};
use rustc_span::def_id::DefIdSet; use rustc_span::def_id::DefIdSet;
use rustc_span::Symbol; use rustc_span::Symbol;
@ -237,7 +236,7 @@ fn encode_mappings_for_function(
// Prepare file IDs for each filename, and prepare the mapping data so that // Prepare file IDs for each filename, and prepare the mapping data so that
// we can pass it through FFI to LLVM. // we can pass it through FFI to LLVM.
for (file_name, counter_regions_for_file) in for (file_name, counter_regions_for_file) in
&counter_regions.group_by(|(_counter, region)| region.file_name) &counter_regions.group_by(|(_, region)| region.file_name)
{ {
// Look up the global file ID for this filename. // Look up the global file ID for this filename.
let global_file_id = global_file_table.global_file_id_for_file_name(file_name); let global_file_id = global_file_table.global_file_id_for_file_name(file_name);
@ -248,17 +247,12 @@ fn encode_mappings_for_function(
// For each counter/region pair in this function+file, convert it to a // For each counter/region pair in this function+file, convert it to a
// form suitable for FFI. // form suitable for FFI.
for (counter, region) in counter_regions_for_file { for (mapping_kind, region) in counter_regions_for_file {
let CodeRegion { file_name: _, start_line, start_col, end_line, end_col } = *region; debug!("Adding counter {mapping_kind:?} to map for {region:?}");
mapping_regions.push(CounterMappingRegion::from_mapping(
debug!("Adding counter {counter:?} to map for {region:?}"); &mapping_kind,
mapping_regions.push(CounterMappingRegion::code_region(
counter,
local_file_id.as_u32(), local_file_id.as_u32(),
start_line, region,
start_col,
end_line,
end_col,
)); ));
} }
} }

View file

@ -160,16 +160,34 @@ pub struct Expression {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
#[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)] #[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
pub struct Mapping { pub enum MappingKind {
pub code_region: CodeRegion, /// Associates a normal region of code with a counter/expression/zero.
Code(CovTerm),
}
/// Indicates whether this mapping uses a counter value, expression value, impl MappingKind {
/// or zero value. /// Iterator over all coverage terms in this mapping kind.
/// pub fn terms(&self) -> impl Iterator<Item = CovTerm> {
/// FIXME: When we add support for mapping kinds other than `Code` let one = |a| std::iter::once(a);
/// (e.g. branch regions, expansion regions), replace this with a dedicated match *self {
/// mapping-kind enum. Self::Code(term) => one(term),
pub term: CovTerm, }
}
/// Returns a copy of this mapping kind, in which all coverage terms have
/// been replaced with ones returned by the given function.
pub fn map_terms(&self, map_fn: impl Fn(CovTerm) -> CovTerm) -> Self {
match *self {
Self::Code(term) => Self::Code(map_fn(term)),
}
}
}
#[derive(Clone, Debug)]
#[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
pub struct Mapping {
pub kind: MappingKind,
pub code_region: CodeRegion,
} }
/// Stores per-function coverage information attached to a `mir::Body`, /// Stores per-function coverage information attached to a `mir::Body`,

View file

@ -493,8 +493,8 @@ fn write_function_coverage_info(
for (id, expression) in expressions.iter_enumerated() { for (id, expression) in expressions.iter_enumerated() {
writeln!(w, "{INDENT}coverage {id:?} => {expression:?};")?; writeln!(w, "{INDENT}coverage {id:?} => {expression:?};")?;
} }
for coverage::Mapping { term, code_region } in mappings { for coverage::Mapping { kind, code_region } in mappings {
writeln!(w, "{INDENT}coverage {term:?} => {code_region:?};")?; writeln!(w, "{INDENT}coverage {kind:?} => {code_region:?};")?;
} }
writeln!(w)?; writeln!(w)?;

View file

@ -9,7 +9,7 @@ mod tests;
use self::counters::{BcbCounter, CoverageCounters}; use self::counters::{BcbCounter, CoverageCounters};
use self::graph::{BasicCoverageBlock, CoverageGraph}; use self::graph::{BasicCoverageBlock, CoverageGraph};
use self::spans::CoverageSpans; use self::spans::{BcbMapping, BcbMappingKind, CoverageSpans};
use crate::MirPass; use crate::MirPass;
@ -141,22 +141,21 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
let file_name = let file_name =
Symbol::intern(&source_file.name.for_codegen(self.tcx.sess).to_string_lossy()); Symbol::intern(&source_file.name.for_codegen(self.tcx.sess).to_string_lossy());
let term_for_bcb = |bcb| {
coverage_counters
.bcb_counter(bcb)
.expect("all BCBs with spans were given counters")
.as_term()
};
coverage_spans coverage_spans
.bcbs_with_coverage_spans() .all_bcb_mappings()
// For each BCB with spans, get a coverage term for its counter. .filter_map(|&BcbMapping { kind: bcb_mapping_kind, span }| {
.map(|(bcb, spans)| { let kind = match bcb_mapping_kind {
let term = coverage_counters BcbMappingKind::Code(bcb) => MappingKind::Code(term_for_bcb(bcb)),
.bcb_counter(bcb) };
.expect("all BCBs with spans were given counters")
.as_term();
(term, spans)
})
// Flatten the spans into individual term/span pairs.
.flat_map(|(term, spans)| spans.iter().map(move |&span| (term, span)))
// Convert each span to a code region, and create the final mapping.
.filter_map(|(term, span)| {
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 { term, code_region }) Some(Mapping { kind, code_region })
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
} }

View file

@ -1,5 +1,5 @@
use rustc_data_structures::graph::WithNumNodes; use rustc_data_structures::graph::WithNumNodes;
use rustc_index::IndexVec; use rustc_index::bit_set::BitSet;
use rustc_middle::mir; use rustc_middle::mir;
use rustc_span::{BytePos, Span, DUMMY_SP}; use rustc_span::{BytePos, Span, DUMMY_SP};
@ -8,9 +8,21 @@ use crate::coverage::ExtractedHirInfo;
mod from_mir; mod from_mir;
#[derive(Clone, Copy, Debug)]
pub(super) enum BcbMappingKind {
/// Associates an ordinary executable code span with its corresponding BCB.
Code(BasicCoverageBlock),
}
#[derive(Debug)]
pub(super) struct BcbMapping {
pub(super) kind: BcbMappingKind,
pub(super) span: Span,
}
pub(super) struct CoverageSpans { pub(super) struct CoverageSpans {
/// Map from BCBs to their list of coverage spans. bcb_has_mappings: BitSet<BasicCoverageBlock>,
bcb_to_spans: IndexVec<BasicCoverageBlock, Vec<Span>>, mappings: Vec<BcbMapping>,
} }
impl CoverageSpans { impl CoverageSpans {
@ -23,36 +35,42 @@ impl CoverageSpans {
hir_info: &ExtractedHirInfo, hir_info: &ExtractedHirInfo,
basic_coverage_blocks: &CoverageGraph, basic_coverage_blocks: &CoverageGraph,
) -> Option<Self> { ) -> Option<Self> {
let mut mappings = vec![];
let coverage_spans = CoverageSpansGenerator::generate_coverage_spans( let coverage_spans = CoverageSpansGenerator::generate_coverage_spans(
mir_body, mir_body,
hir_info, hir_info,
basic_coverage_blocks, basic_coverage_blocks,
); );
mappings.extend(coverage_spans.into_iter().map(|CoverageSpan { bcb, span, .. }| {
// Each span produced by the generator represents an ordinary code region.
BcbMapping { kind: BcbMappingKind::Code(bcb), span }
}));
if coverage_spans.is_empty() { if mappings.is_empty() {
return None; return None;
} }
// Group the coverage spans by BCB, with the BCBs in sorted order. // Identify which BCBs have one or more mappings.
let mut bcb_to_spans = IndexVec::from_elem_n(Vec::new(), basic_coverage_blocks.num_nodes()); let mut bcb_has_mappings = BitSet::new_empty(basic_coverage_blocks.num_nodes());
for CoverageSpan { bcb, span, .. } in coverage_spans { let mut insert = |bcb| {
bcb_to_spans[bcb].push(span); bcb_has_mappings.insert(bcb);
};
for &BcbMapping { kind, span: _ } in &mappings {
match kind {
BcbMappingKind::Code(bcb) => insert(bcb),
}
} }
Some(Self { bcb_to_spans }) Some(Self { bcb_has_mappings, mappings })
} }
pub(super) fn bcb_has_coverage_spans(&self, bcb: BasicCoverageBlock) -> bool { pub(super) fn bcb_has_coverage_spans(&self, bcb: BasicCoverageBlock) -> bool {
!self.bcb_to_spans[bcb].is_empty() self.bcb_has_mappings.contains(bcb)
} }
pub(super) fn bcbs_with_coverage_spans( pub(super) fn all_bcb_mappings(&self) -> impl Iterator<Item = &BcbMapping> {
&self, self.mappings.iter()
) -> impl Iterator<Item = (BasicCoverageBlock, &[Span])> {
self.bcb_to_spans.iter_enumerated().filter_map(|(bcb, spans)| {
// Only yield BCBs that have at least one coverage span.
(!spans.is_empty()).then_some((bcb, spans.as_slice()))
})
} }
} }

View file

@ -4,7 +4,7 @@
fn bar() -> bool { fn bar() -> bool {
let mut _0: bool; let mut _0: bool;
+ coverage Counter(0) => /the/src/instrument_coverage.rs:21:1 - 23:2; + coverage Code(Counter(0)) => /the/src/instrument_coverage.rs:21:1 - 23:2;
+ +
bb0: { bb0: {
+ Coverage::CounterIncrement(0); + Coverage::CounterIncrement(0);

View file

@ -9,11 +9,11 @@
+ coverage ExpressionId(0) => Expression { lhs: Counter(0), op: Add, rhs: Counter(1) }; + coverage ExpressionId(0) => Expression { lhs: Counter(0), op: Add, rhs: Counter(1) };
+ coverage ExpressionId(1) => Expression { lhs: Expression(0), op: Subtract, rhs: Counter(1) }; + coverage ExpressionId(1) => Expression { lhs: Expression(0), op: Subtract, rhs: Counter(1) };
+ coverage Counter(0) => /the/src/instrument_coverage.rs:12:1 - 12:11; + coverage Code(Counter(0)) => /the/src/instrument_coverage.rs:12:1 - 12:11;
+ coverage Expression(0) => /the/src/instrument_coverage.rs:13:5 - 14:17; + coverage Code(Expression(0)) => /the/src/instrument_coverage.rs:13:5 - 14:17;
+ coverage Expression(1) => /the/src/instrument_coverage.rs:15:13 - 15:18; + coverage Code(Expression(1)) => /the/src/instrument_coverage.rs:15:13 - 15:18;
+ coverage Expression(1) => /the/src/instrument_coverage.rs:18:1 - 18:2; + coverage Code(Counter(1)) => /the/src/instrument_coverage.rs:16:10 - 16:11;
+ coverage Counter(1) => /the/src/instrument_coverage.rs:16:10 - 16:11; + coverage Code(Expression(1)) => /the/src/instrument_coverage.rs:18:1 - 18:2;
+ +
bb0: { bb0: {
+ Coverage::CounterIncrement(0); + Coverage::CounterIncrement(0);