coverage: Track used functions in a set instead of a map
This patch dismantles what was left of `FunctionCoverage` in `map_data.rs`, replaces `function_coverage_map` with a set, and overhauls how we prepare covfun records for unused functions.
This commit is contained in:
parent
d34c365eb0
commit
541d4e85d9
3 changed files with 37 additions and 90 deletions
|
@ -1,26 +0,0 @@
|
||||||
use rustc_middle::mir::coverage::{CoverageIdsInfo, FunctionCoverageInfo};
|
|
||||||
|
|
||||||
pub(crate) struct FunctionCoverage<'tcx> {
|
|
||||||
#[expect(unused)] // This whole file gets deleted later in the same PR.
|
|
||||||
pub(crate) function_coverage_info: &'tcx FunctionCoverageInfo,
|
|
||||||
/// If `None`, the corresponding function is unused.
|
|
||||||
ids_info: Option<&'tcx CoverageIdsInfo>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'tcx> FunctionCoverage<'tcx> {
|
|
||||||
pub(crate) fn new_used(
|
|
||||||
function_coverage_info: &'tcx FunctionCoverageInfo,
|
|
||||||
ids_info: &'tcx CoverageIdsInfo,
|
|
||||||
) -> Self {
|
|
||||||
Self { function_coverage_info, ids_info: Some(ids_info) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn new_unused(function_coverage_info: &'tcx FunctionCoverageInfo) -> Self {
|
|
||||||
Self { function_coverage_info, ids_info: None }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true for a used (called) function, and false for an unused function.
|
|
||||||
pub(crate) fn is_used(&self) -> bool {
|
|
||||||
self.ids_info.is_some()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,7 +18,6 @@ use tracing::debug;
|
||||||
|
|
||||||
use crate::common::CodegenCx;
|
use crate::common::CodegenCx;
|
||||||
use crate::coverageinfo::llvm_cov;
|
use crate::coverageinfo::llvm_cov;
|
||||||
use crate::coverageinfo::map_data::FunctionCoverage;
|
|
||||||
use crate::coverageinfo::mapgen::covfun::prepare_covfun_record;
|
use crate::coverageinfo::mapgen::covfun::prepare_covfun_record;
|
||||||
use crate::llvm;
|
use crate::llvm;
|
||||||
|
|
||||||
|
@ -49,23 +48,11 @@ pub(crate) fn finalize(cx: &CodegenCx<'_, '_>) {
|
||||||
|
|
||||||
debug!("Generating coverage map for CodegenUnit: `{}`", cx.codegen_unit.name());
|
debug!("Generating coverage map for CodegenUnit: `{}`", cx.codegen_unit.name());
|
||||||
|
|
||||||
// In order to show that unused functions have coverage counts of zero (0), LLVM requires the
|
|
||||||
// functions exist. Generate synthetic functions with a (required) single counter, and add the
|
|
||||||
// MIR `Coverage` code regions to the `function_coverage_map`, before calling
|
|
||||||
// `ctx.take_function_coverage_map()`.
|
|
||||||
if cx.codegen_unit.is_code_coverage_dead_code_cgu() {
|
|
||||||
add_unused_functions(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME(#132395): Can this be none even when coverage is enabled?
|
// FIXME(#132395): Can this be none even when coverage is enabled?
|
||||||
let function_coverage_map = match cx.coverage_cx {
|
let instances_used = match cx.coverage_cx {
|
||||||
Some(ref cx) => cx.take_function_coverage_map(),
|
Some(ref cx) => cx.instances_used.borrow(),
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
if function_coverage_map.is_empty() {
|
|
||||||
// This CGU has no functions with coverage instrumentation.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The order of entries in this global file table needs to be deterministic,
|
// The order of entries in this global file table needs to be deterministic,
|
||||||
// and ideally should also be independent of the details of stable-hashing,
|
// and ideally should also be independent of the details of stable-hashing,
|
||||||
|
@ -74,18 +61,27 @@ pub(crate) fn finalize(cx: &CodegenCx<'_, '_>) {
|
||||||
// are satisfied, the order can be arbitrary.
|
// are satisfied, the order can be arbitrary.
|
||||||
let mut global_file_table = GlobalFileTable::new();
|
let mut global_file_table = GlobalFileTable::new();
|
||||||
|
|
||||||
let covfun_records = function_coverage_map
|
let mut covfun_records = instances_used
|
||||||
.into_iter()
|
.iter()
|
||||||
|
.copied()
|
||||||
// Sort by symbol name, so that the global file table is built in an
|
// Sort by symbol name, so that the global file table is built in an
|
||||||
// order that doesn't depend on the stable-hash-based order in which
|
// order that doesn't depend on the stable-hash-based order in which
|
||||||
// instances were visited during codegen.
|
// instances were visited during codegen.
|
||||||
.sorted_by_cached_key(|&(instance, _)| tcx.symbol_name(instance).name)
|
.sorted_by_cached_key(|&instance| tcx.symbol_name(instance).name)
|
||||||
.filter_map(|(instance, function_coverage)| {
|
.filter_map(|instance| prepare_covfun_record(tcx, &mut global_file_table, instance, true))
|
||||||
let is_used = function_coverage.is_used();
|
|
||||||
prepare_covfun_record(tcx, &mut global_file_table, instance, is_used)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// In a single designated CGU, also prepare covfun records for functions
|
||||||
|
// in this crate that were instrumented for coverage, but are unused.
|
||||||
|
if cx.codegen_unit.is_code_coverage_dead_code_cgu() {
|
||||||
|
let mut unused_instances = gather_unused_function_instances(cx);
|
||||||
|
// Sort the unused instances by symbol name, for the same reason as the used ones.
|
||||||
|
unused_instances.sort_by_cached_key(|&instance| tcx.symbol_name(instance).name);
|
||||||
|
covfun_records.extend(unused_instances.into_iter().filter_map(|instance| {
|
||||||
|
prepare_covfun_record(tcx, &mut global_file_table, instance, false)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
// If there are no covfun records for this CGU, don't generate a covmap record.
|
// If there are no covfun records for this CGU, don't generate a covmap record.
|
||||||
// Emitting a covmap record without any covfun records causes `llvm-cov` to
|
// Emitting a covmap record without any covfun records causes `llvm-cov` to
|
||||||
// fail when generating coverage reports, and if there are no covfun records
|
// fail when generating coverage reports, and if there are no covfun records
|
||||||
|
@ -261,7 +257,7 @@ fn generate_covmap_record<'ll>(cx: &CodegenCx<'ll, '_>, version: u32, filenames_
|
||||||
/// coverage map (in a single designated CGU) so that we still emit coverage mappings for them.
|
/// coverage map (in a single designated CGU) so that we still emit coverage mappings for them.
|
||||||
/// We also end up adding their symbol names to a special global array that LLVM will include in
|
/// We also end up adding their symbol names to a special global array that LLVM will include in
|
||||||
/// its embedded coverage data.
|
/// its embedded coverage data.
|
||||||
fn add_unused_functions(cx: &CodegenCx<'_, '_>) {
|
fn gather_unused_function_instances<'tcx>(cx: &CodegenCx<'_, 'tcx>) -> Vec<ty::Instance<'tcx>> {
|
||||||
assert!(cx.codegen_unit.is_code_coverage_dead_code_cgu());
|
assert!(cx.codegen_unit.is_code_coverage_dead_code_cgu());
|
||||||
|
|
||||||
let tcx = cx.tcx;
|
let tcx = cx.tcx;
|
||||||
|
@ -279,20 +275,17 @@ fn add_unused_functions(cx: &CodegenCx<'_, '_>) {
|
||||||
&& !usage.used_via_inlining.contains(&d)
|
&& !usage.used_via_inlining.contains(&d)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Scan for unused functions that were instrumented for coverage.
|
// FIXME(#79651): Consider trying to filter out dummy instantiations of
|
||||||
for def_id in tcx.mir_keys(()).iter().copied().filter(|&def_id| is_unused_fn(def_id)) {
|
// unused generic functions from library crates, because they can produce
|
||||||
// Get the coverage info from MIR, skipping functions that were never instrumented.
|
// "unused instantiation" in coverage reports even when they are actually
|
||||||
let body = tcx.optimized_mir(def_id);
|
// used by some downstream crate in the same binary.
|
||||||
let Some(function_coverage_info) = body.function_coverage_info.as_deref() else { continue };
|
|
||||||
|
|
||||||
// FIXME(79651): Consider trying to filter out dummy instantiations of
|
tcx.mir_keys(())
|
||||||
// unused generic functions from library crates, because they can produce
|
.iter()
|
||||||
// "unused instantiation" in coverage reports even when they are actually
|
.copied()
|
||||||
// used by some downstream crate in the same binary.
|
.filter(|&def_id| is_unused_fn(def_id))
|
||||||
|
.map(|def_id| make_dummy_instance(tcx, def_id))
|
||||||
debug!("generating unused fn: {def_id:?}");
|
.collect::<Vec<_>>()
|
||||||
add_unused_function_coverage(cx, def_id, function_coverage_info);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UsageSets<'tcx> {
|
struct UsageSets<'tcx> {
|
||||||
|
@ -357,16 +350,11 @@ fn prepare_usage_sets<'tcx>(tcx: TyCtxt<'tcx>) -> UsageSets<'tcx> {
|
||||||
UsageSets { all_mono_items, used_via_inlining, missing_own_coverage }
|
UsageSets { all_mono_items, used_via_inlining, missing_own_coverage }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_unused_function_coverage<'tcx>(
|
fn make_dummy_instance<'tcx>(tcx: TyCtxt<'tcx>, local_def_id: LocalDefId) -> ty::Instance<'tcx> {
|
||||||
cx: &CodegenCx<'_, 'tcx>,
|
let def_id = local_def_id.to_def_id();
|
||||||
def_id: LocalDefId,
|
|
||||||
function_coverage_info: &'tcx mir::coverage::FunctionCoverageInfo,
|
|
||||||
) {
|
|
||||||
let tcx = cx.tcx;
|
|
||||||
let def_id = def_id.to_def_id();
|
|
||||||
|
|
||||||
// Make a dummy instance that fills in all generics with placeholders.
|
// Make a dummy instance that fills in all generics with placeholders.
|
||||||
let instance = ty::Instance::new(
|
ty::Instance::new(
|
||||||
def_id,
|
def_id,
|
||||||
ty::GenericArgs::for_item(tcx, def_id, |param, _| {
|
ty::GenericArgs::for_item(tcx, def_id, |param, _| {
|
||||||
if let ty::GenericParamDefKind::Lifetime = param.kind {
|
if let ty::GenericParamDefKind::Lifetime = param.kind {
|
||||||
|
@ -375,9 +363,5 @@ fn add_unused_function_coverage<'tcx>(
|
||||||
tcx.mk_param_from_def(param)
|
tcx.mk_param_from_def(param)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
)
|
||||||
|
|
||||||
// An unused function's mappings will all be rewritten to map to zero.
|
|
||||||
let function_coverage = FunctionCoverage::new_unused(function_coverage_info);
|
|
||||||
cx.coverage_cx().function_coverage_map.borrow_mut().insert(instance, function_coverage);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use rustc_abi::Size;
|
||||||
use rustc_codegen_ssa::traits::{
|
use rustc_codegen_ssa::traits::{
|
||||||
BuilderMethods, ConstCodegenMethods, CoverageInfoBuilderMethods, MiscCodegenMethods,
|
BuilderMethods, ConstCodegenMethods, CoverageInfoBuilderMethods, MiscCodegenMethods,
|
||||||
};
|
};
|
||||||
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
|
use rustc_data_structures::fx::{FxHashMap, FxIndexSet};
|
||||||
use rustc_middle::mir::coverage::CoverageKind;
|
use rustc_middle::mir::coverage::CoverageKind;
|
||||||
use rustc_middle::ty::Instance;
|
use rustc_middle::ty::Instance;
|
||||||
use rustc_middle::ty::layout::HasTyCtxt;
|
use rustc_middle::ty::layout::HasTyCtxt;
|
||||||
|
@ -13,18 +13,16 @@ use tracing::{debug, instrument};
|
||||||
|
|
||||||
use crate::builder::Builder;
|
use crate::builder::Builder;
|
||||||
use crate::common::CodegenCx;
|
use crate::common::CodegenCx;
|
||||||
use crate::coverageinfo::map_data::FunctionCoverage;
|
|
||||||
use crate::llvm;
|
use crate::llvm;
|
||||||
|
|
||||||
pub(crate) mod ffi;
|
pub(crate) mod ffi;
|
||||||
mod llvm_cov;
|
mod llvm_cov;
|
||||||
pub(crate) mod map_data;
|
|
||||||
mod mapgen;
|
mod mapgen;
|
||||||
|
|
||||||
/// Extra per-CGU context/state needed for coverage instrumentation.
|
/// Extra per-CGU context/state needed for coverage instrumentation.
|
||||||
pub(crate) struct CguCoverageContext<'ll, 'tcx> {
|
pub(crate) struct CguCoverageContext<'ll, 'tcx> {
|
||||||
/// Coverage data for each instrumented function identified by DefId.
|
/// Coverage data for each instrumented function identified by DefId.
|
||||||
pub(crate) function_coverage_map: RefCell<FxIndexMap<Instance<'tcx>, FunctionCoverage<'tcx>>>,
|
pub(crate) instances_used: RefCell<FxIndexSet<Instance<'tcx>>>,
|
||||||
pub(crate) pgo_func_name_var_map: RefCell<FxHashMap<Instance<'tcx>, &'ll llvm::Value>>,
|
pub(crate) pgo_func_name_var_map: RefCell<FxHashMap<Instance<'tcx>, &'ll llvm::Value>>,
|
||||||
pub(crate) mcdc_condition_bitmap_map: RefCell<FxHashMap<Instance<'tcx>, Vec<&'ll llvm::Value>>>,
|
pub(crate) mcdc_condition_bitmap_map: RefCell<FxHashMap<Instance<'tcx>, Vec<&'ll llvm::Value>>>,
|
||||||
|
|
||||||
|
@ -34,17 +32,13 @@ pub(crate) struct CguCoverageContext<'ll, 'tcx> {
|
||||||
impl<'ll, 'tcx> CguCoverageContext<'ll, 'tcx> {
|
impl<'ll, 'tcx> CguCoverageContext<'ll, 'tcx> {
|
||||||
pub(crate) fn new() -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
function_coverage_map: Default::default(),
|
instances_used: RefCell::<FxIndexSet<_>>::default(),
|
||||||
pgo_func_name_var_map: Default::default(),
|
pgo_func_name_var_map: Default::default(),
|
||||||
mcdc_condition_bitmap_map: Default::default(),
|
mcdc_condition_bitmap_map: Default::default(),
|
||||||
covfun_section_name: Default::default(),
|
covfun_section_name: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take_function_coverage_map(&self) -> FxIndexMap<Instance<'tcx>, FunctionCoverage<'tcx>> {
|
|
||||||
self.function_coverage_map.replace(FxIndexMap::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// LLVM use a temp value to record evaluated mcdc test vector of each decision, which is
|
/// LLVM use a temp value to record evaluated mcdc test vector of each decision, which is
|
||||||
/// called condition bitmap. In order to handle nested decisions, several condition bitmaps can
|
/// called condition bitmap. In order to handle nested decisions, several condition bitmaps can
|
||||||
/// be allocated for a function body. These values are named `mcdc.addr.{i}` and are a 32-bit
|
/// be allocated for a function body. These values are named `mcdc.addr.{i}` and are a 32-bit
|
||||||
|
@ -157,12 +151,7 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> {
|
||||||
// Mark the instance as used in this CGU, for coverage purposes.
|
// Mark the instance as used in this CGU, for coverage purposes.
|
||||||
// This includes functions that were not partitioned into this CGU,
|
// This includes functions that were not partitioned into this CGU,
|
||||||
// but were MIR-inlined into one of this CGU's functions.
|
// but were MIR-inlined into one of this CGU's functions.
|
||||||
coverage_cx.function_coverage_map.borrow_mut().entry(instance).or_insert_with(|| {
|
coverage_cx.instances_used.borrow_mut().insert(instance);
|
||||||
FunctionCoverage::new_used(
|
|
||||||
function_coverage_info,
|
|
||||||
bx.tcx.coverage_ids_info(instance.def),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
match *kind {
|
match *kind {
|
||||||
CoverageKind::SpanMarker | CoverageKind::BlockMarker { .. } => unreachable!(
|
CoverageKind::SpanMarker | CoverageKind::BlockMarker { .. } => unreachable!(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue