Description
When coverage instrumentation and MIR opts are both enabled, coverage relies on two assumptions:
-
MIR opts that would delete
StatementKind::Coveragestatements instead move them into bb0 and change them toCoverageKind::Unreachable. -
MIR opts won't delete all
CoverageKind::Counter statementsfrom an instrumented function.
The first assumption will be lifted by #116046, which stores coverage mappings in a separate per-function struct, so the original mappings will be available during codegen even if MIR opts delete arbitrary coverage statements.
The second assumption is trickier. If a function is instrumented for coverage, but MIR opts remove all of its counter-increment statements (e.g. because bb0's ends with TerminatorKind::Unreachable), then we will codegen a function with no llvm.instrprof.increment intrinsics. This causes LLVM to assume that the function wasn't instrumented, so it will disappear from the final coverage mappings and won't participate in coverage reports.
Coverage has special code for handling functions that are deemed “unused” at the MIR level, but if a function is used (e.g. as a function pointer) but its body is unreachable, then that special code isn't applied.
Ideally what we need is some way for coverage codegen to detect that an instrumented function has no remaining counter-increment statements, and take special action to either re-insert at least one counter-increment (so LLVM treats it as instrumented), or treat it as though it were unused (so that it shows up in reports as never-executed).
@rustbot label +A-code-coverage +T-compiler
Activity
Zalathar commentedon Sep 26, 2023
Creation of this issue was requested in #116166 (comment), so that it can be linked to in a comment explaining why we turn off the
UnreachablePropagationpass when coverage is enabled.Once coverage instrumentation is robust against MIR opts that delete all coverage statements, we can remove that condition and enable
UnreachablePropagationin optimized coverage builds.Zalathar commentedon Sep 26, 2023
Currently it's difficult to detect this case because coverage codegen normally only knows about a function if it contains one or more
StatementKind::Coveragestatements, or if it was not codegenned at all. There is no way to detect a function that was instrumented and then codegenned without any coverage statements.Once we have per-function coverage info from #116046, we'll be in a position to detect instrumented functions that have no remaining coverage statements, because they'll still have the coverage info that was attached during instrumentation.
Zalathar commentedon Sep 26, 2023
Zalathar commentedon Sep 26, 2023
II have a work-in-progress patch that fixes this via a new MIR pass that runs after optimizations, detects functions that were instrumented but no longer have any counter increments, and re-adds a counter increment to bb0. That seems to do the trick.
I’ll polish it up and add it to my patch queue.
Zalathar commentedon Sep 27, 2023
I also found a way to test this that doesn't rely on #113970: write a function that uses
std::intrinsics::unreachable(). That triggers the existing code inUnreachablePropagationthat can remove all statements from a block.Auto merge of rust-lang#116304 - Zalathar:unreachable, r=cjgillot
Auto merge of rust-lang#122860 - Zalathar:unused, r=<try>
Rollup merge of rust-lang#122860 - Zalathar:unused, r=cjgillot
Unrolled build for rust-lang#122860