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;
8
9use rustc_abi::Align;
10use rustc_codegen_ssa::traits::{
11    BaseTypeCodegenMethods, ConstCodegenMethods, StaticCodegenMethods,
12};
13use rustc_middle::mir::coverage::{
14    BasicCoverageBlock, CovTerm, CoverageIdsInfo, Expression, FunctionCoverageInfo, Mapping,
15    MappingKind, Op,
16};
17use rustc_middle::ty::{Instance, TyCtxt};
18use rustc_span::Span;
19use rustc_target::spec::HasTargetSpec;
20use tracing::debug;
21
22use crate::common::CodegenCx;
23use crate::coverageinfo::mapgen::{GlobalFileTable, VirtualFileMapping, spans};
24use crate::coverageinfo::{ffi, llvm_cov};
25use crate::llvm;
26
27/// Intermediate coverage metadata for a single function, used to help build
28/// the final record that will be embedded in the `__llvm_covfun` section.
29#[derive(Debug)]
30pub(crate) struct CovfunRecord<'tcx> {
31    mangled_function_name: &'tcx str,
32    source_hash: u64,
33    is_used: bool,
34
35    virtual_file_mapping: VirtualFileMapping,
36    expressions: Vec<ffi::CounterExpression>,
37    regions: ffi::Regions,
38}
39
40impl<'tcx> CovfunRecord<'tcx> {
41    /// FIXME(Zalathar): Make this the responsibility of the code that determines
42    /// which functions are unused.
43    pub(crate) fn mangled_function_name_if_unused(&self) -> Option<&'tcx str> {
44        (!self.is_used).then_some(self.mangled_function_name)
45    }
46}
47
48pub(crate) fn prepare_covfun_record<'tcx>(
49    tcx: TyCtxt<'tcx>,
50    global_file_table: &mut GlobalFileTable,
51    instance: Instance<'tcx>,
52    is_used: bool,
53) -> Option<CovfunRecord<'tcx>> {
54    let fn_cov_info = tcx.instance_mir(instance.def).function_coverage_info.as_deref()?;
55    let ids_info = tcx.coverage_ids_info(instance.def)?;
56
57    let expressions = prepare_expressions(ids_info);
58
59    let mut covfun = CovfunRecord {
60        mangled_function_name: tcx.symbol_name(instance).name,
61        source_hash: if is_used { fn_cov_info.function_source_hash } else { 0 },
62        is_used,
63        virtual_file_mapping: VirtualFileMapping::default(),
64        expressions,
65        regions: ffi::Regions::default(),
66    };
67
68    fill_region_tables(tcx, global_file_table, fn_cov_info, ids_info, &mut covfun);
69
70    if covfun.regions.has_no_regions() {
71        debug!(?covfun, "function has no mappings to embed; skipping");
72        return None;
73    }
74
75    Some(covfun)
76}
77
78/// Convert the function's coverage-counter expressions into a form suitable for FFI.
79fn prepare_expressions(ids_info: &CoverageIdsInfo) -> Vec<ffi::CounterExpression> {
80    let counter_for_term = ffi::Counter::from_term;
81
82    // We know that LLVM will optimize out any unused expressions before
83    // producing the final coverage map, so there's no need to do the same
84    // thing on the Rust side unless we're confident we can do much better.
85    // (See `CounterExpressionsMinimizer` in `CoverageMappingWriter.cpp`.)
86    ids_info
87        .expressions
88        .iter()
89        .map(move |&Expression { lhs, op, rhs }| ffi::CounterExpression {
90            lhs: counter_for_term(lhs),
91            kind: match op {
92                Op::Add => ffi::ExprKind::Add,
93                Op::Subtract => ffi::ExprKind::Subtract,
94            },
95            rhs: counter_for_term(rhs),
96        })
97        .collect::<Vec<_>>()
98}
99
100/// Populates the mapping region tables in the current function's covfun record.
101fn fill_region_tables<'tcx>(
102    tcx: TyCtxt<'tcx>,
103    global_file_table: &mut GlobalFileTable,
104    fn_cov_info: &'tcx FunctionCoverageInfo,
105    ids_info: &'tcx CoverageIdsInfo,
106    covfun: &mut CovfunRecord<'tcx>,
107) {
108    // Currently a function's mappings must all be in the same file as its body span.
109    let source_map = tcx.sess.source_map();
110    let source_file = source_map.lookup_source_file(fn_cov_info.body_span.lo());
111
112    // Look up the global file ID for that file.
113    let global_file_id = global_file_table.global_file_id_for_file(&source_file);
114
115    // Associate that global file ID with a local file ID for this function.
116    let local_file_id = covfun.virtual_file_mapping.local_id_for_global(global_file_id);
117
118    let ffi::Regions { code_regions, branch_regions, mcdc_branch_regions, mcdc_decision_regions } =
119        &mut covfun.regions;
120
121    let make_cov_span = |span: Span| {
122        spans::make_coverage_span(local_file_id, source_map, fn_cov_info, &source_file, span)
123    };
124    let discard_all = tcx.sess.coverage_discard_all_spans_in_codegen();
125
126    // For each counter/region pair in this function+file, convert it to a
127    // form suitable for FFI.
128    for &Mapping { ref kind, span } in &fn_cov_info.mappings {
129        // If this function is unused, replace all counters with zero.
130        let counter_for_bcb = |bcb: BasicCoverageBlock| -> ffi::Counter {
131            let term = if covfun.is_used {
132                ids_info.term_for_bcb[bcb].expect("every BCB in a mapping was given a term")
133            } else {
134                CovTerm::Zero
135            };
136            ffi::Counter::from_term(term)
137        };
138
139        // Convert the `Span` into coordinates that we can pass to LLVM, or
140        // discard the span if conversion fails. In rare, cases _all_ of a
141        // function's spans are discarded, and the rest of coverage codegen
142        // needs to handle that gracefully to avoid a repeat of #133606.
143        // We don't have a good test case for triggering that organically, so
144        // instead we set `-Zcoverage-options=discard-all-spans-in-codegen`
145        // to force it to occur.
146        let Some(cov_span) = make_cov_span(span) else { continue };
147        if discard_all {
148            continue;
149        }
150
151        match *kind {
152            MappingKind::Code { bcb } => {
153                code_regions.push(ffi::CodeRegion { cov_span, counter: counter_for_bcb(bcb) });
154            }
155            MappingKind::Branch { true_bcb, false_bcb } => {
156                branch_regions.push(ffi::BranchRegion {
157                    cov_span,
158                    true_counter: counter_for_bcb(true_bcb),
159                    false_counter: counter_for_bcb(false_bcb),
160                });
161            }
162            MappingKind::MCDCBranch { true_bcb, false_bcb, mcdc_params } => {
163                mcdc_branch_regions.push(ffi::MCDCBranchRegion {
164                    cov_span,
165                    true_counter: counter_for_bcb(true_bcb),
166                    false_counter: counter_for_bcb(false_bcb),
167                    mcdc_branch_params: ffi::mcdc::BranchParameters::from(mcdc_params),
168                });
169            }
170            MappingKind::MCDCDecision(mcdc_decision_params) => {
171                mcdc_decision_regions.push(ffi::MCDCDecisionRegion {
172                    cov_span,
173                    mcdc_decision_params: ffi::mcdc::DecisionParameters::from(mcdc_decision_params),
174                });
175            }
176        }
177    }
178}
179
180/// Generates the contents of the covfun record for this function, which
181/// contains the function's coverage mapping data. The record is then stored
182/// as a global variable in the `__llvm_covfun` section.
183pub(crate) fn generate_covfun_record<'tcx>(
184    cx: &CodegenCx<'_, 'tcx>,
185    filenames_hash: u64,
186    covfun: &CovfunRecord<'tcx>,
187) {
188    let &CovfunRecord {
189        mangled_function_name,
190        source_hash,
191        is_used,
192        ref virtual_file_mapping,
193        ref expressions,
194        ref regions,
195    } = covfun;
196
197    // Encode the function's coverage mappings into a buffer.
198    let coverage_mapping_buffer = llvm_cov::write_function_mappings_to_buffer(
199        &virtual_file_mapping.to_vec(),
200        expressions,
201        regions,
202    );
203
204    // A covfun record consists of four target-endian integers, followed by the
205    // encoded mapping data in bytes. Note that the length field is 32 bits.
206    // <https://llvm.org/docs/CoverageMappingFormat.html#llvm-ir-representation>
207    // See also `src/llvm-project/clang/lib/CodeGen/CoverageMappingGen.cpp` and
208    // `COVMAP_V3` in `src/llvm-project/llvm/include/llvm/ProfileData/InstrProfData.inc`.
209    let func_name_hash = llvm_cov::hash_bytes(mangled_function_name.as_bytes());
210    let covfun_record = cx.const_struct(
211        &[
212            cx.const_u64(func_name_hash),
213            cx.const_u32(coverage_mapping_buffer.len() as u32),
214            cx.const_u64(source_hash),
215            cx.const_u64(filenames_hash),
216            cx.const_bytes(&coverage_mapping_buffer),
217        ],
218        // This struct needs to be packed, so that the 32-bit length field
219        // doesn't have unexpected padding.
220        true,
221    );
222
223    // Choose a variable name to hold this function's covfun data.
224    // Functions that are used have a suffix ("u") to distinguish them from
225    // unused copies of the same function (from different CGUs), so that if a
226    // linker sees both it won't discard the used copy's data.
227    let u = if is_used { "u" } else { "" };
228    let covfun_var_name = CString::new(format!("__covrec_{func_name_hash:X}{u}")).unwrap();
229    debug!("function record var name: {covfun_var_name:?}");
230
231    let covfun_global = llvm::add_global(cx.llmod, cx.val_ty(covfun_record), &covfun_var_name);
232    llvm::set_initializer(covfun_global, covfun_record);
233    llvm::set_global_constant(covfun_global, true);
234    llvm::set_linkage(covfun_global, llvm::Linkage::LinkOnceODRLinkage);
235    llvm::set_visibility(covfun_global, llvm::Visibility::Hidden);
236    llvm::set_section(covfun_global, cx.covfun_section_name());
237    // LLVM's coverage mapping format specifies 8-byte alignment for items in this section.
238    // <https://llvm.org/docs/CoverageMappingFormat.html>
239    llvm::set_alignment(covfun_global, Align::EIGHT);
240    if cx.target_spec().supports_comdat() {
241        llvm::set_comdat(cx.llmod, covfun_global, &covfun_var_name);
242    }
243
244    cx.add_used_global(covfun_global);
245}