@@ -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,109 @@ 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 ( ) . copied ( ) . filter ( |& def_id| is_unused_fn ( def_id) ) {
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 mut def_ids_seen = FxHashSet :: default ( ) ;
394
+ let def_and_mir_for_all_mono_fns = cgus
395
+ . iter ( )
396
+ . flat_map ( |cgu| cgu. items ( ) . keys ( ) )
397
+ . filter_map ( |item| match item {
398
+ mir:: mono:: MonoItem :: Fn ( instance) => Some ( instance) ,
399
+ mir:: mono:: MonoItem :: Static ( _) | mir:: mono:: MonoItem :: GlobalAsm ( _) => None ,
400
+ } )
401
+ // We only need one arbitrary instance per definition.
402
+ . filter ( move |instance| def_ids_seen. insert ( instance. def_id ( ) ) )
403
+ . map ( |instance| {
404
+ // We don't care about the instance, just its underlying MIR.
405
+ let body = tcx. instance_mir ( instance. def ) ;
406
+ ( instance. def_id ( ) , body)
407
+ } ) ;
408
+
409
+ // Functions whose coverage statments were found inlined into other functions.
410
+ let mut used_via_inlining = FxHashSet :: default ( ) ;
411
+ // Functions that were instrumented, but had all of their coverage statements
412
+ // removed by later MIR transforms (e.g. UnreachablePropagation).
413
+ let mut missing_own_coverage = FxHashSet :: default ( ) ;
414
+
415
+ for ( def_id, body) in def_and_mir_for_all_mono_fns {
416
+ let mut saw_own_coverage = false ;
417
+
418
+ // Inspect every coverage statement in the function's MIR.
419
+ for stmt in body
420
+ . basic_blocks
421
+ . iter ( )
422
+ . flat_map ( |block| & block. statements )
423
+ . filter ( |stmt| matches ! ( stmt. kind, mir:: StatementKind :: Coverage ( _) ) )
424
+ {
425
+ if let Some ( inlined) = stmt. source_info . scope . inlined_instance ( & body. source_scopes ) {
426
+ // This coverage statement was inlined from another function.
427
+ used_via_inlining. insert ( inlined. def_id ( ) ) ;
428
+ } else {
429
+ // Non-inlined coverage statements belong to the enclosing function.
430
+ saw_own_coverage = true ;
417
431
}
418
432
}
433
+
434
+ if !saw_own_coverage && body. function_coverage_info . is_some ( ) {
435
+ missing_own_coverage. insert ( def_id) ;
436
+ }
419
437
}
420
438
421
- result
439
+ UsageSets { all_mono_items , used_via_inlining , missing_own_coverage }
422
440
}
423
441
424
- fn declare_unused_fn < ' tcx > ( tcx : TyCtxt < ' tcx > , def_id : DefId ) -> ty:: Instance < ' tcx > {
425
- ty:: Instance :: new (
442
+ fn add_unused_function_coverage < ' tcx > (
443
+ cx : & CodegenCx < ' _ , ' tcx > ,
444
+ def_id : LocalDefId ,
445
+ function_coverage_info : & ' tcx mir:: coverage:: FunctionCoverageInfo ,
446
+ ) {
447
+ let tcx = cx. tcx ;
448
+ let def_id = def_id. to_def_id ( ) ;
449
+
450
+ // Make a dummy instance that fills in all generics with placeholders.
451
+ let instance = ty:: Instance :: new (
426
452
def_id,
427
453
ty:: GenericArgs :: for_item ( tcx, def_id, |param, _| {
428
454
if let ty:: GenericParamDefKind :: Lifetime = param. kind {
@@ -431,14 +457,8 @@ fn declare_unused_fn<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> ty::Instance<'tc
431
457
tcx. mk_param_from_def ( param)
432
458
}
433
459
} ) ,
434
- )
435
- }
460
+ ) ;
436
461
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
462
// An unused function's mappings will automatically be rewritten to map to
443
463
// zero, because none of its counters/expressions are marked as seen.
444
464
let function_coverage = FunctionCoverageCollector :: unused ( instance, function_coverage_info) ;
0 commit comments