Skip to content

coverage: Switch over to hybrid HIR-aware coverage spans - #161517

Open
Zalathar wants to merge 8 commits into
rust-lang:mainfrom
Zalathar:coverage-point
Open

coverage: Switch over to hybrid HIR-aware coverage spans #161517
Zalathar wants to merge 8 commits into
rust-lang:mainfrom
Zalathar:coverage-point

Conversation

@Zalathar

Copy link
Copy Markdown
Member

One of the deep flaws in coverage instrumentation at the moment is that the instrumentor doesn't have actual knowledge of source-level code structure. Instead, it resorts to guessing source spans based on scanning through MIR statements and terminators, and applying a few crude heuristics that can never give truly accurate results.

(The decision to use MIR-based spans was made during the original implementation of -Zinstrument-coverage. I was not able to find any discussion of this design decision or its consequences in the stabilization thread at #90132.)

This PR is a partial step towards a better way of doing things. During THIR-to-MIR lowering, we now inject special CoverageKind::Point marker statements that represent a specific connection between some part of the source code (in HIR form) to a specific point in MIR control-flow. Instead of trying to recover coverage spans from arbitrary MIR statements/terminators, we now only extract spans from those dedicated marker statements.

Having accurate HIR-to-MIR correlations unlocks a lot of exciting possibilities for better coverage instrumentation. This PR mostly sets the stage for potential future improvements, but does demonstrate the ability to exclude specific HIR expressions from the set of coverage spans (to make assertions less noisy), which was previously unthinkable.


The key to making this all work is being able to accurately inject marker statements during MIR building. There is no single point in MIR building that all THIR expressions pass through, and most THIR expression nodes do not keep track of their corresponding HirId. However, it turns out that THIR building already wraps every expression node in a thir::ExprKind::Scope node that does store the original HirId, and the number of places in MIR building that handle those scopes is relatively small. So if we simply inject an appropriate marker statement at those places, with the HirId that is already available, that takes care of marking every HIR-level expression node in the built MIR.

To avoid major regressions in coverage-span quality, we also need to inject markers in a few extra places that don't correspond to a HIR node, because they represent implied code such as the else {} of a one-sided if, or the automatic return at the end of a function body.


There are necessarily some changes to coverage output as a result of the new approach. I have tried to keep these minimal, but I don't think it's worth trying to chase 1:1 compatibility with the old spans, which were very much a product of several layers of implementation details and heuristics. The differences seem reasonable overall, and in some cases are a clear improvement.

@Zalathar Zalathar added the A-code-coverage Area: Source-based code coverage (-Cinstrument-coverage) label Aug 22, 2026
@rustbot

rustbot commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

Some changes occurred in match lowering

cc @Nadrieril

Some changes occurred to MIR optimizations

cc @rust-lang/wg-mir-opt

Some changes occurred in match checking

cc @Nadrieril

@rustbot rustbot added A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Aug 22, 2026
@rustbot rustbot added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Aug 22, 2026
@rustbot

rustbot commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

r? @BoxyUwU

rustbot has assigned @BoxyUwU.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: compiler
  • compiler expanded to 75 candidates
  • Random selection from 21 candidates

@rust-log-analyzer

This comment has been minimized.

@Zalathar

This comment was marked as resolved.

@rust-log-analyzer

This comment has been minimized.

@rustbot

rustbot commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

This PR changes MIR

cc @oli-obk, @RalfJung, @JakobDegen, @vakaras

@rustbot

This comment has been minimized.

@Zalathar

Copy link
Copy Markdown
Member Author

After some more investigation I found #145569 (comment), which suggests that for thir::Expr nodes that are not Scope, we can also recover the corresponding node from thir::Expr::temp_scope_id, since it's just the local part of the corresponding HIR node's HirId.

That might avoid the need to store a separate HirId in thir::ExprKind::If, for example.

These tests are of limited value for investigating specific problems, but still
have some worth in detecting regressions by adding variety to the test corpus.

(The boundary is a bit fuzzy, but it's easy enough to move tests back out if
appropriate.)
This variant will be used for marker statements that are injected when a HIR
expression is about to be lowered to MIR, and at other points relevant to
coverage instrumentation.

Adding this variant in its own commit keeps the mundane plumbing steps separate
from later changes that will perform injection and analysis.
@rustbot

rustbot commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

In order to make coverage instrumentation HIR-aware, we need to have MIR
building inject marker statements at key locations, so that instrumentation can
accurately reconstruct the relationship between HIR constructs and MIR-level
control flow.

The most important of these is `PointKind::Expr`, which associates a HIR
expression with the start of the MIR that will evaluate that expression. There
isn't a single convenient place to inject these, but we can get close by
observing that THIR building wraps every expression in a
`thir::ExprKind::Scope` node, which also contains the `HirId` of that
expression. If we inject a marker whenever `thir::ExprKind::Scope` is lowered,
that results in an accurate marker for every expression.

Other markers are also injected at key points that don't correspond directly to
a HIR node, to avoid major regressions in coverage-map quality.
Instead of trying to heuristically recover source-code spans from MIR soup, we
can now take advantage of the `CoverageKind::Point` statements injected for
specific HIR expressions.

This is a transitional implementation that lets us remove the old MIR-soup
code, but still relies heavily on the existing span-refinement heuristics, and
does not take advantage of the full possibilities of HIR-aware coverage
instrumentation.

In order to avoid annoying never-executed spans in code that contains
assertions, a heuristic has been added that ignores any expression within the
arguments of a macro-expanded function call that returns `!`.

Some coverage tests now require `//@ min-llvm-version: 23`, due to changes in
how LLVM sorts the coverage mapping entries.
Now that we have a span for every HIR expression, we don't need these markers.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-code-coverage Area: Source-based code coverage (-Cinstrument-coverage) A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants