diff options
Diffstat (limited to 'compiler/rustc_codegen_llvm/src/coverageinfo')
| -rw-r--r-- | compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs | 155 |
1 files changed, 148 insertions, 7 deletions
diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs index 85aaa7e8893..ced3f21f744 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs @@ -3,11 +3,14 @@ use crate::coverageinfo; use crate::llvm; use llvm::coverageinfo::CounterMappingRegion; -use rustc_codegen_ssa::coverageinfo::map::{Counter, CounterExpression}; +use rustc_codegen_ssa::coverageinfo::map::{Counter, CounterExpression, FunctionCoverage}; use rustc_codegen_ssa::traits::ConstMethods; -use rustc_data_structures::fx::FxIndexSet; +use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet}; +use rustc_hir::def_id::{DefId, DefIdSet, LOCAL_CRATE}; use rustc_llvm::RustString; use rustc_middle::mir::coverage::CodeRegion; +use rustc_middle::ty::{Instance, TyCtxt}; +use rustc_span::Symbol; use std::ffi::CString; @@ -26,14 +29,17 @@ use tracing::debug; /// undocumented details in Clang's implementation (that may or may not be important) were also /// replicated for Rust's Coverage Map. pub fn finalize<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>) { + let tcx = cx.tcx; // Ensure LLVM supports Coverage Map Version 4 (encoded as a zero-based value: 3). // If not, the LLVM Version must be less than 11. let version = coverageinfo::mapping_version(); if version != 3 { - cx.tcx.sess.fatal("rustc option `-Z instrument-coverage` requires LLVM 11 or higher."); + tcx.sess.fatal("rustc option `-Z instrument-coverage` requires LLVM 11 or higher."); } - let function_coverage_map = match cx.coverage_context() { + debug!("Generating coverage map for CodegenUnit: `{}`", cx.codegen_unit.name()); + + let mut function_coverage_map = match cx.coverage_context() { Some(ctx) => ctx.take_function_coverage_map(), None => return, }; @@ -42,14 +48,15 @@ pub fn finalize<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>) { return; } + add_unreachable_coverage(tcx, &mut function_coverage_map); + let mut mapgen = CoverageMapGenerator::new(); // Encode coverage mappings and generate function records let mut function_data = Vec::new(); for (instance, function_coverage) in function_coverage_map { - debug!("Generate coverage map for: {:?}", instance); - - let mangled_function_name = cx.tcx.symbol_name(instance).to_string(); + debug!("Generate function coverage for {}, {:?}", cx.codegen_unit.name(), instance); + let mangled_function_name = tcx.symbol_name(instance).to_string(); let function_source_hash = function_coverage.source_hash(); let (expressions, counter_regions) = function_coverage.get_expressions_and_counter_regions(); @@ -228,3 +235,137 @@ fn save_function_record( let is_used = true; coverageinfo::save_func_record_to_mod(cx, func_name_hash, func_record_val, is_used); } + +/// When finalizing the coverage map, `FunctionCoverage` only has the `CodeRegion`s and counters for +/// the functions that went through codegen; such as public functions and "used" functions +/// (functions referenced by other "used" or public items). Any other functions considered unused, +/// or "Unreachable" were still parsed and processed through the MIR stage. +/// +/// We can find the unreachable functions by the set different of all MIR `DefId`s (`tcx` query +/// `mir_keys`) minus the codegenned `DefId`s (`tcx` query `collect_and_partition_mono_items`). +/// +/// *HOWEVER* the codegenned `DefId`s are partitioned across multiple `CodegenUnit`s (CGUs), and +/// this function is processing a `function_coverage_map` for the functions (`Instance`/`DefId`) +/// allocated to only one of those CGUs. We must NOT inject any "Unreachable" functions's +/// `CodeRegion`s more than once, so we have to pick which CGU's `function_coverage_map` to add +/// each "Unreachable" function to. +/// +/// Some constraints: +/// +/// 1. The file name of an "Unreachable" function must match the file name of the existing +/// codegenned (covered) function to which the unreachable code regions will be added. +/// 2. The function to which the unreachable code regions will be added must not be a genaric +/// function (must not have type parameters) because the coverage tools will get confused +/// if the codegenned function has more than one instantiation and additional `CodeRegion`s +/// attached to only one of those instantiations. +fn add_unreachable_coverage<'tcx>( + tcx: TyCtxt<'tcx>, + function_coverage_map: &mut FxHashMap<Instance<'tcx>, FunctionCoverage<'tcx>>, +) { + // Note: If the crate *only* defines generic functions, there are no codegenerated non-generic + // functions to add any unreachable code to. In this case, the unreachable code regions will + // have no coverage, instead of having coverage with zero executions. + // + // This is probably still an improvement over Clang, which does not generate any coverage + // for uninstantiated template functions. + + let has_non_generic_def_ids = + function_coverage_map.keys().any(|instance| instance.def.attrs(tcx).len() == 0); + + if !has_non_generic_def_ids { + // There are no non-generic functions to add unreachable `CodeRegion`s to + return; + } + + let all_def_ids: DefIdSet = + tcx.mir_keys(LOCAL_CRATE).iter().map(|local_def_id| local_def_id.to_def_id()).collect(); + + let (codegenned_def_ids, _) = tcx.collect_and_partition_mono_items(LOCAL_CRATE); + + let mut unreachable_def_ids_by_file: FxHashMap<Symbol, Vec<DefId>> = FxHashMap::default(); + for &non_codegenned_def_id in all_def_ids.difference(codegenned_def_ids) { + // Make sure the non-codegenned (unreachable) function has a file_name + if let Some(non_codegenned_file_name) = tcx.covered_file_name(non_codegenned_def_id) { + let def_ids = unreachable_def_ids_by_file + .entry(*non_codegenned_file_name) + .or_insert_with(|| Vec::new()); + def_ids.push(non_codegenned_def_id); + } + } + + if unreachable_def_ids_by_file.is_empty() { + // There are no unreachable functions with file names to add (in any CGU) + return; + } + + // Since there may be multiple `CodegenUnit`s, some codegenned_def_ids may be codegenned in a + // different CGU, and will be added to the function_coverage_map for each CGU. Determine which + // function_coverage_map has the responsibility for publishing unreachable coverage + // based on file name: + // + // For each covered file name, sort ONLY the non-generic codegenned_def_ids, and if + // covered_def_ids.contains(the first def_id) for a given file_name, add the unreachable code + // region in this function_coverage_map. Otherwise, ignore it and assume another CGU's + // function_coverage_map will be adding it (because it will be first for one, and only one, + // of them). + let mut sorted_codegenned_def_ids: Vec<DefId> = + codegenned_def_ids.iter().map(|def_id| *def_id).collect(); + sorted_codegenned_def_ids.sort_unstable(); + + let mut first_covered_def_id_by_file: FxHashMap<Symbol, DefId> = FxHashMap::default(); + for &def_id in sorted_codegenned_def_ids.iter() { + // Only consider non-generic functions, to potentially add unreachable code regions + if tcx.generics_of(def_id).count() == 0 { + if let Some(covered_file_name) = tcx.covered_file_name(def_id) { + // Only add files known to have unreachable functions + if unreachable_def_ids_by_file.contains_key(covered_file_name) { + first_covered_def_id_by_file.entry(*covered_file_name).or_insert(def_id); + } + } + } + } + + // Get the set of def_ids with coverage regions, known by *this* CoverageContext. + let cgu_covered_def_ids: DefIdSet = + function_coverage_map.keys().map(|instance| instance.def.def_id()).collect(); + + let mut cgu_covered_files: FxHashSet<Symbol> = first_covered_def_id_by_file + .iter() + .filter_map( + |(&file_name, def_id)| { + if cgu_covered_def_ids.contains(def_id) { Some(file_name) } else { None } + }, + ) + .collect(); + + // Find the first covered, non-generic function (instance) for each cgu_covered_file. Take the + // unreachable code regions for that file, and add them to the function. + // + // There are three `for` loops here, but (a) the lists have already been reduced to the minimum + // required values, the lists are further reduced (by `remove()` calls) when elements are no + // longer needed, and there are several opportunities to branch out of loops early. + for (instance, function_coverage) in function_coverage_map.iter_mut() { + if instance.def.attrs(tcx).len() > 0 { + continue; + } + // The covered function is not generic... + let covered_def_id = instance.def.def_id(); + if let Some(covered_file_name) = tcx.covered_file_name(covered_def_id) { + if !cgu_covered_files.remove(&covered_file_name) { + continue; + } + // The covered function's file is one of the files with unreachable code regions, so + // all of the unreachable code regions for this file will be added to this function. + for def_id in + unreachable_def_ids_by_file.remove(&covered_file_name).into_iter().flatten() + { + for ®ion in tcx.covered_code_regions(def_id) { + function_coverage.add_unreachable_region(region.clone()); + } + } + if cgu_covered_files.is_empty() { + break; + } + } + } +} |
