diff options
| author | Rich Kadel <richkadel@google.com> | 2020-10-30 16:09:05 -0700 |
|---|---|---|
| committer | Rich Kadel <richkadel@google.com> | 2020-11-05 18:24:18 -0800 |
| commit | a7d956583ceae1487ad9b0748039bc9f0e8ac7aa (patch) | |
| tree | 18fbdf8ee28787e044247590ed4f3119aee5ba70 | |
| parent | 1973f84ebbb3b2bb4b9a1488b6553ac46b2db8d4 (diff) | |
| download | rust-a7d956583ceae1487ad9b0748039bc9f0e8ac7aa.tar.gz rust-a7d956583ceae1487ad9b0748039bc9f0e8ac7aa.zip | |
Responded to all feedback as of 2020-10-30
| -rw-r--r-- | compiler/rustc_mir/src/transform/coverage/counters.rs | 297 | ||||
| -rw-r--r-- | compiler/rustc_mir/src/transform/coverage/debug.rs | 129 | ||||
| -rw-r--r-- | compiler/rustc_mir/src/transform/coverage/graph.rs | 66 | ||||
| -rw-r--r-- | compiler/rustc_mir/src/transform/coverage/mod.rs | 20 | ||||
| -rw-r--r-- | compiler/rustc_mir/src/transform/coverage/query.rs | 26 | ||||
| -rw-r--r-- | compiler/rustc_mir/src/transform/coverage/spans.rs | 24 | ||||
| -rw-r--r-- | compiler/rustc_session/src/options.rs | 7 | ||||
| -rw-r--r-- | src/test/run-make-fulldeps/coverage-spanview-base/Makefile | 1 | ||||
| -rw-r--r-- | src/test/run-make-fulldeps/coverage/coverage_tools.mk | 5 |
9 files changed, 367 insertions, 208 deletions
diff --git a/compiler/rustc_mir/src/transform/coverage/counters.rs b/compiler/rustc_mir/src/transform/coverage/counters.rs index 7454ec2f438..d6c2f7f7aaf 100644 --- a/compiler/rustc_mir/src/transform/coverage/counters.rs +++ b/compiler/rustc_mir/src/transform/coverage/counters.rs @@ -12,16 +12,6 @@ use rustc_data_structures::graph::WithNumNodes; use rustc_index::bit_set::BitSet; 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 /// `Coverage` statements. pub(crate) struct CoverageCounters { @@ -105,7 +95,6 @@ impl CoverageCounters { /// Counter IDs start from one and go up. fn next_counter(&mut self) -> CounterValueReference { assert!(self.next_counter_id < u32::MAX - self.num_expressions); - assert!(self.next_counter_id <= MAX_COUNTER_GUARD); let next = self.next_counter_id; self.next_counter_id += 1; CounterValueReference::from(next) @@ -131,6 +120,7 @@ struct BcbCounters<'a> { basic_coverage_blocks: &'a mut CoverageGraph, } +// FIXME(richkadel): Add unit tests for `BcbCounters` functions/algorithms. impl<'a> BcbCounters<'a> { fn new( coverage_counters: &'a mut CoverageCounters, @@ -139,7 +129,7 @@ impl<'a> BcbCounters<'a> { 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 /// 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 @@ -162,10 +152,16 @@ impl<'a> BcbCounters<'a> { bcbs_with_coverage.insert(covspan.bcb); } - // FIXME(richkadel): Add more comments to explain the logic here and in the rest of this - // function, and refactor this function to break it up into smaller functions that are - // easier to understand. - + // Walk the `CoverageGraph`. For each `BasicCoverageBlock` node with an associated + // `CoverageSpan`, add a counter. If the `BasicCoverageBlock` branches, add a counter or + // 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); while let Some(bcb) = traversal.next(self.basic_coverage_blocks) { if bcbs_with_coverage.contains(bcb) { @@ -220,11 +216,20 @@ impl<'a> BcbCounters<'a> { .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); - // 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; for branch in branches { + // Skip the selected `expression_branch`, if any. It's expression will be assigned after + // all others. if branch != expression_branch { let branch_counter_operand = if branch.is_only_path_to_target() { 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 = some_sumup_counter_operand.expect("sumup_counter_operand should have a value"); debug!( @@ -301,99 +309,99 @@ impl<'a> BcbCounters<'a> { collect_intermediate_expressions: &mut Vec<CoverageKind>, debug_indent_level: usize, ) -> Result<ExpressionOperandId, Error> { - Ok({ - if let Some(counter_kind) = self.basic_coverage_blocks[bcb].counter() { + // If the BCB already has a counter, return it. + if let Some(counter_kind) = self.basic_coverage_blocks[bcb].counter() { + debug!( + "{}{:?} already has a counter: {}", + NESTED_INDENT.repeat(debug_indent_level), + bcb, + self.format_counter(counter_kind), + ); + return Ok(counter_kind.as_operand_id()); + } + + // 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); + if one_path_to_target || self.bcb_predecessors(bcb).contains(&bcb) { + let counter_kind = self.coverage_counters.make_counter(|| Some(format!("{:?}", bcb))); + if one_path_to_target { debug!( - "{}{:?} already has a counter: {}", + "{}{:?} gets a new counter: {}", NESTED_INDENT.repeat(debug_indent_level), bcb, - self.format_counter(counter_kind), + self.format_counter(&counter_kind), ); - counter_kind.as_operand_id() } else { - let one_path_to_target = self.bcb_has_one_path_to_target(bcb); - if one_path_to_target || self.bcb_predecessors(bcb).contains(&bcb) { - let counter_kind = - self.coverage_counters.make_counter(|| Some(format!("{:?}", bcb))); - if one_path_to_target { - debug!( - "{}{:?} gets a new counter: {}", - NESTED_INDENT.repeat(debug_indent_level), - bcb, - self.format_counter(&counter_kind), - ); - } else { - debug!( - "{}{:?} 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 \ - compiled code will generate an infinite loop.)", - NESTED_INDENT.repeat(debug_indent_level), - bcb, - self.format_counter(&counter_kind), - ); - } - self.basic_coverage_blocks[bcb].set_counter(counter_kind)? - } else { - let mut predecessors = self.bcb_predecessors(bcb).clone().into_iter(); - debug!( - "{}{:?} has multiple incoming edges and will get an expression that sums \ - them up...", - NESTED_INDENT.repeat(debug_indent_level), - bcb, - ); - let first_edge_counter_operand = self - .recursive_get_or_make_edge_counter_operand( - predecessors.next().unwrap(), - bcb, - collect_intermediate_expressions, - debug_indent_level + 1, - )?; - let mut some_sumup_edge_counter_operand = None; - for predecessor in predecessors { - let edge_counter_operand = self - .recursive_get_or_make_edge_counter_operand( - predecessor, - bcb, - collect_intermediate_expressions, - debug_indent_level + 1, - )?; - if let Some(sumup_edge_counter_operand) = - some_sumup_edge_counter_operand.replace(edge_counter_operand) - { - let intermediate_expression = self.coverage_counters.make_expression( - sumup_edge_counter_operand, - Op::Add, - edge_counter_operand, - || None, - ); - debug!( - "{}new intermediate expression: {}", - NESTED_INDENT.repeat(debug_indent_level), - self.format_counter(&intermediate_expression) - ); - let intermediate_expression_operand = - intermediate_expression.as_operand_id(); - collect_intermediate_expressions.push(intermediate_expression); - some_sumup_edge_counter_operand - .replace(intermediate_expression_operand); - } - } - let counter_kind = self.coverage_counters.make_expression( - first_edge_counter_operand, - Op::Add, - some_sumup_edge_counter_operand.unwrap(), - || Some(format!("{:?}", bcb)), - ); - debug!( - "{}{:?} gets a new counter (sum of predecessor counters): {}", - NESTED_INDENT.repeat(debug_indent_level), - bcb, - self.format_counter(&counter_kind) - ); - self.basic_coverage_blocks[bcb].set_counter(counter_kind)? - } + debug!( + "{}{:?} 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 compiled \ + code will generate an infinite loop.)", + NESTED_INDENT.repeat(debug_indent_level), + bcb, + self.format_counter(&counter_kind), + ); } - }) + return self.basic_coverage_blocks[bcb].set_counter(counter_kind); + } + + // 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(); + debug!( + "{}{:?} has multiple incoming edges and will get an expression that sums them up...", + NESTED_INDENT.repeat(debug_indent_level), + bcb, + ); + let first_edge_counter_operand = self.recursive_get_or_make_edge_counter_operand( + predecessors.next().unwrap(), + bcb, + collect_intermediate_expressions, + debug_indent_level + 1, + )?; + let mut some_sumup_edge_counter_operand = None; + for predecessor in predecessors { + let edge_counter_operand = self.recursive_get_or_make_edge_counter_operand( + predecessor, + bcb, + collect_intermediate_expressions, + debug_indent_level + 1, + )?; + if let Some(sumup_edge_counter_operand) = + some_sumup_edge_counter_operand.replace(edge_counter_operand) + { + let intermediate_expression = self.coverage_counters.make_expression( + sumup_edge_counter_operand, + Op::Add, + edge_counter_operand, + || None, + ); + debug!( + "{}new intermediate expression: {}", + NESTED_INDENT.repeat(debug_indent_level), + self.format_counter(&intermediate_expression) + ); + let intermediate_expression_operand = intermediate_expression.as_operand_id(); + collect_intermediate_expressions.push(intermediate_expression); + some_sumup_edge_counter_operand.replace(intermediate_expression_operand); + } + } + let counter_kind = self.coverage_counters.make_expression( + first_edge_counter_operand, + Op::Add, + some_sumup_edge_counter_operand.unwrap(), + || Some(format!("{:?}", bcb)), + ); + debug!( + "{}{:?} gets a new counter (sum of predecessor counters): {}", + NESTED_INDENT.repeat(debug_indent_level), + bcb, + self.format_counter(&counter_kind) + ); + self.basic_coverage_blocks[bcb].set_counter(counter_kind) } fn get_or_make_edge_counter_operand( @@ -417,46 +425,44 @@ impl<'a> BcbCounters<'a> { collect_intermediate_expressions: &mut Vec<CoverageKind>, debug_indent_level: usize, ) -> Result<ExpressionOperandId, Error> { - Ok({ - let successors = self.bcb_successors(from_bcb).iter(); - if successors.len() > 1 { - if let Some(counter_kind) = - self.basic_coverage_blocks[to_bcb].edge_counter_from(from_bcb) - { - debug!( - "{}Edge {:?}->{:?} already has a counter: {}", - NESTED_INDENT.repeat(debug_indent_level), - from_bcb, - to_bcb, - self.format_counter(counter_kind) - ); - counter_kind.as_operand_id() - } else { - let counter_kind = self - .coverage_counters - .make_counter(|| Some(format!("{:?}->{:?}", from_bcb, to_bcb))); - debug!( - "{}Edge {:?}->{:?} gets a new counter: {}", - NESTED_INDENT.repeat(debug_indent_level), - from_bcb, - to_bcb, - self.format_counter(&counter_kind) - ); - self.basic_coverage_blocks[to_bcb] - .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, - )? - } - }) + // 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(); + if successors.len() == 1 { + return self.recursive_get_or_make_counter_operand( + 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!( + "{}Edge {:?}->{:?} already has a counter: {}", + NESTED_INDENT.repeat(debug_indent_level), + from_bcb, + to_bcb, + self.format_counter(counter_kind) + ); + return Ok(counter_kind.as_operand_id()); + } + + // Make a new counter to count this edge. + let counter_kind = + self.coverage_counters.make_counter(|| Some(format!("{:?}->{:?}", from_bcb, to_bcb))); + debug!( + "{}Edge {:?}->{:?} gets a new counter: {}", + NESTED_INDENT.repeat(debug_indent_level), + from_bcb, + to_bcb, + self.format_counter(&counter_kind) + ); + self.basic_coverage_blocks[to_bcb].set_edge_counter_from(from_bcb, counter_kind) } - /// Select a branch for the expression, either the recommended `reloop_branch`, or - /// if none was found, select any branch. + /// Select a branch for the expression, either the recommended `reloop_branch`, or if none was + /// found, select any branch. fn choose_preferred_expression_branch( &self, traversal: &TraverseCoverageGraphWithLoops, @@ -493,9 +499,8 @@ impl<'a> BcbCounters<'a> { } } - /// At most one of the branches (or its edge, from the branching_bcb, - /// if the branch has multiple incoming edges) can have a counter computed by - /// expression. + /// At most, one of the branches (or its edge, from the branching_bcb, if the branch has + /// multiple incoming edges) can have a counter computed by expression. /// /// 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 diff --git a/compiler/rustc_mir/src/transform/coverage/debug.rs b/compiler/rustc_mir/src/transform/coverage/debug.rs index 7080975aee5..cc697dfd7fe 100644 --- a/compiler/rustc_mir/src/transform/coverage/debug.rs +++ b/compiler/rustc_mir/src/transform/coverage/debug.rs @@ -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::spans::CoverageSpan; @@ -20,13 +130,11 @@ const RUSTC_COVERAGE_DEBUG_OPTIONS: &str = "RUSTC_COVERAGE_DEBUG_OPTIONS"; pub(crate) fn debug_options<'a>() -> &'a DebugOptions { 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 -/// "RUSTC_COVERAGE_DEBUG_OPTIONS", if set. Options can be set on the command line by, for example: -/// -/// $ RUSTC_COVERAGE_DEBUG_OPTIONS=counter-format=block,allow_unused_expressions=n cargo build +/// "RUSTC_COVERAGE_DEBUG_OPTIONS", if set. #[derive(Debug, Clone)] pub(crate) struct DebugOptions { pub allow_unused_expressions: bool, @@ -34,7 +142,7 @@ pub(crate) struct DebugOptions { } impl DebugOptions { - fn new() -> Self { + fn from_env() -> Self { let mut allow_unused_expressions = true; let mut counter_format = ExpressionFormat::default(); @@ -152,10 +260,11 @@ impl DebugCounters { } pub fn enable(&mut self) { + debug_assert!(!self.is_enabled()); self.some_counters.replace(FxHashMap::default()); } - pub fn is_enabled(&mut self) -> bool { + pub fn is_enabled(&self) -> bool { self.some_counters.is_some() } @@ -294,12 +403,13 @@ impl GraphvizData { } 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_dependency_counters = 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() } @@ -399,11 +509,12 @@ impl UsedExpressions { } pub fn enable(&mut self) { + debug_assert!(!self.is_enabled()); self.some_used_expression_operands = Some(FxHashMap::default()); 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() } @@ -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() { used_expression_operands.contains_key(&expression.as_operand_id()) } else { diff --git a/compiler/rustc_mir/src/transform/coverage/graph.rs b/compiler/rustc_mir/src/transform/coverage/graph.rs index 84062541701..c2ed2cbb100 100644 --- a/compiler/rustc_mir/src/transform/coverage/graph.rs +++ b/compiler/rustc_mir/src/transform/coverage/graph.rs @@ -82,6 +82,8 @@ impl CoverageGraph { // 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 // 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 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, /// 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 -/// 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 /// 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. @@ -329,7 +332,6 @@ impl BasicCoverageBlockData { &mir_body[self.last_bb()].terminator() } - #[inline(always)] pub fn set_counter( &mut self, counter_kind: CoverageKind, @@ -342,16 +344,15 @@ impl BasicCoverageBlockData { "attempt to add a `Counter` to a BCB target with existing incoming edge counters" ); let operand = counter_kind.as_operand_id(); - let expect_none = self.counter_kind.replace(counter_kind); - if expect_none.is_some() { - return Error::from_string(format!( + if let Some(replaced) = self.counter_kind.replace(counter_kind) { + Error::from_string(format!( "attempt to set a BasicCoverageBlock coverage counter more than once; \ {:?} already had counter {:?}", - self, - expect_none.unwrap(), - )); + self, replaced, + )) + } else { + Ok(operand) } - Ok(operand) } #[inline(always)] @@ -364,7 +365,6 @@ impl BasicCoverageBlockData { self.counter_kind.take() } - #[inline(always)] pub fn set_edge_counter_from( &mut self, from_bcb: BasicCoverageBlock, @@ -383,22 +383,22 @@ impl BasicCoverageBlockData { } } let operand = counter_kind.as_operand_id(); - let expect_none = self + if let Some(replaced) = self .edge_from_bcbs .get_or_insert_with(|| FxHashMap::default()) - .insert(from_bcb, counter_kind); - if expect_none.is_some() { - return Error::from_string(format!( + .insert(from_bcb, counter_kind) + { + Error::from_string(format!( "attempt to set an edge counter more than once; from_bcb: \ {:?} already had counter {:?}", - from_bcb, - expect_none.unwrap(), - )); + from_bcb, replaced, + )) + } else { + Ok(operand) } - Ok(operand) } - #[inline(always)] + #[inline] pub fn edge_counter_from(&self, from_bcb: BasicCoverageBlock) -> Option<&CoverageKind> { if let Some(edge_from_bcbs) = &self.edge_from_bcbs { edge_from_bcbs.get(&from_bcb) @@ -407,7 +407,7 @@ impl BasicCoverageBlockData { } } - #[inline(always)] + #[inline] pub fn take_edge_counters( &mut self, ) -> 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>( body: &'tcx &'a mir::Body<'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 /// 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. +// FIXME(richkadel): Add unit tests for TraversalContext. #[derive(Debug)] pub(crate) struct TraversalContext { /// 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 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 &successor in &basic_coverage_blocks.successors[bcb] { if basic_coverage_blocks.is_dominated_by(bcb, successor) { diff --git a/compiler/rustc_mir/src/transform/coverage/mod.rs b/compiler/rustc_mir/src/transform/coverage/mod.rs index c84ccf19213..c55349239b0 100644 --- a/compiler/rustc_mir/src/transform/coverage/mod.rs +++ b/compiler/rustc_mir/src/transform/coverage/mod.rs @@ -74,9 +74,6 @@ impl<'tcx> MirPass<'tcx> for InstrumentCoverage { trace!("InstrumentCoverage skipped for {:?} (not an FnLikeNode)", mir_source.def_id()); 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()); 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 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 { graphviz_data.enable(); self.coverage_counters.enable_debug(); @@ -139,7 +139,7 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { &self.basic_coverage_blocks, ); - if pretty::dump_enabled(tcx, self.pass_name, def_id) { + if dump_spanview { debug::dump_coverage_spanview( tcx, 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 // `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( coverage_spans, &mut graphviz_data, @@ -262,6 +269,8 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { bug!("Every BasicCoverageBlock should have a Counter or Expression"); }; 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) { None } 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 /// 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( &self, bcb: BasicCoverageBlock, diff --git a/compiler/rustc_mir/src/transform/coverage/query.rs b/compiler/rustc_mir/src/transform/coverage/query.rs index 2fbbc9b675a..e86bb96d29c 100644 --- a/compiler/rustc_mir/src/transform/coverage/query.rs +++ b/compiler/rustc_mir/src/transform/coverage/query.rs @@ -1,5 +1,3 @@ -use super::counters; - use rustc_middle::mir::coverage::*; use rustc_middle::mir::visit::Visitor; use rustc_middle::mir::{Coverage, CoverageInfo, Location}; @@ -44,11 +42,16 @@ struct 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)] fn update_num_counters(&mut self, counter_id: u32) { 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)] fn update_num_expressions(&mut self, expression_id: u32) { let expression_index = u32::MAX - expression_id; @@ -59,10 +62,18 @@ impl CoverageVisitor { if operand_id >= self.info.num_counters { let operand_as_expression_index = u32::MAX - operand_id; if operand_as_expression_index >= self.info.num_expressions { - if operand_id <= counters::MAX_COUNTER_GUARD { - // Since the complete range of counter and expression IDs is not known here, the - // only way to determine if the ID is a counter ID or an expression ID is to - // assume a maximum possible counter ID value. + // The operand ID is outside the known range of counter IDs and also outside the + // known range of expression IDs. In either case, the result of a missing operand + // (if and when used in an expression) will be zero, so from a computation + // 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) } else { 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 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, }; diff --git a/compiler/rustc_mir/src/transform/coverage/spans.rs b/compiler/rustc_mir/src/transform/coverage/spans.rs index 8549ac0e4af..cda4fc12544 100644 --- a/compiler/rustc_mir/src/transform/coverage/spans.rs +++ b/compiler/rustc_mir/src/transform/coverage/spans.rs @@ -656,7 +656,9 @@ fn filtered_statement_span(statement: &'a Statement<'tcx>, body_span: Span) -> O // Ignore `Nop`s | 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: // match somenum { // 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`) // and `_1` is the `Place` for `somenum`. // - // The arm code BasicBlock already has its own assignment for `x` itself, `_3 = 1`, and I've - // 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: + // If and when the Issue is resolved, remove this special case match pattern: StatementKind::FakeRead(cause, _) if cause == FakeReadCause::ForGuardBinding => None, // Retain spans from all other statements @@ -710,13 +704,7 @@ fn filtered_terminator_span(terminator: &'a Terminator<'tcx>, body_span: Span) - // `FalseEdge`. | TerminatorKind::FalseEdge { .. } => None, - // FIXME(richkadel): Note that `Goto` was initially filtered out (by returning `None`, as - // 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. + // FIXME(#78542): Can spans for `TerminatorKind::Goto` be improved to avoid special cases? // // `Goto`s are often the targets of `SwitchInt` branches, and certain important // 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 { let span = original_sp(span, body_span).with_ctxt(SyntaxContext::root()); if body_span.contains(span) { span } else { body_span } diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 4c00361dd31..ceed730e25b 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -887,12 +887,15 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options, dump_mir_exclude_pass_number: bool = (false, parse_bool, [UNTRACKED], "exclude the pass number when dumping MIR (used in tests) (default: no)"), 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], "in addition to `.mir` files, create `.html` files to view spans for \ all `statement`s (including terminators), only `terminator` spans, or \ 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], "emits a future-incompatibility report for lints (RFC 2834)"), emit_stack_sizes: bool = (false, parse_bool, [UNTRACKED], diff --git a/src/test/run-make-fulldeps/coverage-spanview-base/Makefile b/src/test/run-make-fulldeps/coverage-spanview-base/Makefile index ecb5275e716..ec93ca4725e 100644 --- a/src/test/run-make-fulldeps/coverage-spanview-base/Makefile +++ b/src/test/run-make-fulldeps/coverage-spanview-base/Makefile @@ -26,6 +26,7 @@ endif -Zinstrument-coverage \ -Clink-dead-code=$(LINK_DEAD_CODE) \ -Zdump-mir=InstrumentCoverage \ + -Zdump-mir-spanview \ -Zdump-mir-dir="$(TMPDIR)"/mir_dump.$@ for path in "$(TMPDIR)"/mir_dump.$@/*; do \ diff --git a/src/test/run-make-fulldeps/coverage/coverage_tools.mk b/src/test/run-make-fulldeps/coverage/coverage_tools.mk index ad5f465c54f..17f7696a8cf 100644 --- a/src/test/run-make-fulldeps/coverage/coverage_tools.mk +++ b/src/test/run-make-fulldeps/coverage/coverage_tools.mk @@ -37,3 +37,8 @@ endif # tests can be simplified to always test with `-C link-dead-code`. 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? |
