Skip to content

Commit def932c

Browse files
committed
Combination of commits
Fixes multiple issue with counters, with simplification Includes a change to the implicit else span in ast_lowering, so coverage of the implicit else no longer spans the `then` block. Adds coverage for unused closures and async function bodies. Fixes: rust-lang#78542 Adding unreachable regions for known MIR missing from coverage map Cleaned up PR commits, and removed link-dead-code requirement and tests Coverage no longer depends on Issue rust-lang#76038 (`-C link-dead-code` is no longer needed or enforced, so MSVC can use the same tests as Linux and MacOS now) Restrict adding unreachable regions to covered files Improved the code that adds coverage for uncalled functions (with MIR but not-codegenned) to avoid generating coverage in files not already included in the files with covered functions. Resolved last known issue requiring --emit llvm-ir workaround Fixed bugs in how unreachable code spans were added.
1 parent f6c9c1a commit def932c

File tree

354 files changed

+12632
-20484
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

354 files changed

+12632
-20484
lines changed

compiler/rustc_ast_lowering/src/expr.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
347347
// `_ => else_block` where `else_block` is `{}` if there's `None`:
348348
let else_pat = self.pat_wild(span);
349349
let (else_expr, contains_else_clause) = match else_opt {
350-
None => (self.expr_block_empty(span), false),
350+
None => (self.expr_block_empty(span.shrink_to_hi()), false),
351351
Some(els) => (self.lower_expr(els), true),
352352
};
353353
let else_arm = self.arm(else_pat, else_expr);

compiler/rustc_codegen_llvm/src/attributes.rs

-3
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,6 @@ fn set_probestack(cx: &CodegenCx<'ll, '_>, llfn: &'ll Value) {
127127
return;
128128
}
129129

130-
// FIXME(richkadel): Make sure probestack plays nice with `-Z instrument-coverage`
131-
// or disable it if not, similar to above early exits.
132-
133130
// Flag our internal `__rust_probestack` function as the stack probe symbol.
134131
// This is defined in the `compiler-builtins` crate for each architecture.
135132
llvm::AddFunctionAttrStringValue(

compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs

+148-7
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ use crate::coverageinfo;
33
use crate::llvm;
44

55
use llvm::coverageinfo::CounterMappingRegion;
6-
use rustc_codegen_ssa::coverageinfo::map::{Counter, CounterExpression};
6+
use rustc_codegen_ssa::coverageinfo::map::{Counter, CounterExpression, FunctionCoverage};
77
use 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};
910
use rustc_llvm::RustString;
1011
use rustc_middle::mir::coverage::CodeRegion;
12+
use rustc_middle::ty::{Instance, TyCtxt};
13+
use rustc_span::Symbol;
1114

1215
use 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.
2831
pub 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,137 @@ 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+
// Note: If the crate *only* defines generic functions, there are no codegenerated non-generic
266+
// functions to add any unreachable code to. In this case, the unreachable code regions will
267+
// have no coverage, instead of having coverage with zero executions.
268+
//
269+
// This is probably still an improvement over Clang, which does not generate any coverage
270+
// for uninstantiated template functions.
271+
272+
let has_non_generic_def_ids =
273+
function_coverage_map.keys().any(|instance| instance.def.attrs(tcx).len() == 0);
274+
275+
if !has_non_generic_def_ids {
276+
// There are no non-generic functions to add unreachable `CodeRegion`s to
277+
return;
278+
}
279+
280+
let all_def_ids: DefIdSet =
281+
tcx.mir_keys(LOCAL_CRATE).iter().map(|local_def_id| local_def_id.to_def_id()).collect();
282+
283+
let (codegenned_def_ids, _) = tcx.collect_and_partition_mono_items(LOCAL_CRATE);
284+
285+
let mut unreachable_def_ids_by_file: FxHashMap<Symbol, Vec<DefId>> = FxHashMap::default();
286+
for &non_codegenned_def_id in all_def_ids.difference(codegenned_def_ids) {
287+
// Make sure the non-codegenned (unreachable) function has a file_name
288+
if let Some(non_codegenned_file_name) = tcx.covered_file_name(non_codegenned_def_id) {
289+
let def_ids = unreachable_def_ids_by_file
290+
.entry(*non_codegenned_file_name)
291+
.or_insert_with(|| Vec::new());
292+
def_ids.push(non_codegenned_def_id);
293+
}
294+
}
295+
296+
if unreachable_def_ids_by_file.is_empty() {
297+
// There are no unreachable functions with file names to add (in any CGU)
298+
return;
299+
}
300+
301+
// Since there may be multiple `CodegenUnit`s, some codegenned_def_ids may be codegenned in a
302+
// different CGU, and will be added to the function_coverage_map for each CGU. Determine which
303+
// function_coverage_map has the responsibility for publishing unreachable coverage
304+
// based on file name:
305+
//
306+
// For each covered file name, sort ONLY the non-generic codegenned_def_ids, and if
307+
// covered_def_ids.contains(the first def_id) for a given file_name, add the unreachable code
308+
// region in this function_coverage_map. Otherwise, ignore it and assume another CGU's
309+
// function_coverage_map will be adding it (because it will be first for one, and only one,
310+
// of them).
311+
let mut sorted_codegenned_def_ids: Vec<DefId> =
312+
codegenned_def_ids.iter().map(|def_id| *def_id).collect();
313+
sorted_codegenned_def_ids.sort_unstable();
314+
315+
let mut first_covered_def_id_by_file: FxHashMap<Symbol, DefId> = FxHashMap::default();
316+
for &def_id in sorted_codegenned_def_ids.iter() {
317+
// Only consider non-generic functions, to potentially add unreachable code regions
318+
if tcx.generics_of(def_id).count() == 0 {
319+
if let Some(covered_file_name) = tcx.covered_file_name(def_id) {
320+
// Only add files known to have unreachable functions
321+
if unreachable_def_ids_by_file.contains_key(covered_file_name) {
322+
first_covered_def_id_by_file.entry(*covered_file_name).or_insert(def_id);
323+
}
324+
}
325+
}
326+
}
327+
328+
// Get the set of def_ids with coverage regions, known by *this* CoverageContext.
329+
let cgu_covered_def_ids: DefIdSet =
330+
function_coverage_map.keys().map(|instance| instance.def.def_id()).collect();
331+
332+
let mut cgu_covered_files: FxHashSet<Symbol> = first_covered_def_id_by_file
333+
.iter()
334+
.filter_map(
335+
|(&file_name, def_id)| {
336+
if cgu_covered_def_ids.contains(def_id) { Some(file_name) } else { None }
337+
},
338+
)
339+
.collect();
340+
341+
// Find the first covered, non-generic function (instance) for each cgu_covered_file. Take the
342+
// unreachable code regions for that file, and add them to the function.
343+
//
344+
// There are three `for` loops here, but (a) the lists have already been reduced to the minimum
345+
// required values, the lists are further reduced (by `remove()` calls) when elements are no
346+
// longer needed, and there are several opportunities to branch out of loops early.
347+
for (instance, function_coverage) in function_coverage_map.iter_mut() {
348+
if instance.def.attrs(tcx).len() > 0 {
349+
continue;
350+
}
351+
// The covered function is not generic...
352+
let covered_def_id = instance.def.def_id();
353+
if let Some(covered_file_name) = tcx.covered_file_name(covered_def_id) {
354+
if !cgu_covered_files.remove(&covered_file_name) {
355+
continue;
356+
}
357+
// The covered function's file is one of the files with unreachable code regions, so
358+
// all of the unreachable code regions for this file will be added to this function.
359+
for def_id in
360+
unreachable_def_ids_by_file.remove(&covered_file_name).into_iter().flatten()
361+
{
362+
for &region in tcx.covered_code_regions(def_id) {
363+
function_coverage.add_unreachable_region(region.clone());
364+
}
365+
}
366+
if cgu_covered_files.is_empty() {
367+
break;
368+
}
369+
}
370+
}
371+
}

compiler/rustc_codegen_ssa/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#![feature(bool_to_option)]
33
#![feature(option_expect_none)]
44
#![feature(box_patterns)]
5+
#![feature(drain_filter)]
56
#![feature(try_blocks)]
67
#![feature(in_band_lifetimes)]
78
#![feature(nll)]

compiler/rustc_middle/src/arena.rs

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ macro_rules! arena_types {
3232
[decode] borrowck_result:
3333
rustc_middle::mir::BorrowCheckResult<$tcx>,
3434
[decode] unsafety_check_result: rustc_middle::mir::UnsafetyCheckResult,
35+
[decode] code_region: rustc_middle::mir::coverage::CodeRegion,
3536
[] const_allocs: rustc_middle::mir::interpret::Allocation,
3637
// Required for the incremental on-disk cache
3738
[few] mir_keys: rustc_hir::def_id::DefIdSet,

compiler/rustc_middle/src/hir/map/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ fn fn_decl<'hir>(node: Node<'hir>) -> Option<&'hir FnDecl<'hir>> {
4747
}
4848
}
4949

50-
fn fn_sig<'hir>(node: Node<'hir>) -> Option<&'hir FnSig<'hir>> {
50+
pub fn fn_sig<'hir>(node: Node<'hir>) -> Option<&'hir FnSig<'hir>> {
5151
match &node {
5252
Node::Item(Item { kind: ItemKind::Fn(sig, _, _), .. })
5353
| Node::TraitItem(TraitItem { kind: TraitItemKind::Fn(sig, _), .. })

compiler/rustc_middle/src/query/mod.rs

+15
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,21 @@ rustc_queries! {
346346
cache_on_disk_if { key.is_local() }
347347
}
348348

349+
/// Returns the name of the file that contains the function body, if instrumented for coverage.
350+
query covered_file_name(key: DefId) -> Option<Symbol> {
351+
desc { |tcx| "retrieving the covered file name, if instrumented, for `{}`", tcx.def_path_str(key) }
352+
storage(ArenaCacheSelector<'tcx>)
353+
cache_on_disk_if { key.is_local() }
354+
}
355+
356+
/// Returns the `CodeRegions` for a function that has instrumented coverage, in case the
357+
/// function was optimized out before codegen, and before being added to the Coverage Map.
358+
query covered_code_regions(key: DefId) -> Vec<&'tcx mir::coverage::CodeRegion> {
359+
desc { |tcx| "retrieving the covered `CodeRegion`s, if instrumented, for `{}`", tcx.def_path_str(key) }
360+
storage(ArenaCacheSelector<'tcx>)
361+
cache_on_disk_if { key.is_local() }
362+
}
363+
349364
/// The `DefId` is the `DefId` of the containing MIR body. Promoteds do not have their own
350365
/// `DefId`. This function returns all promoteds in the specified body. The body references
351366
/// promoteds by the `DefId` and the `mir::Promoted` index. This is necessary, because

compiler/rustc_middle/src/ty/codec.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ encodable_via_deref! {
162162
ty::Region<'tcx>,
163163
&'tcx mir::Body<'tcx>,
164164
&'tcx mir::UnsafetyCheckResult,
165-
&'tcx mir::BorrowCheckResult<'tcx>
165+
&'tcx mir::BorrowCheckResult<'tcx>,
166+
&'tcx mir::coverage::CodeRegion
166167
}
167168

168169
pub trait TyDecoder<'tcx>: Decoder {
@@ -376,7 +377,8 @@ impl_decodable_via_ref! {
376377
&'tcx Allocation,
377378
&'tcx mir::Body<'tcx>,
378379
&'tcx mir::UnsafetyCheckResult,
379-
&'tcx mir::BorrowCheckResult<'tcx>
380+
&'tcx mir::BorrowCheckResult<'tcx>,
381+
&'tcx mir::coverage::CodeRegion
380382
}
381383

382384
#[macro_export]

compiler/rustc_mir/src/transform/coverage/graph.rs

+10-10
Original file line numberDiff line numberDiff line change
@@ -118,18 +118,8 @@ impl CoverageGraph {
118118

119119
match term.kind {
120120
TerminatorKind::Return { .. }
121-
// FIXME(richkadel): Add test(s) for `Abort` coverage.
122121
| TerminatorKind::Abort
123-
// FIXME(richkadel): Add test(s) for `Assert` coverage.
124-
// Should `Assert` be handled like `FalseUnwind` instead? Since we filter out unwind
125-
// branches when creating the BCB CFG, aren't `Assert`s (without unwinds) just like
126-
// `FalseUnwinds` (which are kind of like `Goto`s)?
127-
| TerminatorKind::Assert { .. }
128-
// FIXME(richkadel): Add test(s) for `Yield` coverage, and confirm coverage is
129-
// sensible for code using the `yield` keyword.
130122
| TerminatorKind::Yield { .. }
131-
// FIXME(richkadel): Also add coverage tests using async/await, and threading.
132-
133123
| TerminatorKind::SwitchInt { .. } => {
134124
// The `bb` has more than one _outgoing_ edge, or exits the function. Save the
135125
// current sequence of `basic_blocks` gathered to this point, as a new
@@ -147,13 +137,22 @@ impl CoverageGraph {
147137
// `Terminator`s `successors()` list) checking the number of successors won't
148138
// work.
149139
}
140+
141+
// The following `TerminatorKind`s are either not expected outside an unwind branch,
142+
// or they should not (under normal circumstances) branch. Coverage graphs are
143+
// simplified by assuring coverage results are accurate for well-behaved programs.
144+
// Programs that panic and unwind may record slightly inaccurate coverage results
145+
// for a coverage region containing the `Terminator` that began the panic. This
146+
// is as intended. (See Issue #78544 for a possible future option to support
147+
// coverage in test programs that panic.)
150148
TerminatorKind::Goto { .. }
151149
| TerminatorKind::Resume
152150
| TerminatorKind::Unreachable
153151
| TerminatorKind::Drop { .. }
154152
| TerminatorKind::DropAndReplace { .. }
155153
| TerminatorKind::Call { .. }
156154
| TerminatorKind::GeneratorDrop
155+
| TerminatorKind::Assert { .. }
157156
| TerminatorKind::FalseEdge { .. }
158157
| TerminatorKind::FalseUnwind { .. }
159158
| TerminatorKind::InlineAsm { .. } => {}
@@ -278,6 +277,7 @@ rustc_index::newtype_index! {
278277
/// A node in the [control-flow graph][CFG] of CoverageGraph.
279278
pub(super) struct BasicCoverageBlock {
280279
DEBUG_FORMAT = "bcb{}",
280+
const START_BCB = 0,
281281
}
282282
}
283283

0 commit comments

Comments
 (0)