Skip to content

Commit a8dfb26

Browse files
committed
Document -Zinstrument-coverage
1 parent 89fdb30 commit a8dfb26

File tree

2 files changed

+160
-0
lines changed

2 files changed

+160
-0
lines changed
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# `source-based-code-coverage`
2+
3+
The feature request for this feature is: [#34701]
4+
5+
The Major Change Proposal (MCP) for this feature is: [#278](https://github.com/rust-lang/compiler-team/issues/278)
6+
7+
------------------------
8+
9+
## Introduction
10+
11+
The Rust compiler includes two code coverage implementations:
12+
13+
* A GCC-compatible, gcov-based coverage implementation, enabled with [`-Zprofile`](profile.md), which operates on DebugInfo.
14+
* A source-based code coverage implementation, enabled with `-Zinstrument-coverage`, which uses LLVM's native coverage instrumentation to generate very precise coverage data.
15+
16+
This document describes how to enable and use the LLVM instrumentation-based coverage, via the `-Zinstrument-coverage` compiler flag.
17+
18+
## How it works
19+
20+
When `-Zinstrument-coverage` is enabled, the Rust compiler enhances rust-based libraries and binaries by:
21+
22+
* Automatically injecting calls to an LLVM intrinsic ([`llvm.instrprof.increment`]), at functions and branches in compiled code, to increment counters when conditional sections of code are executed.
23+
* Embedding additional information in the data section of each library and binary (using the [LLVM Code Coverage Mapping Format]), to define the code regions (start and end positions in the source code) being counted.
24+
25+
When running a coverage-instrumented program, the counter values are written to a `profraw` file at program termination. LLVM bundles tools that read the counter results, combine those results with the coverage map (embedded in the program binary), and generate coverage reports in multiple formats.
26+
27+
## Enable coverage profiling in the Rust compiler
28+
29+
*IMPORTANT:* Rust's coverage profiling features may not be enabled, by default. To enable them, you may need to build a version of the Rust compiler with the `profiler` feature enabled.
30+
31+
First, edit the `config.toml` file, and find the `profiler` feature entry. Uncomment it and set it to `true`:
32+
33+
```toml
34+
# Build the profiler runtime (required when compiling with options that depend
35+
# on this runtime, such as `-C profile-generate` or `-Z instrument-coverage`).
36+
profiler = true
37+
```
38+
39+
Then rebuild the Rust compiler (see [rustc-dev-guide-how-to-build-and-run]).
40+
41+
### Building the demangler
42+
43+
LLVM coverage reporting tools generate results that can include function names and other symbol references, and the raw coverage results report symbols using the compiler's "mangled" version of the symbol names, which can be difficult to interpret. To work around this issue, LLVM coverage tools also support a user-specified symbol name demangler. Rust's symbol name demangler can be built with:
44+
45+
```shell
46+
$ ./x.py build rust-demangler
47+
```
48+
49+
## Compiling with coverage enabled
50+
51+
Set the `-Zinstrument-coverage` compiler flag in order to enable LLVM source-based code coverage profiling.
52+
53+
With `cargo`, you can instrument your program binary *and* dependencies at the same time.
54+
55+
For example (if your project's Cargo.toml builds a binary by default):
56+
57+
```shell
58+
$ cd your-project
59+
$ cargo clean
60+
$ RUSTFLAGS="-Zinstrument-coverage" cargo build
61+
```
62+
63+
If `cargo` is not configured to use your `profiler`-enabled version of `rustc`, set the path explicitly via the `RUSTC` environment variable. Here is another example, using a `stage1` build of `rustc` to compile an `example` binary (from the [`json5format`](https://crates.io/crates/json5format) crate):
64+
65+
```shell
66+
$ RUSTC=$HOME/rust/build/x86_64-unknown-linux-gnu/stage1/bin/rustc \
67+
RUSTFLAGS="-Zinstrument-coverage" \
68+
cargo build --example formatjson5
69+
```
70+
71+
## Running the instrumented binary to generate raw coverage profiling data
72+
73+
In the previous example, `cargo` generated the coverage-instrumented binary `formatjson5`:
74+
75+
```shell
76+
$ echo "{some: 'thing'}" | target/debug/examples/formatjson5 -
77+
```
78+
```json5
79+
{
80+
some: 'thing',
81+
}
82+
```
83+
84+
After running this program, a new file, `default.profraw`, should be in the current working directory. It's often preferable to set a specific file name or path. You can change the output file using the environment variable `LLVM_PROFILE_FILE`:
85+
86+
87+
```shell
88+
$ echo "{some: 'thing'}" \
89+
| LLVM_PROFILE_FILE="formatjson5.profraw" target/debug/examples/formatjson5 -
90+
...
91+
$ ls formatjson5.profraw
92+
formatjson5.profraw
93+
```
94+
95+
If `LLVM_PROFILE_FILE` contains a path to a non-existent directory, the missing directory structure will be created. Additionally, the following special pattern strings are rewritten:
96+
97+
* `%p` - The process ID.
98+
* `%h` - The hostname of the machine running the program.
99+
* `%t` - The value of the TMPDIR environment variable.
100+
* `%Nm` - the instrumented binary’s signature: The runtime creates a pool of N raw profiles, used for on-line profile merging. The runtime takes care of selecting a raw profile from the pool, locking it, and updating it before the program exits. `N` must be between `1` and `9`, and defaults to `1` if omitted (with simply `%m`).
101+
* `%c` - Does not add anything to the filename, but enables a mode (on some platforms, including Darwin) in which profile counter updates are continuously synced to a file. This means that if the instrumented program crashes, or is killed by a signal, perfect coverage information can still be recovered.
102+
103+
## Creating coverage reports
104+
105+
LLVM's tools to process coverage data and coverage maps have some version dependencies. If you encounter a version mismatch, try updating your LLVM tools, or use the LLVM tools bundled with the same Rust distrubition used to rebuild the Rust compiler (as shown in the following examples).
106+
107+
Raw profiles have to be indexed before they can be used to generate coverage reports. This is done using [`llvm-profdata merge`] (which can combine multiple raw profiles and index them at the same time):
108+
109+
```shell
110+
$ $HOME/rust/build/x86_64-unknown-linux-gnu/llvm/bin/llvm-profdata merge \
111+
-sparse formatjson5.profraw -o formatjson5.profdata
112+
```
113+
114+
Finally, the `.profdata` file is used, in combination with the coverage map (from the program binary) to generate coverage reports using [`llvm-cov report`]--for a coverage summaries--and [`llvm-cov show`]--to see detailed coverage of lines and regions (character ranges), overlaid on the original source code.
115+
116+
These commands have several display and filtering options. For example:
117+
118+
```shell
119+
$ $HOME/rust/build/x86_64-unknown-linux-gnu/llvm/bin/llvm-cov show \
120+
-instr-profile=formatjson5.profdata \
121+
target/debug/examples/formatjson5 \
122+
-show-line-counts-or-regions \
123+
-Xdemangler=$HOME/rust/build/x86_64-unknown-linux-gnu/stage0-tools-bin/rust-demangler \
124+
-show-instantiations \
125+
-name=add_quoted_string
126+
```
127+
128+
<img alt="Screenshot of sample `llvm-cov show` result, for function add_quoted_string" src="img/llvm-cov-show-01.png" class="center"/>
129+
<br/>
130+
<br/>
131+
132+
Some of the more notable options in this example include:
133+
134+
* `--instr-profile=<path-to-file>.profdata` - the location of the `.profdata` file created by `llvm-profdata merge`
135+
* `target/debug/examples/formatjson5` - the binary that generated the coverage profiling data (originally as a `.profraw` file)
136+
* `--Xdemangler=<path-to>/rust-demangler` - the location of the `rust-demangler` tool
137+
* `--name=<exact-function-name>` - to show coverage for a specific function (or, consider using another filter option, such as `--name-regex=<pattern>`)
138+
139+
## Interpreting reports
140+
141+
There are four statistics tracked in a coverage summary:
142+
143+
* Function coverage is the percentage of functions that have been executed at least once. A function is considered to be executed if any of its instantiations are executed.
144+
* Instantiation coverage is the percentage of function instantiations that have been executed at least once. Generic functions and functions generated from macros are two kinds of functions that may have multiple instantiations.
145+
* Line coverage is the percentage of code lines that have been executed at least once. Only executable lines within function bodies are considered to be code lines.
146+
* Region coverage is the percentage of code regions that have been executed at least once. A code region may span multiple lines: for example, in a large function body with no control flow. In other cases, a single line can contain multiple code regions: `return x || (y && z)` has countable code regions for `x` (which may resolve the expression, if `x` is `true`), `|| (y && z)` (executed only if `x` was `false`), and `return` (executed in either situation).
147+
148+
Of these four statistics, function coverage is usually the least granular while region coverage is the most granular. The project-wide totals for each statistic are listed in the summary.
149+
150+
## Other references
151+
152+
Rust's implementation and workflow for source-based code coverage is based on the same library and tools used to implement [source-based code coverage in Clang](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html). (This document is partially based on the Clang guide.)
153+
154+
[#34701]: https://github.com/rust-lang/rust/issues/34701
155+
[`llvm.instrprof.increment`]: https://llvm.org/docs/LangRef.html#llvm-instrprof-increment-intrinsic
156+
[LLVM Code Coverage Mapping Format]: https://llvm.org/docs/CoverageMappingFormat.html
157+
[rustc-dev-guide-how-to-build-and-run]: https://rustc-dev-guide.rust-lang.org/building/how-to-build-and-run.html
158+
[`llvm-profdata merge`]: https://llvm.org/docs/CommandGuide/llvm-profdata.html#profdata-merge
159+
[`llvm-cov report`]: https://llvm.org/docs/CommandGuide/llvm-cov.html#llvm-cov-report
160+
[`llvm-cov show`]: https://llvm.org/docs/CommandGuide/llvm-cov.html#llvm-cov-show

0 commit comments

Comments
 (0)