diff options
Diffstat (limited to 'compiler/rustc_mir/src/transform/instrument_coverage.rs')
| -rw-r--r-- | compiler/rustc_mir/src/transform/instrument_coverage.rs | 300 |
1 files changed, 272 insertions, 28 deletions
diff --git a/compiler/rustc_mir/src/transform/instrument_coverage.rs b/compiler/rustc_mir/src/transform/instrument_coverage.rs index f60e6da714a..d3a2bd24123 100644 --- a/compiler/rustc_mir/src/transform/instrument_coverage.rs +++ b/compiler/rustc_mir/src/transform/instrument_coverage.rs @@ -1,23 +1,34 @@ use crate::transform::{MirPass, MirSource}; +use crate::util::pretty; +use crate::util::spanview::{ + source_range_no_file, statement_kind_name, terminator_kind_name, write_spanview_document, + SpanViewable, TOOLTIP_INDENT, +}; + use rustc_data_structures::fingerprint::Fingerprint; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; +use rustc_index::bit_set::BitSet; use rustc_middle::hir; use rustc_middle::ich::StableHashingContext; use rustc_middle::mir; use rustc_middle::mir::coverage::*; use rustc_middle::mir::visit::Visitor; -use rustc_middle::mir::{BasicBlock, Coverage, CoverageInfo, Location, Statement, StatementKind}; +use rustc_middle::mir::{ + BasicBlock, BasicBlockData, Coverage, CoverageInfo, Location, Statement, StatementKind, + TerminatorKind, +}; use rustc_middle::ty::query::Providers; use rustc_middle::ty::TyCtxt; use rustc_span::def_id::DefId; use rustc_span::{FileName, Pos, RealFileName, Span, Symbol}; -/// Inserts call to count_code_region() as a placeholder to be replaced during code generation with -/// the intrinsic llvm.instrprof.increment. +/// 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; -/// The `query` provider for `CoverageInfo`, requested by `codegen_intrinsic_call()` when -/// constructing the arguments for `llvm.instrprof.increment`. +/// The `query` provider for `CoverageInfo`, requested by `codegen_coverage()` (to inject each +/// counter) and `FunctionCoverage::new()` (to extract the coverage map metadata from the MIR). pub(crate) fn provide(providers: &mut Providers) { providers.coverageinfo = |tcx, def_id| coverageinfo_from_mir(tcx, def_id); } @@ -43,8 +54,8 @@ impl Visitor<'_> for CoverageVisitor { } } -fn coverageinfo_from_mir<'tcx>(tcx: TyCtxt<'tcx>, mir_def_id: DefId) -> CoverageInfo { - let mir_body = tcx.optimized_mir(mir_def_id); +fn coverageinfo_from_mir<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> CoverageInfo { + let mir_body = tcx.optimized_mir(def_id); // The `num_counters` argument to `llvm.instrprof.increment` is the number of injected // counters, with each counter having a counter ID from `0..num_counters-1`. MIR optimization @@ -63,18 +74,30 @@ fn coverageinfo_from_mir<'tcx>(tcx: TyCtxt<'tcx>, mir_def_id: DefId) -> Coverage } impl<'tcx> MirPass<'tcx> for InstrumentCoverage { - fn run_pass(&self, tcx: TyCtxt<'tcx>, src: MirSource<'tcx>, mir_body: &mut mir::Body<'tcx>) { + fn run_pass( + &self, + tcx: TyCtxt<'tcx>, + mir_source: MirSource<'tcx>, + mir_body: &mut mir::Body<'tcx>, + ) { // If the InstrumentCoverage pass is called on promoted MIRs, skip them. // See: https://github.com/rust-lang/rust/pull/73011#discussion_r438317601 - if src.promoted.is_none() { - Instrumentor::new(tcx, src, mir_body).inject_counters(); + if mir_source.promoted.is_none() { + Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters(); } } } +#[derive(Clone)] +struct CoverageRegion { + pub span: Span, + pub blocks: Vec<BasicBlock>, +} + struct Instrumentor<'a, 'tcx> { + pass_name: &'a str, tcx: TyCtxt<'tcx>, - mir_def_id: DefId, + mir_source: MirSource<'tcx>, mir_body: &'a mut mir::Body<'tcx>, hir_body: &'tcx rustc_hir::Body<'tcx>, function_source_hash: Option<u64>, @@ -83,12 +106,17 @@ struct Instrumentor<'a, 'tcx> { } impl<'a, 'tcx> Instrumentor<'a, 'tcx> { - fn new(tcx: TyCtxt<'tcx>, src: MirSource<'tcx>, mir_body: &'a mut mir::Body<'tcx>) -> Self { - let mir_def_id = src.def_id(); - let hir_body = hir_body(tcx, mir_def_id); + fn new( + pass_name: &'a str, + tcx: TyCtxt<'tcx>, + mir_source: MirSource<'tcx>, + mir_body: &'a mut mir::Body<'tcx>, + ) -> Self { + let hir_body = hir_body(tcx, mir_source.def_id()); Self { + pass_name, tcx, - mir_def_id, + mir_source, mir_body, hir_body, function_source_hash: None, @@ -127,19 +155,100 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { } fn inject_counters(&mut self) { + let tcx = self.tcx; + let def_id = self.mir_source.def_id(); + let mir_body = &self.mir_body; let body_span = self.hir_body.value.span; - debug!("instrumenting {:?}, span: {:?}", self.mir_def_id, body_span); - - // FIXME(richkadel): As a first step, counters are only injected at the top of each - // function. The complete solution will inject counters at each conditional code branch. - let block = rustc_middle::mir::START_BLOCK; - let counter = self.make_counter(); - self.inject_statement(counter, body_span, block); - - // FIXME(richkadel): The next step to implement source based coverage analysis will be - // instrumenting branches within functions, and some regions will be counted by "counter - // expression". The function to inject counter expression is implemented. Replace this - // "fake use" with real use. + debug!( + "instrumenting {:?}, span: {}", + def_id, + tcx.sess.source_map().span_to_string(body_span) + ); + + if !tcx.sess.opts.debugging_opts.experimental_coverage { + // Coverage at the function level should be accurate. This is the default implementation + // if `-Z experimental-coverage` is *NOT* enabled. + let block = rustc_middle::mir::START_BLOCK; + let counter = self.make_counter(); + self.inject_statement(counter, body_span, block); + return; + } + // FIXME(richkadel): else if `-Z experimental-coverage` *IS* enabled: Efforts are still in + // progress to identify the correct code region spans and associated counters to generate + // accurate Rust coverage reports. + + let block_span = |data: &BasicBlockData<'tcx>| { + // The default span will be the `Terminator` span; but until we have a smarter solution, + // the coverage region also incorporates at least the statements in this BasicBlock as + // well. Extend the span to encompass all, if possible. + // FIXME(richkadel): Assuming the terminator's span is already known to be contained in `body_span`. + let mut span = data.terminator().source_info.span; + // FIXME(richkadel): It's looking unlikely that we should compute a span from MIR + // spans, but if we do keep something like this logic, we will need a smarter way + // to combine `Statement`s and/or `Terminator`s with `Span`s from different + // files. + for statement_span in data.statements.iter().map(|statement| statement.source_info.span) + { + // Only combine Spans from the function's body_span. + if body_span.contains(statement_span) { + span = span.to(statement_span); + } + } + span + }; + + // Traverse the CFG but ignore anything following an `unwind` + let cfg_without_unwind = ShortCircuitPreorder::new(mir_body, |term_kind| { + let mut successors = term_kind.successors(); + match &term_kind { + // SwitchInt successors are never unwind, and all of them should be traversed + TerminatorKind::SwitchInt { .. } => successors, + // For all other kinds, return only the first successor, if any, and ignore unwinds + _ => successors.next().into_iter().chain(&[]), + } + }); + + let mut coverage_regions = Vec::with_capacity(cfg_without_unwind.size_hint().0); + for (bb, data) in cfg_without_unwind { + if !body_span.contains(data.terminator().source_info.span) { + continue; + } + + // FIXME(richkadel): Regions will soon contain multiple blocks. + let mut blocks = Vec::new(); + blocks.push(bb); + let span = block_span(data); + coverage_regions.push(CoverageRegion { span, blocks }); + } + + let span_viewables = if pretty::dump_enabled(tcx, self.pass_name, def_id) { + Some(self.span_viewables(&coverage_regions)) + } else { + None + }; + + // Inject counters for the selected spans + for CoverageRegion { span, blocks } in coverage_regions { + debug!( + "Injecting counter at: {:?}:\n{}\n==========", + span, + tcx.sess.source_map().span_to_snippet(span).expect("Error getting source for span"), + ); + let counter = self.make_counter(); + self.inject_statement(counter, span, blocks[0]); + } + + if let Some(span_viewables) = span_viewables { + let mut file = + pretty::create_dump_file(tcx, "html", None, self.pass_name, &0, self.mir_source) + .expect("Unexpected error creating MIR spanview HTML file"); + write_spanview_document(tcx, def_id, span_viewables, &mut file) + .expect("Unexpected IO error dumping coverage spans as HTML"); + } + + // FIXME(richkadel): Some regions will be counted by "counter expression". Counter + // expressions are supported, but are not yet generated. When they are, remove this `fake_use` + // block. let fake_use = false; if fake_use { let add = false; @@ -193,6 +302,83 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { }; data.statements.push(statement); } + + /// Converts the computed `CoverageRegion`s into `SpanViewable`s. + fn span_viewables(&self, coverage_regions: &Vec<CoverageRegion>) -> Vec<SpanViewable> { + let mut span_viewables = Vec::new(); + for coverage_region in coverage_regions { + span_viewables.push(SpanViewable { + span: coverage_region.span, + title: format!("{}", coverage_region.blocks[0].index()), + tooltip: self.make_tooltip_text(coverage_region), + }); + } + span_viewables + } + + /// A custom tooltip renderer used in a spanview HTML+CSS document used for coverage analysis. + fn make_tooltip_text(&self, coverage_region: &CoverageRegion) -> String { + const INCLUDE_COVERAGE_STATEMENTS: bool = false; + let tcx = self.tcx; + let source_map = tcx.sess.source_map(); + let mut text = Vec::new(); + for (i, &bb) in coverage_region.blocks.iter().enumerate() { + if i > 0 { + text.push("\n".to_owned()); + } + text.push(format!("{:?}: {}:", bb, &source_map.span_to_string(coverage_region.span))); + let data = &self.mir_body.basic_blocks()[bb]; + for statement in &data.statements { + let statement_string = match statement.kind { + StatementKind::Coverage(box ref coverage) => match coverage.kind { + CoverageKind::Counter { id, .. } => { + if !INCLUDE_COVERAGE_STATEMENTS { + continue; + } + format!("increment counter #{}", id.index()) + } + CoverageKind::Expression { id, lhs, op, rhs } => { + if !INCLUDE_COVERAGE_STATEMENTS { + continue; + } + format!( + "expression #{} = {} {} {}", + id.index(), + lhs.index(), + if op == Op::Add { "+" } else { "-" }, + rhs.index() + ) + } + CoverageKind::Unreachable => { + if !INCLUDE_COVERAGE_STATEMENTS { + continue; + } + format!("unreachable") + } + }, + _ => format!("{:?}", statement), + }; + let source_range = source_range_no_file(tcx, &statement.source_info.span); + text.push(format!( + "\n{}{}: {}: {}", + TOOLTIP_INDENT, + source_range, + statement_kind_name(statement), + statement_string + )); + } + let term = data.terminator(); + let source_range = source_range_no_file(tcx, &term.source_info.span); + text.push(format!( + "\n{}{}: {}: {:?}", + TOOLTIP_INDENT, + source_range, + terminator_kind_name(term), + term.kind + )); + } + text.join("") + } } /// Convert the Span into its file name, start line and column, and end line and column @@ -227,7 +413,7 @@ fn make_code_region<'tcx>(tcx: TyCtxt<'tcx>, span: &Span) -> CodeRegion { } 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("DefId is local"); + 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) } @@ -245,3 +431,61 @@ fn hash( node.hash_stable(hcx, &mut stable_hasher); stable_hasher.finish() } + +pub struct ShortCircuitPreorder< + 'a, + 'tcx, + F: Fn(&'tcx TerminatorKind<'tcx>) -> mir::Successors<'tcx>, +> { + body: &'a mir::Body<'tcx>, + visited: BitSet<BasicBlock>, + worklist: Vec<BasicBlock>, + filtered_successors: F, +} + +impl<'a, 'tcx, F: Fn(&'tcx TerminatorKind<'tcx>) -> mir::Successors<'tcx>> + ShortCircuitPreorder<'a, 'tcx, F> +{ + pub fn new( + body: &'a mir::Body<'tcx>, + filtered_successors: F, + ) -> ShortCircuitPreorder<'a, 'tcx, F> { + let worklist = vec![mir::START_BLOCK]; + + ShortCircuitPreorder { + body, + visited: BitSet::new_empty(body.basic_blocks().len()), + worklist, + filtered_successors, + } + } +} + +impl<'a: 'tcx, 'tcx, F: Fn(&'tcx TerminatorKind<'tcx>) -> mir::Successors<'tcx>> Iterator + for ShortCircuitPreorder<'a, 'tcx, F> +{ + type Item = (BasicBlock, &'a BasicBlockData<'tcx>); + + fn next(&mut self) -> Option<(BasicBlock, &'a BasicBlockData<'tcx>)> { + while let Some(idx) = self.worklist.pop() { + if !self.visited.insert(idx) { + continue; + } + + let data = &self.body[idx]; + + if let Some(ref term) = data.terminator { + self.worklist.extend((self.filtered_successors)(&term.kind)); + } + + return Some((idx, data)); + } + + None + } + + fn size_hint(&self) -> (usize, Option<usize>) { + let size = self.body.basic_blocks().len() - self.visited.count(); + (size, Some(size)) + } +} |
