From 5d13cbc98623ae33fac4095ac6a943b926cd8ffd Mon Sep 17 00:00:00 2001 From: Rich Kadel Date: Mon, 27 Jul 2020 16:09:44 -0700 Subject: [PATCH] Tools and tests to experimentally add more counters per function Adds a new mir_dump output file in HTML/CSS to visualize code regions and the MIR features that they came from (including overlapping spans). Adds `--bless` support to `test/run-make-fulldeps` so expected coverage output can be quickly regenerated. Adds a new `-Zexperimental-coverage` option that implies `-Zinstrument-coverage`, but can be used to enable experimental coverage counter injection. Includes a basic, MIR-block-based implementation of coverage injection, available via `-Zexperimental-coverage`. This implementation has known flaws and omissions, but is simple enough to validate the new tools and tests. The existing `-Zinstrument-coverage` option currently enables function-level coverage only, which at least appears to generate accurate coverage reports at that level. Experimental coverage is not accurate at this time. When branch coverage works as intended, the `-Zexperimental-coverage` option should be removed. Also, learned that `-C link-dead-code` (which was enabled automatically under `-Z instrument-coverage`) was causing the linking error that resulted in segmentation faults in coverage instrumented binaries. Link dead code is now disabled under MSVC, allowing `-Zinstrument-coverage` to be enabled under MSVC for the first time. Rust compiler MCP rust-lang/compiler-team#278 Relevant issue: #34701 - Implement support for LLVMs code coverage instrumentation --- library/profiler_builtins/build.rs | 1 + src/librustc_codegen_ssa/back/link.rs | 2 +- src/librustc_middle/mir/mod.rs | 19 +- src/librustc_middle/mir/mono.rs | 2 +- .../monomorphize/partitioning/mod.rs | 4 +- .../transform/instrument_coverage.rs | 598 +++++++++++++++++- src/librustc_session/config.rs | 21 +- src/librustc_session/options.rs | 11 +- src/librustc_session/session.rs | 40 +- src/librustc_span/lib.rs | 7 + src/librustc_span/source_map.rs | 9 + ...ument_coverage.bar.InstrumentCoverage.diff | 2 +- ...ment_coverage.main.InstrumentCoverage.diff | 2 +- .../Makefile | 67 ++ ...d_export_coverage.coverage_of_if_else.json | 59 ++ .../expected_export_coverage.testprog.json} | 48 +- .../prettify_json.py | 0 ...ical_show_coverage.coverage_of_if_else.txt | 64 ++ .../typical_show_coverage.testprog.txt | 55 ++ .../Makefile | 11 + ...d_export_coverage.coverage_of_if_else.json | 59 ++ ...ical_show_coverage.coverage_of_if_else.txt | 64 ++ .../instrument-coverage-llvm-ir-base/Makefile | 65 ++ .../filecheck.testprog.txt | 49 ++ .../testprog.rs | 0 .../Makefile | 11 + .../Makefile | 34 + ...lse.main.-------.InstrumentCoverage.0.html | 583 +++++++++++++++++ ...losure}}.-------.InstrumentCoverage.0.html | 81 +++ ...ure}}[1].-------.InstrumentCoverage.0.html | 81 +++ ...ure}}[2].-------.InstrumentCoverage.0.html | 81 +++ ...rog.main.-------.InstrumentCoverage.0.html | 185 ++++++ ...og.print.-------.InstrumentCoverage.0.html | 99 +++ ...e_called.-------.InstrumentCoverage.0.html | 90 +++ ...e_called.-------.InstrumentCoverage.0.html | 85 +++ ...rap_with.-------.InstrumentCoverage.0.html | 103 +++ .../Makefile | 11 + ...lse.main.-------.InstrumentCoverage.0.html | 583 +++++++++++++++++ ...losure}}.-------.InstrumentCoverage.0.html | 81 +++ ...ure}}[1].-------.InstrumentCoverage.0.html | 81 +++ ...ure}}[2].-------.InstrumentCoverage.0.html | 81 +++ ...rog.main.-------.InstrumentCoverage.0.html | 185 ++++++ ...og.print.-------.InstrumentCoverage.0.html | 99 +++ ...e_called.-------.InstrumentCoverage.0.html | 90 +++ ...e_called.-------.InstrumentCoverage.0.html | 85 +++ ...rap_with.-------.InstrumentCoverage.0.html | 103 +++ .../instrument-coverage/Makefile | 103 --- .../compiletest-ignore-dir | 3 + .../coverage_of_if_else.rs | 51 ++ .../instrument-coverage/coverage_tools.mk | 41 ++ .../filecheck-patterns.txt | 51 -- .../typical_show_coverage.txt | 55 -- src/tools/compiletest/src/runtest.rs | 12 + 53 files changed, 4108 insertions(+), 299 deletions(-) create mode 100644 src/test/run-make-fulldeps/instrument-coverage-cov-reports-base/Makefile create mode 100644 src/test/run-make-fulldeps/instrument-coverage-cov-reports-base/expected_export_coverage.coverage_of_if_else.json rename src/test/run-make-fulldeps/{instrument-coverage/expected_export_coverage.json => instrument-coverage-cov-reports-base/expected_export_coverage.testprog.json} (64%) rename src/test/run-make-fulldeps/{instrument-coverage => instrument-coverage-cov-reports-base}/prettify_json.py (100%) create mode 100644 src/test/run-make-fulldeps/instrument-coverage-cov-reports-base/typical_show_coverage.coverage_of_if_else.txt create mode 100644 src/test/run-make-fulldeps/instrument-coverage-cov-reports-base/typical_show_coverage.testprog.txt create mode 100644 src/test/run-make-fulldeps/instrument-coverage-cov-reports-link-dead-code/Makefile create mode 100644 src/test/run-make-fulldeps/instrument-coverage-cov-reports-link-dead-code/expected_export_coverage.coverage_of_if_else.json create mode 100644 src/test/run-make-fulldeps/instrument-coverage-cov-reports-link-dead-code/typical_show_coverage.coverage_of_if_else.txt create mode 100644 src/test/run-make-fulldeps/instrument-coverage-llvm-ir-base/Makefile create mode 100644 src/test/run-make-fulldeps/instrument-coverage-llvm-ir-base/filecheck.testprog.txt rename src/test/run-make-fulldeps/{instrument-coverage => instrument-coverage-llvm-ir-base}/testprog.rs (100%) create mode 100644 src/test/run-make-fulldeps/instrument-coverage-llvm-ir-link-dead-code/Makefile create mode 100644 src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/Makefile create mode 100644 src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.coverage_of_if_else/coverage_of_if_else.main.-------.InstrumentCoverage.0.html create mode 100644 src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.main-{{closure}}.-------.InstrumentCoverage.0.html create mode 100644 src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.main-{{closure}}[1].-------.InstrumentCoverage.0.html create mode 100644 src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.main-{{closure}}[2].-------.InstrumentCoverage.0.html create mode 100644 src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.main.-------.InstrumentCoverage.0.html create mode 100644 src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.print.-------.InstrumentCoverage.0.html create mode 100644 src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.will_be_called.-------.InstrumentCoverage.0.html create mode 100644 src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.will_not_be_called.-------.InstrumentCoverage.0.html create mode 100644 src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.wrap_with.-------.InstrumentCoverage.0.html create mode 100644 src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/Makefile create mode 100644 src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.coverage_of_if_else/coverage_of_if_else.main.-------.InstrumentCoverage.0.html create mode 100644 src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.main-{{closure}}.-------.InstrumentCoverage.0.html create mode 100644 src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.main-{{closure}}[1].-------.InstrumentCoverage.0.html create mode 100644 src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.main-{{closure}}[2].-------.InstrumentCoverage.0.html create mode 100644 src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.main.-------.InstrumentCoverage.0.html create mode 100644 src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.print.-------.InstrumentCoverage.0.html create mode 100644 src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.will_be_called.-------.InstrumentCoverage.0.html create mode 100644 src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.will_not_be_called.-------.InstrumentCoverage.0.html create mode 100644 src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.wrap_with.-------.InstrumentCoverage.0.html delete mode 100644 src/test/run-make-fulldeps/instrument-coverage/Makefile create mode 100644 src/test/run-make-fulldeps/instrument-coverage/compiletest-ignore-dir create mode 100644 src/test/run-make-fulldeps/instrument-coverage/coverage_of_if_else.rs create mode 100644 src/test/run-make-fulldeps/instrument-coverage/coverage_tools.mk delete mode 100644 src/test/run-make-fulldeps/instrument-coverage/filecheck-patterns.txt delete mode 100644 src/test/run-make-fulldeps/instrument-coverage/typical_show_coverage.txt diff --git a/library/profiler_builtins/build.rs b/library/profiler_builtins/build.rs index b674f73ebf390..2a5d5853fec64 100644 --- a/library/profiler_builtins/build.rs +++ b/library/profiler_builtins/build.rs @@ -20,6 +20,7 @@ fn main() { "InstrProfilingMergeFile.c", "InstrProfilingNameVar.c", "InstrProfilingPlatformDarwin.c", + "InstrProfilingPlatformFuchsia.c", "InstrProfilingPlatformLinux.c", "InstrProfilingPlatformOther.c", "InstrProfilingPlatformWindows.c", diff --git a/src/librustc_codegen_ssa/back/link.rs b/src/librustc_codegen_ssa/back/link.rs index bfcf979d12548..270c8250e1981 100644 --- a/src/librustc_codegen_ssa/back/link.rs +++ b/src/librustc_codegen_ssa/back/link.rs @@ -1668,7 +1668,7 @@ fn linker_with_args<'a, B: ArchiveBuilder<'a>>( // FIXME: Order dependent, applies to the following objects. Where should it be placed? // Try to strip as much out of the generated object by removing unused // sections if possible. See more comments in linker.rs - if sess.opts.cg.link_dead_code != Some(true) { + if !sess.link_dead_code() { let keep_metadata = crate_type == CrateType::Dylib; cmd.gc_sections(keep_metadata); } diff --git a/src/librustc_middle/mir/mod.rs b/src/librustc_middle/mir/mod.rs index ad40cf221bc32..2254c495bf025 100644 --- a/src/librustc_middle/mir/mod.rs +++ b/src/librustc_middle/mir/mod.rs @@ -1521,7 +1521,24 @@ impl Debug for Statement<'_> { AscribeUserType(box (ref place, ref c_ty), ref variance) => { write!(fmt, "AscribeUserType({:?}, {:?}, {:?})", place, variance, c_ty) } - Coverage(box ref coverage) => write!(fmt, "{:?}", coverage), + Coverage(box ref coverage) => { + let rgn = &coverage.code_region; + match coverage.kind { + CoverageKind::Counter { id, .. } => { + write!(fmt, "Coverage counter({:?}) for {:?}", id.index(), rgn) + } + CoverageKind::Expression { id, lhs, op, rhs } => write!( + fmt, + "Coverage expr({:?}) = {} {} {} for {:?}", + id.index(), + lhs.index(), + if op == coverage::Op::Add { "+" } else { "-" }, + rhs.index(), + rgn + ), + CoverageKind::Unreachable => write!(fmt, "Coverage unreachable for {:?}", rgn), + } + } Nop => write!(fmt, "nop"), } } diff --git a/src/librustc_middle/mir/mono.rs b/src/librustc_middle/mir/mono.rs index 0d5f6619df5e0..12bf988ab2dfe 100644 --- a/src/librustc_middle/mir/mono.rs +++ b/src/librustc_middle/mir/mono.rs @@ -86,7 +86,7 @@ impl<'tcx> MonoItem<'tcx> { .debugging_opts .inline_in_all_cgus .unwrap_or_else(|| tcx.sess.opts.optimize != OptLevel::No) - && tcx.sess.opts.cg.link_dead_code != Some(true); + && !tcx.sess.link_dead_code(); match *self { MonoItem::Fn(ref instance) => { diff --git a/src/librustc_mir/monomorphize/partitioning/mod.rs b/src/librustc_mir/monomorphize/partitioning/mod.rs index 9dfbd65e1b1a8..b7638204c09b4 100644 --- a/src/librustc_mir/monomorphize/partitioning/mod.rs +++ b/src/librustc_mir/monomorphize/partitioning/mod.rs @@ -190,7 +190,7 @@ pub fn partition<'tcx>( // Next we try to make as many symbols "internal" as possible, so LLVM has // more freedom to optimize. - if tcx.sess.opts.cg.link_dead_code != Some(true) { + if !tcx.sess.link_dead_code() { let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_internalize_symbols"); partitioner.internalize_symbols(tcx, &mut post_inlining, inlining_map); } @@ -327,7 +327,7 @@ fn collect_and_partition_mono_items<'tcx>( } } None => { - if tcx.sess.opts.cg.link_dead_code == Some(true) { + if tcx.sess.link_dead_code() { MonoItemCollectionMode::Eager } else { MonoItemCollectionMode::Lazy diff --git a/src/librustc_mir/transform/instrument_coverage.rs b/src/librustc_mir/transform/instrument_coverage.rs index f60e6da714a65..fc6a3c7d21244 100644 --- a/src/librustc_mir/transform/instrument_coverage.rs +++ b/src/librustc_mir/transform/instrument_coverage.rs @@ -1,23 +1,37 @@ use crate::transform::{MirPass, MirSource}; +use crate::util::pretty; + 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, + Terminator, 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}; +use rustc_span::{BytePos, FileName, Pos, RealFileName, Span, Symbol}; + +use std::fs; +use std::io::{self, Write}; +use std::iter::Peekable; -/// 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`. +const INDENT: &str = " "; +const NEW_LINE_SPAN: &str = "\n"; + +/// 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 +57,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 +77,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, +} + 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, @@ -83,12 +109,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 +158,113 @@ 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(&[]), + } + }); + + const INJECT_COUNTERS_AT_BRANCH_BLOCKS_ONLY: bool = false; + + let mut coverage_regions = Vec::with_capacity(cfg_without_unwind.size_hint().0); + for (bb, data) in cfg_without_unwind { + if INJECT_COUNTERS_AT_BRANCH_BLOCKS_ONLY { + // Additional filtering, if enabled + match data.terminator().kind { + TerminatorKind::Goto { .. } + | TerminatorKind::Return { .. } + | TerminatorKind::Abort + | TerminatorKind::FalseEdge { .. } + | TerminatorKind::Assert { .. } + | TerminatorKind::Yield { .. } + | TerminatorKind::SwitchInt { .. } => {} + _ => continue, + } + } + + 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 coverage_regions_to_dump = if pretty::dump_enabled(tcx, self.pass_name, def_id) { + Some(coverage_regions.clone()) + } 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(coverage_regions_to_dump) = coverage_regions_to_dump { + self.dump_spans(fn_span(tcx, def_id), coverage_regions_to_dump) + .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 +318,357 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { }; data.statements.push(statement); } + + fn dump_spans( + &self, + body_span: Span, + mut coverage_regions_to_dump: Vec, + ) -> io::Result<()> { + let tcx = self.tcx; + let mut file = + pretty::create_dump_file(tcx, "html", None, self.pass_name, &0, self.mir_source)?; + let header = r#" + + + coverage_of_if_else - Code Regions + + +"#; + + let footer = r#" + +"#; + + writeln!(file, "{}", header)?; + let mut next_pos = body_span.lo(); + let end_pos = body_span.hi(); + let source_map = tcx.sess.source_map(); + let start = source_map.lookup_char_pos(next_pos); + write!( + file, + r#"
{}"#, + start.line - 1, + " ".repeat(start.col.to_usize()) + )?; + coverage_regions_to_dump.sort_unstable_by(|a, b| { + let a = a.span; + let b = b.span; + if a.lo() == b.lo() { + // Sort hi() in reverse order so shorter spans are attempted after longer spans. + // This should give shorter spans a higher "layer", so they are not covered by + // the longer spans. + b.hi().partial_cmp(&a.hi()) + } else { + a.lo().partial_cmp(&b.lo()) + } + .unwrap() + }); + let mut ordered_coverage_regions = coverage_regions_to_dump.iter().peekable(); + let mut alt = false; + while ordered_coverage_regions.peek().is_some() { + next_pos = self.write_coverage_regions( + next_pos, + &mut ordered_coverage_regions, + false, + 1, + &mut file, + )?; + alt = !alt; + } + if next_pos < end_pos { + self.write_coverage_gap(next_pos, end_pos, &mut file)?; + } + write!(file, r#"
"#)?; + writeln!(file, "{}", footer)?; + Ok(()) + } + + /// Recursively process each ordered span. Spans that overlap will have progressively varying + /// styles, such as increased padding for each overlap. Non-overlapping adjacent spans will + /// have alternating style choices, to help distinguish between them if, visually adjacent. + /// The `layer` is incremented for each overlap, and the `alt` bool alternates between true + /// and false, for each adjacent non-overlapping span. Source code between the spans (code + /// that is not in any coverage region) has neutral styling. + fn write_coverage_regions<'b>( + &self, + next_pos: BytePos, + ordered_coverage_regions: &mut Peekable>, + alt: bool, + layer: usize, + file: &mut io::BufWriter, + ) -> io::Result { + let coverage_region = + ordered_coverage_regions.next().expect("ordered_coverage_regions should have some"); + if next_pos < coverage_region.span.lo() { + self.write_coverage_gap(next_pos, coverage_region.span.lo(), file)?; + } + let mut remaining_span = coverage_region.span; + let mut subalt = false; + loop { + let next_coverage_region = match ordered_coverage_regions.peek() { + None => break, + Some(coverage_region) => *coverage_region, + }; + if !next_coverage_region.span.overlaps(remaining_span) { + break; + } + self.write_span( + Some(coverage_region), + remaining_span.until(next_coverage_region.span), + alt, + layer, + file, + )?; + let next_pos = self.write_coverage_regions( + next_coverage_region.span.lo(), + ordered_coverage_regions, + subalt, + layer + 1, + file, + )?; + subalt = !subalt; + if next_pos < remaining_span.hi() { + remaining_span = remaining_span.with_lo(next_pos); + } else { + return Ok(next_pos); + } + } + self.write_span(Some(coverage_region), remaining_span, alt, layer, file) + } + + fn write_coverage_gap( + &self, + lo: BytePos, + hi: BytePos, + file: &mut io::BufWriter, + ) -> io::Result { + self.write_span(None, Span::with_root_ctxt(lo, hi), false, 0, file) + } + + fn write_span( + &self, + coverage_region: Option<&CoverageRegion>, + span: Span, + alt: bool, + layer: usize, + file: &mut io::BufWriter, + ) -> io::Result { + let tcx = self.tcx; + let source_map = tcx.sess.source_map(); + let snippet = source_map.span_to_snippet(span).expect("expected valid span"); + let labeled_snippet = if let Some(coverage_region) = coverage_region { + let first_bb = coverage_region.blocks[0]; + if span.is_empty() { + format!(r#"@{}"#, first_bb.index(),) + } else { + format!( + r#"@{}: {}"#, + first_bb.index(), + escape_html(&snippet), + ) + } + } else { + snippet + }; + let maybe_alt = if layer > 0 { + if alt { " odd" } else { " even" } + } else { + "" + }; + let maybe_tooltip = if let Some(coverage_region) = coverage_region { + format!(" title=\"{}\"", escape_html(&self.make_tooltip_text(coverage_region))) + } else { + "".to_owned() + }; + if layer == 1 { + write!(file, "")?; + } + for (i, line) in labeled_snippet.lines().enumerate() { + if i > 0 { + write!(file, "{}", NEW_LINE_SPAN)?; + } + write!( + file, + r#"{}"#, + maybe_alt, layer, maybe_tooltip, line + )?; + } + if layer == 1 { + write!(file, "")?; + } + Ok(span.hi()) + } + + 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 = self.source_range_no_file(&statement.source_info.span); + text.push(format!( + "\n{}{}: {}: {}", + INDENT, + source_range, + statement_kind_name(statement), + statement_string + )); + } + let term = data.terminator(); + let source_range = self.source_range_no_file(&term.source_info.span); + text.push(format!( + "\n{}{}: {}: {:?}", + INDENT, + source_range, + terminator_kind_name(term), + term.kind + )); + } + text.join("") + } + + fn source_range_no_file(&self, span: &Span) -> String { + let source_map = self.tcx.sess.source_map(); + let start = source_map.lookup_char_pos(span.lo()); + let end = source_map.lookup_char_pos(span.hi()); + format!( + "{}:{}-{}:{}", + start.line, + start.col.to_usize() + 1, + end.line, + end.col.to_usize() + 1 + ) + } +} + +fn statement_kind_name(statement: &Statement<'_>) -> &'static str { + use StatementKind::*; + match statement.kind { + Assign(..) => "Assign", + FakeRead(..) => "FakeRead", + SetDiscriminant { .. } => "SetDiscriminant", + StorageLive(..) => "StorageLive", + StorageDead(..) => "StorageDead", + LlvmInlineAsm(..) => "LlvmInlineAsm", + Retag(..) => "Retag", + AscribeUserType(..) => "AscribeUserType", + Coverage(..) => "Coverage", + Nop => "Nop", + } +} + +fn terminator_kind_name(term: &Terminator<'_>) -> &'static str { + use TerminatorKind::*; + match term.kind { + Goto { .. } => "Goto", + SwitchInt { .. } => "SwitchInt", + Resume => "Resume", + Abort => "Abort", + Return => "Return", + Unreachable => "Unreachable", + Drop { .. } => "Drop", + DropAndReplace { .. } => "DropAndReplace", + Call { .. } => "Call", + Assert { .. } => "Assert", + Yield { .. } => "Yield", + GeneratorDrop => "GeneratorDrop", + FalseEdge { .. } => "FalseEdge", + FalseUnwind { .. } => "FalseUnwind", + InlineAsm { .. } => "InlineAsm", + } +} + +fn escape_html(s: &str) -> String { + s.replace("&", "&").replace("\"", """).replace("<", "<").replace(">", ">") } /// Convert the Span into its file name, start line and column, and end line and column @@ -226,8 +702,14 @@ fn make_code_region<'tcx>(tcx: TyCtxt<'tcx>, span: &Span) -> CodeRegion { } } +fn fn_span<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Span { + let hir_id = + tcx.hir().local_def_id_to_hir_id(def_id.as_local().expect("expected DefId is local")); + tcx.hir().span(hir_id) +} + 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 +727,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, + worklist: Vec, + 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) { + let size = self.body.basic_blocks().len() - self.visited.count(); + (size, Some(size)) + } +} diff --git a/src/librustc_session/config.rs b/src/librustc_session/config.rs index 1808a0ca59bea..64e9c31615faa 100644 --- a/src/librustc_session/config.rs +++ b/src/librustc_session/config.rs @@ -1709,6 +1709,10 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options { ); } + if debugging_opts.experimental_coverage { + debugging_opts.instrument_coverage = true; + } + if debugging_opts.instrument_coverage { if cg.profile_generate.enabled() || cg.profile_use.is_some() { early_error( @@ -1718,20 +1722,11 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options { ); } - // `-Z instrument-coverage` implies: - // * `-Z symbol-mangling-version=v0` - to ensure consistent and reversible name mangling. - // Note, LLVM coverage tools can analyze coverage over multiple runs, including some - // changes to source code; so mangled names must be consistent across compilations. - // * `-C link-dead-code` - so unexecuted code is still counted as zero, rather than be - // optimized out. Note that instrumenting dead code can be explicitly disabled with: - // `-Z instrument-coverage -C link-dead-code=no`. + // `-Z instrument-coverage` implies `-Z symbol-mangling-version=v0` - to ensure consistent + // and reversible name mangling. Note, LLVM coverage tools can analyze coverage over + // multiple runs, including some changes to source code; so mangled names must be consistent + // across compilations. debugging_opts.symbol_mangling_version = SymbolManglingVersion::V0; - if cg.link_dead_code == None { - // FIXME(richkadel): Investigate if the `instrument-coverage` implementation can - // inject ["zero counters"](https://llvm.org/docs/CoverageMappingFormat.html#counter) - // in the coverage map when "dead code" is removed, rather than forcing `link-dead-code`. - cg.link_dead_code = Some(true); - } } if !cg.embed_bitcode { diff --git a/src/librustc_session/options.rs b/src/librustc_session/options.rs index d05f1a3f34b78..e7aeb7a1ac036 100644 --- a/src/librustc_session/options.rs +++ b/src/librustc_session/options.rs @@ -851,6 +851,11 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options, "in addition to `.mir` files, create graphviz `.dot` files (default: no)"), emit_stack_sizes: bool = (false, parse_bool, [UNTRACKED], "emit a section containing stack size metadata (default: no)"), + experimental_coverage: bool = (false, parse_bool, [TRACKED], + "enable and extend the `-Z instrument-coverage` function-level coverage \ + feature, adding additional experimental (likely inaccurate) counters and \ + code regions (used by `rustc` compiler developers to test new coverage \ + counter placements) (default: no)"), fewer_names: bool = (false, parse_bool, [TRACKED], "reduce memory use by retaining fewer names within compilation artifacts (LLVM-IR) \ (default: no)"), @@ -885,9 +890,9 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options, "instrument the generated code to support LLVM source-based code coverage \ reports (note, the compiler build config must include `profiler = true`, \ and is mutually exclusive with `-C profile-generate`/`-C profile-use`); \ - implies `-C link-dead-code` (unless explicitly disabled)` and \ - `-Z symbol-mangling-version=v0`; and disables/overrides some optimization \ - options (default: no)"), + implies `-C link-dead-code` (unless targeting MSVC, or explicitly disabled) \ + and `-Z symbol-mangling-version=v0`; and disables/overrides some Rust \ + optimizations (default: no)"), instrument_mcount: bool = (false, parse_bool, [TRACKED], "insert function instrument code for mcount-based tracing (default: no)"), keep_hygiene_data: bool = (false, parse_bool, [UNTRACKED], diff --git a/src/librustc_session/session.rs b/src/librustc_session/session.rs index c006e593e4755..bef97ce34eeef 100644 --- a/src/librustc_session/session.rs +++ b/src/librustc_session/session.rs @@ -1021,6 +1021,32 @@ impl Session { || self.opts.debugging_opts.sanitizer.intersects(SanitizerSet::ADDRESS | SanitizerSet::MEMORY) } + pub fn link_dead_code(&self) -> bool { + match self.opts.cg.link_dead_code { + Some(explicitly_set) => explicitly_set, + None => { + self.opts.debugging_opts.instrument_coverage + && !self.target.target.options.is_like_msvc + // Issue #64685: (Linker error because `-wL as-needed` conflicts with + // `-C link-dead-code`). As described by this issue, the "link dead code" option + // produces incorrect binaries when compiled and linked under MSVC. The resulting + // Rust programs typically crash with a segmentation fault, or produce an empty + // "*.profraw" file (profiling counter results normally generated during program + // exit). + // + // If not targeting MSVC, `-Z instrument-coverage` implies `-C link-dead-code`, so + // unexecuted code is still counted as zero, rather than be optimized out. Note that + // instrumenting dead code can be explicitly disabled with: + // + // `-Z instrument-coverage -C link-dead-code=no`. + // + // FIXME(richkadel): Investigate if `instrument-coverage` implementation can inject + // [zero counters](https://llvm.org/docs/CoverageMappingFormat.html#counter) in the + // coverage map when "dead code" is removed, rather than forcing `link-dead-code`. + } + } + } + pub fn mark_attr_known(&self, attr: &Attribute) { self.known_attrs.lock().mark(attr) } @@ -1428,20 +1454,6 @@ fn validate_commandline_args_with_session_available(sess: &Session) { ); } - // FIXME(richkadel): See `src/test/run-make-fulldeps/instrument-coverage/Makefile`. After - // compiling with `-Zinstrument-coverage`, the resulting binary generates a segfault during - // the program's exit process (likely while attempting to generate the coverage stats in - // the "*.profraw" file). An investigation to resolve the problem on Windows is ongoing, - // but until this is resolved, the option is disabled on Windows, and the test is skipped - // when targeting `MSVC`. - if sess.opts.debugging_opts.instrument_coverage && sess.target.target.options.is_like_msvc { - sess.warn( - "Rust source-based code coverage instrumentation (with `-Z instrument-coverage`) \ - is not yet supported on Windows when targeting MSVC. The resulting binaries will \ - still be instrumented for experimentation purposes, but may not execute correctly.", - ); - } - const ASAN_SUPPORTED_TARGETS: &[&str] = &[ "aarch64-fuchsia", "aarch64-unknown-linux-gnu", diff --git a/src/librustc_span/lib.rs b/src/librustc_span/lib.rs index c654dade2abd4..b478a1d15c506 100644 --- a/src/librustc_span/lib.rs +++ b/src/librustc_span/lib.rs @@ -400,6 +400,13 @@ impl Span { span.with_lo(span.hi) } + #[inline] + /// Returns true if hi == lo + pub fn is_empty(&self) -> bool { + let span = self.data(); + span.hi == span.lo + } + /// Returns `self` if `self` is not the dummy span, and `other` otherwise. pub fn substitute_dummy(self, other: Span) -> Span { if self.is_dummy() { other } else { self } diff --git a/src/librustc_span/source_map.rs b/src/librustc_span/source_map.rs index 7c656db22ed8c..f6278f52ef958 100644 --- a/src/librustc_span/source_map.rs +++ b/src/librustc_span/source_map.rs @@ -487,6 +487,15 @@ impl SourceMap { } } + /// Returns a new `Span` covering the start and end `BytePos`s of the file containing the given + /// `pos`. This can be used to quickly determine if another `BytePos` or `Span` is from the same + /// file. + pub fn lookup_file_span(&self, pos: BytePos) -> Span { + let idx = self.lookup_source_file_idx(pos); + let sf = (*self.files.borrow().source_files)[idx].clone(); + Span::with_root_ctxt(sf.start_pos, sf.end_pos) + } + /// Returns `Some(span)`, a union of the LHS and RHS span. The LHS must precede the RHS. If /// there are gaps between LHS and RHS, the resulting union will cross these gaps. /// For this to work, diff --git a/src/test/mir-opt/instrument_coverage.bar.InstrumentCoverage.diff b/src/test/mir-opt/instrument_coverage.bar.InstrumentCoverage.diff index 01d837d155ab7..c820376a21cad 100644 --- a/src/test/mir-opt/instrument_coverage.bar.InstrumentCoverage.diff +++ b/src/test/mir-opt/instrument_coverage.bar.InstrumentCoverage.diff @@ -6,7 +6,7 @@ bb0: { _0 = const true; // scope 0 at /the/src/instrument_coverage.rs:20:5: 20:9 -+ Coverage { kind: Counter { function_source_hash: 10208505205182607101, id: CounterValueReference(0) }, code_region: /the/src/instrument_coverage.rs:19:18 - 21:2 }; // scope 0 at /the/src/instrument_coverage.rs:21:2: 21:2 ++ Coverage counter(0) @ /the/src/instrument_coverage.rs:19:18 - 21:2; // scope 0 at /the/src/instrument_coverage.rs:21:2: 21:2 return; // scope 0 at /the/src/instrument_coverage.rs:21:2: 21:2 } } diff --git a/src/test/mir-opt/instrument_coverage.main.InstrumentCoverage.diff b/src/test/mir-opt/instrument_coverage.main.InstrumentCoverage.diff index 43c8be8f45dbb..386c02832c9dd 100644 --- a/src/test/mir-opt/instrument_coverage.main.InstrumentCoverage.diff +++ b/src/test/mir-opt/instrument_coverage.main.InstrumentCoverage.diff @@ -8,7 +8,7 @@ let mut _3: !; // in scope 0 at /the/src/instrument_coverage.rs:12:18: 14:10 bb0: { -+ Coverage { kind: Counter { function_source_hash: 16004455475339839479, id: CounterValueReference(0) }, code_region: /the/src/instrument_coverage.rs:10:11 - 16:2 }; // scope 0 at /the/src/instrument_coverage.rs:11:5: 15:6 ++ Coverage counter(0) @ /the/src/instrument_coverage.rs:10:11 - 16:2; // scope 0 at /the/src/instrument_coverage.rs:11:5: 15:6 falseUnwind -> [real: bb1, cleanup: bb2]; // scope 0 at /the/src/instrument_coverage.rs:11:5: 15:6 } diff --git a/src/test/run-make-fulldeps/instrument-coverage-cov-reports-base/Makefile b/src/test/run-make-fulldeps/instrument-coverage-cov-reports-base/Makefile new file mode 100644 index 0000000000000..be0d3449693dd --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-cov-reports-base/Makefile @@ -0,0 +1,67 @@ +# needs-profiler-support + +# ISSUE(64685): When targeting MSVC, Rust binaries built with both `-Z instrument-coverage` and +# `-C link-dead-code` typically crash (with a seg-fault) or at best generate an empty `*.profraw`. +# See ../instrument-coverage/coverage_tools.mk for more information. + +-include ../instrument-coverage/coverage_tools.mk + +BASEDIR=../instrument-coverage-cov-reports-base +SOURCEDIR=../instrument-coverage + +all: $(patsubst $(SOURCEDIR)/%.rs,%,$(wildcard $(SOURCEDIR)/*.rs)) + +%: $(SOURCEDIR)/%.rs + # Compile the test program with "experimental" coverage instrumentation and generate relevant MIR. + # + # FIXME(richkadel): `-Zexperimental-coverage` to `-Zinstrument-coverage` once we are + # satisfied with the branch-level instrumentation. + $(RUSTC) $(SOURCEDIR)/$@.rs \ + -Zexperimental-coverage \ + -Clink-dead-code=$(LINK_DEAD_CODE) + + # Run it in order to generate some profiling data, + # with `LLVM_PROFILE_FILE=` environment variable set to + # output the coverage stats for this run. + LLVM_PROFILE_FILE="$(TMPDIR)"/$@.profraw \ + $(call RUN,$@) + + # Postprocess the profiling data so it can be used by the llvm-cov tool + "$(LLVM_BIN_DIR)"/llvm-profdata merge --sparse \ + "$(TMPDIR)"/$@.profraw \ + -o "$(TMPDIR)"/$@.profdata + + # Generate a coverage report using `llvm-cov show`. The output ordering + # can be non-deterministic, so ignore the return status. If the test fails + # when comparing the JSON `export`, the `show` output may be useful when + # debugging. + "$(LLVM_BIN_DIR)"/llvm-cov show \ + --Xdemangler="$(RUST_DEMANGLER)" \ + --show-line-counts-or-regions \ + --instr-profile="$(TMPDIR)"/$@.profdata \ + $(call BIN,"$(TMPDIR)"/$@) \ + > "$(TMPDIR)"/actual_show_coverage.$@.txt + +ifdef RUSTC_BLESS_TEST + cp "$(TMPDIR)"/actual_show_coverage.$@.txt typical_show_coverage.$@.txt +else + # Compare the show coverage output (`--bless` refreshes `typical` files) + $(DIFF) typical_show_coverage.$@.txt "$(TMPDIR)"/actual_show_coverage.$@.txt || \ + >&2 echo 'diff failed for `llvm-cov show` on $@ (might not be an error)' +endif + + # Generate a coverage report in JSON, using `llvm-cov export`, and fail if + # there are differences from the expected output. + "$(LLVM_BIN_DIR)"/llvm-cov export \ + --summary-only \ + --instr-profile="$(TMPDIR)"/$@.profdata \ + $(call BIN,"$(TMPDIR)"/$@) \ + | "$(PYTHON)" $(BASEDIR)/prettify_json.py \ + > "$(TMPDIR)"/actual_export_coverage.$@.json + +ifdef RUSTC_BLESS_TEST + cp "$(TMPDIR)"/actual_export_coverage.$@.json expected_export_coverage.$@.json +else + # Check that exported JSON coverage data matches what we expect (`--bless` refreshes `expected`) + $(DIFF) expected_export_coverage.$@.json "$(TMPDIR)"/actual_export_coverage.$@.json +endif diff --git a/src/test/run-make-fulldeps/instrument-coverage-cov-reports-base/expected_export_coverage.coverage_of_if_else.json b/src/test/run-make-fulldeps/instrument-coverage-cov-reports-base/expected_export_coverage.coverage_of_if_else.json new file mode 100644 index 0000000000000..b9041ebebef57 --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-cov-reports-base/expected_export_coverage.coverage_of_if_else.json @@ -0,0 +1,59 @@ +{ + "data": [ + { + "files": [ + { + "filename": "../instrument-coverage/coverage_of_if_else.rs", + "summary": { + "functions": { + "count": 1, + "covered": 1, + "percent": 100 + }, + "instantiations": { + "count": 1, + "covered": 1, + "percent": 100 + }, + "lines": { + "count": 46, + "covered": 19, + "percent": 41.30434782608695 + }, + "regions": { + "count": 75, + "covered": 23, + "notcovered": 52, + "percent": 30.666666666666664 + } + } + } + ], + "totals": { + "functions": { + "count": 1, + "covered": 1, + "percent": 100 + }, + "instantiations": { + "count": 1, + "covered": 1, + "percent": 100 + }, + "lines": { + "count": 46, + "covered": 19, + "percent": 41.30434782608695 + }, + "regions": { + "count": 75, + "covered": 23, + "notcovered": 52, + "percent": 30.666666666666664 + } + } + } + ], + "type": "llvm.coverage.json.export", + "version": "2.0.1" +} diff --git a/src/test/run-make-fulldeps/instrument-coverage/expected_export_coverage.json b/src/test/run-make-fulldeps/instrument-coverage-cov-reports-base/expected_export_coverage.testprog.json similarity index 64% rename from src/test/run-make-fulldeps/instrument-coverage/expected_export_coverage.json rename to src/test/run-make-fulldeps/instrument-coverage-cov-reports-base/expected_export_coverage.testprog.json index 75bec80bdf886..13528335f5a9b 100644 --- a/src/test/run-make-fulldeps/instrument-coverage/expected_export_coverage.json +++ b/src/test/run-make-fulldeps/instrument-coverage-cov-reports-base/expected_export_coverage.testprog.json @@ -6,50 +6,50 @@ "filename": "testprog.rs", "summary": { "functions": { - "count": 7, + "count": 6, "covered": 5, - "percent": 71.42857142857143 + "percent": 83.33333333333334 }, "instantiations": { - "count": 8, - "covered": 6, - "percent": 75 + "count": 7, + "covered": 5, + "percent": 71.42857142857143 }, "lines": { - "count": 30, - "covered": 25, - "percent": 83.33333333333334 + "count": 17, + "covered": 16, + "percent": 94.11764705882352 }, "regions": { - "count": 7, - "covered": 5, - "notcovered": 2, - "percent": 71.42857142857143 + "count": 23, + "covered": 20, + "notcovered": 3, + "percent": 86.95652173913044 } } } ], "totals": { "functions": { - "count": 7, + "count": 6, "covered": 5, - "percent": 71.42857142857143 + "percent": 83.33333333333334 }, "instantiations": { - "count": 8, - "covered": 6, - "percent": 75 + "count": 7, + "covered": 5, + "percent": 71.42857142857143 }, "lines": { - "count": 30, - "covered": 25, - "percent": 83.33333333333334 + "count": 17, + "covered": 16, + "percent": 94.11764705882352 }, "regions": { - "count": 7, - "covered": 5, - "notcovered": 2, - "percent": 71.42857142857143 + "count": 23, + "covered": 20, + "notcovered": 3, + "percent": 86.95652173913044 } } } diff --git a/src/test/run-make-fulldeps/instrument-coverage/prettify_json.py b/src/test/run-make-fulldeps/instrument-coverage-cov-reports-base/prettify_json.py similarity index 100% rename from src/test/run-make-fulldeps/instrument-coverage/prettify_json.py rename to src/test/run-make-fulldeps/instrument-coverage-cov-reports-base/prettify_json.py diff --git a/src/test/run-make-fulldeps/instrument-coverage-cov-reports-base/typical_show_coverage.coverage_of_if_else.txt b/src/test/run-make-fulldeps/instrument-coverage-cov-reports-base/typical_show_coverage.coverage_of_if_else.txt new file mode 100644 index 0000000000000..0c71155960335 --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-cov-reports-base/typical_show_coverage.coverage_of_if_else.txt @@ -0,0 +1,64 @@ + 1| |#![allow(unused_assignments)] + 2| | + 3| |fn main() { + 4| | let mut countdown = 0; + 5| 2| if true { + ^1 + 6| 2| countdown = 10; + 7| 2| } + 8| | + 9| 2| if countdown > 7 { + ^1 + 10| 2| countdown -= 4; + ^1 + 11| 2| } else if countdown > 2 { + ^0 ^0 + 12| 0| if countdown < 1 || countdown > 5 || countdown != 9 { + 13| 0| countdown = 0; + 14| 0| } + 15| 0| countdown -= 5; + 16| 0| } else { + 17| 0| return; + 18| 0| } + 19| 0| + 20| 0| let mut countdown = 0; + 21| 2| if true { + ^1 + 22| 2| countdown = 10; + 23| 2| } + 24| 0| + 25| 2| if countdown > 7 { + ^1 + 26| 2| countdown -= 4; + ^1 + 27| 2| } else if countdown > 2 { + ^0 ^0 + 28| 0| if countdown < 1 || countdown > 5 || countdown != 9 { + 29| 0| countdown = 0; + 30| 0| } + 31| 0| countdown -= 5; + 32| 0| } else { + 33| 0| return; + 34| 0| } + 35| 0| + 36| 0| let mut countdown = 0; + 37| 2| if true { + ^1 + 38| 2| countdown = 10; + 39| 2| } + 40| 0| + 41| 2| if countdown > 7 { + ^1 + 42| 2| countdown -= 4; + ^1 + 43| 2| } else if countdown > 2 { + ^0 ^0 + 44| 0| if countdown < 1 || countdown > 5 || countdown != 9 { + 45| 0| countdown = 0; + 46| 0| } + 47| 0| countdown -= 5; + 48| 0| } else { + 49| 0| return; + 50| 0| } + 51| 1|} + diff --git a/src/test/run-make-fulldeps/instrument-coverage-cov-reports-base/typical_show_coverage.testprog.txt b/src/test/run-make-fulldeps/instrument-coverage-cov-reports-base/typical_show_coverage.testprog.txt new file mode 100644 index 0000000000000..ed5a81525ffce --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-cov-reports-base/typical_show_coverage.testprog.txt @@ -0,0 +1,55 @@ + 1| |pub fn will_be_called() -> &'static str { + 2| | let val = "called"; + 3| | println!("{}", val); + 4| 2| val + 5| 2|} + 6| | + 7| |pub fn will_not_be_called() -> bool { + 8| | println!("should not have been called"); + 9| | false + 10| |} + 11| | + 12| |pub fn print(left: &str, value: T, right: &str) + 13| |where + 14| | T: std::fmt::Display, + 15| 1|{ + 16| 1| println!("{}{}{}", left, value, right); + 17| 1|} + 18| 1| + 19| 1|pub fn wrap_with(inner: T, should_wrap: bool, wrapper: F) + 20| 1|where + 21| 1| F: FnOnce(&T) + 22| 1|{ + 23| 3| if should_wrap { + ^2 + 24| 3| wrapper(&inner) + ^1 + 25| 3| } + 26| 4|} + ------------------ + | testprog[317d481089b8c8fe]::wrap_with::: + | 23| 2| if should_wrap { + | ^1 + | 24| 2| wrapper(&inner) + | ^1 + | 25| 2| } + | 26| 2|} + ------------------ + | Unexecuted instantiation: testprog[317d481089b8c8fe]::wrap_with:: + ------------------ + 27| 2| + 28| 2|fn main() { + 29| 2| let less = 1; + 30| 2| let more = 100; + 31| 2| + 32| 2| if less < more { + ^1 + 33| 2| wrap_with(will_be_called(), less < more, |inner| print(" ***", inner, "*** ")); + ^1 ^1^1 + 34| 1| wrap_with(will_be_called(), more < less, |inner| print(" ***", inner, "*** ")); + ^0 ^0 + 35| 2| } else { + 36| 2| wrap_with(will_not_be_called(), true, |inner| print("wrapped result is: ", inner, "")); + 37| 2| } + 38| 1|} + diff --git a/src/test/run-make-fulldeps/instrument-coverage-cov-reports-link-dead-code/Makefile b/src/test/run-make-fulldeps/instrument-coverage-cov-reports-link-dead-code/Makefile new file mode 100644 index 0000000000000..5942a63b1a5f5 --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-cov-reports-link-dead-code/Makefile @@ -0,0 +1,11 @@ +# needs-profiler-support +# ignore-msvc + +# LINK_DEAD_CODE requires ignore-msvc due to Issue #64685 +LINK_DEAD_CODE=yes + +-include ../instrument-coverage-cov-reports-base/Makefile + +# ISSUE(64685): When targeting MSVC, Rust binaries built with both `-Z instrument-coverage` and +# `-C link-dead-code` typically crash (with a seg-fault) or at best generate an empty `*.profraw`. +# See ../instrument-coverage/coverage_tools.mk for more information. \ No newline at end of file diff --git a/src/test/run-make-fulldeps/instrument-coverage-cov-reports-link-dead-code/expected_export_coverage.coverage_of_if_else.json b/src/test/run-make-fulldeps/instrument-coverage-cov-reports-link-dead-code/expected_export_coverage.coverage_of_if_else.json new file mode 100644 index 0000000000000..b9041ebebef57 --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-cov-reports-link-dead-code/expected_export_coverage.coverage_of_if_else.json @@ -0,0 +1,59 @@ +{ + "data": [ + { + "files": [ + { + "filename": "../instrument-coverage/coverage_of_if_else.rs", + "summary": { + "functions": { + "count": 1, + "covered": 1, + "percent": 100 + }, + "instantiations": { + "count": 1, + "covered": 1, + "percent": 100 + }, + "lines": { + "count": 46, + "covered": 19, + "percent": 41.30434782608695 + }, + "regions": { + "count": 75, + "covered": 23, + "notcovered": 52, + "percent": 30.666666666666664 + } + } + } + ], + "totals": { + "functions": { + "count": 1, + "covered": 1, + "percent": 100 + }, + "instantiations": { + "count": 1, + "covered": 1, + "percent": 100 + }, + "lines": { + "count": 46, + "covered": 19, + "percent": 41.30434782608695 + }, + "regions": { + "count": 75, + "covered": 23, + "notcovered": 52, + "percent": 30.666666666666664 + } + } + } + ], + "type": "llvm.coverage.json.export", + "version": "2.0.1" +} diff --git a/src/test/run-make-fulldeps/instrument-coverage-cov-reports-link-dead-code/typical_show_coverage.coverage_of_if_else.txt b/src/test/run-make-fulldeps/instrument-coverage-cov-reports-link-dead-code/typical_show_coverage.coverage_of_if_else.txt new file mode 100644 index 0000000000000..0c71155960335 --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-cov-reports-link-dead-code/typical_show_coverage.coverage_of_if_else.txt @@ -0,0 +1,64 @@ + 1| |#![allow(unused_assignments)] + 2| | + 3| |fn main() { + 4| | let mut countdown = 0; + 5| 2| if true { + ^1 + 6| 2| countdown = 10; + 7| 2| } + 8| | + 9| 2| if countdown > 7 { + ^1 + 10| 2| countdown -= 4; + ^1 + 11| 2| } else if countdown > 2 { + ^0 ^0 + 12| 0| if countdown < 1 || countdown > 5 || countdown != 9 { + 13| 0| countdown = 0; + 14| 0| } + 15| 0| countdown -= 5; + 16| 0| } else { + 17| 0| return; + 18| 0| } + 19| 0| + 20| 0| let mut countdown = 0; + 21| 2| if true { + ^1 + 22| 2| countdown = 10; + 23| 2| } + 24| 0| + 25| 2| if countdown > 7 { + ^1 + 26| 2| countdown -= 4; + ^1 + 27| 2| } else if countdown > 2 { + ^0 ^0 + 28| 0| if countdown < 1 || countdown > 5 || countdown != 9 { + 29| 0| countdown = 0; + 30| 0| } + 31| 0| countdown -= 5; + 32| 0| } else { + 33| 0| return; + 34| 0| } + 35| 0| + 36| 0| let mut countdown = 0; + 37| 2| if true { + ^1 + 38| 2| countdown = 10; + 39| 2| } + 40| 0| + 41| 2| if countdown > 7 { + ^1 + 42| 2| countdown -= 4; + ^1 + 43| 2| } else if countdown > 2 { + ^0 ^0 + 44| 0| if countdown < 1 || countdown > 5 || countdown != 9 { + 45| 0| countdown = 0; + 46| 0| } + 47| 0| countdown -= 5; + 48| 0| } else { + 49| 0| return; + 50| 0| } + 51| 1|} + diff --git a/src/test/run-make-fulldeps/instrument-coverage-llvm-ir-base/Makefile b/src/test/run-make-fulldeps/instrument-coverage-llvm-ir-base/Makefile new file mode 100644 index 0000000000000..f13142a9fd703 --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-llvm-ir-base/Makefile @@ -0,0 +1,65 @@ +# needs-profiler-support + +# ISSUE(64685): When targeting MSVC, Rust binaries built with both `-Z instrument-coverage` and +# `-C link-dead-code` typically crash (with a seg-fault) or at best generate an empty `*.profraw`. +# See ../instrument-coverage/coverage_tools.mk for more information. + +-include ../instrument-coverage/coverage_tools.mk + +BASEDIR=../instrument-coverage-llvm-ir-base + +ifeq ($(UNAME),Darwin) + INSTR_PROF_DATA_SUFFIX=,regular,live_support + DATA_SECTION_PREFIX=__DATA, + LLVM_COV_SECTION_PREFIX=__LLVM_COV, +else + INSTR_PROF_DATA_SUFFIX= + DATA_SECTION_PREFIX= + LLVM_COV_SECTION_PREFIX= +endif + +ifeq ($(LINK_DEAD_CODE),yes) + DEFINE_INTERNAL=define hidden +else + DEFINE_INTERNAL=define internal +endif + +ifdef IS_WINDOWS + LLVM_FILECHECK_OPTIONS=\ + -check-prefixes=CHECK,WINDOWS \ + -DPRIVATE_GLOBAL='internal global' \ + -DDEFINE_INTERNAL='$(DEFINE_INTERNAL)' \ + -DINSTR_PROF_DATA='.lprfd$$M' \ + -DINSTR_PROF_NAME='.lprfn$$M' \ + -DINSTR_PROF_CNTS='.lprfc$$M' \ + -DINSTR_PROF_VALS='.lprfv$$M' \ + -DINSTR_PROF_VNODES='.lprfnd$$M' \ + -DINSTR_PROF_COVMAP='.lcovmap$$M' \ + -DINSTR_PROF_ORDERFILE='.lorderfile$$M' +else + LLVM_FILECHECK_OPTIONS=\ + -check-prefixes=CHECK \ + -DPRIVATE_GLOBAL='private global' \ + -DDEFINE_INTERNAL='$(DEFINE_INTERNAL)' \ + -DINSTR_PROF_DATA='$(DATA_SECTION_PREFIX)__llvm_prf_data$(INSTR_PROF_DATA_SUFFIX)' \ + -DINSTR_PROF_NAME='$(DATA_SECTION_PREFIX)__llvm_prf_names' \ + -DINSTR_PROF_CNTS='$(DATA_SECTION_PREFIX)__llvm_prf_cnts' \ + -DINSTR_PROF_VALS='$(DATA_SECTION_PREFIX)__llvm_prf_vals' \ + -DINSTR_PROF_VNODES='$(DATA_SECTION_PREFIX)__llvm_prf_vnds' \ + -DINSTR_PROF_COVMAP='$(LLVM_COV_SECTION_PREFIX)__llvm_covmap' \ + -DINSTR_PROF_ORDERFILE='$(DATA_SECTION_PREFIX)__llvm_orderfile' +endif + +all: + # Compile the test program with non-experimental coverage instrumentation, and generate LLVM IR + # + # Note: `-Clink-dead-code=no` disables the option, needed because the option is automatically + # enabled for some platforms, but not for Windows MSVC (due to Issue #64685). The state of this + # option affects the generated MIR and coverage, so it is enabled for tests to ensure the + # tests results are the same across platforms. + $(RUSTC) $(BASEDIR)/testprog.rs \ + -Zinstrument-coverage \ + -Clink-dead-code=$(LINK_DEAD_CODE) \ + --emit=llvm-ir + + cat "$(TMPDIR)"/testprog.ll | "$(LLVM_FILECHECK)" $(BASEDIR)/filecheck.testprog.txt $(LLVM_FILECHECK_OPTIONS) diff --git a/src/test/run-make-fulldeps/instrument-coverage-llvm-ir-base/filecheck.testprog.txt b/src/test/run-make-fulldeps/instrument-coverage-llvm-ir-base/filecheck.testprog.txt new file mode 100644 index 0000000000000..0a3c4aedd5569 --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-llvm-ir-base/filecheck.testprog.txt @@ -0,0 +1,49 @@ +# Check for metadata, variables, declarations, and function definitions injected +# into LLVM IR when compiling with -Zinstrument-coverage. + +WINDOWS: $__llvm_profile_runtime_user = comdat any + +CHECK: @__llvm_coverage_mapping = internal constant +CHECK-SAME: section "[[INSTR_PROF_COVMAP]]", align 8 + +WINDOWS: @__llvm_profile_runtime = external global i32 + +CHECK: @__profc__R{{[a-zA-Z0-9_]+}}testprog14will_be_called = [[PRIVATE_GLOBAL]] +CHECK-SAME: section "[[INSTR_PROF_CNTS]]", align 8 + +CHECK: @__profd__R{{[a-zA-Z0-9_]+}}testprog14will_be_called = [[PRIVATE_GLOBAL]] +CHECK-SAME: @__profc__R{{[a-zA-Z0-9_]+}}testprog14will_be_called, +CHECK-SAME: section "[[INSTR_PROF_DATA]]", align 8 + +CHECK: @__profc__R{{[a-zA-Z0-9_]+}}testprog4main = [[PRIVATE_GLOBAL]] +CHECK-SAME: section "[[INSTR_PROF_CNTS]]", align 8 + +CHECK: @__profd__R{{[a-zA-Z0-9_]+}}testprog4main = [[PRIVATE_GLOBAL]] +CHECK-SAME: @__profc__R{{[a-zA-Z0-9_]+}}testprog4main, +CHECK-SAME: section "[[INSTR_PROF_DATA]]", align 8 + +CHECK: @__llvm_prf_nm = private constant +CHECK-SAME: section "[[INSTR_PROF_NAME]]", align 1 + +CHECK: @llvm.used = appending global +CHECK-SAME: i8* bitcast ({ {{.*}} }* @__llvm_coverage_mapping to i8*) +WINDOWS-SAME: i8* bitcast (i32 ()* @__llvm_profile_runtime_user to i8*) +CHECK-SAME: i8* bitcast ({ {{.*}} }* @__profd__R{{[a-zA-Z0-9_]*}}testprog4main to i8*) +CHECK-SAME: i8* getelementptr inbounds ({{.*}}* @__llvm_prf_nm, i32 0, i32 0) +CHECK-SAME: section "llvm.metadata" + +CHECK: [[DEFINE_INTERNAL]] { {{.*}} } @_R{{[a-zA-Z0-9_]+}}testprog14will_be_called() unnamed_addr #{{[0-9]+}} { +CHECK-NEXT: start: +CHECK-NOT: bb{{[0-9]+}}: +CHECK: %pgocount = load i64, i64* getelementptr inbounds +CHECK-SAME: * @__profc__R{{[a-zA-Z0-9_]+}}testprog14will_be_called, + +CHECK: declare void @llvm.instrprof.increment(i8*, i64, i32, i32) #[[LLVM_INSTRPROF_INCREMENT_ATTR:[0-9]+]] + +WINDOWS: define linkonce_odr hidden i32 @__llvm_profile_runtime_user() #[[LLVM_PROFILE_RUNTIME_USER_ATTR:[0-9]+]] comdat { +WINDOWS-NEXT: %1 = load i32, i32* @__llvm_profile_runtime +WINDOWS-NEXT: ret i32 %1 +WINDOWS-NEXT: } + +CHECK: attributes #[[LLVM_INSTRPROF_INCREMENT_ATTR]] = { nounwind } +WINDOWS: attributes #[[LLVM_PROFILE_RUNTIME_USER_ATTR]] = { noinline } diff --git a/src/test/run-make-fulldeps/instrument-coverage/testprog.rs b/src/test/run-make-fulldeps/instrument-coverage-llvm-ir-base/testprog.rs similarity index 100% rename from src/test/run-make-fulldeps/instrument-coverage/testprog.rs rename to src/test/run-make-fulldeps/instrument-coverage-llvm-ir-base/testprog.rs diff --git a/src/test/run-make-fulldeps/instrument-coverage-llvm-ir-link-dead-code/Makefile b/src/test/run-make-fulldeps/instrument-coverage-llvm-ir-link-dead-code/Makefile new file mode 100644 index 0000000000000..994cba9a8d6bc --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-llvm-ir-link-dead-code/Makefile @@ -0,0 +1,11 @@ +# needs-profiler-support +# ignore-msvc + +# LINK_DEAD_CODE requires ignore-msvc due to Issue #64685 +LINK_DEAD_CODE=yes + +-include ../instrument-coverage-llvm-ir-base/Makefile + +# ISSUE(64685): When targeting MSVC, Rust binaries built with both `-Z instrument-coverage` and +# `-C link-dead-code` typically crash (with a seg-fault) or at best generate an empty `*.profraw`. +# See ../instrument-coverage/coverage_tools.mk for more information. \ No newline at end of file diff --git a/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/Makefile b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/Makefile new file mode 100644 index 0000000000000..845d90e9b7c2d --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/Makefile @@ -0,0 +1,34 @@ +# needs-profiler-support + +# ISSUE(64685): When targeting MSVC, Rust binaries built with both `-Z instrument-coverage` and +# `-C link-dead-code` typically crash (with a seg-fault) or at best generate an empty `*.profraw`. +# See ../instrument-coverage/coverage_tools.mk for more information. + +-include ../instrument-coverage/coverage_tools.mk + +SOURCEDIR=../instrument-coverage + +all: $(patsubst $(SOURCEDIR)/%.rs,%,$(wildcard $(SOURCEDIR)/*.rs)) + +%: $(SOURCEDIR)/%.rs + # Compile the test program with "experimental" coverage instrumentation and generate relevant MIR. + # + # FIXME(richkadel): `-Zexperimental-coverage` to `-Zinstrument-coverage` once we are + # satisfied with the branch-level instrumentation. + $(RUSTC) $(SOURCEDIR)/$@.rs \ + -Zexperimental-coverage \ + -Clink-dead-code=$(LINK_DEAD_CODE) \ + -Zdump-mir=InstrumentCoverage \ + -Zdump-mir-dir="$(TMPDIR)"/mir_dump.$@ + +ifdef RUSTC_BLESS_TEST + mkdir -p expected_mir_dump.$@ + rm -f "$(TMPDIR)"/expected_mir_dump.$@/* + cp "$(TMPDIR)"/mir_dump.$@/*InstrumentCoverage.0.html expected_mir_dump.$@/ +else + # Check that the selected `mir_dump` files match what we expect (`--bless` refreshes `expected`) + mkdir -p "$(TMPDIR)"/actual_mir_dump.$@ + rm -f "$(TMPDIR)"/actual_mir_dump.$@/* + cp "$(TMPDIR)"/mir_dump.$@/*InstrumentCoverage.0.html "$(TMPDIR)"/actual_mir_dump.$@/ + $(DIFF) -r expected_mir_dump.$@/ "$(TMPDIR)"/actual_mir_dump.$@/ +endif diff --git a/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.coverage_of_if_else/coverage_of_if_else.main.-------.InstrumentCoverage.0.html b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.coverage_of_if_else/coverage_of_if_else.main.-------.InstrumentCoverage.0.html new file mode 100644 index 0000000000000..86a43fb5e451a --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.coverage_of_if_else/coverage_of_if_else.main.-------.InstrumentCoverage.0.html @@ -0,0 +1,583 @@ + + + + coverage_of_if_else - Code Regions + + + +
fn main() { + let mut countdown = 0; + @2@4@3: if @0: true@3: { + countdown = 10; + } + + @6@9@25: if @5: countdown > 7@25: { + @8: countdown -= 4@25: ; + } else @10: if @7: countdown > 2@10: { + @22@23@21: if @14@15@16@13@20@12@18@19@17: countdown < 1 || countdown > 5@12: || countdown != 9@21: { + countdown = 0; + @24: } + countdown -= 5@10: ; + } else { + @27@11: return; + }@27: + + let mut countdown = 0; + @30@31@29: if @28: true@29: { + countdown = 10; + }@27: + + @33@52@36: if @32: countdown > 7@36: { + @35: countdown -= 4@36: ; + } else @37: if @34: countdown > 2@37: { + @48@50@49: if @39@47@40@43@42@41@46@45@44: countdown < 1 || countdown > 5@41: || countdown != 9@49: { + countdown = 0; + @51: } + countdown -= 5@37: ; + } else { + @38: return; + } + + let mut countdown = 0; + @56@54@55: if @53: true@55: { + countdown = 10; + }@38: + + @61@58@77: if @57: countdown > 7@77: { + @60: countdown -= 4@77: ; + } else @62: if @59: countdown > 2@62: { + @75@74@73: if @67@68@65@72@64@66@69@71@70: countdown < 1 || countdown > 5@66: || countdown != 9@73: { + countdown = 0; + @76: } + countdown -= 5@62: ; + } else { + @63: return; + }@78: }@26
+ + diff --git a/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.main-{{closure}}.-------.InstrumentCoverage.0.html b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.main-{{closure}}.-------.InstrumentCoverage.0.html new file mode 100644 index 0000000000000..a07f66749da9b --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.main-{{closure}}.-------.InstrumentCoverage.0.html @@ -0,0 +1,81 @@ + + + + coverage_of_if_else - Code Regions + + + +
|inner| @0: print(" ***", inner, "*** "@2: )
+ + diff --git a/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.main-{{closure}}[1].-------.InstrumentCoverage.0.html b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.main-{{closure}}[1].-------.InstrumentCoverage.0.html new file mode 100644 index 0000000000000..0372b9b1cd7dd --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.main-{{closure}}[1].-------.InstrumentCoverage.0.html @@ -0,0 +1,81 @@ + + + + coverage_of_if_else - Code Regions + + + +
|inner| @0: print(" ***", inner, "*** "@2: )
+ + diff --git a/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.main-{{closure}}[2].-------.InstrumentCoverage.0.html b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.main-{{closure}}[2].-------.InstrumentCoverage.0.html new file mode 100644 index 0000000000000..41fb88d58454a --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.main-{{closure}}[2].-------.InstrumentCoverage.0.html @@ -0,0 +1,81 @@ + + + + coverage_of_if_else - Code Regions + + + +
|inner| @0: print("wrapped result is: ", inner, ""@2: )
+ + diff --git a/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.main.-------.InstrumentCoverage.0.html b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.main.-------.InstrumentCoverage.0.html new file mode 100644 index 0000000000000..45eeb4c6e48ff --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.main.-------.InstrumentCoverage.0.html @@ -0,0 +1,185 @@ + + + + coverage_of_if_else - Code Regions + + + +
fn main() { + let less = 1; + let more = 100; + + @2@8@10: if @0: less < more@10: { + @4@5: wrap_with(will_be_called(), less < more, |inner| print(" ***", inner, "*** ")@6: ); + @7: wrap_with(will_be_called(), more < less, |inner| print(" ***", inner, "*** "))@10: ; + } else { + @3@9: wrap_with(will_not_be_called(), true, |inner| print("wrapped result is: ", inner, ""))@10: ; + }@11: }
+ + diff --git a/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.print.-------.InstrumentCoverage.0.html b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.print.-------.InstrumentCoverage.0.html new file mode 100644 index 0000000000000..1cab3f9aae417 --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.print.-------.InstrumentCoverage.0.html @@ -0,0 +1,99 @@ + + + + coverage_of_if_else - Code Regions + + + +
pub fn print(left: &str, value: T, right: &str) +where + T: std::fmt::Display,@7: { + println!("{}{}{}", left, value, right); +}@8
+ + diff --git a/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.will_be_called.-------.InstrumentCoverage.0.html b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.will_be_called.-------.InstrumentCoverage.0.html new file mode 100644 index 0000000000000..622fdff67346e --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.will_be_called.-------.InstrumentCoverage.0.html @@ -0,0 +1,90 @@ + + + + coverage_of_if_else - Code Regions + + + +
pub fn will_be_called() -> &'static str { + let val = "called"; + println!("{}", val); + @4: val +}
+ + diff --git a/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.will_not_be_called.-------.InstrumentCoverage.0.html b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.will_not_be_called.-------.InstrumentCoverage.0.html new file mode 100644 index 0000000000000..702bcb539bb6c --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.will_not_be_called.-------.InstrumentCoverage.0.html @@ -0,0 +1,85 @@ + + + + coverage_of_if_else - Code Regions + + + +
pub fn will_not_be_called() -> bool { + println!("should not have been called"); + @3: false +}
+ + diff --git a/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.wrap_with.-------.InstrumentCoverage.0.html b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.wrap_with.-------.InstrumentCoverage.0.html new file mode 100644 index 0000000000000..73ed1da697320 --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.testprog/testprog.wrap_with.-------.InstrumentCoverage.0.html @@ -0,0 +1,103 @@ + + + + coverage_of_if_else - Code Regions + + + +
pub fn wrap_with(inner: T, should_wrap: bool, wrapper: F) +where + F: FnOnce(&T) +{ + @2@5@3: if @0: should_wrap@3: { + @4: wrapper(&inner)@3: + }@9@10: }@11
+ + diff --git a/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/Makefile b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/Makefile new file mode 100644 index 0000000000000..046718cf54660 --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/Makefile @@ -0,0 +1,11 @@ +# needs-profiler-support +# ignore-msvc + +# LINK_DEAD_CODE requires ignore-msvc due to Issue #64685 +LINK_DEAD_CODE=yes + +-include ../instrument-coverage-mir-cov-html-base/Makefile + +# ISSUE(64685): When targeting MSVC, Rust binaries built with both `-Z instrument-coverage` and +# `-C link-dead-code` typically crash (with a seg-fault) or at best generate an empty `*.profraw`. +# See ../instrument-coverage/coverage_tools.mk for more information. \ No newline at end of file diff --git a/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.coverage_of_if_else/coverage_of_if_else.main.-------.InstrumentCoverage.0.html b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.coverage_of_if_else/coverage_of_if_else.main.-------.InstrumentCoverage.0.html new file mode 100644 index 0000000000000..86a43fb5e451a --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.coverage_of_if_else/coverage_of_if_else.main.-------.InstrumentCoverage.0.html @@ -0,0 +1,583 @@ + + + + coverage_of_if_else - Code Regions + + + +
fn main() { + let mut countdown = 0; + @2@4@3: if @0: true@3: { + countdown = 10; + } + + @6@9@25: if @5: countdown > 7@25: { + @8: countdown -= 4@25: ; + } else @10: if @7: countdown > 2@10: { + @22@23@21: if @14@15@16@13@20@12@18@19@17: countdown < 1 || countdown > 5@12: || countdown != 9@21: { + countdown = 0; + @24: } + countdown -= 5@10: ; + } else { + @27@11: return; + }@27: + + let mut countdown = 0; + @30@31@29: if @28: true@29: { + countdown = 10; + }@27: + + @33@52@36: if @32: countdown > 7@36: { + @35: countdown -= 4@36: ; + } else @37: if @34: countdown > 2@37: { + @48@50@49: if @39@47@40@43@42@41@46@45@44: countdown < 1 || countdown > 5@41: || countdown != 9@49: { + countdown = 0; + @51: } + countdown -= 5@37: ; + } else { + @38: return; + } + + let mut countdown = 0; + @56@54@55: if @53: true@55: { + countdown = 10; + }@38: + + @61@58@77: if @57: countdown > 7@77: { + @60: countdown -= 4@77: ; + } else @62: if @59: countdown > 2@62: { + @75@74@73: if @67@68@65@72@64@66@69@71@70: countdown < 1 || countdown > 5@66: || countdown != 9@73: { + countdown = 0; + @76: } + countdown -= 5@62: ; + } else { + @63: return; + }@78: }@26
+ + diff --git a/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.main-{{closure}}.-------.InstrumentCoverage.0.html b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.main-{{closure}}.-------.InstrumentCoverage.0.html new file mode 100644 index 0000000000000..a07f66749da9b --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.main-{{closure}}.-------.InstrumentCoverage.0.html @@ -0,0 +1,81 @@ + + + + coverage_of_if_else - Code Regions + + + +
|inner| @0: print(" ***", inner, "*** "@2: )
+ + diff --git a/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.main-{{closure}}[1].-------.InstrumentCoverage.0.html b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.main-{{closure}}[1].-------.InstrumentCoverage.0.html new file mode 100644 index 0000000000000..0372b9b1cd7dd --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.main-{{closure}}[1].-------.InstrumentCoverage.0.html @@ -0,0 +1,81 @@ + + + + coverage_of_if_else - Code Regions + + + +
|inner| @0: print(" ***", inner, "*** "@2: )
+ + diff --git a/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.main-{{closure}}[2].-------.InstrumentCoverage.0.html b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.main-{{closure}}[2].-------.InstrumentCoverage.0.html new file mode 100644 index 0000000000000..41fb88d58454a --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.main-{{closure}}[2].-------.InstrumentCoverage.0.html @@ -0,0 +1,81 @@ + + + + coverage_of_if_else - Code Regions + + + +
|inner| @0: print("wrapped result is: ", inner, ""@2: )
+ + diff --git a/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.main.-------.InstrumentCoverage.0.html b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.main.-------.InstrumentCoverage.0.html new file mode 100644 index 0000000000000..45eeb4c6e48ff --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.main.-------.InstrumentCoverage.0.html @@ -0,0 +1,185 @@ + + + + coverage_of_if_else - Code Regions + + + +
fn main() { + let less = 1; + let more = 100; + + @2@8@10: if @0: less < more@10: { + @4@5: wrap_with(will_be_called(), less < more, |inner| print(" ***", inner, "*** ")@6: ); + @7: wrap_with(will_be_called(), more < less, |inner| print(" ***", inner, "*** "))@10: ; + } else { + @3@9: wrap_with(will_not_be_called(), true, |inner| print("wrapped result is: ", inner, ""))@10: ; + }@11: }
+ + diff --git a/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.print.-------.InstrumentCoverage.0.html b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.print.-------.InstrumentCoverage.0.html new file mode 100644 index 0000000000000..1cab3f9aae417 --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.print.-------.InstrumentCoverage.0.html @@ -0,0 +1,99 @@ + + + + coverage_of_if_else - Code Regions + + + +
pub fn print(left: &str, value: T, right: &str) +where + T: std::fmt::Display,@7: { + println!("{}{}{}", left, value, right); +}@8
+ + diff --git a/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.will_be_called.-------.InstrumentCoverage.0.html b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.will_be_called.-------.InstrumentCoverage.0.html new file mode 100644 index 0000000000000..622fdff67346e --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.will_be_called.-------.InstrumentCoverage.0.html @@ -0,0 +1,90 @@ + + + + coverage_of_if_else - Code Regions + + + +
pub fn will_be_called() -> &'static str { + let val = "called"; + println!("{}", val); + @4: val +}
+ + diff --git a/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.will_not_be_called.-------.InstrumentCoverage.0.html b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.will_not_be_called.-------.InstrumentCoverage.0.html new file mode 100644 index 0000000000000..702bcb539bb6c --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.will_not_be_called.-------.InstrumentCoverage.0.html @@ -0,0 +1,85 @@ + + + + coverage_of_if_else - Code Regions + + + +
pub fn will_not_be_called() -> bool { + println!("should not have been called"); + @3: false +}
+ + diff --git a/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.wrap_with.-------.InstrumentCoverage.0.html b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.wrap_with.-------.InstrumentCoverage.0.html new file mode 100644 index 0000000000000..73ed1da697320 --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-link-dead-code/expected_mir_dump.testprog/testprog.wrap_with.-------.InstrumentCoverage.0.html @@ -0,0 +1,103 @@ + + + + coverage_of_if_else - Code Regions + + + +
pub fn wrap_with(inner: T, should_wrap: bool, wrapper: F) +where + F: FnOnce(&T) +{ + @2@5@3: if @0: should_wrap@3: { + @4: wrapper(&inner)@3: + }@9@10: }@11
+ + diff --git a/src/test/run-make-fulldeps/instrument-coverage/Makefile b/src/test/run-make-fulldeps/instrument-coverage/Makefile deleted file mode 100644 index 4392cfec080dc..0000000000000 --- a/src/test/run-make-fulldeps/instrument-coverage/Makefile +++ /dev/null @@ -1,103 +0,0 @@ -# needs-profiler-support -# ignore-msvc - -# FIXME(richkadel): Debug the following problem, and reenable on Windows (by -# removing the `# ignore-msvc` directive above). The current implementation -# generates a segfault when running the instrumented `testprog` executable, -# after the `main()` function completes, but before the process terminates. -# This most likely points to a problem generating the LLVM "testprog.profraw" -# file. - --include ../tools.mk - -UNAME = $(shell uname) - -ifeq ($(UNAME),Darwin) - INSTR_PROF_DATA_SUFFIX=,regular,live_support - DATA_SECTION_PREFIX=__DATA, - LLVM_COV_SECTION_PREFIX=__LLVM_COV, -else - INSTR_PROF_DATA_SUFFIX= - DATA_SECTION_PREFIX= - LLVM_COV_SECTION_PREFIX= -endif - -# This test makes sure that LLVM coverage maps are genereated in LLVM IR. - -COMMON_FLAGS=-Zinstrument-coverage - -all: - # Compile the test program with instrumentation, and also generate LLVM IR - $(RUSTC) $(COMMON_FLAGS) testprog.rs \ - --emit=link,llvm-ir - - # check the LLVM IR -ifdef IS_WIN32 - cat "$(TMPDIR)"/testprog.ll | "$(LLVM_FILECHECK)" filecheck-patterns.txt \ - -check-prefixes=CHECK,WIN32 \ - -DPRIVATE_GLOBAL="internal global" \ - -DINSTR_PROF_DATA=".lprfd$$M" \ - -DINSTR_PROF_NAME=".lprfn$$M" \ - -DINSTR_PROF_CNTS=".lprfc$$M" \ - -DINSTR_PROF_VALS=".lprfv$$M" \ - -DINSTR_PROF_VNODES=".lprfnd$$M" \ - -DINSTR_PROF_COVMAP=".lcovmap$$M" \ - -DINSTR_PROF_ORDERFILE=".lorderfile$$M" -else - cat "$(TMPDIR)"/testprog.ll | "$(LLVM_FILECHECK)" filecheck-patterns.txt \ - -check-prefixes=CHECK \ - -DPRIVATE_GLOBAL="private global" \ - -DINSTR_PROF_DATA="$(DATA_SECTION_PREFIX)__llvm_prf_data$(INSTR_PROF_DATA_SUFFIX)" \ - -DINSTR_PROF_NAME="$(DATA_SECTION_PREFIX)__llvm_prf_names" \ - -DINSTR_PROF_CNTS="$(DATA_SECTION_PREFIX)__llvm_prf_cnts" \ - -DINSTR_PROF_VALS="$(DATA_SECTION_PREFIX)__llvm_prf_vals" \ - -DINSTR_PROF_VNODES="$(DATA_SECTION_PREFIX)__llvm_prf_vnds" \ - -DINSTR_PROF_COVMAP="$(LLVM_COV_SECTION_PREFIX)__llvm_covmap" \ - -DINSTR_PROF_ORDERFILE="$(DATA_SECTION_PREFIX)__llvm_orderfile" -endif - - # Run it in order to generate some profiling data, - # with `LLVM_PROFILE_FILE=` environment variable set to - # output the coverage stats for this run. - LLVM_PROFILE_FILE="$(TMPDIR)"/testprog.profraw \ - $(call RUN,testprog) - - # Postprocess the profiling data so it can be used by the llvm-cov tool - "$(LLVM_BIN_DIR)"/llvm-profdata merge --sparse \ - "$(TMPDIR)"/testprog.profraw \ - -o "$(TMPDIR)"/testprog.profdata - - # Generate a coverage report using `llvm-cov show`. The output ordering - # can be non-deterministic, so ignore the return status. If the test fails - # when comparing the JSON `export`, the `show` output may be useful when - # debugging. - "$(LLVM_BIN_DIR)"/llvm-cov show \ - --Xdemangler="$(RUST_DEMANGLER)" \ - --show-line-counts-or-regions \ - --instr-profile="$(TMPDIR)"/testprog.profdata \ - $(call BIN,"$(TMPDIR)"/testprog) \ - > "$(TMPDIR)"/actual_show_coverage.txt - -ifdef RUSTC_BLESS_TEST - cp "$(TMPDIR)"/actual_show_coverage.txt typical_show_coverage.txt -else - # Compare the show coverage output - $(DIFF) typical_show_coverage.txt "$(TMPDIR)"/actual_show_coverage.txt || \ - >&2 echo 'diff failed for `llvm-cov show` (might not be an error)' -endif - - # Generate a coverage report in JSON, using `llvm-cov export`, and fail if - # there are differences from the expected output. - "$(LLVM_BIN_DIR)"/llvm-cov export \ - --summary-only \ - --instr-profile="$(TMPDIR)"/testprog.profdata \ - $(call BIN,"$(TMPDIR)"/testprog) \ - | "$(PYTHON)" prettify_json.py \ - > "$(TMPDIR)"/actual_export_coverage.json - -ifdef RUSTC_BLESS_TEST - cp "$(TMPDIR)"/actual_export_coverage.json expected_export_coverage.json -else - # Check that the exported JSON coverage data matches what we expect - $(DIFF) expected_export_coverage.json "$(TMPDIR)"/actual_export_coverage.json -endif diff --git a/src/test/run-make-fulldeps/instrument-coverage/compiletest-ignore-dir b/src/test/run-make-fulldeps/instrument-coverage/compiletest-ignore-dir new file mode 100644 index 0000000000000..d57f66a44898e --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage/compiletest-ignore-dir @@ -0,0 +1,3 @@ +# Directory "instrument-coverage" supports the tests at prefix ../instrument-coverage-* + +# Use ./x.py [options] test src/test/run-make-fulldeps/instrument-coverage to run all related tests. \ No newline at end of file diff --git a/src/test/run-make-fulldeps/instrument-coverage/coverage_of_if_else.rs b/src/test/run-make-fulldeps/instrument-coverage/coverage_of_if_else.rs new file mode 100644 index 0000000000000..91741cf8f0dcc --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage/coverage_of_if_else.rs @@ -0,0 +1,51 @@ +#![allow(unused_assignments)] + +fn main() { + let mut countdown = 0; + if true { + countdown = 10; + } + + if countdown > 7 { + countdown -= 4; + } else if countdown > 2 { + if countdown < 1 || countdown > 5 || countdown != 9 { + countdown = 0; + } + countdown -= 5; + } else { + return; + } + + let mut countdown = 0; + if true { + countdown = 10; + } + + if countdown > 7 { + countdown -= 4; + } else if countdown > 2 { + if countdown < 1 || countdown > 5 || countdown != 9 { + countdown = 0; + } + countdown -= 5; + } else { + return; + } + + let mut countdown = 0; + if true { + countdown = 10; + } + + if countdown > 7 { + countdown -= 4; + } else if countdown > 2 { + if countdown < 1 || countdown > 5 || countdown != 9 { + countdown = 0; + } + countdown -= 5; + } else { + return; + } +} diff --git a/src/test/run-make-fulldeps/instrument-coverage/coverage_tools.mk b/src/test/run-make-fulldeps/instrument-coverage/coverage_tools.mk new file mode 100644 index 0000000000000..3d2dfd70cd9d0 --- /dev/null +++ b/src/test/run-make-fulldeps/instrument-coverage/coverage_tools.mk @@ -0,0 +1,41 @@ +# Common Makefile include for Rust `run-make-fulldeps/instrument-coverage-* tests. Include this +# file with the line: +# +# -include ../instrument-coverage/coverage_tools.mk +# +# To enable the Rust compiler option `-C link-dead-code`, also set the following variable +# *BEFORE* the `-include` line: +# +# LINK_DEAD_CODE=yes + +-include ../tools.mk + +ifndef LINK_DEAD_CODE + LINK_DEAD_CODE=no +endif + +# ISSUE(64685): When targeting MSVC, Rust binaries built with both `-Z instrument-coverage` and +# `-C link-dead-code` typically crash (with a seg-fault) or at best generate an empty `*.profraw` +# file, required for coverage reports (as described in the issue comment at +# github.com/rust-lang/rust/issues/64685#issuecomment-679382091 and the expanded notes at +# github.com/richkadel/rust/blob/coverage-msvc-segfault-discuss/coverage-msvc-segfault-notes.md). +# +# Enabling `-C link-dead-code` is preferred when compiling with `-Z instrument-coverage`, so +# `-C link-dead-code` is automatically enabled for all platform targets _except_ MSVC. +# +# Making the state of `-C link-dead-code` platform-dependent creates a problem for cross-platform +# tests because the injected counters, coverage reports, and some low-level output can be different, +# depending on the `-C link-dead-code` setting. For example, coverage reports will not report any +# coverage for a dead code region when the `-C link-dead-code` option is disabled, but with the +# option enabled, those same regions will show coverage counter values (of zero, of course). +# +# To ensure cross-platform `-Z instrument-coverage` generate consistent output, the +# `-C link-dead-code` option is always explicitly enabled or disabled. +# +# Since tests that execute binaries enabled with both `-Z instrument-coverage` and +# `-C link-dead-code` are known to fail, those tests will need the `# ignore-msvc` setting. +# +# If and when the above issue is resolved, the `# ignore-msvc` option can be removed, and the +# tests can be simplified to always test with `-C link-dead-code`. + +UNAME = $(shell uname) diff --git a/src/test/run-make-fulldeps/instrument-coverage/filecheck-patterns.txt b/src/test/run-make-fulldeps/instrument-coverage/filecheck-patterns.txt deleted file mode 100644 index 5a7cc9a188202..0000000000000 --- a/src/test/run-make-fulldeps/instrument-coverage/filecheck-patterns.txt +++ /dev/null @@ -1,51 +0,0 @@ -# Check for metadata, variables, declarations, and function definitions injected -# into LLVM IR when compiling with -Zinstrument-coverage. - -WIN32: $__llvm_profile_runtime_user = comdat any - -CHECK: @__llvm_coverage_mapping = internal constant -CHECK-SAME: section "[[INSTR_PROF_COVMAP]]", align 8 - -WIN32: @__llvm_profile_runtime = external global i32 - -CHECK: @__profc__R{{[a-zA-Z0-9_]+}}testprog14will_be_called = [[PRIVATE_GLOBAL]] -CHECK-SAME: section "[[INSTR_PROF_CNTS]]", align 8 - -CHECK: @__profd__R{{[a-zA-Z0-9_]+}}testprog14will_be_called = [[PRIVATE_GLOBAL]] -CHECK-SAME: @__profc__R{{[a-zA-Z0-9_]+}}testprog14will_be_called, -CHECK-SAME: ()* @_R{{[a-zA-Z0-9_]+}}testprog14will_be_called to i8*), -CHECK-SAME: section "[[INSTR_PROF_DATA]]", align 8 - -CHECK: @__profc__R{{[a-zA-Z0-9_]+}}testprog4main = [[PRIVATE_GLOBAL]] -CHECK-SAME: section "[[INSTR_PROF_CNTS]]", align 8 - -CHECK: @__profd__R{{[a-zA-Z0-9_]+}}testprog4main = [[PRIVATE_GLOBAL]] -CHECK-SAME: @__profc__R{{[a-zA-Z0-9_]+}}testprog4main, -CHECK-SAME: ()* @_R{{[a-zA-Z0-9_]+}}testprog4main to i8*), -CHECK-SAME: section "[[INSTR_PROF_DATA]]", align 8 - -CHECK: @__llvm_prf_nm = private constant -CHECK-SAME: section "[[INSTR_PROF_NAME]]", align 1 - -CHECK: @llvm.used = appending global -CHECK-SAME: i8* bitcast ({ {{.*}} }* @__llvm_coverage_mapping to i8*) -WIN32-SAME: i8* bitcast (i32 ()* @__llvm_profile_runtime_user to i8*) -CHECK-SAME: i8* bitcast ({ {{.*}} }* @__profd__R{{[a-zA-Z0-9_]*}}testprog4main to i8*) -CHECK-SAME: i8* getelementptr inbounds ({{.*}}* @__llvm_prf_nm, i32 0, i32 0) -CHECK-SAME: section "llvm.metadata" - -CHECK: define hidden { {{.*}} } @_R{{[a-zA-Z0-9_]+}}testprog14will_be_called() unnamed_addr #{{[0-9]+}} { -CHECK-NEXT: start: -CHECK-NOT: bb{{[0-9]+}}: -CHECK: %pgocount = load i64, i64* getelementptr inbounds -CHECK-SAME: * @__profc__R{{[a-zA-Z0-9_]+}}testprog14will_be_called, - -CHECK: declare void @llvm.instrprof.increment(i8*, i64, i32, i32) #[[LLVM_INSTRPROF_INCREMENT_ATTR:[0-9]+]] - -WIN32: define linkonce_odr hidden i32 @__llvm_profile_runtime_user() #[[LLVM_PROFILE_RUNTIME_USER_ATTR:[0-9]+]] comdat { -WIN32-NEXT: %1 = load i32, i32* @__llvm_profile_runtime -WIN32-NEXT: ret i32 %1 -WIN32-NEXT: } - -CHECK: attributes #[[LLVM_INSTRPROF_INCREMENT_ATTR]] = { nounwind } -WIN32: attributes #[[LLVM_PROFILE_RUNTIME_USER_ATTR]] = { noinline } \ No newline at end of file diff --git a/src/test/run-make-fulldeps/instrument-coverage/typical_show_coverage.txt b/src/test/run-make-fulldeps/instrument-coverage/typical_show_coverage.txt deleted file mode 100644 index ae123afff0400..0000000000000 --- a/src/test/run-make-fulldeps/instrument-coverage/typical_show_coverage.txt +++ /dev/null @@ -1,55 +0,0 @@ - 1| 2|pub fn will_be_called() -> &'static str { - 2| 2| let val = "called"; - 3| 2| println!("{}", val); - 4| 2| val - 5| 2|} - 6| | - 7| 0|pub fn will_not_be_called() -> bool { - 8| 0| println!("should not have been called"); - 9| 0| false - 10| 0|} - 11| | - 12| |pub fn print(left: &str, value: T, right: &str) - 13| |where - 14| | T: std::fmt::Display, - 15| 1|{ - 16| 1| println!("{}{}{}", left, value, right); - 17| 1|} - 18| | - 19| |pub fn wrap_with(inner: T, should_wrap: bool, wrapper: F) - 20| |where - 21| | F: FnOnce(&T) - 22| 2|{ - 23| 2| if should_wrap { - 24| 2| wrapper(&inner) - 25| 2| } - 26| 2|} - ------------------ - | testprog[317d481089b8c8fe]::wrap_with::: - | 22| 1|{ - | 23| 1| if should_wrap { - | 24| 1| wrapper(&inner) - | 25| 1| } - | 26| 1|} - ------------------ - | testprog[317d481089b8c8fe]::wrap_with::: - | 22| 1|{ - | 23| 1| if should_wrap { - | 24| 1| wrapper(&inner) - | 25| 1| } - | 26| 1|} - ------------------ - 27| | - 28| 1|fn main() { - 29| 1| let less = 1; - 30| 1| let more = 100; - 31| 1| - 32| 1| if less < more { - 33| 1| wrap_with(will_be_called(), less < more, |inner| print(" ***", inner, "*** ")); - 34| 1| wrap_with(will_be_called(), more < less, |inner| print(" ***", inner, "*** ")); - ^0 - 35| 1| } else { - 36| 1| wrap_with(will_not_be_called(), true, |inner| print("wrapped result is: ", inner, "")); - 37| 1| } - 38| 1|} - diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 124a9adcab91a..965b20f5202e0 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -2782,6 +2782,18 @@ impl<'test> TestCx<'test> { cmd.env("RUSTFLAGS", "-Ctarget-feature=-crt-static").env("IS_MUSL_HOST", "1"); } + if self.config.bless { + cmd.env("RUSTC_BLESS_TEST", "--bless"); + // Assume this option is active if the environment variable is "defined", with _any_ value. + // As an example, a `Makefile` can use this option by: + // + // ifdef RUSTC_BLESS_TEST + // cp "$(TMPDIR)"/actual_something.ext expected_something.ext + // else + // $(DIFF) expected_something.ext "$(TMPDIR)"/actual_something.ext + // endif + } + if self.config.target.contains("msvc") && self.config.cc != "" { // We need to pass a path to `lib.exe`, so assume that `cc` is `cl.exe` // and that `lib.exe` lives next to it.