rustc_codegen_llvm/coverageinfo/mapgen/
covfun.rs
1use 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#[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();
110 let source_file = source_map.lookup_source_file(fn_cov_info.body_span.lo());
111
112 let global_file_id = global_file_table.global_file_id_for_file(&source_file);
114
115 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 &Mapping { ref kind, span } in &fn_cov_info.mappings {
129 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 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
180pub(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 let coverage_mapping_buffer = llvm_cov::write_function_mappings_to_buffer(
199 &virtual_file_mapping.to_vec(),
200 expressions,
201 regions,
202 );
203
204 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 true,
221 );
222
223 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::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}