mirror of
https://github.com/rust-lang/rust.git
synced 2024-10-31 22:41:50 +00:00
coverage: Store all of a function's mappings in function coverage info
Previously, mappings were attached to individual coverage statements in MIR. That necessitated special handling in MIR optimizations to avoid deleting those statements, since otherwise codegen would be unable to reassemble the original list of mappings. With this change, a function's list of mappings is now attached to its MIR body, and survives intact even if individual statements are deleted by optimizations.
This commit is contained in:
parent
4099ab1997
commit
6da319f635
@ -23,11 +23,10 @@ pub struct FunctionCoverage<'tcx> {
|
||||
function_coverage_info: &'tcx FunctionCoverageInfo,
|
||||
is_used: bool,
|
||||
|
||||
/// Tracks which counters have been seen, to avoid duplicate mappings
|
||||
/// that might be introduced by MIR inlining.
|
||||
/// Tracks which counters have been seen, so that we can identify mappings
|
||||
/// to counters that were optimized out, and set them to zero.
|
||||
counters_seen: BitSet<CounterId>,
|
||||
expressions: IndexVec<ExpressionId, Option<Expression>>,
|
||||
mappings: Vec<Mapping>,
|
||||
}
|
||||
|
||||
impl<'tcx> FunctionCoverage<'tcx> {
|
||||
@ -63,7 +62,6 @@ impl<'tcx> FunctionCoverage<'tcx> {
|
||||
is_used,
|
||||
counters_seen: BitSet::new_empty(num_counters),
|
||||
expressions: IndexVec::from_elem_n(None, num_expressions),
|
||||
mappings: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,20 +70,13 @@ impl<'tcx> FunctionCoverage<'tcx> {
|
||||
self.is_used
|
||||
}
|
||||
|
||||
/// Adds code regions to be counted by an injected counter intrinsic.
|
||||
/// Marks a counter ID as having been seen in a counter-increment statement.
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
pub(crate) fn add_counter(&mut self, id: CounterId, code_regions: &[CodeRegion]) {
|
||||
if self.counters_seen.insert(id) {
|
||||
self.add_mappings(CovTerm::Counter(id), code_regions);
|
||||
}
|
||||
pub(crate) fn mark_counter_id_seen(&mut self, id: CounterId) {
|
||||
self.counters_seen.insert(id);
|
||||
}
|
||||
|
||||
/// Adds information about a coverage expression, along with zero or more
|
||||
/// code regions mapped to that expression.
|
||||
///
|
||||
/// Both counters and "counter expressions" (or simply, "expressions") can be operands in other
|
||||
/// expressions. These are tracked as separate variants of `CovTerm`, so there is no ambiguity
|
||||
/// between operands that are counter IDs and operands that are expression IDs.
|
||||
/// Adds information about a coverage expression.
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
pub(crate) fn add_counter_expression(
|
||||
&mut self,
|
||||
@ -93,7 +84,6 @@ impl<'tcx> FunctionCoverage<'tcx> {
|
||||
lhs: CovTerm,
|
||||
op: Op,
|
||||
rhs: CovTerm,
|
||||
code_regions: &[CodeRegion],
|
||||
) {
|
||||
debug_assert!(
|
||||
expression_id.as_usize() < self.expressions.len(),
|
||||
@ -107,10 +97,7 @@ impl<'tcx> FunctionCoverage<'tcx> {
|
||||
let expression = Expression { lhs, op, rhs };
|
||||
let slot = &mut self.expressions[expression_id];
|
||||
match slot {
|
||||
None => {
|
||||
*slot = Some(expression);
|
||||
self.add_mappings(CovTerm::Expression(expression_id), code_regions);
|
||||
}
|
||||
None => *slot = Some(expression),
|
||||
// If this expression ID slot has already been filled, it should
|
||||
// contain identical information.
|
||||
Some(ref previous_expression) => assert_eq!(
|
||||
@ -120,29 +107,6 @@ impl<'tcx> FunctionCoverage<'tcx> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds regions that will be marked as "unreachable", with a constant "zero counter".
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
pub(crate) fn add_unreachable_regions(&mut self, code_regions: &[CodeRegion]) {
|
||||
assert!(!code_regions.is_empty(), "unreachable regions always have code regions");
|
||||
self.add_mappings(CovTerm::Zero, code_regions);
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
fn add_mappings(&mut self, term: CovTerm, code_regions: &[CodeRegion]) {
|
||||
self.mappings
|
||||
.extend(code_regions.iter().cloned().map(|code_region| Mapping { term, code_region }));
|
||||
}
|
||||
|
||||
pub(crate) fn finalize(&mut self) {
|
||||
// Reorder the collected mappings so that counter mappings are first and
|
||||
// zero mappings are last, matching the historical order.
|
||||
self.mappings.sort_by_key(|mapping| match mapping.term {
|
||||
CovTerm::Counter(_) => 0,
|
||||
CovTerm::Expression(_) => 1,
|
||||
CovTerm::Zero => u8::MAX,
|
||||
});
|
||||
}
|
||||
|
||||
/// Identify expressions that will always have a value of zero, and note
|
||||
/// their IDs in [`ZeroExpressions`]. Mappings that refer to a zero expression
|
||||
/// can instead become mappings to a constant zero value.
|
||||
@ -235,7 +199,7 @@ impl<'tcx> FunctionCoverage<'tcx> {
|
||||
// the two vectors should correspond 1:1.
|
||||
assert_eq!(self.expressions.len(), counter_expressions.len());
|
||||
|
||||
let counter_regions = self.counter_regions();
|
||||
let counter_regions = self.counter_regions(zero_expressions);
|
||||
|
||||
(counter_expressions, counter_regions)
|
||||
}
|
||||
@ -280,9 +244,26 @@ impl<'tcx> FunctionCoverage<'tcx> {
|
||||
|
||||
/// Converts this function's coverage mappings into an intermediate form
|
||||
/// that will be used by `mapgen` when preparing for FFI.
|
||||
fn counter_regions(&self) -> impl Iterator<Item = (Counter, &CodeRegion)> {
|
||||
self.mappings.iter().map(|&Mapping { term, ref code_region }| {
|
||||
let counter = Counter::from_term(term);
|
||||
fn counter_regions(
|
||||
&self,
|
||||
zero_expressions: ZeroExpressions,
|
||||
) -> impl Iterator<Item = (Counter, &CodeRegion)> {
|
||||
// Historically, mappings were stored directly in counter/expression
|
||||
// statements in MIR, and MIR optimizations would sometimes remove them.
|
||||
// That's mostly no longer true, so now we detect cases where that would
|
||||
// have happened, and zero out the corresponding mappings here instead.
|
||||
let counter_for_term = move |term: CovTerm| {
|
||||
let force_to_zero = match term {
|
||||
CovTerm::Counter(id) => !self.counters_seen.contains(id),
|
||||
CovTerm::Expression(id) => zero_expressions.contains(id),
|
||||
CovTerm::Zero => false,
|
||||
};
|
||||
if force_to_zero { Counter::ZERO } else { Counter::from_term(term) }
|
||||
};
|
||||
|
||||
self.function_coverage_info.mappings.iter().map(move |mapping| {
|
||||
let &Mapping { term, ref code_region } = mapping;
|
||||
let counter = counter_for_term(term);
|
||||
(counter, code_region)
|
||||
})
|
||||
}
|
||||
|
@ -59,10 +59,8 @@ pub fn finalize(cx: &CodegenCx<'_, '_>) {
|
||||
|
||||
// Encode coverage mappings and generate function records
|
||||
let mut function_data = Vec::new();
|
||||
for (instance, mut function_coverage) in function_coverage_map {
|
||||
for (instance, function_coverage) in function_coverage_map {
|
||||
debug!("Generate function coverage for {}, {:?}", cx.codegen_unit.name(), instance);
|
||||
function_coverage.finalize();
|
||||
let function_coverage = function_coverage;
|
||||
|
||||
let mangled_function_name = tcx.symbol_name(instance).name;
|
||||
let source_hash = function_coverage.source_hash();
|
||||
|
@ -88,13 +88,12 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
|
||||
/// For used/called functions, the coverageinfo was already added to the
|
||||
/// `function_coverage_map` (keyed by function `Instance`) during codegen.
|
||||
/// But in this case, since the unused function was _not_ previously
|
||||
/// codegenned, collect the coverage `CodeRegion`s from the MIR and add
|
||||
/// them. Since the function is never called, all of its `CodeRegion`s can be
|
||||
/// added as `unreachable_region`s.
|
||||
/// codegenned, collect the function coverage info from MIR and add an
|
||||
/// "unused" entry to the function coverage map.
|
||||
fn define_unused_fn(&self, def_id: DefId, function_coverage_info: &'tcx FunctionCoverageInfo) {
|
||||
let instance = declare_unused_fn(self, def_id);
|
||||
codegen_unused_fn_and_counter(self, instance);
|
||||
add_unused_function_coverage(self, instance, def_id, function_coverage_info);
|
||||
add_unused_function_coverage(self, instance, function_coverage_info);
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,10 +115,10 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> {
|
||||
.entry(instance)
|
||||
.or_insert_with(|| FunctionCoverage::new(instance, function_coverage_info));
|
||||
|
||||
let Coverage { kind, code_regions } = coverage;
|
||||
let Coverage { kind } = coverage;
|
||||
match *kind {
|
||||
CoverageKind::Counter { id } => {
|
||||
func_coverage.add_counter(id, code_regions);
|
||||
CoverageKind::CounterIncrement { id } => {
|
||||
func_coverage.mark_counter_id_seen(id);
|
||||
// We need to explicitly drop the `RefMut` before calling into `instrprof_increment`,
|
||||
// as that needs an exclusive borrow.
|
||||
drop(coverage_map);
|
||||
@ -147,10 +146,7 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> {
|
||||
bx.instrprof_increment(fn_name, hash, num_counters, index);
|
||||
}
|
||||
CoverageKind::Expression { id, lhs, op, rhs } => {
|
||||
func_coverage.add_counter_expression(id, lhs, op, rhs, code_regions);
|
||||
}
|
||||
CoverageKind::Unreachable => {
|
||||
func_coverage.add_unreachable_regions(code_regions);
|
||||
func_coverage.add_counter_expression(id, lhs, op, rhs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -213,16 +209,11 @@ fn codegen_unused_fn_and_counter<'tcx>(cx: &CodegenCx<'_, 'tcx>, instance: Insta
|
||||
fn add_unused_function_coverage<'tcx>(
|
||||
cx: &CodegenCx<'_, 'tcx>,
|
||||
instance: Instance<'tcx>,
|
||||
def_id: DefId,
|
||||
function_coverage_info: &'tcx FunctionCoverageInfo,
|
||||
) {
|
||||
let tcx = cx.tcx;
|
||||
|
||||
let mut function_coverage = FunctionCoverage::unused(instance, function_coverage_info);
|
||||
for &code_region in tcx.covered_code_regions(def_id) {
|
||||
let code_region = std::slice::from_ref(code_region);
|
||||
function_coverage.add_unreachable_regions(code_region);
|
||||
}
|
||||
// An unused function's mappings will automatically be rewritten to map to
|
||||
// zero, because none of its counters/expressions are marked as seen.
|
||||
let function_coverage = FunctionCoverage::unused(instance, function_coverage_info);
|
||||
|
||||
if let Some(coverage_context) = cx.coverage_context() {
|
||||
coverage_context.function_coverage_map.borrow_mut().insert(instance, function_coverage);
|
||||
|
@ -61,11 +61,13 @@ impl Debug for CovTerm {
|
||||
|
||||
#[derive(Clone, PartialEq, TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
|
||||
pub enum CoverageKind {
|
||||
Counter {
|
||||
/// ID of this counter within its enclosing function.
|
||||
/// Expressions in the same function can refer to it as an operand.
|
||||
id: CounterId,
|
||||
},
|
||||
/// Marks the point in MIR control flow represented by a coverage counter.
|
||||
///
|
||||
/// This is eventually lowered to `llvm.instrprof.increment` in LLVM IR.
|
||||
///
|
||||
/// If this statement does not survive MIR optimizations, any mappings that
|
||||
/// refer to this counter can have those references simplified to zero.
|
||||
CounterIncrement { id: CounterId },
|
||||
Expression {
|
||||
/// ID of this coverage-counter expression within its enclosing function.
|
||||
/// Other expressions in the same function can refer to it as an operand.
|
||||
@ -74,14 +76,13 @@ pub enum CoverageKind {
|
||||
op: Op,
|
||||
rhs: CovTerm,
|
||||
},
|
||||
Unreachable,
|
||||
}
|
||||
|
||||
impl Debug for CoverageKind {
|
||||
fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
|
||||
use CoverageKind::*;
|
||||
match self {
|
||||
Counter { id } => write!(fmt, "Counter({:?})", id.index()),
|
||||
CounterIncrement { id } => write!(fmt, "CounterIncrement({:?})", id.index()),
|
||||
Expression { id, lhs, op, rhs } => write!(
|
||||
fmt,
|
||||
"Expression({:?}) = {:?} {} {:?}",
|
||||
@ -93,7 +94,6 @@ impl Debug for CoverageKind {
|
||||
},
|
||||
rhs,
|
||||
),
|
||||
Unreachable => write!(fmt, "Unreachable"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -158,4 +158,6 @@ pub struct FunctionCoverageInfo {
|
||||
pub function_source_hash: u64,
|
||||
pub num_counters: usize,
|
||||
pub num_expressions: usize,
|
||||
|
||||
pub mappings: Vec<Mapping>,
|
||||
}
|
||||
|
@ -493,6 +493,24 @@ pub fn write_mir_intro<'tcx>(
|
||||
// Add an empty line before the first block is printed.
|
||||
writeln!(w)?;
|
||||
|
||||
if let Some(function_coverage_info) = &body.function_coverage_info {
|
||||
write_function_coverage_info(function_coverage_info, w)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_function_coverage_info(
|
||||
function_coverage_info: &coverage::FunctionCoverageInfo,
|
||||
w: &mut dyn io::Write,
|
||||
) -> io::Result<()> {
|
||||
let coverage::FunctionCoverageInfo { mappings, .. } = function_coverage_info;
|
||||
|
||||
for coverage::Mapping { term, code_region } in mappings {
|
||||
writeln!(w, "{INDENT}coverage {term:?} => {code_region:?};")?;
|
||||
}
|
||||
writeln!(w)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -685,13 +703,7 @@ impl Debug for Statement<'_> {
|
||||
AscribeUserType(box (ref place, ref c_ty), ref variance) => {
|
||||
write!(fmt, "AscribeUserType({place:?}, {variance:?}, {c_ty:?})")
|
||||
}
|
||||
Coverage(box mir::Coverage { ref kind, ref code_regions }) => {
|
||||
if code_regions.is_empty() {
|
||||
write!(fmt, "Coverage::{kind:?}")
|
||||
} else {
|
||||
write!(fmt, "Coverage::{kind:?} for {code_regions:?}")
|
||||
}
|
||||
}
|
||||
Coverage(box mir::Coverage { ref kind }) => write!(fmt, "Coverage::{kind:?}"),
|
||||
Intrinsic(box ref intrinsic) => write!(fmt, "{intrinsic}"),
|
||||
ConstEvalCounter => write!(fmt, "ConstEvalCounter"),
|
||||
Nop => write!(fmt, "nop"),
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
use super::{BasicBlock, Const, Local, UserTypeProjection};
|
||||
|
||||
use crate::mir::coverage::{CodeRegion, CoverageKind};
|
||||
use crate::mir::coverage::CoverageKind;
|
||||
use crate::traits::Reveal;
|
||||
use crate::ty::adjustment::PointerCoercion;
|
||||
use crate::ty::GenericArgsRef;
|
||||
@ -514,7 +514,6 @@ pub enum FakeReadCause {
|
||||
#[derive(TypeFoldable, TypeVisitable)]
|
||||
pub struct Coverage {
|
||||
pub kind: CoverageKind,
|
||||
pub code_regions: Vec<CodeRegion>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, TyEncodable, TyDecodable, Hash, HashStable)]
|
||||
|
@ -581,17 +581,6 @@ rustc_queries! {
|
||||
arena_cache
|
||||
}
|
||||
|
||||
/// Returns the `CodeRegions` for a function that has instrumented coverage, in case the
|
||||
/// function was optimized out before codegen, and before being added to the Coverage Map.
|
||||
query covered_code_regions(key: DefId) -> &'tcx Vec<&'tcx mir::coverage::CodeRegion> {
|
||||
desc {
|
||||
|tcx| "retrieving the covered `CodeRegion`s, if instrumented, for `{}`",
|
||||
tcx.def_path_str(key)
|
||||
}
|
||||
arena_cache
|
||||
cache_on_disk_if { key.is_local() }
|
||||
}
|
||||
|
||||
/// The `DefId` is the `DefId` of the containing MIR body. Promoteds do not have their own
|
||||
/// `DefId`. This function returns all promoteds in the specified body. The body references
|
||||
/// promoteds by the `DefId` and the `mir::Promoted` index. This is necessary, because
|
||||
|
@ -104,6 +104,7 @@ struct Instrumentor<'a, 'tcx> {
|
||||
function_source_hash: u64,
|
||||
basic_coverage_blocks: CoverageGraph,
|
||||
coverage_counters: CoverageCounters,
|
||||
mappings: Vec<Mapping>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
|
||||
@ -144,6 +145,7 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
|
||||
function_source_hash,
|
||||
basic_coverage_blocks,
|
||||
coverage_counters,
|
||||
mappings: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -216,6 +218,7 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
|
||||
function_source_hash: self.function_source_hash,
|
||||
num_counters: self.coverage_counters.num_counters(),
|
||||
num_expressions: self.coverage_counters.num_expressions(),
|
||||
mappings: std::mem::take(&mut self.mappings),
|
||||
}));
|
||||
}
|
||||
|
||||
@ -232,18 +235,16 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
|
||||
bug!("Every BasicCoverageBlock should have a Counter or Expression");
|
||||
});
|
||||
|
||||
// Convert the coverage spans into a vector of code regions to be
|
||||
// associated with this BCB's coverage statement.
|
||||
let code_regions = spans
|
||||
.iter()
|
||||
.map(|&span| make_code_region(source_map, file_name, span, body_span))
|
||||
.collect::<Vec<_>>();
|
||||
let term = counter_kind.as_term();
|
||||
self.mappings.extend(spans.iter().map(|&span| {
|
||||
let code_region = make_code_region(source_map, file_name, span, body_span);
|
||||
Mapping { code_region, term }
|
||||
}));
|
||||
|
||||
inject_statement(
|
||||
self.mir_body,
|
||||
self.make_mir_coverage_kind(&counter_kind),
|
||||
self.bcb_leader_bb(bcb),
|
||||
code_regions,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -301,7 +302,6 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
|
||||
self.mir_body,
|
||||
self.make_mir_coverage_kind(&counter_kind),
|
||||
inject_to_bb,
|
||||
Vec::new(),
|
||||
);
|
||||
}
|
||||
BcbCounter::Expression { .. } => inject_intermediate_expression(
|
||||
@ -329,7 +329,7 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
|
||||
|
||||
fn make_mir_coverage_kind(&self, counter_kind: &BcbCounter) -> CoverageKind {
|
||||
match *counter_kind {
|
||||
BcbCounter::Counter { id } => CoverageKind::Counter { id },
|
||||
BcbCounter::Counter { id } => CoverageKind::CounterIncrement { id },
|
||||
BcbCounter::Expression { id, lhs, op, rhs } => {
|
||||
CoverageKind::Expression { id, lhs, op, rhs }
|
||||
}
|
||||
@ -360,18 +360,13 @@ fn inject_edge_counter_basic_block(
|
||||
new_bb
|
||||
}
|
||||
|
||||
fn inject_statement(
|
||||
mir_body: &mut mir::Body<'_>,
|
||||
counter_kind: CoverageKind,
|
||||
bb: BasicBlock,
|
||||
code_regions: Vec<CodeRegion>,
|
||||
) {
|
||||
debug!(" injecting statement {counter_kind:?} for {bb:?} at code regions: {code_regions:?}");
|
||||
fn inject_statement(mir_body: &mut mir::Body<'_>, counter_kind: CoverageKind, bb: BasicBlock) {
|
||||
debug!(" injecting statement {counter_kind:?} for {bb:?}");
|
||||
let data = &mut mir_body[bb];
|
||||
let source_info = data.terminator().source_info;
|
||||
let statement = Statement {
|
||||
source_info,
|
||||
kind: StatementKind::Coverage(Box::new(Coverage { kind: counter_kind, code_regions })),
|
||||
kind: StatementKind::Coverage(Box::new(Coverage { kind: counter_kind })),
|
||||
};
|
||||
data.statements.insert(0, statement);
|
||||
}
|
||||
@ -385,10 +380,7 @@ fn inject_intermediate_expression(mir_body: &mut mir::Body<'_>, expression: Cove
|
||||
let source_info = data.terminator().source_info;
|
||||
let statement = Statement {
|
||||
source_info,
|
||||
kind: StatementKind::Coverage(Box::new(Coverage {
|
||||
kind: expression,
|
||||
code_regions: Vec::new(),
|
||||
})),
|
||||
kind: StatementKind::Coverage(Box::new(Coverage { kind: expression })),
|
||||
};
|
||||
data.statements.push(statement);
|
||||
}
|
||||
|
@ -2,15 +2,13 @@ use super::*;
|
||||
|
||||
use rustc_data_structures::captures::Captures;
|
||||
use rustc_middle::mir::coverage::*;
|
||||
use rustc_middle::mir::{self, Body, Coverage, CoverageIdsInfo};
|
||||
use rustc_middle::mir::{Body, Coverage, CoverageIdsInfo};
|
||||
use rustc_middle::query::Providers;
|
||||
use rustc_middle::ty::{self, TyCtxt};
|
||||
use rustc_span::def_id::DefId;
|
||||
|
||||
/// A `query` provider for retrieving coverage information injected into MIR.
|
||||
pub(crate) fn provide(providers: &mut Providers) {
|
||||
providers.coverage_ids_info = |tcx, def_id| coverage_ids_info(tcx, def_id);
|
||||
providers.covered_code_regions = |tcx, def_id| covered_code_regions(tcx, def_id);
|
||||
}
|
||||
|
||||
/// Query implementation for `coverage_ids_info`.
|
||||
@ -22,7 +20,7 @@ fn coverage_ids_info<'tcx>(
|
||||
|
||||
let max_counter_id = all_coverage_in_mir_body(mir_body)
|
||||
.filter_map(|coverage| match coverage.kind {
|
||||
CoverageKind::Counter { id } => Some(id),
|
||||
CoverageKind::CounterIncrement { id } => Some(id),
|
||||
_ => None,
|
||||
})
|
||||
.max()
|
||||
@ -31,14 +29,6 @@ fn coverage_ids_info<'tcx>(
|
||||
CoverageIdsInfo { max_counter_id }
|
||||
}
|
||||
|
||||
fn covered_code_regions(tcx: TyCtxt<'_>, def_id: DefId) -> Vec<&CodeRegion> {
|
||||
let body = mir_body(tcx, def_id);
|
||||
all_coverage_in_mir_body(body)
|
||||
// Coverage statements have a list of code regions (possibly empty).
|
||||
.flat_map(|coverage| coverage.code_regions.as_slice())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn all_coverage_in_mir_body<'a, 'tcx>(
|
||||
body: &'a Body<'tcx>,
|
||||
) -> impl Iterator<Item = &'a Coverage> + Captures<'tcx> {
|
||||
@ -56,11 +46,3 @@ fn is_inlined(body: &Body<'_>, statement: &Statement<'_>) -> bool {
|
||||
let scope_data = &body.source_scopes[statement.source_info.scope];
|
||||
scope_data.inlined.is_some() || scope_data.inlined_parent_scope.is_some()
|
||||
}
|
||||
|
||||
/// This function ensures we obtain the correct MIR for the given item irrespective of
|
||||
/// whether that means const mir or runtime mir. For `const fn` this opts for runtime
|
||||
/// mir.
|
||||
fn mir_body(tcx: TyCtxt<'_>, def_id: DefId) -> &mir::Body<'_> {
|
||||
let def = ty::InstanceDef::Item(def_id);
|
||||
tcx.instance_mir(def)
|
||||
}
|
||||
|
@ -28,10 +28,8 @@
|
||||
//! return.
|
||||
|
||||
use crate::MirPass;
|
||||
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
|
||||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_data_structures::fx::FxIndexSet;
|
||||
use rustc_index::{Idx, IndexSlice, IndexVec};
|
||||
use rustc_middle::mir::coverage::*;
|
||||
use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor};
|
||||
use rustc_middle::mir::*;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
@ -337,7 +335,7 @@ pub fn remove_duplicate_unreachable_blocks<'tcx>(tcx: TyCtxt<'tcx>, body: &mut B
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_dead_blocks<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
||||
pub fn remove_dead_blocks<'tcx>(_tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
||||
let reachable = traversal::reachable_as_bitset(body);
|
||||
let num_blocks = body.basic_blocks.len();
|
||||
if num_blocks == reachable.count() {
|
||||
@ -345,10 +343,6 @@ pub fn remove_dead_blocks<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
||||
}
|
||||
|
||||
let basic_blocks = body.basic_blocks.as_mut();
|
||||
let source_scopes = &body.source_scopes;
|
||||
if tcx.sess.instrument_coverage() {
|
||||
save_unreachable_coverage(basic_blocks, source_scopes, &reachable);
|
||||
}
|
||||
|
||||
let mut replacements: Vec<_> = (0..num_blocks).map(BasicBlock::new).collect();
|
||||
let mut orig_index = 0;
|
||||
@ -370,99 +364,6 @@ pub fn remove_dead_blocks<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Some MIR transforms can determine at compile time that a sequences of
|
||||
/// statements will never be executed, so they can be dropped from the MIR.
|
||||
/// For example, an `if` or `else` block that is guaranteed to never be executed
|
||||
/// because its condition can be evaluated at compile time, such as by const
|
||||
/// evaluation: `if false { ... }`.
|
||||
///
|
||||
/// Those statements are bypassed by redirecting paths in the CFG around the
|
||||
/// `dead blocks`; but with `-C instrument-coverage`, the dead blocks usually
|
||||
/// include `Coverage` statements representing the Rust source code regions to
|
||||
/// be counted at runtime. Without these `Coverage` statements, the regions are
|
||||
/// lost, and the Rust source code will show no coverage information.
|
||||
///
|
||||
/// What we want to show in a coverage report is the dead code with coverage
|
||||
/// counts of `0`. To do this, we need to save the code regions, by injecting
|
||||
/// `Unreachable` coverage statements. These are non-executable statements whose
|
||||
/// code regions are still recorded in the coverage map, representing regions
|
||||
/// with `0` executions.
|
||||
///
|
||||
/// If there are no live `Counter` `Coverage` statements remaining, we remove
|
||||
/// `Coverage` statements along with the dead blocks. Since at least one
|
||||
/// counter per function is required by LLVM (and necessary, to add the
|
||||
/// `function_hash` to the counter's call to the LLVM intrinsic
|
||||
/// `instrprof.increment()`).
|
||||
///
|
||||
/// The `generator::StateTransform` MIR pass and MIR inlining can create
|
||||
/// atypical conditions, where all live `Counter`s are dropped from the MIR.
|
||||
///
|
||||
/// With MIR inlining we can have coverage counters belonging to different
|
||||
/// instances in a single body, so the strategy described above is applied to
|
||||
/// coverage counters from each instance individually.
|
||||
fn save_unreachable_coverage(
|
||||
basic_blocks: &mut IndexSlice<BasicBlock, BasicBlockData<'_>>,
|
||||
source_scopes: &IndexSlice<SourceScope, SourceScopeData<'_>>,
|
||||
reachable: &BitSet<BasicBlock>,
|
||||
) {
|
||||
// Identify instances that still have some live coverage counters left.
|
||||
let mut live = FxHashSet::default();
|
||||
for bb in reachable.iter() {
|
||||
let basic_block = &basic_blocks[bb];
|
||||
for statement in &basic_block.statements {
|
||||
let StatementKind::Coverage(coverage) = &statement.kind else { continue };
|
||||
let CoverageKind::Counter { .. } = coverage.kind else { continue };
|
||||
let instance = statement.source_info.scope.inlined_instance(source_scopes);
|
||||
live.insert(instance);
|
||||
}
|
||||
}
|
||||
|
||||
for bb in reachable.iter() {
|
||||
let block = &mut basic_blocks[bb];
|
||||
for statement in &mut block.statements {
|
||||
let StatementKind::Coverage(_) = &statement.kind else { continue };
|
||||
let instance = statement.source_info.scope.inlined_instance(source_scopes);
|
||||
if !live.contains(&instance) {
|
||||
statement.make_nop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if live.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Retain coverage for instances that still have some live counters left.
|
||||
let mut retained_coverage = Vec::new();
|
||||
for dead_block in basic_blocks.indices() {
|
||||
if reachable.contains(dead_block) {
|
||||
continue;
|
||||
}
|
||||
let dead_block = &basic_blocks[dead_block];
|
||||
for statement in &dead_block.statements {
|
||||
let StatementKind::Coverage(coverage) = &statement.kind else { continue };
|
||||
if coverage.code_regions.is_empty() {
|
||||
continue;
|
||||
};
|
||||
let instance = statement.source_info.scope.inlined_instance(source_scopes);
|
||||
if live.contains(&instance) {
|
||||
retained_coverage.push((statement.source_info, coverage.code_regions.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let start_block = &mut basic_blocks[START_BLOCK];
|
||||
start_block.statements.extend(retained_coverage.into_iter().map(
|
||||
|(source_info, code_regions)| Statement {
|
||||
source_info,
|
||||
kind: StatementKind::Coverage(Box::new(Coverage {
|
||||
kind: CoverageKind::Unreachable,
|
||||
code_regions,
|
||||
})),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
pub enum SimplifyLocals {
|
||||
BeforeConstProp,
|
||||
Final,
|
||||
|
@ -1,11 +1,10 @@
|
||||
Function name: <issue_84561::Foo as core::cmp::PartialEq>::eq
|
||||
Raw bytes (14): 0x[01, 01, 00, 02, 01, 04, 0a, 00, 13, 00, 00, 0a, 00, 13]
|
||||
Raw bytes (9): 0x[01, 01, 00, 01, 01, 04, 0a, 00, 13]
|
||||
Number of files: 1
|
||||
- file 0 => global file 1
|
||||
Number of expressions: 0
|
||||
Number of file 0 mappings: 2
|
||||
Number of file 0 mappings: 1
|
||||
- Code(Counter(0)) at (prev + 4, 10) to (start + 0, 19)
|
||||
- Code(Zero) at (prev + 0, 10) to (start + 0, 19)
|
||||
|
||||
Function name: <issue_84561::Foo as core::fmt::Debug>::fmt
|
||||
Raw bytes (29): 0x[01, 01, 02, 01, 05, 05, 02, 04, 01, 89, 01, 09, 00, 25, 05, 00, 25, 00, 26, 02, 01, 09, 00, 0f, 07, 01, 05, 00, 06]
|
||||
|
@ -1,62 +1,45 @@
|
||||
Function name: <try_error_result::Thing1>::get_thing_2
|
||||
Raw bytes (63): 0x[01, 01, 02, 01, 05, 05, 02, 0b, 01, 28, 05, 01, 18, 05, 02, 0d, 00, 14, 00, 00, 0d, 00, 14, 00, 00, 0d, 00, 14, 00, 00, 0d, 00, 14, 00, 00, 0d, 00, 14, 00, 00, 0d, 00, 14, 02, 02, 0d, 00, 1a, 00, 00, 0d, 00, 1a, 00, 00, 0d, 00, 1a, 07, 02, 05, 00, 06]
|
||||
Raw bytes (28): 0x[01, 01, 02, 01, 05, 05, 02, 04, 01, 28, 05, 01, 18, 05, 02, 0d, 00, 14, 02, 02, 0d, 00, 1a, 07, 02, 05, 00, 06]
|
||||
Number of files: 1
|
||||
- file 0 => global file 1
|
||||
Number of expressions: 2
|
||||
- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
|
||||
- expression 1 operands: lhs = Counter(1), rhs = Expression(0, Sub)
|
||||
Number of file 0 mappings: 11
|
||||
Number of file 0 mappings: 4
|
||||
- Code(Counter(0)) at (prev + 40, 5) to (start + 1, 24)
|
||||
- Code(Counter(1)) at (prev + 2, 13) to (start + 0, 20)
|
||||
- Code(Zero) at (prev + 0, 13) to (start + 0, 20)
|
||||
- Code(Zero) at (prev + 0, 13) to (start + 0, 20)
|
||||
- Code(Zero) at (prev + 0, 13) to (start + 0, 20)
|
||||
- Code(Zero) at (prev + 0, 13) to (start + 0, 20)
|
||||
- Code(Zero) at (prev + 0, 13) to (start + 0, 20)
|
||||
- Code(Expression(0, Sub)) at (prev + 2, 13) to (start + 0, 26)
|
||||
= (c0 - c1)
|
||||
- Code(Zero) at (prev + 0, 13) to (start + 0, 26)
|
||||
- Code(Zero) at (prev + 0, 13) to (start + 0, 26)
|
||||
- Code(Expression(1, Add)) at (prev + 2, 5) to (start + 0, 6)
|
||||
= (c1 + (c0 - c1))
|
||||
|
||||
Function name: <try_error_result::Thing2>::call
|
||||
Raw bytes (63): 0x[01, 01, 02, 01, 05, 05, 02, 0b, 01, 33, 05, 01, 18, 05, 02, 0d, 00, 14, 00, 00, 0d, 00, 14, 00, 00, 0d, 00, 14, 00, 00, 0d, 00, 14, 00, 00, 0d, 00, 14, 02, 02, 0d, 00, 13, 00, 00, 0d, 00, 13, 00, 00, 0d, 00, 13, 00, 00, 0d, 00, 13, 07, 02, 05, 00, 06]
|
||||
Raw bytes (28): 0x[01, 01, 02, 01, 05, 05, 02, 04, 01, 33, 05, 01, 18, 05, 02, 0d, 00, 14, 02, 02, 0d, 00, 13, 07, 02, 05, 00, 06]
|
||||
Number of files: 1
|
||||
- file 0 => global file 1
|
||||
Number of expressions: 2
|
||||
- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
|
||||
- expression 1 operands: lhs = Counter(1), rhs = Expression(0, Sub)
|
||||
Number of file 0 mappings: 11
|
||||
Number of file 0 mappings: 4
|
||||
- Code(Counter(0)) at (prev + 51, 5) to (start + 1, 24)
|
||||
- Code(Counter(1)) at (prev + 2, 13) to (start + 0, 20)
|
||||
- Code(Zero) at (prev + 0, 13) to (start + 0, 20)
|
||||
- Code(Zero) at (prev + 0, 13) to (start + 0, 20)
|
||||
- Code(Zero) at (prev + 0, 13) to (start + 0, 20)
|
||||
- Code(Zero) at (prev + 0, 13) to (start + 0, 20)
|
||||
- Code(Expression(0, Sub)) at (prev + 2, 13) to (start + 0, 19)
|
||||
= (c0 - c1)
|
||||
- Code(Zero) at (prev + 0, 13) to (start + 0, 19)
|
||||
- Code(Zero) at (prev + 0, 13) to (start + 0, 19)
|
||||
- Code(Zero) at (prev + 0, 13) to (start + 0, 19)
|
||||
- Code(Expression(1, Add)) at (prev + 2, 5) to (start + 0, 6)
|
||||
= (c1 + (c0 - c1))
|
||||
|
||||
Function name: try_error_result::call
|
||||
Raw bytes (43): 0x[01, 01, 02, 01, 05, 05, 02, 07, 01, 04, 01, 01, 14, 05, 02, 09, 00, 10, 00, 00, 09, 00, 10, 00, 00, 09, 00, 10, 02, 02, 09, 00, 0f, 00, 00, 09, 00, 0f, 07, 02, 01, 00, 02]
|
||||
Raw bytes (28): 0x[01, 01, 02, 01, 05, 05, 02, 04, 01, 04, 01, 01, 14, 05, 02, 09, 00, 10, 02, 02, 09, 00, 0f, 07, 02, 01, 00, 02]
|
||||
Number of files: 1
|
||||
- file 0 => global file 1
|
||||
Number of expressions: 2
|
||||
- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
|
||||
- expression 1 operands: lhs = Counter(1), rhs = Expression(0, Sub)
|
||||
Number of file 0 mappings: 7
|
||||
Number of file 0 mappings: 4
|
||||
- Code(Counter(0)) at (prev + 4, 1) to (start + 1, 20)
|
||||
- Code(Counter(1)) at (prev + 2, 9) to (start + 0, 16)
|
||||
- Code(Zero) at (prev + 0, 9) to (start + 0, 16)
|
||||
- Code(Zero) at (prev + 0, 9) to (start + 0, 16)
|
||||
- Code(Expression(0, Sub)) at (prev + 2, 9) to (start + 0, 15)
|
||||
= (c0 - c1)
|
||||
- Code(Zero) at (prev + 0, 9) to (start + 0, 15)
|
||||
- Code(Expression(1, Add)) at (prev + 2, 1) to (start + 0, 2)
|
||||
= (c1 + (c0 - c1))
|
||||
|
||||
|
@ -4,8 +4,10 @@
|
||||
fn bar() -> bool {
|
||||
let mut _0: bool;
|
||||
|
||||
+ coverage Counter(0) => /the/src/instrument_coverage.rs:20:1 - 22:2;
|
||||
+
|
||||
bb0: {
|
||||
+ Coverage::Counter(0) for [/the/src/instrument_coverage.rs:20:1 - 22:2];
|
||||
+ Coverage::CounterIncrement(0);
|
||||
_0 = const true;
|
||||
return;
|
||||
}
|
||||
|
@ -7,13 +7,19 @@
|
||||
let mut _2: bool;
|
||||
let mut _3: !;
|
||||
|
||||
+ coverage Counter(0) => /the/src/instrument_coverage.rs:11:1 - 11:11;
|
||||
+ coverage Expression(0) => /the/src/instrument_coverage.rs:12:5 - 13:17;
|
||||
+ coverage Expression(1) => /the/src/instrument_coverage.rs:14:13 - 14:18;
|
||||
+ coverage Expression(1) => /the/src/instrument_coverage.rs:17:1 - 17:2;
|
||||
+ coverage Counter(1) => /the/src/instrument_coverage.rs:15:10 - 15:11;
|
||||
+
|
||||
bb0: {
|
||||
+ Coverage::Counter(0) for [/the/src/instrument_coverage.rs:11:1 - 11:11];
|
||||
+ Coverage::CounterIncrement(0);
|
||||
goto -> bb1;
|
||||
}
|
||||
|
||||
bb1: {
|
||||
+ Coverage::Expression(0) = Counter(0) + Counter(1) for [/the/src/instrument_coverage.rs:12:5 - 13:17];
|
||||
+ Coverage::Expression(0) = Counter(0) + Counter(1);
|
||||
falseUnwind -> [real: bb2, unwind: bb6];
|
||||
}
|
||||
|
||||
@ -27,14 +33,14 @@
|
||||
}
|
||||
|
||||
bb4: {
|
||||
+ Coverage::Expression(1) = Expression(0) - Counter(1) for [/the/src/instrument_coverage.rs:14:13 - 14:18, /the/src/instrument_coverage.rs:17:1 - 17:2];
|
||||
+ Coverage::Expression(1) = Expression(0) - Counter(1);
|
||||
_0 = const ();
|
||||
StorageDead(_2);
|
||||
return;
|
||||
}
|
||||
|
||||
bb5: {
|
||||
+ Coverage::Counter(1) for [/the/src/instrument_coverage.rs:15:10 - 15:11];
|
||||
+ Coverage::CounterIncrement(1);
|
||||
_1 = const ();
|
||||
StorageDead(_2);
|
||||
goto -> bb1;
|
||||
|
Loading…
Reference in New Issue
Block a user