Skip to content

Commit b9856b6

Browse files
committed
Auto merge of #138766 - Zalathar:unused-fn, r=SparrowLii
coverage: Deal with unused functions and their names in one place When coverage codegen creates dummy instances and covfun records for unused functions, we already know that they are unused, so we might as well set up the special array of unused function names at the same time. --- The first commit only moves code around; all significant changes are in the second commit. There should be no change in compiler output.
2 parents 25a615b + b3c40cf commit b9856b6

File tree

3 files changed

+180
-161
lines changed

3 files changed

+180
-161
lines changed

Diff for: compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs

+8-153
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,11 @@ use rustc_abi::Align;
55
use rustc_codegen_ssa::traits::{
66
BaseTypeCodegenMethods, ConstCodegenMethods, StaticCodegenMethods,
77
};
8-
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
9-
use rustc_hir::def_id::{DefId, LocalDefId};
8+
use rustc_data_structures::fx::FxIndexMap;
109
use rustc_index::IndexVec;
11-
use rustc_middle::mir;
12-
use rustc_middle::mir::mono::MonoItemPartitions;
13-
use rustc_middle::ty::{self, TyCtxt};
10+
use rustc_middle::ty::TyCtxt;
1411
use rustc_session::RemapFileNameExt;
1512
use rustc_session::config::RemapPathScopeComponents;
16-
use rustc_span::def_id::DefIdSet;
1713
use rustc_span::{SourceFile, StableSourceFileId};
1814
use tracing::debug;
1915

@@ -24,6 +20,7 @@ use crate::llvm;
2420

2521
mod covfun;
2622
mod spans;
23+
mod unused;
2724

2825
/// Generates and exports the coverage map, which is embedded in special
2926
/// linker sections in the final binary.
@@ -76,12 +73,11 @@ pub(crate) fn finalize(cx: &CodegenCx<'_, '_>) {
7673
// In a single designated CGU, also prepare covfun records for functions
7774
// in this crate that were instrumented for coverage, but are unused.
7875
if cx.codegen_unit.is_code_coverage_dead_code_cgu() {
79-
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.
81-
unused_instances.sort_by_cached_key(|&instance| tcx.symbol_name(instance).name);
82-
covfun_records.extend(unused_instances.into_iter().filter_map(|instance| {
83-
prepare_covfun_record(tcx, &mut global_file_table, instance, false)
84-
}));
76+
unused::prepare_covfun_records_for_unused_functions(
77+
cx,
78+
&mut global_file_table,
79+
&mut covfun_records,
80+
);
8581
}
8682

8783
// If there are no covfun records for this CGU, don't generate a covmap record.
@@ -100,33 +96,10 @@ pub(crate) fn finalize(cx: &CodegenCx<'_, '_>) {
10096
// contain multiple covmap records from different compilation units.
10197
let filenames_hash = llvm_cov::hash_bytes(&filenames_buffer);
10298

103-
let mut unused_function_names = vec![];
104-
10599
for covfun in &covfun_records {
106-
unused_function_names.extend(covfun.mangled_function_name_if_unused());
107-
108100
covfun::generate_covfun_record(cx, filenames_hash, covfun)
109101
}
110102

111-
// For unused functions, we need to take their mangled names and store them
112-
// in a specially-named global array. LLVM's `InstrProfiling` pass will
113-
// detect this global and include those names in its `__llvm_prf_names`
114-
// section. (See `llvm/lib/Transforms/Instrumentation/InstrProfiling.cpp`.)
115-
if !unused_function_names.is_empty() {
116-
assert!(cx.codegen_unit.is_code_coverage_dead_code_cgu());
117-
118-
let name_globals = unused_function_names
119-
.into_iter()
120-
.map(|mangled_function_name| cx.const_str(mangled_function_name).0)
121-
.collect::<Vec<_>>();
122-
let initializer = cx.const_array(cx.type_ptr(), &name_globals);
123-
124-
let array = llvm::add_global(cx.llmod, cx.val_ty(initializer), c"__llvm_coverage_names");
125-
llvm::set_global_constant(array, true);
126-
llvm::set_linkage(array, llvm::Linkage::InternalLinkage);
127-
llvm::set_initializer(array, initializer);
128-
}
129-
130103
// Generate the coverage map header, which contains the filenames used by
131104
// this CGU's coverage mappings, and store it in a well-known global.
132105
// (This is skipped if we returned early due to having no covfun records.)
@@ -249,121 +222,3 @@ fn generate_covmap_record<'ll>(cx: &CodegenCx<'ll, '_>, version: u32, filenames_
249222

250223
cx.add_used_global(covmap_global);
251224
}
252-
253-
/// Each CGU will normally only emit coverage metadata for the functions that it actually generates.
254-
/// But since we don't want unused functions to disappear from coverage reports, we also scan for
255-
/// functions that were instrumented but are not participating in codegen.
256-
///
257-
/// These unused functions don't need to be codegenned, but we do need to add them to the function
258-
/// coverage map (in a single designated CGU) so that we still emit coverage mappings for them.
259-
/// We also end up adding their symbol names to a special global array that LLVM will include in
260-
/// its embedded coverage data.
261-
fn gather_unused_function_instances<'tcx>(cx: &CodegenCx<'_, 'tcx>) -> Vec<ty::Instance<'tcx>> {
262-
assert!(cx.codegen_unit.is_code_coverage_dead_code_cgu());
263-
264-
let tcx = cx.tcx;
265-
let usage = prepare_usage_sets(tcx);
266-
267-
let is_unused_fn = |def_id: LocalDefId| -> bool {
268-
// Usage sets expect `DefId`, so convert from `LocalDefId`.
269-
let d: DefId = LocalDefId::to_def_id(def_id);
270-
// To be potentially eligible for "unused function" mappings, a definition must:
271-
// - Be eligible for coverage instrumentation
272-
// - Not participate directly in codegen (or have lost all its coverage statements)
273-
// - Not have any coverage statements inlined into codegenned functions
274-
tcx.is_eligible_for_coverage(def_id)
275-
&& (!usage.all_mono_items.contains(&d) || usage.missing_own_coverage.contains(&d))
276-
&& !usage.used_via_inlining.contains(&d)
277-
};
278-
279-
// FIXME(#79651): Consider trying to filter out dummy instantiations of
280-
// unused generic functions from library crates, because they can produce
281-
// "unused instantiation" in coverage reports even when they are actually
282-
// used by some downstream crate in the same binary.
283-
284-
tcx.mir_keys(())
285-
.iter()
286-
.copied()
287-
.filter(|&def_id| is_unused_fn(def_id))
288-
.map(|def_id| make_dummy_instance(tcx, def_id))
289-
.collect::<Vec<_>>()
290-
}
291-
292-
struct UsageSets<'tcx> {
293-
all_mono_items: &'tcx DefIdSet,
294-
used_via_inlining: FxHashSet<DefId>,
295-
missing_own_coverage: FxHashSet<DefId>,
296-
}
297-
298-
/// Prepare sets of definitions that are relevant to deciding whether something
299-
/// is an "unused function" for coverage purposes.
300-
fn prepare_usage_sets<'tcx>(tcx: TyCtxt<'tcx>) -> UsageSets<'tcx> {
301-
let MonoItemPartitions { all_mono_items, codegen_units, .. } =
302-
tcx.collect_and_partition_mono_items(());
303-
304-
// Obtain a MIR body for each function participating in codegen, via an
305-
// arbitrary instance.
306-
let mut def_ids_seen = FxHashSet::default();
307-
let def_and_mir_for_all_mono_fns = codegen_units
308-
.iter()
309-
.flat_map(|cgu| cgu.items().keys())
310-
.filter_map(|item| match item {
311-
mir::mono::MonoItem::Fn(instance) => Some(instance),
312-
mir::mono::MonoItem::Static(_) | mir::mono::MonoItem::GlobalAsm(_) => None,
313-
})
314-
// We only need one arbitrary instance per definition.
315-
.filter(move |instance| def_ids_seen.insert(instance.def_id()))
316-
.map(|instance| {
317-
// We don't care about the instance, just its underlying MIR.
318-
let body = tcx.instance_mir(instance.def);
319-
(instance.def_id(), body)
320-
});
321-
322-
// Functions whose coverage statements were found inlined into other functions.
323-
let mut used_via_inlining = FxHashSet::default();
324-
// Functions that were instrumented, but had all of their coverage statements
325-
// removed by later MIR transforms (e.g. UnreachablePropagation).
326-
let mut missing_own_coverage = FxHashSet::default();
327-
328-
for (def_id, body) in def_and_mir_for_all_mono_fns {
329-
let mut saw_own_coverage = false;
330-
331-
// Inspect every coverage statement in the function's MIR.
332-
for stmt in body
333-
.basic_blocks
334-
.iter()
335-
.flat_map(|block| &block.statements)
336-
.filter(|stmt| matches!(stmt.kind, mir::StatementKind::Coverage(_)))
337-
{
338-
if let Some(inlined) = stmt.source_info.scope.inlined_instance(&body.source_scopes) {
339-
// This coverage statement was inlined from another function.
340-
used_via_inlining.insert(inlined.def_id());
341-
} else {
342-
// Non-inlined coverage statements belong to the enclosing function.
343-
saw_own_coverage = true;
344-
}
345-
}
346-
347-
if !saw_own_coverage && body.function_coverage_info.is_some() {
348-
missing_own_coverage.insert(def_id);
349-
}
350-
}
351-
352-
UsageSets { all_mono_items, used_via_inlining, missing_own_coverage }
353-
}
354-
355-
fn make_dummy_instance<'tcx>(tcx: TyCtxt<'tcx>, local_def_id: LocalDefId) -> ty::Instance<'tcx> {
356-
let def_id = local_def_id.to_def_id();
357-
358-
// Make a dummy instance that fills in all generics with placeholders.
359-
ty::Instance::new(
360-
def_id,
361-
ty::GenericArgs::for_item(tcx, def_id, |param, _| {
362-
if let ty::GenericParamDefKind::Lifetime = param.kind {
363-
tcx.lifetimes.re_erased.into()
364-
} else {
365-
tcx.mk_param_from_def(param)
366-
}
367-
}),
368-
)
369-
}

Diff for: compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs

-8
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,6 @@ pub(crate) struct CovfunRecord<'tcx> {
3737
regions: ffi::Regions,
3838
}
3939

40-
impl<'tcx> CovfunRecord<'tcx> {
41-
/// FIXME(Zalathar): Make this the responsibility of the code that determines
42-
/// which functions are unused.
43-
pub(crate) fn mangled_function_name_if_unused(&self) -> Option<&'tcx str> {
44-
(!self.is_used).then_some(self.mangled_function_name)
45-
}
46-
}
47-
4840
pub(crate) fn prepare_covfun_record<'tcx>(
4941
tcx: TyCtxt<'tcx>,
5042
global_file_table: &mut GlobalFileTable,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
use rustc_codegen_ssa::traits::{BaseTypeCodegenMethods, ConstCodegenMethods};
2+
use rustc_data_structures::fx::FxHashSet;
3+
use rustc_hir::def_id::{DefId, LocalDefId};
4+
use rustc_middle::mir;
5+
use rustc_middle::mir::mono::MonoItemPartitions;
6+
use rustc_middle::ty::{self, TyCtxt};
7+
use rustc_span::def_id::DefIdSet;
8+
9+
use crate::common::CodegenCx;
10+
use crate::coverageinfo::mapgen::GlobalFileTable;
11+
use crate::coverageinfo::mapgen::covfun::{CovfunRecord, prepare_covfun_record};
12+
use crate::llvm;
13+
14+
/// Each CGU will normally only emit coverage metadata for the functions that it actually generates.
15+
/// But since we don't want unused functions to disappear from coverage reports, we also scan for
16+
/// functions that were instrumented but are not participating in codegen.
17+
///
18+
/// These unused functions don't need to be codegenned, but we do need to add them to the function
19+
/// coverage map (in a single designated CGU) so that we still emit coverage mappings for them.
20+
/// We also end up adding their symbol names to a special global array that LLVM will include in
21+
/// its embedded coverage data.
22+
pub(crate) fn prepare_covfun_records_for_unused_functions<'tcx>(
23+
cx: &CodegenCx<'_, 'tcx>,
24+
global_file_table: &mut GlobalFileTable,
25+
covfun_records: &mut Vec<CovfunRecord<'tcx>>,
26+
) {
27+
assert!(cx.codegen_unit.is_code_coverage_dead_code_cgu());
28+
29+
let mut unused_instances = gather_unused_function_instances(cx);
30+
// Sort the unused instances by symbol name, so that their order isn't hash-sensitive.
31+
unused_instances.sort_by_key(|instance| instance.symbol_name);
32+
33+
// Try to create a covfun record for each unused function.
34+
let mut name_globals = Vec::with_capacity(unused_instances.len());
35+
covfun_records.extend(unused_instances.into_iter().filter_map(|unused| try {
36+
let record = prepare_covfun_record(cx.tcx, global_file_table, unused.instance, false)?;
37+
// If successful, also store its symbol name in a global constant.
38+
name_globals.push(cx.const_str(unused.symbol_name.name).0);
39+
record
40+
}));
41+
42+
// Store the names of unused functions in a specially-named global array.
43+
// LLVM's `InstrProfilling` pass will detect this array, and include the
44+
// referenced names in its `__llvm_prf_names` section.
45+
// (See `llvm/lib/Transforms/Instrumentation/InstrProfiling.cpp`.)
46+
if !name_globals.is_empty() {
47+
let initializer = cx.const_array(cx.type_ptr(), &name_globals);
48+
49+
let array = llvm::add_global(cx.llmod, cx.val_ty(initializer), c"__llvm_coverage_names");
50+
llvm::set_global_constant(array, true);
51+
llvm::set_linkage(array, llvm::Linkage::InternalLinkage);
52+
llvm::set_initializer(array, initializer);
53+
}
54+
}
55+
56+
/// Holds a dummy function instance along with its symbol name, to avoid having
57+
/// to repeatedly query for the name.
58+
struct UnusedInstance<'tcx> {
59+
instance: ty::Instance<'tcx>,
60+
symbol_name: ty::SymbolName<'tcx>,
61+
}
62+
63+
fn gather_unused_function_instances<'tcx>(cx: &CodegenCx<'_, 'tcx>) -> Vec<UnusedInstance<'tcx>> {
64+
assert!(cx.codegen_unit.is_code_coverage_dead_code_cgu());
65+
66+
let tcx = cx.tcx;
67+
let usage = prepare_usage_sets(tcx);
68+
69+
let is_unused_fn = |def_id: LocalDefId| -> bool {
70+
// Usage sets expect `DefId`, so convert from `LocalDefId`.
71+
let d: DefId = LocalDefId::to_def_id(def_id);
72+
// To be potentially eligible for "unused function" mappings, a definition must:
73+
// - Be eligible for coverage instrumentation
74+
// - Not participate directly in codegen (or have lost all its coverage statements)
75+
// - Not have any coverage statements inlined into codegenned functions
76+
tcx.is_eligible_for_coverage(def_id)
77+
&& (!usage.all_mono_items.contains(&d) || usage.missing_own_coverage.contains(&d))
78+
&& !usage.used_via_inlining.contains(&d)
79+
};
80+
81+
// FIXME(#79651): Consider trying to filter out dummy instantiations of
82+
// unused generic functions from library crates, because they can produce
83+
// "unused instantiation" in coverage reports even when they are actually
84+
// used by some downstream crate in the same binary.
85+
86+
tcx.mir_keys(())
87+
.iter()
88+
.copied()
89+
.filter(|&def_id| is_unused_fn(def_id))
90+
.map(|def_id| make_dummy_instance(tcx, def_id))
91+
.map(|instance| UnusedInstance { instance, symbol_name: tcx.symbol_name(instance) })
92+
.collect::<Vec<_>>()
93+
}
94+
95+
struct UsageSets<'tcx> {
96+
all_mono_items: &'tcx DefIdSet,
97+
used_via_inlining: FxHashSet<DefId>,
98+
missing_own_coverage: FxHashSet<DefId>,
99+
}
100+
101+
/// Prepare sets of definitions that are relevant to deciding whether something
102+
/// is an "unused function" for coverage purposes.
103+
fn prepare_usage_sets<'tcx>(tcx: TyCtxt<'tcx>) -> UsageSets<'tcx> {
104+
let MonoItemPartitions { all_mono_items, codegen_units, .. } =
105+
tcx.collect_and_partition_mono_items(());
106+
107+
// Obtain a MIR body for each function participating in codegen, via an
108+
// arbitrary instance.
109+
let mut def_ids_seen = FxHashSet::default();
110+
let def_and_mir_for_all_mono_fns = codegen_units
111+
.iter()
112+
.flat_map(|cgu| cgu.items().keys())
113+
.filter_map(|item| match item {
114+
mir::mono::MonoItem::Fn(instance) => Some(instance),
115+
mir::mono::MonoItem::Static(_) | mir::mono::MonoItem::GlobalAsm(_) => None,
116+
})
117+
// We only need one arbitrary instance per definition.
118+
.filter(move |instance| def_ids_seen.insert(instance.def_id()))
119+
.map(|instance| {
120+
// We don't care about the instance, just its underlying MIR.
121+
let body = tcx.instance_mir(instance.def);
122+
(instance.def_id(), body)
123+
});
124+
125+
// Functions whose coverage statements were found inlined into other functions.
126+
let mut used_via_inlining = FxHashSet::default();
127+
// Functions that were instrumented, but had all of their coverage statements
128+
// removed by later MIR transforms (e.g. UnreachablePropagation).
129+
let mut missing_own_coverage = FxHashSet::default();
130+
131+
for (def_id, body) in def_and_mir_for_all_mono_fns {
132+
let mut saw_own_coverage = false;
133+
134+
// Inspect every coverage statement in the function's MIR.
135+
for stmt in body
136+
.basic_blocks
137+
.iter()
138+
.flat_map(|block| &block.statements)
139+
.filter(|stmt| matches!(stmt.kind, mir::StatementKind::Coverage(_)))
140+
{
141+
if let Some(inlined) = stmt.source_info.scope.inlined_instance(&body.source_scopes) {
142+
// This coverage statement was inlined from another function.
143+
used_via_inlining.insert(inlined.def_id());
144+
} else {
145+
// Non-inlined coverage statements belong to the enclosing function.
146+
saw_own_coverage = true;
147+
}
148+
}
149+
150+
if !saw_own_coverage && body.function_coverage_info.is_some() {
151+
missing_own_coverage.insert(def_id);
152+
}
153+
}
154+
155+
UsageSets { all_mono_items, used_via_inlining, missing_own_coverage }
156+
}
157+
158+
fn make_dummy_instance<'tcx>(tcx: TyCtxt<'tcx>, local_def_id: LocalDefId) -> ty::Instance<'tcx> {
159+
let def_id = local_def_id.to_def_id();
160+
161+
// Make a dummy instance that fills in all generics with placeholders.
162+
ty::Instance::new(
163+
def_id,
164+
ty::GenericArgs::for_item(tcx, def_id, |param, _| {
165+
if let ty::GenericParamDefKind::Lifetime = param.kind {
166+
tcx.lifetimes.re_erased.into()
167+
} else {
168+
tcx.mk_param_from_def(param)
169+
}
170+
}),
171+
)
172+
}

0 commit comments

Comments
 (0)