Skip to main content

rustc_codegen_llvm/coverageinfo/mapgen/
covfun.rs

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`.
6
7use std::ffi::CString;
8use std::iter;
9use std::sync::Arc;
10
11use rustc_abi::Align;
12use rustc_codegen_ssa::traits::{BaseTypeCodegenMethods as _, ConstCodegenMethods};
13use rustc_index::IndexVec;
14use rustc_middle::mir::coverage::{
15    BasicCoverageBlock, CounterId, CovTerm, CoverageCodegenInfo, Expression, ExpressionId, Mapping,
16    MappingKind, Op,
17};
18use rustc_middle::ty::{Instance, TyCtxt};
19use rustc_span::{SourceFile, Span};
20use rustc_target::spec::HasTargetSpec;
21use tracing::debug;
22
23use crate::common::CodegenCx;
24use crate::coverageinfo::mapgen::{GlobalFileTable, LocalFileId, spans};
25use crate::coverageinfo::{ffi, llvm_cov};
26use crate::llvm;
27
28/// Intermediate coverage metadata for a single function, used to help build
29/// the final record that will be embedded in the `__llvm_covfun` section.
30#[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",
                        "expressions", "mappings"];
        let values: &[&dyn ::core::fmt::Debug] =
            &[&self._instance, &self.mangled_function_name, &self.source_hash,
                        &self.is_used, &self.expressions, &&self.mappings];
        ::core::fmt::Formatter::debug_struct_fields_finish(f, "CovfunRecord",
            names, values)
    }
}Debug)]
31pub(crate) struct CovfunRecord<'tcx> {
32    /// Not used directly, but helpful in debug messages.
33    _instance: Instance<'tcx>,
34
35    mangled_function_name: &'tcx str,
36    source_hash: u64,
37    is_used: bool,
38
39    expressions: Vec<ffi::CounterExpression>,
40    mappings: ResolvedMappings,
41}
42
43impl<'tcx> CovfunRecord<'tcx> {
44    /// Iterator that yields all source files referred to by this function's
45    /// coverage mappings. Used to build the global file table for the CGU.
46    pub(crate) fn all_source_files(&self) -> impl Iterator<Item = &SourceFile> {
47        self.mappings.all_source_files()
48    }
49}
50
51pub(crate) fn prepare_covfun_record<'tcx>(
52    tcx: TyCtxt<'tcx>,
53    instance: Instance<'tcx>,
54    is_used: bool,
55) -> Option<CovfunRecord<'tcx>> {
56    let mir_info = tcx.instance_mir(instance.def).coverage_mir_info.as_deref()?;
57    let cg_info = tcx.coverage_codegen_info(instance.def)?;
58
59    let expressions = prepare_expressions(cg_info);
60    let mappings = prepare_resolved_mappings(tcx, cg_info, is_used, &mir_info.mappings)?;
61
62    let covfun = CovfunRecord {
63        _instance: instance,
64        mangled_function_name: tcx.symbol_name(instance).name,
65        source_hash: if is_used { mir_info.function_source_hash } else { 0 },
66        is_used,
67        expressions,
68        mappings,
69    };
70
71    Some(covfun)
72}
73
74fn counter_for_term(term: CovTerm) -> ffi::Counter {
75    match term {
76        CovTerm::Zero => ffi::Counter::ZERO,
77        CovTerm::Counter(id) => ffi::Counter {
78            kind: ffi::CounterKind::CounterValueReference,
79            id: CounterId::as_u32(id),
80        },
81        CovTerm::Expression(id) => {
82            ffi::Counter { kind: ffi::CounterKind::Expression, id: ExpressionId::as_u32(id) }
83        }
84    }
85}
86
87/// Convert the function's coverage-counter expressions into a form suitable for FFI.
88fn prepare_expressions(cg_info: &CoverageCodegenInfo) -> Vec<ffi::CounterExpression> {
89    // We know that LLVM will optimize out any unused expressions before
90    // producing the final coverage map, so there's no need to do the same
91    // thing on the Rust side unless we're confident we can do much better.
92    // (See `CounterExpressionsMinimizer` in `CoverageMappingWriter.cpp`.)
93    cg_info
94        .expressions
95        .iter()
96        .map(move |&Expression { lhs, op, rhs }| ffi::CounterExpression {
97            lhs: counter_for_term(lhs),
98            kind: match op {
99                Op::Add => ffi::ExprKind::Add,
100                Op::Subtract => ffi::ExprKind::Subtract,
101            },
102            rhs: counter_for_term(rhs),
103        })
104        .collect::<Vec<_>>()
105}
106
107/// Intermediate representation of coverage mappings, after all mapping spans
108/// have been resolved to file coordinates (or discarded), but before producing
109/// a final [`llvm_cov::Regions`].
110///
111/// Having a separate resolution step makes it easier to handle edge cases
112/// where a function (or someday an expansion) manages to lose all of its spans,
113/// without accidentally emitting invalid covfun records containing empty files.
114#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ResolvedMappings {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field3_finish(f,
            "ResolvedMappings", "source_file", &self.source_file,
            "code_mappings", &self.code_mappings, "branch_mappings",
            &&self.branch_mappings)
    }
}Debug)]
115struct ResolvedMappings {
116    /// Source file for all of the [`spans::Coords`] in these mappings.
117    source_file: Arc<SourceFile>,
118
119    code_mappings: Vec<CodeMapping>,
120    branch_mappings: Vec<BranchMapping>,
121}
122
123impl ResolvedMappings {
124    fn ensure_nonempty(self) -> Option<Self> {
125        let ResolvedMappings { source_file: _, code_mappings, branch_mappings } = &self;
126        if code_mappings.is_empty() && branch_mappings.is_empty() { None } else { Some(self) }
127    }
128
129    fn all_source_files(&self) -> impl Iterator<Item = &SourceFile> {
130        // FIXME(Zalathar): When expansion regions are supported, this also needs to yield
131        // any source files used by descendant expansions.
132        let ResolvedMappings { source_file, code_mappings: _, branch_mappings: _ } = self;
133        iter::once(source_file.as_ref())
134    }
135}
136
137/// Resolved from [`MappingKind::Code`], and the precursor to [`ffi::CodeRegion`].
138#[derive(#[automatically_derived]
impl ::core::fmt::Debug for CodeMapping {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f, "CodeMapping",
            "coords", &self.coords, "counter", &&self.counter)
    }
}Debug)]
139struct CodeMapping {
140    coords: spans::Coords,
141    counter: ffi::Counter,
142}
143
144/// Resolved from [`MappingKind::Branch`], and the precursor to [`ffi::BranchRegion`].
145#[derive(#[automatically_derived]
impl ::core::fmt::Debug for BranchMapping {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field3_finish(f, "BranchMapping",
            "coords", &self.coords, "true_counter", &self.true_counter,
            "false_counter", &&self.false_counter)
    }
}Debug)]
146struct BranchMapping {
147    coords: spans::Coords,
148    true_counter: ffi::Counter,
149    false_counter: ffi::Counter,
150}
151
152fn prepare_resolved_mappings<'tcx>(
153    tcx: TyCtxt<'tcx>,
154    cg_info: &'tcx CoverageCodegenInfo,
155    is_used: bool,
156    mappings: &[Mapping],
157) -> Option<ResolvedMappings> {
158    // If this function is unused, replace all counters with zero.
159    let counter_for_bcb = |bcb: BasicCoverageBlock| -> ffi::Counter {
160        let term = if is_used {
161            cg_info.term_for_bcb[bcb].expect("every BCB in a mapping was given a term")
162        } else {
163            CovTerm::Zero
164        };
165        counter_for_term(term)
166    };
167
168    // Currently a function's mappings must all be in the same file, so use the
169    // first mapping's span to determine the file.
170    let source_map = tcx.sess.source_map();
171    let first_span = mappings.first()?.span;
172    let source_file = source_map.lookup_source_file(first_span.lo());
173
174    // In rare cases, _all_ of a function's spans are discarded, and coverage
175    // codegen needs to handle that gracefully to avoid #133606.
176    // It's hard for tests to trigger this organically, so instead we set
177    // `-Zcoverage-options=discard-all-spans-in-codegen` to force it to occur.
178    let discard_all = tcx.sess.coverage_options().discard_all_spans_in_codegen;
179    let make_coords = |span: Span| {
180        if discard_all { None } else { spans::make_coords(source_map, &source_file, span) }
181    };
182
183    let mut code_mappings = ::alloc::vec::Vec::new()vec![];
184    let mut branch_mappings = ::alloc::vec::Vec::new()vec![];
185
186    for &Mapping { ref kind, span } in mappings {
187        let Some(coords) = make_coords(span) else { continue };
188        match *kind {
189            MappingKind::Code { bcb } => {
190                code_mappings.push(CodeMapping { coords, counter: counter_for_bcb(bcb) })
191            }
192            MappingKind::Branch { true_bcb, false_bcb } => branch_mappings.push(BranchMapping {
193                coords,
194                true_counter: counter_for_bcb(true_bcb),
195                false_counter: counter_for_bcb(false_bcb),
196            }),
197        }
198    }
199
200    ResolvedMappings { source_file, code_mappings, branch_mappings }.ensure_nonempty()
201}
202
203/// Populates the mapping region tables for the current function's covfun record.
204fn fill_region_tables(
205    global_file_table: &GlobalFileTable,
206    mappings: &ResolvedMappings,
207    virtual_file_mapping: &mut IndexVec<LocalFileId, u32>,
208    regions: &mut llvm_cov::Regions,
209) {
210    let ResolvedMappings { source_file, code_mappings, branch_mappings } = mappings;
211    let Some(global_file_id) = global_file_table.get_existing_id(source_file) else {
212        if true {
    if !false {
        {
            ::core::panicking::panic_fmt(format_args!("couldn\'t find an existing global-file-id for {0:?}",
                    source_file));
        }
    };
};debug_assert!(false, "couldn't find an existing global-file-id for {source_file:?}");
213        return;
214    };
215
216    let llvm_cov::Regions {
217        code_regions,
218        expansion_regions: _, // FIXME(Zalathar): Fill out support for expansion regions
219        branch_regions,
220    } = regions;
221
222    // The global file IDs are stored as `u32` to make FFI easier.
223    // FIXME(Zalathar): Consider giving `newtype_index!` a safe transmute to `&[u32]`.
224    let local_file_id = virtual_file_mapping.push(global_file_id.as_u32());
225
226    for &CodeMapping { coords, counter } in code_mappings {
227        let cov_span = coords.make_coverage_span(local_file_id);
228        code_regions.push(ffi::CodeRegion { cov_span, counter });
229    }
230
231    for &BranchMapping { coords, true_counter, false_counter } in branch_mappings {
232        let cov_span = coords.make_coverage_span(local_file_id);
233        branch_regions.push(ffi::BranchRegion { cov_span, true_counter, false_counter });
234    }
235}
236
237/// Generates and emits the covfun record for this function, which
238/// contains the function's coverage mapping data. The record is emitted
239/// as a global variable in the `__llvm_covfun` section.
240pub(crate) fn emit_covfun_record<'tcx>(
241    cx: &mut CodegenCx<'_, 'tcx>,
242    global_file_table: &GlobalFileTable,
243    covfun: &CovfunRecord<'tcx>,
244) {
245    let &CovfunRecord {
246        _instance,
247        mangled_function_name,
248        source_hash,
249        is_used,
250        ref expressions,
251        ref mappings,
252    } = covfun;
253
254    let mut regions = llvm_cov::Regions::default();
255    let mut virtual_file_mapping = IndexVec::new();
256    fill_region_tables(global_file_table, mappings, &mut virtual_file_mapping, &mut regions);
257
258    if regions.has_no_regions() {
259        if true {
    if !false {
        {
            ::core::panicking::panic_fmt(format_args!("mappings should have produced at least one region: {0:#?}",
                    mappings));
        }
    };
};debug_assert!(false, "mappings should have produced at least one region: {mappings:#?}");
260        return;
261    }
262
263    // Encode the function's coverage mappings into a buffer.
264    let coverage_mapping_buffer = llvm_cov::write_function_mappings_to_buffer(
265        &virtual_file_mapping.raw,
266        expressions,
267        &regions,
268    );
269
270    // A covfun record consists of four target-endian integers, followed by the
271    // encoded mapping data in bytes. Note that the length field is 32 bits.
272    // <https://llvm.org/docs/CoverageMappingFormat.html#llvm-ir-representation>
273    // See also `src/llvm-project/clang/lib/CodeGen/CoverageMappingGen.cpp` and
274    // `COVMAP_V3` in `src/llvm-project/llvm/include/llvm/ProfileData/InstrProfData.inc`.
275    let func_name_hash = llvm_cov::hash_bytes(mangled_function_name.as_bytes());
276    let covfun_record = cx.const_struct(
277        &[
278            cx.const_u64(func_name_hash),
279            cx.const_u32(coverage_mapping_buffer.len() as u32),
280            cx.const_u64(source_hash),
281            cx.const_u64(global_file_table.filenames_hash),
282            cx.const_bytes(&coverage_mapping_buffer),
283        ],
284        // This struct needs to be packed, so that the 32-bit length field
285        // doesn't have unexpected padding.
286        true,
287    );
288
289    // Choose a variable name to hold this function's covfun data.
290    // Functions that are used have a suffix ("u") to distinguish them from
291    // unused copies of the same function (from different CGUs), so that if a
292    // linker sees both it won't discard the used copy's data.
293    let u = if is_used { "u" } else { "" };
294    let 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();
295    {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/809936eac66c547a5127ce1da805f0d3a6789b98/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs:295",
                        "rustc_codegen_llvm::coverageinfo::mapgen::covfun",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/809936eac66c547a5127ce1da805f0d3a6789b98/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs"),
                        ::tracing_core::__macro_support::Option::Some(295u32),
                        ::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};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("function record var name: {0:?}",
                                                    covfun_var_name) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("function record var name: {covfun_var_name:?}");
296
297    let covfun_global = llvm::add_global(cx.llmod, cx.val_ty(covfun_record), &covfun_var_name);
298    llvm::set_initializer(covfun_global, covfun_record);
299    llvm::set_global_constant(covfun_global, true);
300    llvm::set_linkage(covfun_global, llvm::Linkage::LinkOnceODRLinkage);
301    llvm::set_visibility(covfun_global, llvm::Visibility::Hidden);
302    llvm::set_section(covfun_global, cx.covfun_section_name());
303    // LLVM's coverage mapping format specifies 8-byte alignment for items in this section.
304    // <https://llvm.org/docs/CoverageMappingFormat.html>
305    llvm::set_alignment(covfun_global, Align::EIGHT);
306    if cx.target_spec().supports_comdat() {
307        llvm::set_comdat(cx.llmod, covfun_global, &covfun_var_name);
308    }
309
310    cx.add_used_global(covfun_global);
311}