-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Implement support for LLVMs code coverage instrumentation #34701
Comments
I know I'd love to see this work! Do you know what it would entail in terms of what the hoops a prototype would have to jump through? Also, does this work reliably on platforms like Windows? Or is it still best on Linux and "works mostly elsewhere"? |
A prototype would need to generate the LLVM IR for the instrumentation, for that it needs to generate a mapping between source ranges and performance counters (I would start with just functions). Then it needs to generate and embed the IR in the object files. It would be a good idea to look how clang then outputs this into a file to use the same format (which is documented), and test that we can read it with llvm-cov in linux and macos (don't know about windows support but since clang is gaining full windows support if its not there it will be there soon). I think that would be enough for a prototype, from there we can move on to generating instrumentation for conditionals (match/loop/jumps/ifs...) and blocks (how many iterations was a loop body executed). We could go down to expressions, but then it would be wise to offer users a way to control how much instrumentation is generated: functions, branches, and loops (which are branches) is typically enough. We should then support skipped regions (for conditional compilation), expansions (for macros, maybe plugins), and dealing with unreachable code (there is a performance counter that is always zero for that). The meat of the work is in the source=> performance counter mapping, generating the IR, and generating the conforming output.
It works on Mac and Linux, don't know about Windows. Even if it doesn't work everywhere, this is probably the way to make it work in as much platforms as possible anyways. Clang support on windows is pretty good already and it is only getting better. |
@sujayakar's done some awesome work to prototype this on OSX at least, discovering:
That should at least get code coverage working on OSX in some respect! THis is also a pretty strong case that it may not be too hard to actually get this working for all platforms if LLVM's standard tool suite "just works". |
@alexcrichton That's gcov coverage, which is completely different from what @gnzlbg meant in this issue. The correct pass name is |
@sanxiyn is correct. I just want to add that I don't think we should implement clang-like gcov coverage generation. LLVM format can be used for many more things (e.g. profile guided optimizations), and LLVM's llvm-cov tool can generate gcov files from it. |
afaik, there's a PR open for this: #38608 |
As far as I can tell, #38608 is still gcov coverage and not instrprof. |
I believe this is now implemented as |
This is not implemented yet. LLVM coverage is different from gcov coverage, which is also different from sanitizer coverage. LLVM implements three separate coverage mechanisms. |
Would LLVM coverage ( My project (a gameboy emulator) makes very heavy usage of generics and it'd be wonderful to get code coverage working on integration tests but I'm not really sure how. |
I think good code coverage support is the single most important piece of tooling missing in Rust. When I run This information does not mean much either (just because a code-path was exercised does not mean that it was exercised for all inputs), but it would mean more than nothing, and it would make writing tests and asserting the value of test way easier. IMO adding this kind of capability to There is only another part of tooling infrastructure that would come close to this in value, and that would be an undefined behavior detector for all rust code. This might sound like a rant, but I just want to raise awareness that this is a very important issue at least for me (and from the other issues being filled, for others as well) because it directly affects the correctness of rust programs (we don't want them to only be memory safe, but also to have correct logic). Maybe if If I don't have precise auto-completion information or my source code is not perfectly formatted, well, I can live with that. But if my program has undefined behavior and I am not catching it because I am only testing 40% of my code-paths then... I am screwed. Does this make sense? |
Hi, can anyone explain that what's the difference between "LLVM coverage" ( |
I found @kennytm's answer to my question. Let me put here in case someone like me gets lost in the overwhelmed references: #44673 (comment) |
I've done a bit of prototyping on this to see what would be involved in using So, here's a vague "plan of action" (with progress so far) to enable PGO and LLVM-assisted coverage:
Add Frontend-Guided InstrumentationThis is currently implemented in its most basic form as There may be other places that it's useful to add instrumentation (e.g. adding vtable-downcast counts to enable 👍 Enhance the profile-rt libraryWith the above work complete, the generated binaries will count (in-memory) each time an instrumented code path is hit. To extract this information, the profile-rt library should walk the various 👍 Enable profile-guided optimizationWith a 👍 Create a coverage mapLLVM's coverage tooling can use a "Coverage Map" to map between instrumentation counters and blocks of code. The format of the map is well documented at http://llvm.org/docs/CoverageMappingFormat.html and there's code in LLVM (http://llvm.org/doxygen/classllvm_1_1coverage_1_1CoverageMappingWriter.html) for creating the magic format. Note that this may require Rustc manually inserting instrumentation (rather than using Note that the LLVM coverage map format supports various useful features, not well handled in GCOV-style coverage:
❌ Integrate coverage tooling into CargoCreating a Likely requires stabilising ❌ Unanswered QuestionsThere are some bits of Rust code for which coverage is non-obvious:
|
|
I think rust should behave the same as clang here.
llvm-cov at least has the option to show coverage per instantiation (or merged - per -show-instantiations). |
Great write-up @bossmc! I'll take a stab at adding LLVM coverage map generation, unless it's already being worked on elsewhere? Whilst I'm happy to try and figure out a solution on my own, a little direction from the compiler team would no doubt ensure that I arrive at something that's acceptable far sooner! Would anyone be willing to mentor this? |
I don't have time to mentor this but feel free to ask specific questions! |
@bossmc Are you sure that |
Hi, I've been encountering a problem with generating code coverage for a project that uses the crate https://github.com/BenChand/prometheus-code-cov I'm currently using I've tried with these versions of LLVM:
Thanks |
@BenChand - Thanks for putting this together! I'm taking a closer look at recent bug reports on coverage this week. For tracking purposes, can you please copy this info into a Rust bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug&template=bug_report.md Thanks! |
Hello all, Thanks for this! I've been experimenting with it for a few days, and it's working well. I've had a couple of issues, however, and I figured it was best to mention them here, just in case I should file bugs or feature requests. First, is there any way to exclude the tests themselves from the coverage report? In tests that give a message to Second, I'm having trouble specifying the locations of source files. Commands like
I'm not totally certain what the problem is, but it looks like crate source files are being put into the coverage reporting as relative paths, but llvm-cov can only match source files by absolute path. If you look at the output of
If you run with the undocumented option
I'm not certain this is what's causing the problem, but it is suspicious. Overall, though, everything is working well! Thanks so much for all the hard work on this. In case it's useful, here's the small script I've been using to generate coverage in everyday use: #!/bin/bash
IGNORE=".cargo|/rustc|.rustup"
set -euxo pipefail
export RUSTFLAGS="-Zinstrument-coverage"
rm -f target/default.profdata target/default.profdata
crate_name=$(cargo read-manifest | jq -r '.name')
test_binary=$(cargo build --tests --message-format json |
jq -r "select(.reason==\"compiler-artifact\") | select(.target.name == \"${crate_name}\") | .filenames | .[0]")
LLVM_PROFILE_FILE=target/default.profraw cargo test -v
llvm-profdata merge -sparse target/default.profraw -o target/default.profdata
subcommand=report
options=
if [[ $# -gt 0 && $1 == "show" ]]; then
subcommand=show
options='--show-instantiations --show-line-counts-or-regions'
fi
llvm-cov $subcommand \
-Xdemangler=rustfilt \
-instr-profile=target/default.profdata \
--ignore-filename-regex="$IGNORE" \
$options \
"$test_binary" |
@frazerk To get absolute paths for every file, until rust-lang/cargo#5450 or similar is merged, I use llvm-cov also accepts some option |
FYI, for contributors, or anyone interested in the implementation details, I added a section on LLVM Source-Based Code Coverage to the Rustc Dev Guide. |
Try:
(Note the trailing dot.) |
To expand on it a little more, it seems if you pass a directory as one (or more?) of the |
This worked! Adding
This didn't seem to make a difference, unfortunately. I'm pretty sure Now the only thing I'm missing is a way to exclude the tests themselves from coverage reports. It looks like |
Thanks for highlighting these use cases and solutions @frazerk, @briansmith, and @catenacyber ! @frazerk - This sounds like a possible limitation in Thanks! |
Thank you very much for this implementation! I've got it to work & the results look really good! |
It might mean that the traits aren't sufficiently instrumented, or it could mean that none of the executed code (e.g. your tests) actually exercise those traits |
That seems to be what it means, from what I've observed. |
Sorry, I think I've been unclear: I agree that in the linked cases the code really has not been executed at all, and I expected them to be not covered. |
I'm fairly sure it counts coverage for each function executed in the derived trait, but I don't know how Note, there is an open issue related to how the counters are displayed for derived traits (#79626), but that issue isn't attempting to address how to show counters for individual functions from the derived trait. Just something to be aware of. If you have a suggestion for how this could be done, please feel free to post a feature request. |
Just a few questions that occurred to me:
|
Thank you for your answer! I don't have any suggestion on how to get the derived traits displayed - if I'm not mistaken those macros would need to be expanded by rustc(?) & I doubt that the LLVM project would want that included in llvm-cov. @xd009642 Regarding the first - it's already mentioned in the nightly book & using the target directory works as output directory does work. |
@pJunger I think I have to correct my reply here. While experimenting with an unrelated issue, I happened to compile a test program that executed
However, if only one of the functions is executed, I don't think it will be obvious which function it was. |
@maboesanman All - It would be better to file separate Issues or feature requests that can be tracked, rather than adding additional requests to this Issue. (I think this issue is resolved and should be closed?) |
@richkadel where do you suggest these feature requests get made? |
@richkadel Thank you for the update! I had hoped that the |
@maboesanman - I'm not an expert on the rust-lang/rust processes, but starting with https://github.com/rust-lang/rust/issues/new/choose, I see there's an entry for "Feature Requests", and granted, it doesn't suggest filling out a form, but there's a link and a note, "Please discuss language feature requests on the internals forum." Perhaps someone from the compiler team can provide better instructions on this if needed. But the main point is, if there are feature requests, they should be new issues, so this one can be cleanly closed out. Thanks for understanding! |
@xd009642 - also, you may be interested in the discussion at #79899, and the suggestion by @vmx to have |
I think this issue can now be closed. @richkadel has done a great job implementing this under the unstable Thanks @richkadel, @tmandry and everyone else for your help on implementing this feature! 🎉 |
There are ways to more or less easily obtain code coverage information from rust binaries, some of which are in widespread use (e.g. travis-cargo + gcov + coveralls.io). However, these are either platform specific (gcov/kcov are linux only), or incomplete (coverage for documentation tests is not collected).
It would be better if rustc would be able to instrument rust binaries and tests using LLVM Coverage Instrumentation. This would allow code coverage information to work portably across the supported platforms, as well as produce reliable coverage information.
Ideally, such support would be integrated in an easy to use
cargo coverage
subcommand and the Rust Language Server, such that IDEs can use this information to guide the user while writing tests.The text was updated successfully, but these errors were encountered: