rustc_codegen_llvm/coverageinfo/mapgen/
covfun.rs
1use std::ffi::CString;
8
9use rustc_abi::Align;
10use rustc_codegen_ssa::traits::{
11 BaseTypeCodegenMethods as _, 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#[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 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
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 global_file_table: &mut GlobalFileTable,
104 fn_cov_info: &'tcx FunctionCoverageInfo,
105 ids_info: &'tcx CoverageIdsInfo,
106 covfun: &mut CovfunRecord<'tcx>,
107) {
108 let source_map = tcx.sess.source_map();
111 let Some(first_span) = (try { fn_cov_info.mappings.first()?.span }) else {
112 debug_assert!(false, "function has no mappings: {:?}", covfun.mangled_function_name);
113 return;
114 };
115 let source_file = source_map.lookup_source_file(first_span.lo());
116
117 let global_file_id = global_file_table.global_file_id_for_file(&source_file);
119
120 let local_file_id = covfun.virtual_file_mapping.local_id_for_global(global_file_id);
122
123 let discard_all = tcx.sess.coverage_discard_all_spans_in_codegen();
128 let make_coords = |span: Span| {
129 if discard_all { None } else { spans::make_coords(source_map, &source_file, span) }
130 };
131
132 let ffi::Regions {
133 code_regions,
134 expansion_regions: _, branch_regions,
136 mcdc_branch_regions,
137 mcdc_decision_regions,
138 } = &mut covfun.regions;
139
140 for &Mapping { ref kind, span } in &fn_cov_info.mappings {
143 let counter_for_bcb = |bcb: BasicCoverageBlock| -> ffi::Counter {
145 let term = if covfun.is_used {
146 ids_info.term_for_bcb[bcb].expect("every BCB in a mapping was given a term")
147 } else {
148 CovTerm::Zero
149 };
150 ffi::Counter::from_term(term)
151 };
152
153 let Some(coords) = make_coords(span) else { continue };
154 let cov_span = coords.make_coverage_span(local_file_id);
155
156 match *kind {
157 MappingKind::Code { bcb } => {
158 code_regions.push(ffi::CodeRegion { cov_span, counter: counter_for_bcb(bcb) });
159 }
160 MappingKind::Branch { true_bcb, false_bcb } => {
161 branch_regions.push(ffi::BranchRegion {
162 cov_span,
163 true_counter: counter_for_bcb(true_bcb),
164 false_counter: counter_for_bcb(false_bcb),
165 });
166 }
167 MappingKind::MCDCBranch { true_bcb, false_bcb, mcdc_params } => {
168 mcdc_branch_regions.push(ffi::MCDCBranchRegion {
169 cov_span,
170 true_counter: counter_for_bcb(true_bcb),
171 false_counter: counter_for_bcb(false_bcb),
172 mcdc_branch_params: ffi::mcdc::BranchParameters::from(mcdc_params),
173 });
174 }
175 MappingKind::MCDCDecision(mcdc_decision_params) => {
176 mcdc_decision_regions.push(ffi::MCDCDecisionRegion {
177 cov_span,
178 mcdc_decision_params: ffi::mcdc::DecisionParameters::from(mcdc_decision_params),
179 });
180 }
181 }
182 }
183}
184
185pub(crate) fn generate_covfun_record<'tcx>(
189 cx: &CodegenCx<'_, 'tcx>,
190 filenames_hash: u64,
191 covfun: &CovfunRecord<'tcx>,
192) {
193 let &CovfunRecord {
194 mangled_function_name,
195 source_hash,
196 is_used,
197 ref virtual_file_mapping,
198 ref expressions,
199 ref regions,
200 } = covfun;
201
202 let coverage_mapping_buffer = llvm_cov::write_function_mappings_to_buffer(
204 &virtual_file_mapping.to_vec(),
205 expressions,
206 regions,
207 );
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(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}