diff --git a/Cargo.lock b/Cargo.lock index 4ebbb16442eaa..510a0150b5ae9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -703,6 +703,18 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "coverage-dump" +version = "0.1.0" +dependencies = [ + "anyhow", + "leb128", + "md-5", + "miniz_oxide", + "regex", + "rustc-demangle", +] + [[package]] name = "coverage_test_macros" version = "0.0.0" @@ -2009,6 +2021,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + [[package]] name = "libc" version = "0.2.147" diff --git a/Cargo.toml b/Cargo.toml index d2e84d5426f9f..9b11ae8744b4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ members = [ "src/tools/generate-windows-sys", "src/tools/rustdoc-gui-test", "src/tools/opt-dist", + "src/tools/coverage-dump", ] exclude = [ diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs index 7a82d05ce9ea2..763186a58bf9f 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs @@ -1,4 +1,4 @@ -use rustc_middle::mir::coverage::{CounterId, MappedExpressionIndex}; +use rustc_middle::mir::coverage::{CounterId, ExpressionId, Operand}; /// Must match the layout of `LLVMRustCounterKind`. #[derive(Copy, Clone, Debug)] @@ -30,11 +30,8 @@ pub struct Counter { } impl Counter { - /// Constructs a new `Counter` of kind `Zero`. For this `CounterKind`, the - /// `id` is not used. - pub fn zero() -> Self { - Self { kind: CounterKind::Zero, id: 0 } - } + /// A `Counter` of kind `Zero`. For this counter kind, the `id` is not used. + pub(crate) const ZERO: Self = Self { kind: CounterKind::Zero, id: 0 }; /// Constructs a new `Counter` of kind `CounterValueReference`. pub fn counter_value_reference(counter_id: CounterId) -> Self { @@ -42,20 +39,16 @@ impl Counter { } /// Constructs a new `Counter` of kind `Expression`. - pub fn expression(mapped_expression_index: MappedExpressionIndex) -> Self { - Self { kind: CounterKind::Expression, id: mapped_expression_index.into() } - } - - /// Returns true if the `Counter` kind is `Zero`. - pub fn is_zero(&self) -> bool { - matches!(self.kind, CounterKind::Zero) + pub(crate) fn expression(expression_id: ExpressionId) -> Self { + Self { kind: CounterKind::Expression, id: expression_id.as_u32() } } - /// An explicitly-named function to get the ID value, making it more obvious - /// that the stored value is now 0-based. - pub fn zero_based_id(&self) -> u32 { - debug_assert!(!self.is_zero(), "`id` is undefined for CounterKind::Zero"); - self.id + pub(crate) fn from_operand(operand: Operand) -> Self { + match operand { + Operand::Zero => Self::ZERO, + Operand::Counter(id) => Self::counter_value_reference(id), + Operand::Expression(id) => Self::expression(id), + } } } @@ -81,6 +74,11 @@ pub struct CounterExpression { } impl CounterExpression { + /// The dummy expression `(0 - 0)` has a representation of all zeroes, + /// making it marginally more efficient to initialize than `(0 + 0)`. + pub(crate) const DUMMY: Self = + Self { lhs: Counter::ZERO, kind: ExprKind::Subtract, rhs: Counter::ZERO }; + pub fn new(lhs: Counter, kind: ExprKind, rhs: Counter) -> Self { Self { kind, lhs, rhs } } @@ -172,7 +170,7 @@ impl CounterMappingRegion { ) -> Self { Self { counter, - false_counter: Counter::zero(), + false_counter: Counter::ZERO, file_id, expanded_file_id: 0, start_line, @@ -220,8 +218,8 @@ impl CounterMappingRegion { end_col: u32, ) -> Self { Self { - counter: Counter::zero(), - false_counter: Counter::zero(), + counter: Counter::ZERO, + false_counter: Counter::ZERO, file_id, expanded_file_id, start_line, @@ -243,8 +241,8 @@ impl CounterMappingRegion { end_col: u32, ) -> Self { Self { - counter: Counter::zero(), - false_counter: Counter::zero(), + counter: Counter::ZERO, + false_counter: Counter::ZERO, file_id, expanded_file_id: 0, start_line, @@ -268,7 +266,7 @@ impl CounterMappingRegion { ) -> Self { Self { counter, - false_counter: Counter::zero(), + false_counter: Counter::ZERO, file_id, expanded_file_id: 0, start_line, diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs index f1e68af25d406..e69c6be1963d2 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs @@ -1,10 +1,9 @@ use crate::coverageinfo::ffi::{Counter, CounterExpression, ExprKind}; -use rustc_index::{IndexSlice, IndexVec}; -use rustc_middle::bug; -use rustc_middle::mir::coverage::{ - CodeRegion, CounterId, ExpressionId, MappedExpressionIndex, Op, Operand, -}; +use rustc_data_structures::fx::FxHashMap; +use rustc_index::bit_set::BitSet; +use rustc_index::IndexVec; +use rustc_middle::mir::coverage::{CounterId, ExpressionId, Op, Operand}; use rustc_middle::ty::Instance; use rustc_middle::ty::TyCtxt; @@ -13,7 +12,6 @@ pub struct Expression { lhs: Operand, op: Op, rhs: Operand, - region: Option, } /// Collects all of the coverage regions associated with (a) injected counters, (b) counter @@ -28,16 +26,14 @@ pub struct Expression { /// for a gap area is only used as the line execution count if there are no other regions on a /// line." #[derive(Debug)] -pub struct FunctionCoverage<'tcx> { - instance: Instance<'tcx>, +pub struct FunctionCoverage { source_hash: u64, is_used: bool, - counters: IndexVec>, + counters: BitSet, expressions: IndexVec>, - unreachable_regions: Vec, } -impl<'tcx> FunctionCoverage<'tcx> { +impl<'tcx> FunctionCoverage { /// Creates a new set of coverage data for a used (called) function. pub fn new(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> Self { Self::create(tcx, instance, true) @@ -55,12 +51,10 @@ impl<'tcx> FunctionCoverage<'tcx> { instance, coverageinfo, is_used ); Self { - instance, source_hash: 0, // will be set with the first `add_counter()` is_used, - counters: IndexVec::from_elem_n(None, coverageinfo.num_counters as usize), + counters: BitSet::new_empty(coverageinfo.num_counters as usize), expressions: IndexVec::from_elem_n(None, coverageinfo.num_expressions as usize), - unreachable_regions: Vec::new(), } } @@ -69,21 +63,17 @@ impl<'tcx> FunctionCoverage<'tcx> { self.is_used } - /// Sets the function source hash value. If called multiple times for the same function, all - /// calls should have the same hash value. - pub fn set_function_source_hash(&mut self, source_hash: u64) { + /// Adds a Counter, along with setting the function source hash value. + /// If called multiple times for the same function, + /// all calls should have the same `source_hash` value. + pub fn add_counter(&mut self, source_hash: u64, id: CounterId) { if self.source_hash == 0 { self.source_hash = source_hash; } else { debug_assert_eq!(source_hash, self.source_hash); } - } - /// Adds a code region to be counted by an injected counter intrinsic. - pub fn add_counter(&mut self, id: CounterId, region: CodeRegion) { - if let Some(previous_region) = self.counters[id].replace(region.clone()) { - assert_eq!(previous_region, region, "add_counter: code region for id changed"); - } + self.counters.insert(id); } /// Both counters and "counter expressions" (or simply, "expressions") can be operands in other @@ -95,11 +85,10 @@ impl<'tcx> FunctionCoverage<'tcx> { lhs: Operand, op: Op, rhs: Operand, - region: Option, ) { debug!( - "add_counter_expression({:?}, lhs={:?}, op={:?}, rhs={:?} at {:?}", - expression_id, lhs, op, rhs, region + "add_counter_expression({:?}, lhs={:?}, op={:?}, rhs={:?}", + expression_id, lhs, op, rhs ); debug_assert!( expression_id.as_usize() < self.expressions.len(), @@ -109,203 +98,139 @@ impl<'tcx> FunctionCoverage<'tcx> { self.expressions.len(), self, ); - if let Some(previous_expression) = self.expressions[expression_id].replace(Expression { - lhs, - op, - rhs, - region: region.clone(), - }) { + if let Some(previous_expression) = + self.expressions[expression_id].replace(Expression { lhs, op, rhs }) + { assert_eq!( previous_expression, - Expression { lhs, op, rhs, region }, + Expression { lhs, op, rhs }, "add_counter_expression: expression for id changed" ); } } - /// Add a region that will be marked as "unreachable", with a constant "zero counter". - pub fn add_unreachable_region(&mut self, region: CodeRegion) { - self.unreachable_regions.push(region) - } - - /// Return the source hash, generated from the HIR node structure, and used to indicate whether - /// or not the source code structure changed between different compilations. - pub fn source_hash(&self) -> u64 { - self.source_hash - } - - /// Generate an array of CounterExpressions, and an iterator over all `Counter`s and their - /// associated `Regions` (from which the LLVM-specific `CoverageMapGenerator` will create - /// `CounterMappingRegion`s. - pub fn get_expressions_and_counter_regions( - &self, - ) -> (Vec, impl Iterator) { - assert!( - self.source_hash != 0 || !self.is_used, - "No counters provided the source_hash for used function: {:?}", - self.instance - ); + /// Perform some simplifications to make the final coverage mappings + /// slightly smaller. + pub(crate) fn simplify_expressions(&mut self) { + // The set of expressions that were simplified to either `Zero` or a + // `Counter`. Other expressions that refer to these as operands + // can then also be simplified. + let mut simplified_expressions = FxHashMap::default(); + + // For each expression, perform simplifications based on lower-numbered + // expressions, and then update the map of simplified expressions if + // necessary. + // (By construction, expressions can only refer to other expressions + // that have lower IDs, so one simplification pass is sufficient.) + for (id, maybe_expression) in self.expressions.iter_enumerated_mut() { + let Some(expression) = maybe_expression else { + // If an expression is missing, it must have been optimized away, + // so any operand that refers to it can be replaced with zero. + simplified_expressions.insert(id, Operand::Zero); + continue; + }; + + // If an operand refers to an expression that has been simplified, then + // replace that operand with the simplified version. + let maybe_simplify_operand = |operand: &mut Operand| match operand { + Operand::Zero => {} + Operand::Counter(id) => { + if !self.counters.contains(*id) { + *operand = Operand::Zero; + } + } + Operand::Expression(id) => { + if let Some(simplified) = simplified_expressions.get(id) { + *operand = *simplified; + } + } + }; - let counter_regions = self.counter_regions(); - let (counter_expressions, expression_regions) = self.expressions_with_regions(); - let unreachable_regions = self.unreachable_regions(); + maybe_simplify_operand(&mut expression.lhs); + maybe_simplify_operand(&mut expression.rhs); - let counter_regions = - counter_regions.chain(expression_regions.into_iter().chain(unreachable_regions)); - (counter_expressions, counter_regions) - } + // Coverage counter values cannot be negative, so if an expression + // involves subtraction from zero, assume that its RHS must also be zero. + // (Do this after simplifications that could set the LHS to zero.) + if let Expression { lhs: Operand::Zero, op: Op::Subtract, .. } = expression { + expression.rhs = Operand::Zero; + } - fn counter_regions(&self) -> impl Iterator { - self.counters.iter_enumerated().filter_map(|(index, entry)| { - // Option::map() will return None to filter out missing counters. This may happen - // if, for example, a MIR-instrumented counter is removed during an optimization. - entry.as_ref().map(|region| (Counter::counter_value_reference(index), region)) - }) + // After the above simplifications, if the right hand operand is zero, + // we can replace the expression by its left hand side. + if let Expression { lhs, rhs: Operand::Zero, .. } = expression { + simplified_expressions.insert(id, *lhs); + } else + // And the same thing for the left hand side. + if let Expression { lhs: Operand::Zero, rhs, .. } = expression { + simplified_expressions.insert(id, *rhs); + } + } } - fn expressions_with_regions( - &self, - ) -> (Vec, impl Iterator) { - let mut counter_expressions = Vec::with_capacity(self.expressions.len()); - let mut expression_regions = Vec::with_capacity(self.expressions.len()); - let mut new_indexes = IndexVec::from_elem_n(None, self.expressions.len()); - - // This closure converts any `Expression` operand (`lhs` or `rhs` of the `Op::Add` or - // `Op::Subtract` operation) into its native `llvm::coverage::Counter::CounterKind` type - // and value. - // - // Expressions will be returned from this function in a sequential vector (array) of - // `CounterExpression`, so the expression IDs must be mapped from their original, - // potentially sparse set of indexes. - // - // An `Expression` as an operand will have already been encountered as an `Expression` with - // operands, so its new_index will already have been generated (as a 1-up index value). - // (If an `Expression` as an operand does not have a corresponding new_index, it was - // probably optimized out, after the expression was injected into the MIR, so it will - // get a `CounterKind::Zero` instead.) - // - // In other words, an `Expression`s at any given index can include other expressions as - // operands, but expression operands can only come from the subset of expressions having - // `expression_index`s lower than the referencing `Expression`. Therefore, it is - // reasonable to look up the new index of an expression operand while the `new_indexes` - // vector is only complete up to the current `ExpressionIndex`. - type NewIndexes = IndexSlice>; - let id_to_counter = |new_indexes: &NewIndexes, operand: Operand| match operand { - Operand::Zero => Some(Counter::zero()), - Operand::Counter(id) => Some(Counter::counter_value_reference(id)), + /// This will further simplify any expression, "inlining" the left hand side operand + /// if the right hand side is `Zero`. This is similar to `simplify_expressions` above, + /// but works for an already referenced expression. + pub fn simplified_operand(&self, operand: Operand) -> Counter { + let operand = match operand { + Operand::Zero => operand, + Operand::Counter(id) => { + if !self.counters.contains(id) { + Operand::Zero + } else { + operand + } + } Operand::Expression(id) => { - self.expressions - .get(id) - .expect("expression id is out of range") - .as_ref() - // If an expression was optimized out, assume it would have produced a count - // of zero. This ensures that expressions dependent on optimized-out - // expressions are still valid. - .map_or(Some(Counter::zero()), |_| new_indexes[id].map(Counter::expression)) + if let Some(expr) = &self.expressions[id] { + if expr.rhs == Operand::Zero { + expr.lhs + } else if expr.lhs == Operand::Zero { + expr.rhs + } else { + operand + } + } else { + operand + } } }; - for (original_index, expression) in - self.expressions.iter_enumerated().filter_map(|(original_index, entry)| { - // Option::map() will return None to filter out missing expressions. This may happen - // if, for example, a MIR-instrumented expression is removed during an optimization. - entry.as_ref().map(|expression| (original_index, expression)) - }) - { - let optional_region = &expression.region; - let Expression { lhs, op, rhs, .. } = *expression; + Counter::from_operand(operand) + } + /// Return the source hash, generated from the HIR node structure, and used to indicate whether + /// or not the source code structure changed between different compilations. + pub fn source_hash(&self) -> u64 { + self.source_hash + } - if let Some(Some((lhs_counter, mut rhs_counter))) = id_to_counter(&new_indexes, lhs) - .map(|lhs_counter| { - id_to_counter(&new_indexes, rhs).map(|rhs_counter| (lhs_counter, rhs_counter)) - }) - { - if lhs_counter.is_zero() && op.is_subtract() { - // The left side of a subtraction was probably optimized out. As an example, - // a branch condition might be evaluated as a constant expression, and the - // branch could be removed, dropping unused counters in the process. - // - // Since counters are unsigned, we must assume the result of the expression - // can be no more and no less than zero. An expression known to evaluate to zero - // does not need to be added to the coverage map. - // - // Coverage test `loops_branches.rs` includes multiple variations of branches - // based on constant conditional (literal `true` or `false`), and demonstrates - // that the expected counts are still correct. - debug!( - "Expression subtracts from zero (assume unreachable): \ - original_index={:?}, lhs={:?}, op={:?}, rhs={:?}, region={:?}", - original_index, lhs, op, rhs, optional_region, - ); - rhs_counter = Counter::zero(); + /// Convert this function's coverage expression data into a form that can be + /// passed through FFI to LLVM. + pub fn counter_expressions(&self) -> Vec { + // We know that LLVM will optimize out any unused expressions before + // producing the final coverage map, so there's no need to do the same + // thing on the Rust side unless we're confident we can do much better. + // (See `CounterExpressionsMinimizer` in `CoverageMappingWriter.cpp`.) + + self.expressions + .iter() + .map(|expression| match expression { + None => { + // This expression ID was allocated, but we never saw the + // actual expression, so it must have been optimized out. + // Replace it with a dummy expression, and let LLVM take + // care of omitting it from the expression list. + CounterExpression::DUMMY } - debug_assert!( - lhs_counter.is_zero() - // Note: with `as usize` the ID _could_ overflow/wrap if `usize = u16` - || ((lhs_counter.zero_based_id() as usize) - <= usize::max(self.counters.len(), self.expressions.len())), - "lhs id={} > both counters.len()={} and expressions.len()={} - ({:?} {:?} {:?})", - lhs_counter.zero_based_id(), - self.counters.len(), - self.expressions.len(), - lhs_counter, - op, - rhs_counter, - ); - - debug_assert!( - rhs_counter.is_zero() - // Note: with `as usize` the ID _could_ overflow/wrap if `usize = u16` - || ((rhs_counter.zero_based_id() as usize) - <= usize::max(self.counters.len(), self.expressions.len())), - "rhs id={} > both counters.len()={} and expressions.len()={} - ({:?} {:?} {:?})", - rhs_counter.zero_based_id(), - self.counters.len(), - self.expressions.len(), - lhs_counter, - op, - rhs_counter, - ); - - // Both operands exist. `Expression` operands exist in `self.expressions` and have - // been assigned a `new_index`. - let mapped_expression_index = - MappedExpressionIndex::from(counter_expressions.len()); - let expression = CounterExpression::new( - lhs_counter, + &Some(Expression { lhs, op, rhs, .. }) => CounterExpression::new( + Counter::from_operand(lhs), match op { Op::Add => ExprKind::Add, Op::Subtract => ExprKind::Subtract, }, - rhs_counter, - ); - debug!( - "Adding expression {:?} = {:?}, region: {:?}", - mapped_expression_index, expression, optional_region - ); - counter_expressions.push(expression); - new_indexes[original_index] = Some(mapped_expression_index); - if let Some(region) = optional_region { - expression_regions.push((Counter::expression(mapped_expression_index), region)); - } - } else { - bug!( - "expression has one or more missing operands \ - original_index={:?}, lhs={:?}, op={:?}, rhs={:?}, region={:?}", - original_index, - lhs, - op, - rhs, - optional_region, - ); - } - } - (counter_expressions, expression_regions.into_iter()) - } - - fn unreachable_regions(&self) -> impl Iterator { - self.unreachable_regions.iter().map(|region| (Counter::zero(), region)) + Counter::from_operand(rhs), + ), + }) + .collect::>() } } diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs index 97a99e5105675..4ff62b49b1b88 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs @@ -10,7 +10,7 @@ use rustc_hir::def_id::DefId; use rustc_llvm::RustString; use rustc_middle::bug; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; -use rustc_middle::mir::coverage::CodeRegion; +use rustc_middle::mir::coverage::{CodeRegion, CoverageExpression, CoverageRegionKind}; use rustc_middle::ty::TyCtxt; use rustc_span::Symbol; @@ -59,13 +59,35 @@ pub fn finalize(cx: &CodegenCx<'_, '_>) { // Encode coverage mappings and generate function records let mut function_data = Vec::new(); - for (instance, function_coverage) in function_coverage_map { + for (instance, mut function_coverage) in function_coverage_map { debug!("Generate function coverage for {}, {:?}", cx.codegen_unit.name(), instance); + + let mir_body = tcx.instance_mir(instance.def); + let Some(coverage_info) = mir_body.coverage_info.as_ref() else { continue }; + for CoverageExpression { id, lhs, op, rhs } in &coverage_info.expressions { + debug!( + "adding counter expression to coverage_map: instance={:?}, id={:?}, {:?} {:?} {:?}", + instance, id, lhs, op, rhs, + ); + function_coverage.add_counter_expression(*id, *lhs, *op, *rhs); + } + function_coverage.simplify_expressions(); + let expressions = function_coverage.counter_expressions(); + + let counter_regions = coverage_info.regions.iter().map(|region| { + ( + match region.kind { + CoverageRegionKind::Code(operand) => { + function_coverage.simplified_operand(operand) + } + }, + ®ion.code_region, + ) + }); + let mangled_function_name = tcx.symbol_name(instance).name; let source_hash = function_coverage.source_hash(); let is_used = function_coverage.is_used(); - let (expressions, counter_regions) = - function_coverage.get_expressions_and_counter_regions(); let coverage_mapping_buffer = llvm::build_byte_buffer(|coverage_mapping_buffer| { mapgen.write_coverage_mapping(expressions, counter_regions, coverage_mapping_buffer); diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs index 621fd36b2a323..d3ee5e8cce1cf 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs @@ -16,7 +16,7 @@ use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_llvm::RustString; use rustc_middle::bug; -use rustc_middle::mir::coverage::{CodeRegion, CounterId, CoverageKind, ExpressionId, Op, Operand}; +use rustc_middle::mir::coverage::CounterId; use rustc_middle::mir::Coverage; use rustc_middle::ty; use rustc_middle::ty::layout::{FnAbiOf, HasTyCtxt}; @@ -37,7 +37,7 @@ const VAR_ALIGN_BYTES: usize = 8; /// A context object for maintaining all state needed by the coverageinfo module. pub struct CrateCoverageContext<'ll, 'tcx> { /// Coverage data for each instrumented function identified by DefId. - pub(crate) function_coverage_map: RefCell, FunctionCoverage<'tcx>>>, + pub(crate) function_coverage_map: RefCell, FunctionCoverage>>, pub(crate) pgo_func_name_var_map: RefCell, &'ll llvm::Value>>, } @@ -49,7 +49,7 @@ impl<'ll, 'tcx> CrateCoverageContext<'ll, 'tcx> { } } - pub fn take_function_coverage_map(&self) -> FxHashMap, FunctionCoverage<'tcx>> { + pub fn take_function_coverage_map(&self) -> FxHashMap, FunctionCoverage> { self.function_coverage_map.replace(FxHashMap::default()) } } @@ -104,141 +104,33 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> { fn add_coverage(&mut self, instance: Instance<'tcx>, coverage: &Coverage) { let bx = self; - let Coverage { kind, code_region } = coverage.clone(); - match kind { - CoverageKind::Counter { function_source_hash, id } => { - if bx.set_function_source_hash(instance, function_source_hash) { - // If `set_function_source_hash()` returned true, the coverage map is enabled, - // so continue adding the counter. - if let Some(code_region) = code_region { - // Note: Some counters do not have code regions, but may still be referenced - // from expressions. In that case, don't add the counter to the coverage map, - // but do inject the counter intrinsic. - bx.add_coverage_counter(instance, id, code_region); - } - - let coverageinfo = bx.tcx().coverageinfo(instance.def); - - let fn_name = bx.get_pgo_func_name_var(instance); - let hash = bx.const_u64(function_source_hash); - let num_counters = bx.const_u32(coverageinfo.num_counters); - let index = bx.const_u32(id.as_u32()); - debug!( - "codegen intrinsic instrprof.increment(fn_name={:?}, hash={:?}, num_counters={:?}, index={:?})", - fn_name, hash, num_counters, index, - ); - bx.instrprof_increment(fn_name, hash, num_counters, index); - } - } - CoverageKind::Expression { id, lhs, op, rhs } => { - bx.add_coverage_counter_expression(instance, id, lhs, op, rhs, code_region); - } - CoverageKind::Unreachable => { - bx.add_coverage_unreachable( - instance, - code_region.expect("unreachable regions always have code regions"), - ); - } - } - } -} + let Some(coverage_context) = bx.coverage_context() else { return }; + let Coverage { function_source_hash, id } = coverage.clone(); -// These methods used to be part of trait `CoverageInfoBuilderMethods`, but -// after moving most coverage code out of SSA they are now just ordinary methods. -impl<'tcx> Builder<'_, '_, 'tcx> { - /// Returns true if the function source hash was added to the coverage map (even if it had - /// already been added, for this instance). Returns false *only* if `-C instrument-coverage` is - /// not enabled (a coverage map is not being generated). - fn set_function_source_hash( - &mut self, - instance: Instance<'tcx>, - function_source_hash: u64, - ) -> bool { - if let Some(coverage_context) = self.coverage_context() { - debug!( - "ensuring function source hash is set for instance={:?}; function_source_hash={}", - instance, function_source_hash, - ); + { let mut coverage_map = coverage_context.function_coverage_map.borrow_mut(); - coverage_map + let func_coverage = coverage_map .entry(instance) - .or_insert_with(|| FunctionCoverage::new(self.tcx, instance)) - .set_function_source_hash(function_source_hash); - true - } else { - false - } - } + .or_insert_with(|| FunctionCoverage::new(bx.tcx(), instance)); - /// Returns true if the counter was added to the coverage map; false if `-C instrument-coverage` - /// is not enabled (a coverage map is not being generated). - fn add_coverage_counter( - &mut self, - instance: Instance<'tcx>, - id: CounterId, - region: CodeRegion, - ) -> bool { - if let Some(coverage_context) = self.coverage_context() { debug!( - "adding counter to coverage_map: instance={:?}, id={:?}, region={:?}", - instance, id, region, + "ensuring function source hash is set for instance={:?}; function_source_hash={}", + instance, function_source_hash, ); - let mut coverage_map = coverage_context.function_coverage_map.borrow_mut(); - coverage_map - .entry(instance) - .or_insert_with(|| FunctionCoverage::new(self.tcx, instance)) - .add_counter(id, region); - true - } else { - false + func_coverage.add_counter(function_source_hash, id); } - } - /// Returns true if the expression was added to the coverage map; false if - /// `-C instrument-coverage` is not enabled (a coverage map is not being generated). - fn add_coverage_counter_expression( - &mut self, - instance: Instance<'tcx>, - id: ExpressionId, - lhs: Operand, - op: Op, - rhs: Operand, - region: Option, - ) -> bool { - if let Some(coverage_context) = self.coverage_context() { - debug!( - "adding counter expression to coverage_map: instance={:?}, id={:?}, {:?} {:?} {:?}; \ - region: {:?}", - instance, id, lhs, op, rhs, region, - ); - let mut coverage_map = coverage_context.function_coverage_map.borrow_mut(); - coverage_map - .entry(instance) - .or_insert_with(|| FunctionCoverage::new(self.tcx, instance)) - .add_counter_expression(id, lhs, op, rhs, region); - true - } else { - false - } - } + let coverageinfo = bx.tcx().coverageinfo(instance.def); - /// Returns true if the region was added to the coverage map; false if `-C instrument-coverage` - /// is not enabled (a coverage map is not being generated). - fn add_coverage_unreachable(&mut self, instance: Instance<'tcx>, region: CodeRegion) -> bool { - if let Some(coverage_context) = self.coverage_context() { - debug!( - "adding unreachable code to coverage_map: instance={:?}, at {:?}", - instance, region, - ); - let mut coverage_map = coverage_context.function_coverage_map.borrow_mut(); - coverage_map - .entry(instance) - .or_insert_with(|| FunctionCoverage::new(self.tcx, instance)) - .add_unreachable_region(region); - true - } else { - false - } + let fn_name = bx.get_pgo_func_name_var(instance); + let hash = bx.const_u64(function_source_hash); + let num_counters = bx.const_u32(coverageinfo.num_counters); + let index = bx.const_u32(id.as_u32()); + debug!( + "codegen intrinsic instrprof.increment(fn_name={:?}, hash={:?}, num_counters={:?}, index={:?})", + fn_name, hash, num_counters, index, + ); + bx.instrprof_increment(fn_name, hash, num_counters, index); } } @@ -299,12 +191,12 @@ fn codegen_unused_fn_and_counter<'tcx>(cx: &CodegenCx<'_, 'tcx>, instance: Insta fn add_unused_function_coverage<'tcx>( cx: &CodegenCx<'_, 'tcx>, instance: Instance<'tcx>, - def_id: DefId, + _def_id: DefId, ) { let tcx = cx.tcx; - let mut function_coverage = FunctionCoverage::unused(tcx, instance); - for (index, &code_region) in tcx.covered_code_regions(def_id).iter().enumerate() { + let function_coverage = FunctionCoverage::unused(tcx, instance); + /*for (index, &code_region) in tcx.covered_code_regions(def_id).iter().enumerate() { if index == 0 { // Insert at least one real counter so the LLVM CoverageMappingReader will find expected // definitions. @@ -312,7 +204,7 @@ fn add_unused_function_coverage<'tcx>( } else { function_coverage.add_unreachable_region(code_region.clone()); } - } + }*/ if let Some(coverage_context) = cx.coverage_context() { coverage_context.function_coverage_map.borrow_mut().insert(instance, function_coverage); diff --git a/compiler/rustc_middle/src/mir/coverage.rs b/compiler/rustc_middle/src/mir/coverage.rs index 1efb54bdb0872..b3660974609eb 100644 --- a/compiler/rustc_middle/src/mir/coverage.rs +++ b/compiler/rustc_middle/src/mir/coverage.rs @@ -1,5 +1,6 @@ //! Metadata from source code coverage analysis and instrumentation. +use rustc_index::IndexVec; use rustc_macros::HashStable; use rustc_span::Symbol; @@ -45,16 +46,6 @@ impl ExpressionId { } } -rustc_index::newtype_index! { - /// MappedExpressionIndex values ascend from zero, and are recalculated indexes based on their - /// array position in the LLVM coverage map "Expressions" array, which is assembled during the - /// "mapgen" process. They cannot be computed algorithmically, from the other `newtype_index`s. - #[derive(HashStable)] - #[max = 0xFFFF_FFFF] - #[debug_format = "MappedExpressionIndex({})"] - pub struct MappedExpressionIndex {} -} - /// Operand of a coverage-counter expression. /// /// Operands can be a constant zero value, an actual coverage counter, or another @@ -77,46 +68,6 @@ impl Debug for Operand { } } -#[derive(Clone, PartialEq, TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)] -pub enum CoverageKind { - Counter { - function_source_hash: u64, - /// ID of this counter within its enclosing function. - /// Expressions in the same function can refer to it as an operand. - id: CounterId, - }, - Expression { - /// ID of this coverage-counter expression within its enclosing function. - /// Other expressions in the same function can refer to it as an operand. - id: ExpressionId, - lhs: Operand, - op: Op, - rhs: Operand, - }, - Unreachable, -} - -impl Debug for CoverageKind { - fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { - use CoverageKind::*; - match self { - Counter { id, .. } => write!(fmt, "Counter({:?})", id.index()), - Expression { id, lhs, op, rhs } => write!( - fmt, - "Expression({:?}) = {:?} {} {:?}", - id.index(), - lhs, - match op { - Op::Add => "+", - Op::Subtract => "-", - }, - rhs, - ), - Unreachable => write!(fmt, "Unreachable"), - } - } -} - #[derive(Clone, TyEncodable, TyDecodable, Hash, HashStable, PartialEq, Eq, PartialOrd, Ord)] #[derive(TypeFoldable, TypeVisitable)] pub struct CodeRegion { @@ -137,7 +88,7 @@ impl Debug for CodeRegion { } } -#[derive(Copy, Clone, Debug, PartialEq, TyEncodable, TyDecodable, Hash, HashStable)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, TyEncodable, TyDecodable, Hash, HashStable)] #[derive(TypeFoldable, TypeVisitable)] pub enum Op { Subtract, @@ -153,3 +104,52 @@ impl Op { matches!(self, Self::Subtract) } } + +#[derive(Clone, TyEncodable, TyDecodable, Hash, HashStable, PartialEq, Eq)] +#[derive(TypeFoldable, TypeVisitable)] +pub struct CoverageExpression { + /// ID of this coverage-counter expression within its enclosing function. + /// Other expressions in the same function can refer to it as an operand. + pub id: ExpressionId, + pub lhs: Operand, + pub op: Op, + pub rhs: Operand, +} + +impl Debug for CoverageExpression { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + let CoverageExpression { id, lhs, op, rhs } = self; + write!( + fmt, + "Expression({:?}) = {:?} {} {:?}", + id.index(), + lhs, + match op { + Op::Add => "+", + Op::Subtract => "-", + }, + rhs, + ) + } +} + +#[derive(Clone, Debug, TyEncodable, TyDecodable, Hash, HashStable, PartialEq, Eq)] +#[derive(TypeFoldable, TypeVisitable)] +pub enum CoverageRegionKind { + Code(Operand), +} + +#[derive(Clone, Debug, TyEncodable, TyDecodable, Hash, HashStable, PartialEq, Eq)] +#[derive(TypeFoldable, TypeVisitable)] +pub struct CoverageRegion { + pub kind: CoverageRegionKind, + pub code_region: CodeRegion, +} + +#[derive(Clone, Debug, TyEncodable, TyDecodable, Hash, HashStable, PartialEq, Eq)] +#[derive(TypeFoldable, TypeVisitable)] +pub struct CoverageInfo { + pub num_counters: u32, + pub expressions: IndexVec, + pub regions: Vec, +} diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs index 9ef3a1b30e498..7cc6e4003b1d9 100644 --- a/compiler/rustc_middle/src/mir/mod.rs +++ b/compiler/rustc_middle/src/mir/mod.rs @@ -243,6 +243,11 @@ pub struct Body<'tcx> { /// and used for debuginfo. Indexed by a `SourceScope`. pub source_scopes: IndexVec>, + /// This is metadata computed by the `-C instrument-coverage` option. + /// The metadata includes the code regions relevant for code coverage, as + /// well as intermediate expressions used for a regions counter. + pub coverage_info: Option>, + pub generator: Option>>, /// Declarations of locals. @@ -333,6 +338,7 @@ impl<'tcx> Body<'tcx> { source, basic_blocks: BasicBlocks::new(basic_blocks), source_scopes, + coverage_info: None, generator: generator_kind.map(|generator_kind| { Box::new(GeneratorInfo { yield_ty: None, @@ -368,6 +374,7 @@ impl<'tcx> Body<'tcx> { source: MirSource::item(CRATE_DEF_ID.to_def_id()), basic_blocks: BasicBlocks::new(basic_blocks), source_scopes: IndexVec::new(), + coverage_info: None, generator: None, local_decls: IndexVec::new(), user_type_annotations: IndexVec::new(), @@ -1474,10 +1481,9 @@ impl Debug for Statement<'_> { AscribeUserType(box (ref place, ref c_ty), ref variance) => { write!(fmt, "AscribeUserType({place:?}, {variance:?}, {c_ty:?})") } - Coverage(box self::Coverage { ref kind, code_region: Some(ref rgn) }) => { - write!(fmt, "Coverage::{kind:?} for {rgn:?}") + Coverage(box ref coverage) => { + write!(fmt, "Coverage({coverage:?})") } - Coverage(box ref coverage) => write!(fmt, "Coverage::{:?}", coverage.kind), Intrinsic(box ref intrinsic) => write!(fmt, "{intrinsic}"), ConstEvalCounter => write!(fmt, "ConstEvalCounter"), Nop => write!(fmt, "nop"), diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index e91e822f915ab..c82565ccfc754 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -5,7 +5,7 @@ use super::{BasicBlock, Constant, Local, SwitchTargets, UserTypeProjection}; -use crate::mir::coverage::{CodeRegion, CoverageKind}; +use crate::mir::coverage::CounterId; use crate::traits::Reveal; use crate::ty::adjustment::PointerCoercion; use crate::ty::GenericArgsRef; @@ -359,11 +359,9 @@ pub enum StatementKind<'tcx> { /// Disallowed after drop elaboration. AscribeUserType(Box<(Place<'tcx>, UserTypeProjection)>, ty::Variance), - /// Marks the start of a "coverage region", injected with '-Cinstrument-coverage'. A - /// `Coverage` statement carries metadata about the coverage region, used to inject a coverage - /// map into the binary. If `Coverage::kind` is a `Counter`, the statement also generates - /// executable code, to increment a counter variable at runtime, each time the code region is - /// executed. + /// A coverage counter injected with '-Cinstrument-coverage'. + /// This statement generates executable code to increment a counter variable at runtime, + /// each time the code region is executed. Coverage(Box), /// Denotes a call to an intrinsic that does not require an unwind path and always returns. @@ -522,8 +520,8 @@ pub enum FakeReadCause { #[derive(Clone, Debug, PartialEq, TyEncodable, TyDecodable, Hash, HashStable)] #[derive(TypeFoldable, TypeVisitable)] pub struct Coverage { - pub kind: CoverageKind, - pub code_region: Option, + pub function_source_hash: u64, + pub id: CounterId, } #[derive(Clone, Debug, PartialEq, TyEncodable, TyDecodable, Hash, HashStable)] diff --git a/compiler/rustc_middle/src/ty/structural_impls.rs b/compiler/rustc_middle/src/ty/structural_impls.rs index f979ddd00fa01..7e93a236308b8 100644 --- a/compiler/rustc_middle/src/ty/structural_impls.rs +++ b/compiler/rustc_middle/src/ty/structural_impls.rs @@ -469,7 +469,6 @@ TrivialTypeTraversalAndLiftImpls! { ::rustc_target::spec::abi::Abi, crate::mir::coverage::CounterId, crate::mir::coverage::ExpressionId, - crate::mir::coverage::MappedExpressionIndex, crate::mir::Local, crate::mir::Promoted, crate::traits::Reveal, diff --git a/compiler/rustc_mir_build/src/build/custom/mod.rs b/compiler/rustc_mir_build/src/build/custom/mod.rs index e5c2cc6c7bbce..237623d5160e0 100644 --- a/compiler/rustc_mir_build/src/build/custom/mod.rs +++ b/compiler/rustc_mir_build/src/build/custom/mod.rs @@ -48,6 +48,7 @@ pub(super) fn build_custom_mir<'tcx>( source: MirSource::item(did), phase: MirPhase::Built, source_scopes: IndexVec::new(), + coverage_info: None, generator: None, local_decls: IndexVec::new(), user_type_annotations: IndexVec::new(), diff --git a/compiler/rustc_mir_transform/src/coverage/counters.rs b/compiler/rustc_mir_transform/src/coverage/counters.rs index 3d442e5dca9f9..9309f90bba7da 100644 --- a/compiler/rustc_mir_transform/src/coverage/counters.rs +++ b/compiler/rustc_mir_transform/src/coverage/counters.rs @@ -59,8 +59,8 @@ impl Debug for BcbCounter { /// Generates and stores coverage counter and coverage expression information /// associated with nodes/edges in the BCB graph. pub(super) struct CoverageCounters { - next_counter_id: CounterId, - next_expression_id: ExpressionId, + pub(super) next_counter_id: CounterId, + pub(super) next_expression_id: ExpressionId, /// Coverage counters/expressions that are associated with individual BCBs. bcb_counters: IndexVec>, @@ -142,17 +142,6 @@ impl CoverageCounters { expression } - pub fn make_identity_counter(&mut self, counter_operand: Operand) -> BcbCounter { - let some_debug_block_label = if self.debug_counters.is_enabled() { - self.debug_counters.some_block_label(counter_operand).cloned() - } else { - None - }; - self.make_expression(counter_operand, Op::Add, Operand::Zero, || { - some_debug_block_label.clone() - }) - } - /// Counter IDs start from one and go up. fn next_counter(&mut self) -> CounterId { let next = self.next_counter_id; diff --git a/compiler/rustc_mir_transform/src/coverage/debug.rs b/compiler/rustc_mir_transform/src/coverage/debug.rs index af616c498fd3a..90141d497cd9c 100644 --- a/compiler/rustc_mir_transform/src/coverage/debug.rs +++ b/compiler/rustc_mir_transform/src/coverage/debug.rs @@ -285,12 +285,6 @@ impl DebugCounters { } } - pub fn some_block_label(&self, operand: Operand) -> Option<&String> { - self.some_counters.as_ref().and_then(|counters| { - counters.get(&operand).and_then(|debug_counter| debug_counter.some_block_label.as_ref()) - }) - } - pub fn format_counter(&self, counter_kind: &BcbCounter) -> String { match *counter_kind { BcbCounter::Counter { .. } => { diff --git a/compiler/rustc_mir_transform/src/coverage/mod.rs b/compiler/rustc_mir_transform/src/coverage/mod.rs index 8c9eae508b4ae..d2872d28b9541 100644 --- a/compiler/rustc_mir_transform/src/coverage/mod.rs +++ b/compiler/rustc_mir_transform/src/coverage/mod.rs @@ -207,6 +207,16 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { let result = self .coverage_counters .make_bcb_counters(&mut self.basic_coverage_blocks, &coverage_spans); + let num_counters = self.coverage_counters.next_counter_id.as_u32(); + let num_expressions = self.coverage_counters.next_expression_id.as_usize(); + let mut coverage_info = CoverageInfo { + num_counters, + expressions: IndexVec::from_fn_n( + |id| CoverageExpression { id, lhs: Operand::Zero, op: Op::Add, rhs: Operand::Zero }, + num_expressions, + ), + regions: vec![], + }; if let Ok(()) = result { // If debugging, add any intermediate expressions (which are not associated with any @@ -231,6 +241,7 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { coverage_spans, &mut graphviz_data, &mut debug_used_expressions, + &mut coverage_info, ); //////////////////////////////////////////////////// @@ -239,7 +250,11 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { // to ensure `BasicCoverageBlock` counters that other `Expression`s may depend on // are in fact counted, even though they don't directly contribute to counting // their own independent code region's coverage. - self.inject_indirect_counters(&mut graphviz_data, &mut debug_used_expressions); + self.inject_indirect_counters( + &mut graphviz_data, + &mut debug_used_expressions, + &mut coverage_info, + ); // Intermediate expressions will be injected as the final step, after generating // debug output, if any. @@ -273,11 +288,11 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { //////////////////////////////////////////////////// // Finally, inject the intermediate expressions collected along the way. for intermediate_expression in &self.coverage_counters.intermediate_expressions { - inject_intermediate_expression( - self.mir_body, - self.make_mir_coverage_kind(intermediate_expression), - ); + if let BcbCounter::Expression { id, lhs, op, rhs } = intermediate_expression.clone() { + coverage_info.expressions[id] = CoverageExpression { id, lhs, op, rhs } + } } + self.mir_body.coverage_info = Some(Box::new(coverage_info)); } /// Inject a counter for each `CoverageSpan`. There can be multiple `CoverageSpan`s for a given @@ -293,36 +308,56 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { coverage_spans: Vec, graphviz_data: &mut debug::GraphvizData, debug_used_expressions: &mut debug::UsedExpressions, + coverage_info: &mut CoverageInfo, ) { let tcx = self.tcx; let source_map = tcx.sess.source_map(); let body_span = self.body_span; let file_name = Symbol::intern(&self.source_file.name.prefer_remapped().to_string_lossy()); - let mut bcb_counters = IndexVec::from_elem_n(None, self.basic_coverage_blocks.num_nodes()); + let mut bcb_counters = + IndexVec::from_elem_n(None::, self.basic_coverage_blocks.num_nodes()); for covspan in coverage_spans { let bcb = covspan.bcb; let span = covspan.span; - let counter_kind = if let Some(&counter_operand) = bcb_counters[bcb].as_ref() { - self.coverage_counters.make_identity_counter(counter_operand) - } else if let Some(counter_kind) = self.coverage_counters.take_bcb_counter(bcb) { - bcb_counters[bcb] = Some(counter_kind.as_operand()); - debug_used_expressions.add_expression_operands(&counter_kind); - counter_kind - } else { - bug!("Every BasicCoverageBlock should have a Counter or Expression"); - }; + let (counter_kind, was_injected) = + if let Some(counter_kind) = bcb_counters[bcb].as_ref() { + (counter_kind.clone(), true) + } else if let Some(counter_kind) = self.coverage_counters.take_bcb_counter(bcb) { + bcb_counters[bcb] = Some(counter_kind.clone()); + debug_used_expressions.add_expression_operands(&counter_kind); + (counter_kind, false) + } else { + bug!("Every BasicCoverageBlock should have a Counter or Expression"); + }; graphviz_data.add_bcb_coverage_span_with_counter(bcb, &covspan, &counter_kind); let code_region = make_code_region(source_map, file_name, &self.source_file, span, body_span); - inject_statement( - self.mir_body, - self.make_mir_coverage_kind(&counter_kind), - self.bcb_leader_bb(bcb), - Some(code_region), - ); + match counter_kind { + BcbCounter::Counter { id } => { + if !was_injected { + inject_statement( + self.mir_body, + self.bcb_leader_bb(bcb), + self.function_source_hash, + id, + ); + } + coverage_info.regions.push(CoverageRegion { + kind: CoverageRegionKind::Code(Operand::Counter(id)), + code_region, + }); + } + BcbCounter::Expression { id, lhs, op, rhs } => { + coverage_info.expressions[id] = CoverageExpression { id, lhs, op, rhs }; + coverage_info.regions.push(CoverageRegion { + kind: CoverageRegionKind::Code(Operand::Expression(id)), + code_region, + }); + } + } } } @@ -338,6 +373,7 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { &mut self, graphviz_data: &mut debug::GraphvizData, debug_used_expressions: &mut debug::UsedExpressions, + coverage_info: &mut CoverageInfo, ) { let mut bcb_counters_without_direct_coverage_spans = Vec::new(); for (target_bcb, counter_kind) in self.coverage_counters.drain_bcb_counters() { @@ -367,7 +403,7 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { ); match counter_kind { - BcbCounter::Counter { .. } => { + BcbCounter::Counter { id } => { let inject_to_bb = if let Some(from_bcb) = edge_from_bcb { // The MIR edge starts `from_bb` (the outgoing / last BasicBlock in // `from_bcb`) and ends at `to_bb` (the incoming / first BasicBlock in the @@ -400,17 +436,11 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { target_bb }; - inject_statement( - self.mir_body, - self.make_mir_coverage_kind(&counter_kind), - inject_to_bb, - None, - ); + inject_statement(self.mir_body, inject_to_bb, self.function_source_hash, id); + } + BcbCounter::Expression { id, lhs, op, rhs } => { + coverage_info.expressions[id] = CoverageExpression { id, lhs, op, rhs } } - BcbCounter::Expression { .. } => inject_intermediate_expression( - self.mir_body, - self.make_mir_coverage_kind(&counter_kind), - ), } } } @@ -434,17 +464,6 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { fn format_counter(&self, counter_kind: &BcbCounter) -> String { self.coverage_counters.debug_counters.format_counter(counter_kind) } - - fn make_mir_coverage_kind(&self, counter_kind: &BcbCounter) -> CoverageKind { - match *counter_kind { - BcbCounter::Counter { id } => { - CoverageKind::Counter { function_source_hash: self.function_source_hash, id } - } - BcbCounter::Expression { id, lhs, op, rhs } => { - CoverageKind::Expression { id, lhs, op, rhs } - } - } - } } fn inject_edge_counter_basic_block( @@ -472,40 +491,20 @@ fn inject_edge_counter_basic_block( fn inject_statement( mir_body: &mut mir::Body<'_>, - counter_kind: CoverageKind, bb: BasicBlock, - some_code_region: Option, + function_source_hash: u64, + id: CounterId, ) { - debug!( - " injecting statement {:?} for {:?} at code region: {:?}", - counter_kind, bb, some_code_region - ); + debug!(" injecting counter statement {id:?} for {bb:?}",); let data = &mut mir_body[bb]; let source_info = data.terminator().source_info; let statement = Statement { source_info, - kind: StatementKind::Coverage(Box::new(Coverage { - kind: counter_kind, - code_region: some_code_region, - })), + kind: StatementKind::Coverage(Box::new(Coverage { function_source_hash, id })), }; data.statements.insert(0, statement); } -// Non-code expressions are injected into the coverage map, without generating executable code. -fn inject_intermediate_expression(mir_body: &mut mir::Body<'_>, expression: CoverageKind) { - debug_assert!(matches!(expression, CoverageKind::Expression { .. })); - debug!(" injecting non-code expression {:?}", expression); - let inject_in_bb = mir::START_BLOCK; - let data = &mut mir_body[inject_in_bb]; - let source_info = data.terminator().source_info; - let statement = Statement { - source_info, - kind: StatementKind::Coverage(Box::new(Coverage { kind: expression, code_region: None })), - }; - data.statements.push(statement); -} - /// Convert the Span into its file name, start line and column, and end line and column fn make_code_region( source_map: &SourceMap, diff --git a/compiler/rustc_mir_transform/src/coverage/query.rs b/compiler/rustc_mir_transform/src/coverage/query.rs index aa205655f9dab..42e7a244e5991 100644 --- a/compiler/rustc_mir_transform/src/coverage/query.rs +++ b/compiler/rustc_mir_transform/src/coverage/query.rs @@ -1,7 +1,5 @@ -use super::*; - use rustc_middle::mir::coverage::*; -use rustc_middle::mir::{self, Body, Coverage, CoverageInfo}; +use rustc_middle::mir::{self, CoverageInfo}; use rustc_middle::query::Providers; use rustc_middle::ty::{self, TyCtxt}; use rustc_span::def_id::DefId; @@ -24,117 +22,24 @@ pub(crate) fn provide(providers: &mut Providers) { /// /// MIR optimization may split and duplicate some BasicBlock sequences, or optimize out some code /// including injected counters. (It is OK if some counters are optimized out, but those counters -/// are still included in the total `num_counters` or `num_expressions`.) Simply counting the -/// calls may not work; but computing the number of counters or expressions by adding `1` to the -/// highest ID (for a given instrumented function) is valid. -/// -/// This visitor runs twice, first with `add_missing_operands` set to `false`, to find the maximum -/// counter ID and maximum expression ID based on their enum variant `id` fields; then, as a -/// safeguard, with `add_missing_operands` set to `true`, to find any other counter or expression -/// IDs referenced by expression operands, if not already seen. -/// -/// Ideally, each operand ID in a MIR `CoverageKind::Expression` will have a separate MIR `Coverage` -/// statement for the `Counter` or `Expression` with the referenced ID. but since current or future -/// MIR optimizations can theoretically optimize out segments of a MIR, it may not be possible to -/// guarantee this, so the second pass ensures the `CoverageInfo` counts include all referenced IDs. -struct CoverageVisitor { - info: CoverageInfo, - add_missing_operands: bool, -} - -impl CoverageVisitor { - /// Updates `num_counters` to the maximum encountered counter ID plus 1. - #[inline(always)] - fn update_num_counters(&mut self, counter_id: CounterId) { - let counter_id = counter_id.as_u32(); - self.info.num_counters = std::cmp::max(self.info.num_counters, counter_id + 1); - } - - /// Updates `num_expressions` to the maximum encountered expression ID plus 1. - #[inline(always)] - fn update_num_expressions(&mut self, expression_id: ExpressionId) { - let expression_id = expression_id.as_u32(); - self.info.num_expressions = std::cmp::max(self.info.num_expressions, expression_id + 1); - } - - fn update_from_expression_operand(&mut self, operand: Operand) { - match operand { - Operand::Counter(id) => self.update_num_counters(id), - Operand::Expression(id) => self.update_num_expressions(id), - Operand::Zero => {} - } - } - - fn visit_body(&mut self, body: &Body<'_>) { - for bb_data in body.basic_blocks.iter() { - for statement in bb_data.statements.iter() { - if let StatementKind::Coverage(box ref coverage) = statement.kind { - if is_inlined(body, statement) { - continue; - } - self.visit_coverage(coverage); - } - } - } - } - - fn visit_coverage(&mut self, coverage: &Coverage) { - if self.add_missing_operands { - match coverage.kind { - CoverageKind::Expression { lhs, rhs, .. } => { - self.update_from_expression_operand(lhs); - self.update_from_expression_operand(rhs); - } - _ => {} - } - } else { - match coverage.kind { - CoverageKind::Counter { id, .. } => self.update_num_counters(id), - CoverageKind::Expression { id, .. } => self.update_num_expressions(id), - _ => {} - } - } - } -} - +/// are still included in the total `num_counters` or `num_expressions`.) fn coverageinfo<'tcx>(tcx: TyCtxt<'tcx>, instance_def: ty::InstanceDef<'tcx>) -> CoverageInfo { let mir_body = tcx.instance_mir(instance_def); - let mut coverage_visitor = CoverageVisitor { - info: CoverageInfo { num_counters: 0, num_expressions: 0 }, - add_missing_operands: false, - }; - - coverage_visitor.visit_body(mir_body); - - coverage_visitor.add_missing_operands = true; - coverage_visitor.visit_body(mir_body); - - coverage_visitor.info + if let Some(info) = &mir_body.coverage_info { + CoverageInfo { + num_counters: info.num_counters, + num_expressions: info.expressions.len() as u32, + } + } else { + CoverageInfo { num_counters: 0, num_expressions: 0 } + } } fn covered_code_regions(tcx: TyCtxt<'_>, def_id: DefId) -> Vec<&CodeRegion> { let body = mir_body(tcx, def_id); - body.basic_blocks - .iter() - .flat_map(|data| { - data.statements.iter().filter_map(|statement| match statement.kind { - StatementKind::Coverage(box ref coverage) => { - if is_inlined(body, statement) { - None - } else { - coverage.code_region.as_ref() // may be None - } - } - _ => None, - }) - }) - .collect() -} - -fn is_inlined(body: &Body<'_>, statement: &Statement<'_>) -> bool { - let scope_data = &body.source_scopes[statement.source_info.scope]; - scope_data.inlined.is_some() || scope_data.inlined_parent_scope.is_some() + let Some(info) = body.coverage_info.as_ref() else { return vec![] }; + info.regions.iter().map(|region| ®ion.code_region).collect() } /// This function ensures we obtain the correct MIR for the given item irrespective of diff --git a/compiler/rustc_mir_transform/src/simplify.rs b/compiler/rustc_mir_transform/src/simplify.rs index b7a51cfd61966..b647c4658ddba 100644 --- a/compiler/rustc_mir_transform/src/simplify.rs +++ b/compiler/rustc_mir_transform/src/simplify.rs @@ -28,9 +28,8 @@ //! return. use crate::MirPass; -use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; +use rustc_data_structures::fx::FxIndexSet; use rustc_index::{Idx, IndexSlice, IndexVec}; -use rustc_middle::mir::coverage::*; use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor}; use rustc_middle::mir::*; use rustc_middle::ty::TyCtxt; @@ -336,7 +335,7 @@ pub fn remove_duplicate_unreachable_blocks<'tcx>(tcx: TyCtxt<'tcx>, body: &mut B } } -pub fn remove_dead_blocks<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { +pub fn remove_dead_blocks<'tcx>(_tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { let reachable = traversal::reachable_as_bitset(body); let num_blocks = body.basic_blocks.len(); if num_blocks == reachable.count() { @@ -344,7 +343,6 @@ pub fn remove_dead_blocks<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { } let basic_blocks = body.basic_blocks.as_mut(); - let source_scopes = &body.source_scopes; let mut replacements: Vec<_> = (0..num_blocks).map(BasicBlock::new).collect(); let mut used_blocks = 0; for alive_index in reachable.iter() { @@ -358,10 +356,6 @@ pub fn remove_dead_blocks<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { used_blocks += 1; } - if tcx.sess.instrument_coverage() { - save_unreachable_coverage(basic_blocks, source_scopes, used_blocks); - } - basic_blocks.raw.truncate(used_blocks); for block in basic_blocks { @@ -371,91 +365,6 @@ pub fn remove_dead_blocks<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { } } -/// Some MIR transforms can determine at compile time that a sequences of -/// statements will never be executed, so they can be dropped from the MIR. -/// For example, an `if` or `else` block that is guaranteed to never be executed -/// because its condition can be evaluated at compile time, such as by const -/// evaluation: `if false { ... }`. -/// -/// Those statements are bypassed by redirecting paths in the CFG around the -/// `dead blocks`; but with `-C instrument-coverage`, the dead blocks usually -/// include `Coverage` statements representing the Rust source code regions to -/// be counted at runtime. Without these `Coverage` statements, the regions are -/// lost, and the Rust source code will show no coverage information. -/// -/// What we want to show in a coverage report is the dead code with coverage -/// counts of `0`. To do this, we need to save the code regions, by injecting -/// `Unreachable` coverage statements. These are non-executable statements whose -/// code regions are still recorded in the coverage map, representing regions -/// with `0` executions. -/// -/// If there are no live `Counter` `Coverage` statements remaining, we remove -/// `Coverage` statements along with the dead blocks. Since at least one -/// counter per function is required by LLVM (and necessary, to add the -/// `function_hash` to the counter's call to the LLVM intrinsic -/// `instrprof.increment()`). -/// -/// The `generator::StateTransform` MIR pass and MIR inlining can create -/// atypical conditions, where all live `Counter`s are dropped from the MIR. -/// -/// With MIR inlining we can have coverage counters belonging to different -/// instances in a single body, so the strategy described above is applied to -/// coverage counters from each instance individually. -fn save_unreachable_coverage( - basic_blocks: &mut IndexSlice>, - source_scopes: &IndexSlice>, - first_dead_block: usize, -) { - // Identify instances that still have some live coverage counters left. - let mut live = FxHashSet::default(); - for basic_block in &basic_blocks.raw[0..first_dead_block] { - for statement in &basic_block.statements { - let StatementKind::Coverage(coverage) = &statement.kind else { continue }; - let CoverageKind::Counter { .. } = coverage.kind else { continue }; - let instance = statement.source_info.scope.inlined_instance(source_scopes); - live.insert(instance); - } - } - - for block in &mut basic_blocks.raw[..first_dead_block] { - for statement in &mut block.statements { - let StatementKind::Coverage(_) = &statement.kind else { continue }; - let instance = statement.source_info.scope.inlined_instance(source_scopes); - if !live.contains(&instance) { - statement.make_nop(); - } - } - } - - if live.is_empty() { - return; - } - - // Retain coverage for instances that still have some live counters left. - let mut retained_coverage = Vec::new(); - for dead_block in &basic_blocks.raw[first_dead_block..] { - for statement in &dead_block.statements { - let StatementKind::Coverage(coverage) = &statement.kind else { continue }; - let Some(code_region) = &coverage.code_region else { continue }; - let instance = statement.source_info.scope.inlined_instance(source_scopes); - if live.contains(&instance) { - retained_coverage.push((statement.source_info, code_region.clone())); - } - } - } - - let start_block = &mut basic_blocks[START_BLOCK]; - start_block.statements.extend(retained_coverage.into_iter().map( - |(source_info, code_region)| Statement { - source_info, - kind: StatementKind::Coverage(Box::new(Coverage { - kind: CoverageKind::Unreachable, - code_region: Some(code_region), - })), - }, - )); -} - pub enum SimplifyLocals { BeforeConstProp, Final, diff --git a/compiler/rustc_smir/src/rustc_smir/mod.rs b/compiler/rustc_smir/src/rustc_smir/mod.rs index aea59c31379e7..368608b82a09d 100644 --- a/compiler/rustc_smir/src/rustc_smir/mod.rs +++ b/compiler/rustc_smir/src/rustc_smir/mod.rs @@ -15,7 +15,6 @@ use crate::stable_mir::ty::{ }; use crate::stable_mir::{self, Context}; use rustc_hir as hir; -use rustc_middle::mir::coverage::CodeRegion; use rustc_middle::mir::interpret::alloc_range; use rustc_middle::mir::{self, ConstantKind}; use rustc_middle::ty::{self, Ty, TyCtxt, Variance}; @@ -175,10 +174,7 @@ impl<'tcx> Stable<'tcx> for mir::Statement<'tcx> { variance: variance.stable(tables), } } - Coverage(coverage) => stable_mir::mir::Statement::Coverage(stable_mir::mir::Coverage { - kind: coverage.kind.stable(tables), - code_region: coverage.code_region.as_ref().map(|reg| reg.stable(tables)), - }), + Coverage(coverage) => stable_mir::mir::Statement::Coverage(opaque(coverage)), Intrinsic(intrinstic) => { stable_mir::mir::Statement::Intrinsic(intrinstic.stable(tables)) } @@ -479,30 +475,6 @@ impl<'tcx> Stable<'tcx> for mir::Place<'tcx> { } } -impl<'tcx> Stable<'tcx> for mir::coverage::CoverageKind { - type T = stable_mir::mir::CoverageKind; - fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { - use rustc_middle::mir::coverage::CoverageKind; - match self { - CoverageKind::Counter { function_source_hash, id } => { - stable_mir::mir::CoverageKind::Counter { - function_source_hash: *function_source_hash as usize, - id: opaque(id), - } - } - CoverageKind::Expression { id, lhs, op, rhs } => { - stable_mir::mir::CoverageKind::Expression { - id: opaque(id), - lhs: opaque(lhs), - op: op.stable(tables), - rhs: opaque(rhs), - } - } - CoverageKind::Unreachable => stable_mir::mir::CoverageKind::Unreachable, - } - } -} - impl<'tcx> Stable<'tcx> for mir::UserTypeProjection { type T = stable_mir::mir::UserTypeProjection; @@ -511,18 +483,6 @@ impl<'tcx> Stable<'tcx> for mir::UserTypeProjection { } } -impl<'tcx> Stable<'tcx> for mir::coverage::Op { - type T = stable_mir::mir::Op; - - fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { - use rustc_middle::mir::coverage::Op::*; - match self { - Subtract => stable_mir::mir::Op::Subtract, - Add => stable_mir::mir::Op::Add, - } - } -} - impl<'tcx> Stable<'tcx> for mir::Local { type T = stable_mir::mir::Local; fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { @@ -569,20 +529,6 @@ impl<'tcx> Stable<'tcx> for ty::UserTypeAnnotationIndex { } } -impl<'tcx> Stable<'tcx> for CodeRegion { - type T = stable_mir::mir::CodeRegion; - - fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { - stable_mir::mir::CodeRegion { - file_name: self.file_name.as_str().to_string(), - start_line: self.start_line as usize, - start_col: self.start_col as usize, - end_line: self.end_line as usize, - end_col: self.end_col as usize, - } - } -} - impl<'tcx> Stable<'tcx> for mir::UnwindAction { type T = stable_mir::mir::UnwindAction; fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { diff --git a/compiler/rustc_smir/src/stable_mir/mir/body.rs b/compiler/rustc_smir/src/stable_mir/mir/body.rs index c16bd6cbd70e2..a2ab51ff8467c 100644 --- a/compiler/rustc_smir/src/stable_mir/mir/body.rs +++ b/compiler/rustc_smir/src/stable_mir/mir/body.rs @@ -135,9 +135,10 @@ pub enum AsyncGeneratorKind { } pub(crate) type LocalDefId = Opaque; -pub(crate) type CounterValueReference = Opaque; -pub(crate) type InjectedExpressionId = Opaque; -pub(crate) type ExpressionOperandId = Opaque; +/// [`rustc_middle::mir::Coverage`] is heavily tied to internal details of the +/// coverage implementation that are likely to change, and are unlikely to be +/// useful to third-party tools for the foreseeable future. +pub(crate) type Coverage = Opaque; /// The FakeReadCause describes the type of pattern why a FakeRead statement exists. #[derive(Clone, Debug)] @@ -166,42 +167,6 @@ pub enum Variance { Bivariant, } -#[derive(Clone, Debug)] -pub enum Op { - Subtract, - Add, -} - -#[derive(Clone, Debug)] -pub enum CoverageKind { - Counter { - function_source_hash: usize, - id: CounterValueReference, - }, - Expression { - id: InjectedExpressionId, - lhs: ExpressionOperandId, - op: Op, - rhs: ExpressionOperandId, - }, - Unreachable, -} - -#[derive(Clone, Debug)] -pub struct CodeRegion { - pub file_name: String, - pub start_line: usize, - pub start_col: usize, - pub end_line: usize, - pub end_col: usize, -} - -#[derive(Clone, Debug)] -pub struct Coverage { - pub kind: CoverageKind, - pub code_region: Option, -} - #[derive(Clone, Debug)] pub struct CopyNonOverlapping { pub src: Operand, diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs index b366619285338..a24a6a4636d48 100644 --- a/src/bootstrap/builder.rs +++ b/src/bootstrap/builder.rs @@ -703,7 +703,8 @@ impl<'a> Builder<'a> { llvm::Lld, llvm::CrtBeginEnd, tool::RustdocGUITest, - tool::OptimizedDist + tool::OptimizedDist, + tool::CoverageDump, ), Kind::Check | Kind::Clippy | Kind::Fix => describe!( check::Std, @@ -725,6 +726,7 @@ impl<'a> Builder<'a> { test::Tidy, test::Ui, test::RunPassValgrind, + test::CoverageMap, test::RunCoverage, test::MirOpt, test::Codegen, diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs index db3b7ffbea4e5..4e944334485fb 100644 --- a/src/bootstrap/test.rs +++ b/src/bootstrap/test.rs @@ -1335,6 +1335,12 @@ host_test!(RunMakeFullDeps { default_test!(Assembly { path: "tests/assembly", mode: "assembly", suite: "assembly" }); +default_test!(CoverageMap { + path: "tests/coverage-map", + mode: "coverage-map", + suite: "coverage-map" +}); + host_test!(RunCoverage { path: "tests/run-coverage", mode: "run-coverage", suite: "run-coverage" }); host_test!(RunCoverageRustdoc { path: "tests/run-coverage-rustdoc", @@ -1540,6 +1546,14 @@ note: if you're sure you want to do this, please open an issue as to why. In the .arg(builder.ensure(tool::JsonDocLint { compiler: json_compiler, target })); } + if mode == "coverage-map" { + let coverage_dump = builder.ensure(tool::CoverageDump { + compiler: compiler.with_stage(0), + target: compiler.host, + }); + cmd.arg("--coverage-dump-path").arg(coverage_dump); + } + if mode == "run-make" || mode == "run-coverage" { let rust_demangler = builder .ensure(tool::RustDemangler { diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs index 07ff3da6b4aa5..f094dd9d7c90b 100644 --- a/src/bootstrap/tool.rs +++ b/src/bootstrap/tool.rs @@ -306,6 +306,7 @@ bootstrap_tool!( GenerateWindowsSys, "src/tools/generate-windows-sys", "generate-windows-sys"; RustdocGUITest, "src/tools/rustdoc-gui-test", "rustdoc-gui-test", is_unstable_tool = true, allow_features = "test"; OptimizedDist, "src/tools/opt-dist", "opt-dist"; + CoverageDump, "src/tools/coverage-dump", "coverage-dump"; ); #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)] diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index 7c17e92d0dfe2..b91d5a958bb62 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -66,6 +66,7 @@ string_enum! { JsDocTest => "js-doc-test", MirOpt => "mir-opt", Assembly => "assembly", + CoverageMap => "coverage-map", RunCoverage => "run-coverage", } } @@ -161,6 +162,9 @@ pub struct Config { /// The rust-demangler executable. pub rust_demangler_path: Option, + /// The coverage-dump executable. + pub coverage_dump_path: Option, + /// The Python executable to use for LLDB and htmldocck. pub python: String, @@ -639,6 +643,7 @@ pub const UI_EXTENSIONS: &[&str] = &[ UI_STDERR_32, UI_STDERR_16, UI_COVERAGE, + UI_COVERAGE_MAP, ]; pub const UI_STDERR: &str = "stderr"; pub const UI_STDOUT: &str = "stdout"; @@ -649,6 +654,7 @@ pub const UI_STDERR_64: &str = "64bit.stderr"; pub const UI_STDERR_32: &str = "32bit.stderr"; pub const UI_STDERR_16: &str = "16bit.stderr"; pub const UI_COVERAGE: &str = "coverage"; +pub const UI_COVERAGE_MAP: &str = "cov-map"; /// Absolute path to the directory where all output for all tests in the given /// `relative_dir` group should reside. Example: diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs index 1a765477fe501..619ff9b322114 100644 --- a/src/tools/compiletest/src/lib.rs +++ b/src/tools/compiletest/src/lib.rs @@ -48,6 +48,7 @@ pub fn parse_config(args: Vec) -> Config { .reqopt("", "rustc-path", "path to rustc 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") + .optopt("", "coverage-dump-path", "path to coverage-dump to use in tests", "PATH") .reqopt("", "python", "path to python to use for doc tests", "PATH") .optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH") .optopt("", "jsondoclint-path", "path to jsondoclint to use for doc tests", "PATH") @@ -218,6 +219,7 @@ pub fn parse_config(args: Vec) -> Config { rustc_path: opt_path(matches, "rustc-path"), rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from), rust_demangler_path: matches.opt_str("rust-demangler-path").map(PathBuf::from), + coverage_dump_path: matches.opt_str("coverage-dump-path").map(PathBuf::from), python: matches.opt_str("python").unwrap(), jsondocck_path: matches.opt_str("jsondocck-path"), jsondoclint_path: matches.opt_str("jsondoclint-path"), diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 4ef79af3124a8..dcdba1fa8bb4b 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -6,8 +6,8 @@ use crate::common::{Assembly, Incremental, JsDocTest, MirOpt, RunMake, RustdocJs use crate::common::{Codegen, CodegenUnits, DebugInfo, Debugger, Rustdoc}; use crate::common::{CompareMode, FailMode, PassMode}; use crate::common::{Config, TestPaths}; -use crate::common::{Pretty, RunCoverage, RunPassValgrind}; -use crate::common::{UI_COVERAGE, UI_RUN_STDERR, UI_RUN_STDOUT}; +use crate::common::{CoverageMap, Pretty, RunCoverage, RunPassValgrind}; +use crate::common::{UI_COVERAGE, UI_COVERAGE_MAP, UI_RUN_STDERR, UI_RUN_STDOUT}; use crate::compute_diff::{write_diff, write_filtered_diff}; use crate::errors::{self, Error, ErrorKind}; use crate::header::TestProps; @@ -254,6 +254,7 @@ impl<'test> TestCx<'test> { MirOpt => self.run_mir_opt_test(), Assembly => self.run_assembly_test(), JsDocTest => self.run_js_doc_test(), + CoverageMap => self.run_coverage_map_test(), RunCoverage => self.run_coverage_test(), } } @@ -467,6 +468,46 @@ impl<'test> TestCx<'test> { } } + fn run_coverage_map_test(&self) { + let Some(coverage_dump_path) = &self.config.coverage_dump_path else { + self.fatal("missing --coverage-dump"); + }; + + let proc_res = self.compile_test_and_save_ir(); + if !proc_res.status.success() { + self.fatal_proc_rec("compilation failed!", &proc_res); + } + drop(proc_res); + + let llvm_ir_path = self.output_base_name().with_extension("ll"); + + let mut dump_command = Command::new(coverage_dump_path); + dump_command.arg(llvm_ir_path); + let proc_res = self.run_command_to_procres(&mut dump_command); + if !proc_res.status.success() { + self.fatal_proc_rec("coverage-dump failed!", &proc_res); + } + + let kind = UI_COVERAGE_MAP; + + let expected_coverage_dump = self.load_expected_output(kind); + let actual_coverage_dump = self.normalize_output(&proc_res.stdout, &[]); + + let coverage_dump_errors = self.compare_output( + kind, + &actual_coverage_dump, + &expected_coverage_dump, + self.props.compare_output_lines_by_subset, + ); + + if coverage_dump_errors > 0 { + self.fatal_proc_rec( + &format!("{coverage_dump_errors} errors occurred comparing coverage output."), + &proc_res, + ); + } + } + fn run_coverage_test(&self) { let should_run = self.run_if_enabled(); let proc_res = self.compile_test(should_run, Emit::None); @@ -650,6 +691,10 @@ impl<'test> TestCx<'test> { let mut cmd = Command::new(tool_path); configure_cmd_fn(&mut cmd); + self.run_command_to_procres(&mut cmd) + } + + fn run_command_to_procres(&self, cmd: &mut Command) -> ProcRes { let output = cmd.output().unwrap_or_else(|_| panic!("failed to exec `{cmd:?}`")); let proc_res = ProcRes { @@ -2399,6 +2444,12 @@ impl<'test> TestCx<'test> { rustc.arg(dir_opt); } + CoverageMap => { + rustc.arg("-Cinstrument-coverage"); + // These tests only compile to MIR, so they don't need the + // profiler runtime to be present. + rustc.arg("-Zno-profiler-runtime"); + } RunCoverage => { rustc.arg("-Cinstrument-coverage"); } diff --git a/src/tools/coverage-dump/Cargo.toml b/src/tools/coverage-dump/Cargo.toml new file mode 100644 index 0000000000000..7f14286b5d0c4 --- /dev/null +++ b/src/tools/coverage-dump/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "coverage-dump" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.71" +leb128 = "0.2.5" +md5 = { package = "md-5" , version = "0.10.5" } +miniz_oxide = "0.7.1" +regex = "1.8.4" +rustc-demangle = "0.1.23" diff --git a/src/tools/coverage-dump/README.md b/src/tools/coverage-dump/README.md new file mode 100644 index 0000000000000..e2625d5adf27e --- /dev/null +++ b/src/tools/coverage-dump/README.md @@ -0,0 +1,8 @@ +This tool extracts coverage mapping information from an LLVM IR assembly file +(`.ll`), and prints it in a more human-readable form that can be used for +snapshot tests. + +The output format is mostly arbitrary, so it's OK to change the output as long +as any affected tests are also re-blessed. However, the output should be +consistent across different executions on different platforms, so avoid +printing any information that is platform-specific or non-deterministic. diff --git a/src/tools/coverage-dump/src/covfun.rs b/src/tools/coverage-dump/src/covfun.rs new file mode 100644 index 0000000000000..d07ba3e82f051 --- /dev/null +++ b/src/tools/coverage-dump/src/covfun.rs @@ -0,0 +1,293 @@ +use crate::parser::{unescape_llvm_string_contents, Parser}; +use anyhow::{anyhow, Context}; +use regex::Regex; +use std::collections::HashMap; +use std::fmt::{self, Debug, Write as _}; +use std::sync::OnceLock; + +pub(crate) fn dump_covfun_mappings( + llvm_ir: &str, + function_names: &HashMap, +) -> anyhow::Result<()> { + // Extract function coverage entries from the LLVM IR assembly, and associate + // each entry with its (demangled) name. + let mut covfun_entries = llvm_ir + .lines() + .filter_map(covfun_line_data) + .map(|line_data| (function_names.get(&line_data.name_hash).map(String::as_str), line_data)) + .collect::>(); + covfun_entries.sort_by(|a, b| { + // Sort entries primarily by name, to help make the order consistent + // across platforms and relatively insensitive to changes. + // (Sadly we can't use `sort_by_key` because we would need to return references.) + Ord::cmp(&a.0, &b.0) + .then_with(|| Ord::cmp(&a.1.is_used, &b.1.is_used)) + .then_with(|| Ord::cmp(a.1.payload.as_slice(), b.1.payload.as_slice())) + }); + + for (name, line_data) in &covfun_entries { + let name = name.unwrap_or("(unknown)"); + let unused = if line_data.is_used { "" } else { " (unused)" }; + println!("Function name: {name}{unused}"); + + let payload: &[u8] = &line_data.payload; + println!("Raw bytes ({len}): 0x{payload:02x?}", len = payload.len()); + + let mut parser = Parser::new(payload); + + let num_files = parser.read_uleb128_u32()?; + println!("Number of files: {num_files}"); + + for i in 0..num_files { + let global_file_id = parser.read_uleb128_u32()?; + println!("- file {i} => global file {global_file_id}"); + } + + let num_expressions = parser.read_uleb128_u32()?; + println!("Number of expressions: {num_expressions}"); + + let mut expression_resolver = ExpressionResolver::new(); + for i in 0..num_expressions { + let lhs = parser.read_simple_operand()?; + let rhs = parser.read_simple_operand()?; + println!("- expression {i} operands: lhs = {lhs:?}, rhs = {rhs:?}"); + expression_resolver.push_operands(lhs, rhs); + } + + for i in 0..num_files { + let num_mappings = parser.read_uleb128_u32()?; + println!("Number of file {i} mappings: {num_mappings}"); + + for _ in 0..num_mappings { + let (kind, region) = parser.read_mapping_kind_and_region()?; + println!("- {kind:?} at {region:?}"); + + match kind { + // Also print expression mappings in resolved form. + MappingKind::Code(operand @ Operand::Expression { .. }) + | MappingKind::Gap(operand @ Operand::Expression { .. }) => { + println!(" = {}", expression_resolver.format_operand(operand)); + } + // If the mapping is a branch region, print both of its arms + // in resolved form (even if they aren't expressions). + MappingKind::Branch { r#true, r#false } => { + println!(" true = {}", expression_resolver.format_operand(r#true)); + println!(" false = {}", expression_resolver.format_operand(r#false)); + } + _ => (), + } + } + } + + parser.ensure_empty()?; + println!(); + } + Ok(()) +} + +struct CovfunLineData { + name_hash: u64, + is_used: bool, + payload: Vec, +} + +/// Checks a line of LLVM IR assembly to see if it contains an `__llvm_covfun` +/// entry, and if so extracts relevant data in a `CovfunLineData`. +fn covfun_line_data(line: &str) -> Option { + let re = { + // We cheat a little bit and match variable names `@__covrec_[HASH]u` + // rather than the section name, because the section name is harder to + // extract and differs across Linux/Windows/macOS. We also extract the + // symbol name hash from the variable name rather than the data, since + // it's easier and both should match. + static RE: OnceLock = OnceLock::new(); + RE.get_or_init(|| { + Regex::new( + r#"^@__covrec_(?[0-9A-Z]+)(?u)? = .*\[[0-9]+ x i8\] c"(?[^"]*)".*$"#, + ) + .unwrap() + }) + }; + + let captures = re.captures(line)?; + let name_hash = u64::from_str_radix(&captures["name_hash"], 16).unwrap(); + let is_used = captures.name("is_used").is_some(); + let payload = unescape_llvm_string_contents(&captures["payload"]); + + Some(CovfunLineData { name_hash, is_used, payload }) +} + +// Extra parser methods only needed when parsing `covfun` payloads. +impl<'a> Parser<'a> { + fn read_simple_operand(&mut self) -> anyhow::Result { + let raw_operand = self.read_uleb128_u32()?; + Operand::decode(raw_operand).context("decoding operand") + } + + fn read_mapping_kind_and_region(&mut self) -> anyhow::Result<(MappingKind, MappingRegion)> { + let mut kind = self.read_raw_mapping_kind()?; + let mut region = self.read_raw_mapping_region()?; + + const HIGH_BIT: u32 = 1u32 << 31; + if region.end_column & HIGH_BIT != 0 { + region.end_column &= !HIGH_BIT; + kind = match kind { + MappingKind::Code(operand) => MappingKind::Gap(operand), + // LLVM's coverage mapping reader will actually handle this + // case without complaint, but the result is almost certainly + // a meaningless implementation artifact. + _ => return Err(anyhow!("unexpected base kind for gap region: {kind:?}")), + } + } + + Ok((kind, region)) + } + + fn read_raw_mapping_kind(&mut self) -> anyhow::Result { + let raw_mapping_kind = self.read_uleb128_u32()?; + if let Some(operand) = Operand::decode(raw_mapping_kind) { + return Ok(MappingKind::Code(operand)); + } + + assert_eq!(raw_mapping_kind & 0b11, 0); + assert_ne!(raw_mapping_kind, 0); + + let (high, is_expansion) = (raw_mapping_kind >> 3, raw_mapping_kind & 0b100 != 0); + if is_expansion { + Ok(MappingKind::Expansion(high)) + } else { + match high { + 0 => unreachable!("zero kind should have already been handled as a code mapping"), + 2 => Ok(MappingKind::Skip), + 4 => { + let r#true = self.read_simple_operand()?; + let r#false = self.read_simple_operand()?; + Ok(MappingKind::Branch { r#true, r#false }) + } + _ => Err(anyhow!("unknown mapping kind: {raw_mapping_kind:#x}")), + } + } + } + + fn read_raw_mapping_region(&mut self) -> anyhow::Result { + let start_line_offset = self.read_uleb128_u32()?; + let start_column = self.read_uleb128_u32()?; + let end_line_offset = self.read_uleb128_u32()?; + let end_column = self.read_uleb128_u32()?; + Ok(MappingRegion { start_line_offset, start_column, end_line_offset, end_column }) + } +} + +// Represents an expression operand (lhs/rhs), branch region operand (true/false), +// or the value used by a code region or gap region. +#[derive(Clone, Copy, Debug)] +pub(crate) enum Operand { + Zero, + Counter(u32), + Expression(u32, Op), +} + +/// Operator (addition or subtraction) used by an expression. +#[derive(Clone, Copy, Debug)] +pub(crate) enum Op { + Sub, + Add, +} + +impl Operand { + pub(crate) fn decode(input: u32) -> Option { + let (high, tag) = (input >> 2, input & 0b11); + match tag { + 0b00 if high == 0 => Some(Self::Zero), + 0b01 => Some(Self::Counter(high)), + 0b10 => Some(Self::Expression(high, Op::Sub)), + 0b11 => Some(Self::Expression(high, Op::Add)), + // When reading expression or branch operands, the LLVM coverage + // mapping reader will always interpret a `0b00` tag as a zero + // operand, even when the high bits are non-zero. + // We treat that case as failure instead, so that this code can be + // shared by the full mapping-kind reader as well. + _ => None, + } + } +} + +#[derive(Debug)] +enum MappingKind { + Code(Operand), + Gap(Operand), + Expansion(u32), + Skip, + // Using raw identifiers here makes the dump output a little bit nicer + // (via the derived Debug), at the expense of making this tool's source + // code a little bit uglier. + Branch { r#true: Operand, r#false: Operand }, +} + +struct MappingRegion { + /// Offset of this region's start line, relative to the *start line* of + /// the *previous mapping* (or 0). Line numbers are 1-based. + start_line_offset: u32, + /// This region's start column, absolute and 1-based. + start_column: u32, + /// Offset of this region's end line, relative to the *this mapping's* + /// start line. Line numbers are 1-based. + end_line_offset: u32, + /// This region's end column, absolute, 1-based, and exclusive. + /// + /// If the highest bit is set, that bit is cleared and the associated + /// mapping becomes a gap region mapping. + end_column: u32, +} + +impl Debug for MappingRegion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "(prev + {}, {}) to (start + {}, {})", + self.start_line_offset, self.start_column, self.end_line_offset, self.end_column + ) + } +} + +/// Helper type that prints expressions in a "resolved" form, so that +/// developers reading the dump don't need to resolve expressions by hand. +struct ExpressionResolver { + operands: Vec<(Operand, Operand)>, +} + +impl ExpressionResolver { + fn new() -> Self { + Self { operands: Vec::new() } + } + + fn push_operands(&mut self, lhs: Operand, rhs: Operand) { + self.operands.push((lhs, rhs)); + } + + fn format_operand(&self, operand: Operand) -> String { + let mut output = String::new(); + self.write_operand(&mut output, operand); + output + } + + fn write_operand(&self, output: &mut String, operand: Operand) { + match operand { + Operand::Zero => output.push_str("Zero"), + Operand::Counter(id) => write!(output, "c{id}").unwrap(), + Operand::Expression(id, op) => { + let (lhs, rhs) = self.operands[id as usize]; + let op = match op { + Op::Sub => "-", + Op::Add => "+", + }; + + output.push('('); + self.write_operand(output, lhs); + write!(output, " {op} ").unwrap(); + self.write_operand(output, rhs); + output.push(')'); + } + } + } +} diff --git a/src/tools/coverage-dump/src/main.rs b/src/tools/coverage-dump/src/main.rs new file mode 100644 index 0000000000000..93fed1799e041 --- /dev/null +++ b/src/tools/coverage-dump/src/main.rs @@ -0,0 +1,17 @@ +mod covfun; +mod parser; +mod prf_names; + +fn main() -> anyhow::Result<()> { + use anyhow::Context as _; + + let args = std::env::args().collect::>(); + + let llvm_ir_path = args.get(1).context("LLVM IR file not specified")?; + let llvm_ir = std::fs::read_to_string(llvm_ir_path).context("couldn't read LLVM IR file")?; + + let function_names = crate::prf_names::make_function_names_table(&llvm_ir)?; + crate::covfun::dump_covfun_mappings(&llvm_ir, &function_names)?; + + Ok(()) +} diff --git a/src/tools/coverage-dump/src/parser.rs b/src/tools/coverage-dump/src/parser.rs new file mode 100644 index 0000000000000..eefac1a4f94c1 --- /dev/null +++ b/src/tools/coverage-dump/src/parser.rs @@ -0,0 +1,80 @@ +#[cfg(test)] +mod tests; + +use anyhow::ensure; +use regex::bytes; +use std::sync::OnceLock; + +/// Given the raw contents of a string literal in LLVM IR assembly, decodes any +/// backslash escapes and returns a vector containing the resulting byte string. +pub(crate) fn unescape_llvm_string_contents(contents: &str) -> Vec { + let escape_re = { + static RE: OnceLock = OnceLock::new(); + // LLVM IR supports two string escapes: `\\` and `\xx`. + RE.get_or_init(|| bytes::Regex::new(r"\\\\|\\([0-9A-Za-z]{2})").unwrap()) + }; + + fn u8_from_hex_digits(digits: &[u8]) -> u8 { + // We know that the input contains exactly 2 hex digits, so these calls + // should never fail. + assert_eq!(digits.len(), 2); + let digits = std::str::from_utf8(digits).unwrap(); + u8::from_str_radix(digits, 16).unwrap() + } + + escape_re + .replace_all(contents.as_bytes(), |captures: &bytes::Captures<'_>| { + let byte = match captures.get(1) { + None => b'\\', + Some(hex_digits) => u8_from_hex_digits(hex_digits.as_bytes()), + }; + [byte] + }) + .into_owned() +} + +pub(crate) struct Parser<'a> { + rest: &'a [u8], +} + +impl<'a> Parser<'a> { + pub(crate) fn new(input: &'a [u8]) -> Self { + Self { rest: input } + } + + pub(crate) fn ensure_empty(self) -> anyhow::Result<()> { + ensure!(self.rest.is_empty(), "unparsed bytes: 0x{:02x?}", self.rest); + Ok(()) + } + + pub(crate) fn read_n_bytes(&mut self, n: usize) -> anyhow::Result<&'a [u8]> { + ensure!(n <= self.rest.len()); + + let (bytes, rest) = self.rest.split_at(n); + self.rest = rest; + Ok(bytes) + } + + pub(crate) fn read_uleb128_u32(&mut self) -> anyhow::Result { + self.read_uleb128_u64_and_convert() + } + + pub(crate) fn read_uleb128_usize(&mut self) -> anyhow::Result { + self.read_uleb128_u64_and_convert() + } + + fn read_uleb128_u64_and_convert(&mut self) -> anyhow::Result + where + T: TryFrom + 'static, + T::Error: std::error::Error + Send + Sync, + { + let mut temp_rest = self.rest; + let raw_value: u64 = leb128::read::unsigned(&mut temp_rest)?; + let converted_value = T::try_from(raw_value)?; + + // Only update `self.rest` if the above steps succeeded, so that the + // parser position can be used for error reporting if desired. + self.rest = temp_rest; + Ok(converted_value) + } +} diff --git a/src/tools/coverage-dump/src/parser/tests.rs b/src/tools/coverage-dump/src/parser/tests.rs new file mode 100644 index 0000000000000..a673606b9c4c8 --- /dev/null +++ b/src/tools/coverage-dump/src/parser/tests.rs @@ -0,0 +1,38 @@ +use super::unescape_llvm_string_contents; + +// WARNING: These tests don't necessarily run in CI, and were mainly used to +// help track down problems when originally developing this tool. +// (The tool is still tested indirectly by snapshot tests that rely on it.) + +// Tests for `unescape_llvm_string_contents`: + +#[test] +fn unescape_empty() { + assert_eq!(unescape_llvm_string_contents(""), &[]); +} + +#[test] +fn unescape_noop() { + let input = "The quick brown fox jumps over the lazy dog."; + assert_eq!(unescape_llvm_string_contents(input), input.as_bytes()); +} + +#[test] +fn unescape_backslash() { + let input = r"\\Hello\\world\\"; + assert_eq!(unescape_llvm_string_contents(input), r"\Hello\world\".as_bytes()); +} + +#[test] +fn unescape_hex() { + let input = r"\01\02\03\04\0a\0b\0C\0D\fd\fE\FF"; + let expected: &[u8] = &[0x01, 0x02, 0x03, 0x04, 0x0a, 0x0b, 0x0c, 0x0d, 0xfd, 0xfe, 0xff]; + assert_eq!(unescape_llvm_string_contents(input), expected); +} + +#[test] +fn unescape_mixed() { + let input = r"\\01.\5c\5c"; + let expected: &[u8] = br"\01.\\"; + assert_eq!(unescape_llvm_string_contents(input), expected); +} diff --git a/src/tools/coverage-dump/src/prf_names.rs b/src/tools/coverage-dump/src/prf_names.rs new file mode 100644 index 0000000000000..2c0c942e630ce --- /dev/null +++ b/src/tools/coverage-dump/src/prf_names.rs @@ -0,0 +1,85 @@ +use crate::parser::{unescape_llvm_string_contents, Parser}; +use anyhow::{anyhow, ensure}; +use regex::Regex; +use std::collections::HashMap; +use std::sync::OnceLock; + +/// Scans through the contents of an LLVM IR assembly file to find `__llvm_prf_names` +/// entries, decodes them, and creates a table that maps name hash values to +/// (demangled) function names. +pub(crate) fn make_function_names_table(llvm_ir: &str) -> anyhow::Result> { + fn prf_names_payload(line: &str) -> Option<&str> { + let re = { + // We cheat a little bit and match the variable name `@__llvm_prf_nm` + // rather than the section name, because the section name is harder + // to extract and differs across Linux/Windows/macOS. + static RE: OnceLock = OnceLock::new(); + RE.get_or_init(|| { + Regex::new(r#"^@__llvm_prf_nm =.*\[[0-9]+ x i8\] c"([^"]*)".*$"#).unwrap() + }) + }; + + let payload = re.captures(line)?.get(1).unwrap().as_str(); + Some(payload) + } + + /// LLVM's profiler/coverage metadata often uses an MD5 hash truncated to + /// 64 bits as a way to associate data stored in different tables/sections. + fn truncated_md5(bytes: &[u8]) -> u64 { + use md5::{Digest, Md5}; + let mut hasher = Md5::new(); + hasher.update(bytes); + let hash: [u8; 8] = hasher.finalize().as_slice()[..8].try_into().unwrap(); + u64::from_le_bytes(hash) + } + + fn demangle_if_able(symbol_name_bytes: &[u8]) -> anyhow::Result { + // In practice, raw symbol names should always be ASCII. + let symbol_name_str = std::str::from_utf8(symbol_name_bytes)?; + match rustc_demangle::try_demangle(symbol_name_str) { + Ok(d) => Ok(format!("{d:#}")), + // If demangling failed, don't treat it as an error. This lets us + // run the dump tool against non-Rust coverage maps produced by + // `clang`, for testing purposes. + Err(_) => Ok(format!("(couldn't demangle) {symbol_name_str}")), + } + } + + let mut map = HashMap::new(); + + for payload in llvm_ir.lines().filter_map(prf_names_payload).map(unescape_llvm_string_contents) + { + let mut parser = Parser::new(&payload); + let uncompressed_len = parser.read_uleb128_usize()?; + let compressed_len = parser.read_uleb128_usize()?; + + let uncompressed_bytes_vec; + let uncompressed_bytes: &[u8] = if compressed_len == 0 { + // The symbol name bytes are uncompressed, so read them directly. + parser.read_n_bytes(uncompressed_len)? + } else { + // The symbol name bytes are compressed, so read and decompress them. + let compressed_bytes = parser.read_n_bytes(compressed_len)?; + + uncompressed_bytes_vec = miniz_oxide::inflate::decompress_to_vec_zlib_with_limit( + compressed_bytes, + uncompressed_len, + ) + .map_err(|e| anyhow!("{e:?}"))?; + ensure!(uncompressed_bytes_vec.len() == uncompressed_len); + + &uncompressed_bytes_vec + }; + + // Symbol names in the payload are separated by `0x01` bytes. + for raw_name in uncompressed_bytes.split(|&b| b == 0x01) { + let hash = truncated_md5(raw_name); + let demangled = demangle_if_able(raw_name)?; + map.insert(hash, demangled); + } + + parser.ensure_empty()?; + } + + Ok(map) +} diff --git a/tests/coverage-map/if.cov-map b/tests/coverage-map/if.cov-map new file mode 100644 index 0000000000000..3cedb5ffbecb1 --- /dev/null +++ b/tests/coverage-map/if.cov-map @@ -0,0 +1,15 @@ +Function name: if::main +Raw bytes (28): 0x[01, 01, 02, 01, 05, 05, 02, 04, 01, 03, 01, 02, 0c, 05, 02, 0d, 02, 06, 02, 02, 06, 00, 07, 07, 01, 05, 01, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 2 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +- expression 1 operands: lhs = Counter(1), rhs = Expression(0, Sub) +Number of file 0 mappings: 4 +- Code(Counter(0)) at (prev + 3, 1) to (start + 2, 12) +- Code(Counter(1)) at (prev + 2, 13) to (start + 2, 6) +- Code(Expression(0, Sub)) at (prev + 2, 6) to (start + 0, 7) + = (c0 - c1) +- Code(Expression(1, Add)) at (prev + 1, 5) to (start + 1, 2) + = (c1 + (c0 - c1)) + diff --git a/tests/coverage-map/if.rs b/tests/coverage-map/if.rs new file mode 100644 index 0000000000000..ed3f69bdc98d2 --- /dev/null +++ b/tests/coverage-map/if.rs @@ -0,0 +1,9 @@ +// compile-flags: --edition=2021 + +fn main() { + let cond = std::env::args().len() == 1; + if cond { + println!("true"); + } + println!("done"); +} diff --git a/tests/coverage-map/long_and_wide.cov-map b/tests/coverage-map/long_and_wide.cov-map new file mode 100644 index 0000000000000..97aebf9b18ab0 --- /dev/null +++ b/tests/coverage-map/long_and_wide.cov-map @@ -0,0 +1,32 @@ +Function name: long_and_wide::far_function +Raw bytes (10): 0x[01, 01, 00, 01, 01, 96, 01, 01, 00, 15] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 150, 1) to (start + 0, 21) + +Function name: long_and_wide::long_function +Raw bytes (10): 0x[01, 01, 00, 01, 01, 10, 01, 84, 01, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 16, 1) to (start + 132, 2) + +Function name: long_and_wide::main +Raw bytes (9): 0x[01, 01, 00, 01, 01, 07, 01, 04, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 7, 1) to (start + 4, 2) + +Function name: long_and_wide::wide_function +Raw bytes (10): 0x[01, 01, 00, 01, 01, 0e, 01, 00, 8b, 01] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 14, 1) to (start + 0, 139) + diff --git a/tests/coverage-map/long_and_wide.rs b/tests/coverage-map/long_and_wide.rs new file mode 100644 index 0000000000000..a7cbcd4802791 --- /dev/null +++ b/tests/coverage-map/long_and_wide.rs @@ -0,0 +1,150 @@ +// compile-flags: --edition=2021 +// ignore-tidy-linelength + +// This file deliberately contains line and column numbers larger than 127, +// to verify that `coverage-dump`'s ULEB128 parser can handle them. + +fn main() { + wide_function(); + long_function(); + far_function(); +} + +#[rustfmt::skip] +fn wide_function() { /* */ (); } + +fn long_function() { + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // +} + +fn far_function() {} diff --git a/tests/coverage-map/status-quo/README.md b/tests/coverage-map/status-quo/README.md new file mode 100644 index 0000000000000..c3b9f16ac54ff --- /dev/null +++ b/tests/coverage-map/status-quo/README.md @@ -0,0 +1,9 @@ +The tests in this directory were copied from `tests/run-coverage` in order to +capture the current behavior of the instrumentor on non-trivial programs. +The actual mappings have not been closely inspected. + +## Maintenance note + +If a MIR optimization or LLVM upgrade causes these tests to fail, +it should usually be OK to just `--bless` them, +as long as the `run-coverage` test suite still works. diff --git a/tests/coverage-map/status-quo/async2.cov-map b/tests/coverage-map/status-quo/async2.cov-map new file mode 100644 index 0000000000000..5be7c32b69db9 --- /dev/null +++ b/tests/coverage-map/status-quo/async2.cov-map @@ -0,0 +1,134 @@ +Function name: async2::async_func +Raw bytes (9): 0x[01, 01, 00, 01, 01, 14, 01, 00, 17] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 20, 1) to (start + 0, 23) + +Function name: async2::async_func::{closure#0} +Raw bytes (28): 0x[01, 01, 02, 01, 05, 05, 02, 04, 01, 14, 17, 03, 09, 05, 03, 0a, 02, 06, 02, 02, 06, 00, 07, 07, 01, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 2 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +- expression 1 operands: lhs = Counter(1), rhs = Expression(0, Sub) +Number of file 0 mappings: 4 +- Code(Counter(0)) at (prev + 20, 23) to (start + 3, 9) +- Code(Counter(1)) at (prev + 3, 10) to (start + 2, 6) +- Code(Expression(0, Sub)) at (prev + 2, 6) to (start + 0, 7) + = (c0 - c1) +- Code(Expression(1, Add)) at (prev + 1, 1) to (start + 0, 2) + = (c1 + (c0 - c1)) + +Function name: async2::async_func_just_println +Raw bytes (9): 0x[01, 01, 00, 01, 01, 1f, 01, 00, 24] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 31, 1) to (start + 0, 36) + +Function name: async2::async_func_just_println::{closure#0} +Raw bytes (9): 0x[01, 01, 00, 01, 01, 1f, 24, 02, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 31, 36) to (start + 2, 2) + +Function name: async2::executor::block_on:: +Raw bytes (40): 0x[01, 01, 03, 0b, 05, 01, 05, 01, 05, 06, 01, 33, 05, 0a, 36, 02, 0d, 20, 00, 23, 0b, 00, 27, 00, 49, 02, 01, 17, 00, 1a, 05, 01, 0e, 00, 0f, 02, 02, 05, 00, 06] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 3 +- expression 0 operands: lhs = Expression(2, Add), rhs = Counter(1) +- expression 1 operands: lhs = Counter(0), rhs = Counter(1) +- expression 2 operands: lhs = Counter(0), rhs = Counter(1) +Number of file 0 mappings: 6 +- Code(Counter(0)) at (prev + 51, 5) to (start + 10, 54) +- Code(Expression(0, Sub)) at (prev + 13, 32) to (start + 0, 35) + = ((c0 + c1) - c1) +- Code(Expression(2, Add)) at (prev + 0, 39) to (start + 0, 73) + = (c0 + c1) +- Code(Expression(0, Sub)) at (prev + 1, 23) to (start + 0, 26) + = ((c0 + c1) - c1) +- Code(Counter(1)) at (prev + 1, 14) to (start + 0, 15) +- Code(Expression(0, Sub)) at (prev + 2, 5) to (start + 0, 6) + = ((c0 + c1) - c1) + +Function name: async2::executor::block_on:: +Raw bytes (40): 0x[01, 01, 03, 0b, 05, 01, 05, 01, 05, 06, 01, 33, 05, 0a, 36, 02, 0d, 20, 00, 23, 0b, 00, 27, 00, 49, 02, 01, 17, 00, 1a, 05, 01, 0e, 00, 0f, 02, 02, 05, 00, 06] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 3 +- expression 0 operands: lhs = Expression(2, Add), rhs = Counter(1) +- expression 1 operands: lhs = Counter(0), rhs = Counter(1) +- expression 2 operands: lhs = Counter(0), rhs = Counter(1) +Number of file 0 mappings: 6 +- Code(Counter(0)) at (prev + 51, 5) to (start + 10, 54) +- Code(Expression(0, Sub)) at (prev + 13, 32) to (start + 0, 35) + = ((c0 + c1) - c1) +- Code(Expression(2, Add)) at (prev + 0, 39) to (start + 0, 73) + = (c0 + c1) +- Code(Expression(0, Sub)) at (prev + 1, 23) to (start + 0, 26) + = ((c0 + c1) - c1) +- Code(Counter(1)) at (prev + 1, 14) to (start + 0, 15) +- Code(Expression(0, Sub)) at (prev + 2, 5) to (start + 0, 6) + = ((c0 + c1) - c1) + +Function name: async2::executor::block_on::VTABLE::{closure#0} +Raw bytes (9): 0x[01, 01, 00, 01, 01, 37, 11, 00, 33] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 55, 17) to (start + 0, 51) + +Function name: async2::executor::block_on::VTABLE::{closure#1} +Raw bytes (9): 0x[01, 01, 00, 01, 01, 38, 11, 00, 33] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 56, 17) to (start + 0, 51) + +Function name: async2::executor::block_on::VTABLE::{closure#2} +Raw bytes (9): 0x[01, 01, 00, 01, 01, 39, 11, 00, 33] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 57, 17) to (start + 0, 51) + +Function name: async2::executor::block_on::VTABLE::{closure#3} +Raw bytes (9): 0x[01, 01, 00, 01, 01, 3a, 11, 00, 13] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 58, 17) to (start + 0, 19) + +Function name: async2::main +Raw bytes (9): 0x[01, 01, 00, 01, 01, 23, 01, 07, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 35, 1) to (start + 7, 2) + +Function name: async2::non_async_func +Raw bytes (28): 0x[01, 01, 02, 01, 05, 05, 02, 04, 01, 09, 01, 03, 09, 05, 03, 0a, 02, 06, 02, 02, 06, 00, 07, 07, 01, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 2 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +- expression 1 operands: lhs = Counter(1), rhs = Expression(0, Sub) +Number of file 0 mappings: 4 +- Code(Counter(0)) at (prev + 9, 1) to (start + 3, 9) +- Code(Counter(1)) at (prev + 3, 10) to (start + 2, 6) +- Code(Expression(0, Sub)) at (prev + 2, 6) to (start + 0, 7) + = (c0 - c1) +- Code(Expression(1, Add)) at (prev + 1, 1) to (start + 0, 2) + = (c1 + (c0 - c1)) + diff --git a/tests/coverage-map/status-quo/async2.rs b/tests/coverage-map/status-quo/async2.rs new file mode 100644 index 0000000000000..959d48ce9db16 --- /dev/null +++ b/tests/coverage-map/status-quo/async2.rs @@ -0,0 +1,69 @@ +// compile-flags: --edition=2018 + +use core::{ + future::Future, + marker::Send, + pin::Pin, +}; + +fn non_async_func() { + println!("non_async_func was covered"); + let b = true; + if b { + println!("non_async_func println in block"); + } +} + + + + +async fn async_func() { + println!("async_func was covered"); + let b = true; + if b { + println!("async_func println in block"); + } +} + + + + +async fn async_func_just_println() { + println!("async_func_just_println was covered"); +} + +fn main() { + println!("codecovsample::main"); + + non_async_func(); + + executor::block_on(async_func()); + executor::block_on(async_func_just_println()); +} + +mod executor { + use core::{ + future::Future, + pin::Pin, + task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, + }; + + pub fn block_on(mut future: F) -> F::Output { + let mut future = unsafe { Pin::new_unchecked(&mut future) }; + use std::hint::unreachable_unchecked; + static VTABLE: RawWakerVTable = RawWakerVTable::new( + |_| unsafe { unreachable_unchecked() }, // clone + |_| unsafe { unreachable_unchecked() }, // wake + |_| unsafe { unreachable_unchecked() }, // wake_by_ref + |_| (), + ); + let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }; + let mut context = Context::from_waker(&waker); + + loop { + if let Poll::Ready(val) = future.as_mut().poll(&mut context) { + break val; + } + } + } +} diff --git a/tests/coverage-map/status-quo/conditions.cov-map b/tests/coverage-map/status-quo/conditions.cov-map new file mode 100644 index 0000000000000..7d1392a2c13fe --- /dev/null +++ b/tests/coverage-map/status-quo/conditions.cov-map @@ -0,0 +1,253 @@ +Function name: conditions::main +Raw bytes (769): 0x[01, 01, 8d, 01, 01, 05, 09, 97, 01, 2d, 31, 05, 02, b3, 04, 09, 05, 02, 0d, 39, 2d, 31, ae, 04, 0d, b3, 04, 09, 05, 02, 09, 97, 01, 2d, 31, 93, 01, 35, 09, 97, 01, 2d, 31, 35, 8e, 01, 93, 01, 35, 09, 97, 01, 2d, 31, 8b, 01, 3d, 35, 8e, 01, 93, 01, 35, 09, 97, 01, 2d, 31, 86, 01, 29, 8b, 01, 3d, 35, 8e, 01, 93, 01, 35, 09, 97, 01, 2d, 31, 82, 01, 51, 86, 01, 29, 8b, 01, 3d, 35, 8e, 01, 93, 01, 35, 09, 97, 01, 2d, 31, 45, 49, 3d, c3, 03, 45, 49, 4d, 55, 55, de, 01, 4d, 55, db, 01, 59, 55, de, 01, 4d, 55, d6, 01, 25, db, 01, 59, 55, de, 01, 4d, 55, d2, 01, 79, d6, 01, 25, db, 01, 59, 55, de, 01, 4d, 55, 5d, 61, bf, 03, 4d, 3d, c3, 03, 45, 49, b3, 03, ba, 03, 59, b7, 03, 5d, 61, bf, 03, 4d, 3d, c3, 03, 45, 49, af, 03, 65, b3, 03, ba, 03, 59, b7, 03, 5d, 61, bf, 03, 4d, 3d, c3, 03, 45, 49, 6d, 87, 04, 71, 75, 65, aa, 03, af, 03, 65, b3, 03, ba, 03, 59, b7, 03, 5d, 61, bf, 03, 4d, 3d, c3, 03, 45, 49, a7, 03, 6d, 65, aa, 03, af, 03, 65, b3, 03, ba, 03, 59, b7, 03, 5d, 61, bf, 03, 4d, 3d, c3, 03, 45, 49, a2, 03, 21, a7, 03, 6d, 65, aa, 03, af, 03, 65, b3, 03, ba, 03, 59, b7, 03, 5d, 61, bf, 03, 4d, 3d, c3, 03, 45, 49, 9e, 03, 7d, a2, 03, 21, a7, 03, 6d, 65, aa, 03, af, 03, 65, b3, 03, ba, 03, 59, b7, 03, 5d, 61, bf, 03, 4d, 3d, c3, 03, 45, 49, 71, 75, 11, 97, 04, 15, 19, 6d, 87, 04, 71, 75, 83, 04, 11, 6d, 87, 04, 71, 75, fe, 03, 1d, 83, 04, 11, 6d, 87, 04, 71, 75, fa, 03, 89, 01, fe, 03, 1d, 83, 04, 11, 6d, 87, 04, 71, 75, 15, 19, 93, 04, 9b, 04, 11, 97, 04, 15, 19, 9f, 04, aa, 04, a3, 04, a7, 04, 1d, 21, 25, 29, ae, 04, 0d, b3, 04, 09, 05, 02, 44, 01, 03, 01, 02, 0c, 05, 02, 0d, 02, 06, 02, 02, 06, 00, 07, 93, 01, 03, 09, 00, 0a, b3, 04, 00, 10, 00, 1d, 09, 01, 09, 01, 0a, ae, 04, 02, 0f, 00, 1c, 0d, 01, 0c, 00, 19, 1a, 00, 1d, 00, 2a, 41, 00, 2e, 00, 3c, 2d, 00, 3d, 02, 0a, 31, 02, 0a, 00, 0b, 97, 01, 01, 09, 01, 12, aa, 04, 03, 09, 00, 0f, 93, 01, 03, 09, 01, 0c, 35, 01, 0d, 02, 06, 8e, 01, 02, 06, 00, 07, 8b, 01, 02, 08, 00, 15, 3d, 00, 16, 02, 06, 86, 01, 02, 0f, 00, 1c, 82, 01, 01, 0c, 00, 19, 7e, 00, 1d, 00, 2a, 69, 00, 2e, 00, 3c, 45, 00, 3d, 02, 0a, 49, 02, 0a, 00, 0b, c3, 03, 01, 09, 00, 17, 29, 02, 09, 00, 0f, bf, 03, 03, 08, 00, 0c, 4d, 01, 0d, 01, 10, 55, 01, 11, 02, 0a, de, 01, 02, 0a, 00, 0b, db, 01, 02, 0c, 00, 19, 59, 00, 1a, 02, 0a, d6, 01, 03, 11, 00, 1e, d2, 01, 01, 10, 00, 1d, ce, 01, 00, 21, 00, 2e, 81, 01, 00, 32, 00, 40, 5d, 00, 41, 02, 0e, 61, 02, 0e, 00, 0f, b7, 03, 01, 0d, 00, 1b, 25, 02, 0d, 00, 13, ba, 03, 02, 06, 00, 07, af, 03, 03, 09, 01, 0c, 65, 01, 0d, 02, 06, aa, 03, 02, 06, 00, 07, 83, 04, 02, 09, 00, 0a, a7, 03, 00, 10, 00, 1d, 6d, 00, 1e, 02, 06, a2, 03, 02, 0f, 00, 1c, 9e, 03, 01, 0c, 00, 19, 9a, 03, 00, 1d, 00, 2a, 85, 01, 00, 2e, 00, 3c, 71, 00, 3d, 02, 0a, 75, 02, 0a, 00, 0b, 87, 04, 01, 09, 00, 17, 21, 02, 0d, 02, 0f, 93, 04, 05, 09, 00, 0a, 83, 04, 00, 10, 00, 1d, 11, 00, 1e, 02, 06, fe, 03, 02, 0f, 00, 1c, fa, 03, 01, 0c, 00, 19, f6, 03, 00, 1d, 00, 2a, 8d, 01, 00, 2e, 00, 3c, 15, 00, 3d, 02, 0a, 19, 02, 0a, 00, 0b, 97, 04, 01, 09, 00, 17, 1d, 02, 09, 00, 0f, 8f, 04, 02, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 141 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +- expression 1 operands: lhs = Counter(2), rhs = Expression(37, Add) +- expression 2 operands: lhs = Counter(11), rhs = Counter(12) +- expression 3 operands: lhs = Counter(1), rhs = Expression(0, Sub) +- expression 4 operands: lhs = Expression(140, Add), rhs = Counter(2) +- expression 5 operands: lhs = Counter(1), rhs = Expression(0, Sub) +- expression 6 operands: lhs = Counter(3), rhs = Counter(14) +- expression 7 operands: lhs = Counter(11), rhs = Counter(12) +- expression 8 operands: lhs = Expression(139, Sub), rhs = Counter(3) +- expression 9 operands: lhs = Expression(140, Add), rhs = Counter(2) +- expression 10 operands: lhs = Counter(1), rhs = Expression(0, Sub) +- expression 11 operands: lhs = Counter(2), rhs = Expression(37, Add) +- expression 12 operands: lhs = Counter(11), rhs = Counter(12) +- expression 13 operands: lhs = Expression(36, Add), rhs = Counter(13) +- expression 14 operands: lhs = Counter(2), rhs = Expression(37, Add) +- expression 15 operands: lhs = Counter(11), rhs = Counter(12) +- expression 16 operands: lhs = Counter(13), rhs = Expression(35, Sub) +- expression 17 operands: lhs = Expression(36, Add), rhs = Counter(13) +- expression 18 operands: lhs = Counter(2), rhs = Expression(37, Add) +- expression 19 operands: lhs = Counter(11), rhs = Counter(12) +- expression 20 operands: lhs = Expression(34, Add), rhs = Counter(15) +- expression 21 operands: lhs = Counter(13), rhs = Expression(35, Sub) +- expression 22 operands: lhs = Expression(36, Add), rhs = Counter(13) +- expression 23 operands: lhs = Counter(2), rhs = Expression(37, Add) +- expression 24 operands: lhs = Counter(11), rhs = Counter(12) +- expression 25 operands: lhs = Expression(33, Sub), rhs = Counter(10) +- expression 26 operands: lhs = Expression(34, Add), rhs = Counter(15) +- expression 27 operands: lhs = Counter(13), rhs = Expression(35, Sub) +- expression 28 operands: lhs = Expression(36, Add), rhs = Counter(13) +- expression 29 operands: lhs = Counter(2), rhs = Expression(37, Add) +- expression 30 operands: lhs = Counter(11), rhs = Counter(12) +- expression 31 operands: lhs = Expression(32, Sub), rhs = Counter(20) +- expression 32 operands: lhs = Expression(33, Sub), rhs = Counter(10) +- expression 33 operands: lhs = Expression(34, Add), rhs = Counter(15) +- expression 34 operands: lhs = Counter(13), rhs = Expression(35, Sub) +- expression 35 operands: lhs = Expression(36, Add), rhs = Counter(13) +- expression 36 operands: lhs = Counter(2), rhs = Expression(37, Add) +- expression 37 operands: lhs = Counter(11), rhs = Counter(12) +- expression 38 operands: lhs = Counter(17), rhs = Counter(18) +- expression 39 operands: lhs = Counter(15), rhs = Expression(112, Add) +- expression 40 operands: lhs = Counter(17), rhs = Counter(18) +- expression 41 operands: lhs = Counter(19), rhs = Counter(21) +- expression 42 operands: lhs = Counter(21), rhs = Expression(55, Sub) +- expression 43 operands: lhs = Counter(19), rhs = Counter(21) +- expression 44 operands: lhs = Expression(54, Add), rhs = Counter(22) +- expression 45 operands: lhs = Counter(21), rhs = Expression(55, Sub) +- expression 46 operands: lhs = Counter(19), rhs = Counter(21) +- expression 47 operands: lhs = Expression(53, Sub), rhs = Counter(9) +- expression 48 operands: lhs = Expression(54, Add), rhs = Counter(22) +- expression 49 operands: lhs = Counter(21), rhs = Expression(55, Sub) +- expression 50 operands: lhs = Counter(19), rhs = Counter(21) +- expression 51 operands: lhs = Expression(52, Sub), rhs = Counter(30) +- expression 52 operands: lhs = Expression(53, Sub), rhs = Counter(9) +- expression 53 operands: lhs = Expression(54, Add), rhs = Counter(22) +- expression 54 operands: lhs = Counter(21), rhs = Expression(55, Sub) +- expression 55 operands: lhs = Counter(19), rhs = Counter(21) +- expression 56 operands: lhs = Counter(23), rhs = Counter(24) +- expression 57 operands: lhs = Expression(111, Add), rhs = Counter(19) +- expression 58 operands: lhs = Counter(15), rhs = Expression(112, Add) +- expression 59 operands: lhs = Counter(17), rhs = Counter(18) +- expression 60 operands: lhs = Expression(108, Add), rhs = Expression(110, Sub) +- expression 61 operands: lhs = Counter(22), rhs = Expression(109, Add) +- expression 62 operands: lhs = Counter(23), rhs = Counter(24) +- expression 63 operands: lhs = Expression(111, Add), rhs = Counter(19) +- expression 64 operands: lhs = Counter(15), rhs = Expression(112, Add) +- expression 65 operands: lhs = Counter(17), rhs = Counter(18) +- expression 66 operands: lhs = Expression(107, Add), rhs = Counter(25) +- expression 67 operands: lhs = Expression(108, Add), rhs = Expression(110, Sub) +- expression 68 operands: lhs = Counter(22), rhs = Expression(109, Add) +- expression 69 operands: lhs = Counter(23), rhs = Counter(24) +- expression 70 operands: lhs = Expression(111, Add), rhs = Counter(19) +- expression 71 operands: lhs = Counter(15), rhs = Expression(112, Add) +- expression 72 operands: lhs = Counter(17), rhs = Counter(18) +- expression 73 operands: lhs = Counter(27), rhs = Expression(129, Add) +- expression 74 operands: lhs = Counter(28), rhs = Counter(29) +- expression 75 operands: lhs = Counter(25), rhs = Expression(106, Sub) +- expression 76 operands: lhs = Expression(107, Add), rhs = Counter(25) +- expression 77 operands: lhs = Expression(108, Add), rhs = Expression(110, Sub) +- expression 78 operands: lhs = Counter(22), rhs = Expression(109, Add) +- expression 79 operands: lhs = Counter(23), rhs = Counter(24) +- expression 80 operands: lhs = Expression(111, Add), rhs = Counter(19) +- expression 81 operands: lhs = Counter(15), rhs = Expression(112, Add) +- expression 82 operands: lhs = Counter(17), rhs = Counter(18) +- expression 83 operands: lhs = Expression(105, Add), rhs = Counter(27) +- expression 84 operands: lhs = Counter(25), rhs = Expression(106, Sub) +- expression 85 operands: lhs = Expression(107, Add), rhs = Counter(25) +- expression 86 operands: lhs = Expression(108, Add), rhs = Expression(110, Sub) +- expression 87 operands: lhs = Counter(22), rhs = Expression(109, Add) +- expression 88 operands: lhs = Counter(23), rhs = Counter(24) +- expression 89 operands: lhs = Expression(111, Add), rhs = Counter(19) +- expression 90 operands: lhs = Counter(15), rhs = Expression(112, Add) +- expression 91 operands: lhs = Counter(17), rhs = Counter(18) +- expression 92 operands: lhs = Expression(104, Sub), rhs = Counter(8) +- expression 93 operands: lhs = Expression(105, Add), rhs = Counter(27) +- expression 94 operands: lhs = Counter(25), rhs = Expression(106, Sub) +- expression 95 operands: lhs = Expression(107, Add), rhs = Counter(25) +- expression 96 operands: lhs = Expression(108, Add), rhs = Expression(110, Sub) +- expression 97 operands: lhs = Counter(22), rhs = Expression(109, Add) +- expression 98 operands: lhs = Counter(23), rhs = Counter(24) +- expression 99 operands: lhs = Expression(111, Add), rhs = Counter(19) +- expression 100 operands: lhs = Counter(15), rhs = Expression(112, Add) +- expression 101 operands: lhs = Counter(17), rhs = Counter(18) +- expression 102 operands: lhs = Expression(103, Sub), rhs = Counter(31) +- expression 103 operands: lhs = Expression(104, Sub), rhs = Counter(8) +- expression 104 operands: lhs = Expression(105, Add), rhs = Counter(27) +- expression 105 operands: lhs = Counter(25), rhs = Expression(106, Sub) +- expression 106 operands: lhs = Expression(107, Add), rhs = Counter(25) +- expression 107 operands: lhs = Expression(108, Add), rhs = Expression(110, Sub) +- expression 108 operands: lhs = Counter(22), rhs = Expression(109, Add) +- expression 109 operands: lhs = Counter(23), rhs = Counter(24) +- expression 110 operands: lhs = Expression(111, Add), rhs = Counter(19) +- expression 111 operands: lhs = Counter(15), rhs = Expression(112, Add) +- expression 112 operands: lhs = Counter(17), rhs = Counter(18) +- expression 113 operands: lhs = Counter(28), rhs = Counter(29) +- expression 114 operands: lhs = Counter(4), rhs = Expression(133, Add) +- expression 115 operands: lhs = Counter(5), rhs = Counter(6) +- expression 116 operands: lhs = Counter(27), rhs = Expression(129, Add) +- expression 117 operands: lhs = Counter(28), rhs = Counter(29) +- expression 118 operands: lhs = Expression(128, Add), rhs = Counter(4) +- expression 119 operands: lhs = Counter(27), rhs = Expression(129, Add) +- expression 120 operands: lhs = Counter(28), rhs = Counter(29) +- expression 121 operands: lhs = Expression(127, Sub), rhs = Counter(7) +- expression 122 operands: lhs = Expression(128, Add), rhs = Counter(4) +- expression 123 operands: lhs = Counter(27), rhs = Expression(129, Add) +- expression 124 operands: lhs = Counter(28), rhs = Counter(29) +- expression 125 operands: lhs = Expression(126, Sub), rhs = Counter(34) +- expression 126 operands: lhs = Expression(127, Sub), rhs = Counter(7) +- expression 127 operands: lhs = Expression(128, Add), rhs = Counter(4) +- expression 128 operands: lhs = Counter(27), rhs = Expression(129, Add) +- expression 129 operands: lhs = Counter(28), rhs = Counter(29) +- expression 130 operands: lhs = Counter(5), rhs = Counter(6) +- expression 131 operands: lhs = Expression(132, Add), rhs = Expression(134, Add) +- expression 132 operands: lhs = Counter(4), rhs = Expression(133, Add) +- expression 133 operands: lhs = Counter(5), rhs = Counter(6) +- expression 134 operands: lhs = Expression(135, Add), rhs = Expression(138, Sub) +- expression 135 operands: lhs = Expression(136, Add), rhs = Expression(137, Add) +- expression 136 operands: lhs = Counter(7), rhs = Counter(8) +- expression 137 operands: lhs = Counter(9), rhs = Counter(10) +- expression 138 operands: lhs = Expression(139, Sub), rhs = Counter(3) +- expression 139 operands: lhs = Expression(140, Add), rhs = Counter(2) +- expression 140 operands: lhs = Counter(1), rhs = Expression(0, Sub) +Number of file 0 mappings: 68 +- Code(Counter(0)) at (prev + 3, 1) to (start + 2, 12) +- Code(Counter(1)) at (prev + 2, 13) to (start + 2, 6) +- Code(Expression(0, Sub)) at (prev + 2, 6) to (start + 0, 7) + = (c0 - c1) +- Code(Expression(36, Add)) at (prev + 3, 9) to (start + 0, 10) + = (c2 + (c11 + c12)) +- Code(Expression(140, Add)) at (prev + 0, 16) to (start + 0, 29) + = (c1 + (c0 - c1)) +- Code(Counter(2)) at (prev + 1, 9) to (start + 1, 10) +- Code(Expression(139, Sub)) at (prev + 2, 15) to (start + 0, 28) + = ((c1 + (c0 - c1)) - c2) +- Code(Counter(3)) at (prev + 1, 12) to (start + 0, 25) +- Code(Expression(6, Sub)) at (prev + 0, 29) to (start + 0, 42) + = (c3 - c14) +- Code(Counter(16)) at (prev + 0, 46) to (start + 0, 60) +- Code(Counter(11)) at (prev + 0, 61) to (start + 2, 10) +- Code(Counter(12)) at (prev + 2, 10) to (start + 0, 11) +- Code(Expression(37, Add)) at (prev + 1, 9) to (start + 1, 18) + = (c11 + c12) +- Code(Expression(138, Sub)) at (prev + 3, 9) to (start + 0, 15) + = (((c1 + (c0 - c1)) - c2) - c3) +- Code(Expression(36, Add)) at (prev + 3, 9) to (start + 1, 12) + = (c2 + (c11 + c12)) +- Code(Counter(13)) at (prev + 1, 13) to (start + 2, 6) +- Code(Expression(35, Sub)) at (prev + 2, 6) to (start + 0, 7) + = ((c2 + (c11 + c12)) - c13) +- Code(Expression(34, Add)) at (prev + 2, 8) to (start + 0, 21) + = (c13 + ((c2 + (c11 + c12)) - c13)) +- Code(Counter(15)) at (prev + 0, 22) to (start + 2, 6) +- Code(Expression(33, Sub)) at (prev + 2, 15) to (start + 0, 28) + = ((c13 + ((c2 + (c11 + c12)) - c13)) - c15) +- Code(Expression(32, Sub)) at (prev + 1, 12) to (start + 0, 25) + = (((c13 + ((c2 + (c11 + c12)) - c13)) - c15) - c10) +- Code(Expression(31, Sub)) at (prev + 0, 29) to (start + 0, 42) + = ((((c13 + ((c2 + (c11 + c12)) - c13)) - c15) - c10) - c20) +- Code(Counter(26)) at (prev + 0, 46) to (start + 0, 60) +- Code(Counter(17)) at (prev + 0, 61) to (start + 2, 10) +- Code(Counter(18)) at (prev + 2, 10) to (start + 0, 11) +- Code(Expression(112, Add)) at (prev + 1, 9) to (start + 0, 23) + = (c17 + c18) +- Code(Counter(10)) at (prev + 2, 9) to (start + 0, 15) +- Code(Expression(111, Add)) at (prev + 3, 8) to (start + 0, 12) + = (c15 + (c17 + c18)) +- Code(Counter(19)) at (prev + 1, 13) to (start + 1, 16) +- Code(Counter(21)) at (prev + 1, 17) to (start + 2, 10) +- Code(Expression(55, Sub)) at (prev + 2, 10) to (start + 0, 11) + = (c19 - c21) +- Code(Expression(54, Add)) at (prev + 2, 12) to (start + 0, 25) + = (c21 + (c19 - c21)) +- Code(Counter(22)) at (prev + 0, 26) to (start + 2, 10) +- Code(Expression(53, Sub)) at (prev + 3, 17) to (start + 0, 30) + = ((c21 + (c19 - c21)) - c22) +- Code(Expression(52, Sub)) at (prev + 1, 16) to (start + 0, 29) + = (((c21 + (c19 - c21)) - c22) - c9) +- Code(Expression(51, Sub)) at (prev + 0, 33) to (start + 0, 46) + = ((((c21 + (c19 - c21)) - c22) - c9) - c30) +- Code(Counter(32)) at (prev + 0, 50) to (start + 0, 64) +- Code(Counter(23)) at (prev + 0, 65) to (start + 2, 14) +- Code(Counter(24)) at (prev + 2, 14) to (start + 0, 15) +- Code(Expression(109, Add)) at (prev + 1, 13) to (start + 0, 27) + = (c23 + c24) +- Code(Counter(9)) at (prev + 2, 13) to (start + 0, 19) +- Code(Expression(110, Sub)) at (prev + 2, 6) to (start + 0, 7) + = ((c15 + (c17 + c18)) - c19) +- Code(Expression(107, Add)) at (prev + 3, 9) to (start + 1, 12) + = ((c22 + (c23 + c24)) + ((c15 + (c17 + c18)) - c19)) +- Code(Counter(25)) at (prev + 1, 13) to (start + 2, 6) +- Code(Expression(106, Sub)) at (prev + 2, 6) to (start + 0, 7) + = (((c22 + (c23 + c24)) + ((c15 + (c17 + c18)) - c19)) - c25) +- Code(Expression(128, Add)) at (prev + 2, 9) to (start + 0, 10) + = (c27 + (c28 + c29)) +- Code(Expression(105, Add)) at (prev + 0, 16) to (start + 0, 29) + = (c25 + (((c22 + (c23 + c24)) + ((c15 + (c17 + c18)) - c19)) - c25)) +- Code(Counter(27)) at (prev + 0, 30) to (start + 2, 6) +- Code(Expression(104, Sub)) at (prev + 2, 15) to (start + 0, 28) + = ((c25 + (((c22 + (c23 + c24)) + ((c15 + (c17 + c18)) - c19)) - c25)) - c27) +- Code(Expression(103, Sub)) at (prev + 1, 12) to (start + 0, 25) + = (((c25 + (((c22 + (c23 + c24)) + ((c15 + (c17 + c18)) - c19)) - c25)) - c27) - c8) +- Code(Expression(102, Sub)) at (prev + 0, 29) to (start + 0, 42) + = ((((c25 + (((c22 + (c23 + c24)) + ((c15 + (c17 + c18)) - c19)) - c25)) - c27) - c8) - c31) +- Code(Counter(33)) at (prev + 0, 46) to (start + 0, 60) +- Code(Counter(28)) at (prev + 0, 61) to (start + 2, 10) +- Code(Counter(29)) at (prev + 2, 10) to (start + 0, 11) +- Code(Expression(129, Add)) at (prev + 1, 9) to (start + 0, 23) + = (c28 + c29) +- Code(Counter(8)) at (prev + 2, 13) to (start + 2, 15) +- Code(Expression(132, Add)) at (prev + 5, 9) to (start + 0, 10) + = (c4 + (c5 + c6)) +- Code(Expression(128, Add)) at (prev + 0, 16) to (start + 0, 29) + = (c27 + (c28 + c29)) +- Code(Counter(4)) at (prev + 0, 30) to (start + 2, 6) +- Code(Expression(127, Sub)) at (prev + 2, 15) to (start + 0, 28) + = ((c27 + (c28 + c29)) - c4) +- Code(Expression(126, Sub)) at (prev + 1, 12) to (start + 0, 25) + = (((c27 + (c28 + c29)) - c4) - c7) +- Code(Expression(125, Sub)) at (prev + 0, 29) to (start + 0, 42) + = ((((c27 + (c28 + c29)) - c4) - c7) - c34) +- Code(Counter(35)) at (prev + 0, 46) to (start + 0, 60) +- Code(Counter(5)) at (prev + 0, 61) to (start + 2, 10) +- Code(Counter(6)) at (prev + 2, 10) to (start + 0, 11) +- Code(Expression(133, Add)) at (prev + 1, 9) to (start + 0, 23) + = (c5 + c6) +- Code(Counter(7)) at (prev + 2, 9) to (start + 0, 15) +- Code(Expression(131, Add)) at (prev + 2, 1) to (start + 0, 2) + = ((c4 + (c5 + c6)) + (((c7 + c8) + (c9 + c10)) + (((c1 + (c0 - c1)) - c2) - c3))) + diff --git a/tests/coverage-map/status-quo/conditions.rs b/tests/coverage-map/status-quo/conditions.rs new file mode 100644 index 0000000000000..057599d1b471a --- /dev/null +++ b/tests/coverage-map/status-quo/conditions.rs @@ -0,0 +1,87 @@ +#![allow(unused_assignments, unused_variables)] + +fn main() { + let mut countdown = 0; + if true { + countdown = 10; + } + + const B: u32 = 100; + let x = if countdown > 7 { + countdown -= 4; + B + } else if countdown > 2 { + if countdown < 1 || countdown > 5 || countdown != 9 { + countdown = 0; + } + countdown -= 5; + countdown + } else { + return; + }; + + let mut countdown = 0; + if true { + countdown = 10; + } + + if countdown > 7 { + countdown -= 4; + } else if countdown > 2 { + if countdown < 1 || countdown > 5 || countdown != 9 { + countdown = 0; + } + countdown -= 5; + } else { + return; + } + + if true { + let mut countdown = 0; + if true { + countdown = 10; + } + + if countdown > 7 { + countdown -= 4; + } + else if countdown > 2 { + if countdown < 1 || countdown > 5 || countdown != 9 { + countdown = 0; + } + countdown -= 5; + } else { + return; + } + } + + + let mut countdown = 0; + if true { + countdown = 1; + } + + let z = if countdown > 7 { + countdown -= 4; + } else if countdown > 2 { + if countdown < 1 || countdown > 5 || countdown != 9 { + countdown = 0; + } + countdown -= 5; + } else { + let should_be_reachable = countdown; + println!("reached"); + return; + }; + + let w = if countdown > 7 { + countdown -= 4; + } else if countdown > 2 { + if countdown < 1 || countdown > 5 || countdown != 9 { + countdown = 0; + } + countdown -= 5; + } else { + return; + }; +} diff --git a/tests/coverage-map/status-quo/drop_trait.cov-map b/tests/coverage-map/status-quo/drop_trait.cov-map new file mode 100644 index 0000000000000..353f7e94492a0 --- /dev/null +++ b/tests/coverage-map/status-quo/drop_trait.cov-map @@ -0,0 +1,23 @@ +Function name: ::drop +Raw bytes (9): 0x[01, 01, 00, 01, 01, 09, 05, 02, 06] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 9, 5) to (start + 2, 6) + +Function name: drop_trait::main +Raw bytes (28): 0x[01, 01, 02, 01, 05, 05, 02, 04, 01, 0e, 01, 05, 0c, 05, 06, 09, 01, 16, 02, 02, 06, 04, 0b, 07, 05, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 2 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +- expression 1 operands: lhs = Counter(1), rhs = Expression(0, Sub) +Number of file 0 mappings: 4 +- Code(Counter(0)) at (prev + 14, 1) to (start + 5, 12) +- Code(Counter(1)) at (prev + 6, 9) to (start + 1, 22) +- Code(Expression(0, Sub)) at (prev + 2, 6) to (start + 4, 11) + = (c0 - c1) +- Code(Expression(1, Add)) at (prev + 5, 1) to (start + 0, 2) + = (c1 + (c0 - c1)) + diff --git a/tests/coverage-map/status-quo/drop_trait.rs b/tests/coverage-map/status-quo/drop_trait.rs new file mode 100644 index 0000000000000..a9b5d1d1e7fe9 --- /dev/null +++ b/tests/coverage-map/status-quo/drop_trait.rs @@ -0,0 +1,33 @@ +#![allow(unused_assignments)] +// failure-status: 1 + +struct Firework { + strength: i32, +} + +impl Drop for Firework { + fn drop(&mut self) { + println!("BOOM times {}!!!", self.strength); + } +} + +fn main() -> Result<(),u8> { + let _firecracker = Firework { strength: 1 }; + + let _tnt = Firework { strength: 100 }; + + if true { + println!("Exiting with error..."); + return Err(1); + } + + let _ = Firework { strength: 1000 }; + + Ok(()) +} + +// Expected program output: +// Exiting with error... +// BOOM times 100!!! +// BOOM times 1!!! +// Error: 1 diff --git a/tests/coverage-map/status-quo/generics.cov-map b/tests/coverage-map/status-quo/generics.cov-map new file mode 100644 index 0000000000000..d6599940200a2 --- /dev/null +++ b/tests/coverage-map/status-quo/generics.cov-map @@ -0,0 +1,47 @@ +Function name: as core::ops::drop::Drop>::drop +Raw bytes (9): 0x[01, 01, 00, 01, 01, 11, 05, 02, 06] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 17, 5) to (start + 2, 6) + +Function name: >::set_strength +Raw bytes (9): 0x[01, 01, 00, 01, 01, 0a, 05, 02, 06] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 10, 5) to (start + 2, 6) + +Function name: as core::ops::drop::Drop>::drop +Raw bytes (9): 0x[01, 01, 00, 01, 01, 11, 05, 02, 06] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 17, 5) to (start + 2, 6) + +Function name: >::set_strength +Raw bytes (9): 0x[01, 01, 00, 01, 01, 0a, 05, 02, 06] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 10, 5) to (start + 2, 6) + +Function name: generics::main +Raw bytes (28): 0x[01, 01, 02, 01, 05, 05, 02, 04, 01, 16, 01, 08, 0c, 05, 09, 09, 01, 16, 02, 02, 06, 08, 0b, 07, 09, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 2 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +- expression 1 operands: lhs = Counter(1), rhs = Expression(0, Sub) +Number of file 0 mappings: 4 +- Code(Counter(0)) at (prev + 22, 1) to (start + 8, 12) +- Code(Counter(1)) at (prev + 9, 9) to (start + 1, 22) +- Code(Expression(0, Sub)) at (prev + 2, 6) to (start + 8, 11) + = (c0 - c1) +- Code(Expression(1, Add)) at (prev + 9, 1) to (start + 0, 2) + = (c1 + (c0 - c1)) + diff --git a/tests/coverage-map/status-quo/generics.rs b/tests/coverage-map/status-quo/generics.rs new file mode 100644 index 0000000000000..150ffb9db395a --- /dev/null +++ b/tests/coverage-map/status-quo/generics.rs @@ -0,0 +1,48 @@ +#![allow(unused_assignments)] +// failure-status: 1 + +struct Firework where T: Copy + std::fmt::Display { + strength: T, +} + +impl Firework where T: Copy + std::fmt::Display { + #[inline(always)] + fn set_strength(&mut self, new_strength: T) { + self.strength = new_strength; + } +} + +impl Drop for Firework where T: Copy + std::fmt::Display { + #[inline(always)] + fn drop(&mut self) { + println!("BOOM times {}!!!", self.strength); + } +} + +fn main() -> Result<(),u8> { + let mut firecracker = Firework { strength: 1 }; + firecracker.set_strength(2); + + let mut tnt = Firework { strength: 100.1 }; + tnt.set_strength(200.1); + tnt.set_strength(300.3); + + if true { + println!("Exiting with error..."); + return Err(1); + } + + + + + + let _ = Firework { strength: 1000 }; + + Ok(()) +} + +// Expected program output: +// Exiting with error... +// BOOM times 100!!! +// BOOM times 1!!! +// Error: 1 diff --git a/tests/coverage-map/status-quo/loops_branches.cov-map b/tests/coverage-map/status-quo/loops_branches.cov-map new file mode 100644 index 0000000000000..381c9e6b68591 --- /dev/null +++ b/tests/coverage-map/status-quo/loops_branches.cov-map @@ -0,0 +1,114 @@ +Function name: ::fmt +Raw bytes (160): 0x[01, 01, 1c, 01, 05, 6b, 19, 6f, 11, 0d, 02, 6f, 11, 0d, 02, 6b, 19, 6f, 11, 0d, 02, 6b, 19, 6f, 11, 0d, 02, 6b, 19, 6f, 11, 0d, 02, 6b, 19, 6f, 11, 0d, 02, 66, 11, 6b, 19, 6f, 11, 0d, 02, 25, 5f, 62, 19, 66, 11, 6b, 19, 6f, 11, 0d, 02, 14, 01, 09, 05, 01, 10, 05, 02, 10, 00, 15, 00, 01, 17, 00, 1b, 00, 00, 1c, 01, 12, 05, 02, 0e, 00, 0f, 05, 01, 0d, 00, 1e, 25, 00, 1e, 00, 1f, 02, 01, 10, 01, 0a, 66, 03, 0d, 00, 0e, 6b, 00, 12, 00, 17, 66, 01, 10, 00, 14, 66, 01, 14, 00, 19, 00, 01, 1b, 00, 1f, 00, 00, 20, 00, 22, 66, 01, 12, 00, 13, 66, 01, 11, 00, 22, 62, 00, 22, 00, 23, 00, 01, 14, 01, 0e, 19, 03, 09, 00, 0f, 5b, 01, 05, 00, 06] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 28 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +- expression 1 operands: lhs = Expression(26, Add), rhs = Counter(6) +- expression 2 operands: lhs = Expression(27, Add), rhs = Counter(4) +- expression 3 operands: lhs = Counter(3), rhs = Expression(0, Sub) +- expression 4 operands: lhs = Expression(27, Add), rhs = Counter(4) +- expression 5 operands: lhs = Counter(3), rhs = Expression(0, Sub) +- expression 6 operands: lhs = Expression(26, Add), rhs = Counter(6) +- expression 7 operands: lhs = Expression(27, Add), rhs = Counter(4) +- expression 8 operands: lhs = Counter(3), rhs = Expression(0, Sub) +- expression 9 operands: lhs = Expression(26, Add), rhs = Counter(6) +- expression 10 operands: lhs = Expression(27, Add), rhs = Counter(4) +- expression 11 operands: lhs = Counter(3), rhs = Expression(0, Sub) +- expression 12 operands: lhs = Expression(26, Add), rhs = Counter(6) +- expression 13 operands: lhs = Expression(27, Add), rhs = Counter(4) +- expression 14 operands: lhs = Counter(3), rhs = Expression(0, Sub) +- expression 15 operands: lhs = Expression(26, Add), rhs = Counter(6) +- expression 16 operands: lhs = Expression(27, Add), rhs = Counter(4) +- expression 17 operands: lhs = Counter(3), rhs = Expression(0, Sub) +- expression 18 operands: lhs = Expression(25, Sub), rhs = Counter(4) +- expression 19 operands: lhs = Expression(26, Add), rhs = Counter(6) +- expression 20 operands: lhs = Expression(27, Add), rhs = Counter(4) +- expression 21 operands: lhs = Counter(3), rhs = Expression(0, Sub) +- expression 22 operands: lhs = Counter(9), rhs = Expression(23, Add) +- expression 23 operands: lhs = Expression(24, Sub), rhs = Counter(6) +- expression 24 operands: lhs = Expression(25, Sub), rhs = Counter(4) +- expression 25 operands: lhs = Expression(26, Add), rhs = Counter(6) +- expression 26 operands: lhs = Expression(27, Add), rhs = Counter(4) +- expression 27 operands: lhs = Counter(3), rhs = Expression(0, Sub) +Number of file 0 mappings: 20 +- Code(Counter(0)) at (prev + 9, 5) to (start + 1, 16) +- Code(Counter(1)) at (prev + 2, 16) to (start + 0, 21) +- Code(Zero) at (prev + 1, 23) to (start + 0, 27) +- Code(Zero) at (prev + 0, 28) to (start + 1, 18) +- Code(Counter(1)) at (prev + 2, 14) to (start + 0, 15) +- Code(Counter(1)) at (prev + 1, 13) to (start + 0, 30) +- Code(Counter(9)) at (prev + 0, 30) to (start + 0, 31) +- Code(Expression(0, Sub)) at (prev + 1, 16) to (start + 1, 10) + = (c0 - c1) +- Code(Expression(25, Sub)) at (prev + 3, 13) to (start + 0, 14) + = (((c3 + (c0 - c1)) + c4) - c6) +- Code(Expression(26, Add)) at (prev + 0, 18) to (start + 0, 23) + = ((c3 + (c0 - c1)) + c4) +- Code(Expression(25, Sub)) at (prev + 1, 16) to (start + 0, 20) + = (((c3 + (c0 - c1)) + c4) - c6) +- Code(Expression(25, Sub)) at (prev + 1, 20) to (start + 0, 25) + = (((c3 + (c0 - c1)) + c4) - c6) +- Code(Zero) at (prev + 1, 27) to (start + 0, 31) +- Code(Zero) at (prev + 0, 32) to (start + 0, 34) +- Code(Expression(25, Sub)) at (prev + 1, 18) to (start + 0, 19) + = (((c3 + (c0 - c1)) + c4) - c6) +- Code(Expression(25, Sub)) at (prev + 1, 17) to (start + 0, 34) + = (((c3 + (c0 - c1)) + c4) - c6) +- Code(Expression(24, Sub)) at (prev + 0, 34) to (start + 0, 35) + = ((((c3 + (c0 - c1)) + c4) - c6) - c4) +- Code(Zero) at (prev + 1, 20) to (start + 1, 14) +- Code(Counter(6)) at (prev + 3, 9) to (start + 0, 15) +- Code(Expression(22, Add)) at (prev + 1, 5) to (start + 0, 6) + = (c9 + (((((c3 + (c0 - c1)) + c4) - c6) - c4) + c6)) + +Function name: ::fmt +Raw bytes (118): 0x[01, 01, 07, 0b, 19, 0d, 15, 0d, 15, 02, 15, 16, 1b, 02, 15, 19, 25, 14, 01, 23, 05, 01, 11, 00, 01, 12, 01, 0a, 01, 02, 10, 00, 15, 00, 01, 17, 00, 1b, 00, 00, 1c, 00, 1e, 01, 01, 0e, 00, 0f, 01, 01, 0d, 00, 1e, 25, 00, 1e, 00, 1f, 02, 02, 0d, 00, 0e, 0b, 00, 12, 00, 17, 02, 01, 10, 00, 15, 00, 00, 16, 01, 0e, 02, 02, 14, 00, 19, 00, 01, 1b, 00, 1f, 00, 00, 20, 00, 22, 02, 01, 12, 00, 13, 02, 01, 11, 00, 22, 16, 00, 22, 00, 23, 19, 03, 09, 00, 0f, 13, 01, 05, 00, 06] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 7 +- expression 0 operands: lhs = Expression(2, Add), rhs = Counter(6) +- expression 1 operands: lhs = Counter(3), rhs = Counter(5) +- expression 2 operands: lhs = Counter(3), rhs = Counter(5) +- expression 3 operands: lhs = Expression(0, Sub), rhs = Counter(5) +- expression 4 operands: lhs = Expression(5, Sub), rhs = Expression(6, Add) +- expression 5 operands: lhs = Expression(0, Sub), rhs = Counter(5) +- expression 6 operands: lhs = Counter(6), rhs = Counter(9) +Number of file 0 mappings: 20 +- Code(Counter(0)) at (prev + 35, 5) to (start + 1, 17) +- Code(Zero) at (prev + 1, 18) to (start + 1, 10) +- Code(Counter(0)) at (prev + 2, 16) to (start + 0, 21) +- Code(Zero) at (prev + 1, 23) to (start + 0, 27) +- Code(Zero) at (prev + 0, 28) to (start + 0, 30) +- Code(Counter(0)) at (prev + 1, 14) to (start + 0, 15) +- Code(Counter(0)) at (prev + 1, 13) to (start + 0, 30) +- Code(Counter(9)) at (prev + 0, 30) to (start + 0, 31) +- Code(Expression(0, Sub)) at (prev + 2, 13) to (start + 0, 14) + = ((c3 + c5) - c6) +- Code(Expression(2, Add)) at (prev + 0, 18) to (start + 0, 23) + = (c3 + c5) +- Code(Expression(0, Sub)) at (prev + 1, 16) to (start + 0, 21) + = ((c3 + c5) - c6) +- Code(Zero) at (prev + 0, 22) to (start + 1, 14) +- Code(Expression(0, Sub)) at (prev + 2, 20) to (start + 0, 25) + = ((c3 + c5) - c6) +- Code(Zero) at (prev + 1, 27) to (start + 0, 31) +- Code(Zero) at (prev + 0, 32) to (start + 0, 34) +- Code(Expression(0, Sub)) at (prev + 1, 18) to (start + 0, 19) + = ((c3 + c5) - c6) +- Code(Expression(0, Sub)) at (prev + 1, 17) to (start + 0, 34) + = ((c3 + c5) - c6) +- Code(Expression(5, Sub)) at (prev + 0, 34) to (start + 0, 35) + = (((c3 + c5) - c6) - c5) +- Code(Counter(6)) at (prev + 3, 9) to (start + 0, 15) +- Code(Expression(4, Add)) at (prev + 1, 5) to (start + 0, 6) + = ((((c3 + c5) - c6) - c5) + (c6 + c9)) + +Function name: loops_branches::main +Raw bytes (9): 0x[01, 01, 00, 01, 01, 38, 01, 05, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 56, 1) to (start + 5, 2) + diff --git a/tests/coverage-map/status-quo/loops_branches.rs b/tests/coverage-map/status-quo/loops_branches.rs new file mode 100644 index 0000000000000..7116ce47f4b9d --- /dev/null +++ b/tests/coverage-map/status-quo/loops_branches.rs @@ -0,0 +1,61 @@ +#![allow(unused_assignments, unused_variables, while_true)] + +// This test confirms that (1) unexecuted infinite loops are handled correctly by the +// InstrumentCoverage MIR pass; and (2) Counter Expressions that subtract from zero can be dropped. + +struct DebugTest; + +impl std::fmt::Debug for DebugTest { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if true { + if false { + while true { + } + } + write!(f, "cool")?; + } else { + } + + for i in 0..10 { + if true { + if false { + while true {} + } + write!(f, "cool")?; + } else { + } + } + Ok(()) + } +} + +struct DisplayTest; + +impl std::fmt::Display for DisplayTest { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if false { + } else { + if false { + while true {} + } + write!(f, "cool")?; + } + for i in 0..10 { + if false { + } else { + if false { + while true {} + } + write!(f, "cool")?; + } + } + Ok(()) + } +} + +fn main() { + let debug_test = DebugTest; + println!("{:?}", debug_test); + let display_test = DisplayTest; + println!("{}", display_test); +} diff --git a/tests/coverage-map/status-quo/unused.cov-map b/tests/coverage-map/status-quo/unused.cov-map new file mode 100644 index 0000000000000..5d58ebb6d1c58 --- /dev/null +++ b/tests/coverage-map/status-quo/unused.cov-map @@ -0,0 +1,94 @@ +Function name: unused::foo:: +Raw bytes (42): 0x[01, 01, 04, 01, 0f, 05, 09, 03, 0d, 05, 09, 06, 01, 01, 01, 01, 12, 03, 02, 0b, 00, 11, 0a, 01, 09, 00, 0f, 09, 00, 13, 00, 19, 0f, 01, 09, 00, 0f, 0d, 02, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 4 +- expression 0 operands: lhs = Counter(0), rhs = Expression(3, Add) +- expression 1 operands: lhs = Counter(1), rhs = Counter(2) +- expression 2 operands: lhs = Expression(0, Add), rhs = Counter(3) +- expression 3 operands: lhs = Counter(1), rhs = Counter(2) +Number of file 0 mappings: 6 +- Code(Counter(0)) at (prev + 1, 1) to (start + 1, 18) +- Code(Expression(0, Add)) at (prev + 2, 11) to (start + 0, 17) + = (c0 + (c1 + c2)) +- Code(Expression(2, Sub)) at (prev + 1, 9) to (start + 0, 15) + = ((c0 + (c1 + c2)) - c3) +- Code(Counter(2)) at (prev + 0, 19) to (start + 0, 25) +- Code(Expression(3, Add)) at (prev + 1, 9) to (start + 0, 15) + = (c1 + c2) +- Code(Counter(3)) at (prev + 2, 1) to (start + 0, 2) + +Function name: unused::foo:: +Raw bytes (42): 0x[01, 01, 04, 01, 0f, 05, 09, 03, 0d, 05, 09, 06, 01, 01, 01, 01, 12, 03, 02, 0b, 00, 11, 0a, 01, 09, 00, 0f, 09, 00, 13, 00, 19, 0f, 01, 09, 00, 0f, 0d, 02, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 4 +- expression 0 operands: lhs = Counter(0), rhs = Expression(3, Add) +- expression 1 operands: lhs = Counter(1), rhs = Counter(2) +- expression 2 operands: lhs = Expression(0, Add), rhs = Counter(3) +- expression 3 operands: lhs = Counter(1), rhs = Counter(2) +Number of file 0 mappings: 6 +- Code(Counter(0)) at (prev + 1, 1) to (start + 1, 18) +- Code(Expression(0, Add)) at (prev + 2, 11) to (start + 0, 17) + = (c0 + (c1 + c2)) +- Code(Expression(2, Sub)) at (prev + 1, 9) to (start + 0, 15) + = ((c0 + (c1 + c2)) - c3) +- Code(Counter(2)) at (prev + 0, 19) to (start + 0, 25) +- Code(Expression(3, Add)) at (prev + 1, 9) to (start + 0, 15) + = (c1 + c2) +- Code(Counter(3)) at (prev + 2, 1) to (start + 0, 2) + +Function name: unused::main +Raw bytes (9): 0x[01, 01, 00, 01, 01, 23, 01, 04, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 35, 1) to (start + 4, 2) + +Function name: unused::unused_func (unused) +Raw bytes (24): 0x[01, 01, 00, 04, 00, 11, 01, 01, 0e, 00, 01, 0f, 02, 06, 00, 02, 06, 00, 07, 00, 01, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 4 +- Code(Zero) at (prev + 17, 1) to (start + 1, 14) +- Code(Zero) at (prev + 1, 15) to (start + 2, 6) +- Code(Zero) at (prev + 2, 6) to (start + 0, 7) +- Code(Zero) at (prev + 1, 1) to (start + 0, 2) + +Function name: unused::unused_func2 (unused) +Raw bytes (24): 0x[01, 01, 00, 04, 00, 17, 01, 01, 0e, 00, 01, 0f, 02, 06, 00, 02, 06, 00, 07, 00, 01, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 4 +- Code(Zero) at (prev + 23, 1) to (start + 1, 14) +- Code(Zero) at (prev + 1, 15) to (start + 2, 6) +- Code(Zero) at (prev + 2, 6) to (start + 0, 7) +- Code(Zero) at (prev + 1, 1) to (start + 0, 2) + +Function name: unused::unused_func3 (unused) +Raw bytes (24): 0x[01, 01, 00, 04, 00, 1d, 01, 01, 0e, 00, 01, 0f, 02, 06, 00, 02, 06, 00, 07, 00, 01, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 4 +- Code(Zero) at (prev + 29, 1) to (start + 1, 14) +- Code(Zero) at (prev + 1, 15) to (start + 2, 6) +- Code(Zero) at (prev + 2, 6) to (start + 0, 7) +- Code(Zero) at (prev + 1, 1) to (start + 0, 2) + +Function name: unused::unused_template_func::<_> (unused) +Raw bytes (34): 0x[01, 01, 00, 06, 00, 09, 01, 01, 12, 00, 02, 0b, 00, 11, 00, 01, 09, 00, 0f, 00, 00, 13, 00, 19, 00, 01, 09, 00, 0f, 00, 02, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 6 +- Code(Zero) at (prev + 9, 1) to (start + 1, 18) +- Code(Zero) at (prev + 2, 11) to (start + 0, 17) +- Code(Zero) at (prev + 1, 9) to (start + 0, 15) +- Code(Zero) at (prev + 0, 19) to (start + 0, 25) +- Code(Zero) at (prev + 1, 9) to (start + 0, 15) +- Code(Zero) at (prev + 2, 1) to (start + 0, 2) + diff --git a/tests/coverage-map/status-quo/unused.rs b/tests/coverage-map/status-quo/unused.rs new file mode 100644 index 0000000000000..fb6113eb01c2d --- /dev/null +++ b/tests/coverage-map/status-quo/unused.rs @@ -0,0 +1,39 @@ +fn foo(x: T) { + let mut i = 0; + while i < 10 { + i != 0 || i != 0; + i += 1; + } +} + +fn unused_template_func(x: T) { + let mut i = 0; + while i < 10 { + i != 0 || i != 0; + i += 1; + } +} + +fn unused_func(mut a: u32) { + if a != 0 { + a += 1; + } +} + +fn unused_func2(mut a: u32) { + if a != 0 { + a += 1; + } +} + +fn unused_func3(mut a: u32) { + if a != 0 { + a += 1; + } +} + +fn main() -> Result<(), u8> { + foo::(0); + foo::(0.0); + Ok(()) +} diff --git a/tests/coverage-map/trivial.cov-map b/tests/coverage-map/trivial.cov-map new file mode 100644 index 0000000000000..874e294a1c498 --- /dev/null +++ b/tests/coverage-map/trivial.cov-map @@ -0,0 +1,8 @@ +Function name: trivial::main +Raw bytes (9): 0x[01, 01, 00, 01, 01, 03, 01, 00, 0d] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 3, 1) to (start + 0, 13) + diff --git a/tests/coverage-map/trivial.rs b/tests/coverage-map/trivial.rs new file mode 100644 index 0000000000000..d0a9b44fb3605 --- /dev/null +++ b/tests/coverage-map/trivial.rs @@ -0,0 +1,3 @@ +// compile-flags: --edition=2021 + +fn main() {}