From b3c40cf374422ac8f6cbb14fa6747b2fae5762db Mon Sep 17 00:00:00 2001 From: Zalathar Date: Fri, 21 Mar 2025 15:14:22 +1100 Subject: [PATCH] coverage: Deal with unused functions and their names in one place --- .../src/coverageinfo/mapgen.rs | 34 ++----------- .../src/coverageinfo/mapgen/covfun.rs | 8 ---- .../src/coverageinfo/mapgen/unused.rs | 48 ++++++++++++++++++- 3 files changed, 51 insertions(+), 39 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs index 23e068fafac..5e62ce285dd 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs @@ -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::>(); - 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.) diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs index 048e1988c32..93419c2caad 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs @@ -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, diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/unused.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/unused.rs index e7c9075bc15..ab030f5b615 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/unused.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/unused.rs @@ -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> { + global_file_table: &mut GlobalFileTable, + covfun_records: &mut Vec>, +) { + 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> { 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::>() }