minicov/lib.rs
1//! This crate provides code coverage and profile-guided optimization (PGO) support
2//! for `no_std` and embedded programs.
3//!
4//! This is done through a modified version of the LLVM profiling runtime (normally
5//! part of compiler-rt) from which all dependencies on libc have been removed.
6//!
7//! All types of instrumentation using the LLVM profiling runtime are supported:
8//! - Rust code coverage with `-Cinstrument-coverage`.
9//! - Rust profile-guided optimization with `-Cprofile-generate`.
10//! - Clang code coverage with `-fprofile-instr-generate -fcoverage-mapping`.
11//! - Clang profile-guided optimization with `-fprofile-instr-generate`.
12//! - Clang LLVM IR profile-guided optimization with `-fprofile-generate`.
13//!
14//! Note that to profile both C and Rust code at the same time you must use Clang
15//! with the same LLVM version as the LLVM used by rustc. You can pass these flags
16//! to C code compiled with the `cc` crates using [environment variables].
17//!
18//! [environment variables]: https://github.com/rust-lang/cc-rs#external-configuration-via-environment-variables
19//!
20//! ## Usage
21//!
22//! Note: This crate requires a recent nightly compiler.
23//!
24//! 1. Ensure that the following environment variables are set up:
25//!
26//! ```sh
27//! export RUSTFLAGS="-Cinstrument-coverage -Zno-profiler-runtime"
28//! ```
29//!
30//! Note that these flags also apply to build-dependencies and proc
31//! macros by default. This can be worked around by explicitly
32//! specifying a target when invoking cargo:
33//!
34//! ```sh
35//! # Applies RUSTFLAGS to everything
36//! cargo build
37//!
38//! # Doesn't apply RUSTFLAGS to build dependencies and proc macros
39//! cargo build --target x86_64-unknown-linux-gnu
40//! ```
41//!
42//! 2. Add the `minicov` crate as a dependency to your program:
43//!
44//! ```toml
45//! [dependencies]
46//! minicov = "0.3"
47//! ```
48//!
49//! 3. Before your program exits, call `minicov::capture_coverage` with a sink (such
50//! as `Vec<u8>`) and then dump its contents to a file with the `.profraw` extension:
51//!
52//! ```ignore
53//! fn main() {
54//! // ...
55//!
56//! let mut coverage = vec![];
57//! unsafe {
58//! // Note that this function is not thread-safe! Use a lock if needed.
59//! minicov::capture_coverage(&mut coverage).unwrap();
60//! }
61//! std::fs::write("output.profraw", coverage).unwrap();
62//! }
63//! ```
64//!
65//! If your program is running on a different system than your build system then
66//! you will need to transfer this file back to your build system.
67//!
68//! Sinks must implement the `CoverageWriter` trait. If the default `alloc` feature
69//! is enabled then an implementation is provided for `Vec<u8>`.
70//!
71//! 4. Use a tool such as [grcov] or llvm-cov to generate a human-readable coverage
72//! report:
73//!
74//! ```sh
75//! grcov output.profraw -b ./target/debug/my_program -s . -t html -o cov_report
76//! ```
77//!
78//! [grcov]: https://github.com/mozilla/grcov
79//!
80//! ## Profile-guided optimization
81//!
82//! The steps for profile-guided optimzation are similar. The only difference is the
83//! flags passed in `RUSTFLAGS`:
84//!
85//! ```sh
86//! # First run to generate profiling information.
87//! #
88//! # The filename passed to profile-generate doesn't matter, but cc-rs complains
89//! # if it is not provided.
90//! export RUSTFLAGS="-Cprofile-generate=output.profraw -Zno-profiler-runtime"
91//! cargo run --target x86_64-unknown-linux-gnu --release
92//!
93//! # Post-process the profiling information.
94//! # The rust-profdata tool comes from cargo-binutils.
95//! rust-profdata merge -o output.profdata output.profraw
96//!
97//! # Optimized build using PGO. minicov is not needed in this step.
98//! export RUSTFLAGS="-Cprofile-use=output.profdata"
99//! cargo build --target x86_64-unknown-linux-gnu --release
100//! ```
101
102#![no_std]
103#![warn(missing_docs)]
104#![warn(rust_2018_idioms)]
105
106#[cfg(feature = "alloc")]
107extern crate alloc;
108
109#[cfg(feature = "alloc")]
110use alloc::vec::Vec;
111#[cfg(feature = "alloc")]
112use core::alloc::Layout;
113use core::{fmt, slice};
114
115#[allow(non_snake_case)]
116#[repr(C)]
117struct ProfDataIOVec {
118 Data: *mut u8,
119 ElmSize: usize,
120 NumElm: usize,
121 UseZeroPadding: i32,
122}
123
124#[allow(non_snake_case)]
125#[repr(C)]
126struct ProfDataWriter {
127 Write:
128 unsafe extern "C" fn(This: *mut ProfDataWriter, *mut ProfDataIOVec, NumIOVecs: u32) -> u32,
129 WriterCtx: *mut u8,
130}
131
132// Opaque type for our purposes.
133enum VPDataReaderType {}
134
135extern "C" {
136 fn __llvm_profile_begin_counters() -> *const u8;
137 fn __llvm_profile_end_counters() -> *const u8;
138 fn __llvm_profile_reset_counters();
139 fn __llvm_profile_merge_from_buffer(profile: *const u8, size: u64) -> i32;
140 fn __llvm_profile_check_compatibility(profile: *const u8, size: u64) -> i32;
141 fn __llvm_profile_get_version() -> u64;
142 fn lprofWriteData(
143 Writer: *mut ProfDataWriter,
144 VPDataReader: *mut VPDataReaderType,
145 SkipNameDataWrite: i32,
146 ) -> i32;
147 fn lprofGetVPDataReader() -> *mut VPDataReaderType;
148 fn lprofGetLoadModuleSignature() -> u64;
149}
150
151const INSTR_PROF_RAW_VERSION: u64 = 10;
152const VARIANT_MASKS_ALL: u64 = 0xffffffff00000000;
153
154// On some target rustc will insert an artificial dependency on the
155// __llvm_profile_runtime symbol to ensure the static initializer from LLVM's
156// profiling runtime is pulled in by the linker. We don't need any runtime
157// initialization so we just provide the symbol here.
158#[no_mangle]
159static __llvm_profile_runtime: u8 = 0;
160
161// Memory allocation functions used by value profiling. If the "alloc" feature
162// is disabled then value profiling will also be disabled.
163#[cfg(feature = "alloc")]
164#[no_mangle]
165unsafe fn minicov_alloc_zeroed(size: usize, align: usize) -> *mut u8 {
166 alloc::alloc::alloc_zeroed(Layout::from_size_align(size, align).unwrap())
167}
168#[cfg(feature = "alloc")]
169#[no_mangle]
170unsafe extern "C" fn minicov_dealloc(ptr: *mut u8, size: usize, align: usize) {
171 alloc::alloc::dealloc(ptr, Layout::from_size_align(size, align).unwrap())
172}
173#[cfg(not(feature = "alloc"))]
174#[no_mangle]
175unsafe fn minicov_alloc_zeroed(_size: usize, _align: usize) -> *mut u8 {
176 core::ptr::null_mut()
177}
178#[cfg(not(feature = "alloc"))]
179#[no_mangle]
180unsafe extern "C" fn minicov_dealloc(_ptr: *mut u8, _size: usize, _align: usize) {}
181
182/// Sink into which coverage data can be written.
183///
184/// A default implementation for `Vec<u8>` is provided,
185pub trait CoverageWriter {
186 /// Writes the given bytes to the sink.
187 ///
188 /// This method should return an error if all bytes could not be written to
189 /// the sink.
190 fn write(&mut self, data: &[u8]) -> Result<(), CoverageWriteError>;
191}
192
193#[cfg(feature = "alloc")]
194impl CoverageWriter for Vec<u8> {
195 fn write(&mut self, data: &[u8]) -> Result<(), CoverageWriteError> {
196 self.extend_from_slice(data);
197 Ok(())
198 }
199}
200
201/// Callback function passed to `lprofWriteData`.
202unsafe extern "C" fn write_callback<Writer: CoverageWriter>(
203 this: *mut ProfDataWriter,
204 iovecs: *mut ProfDataIOVec,
205 num_iovecs: u32,
206) -> u32 {
207 let writer = &mut *((*this).WriterCtx as *mut Writer);
208 for iov in slice::from_raw_parts(iovecs, num_iovecs as usize) {
209 let len = iov.ElmSize * iov.NumElm;
210 if iov.Data.is_null() {
211 // Pad with zero bytes.
212 let zero = [0; 16];
213 let mut remaining = len;
214 while remaining != 0 {
215 let data = &zero[..usize::min(zero.len(), remaining)];
216 if writer.write(data).is_err() {
217 return 1;
218 }
219 remaining -= data.len();
220 }
221 } else {
222 let data = slice::from_raw_parts(iov.Data, len);
223 if writer.write(data).is_err() {
224 return 1;
225 }
226 }
227 }
228 0
229}
230
231/// Checks that the instrumented binary uses the same profiling data format as
232/// the LLVM profiling runtime.
233fn check_version() {
234 let version = unsafe { __llvm_profile_get_version() & !VARIANT_MASKS_ALL };
235 assert_eq!(
236 version, INSTR_PROF_RAW_VERSION,
237 "Runtime and instrumentation version mismatch"
238 );
239}
240
241/// Returns whether the current binary was built with coverage instrumentation
242/// enabled.
243///
244/// If this returns `false` then the functions in this file will still function
245/// correctly but will emit empty coverage data.
246pub fn coverage_enabled() -> bool {
247 unsafe { __llvm_profile_begin_counters() != __llvm_profile_end_counters() }
248}
249
250/// Captures the coverage data for the current program and writes it into the
251/// given sink.
252///
253/// The data should be saved to a file with the `.profraw` extension, which can
254/// then be processed using the `llvm-profdata` and `llvm-cov` tools.
255///
256/// You should call `reset_coverage` afterwards if you intend to continue
257/// running the program so that future coverage can be merged with the returned
258/// captured coverage.
259///
260/// # Safety
261///
262/// This function is not thread-safe and should not be concurrently called from
263/// multiple threads.
264pub unsafe fn capture_coverage<Writer: CoverageWriter>(
265 writer: &mut Writer,
266) -> Result<(), CoverageWriteError> {
267 check_version();
268
269 let mut prof_writer = ProfDataWriter {
270 Write: write_callback::<Writer>,
271 WriterCtx: writer as *mut Writer as *mut u8,
272 };
273 let res = lprofWriteData(&mut prof_writer, lprofGetVPDataReader(), 0);
274 if res == 0 {
275 Ok(())
276 } else {
277 Err(CoverageWriteError)
278 }
279}
280
281/// Error type returned when trying to merge incompatible coverage data.
282///
283/// This typically happens if the coverage data comes from a different binary.
284#[derive(Copy, Clone, Debug)]
285pub struct IncompatibleCoverageData;
286impl fmt::Display for IncompatibleCoverageData {
287 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288 f.write_str("incompatible coverage data")
289 }
290}
291
292/// Error while trying to write coverage data.
293///
294/// This only happens if the `CoverageWriter` implementation returns an error.
295#[derive(Copy, Clone, Debug)]
296pub struct CoverageWriteError;
297impl fmt::Display for CoverageWriteError {
298 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299 f.write_str("error while writing coverage data")
300 }
301}
302
303/// Merges previously dumped coverage data into the coverage counters.
304///
305/// This should be called prior to dumping if coverage data from a previous run
306/// already exists and should be merged with instead of overwritten.
307///
308/// # Safety
309///
310/// This function is not thread-safe and should not be concurrently called from
311/// multiple threads.
312pub unsafe fn merge_coverage(data: &[u8]) -> Result<(), IncompatibleCoverageData> {
313 check_version();
314
315 if __llvm_profile_check_compatibility(data.as_ptr(), data.len() as u64) == 0
316 && __llvm_profile_merge_from_buffer(data.as_ptr(), data.len() as u64) == 0
317 {
318 Ok(())
319 } else {
320 Err(IncompatibleCoverageData)
321 }
322}
323
324/// Resets all coverage counters in the program to zero.
325///
326/// This function should be called after a process forks to avoid recording
327/// coverage data for the parent process twice.
328///
329/// You should also call this after calling `capture_coverage` if you intend to
330/// continue running with the intention of merging with the captured coverage
331/// later.
332pub fn reset_coverage() {
333 check_version();
334
335 unsafe {
336 __llvm_profile_reset_counters();
337 }
338}
339
340/// Returns the profile header "signature" value associated with the current
341/// executable or shared library.
342///
343/// The signature value can be used to for a profile name that is unique to
344/// this load module so that it does not collide with profiles from other
345/// binaries. It also allows shared libraries to dump merged profile data into
346/// its own profile file.
347pub fn module_signature() -> u64 {
348 unsafe { lprofGetLoadModuleSignature() }
349}