Skip to content

Commit

Permalink
Used functions with only unreachable coverage is now possible
Browse files Browse the repository at this point in the history
Fixes: rust-lang#85081

An `assert!()` in `FunctionCoverage` failed, because PR rust-lang#84797
replaces unreachable `Counter`s with `Unreachable` code regions to be
added to the function's coverage map.

To fix it, and still generate valid LLVM coverage IR, convert one
unreachable to a `Counter`.
  • Loading branch information
richkadel committed May 8, 2021
1 parent 467253f commit 36cb289
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 28 deletions.
2 changes: 1 addition & 1 deletion compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ pub fn finalize<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>) {

// Encode coverage mappings and generate function records
let mut function_data = Vec::new();
for (instance, function_coverage) in function_coverage_map {
for (instance, mut function_coverage) in function_coverage_map {
debug!("Generate function coverage for {}, {:?}", cx.codegen_unit.name(), instance);
let mangled_function_name = tcx.symbol_name(instance).to_string();
let source_hash = function_coverage.source_hash();
Expand Down
12 changes: 3 additions & 9 deletions compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,15 +244,9 @@ fn add_unused_function_coverage(
) {
let tcx = cx.tcx;

let mut function_coverage = FunctionCoverage::unused(tcx, instance);
for (index, &code_region) in tcx.covered_code_regions(def_id).iter().enumerate() {
if index == 0 {
// Insert at least one real counter so the LLVM CoverageMappingReader will find expected
// definitions.
function_coverage.add_counter(UNUSED_FUNCTION_COUNTER_ID, code_region.clone());
} else {
function_coverage.add_unreachable_region(code_region.clone());
}
let mut function_coverage = FunctionCoverage::unused(instance);
for &code_region in tcx.covered_code_regions(def_id).iter() {
function_coverage.add_unreachable_region(code_region.clone());
}

if let Some(coverage_context) = cx.coverage_context() {
Expand Down
61 changes: 43 additions & 18 deletions compiler/rustc_codegen_ssa/src/coverageinfo/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub struct Expression {
/// only whitespace or comments). According to LLVM Code Coverage Mapping documentation, "A count
/// for a gap area is only used as the line execution count if there are no other regions on a
/// line."
#[derive(Debug)]
pub struct FunctionCoverage<'tcx> {
instance: Instance<'tcx>,
source_hash: u64,
Expand All @@ -40,30 +41,34 @@ pub struct FunctionCoverage<'tcx> {
impl<'tcx> FunctionCoverage<'tcx> {
/// Creates a new set of coverage data for a used (called) function.
pub fn new(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> Self {
Self::create(tcx, instance, true)
}

/// Creates a new set of coverage data for an unused (never called) function.
pub fn unused(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> Self {
Self::create(tcx, instance, false)
}

fn create(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, is_used: bool) -> Self {
let coverageinfo = tcx.coverageinfo(instance.def_id());
debug!(
"FunctionCoverage::new(instance={:?}) has coverageinfo={:?}. is_used={}",
instance, coverageinfo, is_used
"FunctionCoverage::new(instance={:?}) has coverageinfo={:?}",
instance, coverageinfo
);
Self {
instance,
source_hash: 0, // will be set with the first `add_counter()`
is_used,
is_used: true,
counters: IndexVec::from_elem_n(None, coverageinfo.num_counters as usize),
expressions: IndexVec::from_elem_n(None, coverageinfo.num_expressions as usize),
unreachable_regions: Vec::new(),
}
}

/// Creates a new set of coverage data for an unused (never called) function.
pub fn unused(instance: Instance<'tcx>) -> Self {
debug!("FunctionCoverage::unused(instance={:?})", instance);
Self {
instance,
source_hash: 0, // will be set with the first `add_counter()`
is_used: false,
counters: IndexVec::from_elem_n(None, CounterValueReference::START.as_usize()),
expressions: IndexVec::new(),
unreachable_regions: Vec::new(),
}
}

/// Returns true for a used (called) function, and false for an unused function.
pub fn is_used(&self) -> bool {
self.is_used
Expand Down Expand Up @@ -142,13 +147,33 @@ impl<'tcx> FunctionCoverage<'tcx> {
/// associated `Regions` (from which the LLVM-specific `CoverageMapGenerator` will create
/// `CounterMappingRegion`s.
pub fn get_expressions_and_counter_regions<'a>(
&'a self,
&'a mut self,
) -> (Vec<CounterExpression>, impl Iterator<Item = (Counter, &'a CodeRegion)>) {
assert!(
self.source_hash != 0 || !self.is_used,
"No counters provided the source_hash for used function: {:?}",
self.instance
);
if self.source_hash == 0 {
// Either this `FunctionCoverage` is _not_ used (created via
// `unused()`, or the function had all statements removed, including
// all `Counter`s.
//
// Dead blocks (removed during MIR transform, typically because
// const evaluation can determine that the block will never be
// called) are removed before codegen, but any coverage statements
// are replaced by `Coverage::unreachable` statements at the top of
// the MIR CFG.
debug!("No counters provided the source_hash for used function:\n{:?}", self);
assert_eq!(
self.expressions.len(),
0,
"Expressions (from MIR) without counters: {:?}",
self.instance
);
// If there are `Unreachable` code regions, but no `Counter`s, we
// need to add at least one Counter, because LLVM seems to require
// it.
if let Some(unreachable_code_region) = self.unreachable_regions.pop() {
// Replace any one of the `Unreachable`s with a counter.
self.counters.push(Some(unreachable_code_region));
}
}

let counter_regions = self.counter_regions();
let (counter_expressions, expression_regions) = self.expressions_with_regions();
Expand Down

0 comments on commit 36cb289

Please sign in to comment.