-
Notifications
You must be signed in to change notification settings - Fork 13.6k
Generating the coverage map #74091
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
Generating the coverage map #74091
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,274 @@ | ||
use crate::llvm; | ||
|
||
use crate::common::CodegenCx; | ||
use crate::coverageinfo; | ||
|
||
use log::debug; | ||
use rustc_codegen_ssa::coverageinfo::map::*; | ||
use rustc_codegen_ssa::traits::{BaseTypeMethods, ConstMethods, MiscMethods}; | ||
use rustc_data_structures::fx::FxHashMap; | ||
use rustc_llvm::RustString; | ||
use rustc_middle::ty::Instance; | ||
use rustc_middle::{bug, mir}; | ||
|
||
use std::collections::BTreeMap; | ||
use std::ffi::CString; | ||
use std::path::PathBuf; | ||
|
||
// FIXME(richkadel): Complete all variations of generating and exporting the coverage map to LLVM. | ||
// The current implementation is an initial foundation with basic capabilities (Counters, but not | ||
// CounterExpressions, etc.). | ||
|
||
/// Generates and exports the Coverage Map. | ||
/// | ||
/// This Coverage Map complies with Coverage Mapping Format version 3 (zero-based encoded as 2), | ||
/// as defined at [LLVM Code Coverage Mapping Format](https://github.com/rust-lang/llvm-project/blob/llvmorg-8.0.0/llvm/docs/CoverageMappingFormat.rst#llvm-code-coverage-mapping-format) | ||
/// and published in Rust's current (July 2020) fork of LLVM. This version is supported by the | ||
/// LLVM coverage tools (`llvm-profdata` and `llvm-cov`) bundled with Rust's fork of LLVM. | ||
/// | ||
/// Consequently, Rust's bundled version of Clang also generates Coverage Maps compliant with | ||
/// version 3. Clang's implementation of Coverage Map generation was referenced when implementing | ||
/// this Rust version, and though the format documentation is very explicit and detailed, some | ||
/// undocumented details in Clang's implementation (that may or may not be important) were also | ||
/// replicated for Rust's Coverage Map. | ||
pub fn finalize<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>) { | ||
let mut coverage_writer = CoverageMappingWriter::new(cx); | ||
|
||
let function_coverage_map = cx.coverage_context().take_function_coverage_map(); | ||
|
||
// Encode coverage mappings and generate function records | ||
let mut function_records = Vec::<&'ll llvm::Value>::new(); | ||
let coverage_mappings_buffer = llvm::build_byte_buffer(|coverage_mappings_buffer| { | ||
for (instance, function_coverage) in function_coverage_map.into_iter() { | ||
if let Some(function_record) = coverage_writer.write_function_mappings_and_record( | ||
instance, | ||
function_coverage, | ||
coverage_mappings_buffer, | ||
) { | ||
function_records.push(function_record); | ||
} | ||
} | ||
}); | ||
|
||
// Encode all filenames covered in this module, ordered by `file_id` | ||
let filenames_buffer = llvm::build_byte_buffer(|filenames_buffer| { | ||
coverageinfo::write_filenames_section_to_buffer( | ||
&coverage_writer.filenames, | ||
filenames_buffer, | ||
); | ||
}); | ||
|
||
if coverage_mappings_buffer.len() > 0 { | ||
// Generate the LLVM IR representation of the coverage map and store it in a well-known | ||
// global constant. | ||
coverage_writer.write_coverage_map( | ||
function_records, | ||
filenames_buffer, | ||
coverage_mappings_buffer, | ||
); | ||
} | ||
} | ||
|
||
struct CoverageMappingWriter<'a, 'll, 'tcx> { | ||
cx: &'a CodegenCx<'ll, 'tcx>, | ||
filenames: Vec<CString>, | ||
filename_to_index: FxHashMap<CString, u32>, | ||
} | ||
|
||
impl<'a, 'll, 'tcx> CoverageMappingWriter<'a, 'll, 'tcx> { | ||
fn new(cx: &'a CodegenCx<'ll, 'tcx>) -> Self { | ||
Self { cx, filenames: Vec::new(), filename_to_index: FxHashMap::<CString, u32>::default() } | ||
} | ||
|
||
/// For the given function, get the coverage region data, stream it to the given buffer, and | ||
/// then generate and return a new function record. | ||
fn write_function_mappings_and_record( | ||
&mut self, | ||
instance: Instance<'tcx>, | ||
mut function_coverage: FunctionCoverage, | ||
coverage_mappings_buffer: &RustString, | ||
) -> Option<&'ll llvm::Value> { | ||
let cx = self.cx; | ||
let coverageinfo: &mir::CoverageInfo = cx.tcx.coverageinfo(instance.def_id()); | ||
debug!( | ||
"Generate coverage map for: {:?}, num_counters: {}, num_expressions: {}", | ||
instance, coverageinfo.num_counters, coverageinfo.num_expressions | ||
); | ||
debug_assert!(coverageinfo.num_counters > 0); | ||
|
||
let regions_in_file_order = function_coverage.regions_in_file_order(cx.sess().source_map()); | ||
if regions_in_file_order.len() == 0 { | ||
return None; | ||
} | ||
|
||
// Stream the coverage mapping regions for the function (`instance`) to the buffer, and | ||
// compute the data byte size used. | ||
let old_len = coverage_mappings_buffer.len(); | ||
self.regions_to_mappings(regions_in_file_order, coverage_mappings_buffer); | ||
let mapping_data_size = coverage_mappings_buffer.len() - old_len; | ||
debug_assert!(mapping_data_size > 0); | ||
|
||
let mangled_function_name = cx.tcx.symbol_name(instance).to_string(); | ||
let name_ref = coverageinfo::compute_hash(&mangled_function_name); | ||
let function_source_hash = function_coverage.source_hash(); | ||
|
||
// Generate and return the function record | ||
let name_ref_val = cx.const_u64(name_ref); | ||
let mapping_data_size_val = cx.const_u32(mapping_data_size as u32); | ||
let func_hash_val = cx.const_u64(function_source_hash); | ||
Some(cx.const_struct( | ||
&[name_ref_val, mapping_data_size_val, func_hash_val], | ||
/*packed=*/ true, | ||
)) | ||
} | ||
|
||
/// For each coverage region, extract its coverage data from the earlier coverage analysis. | ||
/// Use LLVM APIs to convert the data into buffered bytes compliant with the LLVM Coverage | ||
/// Mapping format. | ||
fn regions_to_mappings( | ||
&mut self, | ||
regions_in_file_order: BTreeMap<PathBuf, BTreeMap<CoverageLoc, (usize, CoverageKind)>>, | ||
coverage_mappings_buffer: &RustString, | ||
) { | ||
let mut virtual_file_mapping = Vec::new(); | ||
let mut mapping_regions = coverageinfo::SmallVectorCounterMappingRegion::new(); | ||
let mut expressions = coverageinfo::SmallVectorCounterExpression::new(); | ||
|
||
for (file_id, (file_path, file_coverage_regions)) in | ||
regions_in_file_order.into_iter().enumerate() | ||
{ | ||
let file_id = file_id as u32; | ||
let filename = CString::new(file_path.to_string_lossy().to_string()) | ||
.expect("null error converting filename to C string"); | ||
debug!(" file_id: {} = '{:?}'", file_id, filename); | ||
let filenames_index = match self.filename_to_index.get(&filename) { | ||
Some(index) => *index, | ||
None => { | ||
let index = self.filenames.len() as u32; | ||
self.filenames.push(filename.clone()); | ||
self.filename_to_index.insert(filename, index); | ||
index | ||
} | ||
}; | ||
virtual_file_mapping.push(filenames_index); | ||
|
||
let mut mapping_indexes = vec![0 as u32; file_coverage_regions.len()]; | ||
for (mapping_index, (region_id, _)) in file_coverage_regions.values().enumerate() { | ||
mapping_indexes[*region_id] = mapping_index as u32; | ||
} | ||
|
||
for (region_loc, (region_id, region_kind)) in file_coverage_regions.into_iter() { | ||
let mapping_index = mapping_indexes[region_id]; | ||
match region_kind { | ||
CoverageKind::Counter => { | ||
debug!( | ||
" Counter {}, file_id: {}, region_loc: {}", | ||
mapping_index, file_id, region_loc | ||
); | ||
mapping_regions.push_from( | ||
mapping_index, | ||
file_id, | ||
region_loc.start_line, | ||
region_loc.start_col, | ||
region_loc.end_line, | ||
region_loc.end_col, | ||
); | ||
} | ||
CoverageKind::CounterExpression(lhs, op, rhs) => { | ||
debug!( | ||
" CounterExpression {} = {} {:?} {}, file_id: {}, region_loc: {:?}", | ||
mapping_index, lhs, op, rhs, file_id, region_loc, | ||
); | ||
mapping_regions.push_from( | ||
mapping_index, | ||
file_id, | ||
region_loc.start_line, | ||
region_loc.start_col, | ||
region_loc.end_line, | ||
region_loc.end_col, | ||
); | ||
expressions.push_from(op, lhs, rhs); | ||
} | ||
CoverageKind::Unreachable => { | ||
debug!( | ||
" Unreachable region, file_id: {}, region_loc: {:?}", | ||
file_id, region_loc, | ||
); | ||
bug!("Unreachable region not expected and not yet handled!") | ||
// FIXME(richkadel): implement and call | ||
// mapping_regions.push_from(...) for unreachable regions | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Encode and append the current function's coverage mapping data | ||
coverageinfo::write_mapping_to_buffer( | ||
virtual_file_mapping, | ||
expressions, | ||
mapping_regions, | ||
coverage_mappings_buffer, | ||
); | ||
} | ||
|
||
fn write_coverage_map( | ||
self, | ||
function_records: Vec<&'ll llvm::Value>, | ||
filenames_buffer: Vec<u8>, | ||
mut coverage_mappings_buffer: Vec<u8>, | ||
) { | ||
let cx = self.cx; | ||
|
||
// Concatenate the encoded filenames and encoded coverage mappings, and add additional zero | ||
// bytes as-needed to ensure 8-byte alignment. | ||
let mut coverage_size = coverage_mappings_buffer.len(); | ||
let filenames_size = filenames_buffer.len(); | ||
let remaining_bytes = | ||
(filenames_size + coverage_size) % coverageinfo::COVMAP_VAR_ALIGN_BYTES; | ||
if remaining_bytes > 0 { | ||
let pad = coverageinfo::COVMAP_VAR_ALIGN_BYTES - remaining_bytes; | ||
coverage_mappings_buffer.append(&mut [0].repeat(pad)); | ||
coverage_size += pad; | ||
} | ||
let filenames_and_coverage_mappings = [filenames_buffer, coverage_mappings_buffer].concat(); | ||
let filenames_and_coverage_mappings_val = | ||
cx.const_bytes(&filenames_and_coverage_mappings[..]); | ||
|
||
debug!( | ||
"cov map: n_records = {}, filenames_size = {}, coverage_size = {}, 0-based version = {}", | ||
function_records.len(), | ||
filenames_size, | ||
coverage_size, | ||
coverageinfo::mapping_version() | ||
); | ||
|
||
// Create the coverage data header | ||
let n_records_val = cx.const_u32(function_records.len() as u32); | ||
let filenames_size_val = cx.const_u32(filenames_size as u32); | ||
let coverage_size_val = cx.const_u32(coverage_size as u32); | ||
let version_val = cx.const_u32(coverageinfo::mapping_version()); | ||
let cov_data_header_val = cx.const_struct( | ||
&[n_records_val, filenames_size_val, coverage_size_val, version_val], | ||
/*packed=*/ false, | ||
); | ||
|
||
// Create the function records array | ||
let name_ref_from_u64 = cx.type_i64(); | ||
let mapping_data_size_from_u32 = cx.type_i32(); | ||
let func_hash_from_u64 = cx.type_i64(); | ||
let function_record_ty = cx.type_struct( | ||
&[name_ref_from_u64, mapping_data_size_from_u32, func_hash_from_u64], | ||
/*packed=*/ true, | ||
); | ||
let function_records_val = cx.const_array(function_record_ty, &function_records[..]); | ||
|
||
// Create the complete LLVM coverage data value to add to the LLVM IR | ||
let cov_data_val = cx.const_struct( | ||
&[cov_data_header_val, function_records_val, filenames_and_coverage_mappings_val], | ||
/*packed=*/ false, | ||
); | ||
|
||
// Save the coverage data value to LLVM IR | ||
coverageinfo::save_map_to_mod(cx, cov_data_val); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,83 +1,289 @@ | ||
use rustc_data_structures::fx::FxHashMap; | ||
use std::collections::hash_map; | ||
use std::slice; | ||
use rustc_data_structures::sync::Lrc; | ||
use rustc_middle::mir; | ||
use rustc_span::source_map::{Pos, SourceFile, SourceMap}; | ||
use rustc_span::{BytePos, FileName, RealFileName}; | ||
|
||
use std::cmp::{Ord, Ordering}; | ||
use std::collections::BTreeMap; | ||
use std::fmt; | ||
use std::path::PathBuf; | ||
|
||
#[derive(Copy, Clone, Debug)] | ||
#[repr(C)] | ||
pub enum CounterOp { | ||
Add, | ||
// Note the order (and therefore the default values) is important. With the attribute | ||
// `#[repr(C)]`, this enum matches the layout of the LLVM enum defined for the nested enum, | ||
// `llvm::coverage::CounterExpression::ExprKind`, as shown in the following source snippet: | ||
// https://github.com/rust-lang/llvm-project/blob/f208b70fbc4dee78067b3c5bd6cb92aa3ba58a1e/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h#L146 | ||
Subtract, | ||
Add, | ||
} | ||
|
||
#[derive(Copy, Clone, Debug)] | ||
pub enum CoverageKind { | ||
Counter, | ||
CounterExpression(u32, CounterOp, u32), | ||
Unreachable, | ||
} | ||
|
||
pub struct CoverageSpan { | ||
#[derive(Clone, Debug)] | ||
pub struct CoverageRegion { | ||
pub kind: CoverageKind, | ||
pub start_byte_pos: u32, | ||
pub end_byte_pos: u32, | ||
} | ||
|
||
pub struct CoverageRegion { | ||
pub kind: CoverageKind, | ||
pub coverage_span: CoverageSpan, | ||
impl CoverageRegion { | ||
pub fn source_loc(&self, source_map: &SourceMap) -> Option<(Lrc<SourceFile>, CoverageLoc)> { | ||
let (start_file, start_line, start_col) = | ||
lookup_file_line_col(source_map, BytePos::from_u32(self.start_byte_pos)); | ||
let (end_file, end_line, end_col) = | ||
lookup_file_line_col(source_map, BytePos::from_u32(self.end_byte_pos)); | ||
let start_file_path = match &start_file.name { | ||
FileName::Real(RealFileName::Named(path)) => path, | ||
_ => { | ||
bug!("start_file_path should be a RealFileName, but it was: {:?}", start_file.name) | ||
} | ||
}; | ||
let end_file_path = match &end_file.name { | ||
FileName::Real(RealFileName::Named(path)) => path, | ||
_ => bug!("end_file_path should be a RealFileName, but it was: {:?}", end_file.name), | ||
}; | ||
if start_file_path == end_file_path { | ||
Some((start_file, CoverageLoc { start_line, start_col, end_line, end_col })) | ||
} else { | ||
None | ||
// FIXME(richkadel): There seems to be a problem computing the file location in | ||
// some cases. I need to investigate this more. When I generate and show coverage | ||
// for the example binary in the crates.io crate `json5format`, I had a couple of | ||
// notable problems: | ||
// | ||
// 1. I saw a lot of coverage spans in `llvm-cov show` highlighting regions in | ||
// various comments (not corresponding to rustdoc code), indicating a possible | ||
// problem with the byte_pos-to-source-map implementation. | ||
// | ||
// 2. And (perhaps not related) when I build the aforementioned example binary with: | ||
// `RUST_FLAGS="-Zinstrument-coverage" cargo build --example formatjson5` | ||
// and then run that binary with | ||
// `LLVM_PROFILE_FILE="formatjson5.profraw" ./target/debug/examples/formatjson5 \ | ||
// some.json5` for some reason the binary generates *TWO* `.profraw` files. One | ||
// named `default.profraw` and the other named `formatjson5.profraw` (the expected | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe @petrhosek knows something about this? |
||
// name, in this case). | ||
// | ||
// If the byte range conversion is wrong, fix it. But if it | ||
// is right, then it is possible for the start and end to be in different files. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suppose it is possible in the case of macro expansions (depending on how we handle those spans; if we always resolved to the macro call site then I don't think this should ever happen). Though even macros can't define only the opening / closing end of a block (they must always be balanced), so I don't actually see how this could happen. |
||
// Can I do something other than ignore coverages that span multiple files? | ||
// | ||
// If I can resolve this, remove the "Option<>" result type wrapper | ||
// `regions_in_file_order()` accordingly. | ||
} | ||
} | ||
} | ||
|
||
impl Default for CoverageRegion { | ||
fn default() -> Self { | ||
Self { | ||
// The default kind (Unreachable) is a placeholder that will be overwritten before | ||
// backend codegen. | ||
kind: CoverageKind::Unreachable, | ||
start_byte_pos: 0, | ||
end_byte_pos: 0, | ||
} | ||
} | ||
} | ||
|
||
/// A source code region used with coverage information. | ||
#[derive(Debug, Eq, PartialEq)] | ||
pub struct CoverageLoc { | ||
/// The (1-based) line number of the region start. | ||
pub start_line: u32, | ||
/// The (1-based) column number of the region start. | ||
pub start_col: u32, | ||
/// The (1-based) line number of the region end. | ||
pub end_line: u32, | ||
/// The (1-based) column number of the region end. | ||
pub end_col: u32, | ||
} | ||
|
||
impl Ord for CoverageLoc { | ||
fn cmp(&self, other: &Self) -> Ordering { | ||
(self.start_line, &self.start_col, &self.end_line, &self.end_col).cmp(&( | ||
other.start_line, | ||
&other.start_col, | ||
&other.end_line, | ||
&other.end_col, | ||
)) | ||
} | ||
} | ||
|
||
impl PartialOrd for CoverageLoc { | ||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { | ||
Some(self.cmp(other)) | ||
} | ||
} | ||
|
||
impl fmt::Display for CoverageLoc { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
// Customize debug format, and repeat the file name, so generated location strings are | ||
// "clickable" in many IDEs. | ||
write!(f, "{}:{} - {}:{}", self.start_line, self.start_col, self.end_line, self.end_col) | ||
} | ||
} | ||
|
||
fn lookup_file_line_col(source_map: &SourceMap, byte_pos: BytePos) -> (Lrc<SourceFile>, u32, u32) { | ||
let found = source_map | ||
.lookup_line(byte_pos) | ||
.expect("should find coverage region byte position in source"); | ||
let file = found.sf; | ||
let line_pos = file.line_begin_pos(byte_pos); | ||
|
||
// Use 1-based indexing. | ||
let line = (found.line + 1) as u32; | ||
let col = (byte_pos - line_pos).to_u32() + 1; | ||
|
||
(file, line, col) | ||
} | ||
|
||
/// Collects all of the coverage regions associated with (a) injected counters, (b) counter | ||
/// expressions (additions or subtraction), and (c) unreachable regions (always counted as zero), | ||
/// for a given Function. Counters and counter expressions are indexed because they can be operands | ||
/// in an expression. | ||
/// in an expression. This struct also stores the `function_source_hash`, computed during | ||
/// instrumentation and forwarded with counters. | ||
/// | ||
/// Note, it's important to distinguish the `unreachable` region type from what LLVM's refers to as | ||
/// a "gap region" (or "gap area"). A gap region is a code region within a counted region (either | ||
/// counter or expression), but the line or lines in the gap region are not executable (such as | ||
/// lines with only whitespace or comments). According to LLVM Code Coverage Mapping documentation, | ||
/// "A count for a gap area is only used as the line execution count if there are no other regions | ||
/// on a line." | ||
#[derive(Default)] | ||
pub struct FunctionCoverageRegions { | ||
indexed: FxHashMap<u32, CoverageRegion>, | ||
unreachable: Vec<CoverageSpan>, | ||
pub struct FunctionCoverage { | ||
source_hash: u64, | ||
counters: Vec<CoverageRegion>, | ||
expressions: Vec<CoverageRegion>, | ||
unreachable: Vec<CoverageRegion>, | ||
translated: bool, | ||
} | ||
|
||
impl FunctionCoverageRegions { | ||
pub fn add_counter(&mut self, index: u32, start_byte_pos: u32, end_byte_pos: u32) { | ||
self.indexed.insert( | ||
index, | ||
CoverageRegion { | ||
kind: CoverageKind::Counter, | ||
coverage_span: CoverageSpan { start_byte_pos, end_byte_pos }, | ||
}, | ||
); | ||
impl FunctionCoverage { | ||
pub fn with_coverageinfo<'tcx>(coverageinfo: &'tcx mir::CoverageInfo) -> Self { | ||
Self { | ||
source_hash: 0, // will be set with the first `add_counter()` | ||
counters: vec![CoverageRegion::default(); coverageinfo.num_counters as usize], | ||
expressions: vec![CoverageRegion::default(); coverageinfo.num_expressions as usize], | ||
unreachable: Vec::new(), | ||
translated: false, | ||
} | ||
} | ||
|
||
pub fn add_counter_expression( | ||
/// Adds a code region to be counted by an injected counter intrinsic. Return a counter ID | ||
/// for the call. | ||
pub fn add_counter( | ||
&mut self, | ||
source_hash: u64, | ||
index: u32, | ||
start_byte_pos: u32, | ||
end_byte_pos: u32, | ||
) { | ||
self.source_hash = source_hash; | ||
self.counters[index as usize] = | ||
CoverageRegion { kind: CoverageKind::Counter, start_byte_pos, end_byte_pos }; | ||
} | ||
|
||
pub fn add_counter_expression( | ||
&mut self, | ||
translated_index: u32, | ||
lhs: u32, | ||
op: CounterOp, | ||
rhs: u32, | ||
start_byte_pos: u32, | ||
end_byte_pos: u32, | ||
) { | ||
self.indexed.insert( | ||
index, | ||
CoverageRegion { | ||
kind: CoverageKind::CounterExpression(lhs, op, rhs), | ||
coverage_span: CoverageSpan { start_byte_pos, end_byte_pos }, | ||
}, | ||
); | ||
let index = u32::MAX - translated_index; | ||
// Counter expressions start with "translated indexes", descending from `u32::MAX`, so | ||
// the range of expression indexes is disjoint from the range of counter indexes. This way, | ||
// both counters and expressions can be operands in other expressions. | ||
// | ||
// Once all counters have been added, the final "region index" for an expression is | ||
// `counters.len() + expression_index` (where `expression_index` is its index in | ||
// `self.expressions`), and the expression operands (`lhs` and `rhs`) can be converted to | ||
// final "region index" references by the same conversion, after subtracting from | ||
// `u32::MAX`. | ||
self.expressions[index as usize] = CoverageRegion { | ||
kind: CoverageKind::CounterExpression(lhs, op, rhs), | ||
start_byte_pos, | ||
end_byte_pos, | ||
}; | ||
} | ||
|
||
pub fn add_unreachable(&mut self, start_byte_pos: u32, end_byte_pos: u32) { | ||
self.unreachable.push(CoverageSpan { start_byte_pos, end_byte_pos }); | ||
self.unreachable.push(CoverageRegion { | ||
kind: CoverageKind::Unreachable, | ||
start_byte_pos, | ||
end_byte_pos, | ||
}); | ||
} | ||
|
||
pub fn source_hash(&self) -> u64 { | ||
self.source_hash | ||
} | ||
|
||
fn regions(&'a mut self) -> impl Iterator<Item = &'a CoverageRegion> { | ||
assert!(self.source_hash != 0); | ||
self.ensure_expressions_translated(); | ||
self.counters.iter().chain(self.expressions.iter().chain(self.unreachable.iter())) | ||
} | ||
|
||
pub fn indexed_regions(&self) -> hash_map::Iter<'_, u32, CoverageRegion> { | ||
self.indexed.iter() | ||
pub fn regions_in_file_order( | ||
&'a mut self, | ||
source_map: &SourceMap, | ||
) -> BTreeMap<PathBuf, BTreeMap<CoverageLoc, (usize, CoverageKind)>> { | ||
let mut regions_in_file_order = BTreeMap::new(); | ||
for (region_id, region) in self.regions().enumerate() { | ||
if let Some((source_file, region_loc)) = region.source_loc(source_map) { | ||
// FIXME(richkadel): `region.source_loc()` sometimes fails with two different | ||
// filenames for the start and end byte position. This seems wrong, but for | ||
// now, if encountered, the region is skipped. If resolved, convert the result | ||
// to a non-option value so regions are never skipped. | ||
let real_file_path = match &(*source_file).name { | ||
FileName::Real(RealFileName::Named(path)) => path.clone(), | ||
_ => bug!("coverage mapping expected only real, named files"), | ||
}; | ||
let file_coverage_regions = | ||
regions_in_file_order.entry(real_file_path).or_insert_with(|| BTreeMap::new()); | ||
file_coverage_regions.insert(region_loc, (region_id, region.kind)); | ||
} | ||
} | ||
regions_in_file_order | ||
} | ||
|
||
pub fn unreachable_regions(&self) -> slice::Iter<'_, CoverageSpan> { | ||
self.unreachable.iter() | ||
/// A one-time translation of expression operands is needed, for any operands referencing | ||
/// other CounterExpressions. CounterExpression operands get an initial operand ID that is | ||
/// computed by the simple translation: `u32::max - expression_index` because, when created, | ||
/// the total number of Counters is not yet known. This function recomputes region indexes | ||
/// for expressions so they start with the next region index after the last counter index. | ||
fn ensure_expressions_translated(&mut self) { | ||
if !self.translated { | ||
self.translated = true; | ||
let start = self.counters.len() as u32; | ||
assert!( | ||
(start as u64 + self.expressions.len() as u64) < u32::MAX as u64, | ||
"the number of counters and counter expressions in a single function exceeds {}", | ||
u32::MAX | ||
); | ||
for region in self.expressions.iter_mut() { | ||
match region.kind { | ||
CoverageKind::CounterExpression(lhs, op, rhs) => { | ||
let lhs = to_region_index(start, lhs); | ||
let rhs = to_region_index(start, rhs); | ||
region.kind = CoverageKind::CounterExpression(lhs, op, rhs); | ||
} | ||
_ => bug!("expressions must only contain CounterExpression kinds"), | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
fn to_region_index(start: u32, index: u32) -> u32 { | ||
if index < start { index } else { start + (u32::MAX - index) } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
//! Validity checking for fake lang items | ||
use crate::def_id::DefId; | ||
use crate::{lang_items, LangItem, LanguageItems}; | ||
|
||
use rustc_data_structures::fx::FxHashMap; | ||
use rustc_span::symbol::{sym, Symbol}; | ||
|
||
use lazy_static::lazy_static; | ||
|
||
macro_rules! fake_lang_items { | ||
($($item:ident, $name:ident, $method:ident;)*) => ( | ||
|
||
lazy_static! { | ||
pub static ref FAKE_ITEMS_REFS: FxHashMap<Symbol, LangItem> = { | ||
let mut map = FxHashMap::default(); | ||
$(map.insert(sym::$name, lang_items::$item);)* | ||
map | ||
}; | ||
} | ||
|
||
impl LanguageItems { | ||
pub fn is_fake_lang_item(&self, item_def_id: DefId) -> bool { | ||
let did = Some(item_def_id); | ||
|
||
$(self.$method() == did)||* | ||
} | ||
} | ||
|
||
) } | ||
|
||
fake_lang_items! { | ||
// Variant name, Symbol, Method name, | ||
CountCodeRegionFnLangItem, count_code_region, count_code_region_fn; | ||
CoverageCounterAddFnLangItem, coverage_counter_add, coverage_counter_add_fn; | ||
CoverageCounterSubtractFnLangItem, coverage_counter_subtract, coverage_counter_subtract_fn; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
#include "rustllvm.h" | ||
#include "llvm/ProfileData/Coverage/CoverageMapping.h" | ||
#include "llvm/ProfileData/Coverage/CoverageMappingWriter.h" | ||
#include "llvm/ProfileData/InstrProf.h" | ||
#include "llvm/ADT/ArrayRef.h" | ||
|
||
#include <iostream> | ||
|
||
using namespace llvm; | ||
|
||
extern "C" SmallVectorTemplateBase<coverage::CounterExpression> | ||
richkadel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
*LLVMRustCoverageSmallVectorCounterExpressionCreate() { | ||
return new SmallVector<coverage::CounterExpression, 32>(); | ||
} | ||
|
||
extern "C" void LLVMRustCoverageSmallVectorCounterExpressionDispose( | ||
SmallVectorTemplateBase<coverage::CounterExpression> *Vector) { | ||
delete Vector; | ||
} | ||
|
||
extern "C" void LLVMRustCoverageSmallVectorCounterExpressionAdd( | ||
SmallVectorTemplateBase<coverage::CounterExpression> *Expressions, | ||
coverage::CounterExpression::ExprKind Kind, | ||
unsigned LeftIndex, | ||
unsigned RightIndex) { | ||
auto LHS = coverage::Counter::getCounter(LeftIndex); | ||
auto RHS = coverage::Counter::getCounter(RightIndex); | ||
Expressions->push_back(coverage::CounterExpression { Kind, LHS, RHS }); | ||
} | ||
|
||
extern "C" SmallVectorTemplateBase<coverage::CounterMappingRegion> | ||
*LLVMRustCoverageSmallVectorCounterMappingRegionCreate() { | ||
return new SmallVector<coverage::CounterMappingRegion, 32>(); | ||
} | ||
|
||
extern "C" void LLVMRustCoverageSmallVectorCounterMappingRegionDispose( | ||
SmallVectorTemplateBase<coverage::CounterMappingRegion> *Vector) { | ||
delete Vector; | ||
} | ||
|
||
extern "C" void LLVMRustCoverageSmallVectorCounterMappingRegionAdd( | ||
SmallVectorTemplateBase<coverage::CounterMappingRegion> *MappingRegions, | ||
unsigned Index, | ||
unsigned FileID, | ||
unsigned LineStart, | ||
unsigned ColumnStart, | ||
unsigned LineEnd, | ||
unsigned ColumnEnd) { | ||
auto Counter = coverage::Counter::getCounter(Index); | ||
MappingRegions->push_back(coverage::CounterMappingRegion::makeRegion( | ||
Counter, FileID, LineStart, | ||
ColumnStart, LineEnd, ColumnEnd)); | ||
|
||
// FIXME(richkadel): As applicable, implement additional CounterMappingRegion types using the | ||
// static method alternatives to `coverage::CounterMappingRegion::makeRegion`: | ||
// | ||
// makeExpansion(unsigned FileID, unsigned ExpandedFileID, unsigned LineStart, | ||
// unsigned ColumnStart, unsigned LineEnd, unsigned ColumnEnd) { | ||
// makeSkipped(unsigned FileID, unsigned LineStart, unsigned ColumnStart, | ||
// unsigned LineEnd, unsigned ColumnEnd) { | ||
// makeGapRegion(Counter Count, unsigned FileID, unsigned LineStart, | ||
// unsigned ColumnStart, unsigned LineEnd, unsigned ColumnEnd) { | ||
} | ||
|
||
extern "C" void LLVMRustCoverageWriteFilenamesSectionToBuffer( | ||
const char* const Filenames[], | ||
size_t FilenamesLen, | ||
RustStringRef BufferOut) { | ||
SmallVector<StringRef,32> FilenameRefs; | ||
for (size_t i = 0; i < FilenamesLen; i++) { | ||
FilenameRefs.push_back(StringRef(Filenames[i])); | ||
} | ||
auto FilenamesWriter = coverage::CoverageFilenamesSectionWriter( | ||
makeArrayRef(FilenameRefs)); | ||
RawRustStringOstream OS(BufferOut); | ||
FilenamesWriter.write(OS); | ||
} | ||
|
||
extern "C" void LLVMRustCoverageWriteMappingToBuffer( | ||
const unsigned *VirtualFileMappingIDs, | ||
unsigned NumVirtualFileMappingIDs, | ||
wesleywiser marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const SmallVectorImpl<coverage::CounterExpression> *Expressions, | ||
SmallVectorImpl<coverage::CounterMappingRegion> *MappingRegions, | ||
RustStringRef BufferOut) { | ||
auto CoverageMappingWriter = coverage::CoverageMappingWriter( | ||
makeArrayRef(VirtualFileMappingIDs, NumVirtualFileMappingIDs), | ||
makeArrayRef(*Expressions), | ||
MutableArrayRef<coverage::CounterMappingRegion> { *MappingRegions }); | ||
RawRustStringOstream OS(BufferOut); | ||
CoverageMappingWriter.write(OS); | ||
} | ||
|
||
extern "C" uint64_t LLVMRustCoverageComputeHash(const char *Name) { | ||
StringRef NameRef(Name); | ||
return IndexedInstrProf::ComputeHash(NameRef); | ||
} | ||
|
||
extern "C" void LLVMRustCoverageWriteSectionNameToString(LLVMModuleRef M, | ||
RustStringRef Str) { | ||
Triple TargetTriple(unwrap(M)->getTargetTriple()); | ||
auto name = getInstrProfSectionName(IPSK_covmap, | ||
TargetTriple.getObjectFormat()); | ||
RawRustStringOstream OS(Str); | ||
OS << name; | ||
} | ||
|
||
extern "C" void LLVMRustCoverageWriteMappingVarNameToString(RustStringRef Str) { | ||
auto name = getCoverageMappingVarName(); | ||
RawRustStringOstream OS(Str); | ||
OS << name; | ||
} | ||
|
||
extern "C" uint32_t LLVMRustCoverageMappingVersion() { | ||
return coverage::CovMapVersion::CurrentVersion; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# needs-profiler-support | ||
# ignore-msvc | ||
|
||
# FIXME(richkadel): Debug the following problem, and reenable on Windows (by | ||
# removing the `# ignore-msvc` directive above). The current implementation | ||
# generates a segfault when running the instrumented `main` executable, | ||
# after the `main` program code executes, but before the process terminates. | ||
# This most likely points to a problem generating the LLVM "main.profraw" | ||
# file. | ||
|
||
-include ../tools.mk | ||
|
||
# This test makes sure that LLVM coverage maps are genereated in LLVM IR. | ||
|
||
COMMON_FLAGS=-Zinstrument-coverage | ||
|
||
all: | ||
# Compile the test program with instrumentation, and also generate LLVM IR | ||
$(RUSTC) $(COMMON_FLAGS) main.rs | ||
|
||
# Run it in order to generate some profiling data, | ||
# with `LLVM_PROFILE_FILE=<profdata_file>` environment variable set to | ||
# output the coverage stats for this run. | ||
LLVM_PROFILE_FILE="$(TMPDIR)"/main.profraw \ | ||
$(call RUN,main) | ||
|
||
# Postprocess the profiling data so it can be used by the llvm-cov tool | ||
"$(LLVM_BIN_DIR)"/llvm-profdata merge --sparse \ | ||
"$(TMPDIR)"/main.profraw \ | ||
-o "$(TMPDIR)"/main.profdata | ||
|
||
# Generate a coverage report using `llvm-cov show`. The output ordering | ||
# can be non-deterministic, so ignore the return status. If the test fails | ||
# when comparing the JSON `export`, the `show` output may be useful when | ||
# debugging. | ||
"$(LLVM_BIN_DIR)"/llvm-cov show \ | ||
--Xdemangler="$(RUST_DEMANGLER)" \ | ||
--show-line-counts-or-regions \ | ||
--instr-profile="$(TMPDIR)"/main.profdata \ | ||
$(call BIN,"$(TMPDIR)"/main) \ | ||
> "$(TMPDIR)"/actual_show_coverage.txt | ||
|
||
# Compare the show coverage output | ||
$(DIFF) typical_show_coverage.txt "$(TMPDIR)"/actual_show_coverage.txt || \ | ||
>&2 echo 'diff failed for `llvm-cov show` (might not be an error)' | ||
|
||
# Generate a coverage report in JSON, using `llvm-cov export`, and fail if | ||
# there are differences from the expected output. | ||
"$(LLVM_BIN_DIR)"/llvm-cov export \ | ||
--summary-only \ | ||
--instr-profile="$(TMPDIR)"/main.profdata \ | ||
$(call BIN,"$(TMPDIR)"/main) \ | ||
| "$(PYTHON)" prettify_json.py \ | ||
> "$(TMPDIR)"/actual_export_coverage.json | ||
|
||
# Check that the exported JSON coverage data matches what we expect | ||
$(DIFF) expected_export_coverage.json "$(TMPDIR)"/actual_export_coverage.json |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
{ | ||
"data": [ | ||
{ | ||
"files": [ | ||
{ | ||
"filename": "main.rs", | ||
"summary": { | ||
"functions": { | ||
"count": 7, | ||
"covered": 5, | ||
"percent": 71.42857142857143 | ||
}, | ||
"instantiations": { | ||
"count": 8, | ||
"covered": 6, | ||
"percent": 75 | ||
}, | ||
"lines": { | ||
"count": 30, | ||
"covered": 25, | ||
"percent": 83.33333333333334 | ||
}, | ||
"regions": { | ||
"count": 7, | ||
"covered": 5, | ||
"notcovered": 2, | ||
"percent": 71.42857142857143 | ||
} | ||
} | ||
} | ||
], | ||
"totals": { | ||
"functions": { | ||
"count": 7, | ||
"covered": 5, | ||
"percent": 71.42857142857143 | ||
}, | ||
"instantiations": { | ||
"count": 8, | ||
"covered": 6, | ||
"percent": 75 | ||
}, | ||
"lines": { | ||
"count": 30, | ||
"covered": 25, | ||
"percent": 83.33333333333334 | ||
}, | ||
"regions": { | ||
"count": 7, | ||
"covered": 5, | ||
"notcovered": 2, | ||
"percent": 71.42857142857143 | ||
} | ||
} | ||
} | ||
], | ||
"type": "llvm.coverage.json.export", | ||
"version": "2.0.0" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
pub fn will_be_called() -> &'static str { | ||
let val = "called"; | ||
println!("{}", val); | ||
val | ||
} | ||
|
||
pub fn will_not_be_called() -> bool { | ||
println!("should not have been called"); | ||
false | ||
} | ||
|
||
pub fn print<T>(left: &str, value: T, right: &str) | ||
where | ||
T: std::fmt::Display, | ||
{ | ||
println!("{}{}{}", left, value, right); | ||
} | ||
|
||
pub fn wrap_with<F, T>(inner: T, should_wrap: bool, wrapper: F) | ||
where | ||
F: FnOnce(&T) | ||
{ | ||
if should_wrap { | ||
wrapper(&inner) | ||
} | ||
} | ||
|
||
fn main() { | ||
let less = 1; | ||
let more = 100; | ||
|
||
if less < more { | ||
wrap_with(will_be_called(), less < more, |inner| print(" ***", inner, "*** ")); | ||
wrap_with(will_be_called(), more < less, |inner| print(" ***", inner, "*** ")); | ||
} else { | ||
wrap_with(will_not_be_called(), true, |inner| print("wrapped result is: ", inner, "")); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
#!/usr/bin/env python | ||
|
||
import sys | ||
import json | ||
|
||
# Try to decode line in order to ensure it is a valid JSON document | ||
for line in sys.stdin: | ||
parsed = json.loads(line) | ||
print (json.dumps(parsed, indent=2, separators=(',', ': '), sort_keys=True)) |
Uh oh!
There was an error while loading. Please reload this page.