about summary refs log tree commit diff
path: root/compiler/rustc_mir/src/transform/coverage/mod.rs
diff options
context:
space:
mode:
authorRich Kadel <richkadel@google.com>2020-10-23 00:45:07 -0700
committerRich Kadel <richkadel@google.com>2020-11-05 18:24:12 -0800
commitc7ae4c2cb6d7e58ad0f9c12047e3d747c26a9d71 (patch)
treefdbb5014c0353a7a29a61b07364575b04d81362a /compiler/rustc_mir/src/transform/coverage/mod.rs
parentc7747cc772f4b4c30ede3616678d7a3bc2c89bf7 (diff)
downloadrust-c7ae4c2cb6d7e58ad0f9c12047e3d747c26a9d71.tar.gz
rust-c7ae4c2cb6d7e58ad0f9c12047e3d747c26a9d71.zip
Splitting transform/instrument_coverage.rs into transform/coverage/...
Diffstat (limited to 'compiler/rustc_mir/src/transform/coverage/mod.rs')
-rw-r--r--compiler/rustc_mir/src/transform/coverage/mod.rs241
1 files changed, 241 insertions, 0 deletions
diff --git a/compiler/rustc_mir/src/transform/coverage/mod.rs b/compiler/rustc_mir/src/transform/coverage/mod.rs
new file mode 100644
index 00000000000..9961afba8e7
--- /dev/null
+++ b/compiler/rustc_mir/src/transform/coverage/mod.rs
@@ -0,0 +1,241 @@
+pub mod query;
+
+mod counters;
+mod debug;
+mod graph;
+mod spans;
+
+use counters::CoverageCounters;
+use graph::BasicCoverageBlocks;
+use spans::{CoverageSpan, CoverageSpans};
+
+use crate::transform::MirPass;
+use crate::util::pretty;
+
+use rustc_data_structures::fingerprint::Fingerprint;
+use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
+use rustc_data_structures::sync::Lrc;
+use rustc_index::vec::IndexVec;
+use rustc_middle::hir;
+use rustc_middle::hir::map::blocks::FnLikeNode;
+use rustc_middle::ich::StableHashingContext;
+use rustc_middle::mir::coverage::*;
+use rustc_middle::mir::{self, BasicBlock, Coverage, Statement, StatementKind};
+use rustc_middle::ty::TyCtxt;
+use rustc_span::def_id::DefId;
+use rustc_span::{CharPos, Pos, SourceFile, Span, Symbol};
+
+/// Inserts `StatementKind::Coverage` statements that either instrument the binary with injected
+/// counters, via intrinsic `llvm.instrprof.increment`, and/or inject metadata used during codegen
+/// to construct the coverage map.
+pub struct InstrumentCoverage;
+
+impl<'tcx> MirPass<'tcx> for InstrumentCoverage {
+    fn run_pass(&self, tcx: TyCtxt<'tcx>, mir_body: &mut mir::Body<'tcx>) {
+        let mir_source = mir_body.source;
+
+        // If the InstrumentCoverage pass is called on promoted MIRs, skip them.
+        // See: https://github.com/rust-lang/rust/pull/73011#discussion_r438317601
+        if mir_source.promoted.is_some() {
+            trace!(
+                "InstrumentCoverage skipped for {:?} (already promoted for Miri evaluation)",
+                mir_source.def_id()
+            );
+            return;
+        }
+
+        let hir_id = tcx.hir().local_def_id_to_hir_id(mir_source.def_id().expect_local());
+        let is_fn_like = FnLikeNode::from_node(tcx.hir().get(hir_id)).is_some();
+
+        // Only instrument functions, methods, and closures (not constants since they are evaluated
+        // at compile time by Miri).
+        // FIXME(#73156): Handle source code coverage in const eval, but note, if and when const
+        // expressions get coverage spans, we will probably have to "carve out" space for const
+        // expressions from coverage spans in enclosing MIR's, like we do for closures. (That might
+        // be tricky if const expressions have no corresponding statements in the enclosing MIR.
+        // Closures are carved out by their initial `Assign` statement.)
+        if !is_fn_like {
+            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();
+        trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());
+    }
+}
+
+struct Instrumentor<'a, 'tcx> {
+    pass_name: &'a str,
+    tcx: TyCtxt<'tcx>,
+    mir_body: &'a mut mir::Body<'tcx>,
+    body_span: Span,
+    basic_coverage_blocks: BasicCoverageBlocks,
+    coverage_counters: CoverageCounters,
+}
+
+impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
+    fn new(pass_name: &'a str, tcx: TyCtxt<'tcx>, mir_body: &'a mut mir::Body<'tcx>) -> Self {
+        let hir_body = hir_body(tcx, mir_body.source.def_id());
+        let body_span = hir_body.value.span;
+        let function_source_hash = hash_mir_source(tcx, hir_body);
+        let basic_coverage_blocks = BasicCoverageBlocks::from_mir(mir_body);
+        Self {
+            pass_name,
+            tcx,
+            mir_body,
+            body_span,
+            basic_coverage_blocks,
+            coverage_counters: CoverageCounters::new(function_source_hash),
+        }
+    }
+
+    fn inject_counters(&'a mut self) {
+        let tcx = self.tcx;
+        let source_map = tcx.sess.source_map();
+        let mir_source = self.mir_body.source;
+        let def_id = mir_source.def_id();
+        let body_span = self.body_span;
+
+        debug!("instrumenting {:?}, span: {}", def_id, source_map.span_to_string(body_span));
+
+        ////////////////////////////////////////////////////
+        // Compute `CoverageSpan`s from the `BasicCoverageBlocks`.
+        let coverage_spans = CoverageSpans::generate_coverage_spans(
+            &self.mir_body,
+            body_span,
+            &self.basic_coverage_blocks,
+        );
+
+        if pretty::dump_enabled(tcx, self.pass_name, def_id) {
+            debug::dump_coverage_spanview(
+                tcx,
+                self.mir_body,
+                &self.basic_coverage_blocks,
+                self.pass_name,
+                &coverage_spans,
+            );
+        }
+
+        self.inject_coverage_span_counters(coverage_spans);
+    }
+
+    /// Inject a counter for each `CoverageSpan`. There can be multiple `CoverageSpan`s for a given
+    /// BCB, but only one actual counter needs to be incremented per BCB. `bcb_counters` maps each
+    /// `bcb` to its `Counter`, when injected. Subsequent `CoverageSpan`s for a BCB that already has
+    /// a `Counter` will inject an `Expression` instead, and compute its value by adding `ZERO` to
+    /// the BCB `Counter` value.
+    fn inject_coverage_span_counters(&mut self, coverage_spans: Vec<CoverageSpan>) {
+        let tcx = self.tcx;
+        let source_map = tcx.sess.source_map();
+        let body_span = self.body_span;
+        let source_file = source_map.lookup_source_file(body_span.lo());
+        let file_name = Symbol::intern(&source_file.name.to_string());
+
+        let mut bb_counters = IndexVec::from_elem_n(None, self.mir_body.basic_blocks().len());
+        for CoverageSpan { span, bcb_leader_bb: bb, .. } in coverage_spans {
+            if let Some(&counter_operand) = bb_counters[bb].as_ref() {
+                let expression = self.coverage_counters.make_expression(
+                    counter_operand,
+                    Op::Add,
+                    ExpressionOperandId::ZERO,
+                );
+                debug!(
+                    "Injecting counter expression {:?} at: {:?}:\n{}\n==========",
+                    expression,
+                    span,
+                    source_map.span_to_snippet(span).expect("Error getting source for span"),
+                );
+                let code_region = make_code_region(file_name, &source_file, span, body_span);
+                inject_statement(self.mir_body, expression, bb, Some(code_region));
+            } else {
+                let counter = self.coverage_counters.make_counter();
+                debug!(
+                    "Injecting counter {:?} at: {:?}:\n{}\n==========",
+                    counter,
+                    span,
+                    source_map.span_to_snippet(span).expect("Error getting source for span"),
+                );
+                let counter_operand = counter.as_operand_id();
+                bb_counters[bb] = Some(counter_operand);
+                let code_region = make_code_region(file_name, &source_file, span, body_span);
+                inject_statement(self.mir_body, counter, bb, Some(code_region));
+            }
+        }
+    }
+}
+
+fn inject_statement(
+    mir_body: &mut mir::Body<'tcx>,
+    counter_kind: CoverageKind,
+    bb: BasicBlock,
+    some_code_region: Option<CodeRegion>,
+) {
+    debug!(
+        "  injecting statement {:?} for {:?} at code region: {:?}",
+        counter_kind, bb, some_code_region
+    );
+    let data = &mut mir_body[bb];
+    let source_info = data.terminator().source_info;
+    let statement = Statement {
+        source_info,
+        kind: StatementKind::Coverage(box Coverage {
+            kind: counter_kind,
+            code_region: some_code_region,
+        }),
+    };
+    data.statements.push(statement);
+}
+
+/// Convert the Span into its file name, start line and column, and end line and column
+fn make_code_region(
+    file_name: Symbol,
+    source_file: &Lrc<SourceFile>,
+    span: Span,
+    body_span: Span,
+) -> CodeRegion {
+    let (start_line, mut start_col) = source_file.lookup_file_pos(span.lo());
+    let (end_line, end_col) = if span.hi() == span.lo() {
+        let (end_line, mut end_col) = (start_line, start_col);
+        // Extend an empty span by one character so the region will be counted.
+        let CharPos(char_pos) = start_col;
+        if span.hi() == body_span.hi() {
+            start_col = CharPos(char_pos - 1);
+        } else {
+            end_col = CharPos(char_pos + 1);
+        }
+        (end_line, end_col)
+    } else {
+        source_file.lookup_file_pos(span.hi())
+    };
+    CodeRegion {
+        file_name,
+        start_line: start_line as u32,
+        start_col: start_col.to_u32() + 1,
+        end_line: end_line as u32,
+        end_col: end_col.to_u32() + 1,
+    }
+}
+
+fn hir_body<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> &'tcx rustc_hir::Body<'tcx> {
+    let hir_node = tcx.hir().get_if_local(def_id).expect("expected DefId is local");
+    let fn_body_id = hir::map::associated_body(hir_node).expect("HIR node is a function with body");
+    tcx.hir().body(fn_body_id)
+}
+
+fn hash_mir_source<'tcx>(tcx: TyCtxt<'tcx>, hir_body: &'tcx rustc_hir::Body<'tcx>) -> u64 {
+    let mut hcx = tcx.create_no_span_stable_hashing_context();
+    hash(&mut hcx, &hir_body.value).to_smaller_hash()
+}
+
+fn hash(
+    hcx: &mut StableHashingContext<'tcx>,
+    node: &impl HashStable<StableHashingContext<'tcx>>,
+) -> Fingerprint {
+    let mut stable_hasher = StableHasher::new();
+    node.hash_stable(hcx, &mut stable_hasher);
+    stable_hasher.finish()
+}