1
Fork 0

Responded to all feedback as of 2020-10-30

This commit is contained in:
Rich Kadel 2020-10-30 16:09:05 -07:00
parent 1973f84ebb
commit a7d956583c
9 changed files with 367 additions and 208 deletions

View file

@ -12,16 +12,6 @@ use rustc_data_structures::graph::WithNumNodes;
use rustc_index::bit_set::BitSet; use rustc_index::bit_set::BitSet;
use rustc_middle::mir::coverage::*; use rustc_middle::mir::coverage::*;
// When evaluating an expression operand to determine if it references a `Counter` or an
// `Expression`, the range of counter or expression IDs must be known in order to answer the
// question: "Does this ID fall inside the range of counters," for example. If "yes," the ID refers
// to a counter, otherwise the ID refers to an expression.
//
// But in situations where the range is not currently known, the only fallback is to assume a
// specific range limit. `MAX_COUNTER_GUARD` enforces a limit on the number of counters, and
// therefore a limit on the range of counter IDs.
pub(crate) const MAX_COUNTER_GUARD: u32 = (u32::MAX / 2) + 1;
/// Manages the counter and expression indexes/IDs to generate `CoverageKind` components for MIR /// Manages the counter and expression indexes/IDs to generate `CoverageKind` components for MIR
/// `Coverage` statements. /// `Coverage` statements.
pub(crate) struct CoverageCounters { pub(crate) struct CoverageCounters {
@ -105,7 +95,6 @@ impl CoverageCounters {
/// Counter IDs start from one and go up. /// Counter IDs start from one and go up.
fn next_counter(&mut self) -> CounterValueReference { fn next_counter(&mut self) -> CounterValueReference {
assert!(self.next_counter_id < u32::MAX - self.num_expressions); assert!(self.next_counter_id < u32::MAX - self.num_expressions);
assert!(self.next_counter_id <= MAX_COUNTER_GUARD);
let next = self.next_counter_id; let next = self.next_counter_id;
self.next_counter_id += 1; self.next_counter_id += 1;
CounterValueReference::from(next) CounterValueReference::from(next)
@ -131,6 +120,7 @@ struct BcbCounters<'a> {
basic_coverage_blocks: &'a mut CoverageGraph, basic_coverage_blocks: &'a mut CoverageGraph,
} }
// FIXME(richkadel): Add unit tests for `BcbCounters` functions/algorithms.
impl<'a> BcbCounters<'a> { impl<'a> BcbCounters<'a> {
fn new( fn new(
coverage_counters: &'a mut CoverageCounters, coverage_counters: &'a mut CoverageCounters,
@ -139,7 +129,7 @@ impl<'a> BcbCounters<'a> {
Self { coverage_counters, basic_coverage_blocks } Self { coverage_counters, basic_coverage_blocks }
} }
/// If two `CoverageGraph` branch from another `BasicCoverageBlock`, one of the branches /// If two `BasicCoverageBlock`s branch from another `BasicCoverageBlock`, one of the branches
/// can be counted by `Expression` by subtracting the other branch from the branching /// can be counted by `Expression` by subtracting the other branch from the branching
/// block. Otherwise, the `BasicCoverageBlock` executed the least should have the `Counter`. /// block. Otherwise, the `BasicCoverageBlock` executed the least should have the `Counter`.
/// One way to predict which branch executes the least is by considering loops. A loop is exited /// One way to predict which branch executes the least is by considering loops. A loop is exited
@ -162,10 +152,16 @@ impl<'a> BcbCounters<'a> {
bcbs_with_coverage.insert(covspan.bcb); bcbs_with_coverage.insert(covspan.bcb);
} }
// FIXME(richkadel): Add more comments to explain the logic here and in the rest of this // Walk the `CoverageGraph`. For each `BasicCoverageBlock` node with an associated
// function, and refactor this function to break it up into smaller functions that are // `CoverageSpan`, add a counter. If the `BasicCoverageBlock` branches, add a counter or
// easier to understand. // expression to each branch `BasicCoverageBlock` (if the branch BCB has only one incoming
// edge) or edge from the branching BCB to the branch BCB (if the branch BCB has multiple
// incoming edges).
//
// The `TraverseCoverageGraphWithLoops` traversal ensures that, when a loop is encountered,
// all `BasicCoverageBlock` nodes in the loop are visited before visiting any node outside
// the loop. The `traversal` state includes a `context_stack`, providing a way to know if
// the current BCB is in one or more nested loops or not.
let mut traversal = TraverseCoverageGraphWithLoops::new(&self.basic_coverage_blocks); let mut traversal = TraverseCoverageGraphWithLoops::new(&self.basic_coverage_blocks);
while let Some(bcb) = traversal.next(self.basic_coverage_blocks) { while let Some(bcb) = traversal.next(self.basic_coverage_blocks) {
if bcbs_with_coverage.contains(bcb) { if bcbs_with_coverage.contains(bcb) {
@ -220,11 +216,20 @@ impl<'a> BcbCounters<'a> {
.join("\n "), .join("\n "),
); );
// Use the `traversal` state to decide if a subset of the branches exit a loop, making it
// likely that branch is executed less than branches that do not exit the same loop. In this
// case, any branch that does not exit the loop (and has not already been assigned a
// counter) should be counted by expression, if possible. (If a preferred expression branch
// is not selected based on the loop context, select any branch without an existing
// counter.)
let expression_branch = self.choose_preferred_expression_branch(traversal, &branches); let expression_branch = self.choose_preferred_expression_branch(traversal, &branches);
// Assign a Counter or Expression to each branch, plus additional
// `Expression`s, as needed, to sum up intermediate results. // Assign a Counter or Expression to each branch, plus additional `Expression`s, as needed,
// to sum up intermediate results.
let mut some_sumup_counter_operand = None; let mut some_sumup_counter_operand = None;
for branch in branches { for branch in branches {
// Skip the selected `expression_branch`, if any. It's expression will be assigned after
// all others.
if branch != expression_branch { if branch != expression_branch {
let branch_counter_operand = if branch.is_only_path_to_target() { let branch_counter_operand = if branch.is_only_path_to_target() {
debug!( debug!(
@ -263,6 +268,9 @@ impl<'a> BcbCounters<'a> {
} }
} }
} }
// Assign the final expression to the `expression_branch` by subtracting the total of all
// other branches from the counter of the branching BCB.
let sumup_counter_operand = let sumup_counter_operand =
some_sumup_counter_operand.expect("sumup_counter_operand should have a value"); some_sumup_counter_operand.expect("sumup_counter_operand should have a value");
debug!( debug!(
@ -301,7 +309,7 @@ impl<'a> BcbCounters<'a> {
collect_intermediate_expressions: &mut Vec<CoverageKind>, collect_intermediate_expressions: &mut Vec<CoverageKind>,
debug_indent_level: usize, debug_indent_level: usize,
) -> Result<ExpressionOperandId, Error> { ) -> Result<ExpressionOperandId, Error> {
Ok({ // If the BCB already has a counter, return it.
if let Some(counter_kind) = self.basic_coverage_blocks[bcb].counter() { if let Some(counter_kind) = self.basic_coverage_blocks[bcb].counter() {
debug!( debug!(
"{}{:?} already has a counter: {}", "{}{:?} already has a counter: {}",
@ -309,12 +317,15 @@ impl<'a> BcbCounters<'a> {
bcb, bcb,
self.format_counter(counter_kind), self.format_counter(counter_kind),
); );
counter_kind.as_operand_id() return Ok(counter_kind.as_operand_id());
} else { }
// A BCB with only one incoming edge gets a simple `Counter` (via `make_counter()`).
// Also, a BCB that loops back to itself gets a simple `Counter`. This may indicate the
// program results in a tight infinite loop, but it should still compile.
let one_path_to_target = self.bcb_has_one_path_to_target(bcb); let one_path_to_target = self.bcb_has_one_path_to_target(bcb);
if one_path_to_target || self.bcb_predecessors(bcb).contains(&bcb) { if one_path_to_target || self.bcb_predecessors(bcb).contains(&bcb) {
let counter_kind = let counter_kind = self.coverage_counters.make_counter(|| Some(format!("{:?}", bcb)));
self.coverage_counters.make_counter(|| Some(format!("{:?}", bcb)));
if one_path_to_target { if one_path_to_target {
debug!( debug!(
"{}{:?} gets a new counter: {}", "{}{:?} gets a new counter: {}",
@ -325,24 +336,27 @@ impl<'a> BcbCounters<'a> {
} else { } else {
debug!( debug!(
"{}{:?} has itself as its own predecessor. It can't be part of its own \ "{}{:?} has itself as its own predecessor. It can't be part of its own \
Expression sum, so it will get its own new counter: {}. (Note, the \ Expression sum, so it will get its own new counter: {}. (Note, the compiled \
compiled code will generate an infinite loop.)", code will generate an infinite loop.)",
NESTED_INDENT.repeat(debug_indent_level), NESTED_INDENT.repeat(debug_indent_level),
bcb, bcb,
self.format_counter(&counter_kind), self.format_counter(&counter_kind),
); );
} }
self.basic_coverage_blocks[bcb].set_counter(counter_kind)? return self.basic_coverage_blocks[bcb].set_counter(counter_kind);
} else { }
// A BCB with multiple incoming edges can compute its count by `Expression`, summing up the
// counters and/or expressions of its incoming edges. This will recursively get or create
// counters for those incoming edges first, then call `make_expression()` to sum them up,
// with additional intermediate expressions as needed.
let mut predecessors = self.bcb_predecessors(bcb).clone().into_iter(); let mut predecessors = self.bcb_predecessors(bcb).clone().into_iter();
debug!( debug!(
"{}{:?} has multiple incoming edges and will get an expression that sums \ "{}{:?} has multiple incoming edges and will get an expression that sums them up...",
them up...",
NESTED_INDENT.repeat(debug_indent_level), NESTED_INDENT.repeat(debug_indent_level),
bcb, bcb,
); );
let first_edge_counter_operand = self let first_edge_counter_operand = self.recursive_get_or_make_edge_counter_operand(
.recursive_get_or_make_edge_counter_operand(
predecessors.next().unwrap(), predecessors.next().unwrap(),
bcb, bcb,
collect_intermediate_expressions, collect_intermediate_expressions,
@ -350,8 +364,7 @@ impl<'a> BcbCounters<'a> {
)?; )?;
let mut some_sumup_edge_counter_operand = None; let mut some_sumup_edge_counter_operand = None;
for predecessor in predecessors { for predecessor in predecessors {
let edge_counter_operand = self let edge_counter_operand = self.recursive_get_or_make_edge_counter_operand(
.recursive_get_or_make_edge_counter_operand(
predecessor, predecessor,
bcb, bcb,
collect_intermediate_expressions, collect_intermediate_expressions,
@ -371,11 +384,9 @@ impl<'a> BcbCounters<'a> {
NESTED_INDENT.repeat(debug_indent_level), NESTED_INDENT.repeat(debug_indent_level),
self.format_counter(&intermediate_expression) self.format_counter(&intermediate_expression)
); );
let intermediate_expression_operand = let intermediate_expression_operand = intermediate_expression.as_operand_id();
intermediate_expression.as_operand_id();
collect_intermediate_expressions.push(intermediate_expression); collect_intermediate_expressions.push(intermediate_expression);
some_sumup_edge_counter_operand some_sumup_edge_counter_operand.replace(intermediate_expression_operand);
.replace(intermediate_expression_operand);
} }
} }
let counter_kind = self.coverage_counters.make_expression( let counter_kind = self.coverage_counters.make_expression(
@ -390,10 +401,7 @@ impl<'a> BcbCounters<'a> {
bcb, bcb,
self.format_counter(&counter_kind) self.format_counter(&counter_kind)
); );
self.basic_coverage_blocks[bcb].set_counter(counter_kind)? self.basic_coverage_blocks[bcb].set_counter(counter_kind)
}
}
})
} }
fn get_or_make_edge_counter_operand( fn get_or_make_edge_counter_operand(
@ -417,12 +425,19 @@ impl<'a> BcbCounters<'a> {
collect_intermediate_expressions: &mut Vec<CoverageKind>, collect_intermediate_expressions: &mut Vec<CoverageKind>,
debug_indent_level: usize, debug_indent_level: usize,
) -> Result<ExpressionOperandId, Error> { ) -> Result<ExpressionOperandId, Error> {
Ok({ // If the source BCB has only one successor (assumed to be the given target), an edge
// counter is unnecessary. Just get or make a counter for the source BCB.
let successors = self.bcb_successors(from_bcb).iter(); let successors = self.bcb_successors(from_bcb).iter();
if successors.len() > 1 { if successors.len() == 1 {
if let Some(counter_kind) = return self.recursive_get_or_make_counter_operand(
self.basic_coverage_blocks[to_bcb].edge_counter_from(from_bcb) from_bcb,
{ collect_intermediate_expressions,
debug_indent_level + 1,
);
}
// If the edge already has a counter, return it.
if let Some(counter_kind) = self.basic_coverage_blocks[to_bcb].edge_counter_from(from_bcb) {
debug!( debug!(
"{}Edge {:?}->{:?} already has a counter: {}", "{}Edge {:?}->{:?} already has a counter: {}",
NESTED_INDENT.repeat(debug_indent_level), NESTED_INDENT.repeat(debug_indent_level),
@ -430,11 +445,12 @@ impl<'a> BcbCounters<'a> {
to_bcb, to_bcb,
self.format_counter(counter_kind) self.format_counter(counter_kind)
); );
counter_kind.as_operand_id() return Ok(counter_kind.as_operand_id());
} else { }
let counter_kind = self
.coverage_counters // Make a new counter to count this edge.
.make_counter(|| Some(format!("{:?}->{:?}", from_bcb, to_bcb))); let counter_kind =
self.coverage_counters.make_counter(|| Some(format!("{:?}->{:?}", from_bcb, to_bcb)));
debug!( debug!(
"{}Edge {:?}->{:?} gets a new counter: {}", "{}Edge {:?}->{:?} gets a new counter: {}",
NESTED_INDENT.repeat(debug_indent_level), NESTED_INDENT.repeat(debug_indent_level),
@ -442,21 +458,11 @@ impl<'a> BcbCounters<'a> {
to_bcb, to_bcb,
self.format_counter(&counter_kind) self.format_counter(&counter_kind)
); );
self.basic_coverage_blocks[to_bcb] self.basic_coverage_blocks[to_bcb].set_edge_counter_from(from_bcb, counter_kind)
.set_edge_counter_from(from_bcb, counter_kind)?
}
} else {
self.recursive_get_or_make_counter_operand(
from_bcb,
collect_intermediate_expressions,
debug_indent_level + 1,
)?
}
})
} }
/// Select a branch for the expression, either the recommended `reloop_branch`, or /// Select a branch for the expression, either the recommended `reloop_branch`, or if none was
/// if none was found, select any branch. /// found, select any branch.
fn choose_preferred_expression_branch( fn choose_preferred_expression_branch(
&self, &self,
traversal: &TraverseCoverageGraphWithLoops, traversal: &TraverseCoverageGraphWithLoops,
@ -493,9 +499,8 @@ impl<'a> BcbCounters<'a> {
} }
} }
/// At most one of the branches (or its edge, from the branching_bcb, /// At most, one of the branches (or its edge, from the branching_bcb, if the branch has
/// if the branch has multiple incoming edges) can have a counter computed by /// multiple incoming edges) can have a counter computed by expression.
/// expression.
/// ///
/// If at least one of the branches leads outside of a loop (`found_loop_exit` is /// If at least one of the branches leads outside of a loop (`found_loop_exit` is
/// true), and at least one other branch does not exit the loop (the first of which /// true), and at least one other branch does not exit the loop (the first of which

View file

@ -1,3 +1,113 @@
//! The `InstrumentCoverage` MIR pass implementation includes debugging tools and options
//! to help developers understand and/or improve the analysis and instrumentation of a MIR.
//!
//! To enable coverage, include the rustc command line option:
//!
//! * `-Z instrument-coverage`
//!
//! MIR Dump Files, with additional `CoverageGraph` graphviz and `CoverageSpan` spanview
//! ------------------------------------------------------------------------------------
//!
//! Additional debugging options include:
//!
//! * `-Z dump-mir=InstrumentCoverage` - Generate `.mir` files showing the state of the MIR,
//! before and after the `InstrumentCoverage` pass, for each compiled function.
//!
//! * `-Z dump-mir-graphviz` - If `-Z dump-mir` is also enabled for the current MIR node path,
//! each MIR dump is accompanied by a before-and-after graphical view of the MIR, in Graphviz
//! `.dot` file format (which can be visually rendered as a graph using any of a number of free
//! Graphviz viewers and IDE extensions).
//!
//! For the `InstrumentCoverage` pass, this option also enables generation of an additional
//! Graphviz `.dot` file for each function, rendering the `CoverageGraph`: the control flow
//! graph (CFG) of `BasicCoverageBlocks` (BCBs), as nodes, internally labeled to show the
//! `CoverageSpan`-based MIR elements each BCB represents (`BasicBlock`s, `Statement`s and
//! `Terminator`s), assigned coverage counters and/or expressions, and edge counters, as needed.
//!
//! (Note the additional option, `-Z graphviz-dark-mode`, can be added, to change the rendered
//! output from its default black-on-white background to a dark color theme, if desired.)
//!
//! * `-Z dump-mir-spanview` - If `-Z dump-mir` is also enabled for the current MIR node path,
//! each MIR dump is accompanied by a before-and-after `.html` document showing the function's
//! original source code, highlighted by it's MIR spans, at the `statement`-level (by default),
//! `terminator` only, or encompassing span for the `Terminator` plus all `Statement`s, in each
//! `block` (`BasicBlock`).
//!
//! For the `InstrumentCoverage` pass, this option also enables generation of an additional
//! spanview `.html` file for each function, showing the aggregated `CoverageSpan`s that will
//! require counters (or counter expressions) for accurate coverage analysis.
//!
//! Debug Logging
//! -------------
//!
//! The `InstrumentCoverage` pass includes debug logging messages at various phases and decision
//! points, which can be enabled via environment variable:
//!
//! ```shell
//! RUSTC_LOG=rustc_mir::transform::coverage=debug
//! ```
//!
//! Other module paths with coverage-related debug logs may also be of interest, particularly for
//! debugging the coverage map data, injected as global variables in the LLVM IR (during rustc's
//! code generation pass). For example:
//!
//! ```shell
//! RUSTC_LOG=rustc_mir::transform::coverage,rustc_codegen_ssa::coverageinfo,rustc_codegen_llvm::coverageinfo=debug
//! ```
//!
//! Coverage Debug Options
//! ---------------------------------
//!
//! Additional debugging options can be enabled using the environment variable:
//!
//! ```shell
//! RUSTC_COVERAGE_DEBUG_OPTIONS=<options>
//! ```
//!
//! These options are comma-separated, and specified in the format `option-name=value`. For example:
//!
//! ```shell
//! $ RUSTC_COVERAGE_DEBUG_OPTIONS=counter-format=id+operation,allow-unused-expressions=yes cargo build
//! ```
//!
//! Coverage debug options include:
//!
//! * `allow-unused-expressions=yes` or `no` (default: `no`)
//!
//! The `InstrumentCoverage` algorithms _should_ only create and assign expressions to a
//! `BasicCoverageBlock`, or an incoming edge, if that expression is either (a) required to
//! count a `CoverageSpan`, or (b) a dependency of some other required counter expression.
//!
//! If an expression is generated that does not map to a `CoverageSpan` or dependency, this
//! probably indicates there was a bug in the algorithm that creates and assigns counters
//! and expressions.
//!
//! When this kind of bug is encountered, the rustc compiler will panic by default. Setting:
//! `allow-unused-expressions=yes` will log a warning message instead of panicking (effectively
//! ignoring the unused expressions), which may be helpful when debugging the root cause of
//! the problem.
//!
//! * `counter-format=<choices>`, where `<choices>` can be any plus-separated combination of `id`,
//! `block`, and/or `operation` (default: `block+operation`)
//!
//! This option effects both the `CoverageGraph` (graphviz `.dot` files) and debug logging, when
//! generating labels for counters and expressions.
//!
//! Depending on the values and combinations, counters can be labeled by:
//!
//! * `id` - counter or expression ID (ascending counter IDs, starting at 1, or descending
//! expression IDs, starting at `u32:MAX`)
//! * `block` - the `BasicCoverageBlock` label (for example, `bcb0`) or edge label (for
//! example `bcb0->bcb1`), for counters or expressions assigned to count a
//! `BasicCoverageBlock` or edge. Intermediate expressions (not directly associated with
//! a BCB or edge) will be labeled by their expression ID, unless `operation` is also
//! specified.
//! * `operation` - applied to expressions only, labels include the left-hand-side counter
//! or expression label (lhs operand), the operator (`+` or `-`), and the right-hand-side
//! counter or expression (rhs operand). Expression operand labels are generated
//! recursively, generating labels with nested operations, enclosed in parentheses
//! (for example: `bcb2 + (bcb0 - bcb1)`).
use super::graph::{BasicCoverageBlock, BasicCoverageBlockData, CoverageGraph}; use super::graph::{BasicCoverageBlock, BasicCoverageBlockData, CoverageGraph};
use super::spans::CoverageSpan; use super::spans::CoverageSpan;
@ -20,13 +130,11 @@ const RUSTC_COVERAGE_DEBUG_OPTIONS: &str = "RUSTC_COVERAGE_DEBUG_OPTIONS";
pub(crate) fn debug_options<'a>() -> &'a DebugOptions { pub(crate) fn debug_options<'a>() -> &'a DebugOptions {
static DEBUG_OPTIONS: SyncOnceCell<DebugOptions> = SyncOnceCell::new(); static DEBUG_OPTIONS: SyncOnceCell<DebugOptions> = SyncOnceCell::new();
&DEBUG_OPTIONS.get_or_init(|| DebugOptions::new()) &DEBUG_OPTIONS.get_or_init(|| DebugOptions::from_env())
} }
/// Parses and maintains coverage-specific debug options captured from the environment variable /// Parses and maintains coverage-specific debug options captured from the environment variable
/// "RUSTC_COVERAGE_DEBUG_OPTIONS", if set. Options can be set on the command line by, for example: /// "RUSTC_COVERAGE_DEBUG_OPTIONS", if set.
///
/// $ RUSTC_COVERAGE_DEBUG_OPTIONS=counter-format=block,allow_unused_expressions=n cargo build
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct DebugOptions { pub(crate) struct DebugOptions {
pub allow_unused_expressions: bool, pub allow_unused_expressions: bool,
@ -34,7 +142,7 @@ pub(crate) struct DebugOptions {
} }
impl DebugOptions { impl DebugOptions {
fn new() -> Self { fn from_env() -> Self {
let mut allow_unused_expressions = true; let mut allow_unused_expressions = true;
let mut counter_format = ExpressionFormat::default(); let mut counter_format = ExpressionFormat::default();
@ -152,10 +260,11 @@ impl DebugCounters {
} }
pub fn enable(&mut self) { pub fn enable(&mut self) {
debug_assert!(!self.is_enabled());
self.some_counters.replace(FxHashMap::default()); self.some_counters.replace(FxHashMap::default());
} }
pub fn is_enabled(&mut self) -> bool { pub fn is_enabled(&self) -> bool {
self.some_counters.is_some() self.some_counters.is_some()
} }
@ -294,12 +403,13 @@ impl GraphvizData {
} }
pub fn enable(&mut self) { pub fn enable(&mut self) {
debug_assert!(!self.is_enabled());
self.some_bcb_to_coverage_spans_with_counters = Some(FxHashMap::default()); self.some_bcb_to_coverage_spans_with_counters = Some(FxHashMap::default());
self.some_bcb_to_dependency_counters = Some(FxHashMap::default()); self.some_bcb_to_dependency_counters = Some(FxHashMap::default());
self.some_edge_to_counter = Some(FxHashMap::default()); self.some_edge_to_counter = Some(FxHashMap::default());
} }
pub fn is_enabled(&mut self) -> bool { pub fn is_enabled(&self) -> bool {
self.some_bcb_to_coverage_spans_with_counters.is_some() self.some_bcb_to_coverage_spans_with_counters.is_some()
} }
@ -399,11 +509,12 @@ impl UsedExpressions {
} }
pub fn enable(&mut self) { pub fn enable(&mut self) {
debug_assert!(!self.is_enabled());
self.some_used_expression_operands = Some(FxHashMap::default()); self.some_used_expression_operands = Some(FxHashMap::default());
self.some_unused_expressions = Some(Vec::new()); self.some_unused_expressions = Some(Vec::new());
} }
pub fn is_enabled(&mut self) -> bool { pub fn is_enabled(&self) -> bool {
self.some_used_expression_operands.is_some() self.some_used_expression_operands.is_some()
} }
@ -416,7 +527,7 @@ impl UsedExpressions {
} }
} }
pub fn expression_is_used(&mut self, expression: &CoverageKind) -> bool { pub fn expression_is_used(&self, expression: &CoverageKind) -> bool {
if let Some(used_expression_operands) = self.some_used_expression_operands.as_ref() { if let Some(used_expression_operands) = self.some_used_expression_operands.as_ref() {
used_expression_operands.contains_key(&expression.as_operand_id()) used_expression_operands.contains_key(&expression.as_operand_id())
} else { } else {

View file

@ -82,6 +82,8 @@ impl CoverageGraph {
// each block terminator's `successors()`. Coverage spans must map to actual source code, // each block terminator's `successors()`. Coverage spans must map to actual source code,
// so compiler generated blocks and paths can be ignored. To that end, the CFG traversal // so compiler generated blocks and paths can be ignored. To that end, the CFG traversal
// intentionally omits unwind paths. // intentionally omits unwind paths.
// FIXME(#78544): MIR InstrumentCoverage: Improve coverage of `#[should_panic]` tests and
// `catch_unwind()` handlers.
let mir_cfg_without_unwind = ShortCircuitPreorder::new(&mir_body, bcb_filtered_successors); let mir_cfg_without_unwind = ShortCircuitPreorder::new(&mir_body, bcb_filtered_successors);
let mut basic_blocks = Vec::new(); let mut basic_blocks = Vec::new();
@ -288,7 +290,8 @@ rustc_index::newtype_index! {
/// * The BCB CFG ignores (trims) branches not relevant to coverage, such as unwind-related code, /// * The BCB CFG ignores (trims) branches not relevant to coverage, such as unwind-related code,
/// that is injected by the Rust compiler but has no physical source code to count. This also /// that is injected by the Rust compiler but has no physical source code to count. This also
/// means a BasicBlock with a `Call` terminator can be merged into its primary successor target /// means a BasicBlock with a `Call` terminator can be merged into its primary successor target
/// block, in the same BCB. /// block, in the same BCB. (But, note: Issue #78544: "MIR InstrumentCoverage: Improve coverage
/// of `#[should_panic]` tests and `catch_unwind()` handlers")
/// * Some BasicBlock terminators support Rust-specific concerns--like borrow-checking--that are /// * Some BasicBlock terminators support Rust-specific concerns--like borrow-checking--that are
/// not relevant to coverage analysis. `FalseUnwind`, for example, can be treated the same as /// not relevant to coverage analysis. `FalseUnwind`, for example, can be treated the same as
/// a `Goto`, and merged with its successor into the same BCB. /// a `Goto`, and merged with its successor into the same BCB.
@ -329,7 +332,6 @@ impl BasicCoverageBlockData {
&mir_body[self.last_bb()].terminator() &mir_body[self.last_bb()].terminator()
} }
#[inline(always)]
pub fn set_counter( pub fn set_counter(
&mut self, &mut self,
counter_kind: CoverageKind, counter_kind: CoverageKind,
@ -342,17 +344,16 @@ impl BasicCoverageBlockData {
"attempt to add a `Counter` to a BCB target with existing incoming edge counters" "attempt to add a `Counter` to a BCB target with existing incoming edge counters"
); );
let operand = counter_kind.as_operand_id(); let operand = counter_kind.as_operand_id();
let expect_none = self.counter_kind.replace(counter_kind); if let Some(replaced) = self.counter_kind.replace(counter_kind) {
if expect_none.is_some() { Error::from_string(format!(
return Error::from_string(format!(
"attempt to set a BasicCoverageBlock coverage counter more than once; \ "attempt to set a BasicCoverageBlock coverage counter more than once; \
{:?} already had counter {:?}", {:?} already had counter {:?}",
self, self, replaced,
expect_none.unwrap(), ))
)); } else {
}
Ok(operand) Ok(operand)
} }
}
#[inline(always)] #[inline(always)]
pub fn counter(&self) -> Option<&CoverageKind> { pub fn counter(&self) -> Option<&CoverageKind> {
@ -364,7 +365,6 @@ impl BasicCoverageBlockData {
self.counter_kind.take() self.counter_kind.take()
} }
#[inline(always)]
pub fn set_edge_counter_from( pub fn set_edge_counter_from(
&mut self, &mut self,
from_bcb: BasicCoverageBlock, from_bcb: BasicCoverageBlock,
@ -383,22 +383,22 @@ impl BasicCoverageBlockData {
} }
} }
let operand = counter_kind.as_operand_id(); let operand = counter_kind.as_operand_id();
let expect_none = self if let Some(replaced) = self
.edge_from_bcbs .edge_from_bcbs
.get_or_insert_with(|| FxHashMap::default()) .get_or_insert_with(|| FxHashMap::default())
.insert(from_bcb, counter_kind); .insert(from_bcb, counter_kind)
if expect_none.is_some() { {
return Error::from_string(format!( Error::from_string(format!(
"attempt to set an edge counter more than once; from_bcb: \ "attempt to set an edge counter more than once; from_bcb: \
{:?} already had counter {:?}", {:?} already had counter {:?}",
from_bcb, from_bcb, replaced,
expect_none.unwrap(), ))
)); } else {
}
Ok(operand) Ok(operand)
} }
}
#[inline(always)] #[inline]
pub fn edge_counter_from(&self, from_bcb: BasicCoverageBlock) -> Option<&CoverageKind> { pub fn edge_counter_from(&self, from_bcb: BasicCoverageBlock) -> Option<&CoverageKind> {
if let Some(edge_from_bcbs) = &self.edge_from_bcbs { if let Some(edge_from_bcbs) = &self.edge_from_bcbs {
edge_from_bcbs.get(&from_bcb) edge_from_bcbs.get(&from_bcb)
@ -407,7 +407,7 @@ impl BasicCoverageBlockData {
} }
} }
#[inline(always)] #[inline]
pub fn take_edge_counters( pub fn take_edge_counters(
&mut self, &mut self,
) -> Option<impl Iterator<Item = (BasicCoverageBlock, CoverageKind)>> { ) -> Option<impl Iterator<Item = (BasicCoverageBlock, CoverageKind)>> {
@ -476,6 +476,9 @@ impl std::fmt::Debug for BcbBranch {
} }
} }
// Returns the `Terminator`s non-unwind successors.
// FIXME(#78544): MIR InstrumentCoverage: Improve coverage of `#[should_panic]` tests and
// `catch_unwind()` handlers.
fn bcb_filtered_successors<'a, 'tcx>( fn bcb_filtered_successors<'a, 'tcx>(
body: &'tcx &'a mir::Body<'tcx>, body: &'tcx &'a mir::Body<'tcx>,
term_kind: &'tcx TerminatorKind<'tcx>, term_kind: &'tcx TerminatorKind<'tcx>,
@ -495,6 +498,7 @@ fn bcb_filtered_successors<'a, 'tcx>(
/// Maintains separate worklists for each loop in the BasicCoverageBlock CFG, plus one for the /// Maintains separate worklists for each loop in the BasicCoverageBlock CFG, plus one for the
/// CoverageGraph outside all loops. This supports traversing the BCB CFG in a way that /// CoverageGraph outside all loops. This supports traversing the BCB CFG in a way that
/// ensures a loop is completely traversed before processing Blocks after the end of the loop. /// ensures a loop is completely traversed before processing Blocks after the end of the loop.
// FIXME(richkadel): Add unit tests for TraversalContext.
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct TraversalContext { pub(crate) struct TraversalContext {
/// From one or more backedges returning to a loop header. /// From one or more backedges returning to a loop header.
@ -644,7 +648,27 @@ fn find_loop_backedges(
let num_bcbs = basic_coverage_blocks.num_nodes(); let num_bcbs = basic_coverage_blocks.num_nodes();
let mut backedges = IndexVec::from_elem_n(Vec::<BasicCoverageBlock>::new(), num_bcbs); let mut backedges = IndexVec::from_elem_n(Vec::<BasicCoverageBlock>::new(), num_bcbs);
// Identify loops by their backedges // Identify loops by their backedges.
//
// The computational complexity is bounded by: n(s) x d where `n` is the number of
// `BasicCoverageBlock` nodes (the simplified/reduced representation of the CFG derived from the
// MIR); `s` is the average number of successors per node (which is most likely less than 2, and
// independent of the size of the function, so it can be treated as a constant);
// and `d` is the average number of dominators per node.
//
// The average number of dominators depends on the size and complexity of the function, and
// nodes near the start of the function's control flow graph typically have less dominators
// than nodes near the end of the CFG. Without doing a detailed mathematical analysis, I
// think the resulting complexity has the characteristics of O(n log n).
//
// The overall complexity appears to be comparable to many other MIR transform algorithms, and I
// don't expect that this function is creating a performance hot spot, but if this becomes an
// issue, there may be ways to optimize the `is_dominated_by` algorithm (as indicated by an
// existing `FIXME` comment in that code), or possibly ways to optimize it's usage here, perhaps
// by keeping track of results for visited `BasicCoverageBlock`s if they can be used to short
// circuit downstream `is_dominated_by` checks.
//
// For now, that kind of optimization seems unnecessarily complicated.
for (bcb, _) in basic_coverage_blocks.iter_enumerated() { for (bcb, _) in basic_coverage_blocks.iter_enumerated() {
for &successor in &basic_coverage_blocks.successors[bcb] { for &successor in &basic_coverage_blocks.successors[bcb] {
if basic_coverage_blocks.is_dominated_by(bcb, successor) { if basic_coverage_blocks.is_dominated_by(bcb, successor) {

View file

@ -74,9 +74,6 @@ impl<'tcx> MirPass<'tcx> for InstrumentCoverage {
trace!("InstrumentCoverage skipped for {:?} (not an FnLikeNode)", mir_source.def_id()); trace!("InstrumentCoverage skipped for {:?} (not an FnLikeNode)", mir_source.def_id());
return; return;
} }
// FIXME(richkadel): By comparison, the MIR pass `ConstProp` includes associated constants,
// with functions, methods, and closures. I assume Miri is used for associated constants as
// well. If not, we may need to include them here too.
trace!("InstrumentCoverage starting for {:?}", mir_source.def_id()); trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());
Instrumentor::new(&self.name(), tcx, mir_body).inject_counters(); Instrumentor::new(&self.name(), tcx, mir_body).inject_counters();
@ -121,7 +118,10 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
let mut graphviz_data = debug::GraphvizData::new(); let mut graphviz_data = debug::GraphvizData::new();
let mut debug_used_expressions = debug::UsedExpressions::new(); let mut debug_used_expressions = debug::UsedExpressions::new();
let dump_graphviz = tcx.sess.opts.debugging_opts.dump_mir_graphviz; let dump_mir = pretty::dump_enabled(tcx, self.pass_name, def_id);
let dump_graphviz = dump_mir && tcx.sess.opts.debugging_opts.dump_mir_graphviz;
let dump_spanview = dump_mir && tcx.sess.opts.debugging_opts.dump_mir_spanview.is_some();
if dump_graphviz { if dump_graphviz {
graphviz_data.enable(); graphviz_data.enable();
self.coverage_counters.enable_debug(); self.coverage_counters.enable_debug();
@ -139,7 +139,7 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
&self.basic_coverage_blocks, &self.basic_coverage_blocks,
); );
if pretty::dump_enabled(tcx, self.pass_name, def_id) { if dump_spanview {
debug::dump_coverage_spanview( debug::dump_coverage_spanview(
tcx, tcx,
self.mir_body, self.mir_body,
@ -174,6 +174,13 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
//////////////////////////////////////////////////// ////////////////////////////////////////////////////
// Remove the counter or edge counter from of each `CoverageSpan`s associated // Remove the counter or edge counter from of each `CoverageSpan`s associated
// `BasicCoverageBlock`, and inject a `Coverage` statement into the MIR. // `BasicCoverageBlock`, and inject a `Coverage` statement into the MIR.
//
// `Coverage` statements injected from `CoverageSpan`s will include the code regions
// (source code start and end positions) to be counted by the associated counter.
//
// These `CoverageSpan`-associated counters are removed from their associated
// `BasicCoverageBlock`s so that the only remaining counters in the `CoverageGraph`
// are indirect counters (to be injected next, without associated code regions).
self.inject_coverage_span_counters( self.inject_coverage_span_counters(
coverage_spans, coverage_spans,
&mut graphviz_data, &mut graphviz_data,
@ -262,6 +269,8 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
bug!("Every BasicCoverageBlock should have a Counter or Expression"); bug!("Every BasicCoverageBlock should have a Counter or Expression");
}; };
graphviz_data.add_bcb_coverage_span_with_counter(bcb, &covspan, &counter_kind); graphviz_data.add_bcb_coverage_span_with_counter(bcb, &covspan, &counter_kind);
// FIXME(#78542): Can spans for `TerminatorKind::Goto` be improved to avoid special
// cases?
let some_code_region = if self.is_code_region_redundant(bcb, span, body_span) { let some_code_region = if self.is_code_region_redundant(bcb, span, body_span) {
None None
} else { } else {
@ -280,6 +289,7 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
/// ///
/// If this method returns `true`, the counter (which other `Expressions` may depend on) is /// If this method returns `true`, the counter (which other `Expressions` may depend on) is
/// still injected, but without an associated code region. /// still injected, but without an associated code region.
// FIXME(#78542): Can spans for `TerminatorKind::Goto` be improved to avoid special cases?
fn is_code_region_redundant( fn is_code_region_redundant(
&self, &self,
bcb: BasicCoverageBlock, bcb: BasicCoverageBlock,

View file

@ -1,5 +1,3 @@
use super::counters;
use rustc_middle::mir::coverage::*; use rustc_middle::mir::coverage::*;
use rustc_middle::mir::visit::Visitor; use rustc_middle::mir::visit::Visitor;
use rustc_middle::mir::{Coverage, CoverageInfo, Location}; use rustc_middle::mir::{Coverage, CoverageInfo, Location};
@ -44,11 +42,16 @@ struct CoverageVisitor {
} }
impl CoverageVisitor { impl CoverageVisitor {
/// Updates `num_counters` to the maximum encountered zero-based counter_id plus 1. Note the
/// final computed number of counters should be the number of all `CoverageKind::Counter`
/// statements in the MIR *plus one* for the implicit `ZERO` counter.
#[inline(always)] #[inline(always)]
fn update_num_counters(&mut self, counter_id: u32) { fn update_num_counters(&mut self, counter_id: u32) {
self.info.num_counters = std::cmp::max(self.info.num_counters, counter_id + 1); self.info.num_counters = std::cmp::max(self.info.num_counters, counter_id + 1);
} }
/// Computes an expression index for each expression ID, and updates `num_expressions` to the
/// maximum encountered index plus 1.
#[inline(always)] #[inline(always)]
fn update_num_expressions(&mut self, expression_id: u32) { fn update_num_expressions(&mut self, expression_id: u32) {
let expression_index = u32::MAX - expression_id; let expression_index = u32::MAX - expression_id;
@ -59,10 +62,18 @@ impl CoverageVisitor {
if operand_id >= self.info.num_counters { if operand_id >= self.info.num_counters {
let operand_as_expression_index = u32::MAX - operand_id; let operand_as_expression_index = u32::MAX - operand_id;
if operand_as_expression_index >= self.info.num_expressions { if operand_as_expression_index >= self.info.num_expressions {
if operand_id <= counters::MAX_COUNTER_GUARD { // The operand ID is outside the known range of counter IDs and also outside the
// Since the complete range of counter and expression IDs is not known here, the // known range of expression IDs. In either case, the result of a missing operand
// only way to determine if the ID is a counter ID or an expression ID is to // (if and when used in an expression) will be zero, so from a computation
// assume a maximum possible counter ID value. // perspective, it doesn't matter whether it is interepretted as a counter or an
// expression.
//
// However, the `num_counters` and `num_expressions` query results are used to
// allocate arrays when generating the coverage map (during codegen), so choose
// the type that grows either `num_counters` or `num_expressions` the least.
if operand_id - self.info.num_counters
< operand_as_expression_index - self.info.num_expressions
{
self.update_num_counters(operand_id) self.update_num_counters(operand_id)
} else { } else {
self.update_num_expressions(operand_id) self.update_num_expressions(operand_id)
@ -100,7 +111,8 @@ fn coverageinfo_from_mir<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> CoverageInfo
let mir_body = tcx.optimized_mir(def_id); let mir_body = tcx.optimized_mir(def_id);
let mut coverage_visitor = CoverageVisitor { let mut coverage_visitor = CoverageVisitor {
info: CoverageInfo { num_counters: 0, num_expressions: 0 }, // num_counters always has at least the `ZERO` counter.
info: CoverageInfo { num_counters: 1, num_expressions: 0 },
add_missing_operands: false, add_missing_operands: false,
}; };

View file

@ -656,7 +656,9 @@ fn filtered_statement_span(statement: &'a Statement<'tcx>, body_span: Span) -> O
// Ignore `Nop`s // Ignore `Nop`s
| StatementKind::Nop => None, | StatementKind::Nop => None,
// FIXME(richkadel): Look into a possible issue assigning the span to a // FIXME(#78546): MIR InstrumentCoverage - Can the source_info.span for `FakeRead`
// statements be more consistent?
//
// FakeReadCause::ForGuardBinding, in this example: // FakeReadCause::ForGuardBinding, in this example:
// match somenum { // match somenum {
// x if x < 1 => { ... } // x if x < 1 => { ... }
@ -669,15 +671,7 @@ fn filtered_statement_span(statement: &'a Statement<'tcx>, body_span: Span) -> O
// _4 = &_1; (at the span for the first `x`) // _4 = &_1; (at the span for the first `x`)
// and `_1` is the `Place` for `somenum`. // and `_1` is the `Place` for `somenum`.
// //
// The arm code BasicBlock already has its own assignment for `x` itself, `_3 = 1`, and I've // If and when the Issue is resolved, remove this special case match pattern:
// decided it's reasonable for that span (even though outside the arm code) to be part of
// the counted coverage of the arm code execution, but I can't justify including the literal
// `1` in the arm code. I'm pretty sure that, if the `FakeRead(ForGuardBinding)` has a
// purpose in codegen, it's probably in the right BasicBlock, but if so, the `Statement`s
// `source_info.span` can't be right.
//
// Consider correcting the span assignment, assuming there is a better solution, and see if
// the following pattern can be removed here:
StatementKind::FakeRead(cause, _) if cause == FakeReadCause::ForGuardBinding => None, StatementKind::FakeRead(cause, _) if cause == FakeReadCause::ForGuardBinding => None,
// Retain spans from all other statements // Retain spans from all other statements
@ -710,13 +704,7 @@ fn filtered_terminator_span(terminator: &'a Terminator<'tcx>, body_span: Span) -
// `FalseEdge`. // `FalseEdge`.
| TerminatorKind::FalseEdge { .. } => None, | TerminatorKind::FalseEdge { .. } => None,
// FIXME(richkadel): Note that `Goto` was initially filtered out (by returning `None`, as // FIXME(#78542): Can spans for `TerminatorKind::Goto` be improved to avoid special cases?
// with the `TerminatorKind`s above) because its `Span` was way to broad to be beneficial,
// and, at the time, `Goto` didn't seem to provide any additional contributions to the
// coverage analysis. Upon further review, `Goto` terminated blocks do appear to benefit
// the coverage analysis, and the BCB CFG. To overcome the issues with the `Spans`, the
// coverage algorithms--and the final coverage map generation--include some exceptional
// behaviors.
// //
// `Goto`s are often the targets of `SwitchInt` branches, and certain important // `Goto`s are often the targets of `SwitchInt` branches, and certain important
// optimizations to replace some `Counter`s with `Expression`s require a separate // optimizations to replace some `Counter`s with `Expression`s require a separate
@ -750,7 +738,7 @@ fn filtered_terminator_span(terminator: &'a Terminator<'tcx>, body_span: Span) -
} }
} }
#[inline(always)] #[inline]
fn function_source_span(span: Span, body_span: Span) -> Span { fn function_source_span(span: Span, body_span: Span) -> Span {
let span = original_sp(span, body_span).with_ctxt(SyntaxContext::root()); let span = original_sp(span, body_span).with_ctxt(SyntaxContext::root());
if body_span.contains(span) { span } else { body_span } if body_span.contains(span) { span } else { body_span }

View file

@ -887,12 +887,15 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
dump_mir_exclude_pass_number: bool = (false, parse_bool, [UNTRACKED], dump_mir_exclude_pass_number: bool = (false, parse_bool, [UNTRACKED],
"exclude the pass number when dumping MIR (used in tests) (default: no)"), "exclude the pass number when dumping MIR (used in tests) (default: no)"),
dump_mir_graphviz: bool = (false, parse_bool, [UNTRACKED], dump_mir_graphviz: bool = (false, parse_bool, [UNTRACKED],
"in addition to `.mir` files, create graphviz `.dot` files (default: no)"), "in addition to `.mir` files, create graphviz `.dot` files (and with \
`-Z instrument-coverage`, also create a `.dot` file for the MIR-derived \
coverage graph) (default: no)"),
dump_mir_spanview: Option<MirSpanview> = (None, parse_mir_spanview, [UNTRACKED], dump_mir_spanview: Option<MirSpanview> = (None, parse_mir_spanview, [UNTRACKED],
"in addition to `.mir` files, create `.html` files to view spans for \ "in addition to `.mir` files, create `.html` files to view spans for \
all `statement`s (including terminators), only `terminator` spans, or \ all `statement`s (including terminators), only `terminator` spans, or \
computed `block` spans (one span encompassing a block's terminator and \ computed `block` spans (one span encompassing a block's terminator and \
all statements)."), all statements). If `-Z instrument-coverage` is also enabled, create \
an additional `.html` file showing the computed coverage spans."),
emit_future_incompat_report: bool = (false, parse_bool, [UNTRACKED], emit_future_incompat_report: bool = (false, parse_bool, [UNTRACKED],
"emits a future-incompatibility report for lints (RFC 2834)"), "emits a future-incompatibility report for lints (RFC 2834)"),
emit_stack_sizes: bool = (false, parse_bool, [UNTRACKED], emit_stack_sizes: bool = (false, parse_bool, [UNTRACKED],

View file

@ -26,6 +26,7 @@ endif
-Zinstrument-coverage \ -Zinstrument-coverage \
-Clink-dead-code=$(LINK_DEAD_CODE) \ -Clink-dead-code=$(LINK_DEAD_CODE) \
-Zdump-mir=InstrumentCoverage \ -Zdump-mir=InstrumentCoverage \
-Zdump-mir-spanview \
-Zdump-mir-dir="$(TMPDIR)"/mir_dump.$@ -Zdump-mir-dir="$(TMPDIR)"/mir_dump.$@
for path in "$(TMPDIR)"/mir_dump.$@/*; do \ for path in "$(TMPDIR)"/mir_dump.$@/*; do \

View file

@ -37,3 +37,8 @@ endif
# tests can be simplified to always test with `-C link-dead-code`. # tests can be simplified to always test with `-C link-dead-code`.
UNAME = $(shell uname) UNAME = $(shell uname)
# FIXME(richkadel): Can any of the features tested by `run-make-fulldeps/coverage-*` tests be tested
# just as completely by more focused unit tests of the code logic itself, to reduce the number of
# test result files generated and maintained, and to help identify specific test failures and root
# causes more easily?