Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] incomplete attempt to inject additional coverage for unused MIR #79392

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 38 additions & 3 deletions compiler/rustc_codegen_llvm/src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,27 @@ use crate::metadata;
use crate::value::Value;

use rustc_codegen_ssa::base::maybe_create_entry_wrapper;
use rustc_codegen_ssa::coverageinfo::map::FunctionCoverage;
use rustc_codegen_ssa::mono_item::MonoItemExt;
use rustc_codegen_ssa::traits::*;
use rustc_codegen_ssa::{ModuleCodegen, ModuleKind};
use rustc_data_structures::small_c_str::SmallCStr;
use rustc_hir::def_id::LOCAL_CRATE;
use rustc_middle::dep_graph;
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs;
use rustc_middle::middle::cstore::EncodedMetadata;
use rustc_middle::middle::exported_symbols;
use rustc_middle::mir::mono::{Linkage, Visibility};
use rustc_middle::ty::TyCtxt;
use rustc_middle::ty::{Instance, TyCtxt};
use rustc_middle::ty::subst::InternalSubsts;
use rustc_session::config::{DebugInfo, SanitizerSet};
use rustc_span::symbol::Symbol;

use std::ffi::CString;
use std::time::Instant;

use tracing::debug;

pub fn write_compressed_metadata<'tcx>(
tcx: TyCtxt<'tcx>,
metadata: &EncodedMetadata,
Expand Down Expand Up @@ -109,7 +114,7 @@ pub fn compile_codegen_unit(
let cost = time_to_codegen.as_nanos() as u64;

fn module_codegen(tcx: TyCtxt<'_>, cgu_name: Symbol) -> ModuleCodegen<ModuleLlvm> {
let cgu = tcx.codegen_unit(cgu_name);
let (index, cgu) = tcx.indexed_codegen_unit(cgu_name);
let _prof_timer = tcx.prof.generic_activity_with_args(
"codegen_module",
&[cgu_name.to_string(), cgu.size_estimate().to_string()],
Expand Down Expand Up @@ -145,7 +150,37 @@ pub fn compile_codegen_unit(

// Finalize code coverage by injecting the coverage map. Note, the coverage map will
// also be added to the `llvm.used` variable, created next.
if cx.sess().opts.debugging_opts.instrument_coverage {
if let Some(coverage_cx) = cx.coverage_context() {
if index == 0 {
// If this is the first CGU for the current `Crate` (because this should only
// be done once per `Crate`), find any MIR not associated with a `MonoItem`,
// and add it's `mir::Body`s code region to the Coverage Map, with a `Zero`
// Counter. Codegen was not done for these items, so nothing was added to the
// Coverage Map otherwise.
let mut coverage_map = coverage_cx.function_coverage_map.borrow_mut();
for local_def_id in tcx
.mir_keys(LOCAL_CRATE)
.iter()
.filter(|&def_id| !tcx.is_codegened_item(*def_id))
{
let def_id = local_def_id.to_def_id();
if let Some((hash, region)) = tcx.uncovered_function_hash_and_region(def_id) {
let substs = InternalSubsts::identity_for_item(tcx, def_id);
let instance = Instance::new(def_id, substs);
debug!(
"adding a coverage map entry for uncovered function {:?}, \
mangled name={}, function source hash={} at {:?}",
instance, cx.tcx.symbol_name(instance).to_string(), hash, region,
);
let mut function_coverage = FunctionCoverage::new(tcx, instance);
function_coverage.set_function_source_hash(*hash);
function_coverage.add_unreachable_region(region.clone());
coverage_map.insert(instance, function_coverage).expect_none(
"uncovered functions should not already be in the coverage map",
);
}
}
}
cx.coverageinfo_finalize();
}

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_codegen_llvm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#![feature(extern_types)]
#![feature(in_band_lifetimes)]
#![feature(nll)]
#![feature(option_expect_none)]
#![feature(or_patterns)]
#![recursion_limit = "256"]

Expand Down
8 changes: 6 additions & 2 deletions compiler/rustc_codegen_ssa/src/coverageinfo/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub struct Expression {
/// only whitespace or comments). According to LLVM Code Coverage Mapping documentation, "A count
/// for a gap area is only used as the line execution count if there are no other regions on a
/// line."
#[derive(Debug)]
pub struct FunctionCoverage<'tcx> {
instance: Instance<'tcx>,
source_hash: u64,
Expand Down Expand Up @@ -55,6 +56,7 @@ impl<'tcx> FunctionCoverage<'tcx> {
/// Sets the function source hash value. If called multiple times for the same function, all
/// calls should have the same hash value.
pub fn set_function_source_hash(&mut self, source_hash: u64) {
debug!("set_function_source_hash({}), for {:?}", source_hash, self.instance);
if self.source_hash == 0 {
self.source_hash = source_hash;
} else {
Expand All @@ -64,6 +66,7 @@ impl<'tcx> FunctionCoverage<'tcx> {

/// Adds a code region to be counted by an injected counter intrinsic.
pub fn add_counter(&mut self, id: CounterValueReference, region: CodeRegion) {
debug!("add_counter({:?}) at {:?}, for {:?}", id, region, self.instance);
self.counters[id].replace(region).expect_none("add_counter called with duplicate `id`");
}

Expand All @@ -90,8 +93,8 @@ impl<'tcx> FunctionCoverage<'tcx> {
region: Option<CodeRegion>,
) {
debug!(
"add_counter_expression({:?}, lhs={:?}, op={:?}, rhs={:?} at {:?}",
expression_id, lhs, op, rhs, region
"add_counter_expression({:?}, lhs={:?}, op={:?}, rhs={:?}) at {:?}, for {:?}",
expression_id, lhs, op, rhs, region, self.instance
);
let expression_index = self.expression_index(u32::from(expression_id));
self.expressions[expression_index]
Expand All @@ -101,6 +104,7 @@ impl<'tcx> FunctionCoverage<'tcx> {

/// Add a region that will be marked as "unreachable", with a constant "zero counter".
pub fn add_unreachable_region(&mut self, region: CodeRegion) {
debug!("add_unreachable_region() at {:?}, for {:?}", region, self.instance);
self.unreachable_regions.push(region)
}

Expand Down
11 changes: 11 additions & 0 deletions compiler/rustc_middle/src/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,14 @@ rustc_queries! {
cache_on_disk_if { key.is_local() }
}

/// Returns the function source hash and code region for the body of a function that is
/// uncovered, because it was unreachable and not codegen'ed.
query uncovered_function_hash_and_region(key: DefId) -> Option<(u64, mir::coverage::CodeRegion)> {
desc { |tcx| "retrieving uncovered function hash and code region from MIR for `{}`", tcx.def_path_str(key) }
storage(ArenaCacheSelector<'tcx>)
cache_on_disk_if { key.is_local() }
}

/// The `DefId` is the `DefId` of the containing MIR body. Promoteds do not have their own
/// `DefId`. This function returns all promoteds in the specified body. The body references
/// promoteds by the `DefId` and the `mir::Promoted` index. This is necessary, because
Expand Down Expand Up @@ -1411,6 +1419,9 @@ rustc_queries! {
query codegen_unit(_: Symbol) -> &'tcx CodegenUnit<'tcx> {
desc { "codegen_unit" }
}
query indexed_codegen_unit(_: Symbol) -> (usize, &'tcx CodegenUnit<'tcx>) {
desc { "indexed_codegen_unit" }
}
query unused_generic_params(key: DefId) -> FiniteBitSet<u32> {
cache_on_disk_if { key.is_local() }
desc {
Expand Down
8 changes: 8 additions & 0 deletions compiler/rustc_mir/src/monomorphize/partitioning/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,4 +430,12 @@ pub fn provide(providers: &mut Providers) {
.find(|cgu| cgu.name() == name)
.unwrap_or_else(|| panic!("failed to find cgu with name {:?}", name))
};

providers.indexed_codegen_unit = |tcx, name| {
let (_, all) = tcx.collect_and_partition_mono_items(LOCAL_CRATE);
all.iter()
.enumerate()
.find(|(_, cgu)| cgu.name() == name)
.unwrap_or_else(|| panic!("failed to find cgu with name {:?}", name))
};
}
26 changes: 26 additions & 0 deletions compiler/rustc_mir/src/transform/coverage/query.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
use super::*;

use rustc_middle::mir::coverage::*;
use rustc_middle::mir::visit::Visitor;
use rustc_middle::mir::{Coverage, CoverageInfo, Location};
use rustc_middle::ty::query::Providers;
use rustc_middle::ty::TyCtxt;
use rustc_span::BytePos;
use rustc_span::def_id::DefId;

/// 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);
providers.uncovered_function_hash_and_region =
|tcx, def_id| uncovered_function_hash_and_region(tcx, def_id);
}

/// The `num_counters` argument to `llvm.instrprof.increment` is the max counter_id + 1, or in
Expand Down Expand Up @@ -123,3 +128,24 @@ fn coverageinfo_from_mir<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> CoverageInfo

coverage_visitor.info
}

fn uncovered_function_hash_and_region<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Option<(u64, CodeRegion)> {
let mir_body = tcx.optimized_mir(def_id);
if ! mir_body.basic_blocks().iter_enumerated().any(|(_, data)| data.statements.iter().any(|statement| match statement.kind {
StatementKind::Coverage(_) => true,
_ => false,
})) {
return None;

}
let hir_body = hir_body(tcx, def_id);
let body_span = hir_body.value.span;
let source_map = tcx.sess.source_map();
let source_file = source_map.lookup_source_file(body_span.lo());
let file_name = Symbol::intern(&source_file.name.to_string());
let function_span_to_closing_brace = body_span.with_hi(body_span.hi() - BytePos(1));
Some((
hash_mir_source(tcx, hir_body),
make_code_region(file_name, &source_file, function_span_to_closing_brace, body_span),
))
}
67 changes: 67 additions & 0 deletions src/test/run-make-fulldeps/coverage/async.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#![allow(unused_assignments)]

// require-rust-edition-2018

async fn f() -> u8 {
println!("executed body of async fn f()");
1
}

async fn foo() -> [bool; 10] { [false; 10] }

pub async fn g(x: u8) {
match x {
y if f().await == y => (),
_ => (),
}
}

// #78366: check the reference to the binding is recorded even if the binding is not autorefed

async fn h(x: usize) {
match x {
y if foo().await[y] => (),
_ => (),
}
}

async fn i(x: u8) {
match x {
y if f().await == y + 1 => (),
_ => (),
}
}

fn main() {
let _ = g(10);
let _ = h(9);
let mut future = Box::pin(i(8));
executor::block_on(future.as_mut());
}

mod executor {
use core::{
future::Future,
pin::Pin,
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
};

pub fn block_on<F: Future>(mut future: F) -> F::Output {
let mut future = unsafe { Pin::new_unchecked(&mut future) };

static VTABLE: RawWakerVTable = RawWakerVTable::new(
|_| unimplemented!("clone"),
|_| unimplemented!("wake"),
|_| unimplemented!("wake_by_ref"),
|_| (),
);
let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) };
let mut context = Context::from_waker(&waker);

loop {
if let Poll::Ready(val) = future.as_mut().poll(&mut context) {
break val;
}
}
}
}
12 changes: 11 additions & 1 deletion src/test/run-make-fulldeps/coverage/if.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
#![allow(unused_assignments, unused_variables)]
// #![allow(unused_assignments, unused_variables)]

fn notcalled() {
println!("pub never called");
}

fn main() {
// let unused = || {
// println!("closure never called");
// };



// Initialize test constants in a way that cannot be determined at compile time, to ensure
// rustc and LLVM cannot optimize out statements (or coverage counters) downstream from
// dependent conditions.
Expand Down