Auto merge of #74091 - richkadel:llvm-coverage-map-gen-4, r=tmandry
Generating the coverage map @tmandry @wesleywiser rustc now generates the coverage map and can support (limited) coverage report generation, at the function level. Example commands to generate a coverage report: ```shell $ BUILD=$HOME/rust/build/x86_64-unknown-linux-gnu $ $BUILD/stage1/bin/rustc -Zinstrument-coverage \ $HOME/rust/src/test/run-make-fulldeps/instrument-coverage/main.rs $ LLVM_PROFILE_FILE="main.profraw" ./main called $ $BUILD/llvm/bin/llvm-profdata merge -sparse main.profraw -o main.profdata $ $BUILD/llvm/bin/llvm-cov show --instr-profile=main.profdata main ```  r? @wesleywiser Rust compiler MCP rust-lang/compiler-team#278 Relevant issue: #34701 - Implement support for LLVMs code coverage instrumentation
This commit is contained in:
commit
47ea6d90b0
52 changed files with 1726 additions and 264 deletions
|
@ -2821,6 +2821,13 @@ dependencies = [
|
||||||
"rls-span",
|
"rls-span",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-demangler"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"rustc-demangle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustbook"
|
name = "rustbook"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
@ -17,6 +17,7 @@ members = [
|
||||||
"src/tools/remote-test-client",
|
"src/tools/remote-test-client",
|
||||||
"src/tools/remote-test-server",
|
"src/tools/remote-test-server",
|
||||||
"src/tools/rust-installer",
|
"src/tools/rust-installer",
|
||||||
|
"src/tools/rust-demangler",
|
||||||
"src/tools/cargo",
|
"src/tools/cargo",
|
||||||
"src/tools/rustdoc",
|
"src/tools/rustdoc",
|
||||||
"src/tools/rls",
|
"src/tools/rls",
|
||||||
|
|
|
@ -370,6 +370,7 @@ impl<'a> Builder<'a> {
|
||||||
tool::Cargo,
|
tool::Cargo,
|
||||||
tool::Rls,
|
tool::Rls,
|
||||||
tool::RustAnalyzer,
|
tool::RustAnalyzer,
|
||||||
|
tool::RustDemangler,
|
||||||
tool::Rustdoc,
|
tool::Rustdoc,
|
||||||
tool::Clippy,
|
tool::Clippy,
|
||||||
tool::CargoClippy,
|
tool::CargoClippy,
|
||||||
|
|
|
@ -1022,6 +1022,10 @@ impl Step for Compiletest {
|
||||||
cmd.arg("--rustdoc-path").arg(builder.rustdoc(compiler));
|
cmd.arg("--rustdoc-path").arg(builder.rustdoc(compiler));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if mode == "run-make" && suite.ends_with("fulldeps") {
|
||||||
|
cmd.arg("--rust-demangler-path").arg(builder.tool_exe(Tool::RustDemangler));
|
||||||
|
}
|
||||||
|
|
||||||
cmd.arg("--src-base").arg(builder.src.join("src/test").join(suite));
|
cmd.arg("--src-base").arg(builder.src.join("src/test").join(suite));
|
||||||
cmd.arg("--build-base").arg(testdir(builder, compiler.host).join(suite));
|
cmd.arg("--build-base").arg(testdir(builder, compiler.host).join(suite));
|
||||||
cmd.arg("--stage-id").arg(format!("stage{}-{}", compiler.stage, target));
|
cmd.arg("--stage-id").arg(format!("stage{}-{}", compiler.stage, target));
|
||||||
|
|
|
@ -361,6 +361,7 @@ bootstrap_tool!(
|
||||||
Compiletest, "src/tools/compiletest", "compiletest", is_unstable_tool = true;
|
Compiletest, "src/tools/compiletest", "compiletest", is_unstable_tool = true;
|
||||||
BuildManifest, "src/tools/build-manifest", "build-manifest";
|
BuildManifest, "src/tools/build-manifest", "build-manifest";
|
||||||
RemoteTestClient, "src/tools/remote-test-client", "remote-test-client";
|
RemoteTestClient, "src/tools/remote-test-client", "remote-test-client";
|
||||||
|
RustDemangler, "src/tools/rust-demangler", "rust-demangler";
|
||||||
RustInstaller, "src/tools/rust-installer", "fabricate", is_external_tool = true;
|
RustInstaller, "src/tools/rust-installer", "fabricate", is_external_tool = true;
|
||||||
RustdocTheme, "src/tools/rustdoc-themes", "rustdoc-themes";
|
RustdocTheme, "src/tools/rustdoc-themes", "rustdoc-themes";
|
||||||
ExpandYamlAnchors, "src/tools/expand-yaml-anchors", "expand-yaml-anchors";
|
ExpandYamlAnchors, "src/tools/expand-yaml-anchors", "expand-yaml-anchors";
|
||||||
|
|
|
@ -1958,8 +1958,14 @@ extern "rust-intrinsic" {
|
||||||
/// Internal placeholder for injecting code coverage counters when the "instrument-coverage"
|
/// Internal placeholder for injecting code coverage counters when the "instrument-coverage"
|
||||||
/// option is enabled. The placeholder is replaced with `llvm.instrprof.increment` during code
|
/// option is enabled. The placeholder is replaced with `llvm.instrprof.increment` during code
|
||||||
/// generation.
|
/// generation.
|
||||||
|
#[cfg(not(bootstrap))]
|
||||||
#[lang = "count_code_region"]
|
#[lang = "count_code_region"]
|
||||||
pub fn count_code_region(index: u32, start_byte_pos: u32, end_byte_pos: u32);
|
pub fn count_code_region(
|
||||||
|
function_source_hash: u64,
|
||||||
|
index: u32,
|
||||||
|
start_byte_pos: u32,
|
||||||
|
end_byte_pos: u32,
|
||||||
|
);
|
||||||
|
|
||||||
/// Internal marker for code coverage expressions, injected into the MIR when the
|
/// Internal marker for code coverage expressions, injected into the MIR when the
|
||||||
/// "instrument-coverage" option is enabled. This intrinsic is not converted into a
|
/// "instrument-coverage" option is enabled. This intrinsic is not converted into a
|
||||||
|
@ -1967,6 +1973,8 @@ extern "rust-intrinsic" {
|
||||||
/// "coverage map", which is injected into the generated code, as additional data.
|
/// "coverage map", which is injected into the generated code, as additional data.
|
||||||
/// This marker identifies a code region and two other counters or counter expressions
|
/// This marker identifies a code region and two other counters or counter expressions
|
||||||
/// whose sum is the number of times the code region was executed.
|
/// whose sum is the number of times the code region was executed.
|
||||||
|
#[cfg(not(bootstrap))]
|
||||||
|
#[lang = "coverage_counter_add"]
|
||||||
pub fn coverage_counter_add(
|
pub fn coverage_counter_add(
|
||||||
index: u32,
|
index: u32,
|
||||||
left_index: u32,
|
left_index: u32,
|
||||||
|
@ -1978,6 +1986,8 @@ extern "rust-intrinsic" {
|
||||||
/// This marker identifies a code region and two other counters or counter expressions
|
/// This marker identifies a code region and two other counters or counter expressions
|
||||||
/// whose difference is the number of times the code region was executed.
|
/// whose difference is the number of times the code region was executed.
|
||||||
/// (See `coverage_counter_add` for more information.)
|
/// (See `coverage_counter_add` for more information.)
|
||||||
|
#[cfg(not(bootstrap))]
|
||||||
|
#[lang = "coverage_counter_subtract"]
|
||||||
pub fn coverage_counter_subtract(
|
pub fn coverage_counter_subtract(
|
||||||
index: u32,
|
index: u32,
|
||||||
left_index: u32,
|
left_index: u32,
|
||||||
|
|
|
@ -133,6 +133,9 @@ fn set_probestack(cx: &CodegenCx<'ll, '_>, llfn: &'ll Value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME(richkadel): Make sure probestack plays nice with `-Z instrument-coverage`
|
||||||
|
// or disable it if not, similar to above early exits.
|
||||||
|
|
||||||
// Flag our internal `__rust_probestack` function as the stack probe symbol.
|
// Flag our internal `__rust_probestack` function as the stack probe symbol.
|
||||||
// This is defined in the `compiler-builtins` crate for each architecture.
|
// This is defined in the `compiler-builtins` crate for each architecture.
|
||||||
llvm::AddFunctionAttrStringValue(
|
llvm::AddFunctionAttrStringValue(
|
||||||
|
|
|
@ -144,17 +144,18 @@ pub fn compile_codegen_unit(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Finalize code coverage by injecting the coverage map. Note, the coverage map will
|
||||||
|
// also be added to the `llvm.used` variable, created next.
|
||||||
|
if cx.sess().opts.debugging_opts.instrument_coverage {
|
||||||
|
cx.coverageinfo_finalize();
|
||||||
|
}
|
||||||
|
|
||||||
// Create the llvm.used variable
|
// Create the llvm.used variable
|
||||||
// This variable has type [N x i8*] and is stored in the llvm.metadata section
|
// This variable has type [N x i8*] and is stored in the llvm.metadata section
|
||||||
if !cx.used_statics().borrow().is_empty() {
|
if !cx.used_statics().borrow().is_empty() {
|
||||||
cx.create_used_variable()
|
cx.create_used_variable()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finalize code coverage by injecting the coverage map
|
|
||||||
if cx.sess().opts.debugging_opts.instrument_coverage {
|
|
||||||
cx.coverageinfo_finalize();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finalize debuginfo
|
// Finalize debuginfo
|
||||||
if cx.sess().opts.debuginfo != DebugInfo::None {
|
if cx.sess().opts.debuginfo != DebugInfo::None {
|
||||||
cx.debuginfo_finalize();
|
cx.debuginfo_finalize();
|
||||||
|
|
|
@ -1060,7 +1060,7 @@ impl BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
|
||||||
fn_name, hash, num_counters, index
|
fn_name, hash, num_counters, index
|
||||||
);
|
);
|
||||||
|
|
||||||
let llfn = unsafe { llvm::LLVMRustGetInstrprofIncrementIntrinsic(self.cx().llmod) };
|
let llfn = unsafe { llvm::LLVMRustGetInstrProfIncrementIntrinsic(self.cx().llmod) };
|
||||||
let args = &[fn_name, hash, num_counters, index];
|
let args = &[fn_name, hash, num_counters, index];
|
||||||
let args = self.check_call("call", llfn, args);
|
let args = self.check_call("call", llfn, args);
|
||||||
|
|
||||||
|
|
|
@ -493,10 +493,14 @@ impl StaticMethods for CodegenCx<'ll, 'tcx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if attrs.flags.contains(CodegenFnAttrFlags::USED) {
|
if attrs.flags.contains(CodegenFnAttrFlags::USED) {
|
||||||
// This static will be stored in the llvm.used variable which is an array of i8*
|
self.add_used_global(g);
|
||||||
let cast = llvm::LLVMConstPointerCast(g, self.type_i8p());
|
|
||||||
self.used_statics.borrow_mut().push(cast);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a global value to a list to be stored in the `llvm.used` variable, an array of i8*.
|
||||||
|
fn add_used_global(&self, global: &'ll Value) {
|
||||||
|
let cast = unsafe { llvm::LLVMConstPointerCast(global, self.type_i8p()) };
|
||||||
|
self.used_statics.borrow_mut().push(cast);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
274
src/librustc_codegen_llvm/coverageinfo/mapgen.rs
Normal file
274
src/librustc_codegen_llvm/coverageinfo/mapgen.rs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,67 +1,44 @@
|
||||||
|
use crate::llvm;
|
||||||
|
|
||||||
use crate::builder::Builder;
|
use crate::builder::Builder;
|
||||||
use crate::common::CodegenCx;
|
use crate::common::CodegenCx;
|
||||||
|
|
||||||
|
use libc::c_uint;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use rustc_codegen_ssa::coverageinfo::map::*;
|
use rustc_codegen_ssa::coverageinfo::map::*;
|
||||||
use rustc_codegen_ssa::traits::{CoverageInfoBuilderMethods, CoverageInfoMethods};
|
use rustc_codegen_ssa::traits::{
|
||||||
|
BaseTypeMethods, CoverageInfoBuilderMethods, CoverageInfoMethods, StaticMethods,
|
||||||
|
};
|
||||||
use rustc_data_structures::fx::FxHashMap;
|
use rustc_data_structures::fx::FxHashMap;
|
||||||
|
use rustc_llvm::RustString;
|
||||||
use rustc_middle::ty::Instance;
|
use rustc_middle::ty::Instance;
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::ffi::CString;
|
||||||
|
|
||||||
|
pub mod mapgen;
|
||||||
|
|
||||||
|
const COVMAP_VAR_ALIGN_BYTES: usize = 8;
|
||||||
|
|
||||||
/// A context object for maintaining all state needed by the coverageinfo module.
|
/// A context object for maintaining all state needed by the coverageinfo module.
|
||||||
pub struct CrateCoverageContext<'tcx> {
|
pub struct CrateCoverageContext<'tcx> {
|
||||||
// Coverage region data for each instrumented function identified by DefId.
|
// Coverage region data for each instrumented function identified by DefId.
|
||||||
pub(crate) coverage_regions: RefCell<FxHashMap<Instance<'tcx>, FunctionCoverageRegions>>,
|
pub(crate) function_coverage_map: RefCell<FxHashMap<Instance<'tcx>, FunctionCoverage>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'tcx> CrateCoverageContext<'tcx> {
|
impl<'tcx> CrateCoverageContext<'tcx> {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { coverage_regions: Default::default() }
|
Self { function_coverage_map: Default::default() }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Generates and exports the Coverage Map.
|
pub fn take_function_coverage_map(&self) -> FxHashMap<Instance<'tcx>, FunctionCoverage> {
|
||||||
// FIXME(richkadel): Actually generate and export the coverage map to LLVM.
|
self.function_coverage_map.replace(FxHashMap::default())
|
||||||
// The current implementation is actually just debug messages to show the data is available.
|
|
||||||
pub fn finalize(cx: &CodegenCx<'_, '_>) {
|
|
||||||
let coverage_regions = &*cx.coverage_context().coverage_regions.borrow();
|
|
||||||
for instance in coverage_regions.keys() {
|
|
||||||
let coverageinfo = cx.tcx.coverageinfo(instance.def_id());
|
|
||||||
debug_assert!(coverageinfo.num_counters > 0);
|
|
||||||
debug!(
|
|
||||||
"Generate coverage map for: {:?}, hash: {}, num_counters: {}",
|
|
||||||
instance, coverageinfo.hash, coverageinfo.num_counters
|
|
||||||
);
|
|
||||||
let function_coverage_regions = &coverage_regions[instance];
|
|
||||||
for (index, region) in function_coverage_regions.indexed_regions() {
|
|
||||||
match region.kind {
|
|
||||||
CoverageKind::Counter => debug!(
|
|
||||||
" Counter {}, for {}..{}",
|
|
||||||
index, region.coverage_span.start_byte_pos, region.coverage_span.end_byte_pos
|
|
||||||
),
|
|
||||||
CoverageKind::CounterExpression(lhs, op, rhs) => debug!(
|
|
||||||
" CounterExpression {} = {} {:?} {}, for {}..{}",
|
|
||||||
index,
|
|
||||||
lhs,
|
|
||||||
op,
|
|
||||||
rhs,
|
|
||||||
region.coverage_span.start_byte_pos,
|
|
||||||
region.coverage_span.end_byte_pos
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for unreachable in function_coverage_regions.unreachable_regions() {
|
|
||||||
debug!(
|
|
||||||
" Unreachable code region: {}..{}",
|
|
||||||
unreachable.start_byte_pos, unreachable.end_byte_pos
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CoverageInfoMethods for CodegenCx<'ll, 'tcx> {
|
impl CoverageInfoMethods for CodegenCx<'ll, 'tcx> {
|
||||||
fn coverageinfo_finalize(&self) {
|
fn coverageinfo_finalize(&self) {
|
||||||
finalize(self)
|
mapgen::finalize(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,20 +46,22 @@ impl CoverageInfoBuilderMethods<'tcx> for Builder<'a, 'll, 'tcx> {
|
||||||
fn add_counter_region(
|
fn add_counter_region(
|
||||||
&mut self,
|
&mut self,
|
||||||
instance: Instance<'tcx>,
|
instance: Instance<'tcx>,
|
||||||
|
function_source_hash: u64,
|
||||||
index: u32,
|
index: u32,
|
||||||
start_byte_pos: u32,
|
start_byte_pos: u32,
|
||||||
end_byte_pos: u32,
|
end_byte_pos: u32,
|
||||||
) {
|
) {
|
||||||
debug!(
|
debug!(
|
||||||
"adding counter to coverage map: instance={:?}, index={}, byte range {}..{}",
|
"adding counter to coverage_regions: instance={:?}, function_source_hash={}, index={}, byte range {}..{}",
|
||||||
instance, index, start_byte_pos, end_byte_pos,
|
instance, function_source_hash, index, start_byte_pos, end_byte_pos,
|
||||||
);
|
|
||||||
let mut coverage_regions = self.coverage_context().coverage_regions.borrow_mut();
|
|
||||||
coverage_regions.entry(instance).or_default().add_counter(
|
|
||||||
index,
|
|
||||||
start_byte_pos,
|
|
||||||
end_byte_pos,
|
|
||||||
);
|
);
|
||||||
|
let mut coverage_regions = self.coverage_context().function_coverage_map.borrow_mut();
|
||||||
|
coverage_regions
|
||||||
|
.entry(instance)
|
||||||
|
.or_insert_with(|| {
|
||||||
|
FunctionCoverage::with_coverageinfo(self.tcx.coverageinfo(instance.def_id()))
|
||||||
|
})
|
||||||
|
.add_counter(function_source_hash, index, start_byte_pos, end_byte_pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_counter_expression_region(
|
fn add_counter_expression_region(
|
||||||
|
@ -96,18 +75,16 @@ impl CoverageInfoBuilderMethods<'tcx> for Builder<'a, 'll, 'tcx> {
|
||||||
end_byte_pos: u32,
|
end_byte_pos: u32,
|
||||||
) {
|
) {
|
||||||
debug!(
|
debug!(
|
||||||
"adding counter expression to coverage map: instance={:?}, index={}, {} {:?} {}, byte range {}..{}",
|
"adding counter expression to coverage_regions: instance={:?}, index={}, {} {:?} {}, byte range {}..{}",
|
||||||
instance, index, lhs, op, rhs, start_byte_pos, end_byte_pos,
|
instance, index, lhs, op, rhs, start_byte_pos, end_byte_pos,
|
||||||
);
|
);
|
||||||
let mut coverage_regions = self.coverage_context().coverage_regions.borrow_mut();
|
let mut coverage_regions = self.coverage_context().function_coverage_map.borrow_mut();
|
||||||
coverage_regions.entry(instance).or_default().add_counter_expression(
|
coverage_regions
|
||||||
index,
|
.entry(instance)
|
||||||
lhs,
|
.or_insert_with(|| {
|
||||||
op,
|
FunctionCoverage::with_coverageinfo(self.tcx.coverageinfo(instance.def_id()))
|
||||||
rhs,
|
})
|
||||||
start_byte_pos,
|
.add_counter_expression(index, lhs, op, rhs, start_byte_pos, end_byte_pos);
|
||||||
end_byte_pos,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_unreachable_region(
|
fn add_unreachable_region(
|
||||||
|
@ -117,10 +94,175 @@ impl CoverageInfoBuilderMethods<'tcx> for Builder<'a, 'll, 'tcx> {
|
||||||
end_byte_pos: u32,
|
end_byte_pos: u32,
|
||||||
) {
|
) {
|
||||||
debug!(
|
debug!(
|
||||||
"adding unreachable code to coverage map: instance={:?}, byte range {}..{}",
|
"adding unreachable code to coverage_regions: instance={:?}, byte range {}..{}",
|
||||||
instance, start_byte_pos, end_byte_pos,
|
instance, start_byte_pos, end_byte_pos,
|
||||||
);
|
);
|
||||||
let mut coverage_regions = self.coverage_context().coverage_regions.borrow_mut();
|
let mut coverage_regions = self.coverage_context().function_coverage_map.borrow_mut();
|
||||||
coverage_regions.entry(instance).or_default().add_unreachable(start_byte_pos, end_byte_pos);
|
coverage_regions
|
||||||
|
.entry(instance)
|
||||||
|
.or_insert_with(|| {
|
||||||
|
FunctionCoverage::with_coverageinfo(self.tcx.coverageinfo(instance.def_id()))
|
||||||
|
})
|
||||||
|
.add_unreachable(start_byte_pos, end_byte_pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This struct wraps an opaque reference to the C++ template instantiation of
|
||||||
|
/// `llvm::SmallVector<coverage::CounterExpression>`. Each `coverage::CounterExpression` object is
|
||||||
|
/// constructed from primative-typed arguments, and pushed to the `SmallVector`, in the C++
|
||||||
|
/// implementation of `LLVMRustCoverageSmallVectorCounterExpressionAdd()` (see
|
||||||
|
/// `src/rustllvm/CoverageMappingWrapper.cpp`).
|
||||||
|
pub struct SmallVectorCounterExpression<'a> {
|
||||||
|
pub raw: &'a mut llvm::coverageinfo::SmallVectorCounterExpression<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SmallVectorCounterExpression<'a> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
SmallVectorCounterExpression {
|
||||||
|
raw: unsafe { llvm::LLVMRustCoverageSmallVectorCounterExpressionCreate() },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_ptr(&self) -> *const llvm::coverageinfo::SmallVectorCounterExpression<'a> {
|
||||||
|
self.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_from(
|
||||||
|
&mut self,
|
||||||
|
kind: rustc_codegen_ssa::coverageinfo::CounterOp,
|
||||||
|
left_index: u32,
|
||||||
|
right_index: u32,
|
||||||
|
) {
|
||||||
|
unsafe {
|
||||||
|
llvm::LLVMRustCoverageSmallVectorCounterExpressionAdd(
|
||||||
|
&mut *(self.raw as *mut _),
|
||||||
|
kind,
|
||||||
|
left_index,
|
||||||
|
right_index,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for SmallVectorCounterExpression<'a> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
llvm::LLVMRustCoverageSmallVectorCounterExpressionDispose(&mut *(self.raw as *mut _));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This struct wraps an opaque reference to the C++ template instantiation of
|
||||||
|
/// `llvm::SmallVector<coverage::CounterMappingRegion>`. Each `coverage::CounterMappingRegion`
|
||||||
|
/// object is constructed from primative-typed arguments, and pushed to the `SmallVector`, in the
|
||||||
|
/// C++ implementation of `LLVMRustCoverageSmallVectorCounterMappingRegionAdd()` (see
|
||||||
|
/// `src/rustllvm/CoverageMappingWrapper.cpp`).
|
||||||
|
pub struct SmallVectorCounterMappingRegion<'a> {
|
||||||
|
pub raw: &'a mut llvm::coverageinfo::SmallVectorCounterMappingRegion<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SmallVectorCounterMappingRegion<'a> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
SmallVectorCounterMappingRegion {
|
||||||
|
raw: unsafe { llvm::LLVMRustCoverageSmallVectorCounterMappingRegionCreate() },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_ptr(&self) -> *const llvm::coverageinfo::SmallVectorCounterMappingRegion<'a> {
|
||||||
|
self.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_from(
|
||||||
|
&mut self,
|
||||||
|
index: u32,
|
||||||
|
file_id: u32,
|
||||||
|
line_start: u32,
|
||||||
|
column_start: u32,
|
||||||
|
line_end: u32,
|
||||||
|
column_end: u32,
|
||||||
|
) {
|
||||||
|
unsafe {
|
||||||
|
llvm::LLVMRustCoverageSmallVectorCounterMappingRegionAdd(
|
||||||
|
&mut *(self.raw as *mut _),
|
||||||
|
index,
|
||||||
|
file_id,
|
||||||
|
line_start,
|
||||||
|
column_start,
|
||||||
|
line_end,
|
||||||
|
column_end,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for SmallVectorCounterMappingRegion<'a> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
llvm::LLVMRustCoverageSmallVectorCounterMappingRegionDispose(
|
||||||
|
&mut *(self.raw as *mut _),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn write_filenames_section_to_buffer(filenames: &Vec<CString>, buffer: &RustString) {
|
||||||
|
let c_str_vec = filenames.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: SmallVectorCounterExpression<'_>,
|
||||||
|
mapping_regions: SmallVectorCounterMappingRegion<'_>,
|
||||||
|
buffer: &RustString,
|
||||||
|
) {
|
||||||
|
unsafe {
|
||||||
|
llvm::LLVMRustCoverageWriteMappingToBuffer(
|
||||||
|
virtual_file_mapping.as_ptr(),
|
||||||
|
virtual_file_mapping.len() as c_uint,
|
||||||
|
expressions.as_ptr(),
|
||||||
|
mapping_regions.as_ptr(),
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -90,45 +90,64 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> {
|
||||||
args: &Vec<Operand<'tcx>>,
|
args: &Vec<Operand<'tcx>>,
|
||||||
caller_instance: ty::Instance<'tcx>,
|
caller_instance: ty::Instance<'tcx>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match intrinsic {
|
if self.tcx.sess.opts.debugging_opts.instrument_coverage {
|
||||||
sym::count_code_region => {
|
// Add the coverage information from the MIR to the Codegen context. Some coverage
|
||||||
use coverage::count_code_region_args::*;
|
// intrinsics are used only to pass along the coverage information (returns `false`
|
||||||
self.add_counter_region(
|
// for `is_codegen_intrinsic()`), but `count_code_region` is also converted into an
|
||||||
caller_instance,
|
// LLVM intrinsic to increment a coverage counter.
|
||||||
op_to_u32(&args[COUNTER_INDEX]),
|
match intrinsic {
|
||||||
op_to_u32(&args[START_BYTE_POS]),
|
sym::count_code_region => {
|
||||||
op_to_u32(&args[END_BYTE_POS]),
|
use coverage::count_code_region_args::*;
|
||||||
);
|
self.add_counter_region(
|
||||||
true // Also inject the counter increment in the backend
|
caller_instance,
|
||||||
|
op_to_u64(&args[FUNCTION_SOURCE_HASH]),
|
||||||
|
op_to_u32(&args[COUNTER_INDEX]),
|
||||||
|
op_to_u32(&args[START_BYTE_POS]),
|
||||||
|
op_to_u32(&args[END_BYTE_POS]),
|
||||||
|
);
|
||||||
|
return true; // Also inject the counter increment in the backend
|
||||||
|
}
|
||||||
|
sym::coverage_counter_add | sym::coverage_counter_subtract => {
|
||||||
|
use coverage::coverage_counter_expression_args::*;
|
||||||
|
self.add_counter_expression_region(
|
||||||
|
caller_instance,
|
||||||
|
op_to_u32(&args[COUNTER_EXPRESSION_INDEX]),
|
||||||
|
op_to_u32(&args[LEFT_INDEX]),
|
||||||
|
if intrinsic == sym::coverage_counter_add {
|
||||||
|
CounterOp::Add
|
||||||
|
} else {
|
||||||
|
CounterOp::Subtract
|
||||||
|
},
|
||||||
|
op_to_u32(&args[RIGHT_INDEX]),
|
||||||
|
op_to_u32(&args[START_BYTE_POS]),
|
||||||
|
op_to_u32(&args[END_BYTE_POS]),
|
||||||
|
);
|
||||||
|
return false; // Does not inject backend code
|
||||||
|
}
|
||||||
|
sym::coverage_unreachable => {
|
||||||
|
use coverage::coverage_unreachable_args::*;
|
||||||
|
self.add_unreachable_region(
|
||||||
|
caller_instance,
|
||||||
|
op_to_u32(&args[START_BYTE_POS]),
|
||||||
|
op_to_u32(&args[END_BYTE_POS]),
|
||||||
|
);
|
||||||
|
return false; // Does not inject backend code
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
sym::coverage_counter_add | sym::coverage_counter_subtract => {
|
} else {
|
||||||
use coverage::coverage_counter_expression_args::*;
|
// NOT self.tcx.sess.opts.debugging_opts.instrument_coverage
|
||||||
self.add_counter_expression_region(
|
if intrinsic == sym::count_code_region {
|
||||||
caller_instance,
|
// An external crate may have been pre-compiled with coverage instrumentation, and
|
||||||
op_to_u32(&args[COUNTER_EXPRESSION_INDEX]),
|
// some references from the current crate to the external crate might carry along
|
||||||
op_to_u32(&args[LEFT_INDEX]),
|
// the call terminators to coverage intrinsics, like `count_code_region` (for
|
||||||
if intrinsic == sym::coverage_counter_add {
|
// example, when instantiating a generic function). If the current crate has
|
||||||
CounterOp::Add
|
// `instrument_coverage` disabled, the `count_code_region` call terminators should
|
||||||
} else {
|
// be ignored.
|
||||||
CounterOp::Subtract
|
return false; // Do not inject coverage counters inlined from external crates
|
||||||
},
|
|
||||||
op_to_u32(&args[RIGHT_INDEX]),
|
|
||||||
op_to_u32(&args[START_BYTE_POS]),
|
|
||||||
op_to_u32(&args[END_BYTE_POS]),
|
|
||||||
);
|
|
||||||
false // Does not inject backend code
|
|
||||||
}
|
}
|
||||||
sym::coverage_unreachable => {
|
|
||||||
use coverage::coverage_unreachable_args::*;
|
|
||||||
self.add_unreachable_region(
|
|
||||||
caller_instance,
|
|
||||||
op_to_u32(&args[START_BYTE_POS]),
|
|
||||||
op_to_u32(&args[END_BYTE_POS]),
|
|
||||||
);
|
|
||||||
false // Does not inject backend code
|
|
||||||
}
|
|
||||||
_ => true, // Unhandled intrinsics should be passed to `codegen_intrinsic_call()`
|
|
||||||
}
|
}
|
||||||
|
true // Unhandled intrinsics should be passed to `codegen_intrinsic_call()`
|
||||||
}
|
}
|
||||||
|
|
||||||
fn codegen_intrinsic_call(
|
fn codegen_intrinsic_call(
|
||||||
|
@ -197,12 +216,13 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> {
|
||||||
let coverageinfo = tcx.coverageinfo(caller_instance.def_id());
|
let coverageinfo = tcx.coverageinfo(caller_instance.def_id());
|
||||||
let mangled_fn = tcx.symbol_name(caller_instance);
|
let mangled_fn = tcx.symbol_name(caller_instance);
|
||||||
let (mangled_fn_name, _len_val) = self.const_str(Symbol::intern(mangled_fn.name));
|
let (mangled_fn_name, _len_val) = self.const_str(Symbol::intern(mangled_fn.name));
|
||||||
let hash = self.const_u64(coverageinfo.hash);
|
|
||||||
let num_counters = self.const_u32(coverageinfo.num_counters);
|
let num_counters = self.const_u32(coverageinfo.num_counters);
|
||||||
use coverage::count_code_region_args::*;
|
use coverage::count_code_region_args::*;
|
||||||
|
let hash = args[FUNCTION_SOURCE_HASH].immediate();
|
||||||
let index = args[COUNTER_INDEX].immediate();
|
let index = args[COUNTER_INDEX].immediate();
|
||||||
debug!(
|
debug!(
|
||||||
"count_code_region to LLVM intrinsic instrprof.increment(fn_name={}, hash={:?}, num_counters={:?}, index={:?})",
|
"translating Rust intrinsic `count_code_region()` to LLVM intrinsic: \
|
||||||
|
instrprof.increment(fn_name={}, hash={:?}, num_counters={:?}, index={:?})",
|
||||||
mangled_fn.name, hash, num_counters, index,
|
mangled_fn.name, hash, num_counters, index,
|
||||||
);
|
);
|
||||||
self.instrprof_increment(mangled_fn_name, hash, num_counters, index)
|
self.instrprof_increment(mangled_fn_name, hash, num_counters, index)
|
||||||
|
@ -2222,3 +2242,7 @@ fn float_type_width(ty: Ty<'_>) -> Option<u64> {
|
||||||
fn op_to_u32<'tcx>(op: &Operand<'tcx>) -> u32 {
|
fn op_to_u32<'tcx>(op: &Operand<'tcx>) -> u32 {
|
||||||
Operand::scalar_from_const(op).to_u32().expect("Scalar is u32")
|
Operand::scalar_from_const(op).to_u32().expect("Scalar is u32")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn op_to_u64<'tcx>(op: &Operand<'tcx>) -> u64 {
|
||||||
|
Operand::scalar_from_const(op).to_u64().expect("Scalar is u64")
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#![allow(non_camel_case_types)]
|
#![allow(non_camel_case_types)]
|
||||||
#![allow(non_upper_case_globals)]
|
#![allow(non_upper_case_globals)]
|
||||||
|
|
||||||
|
use super::coverageinfo::{SmallVectorCounterExpression, SmallVectorCounterMappingRegion};
|
||||||
|
|
||||||
use super::debuginfo::{
|
use super::debuginfo::{
|
||||||
DIArray, DIBasicType, DIBuilder, DICompositeType, DIDerivedType, DIDescriptor, DIEnumerator,
|
DIArray, DIBasicType, DIBuilder, DICompositeType, DIDerivedType, DIDescriptor, DIEnumerator,
|
||||||
DIFile, DIFlags, DIGlobalVariableExpression, DILexicalBlock, DINameSpace, DISPFlags, DIScope,
|
DIFile, DIFlags, DIGlobalVariableExpression, DILexicalBlock, DINameSpace, DISPFlags, DIScope,
|
||||||
|
@ -650,6 +652,16 @@ pub struct Linker<'a>(InvariantOpaque<'a>);
|
||||||
pub type DiagnosticHandler = unsafe extern "C" fn(&DiagnosticInfo, *mut c_void);
|
pub type DiagnosticHandler = unsafe extern "C" fn(&DiagnosticInfo, *mut c_void);
|
||||||
pub type InlineAsmDiagHandler = unsafe extern "C" fn(&SMDiagnostic, *const c_void, c_uint);
|
pub type InlineAsmDiagHandler = unsafe extern "C" fn(&SMDiagnostic, *const c_void, c_uint);
|
||||||
|
|
||||||
|
pub mod coverageinfo {
|
||||||
|
use super::InvariantOpaque;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct SmallVectorCounterExpression<'a>(InvariantOpaque<'a>);
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct SmallVectorCounterMappingRegion<'a>(InvariantOpaque<'a>);
|
||||||
|
}
|
||||||
|
|
||||||
pub mod debuginfo {
|
pub mod debuginfo {
|
||||||
use super::{InvariantOpaque, Metadata};
|
use super::{InvariantOpaque, Metadata};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
|
@ -1365,7 +1377,7 @@ extern "C" {
|
||||||
|
|
||||||
// Miscellaneous instructions
|
// Miscellaneous instructions
|
||||||
pub fn LLVMBuildPhi(B: &Builder<'a>, Ty: &'a Type, Name: *const c_char) -> &'a Value;
|
pub fn LLVMBuildPhi(B: &Builder<'a>, Ty: &'a Type, Name: *const c_char) -> &'a Value;
|
||||||
pub fn LLVMRustGetInstrprofIncrementIntrinsic(M: &Module) -> &'a Value;
|
pub fn LLVMRustGetInstrProfIncrementIntrinsic(M: &Module) -> &'a Value;
|
||||||
pub fn LLVMRustBuildCall(
|
pub fn LLVMRustBuildCall(
|
||||||
B: &Builder<'a>,
|
B: &Builder<'a>,
|
||||||
Fn: &'a Value,
|
Fn: &'a Value,
|
||||||
|
@ -1633,6 +1645,58 @@ extern "C" {
|
||||||
ConstraintsLen: size_t,
|
ConstraintsLen: size_t,
|
||||||
) -> bool;
|
) -> bool;
|
||||||
|
|
||||||
|
pub fn LLVMRustCoverageSmallVectorCounterExpressionCreate()
|
||||||
|
-> &'a mut SmallVectorCounterExpression<'a>;
|
||||||
|
pub fn LLVMRustCoverageSmallVectorCounterExpressionDispose(
|
||||||
|
Container: &'a mut SmallVectorCounterExpression<'a>,
|
||||||
|
);
|
||||||
|
pub fn LLVMRustCoverageSmallVectorCounterExpressionAdd(
|
||||||
|
Container: &mut SmallVectorCounterExpression<'a>,
|
||||||
|
Kind: rustc_codegen_ssa::coverageinfo::CounterOp,
|
||||||
|
LeftIndex: c_uint,
|
||||||
|
RightIndex: c_uint,
|
||||||
|
);
|
||||||
|
|
||||||
|
pub fn LLVMRustCoverageSmallVectorCounterMappingRegionCreate()
|
||||||
|
-> &'a mut SmallVectorCounterMappingRegion<'a>;
|
||||||
|
pub fn LLVMRustCoverageSmallVectorCounterMappingRegionDispose(
|
||||||
|
Container: &'a mut SmallVectorCounterMappingRegion<'a>,
|
||||||
|
);
|
||||||
|
pub fn LLVMRustCoverageSmallVectorCounterMappingRegionAdd(
|
||||||
|
Container: &mut SmallVectorCounterMappingRegion<'a>,
|
||||||
|
Index: c_uint,
|
||||||
|
FileID: c_uint,
|
||||||
|
LineStart: c_uint,
|
||||||
|
ColumnStart: c_uint,
|
||||||
|
LineEnd: c_uint,
|
||||||
|
ColumnEnd: c_uint,
|
||||||
|
);
|
||||||
|
|
||||||
|
#[allow(improper_ctypes)]
|
||||||
|
pub fn LLVMRustCoverageWriteFilenamesSectionToBuffer(
|
||||||
|
Filenames: *const *const c_char,
|
||||||
|
FilenamesLen: size_t,
|
||||||
|
BufferOut: &RustString,
|
||||||
|
);
|
||||||
|
|
||||||
|
#[allow(improper_ctypes)]
|
||||||
|
pub fn LLVMRustCoverageWriteMappingToBuffer(
|
||||||
|
VirtualFileMappingIDs: *const c_uint,
|
||||||
|
NumVirtualFileMappingIDs: c_uint,
|
||||||
|
Expressions: *const SmallVectorCounterExpression<'_>,
|
||||||
|
MappingRegions: *const SmallVectorCounterMappingRegion<'_>,
|
||||||
|
BufferOut: &RustString,
|
||||||
|
);
|
||||||
|
|
||||||
|
pub fn LLVMRustCoverageComputeHash(Name: *const c_char) -> u64;
|
||||||
|
|
||||||
|
#[allow(improper_ctypes)]
|
||||||
|
pub fn LLVMRustCoverageWriteSectionNameToString(M: &Module, Str: &RustString);
|
||||||
|
|
||||||
|
#[allow(improper_ctypes)]
|
||||||
|
pub fn LLVMRustCoverageWriteMappingVarNameToString(Str: &RustString);
|
||||||
|
|
||||||
|
pub fn LLVMRustCoverageMappingVersion() -> u32;
|
||||||
pub fn LLVMRustDebugMetadataVersion() -> u32;
|
pub fn LLVMRustDebugMetadataVersion() -> u32;
|
||||||
pub fn LLVMRustVersionMajor() -> u32;
|
pub fn LLVMRustVersionMajor() -> u32;
|
||||||
pub fn LLVMRustVersionMinor() -> u32;
|
pub fn LLVMRustVersionMinor() -> u32;
|
||||||
|
|
|
@ -12,7 +12,7 @@ use libc::c_uint;
|
||||||
use rustc_data_structures::small_c_str::SmallCStr;
|
use rustc_data_structures::small_c_str::SmallCStr;
|
||||||
use rustc_llvm::RustString;
|
use rustc_llvm::RustString;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::ffi::CStr;
|
use std::ffi::{CStr, CString};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::string::FromUtf8Error;
|
use std::string::FromUtf8Error;
|
||||||
|
|
||||||
|
@ -189,6 +189,42 @@ pub fn mk_section_iter(llof: &ffi::ObjectFile) -> SectionIter<'_> {
|
||||||
unsafe { SectionIter { llsi: LLVMGetSections(llof) } }
|
unsafe { SectionIter { llsi: LLVMGetSections(llof) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_section(llglobal: &Value, section_name: &str) {
|
||||||
|
let section_name_cstr = CString::new(section_name).expect("unexpected CString error");
|
||||||
|
unsafe {
|
||||||
|
LLVMSetSection(llglobal, section_name_cstr.as_ptr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_global<'a>(llmod: &'a Module, ty: &'a Type, name: &str) -> &'a Value {
|
||||||
|
let name_cstr = CString::new(name).expect("unexpected CString error");
|
||||||
|
unsafe { LLVMAddGlobal(llmod, ty, name_cstr.as_ptr()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_initializer(llglobal: &Value, constant_val: &Value) {
|
||||||
|
unsafe {
|
||||||
|
LLVMSetInitializer(llglobal, constant_val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_global_constant(llglobal: &Value, is_constant: bool) {
|
||||||
|
unsafe {
|
||||||
|
LLVMSetGlobalConstant(llglobal, if is_constant { ffi::True } else { ffi::False });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_linkage(llglobal: &Value, linkage: Linkage) {
|
||||||
|
unsafe {
|
||||||
|
LLVMRustSetLinkage(llglobal, linkage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_alignment(llglobal: &Value, bytes: usize) {
|
||||||
|
unsafe {
|
||||||
|
ffi::LLVMSetAlignment(llglobal, bytes as c_uint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Safe wrapper around `LLVMGetParam`, because segfaults are no fun.
|
/// Safe wrapper around `LLVMGetParam`, because segfaults are no fun.
|
||||||
pub fn get_param(llfn: &Value, index: c_uint) -> &Value {
|
pub fn get_param(llfn: &Value, index: c_uint) -> &Value {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -225,6 +261,12 @@ pub fn build_string(f: impl FnOnce(&RustString)) -> Result<String, FromUtf8Error
|
||||||
String::from_utf8(sr.bytes.into_inner())
|
String::from_utf8(sr.bytes.into_inner())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn build_byte_buffer(f: impl FnOnce(&RustString)) -> Vec<u8> {
|
||||||
|
let sr = RustString { bytes: RefCell::new(Vec::new()) };
|
||||||
|
f(&sr);
|
||||||
|
sr.bytes.into_inner()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn twine_to_string(tr: &Twine) -> String {
|
pub fn twine_to_string(tr: &Twine) -> String {
|
||||||
unsafe {
|
unsafe {
|
||||||
build_string(|s| LLVMRustWriteTwineToString(tr, s)).expect("got a non-UTF8 Twine from LLVM")
|
build_string(|s| LLVMRustWriteTwineToString(tr, s)).expect("got a non-UTF8 Twine from LLVM")
|
||||||
|
|
|
@ -1659,7 +1659,7 @@ fn linker_with_args<'a, B: ArchiveBuilder<'a>>(
|
||||||
// FIXME: Order dependent, applies to the following objects. Where should it be placed?
|
// FIXME: Order dependent, applies to the following objects. Where should it be placed?
|
||||||
// Try to strip as much out of the generated object by removing unused
|
// Try to strip as much out of the generated object by removing unused
|
||||||
// sections if possible. See more comments in linker.rs
|
// sections if possible. See more comments in linker.rs
|
||||||
if !sess.opts.cg.link_dead_code {
|
if sess.opts.cg.link_dead_code != Some(true) {
|
||||||
let keep_metadata = crate_type == CrateType::Dylib;
|
let keep_metadata = crate_type == CrateType::Dylib;
|
||||||
cmd.gc_sections(keep_metadata);
|
cmd.gc_sections(keep_metadata);
|
||||||
}
|
}
|
||||||
|
@ -1695,7 +1695,7 @@ fn linker_with_args<'a, B: ArchiveBuilder<'a>>(
|
||||||
);
|
);
|
||||||
|
|
||||||
// OBJECT-FILES-NO, AUDIT-ORDER
|
// OBJECT-FILES-NO, AUDIT-ORDER
|
||||||
if sess.opts.cg.profile_generate.enabled() {
|
if sess.opts.cg.profile_generate.enabled() || sess.opts.debugging_opts.instrument_coverage {
|
||||||
cmd.pgo_gen();
|
cmd.pgo_gen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -203,6 +203,17 @@ fn exported_symbols_provider_local(
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tcx.sess.opts.debugging_opts.instrument_coverage {
|
||||||
|
// Similar to PGO profiling, preserve symbols used by LLVM InstrProf coverage profiling.
|
||||||
|
const COVERAGE_WEAK_SYMBOLS: [&str; 3] =
|
||||||
|
["__llvm_profile_filename", "__llvm_coverage_mapping", "__llvm_covmap"];
|
||||||
|
|
||||||
|
symbols.extend(COVERAGE_WEAK_SYMBOLS.iter().map(|sym| {
|
||||||
|
let exported_symbol = ExportedSymbol::NoDefId(SymbolName::new(tcx, sym));
|
||||||
|
(exported_symbol, SymbolExportLevel::C)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
if tcx.sess.opts.debugging_opts.sanitizer.contains(SanitizerSet::MEMORY) {
|
if tcx.sess.opts.debugging_opts.sanitizer.contains(SanitizerSet::MEMORY) {
|
||||||
// Similar to profiling, preserve weak msan symbol during LTO.
|
// Similar to profiling, preserve weak msan symbol during LTO.
|
||||||
const MSAN_WEAK_SYMBOLS: [&str; 2] = ["__msan_track_origins", "__msan_keep_going"];
|
const MSAN_WEAK_SYMBOLS: [&str; 2] = ["__msan_track_origins", "__msan_keep_going"];
|
||||||
|
|
|
@ -1,32 +1,154 @@
|
||||||
use rustc_data_structures::fx::FxHashMap;
|
use rustc_data_structures::sync::Lrc;
|
||||||
use std::collections::hash_map;
|
use rustc_middle::mir;
|
||||||
use std::slice;
|
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)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
#[repr(C)]
|
||||||
pub enum CounterOp {
|
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,
|
Subtract,
|
||||||
|
Add,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum CoverageKind {
|
pub enum CoverageKind {
|
||||||
Counter,
|
Counter,
|
||||||
CounterExpression(u32, CounterOp, u32),
|
CounterExpression(u32, CounterOp, u32),
|
||||||
|
Unreachable,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CoverageSpan {
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct CoverageRegion {
|
||||||
|
pub kind: CoverageKind,
|
||||||
pub start_byte_pos: u32,
|
pub start_byte_pos: u32,
|
||||||
pub end_byte_pos: u32,
|
pub end_byte_pos: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CoverageRegion {
|
impl CoverageRegion {
|
||||||
pub kind: CoverageKind,
|
pub fn source_loc(&self, source_map: &SourceMap) -> Option<(Lrc<SourceFile>, CoverageLoc)> {
|
||||||
pub coverage_span: CoverageSpan,
|
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
|
||||||
|
// 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.
|
||||||
|
// 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
|
/// 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),
|
/// 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
|
/// 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
|
/// 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
|
/// a "gap region" (or "gap area"). A gap region is a code region within a counted region (either
|
||||||
|
@ -34,50 +156,134 @@ pub struct CoverageRegion {
|
||||||
/// lines with only whitespace or comments). According to LLVM Code Coverage Mapping documentation,
|
/// 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
|
/// "A count for a gap area is only used as the line execution count if there are no other regions
|
||||||
/// on a line."
|
/// on a line."
|
||||||
#[derive(Default)]
|
pub struct FunctionCoverage {
|
||||||
pub struct FunctionCoverageRegions {
|
source_hash: u64,
|
||||||
indexed: FxHashMap<u32, CoverageRegion>,
|
counters: Vec<CoverageRegion>,
|
||||||
unreachable: Vec<CoverageSpan>,
|
expressions: Vec<CoverageRegion>,
|
||||||
|
unreachable: Vec<CoverageRegion>,
|
||||||
|
translated: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FunctionCoverageRegions {
|
impl FunctionCoverage {
|
||||||
pub fn add_counter(&mut self, index: u32, start_byte_pos: u32, end_byte_pos: u32) {
|
pub fn with_coverageinfo<'tcx>(coverageinfo: &'tcx mir::CoverageInfo) -> Self {
|
||||||
self.indexed.insert(
|
Self {
|
||||||
index,
|
source_hash: 0, // will be set with the first `add_counter()`
|
||||||
CoverageRegion {
|
counters: vec![CoverageRegion::default(); coverageinfo.num_counters as usize],
|
||||||
kind: CoverageKind::Counter,
|
expressions: vec![CoverageRegion::default(); coverageinfo.num_expressions as usize],
|
||||||
coverage_span: CoverageSpan { start_byte_pos, end_byte_pos },
|
unreachable: Vec::new(),
|
||||||
},
|
translated: false,
|
||||||
);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(
|
pub fn add_counter_expression(
|
||||||
&mut self,
|
&mut self,
|
||||||
index: u32,
|
translated_index: u32,
|
||||||
lhs: u32,
|
lhs: u32,
|
||||||
op: CounterOp,
|
op: CounterOp,
|
||||||
rhs: u32,
|
rhs: u32,
|
||||||
start_byte_pos: u32,
|
start_byte_pos: u32,
|
||||||
end_byte_pos: u32,
|
end_byte_pos: u32,
|
||||||
) {
|
) {
|
||||||
self.indexed.insert(
|
let index = u32::MAX - translated_index;
|
||||||
index,
|
// Counter expressions start with "translated indexes", descending from `u32::MAX`, so
|
||||||
CoverageRegion {
|
// the range of expression indexes is disjoint from the range of counter indexes. This way,
|
||||||
kind: CoverageKind::CounterExpression(lhs, op, rhs),
|
// both counters and expressions can be operands in other expressions.
|
||||||
coverage_span: CoverageSpan { start_byte_pos, end_byte_pos },
|
//
|
||||||
},
|
// 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) {
|
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 indexed_regions(&self) -> hash_map::Iter<'_, u32, CoverageRegion> {
|
pub fn source_hash(&self) -> u64 {
|
||||||
self.indexed.iter()
|
self.source_hash
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unreachable_regions(&self) -> slice::Iter<'_, CoverageSpan> {
|
fn regions(&'a mut self) -> impl Iterator<Item = &'a CoverageRegion> {
|
||||||
self.unreachable.iter()
|
assert!(self.source_hash != 0);
|
||||||
|
self.ensure_expressions_translated();
|
||||||
|
self.counters.iter().chain(self.expressions.iter().chain(self.unreachable.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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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) }
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ pub trait CoverageInfoBuilderMethods<'tcx>: BackendTypes {
|
||||||
fn add_counter_region(
|
fn add_counter_region(
|
||||||
&mut self,
|
&mut self,
|
||||||
instance: Instance<'tcx>,
|
instance: Instance<'tcx>,
|
||||||
|
function_source_hash: u64,
|
||||||
index: u32,
|
index: u32,
|
||||||
start_byte_pos: u32,
|
start_byte_pos: u32,
|
||||||
end_byte_pos: u32,
|
end_byte_pos: u32,
|
||||||
|
|
|
@ -5,6 +5,18 @@ use rustc_target::abi::Align;
|
||||||
pub trait StaticMethods: BackendTypes {
|
pub trait StaticMethods: BackendTypes {
|
||||||
fn static_addr_of(&self, cv: Self::Value, align: Align, kind: Option<&str>) -> Self::Value;
|
fn static_addr_of(&self, cv: Self::Value, align: Align, kind: Option<&str>) -> Self::Value;
|
||||||
fn codegen_static(&self, def_id: DefId, is_mutable: bool);
|
fn codegen_static(&self, def_id: DefId, is_mutable: bool);
|
||||||
|
|
||||||
|
/// Mark the given global value as "used", to prevent a backend from potentially removing a
|
||||||
|
/// static variable that may otherwise appear unused.
|
||||||
|
///
|
||||||
|
/// Static variables in Rust can be annotated with the `#[used]` attribute to direct the `rustc`
|
||||||
|
/// compiler to mark the variable as a "used global".
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// #[used]
|
||||||
|
/// static FOO: u32 = 0;
|
||||||
|
/// ```
|
||||||
|
fn add_used_global(&self, global: Self::Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait StaticBuilderMethods: BackendTypes {
|
pub trait StaticBuilderMethods: BackendTypes {
|
||||||
|
|
37
src/librustc_hir/fake_lang_items.rs
Normal file
37
src/librustc_hir/fake_lang_items.rs
Normal file
|
@ -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;
|
||||||
|
}
|
|
@ -276,8 +276,6 @@ language_item_table! {
|
||||||
|
|
||||||
StartFnLangItem, sym::start, start_fn, Target::Fn;
|
StartFnLangItem, sym::start, start_fn, Target::Fn;
|
||||||
|
|
||||||
CountCodeRegionFnLangItem, sym::count_code_region, count_code_region_fn, Target::Fn;
|
|
||||||
|
|
||||||
EhPersonalityLangItem, sym::eh_personality, eh_personality, Target::Fn;
|
EhPersonalityLangItem, sym::eh_personality, eh_personality, Target::Fn;
|
||||||
EhCatchTypeinfoLangItem, sym::eh_catch_typeinfo, eh_catch_typeinfo, Target::Static;
|
EhCatchTypeinfoLangItem, sym::eh_catch_typeinfo, eh_catch_typeinfo, Target::Static;
|
||||||
|
|
||||||
|
@ -295,4 +293,9 @@ language_item_table! {
|
||||||
TerminationTraitLangItem, sym::termination, termination, Target::Trait;
|
TerminationTraitLangItem, sym::termination, termination, Target::Trait;
|
||||||
|
|
||||||
TryTraitLangItem, kw::Try, try_trait, Target::Trait;
|
TryTraitLangItem, kw::Try, try_trait, Target::Trait;
|
||||||
|
|
||||||
|
// language items related to source code coverage instrumentation (-Zinstrument-coverage)
|
||||||
|
CountCodeRegionFnLangItem, sym::count_code_region, count_code_region_fn, Target::Fn;
|
||||||
|
CoverageCounterAddFnLangItem, sym::coverage_counter_add, coverage_counter_add_fn, Target::Fn;
|
||||||
|
CoverageCounterSubtractFnLangItem, sym::coverage_counter_subtract, coverage_counter_subtract_fn, Target::Fn;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ mod arena;
|
||||||
pub mod def;
|
pub mod def;
|
||||||
pub mod definitions;
|
pub mod definitions;
|
||||||
pub use rustc_span::def_id;
|
pub use rustc_span::def_id;
|
||||||
|
pub mod fake_lang_items;
|
||||||
mod hir;
|
mod hir;
|
||||||
pub mod hir_id;
|
pub mod hir_id;
|
||||||
pub mod intravisit;
|
pub mod intravisit;
|
||||||
|
|
|
@ -401,7 +401,7 @@ fn test_codegen_options_tracking_hash() {
|
||||||
untracked!(incremental, Some(String::from("abc")));
|
untracked!(incremental, Some(String::from("abc")));
|
||||||
// `link_arg` is omitted because it just forwards to `link_args`.
|
// `link_arg` is omitted because it just forwards to `link_args`.
|
||||||
untracked!(link_args, vec![String::from("abc"), String::from("def")]);
|
untracked!(link_args, vec![String::from("abc"), String::from("def")]);
|
||||||
untracked!(link_dead_code, true);
|
untracked!(link_dead_code, Some(true));
|
||||||
untracked!(linker, Some(PathBuf::from("linker")));
|
untracked!(linker, Some(PathBuf::from("linker")));
|
||||||
untracked!(linker_flavor, Some(LinkerFlavor::Gcc));
|
untracked!(linker_flavor, Some(LinkerFlavor::Gcc));
|
||||||
untracked!(no_stack_check, true);
|
untracked!(no_stack_check, true);
|
||||||
|
|
|
@ -104,8 +104,16 @@ fn main() {
|
||||||
optional_components.push("riscv");
|
optional_components.push("riscv");
|
||||||
}
|
}
|
||||||
|
|
||||||
let required_components =
|
let required_components = &[
|
||||||
&["ipo", "bitreader", "bitwriter", "linker", "asmparser", "lto", "instrumentation"];
|
"ipo",
|
||||||
|
"bitreader",
|
||||||
|
"bitwriter",
|
||||||
|
"linker",
|
||||||
|
"asmparser",
|
||||||
|
"lto",
|
||||||
|
"coverage",
|
||||||
|
"instrumentation",
|
||||||
|
];
|
||||||
|
|
||||||
let components = output(Command::new(&llvm_config).arg("--components"));
|
let components = output(Command::new(&llvm_config).arg("--components"));
|
||||||
let mut components = components.split_whitespace().collect::<Vec<_>>();
|
let mut components = components.split_whitespace().collect::<Vec<_>>();
|
||||||
|
@ -169,6 +177,7 @@ fn main() {
|
||||||
cfg.file("../rustllvm/PassWrapper.cpp")
|
cfg.file("../rustllvm/PassWrapper.cpp")
|
||||||
.file("../rustllvm/RustWrapper.cpp")
|
.file("../rustllvm/RustWrapper.cpp")
|
||||||
.file("../rustllvm/ArchiveWrapper.cpp")
|
.file("../rustllvm/ArchiveWrapper.cpp")
|
||||||
|
.file("../rustllvm/CoverageMappingWrapper.cpp")
|
||||||
.file("../rustllvm/Linker.cpp")
|
.file("../rustllvm/Linker.cpp")
|
||||||
.cpp(true)
|
.cpp(true)
|
||||||
.cpp_link_stdlib(None) // we handle this below
|
.cpp_link_stdlib(None) // we handle this below
|
||||||
|
|
|
@ -13,6 +13,12 @@ pub struct RustString {
|
||||||
pub bytes: RefCell<Vec<u8>>,
|
pub bytes: RefCell<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RustString {
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.bytes.borrow().len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Appending to a Rust string -- used by RawRustStringOstream.
|
/// Appending to a Rust string -- used by RawRustStringOstream.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
#[allow(improper_ctypes_definitions)]
|
#[allow(improper_ctypes_definitions)]
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
|
|
||||||
/// Positional arguments to `libcore::count_code_region()`
|
/// Positional arguments to `libcore::count_code_region()`
|
||||||
pub mod count_code_region_args {
|
pub mod count_code_region_args {
|
||||||
pub const COUNTER_INDEX: usize = 0;
|
pub const FUNCTION_SOURCE_HASH: usize = 0;
|
||||||
pub const START_BYTE_POS: usize = 1;
|
pub const COUNTER_INDEX: usize = 1;
|
||||||
pub const END_BYTE_POS: usize = 2;
|
pub const START_BYTE_POS: usize = 2;
|
||||||
|
pub const END_BYTE_POS: usize = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Positional arguments to `libcore::coverage_counter_add()` and
|
/// Positional arguments to `libcore::coverage_counter_add()` and
|
||||||
|
|
|
@ -86,7 +86,7 @@ impl<'tcx> MonoItem<'tcx> {
|
||||||
.debugging_opts
|
.debugging_opts
|
||||||
.inline_in_all_cgus
|
.inline_in_all_cgus
|
||||||
.unwrap_or_else(|| tcx.sess.opts.optimize != OptLevel::No)
|
.unwrap_or_else(|| tcx.sess.opts.optimize != OptLevel::No)
|
||||||
&& !tcx.sess.opts.cg.link_dead_code;
|
&& tcx.sess.opts.cg.link_dead_code != Some(true);
|
||||||
|
|
||||||
match *self {
|
match *self {
|
||||||
MonoItem::Fn(ref instance) => {
|
MonoItem::Fn(ref instance) => {
|
||||||
|
|
|
@ -400,13 +400,11 @@ pub struct DestructuredConst<'tcx> {
|
||||||
/// `InstrumentCoverage` MIR pass and can be retrieved via the `coverageinfo` query.
|
/// `InstrumentCoverage` MIR pass and can be retrieved via the `coverageinfo` query.
|
||||||
#[derive(Clone, RustcEncodable, RustcDecodable, Debug, HashStable)]
|
#[derive(Clone, RustcEncodable, RustcDecodable, Debug, HashStable)]
|
||||||
pub struct CoverageInfo {
|
pub struct CoverageInfo {
|
||||||
/// A hash value that can be used by the consumer of the coverage profile data to detect
|
|
||||||
/// changes to the instrumented source of the associated MIR body (typically, for an
|
|
||||||
/// individual function).
|
|
||||||
pub hash: u64,
|
|
||||||
|
|
||||||
/// The total number of coverage region counters added to the MIR `Body`.
|
/// The total number of coverage region counters added to the MIR `Body`.
|
||||||
pub num_counters: u32,
|
pub num_counters: u32,
|
||||||
|
|
||||||
|
/// The total number of coverage region counter expressions added to the MIR `Body`.
|
||||||
|
pub num_expressions: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'tcx> TyCtxt<'tcx> {
|
impl<'tcx> TyCtxt<'tcx> {
|
||||||
|
|
|
@ -161,7 +161,7 @@ where
|
||||||
|
|
||||||
// Next we try to make as many symbols "internal" as possible, so LLVM has
|
// Next we try to make as many symbols "internal" as possible, so LLVM has
|
||||||
// more freedom to optimize.
|
// more freedom to optimize.
|
||||||
if !tcx.sess.opts.cg.link_dead_code {
|
if tcx.sess.opts.cg.link_dead_code != Some(true) {
|
||||||
let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_internalize_symbols");
|
let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_internalize_symbols");
|
||||||
internalize_symbols(tcx, &mut post_inlining, inlining_map);
|
internalize_symbols(tcx, &mut post_inlining, inlining_map);
|
||||||
}
|
}
|
||||||
|
@ -906,7 +906,7 @@ fn collect_and_partition_mono_items(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
if tcx.sess.opts.cg.link_dead_code {
|
if tcx.sess.opts.cg.link_dead_code == Some(true) {
|
||||||
MonoItemCollectionMode::Eager
|
MonoItemCollectionMode::Eager
|
||||||
} else {
|
} else {
|
||||||
MonoItemCollectionMode::Lazy
|
MonoItemCollectionMode::Lazy
|
||||||
|
|
|
@ -35,46 +35,64 @@ fn coverageinfo_from_mir<'tcx>(tcx: TyCtxt<'tcx>, mir_def_id: DefId) -> Coverage
|
||||||
// represents a single function. Validate and/or correct if inlining (which should be disabled
|
// represents a single function. Validate and/or correct if inlining (which should be disabled
|
||||||
// if -Zinstrument-coverage is enabled) and/or monomorphization invalidates these assumptions.
|
// if -Zinstrument-coverage is enabled) and/or monomorphization invalidates these assumptions.
|
||||||
let count_code_region_fn = tcx.require_lang_item(lang_items::CountCodeRegionFnLangItem, None);
|
let count_code_region_fn = tcx.require_lang_item(lang_items::CountCodeRegionFnLangItem, None);
|
||||||
|
let coverage_counter_add_fn =
|
||||||
|
tcx.require_lang_item(lang_items::CoverageCounterAddFnLangItem, None);
|
||||||
|
let coverage_counter_subtract_fn =
|
||||||
|
tcx.require_lang_item(lang_items::CoverageCounterSubtractFnLangItem, None);
|
||||||
|
|
||||||
// The `num_counters` argument to `llvm.instrprof.increment` is the number of injected
|
// The `num_counters` argument to `llvm.instrprof.increment` is the number of injected
|
||||||
// counters, with each counter having an index from `0..num_counters-1`. MIR optimization
|
// counters, with each counter having an index from `0..num_counters-1`. MIR optimization
|
||||||
// may split and duplicate some BasicBlock sequences. Simply counting the calls may not
|
// may split and duplicate some BasicBlock sequences. Simply counting the calls may not
|
||||||
// not work; but computing the num_counters by adding `1` to the highest index (for a given
|
// not work; but computing the num_counters by adding `1` to the highest index (for a given
|
||||||
// instrumented function) is valid.
|
// instrumented function) is valid.
|
||||||
|
//
|
||||||
|
// `num_expressions` is the number of counter expressions added to the MIR body. Both
|
||||||
|
// `num_counters` and `num_expressions` are used to initialize new vectors, during backend
|
||||||
|
// code generate, to lookup counters and expressions by their simple u32 indexes.
|
||||||
let mut num_counters: u32 = 0;
|
let mut num_counters: u32 = 0;
|
||||||
for terminator in traversal::preorder(mir_body)
|
let mut num_expressions: u32 = 0;
|
||||||
.map(|(_, data)| (data, count_code_region_fn))
|
for terminator in
|
||||||
.filter_map(terminators_that_call_given_fn)
|
traversal::preorder(mir_body).map(|(_, data)| data).filter_map(call_terminators)
|
||||||
{
|
{
|
||||||
if let TerminatorKind::Call { args, .. } = &terminator.kind {
|
if let TerminatorKind::Call { func: Operand::Constant(func), args, .. } = &terminator.kind {
|
||||||
let index_arg = args.get(count_code_region_args::COUNTER_INDEX).expect("arg found");
|
match func.literal.ty.kind {
|
||||||
let index =
|
FnDef(id, _) if id == count_code_region_fn => {
|
||||||
mir::Operand::scalar_from_const(index_arg).to_u32().expect("index arg is u32");
|
let index_arg =
|
||||||
num_counters = std::cmp::max(num_counters, index + 1);
|
args.get(count_code_region_args::COUNTER_INDEX).expect("arg found");
|
||||||
}
|
let counter_index = mir::Operand::scalar_from_const(index_arg)
|
||||||
}
|
.to_u32()
|
||||||
let hash = if num_counters > 0 { hash_mir_source(tcx, mir_def_id) } else { 0 };
|
.expect("index arg is u32");
|
||||||
CoverageInfo { num_counters, hash }
|
num_counters = std::cmp::max(num_counters, counter_index + 1);
|
||||||
}
|
|
||||||
|
|
||||||
fn terminators_that_call_given_fn(
|
|
||||||
(data, fn_def_id): (&'tcx BasicBlockData<'tcx>, DefId),
|
|
||||||
) -> Option<&'tcx Terminator<'tcx>> {
|
|
||||||
if let Some(terminator) = &data.terminator {
|
|
||||||
if let TerminatorKind::Call { func: Operand::Constant(func), .. } = &terminator.kind {
|
|
||||||
if let FnDef(called_fn_def_id, _) = func.literal.ty.kind {
|
|
||||||
if called_fn_def_id == fn_def_id {
|
|
||||||
return Some(&terminator);
|
|
||||||
}
|
}
|
||||||
|
FnDef(id, _)
|
||||||
|
if id == coverage_counter_add_fn || id == coverage_counter_subtract_fn =>
|
||||||
|
{
|
||||||
|
let index_arg = args
|
||||||
|
.get(coverage_counter_expression_args::COUNTER_EXPRESSION_INDEX)
|
||||||
|
.expect("arg found");
|
||||||
|
let translated_index = mir::Operand::scalar_from_const(index_arg)
|
||||||
|
.to_u32()
|
||||||
|
.expect("index arg is u32");
|
||||||
|
// 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.
|
||||||
|
let expression_index = u32::MAX - translated_index;
|
||||||
|
num_expressions = std::cmp::max(num_expressions, expression_index + 1);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
CoverageInfo { num_counters, num_expressions }
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Instrumentor<'tcx> {
|
fn call_terminators(data: &'tcx BasicBlockData<'tcx>) -> Option<&'tcx Terminator<'tcx>> {
|
||||||
tcx: TyCtxt<'tcx>,
|
let terminator = data.terminator();
|
||||||
num_counters: u32,
|
match terminator.kind {
|
||||||
|
TerminatorKind::Call { .. } => Some(terminator),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'tcx> MirPass<'tcx> for InstrumentCoverage {
|
impl<'tcx> MirPass<'tcx> for InstrumentCoverage {
|
||||||
|
@ -83,42 +101,106 @@ impl<'tcx> MirPass<'tcx> for InstrumentCoverage {
|
||||||
// If the InstrumentCoverage pass is called on promoted MIRs, skip them.
|
// If the InstrumentCoverage pass is called on promoted MIRs, skip them.
|
||||||
// See: https://github.com/rust-lang/rust/pull/73011#discussion_r438317601
|
// See: https://github.com/rust-lang/rust/pull/73011#discussion_r438317601
|
||||||
if src.promoted.is_none() {
|
if src.promoted.is_none() {
|
||||||
debug!(
|
Instrumentor::new(tcx, src, mir_body).inject_counters();
|
||||||
"instrumenting {:?}, span: {}",
|
|
||||||
src.def_id(),
|
|
||||||
tcx.sess.source_map().span_to_string(mir_body.span)
|
|
||||||
);
|
|
||||||
Instrumentor::new(tcx).inject_counters(mir_body);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'tcx> Instrumentor<'tcx> {
|
/// Distinguishes the expression operators.
|
||||||
fn new(tcx: TyCtxt<'tcx>) -> Self {
|
enum Op {
|
||||||
Self { tcx, num_counters: 0 }
|
Add,
|
||||||
|
Subtract,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Instrumentor<'a, 'tcx> {
|
||||||
|
tcx: TyCtxt<'tcx>,
|
||||||
|
mir_def_id: DefId,
|
||||||
|
mir_body: &'a mut mir::Body<'tcx>,
|
||||||
|
hir_body: &'tcx rustc_hir::Body<'tcx>,
|
||||||
|
function_source_hash: Option<u64>,
|
||||||
|
num_counters: u32,
|
||||||
|
num_expressions: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
|
||||||
|
fn new(tcx: TyCtxt<'tcx>, src: MirSource<'tcx>, mir_body: &'a mut mir::Body<'tcx>) -> Self {
|
||||||
|
let mir_def_id = src.def_id();
|
||||||
|
let hir_body = hir_body(tcx, mir_def_id);
|
||||||
|
Self {
|
||||||
|
tcx,
|
||||||
|
mir_def_id,
|
||||||
|
mir_body,
|
||||||
|
hir_body,
|
||||||
|
function_source_hash: None,
|
||||||
|
num_counters: 0,
|
||||||
|
num_expressions: 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Counter IDs start from zero and go up.
|
||||||
fn next_counter(&mut self) -> u32 {
|
fn next_counter(&mut self) -> u32 {
|
||||||
|
assert!(self.num_counters < u32::MAX - self.num_expressions);
|
||||||
let next = self.num_counters;
|
let next = self.num_counters;
|
||||||
self.num_counters += 1;
|
self.num_counters += 1;
|
||||||
next
|
next
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inject_counters(&mut self, mir_body: &mut mir::Body<'tcx>) {
|
/// Expression IDs start from u32::MAX and go down because a CounterExpression can reference
|
||||||
// FIXME(richkadel): As a first step, counters are only injected at the top of each
|
/// (add or subtract counts) of both Counter regions and CounterExpression regions. The indexes
|
||||||
// function. The complete solution will inject counters at each conditional code branch.
|
/// of each type of region must be contiguous, but also must be unique across both sets.
|
||||||
let code_region = mir_body.span;
|
/// The expression IDs are eventually translated into region indexes (starting after the last
|
||||||
let next_block = START_BLOCK;
|
/// counter index, for the given function), during backend code generation, by the helper method
|
||||||
self.inject_counter(mir_body, code_region, next_block);
|
/// `rustc_codegen_ssa::coverageinfo::map::FunctionCoverage::translate_expressions()`.
|
||||||
|
fn next_expression(&mut self) -> u32 {
|
||||||
|
assert!(self.num_counters < u32::MAX - self.num_expressions);
|
||||||
|
let next = u32::MAX - self.num_expressions;
|
||||||
|
self.num_expressions += 1;
|
||||||
|
next
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inject_counter(
|
fn function_source_hash(&mut self) -> u64 {
|
||||||
&mut self,
|
match self.function_source_hash {
|
||||||
mir_body: &mut mir::Body<'tcx>,
|
Some(hash) => hash,
|
||||||
code_region: Span,
|
None => {
|
||||||
next_block: BasicBlock,
|
let hash = hash_mir_source(self.tcx, self.hir_body);
|
||||||
) {
|
self.function_source_hash.replace(hash);
|
||||||
|
hash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inject_counters(&mut self) {
|
||||||
|
let body_span = self.hir_body.value.span;
|
||||||
|
debug!(
|
||||||
|
"instrumenting {:?}, span: {}",
|
||||||
|
self.mir_def_id,
|
||||||
|
self.tcx.sess.source_map().span_to_string(body_span)
|
||||||
|
);
|
||||||
|
|
||||||
|
// FIXME(richkadel): As a first step, counters are only injected at the top of each
|
||||||
|
// function. The complete solution will inject counters at each conditional code branch.
|
||||||
|
let next_block = START_BLOCK;
|
||||||
|
self.inject_counter(body_span, next_block);
|
||||||
|
|
||||||
|
// FIXME(richkadel): The next step to implement source based coverage analysis will be
|
||||||
|
// instrumenting branches within functions, and some regions will be counted by "counter
|
||||||
|
// expression". The function to inject counter expression is implemented. Replace this
|
||||||
|
// "fake use" with real use.
|
||||||
|
let fake_use = false;
|
||||||
|
if fake_use {
|
||||||
|
let add = false;
|
||||||
|
if add {
|
||||||
|
self.inject_counter_expression(body_span, next_block, 1, Op::Add, 2);
|
||||||
|
} else {
|
||||||
|
self.inject_counter_expression(body_span, next_block, 1, Op::Subtract, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inject_counter(&mut self, code_region: Span, next_block: BasicBlock) -> u32 {
|
||||||
|
let counter_id = self.next_counter();
|
||||||
|
let function_source_hash = self.function_source_hash();
|
||||||
let injection_point = code_region.shrink_to_lo();
|
let injection_point = code_region.shrink_to_lo();
|
||||||
|
|
||||||
let count_code_region_fn = function_handle(
|
let count_code_region_fn = function_handle(
|
||||||
|
@ -127,13 +209,14 @@ impl<'tcx> Instrumentor<'tcx> {
|
||||||
injection_point,
|
injection_point,
|
||||||
);
|
);
|
||||||
|
|
||||||
let index = self.next_counter();
|
|
||||||
|
|
||||||
let mut args = Vec::new();
|
let mut args = Vec::new();
|
||||||
|
|
||||||
use count_code_region_args::*;
|
use count_code_region_args::*;
|
||||||
|
debug_assert_eq!(FUNCTION_SOURCE_HASH, args.len());
|
||||||
|
args.push(self.const_u64(function_source_hash, injection_point));
|
||||||
|
|
||||||
debug_assert_eq!(COUNTER_INDEX, args.len());
|
debug_assert_eq!(COUNTER_INDEX, args.len());
|
||||||
args.push(self.const_u32(index, injection_point));
|
args.push(self.const_u32(counter_id, injection_point));
|
||||||
|
|
||||||
debug_assert_eq!(START_BYTE_POS, args.len());
|
debug_assert_eq!(START_BYTE_POS, args.len());
|
||||||
args.push(self.const_u32(code_region.lo().to_u32(), injection_point));
|
args.push(self.const_u32(code_region.lo().to_u32(), injection_point));
|
||||||
|
@ -141,36 +224,98 @@ impl<'tcx> Instrumentor<'tcx> {
|
||||||
debug_assert_eq!(END_BYTE_POS, args.len());
|
debug_assert_eq!(END_BYTE_POS, args.len());
|
||||||
args.push(self.const_u32(code_region.hi().to_u32(), injection_point));
|
args.push(self.const_u32(code_region.hi().to_u32(), injection_point));
|
||||||
|
|
||||||
let mut patch = MirPatch::new(mir_body);
|
self.inject_call(count_code_region_fn, args, injection_point, next_block);
|
||||||
|
|
||||||
let temp = patch.new_temp(self.tcx.mk_unit(), code_region);
|
counter_id
|
||||||
let new_block = patch.new_block(placeholder_block(code_region));
|
}
|
||||||
|
|
||||||
|
fn inject_counter_expression(
|
||||||
|
&mut self,
|
||||||
|
code_region: Span,
|
||||||
|
next_block: BasicBlock,
|
||||||
|
lhs: u32,
|
||||||
|
op: Op,
|
||||||
|
rhs: u32,
|
||||||
|
) -> u32 {
|
||||||
|
let expression_id = self.next_expression();
|
||||||
|
let injection_point = code_region.shrink_to_lo();
|
||||||
|
|
||||||
|
let count_code_region_fn = function_handle(
|
||||||
|
self.tcx,
|
||||||
|
self.tcx.require_lang_item(
|
||||||
|
match op {
|
||||||
|
Op::Add => lang_items::CoverageCounterAddFnLangItem,
|
||||||
|
Op::Subtract => lang_items::CoverageCounterSubtractFnLangItem,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
injection_point,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut args = Vec::new();
|
||||||
|
|
||||||
|
use coverage_counter_expression_args::*;
|
||||||
|
debug_assert_eq!(COUNTER_EXPRESSION_INDEX, args.len());
|
||||||
|
args.push(self.const_u32(expression_id, injection_point));
|
||||||
|
|
||||||
|
debug_assert_eq!(LEFT_INDEX, args.len());
|
||||||
|
args.push(self.const_u32(lhs, injection_point));
|
||||||
|
|
||||||
|
debug_assert_eq!(RIGHT_INDEX, args.len());
|
||||||
|
args.push(self.const_u32(rhs, injection_point));
|
||||||
|
|
||||||
|
debug_assert_eq!(START_BYTE_POS, args.len());
|
||||||
|
args.push(self.const_u32(code_region.lo().to_u32(), injection_point));
|
||||||
|
|
||||||
|
debug_assert_eq!(END_BYTE_POS, args.len());
|
||||||
|
args.push(self.const_u32(code_region.hi().to_u32(), injection_point));
|
||||||
|
|
||||||
|
self.inject_call(count_code_region_fn, args, injection_point, next_block);
|
||||||
|
|
||||||
|
expression_id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inject_call(
|
||||||
|
&mut self,
|
||||||
|
func: Operand<'tcx>,
|
||||||
|
args: Vec<Operand<'tcx>>,
|
||||||
|
fn_span: Span,
|
||||||
|
next_block: BasicBlock,
|
||||||
|
) {
|
||||||
|
let mut patch = MirPatch::new(self.mir_body);
|
||||||
|
|
||||||
|
let temp = patch.new_temp(self.tcx.mk_unit(), fn_span);
|
||||||
|
let new_block = patch.new_block(placeholder_block(fn_span));
|
||||||
patch.patch_terminator(
|
patch.patch_terminator(
|
||||||
new_block,
|
new_block,
|
||||||
TerminatorKind::Call {
|
TerminatorKind::Call {
|
||||||
func: count_code_region_fn,
|
func,
|
||||||
args,
|
args,
|
||||||
// new_block will swapped with the next_block, after applying patch
|
// new_block will swapped with the next_block, after applying patch
|
||||||
destination: Some((Place::from(temp), new_block)),
|
destination: Some((Place::from(temp), new_block)),
|
||||||
cleanup: None,
|
cleanup: None,
|
||||||
from_hir_call: false,
|
from_hir_call: false,
|
||||||
fn_span: injection_point,
|
fn_span,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
patch.add_statement(new_block.start_location(), StatementKind::StorageLive(temp));
|
patch.add_statement(new_block.start_location(), StatementKind::StorageLive(temp));
|
||||||
patch.add_statement(next_block.start_location(), StatementKind::StorageDead(temp));
|
patch.add_statement(next_block.start_location(), StatementKind::StorageDead(temp));
|
||||||
|
|
||||||
patch.apply(mir_body);
|
patch.apply(self.mir_body);
|
||||||
|
|
||||||
// To insert the `new_block` in front of the first block in the counted branch (the
|
// To insert the `new_block` in front of the first block in the counted branch (the
|
||||||
// `next_block`), just swap the indexes, leaving the rest of the graph unchanged.
|
// `next_block`), just swap the indexes, leaving the rest of the graph unchanged.
|
||||||
mir_body.basic_blocks_mut().swap(next_block, new_block);
|
self.mir_body.basic_blocks_mut().swap(next_block, new_block);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn const_u32(&self, value: u32, span: Span) -> Operand<'tcx> {
|
fn const_u32(&self, value: u32, span: Span) -> Operand<'tcx> {
|
||||||
Operand::const_from_scalar(self.tcx, self.tcx.types.u32, Scalar::from_u32(value), span)
|
Operand::const_from_scalar(self.tcx, self.tcx.types.u32, Scalar::from_u32(value), span)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn const_u64(&self, value: u64, span: Span) -> Operand<'tcx> {
|
||||||
|
Operand::const_from_scalar(self.tcx, self.tcx.types.u64, Scalar::from_u64(value), span)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn function_handle<'tcx>(tcx: TyCtxt<'tcx>, fn_def_id: DefId, span: Span) -> Operand<'tcx> {
|
fn function_handle<'tcx>(tcx: TyCtxt<'tcx>, fn_def_id: DefId, span: Span) -> Operand<'tcx> {
|
||||||
|
@ -192,10 +337,13 @@ fn placeholder_block(span: Span) -> BasicBlockData<'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_mir_source<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> u64 {
|
fn hir_body<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> &'tcx rustc_hir::Body<'tcx> {
|
||||||
let hir_node = tcx.hir().get_if_local(def_id).expect("DefId is local");
|
let hir_node = tcx.hir().get_if_local(def_id).expect("DefId is local");
|
||||||
let fn_body_id = hir::map::associated_body(hir_node).expect("HIR node is a function with body");
|
let fn_body_id = hir::map::associated_body(hir_node).expect("HIR node is a function with body");
|
||||||
let hir_body = tcx.hir().body(fn_body_id);
|
tcx.hir().body(fn_body_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_mir_source<'tcx>(tcx: TyCtxt<'tcx>, hir_body: &'tcx rustc_hir::Body<'tcx>) -> u64 {
|
||||||
let mut hcx = tcx.create_no_span_stable_hashing_context();
|
let mut hcx = tcx.create_no_span_stable_hashing_context();
|
||||||
hash(&mut hcx, &hir_body.value).to_smaller_hash()
|
hash(&mut hcx, &hir_body.value).to_smaller_hash()
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,13 @@
|
||||||
use rustc_data_structures::fx::FxHashSet;
|
use rustc_data_structures::fx::FxHashSet;
|
||||||
use rustc_errors::struct_span_err;
|
use rustc_errors::struct_span_err;
|
||||||
use rustc_hir as hir;
|
use rustc_hir as hir;
|
||||||
|
use rustc_hir::fake_lang_items::FAKE_ITEMS_REFS;
|
||||||
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
|
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
|
||||||
use rustc_hir::lang_items;
|
use rustc_hir::lang_items;
|
||||||
use rustc_hir::lang_items::ITEM_REFS;
|
|
||||||
use rustc_hir::weak_lang_items::WEAK_ITEMS_REFS;
|
use rustc_hir::weak_lang_items::WEAK_ITEMS_REFS;
|
||||||
use rustc_middle::middle::lang_items::required;
|
use rustc_middle::middle::lang_items::required;
|
||||||
use rustc_middle::ty::TyCtxt;
|
use rustc_middle::ty::TyCtxt;
|
||||||
use rustc_session::config::CrateType;
|
use rustc_session::config::CrateType;
|
||||||
use rustc_span::symbol::sym;
|
|
||||||
use rustc_span::symbol::Symbol;
|
use rustc_span::symbol::Symbol;
|
||||||
use rustc_span::Span;
|
use rustc_span::Span;
|
||||||
|
|
||||||
|
@ -77,15 +76,14 @@ impl<'a, 'tcx> Context<'a, 'tcx> {
|
||||||
if self.items.require(item).is_err() {
|
if self.items.require(item).is_err() {
|
||||||
self.items.missing.push(item);
|
self.items.missing.push(item);
|
||||||
}
|
}
|
||||||
} else if name == sym::count_code_region {
|
} else if let Some(&item) = FAKE_ITEMS_REFS.get(&name) {
|
||||||
// `core::intrinsics::code_count_region()` is (currently) the only `extern` lang item
|
// Ensure "fake lang items" are registered. These are `extern` lang items that are
|
||||||
// that is never actually linked. It is not a `weak_lang_item` that can be registered
|
// injected into the MIR automatically (such as source code coverage counters), but are
|
||||||
// when used, and should be registered here instead.
|
// never actually linked; therefore, unlike "weak lang items", they cannot by registered
|
||||||
if let Some((item_index, _)) = ITEM_REFS.get(&name).cloned() {
|
// when used, because they never appear to be used.
|
||||||
if self.items.items[item_index].is_none() {
|
if self.items.items[item as usize].is_none() {
|
||||||
let item_def_id = self.tcx.hir().local_def_id(hir_id).to_def_id();
|
let item_def_id = self.tcx.hir().local_def_id(hir_id).to_def_id();
|
||||||
self.items.items[item_index] = Some(item_def_id);
|
self.items.items[item as usize] = Some(item_def_id);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
struct_span_err!(self.tcx.sess, span, E0264, "unknown external lang item: `{}`", name)
|
struct_span_err!(self.tcx.sess, span, E0264, "unknown external lang item: `{}`", name)
|
||||||
|
|
|
@ -1707,6 +1707,31 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if debugging_opts.instrument_coverage {
|
||||||
|
if cg.profile_generate.enabled() || cg.profile_use.is_some() {
|
||||||
|
early_error(
|
||||||
|
error_format,
|
||||||
|
"option `-Z instrument-coverage` is not compatible with either `-C profile-use` \
|
||||||
|
or `-C profile-generate`",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// `-Z instrument-coverage` implies:
|
||||||
|
// * `-Z symbol-mangling-version=v0` - to ensure consistent and reversable name mangling.
|
||||||
|
// Note, LLVM coverage tools can analyze coverage over multiple runs, including some
|
||||||
|
// changes to source code; so mangled names must be consistent across compilations.
|
||||||
|
// * `-C link-dead-code` - so unexecuted code is still counted as zero, rather than be
|
||||||
|
// optimized out. Note that instrumenting dead code can be explicitly disabled with:
|
||||||
|
// `-Z instrument-coverage -C link-dead-code=no`.
|
||||||
|
debugging_opts.symbol_mangling_version = SymbolManglingVersion::V0;
|
||||||
|
if cg.link_dead_code == None {
|
||||||
|
// FIXME(richkadel): Investigate if the `instrument-coverage` implementation can
|
||||||
|
// inject ["zero counters"](https://llvm.org/docs/CoverageMappingFormat.html#counter)
|
||||||
|
// in the coverage map when "dead code" is removed, rather than forcing `link-dead-code`.
|
||||||
|
cg.link_dead_code = Some(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !cg.embed_bitcode {
|
if !cg.embed_bitcode {
|
||||||
match cg.lto {
|
match cg.lto {
|
||||||
LtoCli::No | LtoCli::Unspecified => {}
|
LtoCli::No | LtoCli::Unspecified => {}
|
||||||
|
|
|
@ -715,7 +715,7 @@ options! {CodegenOptions, CodegenSetter, basic_codegen_options,
|
||||||
"a single extra argument to append to the linker invocation (can be used several times)"),
|
"a single extra argument to append to the linker invocation (can be used several times)"),
|
||||||
link_args: Vec<String> = (Vec::new(), parse_list, [UNTRACKED],
|
link_args: Vec<String> = (Vec::new(), parse_list, [UNTRACKED],
|
||||||
"extra arguments to append to the linker invocation (space separated)"),
|
"extra arguments to append to the linker invocation (space separated)"),
|
||||||
link_dead_code: bool = (false, parse_bool, [UNTRACKED],
|
link_dead_code: Option<bool> = (None, parse_opt_bool, [UNTRACKED],
|
||||||
"keep dead code at link time (useful for code coverage) (default: no)"),
|
"keep dead code at link time (useful for code coverage) (default: no)"),
|
||||||
linker: Option<PathBuf> = (None, parse_opt_pathbuf, [UNTRACKED],
|
linker: Option<PathBuf> = (None, parse_opt_pathbuf, [UNTRACKED],
|
||||||
"system linker to link outputs with"),
|
"system linker to link outputs with"),
|
||||||
|
@ -880,10 +880,12 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
|
||||||
(such as entering an empty infinite loop) by inserting llvm.sideeffect \
|
(such as entering an empty infinite loop) by inserting llvm.sideeffect \
|
||||||
(default: no)"),
|
(default: no)"),
|
||||||
instrument_coverage: bool = (false, parse_bool, [TRACKED],
|
instrument_coverage: bool = (false, parse_bool, [TRACKED],
|
||||||
"instrument the generated code with LLVM code region counters to (in the \
|
"instrument the generated code to support LLVM source-based code coverage \
|
||||||
future) generate coverage reports; disables/overrides some optimization \
|
reports (note, the compiler build config must include `profiler = true`, \
|
||||||
options (note, the compiler build config must include `profiler = true`) \
|
and is mutually exclusive with `-C profile-generate`/`-C profile-use`); \
|
||||||
(default: no)"),
|
implies `-C link-dead-code` (unless explicitly disabled)` and
|
||||||
|
`-Z symbol-mangling-version=v0`; and disables/overrides some optimization \
|
||||||
|
options (default: no)"),
|
||||||
instrument_mcount: bool = (false, parse_bool, [TRACKED],
|
instrument_mcount: bool = (false, parse_bool, [TRACKED],
|
||||||
"insert function instrument code for mcount-based tracing (default: no)"),
|
"insert function instrument code for mcount-based tracing (default: no)"),
|
||||||
keep_hygiene_data: bool = (false, parse_bool, [UNTRACKED],
|
keep_hygiene_data: bool = (false, parse_bool, [UNTRACKED],
|
||||||
|
|
|
@ -1357,6 +1357,20 @@ fn validate_commandline_args_with_session_available(sess: &Session) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME(richkadel): See `src/test/run-make-fulldeps/instrument-coverage/Makefile`. After
|
||||||
|
// compiling with `-Zinstrument-coverage`, the resulting binary generates a segfault during
|
||||||
|
// the program's exit process (likely while attempting to generate the coverage stats in
|
||||||
|
// the "*.profraw" file). An investigation to resolve the problem on Windows is ongoing,
|
||||||
|
// but until this is resolved, the option is disabled on Windows, and the test is skipped
|
||||||
|
// when targeting `MSVC`.
|
||||||
|
if sess.opts.debugging_opts.instrument_coverage && sess.target.target.options.is_like_msvc {
|
||||||
|
sess.warn(
|
||||||
|
"Rust source-based code coverage instrumentation (with `-Z instrument-coverage`) \
|
||||||
|
is not yet supported on Windows when targeting MSVC. The resulting binaries will \
|
||||||
|
still be instrumented for experimentation purposes, but may not execute correctly.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const ASAN_SUPPORTED_TARGETS: &[&str] = &[
|
const ASAN_SUPPORTED_TARGETS: &[&str] = &[
|
||||||
"aarch64-fuchsia",
|
"aarch64-fuchsia",
|
||||||
"aarch64-unknown-linux-gnu",
|
"aarch64-unknown-linux-gnu",
|
||||||
|
|
|
@ -386,7 +386,7 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
sym::count_code_region => {
|
sym::count_code_region => {
|
||||||
(0, vec![tcx.types.u32, tcx.types.u32, tcx.types.u32], tcx.mk_unit())
|
(0, vec![tcx.types.u64, tcx.types.u32, tcx.types.u32, tcx.types.u32], tcx.mk_unit())
|
||||||
}
|
}
|
||||||
|
|
||||||
sym::coverage_counter_add | sym::coverage_counter_subtract => (
|
sym::coverage_counter_add | sym::coverage_counter_subtract => (
|
||||||
|
|
115
src/rustllvm/CoverageMappingWrapper.cpp
Normal file
115
src/rustllvm/CoverageMappingWrapper.cpp
Normal file
|
@ -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>
|
||||||
|
*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,
|
||||||
|
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;
|
||||||
|
}
|
|
@ -1395,7 +1395,7 @@ extern "C" LLVMValueRef LLVMRustBuildCall(LLVMBuilderRef B, LLVMValueRef Fn,
|
||||||
FTy, Callee, makeArrayRef(unwrap(Args), NumArgs), Bundles));
|
FTy, Callee, makeArrayRef(unwrap(Args), NumArgs), Bundles));
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" LLVMValueRef LLVMRustGetInstrprofIncrementIntrinsic(LLVMModuleRef M) {
|
extern "C" LLVMValueRef LLVMRustGetInstrProfIncrementIntrinsic(LLVMModuleRef M) {
|
||||||
return wrap(llvm::Intrinsic::getDeclaration(unwrap(M),
|
return wrap(llvm::Intrinsic::getDeclaration(unwrap(M),
|
||||||
(llvm::Intrinsic::ID)llvm::Intrinsic::instrprof_increment));
|
(llvm::Intrinsic::ID)llvm::Intrinsic::instrprof_increment));
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "llvm-c/Object.h"
|
#include "llvm-c/Object.h"
|
||||||
#include "llvm/ADT/ArrayRef.h"
|
#include "llvm/ADT/ArrayRef.h"
|
||||||
#include "llvm/ADT/DenseSet.h"
|
#include "llvm/ADT/DenseSet.h"
|
||||||
|
#include "llvm/ADT/SmallVector.h"
|
||||||
#include "llvm/ADT/Triple.h"
|
#include "llvm/ADT/Triple.h"
|
||||||
#include "llvm/Analysis/Lint.h"
|
#include "llvm/Analysis/Lint.h"
|
||||||
#include "llvm/Analysis/Passes.h"
|
#include "llvm/Analysis/Passes.h"
|
||||||
|
|
|
@ -3,34 +3,40 @@
|
||||||
|
|
||||||
fn bar() -> bool {
|
fn bar() -> bool {
|
||||||
let mut _0: bool; // return place in scope 0 at $DIR/instrument_coverage.rs:18:13: 18:17
|
let mut _0: bool; // return place in scope 0 at $DIR/instrument_coverage.rs:18:13: 18:17
|
||||||
+ let mut _1: (); // in scope 0 at $DIR/instrument_coverage.rs:18:1: 20:2
|
+ let mut _1: (); // in scope 0 at $DIR/instrument_coverage.rs:18:18: 18:18
|
||||||
|
|
||||||
bb0: {
|
bb0: {
|
||||||
+ StorageLive(_1); // scope 0 at $DIR/instrument_coverage.rs:18:1: 20:2
|
+ StorageLive(_1); // scope 0 at $DIR/instrument_coverage.rs:18:18: 18:18
|
||||||
+ _1 = const std::intrinsics::count_code_region(const 0_u32, const 484_u32, const 513_u32) -> bb2; // scope 0 at $DIR/instrument_coverage.rs:18:1: 20:2
|
+ _1 = const std::intrinsics::count_code_region(const 10208505205182607101_u64, const 0_u32, const 501_u32, const 513_u32) -> bb2; // scope 0 at $DIR/instrument_coverage.rs:18:18: 18:18
|
||||||
+ // ty::Const
|
+ // ty::Const
|
||||||
+ // + ty: unsafe extern "rust-intrinsic" fn(u32, u32, u32) {std::intrinsics::count_code_region}
|
+ // + ty: unsafe extern "rust-intrinsic" fn(u64, u32, u32, u32) {std::intrinsics::count_code_region}
|
||||||
+ // + val: Value(Scalar(<ZST>))
|
+ // + val: Value(Scalar(<ZST>))
|
||||||
+ // mir::Constant
|
+ // mir::Constant
|
||||||
+ // + span: $DIR/instrument_coverage.rs:18:1: 18:1
|
+ // + span: $DIR/instrument_coverage.rs:18:18: 18:18
|
||||||
+ // + literal: Const { ty: unsafe extern "rust-intrinsic" fn(u32, u32, u32) {std::intrinsics::count_code_region}, val: Value(Scalar(<ZST>)) }
|
+ // + literal: Const { ty: unsafe extern "rust-intrinsic" fn(u64, u32, u32, u32) {std::intrinsics::count_code_region}, val: Value(Scalar(<ZST>)) }
|
||||||
|
+ // ty::Const
|
||||||
|
+ // + ty: u64
|
||||||
|
+ // + val: Value(Scalar(0x8dabe565aaa2aefd))
|
||||||
|
+ // mir::Constant
|
||||||
|
+ // + span: $DIR/instrument_coverage.rs:18:18: 18:18
|
||||||
|
+ // + literal: Const { ty: u64, val: Value(Scalar(0x8dabe565aaa2aefd)) }
|
||||||
+ // ty::Const
|
+ // ty::Const
|
||||||
+ // + ty: u32
|
+ // + ty: u32
|
||||||
+ // + val: Value(Scalar(0x00000000))
|
+ // + val: Value(Scalar(0x00000000))
|
||||||
+ // mir::Constant
|
+ // mir::Constant
|
||||||
+ // + span: $DIR/instrument_coverage.rs:18:1: 18:1
|
+ // + span: $DIR/instrument_coverage.rs:18:18: 18:18
|
||||||
+ // + literal: Const { ty: u32, val: Value(Scalar(0x00000000)) }
|
+ // + literal: Const { ty: u32, val: Value(Scalar(0x00000000)) }
|
||||||
+ // ty::Const
|
+ // ty::Const
|
||||||
+ // + ty: u32
|
+ // + ty: u32
|
||||||
+ // + val: Value(Scalar(0x000001e4))
|
+ // + val: Value(Scalar(0x000001f5))
|
||||||
+ // mir::Constant
|
+ // mir::Constant
|
||||||
+ // + span: $DIR/instrument_coverage.rs:18:1: 18:1
|
+ // + span: $DIR/instrument_coverage.rs:18:18: 18:18
|
||||||
+ // + literal: Const { ty: u32, val: Value(Scalar(0x000001e4)) }
|
+ // + literal: Const { ty: u32, val: Value(Scalar(0x000001f5)) }
|
||||||
+ // ty::Const
|
+ // ty::Const
|
||||||
+ // + ty: u32
|
+ // + ty: u32
|
||||||
+ // + val: Value(Scalar(0x00000201))
|
+ // + val: Value(Scalar(0x00000201))
|
||||||
+ // mir::Constant
|
+ // mir::Constant
|
||||||
+ // + span: $DIR/instrument_coverage.rs:18:1: 18:1
|
+ // + span: $DIR/instrument_coverage.rs:18:18: 18:18
|
||||||
+ // + literal: Const { ty: u32, val: Value(Scalar(0x00000201)) }
|
+ // + literal: Const { ty: u32, val: Value(Scalar(0x00000201)) }
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
|
|
|
@ -6,35 +6,41 @@
|
||||||
let mut _1: (); // in scope 0 at $DIR/instrument_coverage.rs:9:1: 15:2
|
let mut _1: (); // in scope 0 at $DIR/instrument_coverage.rs:9:1: 15:2
|
||||||
let mut _2: bool; // in scope 0 at $DIR/instrument_coverage.rs:11:12: 11:17
|
let mut _2: bool; // in scope 0 at $DIR/instrument_coverage.rs:11:12: 11:17
|
||||||
let mut _3: !; // in scope 0 at $DIR/instrument_coverage.rs:11:18: 13:10
|
let mut _3: !; // in scope 0 at $DIR/instrument_coverage.rs:11:18: 13:10
|
||||||
+ let mut _4: (); // in scope 0 at $DIR/instrument_coverage.rs:9:1: 15:2
|
+ let mut _4: (); // in scope 0 at $DIR/instrument_coverage.rs:9:11: 9:11
|
||||||
|
|
||||||
bb0: {
|
bb0: {
|
||||||
- falseUnwind -> [real: bb1, cleanup: bb2]; // scope 0 at $DIR/instrument_coverage.rs:10:5: 14:6
|
- falseUnwind -> [real: bb1, cleanup: bb2]; // scope 0 at $DIR/instrument_coverage.rs:10:5: 14:6
|
||||||
+ StorageLive(_4); // scope 0 at $DIR/instrument_coverage.rs:9:1: 15:2
|
+ StorageLive(_4); // scope 0 at $DIR/instrument_coverage.rs:9:11: 9:11
|
||||||
+ _4 = const std::intrinsics::count_code_region(const 0_u32, const 387_u32, const 465_u32) -> bb7; // scope 0 at $DIR/instrument_coverage.rs:9:1: 15:2
|
+ _4 = const std::intrinsics::count_code_region(const 16004455475339839479_u64, const 0_u32, const 397_u32, const 465_u32) -> bb7; // scope 0 at $DIR/instrument_coverage.rs:9:11: 9:11
|
||||||
+ // ty::Const
|
+ // ty::Const
|
||||||
+ // + ty: unsafe extern "rust-intrinsic" fn(u32, u32, u32) {std::intrinsics::count_code_region}
|
+ // + ty: unsafe extern "rust-intrinsic" fn(u64, u32, u32, u32) {std::intrinsics::count_code_region}
|
||||||
+ // + val: Value(Scalar(<ZST>))
|
+ // + val: Value(Scalar(<ZST>))
|
||||||
+ // mir::Constant
|
+ // mir::Constant
|
||||||
+ // + span: $DIR/instrument_coverage.rs:9:1: 9:1
|
+ // + span: $DIR/instrument_coverage.rs:9:11: 9:11
|
||||||
+ // + literal: Const { ty: unsafe extern "rust-intrinsic" fn(u32, u32, u32) {std::intrinsics::count_code_region}, val: Value(Scalar(<ZST>)) }
|
+ // + literal: Const { ty: unsafe extern "rust-intrinsic" fn(u64, u32, u32, u32) {std::intrinsics::count_code_region}, val: Value(Scalar(<ZST>)) }
|
||||||
|
+ // ty::Const
|
||||||
|
+ // + ty: u64
|
||||||
|
+ // + val: Value(Scalar(0xde1b3f75a72fc7f7))
|
||||||
|
+ // mir::Constant
|
||||||
|
+ // + span: $DIR/instrument_coverage.rs:9:11: 9:11
|
||||||
|
+ // + literal: Const { ty: u64, val: Value(Scalar(0xde1b3f75a72fc7f7)) }
|
||||||
+ // ty::Const
|
+ // ty::Const
|
||||||
+ // + ty: u32
|
+ // + ty: u32
|
||||||
+ // + val: Value(Scalar(0x00000000))
|
+ // + val: Value(Scalar(0x00000000))
|
||||||
+ // mir::Constant
|
+ // mir::Constant
|
||||||
+ // + span: $DIR/instrument_coverage.rs:9:1: 9:1
|
+ // + span: $DIR/instrument_coverage.rs:9:11: 9:11
|
||||||
+ // + literal: Const { ty: u32, val: Value(Scalar(0x00000000)) }
|
+ // + literal: Const { ty: u32, val: Value(Scalar(0x00000000)) }
|
||||||
+ // ty::Const
|
+ // ty::Const
|
||||||
+ // + ty: u32
|
+ // + ty: u32
|
||||||
+ // + val: Value(Scalar(0x00000183))
|
+ // + val: Value(Scalar(0x0000018d))
|
||||||
+ // mir::Constant
|
+ // mir::Constant
|
||||||
+ // + span: $DIR/instrument_coverage.rs:9:1: 9:1
|
+ // + span: $DIR/instrument_coverage.rs:9:11: 9:11
|
||||||
+ // + literal: Const { ty: u32, val: Value(Scalar(0x00000183)) }
|
+ // + literal: Const { ty: u32, val: Value(Scalar(0x0000018d)) }
|
||||||
+ // ty::Const
|
+ // ty::Const
|
||||||
+ // + ty: u32
|
+ // + ty: u32
|
||||||
+ // + val: Value(Scalar(0x000001d1))
|
+ // + val: Value(Scalar(0x000001d1))
|
||||||
+ // mir::Constant
|
+ // mir::Constant
|
||||||
+ // + span: $DIR/instrument_coverage.rs:9:1: 9:1
|
+ // + span: $DIR/instrument_coverage.rs:9:11: 9:11
|
||||||
+ // + literal: Const { ty: u32, val: Value(Scalar(0x000001d1)) }
|
+ // + literal: Const { ty: u32, val: Value(Scalar(0x000001d1)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
57
src/test/run-make-fulldeps/instrument-coverage/Makefile
Normal file
57
src/test/run-make-fulldeps/instrument-coverage/Makefile
Normal file
|
@ -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
|
|
@ -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"
|
||||||
|
}
|
38
src/test/run-make-fulldeps/instrument-coverage/main.rs
Normal file
38
src/test/run-make-fulldeps/instrument-coverage/main.rs
Normal file
|
@ -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, ""));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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))
|
|
@ -0,0 +1,55 @@
|
||||||
|
1| 2|pub fn will_be_called() -> &'static str {
|
||||||
|
2| 2| let val = "called";
|
||||||
|
3| 2| println!("{}", val);
|
||||||
|
4| 2| val
|
||||||
|
5| 2|}
|
||||||
|
6| |
|
||||||
|
7| 0|pub fn will_not_be_called() -> bool {
|
||||||
|
8| 0| println!("should not have been called");
|
||||||
|
9| 0| false
|
||||||
|
10| 0|}
|
||||||
|
11| |
|
||||||
|
12| |pub fn print<T>(left: &str, value: T, right: &str)
|
||||||
|
13| |where
|
||||||
|
14| | T: std::fmt::Display,
|
||||||
|
15| 1|{
|
||||||
|
16| 1| println!("{}{}{}", left, value, right);
|
||||||
|
17| 1|}
|
||||||
|
18| |
|
||||||
|
19| |pub fn wrap_with<F, T>(inner: T, should_wrap: bool, wrapper: F)
|
||||||
|
20| |where
|
||||||
|
21| | F: FnOnce(&T)
|
||||||
|
22| 2|{
|
||||||
|
23| 2| if should_wrap {
|
||||||
|
24| 2| wrapper(&inner)
|
||||||
|
25| 2| }
|
||||||
|
26| 2|}
|
||||||
|
------------------
|
||||||
|
| main[317d481089b8c8fe]::wrap_with::<main[317d481089b8c8fe]::main::{closure#0}, &str>:
|
||||||
|
| 22| 1|{
|
||||||
|
| 23| 1| if should_wrap {
|
||||||
|
| 24| 1| wrapper(&inner)
|
||||||
|
| 25| 1| }
|
||||||
|
| 26| 1|}
|
||||||
|
------------------
|
||||||
|
| main[317d481089b8c8fe]::wrap_with::<main[317d481089b8c8fe]::main::{closure#1}, &str>:
|
||||||
|
| 22| 1|{
|
||||||
|
| 23| 1| if should_wrap {
|
||||||
|
| 24| 1| wrapper(&inner)
|
||||||
|
| 25| 1| }
|
||||||
|
| 26| 1|}
|
||||||
|
------------------
|
||||||
|
27| |
|
||||||
|
28| 1|fn main() {
|
||||||
|
29| 1| let less = 1;
|
||||||
|
30| 1| let more = 100;
|
||||||
|
31| 1|
|
||||||
|
32| 1| if less < more {
|
||||||
|
33| 1| wrap_with(will_be_called(), less < more, |inner| print(" ***", inner, "*** "));
|
||||||
|
34| 1| wrap_with(will_be_called(), more < less, |inner| print(" ***", inner, "*** "));
|
||||||
|
^0
|
||||||
|
35| 1| } else {
|
||||||
|
36| 1| wrap_with(will_not_be_called(), true, |inner| print("wrapped result is: ", inner, ""));
|
||||||
|
37| 1| }
|
||||||
|
38| 1|}
|
||||||
|
|
|
@ -18,6 +18,9 @@ endif
|
||||||
HTMLDOCCK := '$(PYTHON)' '$(S)/src/etc/htmldocck.py'
|
HTMLDOCCK := '$(PYTHON)' '$(S)/src/etc/htmldocck.py'
|
||||||
CGREP := "$(S)/src/etc/cat-and-grep.sh"
|
CGREP := "$(S)/src/etc/cat-and-grep.sh"
|
||||||
|
|
||||||
|
# diff with common flags for multi-platform diffs against text output
|
||||||
|
DIFF := diff -u --strip-trailing-cr
|
||||||
|
|
||||||
# This is the name of the binary we will generate and run; use this
|
# This is the name of the binary we will generate and run; use this
|
||||||
# e.g. for `$(CC) -o $(RUN_BINFILE)`.
|
# e.g. for `$(CC) -o $(RUN_BINFILE)`.
|
||||||
RUN_BINFILE = $(TMPDIR)/$(1)
|
RUN_BINFILE = $(TMPDIR)/$(1)
|
||||||
|
|
|
@ -186,6 +186,9 @@ pub struct Config {
|
||||||
/// The rustdoc executable.
|
/// The rustdoc executable.
|
||||||
pub rustdoc_path: Option<PathBuf>,
|
pub rustdoc_path: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// The rust-demangler executable.
|
||||||
|
pub rust_demangler_path: Option<PathBuf>,
|
||||||
|
|
||||||
/// The Python executable to use for LLDB.
|
/// The Python executable to use for LLDB.
|
||||||
pub lldb_python: String,
|
pub lldb_python: String,
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,7 @@ pub fn parse_config(args: Vec<String>) -> Config {
|
||||||
.reqopt("", "run-lib-path", "path to target shared libraries", "PATH")
|
.reqopt("", "run-lib-path", "path to target shared libraries", "PATH")
|
||||||
.reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH")
|
.reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH")
|
||||||
.optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH")
|
.optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH")
|
||||||
|
.optopt("", "rust-demangler-path", "path to rust-demangler to use in tests", "PATH")
|
||||||
.reqopt("", "lldb-python", "path to python to use for doc tests", "PATH")
|
.reqopt("", "lldb-python", "path to python to use for doc tests", "PATH")
|
||||||
.reqopt("", "docck-python", "path to python to use for doc tests", "PATH")
|
.reqopt("", "docck-python", "path to python to use for doc tests", "PATH")
|
||||||
.optopt("", "valgrind-path", "path to Valgrind executable for Valgrind tests", "PROGRAM")
|
.optopt("", "valgrind-path", "path to Valgrind executable for Valgrind tests", "PROGRAM")
|
||||||
|
@ -182,6 +183,7 @@ pub fn parse_config(args: Vec<String>) -> Config {
|
||||||
run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
|
run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
|
||||||
rustc_path: opt_path(matches, "rustc-path"),
|
rustc_path: opt_path(matches, "rustc-path"),
|
||||||
rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from),
|
rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from),
|
||||||
|
rust_demangler_path: matches.opt_str("rust-demangler-path").map(PathBuf::from),
|
||||||
lldb_python: matches.opt_str("lldb-python").unwrap(),
|
lldb_python: matches.opt_str("lldb-python").unwrap(),
|
||||||
docck_python: matches.opt_str("docck-python").unwrap(),
|
docck_python: matches.opt_str("docck-python").unwrap(),
|
||||||
valgrind_path: matches.opt_str("valgrind-path"),
|
valgrind_path: matches.opt_str("valgrind-path"),
|
||||||
|
@ -246,6 +248,7 @@ pub fn log_config(config: &Config) {
|
||||||
logv(c, format!("run_lib_path: {:?}", config.run_lib_path));
|
logv(c, format!("run_lib_path: {:?}", config.run_lib_path));
|
||||||
logv(c, format!("rustc_path: {:?}", config.rustc_path.display()));
|
logv(c, format!("rustc_path: {:?}", config.rustc_path.display()));
|
||||||
logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
|
logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
|
||||||
|
logv(c, format!("rust_demangler_path: {:?}", config.rust_demangler_path));
|
||||||
logv(c, format!("src_base: {:?}", config.src_base.display()));
|
logv(c, format!("src_base: {:?}", config.src_base.display()));
|
||||||
logv(c, format!("build_base: {:?}", config.build_base.display()));
|
logv(c, format!("build_base: {:?}", config.build_base.display()));
|
||||||
logv(c, format!("stage_id: {}", config.stage_id));
|
logv(c, format!("stage_id: {}", config.stage_id));
|
||||||
|
@ -479,6 +482,8 @@ fn common_inputs_stamp(config: &Config) -> Stamp {
|
||||||
stamp.add_path(&rustdoc_path);
|
stamp.add_path(&rustdoc_path);
|
||||||
stamp.add_path(&rust_src_dir.join("src/etc/htmldocck.py"));
|
stamp.add_path(&rust_src_dir.join("src/etc/htmldocck.py"));
|
||||||
}
|
}
|
||||||
|
// FIXME(richkadel): Do I need to add an `if let Some(rust_demangler_path) contribution to the
|
||||||
|
// stamp here as well?
|
||||||
|
|
||||||
// Compiletest itself.
|
// Compiletest itself.
|
||||||
stamp.add_dir(&rust_src_dir.join("src/tools/compiletest/"));
|
stamp.add_dir(&rust_src_dir.join("src/tools/compiletest/"));
|
||||||
|
|
|
@ -2739,6 +2739,10 @@ impl<'test> TestCx<'test> {
|
||||||
cmd.env("RUSTDOC", cwd.join(rustdoc));
|
cmd.env("RUSTDOC", cwd.join(rustdoc));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(ref rust_demangler) = self.config.rust_demangler_path {
|
||||||
|
cmd.env("RUST_DEMANGLER", cwd.join(rust_demangler));
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(ref node) = self.config.nodejs {
|
if let Some(ref node) = self.config.nodejs {
|
||||||
cmd.env("NODE", node);
|
cmd.env("NODE", node);
|
||||||
}
|
}
|
||||||
|
|
12
src/tools/rust-demangler/Cargo.toml
Normal file
12
src/tools/rust-demangler/Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
authors = ["The Rust Project Developers"]
|
||||||
|
name = "rust-demangler"
|
||||||
|
version = "0.0.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rustc-demangle = "0.1"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "rust-demangler"
|
||||||
|
path = "main.rs"
|
39
src/tools/rust-demangler/main.rs
Normal file
39
src/tools/rust-demangler/main.rs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
//! Demangles rustc mangled names.
|
||||||
|
//!
|
||||||
|
//! This tool uses https://crates.io/crates/rustc-demangle to convert an input buffer of
|
||||||
|
//! newline-separated mangled names into their demangled translations.
|
||||||
|
//!
|
||||||
|
//! This tool can be leveraged by other applications that support third-party demanglers.
|
||||||
|
//! It takes a list of mangled names (one per line) on standard input, and prints a corresponding
|
||||||
|
//! list of demangled names. The tool is designed to support other programs that can leverage a
|
||||||
|
//! third-party demangler, such as `llvm-cov`, via the `-Xdemangler=<path-to-demangler>` option.
|
||||||
|
//!
|
||||||
|
//! To use `rust-demangler`, first build the tool with:
|
||||||
|
//!
|
||||||
|
//! ```shell
|
||||||
|
//! $ ./x.py build rust-demangler
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Then, with `llvm-cov` for example, add the `-Xdemangler=...` option:
|
||||||
|
//!
|
||||||
|
//! ```shell
|
||||||
|
//! $ TARGET="${PWD}/build/x86_64-unknown-linux-gnu"
|
||||||
|
//! $ "${TARGET}"/llvm/bin/llvm-cov show --Xdemangler="${TARGET}"/stage0-tools-bin/rust-demangler \
|
||||||
|
//! --instr-profile=main.profdata ./main --show-line-counts-or-regions
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
use rustc_demangle::demangle;
|
||||||
|
use std::io::{self, Read, Write};
|
||||||
|
|
||||||
|
fn main() -> io::Result<()> {
|
||||||
|
let mut buffer = String::new();
|
||||||
|
io::stdin().read_to_string(&mut buffer)?;
|
||||||
|
let lines = buffer.lines();
|
||||||
|
let mut demangled = Vec::new();
|
||||||
|
for mangled in lines {
|
||||||
|
demangled.push(demangle(mangled).to_string());
|
||||||
|
}
|
||||||
|
demangled.push("".to_string());
|
||||||
|
io::stdout().write_all(demangled.join("\n").as_bytes())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue