@@ -6,9 +6,8 @@ use crate::llvm;
6
6
7
7
use itertools:: Itertools as _;
8
8
use rustc_codegen_ssa:: traits:: { BaseTypeMethods , ConstMethods } ;
9
- use rustc_data_structures:: fx:: { FxIndexMap , FxIndexSet } ;
10
- use rustc_hir:: def:: DefKind ;
11
- use rustc_hir:: def_id:: DefId ;
9
+ use rustc_data_structures:: fx:: { FxHashSet , FxIndexMap , FxIndexSet } ;
10
+ use rustc_hir:: def_id:: { DefId , LocalDefId } ;
12
11
use rustc_index:: IndexVec ;
13
12
use rustc_middle:: bug;
14
13
use rustc_middle:: mir;
@@ -335,16 +334,9 @@ fn save_function_record(
335
334
) ;
336
335
}
337
336
338
- /// When finalizing the coverage map, `FunctionCoverage` only has the `CodeRegion`s and counters for
339
- /// the functions that went through codegen; such as public functions and "used" functions
340
- /// (functions referenced by other "used" or public items). Any other functions considered unused,
341
- /// or "Unreachable", were still parsed and processed through the MIR stage, but were not
342
- /// codegenned. (Note that `-Clink-dead-code` can force some unused code to be codegenned, but
343
- /// that flag is known to cause other errors, when combined with `-C instrument-coverage`; and
344
- /// `-Clink-dead-code` will not generate code for unused generic functions.)
345
- ///
346
- /// We can find the unused functions (including generic functions) by the set difference of all MIR
347
- /// `DefId`s (`tcx` query `mir_keys`) minus the codegenned `DefId`s (`codegenned_and_inlined_items`).
337
+ /// Each CGU will normally only emit coverage metadata for the functions that it actually generates.
338
+ /// But since we don't want unused functions to disappear from coverage reports, we also scan for
339
+ /// functions that were instrumented but are not participating in codegen.
348
340
///
349
341
/// These unused functions don't need to be codegenned, but we do need to add them to the function
350
342
/// coverage map (in a single designated CGU) so that we still emit coverage mappings for them.
@@ -354,75 +346,111 @@ fn add_unused_functions(cx: &CodegenCx<'_, '_>) {
354
346
assert ! ( cx. codegen_unit. is_code_coverage_dead_code_cgu( ) ) ;
355
347
356
348
let tcx = cx. tcx ;
349
+ let usage = prepare_usage_sets ( tcx) ;
350
+
351
+ let is_unused_fn = |def_id : & & LocalDefId | -> bool {
352
+ let def_id = def_id. to_def_id ( ) ;
353
+
354
+ // To be eligible for "unused function" mappings, a definition must:
355
+ // - Be function-like
356
+ // - Not participate directly in codegen (or have lost all its coverage statements)
357
+ // - Not have any coverage statements inlined into codegenned functions
358
+ tcx. def_kind ( def_id) . is_fn_like ( )
359
+ && ( !usage. all_mono_items . contains ( & def_id)
360
+ || usage. missing_own_coverage . contains ( & def_id) )
361
+ && !usage. used_via_inlining . contains ( & def_id)
362
+ } ;
357
363
358
- let eligible_def_ids = tcx. mir_keys ( ( ) ) . iter ( ) . filter_map ( |local_def_id| {
359
- let def_id = local_def_id. to_def_id ( ) ;
360
- let kind = tcx. def_kind ( def_id) ;
361
- // `mir_keys` will give us `DefId`s for all kinds of things, not
362
- // just "functions", like consts, statics, etc. Filter those out.
363
- if !matches ! ( kind, DefKind :: Fn | DefKind :: AssocFn | DefKind :: Closure ) {
364
- return None ;
365
- }
364
+ // Scan for unused functions that were instrumented for coverage.
365
+ for & def_id in tcx. mir_keys ( ( ) ) . iter ( ) . filter ( is_unused_fn) {
366
+ // Get the coverage info from MIR, skipping functions that were never instrumented.
367
+ let body = tcx. optimized_mir ( def_id) ;
368
+ let Some ( function_coverage_info) = body. function_coverage_info . as_deref ( ) else { continue } ;
366
369
367
370
// FIXME(79651): Consider trying to filter out dummy instantiations of
368
371
// unused generic functions from library crates, because they can produce
369
372
// "unused instantiation" in coverage reports even when they are actually
370
373
// used by some downstream crate in the same binary.
371
374
372
- Some ( local_def_id. to_def_id ( ) )
373
- } ) ;
374
-
375
- let codegenned_def_ids = codegenned_and_inlined_items ( tcx) ;
376
-
377
- // For each `DefId` that should have coverage instrumentation but wasn't
378
- // codegenned, add it to the function coverage map as an unused function.
379
- for def_id in eligible_def_ids. filter ( |id| !codegenned_def_ids. contains ( id) ) {
380
- // Skip any function that didn't have coverage data added to it by the
381
- // coverage instrumentor.
382
- let body = tcx. instance_mir ( ty:: InstanceDef :: Item ( def_id) ) ;
383
- let Some ( function_coverage_info) = body. function_coverage_info . as_deref ( ) else {
384
- continue ;
385
- } ;
386
-
387
375
debug ! ( "generating unused fn: {def_id:?}" ) ;
388
- let instance = declare_unused_fn ( tcx, def_id) ;
389
- add_unused_function_coverage ( cx, instance, function_coverage_info) ;
376
+ add_unused_function_coverage ( cx, def_id, function_coverage_info) ;
390
377
}
391
378
}
392
379
393
- /// All items participating in code generation together with (instrumented)
394
- /// items inlined into them.
395
- fn codegenned_and_inlined_items ( tcx : TyCtxt < ' _ > ) -> DefIdSet {
396
- let ( items, cgus) = tcx. collect_and_partition_mono_items ( ( ) ) ;
397
- let mut visited = DefIdSet :: default ( ) ;
398
- let mut result = items. clone ( ) ;
399
-
400
- for cgu in cgus {
401
- for item in cgu. items ( ) . keys ( ) {
402
- if let mir:: mono:: MonoItem :: Fn ( ref instance) = item {
403
- let did = instance. def_id ( ) ;
404
- if !visited. insert ( did) {
405
- continue ;
406
- }
407
- let body = tcx. instance_mir ( instance. def ) ;
408
- for block in body. basic_blocks . iter ( ) {
409
- for statement in & block. statements {
410
- let mir:: StatementKind :: Coverage ( _) = statement. kind else { continue } ;
411
- let scope = statement. source_info . scope ;
412
- if let Some ( inlined) = scope. inlined_instance ( & body. source_scopes ) {
413
- result. insert ( inlined. def_id ( ) ) ;
414
- }
415
- }
416
- }
380
+ struct UsageSets < ' tcx > {
381
+ all_mono_items : & ' tcx DefIdSet ,
382
+ used_via_inlining : FxHashSet < DefId > ,
383
+ missing_own_coverage : FxHashSet < DefId > ,
384
+ }
385
+
386
+ /// Prepare sets of definitions that are relevant to deciding whether something
387
+ /// is an "unused function" for coverage purposes.
388
+ fn prepare_usage_sets < ' tcx > ( tcx : TyCtxt < ' tcx > ) -> UsageSets < ' tcx > {
389
+ let ( all_mono_items, cgus) = tcx. collect_and_partition_mono_items ( ( ) ) ;
390
+
391
+ // Obtain a MIR body for each function participating in codegen, via an
392
+ // arbitrary instance.
393
+ let def_and_mir_for_all_mono_fns = cgus
394
+ . iter ( )
395
+ . flat_map ( |cgu| cgu. items ( ) . keys ( ) )
396
+ . filter_map ( |item| match item {
397
+ mir:: mono:: MonoItem :: Fn ( instance) => Some ( instance) ,
398
+ mir:: mono:: MonoItem :: Static ( _) | mir:: mono:: MonoItem :: GlobalAsm ( _) => None ,
399
+ } )
400
+ . filter ( {
401
+ // We only need one arbitrary instance per definition.
402
+ let mut seen = FxHashSet :: default ( ) ;
403
+ move |instance| seen. insert ( instance. def_id ( ) )
404
+ } )
405
+ . map ( |instance| {
406
+ // We don't care about the instance, just its underlying MIR.
407
+ let body = tcx. instance_mir ( instance. def ) ;
408
+ ( instance. def_id ( ) , body)
409
+ } ) ;
410
+
411
+ // Functions whose coverage statments were found inlined into other functions.
412
+ let mut used_via_inlining = FxHashSet :: default ( ) ;
413
+ // Functions that were instrumented, but had all of their coverage statements
414
+ // removed by later MIR transforms (e.g. UnreachablePropagation).
415
+ let mut missing_own_coverage = FxHashSet :: default ( ) ;
416
+
417
+ for ( def_id, body) in def_and_mir_for_all_mono_fns {
418
+ let mut saw_own_coverage = false ;
419
+
420
+ // Inspect every coverage statement in the function's MIR.
421
+ for stmt in body
422
+ . basic_blocks
423
+ . iter ( )
424
+ . flat_map ( |block| & block. statements )
425
+ . filter ( |stmt| matches ! ( stmt. kind, mir:: StatementKind :: Coverage ( _) ) )
426
+ {
427
+ if let Some ( inlined) = stmt. source_info . scope . inlined_instance ( & body. source_scopes ) {
428
+ // This coverage statement was inlined from another function.
429
+ used_via_inlining. insert ( inlined. def_id ( ) ) ;
430
+ } else {
431
+ // Non-inlined coverage statements belong to the enclosing function.
432
+ saw_own_coverage = true ;
417
433
}
418
434
}
435
+
436
+ if !saw_own_coverage && body. function_coverage_info . is_some ( ) {
437
+ missing_own_coverage. insert ( def_id) ;
438
+ }
419
439
}
420
440
421
- result
441
+ UsageSets { all_mono_items , used_via_inlining , missing_own_coverage }
422
442
}
423
443
424
- fn declare_unused_fn < ' tcx > ( tcx : TyCtxt < ' tcx > , def_id : DefId ) -> ty:: Instance < ' tcx > {
425
- ty:: Instance :: new (
444
+ fn add_unused_function_coverage < ' tcx > (
445
+ cx : & CodegenCx < ' _ , ' tcx > ,
446
+ def_id : LocalDefId ,
447
+ function_coverage_info : & ' tcx mir:: coverage:: FunctionCoverageInfo ,
448
+ ) {
449
+ let tcx = cx. tcx ;
450
+ let def_id = def_id. to_def_id ( ) ;
451
+
452
+ // Make a dummy instance that fills in all generics with placeholders.
453
+ let instance = ty:: Instance :: new (
426
454
def_id,
427
455
ty:: GenericArgs :: for_item ( tcx, def_id, |param, _| {
428
456
if let ty:: GenericParamDefKind :: Lifetime = param. kind {
@@ -431,14 +459,8 @@ fn declare_unused_fn<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> ty::Instance<'tc
431
459
tcx. mk_param_from_def ( param)
432
460
}
433
461
} ) ,
434
- )
435
- }
462
+ ) ;
436
463
437
- fn add_unused_function_coverage < ' tcx > (
438
- cx : & CodegenCx < ' _ , ' tcx > ,
439
- instance : ty:: Instance < ' tcx > ,
440
- function_coverage_info : & ' tcx mir:: coverage:: FunctionCoverageInfo ,
441
- ) {
442
464
// An unused function's mappings will automatically be rewritten to map to
443
465
// zero, because none of its counters/expressions are marked as seen.
444
466
let function_coverage = FunctionCoverageCollector :: unused ( instance, function_coverage_info) ;
0 commit comments