diff options
| author | mark <markm@cs.wisc.edu> | 2020-08-27 22:58:48 -0500 |
|---|---|---|
| committer | Vadim Petrochenkov <vadim.petrochenkov@gmail.com> | 2020-08-30 18:45:07 +0300 |
| commit | 9e5f7d5631b8f4009ac1c693e585d4b7108d4275 (patch) | |
| tree | 158a05eb3f204a8e72939b58427d0c2787a4eade /compiler/rustc_codegen_llvm/src/coverageinfo | |
| parent | db534b3ac286cf45688c3bbae6aa6e77439e52d2 (diff) | |
| download | rust-9e5f7d5631b8f4009ac1c693e585d4b7108d4275.tar.gz rust-9e5f7d5631b8f4009ac1c693e585d4b7108d4275.zip | |
mv compiler to compiler/
Diffstat (limited to 'compiler/rustc_codegen_llvm/src/coverageinfo')
| -rw-r--r-- | compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs | 229 | ||||
| -rw-r--r-- | compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs | 179 |
2 files changed, 408 insertions, 0 deletions
diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs new file mode 100644 index 00000000000..ec6c177614d --- /dev/null +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs @@ -0,0 +1,229 @@ +use crate::common::CodegenCx; +use crate::coverageinfo; +use crate::llvm; + +use llvm::coverageinfo::CounterMappingRegion; +use rustc_codegen_ssa::coverageinfo::map::{Counter, CounterExpression}; +use rustc_codegen_ssa::traits::{BaseTypeMethods, ConstMethods}; +use rustc_data_structures::fx::FxIndexSet; +use rustc_llvm::RustString; +use rustc_middle::mir::coverage::CodeRegion; + +use std::ffi::CString; + +use tracing::debug; + +/// 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 function_coverage_map = cx.coverage_context().take_function_coverage_map(); + if function_coverage_map.is_empty() { + // This module has no functions with coverage instrumentation + return; + } + + let mut mapgen = CoverageMapGenerator::new(); + + // 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() { + 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); + } + }); + + // Encode all filenames referenced by counters/expressions in this module + let filenames_buffer = llvm::build_byte_buffer(|filenames_buffer| { + coverageinfo::write_filenames_section_to_buffer(&mapgen.filenames, filenames_buffer); + }); + + // 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, + ); +} + +struct CoverageMapGenerator { + filenames: FxIndexSet<CString>, +} + +impl CoverageMapGenerator { + fn new() -> Self { + Self { filenames: FxIndexSet::default() } + } + + /// 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( + &mut self, + expressions: Vec<CounterExpression>, + counter_regions: impl Iterator<Item = (Counter, &'a CodeRegion)>, + coverage_mappings_buffer: &RustString, + ) { + let mut counter_regions = counter_regions.collect::<Vec<_>>(); + if counter_regions.is_empty() { + return; + } + + let mut virtual_file_mapping = Vec::new(); + let mut mapping_regions = Vec::new(); + let mut current_file_name = None; + let mut current_file_id = 0; + + // Convert the list of (Counter, CodeRegion) pairs to an array of `CounterMappingRegion`, sorted + // 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. + counter_regions.sort_unstable_by_key(|(_counter, region)| *region); + for (counter, region) in counter_regions { + 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); + if !same_file { + if current_file_name.is_some() { + current_file_id += 1; + } + current_file_name = Some(file_name); + let c_filename = CString::new(file_name.to_string()) + .expect("null error converting filename to C string"); + debug!(" file_id: {} = '{:?}'", current_file_id, c_filename); + let (filenames_index, _) = self.filenames.insert_full(c_filename); + virtual_file_mapping.push(filenames_index as u32); + } + mapping_regions.push(CounterMappingRegion::code_region( + counter, + current_file_id, + start_line, + start_col, + end_line, + end_col, + )); + } + + // Encode and append the current function's coverage mapping data + coverageinfo::write_mapping_to_buffer( + virtual_file_mapping, + expressions, + mapping_regions, + coverage_mappings_buffer, + ); + } + + /// 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( + self, + cx: &CodegenCx<'ll, 'tcx>, + 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); + } +} diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs new file mode 100644 index 00000000000..2bd37bf9c4f --- /dev/null +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs @@ -0,0 +1,179 @@ +use crate::llvm; + +use crate::builder::Builder; +use crate::common::CodegenCx; + +use libc::c_uint; +use llvm::coverageinfo::CounterMappingRegion; +use rustc_codegen_ssa::coverageinfo::map::{CounterExpression, FunctionCoverage}; +use rustc_codegen_ssa::traits::{ + BaseTypeMethods, CoverageInfoBuilderMethods, CoverageInfoMethods, MiscMethods, StaticMethods, +}; +use rustc_data_structures::fx::FxHashMap; +use rustc_llvm::RustString; +use rustc_middle::mir::coverage::{ + CodeRegion, CounterValueReference, ExpressionOperandId, InjectedExpressionIndex, Op, +}; +use rustc_middle::ty::Instance; + +use std::cell::RefCell; +use std::ffi::CString; + +use tracing::debug; + +pub mod mapgen; + +const COVMAP_VAR_ALIGN_BYTES: usize = 8; + +/// A context object for maintaining all state needed by the coverageinfo module. +pub struct CrateCoverageContext<'tcx> { + // Coverage region data for each instrumented function identified by DefId. + pub(crate) function_coverage_map: RefCell<FxHashMap<Instance<'tcx>, FunctionCoverage>>, +} + +impl<'tcx> CrateCoverageContext<'tcx> { + pub fn new() -> Self { + Self { function_coverage_map: Default::default() } + } + + pub fn take_function_coverage_map(&self) -> FxHashMap<Instance<'tcx>, FunctionCoverage> { + self.function_coverage_map.replace(FxHashMap::default()) + } +} + +impl CoverageInfoMethods for CodegenCx<'ll, 'tcx> { + fn coverageinfo_finalize(&self) { + mapgen::finalize(self) + } +} + +impl CoverageInfoBuilderMethods<'tcx> for Builder<'a, 'll, 'tcx> { + /// Calls llvm::createPGOFuncNameVar() with the given function instance's mangled function name. + /// The LLVM API returns an llvm::GlobalVariable containing the function name, with the specific + /// variable name and linkage required by LLVM InstrProf source-based coverage instrumentation. + fn create_pgo_func_name_var(&self, instance: Instance<'tcx>) -> Self::Value { + let llfn = self.cx.get_fn(instance); + let mangled_fn_name = CString::new(self.tcx.symbol_name(instance).name) + .expect("error converting function name to C string"); + unsafe { llvm::LLVMRustCoverageCreatePGOFuncNameVar(llfn, mangled_fn_name.as_ptr()) } + } + + fn add_counter_region( + &mut self, + instance: Instance<'tcx>, + function_source_hash: u64, + id: CounterValueReference, + region: CodeRegion, + ) { + debug!( + "adding counter to coverage_regions: instance={:?}, function_source_hash={}, id={:?}, \ + at {:?}", + instance, function_source_hash, id, region, + ); + let mut coverage_regions = self.coverage_context().function_coverage_map.borrow_mut(); + coverage_regions + .entry(instance) + .or_insert_with(|| FunctionCoverage::new(self.tcx, instance)) + .add_counter(function_source_hash, id, region); + } + + fn add_counter_expression_region( + &mut self, + instance: Instance<'tcx>, + id: InjectedExpressionIndex, + lhs: ExpressionOperandId, + op: Op, + rhs: ExpressionOperandId, + region: CodeRegion, + ) { + debug!( + "adding counter expression to coverage_regions: instance={:?}, id={:?}, {:?} {:?} {:?}, \ + at {:?}", + instance, id, lhs, op, rhs, region, + ); + let mut coverage_regions = self.coverage_context().function_coverage_map.borrow_mut(); + coverage_regions + .entry(instance) + .or_insert_with(|| FunctionCoverage::new(self.tcx, instance)) + .add_counter_expression(id, lhs, op, rhs, region); + } + + fn add_unreachable_region(&mut self, instance: Instance<'tcx>, region: CodeRegion) { + debug!( + "adding unreachable code to coverage_regions: instance={:?}, at {:?}", + instance, region, + ); + let mut coverage_regions = self.coverage_context().function_coverage_map.borrow_mut(); + coverage_regions + .entry(instance) + .or_insert_with(|| FunctionCoverage::new(self.tcx, instance)) + .add_unreachable_region(region); + } +} + +pub(crate) fn write_filenames_section_to_buffer<'a>( + filenames: impl IntoIterator<Item = &'a CString>, + buffer: &RustString, +) { + let c_str_vec = filenames.into_iter().map(|cstring| cstring.as_ptr()).collect::<Vec<_>>(); + unsafe { + llvm::LLVMRustCoverageWriteFilenamesSectionToBuffer( + c_str_vec.as_ptr(), + c_str_vec.len(), + buffer, + ); + } +} + +pub(crate) fn write_mapping_to_buffer( + virtual_file_mapping: Vec<u32>, + expressions: Vec<CounterExpression>, + mut mapping_regions: Vec<CounterMappingRegion>, + buffer: &RustString, +) { + unsafe { + llvm::LLVMRustCoverageWriteMappingToBuffer( + virtual_file_mapping.as_ptr(), + virtual_file_mapping.len() as c_uint, + expressions.as_ptr(), + expressions.len() as c_uint, + mapping_regions.as_mut_ptr(), + mapping_regions.len() as c_uint, + buffer, + ); + } +} + +pub(crate) fn compute_hash(name: &str) -> u64 { + let name = CString::new(name).expect("null error converting hashable name to C string"); + unsafe { llvm::LLVMRustCoverageComputeHash(name.as_ptr()) } +} + +pub(crate) fn mapping_version() -> u32 { + unsafe { llvm::LLVMRustCoverageMappingVersion() } +} + +pub(crate) fn save_map_to_mod<'ll, 'tcx>( + cx: &CodegenCx<'ll, 'tcx>, + cov_data_val: &'ll llvm::Value, +) { + let covmap_var_name = llvm::build_string(|s| unsafe { + llvm::LLVMRustCoverageWriteMappingVarNameToString(s); + }) + .expect("Rust Coverage Mapping var name failed UTF-8 conversion"); + debug!("covmap var name: {:?}", covmap_var_name); + + let covmap_section_name = llvm::build_string(|s| unsafe { + llvm::LLVMRustCoverageWriteSectionNameToString(cx.llmod, s); + }) + .expect("Rust Coverage section name failed UTF-8 conversion"); + debug!("covmap section name: {:?}", covmap_section_name); + + let llglobal = llvm::add_global(cx.llmod, cx.val_ty(cov_data_val), &covmap_var_name); + llvm::set_initializer(llglobal, cov_data_val); + llvm::set_global_constant(llglobal, true); + llvm::set_linkage(llglobal, llvm::Linkage::InternalLinkage); + llvm::set_section(llglobal, &covmap_section_name); + llvm::set_alignment(llglobal, COVMAP_VAR_ALIGN_BYTES); + cx.add_used_global(llglobal); +} |
