1
1
use std:: iter;
2
2
3
- use itertools:: Itertools as _ ;
3
+ use itertools:: Itertools ;
4
4
use rustc_abi:: Align ;
5
5
use rustc_codegen_ssa:: traits:: {
6
6
BaseTypeCodegenMethods , ConstCodegenMethods , StaticCodegenMethods ,
7
7
} ;
8
8
use rustc_data_structures:: fx:: { FxHashSet , FxIndexMap , FxIndexSet } ;
9
9
use rustc_hir:: def_id:: { DefId , LocalDefId } ;
10
10
use rustc_index:: IndexVec ;
11
+ use rustc_middle:: mir;
11
12
use rustc_middle:: ty:: { self , TyCtxt } ;
12
- use rustc_middle:: { bug, mir} ;
13
13
use rustc_session:: RemapFileNameExt ;
14
14
use rustc_session:: config:: RemapPathScopeComponents ;
15
15
use rustc_span:: def_id:: DefIdSet ;
@@ -18,7 +18,6 @@ use tracing::debug;
18
18
19
19
use crate :: common:: CodegenCx ;
20
20
use crate :: coverageinfo:: llvm_cov;
21
- use crate :: coverageinfo:: map_data:: FunctionCoverage ;
22
21
use crate :: coverageinfo:: mapgen:: covfun:: prepare_covfun_record;
23
22
use crate :: llvm;
24
23
@@ -49,46 +48,40 @@ pub(crate) fn finalize(cx: &CodegenCx<'_, '_>) {
49
48
50
49
debug ! ( "Generating coverage map for CodegenUnit: `{}`" , cx. codegen_unit. name( ) ) ;
51
50
52
- // In order to show that unused functions have coverage counts of zero (0), LLVM requires the
53
- // functions exist. Generate synthetic functions with a (required) single counter, and add the
54
- // MIR `Coverage` code regions to the `function_coverage_map`, before calling
55
- // `ctx.take_function_coverage_map()`.
56
- if cx. codegen_unit . is_code_coverage_dead_code_cgu ( ) {
57
- add_unused_functions ( cx) ;
58
- }
59
-
60
51
// FIXME(#132395): Can this be none even when coverage is enabled?
61
- let function_coverage_map = match cx. coverage_cx {
62
- Some ( ref cx) => cx. take_function_coverage_map ( ) ,
52
+ let instances_used = match cx. coverage_cx {
53
+ Some ( ref cx) => cx. instances_used . borrow ( ) ,
63
54
None => return ,
64
55
} ;
65
- if function_coverage_map. is_empty ( ) {
66
- // This CGU has no functions with coverage instrumentation.
67
- return ;
68
- }
69
56
70
- let all_file_names = function_coverage_map
71
- . iter ( )
72
- . map ( |( _, fn_cov) | fn_cov. function_coverage_info . body_span )
73
- . map ( |span| span_file_name ( tcx, span) ) ;
74
- let global_file_table = GlobalFileTable :: new ( all_file_names) ;
57
+ // The order of entries in this global file table needs to be deterministic,
58
+ // and ideally should also be independent of the details of stable-hashing,
59
+ // because coverage tests snapshots (`.cov-map`) can observe the order and
60
+ // would need to be re-blessed if it changes. As long as those requirements
61
+ // are satisfied, the order can be arbitrary.
62
+ let mut global_file_table = GlobalFileTable :: new ( ) ;
75
63
76
- // Encode all filenames referenced by coverage mappings in this CGU.
77
- let filenames_buffer = global_file_table. make_filenames_buffer ( tcx) ;
78
- // The `llvm-cov` tool uses this hash to associate each covfun record with
79
- // its corresponding filenames table, since the final binary will typically
80
- // contain multiple covmap records from different compilation units.
81
- let filenames_hash = llvm_cov:: hash_bytes ( & filenames_buffer) ;
82
-
83
- let mut unused_function_names = Vec :: new ( ) ;
84
-
85
- let covfun_records = function_coverage_map
86
- . into_iter ( )
87
- . filter_map ( |( instance, function_coverage) | {
88
- prepare_covfun_record ( tcx, & global_file_table, instance, & function_coverage)
89
- } )
64
+ let mut covfun_records = instances_used
65
+ . iter ( )
66
+ . copied ( )
67
+ // Sort by symbol name, so that the global file table is built in an
68
+ // order that doesn't depend on the stable-hash-based order in which
69
+ // instances were visited during codegen.
70
+ . sorted_by_cached_key ( |& instance| tcx. symbol_name ( instance) . name )
71
+ . filter_map ( |instance| prepare_covfun_record ( tcx, & mut global_file_table, instance, true ) )
90
72
. collect :: < Vec < _ > > ( ) ;
91
73
74
+ // In a single designated CGU, also prepare covfun records for functions
75
+ // in this crate that were instrumented for coverage, but are unused.
76
+ if cx. codegen_unit . is_code_coverage_dead_code_cgu ( ) {
77
+ let mut unused_instances = gather_unused_function_instances ( cx) ;
78
+ // Sort the unused instances by symbol name, for the same reason as the used ones.
79
+ unused_instances. sort_by_cached_key ( |& instance| tcx. symbol_name ( instance) . name ) ;
80
+ covfun_records. extend ( unused_instances. into_iter ( ) . filter_map ( |instance| {
81
+ prepare_covfun_record ( tcx, & mut global_file_table, instance, false )
82
+ } ) ) ;
83
+ }
84
+
92
85
// If there are no covfun records for this CGU, don't generate a covmap record.
93
86
// Emitting a covmap record without any covfun records causes `llvm-cov` to
94
87
// fail when generating coverage reports, and if there are no covfun records
@@ -98,6 +91,15 @@ pub(crate) fn finalize(cx: &CodegenCx<'_, '_>) {
98
91
return ;
99
92
}
100
93
94
+ // Encode all filenames referenced by coverage mappings in this CGU.
95
+ let filenames_buffer = global_file_table. make_filenames_buffer ( tcx) ;
96
+ // The `llvm-cov` tool uses this hash to associate each covfun record with
97
+ // its corresponding filenames table, since the final binary will typically
98
+ // contain multiple covmap records from different compilation units.
99
+ let filenames_hash = llvm_cov:: hash_bytes ( & filenames_buffer) ;
100
+
101
+ let mut unused_function_names = vec ! [ ] ;
102
+
101
103
for covfun in & covfun_records {
102
104
unused_function_names. extend ( covfun. mangled_function_name_if_unused ( ) ) ;
103
105
@@ -137,22 +139,13 @@ struct GlobalFileTable {
137
139
}
138
140
139
141
impl GlobalFileTable {
140
- fn new ( all_file_names : impl IntoIterator < Item = Symbol > ) -> Self {
141
- // Collect all of the filenames into a set. Filenames usually come in
142
- // contiguous runs, so we can dedup adjacent ones to save work.
143
- let mut raw_file_table = all_file_names. into_iter ( ) . dedup ( ) . collect :: < FxIndexSet < Symbol > > ( ) ;
144
-
145
- // Sort the file table by its actual string values, not the arbitrary
146
- // ordering of its symbols.
147
- raw_file_table. sort_unstable_by ( |a, b| a. as_str ( ) . cmp ( b. as_str ( ) ) ) ;
148
-
149
- Self { raw_file_table }
142
+ fn new ( ) -> Self {
143
+ Self { raw_file_table : FxIndexSet :: default ( ) }
150
144
}
151
145
152
- fn global_file_id_for_file_name ( & self , file_name : Symbol ) -> GlobalFileId {
153
- let raw_id = self . raw_file_table . get_index_of ( & file_name) . unwrap_or_else ( || {
154
- bug ! ( "file name not found in prepared global file table: {file_name}" ) ;
155
- } ) ;
146
+ fn global_file_id_for_file_name ( & mut self , file_name : Symbol ) -> GlobalFileId {
147
+ // Ensure the given file has a table entry, and get its index.
148
+ let ( raw_id, _) = self . raw_file_table . insert_full ( file_name) ;
156
149
// The raw file table doesn't include an entry for the working dir
157
150
// (which has ID 0), so add 1 to get the correct ID.
158
151
GlobalFileId :: from_usize ( raw_id + 1 )
@@ -264,39 +257,35 @@ fn generate_covmap_record<'ll>(cx: &CodegenCx<'ll, '_>, version: u32, filenames_
264
257
/// coverage map (in a single designated CGU) so that we still emit coverage mappings for them.
265
258
/// We also end up adding their symbol names to a special global array that LLVM will include in
266
259
/// its embedded coverage data.
267
- fn add_unused_functions ( cx : & CodegenCx < ' _ , ' _ > ) {
260
+ fn gather_unused_function_instances < ' tcx > ( cx : & CodegenCx < ' _ , ' tcx > ) -> Vec < ty :: Instance < ' tcx > > {
268
261
assert ! ( cx. codegen_unit. is_code_coverage_dead_code_cgu( ) ) ;
269
262
270
263
let tcx = cx. tcx ;
271
264
let usage = prepare_usage_sets ( tcx) ;
272
265
273
266
let is_unused_fn = |def_id : LocalDefId | -> bool {
274
- let def_id = def_id . to_def_id ( ) ;
275
-
276
- // To be eligible for "unused function" mappings, a definition must:
277
- // - Be function-like
267
+ // Usage sets expect `DefId`, so convert from `LocalDefId`.
268
+ let d : DefId = LocalDefId :: to_def_id ( def_id ) ;
269
+ // To be potentially eligible for "unused function" mappings, a definition must:
270
+ // - Be eligible for coverage instrumentation
278
271
// - Not participate directly in codegen (or have lost all its coverage statements)
279
272
// - Not have any coverage statements inlined into codegenned functions
280
- tcx. def_kind ( def_id) . is_fn_like ( )
281
- && ( !usage. all_mono_items . contains ( & def_id)
282
- || usage. missing_own_coverage . contains ( & def_id) )
283
- && !usage. used_via_inlining . contains ( & def_id)
273
+ tcx. is_eligible_for_coverage ( def_id)
274
+ && ( !usage. all_mono_items . contains ( & d) || usage. missing_own_coverage . contains ( & d) )
275
+ && !usage. used_via_inlining . contains ( & d)
284
276
} ;
285
277
286
- // Scan for unused functions that were instrumented for coverage.
287
- for def_id in tcx. mir_keys ( ( ) ) . iter ( ) . copied ( ) . filter ( |& def_id| is_unused_fn ( def_id) ) {
288
- // Get the coverage info from MIR, skipping functions that were never instrumented.
289
- let body = tcx. optimized_mir ( def_id) ;
290
- let Some ( function_coverage_info) = body. function_coverage_info . as_deref ( ) else { continue } ;
278
+ // FIXME(#79651): Consider trying to filter out dummy instantiations of
279
+ // unused generic functions from library crates, because they can produce
280
+ // "unused instantiation" in coverage reports even when they are actually
281
+ // used by some downstream crate in the same binary.
291
282
292
- // FIXME(79651): Consider trying to filter out dummy instantiations of
293
- // unused generic functions from library crates, because they can produce
294
- // "unused instantiation" in coverage reports even when they are actually
295
- // used by some downstream crate in the same binary.
296
-
297
- debug ! ( "generating unused fn: {def_id:?}" ) ;
298
- add_unused_function_coverage ( cx, def_id, function_coverage_info) ;
299
- }
283
+ tcx. mir_keys ( ( ) )
284
+ . iter ( )
285
+ . copied ( )
286
+ . filter ( |& def_id| is_unused_fn ( def_id) )
287
+ . map ( |def_id| make_dummy_instance ( tcx, def_id) )
288
+ . collect :: < Vec < _ > > ( )
300
289
}
301
290
302
291
struct UsageSets < ' tcx > {
@@ -361,16 +350,11 @@ fn prepare_usage_sets<'tcx>(tcx: TyCtxt<'tcx>) -> UsageSets<'tcx> {
361
350
UsageSets { all_mono_items, used_via_inlining, missing_own_coverage }
362
351
}
363
352
364
- fn add_unused_function_coverage < ' tcx > (
365
- cx : & CodegenCx < ' _ , ' tcx > ,
366
- def_id : LocalDefId ,
367
- function_coverage_info : & ' tcx mir:: coverage:: FunctionCoverageInfo ,
368
- ) {
369
- let tcx = cx. tcx ;
370
- let def_id = def_id. to_def_id ( ) ;
353
+ fn make_dummy_instance < ' tcx > ( tcx : TyCtxt < ' tcx > , local_def_id : LocalDefId ) -> ty:: Instance < ' tcx > {
354
+ let def_id = local_def_id. to_def_id ( ) ;
371
355
372
356
// Make a dummy instance that fills in all generics with placeholders.
373
- let instance = ty:: Instance :: new (
357
+ ty:: Instance :: new (
374
358
def_id,
375
359
ty:: GenericArgs :: for_item ( tcx, def_id, |param, _| {
376
360
if let ty:: GenericParamDefKind :: Lifetime = param. kind {
@@ -379,9 +363,5 @@ fn add_unused_function_coverage<'tcx>(
379
363
tcx. mk_param_from_def ( param)
380
364
}
381
365
} ) ,
382
- ) ;
383
-
384
- // An unused function's mappings will all be rewritten to map to zero.
385
- let function_coverage = FunctionCoverage :: new_unused ( function_coverage_info) ;
386
- cx. coverage_cx ( ) . function_coverage_map . borrow_mut ( ) . insert ( instance, function_coverage) ;
366
+ )
387
367
}
0 commit comments