@@ -6,9 +6,8 @@ use crate::llvm;
66
77use itertools:: Itertools as _;
88use 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 } ;
1211use rustc_index:: IndexVec ;
1312use rustc_middle:: bug;
1413use rustc_middle:: mir;
@@ -335,16 +334,9 @@ fn save_function_record(
335334 ) ;
336335}
337336
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.
348340///
349341/// These unused functions don't need to be codegenned, but we do need to add them to the function
350342/// 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<'_, '_>) {
354346 assert ! ( cx. codegen_unit. is_code_coverage_dead_code_cgu( ) ) ;
355347
356348 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+ } ;
357363
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 } ;
366369
367370 // FIXME(79651): Consider trying to filter out dummy instantiations of
368371 // unused generic functions from library crates, because they can produce
369372 // "unused instantiation" in coverage reports even when they are actually
370373 // used by some downstream crate in the same binary.
371374
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-
387375 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) ;
390377 }
391378}
392379
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 ;
417431 }
418432 }
433+
434+ if !saw_own_coverage && body. function_coverage_info . is_some ( ) {
435+ missing_own_coverage. insert ( def_id) ;
436+ }
419437 }
420438
421- result
439+ UsageSets { all_mono_items , used_via_inlining , missing_own_coverage }
422440}
423441
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 (
426452 def_id,
427453 ty:: GenericArgs :: for_item ( tcx, def_id, |param, _| {
428454 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
431457 tcx. mk_param_from_def ( param)
432458 }
433459 } ) ,
434- )
435- }
460+ ) ;
436461
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- ) {
442462 // An unused function's mappings will automatically be rewritten to map to
443463 // zero, because none of its counters/expressions are marked as seen.
444464 let function_coverage = FunctionCoverageCollector :: unused ( instance, function_coverage_info) ;
0 commit comments