rustc_codegen_llvm/coverageinfo/mapgen/
covfun.rs1use std::ffi::CString;
8use std::sync::Arc;
9
10use rustc_abi::Align;
11use rustc_codegen_ssa::traits::{
12 BaseTypeCodegenMethods as _, ConstCodegenMethods, StaticCodegenMethods,
13};
14use rustc_middle::mir::coverage::{
15 BasicCoverageBlock, CovTerm, CoverageIdsInfo, Expression, FunctionCoverageInfo, 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, VirtualFileMapping, spans};
25use crate::coverageinfo::{ffi, llvm_cov};
26use crate::llvm;
27
28#[derive(Debug)]
31pub(crate) struct CovfunRecord<'tcx> {
32 mangled_function_name: &'tcx str,
33 source_hash: u64,
34 is_used: bool,
35
36 virtual_file_mapping: VirtualFileMapping,
37 expressions: Vec<ffi::CounterExpression>,
38 regions: ffi::Regions,
39}
40
41impl<'tcx> CovfunRecord<'tcx> {
42 pub(crate) fn all_source_files(&self) -> impl Iterator<Item = &SourceFile> {
45 self.virtual_file_mapping.local_file_table.iter().map(Arc::as_ref)
46 }
47}
48
49pub(crate) fn prepare_covfun_record<'tcx>(
50 tcx: TyCtxt<'tcx>,
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, 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
78fn prepare_expressions(ids_info: &CoverageIdsInfo) -> Vec<ffi::CounterExpression> {
80 let counter_for_term = ffi::Counter::from_term;
81
82 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
100fn fill_region_tables<'tcx>(
102 tcx: TyCtxt<'tcx>,
103 fn_cov_info: &'tcx FunctionCoverageInfo,
104 ids_info: &'tcx CoverageIdsInfo,
105 covfun: &mut CovfunRecord<'tcx>,
106) {
107 let source_map = tcx.sess.source_map();
110 let Some(first_span) = (try { fn_cov_info.mappings.first()?.span }) else {
111 debug_assert!(false, "function has no mappings: {:?}", covfun.mangled_function_name);
112 return;
113 };
114 let source_file = source_map.lookup_source_file(first_span.lo());
115
116 let local_file_id = covfun.virtual_file_mapping.push_file(&source_file);
117
118 let discard_all = tcx.sess.coverage_discard_all_spans_in_codegen();
123 let make_coords = |span: Span| {
124 if discard_all { None } else { spans::make_coords(source_map, &source_file, span) }
125 };
126
127 let ffi::Regions {
128 code_regions,
129 expansion_regions: _, branch_regions,
131 mcdc_branch_regions,
132 mcdc_decision_regions,
133 } = &mut covfun.regions;
134
135 for &Mapping { ref kind, span } in &fn_cov_info.mappings {
138 let counter_for_bcb = |bcb: BasicCoverageBlock| -> ffi::Counter {
140 let term = if covfun.is_used {
141 ids_info.term_for_bcb[bcb].expect("every BCB in a mapping was given a term")
142 } else {
143 CovTerm::Zero
144 };
145 ffi::Counter::from_term(term)
146 };
147
148 let Some(coords) = make_coords(span) else { continue };
149 let cov_span = coords.make_coverage_span(local_file_id);
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
180pub(crate) fn generate_covfun_record<'tcx>(
184 cx: &CodegenCx<'_, 'tcx>,
185 global_file_table: &GlobalFileTable,
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 let Some(local_file_table) = virtual_file_mapping.resolve_all(global_file_table) else {
198 debug_assert!(
199 false,
200 "all local files should be present in the global file table: \
201 global_file_table = {global_file_table:?}, \
202 virtual_file_mapping = {virtual_file_mapping:?}"
203 );
204 return;
205 };
206
207 let coverage_mapping_buffer =
209 llvm_cov::write_function_mappings_to_buffer(&local_file_table, expressions, regions);
210
211 let func_name_hash = llvm_cov::hash_bytes(mangled_function_name.as_bytes());
217 let covfun_record = cx.const_struct(
218 &[
219 cx.const_u64(func_name_hash),
220 cx.const_u32(coverage_mapping_buffer.len() as u32),
221 cx.const_u64(source_hash),
222 cx.const_u64(global_file_table.filenames_hash),
223 cx.const_bytes(&coverage_mapping_buffer),
224 ],
225 true,
228 );
229
230 let u = if is_used { "u" } else { "" };
235 let covfun_var_name = CString::new(format!("__covrec_{func_name_hash:X}{u}")).unwrap();
236 debug!("function record var name: {covfun_var_name:?}");
237
238 let covfun_global = llvm::add_global(cx.llmod, cx.val_ty(covfun_record), &covfun_var_name);
239 llvm::set_initializer(covfun_global, covfun_record);
240 llvm::set_global_constant(covfun_global, true);
241 llvm::set_linkage(covfun_global, llvm::Linkage::LinkOnceODRLinkage);
242 llvm::set_visibility(covfun_global, llvm::Visibility::Hidden);
243 llvm::set_section(covfun_global, cx.covfun_section_name());
244 llvm::set_alignment(covfun_global, Align::EIGHT);
247 if cx.target_spec().supports_comdat() {
248 llvm::set_comdat(cx.llmod, covfun_global, &covfun_var_name);
249 }
250
251 cx.add_used_global(covfun_global);
252}