about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRich Kadel <richkadel@google.com>2020-10-30 16:09:05 -0700
committerRich Kadel <richkadel@google.com>2020-11-05 18:24:18 -0800
commita7d956583ceae1487ad9b0748039bc9f0e8ac7aa (patch)
tree18fbdf8ee28787e044247590ed4f3119aee5ba70
parent1973f84ebbb3b2bb4b9a1488b6553ac46b2db8d4 (diff)
downloadrust-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.rs297
-rw-r--r--compiler/rustc_mir/src/transform/coverage/debug.rs129
-rw-r--r--compiler/rustc_mir/src/transform/coverage/graph.rs66
-rw-r--r--compiler/rustc_mir/src/transform/coverage/mod.rs20
-rw-r--r--compiler/rustc_mir/src/transform/coverage/query.rs26
-rw-r--r--compiler/rustc_mir/src/transform/coverage/spans.rs24
-rw-r--r--compiler/rustc_session/src/options.rs7
-rw-r--r--src/test/run-make-fulldeps/coverage-spanview-base/Makefile1
-rw-r--r--src/test/run-make-fulldeps/coverage/coverage_tools.mk5
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?