2020-07-02 18:27:15 +00:00
use crate ::common ::CodegenCx ;
use crate ::coverageinfo ;
2020-07-25 04:14:28 +00:00
use crate ::llvm ;
2020-07-02 18:27:15 +00:00
2020-07-29 06:09:16 +00:00
use llvm ::coverageinfo ::CounterMappingRegion ;
2020-08-15 11:42:13 +00:00
use rustc_codegen_ssa ::coverageinfo ::map ::{ Counter , CounterExpression } ;
2020-07-25 04:14:28 +00:00
use rustc_codegen_ssa ::traits ::{ BaseTypeMethods , ConstMethods } ;
2020-08-08 03:42:29 +00:00
use rustc_data_structures ::fx ::FxIndexSet ;
2020-07-02 18:27:15 +00:00
use rustc_llvm ::RustString ;
2020-08-15 11:42:13 +00:00
use rustc_middle ::mir ::coverage ::CodeRegion ;
2020-07-02 18:27:15 +00:00
use std ::ffi ::CString ;
2020-08-15 11:42:13 +00:00
use tracing ::debug ;
2020-07-02 18:27:15 +00:00
/// 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 > ) {
2020-10-23 21:58:08 +00:00
let function_coverage_map = match cx . coverage_context ( ) {
Some ( ctx ) = > ctx . take_function_coverage_map ( ) ,
None = > return ,
} ;
2020-07-27 23:25:08 +00:00
if function_coverage_map . is_empty ( ) {
2020-07-25 04:14:28 +00:00
// This module has no functions with coverage instrumentation
return ;
}
let mut mapgen = CoverageMapGenerator ::new ( ) ;
2020-07-02 18:27:15 +00:00
// 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 ( ) {
2020-07-25 04:14:28 +00:00
debug! ( " Generate coverage map for: {:?} " , instance ) ;
let mangled_function_name = cx . tcx . symbol_name ( instance ) . to_string ( ) ;
let function_source_hash = function_coverage . source_hash ( ) ;
let ( expressions , counter_regions ) =
function_coverage . get_expressions_and_counter_regions ( ) ;
let old_len = coverage_mappings_buffer . len ( ) ;
mapgen . write_coverage_mappings ( expressions , counter_regions , coverage_mappings_buffer ) ;
let mapping_data_size = coverage_mappings_buffer . len ( ) - old_len ;
debug_assert! (
mapping_data_size > 0 ,
" Every `FunctionCoverage` should have at least one counter "
) ;
let function_record = mapgen . make_function_record (
cx ,
mangled_function_name ,
function_source_hash ,
mapping_data_size ,
) ;
function_records . push ( function_record ) ;
2020-07-02 18:27:15 +00:00
}
} ) ;
2020-07-25 04:14:28 +00:00
// Encode all filenames referenced by counters/expressions in this module
2020-07-02 18:27:15 +00:00
let filenames_buffer = llvm ::build_byte_buffer ( | filenames_buffer | {
2020-07-25 04:14:28 +00:00
coverageinfo ::write_filenames_section_to_buffer ( & mapgen . filenames , filenames_buffer ) ;
2020-07-02 18:27:15 +00:00
} ) ;
2020-07-25 04:14:28 +00:00
// Generate the LLVM IR representation of the coverage map and store it in a well-known global
mapgen . save_generated_coverage_map (
cx ,
function_records ,
filenames_buffer ,
coverage_mappings_buffer ,
) ;
2020-07-02 18:27:15 +00:00
}
2020-07-25 04:14:28 +00:00
struct CoverageMapGenerator {
2020-08-08 03:42:29 +00:00
filenames : FxIndexSet < CString > ,
2020-07-02 18:27:15 +00:00
}
2020-07-25 04:14:28 +00:00
impl CoverageMapGenerator {
fn new ( ) -> Self {
2020-08-08 03:42:29 +00:00
Self { filenames : FxIndexSet ::default ( ) }
2020-07-02 18:27:15 +00:00
}
2020-07-25 04:14:28 +00:00
/// Using the `expressions` and `counter_regions` collected for the current function, generate
/// the `mapping_regions` and `virtual_file_mapping`, and capture any new filenames. Then use
/// LLVM APIs to encode the `virtual_file_mapping`, `expressions`, and `mapping_regions` into
/// the given `coverage_mappings` byte buffer, compliant with the LLVM Coverage Mapping format.
fn write_coverage_mappings (
2020-07-02 18:27:15 +00:00
& mut self ,
2020-07-25 04:14:28 +00:00
expressions : Vec < CounterExpression > ,
2020-08-15 11:42:13 +00:00
counter_regions : impl Iterator < Item = ( Counter , & ' a CodeRegion ) > ,
2020-07-02 18:27:15 +00:00
coverage_mappings_buffer : & RustString ,
2020-07-25 04:14:28 +00:00
) {
let mut counter_regions = counter_regions . collect ::< Vec < _ > > ( ) ;
2020-07-27 23:25:08 +00:00
if counter_regions . is_empty ( ) {
2020-07-25 04:14:28 +00:00
return ;
2020-07-02 18:27:15 +00:00
}
let mut virtual_file_mapping = Vec ::new ( ) ;
2020-07-25 04:14:28 +00:00
let mut mapping_regions = Vec ::new ( ) ;
2020-08-02 03:03:59 +00:00
let mut current_file_name = None ;
2020-07-25 04:14:28 +00:00
let mut current_file_id = 0 ;
2020-08-15 11:42:13 +00:00
// Convert the list of (Counter, CodeRegion) pairs to an array of `CounterMappingRegion`, sorted
2020-07-25 04:14:28 +00:00
// by filename and position. Capture any new files to compute the `CounterMappingRegion`s
// `file_id` (indexing files referenced by the current function), and construct the
// function-specific `virtual_file_mapping` from `file_id` to its index in the module's
// `filenames` array.
2020-07-27 23:25:08 +00:00
counter_regions . sort_unstable_by_key ( | ( _counter , region ) | * region ) ;
2020-07-25 04:14:28 +00:00
for ( counter , region ) in counter_regions {
2020-08-15 11:42:13 +00:00
let CodeRegion { file_name , start_line , start_col , end_line , end_col } = * region ;
let same_file = current_file_name . as_ref ( ) . map_or ( false , | p | * p = = file_name ) ;
2020-07-25 04:14:28 +00:00
if ! same_file {
2020-08-02 03:03:59 +00:00
if current_file_name . is_some ( ) {
2020-07-25 04:14:28 +00:00
current_file_id + = 1 ;
2020-07-02 18:27:15 +00:00
}
2020-08-15 11:42:13 +00:00
current_file_name = Some ( file_name ) ;
let c_filename = CString ::new ( file_name . to_string ( ) )
. expect ( " null error converting filename to C string " ) ;
2020-08-02 03:03:59 +00:00
debug! ( " file_id: {} = '{:?}' " , current_file_id , c_filename ) ;
2020-08-08 03:42:29 +00:00
let ( filenames_index , _ ) = self . filenames . insert_full ( c_filename ) ;
virtual_file_mapping . push ( filenames_index as u32 ) ;
2020-07-02 18:27:15 +00:00
}
Updates to experimental coverage counter injection
This is a combination of 18 commits.
Commit #2:
Additional examples and some small improvements.
Commit #3:
fixed mir-opt non-mir extensions and spanview title elements
Corrected a fairly recent assumption in runtest.rs that all MIR dump
files end in .mir. (It was appending .mir to the graphviz .dot and
spanview .html file names when generating blessed output files. That
also left outdated files in the baseline alongside the files with the
incorrect names, which I've now removed.)
Updated spanview HTML title elements to match their content, replacing a
hardcoded and incorrect name that was left in accidentally when
originally submitted.
Commit #4:
added more test examples
also improved Makefiles with support for non-zero exit status and to
force validation of tests unless a specific test overrides it with a
specific comment.
Commit #5:
Fixed rare issues after testing on real-world crate
Commit #6:
Addressed PR feedback, and removed temporary -Zexperimental-coverage
-Zinstrument-coverage once again supports the latest capabilities of
LLVM instrprof coverage instrumentation.
Also fixed a bug in spanview.
Commit #7:
Fix closure handling, add tests for closures and inner items
And cleaned up other tests for consistency, and to make it more clear
where spans start/end by breaking up lines.
Commit #8:
renamed "typical" test results "expected"
Now that the `llvm-cov show` tests are improved to normally expect
matching actuals, and to allow individual tests to override that
expectation.
Commit #9:
test coverage of inline generic struct function
Commit #10:
Addressed review feedback
* Removed unnecessary Unreachable filter.
* Replaced a match wildcard with remining variants.
* Added more comments to help clarify the role of successors() in the
CFG traversal
Commit #11:
refactoring based on feedback
* refactored `fn coverage_spans()`.
* changed the way I expand an empty coverage span to improve performance
* fixed a typo that I had accidently left in, in visit.rs
Commit #12:
Optimized use of SourceMap and SourceFile
Commit #13:
Fixed a regression, and synched with upstream
Some generated test file names changed due to some new change upstream.
Commit #14:
Stripping out crate disambiguators from demangled names
These can vary depending on the test platform.
Commit #15:
Ignore llvm-cov show diff on test with generics, expand IO error message
Tests with generics produce llvm-cov show results with demangled names
that can include an unstable "crate disambiguator" (hex value). The
value changes when run in the Rust CI Windows environment. I added a sed
filter to strip them out (in a prior commit), but sed also appears to
fail in the same environment. Until I can figure out a workaround, I'm
just going to ignore this specific test result. I added a FIXME to
follow up later, but it's not that critical.
I also saw an error with Windows GNU, but the IO error did not
specify a path for the directory or file that triggered the error. I
updated the error messages to provide more info for next, time but also
noticed some other tests with similar steps did not fail. Looks
spurious.
Commit #16:
Modify rust-demangler to strip disambiguators by default
Commit #17:
Remove std::process::exit from coverage tests
Due to Issue #77553, programs that call std::process::exit() do not
generate coverage results on Windows MSVC.
Commit #18:
fix: test file paths exceeding Windows max path len
2020-09-01 23:15:17 +00:00
debug! ( " Adding counter {:?} to map for {:?} " , counter , region , ) ;
2020-07-29 06:09:16 +00:00
mapping_regions . push ( CounterMappingRegion ::code_region (
2020-07-25 04:14:28 +00:00
counter ,
current_file_id ,
start_line ,
start_col ,
end_line ,
end_col ,
) ) ;
2020-07-02 18:27:15 +00:00
}
// Encode and append the current function's coverage mapping data
coverageinfo ::write_mapping_to_buffer (
virtual_file_mapping ,
expressions ,
mapping_regions ,
coverage_mappings_buffer ,
) ;
}
2020-07-25 04:14:28 +00:00
/// Generate and return the function record `Value`
fn make_function_record (
& mut self ,
cx : & CodegenCx < ' ll , ' tcx > ,
mangled_function_name : String ,
function_source_hash : u64 ,
mapping_data_size : usize ,
) -> & ' ll llvm ::Value {
let name_ref = coverageinfo ::compute_hash ( & mangled_function_name ) ;
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 ) ;
cx . const_struct (
& [ name_ref_val , mapping_data_size_val , func_hash_val ] ,
/* packed= */ true ,
)
}
/// Combine the filenames and coverage mappings buffers, construct coverage map header and the
/// array of function records, and combine everything into the complete coverage map. Save the
/// coverage map data into the LLVM IR as a static global using a specific, well-known section
/// and name.
fn save_generated_coverage_map (
2020-07-02 18:27:15 +00:00
self ,
2020-07-25 04:14:28 +00:00
cx : & CodegenCx < ' ll , ' tcx > ,
2020-07-02 18:27:15 +00:00
function_records : Vec < & ' ll llvm ::Value > ,
filenames_buffer : Vec < u8 > ,
mut coverage_mappings_buffer : Vec < u8 > ,
) {
// 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 ) ;
}
}