@@ -3,11 +3,14 @@ use crate::coverageinfo;
3
3
use crate :: llvm;
4
4
5
5
use llvm:: coverageinfo:: CounterMappingRegion ;
6
- use rustc_codegen_ssa:: coverageinfo:: map:: { Counter , CounterExpression } ;
6
+ use rustc_codegen_ssa:: coverageinfo:: map:: { Counter , CounterExpression , FunctionCoverage } ;
7
7
use rustc_codegen_ssa:: traits:: ConstMethods ;
8
- use rustc_data_structures:: fx:: FxIndexSet ;
8
+ use rustc_data_structures:: fx:: { FxHashMap , FxHashSet , FxIndexSet } ;
9
+ use rustc_hir:: def_id:: { DefId , DefIdSet , LOCAL_CRATE } ;
9
10
use rustc_llvm:: RustString ;
10
11
use rustc_middle:: mir:: coverage:: CodeRegion ;
12
+ use rustc_middle:: ty:: { Instance , TyCtxt } ;
13
+ use rustc_span:: Symbol ;
11
14
12
15
use std:: ffi:: CString ;
13
16
@@ -26,14 +29,17 @@ use tracing::debug;
26
29
/// undocumented details in Clang's implementation (that may or may not be important) were also
27
30
/// replicated for Rust's Coverage Map.
28
31
pub fn finalize < ' ll , ' tcx > ( cx : & CodegenCx < ' ll , ' tcx > ) {
32
+ let tcx = cx. tcx ;
29
33
// Ensure LLVM supports Coverage Map Version 4 (encoded as a zero-based value: 3).
30
34
// If not, the LLVM Version must be less than 11.
31
35
let version = coverageinfo:: mapping_version ( ) ;
32
36
if version != 3 {
33
- cx . tcx . sess . fatal ( "rustc option `-Z instrument-coverage` requires LLVM 11 or higher." ) ;
37
+ tcx. sess . fatal ( "rustc option `-Z instrument-coverage` requires LLVM 11 or higher." ) ;
34
38
}
35
39
36
- let function_coverage_map = match cx. coverage_context ( ) {
40
+ debug ! ( "Generating coverage map for CodegenUnit: `{}`" , cx. codegen_unit. name( ) ) ;
41
+
42
+ let mut function_coverage_map = match cx. coverage_context ( ) {
37
43
Some ( ctx) => ctx. take_function_coverage_map ( ) ,
38
44
None => return ,
39
45
} ;
@@ -42,14 +48,15 @@ pub fn finalize<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>) {
42
48
return ;
43
49
}
44
50
51
+ add_unreachable_coverage ( tcx, & mut function_coverage_map) ;
52
+
45
53
let mut mapgen = CoverageMapGenerator :: new ( ) ;
46
54
47
55
// Encode coverage mappings and generate function records
48
56
let mut function_data = Vec :: new ( ) ;
49
57
for ( instance, function_coverage) in function_coverage_map {
50
- debug ! ( "Generate coverage map for: {:?}" , instance) ;
51
-
52
- let mangled_function_name = cx. tcx . symbol_name ( instance) . to_string ( ) ;
58
+ debug ! ( "Generate function coverage for {}, {:?}" , cx. codegen_unit. name( ) , instance) ;
59
+ let mangled_function_name = tcx. symbol_name ( instance) . to_string ( ) ;
53
60
let function_source_hash = function_coverage. source_hash ( ) ;
54
61
let ( expressions, counter_regions) =
55
62
function_coverage. get_expressions_and_counter_regions ( ) ;
@@ -228,3 +235,156 @@ fn save_function_record(
228
235
let is_used = true ;
229
236
coverageinfo:: save_func_record_to_mod ( cx, func_name_hash, func_record_val, is_used) ;
230
237
}
238
+
239
+ /// When finalizing the coverage map, `FunctionCoverage` only has the `CodeRegion`s and counters for
240
+ /// the functions that went through codegen; such as public functions and "used" functions
241
+ /// (functions referenced by other "used" or public items). Any other functions considered unused,
242
+ /// or "Unreachable" were still parsed and processed through the MIR stage.
243
+ ///
244
+ /// We can find the unreachable functions by the set different of all MIR `DefId`s (`tcx` query
245
+ /// `mir_keys`) minus the codegenned `DefId`s (`tcx` query `collect_and_partition_mono_items`).
246
+ ///
247
+ /// *HOWEVER* the codegenned `DefId`s are partitioned across multiple `CodegenUnit`s (CGUs), and
248
+ /// this function is processing a `function_coverage_map` for the functions (`Instance`/`DefId`)
249
+ /// allocated to only one of those CGUs. We must NOT inject any "Unreachable" functions's
250
+ /// `CodeRegion`s more than once, so we have to pick which CGU's `function_coverage_map` to add
251
+ /// each "Unreachable" function to.
252
+ ///
253
+ /// Some constraints:
254
+ ///
255
+ /// 1. The file name of an "Unreachable" function must match the file name of the existing
256
+ /// codegenned (covered) function to which the unreachable code regions will be added.
257
+ /// 2. The function to which the unreachable code regions will be added must not be a genaric
258
+ /// function (must not have type parameters) because the coverage tools will get confused
259
+ /// if the codegenned function has more than one instantiation and additional `CodeRegion`s
260
+ /// attached to only one of those instantiations.
261
+ fn add_unreachable_coverage < ' tcx > (
262
+ tcx : TyCtxt < ' tcx > ,
263
+ function_coverage_map : & mut FxHashMap < Instance < ' tcx > , FunctionCoverage < ' tcx > > ,
264
+ ) {
265
+ // FIXME(#79622): Can this solution be simplified and/or improved? Are there other sources
266
+ // of compiler state data that might help (or better sources that could be exposed, but
267
+ // aren't yet)?
268
+
269
+ // Note: If the crate *only* defines generic functions, there are no codegenerated non-generic
270
+ // functions to add any unreachable code to. In this case, the unreachable code regions will
271
+ // have no coverage, instead of having coverage with zero executions.
272
+ //
273
+ // This is probably still an improvement over Clang, which does not generate any coverage
274
+ // for uninstantiated template functions.
275
+
276
+ let has_non_generic_def_ids =
277
+ function_coverage_map. keys ( ) . any ( |instance| instance. def . attrs ( tcx) . len ( ) == 0 ) ;
278
+
279
+ if !has_non_generic_def_ids {
280
+ // There are no non-generic functions to add unreachable `CodeRegion`s to
281
+ return ;
282
+ }
283
+
284
+ let all_def_ids: DefIdSet =
285
+ tcx. mir_keys ( LOCAL_CRATE ) . iter ( ) . map ( |local_def_id| local_def_id. to_def_id ( ) ) . collect ( ) ;
286
+
287
+ let ( codegenned_def_ids, _) = tcx. collect_and_partition_mono_items ( LOCAL_CRATE ) ;
288
+
289
+ let mut unreachable_def_ids_by_file: FxHashMap < Symbol , Vec < DefId > > = FxHashMap :: default ( ) ;
290
+ for & non_codegenned_def_id in all_def_ids. difference ( codegenned_def_ids) {
291
+ // Make sure the non-codegenned (unreachable) function has a file_name
292
+ if let Some ( non_codegenned_file_name) = tcx. covered_file_name ( non_codegenned_def_id) {
293
+ let def_ids = unreachable_def_ids_by_file
294
+ . entry ( * non_codegenned_file_name)
295
+ . or_insert_with ( || Vec :: new ( ) ) ;
296
+ def_ids. push ( non_codegenned_def_id) ;
297
+ }
298
+ }
299
+
300
+ if unreachable_def_ids_by_file. is_empty ( ) {
301
+ // There are no unreachable functions with file names to add (in any CGU)
302
+ return ;
303
+ }
304
+
305
+ // Since there may be multiple `CodegenUnit`s, some codegenned_def_ids may be codegenned in a
306
+ // different CGU, and will be added to the function_coverage_map for each CGU. Determine which
307
+ // function_coverage_map has the responsibility for publishing unreachable coverage
308
+ // based on file name:
309
+ //
310
+ // For each covered file name, sort ONLY the non-generic codegenned_def_ids, and if
311
+ // covered_def_ids.contains(the first def_id) for a given file_name, add the unreachable code
312
+ // region in this function_coverage_map. Otherwise, ignore it and assume another CGU's
313
+ // function_coverage_map will be adding it (because it will be first for one, and only one,
314
+ // of them).
315
+ let mut sorted_codegenned_def_ids: Vec < DefId > =
316
+ codegenned_def_ids. iter ( ) . map ( |def_id| * def_id) . collect ( ) ;
317
+ sorted_codegenned_def_ids. sort_unstable ( ) ;
318
+
319
+ let mut first_covered_def_id_by_file: FxHashMap < Symbol , DefId > = FxHashMap :: default ( ) ;
320
+ for & def_id in sorted_codegenned_def_ids. iter ( ) {
321
+ // Only consider non-generic functions, to potentially add unreachable code regions
322
+ if tcx. generics_of ( def_id) . count ( ) == 0 {
323
+ if let Some ( covered_file_name) = tcx. covered_file_name ( def_id) {
324
+ // Only add files known to have unreachable functions
325
+ if unreachable_def_ids_by_file. contains_key ( covered_file_name) {
326
+ first_covered_def_id_by_file. entry ( * covered_file_name) . or_insert ( def_id) ;
327
+ }
328
+ }
329
+ }
330
+ }
331
+
332
+ // Get the set of def_ids with coverage regions, known by *this* CoverageContext.
333
+ let cgu_covered_def_ids: DefIdSet =
334
+ function_coverage_map. keys ( ) . map ( |instance| instance. def . def_id ( ) ) . collect ( ) ;
335
+
336
+ let mut cgu_covered_files: FxHashSet < Symbol > = first_covered_def_id_by_file
337
+ . iter ( )
338
+ . filter_map (
339
+ |( & file_name, def_id) | {
340
+ if cgu_covered_def_ids. contains ( def_id) { Some ( file_name) } else { None }
341
+ } ,
342
+ )
343
+ . collect ( ) ;
344
+
345
+ // Find the first covered, non-generic function (instance) for each cgu_covered_file. Take the
346
+ // unreachable code regions for that file, and add them to the function.
347
+ //
348
+ // There are three `for` loops here, but (a) the lists have already been reduced to the minimum
349
+ // required values, the lists are further reduced (by `remove()` calls) when elements are no
350
+ // longer needed, and there are several opportunities to branch out of loops early.
351
+ for ( instance, function_coverage) in function_coverage_map. iter_mut ( ) {
352
+ if instance. def . attrs ( tcx) . len ( ) > 0 {
353
+ continue ;
354
+ }
355
+ // The covered function is not generic...
356
+ let covered_def_id = instance. def . def_id ( ) ;
357
+ if let Some ( covered_file_name) = tcx. covered_file_name ( covered_def_id) {
358
+ if !cgu_covered_files. remove ( & covered_file_name) {
359
+ continue ;
360
+ }
361
+ // The covered function's file is one of the files with unreachable code regions, so
362
+ // all of the unreachable code regions for this file will be added to this function.
363
+ for def_id in
364
+ unreachable_def_ids_by_file. remove ( & covered_file_name) . into_iter ( ) . flatten ( )
365
+ {
366
+ // Note, this loop adds an unreachable code regions for each MIR-derived region.
367
+ // Alternatively, we could add a single code region for the maximum span of all
368
+ // code regions here.
369
+ //
370
+ // Observed downsides of this approach are:
371
+ //
372
+ // 1. The coverage results will appear inconsistent compared with the same (or
373
+ // similar) code in a function that is reached.
374
+ // 2. If the function is unreachable from one crate but reachable when compiling
375
+ // another referencing crate (such as a cross-crate reference to a
376
+ // generic function or inlined function), actual coverage regions overlaid
377
+ // on a single larger code span of `Zero` coverage can appear confusing or
378
+ // wrong. Chaning the unreachable coverage from a `code_region` to a
379
+ // `gap_region` can help, but still can look odd with `0` line counts for
380
+ // lines between executed (> 0) lines (such as for blank lines or comments).
381
+ for & region in tcx. covered_code_regions ( def_id) {
382
+ function_coverage. add_unreachable_region ( region. clone ( ) ) ;
383
+ }
384
+ }
385
+ if cgu_covered_files. is_empty ( ) {
386
+ break ;
387
+ }
388
+ }
389
+ }
390
+ }
0 commit comments