Skip to content

Commit a7d9565

Browse files
committed
Responded to all feedback as of 2020-10-30
1 parent 1973f84 commit a7d9565

File tree

9 files changed

+367
-208
lines changed

9 files changed

+367
-208
lines changed

compiler/rustc_mir/src/transform/coverage/counters.rs

Lines changed: 151 additions & 146 deletions
Large diffs are not rendered by default.

compiler/rustc_mir/src/transform/coverage/debug.rs

Lines changed: 120 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,113 @@
1+
//! The `InstrumentCoverage` MIR pass implementation includes debugging tools and options
2+
//! to help developers understand and/or improve the analysis and instrumentation of a MIR.
3+
//!
4+
//! To enable coverage, include the rustc command line option:
5+
//!
6+
//! * `-Z instrument-coverage`
7+
//!
8+
//! MIR Dump Files, with additional `CoverageGraph` graphviz and `CoverageSpan` spanview
9+
//! ------------------------------------------------------------------------------------
10+
//!
11+
//! Additional debugging options include:
12+
//!
13+
//! * `-Z dump-mir=InstrumentCoverage` - Generate `.mir` files showing the state of the MIR,
14+
//! before and after the `InstrumentCoverage` pass, for each compiled function.
15+
//!
16+
//! * `-Z dump-mir-graphviz` - If `-Z dump-mir` is also enabled for the current MIR node path,
17+
//! each MIR dump is accompanied by a before-and-after graphical view of the MIR, in Graphviz
18+
//! `.dot` file format (which can be visually rendered as a graph using any of a number of free
19+
//! Graphviz viewers and IDE extensions).
20+
//!
21+
//! For the `InstrumentCoverage` pass, this option also enables generation of an additional
22+
//! Graphviz `.dot` file for each function, rendering the `CoverageGraph`: the control flow
23+
//! graph (CFG) of `BasicCoverageBlocks` (BCBs), as nodes, internally labeled to show the
24+
//! `CoverageSpan`-based MIR elements each BCB represents (`BasicBlock`s, `Statement`s and
25+
//! `Terminator`s), assigned coverage counters and/or expressions, and edge counters, as needed.
26+
//!
27+
//! (Note the additional option, `-Z graphviz-dark-mode`, can be added, to change the rendered
28+
//! output from its default black-on-white background to a dark color theme, if desired.)
29+
//!
30+
//! * `-Z dump-mir-spanview` - If `-Z dump-mir` is also enabled for the current MIR node path,
31+
//! each MIR dump is accompanied by a before-and-after `.html` document showing the function's
32+
//! original source code, highlighted by it's MIR spans, at the `statement`-level (by default),
33+
//! `terminator` only, or encompassing span for the `Terminator` plus all `Statement`s, in each
34+
//! `block` (`BasicBlock`).
35+
//!
36+
//! For the `InstrumentCoverage` pass, this option also enables generation of an additional
37+
//! spanview `.html` file for each function, showing the aggregated `CoverageSpan`s that will
38+
//! require counters (or counter expressions) for accurate coverage analysis.
39+
//!
40+
//! Debug Logging
41+
//! -------------
42+
//!
43+
//! The `InstrumentCoverage` pass includes debug logging messages at various phases and decision
44+
//! points, which can be enabled via environment variable:
45+
//!
46+
//! ```shell
47+
//! RUSTC_LOG=rustc_mir::transform::coverage=debug
48+
//! ```
49+
//!
50+
//! Other module paths with coverage-related debug logs may also be of interest, particularly for
51+
//! debugging the coverage map data, injected as global variables in the LLVM IR (during rustc's
52+
//! code generation pass). For example:
53+
//!
54+
//! ```shell
55+
//! RUSTC_LOG=rustc_mir::transform::coverage,rustc_codegen_ssa::coverageinfo,rustc_codegen_llvm::coverageinfo=debug
56+
//! ```
57+
//!
58+
//! Coverage Debug Options
59+
//! ---------------------------------
60+
//!
61+
//! Additional debugging options can be enabled using the environment variable:
62+
//!
63+
//! ```shell
64+
//! RUSTC_COVERAGE_DEBUG_OPTIONS=<options>
65+
//! ```
66+
//!
67+
//! These options are comma-separated, and specified in the format `option-name=value`. For example:
68+
//!
69+
//! ```shell
70+
//! $ RUSTC_COVERAGE_DEBUG_OPTIONS=counter-format=id+operation,allow-unused-expressions=yes cargo build
71+
//! ```
72+
//!
73+
//! Coverage debug options include:
74+
//!
75+
//! * `allow-unused-expressions=yes` or `no` (default: `no`)
76+
//!
77+
//! The `InstrumentCoverage` algorithms _should_ only create and assign expressions to a
78+
//! `BasicCoverageBlock`, or an incoming edge, if that expression is either (a) required to
79+
//! count a `CoverageSpan`, or (b) a dependency of some other required counter expression.
80+
//!
81+
//! If an expression is generated that does not map to a `CoverageSpan` or dependency, this
82+
//! probably indicates there was a bug in the algorithm that creates and assigns counters
83+
//! and expressions.
84+
//!
85+
//! When this kind of bug is encountered, the rustc compiler will panic by default. Setting:
86+
//! `allow-unused-expressions=yes` will log a warning message instead of panicking (effectively
87+
//! ignoring the unused expressions), which may be helpful when debugging the root cause of
88+
//! the problem.
89+
//!
90+
//! * `counter-format=<choices>`, where `<choices>` can be any plus-separated combination of `id`,
91+
//! `block`, and/or `operation` (default: `block+operation`)
92+
//!
93+
//! This option effects both the `CoverageGraph` (graphviz `.dot` files) and debug logging, when
94+
//! generating labels for counters and expressions.
95+
//!
96+
//! Depending on the values and combinations, counters can be labeled by:
97+
//!
98+
//! * `id` - counter or expression ID (ascending counter IDs, starting at 1, or descending
99+
//! expression IDs, starting at `u32:MAX`)
100+
//! * `block` - the `BasicCoverageBlock` label (for example, `bcb0`) or edge label (for
101+
//! example `bcb0->bcb1`), for counters or expressions assigned to count a
102+
//! `BasicCoverageBlock` or edge. Intermediate expressions (not directly associated with
103+
//! a BCB or edge) will be labeled by their expression ID, unless `operation` is also
104+
//! specified.
105+
//! * `operation` - applied to expressions only, labels include the left-hand-side counter
106+
//! or expression label (lhs operand), the operator (`+` or `-`), and the right-hand-side
107+
//! counter or expression (rhs operand). Expression operand labels are generated
108+
//! recursively, generating labels with nested operations, enclosed in parentheses
109+
//! (for example: `bcb2 + (bcb0 - bcb1)`).
110+
1111
use super::graph::{BasicCoverageBlock, BasicCoverageBlockData, CoverageGraph};
2112
use super::spans::CoverageSpan;
3113

@@ -20,21 +130,19 @@ const RUSTC_COVERAGE_DEBUG_OPTIONS: &str = "RUSTC_COVERAGE_DEBUG_OPTIONS";
20130
pub(crate) fn debug_options<'a>() -> &'a DebugOptions {
21131
static DEBUG_OPTIONS: SyncOnceCell<DebugOptions> = SyncOnceCell::new();
22132

23-
&DEBUG_OPTIONS.get_or_init(|| DebugOptions::new())
133+
&DEBUG_OPTIONS.get_or_init(|| DebugOptions::from_env())
24134
}
25135

26136
/// Parses and maintains coverage-specific debug options captured from the environment variable
27-
/// "RUSTC_COVERAGE_DEBUG_OPTIONS", if set. Options can be set on the command line by, for example:
28-
///
29-
/// $ RUSTC_COVERAGE_DEBUG_OPTIONS=counter-format=block,allow_unused_expressions=n cargo build
137+
/// "RUSTC_COVERAGE_DEBUG_OPTIONS", if set.
30138
#[derive(Debug, Clone)]
31139
pub(crate) struct DebugOptions {
32140
pub allow_unused_expressions: bool,
33141
counter_format: ExpressionFormat,
34142
}
35143

36144
impl DebugOptions {
37-
fn new() -> Self {
145+
fn from_env() -> Self {
38146
let mut allow_unused_expressions = true;
39147
let mut counter_format = ExpressionFormat::default();
40148

@@ -152,10 +260,11 @@ impl DebugCounters {
152260
}
153261

154262
pub fn enable(&mut self) {
263+
debug_assert!(!self.is_enabled());
155264
self.some_counters.replace(FxHashMap::default());
156265
}
157266

158-
pub fn is_enabled(&mut self) -> bool {
267+
pub fn is_enabled(&self) -> bool {
159268
self.some_counters.is_some()
160269
}
161270

@@ -294,12 +403,13 @@ impl GraphvizData {
294403
}
295404

296405
pub fn enable(&mut self) {
406+
debug_assert!(!self.is_enabled());
297407
self.some_bcb_to_coverage_spans_with_counters = Some(FxHashMap::default());
298408
self.some_bcb_to_dependency_counters = Some(FxHashMap::default());
299409
self.some_edge_to_counter = Some(FxHashMap::default());
300410
}
301411

302-
pub fn is_enabled(&mut self) -> bool {
412+
pub fn is_enabled(&self) -> bool {
303413
self.some_bcb_to_coverage_spans_with_counters.is_some()
304414
}
305415

@@ -399,11 +509,12 @@ impl UsedExpressions {
399509
}
400510

401511
pub fn enable(&mut self) {
512+
debug_assert!(!self.is_enabled());
402513
self.some_used_expression_operands = Some(FxHashMap::default());
403514
self.some_unused_expressions = Some(Vec::new());
404515
}
405516

406-
pub fn is_enabled(&mut self) -> bool {
517+
pub fn is_enabled(&self) -> bool {
407518
self.some_used_expression_operands.is_some()
408519
}
409520

@@ -416,7 +527,7 @@ impl UsedExpressions {
416527
}
417528
}
418529

419-
pub fn expression_is_used(&mut self, expression: &CoverageKind) -> bool {
530+
pub fn expression_is_used(&self, expression: &CoverageKind) -> bool {
420531
if let Some(used_expression_operands) = self.some_used_expression_operands.as_ref() {
421532
used_expression_operands.contains_key(&expression.as_operand_id())
422533
} else {

compiler/rustc_mir/src/transform/coverage/graph.rs

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ impl CoverageGraph {
8282
// each block terminator's `successors()`. Coverage spans must map to actual source code,
8383
// so compiler generated blocks and paths can be ignored. To that end, the CFG traversal
8484
// intentionally omits unwind paths.
85+
// FIXME(#78544): MIR InstrumentCoverage: Improve coverage of `#[should_panic]` tests and
86+
// `catch_unwind()` handlers.
8587
let mir_cfg_without_unwind = ShortCircuitPreorder::new(&mir_body, bcb_filtered_successors);
8688

8789
let mut basic_blocks = Vec::new();
@@ -288,7 +290,8 @@ rustc_index::newtype_index! {
288290
/// * The BCB CFG ignores (trims) branches not relevant to coverage, such as unwind-related code,
289291
/// that is injected by the Rust compiler but has no physical source code to count. This also
290292
/// means a BasicBlock with a `Call` terminator can be merged into its primary successor target
291-
/// block, in the same BCB.
293+
/// block, in the same BCB. (But, note: Issue #78544: "MIR InstrumentCoverage: Improve coverage
294+
/// of `#[should_panic]` tests and `catch_unwind()` handlers")
292295
/// * Some BasicBlock terminators support Rust-specific concerns--like borrow-checking--that are
293296
/// not relevant to coverage analysis. `FalseUnwind`, for example, can be treated the same as
294297
/// a `Goto`, and merged with its successor into the same BCB.
@@ -329,7 +332,6 @@ impl BasicCoverageBlockData {
329332
&mir_body[self.last_bb()].terminator()
330333
}
331334

332-
#[inline(always)]
333335
pub fn set_counter(
334336
&mut self,
335337
counter_kind: CoverageKind,
@@ -342,16 +344,15 @@ impl BasicCoverageBlockData {
342344
"attempt to add a `Counter` to a BCB target with existing incoming edge counters"
343345
);
344346
let operand = counter_kind.as_operand_id();
345-
let expect_none = self.counter_kind.replace(counter_kind);
346-
if expect_none.is_some() {
347-
return Error::from_string(format!(
347+
if let Some(replaced) = self.counter_kind.replace(counter_kind) {
348+
Error::from_string(format!(
348349
"attempt to set a BasicCoverageBlock coverage counter more than once; \
349350
{:?} already had counter {:?}",
350-
self,
351-
expect_none.unwrap(),
352-
));
351+
self, replaced,
352+
))
353+
} else {
354+
Ok(operand)
353355
}
354-
Ok(operand)
355356
}
356357

357358
#[inline(always)]
@@ -364,7 +365,6 @@ impl BasicCoverageBlockData {
364365
self.counter_kind.take()
365366
}
366367

367-
#[inline(always)]
368368
pub fn set_edge_counter_from(
369369
&mut self,
370370
from_bcb: BasicCoverageBlock,
@@ -383,22 +383,22 @@ impl BasicCoverageBlockData {
383383
}
384384
}
385385
let operand = counter_kind.as_operand_id();
386-
let expect_none = self
386+
if let Some(replaced) = self
387387
.edge_from_bcbs
388388
.get_or_insert_with(|| FxHashMap::default())
389-
.insert(from_bcb, counter_kind);
390-
if expect_none.is_some() {
391-
return Error::from_string(format!(
389+
.insert(from_bcb, counter_kind)
390+
{
391+
Error::from_string(format!(
392392
"attempt to set an edge counter more than once; from_bcb: \
393393
{:?} already had counter {:?}",
394-
from_bcb,
395-
expect_none.unwrap(),
396-
));
394+
from_bcb, replaced,
395+
))
396+
} else {
397+
Ok(operand)
397398
}
398-
Ok(operand)
399399
}
400400

401-
#[inline(always)]
401+
#[inline]
402402
pub fn edge_counter_from(&self, from_bcb: BasicCoverageBlock) -> Option<&CoverageKind> {
403403
if let Some(edge_from_bcbs) = &self.edge_from_bcbs {
404404
edge_from_bcbs.get(&from_bcb)
@@ -407,7 +407,7 @@ impl BasicCoverageBlockData {
407407
}
408408
}
409409

410-
#[inline(always)]
410+
#[inline]
411411
pub fn take_edge_counters(
412412
&mut self,
413413
) -> Option<impl Iterator<Item = (BasicCoverageBlock, CoverageKind)>> {
@@ -476,6 +476,9 @@ impl std::fmt::Debug for BcbBranch {
476476
}
477477
}
478478

479+
// Returns the `Terminator`s non-unwind successors.
480+
// FIXME(#78544): MIR InstrumentCoverage: Improve coverage of `#[should_panic]` tests and
481+
// `catch_unwind()` handlers.
479482
fn bcb_filtered_successors<'a, 'tcx>(
480483
body: &'tcx &'a mir::Body<'tcx>,
481484
term_kind: &'tcx TerminatorKind<'tcx>,
@@ -495,6 +498,7 @@ fn bcb_filtered_successors<'a, 'tcx>(
495498
/// Maintains separate worklists for each loop in the BasicCoverageBlock CFG, plus one for the
496499
/// CoverageGraph outside all loops. This supports traversing the BCB CFG in a way that
497500
/// ensures a loop is completely traversed before processing Blocks after the end of the loop.
501+
// FIXME(richkadel): Add unit tests for TraversalContext.
498502
#[derive(Debug)]
499503
pub(crate) struct TraversalContext {
500504
/// From one or more backedges returning to a loop header.
@@ -644,7 +648,27 @@ fn find_loop_backedges(
644648
let num_bcbs = basic_coverage_blocks.num_nodes();
645649
let mut backedges = IndexVec::from_elem_n(Vec::<BasicCoverageBlock>::new(), num_bcbs);
646650

647-
// Identify loops by their backedges
651+
// Identify loops by their backedges.
652+
//
653+
// The computational complexity is bounded by: n(s) x d where `n` is the number of
654+
// `BasicCoverageBlock` nodes (the simplified/reduced representation of the CFG derived from the
655+
// MIR); `s` is the average number of successors per node (which is most likely less than 2, and
656+
// independent of the size of the function, so it can be treated as a constant);
657+
// and `d` is the average number of dominators per node.
658+
//
659+
// The average number of dominators depends on the size and complexity of the function, and
660+
// nodes near the start of the function's control flow graph typically have less dominators
661+
// than nodes near the end of the CFG. Without doing a detailed mathematical analysis, I
662+
// think the resulting complexity has the characteristics of O(n log n).
663+
//
664+
// The overall complexity appears to be comparable to many other MIR transform algorithms, and I
665+
// don't expect that this function is creating a performance hot spot, but if this becomes an
666+
// issue, there may be ways to optimize the `is_dominated_by` algorithm (as indicated by an
667+
// existing `FIXME` comment in that code), or possibly ways to optimize it's usage here, perhaps
668+
// by keeping track of results for visited `BasicCoverageBlock`s if they can be used to short
669+
// circuit downstream `is_dominated_by` checks.
670+
//
671+
// For now, that kind of optimization seems unnecessarily complicated.
648672
for (bcb, _) in basic_coverage_blocks.iter_enumerated() {
649673
for &successor in &basic_coverage_blocks.successors[bcb] {
650674
if basic_coverage_blocks.is_dominated_by(bcb, successor) {

compiler/rustc_mir/src/transform/coverage/mod.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,6 @@ impl<'tcx> MirPass<'tcx> for InstrumentCoverage {
7474
trace!("InstrumentCoverage skipped for {:?} (not an FnLikeNode)", mir_source.def_id());
7575
return;
7676
}
77-
// FIXME(richkadel): By comparison, the MIR pass `ConstProp` includes associated constants,
78-
// with functions, methods, and closures. I assume Miri is used for associated constants as
79-
// well. If not, we may need to include them here too.
8077

8178
trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());
8279
Instrumentor::new(&self.name(), tcx, mir_body).inject_counters();
@@ -121,7 +118,10 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
121118
let mut graphviz_data = debug::GraphvizData::new();
122119
let mut debug_used_expressions = debug::UsedExpressions::new();
123120

124-
let dump_graphviz = tcx.sess.opts.debugging_opts.dump_mir_graphviz;
121+
let dump_mir = pretty::dump_enabled(tcx, self.pass_name, def_id);
122+
let dump_graphviz = dump_mir && tcx.sess.opts.debugging_opts.dump_mir_graphviz;
123+
let dump_spanview = dump_mir && tcx.sess.opts.debugging_opts.dump_mir_spanview.is_some();
124+
125125
if dump_graphviz {
126126
graphviz_data.enable();
127127
self.coverage_counters.enable_debug();
@@ -139,7 +139,7 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
139139
&self.basic_coverage_blocks,
140140
);
141141

142-
if pretty::dump_enabled(tcx, self.pass_name, def_id) {
142+
if dump_spanview {
143143
debug::dump_coverage_spanview(
144144
tcx,
145145
self.mir_body,
@@ -174,6 +174,13 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
174174
////////////////////////////////////////////////////
175175
// Remove the counter or edge counter from of each `CoverageSpan`s associated
176176
// `BasicCoverageBlock`, and inject a `Coverage` statement into the MIR.
177+
//
178+
// `Coverage` statements injected from `CoverageSpan`s will include the code regions
179+
// (source code start and end positions) to be counted by the associated counter.
180+
//
181+
// These `CoverageSpan`-associated counters are removed from their associated
182+
// `BasicCoverageBlock`s so that the only remaining counters in the `CoverageGraph`
183+
// are indirect counters (to be injected next, without associated code regions).
177184
self.inject_coverage_span_counters(
178185
coverage_spans,
179186
&mut graphviz_data,
@@ -262,6 +269,8 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
262269
bug!("Every BasicCoverageBlock should have a Counter or Expression");
263270
};
264271
graphviz_data.add_bcb_coverage_span_with_counter(bcb, &covspan, &counter_kind);
272+
// FIXME(#78542): Can spans for `TerminatorKind::Goto` be improved to avoid special
273+
// cases?
265274
let some_code_region = if self.is_code_region_redundant(bcb, span, body_span) {
266275
None
267276
} else {
@@ -280,6 +289,7 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
280289
///
281290
/// If this method returns `true`, the counter (which other `Expressions` may depend on) is
282291
/// still injected, but without an associated code region.
292+
// FIXME(#78542): Can spans for `TerminatorKind::Goto` be improved to avoid special cases?
283293
fn is_code_region_redundant(
284294
&self,
285295
bcb: BasicCoverageBlock,

0 commit comments

Comments
 (0)