@@ -56,31 +56,26 @@ pub(crate) fn finalize(cx: &CodegenCx<'_, '_>) {
56
56
None => return ,
57
57
} ;
58
58
59
- // The order of entries in this global file table needs to be deterministic,
60
- // and ideally should also be independent of the details of stable-hashing,
61
- // because coverage tests snapshots (`.cov-map`) can observe the order and
62
- // would need to be re-blessed if it changes. As long as those requirements
63
- // are satisfied, the order can be arbitrary.
64
- let mut global_file_table = GlobalFileTable :: new ( ) ;
65
-
66
59
let mut covfun_records = instances_used
67
60
. iter ( )
68
61
. copied ( )
69
62
// Sort by symbol name, so that the global file table is built in an
70
63
// order that doesn't depend on the stable-hash-based order in which
71
64
// instances were visited during codegen.
72
65
. sorted_by_cached_key ( |& instance| tcx. symbol_name ( instance) . name )
73
- . filter_map ( |instance| prepare_covfun_record ( tcx, & mut global_file_table , instance, true ) )
66
+ . filter_map ( |instance| prepare_covfun_record ( tcx, instance, true ) )
74
67
. collect :: < Vec < _ > > ( ) ;
75
68
76
69
// In a single designated CGU, also prepare covfun records for functions
77
70
// in this crate that were instrumented for coverage, but are unused.
78
71
if cx. codegen_unit . is_code_coverage_dead_code_cgu ( ) {
79
72
let mut unused_instances = gather_unused_function_instances ( cx) ;
80
- // Sort the unused instances by symbol name, for the same reason as the used ones.
73
+ // Sort the unused instances by symbol name, so that their order doesn't
74
+ // depend on the stable-hash-sensitive order that they were discovered in.
81
75
unused_instances. sort_by_cached_key ( |& instance| tcx. symbol_name ( instance) . name ) ;
82
76
covfun_records. extend ( unused_instances. into_iter ( ) . filter_map ( |instance| {
83
- prepare_covfun_record ( tcx, & mut global_file_table, instance, false )
77
+ // Prepare a suitable covfun record, with all counters forced to zero.
78
+ prepare_covfun_record ( tcx, instance, false )
84
79
} ) ) ;
85
80
}
86
81
@@ -93,19 +88,17 @@ pub(crate) fn finalize(cx: &CodegenCx<'_, '_>) {
93
88
return ;
94
89
}
95
90
96
- // Encode all filenames referenced by coverage mappings in this CGU.
97
- let filenames_buffer = global_file_table. make_filenames_buffer ( tcx) ;
98
- // The `llvm-cov` tool uses this hash to associate each covfun record with
99
- // its corresponding filenames table, since the final binary will typically
100
- // contain multiple covmap records from different compilation units.
101
- let filenames_hash = llvm_cov:: hash_bytes ( & filenames_buffer) ;
91
+ // Prepare the global file table for this CGU, containing all paths needed
92
+ // by one or more covfun records.
93
+ let global_file_table =
94
+ GlobalFileTable :: build ( tcx, covfun_records. iter ( ) . flat_map ( |c| c. all_source_files ( ) ) ) ;
102
95
103
96
let mut unused_function_names = vec ! [ ] ;
104
97
105
98
for covfun in & covfun_records {
106
99
unused_function_names. extend ( covfun. mangled_function_name_if_unused ( ) ) ;
107
100
108
- covfun:: generate_covfun_record ( cx, filenames_hash , covfun)
101
+ covfun:: generate_covfun_record ( cx, & global_file_table , covfun)
109
102
}
110
103
111
104
// For unused functions, we need to take their mangled names and store them
@@ -130,54 +123,76 @@ pub(crate) fn finalize(cx: &CodegenCx<'_, '_>) {
130
123
// Generate the coverage map header, which contains the filenames used by
131
124
// this CGU's coverage mappings, and store it in a well-known global.
132
125
// (This is skipped if we returned early due to having no covfun records.)
133
- generate_covmap_record ( cx, covmap_version, & filenames_buffer) ;
126
+ generate_covmap_record ( cx, covmap_version, & global_file_table . filenames_buffer ) ;
134
127
}
135
128
136
- /// Maps "global" (per-CGU) file ID numbers to their underlying source files.
129
+ /// Maps "global" (per-CGU) file ID numbers to their underlying source file paths.
130
+ #[ derive( Debug ) ]
137
131
struct GlobalFileTable {
138
132
/// This "raw" table doesn't include the working dir, so a file's
139
133
/// global ID is its index in this set **plus one**.
140
- raw_file_table : FxIndexMap < StableSourceFileId , Arc < SourceFile > > ,
134
+ raw_file_table : FxIndexMap < StableSourceFileId , String > ,
135
+
136
+ /// The file table in encoded form (possibly compressed), which can be
137
+ /// included directly in this CGU's `__llvm_covmap` record.
138
+ filenames_buffer : Vec < u8 > ,
139
+
140
+ /// Truncated hash of the bytes in `filenames_buffer`.
141
+ ///
142
+ /// The `llvm-cov` tool uses this hash to associate each covfun record with
143
+ /// its corresponding filenames table, since the final binary will typically
144
+ /// contain multiple covmap records from different compilation units.
145
+ filenames_hash : u64 ,
141
146
}
142
147
143
148
impl GlobalFileTable {
144
- fn new ( ) -> Self {
145
- Self { raw_file_table : FxIndexMap :: default ( ) }
146
- }
149
+ /// Builds a "global file table" for this CGU, mapping numeric IDs to
150
+ /// path strings.
151
+ fn build < ' a > ( tcx : TyCtxt < ' _ > , all_files : impl Iterator < Item = & ' a SourceFile > ) -> Self {
152
+ let mut raw_file_table = FxIndexMap :: default ( ) ;
153
+
154
+ for file in all_files {
155
+ raw_file_table. entry ( file. stable_id ) . or_insert_with ( || {
156
+ file. name
157
+ . for_scope ( tcx. sess , RemapPathScopeComponents :: MACRO )
158
+ . to_string_lossy ( )
159
+ . into_owned ( )
160
+ } ) ;
161
+ }
147
162
148
- fn global_file_id_for_file ( & mut self , file : & Arc < SourceFile > ) -> GlobalFileId {
149
- // Ensure the given file has a table entry, and get its index.
150
- let entry = self . raw_file_table . entry ( file. stable_id ) ;
151
- let raw_id = entry. index ( ) ;
152
- entry. or_insert_with ( || Arc :: clone ( file) ) ;
163
+ // FIXME(Zalathar): Consider sorting the file table here, but maybe
164
+ // only after adding filename support to coverage-dump, so that the
165
+ // table order isn't directly visible in `.coverage-map` snapshots.
153
166
154
- // The raw file table doesn't include an entry for the working dir
155
- // (which has ID 0), so add 1 to get the correct ID.
156
- GlobalFileId :: from_usize ( raw_id + 1 )
157
- }
167
+ let mut table = Vec :: with_capacity ( raw_file_table. len ( ) + 1 ) ;
158
168
159
- fn make_filenames_buffer ( & self , tcx : TyCtxt < ' _ > ) -> Vec < u8 > {
160
- let mut table = Vec :: with_capacity ( self . raw_file_table . len ( ) + 1 ) ;
161
-
162
- // LLVM Coverage Mapping Format version 6 (zero-based encoded as 5)
163
- // requires setting the first filename to the compilation directory.
164
- // Since rustc generates coverage maps with relative paths, the
165
- // compilation directory can be combined with the relative paths
166
- // to get absolute paths, if needed.
167
- table. push (
168
- tcx. sess
169
- . opts
170
- . working_dir
171
- . for_scope ( tcx. sess , RemapPathScopeComponents :: MACRO )
172
- . to_string_lossy ( ) ,
173
- ) ;
169
+ // Since version 6 of the LLVM coverage mapping format, the first entry
170
+ // in the global file table is treated as a base directory, used to
171
+ // resolve any other entries that are stored as relative paths.
172
+ let base_dir = tcx
173
+ . sess
174
+ . opts
175
+ . working_dir
176
+ . for_scope ( tcx. sess , RemapPathScopeComponents :: MACRO )
177
+ . to_string_lossy ( ) ;
178
+ table. push ( base_dir. as_ref ( ) ) ;
174
179
175
180
// Add the regular entries after the base directory.
176
- table. extend ( self . raw_file_table . values ( ) . map ( |file| {
177
- file. name . for_scope ( tcx. sess , RemapPathScopeComponents :: MACRO ) . to_string_lossy ( )
178
- } ) ) ;
181
+ table. extend ( raw_file_table. values ( ) . map ( |name| name. as_str ( ) ) ) ;
182
+
183
+ // Encode the file table into a buffer, and get the hash of its encoded
184
+ // bytes, so that we can embed that hash in `__llvm_covfun` records.
185
+ let filenames_buffer = llvm_cov:: write_filenames_to_buffer ( & table) ;
186
+ let filenames_hash = llvm_cov:: hash_bytes ( & filenames_buffer) ;
187
+
188
+ Self { raw_file_table, filenames_buffer, filenames_hash }
189
+ }
179
190
180
- llvm_cov:: write_filenames_to_buffer ( & table)
191
+ fn get_existing_id ( & self , file : & SourceFile ) -> Option < GlobalFileId > {
192
+ let raw_id = self . raw_file_table . get_index_of ( & file. stable_id ) ?;
193
+ // The raw file table doesn't include an entry for the base dir
194
+ // (which has ID 0), so add 1 to get the correct ID.
195
+ Some ( GlobalFileId :: from_usize ( raw_id + 1 ) )
181
196
}
182
197
}
183
198
@@ -193,26 +208,31 @@ rustc_index::newtype_index! {
193
208
struct LocalFileId { }
194
209
}
195
210
196
- /// Holds a mapping from "local" (per-function) file IDs to "global" (per-CGU)
197
- /// file IDs .
211
+ /// Holds a mapping from "local" (per-function) file IDs to their corresponding
212
+ /// source files .
198
213
#[ derive( Debug , Default ) ]
199
214
struct VirtualFileMapping {
200
- local_to_global : IndexVec < LocalFileId , GlobalFileId > ,
201
- global_to_local : FxIndexMap < GlobalFileId , LocalFileId > ,
215
+ local_file_table : IndexVec < LocalFileId , Arc < SourceFile > > ,
202
216
}
203
217
204
218
impl VirtualFileMapping {
205
- fn local_id_for_global ( & mut self , global_file_id : GlobalFileId ) -> LocalFileId {
206
- * self
207
- . global_to_local
208
- . entry ( global_file_id)
209
- . or_insert_with ( || self . local_to_global . push ( global_file_id) )
219
+ fn push_file ( & mut self , source_file : & Arc < SourceFile > ) -> LocalFileId {
220
+ self . local_file_table . push ( Arc :: clone ( source_file) )
210
221
}
211
222
212
- fn to_vec ( & self ) -> Vec < u32 > {
213
- // This clone could be avoided by transmuting `&[GlobalFileId]` to `&[u32]`,
214
- // but it isn't hot or expensive enough to justify the extra unsafety.
215
- self . local_to_global . iter ( ) . map ( |& global| GlobalFileId :: as_u32 ( global) ) . collect ( )
223
+ /// Resolves all of the filenames in this local file mapping to a list of
224
+ /// global file IDs in its CGU, for inclusion in this function's
225
+ /// `__llvm_covfun` record.
226
+ ///
227
+ /// The global file IDs are returned as `u32` to make FFI easier.
228
+ fn resolve_all ( & self , global_file_table : & GlobalFileTable ) -> Option < Vec < u32 > > {
229
+ self . local_file_table
230
+ . iter ( )
231
+ . map ( |file| try {
232
+ let id = global_file_table. get_existing_id ( file) ?;
233
+ GlobalFileId :: as_u32 ( id)
234
+ } )
235
+ . collect :: < Option < Vec < _ > > > ( )
216
236
}
217
237
}
218
238
0 commit comments