@@ -3,11 +3,14 @@ use crate::coverageinfo;
33use crate :: llvm;
44
55use llvm:: coverageinfo:: CounterMappingRegion ;
6- use rustc_codegen_ssa:: coverageinfo:: map:: { Counter , CounterExpression } ;
6+ use rustc_codegen_ssa:: coverageinfo:: map:: { Counter , CounterExpression , FunctionCoverage } ;
77use rustc_codegen_ssa:: traits:: ConstMethods ;
8- use rustc_data_structures:: fx:: FxIndexSet ;
8+ use rustc_data_structures:: fx:: { FxHashMap , FxHashSet , FxIndexSet } ;
9+ use rustc_hir:: def_id:: { DefId , DefIdSet , LOCAL_CRATE } ;
910use rustc_llvm:: RustString ;
1011use rustc_middle:: mir:: coverage:: CodeRegion ;
12+ use rustc_middle:: ty:: { Instance , TyCtxt } ;
13+ use rustc_span:: Symbol ;
1114
1215use std:: ffi:: CString ;
1316
@@ -26,14 +29,17 @@ use tracing::debug;
2629/// undocumented details in Clang's implementation (that may or may not be important) were also
2730/// replicated for Rust's Coverage Map.
2831pub fn finalize < ' ll , ' tcx > ( cx : & CodegenCx < ' ll , ' tcx > ) {
32+ let tcx = cx. tcx ;
2933 // Ensure LLVM supports Coverage Map Version 4 (encoded as a zero-based value: 3).
3034 // If not, the LLVM Version must be less than 11.
3135 let version = coverageinfo:: mapping_version ( ) ;
3236 if version != 3 {
33- cx . tcx . sess . fatal ( "rustc option `-Z instrument-coverage` requires LLVM 11 or higher." ) ;
37+ tcx. sess . fatal ( "rustc option `-Z instrument-coverage` requires LLVM 11 or higher." ) ;
3438 }
3539
36- let function_coverage_map = match cx. coverage_context ( ) {
40+ debug ! ( "Generating coverage map for CodegenUnit: `{}`" , cx. codegen_unit. name( ) ) ;
41+
42+ let mut function_coverage_map = match cx. coverage_context ( ) {
3743 Some ( ctx) => ctx. take_function_coverage_map ( ) ,
3844 None => return ,
3945 } ;
@@ -42,14 +48,15 @@ pub fn finalize<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>) {
4248 return ;
4349 }
4450
51+ add_unreachable_coverage ( tcx, & mut function_coverage_map) ;
52+
4553 let mut mapgen = CoverageMapGenerator :: new ( ) ;
4654
4755 // Encode coverage mappings and generate function records
4856 let mut function_data = Vec :: new ( ) ;
4957 for ( instance, function_coverage) in function_coverage_map {
50- debug ! ( "Generate coverage map for: {:?}" , instance) ;
51-
52- let mangled_function_name = cx. tcx . symbol_name ( instance) . to_string ( ) ;
58+ debug ! ( "Generate function coverage for {}, {:?}" , cx. codegen_unit. name( ) , instance) ;
59+ let mangled_function_name = tcx. symbol_name ( instance) . to_string ( ) ;
5360 let function_source_hash = function_coverage. source_hash ( ) ;
5461 let ( expressions, counter_regions) =
5562 function_coverage. get_expressions_and_counter_regions ( ) ;
@@ -228,3 +235,156 @@ fn save_function_record(
228235 let is_used = true ;
229236 coverageinfo:: save_func_record_to_mod ( cx, func_name_hash, func_record_val, is_used) ;
230237}
238+
239+ /// When finalizing the coverage map, `FunctionCoverage` only has the `CodeRegion`s and counters for
240+ /// the functions that went through codegen; such as public functions and "used" functions
241+ /// (functions referenced by other "used" or public items). Any other functions considered unused,
242+ /// or "Unreachable" were still parsed and processed through the MIR stage.
243+ ///
244+ /// We can find the unreachable functions by the set different of all MIR `DefId`s (`tcx` query
245+ /// `mir_keys`) minus the codegenned `DefId`s (`tcx` query `collect_and_partition_mono_items`).
246+ ///
247+ /// *HOWEVER* the codegenned `DefId`s are partitioned across multiple `CodegenUnit`s (CGUs), and
248+ /// this function is processing a `function_coverage_map` for the functions (`Instance`/`DefId`)
249+ /// allocated to only one of those CGUs. We must NOT inject any "Unreachable" functions's
250+ /// `CodeRegion`s more than once, so we have to pick which CGU's `function_coverage_map` to add
251+ /// each "Unreachable" function to.
252+ ///
253+ /// Some constraints:
254+ ///
255+ /// 1. The file name of an "Unreachable" function must match the file name of the existing
256+ /// codegenned (covered) function to which the unreachable code regions will be added.
257+ /// 2. The function to which the unreachable code regions will be added must not be a genaric
258+ /// function (must not have type parameters) because the coverage tools will get confused
259+ /// if the codegenned function has more than one instantiation and additional `CodeRegion`s
260+ /// attached to only one of those instantiations.
261+ fn add_unreachable_coverage < ' tcx > (
262+ tcx : TyCtxt < ' tcx > ,
263+ function_coverage_map : & mut FxHashMap < Instance < ' tcx > , FunctionCoverage < ' tcx > > ,
264+ ) {
265+ // FIXME(#79622): Can this solution be simplified and/or improved? Are there other sources
266+ // of compiler state data that might help (or better sources that could be exposed, but
267+ // aren't yet)?
268+
269+ // Note: If the crate *only* defines generic functions, there are no codegenerated non-generic
270+ // functions to add any unreachable code to. In this case, the unreachable code regions will
271+ // have no coverage, instead of having coverage with zero executions.
272+ //
273+ // This is probably still an improvement over Clang, which does not generate any coverage
274+ // for uninstantiated template functions.
275+
276+ let has_non_generic_def_ids =
277+ function_coverage_map. keys ( ) . any ( |instance| instance. def . attrs ( tcx) . len ( ) == 0 ) ;
278+
279+ if !has_non_generic_def_ids {
280+ // There are no non-generic functions to add unreachable `CodeRegion`s to
281+ return ;
282+ }
283+
284+ let all_def_ids: DefIdSet =
285+ tcx. mir_keys ( LOCAL_CRATE ) . iter ( ) . map ( |local_def_id| local_def_id. to_def_id ( ) ) . collect ( ) ;
286+
287+ let ( codegenned_def_ids, _) = tcx. collect_and_partition_mono_items ( LOCAL_CRATE ) ;
288+
289+ let mut unreachable_def_ids_by_file: FxHashMap < Symbol , Vec < DefId > > = FxHashMap :: default ( ) ;
290+ for & non_codegenned_def_id in all_def_ids. difference ( codegenned_def_ids) {
291+ // Make sure the non-codegenned (unreachable) function has a file_name
292+ if let Some ( non_codegenned_file_name) = tcx. covered_file_name ( non_codegenned_def_id) {
293+ let def_ids = unreachable_def_ids_by_file
294+ . entry ( * non_codegenned_file_name)
295+ . or_insert_with ( || Vec :: new ( ) ) ;
296+ def_ids. push ( non_codegenned_def_id) ;
297+ }
298+ }
299+
300+ if unreachable_def_ids_by_file. is_empty ( ) {
301+ // There are no unreachable functions with file names to add (in any CGU)
302+ return ;
303+ }
304+
305+ // Since there may be multiple `CodegenUnit`s, some codegenned_def_ids may be codegenned in a
306+ // different CGU, and will be added to the function_coverage_map for each CGU. Determine which
307+ // function_coverage_map has the responsibility for publishing unreachable coverage
308+ // based on file name:
309+ //
310+ // For each covered file name, sort ONLY the non-generic codegenned_def_ids, and if
311+ // covered_def_ids.contains(the first def_id) for a given file_name, add the unreachable code
312+ // region in this function_coverage_map. Otherwise, ignore it and assume another CGU's
313+ // function_coverage_map will be adding it (because it will be first for one, and only one,
314+ // of them).
315+ let mut sorted_codegenned_def_ids: Vec < DefId > =
316+ codegenned_def_ids. iter ( ) . map ( |def_id| * def_id) . collect ( ) ;
317+ sorted_codegenned_def_ids. sort_unstable ( ) ;
318+
319+ let mut first_covered_def_id_by_file: FxHashMap < Symbol , DefId > = FxHashMap :: default ( ) ;
320+ for & def_id in sorted_codegenned_def_ids. iter ( ) {
321+ // Only consider non-generic functions, to potentially add unreachable code regions
322+ if tcx. generics_of ( def_id) . count ( ) == 0 {
323+ if let Some ( covered_file_name) = tcx. covered_file_name ( def_id) {
324+ // Only add files known to have unreachable functions
325+ if unreachable_def_ids_by_file. contains_key ( covered_file_name) {
326+ first_covered_def_id_by_file. entry ( * covered_file_name) . or_insert ( def_id) ;
327+ }
328+ }
329+ }
330+ }
331+
332+ // Get the set of def_ids with coverage regions, known by *this* CoverageContext.
333+ let cgu_covered_def_ids: DefIdSet =
334+ function_coverage_map. keys ( ) . map ( |instance| instance. def . def_id ( ) ) . collect ( ) ;
335+
336+ let mut cgu_covered_files: FxHashSet < Symbol > = first_covered_def_id_by_file
337+ . iter ( )
338+ . filter_map (
339+ |( & file_name, def_id) | {
340+ if cgu_covered_def_ids. contains ( def_id) { Some ( file_name) } else { None }
341+ } ,
342+ )
343+ . collect ( ) ;
344+
345+ // Find the first covered, non-generic function (instance) for each cgu_covered_file. Take the
346+ // unreachable code regions for that file, and add them to the function.
347+ //
348+ // There are three `for` loops here, but (a) the lists have already been reduced to the minimum
349+ // required values, the lists are further reduced (by `remove()` calls) when elements are no
350+ // longer needed, and there are several opportunities to branch out of loops early.
351+ for ( instance, function_coverage) in function_coverage_map. iter_mut ( ) {
352+ if instance. def . attrs ( tcx) . len ( ) > 0 {
353+ continue ;
354+ }
355+ // The covered function is not generic...
356+ let covered_def_id = instance. def . def_id ( ) ;
357+ if let Some ( covered_file_name) = tcx. covered_file_name ( covered_def_id) {
358+ if !cgu_covered_files. remove ( & covered_file_name) {
359+ continue ;
360+ }
361+ // The covered function's file is one of the files with unreachable code regions, so
362+ // all of the unreachable code regions for this file will be added to this function.
363+ for def_id in
364+ unreachable_def_ids_by_file. remove ( & covered_file_name) . into_iter ( ) . flatten ( )
365+ {
366+ // Note, this loop adds an unreachable code regions for each MIR-derived region.
367+ // Alternatively, we could add a single code region for the maximum span of all
368+ // code regions here.
369+ //
370+ // Observed downsides of this approach are:
371+ //
372+ // 1. The coverage results will appear inconsistent compared with the same (or
373+ // similar) code in a function that is reached.
374+ // 2. If the function is unreachable from one crate but reachable when compiling
375+ // another referencing crate (such as a cross-crate reference to a
376+ // generic function or inlined function), actual coverage regions overlaid
377+ // on a single larger code span of `Zero` coverage can appear confusing or
378+ // wrong. Chaning the unreachable coverage from a `code_region` to a
379+ // `gap_region` can help, but still can look odd with `0` line counts for
380+ // lines between executed (> 0) lines (such as for blank lines or comments).
381+ for & region in tcx. covered_code_regions ( def_id) {
382+ function_coverage. add_unreachable_region ( region. clone ( ) ) ;
383+ }
384+ }
385+ if cgu_covered_files. is_empty ( ) {
386+ break ;
387+ }
388+ }
389+ }
390+ }
0 commit comments