Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

coverage: Completely overhaul counter assignment, using node-flow graphs #135481

Merged
merged 4 commits into from
Jan 17, 2025

Conversation

Zalathar
Copy link
Contributor

The existing code for choosing where to put physical counter-increments gets the job done, but is very ad-hoc and hard to modify without introducing tricky regressions.

This PR replaces all of that with a more principled approach, based on the algorithm described in "Optimal measurement points for program frequency counts" (Knuth & Stevenson, 1973).


We start by ensuring that our graph has “balanced flow”, i.e. each node's flow (execution count) is equal to the sum of all its in-edge flows, and equal to the sum of all its out-edge flows. That isn't naturally true of control-flow graphs, so we introduce a wrapper type BalancedFlowGraph to fix that by introducing synthetic nodes and edges as needed.

Once our graph has balanced flow, the next step is to create another view of that graph in which each node's successors have all been merged into one “supernode”. Consequently, each node's out-edges can be coalesced into a single out-edge to one of those supernodes. Because of the balanced-flow property, the flow of that coalesced edge is equal to the flow of the original node.

Having expressed all of our node flows as edge flows, we can then analyze node flows using techniques for analyzing edge flows. We incrementally build a spanning tree over the merged supernodes, such that each new edge in the spanning tree represents a node whose flow can be computed from that of other nodes.

When this is done, we end up with a list of “counter terms” for each node, describing which nodes need physical counters, and how the remaining nodes can have their flow calculated by adding and subtracting those physical counters.


The re-blessed coverage tests show that this results in modest or major improvements for our test programs. Some tests need fewer physical counters, some tests need fewer expression nodes for the same number of physical counters, and some tests show striking reductions in both.

This is similar to the existing `union`, except that bits in the RHS are
negated before being incorporated into the LHS.

Currently only `DenseBitSet` is supported. Supporting other bitset types is
possible, but non-trivial, and currently isn't needed.
@Zalathar Zalathar added the A-code-coverage Area: Source-based code coverage (-Cinstrument-coverage) label Jan 14, 2025
@rustbot
Copy link
Collaborator

rustbot commented Jan 14, 2025

r? @davidtwco

rustbot has assigned @davidtwco.
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

@rustbot rustbot added 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. labels Jan 14, 2025
@rustbot
Copy link
Collaborator

rustbot commented Jan 14, 2025

Some changes occurred to MIR optimizations

cc @rust-lang/wg-mir-opt

@rust-log-analyzer

This comment has been minimized.

@rustbot
Copy link
Collaborator

rustbot commented Jan 15, 2025

Some changes occurred to MIR optimizations

cc @rust-lang/wg-mir-opt

@Zalathar
Copy link
Contributor Author

One of the nice things about this approach, compared to the status-quo, is that we only need to instrument control-flow nodes, not edges.

The current code instruments control-flow edges by inserting extra MIR blocks, and then adding counters to those blocks. That works, but it's nice to be able to only instrument nodes, so that the MIR structure isn't disturbed.

Copy link
Contributor

@oli-obk oli-obk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My third time reading the code in two days. Unfortunately I fail to build a full model of this change in my head. All the code is doing what the comments describe, and the code makes sense in small parts. I have absolutely no idea if it's doing the algorithm from the paper.

Uh... r=me with nits unless you want a reviewer that will actually grok the whole thing.

@Zalathar
Copy link
Contributor Author

Added a FIXME, and made yank_to_spantree_root iterative (diff).

This avoids having to worry about stack space when traversing very long
spantree paths, e.g. when instrumenting a long sequence of if/else statements.
@oli-obk oli-obk assigned oli-obk and unassigned davidtwco Jan 16, 2025
@oli-obk
Copy link
Contributor

oli-obk commented Jan 16, 2025

@bors r+

@bors
Copy link
Contributor

bors commented Jan 16, 2025

📌 Commit 9e9ca8e has been approved by oli-obk

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jan 16, 2025
@Zalathar
Copy link
Contributor Author

Ah whoops, just fixed a typo (diff) and didn't see that this had been approved.

@bors r=oli-obk

@bors
Copy link
Contributor

bors commented Jan 16, 2025

📌 Commit 6eabf03 has been approved by oli-obk

It is now in the queue for this repository.

bors added a commit to rust-lang-ci/rust that referenced this pull request Jan 16, 2025
…iaskrgr

Rollup of 7 pull requests

Successful merges:

 - rust-lang#134754 (Implement `use` associated items of traits)
 - rust-lang#135481 (coverage: Completely overhaul counter assignment, using node-flow graphs)
 - rust-lang#135504 (Allow coercing safe-to-call target_feature functions to safe fn pointers)
 - rust-lang#135561 (Update docs for `-Clink-dead-code` to discourage its use)
 - rust-lang#135574 (ci: mirror ubuntu:22.04 to ghcr.io)
 - rust-lang#135585 (resolve symlinks of LLVM tool binaries before copying them)
 - rust-lang#135588 (Add license-metadata.json to rustc-src tarball.)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit 4aae8d1 into rust-lang:master Jan 17, 2025
6 checks passed
@rustbot rustbot added this to the 1.86.0 milestone Jan 17, 2025
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Jan 17, 2025
Rollup merge of rust-lang#135481 - Zalathar:node-flow, r=oli-obk

coverage: Completely overhaul counter assignment, using node-flow graphs

The existing code for choosing where to put physical counter-increments gets the job done, but is very ad-hoc and hard to modify without introducing tricky regressions.

This PR replaces all of that with a more principled approach, based on the algorithm described in "Optimal measurement points for program frequency counts" (Knuth & Stevenson, 1973).

---

We start by ensuring that our graph has “balanced flow”, i.e. each node's flow (execution count) is equal to the sum of all its in-edge flows, and equal to the sum of all its out-edge flows. That isn't naturally true of control-flow graphs, so we introduce a wrapper type `BalancedFlowGraph` to fix that by introducing synthetic nodes and edges as needed.

Once our graph has balanced flow, the next step is to create another view of that graph in which each node's successors have all been merged into one “supernode”. Consequently, each node's out-edges can be coalesced into a single out-edge to one of those supernodes. Because of the balanced-flow property, the flow of that coalesced edge is equal to the flow of the original node.

Having expressed all of our node flows as edge flows, we can then analyze node flows using techniques for analyzing edge flows. We incrementally build a spanning tree over the merged supernodes, such that each new edge in the spanning tree represents a node whose flow can be computed from that of other nodes.

When this is done, we end up with a list of “counter terms” for each node, describing which nodes need physical counters, and how the remaining nodes can have their flow calculated by adding and subtracting those physical counters.

---

The re-blessed coverage tests show that this results in modest or major improvements for our test programs. Some tests need fewer physical counters, some tests need fewer expression nodes for the same number of physical counters, and some tests show striking reductions in both.
@Zalathar Zalathar deleted the node-flow branch January 17, 2025 01:29
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Jan 18, 2025
…ler-errors

coverage: Clean up a few things after the counters overhaul

Follow-up to rust-lang#135481. No functional change; this is mostly just deleting or moving code.
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Jan 19, 2025
…ler-errors

coverage: Clean up a few things after the counters overhaul

Follow-up to rust-lang#135481. No functional change; this is mostly just deleting or moving code.
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Jan 19, 2025
Rollup merge of rust-lang#135680 - Zalathar:counters-cleanup, r=compiler-errors

coverage: Clean up a few things after the counters overhaul

Follow-up to rust-lang#135481. No functional change; this is mostly just deleting or moving code.
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Jan 24, 2025
coverage: Prepare for upcoming changes to counter creation

This is a collection of smaller changes to coverage instrumentation code that have been extracted from a larger PR that I'm still working on, in order to hopefully make review easier.

Each individual change should hopefully be mostly self-explanatory. One of the big goals of the upcoming PR will be to defer certain parts of counter-creation until codegen, via the query system, so that ends up being a recurring theme in these changes. Several of the changes are follow-ups to rust-lang#135481.

There should be no observable change in compiler output.
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Jan 24, 2025
coverage: Prepare for upcoming changes to counter creation

This is a collection of smaller changes to coverage instrumentation code that have been extracted from a larger PR that I'm still working on, in order to hopefully make review easier.

Each individual change should hopefully be mostly self-explanatory. One of the big goals of the upcoming PR will be to defer certain parts of counter-creation until codegen, via the query system, so that ends up being a recurring theme in these changes. Several of the changes are follow-ups to rust-lang#135481.

There should be no observable change in compiler output.
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Jan 24, 2025
Rollup merge of rust-lang#135873 - Zalathar:be-prepared, r=oli-obk

coverage: Prepare for upcoming changes to counter creation

This is a collection of smaller changes to coverage instrumentation code that have been extracted from a larger PR that I'm still working on, in order to hopefully make review easier.

Each individual change should hopefully be mostly self-explanatory. One of the big goals of the upcoming PR will be to defer certain parts of counter-creation until codegen, via the query system, so that ends up being a recurring theme in these changes. Several of the changes are follow-ups to rust-lang#135481.

There should be no observable change in compiler output.
GuillaumeGomez added a commit to GuillaumeGomez/rust that referenced this pull request Jan 27, 2025
Incorporate `iter_nodes` into `graph::DirectedGraph`

This helper method iterates over all node IDs in the dense range `0..num_nodes`.

In practice, we have a lot of graph-algorithm code that already assumes that nodes are densely numbered, by using `num_nodes` to allocate per-node indexed data structures. So I don't think this is actually a substantial change to the de-facto semantics of `graph::DirectedGraph`.

---

Resolves a FIXME from rust-lang#135481.
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Jan 27, 2025
Rollup merge of rust-lang#136082 - Zalathar:iter-nodes, r=oli-obk

Incorporate `iter_nodes` into `graph::DirectedGraph`

This helper method iterates over all node IDs in the dense range `0..num_nodes`.

In practice, we have a lot of graph-algorithm code that already assumes that nodes are densely numbered, by using `num_nodes` to allocate per-node indexed data structures. So I don't think this is actually a substantial change to the de-facto semantics of `graph::DirectedGraph`.

---

Resolves a FIXME from rust-lang#135481.
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) S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. 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.

6 participants