summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_codegen_llvm/src/coverageinfo
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 18:31:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 18:31:44 +0000
commitc23a457e72abe608715ac76f076f47dc42af07a5 (patch)
tree2772049aaf84b5c9d0ed12ec8d86812f7a7904b6 /compiler/rustc_codegen_llvm/src/coverageinfo
parentReleasing progress-linux version 1.73.0+dfsg1-1~progress7.99u1. (diff)
downloadrustc-c23a457e72abe608715ac76f076f47dc42af07a5.tar.xz
rustc-c23a457e72abe608715ac76f076f47dc42af07a5.zip
Merging upstream version 1.74.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'compiler/rustc_codegen_llvm/src/coverageinfo')
-rw-r--r--compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs46
-rw-r--r--compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs248
-rw-r--r--compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs200
-rw-r--r--compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs169
4 files changed, 280 insertions, 383 deletions
diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs
index 7a82d05ce..763186a58 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 f1e68af25..e83110dca 100644
--- a/compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs
+++ b/compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs
@@ -1,10 +1,8 @@
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::FxIndexSet;
+use rustc_index::IndexVec;
+use rustc_middle::mir::coverage::{CodeRegion, CounterId, ExpressionId, Op, Operand};
use rustc_middle::ty::Instance;
use rustc_middle::ty::TyCtxt;
@@ -128,6 +126,58 @@ impl<'tcx> FunctionCoverage<'tcx> {
self.unreachable_regions.push(region)
}
+ /// Perform some simplifications to make the final coverage mappings
+ /// slightly smaller.
+ ///
+ /// This method mainly exists to preserve the simplifications that were
+ /// already being performed by the Rust-side expression renumbering, so that
+ /// the resulting coverage mappings don't get worse.
+ pub(crate) fn simplify_expressions(&mut self) {
+ // The set of expressions that either were optimized out entirely, or
+ // have zero as both of their operands, and will therefore always have
+ // a value of zero. Other expressions that refer to these as operands
+ // can have those operands replaced with `Operand::Zero`.
+ let mut zero_expressions = FxIndexSet::default();
+
+ // For each expression, perform simplifications based on lower-numbered
+ // expressions, and then update the set of always-zero 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.
+ zero_expressions.insert(id);
+ continue;
+ };
+
+ // If an operand refers to an expression that is always zero, then
+ // that operand can be replaced with `Operand::Zero`.
+ let maybe_set_operand_to_zero = |operand: &mut Operand| match &*operand {
+ Operand::Expression(id) if zero_expressions.contains(id) => {
+ *operand = Operand::Zero;
+ }
+ _ => (),
+ };
+ maybe_set_operand_to_zero(&mut expression.lhs);
+ maybe_set_operand_to_zero(&mut expression.rhs);
+
+ // 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;
+ }
+
+ // After the above simplifications, if both operands are zero, then
+ // we know that this expression is always zero too.
+ if let Expression { lhs: Operand::Zero, rhs: Operand::Zero, .. } = expression {
+ zero_expressions.insert(id);
+ }
+ }
+ }
+
/// 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 {
@@ -146,8 +196,14 @@ impl<'tcx> FunctionCoverage<'tcx> {
self.instance
);
+ let counter_expressions = self.counter_expressions();
+ // Expression IDs are indices into `self.expressions`, and on the LLVM
+ // side they will be treated as indices into `counter_expressions`, so
+ // the two vectors should correspond 1:1.
+ assert_eq!(self.expressions.len(), counter_expressions.len());
+
let counter_regions = self.counter_regions();
- let (counter_expressions, expression_regions) = self.expressions_with_regions();
+ let expression_regions = self.expression_regions();
let unreachable_regions = self.unreachable_regions();
let counter_regions =
@@ -163,149 +219,53 @@ impl<'tcx> FunctionCoverage<'tcx> {
})
}
- fn expressions_with_regions(
- &self,
- ) -> (Vec<CounterExpression>, impl Iterator<Item = (Counter, &CodeRegion)>) {
- 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());
+ /// Convert this function's coverage expression data into a form that can be
+ /// passed through FFI to LLVM.
+ fn counter_expressions(&self) -> Vec<CounterExpression> {
+ // 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`.)
- // 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<ExpressionId, Option<MappedExpressionIndex>>;
- 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)),
- 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))
- }
- };
-
- 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;
-
- 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();
+ 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,
- 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));
+ &Some(Expression { lhs, op, rhs, .. }) => {
+ // Convert the operands and operator as normal.
+ CounterExpression::new(
+ Counter::from_operand(lhs),
+ match op {
+ Op::Add => ExprKind::Add,
+ Op::Subtract => ExprKind::Subtract,
+ },
+ Counter::from_operand(rhs),
+ )
}
- } 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())
+ })
+ .collect::<Vec<_>>()
+ }
+
+ fn expression_regions(&self) -> Vec<(Counter, &CodeRegion)> {
+ // Find all of the expression IDs that weren't optimized out AND have
+ // an attached code region, and return the corresponding mapping as a
+ // counter/region pair.
+ self.expressions
+ .iter_enumerated()
+ .filter_map(|(id, expression)| {
+ let code_region = expression.as_ref()?.region.as_ref()?;
+ Some((Counter::expression(id), code_region))
+ })
+ .collect::<Vec<_>>()
}
fn unreachable_regions(&self) -> impl Iterator<Item = (Counter, &CodeRegion)> {
- self.unreachable_regions.iter().map(|region| (Counter::zero(), region))
+ self.unreachable_regions.iter().map(|region| (Counter::ZERO, region))
}
}
diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs
index 97a99e510..d4e775256 100644
--- a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs
+++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs
@@ -1,13 +1,14 @@
use crate::common::CodegenCx;
use crate::coverageinfo;
-use crate::coverageinfo::ffi::{Counter, CounterExpression, CounterMappingRegion};
+use crate::coverageinfo::ffi::CounterMappingRegion;
+use crate::coverageinfo::map_data::FunctionCoverage;
use crate::llvm;
use rustc_codegen_ssa::traits::ConstMethods;
use rustc_data_structures::fx::FxIndexSet;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId;
-use rustc_llvm::RustString;
+use rustc_index::IndexVec;
use rustc_middle::bug;
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
use rustc_middle::mir::coverage::CodeRegion;
@@ -55,21 +56,21 @@ pub fn finalize(cx: &CodegenCx<'_, '_>) {
return;
}
- let mut mapgen = CoverageMapGenerator::new(tcx);
+ let mut global_file_table = GlobalFileTable::new(tcx);
// 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);
+ function_coverage.simplify_expressions();
+ let function_coverage = function_coverage;
+
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);
- });
+ let coverage_mapping_buffer =
+ encode_mappings_for_function(&mut global_file_table, &function_coverage);
if coverage_mapping_buffer.is_empty() {
if function_coverage.is_used() {
@@ -87,19 +88,14 @@ pub fn finalize(cx: &CodegenCx<'_, '_>) {
}
// Encode all filenames referenced by counters/expressions in this module
- let filenames_buffer = llvm::build_byte_buffer(|filenames_buffer| {
- coverageinfo::write_filenames_section_to_buffer(
- mapgen.filenames.iter().map(Symbol::as_str),
- filenames_buffer,
- );
- });
+ let filenames_buffer = global_file_table.into_filenames_buffer();
let filenames_size = filenames_buffer.len();
let filenames_val = cx.const_bytes(&filenames_buffer);
let filenames_ref = coverageinfo::hash_bytes(&filenames_buffer);
// Generate the LLVM IR representation of the coverage map and store it in a well-known global
- let cov_data_val = mapgen.generate_coverage_map(cx, version, filenames_size, filenames_val);
+ let cov_data_val = generate_coverage_map(cx, version, filenames_size, filenames_val);
let covfun_section_name = coverageinfo::covfun_section_name(cx);
for (mangled_function_name, source_hash, is_used, coverage_mapping_buffer) in function_data {
@@ -118,13 +114,13 @@ pub fn finalize(cx: &CodegenCx<'_, '_>) {
coverageinfo::save_cov_data_to_mod(cx, cov_data_val);
}
-struct CoverageMapGenerator {
- filenames: FxIndexSet<Symbol>,
+struct GlobalFileTable {
+ global_file_table: FxIndexSet<Symbol>,
}
-impl CoverageMapGenerator {
+impl GlobalFileTable {
fn new(tcx: TyCtxt<'_>) -> Self {
- let mut filenames = FxIndexSet::default();
+ let mut global_file_table = FxIndexSet::default();
// LLVM Coverage Mapping Format version 6 (zero-based encoded as 5)
// requires setting the first filename to the compilation directory.
// Since rustc generates coverage maps with relative paths, the
@@ -133,94 +129,114 @@ impl CoverageMapGenerator {
let working_dir = Symbol::intern(
&tcx.sess.opts.working_dir.remapped_path_if_available().to_string_lossy(),
);
- filenames.insert(working_dir);
- Self { filenames }
+ global_file_table.insert(working_dir);
+ Self { global_file_table }
}
- /// Using the `expressions` and `counter_regions` collected for the current function, generate
- /// the `mapping_regions` and `virtual_file_mapping`, and capture any new filenames. Then use
- /// LLVM APIs to encode the `virtual_file_mapping`, `expressions`, and `mapping_regions` into
- /// the given `coverage_mapping` byte buffer, compliant with the LLVM Coverage Mapping format.
- fn write_coverage_mapping<'a>(
- &mut self,
- expressions: Vec<CounterExpression>,
- counter_regions: impl Iterator<Item = (Counter, &'a CodeRegion)>,
- coverage_mapping_buffer: &RustString,
- ) {
- let mut counter_regions = counter_regions.collect::<Vec<_>>();
- if counter_regions.is_empty() {
- return;
- }
+ fn global_file_id_for_file_name(&mut self, file_name: Symbol) -> u32 {
+ let (global_file_id, _) = self.global_file_table.insert_full(file_name);
+ global_file_id as u32
+ }
- let mut virtual_file_mapping = Vec::new();
- let mut mapping_regions = Vec::new();
- let mut current_file_name = None;
- let mut current_file_id = 0;
-
- // Convert the list of (Counter, CodeRegion) pairs to an array of `CounterMappingRegion`, sorted
- // by filename and position. Capture any new files to compute the `CounterMappingRegion`s
- // `file_id` (indexing files referenced by the current function), and construct the
- // function-specific `virtual_file_mapping` from `file_id` to its index in the module's
- // `filenames` array.
- counter_regions.sort_unstable_by_key(|(_counter, region)| *region);
- for (counter, region) in counter_regions {
- let CodeRegion { file_name, start_line, start_col, end_line, end_col } = *region;
- let same_file = current_file_name.is_some_and(|p| p == file_name);
- if !same_file {
- if current_file_name.is_some() {
- current_file_id += 1;
- }
- current_file_name = Some(file_name);
- debug!(" file_id: {} = '{:?}'", current_file_id, file_name);
- let (filenames_index, _) = self.filenames.insert_full(file_name);
- virtual_file_mapping.push(filenames_index as u32);
- }
- debug!("Adding counter {:?} to map for {:?}", counter, region);
+ fn into_filenames_buffer(self) -> Vec<u8> {
+ // This method takes `self` so that the caller can't accidentally
+ // modify the original file table after encoding it into a buffer.
+
+ llvm::build_byte_buffer(|buffer| {
+ coverageinfo::write_filenames_section_to_buffer(
+ self.global_file_table.iter().map(Symbol::as_str),
+ buffer,
+ );
+ })
+ }
+}
+
+/// Using the expressions and counter regions collected for a single function,
+/// generate the variable-sized payload of its corresponding `__llvm_covfun`
+/// entry. The payload is returned as a vector of bytes.
+///
+/// Newly-encountered filenames will be added to the global file table.
+fn encode_mappings_for_function(
+ global_file_table: &mut GlobalFileTable,
+ function_coverage: &FunctionCoverage<'_>,
+) -> Vec<u8> {
+ let (expressions, counter_regions) = function_coverage.get_expressions_and_counter_regions();
+
+ let mut counter_regions = counter_regions.collect::<Vec<_>>();
+ if counter_regions.is_empty() {
+ return Vec::new();
+ }
+
+ let mut virtual_file_mapping = IndexVec::<u32, u32>::new();
+ let mut mapping_regions = Vec::with_capacity(counter_regions.len());
+
+ // Sort the list of (counter, region) mapping pairs by region, so that they
+ // can be grouped by filename. Prepare file IDs for each filename, and
+ // prepare the mapping data so that we can pass it through FFI to LLVM.
+ counter_regions.sort_by_key(|(_counter, region)| *region);
+ for counter_regions_for_file in
+ counter_regions.group_by(|(_, a), (_, b)| a.file_name == b.file_name)
+ {
+ // Look up (or allocate) the global file ID for this filename.
+ let file_name = counter_regions_for_file[0].1.file_name;
+ let global_file_id = global_file_table.global_file_id_for_file_name(file_name);
+
+ // Associate that global file ID with a local file ID for this function.
+ let local_file_id: u32 = virtual_file_mapping.push(global_file_id);
+ debug!(" file id: local {local_file_id} => global {global_file_id} = '{file_name:?}'");
+
+ // For each counter/region pair in this function+file, convert it to a
+ // form suitable for FFI.
+ for &(counter, region) in counter_regions_for_file {
+ let CodeRegion { file_name: _, start_line, start_col, end_line, end_col } = *region;
+
+ debug!("Adding counter {counter:?} to map for {region:?}");
mapping_regions.push(CounterMappingRegion::code_region(
counter,
- current_file_id,
+ local_file_id,
start_line,
start_col,
end_line,
end_col,
));
}
+ }
- // Encode and append the current function's coverage mapping data
+ // Encode the function's coverage mappings into a buffer.
+ llvm::build_byte_buffer(|buffer| {
coverageinfo::write_mapping_to_buffer(
- virtual_file_mapping,
+ virtual_file_mapping.raw,
expressions,
mapping_regions,
- coverage_mapping_buffer,
+ buffer,
);
- }
+ })
+}
- /// Construct coverage map header and the array of function records, and combine them into the
- /// coverage map. Save the coverage map data into the LLVM IR as a static global using a
- /// specific, well-known section and name.
- fn generate_coverage_map<'ll>(
- self,
- cx: &CodegenCx<'ll, '_>,
- version: u32,
- filenames_size: usize,
- filenames_val: &'ll llvm::Value,
- ) -> &'ll llvm::Value {
- debug!("cov map: filenames_size = {}, 0-based version = {}", filenames_size, version);
-
- // Create the coverage data header (Note, fields 0 and 2 are now always zero,
- // as of `llvm::coverage::CovMapVersion::Version4`.)
- let zero_was_n_records_val = cx.const_u32(0);
- let filenames_size_val = cx.const_u32(filenames_size as u32);
- let zero_was_coverage_size_val = cx.const_u32(0);
- let version_val = cx.const_u32(version);
- let cov_data_header_val = cx.const_struct(
- &[zero_was_n_records_val, filenames_size_val, zero_was_coverage_size_val, version_val],
- /*packed=*/ false,
- );
+/// Construct coverage map header and the array of function records, and combine them into the
+/// coverage map. Save the coverage map data into the LLVM IR as a static global using a
+/// specific, well-known section and name.
+fn generate_coverage_map<'ll>(
+ cx: &CodegenCx<'ll, '_>,
+ version: u32,
+ filenames_size: usize,
+ filenames_val: &'ll llvm::Value,
+) -> &'ll llvm::Value {
+ debug!("cov map: filenames_size = {}, 0-based version = {}", filenames_size, version);
+
+ // Create the coverage data header (Note, fields 0 and 2 are now always zero,
+ // as of `llvm::coverage::CovMapVersion::Version4`.)
+ let zero_was_n_records_val = cx.const_u32(0);
+ let filenames_size_val = cx.const_u32(filenames_size as u32);
+ let zero_was_coverage_size_val = cx.const_u32(0);
+ let version_val = cx.const_u32(version);
+ let cov_data_header_val = cx.const_struct(
+ &[zero_was_n_records_val, filenames_size_val, zero_was_coverage_size_val, version_val],
+ /*packed=*/ false,
+ );
- // Create the complete LLVM coverage data value to add to the LLVM IR
- cx.const_struct(&[cov_data_header_val, filenames_val], /*packed=*/ false)
- }
+ // Create the complete LLVM coverage data value to add to the LLVM IR
+ cx.const_struct(&[cov_data_header_val, filenames_val], /*packed=*/ false)
}
/// Construct a function record and combine it with the function's coverage mapping data.
@@ -317,10 +333,10 @@ fn add_unused_functions(cx: &CodegenCx<'_, '_>) {
{
let codegen_fn_attrs = tcx.codegen_fn_attrs(non_codegenned_def_id);
- // If a function is marked `#[no_coverage]`, then skip generating a
+ // If a function is marked `#[coverage(off)]`, then skip generating a
// dead code stub for it.
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NO_COVERAGE) {
- debug!("skipping unused fn marked #[no_coverage]: {:?}", non_codegenned_def_id);
+ debug!("skipping unused fn marked #[coverage(off)]: {:?}", non_codegenned_def_id);
continue;
}
diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs
index 621fd36b2..c70cb670e 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, CoverageKind};
use rustc_middle::mir::Coverage;
use rustc_middle::ty;
use rustc_middle::ty::layout::{FnAbiOf, HasTyCtxt};
@@ -104,144 +104,67 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> {
fn add_coverage(&mut self, instance: Instance<'tcx>, coverage: &Coverage) {
let bx = self;
+ let Some(coverage_context) = bx.coverage_context() else { return };
+ let mut coverage_map = coverage_context.function_coverage_map.borrow_mut();
+ let func_coverage = coverage_map
+ .entry(instance)
+ .or_insert_with(|| FunctionCoverage::new(bx.tcx(), instance));
+
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!(
+ "ensuring function source hash is set for instance={:?}; function_source_hash={}",
+ instance, function_source_hash,
+ );
+ func_coverage.set_function_source_hash(function_source_hash);
+
+ 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.
debug!(
- "codegen intrinsic instrprof.increment(fn_name={:?}, hash={:?}, num_counters={:?}, index={:?})",
- fn_name, hash, num_counters, index,
+ "adding counter to coverage_map: instance={:?}, id={:?}, region={:?}",
+ instance, id, code_region,
);
- bx.instrprof_increment(fn_name, hash, num_counters, index);
+ func_coverage.add_counter(id, code_region);
}
+ // We need to explicitly drop the `RefMut` before calling into `instrprof_increment`,
+ // as that needs an exclusive borrow.
+ drop(coverage_map);
+
+ 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);
+ debug!(
+ "adding counter expression to coverage_map: instance={:?}, id={:?}, {:?} {:?} {:?}; region: {:?}",
+ instance, id, lhs, op, rhs, code_region,
+ );
+ func_coverage.add_counter_expression(id, lhs, op, rhs, code_region);
}
CoverageKind::Unreachable => {
- bx.add_coverage_unreachable(
- instance,
- code_region.expect("unreachable regions always have code regions"),
+ let code_region =
+ code_region.expect("unreachable regions always have code regions");
+ debug!(
+ "adding unreachable code to coverage_map: instance={:?}, at {:?}",
+ instance, code_region,
);
+ func_coverage.add_unreachable_region(code_region);
}
}
}
}
-// 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
- .entry(instance)
- .or_insert_with(|| FunctionCoverage::new(self.tcx, instance))
- .set_function_source_hash(function_source_hash);
- true
- } else {
- false
- }
- }
-
- /// 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,
- );
- 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
- }
- }
-
- /// 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<CodeRegion>,
- ) -> 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
- }
- }
-
- /// 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
- }
- }
-}
-
fn declare_unused_fn<'tcx>(cx: &CodegenCx<'_, 'tcx>, def_id: DefId) -> Instance<'tcx> {
let tcx = cx.tcx;