rustc_codegen_llvm/coverageinfo/mapgen/
covfun.rs1use std::ffi::CString;
8use std::sync::Arc;
9
10use rustc_abi::Align;
11use rustc_codegen_ssa::traits::{BaseTypeCodegenMethods as _, ConstCodegenMethods};
12use rustc_middle::mir::coverage::{
13 BasicCoverageBlock, CovTerm, CoverageIdsInfo, Expression, FunctionCoverageInfo, Mapping,
14 MappingKind, Op,
15};
16use rustc_middle::ty::{Instance, TyCtxt};
17use rustc_span::{SourceFile, Span};
18use rustc_target::spec::HasTargetSpec;
19use tracing::debug;
20
21use crate::common::CodegenCx;
22use crate::coverageinfo::mapgen::{GlobalFileTable, VirtualFileMapping, spans};
23use crate::coverageinfo::{ffi, llvm_cov};
24use crate::llvm;
25
26#[derive(Debug)]
29pub(crate) struct CovfunRecord<'tcx> {
30 mangled_function_name: &'tcx str,
31 source_hash: u64,
32 is_used: bool,
33
34 virtual_file_mapping: VirtualFileMapping,
35 expressions: Vec<ffi::CounterExpression>,
36 regions: ffi::Regions,
37}
38
39impl<'tcx> CovfunRecord<'tcx> {
40 pub(crate) fn all_source_files(&self) -> impl Iterator<Item = &SourceFile> {
43 self.virtual_file_mapping.local_file_table.iter().map(Arc::as_ref)
44 }
45}
46
47pub(crate) fn prepare_covfun_record<'tcx>(
48 tcx: TyCtxt<'tcx>,
49 instance: Instance<'tcx>,
50 is_used: bool,
51) -> Option<CovfunRecord<'tcx>> {
52 let fn_cov_info = tcx.instance_mir(instance.def).function_coverage_info.as_deref()?;
53 let ids_info = tcx.coverage_ids_info(instance.def)?;
54
55 let expressions = prepare_expressions(ids_info);
56
57 let mut covfun = CovfunRecord {
58 mangled_function_name: tcx.symbol_name(instance).name,
59 source_hash: if is_used { fn_cov_info.function_source_hash } else { 0 },
60 is_used,
61 virtual_file_mapping: VirtualFileMapping::default(),
62 expressions,
63 regions: ffi::Regions::default(),
64 };
65
66 fill_region_tables(tcx, fn_cov_info, ids_info, &mut covfun);
67
68 if covfun.regions.has_no_regions() {
69 debug!(?covfun, "function has no mappings to embed; skipping");
70 return None;
71 }
72
73 Some(covfun)
74}
75
76fn prepare_expressions(ids_info: &CoverageIdsInfo) -> Vec<ffi::CounterExpression> {
78 let counter_for_term = ffi::Counter::from_term;
79
80 ids_info
85 .expressions
86 .iter()
87 .map(move |&Expression { lhs, op, rhs }| ffi::CounterExpression {
88 lhs: counter_for_term(lhs),
89 kind: match op {
90 Op::Add => ffi::ExprKind::Add,
91 Op::Subtract => ffi::ExprKind::Subtract,
92 },
93 rhs: counter_for_term(rhs),
94 })
95 .collect::<Vec<_>>()
96}
97
98fn fill_region_tables<'tcx>(
100 tcx: TyCtxt<'tcx>,
101 fn_cov_info: &'tcx FunctionCoverageInfo,
102 ids_info: &'tcx CoverageIdsInfo,
103 covfun: &mut CovfunRecord<'tcx>,
104) {
105 let source_map = tcx.sess.source_map();
108 let Some(first_span) = (try { fn_cov_info.mappings.first()?.span }) else {
109 debug_assert!(false, "function has no mappings: {:?}", covfun.mangled_function_name);
110 return;
111 };
112 let source_file = source_map.lookup_source_file(first_span.lo());
113
114 let local_file_id = covfun.virtual_file_mapping.push_file(&source_file);
115
116 let discard_all = tcx.sess.coverage_discard_all_spans_in_codegen();
121 let make_coords = |span: Span| {
122 if discard_all { None } else { spans::make_coords(source_map, &source_file, span) }
123 };
124
125 let ffi::Regions {
126 code_regions,
127 expansion_regions: _, branch_regions,
129 mcdc_branch_regions,
130 mcdc_decision_regions,
131 } = &mut covfun.regions;
132
133 for &Mapping { ref kind, span } in &fn_cov_info.mappings {
136 let counter_for_bcb = |bcb: BasicCoverageBlock| -> ffi::Counter {
138 let term = if covfun.is_used {
139 ids_info.term_for_bcb[bcb].expect("every BCB in a mapping was given a term")
140 } else {
141 CovTerm::Zero
142 };
143 ffi::Counter::from_term(term)
144 };
145
146 let Some(coords) = make_coords(span) else { continue };
147 let cov_span = coords.make_coverage_span(local_file_id);
148
149 match *kind {
150 MappingKind::Code { bcb } => {
151 code_regions.push(ffi::CodeRegion { cov_span, counter: counter_for_bcb(bcb) });
152 }
153 MappingKind::Branch { true_bcb, false_bcb } => {
154 branch_regions.push(ffi::BranchRegion {
155 cov_span,
156 true_counter: counter_for_bcb(true_bcb),
157 false_counter: counter_for_bcb(false_bcb),
158 });
159 }
160 MappingKind::MCDCBranch { true_bcb, false_bcb, mcdc_params } => {
161 mcdc_branch_regions.push(ffi::MCDCBranchRegion {
162 cov_span,
163 true_counter: counter_for_bcb(true_bcb),
164 false_counter: counter_for_bcb(false_bcb),
165 mcdc_branch_params: ffi::mcdc::BranchParameters::from(mcdc_params),
166 });
167 }
168 MappingKind::MCDCDecision(mcdc_decision_params) => {
169 mcdc_decision_regions.push(ffi::MCDCDecisionRegion {
170 cov_span,
171 mcdc_decision_params: ffi::mcdc::DecisionParameters::from(mcdc_decision_params),
172 });
173 }
174 }
175 }
176}
177
178pub(crate) fn generate_covfun_record<'tcx>(
182 cx: &mut CodegenCx<'_, 'tcx>,
183 global_file_table: &GlobalFileTable,
184 covfun: &CovfunRecord<'tcx>,
185) {
186 let &CovfunRecord {
187 mangled_function_name,
188 source_hash,
189 is_used,
190 ref virtual_file_mapping,
191 ref expressions,
192 ref regions,
193 } = covfun;
194
195 let Some(local_file_table) = virtual_file_mapping.resolve_all(global_file_table) else {
196 debug_assert!(
197 false,
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 );
202 return;
203 };
204
205 let coverage_mapping_buffer =
207 llvm_cov::write_function_mappings_to_buffer(&local_file_table, expressions, regions);
208
209 let func_name_hash = llvm_cov::hash_bytes(mangled_function_name.as_bytes());
215 let covfun_record = cx.const_struct(
216 &[
217 cx.const_u64(func_name_hash),
218 cx.const_u32(coverage_mapping_buffer.len() as u32),
219 cx.const_u64(source_hash),
220 cx.const_u64(global_file_table.filenames_hash),
221 cx.const_bytes(&coverage_mapping_buffer),
222 ],
223 true,
226 );
227
228 let u = if is_used { "u" } else { "" };
233 let covfun_var_name = CString::new(format!("__covrec_{func_name_hash:X}{u}")).unwrap();
234 debug!("function record var name: {covfun_var_name:?}");
235
236 let 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::set_alignment(covfun_global, Align::EIGHT);
245 if cx.target_spec().supports_comdat() {
246 llvm::set_comdat(cx.llmod, covfun_global, &covfun_var_name);
247 }
248
249 cx.add_used_global(covfun_global);
250}