2024-12-11 03:28:55 +00:00
|
|
|
//! For each function that was instrumented for coverage, we need to embed its
|
|
|
|
//! corresponding coverage mapping metadata inside the `__llvm_covfun`[^win]
|
|
|
|
//! linker section of the final binary.
|
|
|
|
//!
|
|
|
|
//! [^win]: On Windows the section name is `.lcovfun`.
|
|
|
|
|
|
|
|
use std::ffi::CString;
|
|
|
|
|
|
|
|
use rustc_abi::Align;
|
|
|
|
use rustc_codegen_ssa::traits::{
|
|
|
|
BaseTypeCodegenMethods, ConstCodegenMethods, StaticCodegenMethods,
|
|
|
|
};
|
2024-12-12 10:36:20 +00:00
|
|
|
use rustc_middle::mir::coverage::{
|
2025-01-20 11:43:59 +00:00
|
|
|
BasicCoverageBlock, CovTerm, CoverageIdsInfo, Expression, FunctionCoverageInfo, Mapping,
|
|
|
|
MappingKind, Op,
|
2024-12-12 10:36:20 +00:00
|
|
|
};
|
2024-12-11 03:28:55 +00:00
|
|
|
use rustc_middle::ty::{Instance, TyCtxt};
|
2024-12-18 10:00:51 +00:00
|
|
|
use rustc_span::Span;
|
2024-12-11 03:28:55 +00:00
|
|
|
use rustc_target::spec::HasTargetSpec;
|
|
|
|
use tracing::debug;
|
|
|
|
|
|
|
|
use crate::common::CodegenCx;
|
2024-12-18 10:19:30 +00:00
|
|
|
use crate::coverageinfo::mapgen::{GlobalFileTable, VirtualFileMapping, spans};
|
2024-12-11 03:28:55 +00:00
|
|
|
use crate::coverageinfo::{ffi, llvm_cov};
|
|
|
|
use crate::llvm;
|
|
|
|
|
2024-12-11 04:03:31 +00:00
|
|
|
/// Intermediate coverage metadata for a single function, used to help build
|
|
|
|
/// the final record that will be embedded in the `__llvm_covfun` section.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub(crate) struct CovfunRecord<'tcx> {
|
|
|
|
mangled_function_name: &'tcx str,
|
|
|
|
source_hash: u64,
|
|
|
|
is_used: bool,
|
2024-12-11 04:41:02 +00:00
|
|
|
|
|
|
|
virtual_file_mapping: VirtualFileMapping,
|
|
|
|
expressions: Vec<ffi::CounterExpression>,
|
|
|
|
regions: ffi::Regions,
|
2024-12-11 04:03:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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>,
|
2024-12-13 11:40:45 +00:00
|
|
|
global_file_table: &mut GlobalFileTable,
|
2024-12-11 03:28:55 +00:00
|
|
|
instance: Instance<'tcx>,
|
2024-12-14 12:14:19 +00:00
|
|
|
is_used: bool,
|
2024-12-11 04:03:31 +00:00
|
|
|
) -> Option<CovfunRecord<'tcx>> {
|
2024-12-12 10:36:20 +00:00
|
|
|
let fn_cov_info = tcx.instance_mir(instance.def).function_coverage_info.as_deref()?;
|
2025-01-22 01:58:37 +00:00
|
|
|
let ids_info = tcx.coverage_ids_info(instance.def)?;
|
2024-12-12 10:36:20 +00:00
|
|
|
|
2024-12-14 12:14:19 +00:00
|
|
|
let expressions = prepare_expressions(fn_cov_info, ids_info, is_used);
|
2024-12-12 10:36:20 +00:00
|
|
|
|
2024-12-11 04:41:02 +00:00
|
|
|
let mut covfun = CovfunRecord {
|
|
|
|
mangled_function_name: tcx.symbol_name(instance).name,
|
2024-12-14 12:14:19 +00:00
|
|
|
source_hash: if is_used { fn_cov_info.function_source_hash } else { 0 },
|
|
|
|
is_used,
|
2024-12-11 04:41:02 +00:00
|
|
|
virtual_file_mapping: VirtualFileMapping::default(),
|
2024-12-12 10:36:20 +00:00
|
|
|
expressions,
|
2024-12-11 04:41:02 +00:00
|
|
|
regions: ffi::Regions::default(),
|
|
|
|
};
|
|
|
|
|
2024-12-12 10:46:34 +00:00
|
|
|
fill_region_tables(tcx, global_file_table, fn_cov_info, ids_info, &mut covfun);
|
2024-12-11 04:41:02 +00:00
|
|
|
|
|
|
|
if covfun.regions.has_no_regions() {
|
2024-12-18 10:23:48 +00:00
|
|
|
debug!(?covfun, "function has no mappings to embed; skipping");
|
|
|
|
return None;
|
2024-12-11 03:28:55 +00:00
|
|
|
}
|
|
|
|
|
2024-12-11 04:41:02 +00:00
|
|
|
Some(covfun)
|
2024-12-11 03:28:55 +00:00
|
|
|
}
|
|
|
|
|
2024-12-12 10:36:20 +00:00
|
|
|
/// Convert the function's coverage-counter expressions into a form suitable for FFI.
|
|
|
|
fn prepare_expressions(
|
|
|
|
fn_cov_info: &FunctionCoverageInfo,
|
|
|
|
ids_info: &CoverageIdsInfo,
|
|
|
|
is_used: bool,
|
|
|
|
) -> Vec<ffi::CounterExpression> {
|
|
|
|
// If any counters or expressions were removed by MIR opts, replace their
|
|
|
|
// terms with zero.
|
|
|
|
let counter_for_term = |term| {
|
|
|
|
if !is_used || ids_info.is_zero_term(term) {
|
|
|
|
ffi::Counter::ZERO
|
|
|
|
} else {
|
|
|
|
ffi::Counter::from_term(term)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// We know that LLVM will optimize out any unused expressions before
|
|
|
|
// producing the final coverage map, so there's no need to do the same
|
|
|
|
// thing on the Rust side unless we're confident we can do much better.
|
|
|
|
// (See `CounterExpressionsMinimizer` in `CoverageMappingWriter.cpp`.)
|
|
|
|
fn_cov_info
|
|
|
|
.expressions
|
|
|
|
.iter()
|
|
|
|
.map(move |&Expression { lhs, op, rhs }| ffi::CounterExpression {
|
|
|
|
lhs: counter_for_term(lhs),
|
|
|
|
kind: match op {
|
|
|
|
Op::Add => ffi::ExprKind::Add,
|
|
|
|
Op::Subtract => ffi::ExprKind::Subtract,
|
|
|
|
},
|
|
|
|
rhs: counter_for_term(rhs),
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
}
|
|
|
|
|
2024-12-11 04:41:02 +00:00
|
|
|
/// Populates the mapping region tables in the current function's covfun record.
|
|
|
|
fn fill_region_tables<'tcx>(
|
|
|
|
tcx: TyCtxt<'tcx>,
|
2024-12-13 11:40:45 +00:00
|
|
|
global_file_table: &mut GlobalFileTable,
|
2024-12-12 10:46:34 +00:00
|
|
|
fn_cov_info: &'tcx FunctionCoverageInfo,
|
|
|
|
ids_info: &'tcx CoverageIdsInfo,
|
2024-12-11 04:41:02 +00:00
|
|
|
covfun: &mut CovfunRecord<'tcx>,
|
|
|
|
) {
|
2024-12-11 03:28:55 +00:00
|
|
|
// Currently a function's mappings must all be in the same file as its body span.
|
2024-12-18 10:00:51 +00:00
|
|
|
let source_map = tcx.sess.source_map();
|
|
|
|
let source_file = source_map.lookup_source_file(fn_cov_info.body_span.lo());
|
2024-12-11 03:28:55 +00:00
|
|
|
|
2024-12-18 10:19:30 +00:00
|
|
|
// Look up the global file ID for that file.
|
|
|
|
let global_file_id = global_file_table.global_file_id_for_file(&source_file);
|
2024-12-11 03:28:55 +00:00
|
|
|
|
|
|
|
// Associate that global file ID with a local file ID for this function.
|
2024-12-11 04:41:02 +00:00
|
|
|
let local_file_id = covfun.virtual_file_mapping.local_id_for_global(global_file_id);
|
2024-12-11 03:28:55 +00:00
|
|
|
|
2024-12-11 04:41:02 +00:00
|
|
|
let ffi::Regions { code_regions, branch_regions, mcdc_branch_regions, mcdc_decision_regions } =
|
|
|
|
&mut covfun.regions;
|
|
|
|
|
2024-12-18 10:00:51 +00:00
|
|
|
let make_cov_span = |span: Span| {
|
|
|
|
spans::make_coverage_span(local_file_id, source_map, fn_cov_info, &source_file, span)
|
|
|
|
};
|
2024-12-19 10:53:10 +00:00
|
|
|
let discard_all = tcx.sess.coverage_discard_all_spans_in_codegen();
|
2024-12-18 10:00:51 +00:00
|
|
|
|
2024-12-11 03:28:55 +00:00
|
|
|
// For each counter/region pair in this function+file, convert it to a
|
|
|
|
// form suitable for FFI.
|
2024-12-12 10:46:34 +00:00
|
|
|
let is_zero_term = |term| !covfun.is_used || ids_info.is_zero_term(term);
|
2024-12-18 10:00:51 +00:00
|
|
|
for &Mapping { ref kind, span } in &fn_cov_info.mappings {
|
2024-12-12 10:46:34 +00:00
|
|
|
// If the mapping refers to counters/expressions that were removed by
|
|
|
|
// MIR opts, replace those occurrences with zero.
|
2025-01-20 11:43:59 +00:00
|
|
|
let counter_for_bcb = |bcb: BasicCoverageBlock| -> ffi::Counter {
|
|
|
|
let term =
|
|
|
|
fn_cov_info.term_for_bcb[bcb].expect("every BCB in a mapping was given a term");
|
|
|
|
let term = if is_zero_term(term) { CovTerm::Zero } else { term };
|
|
|
|
ffi::Counter::from_term(term)
|
|
|
|
};
|
2024-12-12 10:46:34 +00:00
|
|
|
|
2024-12-19 10:53:10 +00:00
|
|
|
// Convert the `Span` into coordinates that we can pass to LLVM, or
|
|
|
|
// discard the span if conversion fails. In rare, cases _all_ of a
|
|
|
|
// function's spans are discarded, and the rest of coverage codegen
|
|
|
|
// needs to handle that gracefully to avoid a repeat of #133606.
|
|
|
|
// We don't have a good test case for triggering that organically, so
|
|
|
|
// instead we set `-Zcoverage-options=discard-all-spans-in-codegen`
|
|
|
|
// to force it to occur.
|
2024-12-18 10:00:51 +00:00
|
|
|
let Some(cov_span) = make_cov_span(span) else { continue };
|
2024-12-19 10:53:10 +00:00
|
|
|
if discard_all {
|
|
|
|
continue;
|
|
|
|
}
|
2024-12-18 10:00:51 +00:00
|
|
|
|
2025-01-20 11:43:59 +00:00
|
|
|
match *kind {
|
|
|
|
MappingKind::Code { bcb } => {
|
|
|
|
code_regions.push(ffi::CodeRegion { cov_span, counter: counter_for_bcb(bcb) });
|
2024-12-11 03:28:55 +00:00
|
|
|
}
|
2025-01-20 11:43:59 +00:00
|
|
|
MappingKind::Branch { true_bcb, false_bcb } => {
|
2024-12-11 03:28:55 +00:00
|
|
|
branch_regions.push(ffi::BranchRegion {
|
2024-12-18 07:59:27 +00:00
|
|
|
cov_span,
|
2025-01-20 11:43:59 +00:00
|
|
|
true_counter: counter_for_bcb(true_bcb),
|
|
|
|
false_counter: counter_for_bcb(false_bcb),
|
2024-12-11 03:28:55 +00:00
|
|
|
});
|
|
|
|
}
|
2025-01-20 11:43:59 +00:00
|
|
|
MappingKind::MCDCBranch { true_bcb, false_bcb, mcdc_params } => {
|
2024-12-11 03:28:55 +00:00
|
|
|
mcdc_branch_regions.push(ffi::MCDCBranchRegion {
|
2024-12-18 07:59:27 +00:00
|
|
|
cov_span,
|
2025-01-20 11:43:59 +00:00
|
|
|
true_counter: counter_for_bcb(true_bcb),
|
|
|
|
false_counter: counter_for_bcb(false_bcb),
|
2024-12-11 03:28:55 +00:00
|
|
|
mcdc_branch_params: ffi::mcdc::BranchParameters::from(mcdc_params),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
MappingKind::MCDCDecision(mcdc_decision_params) => {
|
|
|
|
mcdc_decision_regions.push(ffi::MCDCDecisionRegion {
|
2024-12-18 07:59:27 +00:00
|
|
|
cov_span,
|
2024-12-11 03:28:55 +00:00
|
|
|
mcdc_decision_params: ffi::mcdc::DecisionParameters::from(mcdc_decision_params),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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.
|
2024-12-11 04:03:31 +00:00
|
|
|
pub(crate) fn generate_covfun_record<'tcx>(
|
|
|
|
cx: &CodegenCx<'_, 'tcx>,
|
2024-12-12 09:28:10 +00:00
|
|
|
filenames_hash: u64,
|
2024-12-11 04:03:31 +00:00
|
|
|
covfun: &CovfunRecord<'tcx>,
|
2024-12-11 03:28:55 +00:00
|
|
|
) {
|
2024-12-11 04:03:31 +00:00
|
|
|
let &CovfunRecord {
|
|
|
|
mangled_function_name,
|
|
|
|
source_hash,
|
|
|
|
is_used,
|
2024-12-11 04:41:02 +00:00
|
|
|
ref virtual_file_mapping,
|
|
|
|
ref expressions,
|
|
|
|
ref regions,
|
2024-12-11 04:03:31 +00:00
|
|
|
} = covfun;
|
|
|
|
|
2024-12-11 04:41:02 +00:00
|
|
|
// Encode the function's coverage mappings into a buffer.
|
|
|
|
let coverage_mapping_buffer = llvm_cov::write_function_mappings_to_buffer(
|
|
|
|
&virtual_file_mapping.to_vec(),
|
|
|
|
expressions,
|
|
|
|
regions,
|
|
|
|
);
|
|
|
|
|
2024-12-12 09:28:10 +00:00
|
|
|
// A covfun record consists of four target-endian integers, followed by the
|
|
|
|
// encoded mapping data in bytes. Note that the length field is 32 bits.
|
|
|
|
// <https://llvm.org/docs/CoverageMappingFormat.html#llvm-ir-representation>
|
|
|
|
// See also `src/llvm-project/clang/lib/CodeGen/CoverageMappingGen.cpp` and
|
|
|
|
// `COVMAP_V3` in `src/llvm-project/llvm/include/llvm/ProfileData/InstrProfData.inc`.
|
2024-12-11 03:28:55 +00:00
|
|
|
let func_name_hash = llvm_cov::hash_bytes(mangled_function_name.as_bytes());
|
2024-12-12 09:28:10 +00:00
|
|
|
let covfun_record = cx.const_struct(
|
2024-12-11 03:28:55 +00:00
|
|
|
&[
|
2024-12-12 09:28:10 +00:00
|
|
|
cx.const_u64(func_name_hash),
|
|
|
|
cx.const_u32(coverage_mapping_buffer.len() as u32),
|
|
|
|
cx.const_u64(source_hash),
|
|
|
|
cx.const_u64(filenames_hash),
|
|
|
|
cx.const_bytes(&coverage_mapping_buffer),
|
2024-12-11 03:28:55 +00:00
|
|
|
],
|
2024-12-12 09:28:10 +00:00
|
|
|
// This struct needs to be packed, so that the 32-bit length field
|
|
|
|
// doesn't have unexpected padding.
|
|
|
|
true,
|
2024-12-11 03:28:55 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
// 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.
|
2024-12-12 09:28:10 +00:00
|
|
|
let u = if is_used { "u" } else { "" };
|
|
|
|
let covfun_var_name = CString::new(format!("__covrec_{func_name_hash:X}{u}")).unwrap();
|
|
|
|
debug!("function record var name: {covfun_var_name:?}");
|
|
|
|
|
|
|
|
let covfun_global = llvm::add_global(cx.llmod, cx.val_ty(covfun_record), &covfun_var_name);
|
|
|
|
llvm::set_initializer(covfun_global, covfun_record);
|
|
|
|
llvm::set_global_constant(covfun_global, true);
|
|
|
|
llvm::set_linkage(covfun_global, llvm::Linkage::LinkOnceODRLinkage);
|
|
|
|
llvm::set_visibility(covfun_global, llvm::Visibility::Hidden);
|
|
|
|
llvm::set_section(covfun_global, cx.covfun_section_name());
|
2024-12-11 03:28:55 +00:00
|
|
|
// LLVM's coverage mapping format specifies 8-byte alignment for items in this section.
|
|
|
|
// <https://llvm.org/docs/CoverageMappingFormat.html>
|
2024-12-12 09:28:10 +00:00
|
|
|
llvm::set_alignment(covfun_global, Align::EIGHT);
|
2024-12-11 03:28:55 +00:00
|
|
|
if cx.target_spec().supports_comdat() {
|
2024-12-12 09:28:10 +00:00
|
|
|
llvm::set_comdat(cx.llmod, covfun_global, &covfun_var_name);
|
2024-12-11 03:28:55 +00:00
|
|
|
}
|
2024-12-12 09:28:10 +00:00
|
|
|
|
|
|
|
cx.add_used_global(covfun_global);
|
2024-12-11 03:28:55 +00:00
|
|
|
}
|