]>
Commit | Line | Data |
---|---|---|
3dfed10e XL |
1 | use crate::common::CodegenCx; |
2 | use crate::coverageinfo; | |
3 | use crate::llvm; | |
4 | ||
5 | use llvm::coverageinfo::CounterMappingRegion; | |
cdc7bbd5 XL |
6 | use rustc_codegen_ssa::coverageinfo::map::{Counter, CounterExpression}; |
7 | use rustc_codegen_ssa::traits::{ConstMethods, CoverageInfoMethods}; | |
fc512014 | 8 | use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet}; |
17df50a5 | 9 | use rustc_hir::def_id::{DefId, DefIdSet}; |
3dfed10e XL |
10 | use rustc_llvm::RustString; |
11 | use rustc_middle::mir::coverage::CodeRegion; | |
a2a8927a | 12 | use rustc_middle::ty::TyCtxt; |
fc512014 | 13 | use rustc_span::Symbol; |
3dfed10e XL |
14 | |
15 | use std::ffi::CString; | |
16 | ||
17 | use tracing::debug; | |
18 | ||
19 | /// Generates and exports the Coverage Map. | |
20 | /// | |
a2a8927a XL |
21 | /// Rust Coverage Map generation supports LLVM Coverage Mapping Format versions |
22 | /// 5 (LLVM 12, only) and 6 (zero-based encoded as 4 and 5, respectively), as defined at | |
23 | /// [LLVM Code Coverage Mapping Format](https://github.com/rust-lang/llvm-project/blob/rustc/13.0-2021-09-30/llvm/docs/CoverageMappingFormat.rst#llvm-code-coverage-mapping-format). | |
24 | /// These versions are supported by the LLVM coverage tools (`llvm-profdata` and `llvm-cov`) | |
25 | /// bundled with Rust's fork of LLVM. | |
3dfed10e XL |
26 | /// |
27 | /// Consequently, Rust's bundled version of Clang also generates Coverage Maps compliant with | |
cdc7bbd5 XL |
28 | /// the same version. Clang's implementation of Coverage Map generation was referenced when |
29 | /// implementing this Rust version, and though the format documentation is very explicit and | |
30 | /// detailed, some undocumented details in Clang's implementation (that may or may not be important) | |
31 | /// were also replicated for Rust's Coverage Map. | |
3dfed10e | 32 | pub fn finalize<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>) { |
fc512014 | 33 | let tcx = cx.tcx; |
cdc7bbd5 | 34 | |
a2a8927a XL |
35 | // Ensure the installed version of LLVM supports at least Coverage Map |
36 | // Version 5 (encoded as a zero-based value: 4), which was introduced with | |
37 | // LLVM 12. | |
fc512014 | 38 | let version = coverageinfo::mapping_version(); |
a2a8927a XL |
39 | if version < 4 { |
40 | tcx.sess.fatal("rustc option `-Z instrument-coverage` requires LLVM 12 or higher."); | |
fc512014 XL |
41 | } |
42 | ||
43 | debug!("Generating coverage map for CodegenUnit: `{}`", cx.codegen_unit.name()); | |
44 | ||
cdc7bbd5 XL |
45 | // In order to show that unused functions have coverage counts of zero (0), LLVM requires the |
46 | // functions exist. Generate synthetic functions with a (required) single counter, and add the | |
47 | // MIR `Coverage` code regions to the `function_coverage_map`, before calling | |
48 | // `ctx.take_function_coverage_map()`. | |
49 | if !tcx.sess.instrument_coverage_except_unused_functions() { | |
50 | add_unused_functions(cx); | |
51 | } | |
52 | ||
53 | let function_coverage_map = match cx.coverage_context() { | |
29967ef6 XL |
54 | Some(ctx) => ctx.take_function_coverage_map(), |
55 | None => return, | |
56 | }; | |
cdc7bbd5 | 57 | |
3dfed10e XL |
58 | if function_coverage_map.is_empty() { |
59 | // This module has no functions with coverage instrumentation | |
60 | return; | |
61 | } | |
62 | ||
a2a8927a | 63 | let mut mapgen = CoverageMapGenerator::new(tcx, version); |
3dfed10e XL |
64 | |
65 | // Encode coverage mappings and generate function records | |
fc512014 XL |
66 | let mut function_data = Vec::new(); |
67 | for (instance, function_coverage) in function_coverage_map { | |
68 | debug!("Generate function coverage for {}, {:?}", cx.codegen_unit.name(), instance); | |
69 | let mangled_function_name = tcx.symbol_name(instance).to_string(); | |
cdc7bbd5 XL |
70 | let source_hash = function_coverage.source_hash(); |
71 | let is_used = function_coverage.is_used(); | |
fc512014 XL |
72 | let (expressions, counter_regions) = |
73 | function_coverage.get_expressions_and_counter_regions(); | |
74 | ||
75 | let coverage_mapping_buffer = llvm::build_byte_buffer(|coverage_mapping_buffer| { | |
76 | mapgen.write_coverage_mapping(expressions, counter_regions, coverage_mapping_buffer); | |
77 | }); | |
78 | debug_assert!( | |
c295e0f8 | 79 | !coverage_mapping_buffer.is_empty(), |
fc512014 XL |
80 | "Every `FunctionCoverage` should have at least one counter" |
81 | ); | |
82 | ||
cdc7bbd5 | 83 | function_data.push((mangled_function_name, source_hash, is_used, coverage_mapping_buffer)); |
fc512014 | 84 | } |
3dfed10e XL |
85 | |
86 | // Encode all filenames referenced by counters/expressions in this module | |
87 | let filenames_buffer = llvm::build_byte_buffer(|filenames_buffer| { | |
88 | coverageinfo::write_filenames_section_to_buffer(&mapgen.filenames, filenames_buffer); | |
89 | }); | |
90 | ||
fc512014 | 91 | let filenames_size = filenames_buffer.len(); |
a2a8927a | 92 | let filenames_val = cx.const_bytes(&filenames_buffer); |
fc512014 XL |
93 | let filenames_ref = coverageinfo::hash_bytes(filenames_buffer); |
94 | ||
3dfed10e | 95 | // Generate the LLVM IR representation of the coverage map and store it in a well-known global |
fc512014 XL |
96 | let cov_data_val = mapgen.generate_coverage_map(cx, version, filenames_size, filenames_val); |
97 | ||
cdc7bbd5 | 98 | for (mangled_function_name, source_hash, is_used, coverage_mapping_buffer) in function_data { |
fc512014 XL |
99 | save_function_record( |
100 | cx, | |
101 | mangled_function_name, | |
cdc7bbd5 | 102 | source_hash, |
fc512014 XL |
103 | filenames_ref, |
104 | coverage_mapping_buffer, | |
cdc7bbd5 | 105 | is_used, |
fc512014 XL |
106 | ); |
107 | } | |
108 | ||
109 | // Save the coverage data value to LLVM IR | |
110 | coverageinfo::save_cov_data_to_mod(cx, cov_data_val); | |
3dfed10e XL |
111 | } |
112 | ||
113 | struct CoverageMapGenerator { | |
114 | filenames: FxIndexSet<CString>, | |
115 | } | |
116 | ||
117 | impl CoverageMapGenerator { | |
a2a8927a XL |
118 | fn new(tcx: TyCtxt<'_>, version: u32) -> Self { |
119 | let mut filenames = FxIndexSet::default(); | |
120 | if version >= 5 { | |
121 | // LLVM Coverage Mapping Format version 6 (zero-based encoded as 5) | |
122 | // requires setting the first filename to the compilation directory. | |
123 | // Since rustc generates coverage maps with relative paths, the | |
124 | // compilation directory can be combined with the the relative paths | |
125 | // to get absolute paths, if needed. | |
126 | let working_dir = tcx | |
127 | .sess | |
128 | .opts | |
129 | .working_dir | |
130 | .remapped_path_if_available() | |
131 | .to_string_lossy() | |
132 | .to_string(); | |
133 | let c_filename = | |
134 | CString::new(working_dir).expect("null error converting filename to C string"); | |
135 | filenames.insert(c_filename); | |
136 | } | |
137 | Self { filenames } | |
3dfed10e XL |
138 | } |
139 | ||
140 | /// Using the `expressions` and `counter_regions` collected for the current function, generate | |
141 | /// the `mapping_regions` and `virtual_file_mapping`, and capture any new filenames. Then use | |
142 | /// LLVM APIs to encode the `virtual_file_mapping`, `expressions`, and `mapping_regions` into | |
fc512014 | 143 | /// the given `coverage_mapping` byte buffer, compliant with the LLVM Coverage Mapping format. |
a2a8927a | 144 | fn write_coverage_mapping<'a>( |
3dfed10e XL |
145 | &mut self, |
146 | expressions: Vec<CounterExpression>, | |
147 | counter_regions: impl Iterator<Item = (Counter, &'a CodeRegion)>, | |
fc512014 | 148 | coverage_mapping_buffer: &RustString, |
3dfed10e XL |
149 | ) { |
150 | let mut counter_regions = counter_regions.collect::<Vec<_>>(); | |
151 | if counter_regions.is_empty() { | |
152 | return; | |
153 | } | |
154 | ||
155 | let mut virtual_file_mapping = Vec::new(); | |
156 | let mut mapping_regions = Vec::new(); | |
157 | let mut current_file_name = None; | |
158 | let mut current_file_id = 0; | |
159 | ||
160 | // Convert the list of (Counter, CodeRegion) pairs to an array of `CounterMappingRegion`, sorted | |
161 | // by filename and position. Capture any new files to compute the `CounterMappingRegion`s | |
162 | // `file_id` (indexing files referenced by the current function), and construct the | |
163 | // function-specific `virtual_file_mapping` from `file_id` to its index in the module's | |
164 | // `filenames` array. | |
165 | counter_regions.sort_unstable_by_key(|(_counter, region)| *region); | |
166 | for (counter, region) in counter_regions { | |
167 | let CodeRegion { file_name, start_line, start_col, end_line, end_col } = *region; | |
168 | let same_file = current_file_name.as_ref().map_or(false, |p| *p == file_name); | |
169 | if !same_file { | |
170 | if current_file_name.is_some() { | |
171 | current_file_id += 1; | |
172 | } | |
173 | current_file_name = Some(file_name); | |
174 | let c_filename = CString::new(file_name.to_string()) | |
175 | .expect("null error converting filename to C string"); | |
176 | debug!(" file_id: {} = '{:?}'", current_file_id, c_filename); | |
177 | let (filenames_index, _) = self.filenames.insert_full(c_filename); | |
178 | virtual_file_mapping.push(filenames_index as u32); | |
179 | } | |
29967ef6 | 180 | debug!("Adding counter {:?} to map for {:?}", counter, region); |
3dfed10e XL |
181 | mapping_regions.push(CounterMappingRegion::code_region( |
182 | counter, | |
183 | current_file_id, | |
184 | start_line, | |
185 | start_col, | |
186 | end_line, | |
187 | end_col, | |
188 | )); | |
189 | } | |
190 | ||
191 | // Encode and append the current function's coverage mapping data | |
192 | coverageinfo::write_mapping_to_buffer( | |
193 | virtual_file_mapping, | |
194 | expressions, | |
195 | mapping_regions, | |
fc512014 | 196 | coverage_mapping_buffer, |
3dfed10e XL |
197 | ); |
198 | } | |
199 | ||
fc512014 XL |
200 | /// Construct coverage map header and the array of function records, and combine them into the |
201 | /// coverage map. Save the coverage map data into the LLVM IR as a static global using a | |
202 | /// specific, well-known section and name. | |
a2a8927a | 203 | fn generate_coverage_map<'ll>( |
3dfed10e | 204 | self, |
a2a8927a | 205 | cx: &CodegenCx<'ll, '_>, |
fc512014 XL |
206 | version: u32, |
207 | filenames_size: usize, | |
208 | filenames_val: &'ll llvm::Value, | |
209 | ) -> &'ll llvm::Value { | |
210 | debug!("cov map: filenames_size = {}, 0-based version = {}", filenames_size, version); | |
3dfed10e | 211 | |
fc512014 XL |
212 | // Create the coverage data header (Note, fields 0 and 2 are now always zero, |
213 | // as of `llvm::coverage::CovMapVersion::Version4`.) | |
214 | let zero_was_n_records_val = cx.const_u32(0); | |
3dfed10e | 215 | let filenames_size_val = cx.const_u32(filenames_size as u32); |
fc512014 XL |
216 | let zero_was_coverage_size_val = cx.const_u32(0); |
217 | let version_val = cx.const_u32(version); | |
3dfed10e | 218 | let cov_data_header_val = cx.const_struct( |
fc512014 | 219 | &[zero_was_n_records_val, filenames_size_val, zero_was_coverage_size_val, version_val], |
3dfed10e XL |
220 | /*packed=*/ false, |
221 | ); | |
222 | ||
3dfed10e | 223 | // Create the complete LLVM coverage data value to add to the LLVM IR |
fc512014 XL |
224 | cx.const_struct(&[cov_data_header_val, filenames_val], /*packed=*/ false) |
225 | } | |
226 | } | |
227 | ||
228 | /// Construct a function record and combine it with the function's coverage mapping data. | |
229 | /// Save the function record into the LLVM IR as a static global using a | |
230 | /// specific, well-known section and name. | |
231 | fn save_function_record( | |
a2a8927a | 232 | cx: &CodegenCx<'_, '_>, |
fc512014 | 233 | mangled_function_name: String, |
cdc7bbd5 | 234 | source_hash: u64, |
fc512014 XL |
235 | filenames_ref: u64, |
236 | coverage_mapping_buffer: Vec<u8>, | |
cdc7bbd5 | 237 | is_used: bool, |
fc512014 XL |
238 | ) { |
239 | // Concatenate the encoded coverage mappings | |
240 | let coverage_mapping_size = coverage_mapping_buffer.len(); | |
a2a8927a | 241 | let coverage_mapping_val = cx.const_bytes(&coverage_mapping_buffer); |
fc512014 XL |
242 | |
243 | let func_name_hash = coverageinfo::hash_str(&mangled_function_name); | |
244 | let func_name_hash_val = cx.const_u64(func_name_hash); | |
245 | let coverage_mapping_size_val = cx.const_u32(coverage_mapping_size as u32); | |
cdc7bbd5 | 246 | let source_hash_val = cx.const_u64(source_hash); |
fc512014 XL |
247 | let filenames_ref_val = cx.const_u64(filenames_ref); |
248 | let func_record_val = cx.const_struct( | |
249 | &[ | |
250 | func_name_hash_val, | |
251 | coverage_mapping_size_val, | |
cdc7bbd5 | 252 | source_hash_val, |
fc512014 XL |
253 | filenames_ref_val, |
254 | coverage_mapping_val, | |
255 | ], | |
256 | /*packed=*/ true, | |
257 | ); | |
3dfed10e | 258 | |
fc512014 XL |
259 | coverageinfo::save_func_record_to_mod(cx, func_name_hash, func_record_val, is_used); |
260 | } | |
261 | ||
262 | /// When finalizing the coverage map, `FunctionCoverage` only has the `CodeRegion`s and counters for | |
263 | /// the functions that went through codegen; such as public functions and "used" functions | |
264 | /// (functions referenced by other "used" or public items). Any other functions considered unused, | |
cdc7bbd5 XL |
265 | /// or "Unreachable", were still parsed and processed through the MIR stage, but were not |
266 | /// codegenned. (Note that `-Clink-dead-code` can force some unused code to be codegenned, but | |
267 | /// that flag is known to cause other errors, when combined with `-Z instrument-coverage`; and | |
268 | /// `-Clink-dead-code` will not generate code for unused generic functions.) | |
fc512014 | 269 | /// |
cdc7bbd5 XL |
270 | /// We can find the unused functions (including generic functions) by the set difference of all MIR |
271 | /// `DefId`s (`tcx` query `mir_keys`) minus the codegenned `DefId`s (`tcx` query | |
17df50a5 | 272 | /// `codegened_and_inlined_items`). |
fc512014 XL |
273 | /// |
274 | /// *HOWEVER* the codegenned `DefId`s are partitioned across multiple `CodegenUnit`s (CGUs), and | |
275 | /// this function is processing a `function_coverage_map` for the functions (`Instance`/`DefId`) | |
cdc7bbd5 XL |
276 | /// allocated to only one of those CGUs. We must NOT inject any unused functions's `CodeRegion`s |
277 | /// more than once, so we have to pick a CGUs `function_coverage_map` into which the unused | |
278 | /// function will be inserted. | |
279 | fn add_unused_functions<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>) { | |
280 | let tcx = cx.tcx; | |
281 | ||
fc512014 XL |
282 | // FIXME(#79622): Can this solution be simplified and/or improved? Are there other sources |
283 | // of compiler state data that might help (or better sources that could be exposed, but | |
284 | // aren't yet)? | |
285 | ||
cdc7bbd5 | 286 | let ignore_unused_generics = tcx.sess.instrument_coverage_except_unused_generics(); |
fc512014 | 287 | |
cdc7bbd5 | 288 | let all_def_ids: DefIdSet = tcx |
17df50a5 | 289 | .mir_keys(()) |
cdc7bbd5 XL |
290 | .iter() |
291 | .filter_map(|local_def_id| { | |
292 | let def_id = local_def_id.to_def_id(); | |
293 | if ignore_unused_generics && tcx.generics_of(def_id).requires_monomorphization(tcx) { | |
294 | return None; | |
295 | } | |
296 | Some(local_def_id.to_def_id()) | |
297 | }) | |
298 | .collect(); | |
fc512014 | 299 | |
17df50a5 | 300 | let codegenned_def_ids = tcx.codegened_and_inlined_items(()); |
fc512014 | 301 | |
cdc7bbd5 | 302 | let mut unused_def_ids_by_file: FxHashMap<Symbol, Vec<DefId>> = FxHashMap::default(); |
fc512014 | 303 | for &non_codegenned_def_id in all_def_ids.difference(codegenned_def_ids) { |
17df50a5 XL |
304 | // Make sure the non-codegenned (unused) function has at least one MIR |
305 | // `Coverage` statement with a code region, and return its file name. | |
fc512014 | 306 | if let Some(non_codegenned_file_name) = tcx.covered_file_name(non_codegenned_def_id) { |
cdc7bbd5 XL |
307 | let def_ids = |
308 | unused_def_ids_by_file.entry(*non_codegenned_file_name).or_insert_with(Vec::new); | |
fc512014 XL |
309 | def_ids.push(non_codegenned_def_id); |
310 | } | |
311 | } | |
312 | ||
cdc7bbd5 XL |
313 | if unused_def_ids_by_file.is_empty() { |
314 | // There are no unused functions with file names to add (in any CGU) | |
fc512014 XL |
315 | return; |
316 | } | |
317 | ||
cdc7bbd5 XL |
318 | // Each `CodegenUnit` (CGU) has its own function_coverage_map, and generates a specific binary |
319 | // with its own coverage map. | |
320 | // | |
321 | // Each covered function `Instance` can be included in only one coverage map, produced from a | |
322 | // specific function_coverage_map, from a specific CGU. | |
323 | // | |
324 | // Since unused functions did not generate code, they are not associated with any CGU yet. | |
fc512014 | 325 | // |
cdc7bbd5 XL |
326 | // To avoid injecting the unused functions in multiple coverage maps (for multiple CGUs) |
327 | // determine which function_coverage_map has the responsibility for publishing unreachable | |
328 | // coverage, based on file name: For each unused function, find the CGU that generates the | |
329 | // first function (based on sorted `DefId`) from the same file. | |
330 | // | |
331 | // Add a new `FunctionCoverage` to the `function_coverage_map`, with unreachable code regions | |
332 | // for each region in it's MIR. | |
333 | ||
334 | // Convert the `HashSet` of `codegenned_def_ids` to a sortable vector, and sort them. | |
c295e0f8 | 335 | let mut sorted_codegenned_def_ids: Vec<DefId> = codegenned_def_ids.iter().copied().collect(); |
fc512014 XL |
336 | sorted_codegenned_def_ids.sort_unstable(); |
337 | ||
338 | let mut first_covered_def_id_by_file: FxHashMap<Symbol, DefId> = FxHashMap::default(); | |
339 | for &def_id in sorted_codegenned_def_ids.iter() { | |
cdc7bbd5 XL |
340 | if let Some(covered_file_name) = tcx.covered_file_name(def_id) { |
341 | // Only add files known to have unused functions | |
342 | if unused_def_ids_by_file.contains_key(covered_file_name) { | |
343 | first_covered_def_id_by_file.entry(*covered_file_name).or_insert(def_id); | |
fc512014 XL |
344 | } |
345 | } | |
346 | } | |
347 | ||
348 | // Get the set of def_ids with coverage regions, known by *this* CoverageContext. | |
cdc7bbd5 XL |
349 | let cgu_covered_def_ids: DefIdSet = match cx.coverage_context() { |
350 | Some(ctx) => ctx | |
351 | .function_coverage_map | |
352 | .borrow() | |
353 | .keys() | |
354 | .map(|&instance| instance.def.def_id()) | |
355 | .collect(), | |
356 | None => return, | |
357 | }; | |
fc512014 | 358 | |
cdc7bbd5 | 359 | let cgu_covered_files: FxHashSet<Symbol> = first_covered_def_id_by_file |
fc512014 XL |
360 | .iter() |
361 | .filter_map( | |
362 | |(&file_name, def_id)| { | |
363 | if cgu_covered_def_ids.contains(def_id) { Some(file_name) } else { None } | |
364 | }, | |
365 | ) | |
366 | .collect(); | |
367 | ||
cdc7bbd5 XL |
368 | // For each file for which this CGU is responsible for adding unused function coverage, |
369 | // get the `def_id`s for each unused function (if any), define a synthetic function with a | |
370 | // single LLVM coverage counter, and add the function's coverage `CodeRegion`s. to the | |
371 | // function_coverage_map. | |
372 | for covered_file_name in cgu_covered_files { | |
373 | for def_id in unused_def_ids_by_file.remove(&covered_file_name).into_iter().flatten() { | |
374 | cx.define_unused_fn(def_id); | |
fc512014 | 375 | } |
3dfed10e XL |
376 | } |
377 | } |