coverage: Deal with unused functions and their names in one place

This commit is contained in:
Zalathar 2025-03-21 15:14:22 +11:00
parent 75135aaf19
commit b3c40cf374
3 changed files with 51 additions and 39 deletions

View file

@ -73,12 +73,11 @@ pub(crate) fn finalize(cx: &CodegenCx<'_, '_>) {
// 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 = unused::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)
}));
unused::prepare_covfun_records_for_unused_functions(
cx,
&mut global_file_table,
&mut covfun_records,
);
}
// If there are no covfun records for this CGU, don't generate a covmap record.
@ -97,33 +96,10 @@ pub(crate) fn finalize(cx: &CodegenCx<'_, '_>) {
// contain multiple covmap records from different compilation units.
let filenames_hash = llvm_cov::hash_bytes(&filenames_buffer);
let mut unused_function_names = vec![];
for covfun in &covfun_records {
unused_function_names.extend(covfun.mangled_function_name_if_unused());
covfun::generate_covfun_record(cx, filenames_hash, covfun)
}
// For unused functions, we need to take their mangled names and store them
// in a specially-named global array. LLVM's `InstrProfiling` pass will
// detect this global and include those names in its `__llvm_prf_names`
// section. (See `llvm/lib/Transforms/Instrumentation/InstrProfiling.cpp`.)
if !unused_function_names.is_empty() {
assert!(cx.codegen_unit.is_code_coverage_dead_code_cgu());
let name_globals = unused_function_names
.into_iter()
.map(|mangled_function_name| cx.const_str(mangled_function_name).0)
.collect::<Vec<_>>();
let initializer = cx.const_array(cx.type_ptr(), &name_globals);
let array = llvm::add_global(cx.llmod, cx.val_ty(initializer), c"__llvm_coverage_names");
llvm::set_global_constant(array, true);
llvm::set_linkage(array, llvm::Linkage::InternalLinkage);
llvm::set_initializer(array, initializer);
}
// Generate the coverage map header, which contains the filenames used by
// this CGU's coverage mappings, and store it in a well-known global.
// (This is skipped if we returned early due to having no covfun records.)

View file

@ -37,14 +37,6 @@ pub(crate) struct CovfunRecord<'tcx> {
regions: ffi::Regions,
}
impl<'tcx> CovfunRecord<'tcx> {
/// FIXME(Zalathar): Make this the responsibility of the code that determines
/// which functions are unused.
pub(crate) fn mangled_function_name_if_unused(&self) -> Option<&'tcx str> {
(!self.is_used).then_some(self.mangled_function_name)
}
}
pub(crate) fn prepare_covfun_record<'tcx>(
tcx: TyCtxt<'tcx>,
global_file_table: &mut GlobalFileTable,

View file

@ -1,3 +1,4 @@
use rustc_codegen_ssa::traits::{BaseTypeCodegenMethods, ConstCodegenMethods};
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_middle::mir;
@ -6,6 +7,9 @@ use rustc_middle::ty::{self, TyCtxt};
use rustc_span::def_id::DefIdSet;
use crate::common::CodegenCx;
use crate::coverageinfo::mapgen::GlobalFileTable;
use crate::coverageinfo::mapgen::covfun::{CovfunRecord, prepare_covfun_record};
use crate::llvm;
/// Each CGU will normally only emit coverage metadata for the functions that it actually generates.
/// But since we don't want unused functions to disappear from coverage reports, we also scan for
@ -15,9 +19,48 @@ use crate::common::CodegenCx;
/// 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
/// its embedded coverage data.
pub(crate) fn gather_unused_function_instances<'tcx>(
pub(crate) fn prepare_covfun_records_for_unused_functions<'tcx>(
cx: &CodegenCx<'_, 'tcx>,
) -> Vec<ty::Instance<'tcx>> {
global_file_table: &mut GlobalFileTable,
covfun_records: &mut Vec<CovfunRecord<'tcx>>,
) {
assert!(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, so that their order isn't hash-sensitive.
unused_instances.sort_by_key(|instance| instance.symbol_name);
// Try to create a covfun record for each unused function.
let mut name_globals = Vec::with_capacity(unused_instances.len());
covfun_records.extend(unused_instances.into_iter().filter_map(|unused| try {
let record = prepare_covfun_record(cx.tcx, global_file_table, unused.instance, false)?;
// If successful, also store its symbol name in a global constant.
name_globals.push(cx.const_str(unused.symbol_name.name).0);
record
}));
// Store the names of unused functions in a specially-named global array.
// LLVM's `InstrProfilling` pass will detect this array, and include the
// referenced names in its `__llvm_prf_names` section.
// (See `llvm/lib/Transforms/Instrumentation/InstrProfiling.cpp`.)
if !name_globals.is_empty() {
let initializer = cx.const_array(cx.type_ptr(), &name_globals);
let array = llvm::add_global(cx.llmod, cx.val_ty(initializer), c"__llvm_coverage_names");
llvm::set_global_constant(array, true);
llvm::set_linkage(array, llvm::Linkage::InternalLinkage);
llvm::set_initializer(array, initializer);
}
}
/// Holds a dummy function instance along with its symbol name, to avoid having
/// to repeatedly query for the name.
struct UnusedInstance<'tcx> {
instance: ty::Instance<'tcx>,
symbol_name: ty::SymbolName<'tcx>,
}
fn gather_unused_function_instances<'tcx>(cx: &CodegenCx<'_, 'tcx>) -> Vec<UnusedInstance<'tcx>> {
assert!(cx.codegen_unit.is_code_coverage_dead_code_cgu());
let tcx = cx.tcx;
@ -45,6 +88,7 @@ pub(crate) fn gather_unused_function_instances<'tcx>(
.copied()
.filter(|&def_id| is_unused_fn(def_id))
.map(|def_id| make_dummy_instance(tcx, def_id))
.map(|instance| UnusedInstance { instance, symbol_name: tcx.symbol_name(instance) })
.collect::<Vec<_>>()
}