Skip to content

Commit 8736e17

Browse files
authored
Rollup merge of rust-lang#134323 - Zalathar:dismantle-map-data, r=jieyouxu
coverage: Dismantle `map_data.rs` by moving its responsibilities elsewhere This is a series of incremental changes that combine to let us get rid of `coverageinfo/map_data.rs`, by moving all of its responsibilities into more appropriate places. Some of the notable consequences are: - We once again build the per-CGU file table on the fly while preparing individual covfun records, instead of building the whole table up-front. The up-front approach was introduced by rust-lang#117042 to work around various other problems in generating the covmap/covfun records, but subsequent cleanups have made that approach no longer necessary. - Expression conversion and mapping-region conversion are now performed directly in `mapgen::covfun`, which should make future changes easier. - We no longer insert unused function instances into the same map that is also used to track used function instances. This helps to decouple the handling of used vs unused functions. --- There should be no meaningful change to compiler output. The file table is no longer sorted, because reordering it would invalidate the file indices stored in individual covfun records, but the table order should still be deterministic (albeit arbitrary). There are some subsequent cleanups that I intend to investigate, but this is enough change for one PR.
2 parents b0b6b62 + 541d4e8 commit 8736e17

File tree

5 files changed

+133
-208
lines changed

5 files changed

+133
-208
lines changed

compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs

-84
This file was deleted.

compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs

+65-85
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
use std::iter;
22

3-
use itertools::Itertools as _;
3+
use itertools::Itertools;
44
use rustc_abi::Align;
55
use rustc_codegen_ssa::traits::{
66
BaseTypeCodegenMethods, ConstCodegenMethods, StaticCodegenMethods,
77
};
88
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
99
use rustc_hir::def_id::{DefId, LocalDefId};
1010
use rustc_index::IndexVec;
11+
use rustc_middle::mir;
1112
use rustc_middle::ty::{self, TyCtxt};
12-
use rustc_middle::{bug, mir};
1313
use rustc_session::RemapFileNameExt;
1414
use rustc_session::config::RemapPathScopeComponents;
1515
use rustc_span::def_id::DefIdSet;
@@ -18,7 +18,6 @@ use tracing::debug;
1818

1919
use crate::common::CodegenCx;
2020
use crate::coverageinfo::llvm_cov;
21-
use crate::coverageinfo::map_data::FunctionCoverage;
2221
use crate::coverageinfo::mapgen::covfun::prepare_covfun_record;
2322
use crate::llvm;
2423

@@ -49,46 +48,40 @@ pub(crate) fn finalize(cx: &CodegenCx<'_, '_>) {
4948

5049
debug!("Generating coverage map for CodegenUnit: `{}`", cx.codegen_unit.name());
5150

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-
6051
// 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(),
6354
None => return,
6455
};
65-
if function_coverage_map.is_empty() {
66-
// This CGU has no functions with coverage instrumentation.
67-
return;
68-
}
6956

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();
7563

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))
9072
.collect::<Vec<_>>();
9173

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+
9285
// If there are no covfun records for this CGU, don't generate a covmap record.
9386
// Emitting a covmap record without any covfun records causes `llvm-cov` to
9487
// fail when generating coverage reports, and if there are no covfun records
@@ -98,6 +91,15 @@ pub(crate) fn finalize(cx: &CodegenCx<'_, '_>) {
9891
return;
9992
}
10093

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+
101103
for covfun in &covfun_records {
102104
unused_function_names.extend(covfun.mangled_function_name_if_unused());
103105

@@ -137,22 +139,13 @@ struct GlobalFileTable {
137139
}
138140

139141
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() }
150144
}
151145

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);
156149
// The raw file table doesn't include an entry for the working dir
157150
// (which has ID 0), so add 1 to get the correct ID.
158151
GlobalFileId::from_usize(raw_id + 1)
@@ -264,39 +257,35 @@ fn generate_covmap_record<'ll>(cx: &CodegenCx<'ll, '_>, version: u32, filenames_
264257
/// coverage map (in a single designated CGU) so that we still emit coverage mappings for them.
265258
/// We also end up adding their symbol names to a special global array that LLVM will include in
266259
/// 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>> {
268261
assert!(cx.codegen_unit.is_code_coverage_dead_code_cgu());
269262

270263
let tcx = cx.tcx;
271264
let usage = prepare_usage_sets(tcx);
272265

273266
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
278271
// - Not participate directly in codegen (or have lost all its coverage statements)
279272
// - 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)
284276
};
285277

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.
291282

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<_>>()
300289
}
301290

302291
struct UsageSets<'tcx> {
@@ -361,16 +350,11 @@ fn prepare_usage_sets<'tcx>(tcx: TyCtxt<'tcx>) -> UsageSets<'tcx> {
361350
UsageSets { all_mono_items, used_via_inlining, missing_own_coverage }
362351
}
363352

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();
371355

372356
// Make a dummy instance that fills in all generics with placeholders.
373-
let instance = ty::Instance::new(
357+
ty::Instance::new(
374358
def_id,
375359
ty::GenericArgs::for_item(tcx, def_id, |param, _| {
376360
if let ty::GenericParamDefKind::Lifetime = param.kind {
@@ -379,9 +363,5 @@ fn add_unused_function_coverage<'tcx>(
379363
tcx.mk_param_from_def(param)
380364
}
381365
}),
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+
)
387367
}

0 commit comments

Comments
 (0)