Skip to content

Commit 6513f50

Browse files
committed
Auto merge of rust-lang#79109 - richkadel:llvm-coverage-counters-2.0.5, r=tmandry
Coverage tests for remaining TerminatorKinds and async, improve Assert Tested and validate results for panic unwind, panic abort, assert!() macro, TerminatorKind::Assert (for example, numeric overflow), and async/await. Implemented a previous documented idea to change Assert handling to be the same as FalseUnwind and Goto, so it doesn't get its own BasicCoverageBlock anymore. This changed a couple of coverage regions, but I validated those changes are not any worse than the prior results, and probably help assure some consistency (even if some people might disagree with how the code region is consistently computed). Fixed issue with async/await. AggregateKind::Generator needs to be handled like AggregateKind::Closure; coverage span for the outer async function should not "cover" the async body, which is actually executed in a separate "closure" MIR.
2 parents 5be3f9f + dc4bd90 commit 6513f50

File tree

363 files changed

+22960
-19417
lines changed

Some content is hidden

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

363 files changed

+22960
-19417
lines changed

Diff for: 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);

Diff for: 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(

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

+167-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,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+
}

Diff for: 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)]

Diff for: 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,

Diff for: 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, _), .. })

Diff for: 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

Diff for: 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]

Diff for: compiler/rustc_mir/src/transform/coverage/graph.rs

+12-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,24 @@ 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 program executions that
144+
// don't panic.
145+
//
146+
// Programs that panic and unwind may record slightly inaccurate coverage results
147+
// for a coverage region containing the `Terminator` that began the panic. This
148+
// is as intended. (See Issue #78544 for a possible future option to support
149+
// coverage in test programs that panic.)
150150
TerminatorKind::Goto { .. }
151151
| TerminatorKind::Resume
152152
| TerminatorKind::Unreachable
153153
| TerminatorKind::Drop { .. }
154154
| TerminatorKind::DropAndReplace { .. }
155155
| TerminatorKind::Call { .. }
156156
| TerminatorKind::GeneratorDrop
157+
| TerminatorKind::Assert { .. }
157158
| TerminatorKind::FalseEdge { .. }
158159
| TerminatorKind::FalseUnwind { .. }
159160
| TerminatorKind::InlineAsm { .. } => {}
@@ -278,6 +279,7 @@ rustc_index::newtype_index! {
278279
/// A node in the [control-flow graph][CFG] of CoverageGraph.
279280
pub(super) struct BasicCoverageBlock {
280281
DEBUG_FORMAT = "bcb{}",
282+
const START_BCB = 0,
281283
}
282284
}
283285

0 commit comments

Comments
 (0)