Rollup merge of #132124 - Zalathar:consolidate-covstar, r=jieyouxu
coverage: Consolidate creation of covmap/covfun records This code for creating covmap/covfun records during codegen was split across multiple functions and files for dubious historical reasons. Having it all in one place makes it easier to follow. This PR also includes two semi-related cleanups: - Getting the codegen context's `coverage_cx` state is made infallible, since it should always exist when running the code paths that need it. - The value of `covfun_section_name` is saved in the codegen context, since it never changes at runtime, and the code that needs it has access to the context anyway. --- Background: Coverage instrumentation generates two kinds of metadata that are embedded in the final binary. There is per-CGU information that goes in the `__llvm_covmap` linker section, and per-function information that goes in the `__llvm_covfun` section (except on Windows, where slightly different section names are used).
This commit is contained in:
commit
c26280a8ba
@ -80,6 +80,7 @@ pub(crate) struct CodegenCx<'ll, 'tcx> {
|
||||
|
||||
pub isize_ty: &'ll Type,
|
||||
|
||||
/// Extra codegen state needed when coverage instrumentation is enabled.
|
||||
pub coverage_cx: Option<coverageinfo::CrateCoverageContext<'ll, 'tcx>>,
|
||||
pub dbg_cx: Option<debuginfo::CodegenUnitDebugContext<'ll, 'tcx>>,
|
||||
|
||||
@ -592,11 +593,10 @@ pub(crate) fn statics_to_rauw(&self) -> &RefCell<Vec<(&'ll Value, &'ll Value)>>
|
||||
&self.statics_to_rauw
|
||||
}
|
||||
|
||||
/// Extra state that is only available when coverage instrumentation is enabled.
|
||||
#[inline]
|
||||
pub(crate) fn coverage_context(
|
||||
&self,
|
||||
) -> Option<&coverageinfo::CrateCoverageContext<'ll, 'tcx>> {
|
||||
self.coverage_cx.as_ref()
|
||||
pub(crate) fn coverage_cx(&self) -> &coverageinfo::CrateCoverageContext<'ll, 'tcx> {
|
||||
self.coverage_cx.as_ref().expect("only called when coverage instrumentation is enabled")
|
||||
}
|
||||
|
||||
pub(crate) fn create_used_variable_impl(&self, name: &'static CStr, values: &[&'ll Value]) {
|
||||
|
@ -1,7 +1,10 @@
|
||||
use std::ffi::CStr;
|
||||
use std::ffi::CString;
|
||||
|
||||
use itertools::Itertools as _;
|
||||
use rustc_codegen_ssa::traits::{BaseTypeCodegenMethods, ConstCodegenMethods};
|
||||
use rustc_abi::Align;
|
||||
use rustc_codegen_ssa::traits::{
|
||||
BaseTypeCodegenMethods, ConstCodegenMethods, StaticCodegenMethods,
|
||||
};
|
||||
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
|
||||
use rustc_hir::def_id::{DefId, LocalDefId};
|
||||
use rustc_index::IndexVec;
|
||||
@ -10,6 +13,7 @@
|
||||
use rustc_middle::{bug, mir};
|
||||
use rustc_span::Symbol;
|
||||
use rustc_span::def_id::DefIdSet;
|
||||
use rustc_target::spec::HasTargetSpec;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::common::CodegenCx;
|
||||
@ -50,11 +54,7 @@ pub(crate) fn finalize(cx: &CodegenCx<'_, '_>) {
|
||||
add_unused_functions(cx);
|
||||
}
|
||||
|
||||
let function_coverage_map = match cx.coverage_context() {
|
||||
Some(ctx) => ctx.take_function_coverage_map(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
let function_coverage_map = cx.coverage_cx().take_function_coverage_map();
|
||||
if function_coverage_map.is_empty() {
|
||||
// This module has no functions with coverage instrumentation
|
||||
return;
|
||||
@ -78,11 +78,9 @@ pub(crate) fn finalize(cx: &CodegenCx<'_, '_>) {
|
||||
|
||||
// Generate the coverage map header, which contains the filenames used by
|
||||
// this CGU's coverage mappings, and store it in a well-known global.
|
||||
let cov_data_val = generate_coverage_map(cx, covmap_version, filenames_size, filenames_val);
|
||||
coverageinfo::save_cov_data_to_mod(cx, cov_data_val);
|
||||
generate_covmap_record(cx, covmap_version, filenames_size, filenames_val);
|
||||
|
||||
let mut unused_function_names = Vec::new();
|
||||
let covfun_section_name = coverageinfo::covfun_section_name(cx);
|
||||
|
||||
// Encode coverage mappings and generate function records
|
||||
for (instance, function_coverage) in function_coverage_entries {
|
||||
@ -111,9 +109,8 @@ pub(crate) fn finalize(cx: &CodegenCx<'_, '_>) {
|
||||
unused_function_names.push(mangled_function_name);
|
||||
}
|
||||
|
||||
save_function_record(
|
||||
generate_covfun_record(
|
||||
cx,
|
||||
&covfun_section_name,
|
||||
mangled_function_name,
|
||||
source_hash,
|
||||
filenames_ref,
|
||||
@ -308,15 +305,15 @@ fn encode_mappings_for_function(
|
||||
})
|
||||
}
|
||||
|
||||
/// Construct coverage map header and the array of function records, and combine them into the
|
||||
/// coverage map. Save the coverage map data into the LLVM IR as a static global using a
|
||||
/// specific, well-known section and name.
|
||||
fn generate_coverage_map<'ll>(
|
||||
/// Generates the contents of the covmap record for this CGU, which mostly
|
||||
/// consists of a header and a list of filenames. The record is then stored
|
||||
/// as a global variable in the `__llvm_covmap` section.
|
||||
fn generate_covmap_record<'ll>(
|
||||
cx: &CodegenCx<'ll, '_>,
|
||||
version: u32,
|
||||
filenames_size: usize,
|
||||
filenames_val: &'ll llvm::Value,
|
||||
) -> &'ll llvm::Value {
|
||||
) {
|
||||
debug!("cov map: filenames_size = {}, 0-based version = {}", filenames_size, version);
|
||||
|
||||
// Create the coverage data header (Note, fields 0 and 2 are now always zero,
|
||||
@ -331,15 +328,37 @@ fn generate_coverage_map<'ll>(
|
||||
);
|
||||
|
||||
// Create the complete LLVM coverage data value to add to the LLVM IR
|
||||
cx.const_struct(&[cov_data_header_val, filenames_val], /*packed=*/ false)
|
||||
let covmap_data =
|
||||
cx.const_struct(&[cov_data_header_val, filenames_val], /*packed=*/ false);
|
||||
|
||||
let covmap_var_name = CString::new(llvm::build_byte_buffer(|s| unsafe {
|
||||
llvm::LLVMRustCoverageWriteMappingVarNameToString(s);
|
||||
}))
|
||||
.unwrap();
|
||||
debug!("covmap var name: {:?}", covmap_var_name);
|
||||
|
||||
let covmap_section_name = CString::new(llvm::build_byte_buffer(|s| unsafe {
|
||||
llvm::LLVMRustCoverageWriteMapSectionNameToString(cx.llmod, s);
|
||||
}))
|
||||
.expect("covmap section name should not contain NUL");
|
||||
debug!("covmap section name: {:?}", covmap_section_name);
|
||||
|
||||
let llglobal = llvm::add_global(cx.llmod, cx.val_ty(covmap_data), &covmap_var_name);
|
||||
llvm::set_initializer(llglobal, covmap_data);
|
||||
llvm::set_global_constant(llglobal, true);
|
||||
llvm::set_linkage(llglobal, llvm::Linkage::PrivateLinkage);
|
||||
llvm::set_section(llglobal, &covmap_section_name);
|
||||
// LLVM's coverage mapping format specifies 8-byte alignment for items in this section.
|
||||
// <https://llvm.org/docs/CoverageMappingFormat.html>
|
||||
llvm::set_alignment(llglobal, Align::EIGHT);
|
||||
cx.add_used_global(llglobal);
|
||||
}
|
||||
|
||||
/// Construct a function record and combine it with the function's coverage mapping data.
|
||||
/// Save the function record into the LLVM IR as a static global using a
|
||||
/// specific, well-known section and name.
|
||||
fn save_function_record(
|
||||
/// Generates the contents of the covfun record for this function, which
|
||||
/// contains the function's coverage mapping data. The record is then stored
|
||||
/// as a global variable in the `__llvm_covfun` section.
|
||||
fn generate_covfun_record(
|
||||
cx: &CodegenCx<'_, '_>,
|
||||
covfun_section_name: &CStr,
|
||||
mangled_function_name: &str,
|
||||
source_hash: u64,
|
||||
filenames_ref: u64,
|
||||
@ -366,13 +385,28 @@ fn save_function_record(
|
||||
/*packed=*/ true,
|
||||
);
|
||||
|
||||
coverageinfo::save_func_record_to_mod(
|
||||
cx,
|
||||
covfun_section_name,
|
||||
func_name_hash,
|
||||
func_record_val,
|
||||
is_used,
|
||||
);
|
||||
// Choose a variable name to hold this function's covfun data.
|
||||
// Functions that are used have a suffix ("u") to distinguish them from
|
||||
// unused copies of the same function (from different CGUs), so that if a
|
||||
// linker sees both it won't discard the used copy's data.
|
||||
let func_record_var_name =
|
||||
CString::new(format!("__covrec_{:X}{}", func_name_hash, if is_used { "u" } else { "" }))
|
||||
.unwrap();
|
||||
debug!("function record var name: {:?}", func_record_var_name);
|
||||
|
||||
let llglobal = llvm::add_global(cx.llmod, cx.val_ty(func_record_val), &func_record_var_name);
|
||||
llvm::set_initializer(llglobal, func_record_val);
|
||||
llvm::set_global_constant(llglobal, true);
|
||||
llvm::set_linkage(llglobal, llvm::Linkage::LinkOnceODRLinkage);
|
||||
llvm::set_visibility(llglobal, llvm::Visibility::Hidden);
|
||||
llvm::set_section(llglobal, cx.covfun_section_name());
|
||||
// LLVM's coverage mapping format specifies 8-byte alignment for items in this section.
|
||||
// <https://llvm.org/docs/CoverageMappingFormat.html>
|
||||
llvm::set_alignment(llglobal, Align::EIGHT);
|
||||
if cx.target_spec().supports_comdat() {
|
||||
llvm::set_comdat(cx.llmod, llglobal, &func_record_var_name);
|
||||
}
|
||||
cx.add_used_global(llglobal);
|
||||
}
|
||||
|
||||
/// Each CGU will normally only emit coverage metadata for the functions that it actually generates.
|
||||
@ -504,9 +538,5 @@ fn add_unused_function_coverage<'tcx>(
|
||||
// zero, because none of its counters/expressions are marked as seen.
|
||||
let function_coverage = FunctionCoverageCollector::unused(instance, function_coverage_info);
|
||||
|
||||
if let Some(coverage_context) = cx.coverage_context() {
|
||||
coverage_context.function_coverage_map.borrow_mut().insert(instance, function_coverage);
|
||||
} else {
|
||||
bug!("Could not get the `coverage_context`");
|
||||
}
|
||||
cx.coverage_cx().function_coverage_map.borrow_mut().insert(instance, function_coverage);
|
||||
}
|
||||
|
@ -1,19 +1,16 @@
|
||||
use std::cell::RefCell;
|
||||
use std::cell::{OnceCell, RefCell};
|
||||
use std::ffi::{CStr, CString};
|
||||
|
||||
use libc::c_uint;
|
||||
use rustc_codegen_ssa::traits::{
|
||||
BaseTypeCodegenMethods, BuilderMethods, ConstCodegenMethods, CoverageInfoBuilderMethods,
|
||||
MiscCodegenMethods, StaticCodegenMethods,
|
||||
BuilderMethods, ConstCodegenMethods, CoverageInfoBuilderMethods, MiscCodegenMethods,
|
||||
};
|
||||
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
|
||||
use rustc_llvm::RustString;
|
||||
use rustc_middle::bug;
|
||||
use rustc_middle::mir::coverage::CoverageKind;
|
||||
use rustc_middle::ty::Instance;
|
||||
use rustc_middle::ty::layout::HasTyCtxt;
|
||||
use rustc_target::abi::{Align, Size};
|
||||
use rustc_target::spec::HasTargetSpec;
|
||||
use rustc_target::abi::Size;
|
||||
use tracing::{debug, instrument};
|
||||
|
||||
use crate::builder::Builder;
|
||||
@ -32,6 +29,8 @@ pub(crate) struct CrateCoverageContext<'ll, 'tcx> {
|
||||
RefCell<FxIndexMap<Instance<'tcx>, FunctionCoverageCollector<'tcx>>>,
|
||||
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>>>,
|
||||
|
||||
covfun_section_name: OnceCell<CString>,
|
||||
}
|
||||
|
||||
impl<'ll, 'tcx> CrateCoverageContext<'ll, 'tcx> {
|
||||
@ -40,6 +39,7 @@ pub(crate) fn new() -> Self {
|
||||
function_coverage_map: Default::default(),
|
||||
pgo_func_name_var_map: Default::default(),
|
||||
mcdc_condition_bitmap_map: Default::default(),
|
||||
covfun_section_name: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,27 +66,38 @@ fn try_get_mcdc_condition_bitmap(
|
||||
}
|
||||
}
|
||||
|
||||
// These methods used to be part of trait `CoverageInfoMethods`, which no longer
|
||||
// exists after most coverage code was moved out of SSA.
|
||||
impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
|
||||
pub(crate) fn coverageinfo_finalize(&self) {
|
||||
mapgen::finalize(self)
|
||||
}
|
||||
|
||||
/// Returns the section name to use when embedding per-function coverage information
|
||||
/// in the object file, according to the target's object file format. LLVM's coverage
|
||||
/// tools use information from this section when producing coverage reports.
|
||||
///
|
||||
/// Typical values are:
|
||||
/// - `__llvm_covfun` on Linux
|
||||
/// - `__LLVM_COV,__llvm_covfun` on macOS (includes `__LLVM_COV,` segment prefix)
|
||||
/// - `.lcovfun$M` on Windows (includes `$M` sorting suffix)
|
||||
fn covfun_section_name(&self) -> &CStr {
|
||||
self.coverage_cx().covfun_section_name.get_or_init(|| {
|
||||
CString::new(llvm::build_byte_buffer(|s| unsafe {
|
||||
llvm::LLVMRustCoverageWriteFuncSectionNameToString(self.llmod, s);
|
||||
}))
|
||||
.expect("covfun section name should not contain NUL")
|
||||
})
|
||||
}
|
||||
|
||||
/// For LLVM codegen, returns a function-specific `Value` for a global
|
||||
/// string, to hold the function name passed to LLVM intrinsic
|
||||
/// `instrprof.increment()`. The `Value` is only created once per instance.
|
||||
/// Multiple invocations with the same instance return the same `Value`.
|
||||
fn get_pgo_func_name_var(&self, instance: Instance<'tcx>) -> &'ll llvm::Value {
|
||||
if let Some(coverage_context) = self.coverage_context() {
|
||||
debug!("getting pgo_func_name_var for instance={:?}", instance);
|
||||
let mut pgo_func_name_var_map = coverage_context.pgo_func_name_var_map.borrow_mut();
|
||||
pgo_func_name_var_map
|
||||
.entry(instance)
|
||||
.or_insert_with(|| create_pgo_func_name_var(self, instance))
|
||||
} else {
|
||||
bug!("Could not get the `coverage_context`");
|
||||
}
|
||||
debug!("getting pgo_func_name_var for instance={:?}", instance);
|
||||
let mut pgo_func_name_var_map = self.coverage_cx().pgo_func_name_var_map.borrow_mut();
|
||||
pgo_func_name_var_map
|
||||
.entry(instance)
|
||||
.or_insert_with(|| create_pgo_func_name_var(self, instance))
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,11 +131,7 @@ fn init_coverage(&mut self, instance: Instance<'tcx>) {
|
||||
cond_bitmaps.push(cond_bitmap);
|
||||
}
|
||||
|
||||
self.coverage_context()
|
||||
.expect("always present when coverage is enabled")
|
||||
.mcdc_condition_bitmap_map
|
||||
.borrow_mut()
|
||||
.insert(instance, cond_bitmaps);
|
||||
self.coverage_cx().mcdc_condition_bitmap_map.borrow_mut().insert(instance, cond_bitmaps);
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
@ -145,8 +152,7 @@ fn add_coverage(&mut self, instance: Instance<'tcx>, kind: &CoverageKind) {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(coverage_context) = bx.coverage_context() else { return };
|
||||
let mut coverage_map = coverage_context.function_coverage_map.borrow_mut();
|
||||
let mut coverage_map = bx.coverage_cx().function_coverage_map.borrow_mut();
|
||||
let func_coverage = coverage_map
|
||||
.entry(instance)
|
||||
.or_insert_with(|| FunctionCoverageCollector::new(instance, function_coverage_info));
|
||||
@ -188,7 +194,8 @@ fn add_coverage(&mut self, instance: Instance<'tcx>, kind: &CoverageKind) {
|
||||
}
|
||||
CoverageKind::CondBitmapUpdate { index, decision_depth } => {
|
||||
drop(coverage_map);
|
||||
let cond_bitmap = coverage_context
|
||||
let cond_bitmap = bx
|
||||
.coverage_cx()
|
||||
.try_get_mcdc_condition_bitmap(&instance, decision_depth)
|
||||
.expect("mcdc cond bitmap should have been allocated for updating");
|
||||
let cond_index = bx.const_i32(index as i32);
|
||||
@ -196,7 +203,7 @@ fn add_coverage(&mut self, instance: Instance<'tcx>, kind: &CoverageKind) {
|
||||
}
|
||||
CoverageKind::TestVectorBitmapUpdate { bitmap_idx, decision_depth } => {
|
||||
drop(coverage_map);
|
||||
let cond_bitmap = coverage_context
|
||||
let cond_bitmap = bx.coverage_cx()
|
||||
.try_get_mcdc_condition_bitmap(&instance, decision_depth)
|
||||
.expect("mcdc cond bitmap should have been allocated for merging into the global bitmap");
|
||||
assert!(
|
||||
@ -290,82 +297,3 @@ pub(crate) fn hash_bytes(bytes: &[u8]) -> u64 {
|
||||
pub(crate) fn mapping_version() -> u32 {
|
||||
unsafe { llvm::LLVMRustCoverageMappingVersion() }
|
||||
}
|
||||
|
||||
pub(crate) fn save_cov_data_to_mod<'ll, 'tcx>(
|
||||
cx: &CodegenCx<'ll, 'tcx>,
|
||||
cov_data_val: &'ll llvm::Value,
|
||||
) {
|
||||
let covmap_var_name = CString::new(llvm::build_byte_buffer(|s| unsafe {
|
||||
llvm::LLVMRustCoverageWriteMappingVarNameToString(s);
|
||||
}))
|
||||
.unwrap();
|
||||
debug!("covmap var name: {:?}", covmap_var_name);
|
||||
|
||||
let covmap_section_name = CString::new(llvm::build_byte_buffer(|s| unsafe {
|
||||
llvm::LLVMRustCoverageWriteMapSectionNameToString(cx.llmod, s);
|
||||
}))
|
||||
.expect("covmap section name should not contain NUL");
|
||||
debug!("covmap section name: {:?}", covmap_section_name);
|
||||
|
||||
let llglobal = llvm::add_global(cx.llmod, cx.val_ty(cov_data_val), &covmap_var_name);
|
||||
llvm::set_initializer(llglobal, cov_data_val);
|
||||
llvm::set_global_constant(llglobal, true);
|
||||
llvm::set_linkage(llglobal, llvm::Linkage::PrivateLinkage);
|
||||
llvm::set_section(llglobal, &covmap_section_name);
|
||||
// LLVM's coverage mapping format specifies 8-byte alignment for items in this section.
|
||||
llvm::set_alignment(llglobal, Align::EIGHT);
|
||||
cx.add_used_global(llglobal);
|
||||
}
|
||||
|
||||
pub(crate) fn save_func_record_to_mod<'ll, 'tcx>(
|
||||
cx: &CodegenCx<'ll, 'tcx>,
|
||||
covfun_section_name: &CStr,
|
||||
func_name_hash: u64,
|
||||
func_record_val: &'ll llvm::Value,
|
||||
is_used: bool,
|
||||
) {
|
||||
// Assign a name to the function record. This is used to merge duplicates.
|
||||
//
|
||||
// In LLVM, a "translation unit" (effectively, a `Crate` in Rust) can describe functions that
|
||||
// are included-but-not-used. If (or when) Rust generates functions that are
|
||||
// included-but-not-used, note that a dummy description for a function included-but-not-used
|
||||
// in a Crate can be replaced by full description provided by a different Crate. The two kinds
|
||||
// of descriptions play distinct roles in LLVM IR; therefore, assign them different names (by
|
||||
// appending "u" to the end of the function record var name, to prevent `linkonce_odr` merging.
|
||||
let func_record_var_name =
|
||||
CString::new(format!("__covrec_{:X}{}", func_name_hash, if is_used { "u" } else { "" }))
|
||||
.unwrap();
|
||||
debug!("function record var name: {:?}", func_record_var_name);
|
||||
debug!("function record section name: {:?}", covfun_section_name);
|
||||
|
||||
let llglobal = llvm::add_global(cx.llmod, cx.val_ty(func_record_val), &func_record_var_name);
|
||||
llvm::set_initializer(llglobal, func_record_val);
|
||||
llvm::set_global_constant(llglobal, true);
|
||||
llvm::set_linkage(llglobal, llvm::Linkage::LinkOnceODRLinkage);
|
||||
llvm::set_visibility(llglobal, llvm::Visibility::Hidden);
|
||||
llvm::set_section(llglobal, covfun_section_name);
|
||||
// LLVM's coverage mapping format specifies 8-byte alignment for items in this section.
|
||||
llvm::set_alignment(llglobal, Align::EIGHT);
|
||||
if cx.target_spec().supports_comdat() {
|
||||
llvm::set_comdat(cx.llmod, llglobal, &func_record_var_name);
|
||||
}
|
||||
cx.add_used_global(llglobal);
|
||||
}
|
||||
|
||||
/// Returns the section name string to pass through to the linker when embedding
|
||||
/// per-function coverage information in the object file, according to the target
|
||||
/// platform's object file format.
|
||||
///
|
||||
/// LLVM's coverage tools read coverage mapping details from this section when
|
||||
/// producing coverage reports.
|
||||
///
|
||||
/// Typical values are:
|
||||
/// - `__llvm_covfun` on Linux
|
||||
/// - `__LLVM_COV,__llvm_covfun` on macOS (includes `__LLVM_COV,` segment prefix)
|
||||
/// - `.lcovfun$M` on Windows (includes `$M` sorting suffix)
|
||||
pub(crate) fn covfun_section_name(cx: &CodegenCx<'_, '_>) -> CString {
|
||||
CString::new(llvm::build_byte_buffer(|s| unsafe {
|
||||
llvm::LLVMRustCoverageWriteFuncSectionNameToString(cx.llmod, s);
|
||||
}))
|
||||
.expect("covfun section name should not contain NUL")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user