1//! For each function that was instrumented for coverage, we need to embed its
2//! corresponding coverage mapping metadata inside the `__llvm_covfun`[^win]
3//! linker section of the final binary.
4//!
5//! [^win]: On Windows the section name is `.lcovfun`.
67use std::ffi::CString;
8use std::sync::Arc;
910use rustc_abi::Align;
11use rustc_codegen_ssa::traits::{BaseTypeCodegenMethods as _, ConstCodegenMethods};
12use rustc_middle::mir::coverage::{
13BasicCoverageBlock, CounterId, CovTerm, CoverageIdsInfo, Expression, ExpressionId,
14FunctionCoverageInfo, Mapping, MappingKind, Op,
15};
16use rustc_middle::ty::{Instance, TyCtxt};
17use rustc_span::{SourceFile, Span};
18use rustc_target::spec::HasTargetSpec;
19use tracing::debug;
2021use crate::common::CodegenCx;
22use crate::coverageinfo::mapgen::{GlobalFileTable, VirtualFileMapping, spans};
23use crate::coverageinfo::{ffi, llvm_cov};
24use crate::llvm;
2526/// Intermediate coverage metadata for a single function, used to help build
27/// the final record that will be embedded in the `__llvm_covfun` section.
28#[derive(#[automatically_derived]
impl<'tcx> ::core::fmt::Debug for CovfunRecord<'tcx> {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
let names: &'static _ =
&["_instance", "mangled_function_name", "source_hash", "is_used",
"virtual_file_mapping", "expressions", "regions"];
let values: &[&dyn ::core::fmt::Debug] =
&[&self._instance, &self.mangled_function_name, &self.source_hash,
&self.is_used, &self.virtual_file_mapping,
&self.expressions, &&self.regions];
::core::fmt::Formatter::debug_struct_fields_finish(f, "CovfunRecord",
names, values)
}
}Debug)]
29pub(crate) struct CovfunRecord<'tcx> {
30/// Not used directly, but helpful in debug messages.
31_instance: Instance<'tcx>,
3233 mangled_function_name: &'tcx str,
34 source_hash: u64,
35 is_used: bool,
3637 virtual_file_mapping: VirtualFileMapping,
38 expressions: Vec<ffi::CounterExpression>,
39 regions: llvm_cov::Regions,
40}
4142impl<'tcx> CovfunRecord<'tcx> {
43/// Iterator that yields all source files referred to by this function's
44 /// coverage mappings. Used to build the global file table for the CGU.
45pub(crate) fn all_source_files(&self) -> impl Iterator<Item = &SourceFile> {
46self.virtual_file_mapping.local_file_table.iter().map(Arc::as_ref)
47 }
48}
4950pub(crate) fn prepare_covfun_record<'tcx>(
51 tcx: TyCtxt<'tcx>,
52 instance: Instance<'tcx>,
53 is_used: bool,
54) -> Option<CovfunRecord<'tcx>> {
55let fn_cov_info = tcx.instance_mir(instance.def).function_coverage_info.as_deref()?;
56let ids_info = tcx.coverage_ids_info(instance.def)?;
5758let expressions = prepare_expressions(ids_info);
5960let mut covfun = CovfunRecord {
61 _instance: instance,
62 mangled_function_name: tcx.symbol_name(instance).name,
63 source_hash: if is_used { fn_cov_info.function_source_hash } else { 0 },
64is_used,
65 virtual_file_mapping: VirtualFileMapping::default(),
66expressions,
67 regions: llvm_cov::Regions::default(),
68 };
6970fill_region_tables(tcx, fn_cov_info, ids_info, &mut covfun);
7172if covfun.regions.has_no_regions() {
73{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs:73",
"rustc_codegen_llvm::coverageinfo::mapgen::covfun",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs"),
::tracing_core::__macro_support::Option::Some(73u32),
::tracing_core::__macro_support::Option::Some("rustc_codegen_llvm::coverageinfo::mapgen::covfun"),
::tracing_core::field::FieldSet::new(&["message", "covfun"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("function has no mappings to embed; skipping")
as &dyn Value)),
(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&debug(&covfun) as
&dyn Value))])
});
} else { ; }
};debug!(?covfun, "function has no mappings to embed; skipping");
74return None;
75 }
7677Some(covfun)
78}
7980pub(crate) fn counter_for_term(term: CovTerm) -> ffi::Counter {
81use ffi::Counter;
82match term {
83 CovTerm::Zero => Counter::ZERO,
84 CovTerm::Counter(id) => {
85Counter { kind: ffi::CounterKind::CounterValueReference, id: CounterId::as_u32(id) }
86 }
87 CovTerm::Expression(id) => {
88Counter { kind: ffi::CounterKind::Expression, id: ExpressionId::as_u32(id) }
89 }
90 }
91}
9293/// Convert the function's coverage-counter expressions into a form suitable for FFI.
94fn prepare_expressions(ids_info: &CoverageIdsInfo) -> Vec<ffi::CounterExpression> {
95// We know that LLVM will optimize out any unused expressions before
96 // producing the final coverage map, so there's no need to do the same
97 // thing on the Rust side unless we're confident we can do much better.
98 // (See `CounterExpressionsMinimizer` in `CoverageMappingWriter.cpp`.)
99ids_info100 .expressions
101 .iter()
102 .map(move |&Expression { lhs, op, rhs }| ffi::CounterExpression {
103 lhs: counter_for_term(lhs),
104 kind: match op {
105 Op::Add => ffi::ExprKind::Add,
106 Op::Subtract => ffi::ExprKind::Subtract,
107 },
108 rhs: counter_for_term(rhs),
109 })
110 .collect::<Vec<_>>()
111}
112113/// Populates the mapping region tables in the current function's covfun record.
114fn fill_region_tables<'tcx>(
115 tcx: TyCtxt<'tcx>,
116 fn_cov_info: &'tcx FunctionCoverageInfo,
117 ids_info: &'tcx CoverageIdsInfo,
118 covfun: &mut CovfunRecord<'tcx>,
119) {
120// If this function is unused, replace all counters with zero.
121let counter_for_bcb = |bcb: BasicCoverageBlock| -> ffi::Counter {
122let term = if covfun.is_used {
123ids_info.term_for_bcb[bcb].expect("every BCB in a mapping was given a term")
124 } else {
125 CovTerm::Zero126 };
127counter_for_term(term)
128 };
129130// Currently a function's mappings must all be in the same file, so use the
131 // first mapping's span to determine the file.
132let source_map = tcx.sess.source_map();
133let Some(first_span) = (try { fn_cov_info.mappings.first()?.span }) else {
134if true {
if !false {
{
::core::panicking::panic_fmt(format_args!("function has no mappings: {0:?}",
covfun));
}
};
};debug_assert!(false, "function has no mappings: {covfun:?}");
135return;
136 };
137let source_file = source_map.lookup_source_file(first_span.lo());
138139let local_file_id = covfun.virtual_file_mapping.push_file(&source_file);
140141// In rare cases, _all_ of a function's spans are discarded, and coverage
142 // codegen needs to handle that gracefully to avoid #133606.
143 // It's hard for tests to trigger this organically, so instead we set
144 // `-Zcoverage-options=discard-all-spans-in-codegen` to force it to occur.
145let discard_all = tcx.sess.coverage_options().discard_all_spans_in_codegen;
146let make_coords = |span: Span| {
147if discard_all { None } else { spans::make_coords(source_map, &source_file, span) }
148 };
149150let llvm_cov::Regions {
151 code_regions,
152 expansion_regions: _, // FIXME(Zalathar): Fill out support for expansion regions
153branch_regions,
154 } = &mut covfun.regions;
155156// For each counter/region pair in this function+file, convert it to a
157 // form suitable for FFI.
158for &Mapping { ref kind, span } in &fn_cov_info.mappings {
159let Some(coords) = make_coords(span) else { continue };
160let cov_span = coords.make_coverage_span(local_file_id);
161162match *kind {
163 MappingKind::Code { bcb } => {
164 code_regions.push(ffi::CodeRegion { cov_span, counter: counter_for_bcb(bcb) });
165 }
166 MappingKind::Branch { true_bcb, false_bcb } => {
167 branch_regions.push(ffi::BranchRegion {
168 cov_span,
169 true_counter: counter_for_bcb(true_bcb),
170 false_counter: counter_for_bcb(false_bcb),
171 });
172 }
173 }
174 }
175}
176177/// Generates the contents of the covfun record for this function, which
178/// contains the function's coverage mapping data. The record is then stored
179/// as a global variable in the `__llvm_covfun` section.
180pub(crate) fn generate_covfun_record<'tcx>(
181 cx: &mut CodegenCx<'_, 'tcx>,
182 global_file_table: &GlobalFileTable,
183 covfun: &CovfunRecord<'tcx>,
184) {
185let &CovfunRecord {
186 _instance,
187 mangled_function_name,
188 source_hash,
189 is_used,
190ref virtual_file_mapping,
191ref expressions,
192ref regions,
193 } = covfun;
194195let Some(local_file_table) = virtual_file_mapping.resolve_all(global_file_table) else {
196if true {
if !false {
{
::core::panicking::panic_fmt(format_args!("all local files should be present in the global file table: global_file_table = {0:?}, virtual_file_mapping = {1:?}",
global_file_table, virtual_file_mapping));
}
};
};debug_assert!(
197false,
198"all local files should be present in the global file table: \
199 global_file_table = {global_file_table:?}, \
200 virtual_file_mapping = {virtual_file_mapping:?}"
201);
202return;
203 };
204205// Encode the function's coverage mappings into a buffer.
206let coverage_mapping_buffer =
207 llvm_cov::write_function_mappings_to_buffer(&local_file_table, expressions, regions);
208209// A covfun record consists of four target-endian integers, followed by the
210 // encoded mapping data in bytes. Note that the length field is 32 bits.
211 // <https://llvm.org/docs/CoverageMappingFormat.html#llvm-ir-representation>
212 // See also `src/llvm-project/clang/lib/CodeGen/CoverageMappingGen.cpp` and
213 // `COVMAP_V3` in `src/llvm-project/llvm/include/llvm/ProfileData/InstrProfData.inc`.
214let func_name_hash = llvm_cov::hash_bytes(mangled_function_name.as_bytes());
215let covfun_record = cx.const_struct(
216&[
217cx.const_u64(func_name_hash),
218cx.const_u32(coverage_mapping_buffer.len() as u32),
219cx.const_u64(source_hash),
220cx.const_u64(global_file_table.filenames_hash),
221cx.const_bytes(&coverage_mapping_buffer),
222 ],
223// This struct needs to be packed, so that the 32-bit length field
224 // doesn't have unexpected padding.
225true,
226 );
227228// Choose a variable name to hold this function's covfun data.
229 // Functions that are used have a suffix ("u") to distinguish them from
230 // unused copies of the same function (from different CGUs), so that if a
231 // linker sees both it won't discard the used copy's data.
232let u = if is_used { "u" } else { "" };
233let covfun_var_name = CString::new(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("__covrec_{0:X}{1}", func_name_hash,
u))
})format!("__covrec_{func_name_hash:X}{u}")).unwrap();
234{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs:234",
"rustc_codegen_llvm::coverageinfo::mapgen::covfun",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs"),
::tracing_core::__macro_support::Option::Some(234u32),
::tracing_core::__macro_support::Option::Some("rustc_codegen_llvm::coverageinfo::mapgen::covfun"),
::tracing_core::field::FieldSet::new(&["message"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("function record var name: {0:?}",
covfun_var_name) as &dyn Value))])
});
} else { ; }
};debug!("function record var name: {covfun_var_name:?}");
235236let covfun_global = llvm::add_global(cx.llmod, cx.val_ty(covfun_record), &covfun_var_name);
237 llvm::set_initializer(covfun_global, covfun_record);
238 llvm::set_global_constant(covfun_global, true);
239 llvm::set_linkage(covfun_global, llvm::Linkage::LinkOnceODRLinkage);
240 llvm::set_visibility(covfun_global, llvm::Visibility::Hidden);
241 llvm::set_section(covfun_global, cx.covfun_section_name());
242// LLVM's coverage mapping format specifies 8-byte alignment for items in this section.
243 // <https://llvm.org/docs/CoverageMappingFormat.html>
244llvm::set_alignment(covfun_global, Align::EIGHT);
245if cx.target_spec().supports_comdat() {
246 llvm::set_comdat(cx.llmod, covfun_global, &covfun_var_name);
247 }
248249cx.add_used_global(covfun_global);
250}