-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Working branch-level code coverage #77080
Conversation
🦈 we're gonna need a bigger tooltip 🦈 Using spanview is great for understanding how the coverage algorithm is determined and the content of the I liked having an implementation that avoided JavaScript, but considering the DOM features are fairly advanced already, some JavaScript is probably not a major ask. The downside is, given I usually view these inside VSCode panels, the tooltips would often extend beyond the frame of the HTML preview, and native tooltips have no problem with overlapping the frame (displaying above the VSCode editor window). JavaScript tooltips are going to get cutoff (require scrolling or growing the pane). Tradeoffs. |
Most of the changed files are auto-generated test results (using --bless). You may be interested in seeing the actual coverage results, and a good place to look is at the https://github.com/rust-lang/rust/pull/77080/files#diff-6502ae3b67c4afbb171d2c9525127e69 These results look right to me. You can also browse, hover, and click on the rendered spanview files by opening the github location with the additional prefix (in front of the entire URL) For example: (If there's an easier way to view HTML files from github, let me know.) |
New commit corrects for a couple of problems I just noticed in the spanview test output: Corrected a fairly recent assumption in runtest.rs that all MIR dump Updated spanview HTML title elements to match their content, replacing a |
Ready for review (no longer "draft/WIP"). Last commit: added more test examples also improved Makefiles with support for non-zero exit status and to @tmandry @wesleywiser - I added a few more examples. The results look really good. Take a look at the coverage results in src/test/run-make-fulldeps/instrument-coverage-cov-reports-link-dead-code/typical_* And the coverage spanview files for reference. Hopefully this gives you confidence as well. I know we can add more tests, and I'll work on those. Other than that, I believe the next (perhaps last) thing needed is to replace some counters with expressions, where they can be computed. Thanks! |
7c1a040
to
60a480d
Compare
I compiled |
Note to those following: Rust compiler MCP rust-lang/compiler-team#278 This PR implements branch-level coverage. Support for LLVM code coverage is nearly complete. See some examples in the main PR comment. The one known remaining feature I'll be implementing next is to replace some counters with counter expressions, where possible. |
@tmandry - I made updates based on your feedback, or in some cases, I replied to your comments here in the PR. Please take a look and see if this answers your questions. Let me know if these answers resolve your feedback/questions. Thanks! |
5d87d4f
to
9f153d1
Compare
[UPDATE: This is fixed. See the comment later in this thread.] I wanted to highlight one problem that I did notice when compiling the If you look at the closure alone, the coverage seems to be wrong. It's showing This closure was not executed. But the method So the line coverage of The coverage algorithm processes one MIR (function/closure) at a time, so even though the coverage algorithm deconflicts spans within a MIR, it doesn't have the context (I guess) to know to deconflict spans across different MIRs. (Or maybe it does, ...still thinking about that.) Another possible solution, if not at the MIR coverage generation level, would be to resolve this at the module level during coverage map generation. I think this is OK for this PR, and it is still technically correct, but I'll think about how to fix it and try to do something better in a follow-up PR, if the reviewers are OK with that. |
(It is odd that some lines have |
Update on the closure issue. I believe I have a fairly sensible solution. I'm testing it now and will update this PR. |
closure span handling is fixed! (See changes in 6ec5b4a) I added two new test cases, one for closures (to debug and resolve that problem), and one for inner items (including inner functions). The inner items worked fine without the new changes, but I hadn't tested that and didn't know until I did. |
I had an idea for improving test coverage (for a future PR). Let me know your thoughts. The tests in https://github.com/richkadel/rust/tree/llvm-coverage-counters-2/src/test/run-make-fulldeps/ for By contrast, the mir-opt test is still quite primitive. It doesn't even have Counter Expressions. I think it's a good idea to validate the MIR results from the It should be easy for me to change the The Makefiles already pull the sources from a different source directory anyway. Thoughts? |
Sorry, but one more new test. I was wondering if the latest changes would still work for structs/functions with parameterized types, and it appears they do still work: |
Note: Using this PR's Overall This is hardly scientific, and there may be outliers in this particular test. (The And the size increase is worth exploring at a more granular level as well. But at least this gives us some idea of the order of magnitude of impact. |
There should be some reduction in binary size after I replace some counters with expressions, but that will also add some time to the compiling. I think the bigger impact of expressions will be binary execution time (which I haven't performance-tested here). I'd love to find ways to reduce the compile-time impact. I don't know of any low-hanging fruit to impact that, off the top of my head. Maybe reviewers will see something. Performance profiling of the instrumentation MIR pass and coverage map generation, will be good to do eventually. I'm also not sure how much LLVM's codegen implementation of this is adding. (I'm curious how these numbers stack up against Clang.) |
Update: I believe I've addressed all feedback (to date) from @tmandry. @wesleywiser has a review in progress, so we want to wait for that before merging. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The added comments help a lot, thanks!
@@ -787,7 +793,11 @@ fn filtered_statement_span(statement: &'a Statement<'tcx>, body_span: Span) -> O | |||
// These statements have spans that are often outside the scope of the executed source code |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
General comment on this code: I think the ideal we'd like to work toward is not having special handling for each StatementKind, i.e. this feels like a workaround for issues in the SourceInfo that could maybe be fixed elsewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tmandry @wesleywiser - I don't think this is practical at the current time. There are very few special cases now. (There used to be more, but I was able to remove most of them.) The special cases that are left are (for the most part) necessary right now.
I think the only way to avoid this in the long term would require removing the spans from StorageLive
and StorageDead
(which I doubt is feasible). But that's just moving the special case handling outside of the InstrumentCoverage pass.
From a coverage perspective, these statements are problematic because their spans refer to the variable declarations, which are quite often in a completely different area in the source from where they actually need to come alive or die.
(Control Flow vs. Data Flow)
As for the other "special cases", I could remove Coverage
and Nop
. They are just included for completeness, but should not crop up here anyway.
The ForGuardBinding
special case is definitely something I would like to remove, but there's already a FIXME for that. That's there because there is a bug somewhere else in the MIR code (I guess) producing an invalid Span.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I'm actually amazed that these are the only special cases. It's way better than I expected, and I'm impressed that the MIR generally stays pretty true to the original source code.)
let right_cutoff = curr.span.hi(); | ||
let has_pre_closure_span = prev.span.lo() < right_cutoff; | ||
let has_post_closure_span = prev.span.hi() > right_cutoff; | ||
if has_pre_closure_span { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed that right around here my eyes start glossing over a bit. If you have any ideas for how to simplify this nested logic tree, I think it would help. Maybe extracting helper functions so it's all high level code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been refactoring this today. Update coming shortly. Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@tmandry - I refactored the coverage_span()
logic into a new struct/impl CoverageSpanRefinery
. Let me know if this works for you.
For the performance concerns, we could add either a representative benchmark or just a benchmarking mode with I'm not sure how keen people are on adding a new mode, cc @Mark-Simulacrum |
a8233f9
to
6f62766
Compare
@bors r=tmandry |
📌 Commit 6f62766 has been approved by |
(shoot... forgot to git add the fixed format) |
⌛ Testing commit 6f62766 with merge c04e76bea9a378b482f845ef87e11f2419be7480... |
💔 Test failed - checks-actions |
Spurious network error...
|
@bors r=tmandry |
💡 This pull request was already approved, no need to approve it again.
|
📌 Commit 6f62766 has been approved by |
@bors retry |
☀️ Test successful - checks-actions, checks-azure |
Don't create a separate "basename" when naming and opening a MIR dump file These functions were split up by rust-lang#77080, in order to support passing the dump file's “basename” (filename without extension) to the implementation of `-Zdump-mir-spanview`, so that it could be used as a page title. That flag has since been removed (rust-lang#119566), so now there's no particular reason for this code to handle the basename separately from the filename or full path. This PR therefore restores things to (roughly) how they were before rust-lang#77080.
Don't create a separate "basename" when naming and opening a MIR dump file These functions were split up by rust-lang#77080, in order to support passing the dump file's “basename” (filename without extension) to the implementation of `-Zdump-mir-spanview`, so that it could be used as a page title. That flag has since been removed (rust-lang#119566), so now there's no particular reason for this code to handle the basename separately from the filename or full path. This PR therefore restores things to (roughly) how they were before rust-lang#77080.
Don't create a separate "basename" when naming and opening a MIR dump file These functions were split up by rust-lang#77080, in order to support passing the dump file's “basename” (filename without extension) to the implementation of `-Zdump-mir-spanview`, so that it could be used as a page title. That flag has since been removed (rust-lang#119566), so now there's no particular reason for this code to handle the basename separately from the filename or full path. This PR therefore restores things to (roughly) how they were before rust-lang#77080.
Rollup merge of rust-lang#120038 - Zalathar:dump-path, r=WaffleLapkin Don't create a separate "basename" when naming and opening a MIR dump file These functions were split up by rust-lang#77080, in order to support passing the dump file's “basename” (filename without extension) to the implementation of `-Zdump-mir-spanview`, so that it could be used as a page title. That flag has since been removed (rust-lang#119566), so now there's no particular reason for this code to handle the basename separately from the filename or full path. This PR therefore restores things to (roughly) how they were before rust-lang#77080.
Add a generalized implementation for computing branch-level coverage spans.
This iteration resolves some of the challenges I had identified a few weeks ago.
I've tried to implement a solution that is general enough to work for a lot of different graphs/patterns. It's encouraging to see the results on fairly large and complex crates seem to meet my expectations. This may be a "functionally complete" implementation.
Except for bug fixes or edge cases I haven't run into yet, the next and essentially final step, I think, is to replace some Counters with CounterExpressions (where their counter values can be computed by adding or subtracting other counters/expressions).
Examples of branch-level coverage support enabled in this PR:
Examples of coverage analysis results (MIR spanview files) used to inject counters in the right
BasicBlocks
:Here is some sample coverage output after compiling a few real-world crates with the new branch-level coverage features:
r? @tmandry
FYI: @wesleywiser