From 5068ae1ca05b2be0c2a98206a58d894aa620b312 Mon Sep 17 00:00:00 2001 From: Rich Kadel Date: Wed, 3 Jun 2020 21:19:34 -0700 Subject: [PATCH 01/14] [WIP] injects llvm intrinsic instrprof.increment for coverage reports This initial version only injects counters at the top of each function. Rust Coverage will require injecting additional counters at each conditional code branch. --- src/libcore/intrinsics.rs | 9 + src/librustc_codegen_llvm/builder.rs | 27 ++ src/librustc_codegen_llvm/context.rs | 2 + src/librustc_codegen_llvm/intrinsic.rs | 21 + src/librustc_codegen_llvm/llvm/ffi.rs | 1 + src/librustc_codegen_ssa/back/write.rs | 6 + src/librustc_codegen_ssa/mir/block.rs | 6 +- src/librustc_codegen_ssa/traits/builder.rs | 8 + src/librustc_codegen_ssa/traits/intrinsic.rs | 1 + src/librustc_hir/lang_items.rs | 2 + src/librustc_interface/tests.rs | 1 + src/librustc_middle/mir/mono.rs | 1 + src/librustc_middle/ty/instance.rs | 7 + src/librustc_middle/ty/mod.rs | 1 + src/librustc_middle/ty/structural_impls.rs | 11 +- src/librustc_mir/interpret/terminator.rs | 3 + src/librustc_mir/monomorphize/collector.rs | 5 +- src/librustc_mir/monomorphize/partitioning.rs | 2 + src/librustc_mir/shim.rs | 3 + .../transform/instrument_coverage.rs | 100 +++++ src/librustc_mir/transform/mod.rs | 3 + src/librustc_session/options.rs | 3 + src/librustc_span/symbol.rs | 1 + src/librustc_ty/instance.rs | 4 + src/rustllvm/RustWrapper.cpp | 6 + .../codegen/coverage-experiments/Cargo.lock | 5 + .../codegen/coverage-experiments/Cargo.toml | 103 +++++ .../README-THIS-IS-TEMPORARY.md | 157 ++++++++ .../src/coverage_injection_test.rs | 335 ++++++++++++++++ .../src/coverage_injection_test2.rs | 320 ++++++++++++++++ .../src/coverage_injection_test_alt.rs | 362 ++++++++++++++++++ .../coverage-experiments/src/drop_trait.rs | 25 ++ .../src/drop_trait_with_comments_prints.rs | 53 +++ .../codegen/coverage-experiments/src/for.rs | 41 ++ .../src/for_with_comments.rs | 24 ++ .../codegen/coverage-experiments/src/if.rs | 80 ++++ .../src/if_with_comments.rs | 39 ++ .../src/increment_intrinsic.rs | 11 + .../coverage-experiments/src/just_main.rs | 3 + .../coverage-experiments/src/lazy_boolean.rs | 17 + .../src/loop_break_value.rs | 15 + .../codegen/coverage-experiments/src/match.rs | 22 ++ .../src/match_with_increment.rs | 305 +++++++++++++++ .../src/match_with_increment_alt.rs | 296 ++++++++++++++ .../src/match_without_increment.mir | 0 .../src/match_without_increment.rs | 5 + .../src/match_without_increment_alt.mir | 0 ..._mark_err_status_handling_with_comments.rs | 24 ++ .../codegen/coverage-experiments/src/while.rs | 23 ++ .../coverage-experiments/src/while_clean.rs | 6 + .../src/while_early_return.rs | 10 + .../src/while_with_comments.rs | 51 +++ 52 files changed, 2561 insertions(+), 5 deletions(-) create mode 100644 src/librustc_mir/transform/instrument_coverage.rs create mode 100644 src/test/codegen/coverage-experiments/Cargo.lock create mode 100644 src/test/codegen/coverage-experiments/Cargo.toml create mode 100644 src/test/codegen/coverage-experiments/README-THIS-IS-TEMPORARY.md create mode 100644 src/test/codegen/coverage-experiments/src/coverage_injection_test.rs create mode 100644 src/test/codegen/coverage-experiments/src/coverage_injection_test2.rs create mode 100644 src/test/codegen/coverage-experiments/src/coverage_injection_test_alt.rs create mode 100644 src/test/codegen/coverage-experiments/src/drop_trait.rs create mode 100644 src/test/codegen/coverage-experiments/src/drop_trait_with_comments_prints.rs create mode 100644 src/test/codegen/coverage-experiments/src/for.rs create mode 100644 src/test/codegen/coverage-experiments/src/for_with_comments.rs create mode 100644 src/test/codegen/coverage-experiments/src/if.rs create mode 100644 src/test/codegen/coverage-experiments/src/if_with_comments.rs create mode 100644 src/test/codegen/coverage-experiments/src/increment_intrinsic.rs create mode 100644 src/test/codegen/coverage-experiments/src/just_main.rs create mode 100644 src/test/codegen/coverage-experiments/src/lazy_boolean.rs create mode 100644 src/test/codegen/coverage-experiments/src/loop_break_value.rs create mode 100644 src/test/codegen/coverage-experiments/src/match.rs create mode 100644 src/test/codegen/coverage-experiments/src/match_with_increment.rs create mode 100644 src/test/codegen/coverage-experiments/src/match_with_increment_alt.rs create mode 100644 src/test/codegen/coverage-experiments/src/match_without_increment.mir create mode 100644 src/test/codegen/coverage-experiments/src/match_without_increment.rs create mode 100644 src/test/codegen/coverage-experiments/src/match_without_increment_alt.mir create mode 100644 src/test/codegen/coverage-experiments/src/question_mark_err_status_handling_with_comments.rs create mode 100644 src/test/codegen/coverage-experiments/src/while.rs create mode 100644 src/test/codegen/coverage-experiments/src/while_clean.rs create mode 100644 src/test/codegen/coverage-experiments/src/while_early_return.rs create mode 100644 src/test/codegen/coverage-experiments/src/while_with_comments.rs diff --git a/src/libcore/intrinsics.rs b/src/libcore/intrinsics.rs index 85076a573b528..abb35e838ea28 100644 --- a/src/libcore/intrinsics.rs +++ b/src/libcore/intrinsics.rs @@ -1943,6 +1943,15 @@ extern "rust-intrinsic" { pub fn miri_start_panic(payload: *mut u8) -> !; } +#[cfg(not(bootstrap))] +#[cfg_attr(not(bootstrap), lang = "count_code_region")] +pub fn count_code_region(_index: u32) { + #[cfg_attr(not(bootstrap), allow(unused_unsafe))] // remove `unsafe` on bootstrap bump + unsafe { + abort() + } +} + // Some functions are defined here because they accidentally got made // available in this module on stable. See . // (`transmute` also falls into this category, but it cannot be wrapped due to the diff --git a/src/librustc_codegen_llvm/builder.rs b/src/librustc_codegen_llvm/builder.rs index f5ae9824df894..ba285b5ef38d1 100644 --- a/src/librustc_codegen_llvm/builder.rs +++ b/src/librustc_codegen_llvm/builder.rs @@ -997,6 +997,33 @@ impl BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { self.call_lifetime_intrinsic("llvm.lifetime.end.p0i8", ptr, size); } + fn instrprof_increment( + &mut self, + fn_name: &'ll Value, + hash: &'ll Value, + num_counters: &'ll Value, + index: &'ll Value, + ) -> &'ll Value { + debug!( + "instrprof_increment() with args ({:?}, {:?}, {:?}, {:?})", + fn_name, hash, num_counters, index + ); + + let llfn = unsafe { llvm::LLVMRustGetInstrprofIncrementIntrinsic(self.cx().llmod) }; + let args = &[fn_name, hash, num_counters, index]; + let args = self.check_call("call", llfn, args); + + unsafe { + llvm::LLVMRustBuildCall( + self.llbuilder, + llfn, + args.as_ptr() as *const &llvm::Value, + args.len() as c_uint, + None, + ) + } + } + fn call( &mut self, llfn: &'ll Value, diff --git a/src/librustc_codegen_llvm/context.rs b/src/librustc_codegen_llvm/context.rs index 4c810a37d4180..7ff5ac5cbdc10 100644 --- a/src/librustc_codegen_llvm/context.rs +++ b/src/librustc_codegen_llvm/context.rs @@ -749,6 +749,8 @@ impl CodegenCx<'b, 'tcx> { ifn!("llvm.lifetime.start.p0i8", fn(t_i64, i8p) -> void); ifn!("llvm.lifetime.end.p0i8", fn(t_i64, i8p) -> void); + ifn!("llvm.instrprof.increment", fn(i8p, t_i64, t_i32, t_i32) -> void); + ifn!("llvm.expect.i1", fn(i1, i1) -> i1); ifn!("llvm.eh.typeid.for", fn(i8p) -> t_i32); ifn!("llvm.localescape", fn(...) -> void); diff --git a/src/librustc_codegen_llvm/intrinsic.rs b/src/librustc_codegen_llvm/intrinsic.rs index 1e6d2e3dbb74e..7fddda99185b4 100644 --- a/src/librustc_codegen_llvm/intrinsic.rs +++ b/src/librustc_codegen_llvm/intrinsic.rs @@ -7,6 +7,8 @@ use crate::type_of::LayoutLlvmExt; use crate::va_arg::emit_va_arg; use crate::value::Value; +use log::debug; + use rustc_ast::ast; use rustc_codegen_ssa::base::{compare_simd_types, to_immediate, wants_msvc_seh}; use rustc_codegen_ssa::common::span_invalid_monomorphization_error; @@ -21,6 +23,7 @@ use rustc_middle::ty::layout::{FnAbiExt, HasTyCtxt}; use rustc_middle::ty::{self, Ty}; use rustc_middle::{bug, span_bug}; use rustc_span::Span; +use rustc_span::Symbol; use rustc_target::abi::{self, HasDataLayout, LayoutOf, Primitive}; use rustc_target::spec::PanicStrategy; @@ -86,6 +89,7 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> { args: &[OperandRef<'tcx, &'ll Value>], llresult: &'ll Value, span: Span, + caller_instance: ty::Instance<'tcx>, ) { let tcx = self.tcx; let callee_ty = instance.monomorphic_ty(tcx); @@ -136,6 +140,23 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> { let llfn = self.get_intrinsic(&("llvm.debugtrap")); self.call(llfn, &[], None) } + "count_code_region" => { + if let ty::InstanceDef::Item(fn_def_id) = caller_instance.def { + let caller_fn_path = tcx.def_path_str(fn_def_id); + debug!( + "count_code_region to llvm.instrprof.increment(fn_name={})", + caller_fn_path + ); + + let (fn_name, _len_val) = self.const_str(Symbol::intern(&caller_fn_path)); + let index = args[0].immediate(); + let hash = self.const_u64(1234); + let num_counters = self.const_u32(1); + self.instrprof_increment(fn_name, hash, num_counters, index) + } else { + bug!("intrinsic count_code_region: no src.instance"); + } + } "va_start" => self.va_start(args[0].immediate()), "va_end" => self.va_end(args[0].immediate()), "va_copy" => { diff --git a/src/librustc_codegen_llvm/llvm/ffi.rs b/src/librustc_codegen_llvm/llvm/ffi.rs index 54cf99e1c6d6c..372fb17573a4b 100644 --- a/src/librustc_codegen_llvm/llvm/ffi.rs +++ b/src/librustc_codegen_llvm/llvm/ffi.rs @@ -1360,6 +1360,7 @@ extern "C" { // Miscellaneous instructions pub fn LLVMBuildPhi(B: &Builder<'a>, Ty: &'a Type, Name: *const c_char) -> &'a Value; + pub fn LLVMRustGetInstrprofIncrementIntrinsic(M: &Module) -> &'a Value; pub fn LLVMRustBuildCall( B: &Builder<'a>, Fn: &'a Value, diff --git a/src/librustc_codegen_ssa/back/write.rs b/src/librustc_codegen_ssa/back/write.rs index c118e5ebdb72d..49054765b9dae 100644 --- a/src/librustc_codegen_ssa/back/write.rs +++ b/src/librustc_codegen_ssa/back/write.rs @@ -175,6 +175,12 @@ impl ModuleConfig { if sess.opts.debugging_opts.profile && !is_compiler_builtins { passes.push("insert-gcov-profiling".to_owned()); } + + // The rustc option `-Zinstrument_coverage` injects intrinsic calls to + // `llvm.instrprof.increment()`, which requires the LLVM `instrprof` pass. + if sess.opts.debugging_opts.instrument_coverage { + passes.push("instrprof".to_owned()); + } passes }, vec![] diff --git a/src/librustc_codegen_ssa/mir/block.rs b/src/librustc_codegen_ssa/mir/block.rs index ef59ad486eefe..d7db657154993 100644 --- a/src/librustc_codegen_ssa/mir/block.rs +++ b/src/librustc_codegen_ssa/mir/block.rs @@ -566,7 +566,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // Handle intrinsics old codegen wants Expr's for, ourselves. let intrinsic = match def { - Some(ty::InstanceDef::Intrinsic(def_id)) => Some(bx.tcx().item_name(def_id).as_str()), + Some(ty::InstanceDef::Intrinsic(def_id)) + | Some(ty::InstanceDef::InjectedCode(def_id)) => { + Some(bx.tcx().item_name(def_id).as_str()) + } _ => None, }; let intrinsic = intrinsic.as_ref().map(|s| &s[..]); @@ -693,6 +696,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { &args, dest, terminator.source_info.span, + self.instance, ); if let ReturnDest::IndirectOperand(dst, _) = ret_dest { diff --git a/src/librustc_codegen_ssa/traits/builder.rs b/src/librustc_codegen_ssa/traits/builder.rs index caba7ebef593b..7ffc9f15bffdc 100644 --- a/src/librustc_codegen_ssa/traits/builder.rs +++ b/src/librustc_codegen_ssa/traits/builder.rs @@ -260,6 +260,14 @@ pub trait BuilderMethods<'a, 'tcx>: /// Called for `StorageDead` fn lifetime_end(&mut self, ptr: Self::Value, size: Size); + fn instrprof_increment( + &mut self, + fn_name: Self::Value, + hash: Self::Value, + num_counters: Self::Value, + index: Self::Value, + ) -> Self::Value; + fn call( &mut self, llfn: Self::Value, diff --git a/src/librustc_codegen_ssa/traits/intrinsic.rs b/src/librustc_codegen_ssa/traits/intrinsic.rs index 9d48e233de655..f62019498511c 100644 --- a/src/librustc_codegen_ssa/traits/intrinsic.rs +++ b/src/librustc_codegen_ssa/traits/intrinsic.rs @@ -15,6 +15,7 @@ pub trait IntrinsicCallMethods<'tcx>: BackendTypes { args: &[OperandRef<'tcx, Self::Value>], llresult: Self::Value, span: Span, + caller_instance: ty::Instance<'tcx>, ); fn abort(&mut self); diff --git a/src/librustc_hir/lang_items.rs b/src/librustc_hir/lang_items.rs index 83bada4041963..091ded6d74d0f 100644 --- a/src/librustc_hir/lang_items.rs +++ b/src/librustc_hir/lang_items.rs @@ -242,6 +242,8 @@ language_item_table! { StartFnLangItem, "start", start_fn, Target::Fn; + CountCodeRegionFnLangItem, "count_code_region", count_code_region_fn, Target::Fn; + EhPersonalityLangItem, "eh_personality", eh_personality, Target::Fn; EhCatchTypeinfoLangItem, "eh_catch_typeinfo", eh_catch_typeinfo, Target::Static; diff --git a/src/librustc_interface/tests.rs b/src/librustc_interface/tests.rs index 87647f3b0b017..c2a7d1a4a6102 100644 --- a/src/librustc_interface/tests.rs +++ b/src/librustc_interface/tests.rs @@ -548,6 +548,7 @@ fn test_debugging_options_tracking_hash() { tracked!(human_readable_cgu_names, true); tracked!(inline_in_all_cgus, Some(true)); tracked!(insert_sideeffect, true); + tracked!(instrument_coverage, true); tracked!(instrument_mcount, true); tracked!(link_only, true); tracked!(merge_functions, Some(MergeFunctions::Disabled)); diff --git a/src/librustc_middle/mir/mono.rs b/src/librustc_middle/mir/mono.rs index c889dbc0a4498..b2c00849d9f83 100644 --- a/src/librustc_middle/mir/mono.rs +++ b/src/librustc_middle/mir/mono.rs @@ -352,6 +352,7 @@ impl<'tcx> CodegenUnit<'tcx> { InstanceDef::VtableShim(..) | InstanceDef::ReifyShim(..) | InstanceDef::Intrinsic(..) + | InstanceDef::InjectedCode(..) | InstanceDef::FnPtrShim(..) | InstanceDef::Virtual(..) | InstanceDef::ClosureOnceShim { .. } diff --git a/src/librustc_middle/ty/instance.rs b/src/librustc_middle/ty/instance.rs index 1ce079821a22e..4f88e64c5039a 100644 --- a/src/librustc_middle/ty/instance.rs +++ b/src/librustc_middle/ty/instance.rs @@ -21,6 +21,10 @@ pub enum InstanceDef<'tcx> { Item(DefId), Intrinsic(DefId), + /// Injected call to a placeholder function that is replaced with + /// For example: `core::intrinsic::count_code_region()` for code coverage. + InjectedCode(DefId), + /// `::method` where `method` receives unsizeable `self: Self`. VtableShim(DefId), @@ -149,6 +153,7 @@ impl<'tcx> InstanceDef<'tcx> { | InstanceDef::FnPtrShim(def_id, _) | InstanceDef::Virtual(def_id, _) | InstanceDef::Intrinsic(def_id) + | InstanceDef::InjectedCode(def_id) | InstanceDef::ClosureOnceShim { call_once: def_id } | InstanceDef::DropGlue(def_id, _) | InstanceDef::CloneShim(def_id, _) => def_id, @@ -236,6 +241,7 @@ impl<'tcx> fmt::Display for Instance<'tcx> { InstanceDef::VtableShim(_) => write!(f, " - shim(vtable)"), InstanceDef::ReifyShim(_) => write!(f, " - shim(reify)"), InstanceDef::Intrinsic(_) => write!(f, " - intrinsic"), + InstanceDef::InjectedCode(_) => write!(f, " - injected-code"), InstanceDef::Virtual(_, num) => write!(f, " - virtual#{}", num), InstanceDef::FnPtrShim(_, ty) => write!(f, " - shim({:?})", ty), InstanceDef::ClosureOnceShim { .. } => write!(f, " - shim"), @@ -415,6 +421,7 @@ impl<'tcx> Instance<'tcx> { | InstanceDef::FnPtrShim(..) | InstanceDef::Item(_) | InstanceDef::Intrinsic(..) + | InstanceDef::InjectedCode(..) | InstanceDef::ReifyShim(..) | InstanceDef::Virtual(..) | InstanceDef::VtableShim(..) => Some(self.substs), diff --git a/src/librustc_middle/ty/mod.rs b/src/librustc_middle/ty/mod.rs index 93ef73171993c..9b1e717731e82 100644 --- a/src/librustc_middle/ty/mod.rs +++ b/src/librustc_middle/ty/mod.rs @@ -2717,6 +2717,7 @@ impl<'tcx> TyCtxt<'tcx> { ty::InstanceDef::VtableShim(..) | ty::InstanceDef::ReifyShim(..) | ty::InstanceDef::Intrinsic(..) + | ty::InstanceDef::InjectedCode(..) | ty::InstanceDef::FnPtrShim(..) | ty::InstanceDef::Virtual(..) | ty::InstanceDef::ClosureOnceShim { .. } diff --git a/src/librustc_middle/ty/structural_impls.rs b/src/librustc_middle/ty/structural_impls.rs index f6f5dfd651612..b6cbd2082a518 100644 --- a/src/librustc_middle/ty/structural_impls.rs +++ b/src/librustc_middle/ty/structural_impls.rs @@ -674,6 +674,7 @@ impl<'a, 'tcx> Lift<'tcx> for ty::InstanceDef<'a> { ty::InstanceDef::VtableShim(def_id) => Some(ty::InstanceDef::VtableShim(def_id)), ty::InstanceDef::ReifyShim(def_id) => Some(ty::InstanceDef::ReifyShim(def_id)), ty::InstanceDef::Intrinsic(def_id) => Some(ty::InstanceDef::Intrinsic(def_id)), + ty::InstanceDef::InjectedCode(def_id) => Some(ty::InstanceDef::Intrinsic(def_id)), ty::InstanceDef::FnPtrShim(def_id, ref ty) => { Some(ty::InstanceDef::FnPtrShim(def_id, tcx.lift(ty)?)) } @@ -846,6 +847,7 @@ impl<'tcx> TypeFoldable<'tcx> for ty::instance::Instance<'tcx> { VtableShim(did) => VtableShim(did.fold_with(folder)), ReifyShim(did) => ReifyShim(did.fold_with(folder)), Intrinsic(did) => Intrinsic(did.fold_with(folder)), + InjectedCode(did) => InjectedCode(did.fold_with(folder)), FnPtrShim(did, ty) => FnPtrShim(did.fold_with(folder), ty.fold_with(folder)), Virtual(did, i) => Virtual(did.fold_with(folder), i), ClosureOnceShim { call_once } => { @@ -861,9 +863,12 @@ impl<'tcx> TypeFoldable<'tcx> for ty::instance::Instance<'tcx> { use crate::ty::InstanceDef::*; self.substs.visit_with(visitor) || match self.def { - Item(did) | VtableShim(did) | ReifyShim(did) | Intrinsic(did) | Virtual(did, _) => { - did.visit_with(visitor) - } + Item(did) + | VtableShim(did) + | ReifyShim(did) + | Intrinsic(did) + | InjectedCode(did) + | Virtual(did, _) => did.visit_with(visitor), FnPtrShim(did, ty) | CloneShim(did, ty) => { did.visit_with(visitor) || ty.visit_with(visitor) } diff --git a/src/librustc_mir/interpret/terminator.rs b/src/librustc_mir/interpret/terminator.rs index cd7621ea9752b..82fa471b54d73 100644 --- a/src/librustc_mir/interpret/terminator.rs +++ b/src/librustc_mir/interpret/terminator.rs @@ -257,6 +257,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { assert!(caller_abi == Abi::RustIntrinsic || caller_abi == Abi::PlatformIntrinsic); M::call_intrinsic(self, instance, args, ret, unwind) } + ty::InstanceDef::InjectedCode(..) => { + M::call_intrinsic(self, instance, args, ret, unwind) + } ty::InstanceDef::VtableShim(..) | ty::InstanceDef::ReifyShim(..) | ty::InstanceDef::ClosureOnceShim { .. } diff --git a/src/librustc_mir/monomorphize/collector.rs b/src/librustc_mir/monomorphize/collector.rs index 994d1e69f2e3e..24c4226bb4e94 100644 --- a/src/librustc_mir/monomorphize/collector.rs +++ b/src/librustc_mir/monomorphize/collector.rs @@ -714,7 +714,9 @@ fn visit_instance_use<'tcx>( } match instance.def { - ty::InstanceDef::Virtual(..) | ty::InstanceDef::Intrinsic(_) => { + ty::InstanceDef::Virtual(..) + | ty::InstanceDef::Intrinsic(_) + | ty::InstanceDef::InjectedCode(_) => { if !is_direct_call { bug!("{:?} being reified", instance); } @@ -751,6 +753,7 @@ fn should_monomorphize_locally<'tcx>(tcx: TyCtxt<'tcx>, instance: &Instance<'tcx | ty::InstanceDef::FnPtrShim(..) | ty::InstanceDef::DropGlue(..) | ty::InstanceDef::Intrinsic(_) + | ty::InstanceDef::InjectedCode(_) | ty::InstanceDef::CloneShim(..) => return true, }; diff --git a/src/librustc_mir/monomorphize/partitioning.rs b/src/librustc_mir/monomorphize/partitioning.rs index db1ea72c0a531..7c97b9d611e15 100644 --- a/src/librustc_mir/monomorphize/partitioning.rs +++ b/src/librustc_mir/monomorphize/partitioning.rs @@ -322,6 +322,7 @@ fn mono_item_visibility( | InstanceDef::FnPtrShim(..) | InstanceDef::Virtual(..) | InstanceDef::Intrinsic(..) + | InstanceDef::InjectedCode(..) | InstanceDef::ClosureOnceShim { .. } | InstanceDef::DropGlue(..) | InstanceDef::CloneShim(..) => return Visibility::Hidden, @@ -717,6 +718,7 @@ fn characteristic_def_id_of_mono_item<'tcx>( | ty::InstanceDef::FnPtrShim(..) | ty::InstanceDef::ClosureOnceShim { .. } | ty::InstanceDef::Intrinsic(..) + | ty::InstanceDef::InjectedCode(..) | ty::InstanceDef::DropGlue(..) | ty::InstanceDef::Virtual(..) | ty::InstanceDef::CloneShim(..) => return None, diff --git a/src/librustc_mir/shim.rs b/src/librustc_mir/shim.rs index f95fd9b9e90c5..b4477d9c86d43 100644 --- a/src/librustc_mir/shim.rs +++ b/src/librustc_mir/shim.rs @@ -109,6 +109,9 @@ fn make_shim<'tcx>(tcx: TyCtxt<'tcx>, instance: ty::InstanceDef<'tcx>) -> Body<' ty::InstanceDef::Intrinsic(_) => { bug!("creating shims from intrinsics ({:?}) is unsupported", instance) } + ty::InstanceDef::InjectedCode(_) => { + bug!("creating shims from injected code ({:?}) is unsupported", instance) + } }; debug!("make_shim({:?}) = untransformed {:?}", instance, result); diff --git a/src/librustc_mir/transform/instrument_coverage.rs b/src/librustc_mir/transform/instrument_coverage.rs new file mode 100644 index 0000000000000..045cd03d1f7da --- /dev/null +++ b/src/librustc_mir/transform/instrument_coverage.rs @@ -0,0 +1,100 @@ +use crate::transform::{MirPass, MirSource}; +use rustc_index::vec::Idx; +use rustc_middle::mir::interpret::Scalar; +use rustc_middle::mir::*; +use rustc_middle::mir::{Local, LocalDecl}; +use rustc_middle::ty; +use rustc_middle::ty::Ty; +use rustc_middle::ty::TyCtxt; +use rustc_span::def_id::DefId; +use rustc_span::Span; + +pub struct InstrumentCoverage; + +/** + * Inserts call to count_code_region() as a placeholder to be replaced during code generation with + * the intrinsic llvm.instrprof.increment. + */ + +// 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. + +impl<'tcx> MirPass<'tcx> for InstrumentCoverage { + fn run_pass(&self, tcx: TyCtxt<'tcx>, src: MirSource<'tcx>, body: &mut Body<'tcx>) { + if tcx.sess.opts.debugging_opts.instrument_coverage { + if let Some(callee_fn_def_id) = tcx.lang_items().count_code_region_fn() { + debug!("instrumenting {:?}", src.def_id()); + instrument_coverage(tcx, callee_fn_def_id, body); + } + } + } +} + +pub fn instrument_coverage<'tcx>( + tcx: TyCtxt<'tcx>, + callee_fn_def_id: DefId, + body: &mut Body<'tcx>, +) { + let span = body.span.shrink_to_lo(); + + let ret_ty = tcx.fn_sig(callee_fn_def_id).output(); + let ret_ty = ret_ty.no_bound_vars().unwrap(); + let substs = tcx.mk_substs(::std::iter::once(ty::subst::GenericArg::from(ret_ty))); + + let count_code_region_fn: Operand<'_> = + Operand::function_handle(tcx, callee_fn_def_id, substs, span); + + let index = const_int_operand(tcx, span.clone(), tcx.types.u32, 0); + + let args = vec![index]; + + let source_info = SourceInfo { span: span, scope: OUTERMOST_SOURCE_SCOPE }; + + let new_block = START_BLOCK + body.basic_blocks().len(); + + let next_local = body.local_decls.len(); + let new_temp = Local::new(next_local); + let unit_temp = Place::from(new_temp); + + let storage_live = Statement { source_info, kind: StatementKind::StorageLive(new_temp) }; + let storage_dead = Statement { source_info, kind: StatementKind::StorageDead(new_temp) }; + + let count_code_region_call = TerminatorKind::Call { + func: count_code_region_fn, + args, + destination: Some((unit_temp, new_block)), + cleanup: None, + from_hir_call: false, + }; + + body.local_decls.push(LocalDecl::new(tcx.mk_unit(), body.span)); + body.basic_blocks_mut().push(BasicBlockData { + statements: vec![storage_live], + is_cleanup: false, + terminator: Some(Terminator { source_info, kind: count_code_region_call }), + }); + + body.basic_blocks_mut().swap(START_BLOCK, new_block); + body[new_block].statements.push(storage_dead); + + // FIXME(richkadel): ALSO add each computed Span for each conditional branch to the coverage map + // and provide that map to LLVM to encode in the final binary. +} + +fn const_int_operand<'tcx>( + tcx: TyCtxt<'tcx>, + span: Span, + ty: Ty<'tcx>, + val: u128, +) -> Operand<'tcx> { + let param_env_and_ty = ty::ParamEnv::empty().and(ty); + let size = tcx + .layout_of(param_env_and_ty) + .unwrap_or_else(|e| panic!("could not compute layout for {:?}: {:?}", ty, e)) + .size; + Operand::Constant(box Constant { + span, + user_ty: None, + literal: ty::Const::from_scalar(tcx, Scalar::from_uint(val, size), ty), + }) +} diff --git a/src/librustc_mir/transform/mod.rs b/src/librustc_mir/transform/mod.rs index 4240b528a6124..e03ef48f74838 100644 --- a/src/librustc_mir/transform/mod.rs +++ b/src/librustc_mir/transform/mod.rs @@ -28,6 +28,7 @@ pub mod elaborate_drops; pub mod generator; pub mod inline; pub mod instcombine; +pub mod instrument_coverage; pub mod no_landing_pads; pub mod nrvo; pub mod promote_consts; @@ -287,6 +288,8 @@ fn mir_validated( &[&[ // What we need to run borrowck etc. &promote_pass, + // FIXME(richkadel): is this the best place for the InstrumentCoverage pass? + &instrument_coverage::InstrumentCoverage, &simplify::SimplifyCfg::new("qualify-consts"), ]], ); diff --git a/src/librustc_session/options.rs b/src/librustc_session/options.rs index d22c6ec9d7d01..599ce595e1314 100644 --- a/src/librustc_session/options.rs +++ b/src/librustc_session/options.rs @@ -876,6 +876,9 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options, "fix undefined behavior when a thread doesn't eventually make progress \ (such as entering an empty infinite loop) by inserting llvm.sideeffect \ (default: no)"), + instrument_coverage: bool = (false, parse_bool, [TRACKED], + "instrument the generated code with LLVM code region counters for \ + generating coverage reports (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_span/symbol.rs b/src/librustc_span/symbol.rs index fdeb58b7b7a31..623c279734733 100644 --- a/src/librustc_span/symbol.rs +++ b/src/librustc_span/symbol.rs @@ -240,6 +240,7 @@ symbols! { copy_closures, core, core_intrinsics, + count_code_region, crate_id, crate_in_paths, crate_local, diff --git a/src/librustc_ty/instance.rs b/src/librustc_ty/instance.rs index 0acf769168137..d4ceeff324450 100644 --- a/src/librustc_ty/instance.rs +++ b/src/librustc_ty/instance.rs @@ -35,6 +35,10 @@ fn resolve_instance<'tcx>( debug!(" => intrinsic"); ty::InstanceDef::Intrinsic(def_id) } + ty::FnDef(def_id, _) if Some(def_id) == tcx.lang_items().count_code_region_fn() => { + debug!(" => injected placeholder function to be replaced"); + ty::InstanceDef::InjectedCode(def_id) + } ty::FnDef(def_id, substs) if Some(def_id) == tcx.lang_items().drop_in_place_fn() => { let ty = substs.type_at(0); diff --git a/src/rustllvm/RustWrapper.cpp b/src/rustllvm/RustWrapper.cpp index 4704622922af0..cdb3a157eab97 100644 --- a/src/rustllvm/RustWrapper.cpp +++ b/src/rustllvm/RustWrapper.cpp @@ -5,6 +5,7 @@ #include "llvm/IR/DiagnosticPrinter.h" #include "llvm/IR/GlobalVariable.h" #include "llvm/IR/Instructions.h" +#include "llvm/IR/Intrinsics.h" #include "llvm/Object/Archive.h" #include "llvm/Object/ObjectFile.h" #include "llvm/Bitcode/BitcodeWriterPass.h" @@ -1364,6 +1365,11 @@ extern "C" LLVMValueRef LLVMRustBuildCall(LLVMBuilderRef B, LLVMValueRef Fn, unwrap(Fn), makeArrayRef(unwrap(Args), NumArgs), Bundles)); } +extern "C" LLVMValueRef LLVMRustGetInstrprofIncrementIntrinsic(LLVMModuleRef M) { + return wrap(llvm::Intrinsic::getDeclaration(unwrap(M), + (llvm::Intrinsic::ID)llvm::Intrinsic::instrprof_increment)); +} + extern "C" LLVMValueRef LLVMRustBuildMemCpy(LLVMBuilderRef B, LLVMValueRef Dst, unsigned DstAlign, LLVMValueRef Src, unsigned SrcAlign, diff --git a/src/test/codegen/coverage-experiments/Cargo.lock b/src/test/codegen/coverage-experiments/Cargo.lock new file mode 100644 index 0000000000000..132469cbb182c --- /dev/null +++ b/src/test/codegen/coverage-experiments/Cargo.lock @@ -0,0 +1,5 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "coverage_experiments" +version = "0.1.0" diff --git a/src/test/codegen/coverage-experiments/Cargo.toml b/src/test/codegen/coverage-experiments/Cargo.toml new file mode 100644 index 0000000000000..296a8d5c9af2d --- /dev/null +++ b/src/test/codegen/coverage-experiments/Cargo.toml @@ -0,0 +1,103 @@ +[workspace] + +[package] +name = "coverage_experiments" +version = "0.1.0" +license = "BSD-3-Clause" +authors = ["rust-fuchsia@fuchsia.com"] +edition = "2018" + +[[bin]] + +name = "coverage_injection_test" +path = "src/coverage_injection_test.rs" + +[[bin]] + +name = "coverage_injection_test2" +path = "src/coverage_injection_test2.rs" + +[[bin]] + +name = "while" +path = "src/while.rs" + +[[bin]] + +name = "while_clean" +path = "src/while_clean.rs" + +[[bin]] + +name = "while_early_return" +path = "src/while_early_return.rs" + +[[bin]] + +name = "if_with_comments" +path = "src/if_with_comments.rs" + +[[bin]] + +name = "if" +path = "src/if.rs" + +[[bin]] + +name = "increment_intrinsic" +path = "src/increment_intrinsic.rs" + +[[bin]] + +name = "just_main" +path = "src/just_main.rs" + +[[bin]] + +name = "lazy_boolean" +path = "src/lazy_boolean.rs" + +[[bin]] + +name = "match" +path = "src/match.rs" + +[[bin]] + +name = "match_without_increment" +path = "src/match_without_increment.rs" # identical to -Zunpretty=hir output + +[[bin]] + +name = "match_with_increment" +path = "src/match_with_increment.rs" + +[[bin]] + +name = "match_with_increment_alt" +path = "src/match_with_increment_alt.rs" + +[[bin]] + +name = "loop_break_value" +path = "src/loop_break_value.rs" + +[[bin]] + +name = "for_with_comments" +path = "src/for_with_comments.rs" + +[[bin]] + +name = "for" +path = "src/for.rs" + +[[bin]] + +name = "drop_trait" +path = "src/drop_trait.rs" + +#[dependencies] # Should not need to manually add coverage dependencies +#version = "0.1.0" +#path = "../__builtin" # for mod __builtin::coverage + diff --git a/src/test/codegen/coverage-experiments/README-THIS-IS-TEMPORARY.md b/src/test/codegen/coverage-experiments/README-THIS-IS-TEMPORARY.md new file mode 100644 index 0000000000000..3b69c0a406594 --- /dev/null +++ b/src/test/codegen/coverage-experiments/README-THIS-IS-TEMPORARY.md @@ -0,0 +1,157 @@ +# codegen/coverage-experiments +*

THIS DIRECTORY IS TEMPORARY

* + +This directory contains some work-in-progress (WIP) code used for experimental development and +testing of Rust Coverage feature development. + +The code in this directory will be removed, or migrated into product tests, when the Rust +Coverage feature is complete. + +[TOC] + +## Development Notes + +### config.toml + +config.toml probably requires (I should verify that intrinsic `llvm.instrprof.increment` +code generation ONLY works with this config option): + + profiler = true + +## First build + +```shell +./x.py clean +./x.py build -i --stage 1 src/libstd +``` + +## Incremental builds *IF POSSIBLE!* + +```shell +./x.py build -i --stage 1 src/libstd --keep-stage 1 +``` + +*Note: Some changes made for Rust Coverage required the full build (without `--keep-stage 1`), and in some cases, required `./x.py clean` first!. Occassionally I would get errors when building or when compiling a test program with `--Zinstrument-coverage` that work correctly only after a full clean and build.* + +## Compile a test program with LLVM coverage instrumentation + +*Note: This PR is still a work in progress. At the time of this writing, the `llvm.instrprof.increment` intrinsic is injected, and recognized by the LLVM code generation stage, but it does not appear to be included in the final binary. This is not surprising since other steps are still to be implemented, such as generating the coverage map. See the suggested additional `llvm` flags for ways to verify the `llvm` passes at least get the right intrinsic.* + +Suggested debug configuration to confirm Rust coverage features: +```shell +$ export RUSTC_LOG=rustc_codegen_llvm::intrinsic,rustc_mir::transform::instrument_coverage=debug +``` + +Ensure the new compiled `rustc` is used (the path below, relative to the `rust` code repository root, is an example only): + +```shell +$ build/x86_64-unknown-linux-gnu/stage1/bin/rustc \ + src/test/codegen/coverage-experiments/just_main.rs \ + -Zinstrument-coverage +``` + +### About the test programs in coverage-experiments/src/ + +The `coverage-experiments/src/` directory contains some sample (and very simple) Rust programs used to analyze Rust compiler output at various stages, with or without the Rust code coverage compiler option. For now, these are only used for the in-progress development and will be removed at a future date. (These are *not* formal test programs.) + +The src director may also contain some snapshots of mir output from experimentation, particularly if the saved snapshots highlight results that are important to the future development, individually or when compared with other output files. + +Be aware that some of the files and/or comments may be outdated. + +### Additional `llvm` flags (append to the `rustc` command) + +These optional flags generate additional files and/or terminal output. LLVM's `-print-before=all` should show the `instrprof.increment` intrinsic with arguments computed by the experimental Rust coverage feature code: + +```shell + --emit llvm-ir \ + -Zverify-llvm-ir \ + -Zprint-llvm-passes \ + -Csave-temps \ + -Cllvm-args=-print-before-all +``` + +### Additional flags for MIR analysis and transforms + +These optional flags generate a directory with many files representing the MIR as text (`.mir` files) and as a visual graph (`.dot` files) rendered by `graphviz`. (**Some IDEs, such as `VSCode` have `graphviz` extensions.**) + +```shell + -Zdump-mir=main \ + -Zdump-mir-graphviz +``` + +### Flags I've used but appear to be irrelvant to `-Zinstrument-coverage` after all: +```shell + # -Zprofile + # -Ccodegen-units=1 + # -Cinline-threshold=0 + # -Clink-dead-code + # -Coverflow-checks=off +``` + +## Run the test program compiled with code coverage instrumentation (maybe): + +As stated above, at the time of this writing, this work-in-progress seems to generate `llvm.instrprof.increment` intrinsic calls correctly, and are visibile in early `llvm` code generation passes, but are eventually stripped. + +The test program should run as expected, currently does not generate any coverage output. + +*Example:* + +```shell + $ src/test/codegen/coverage-experiments/just_main + hello world! (should be covered) +``` + +### Running the coverage-enabled `rustc` compiler in the `lldb` debugger: + +For example, to verify the intrinsic is codegen'ed, set breakpoint in `lldb` where it validates a certain instruction is the `llvm.instrprof.increment` instruction. + +First, update config.toml for debugging: + +```toml + [llvm] + optimize = false + release-debuginfo = true + + [rust] + debug = true + debuginfo-level = 2 +``` + +*(Note, in case this is relevant after all, I also have the following changes; but I don't think I need them:)* + +```toml + # Add and uncomment these if relevant/useful: + # codegen-units = 0 + # python = '/usr/bin/python3.6' +``` + +Run the compiler with additional flags as needed: + +```shell +lldb \ + build/x86_64-unknown-linux-gnu/stage1/bin/rustc \ + -- \ + src/test/codegen/coverage-experiments/just_main.rs \ + -Zinstrument-coverage \ + -Zdump-mir=main \ + -Zdump-mir-graphviz +``` + +Note the specific line numbers may be different: + +```c++ +(lldb) b lib/Transforms/Instrumentation/InstrProfiling.cpp:418 +(lldb) r + +Process 93855 stopped +* thread #6, name = 'rustc', stop reason = breakpoint 2.1 + frame #0: 0x00007fffedff7738 librustc_driver-5a0990d8d18fb2b4.so`llvm::InstrProfiling::lowerIntrinsics(this=0x00007fffcc001d40, F=0x00007fffe4552198) at InstrProfiling.cpp:418:23 + 415 auto Instr = I++; + 416 InstrProfIncrementInst *Inc = castToIncrementInst(&*Instr); + 417 if (Inc) { +-> 418 lowerIncrement(Inc); + 419 MadeChange = true; + 420 } else if (auto *Ind = dyn_cast(Instr)) { + 421 lowerValueProfileInst(Ind); +(lldb) +``` \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/coverage_injection_test.rs b/src/test/codegen/coverage-experiments/src/coverage_injection_test.rs new file mode 100644 index 0000000000000..231da1dc1a67f --- /dev/null +++ b/src/test/codegen/coverage-experiments/src/coverage_injection_test.rs @@ -0,0 +1,335 @@ +/* */ use std::io::Error; +/* */ use std::io::ErrorKind; +/* */ +/* */ /// Align Rust counter increment with with: +/* */ /// [‘llvm.instrprof.increment’ Intrinsic](https://llvm.org/docs/LangRef.html#llvm-instrprof-increment-intrinsic) +/* */ /// +/* */ /// declare void @llvm.instrprof.increment(i8* , i64 , i32 , i32 ) +/* */ /// +/* */ /// The first argument is a pointer to a global variable containing the name of the entity +/* */ /// being instrumented. This should generally be the (mangled) function name for a set of +/* */ /// counters. +/* */ /// +/* */ /// The second argument is a hash value that can be used by the consumer of the profile data +/* */ /// to detect changes to the instrumented source, and the third is the number of counters +/* */ /// associated with name. It is an error if hash or num-counters differ between two +/* */ /// instances of instrprof.increment that refer to the same name. +/* */ /// +/* */ /// The last argument refers to which of the counters for name should be incremented. It +/* */ /// should be a value between 0 and num-counters. +/* */ /// +/* */ /// # Arguments +/* */ /// +/* */ /// `mangled_fn_name` - &'static ref to computed and injected static str, using: +/* */ /// +/* */ /// ``` +/* */ /// fn rustc_symbol_mangling::compute_symbol_name( +/* */ /// tcx: TyCtxt<'tcx>, +/* */ /// instance: Instance<'tcx>, +/* */ /// compute_instantiating_crate: impl FnOnce() -> CrateNum, +/* */ /// ) -> String +/* */ /// ``` +/* */ /// +/* */ /// `source_version_hash` - Compute hash based that only changes if there are "significant" +/* */ /// to control-flow inside the function. +/* */ /// +/* */ /// `num_counters` - The total number of counter calls [MAX(counter_index) + 1] within the +/* */ /// function. +/* */ /// +/* */ /// `counter_index` - zero-based counter index scoped by the function. (Ordering of +/* */ /// counters, relative to the source code location, is apparently not expected.) +/* */ /// +/* */ /// # Notes +/* */ /// +/* */ /// * The mangled_fn_name may not be computable until generics are monomorphized (see +/* */ /// parameters required by rustc_symbol_mangling::compute_symbol_name). +/* */ /// * The version hash may be computable from AST analysis, and may not benefit from further +/* */ /// lowering. +/* */ /// * num_counters depends on having already identified all counter insertion locations. +/* */ /// * counter_index can be computed at time of counter insertion (incrementally). +/* */ /// * Numeric parameters are signed to match the llvm increment intrinsic parameter types. +/* */ fn __lower_incr_cov(_mangled_fn_name: &'static str, _fn_version_hash: i64, _num_counters: i32, _counter_index: i32) { +/* */ } +/* */ +/* */ /// A coverage counter implementation that will work as both an intermediate coverage +/* */ /// counting and reporting implementation at the AST-level only--for debugging and +/* */ /// development--but also serves as a "marker" to be replaced by calls to LLVM +/* */ /// intrinsic coverage counter APIs during the lowering process. +/* */ /// +/* */ /// Calls to this function will be injected automatically into the AST. When LLVM intrinsics +/* */ /// are enabled, the counter function calls that were injected into the AST serve as +/* */ /// placeholders, to be replaced by an alternative, such as: +/* */ /// +/* */ /// * direct invocation of the `llvm.instrprof.increment()` intrinsic; or +/* */ /// * the `__lower_incr_cov()` function, defined above, that would invoke the +/* */ /// `llvm.instrprof.increment()` intrinsic; or +/* */ /// * a similar expression wrapper, with the additional parameters (as defined above +/* */ /// for `__lower_incr_cov()`, that invokes `llvm.instrprof.increment()` and returns the +/* */ /// result of the wrapped expression) +/* */ /// +/* */ /// The first two options would require replacing the inlined wrapper call with something +/* */ /// like: +/* */ /// +/* */ /// ``` +/* */ /// { let result = {expr}; __inlined_incr_cov(context, counter); result } +/* */ /// ``` +/* */ /// +/* */ /// But if the lowering process is already unwrapping the inlined call to `__incr_cov()`, then +/* */ /// it may be a perfect opportunity to replace the function with one of these more +/* */ /// direct methods. +/* */ /// +/* */ #[inline(always)] +/* */ pub fn __incr_cov(region_loc: &str, /*index: u32,*/ result: T) -> T { +/* */ // Either call the intermediate non-llvm coverage counter API or +/* */ // replace the call to this function with the expanded `__lower_incr_cov()` call. +/* */ +/* */ // let _lock = increment_counter(counter); +/* */ println!("{}", region_loc); +/* */ +/* */ result +/* */ } +/* */ +/* */ /// Write a report identifying each incremented counter and the number of times each counter +/* */ /// was incremented. +/* */ fn __report() { +/* */ println!("WRITE REPORT!"); +/* */ } +/* */ +/* */ /// Increment the counter after evaluating the wrapped expression (see `__incr_cov()`), then +/* */ /// write a report identifying each incremented counter and the number of times each counter +/* */ /// was incremented. +/* */ #[inline(always)] +/* */ pub fn __incr_cov_and_report(region_loc: &str, /*counter: u32,*/ result: T) -> T { +/* */ __incr_cov(region_loc, /*counter,*/ ()); +/* */ __report(); +/* */ result +/* */ } +/* */ +/* */ macro_rules! from { +/* */ ($from:expr) => { &format!("from: {}\n to: {}:{}:{}", $from, file!(), line!(), column!()) }; +/* */ } +/* */ +/* */ #[derive(Debug)] +/* */ enum TestEnum { +/* */ Red, +/* */ Green, +/* */ Blue, +/* */ } +/* */ +/* */ struct TestStruct { +/* */ field: i32, +/* */ } +/* */ +/* */ // IMPORTANT! IS WRAPPING main() ENOUGH? OR DO I ALSO NEED TO WRAP THREAD FUNCTIONS, ASSUMING +/* */ // THEY ARE STILL RUNNING WITH MAIN EXITS? (IF THEY CAN). NOT SURE HOW RUST HANDLES THAT. +/* */ +/* */ // I SUSPECT USING THREAD_LOCAL COUNTERS MAY NOT ACTUALLY BE AN OPTIMIZATION OVER MUTEX LOCKS, +/* */ // BUT MAYBE I SHOULD ASK. +/* */ +/* */ impl TestStruct { +/* - */ fn new() -> Self { +/* ┃ */ __incr_cov(from!("fn new()"),Self::new_with_value(31415)) // function-scoped counter index = 0 +/* - */ } +/* */ +/* - */ fn new_with_value(field: i32) -> Self { +/* ┃ */ __incr_cov(from!("fn new_with_value()"),Self { +/* ┃ */ field, +/* ┃ */ }) // function-scoped counter index = 0 +/* - */ } +/* */ +/* */ fn call_closure(&self, closure: F) -> bool +/* */ where +/* */ F: FnOnce( +/* */ i32, +/* */ ) -> bool, +/* - */ { +/* ┃ */ __incr_cov(from!("fn call_closure()"),closure(123)) // function-scoped counter index = 0 +/* - */ } +/* */ +/* - */ fn various(&self) -> Result<(),Error> { +/* ┃ */ use TestEnum::*; +/* ┃ */ let mut color = Red; +/* ┃ */ let _ = color; +/* ┃ */ color = Blue; +/* ┃ */ let _ = color; +/* ┃ */ color = Green; +/* ┃ */ match __incr_cov(from!("fn various"),color) { // function-scoped counter index = 0 +/* : */ +/* : */ // !!! RECORD SPAN FROM START OF INNERMOST CONTAINING BLOCK (THE FUNCTION IN THIS CASE) TO END OF MATCH EXPRESSION +/* : */ // If `match`, `while`, `loop`, `for`, `if`, etc. expression has a `return`, `break`, or `continue` +/* : */ // (if legal), then RECORD SPAN FROM START OF INNERMOST CONTAINING BLOCK TO END OF `return` EXPRESSION +/* : */ // If the expression includes lazy booleans, nest calls to `__incr_cov()`. +/* : I */ Red => __incr_cov(from!("Red => or end of MatchArmGuard expression inside pattern, if any"),println!("roses")), +/* : - */ Green => { +/* : ┃ */ let spidey = 100; +/* : ┃ */ let goblin = 50; +/* : ┃ */ // if spidey > goblin {__incr_cov(from!(""),{ +/* : ┃ */ // println!("what ev"); +/* : ┃ */ // })} +/* : ┃ */ // ACTUALLY, WRAPPING THE ENTIRE IF BLOCK IN `__incr_cov` IS NOT A GREAT GENERAL RULE. +/* : ┃ */ // JUST INSERTING A `return`, `break`, or `continue` IN THAT BLOCK (without an intermediate condition) +/* : ┃ */ // MAKES THE `__incr_cov()` CALL UNREACHABLE! +/* : ┃ */ // MY ORIGINAL SOLUTION WORKS BETTER (WRAP LAST EXPRESSION OR AFTER LAST SEMICOLON STATEMENT IN BLOCK) +/* : ┃ */ // UNLESS THE EXPRESSION IS NOT A BLOCK. +/* : ┃ - */ if __incr_cov(from!("Green => or end of MatchArmGuard expression inside pattern, if any"),spidey > goblin) { +/* : : ┃ */ println!("spidey beats goblin"); +/* : : ┃ */ __incr_cov(from!("block start"),()); +/* : ┃ - */ } else if __incr_cov(from!("`else if` on this line"),spidey == goblin) { +/* : : ┃ */ // COVERAGE NOTE: Do we mark only the expression span (that may be trivial, as in this case), +/* : : ┃ */ // or associate it with the outer block, similar to how the `if` expression is associated with +/* : : ┃ */ // the outer block? (Although it is a continuation, in a sense, it is discontiguous in this case, +/* : : ┃ */ // so I think simpler to just make it its own coverage region.) +/* : : ┃ */ println!("it's a draw"); +/* : : ┃ */ __incr_cov(from!("block start"),()); +/* : ┃ - - - */ } else if if __incr_cov(from!("`else if` on this line"),true) { +/* : : : ┃ */ // return __incr_cov(from!("after `if true`"),Ok(())); +/* : : : ┃ */ // ACTUALLY, BECAUSE OF `return`, WE DO NOT RECORD THE `if true` EVEN THOUGH WE COVERED IT. +/* : : : ┃ */ // IN FACT, IF THIS NESTED CONDITIONAL IN A CONDITIONAL EXPRESSION WAS AN `if` (WITHOUT PRECEDING ELSE) +/* : : : ┃ */ // WE WOULD NOT HAVE RECORDED THE COVERAGE OF STATEMENTS LEADING UP TO THE `if`, SO +/* : : : ┃ */ // IT SHOULD BE: +/* ┏-:---:-------:---< */ return __incr_cov(from!(""),Ok(())); +/* V : : : : */ // NOTE THE `from` STRING IS SAME FOR THE `else if`s `__incr_cov` AND THIS `return`. +/* : : : : */ // ONLY ONE OF THESE WILL EXECUTE, TO RECORD COVERAGE FROM THAT SPOT. +/* : : ┃ - */ } else { +/* : : : I */ __incr_cov(from!("`else`"),false) +/* : : - - */ } { +/* : : ┃ */ println!("wierd science"); +/* : : ┃ */ __incr_cov(from!("block start"),()); +/* : ┃ - */ } else { +/* : : ┃ */ println!("goblin wins"); +/* ┏-:---:---< */ return __incr_cov(from!("`else`"),Ok(())); // THIS COUNTS LAST STATEMENT IN `else` BLOCK +/* V : : : */ // COVERAGE NOTE: When counting the span for `return`, +/* : : : */ // `break`, or `continue`, also report the outer spans +/* : : : */ // got this far--including this `else` block. Record +/* : : : */ // The start positions for those outer blocks, but: +/* : : : */ // * For the block containing the `return`, `break`, or +/* : : : */ // `continue`, end report the end position is the +/* : : : */ // start of the `return` span (or 1 char before it). +/* : : : */ // * Anything else? +/* : ┃ - */ } +/* : ┃ - */ // __incr_cov(from!(""),()); // DO NOT COUNT HERE IF NO STATEMENTS AFTER LAST `if` or `match` +/* : - */ }, +/* : I */ Blue => __incr_cov(from!("Blue => or end of MatchArmGuard expression inside pattern, if any"),println!("violets")), +/* ┃ */ } +/* ┃ */ +/* ┃ */ let condition1 = true; +/* ┃ */ let condition2 = false; +/* ┃ */ let condition3 = true; +/* ┃ */ +/* ┃ */ println!("Called `various()` for TestStruct with field={}", self.field); +/* ┃ */ +/* ┃ - */ if __incr_cov(from!("after block end of prior `match` (or `if-else if-else`)"),condition1) { +/* : ┃ */ println!("before while loop"); +/* : ┃ */ let mut countdown = 10; +/* : ┃ */ __incr_cov(from!("block start"),()); // Must increment before repeated while text expression +/* : : I */ while __incr_cov(from!("while test"), countdown > 0) { // span is just the while test expression +/* : : ┃ */ println!("top of `while` loop"); +/* : : ┃ */ countdown -= 1; +/* : : ┃ */ // __incr_cov(from!("while loop"),()); // Counter not needed, but span is computed as "while test" minus "block start" +/* : : ┃ */ // If test expression is 11, and the outer block runs only once, 11-1 = 10 +/* : ┃ - */ } +/* : ┃ */ println!("before for loop"); +/* : ┃ - */ for index in __incr_cov(from!("end of while"),0..10) { +/* : : ┃ */ println!("top of `for` loop"); +/* : : ┃ - */ if __incr_cov(from!("block start"),index == 8) { +/* : : : ┃ */ println!("before break"); +/* : : : ┃ */ // note the following is not legal here: +/* : : : ┃ */ // "can only break with a value inside `loop` or breakable block" +/* : : : ┃ */ // break __incr_cov(from!(""),()); +/* : : : ┃ */ __incr_cov(from!("block start"),()); +/* : : ┏-----< */ break; +/* : : V : : */ +/* : : : : */ // FIXME(richkadel): add examples with loop labels, breaking out of inner and outer loop to outer loop label, with expression. +/* : : : : */ // May want to record both the span and the start position after the broken out block depdnding on label +/* : : ┃ - */ } +/* : : ┃ */ println!("after `break` test"); +/* : : ┃ - */ if __incr_cov(from!("block end of `if index == 8`"),condition2) { +/* ┏-:---:---:---< */ return __incr_cov(from!("block start"),Ok(())); +/* V : : ┃ - */ } +/* : : ┃ */ +/* : : ┃ */ // BECAUSE THE PREVIOUS COVERAGE REGION HAS A `return`, THEN +/* : : ┃ */ // IF PREVIOUS COVERAGE REGION IS NOT COUNTED THEN OUTER REGION REACHED HERE. +/* : : ┃ */ // ADD A COVERAGE REGION FOR THE SPAN FROM JUST AFTER PREVIOUS REGION TO END +/* : : ┃ */ // OF OUTER SPAN, THEN TRUNCATE TO NEXT REGION NOT REACHED. +/* : : ┃ - */ if index % 3 == 2 { // NO __incr_cov() HERE BECAUSE NO STATEMENTS BETWEEN LAST CONDITIONAL BLOCK AND START OF THIS ONE +/* : : Λ : ┃ */ __incr_cov(from!("block end of `if condition2`"),()); +/* : : ┗-----< */ continue; +/* : : ┃ - */ } +/* : : ┃ */ println!("after `continue` test"); +/* : : ┃ */ // maybe add a runtime flag for a possible `return` here? +/* : : ┃ */ __incr_cov(from!("for loop"),()); +/* : ┃ - */ } +/* : ┃ */ println!("after for loop"); +/* : ┃ */ let result = if { // START OF NEW CONDITIONAL EXPRESSION. NEXT "GUARANTEED" COUNTER SHOULD COUNT FROM END OF LAST CONDITIONAL EXPRESSION +/* : ┃ */ // A "GUARANTEED" COUNTER CALL IS ONE THAT WILL BE CALLED REGARDLESS OF OTHER CONDITIONS. THIS INCLUDES: +/* : ┃ */ // * A CONDITIONAL EXPRESSION THAT IS NOT A BLOCK (OR ANOTHER CONDITIONAL STATEMENT, WHICH WOULD CONTAIN A BLOCK) +/* : ┃ */ // * OR IF THE NEXT CONDITIONAL EXPRESSION IS A BLOCK OR CONDITIONAL STATEMENT, THEN THE FIRST "GUARANTEED" COUNTER IN THAT BLOCK +/* : ┃ */ // * END OF BLOCK IF THE BLOCK DOES NOT HAVE INNER CONDITIONAL EXPRESSIONS +/* : ┃ */ // * BRANCHING STATEMENTS (`return`, `break`, `continue`) BY EITHER WRAPPING THE BRANCH STATEMENT NON-BLOCK EXPRESSION, +/* : ┃ */ // OR PREPENDING A COUNTER WITH EMPTY TUPLE IF NO EXPRESSION, OR IF EXPRESSION IS A BLOCK, THEN THE NEXT "GUARANTEED" +/* : ┃ */ // COUNTER CALL WITHIN THAT BLOCK. +/* : ┃ */ // BASICALLY, CARRY THE START OF COVERAGE SPAN FORWARD UNTIL THE GUARANTEED COUNTER IS FOUND +/* : ┃ */ println!("after result = if ..."); +/* : ┃ - */ if __incr_cov(from!("block end of `for` loop"),condition2) { +/* : : ┃ */ println!("before first return"); +/* ┏-:---:-------< */ return __incr_cov(from!("block start"),Ok(())); +/* V : : - */ } else if __incr_cov(from!("`else`"),condition3) { +/* : : ┃ */ // THE ABOVE COUNTER IS _NOT_ REALLY NECESSARY IF EXPRESSION IS GUARANTEED TO EXECUTE. +/* : : ┃ */ // IF WE GET COUNTER IN `else if` BLOCK WE COVERED EXPRESSION. +/* : : ┃ */ // IF WE GET TO ANY REMAINING `else` or `else if` BLOCK WE KNOW WE EVALUATED THIS CONDITION +/* : : ┃ */ // AND ALL OTHERS UP TO THE EXECUTED BLOCK. BUT THE SPAN WOULD HAVE "HOLES" FOR UNEXECUTED BLOCKS. +/* : : ┃ */ println!("not second return"); +/* ┏-:---:-------< */ return __incr_cov(from!("block start"),Ok(())); +/* V : : - */ } else { +/* : : ┃ */ println!("not returning"); +/* : : ┃ */ __incr_cov(from!("block start"),false) +/* : : - */ } +/* : ┃ */ // NO COUNTER HERE BECAUSE NO STATEMENTS AFTER CONDITIONAL BLOCK +/* : ┃ - */ } { +/* : : ┃ */ println!("branched condition returned true"); +/* : : ┃ */ __incr_cov(from!(""),Ok(())) +/* : ┃ - */ } else if self.call_closure( +/* : : - */ |closure_param| __incr_cov(from!(""), +/* : : ┃ - */ if condition3 { +/* : : : ┃ */ println!("in closure, captured condition said to print the param {}", closure_param); +/* : : : ┃ */ __incr_cov(from!(""),false) +/* : : ┃ - */ } else { +/* : : : ┃ */ println!("in closure, captured condition was false"); +/* : : : ┃ */ __incr_cov(from!(""),true) +/* : : ┃ - */ } +/* : : - */ ) +/* : : - */ ) { +/* : : ┃ */ println!("closure returned true"); +/* : : ┃ */ __incr_cov(from!(""),Err(Error::new(ErrorKind::Other, "Result is error if closure returned true"))) +/* : ┃ - */ } else { +/* : : ┃ */ println!("closure returned false"); +/* : : ┃ */ __incr_cov(from!(""),Err(Error::new(ErrorKind::Other, "Result is error if closure returned false"))) +/* : ┃ - */ }; +/* : ┃ */ println!("bottom of function might be skipped if early `return`"); +/* : ┃ */ __incr_cov(from!("if condition1"),result) +/* ┃ - */ } else { +/* : ┃ */ println!("skipping everything in `various()`"); +/* : ┃ */ __incr_cov(from!(""),Ok(())) +/* ┃ - */ } +/* ┃ - */ // __incr_cov(from!(""),0) // DO NOT COUNT IF NO STATEMENTS AFTER CONDITIONAL BLOCK. ALL COVERAGE IS ALREADY COUNTED +/* - */ } +/* */ } +/* */ +/* - */ fn main() -> Result<(), std::io::Error> { +/* ┃ */ //let mut status: u8 = 2; +/* ┃ */ let mut status: u8 = 1; +/* : - */ let result = if status < 2 && +/* : ┃ */ __incr_cov(from!(""),{ +/* : ┃ */ status -= 1; +/* : ┃ */ status == 0 +/* : - - */ }) { +/* : ┃ */ let test_struct = TestStruct::new_with_value(100); +/* : ┃ */ let _ = test_struct.various(); +/* ┏-:---< */ return __incr_cov_and_report(from!(""),Err(Error::new(ErrorKind::Other, format!("Error status {}", status)))) +/* V : - */ } else { +/* : ┃ */ let test_struct = TestStruct::new(); +/* : ┃ */ __incr_cov(from!(""),test_struct.various()) +/* : - */ }; +/* ┃ */ println!("done"); +/* ┃ */ __incr_cov_and_report(from!(""),result) // function-scoped counter index = 0 +/* - */ } \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/coverage_injection_test2.rs b/src/test/codegen/coverage-experiments/src/coverage_injection_test2.rs new file mode 100644 index 0000000000000..8f4399ab51d09 --- /dev/null +++ b/src/test/codegen/coverage-experiments/src/coverage_injection_test2.rs @@ -0,0 +1,320 @@ +/* */ use std::io::Error; +/* */ use std::io::ErrorKind; +/* */ +/* */ /// Align Rust counter increment with with: +/* */ /// [‘llvm.instrprof.increment’ Intrinsic](https://llvm.org/docs/LangRef.html#llvm-instrprof-increment-intrinsic) +/* */ /// +/* */ /// declare void @llvm.instrprof.increment(i8* , i64 , i32 , i32 ) +/* */ /// +/* */ /// The first argument is a pointer to a global variable containing the name of the entity +/* */ /// being instrumented. This should generally be the (mangled) function name for a set of +/* */ /// counters. +/* */ /// +/* */ /// The second argument is a hash value that can be used by the consumer of the profile data +/* */ /// to detect changes to the instrumented source, and the third is the number of counters +/* */ /// associated with name. It is an error if hash or num-counters differ between two +/* */ /// instances of instrprof.increment that refer to the same name. +/* */ /// +/* */ /// The last argument refers to which of the counters for name should be incremented. It +/* */ /// should be a value between 0 and num-counters. +/* */ /// +/* */ /// # Arguments +/* */ /// +/* */ /// `mangled_fn_name` - &'static ref to computed and injected static str, using: +/* */ /// +/* */ /// ``` +/* */ /// fn rustc_symbol_mangling::compute_symbol_name( +/* */ /// tcx: TyCtxt<'tcx>, +/* */ /// instance: Instance<'tcx>, +/* */ /// compute_instantiating_crate: impl FnOnce() -> CrateNum, +/* */ /// ) -> String +/* */ /// ``` +/* */ /// +/* */ /// `source_version_hash` - Compute hash based that only changes if there are "significant" +/* */ /// to control-flow inside the function. +/* */ /// +/* */ /// `num_counters` - The total number of counter calls [MAX(counter_index) + 1] within the +/* */ /// function. +/* */ /// +/* */ /// `counter_index` - zero-based counter index scoped by the function. (Ordering of +/* */ /// counters, relative to the source code location, is apparently not expected.) +/* */ /// +/* */ /// # Notes +/* */ /// +/* */ /// * The mangled_fn_name may not be computable until generics are monomorphized (see +/* */ /// parameters required by rustc_symbol_mangling::compute_symbol_name). +/* */ /// * The version hash may be computable from AST analysis, and may not benefit from further +/* */ /// lowering. +/* */ /// * num_counters depends on having already identified all counter insertion locations. +/* */ /// * counter_index can be computed at time of counter insertion (incrementally). +/* */ /// * Numeric parameters are signed to match the llvm increment intrinsic parameter types. +/* */ fn __lower_incr_cov(_mangled_fn_name: &'static str, _fn_version_hash: i64, _num_counters: i32, _counter_index: i32) { +/* */ } +/* */ +/* */ /// A coverage counter implementation that will work as both an intermediate coverage +/* */ /// counting and reporting implementation at the AST-level only--for debugging and +/* */ /// development--but also serves as a "marker" to be replaced by calls to LLVM +/* */ /// intrinsic coverage counter APIs during the lowering process. +/* */ /// +/* */ /// Calls to this function will be injected automatically into the AST. When LLVM intrinsics +/* */ /// are enabled, the counter function calls that were injected into the AST serve as +/* */ /// placeholders, to be replaced by an alternative, such as: +/* */ /// +/* */ /// * direct invocation of the `llvm.instrprof.increment()` intrinsic; or +/* */ /// * the `__lower_incr_cov()` function, defined above, that would invoke the +/* */ /// `llvm.instrprof.increment()` intrinsic; or +/* */ /// * a similar expression wrapper, with the additional parameters (as defined above +/* */ /// for `__lower_incr_cov()`, that invokes `llvm.instrprof.increment()` and returns the +/* */ /// result of the wrapped expression) +/* */ /// +/* */ /// The first two options would require replacing the inlined wrapper call with something +/* */ /// like: +/* */ /// +/* */ /// ``` +/* */ /// { let result = {expr}; __inlined_incr_cov(context, counter); result } +/* */ /// ``` +/* */ /// +/* */ /// But if the lowering process is already unwrapping the inlined call to `__incr_cov()`, then +/* */ /// it may be a perfect opportunity to replace the function with one of these more +/* */ /// direct methods. +/* */ /// +/* */ #[inline(always)] +/* */ pub fn __incr_cov(region_loc: &str) { +/* */ // Either call the intermediate non-llvm coverage counter API or +/* */ // replace the call to this function with the expanded `__lower_incr_cov()` call. +/* */ +/* */ // let _lock = increment_counter(counter); +/* */ println!("{}", region_loc); +/* */ } +/* */ +/* */ /// Write a report identifying each incremented counter and the number of times each counter +/* */ /// was incremented. +/* */ fn __report() { +/* */ println!("WRITE REPORT!"); +/* */ } +/* */ +/* */ macro_rules! from { +/* */ ($from:expr) => { &format!("from: {}\n to: {}:{}:{}", $from, file!(), line!(), column!()) }; +/* */ } +/* */ +/* */ #[derive(Debug)] +/* */ enum TestEnum { +/* */ Red, +/* */ Green, +/* */ Blue, +/* */ } +/* */ +/* */ struct TestStruct { +/* */ field: i32, +/* */ } +/* */ +/* */ // IMPORTANT! IS WRAPPING main() ENOUGH? OR DO I ALSO NEED TO WRAP THREAD FUNCTIONS, ASSUMING +/* */ // THEY ARE STILL RUNNING WITH MAIN EXITS? (IF THEY CAN). NOT SURE HOW RUST HANDLES THAT. +/* */ +/* */ // I SUSPECT USING THREAD_LOCAL COUNTERS MAY NOT ACTUALLY BE AN OPTIMIZATION OVER MUTEX LOCKS, +/* */ // BUT MAYBE I SHOULD ASK. +/* */ +/* */ impl TestStruct { +/* - */ fn new() -> Self { +/* ┃ */ let __result = Self::new_with_value(31415); // function-scoped counter index = 0 +/* ┃ */ __incr_cov(from!("fn new()")); +/* ┃ */ __result +/* - */ } +/* */ +/* - */ fn new_with_value(field: i32) -> Self { +/* ┃ */ let __result = Self { +/* ┃ */ field, +/* ┃ */ }; +/* ┃ */ __incr_cov(from!("fn new_with_value()")); // function-scoped counter index = 0 +/* ┃ */ __result +/* - */ } +/* */ +/* */ fn call_closure(&self, closure: F) -> bool +/* */ where +/* */ F: FnOnce( +/* */ i32, +/* */ ) -> bool, +/* - */ { +/* ┃ */ let __result = closure(123); +/* ┃ */ __incr_cov(from!("fn call_closure()")); // function-scoped counter index = 0 +/* ┃ */ __result +/* - */ } +/* */ +/* - */ fn various(&self) -> Result<(),Error> { +/* ┃ */ use TestEnum::*; +/* ┃ */ let mut color = Red; +/* ┃ */ let _ = color; +/* ┃ */ color = Blue; +/* ┃ */ let _ = color; +/* ┃ */ color = Green; +/* ┃ */ match { let __result = color; __incr_cov(from!("fn various")); __result } { // function-scoped counter index = 0 +/* : */ +/* : */ // !!! RECORD SPAN FROM START OF INNERMOST CONTAINING BLOCK (THE FUNCTION IN THIS CASE) TO END OF MATCH EXPRESSION +/* : */ // If `match`, `while`, `loop`, `for`, `if`, etc. expression has a `return`, `break`, or `continue` +/* : */ // (if legal), then RECORD SPAN FROM START OF INNERMOST CONTAINING BLOCK TO END OF `return` EXPRESSION +/* : */ // If the expression includes lazy booleans, nest calls to `__incr_cov()`. +/* : I */ Red => {println!("roses"); __incr_cov(from!("Red => or end of MatchArmGuard expression inside pattern, if any"));} +/* : - */ Green => { +/* : ┃ */ let spidey = 100; +/* : ┃ */ let goblin = 50; +/* : ┃ */ // if spidey > goblin {__incr_cov(from!(""),{ +/* : ┃ */ // println!("what ev"); +/* : ┃ */ // })} +/* : ┃ */ // ACTUALLY, WRAPPING THE ENTIRE IF BLOCK IN `__incr_cov` IS NOT A GREAT GENERAL RULE. +/* : ┃ */ // JUST INSERTING A `return`, `break`, or `continue` IN THAT BLOCK (without an intermediate condition) +/* : ┃ */ // MAKES THE `__incr_cov()` CALL UNREACHABLE! +/* : ┃ */ // MY ORIGINAL SOLUTION WORKS BETTER (WRAP LAST EXPRESSION OR AFTER LAST SEMICOLON STATEMENT IN BLOCK) +/* : ┃ */ // UNLESS THE EXPRESSION IS NOT A BLOCK. +/* : ┃ - */ if { let __result = spidey > goblin; __incr_cov(from!("Green => or end of MatchArmGuard expression inside pattern, if any")); __result } { +/* : : ┃ */ println!("spidey beats goblin"); +/* : : ┃ */ __incr_cov(from!("block start")); +/* : ┃ - */ } else if { let __result = spidey == goblin; __incr_cov(from!("`else if` on this line")); __result } { +/* : : ┃ */ // COVERAGE NOTE: Do we mark only the expression span (that may be trivial, as in this case), +/* : : ┃ */ // or associate it with the outer block, similar to how the `if` expression is associated with +/* : : ┃ */ // the outer block? (Although it is a continuation, in a sense, it is discontiguous in this case, +/* : : ┃ */ // so I think simpler to just make it its own coverage region.) +/* : : ┃ */ println!("it's a draw"); +/* : : ┃ */ __incr_cov(from!("block start")); +/* : ┃ - - - */ } else if if { let __result = true; __incr_cov(from!("`else if` on this line")); __result } { +/* : : : ┃ */ // return __incr_cov(from!("after `if true`"),Ok(())); +/* : : : ┃ */ // ACTUALLY, BECAUSE OF `return`, WE DO NOT RECORD THE `if true` EVEN THOUGH WE COVERED IT. +/* : : : ┃ */ // IN FACT, IF THIS NESTED CONDITIONAL IN A CONDITIONAL EXPRESSION WAS AN `if` (WITHOUT PRECEDING ELSE) +/* : : : ┃ */ // WE WOULD NOT HAVE RECORDED THE COVERAGE OF STATEMENTS LEADING UP TO THE `if`, SO +/* : : : ┃ */ // IT SHOULD BE: +/* ┏-:---:-------:---< */ return { let __result = Ok(()); __incr_cov(from!("")); __result }; +/* V : : : : */ // NOTE THE `from` STRING IS SAME FOR THE `else if`s `__incr_cov` AND THIS `return`. +/* : : : : */ // ONLY ONE OF THESE WILL EXECUTE, TO RECORD COVERAGE FROM THAT SPOT. +/* : : ┃ - */ } else { +/* : : : I */ { let __result = false; __incr_cov(from!("`else`")); __result } +/* : : - - */ } { +/* : : ┃ */ println!("wierd science"); +/* : : ┃ */ __incr_cov(from!("block start")); +/* : ┃ - */ } else { +/* : : ┃ */ println!("goblin wins"); +/* ┏-:---:---< */ return { let __result = Ok(()); __incr_cov(from!("`else`")); __result }; // THIS COUNTS LAST STATEMENT IN `else` BLOCK +/* V : : : */ // COVERAGE NOTE: When counting the span for `return`, +/* : : : */ // `break`, or `continue`, also report the outer spans +/* : : : */ // got this far--including this `else` block. Record +/* : : : */ // The start positions for those outer blocks, but: +/* : : : */ // * For the block containing the `return`, `break`, or +/* : : : */ // `continue`, end report the end position is the +/* : : : */ // start of the `return` span (or 1 char before it). +/* : : : */ // * Anything else? +/* : ┃ - */ } +/* : ┃ - */ // __incr_cov(from!("")); // DO NOT COUNT HERE IF NO STATEMENTS AFTER LAST `if` or `match` +/* : - */ }, +/* : I */ Blue => { println!("violets"); __incr_cov(from!("Blue => or end of MatchArmGuard expression inside pattern, if any")); } +/* ┃ */ } +/* ┃ */ +/* ┃ */ let condition1 = true; +/* ┃ */ let condition2 = false; +/* ┃ */ let condition3 = true; +/* ┃ */ +/* ┃ */ println!("Called `various()` for TestStruct with field={}", self.field); +/* ┃ */ +/* ┃ - */ if { let __result = condition1; __incr_cov(from!("after block end of prior `match` (or `if-else if-else`)")); __result } { +/* : ┃ */ println!("before for loop"); +/* : ┃ - */ for index in { let __result = 0..10; __incr_cov(from!("block start")); __result } { +/* : : ┃ */ println!("top of `for` loop"); +/* : : ┃ - */ if { let __result = index == 8; __incr_cov(from!("block start")); __result } { +/* : : : ┃ */ println!("before break"); +/* : : : ┃ */ // note the following is not legal here: +/* : : : ┃ */ // "can only break with a value inside `loop` or breakable block" +/* : : : ┃ */ // break __incr_cov(from!("")); +/* : : : ┃ */ __incr_cov(from!("block start")); +/* : : ┏-----< */ break; +/* : : V : : */ +/* : : : : */ // FIXME(richkadel): add examples with loop labels, breaking out of inner and outer loop to outer loop label, with expression. +/* : : : : */ // May want to record both the span and the start position after the broken out block depdnding on label +/* : : ┃ - */ } +/* : : ┃ */ println!("after `break` test"); +/* : : ┃ - */ if { let __result = condition2; __incr_cov(from!("block end of `if index == 8`")); __result } { +/* ┏-:---:---:---< */ return { let __result = Ok(()); __incr_cov(from!("block start")); __result }; +/* V : : ┃ - */ } +/* : : ┃ */ +/* : : ┃ */ // BECAUSE THE PREVIOUS COVERAGE REGION HAS A `return`, THEN +/* : : ┃ */ // IF PREVIOUS COVERAGE REGION IS NOT COUNTED THEN OUTER REGION REACHED HERE. +/* : : ┃ */ // ADD A COVERAGE REGION FOR THE SPAN FROM JUST AFTER PREVIOUS REGION TO END +/* : : ┃ */ // OF OUTER SPAN, THEN TRUNCATE TO NEXT REGION NOT REACHED. +/* : : ┃ - */ if index % 3 == 2 { // NO __incr_cov() HERE BECAUSE NO STATEMENTS BETWEEN LAST CONDITIONAL BLOCK AND START OF THIS ONE +/* : : Λ : ┃ */ __incr_cov(from!("block end of `if condition2`")); +/* : : ┗-----< */ continue; +/* : : ┃ - */ } +/* : : ┃ */ println!("after `continue` test"); +/* : : ┃ */ // maybe add a runtime flag for a possible `return` here? +/* : : ┃ */ __incr_cov(from!("")); +/* : ┃ - */ } +/* : ┃ */ println!("after for loop"); +/* : ┃ */ let result = if { // START OF NEW CONDITIONAL EXPRESSION. NEXT "GUARANTEED" COUNTER SHOULD COUNT FROM END OF LAST CONDITIONAL EXPRESSION +/* : ┃ */ // A "GUARANTEED" COUNTER CALL IS ONE THAT WILL BE CALLED REGARDLESS OF OTHER CONDITIONS. THIS INCLUDES: +/* : ┃ */ // * A CONDITIONAL EXPRESSION THAT IS NOT A BLOCK (OR ANOTHER CONDITIONAL STATEMENT, WHICH WOULD CONTAIN A BLOCK) +/* : ┃ */ // * OR IF THE NEXT CONDITIONAL EXPRESSION IS A BLOCK OR CONDITIONAL STATEMENT, THEN THE FIRST "GUARANTEED" COUNTER IN THAT BLOCK +/* : ┃ */ // * END OF BLOCK IF THE BLOCK DOES NOT HAVE INNER CONDITIONAL EXPRESSIONS +/* : ┃ */ // * BRANCHING STATEMENTS (`return`, `break`, `continue`) BY EITHER WRAPPING THE BRANCH STATEMENT NON-BLOCK EXPRESSION, +/* : ┃ */ // OR PREPENDING A COUNTER WITH EMPTY TUPLE IF NO EXPRESSION, OR IF EXPRESSION IS A BLOCK, THEN THE NEXT "GUARANTEED" +/* : ┃ */ // COUNTER CALL WITHIN THAT BLOCK. +/* : ┃ */ // BASICALLY, CARRY THE START OF COVERAGE SPAN FORWARD UNTIL THE GUARANTEED COUNTER IS FOUND +/* : ┃ */ println!("after result = if ..."); +/* : ┃ - */ if { let __result = condition2; __incr_cov(from!("block end of `for` loop")); __result } { +/* : : ┃ */ println!("before first return"); +/* ┏-:---:-------< */ return { let __result = Ok(()); __incr_cov(from!("block start")); __result }; +/* V : : - */ } else if { let __result = condition3; __incr_cov(from!("`else`")); __result } { +/* : : ┃ */ // THE ABOVE COUNTER IS _NOT_ REALLY NECESSARY IF EXPRESSION IS GUARANTEED TO EXECUTE. +/* : : ┃ */ // IF WE GET COUNTER IN `else if` BLOCK WE COVERED EXPRESSION. +/* : : ┃ */ // IF WE GET TO ANY REMAINING `else` or `else if` BLOCK WE KNOW WE EVALUATED THIS CONDITION +/* : : ┃ */ // AND ALL OTHERS UP TO THE EXECUTED BLOCK. BUT THE SPAN WOULD HAVE "HOLES" FOR UNEXECUTED BLOCKS. +/* : : ┃ */ println!("not second return"); +/* ┏-:---:-------< */ return { let __result = Ok(()); __incr_cov(from!("block start")); __result }; +/* V : : - */ } else { +/* : : ┃ */ println!("not returning"); +/* : : ┃ */ { let __result = false; __incr_cov(from!("block start")); __result } +/* : : - */ } +/* : ┃ */ // NO COUNTER HERE BECAUSE NO STATEMENTS AFTER CONDITIONAL BLOCK +/* : ┃ - */ } { +/* : : ┃ */ println!("branched condition returned true"); +/* : : ┃ */ { let __result = Ok(()); __incr_cov(from!("")); __result } +/* : ┃ - */ } else if self.call_closure( +/* : : - */ |closure_param| { +/* : : ┃ - */ let __result = if condition3 { +/* : : : ┃ */ println!("in closure, captured condition said to print the param {}", closure_param); +/* : : : ┃ */ { let __result = false; __incr_cov(from!("")); __result } +/* : : ┃ - */ } else { +/* : : : ┃ */ println!("in closure, captured condition was false"); +/* : : : ┃ */ { let __result = true; __incr_cov(from!("")); __result } +/* : : ┃ - */ }; +/* : : - */ __incr_cov(from!("")); __result } +/* : : - */ ) { +/* : : ┃ */ println!("closure returned true"); +/* : : ┃ */ { let __result = Err(Error::new(ErrorKind::Other, "Result is error if closure returned true")); __incr_cov(from!("")); __result } +/* : ┃ - */ } else { +/* : : ┃ */ println!("closure returned false"); +/* : : ┃ */ { let __result = Err(Error::new(ErrorKind::Other, "Result is error if closure returned false")); __incr_cov(from!("")); __result } +/* : ┃ - */ }; +/* : ┃ */ println!("bottom of function might be skipped if early `return`"); +/* : ┃ */ { let __result = result; __incr_cov(from!("if condition1")); __result } +/* ┃ - */ } else { +/* : ┃ */ println!("skipping everything in `various()`"); +/* : ┃ */ { let __result = Ok(()); __incr_cov(from!("")); __result } +/* ┃ - */ } +/* ┃ - */ // __incr_cov(from!(""),0) // DO NOT COUNT IF NO STATEMENTS AFTER CONDITIONAL BLOCK. ALL COVERAGE IS ALREADY COUNTED +/* - */ } +/* */ } +/* */ +/* - */ fn main() -> Result<(), std::io::Error> { +/* ┃ */ //let mut status: u8 = 2; +/* ┃ */ let mut status: u8 = 1; +/* : - */ let result = if status < 2 && +/* : ┃ */ { let __result = { +/* : ┃ */ status -= 1; +/* : ┃ */ status == 0 +/* : - - */ }; __incr_cov(from!("")); __result } { +/* : ┃ */ let test_struct = TestStruct::new_with_value(100); +/* : ┃ */ let _ = test_struct.various(); +/* ┏-:---< */ return { let __result = Err(Error::new(ErrorKind::Other, format!("Error status {}", status))); __incr_cov(from!("")); __report(); __result } +/* V : - */ } else { +/* : ┃ */ let test_struct = TestStruct::new(); +/* : ┃ */ { let __result = test_struct.various(); __incr_cov(from!("")); __result } +/* : - */ }; +/* ┃ */ println!("done"); +/* ┃ */ { let __result = result; __incr_cov(from!("")); __report(); __result } +/* - */ } \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/coverage_injection_test_alt.rs b/src/test/codegen/coverage-experiments/src/coverage_injection_test_alt.rs new file mode 100644 index 0000000000000..20c4835dd882e --- /dev/null +++ b/src/test/codegen/coverage-experiments/src/coverage_injection_test_alt.rs @@ -0,0 +1,362 @@ +/* */ use std::io::Error; +/* */ use std::io::ErrorKind; +/* */ +/* */ /// Align Rust counter increment with with: +/* */ /// [‘llvm.instrprof.increment’ Intrinsic](https://llvm.org/docs/LangRef.html#llvm-instrprof-increment-intrinsic) +/* */ /// +/* */ /// declare void @llvm.instrprof.increment(i8* , i64 , i32 , i32 ) +/* */ /// +/* */ /// The first argument is a pointer to a global variable containing the name of the entity +/* */ /// being instrumented. This should generally be the (mangled) function name for a set of +/* */ /// counters. +/* */ /// +/* */ /// The second argument is a hash value that can be used by the consumer of the profile data +/* */ /// to detect changes to the instrumented source, and the third is the number of counters +/* */ /// associated with name. It is an error if hash or num-counters differ between two +/* */ /// instances of instrprof.increment that refer to the same name. +/* */ /// +/* */ /// The last argument refers to which of the counters for name should be incremented. It +/* */ /// should be a value between 0 and num-counters. +/* */ /// +/* */ /// # Arguments +/* */ /// +/* */ /// `mangled_fn_name` - &'static ref to computed and injected static str, using: +/* */ /// +/* */ /// ``` +/* */ /// fn rustc_symbol_mangling::compute_symbol_name( +/* */ /// tcx: TyCtxt<'tcx>, +/* */ /// instance: Instance<'tcx>, +/* */ /// compute_instantiating_crate: impl FnOnce() -> CrateNum, +/* */ /// ) -> String +/* */ /// ``` +/* */ /// +/* */ /// `source_version_hash` - Compute hash based that only changes if there are "significant" +/* */ /// to control-flow inside the function. +/* */ /// +/* */ /// `num_counters` - The total number of counter calls [MAX(counter_index) + 1] within the +/* */ /// function. +/* */ /// +/* */ /// `counter_index` - zero-based counter index scoped by the function. (Ordering of +/* */ /// counters, relative to the source code location, is apparently not expected.) +/* */ /// +/* */ /// # Notes +/* */ /// +/* */ /// * The mangled_fn_name may not be computable until generics are monomorphized (see +/* */ /// parameters required by rustc_symbol_mangling::compute_symbol_name). +/* */ /// * The version hash may be computable from AST analysis, and may not benefit from further +/* */ /// lowering. +/* */ /// * num_counters depends on having already identified all counter insertion locations. +/* */ /// * counter_index can be computed at time of counter insertion (incrementally). +/* */ /// * Numeric parameters are signed to match the llvm increment intrinsic parameter types. +/* */ fn __lower_incr_cov(_mangled_fn_name: &'static str, _fn_version_hash: i64, _num_counters: i32, _counter_index: i32) { +/* */ } +/* */ +/* */ /// A coverage counter implementation that will work as both an intermediate coverage +/* */ /// counting and reporting implementation at the AST-level only--for debugging and +/* */ /// development--but also serves as a "marker" to be replaced by calls to LLVM +/* */ /// intrinsic coverage counter APIs during the lowering process. +/* */ /// +/* */ /// Calls to this function will be injected automatically into the AST. When LLVM intrinsics +/* */ /// are enabled, the counter function calls that were injected into the AST serve as +/* */ /// placeholders, to be replaced by an alternative, such as: +/* */ /// +/* */ /// * direct invocation of the `llvm.instrprof.increment()` intrinsic; or +/* */ /// * the `__lower_incr_cov()` function, defined above, that would invoke the +/* */ /// `llvm.instrprof.increment()` intrinsic; or +/* */ /// * a similar expression wrapper, with the additional parameters (as defined above +/* */ /// for `__lower_incr_cov()`, that invokes `llvm.instrprof.increment()` and returns the +/* */ /// result of the wrapped expression) +/* */ /// +/* */ /// The first two options would require replacing the inlined wrapper call with something +/* */ /// like: +/* */ /// +/* */ /// ``` +/* */ /// { let result = {expr}; __inlined_incr_cov(context, counter); result } +/* */ /// ``` +/* */ /// +/* */ /// But if the lowering process is already unwrapping the inlined call to `__incr_cov()`, then +/* */ /// it may be a perfect opportunity to replace the function with one of these more +/* */ /// direct methods. +/* */ /// +/* */ #[inline(always)] +/* */ pub fn __incr_cov(region_loc: &str, /*index: u32,*/) { +/* */ // Either call the intermediate non-llvm coverage counter API or +/* */ // replace the call to this function with the expanded `__lower_incr_cov()` call. +/* */ +/* */ // let _lock = increment_counter(counter); +/* */ println!("{}", region_loc); +/* */ } +/* */ +/* */ /// Write a report identifying each incremented counter and the number of times each counter +/* */ /// was incremented. +/* */ fn __report() { +/* */ println!("WRITE REPORT!"); +/* */ } +/* */ +/* */ /// Increment the counter after evaluating the wrapped expression (see `__incr_cov()`), then +/* */ /// write a report identifying each incremented counter and the number of times each counter +/* */ /// was incremented. +/* */ #[inline(always)] +/* */ pub fn __incr_cov_and_report(region_loc: &str, /*counter: u32,*/ result: T) -> T { +/* */ __incr_cov(region_loc, /*counter,*/); +/* */ __report(); +/* */ result +/* */ } +/* */ +/* */ macro_rules! from { +/* */ ($from:expr) => { &format!("from: {}\n to: {}:{}:{}", $from, file!(), line!(), column!()) }; +/* */ } +/* */ +/* */ macro_rules! to { +/* */ ($to:expr) => { &format!("to: {}\n to: {}:{}:{}", $to, file!(), line!(), column!()) }; +/* */ } +/* */ +/* */ #[derive(Debug)] +/* */ enum TestEnum { +/* */ Red, +/* */ Green, +/* */ Blue, +/* */ } +/* */ +/* */ struct TestStruct { +/* */ field: i32, +/* */ } +/* */ +/* */ // IMPORTANT! IS WRAPPING main() ENOUGH? OR DO I ALSO NEED TO WRAP THREAD FUNCTIONS, ASSUMING +/* */ // THEY ARE STILL RUNNING WITH MAIN EXITS? (IF THEY CAN). NOT SURE HOW RUST HANDLES THAT. +/* */ +/* */ // I SUSPECT USING THREAD_LOCAL COUNTERS MAY NOT ACTUALLY BE AN OPTIMIZATION OVER MUTEX LOCKS, +/* */ // BUT MAYBE I SHOULD ASK. +/* */ +/* */ impl TestStruct { +/* - */ fn new() -> Self { +/* ┃ */ __incr_cov(to!("end of fn new()")); // function-scoped counter index = 0 +/* ┃ */ Self::new_with_value(31415) +/* - */ } +/* */ +/* - */ fn new_with_value(field: i32) -> Self { +/* ┃ */ __incr_cov(to!("end of fn new_with_value()")); // function-scoped counter index = 0 +/* ┃ */ Self { +/* ┃ */ field, +/* ┃ */ } +/* - */ } +/* */ +/* */ fn call_closure(&self, closure: F) -> bool +/* */ where +/* */ F: FnOnce( +/* */ i32, +/* */ ) -> bool, +/* - */ { +/* ┃ */ __incr_cov(to!("end of fn call_closure()")); // function-scoped counter index = 0 +/* ┃ */ closure(123) +/* - */ } +/* */ +/* - */ fn various(&self) -> Result<(),Error> { +/* ┃ */ __incr_cov(to!("just before next branch: after `match color`: pattern selection")); +/* ┃ */ use TestEnum::*; +/* ┃ */ let mut color = Red; +/* ┃ */ let _ = color; +/* ┃ */ color = Blue; +/* ┃ */ let _ = color; +/* ┃ */ color = Green; +/* ┃ */ match color { // function-scoped counter index = 0 +/* : */ +/* : */ // !!! RECORD SPAN FROM START OF INNERMOST CONTAINING BLOCK (THE FUNCTION IN THIS CASE) TO END OF MATCH EXPRESSION +/* : */ // If `match`, `while`, `loop`, `for`, `if`, etc. expression has a `return`, `break`, or `continue` +/* : */ // (if legal), then RECORD SPAN FROM START OF INNERMOST CONTAINING BLOCK TO END OF `return` EXPRESSION +/* : */ // If the expression includes lazy booleans, nest calls to `__incr_cov()`. +/* : - */ Red => { +/* : ┃ */ __incr_cov(to!("end of matched Red")); +/* : ┃ */ println!("roses"); +/* : - */ } +/* : - */ Green => { +/* : ┃ */ __incr_cov(to!("just before next branch: after `if spidey > goblin`")); +/* : ┃ */ let spidey = 100; +/* : ┃ */ let goblin = 50; +/* : ┃ */ // if spidey > goblin {__incr_cov(from!(""),{ +/* : ┃ */ // println!("what ev"); +/* : ┃ */ // })} +/* : ┃ */ // ACTUALLY, WRAPPING THE ENTIRE IF BLOCK IN `__incr_cov` IS NOT A GREAT GENERAL RULE. +/* : ┃ */ // JUST INSERTING A `return`, `break`, or `continue` IN THAT BLOCK (without an intermediate condition) +/* : ┃ */ // MAKES THE `__incr_cov()` CALL UNREACHABLE! +/* : ┃ */ // MY ORIGINAL SOLUTION WORKS BETTER (WRAP LAST EXPRESSION OR AFTER LAST SEMICOLON STATEMENT IN BLOCK) +/* : ┃ */ // UNLESS THE EXPRESSION IS NOT A BLOCK. +/* : ┃ - */ if spidey > goblin { +/* : : ┃ */ __incr_cov(to!("end of if block, if no earlier branch in this scope")); +/* : : ┃ */ println!("spidey beats goblin"); +/* : : ┃ */ +/* : ┃ - */ } else if { +/* : : : */ // Make sure we can't compute the coverage count here. +/* : : : */ // We know the expression executed if the previous if block DID NOT +/* : : : */ // execute, and either this `else if` block does execute OR any subsequent +/* : : : */ // `else if` or `else` blocks execute, OR none of the blocks in the +/* : : : */ // `if`, `else if` or `else` blocks execute. +/* : : : */ // `if`, `else if` or `else` blocks execute. +/* : : ┃ */ __incr_cov(to!("end of `else if spidey == goblin` expression")); +/* : : ┃ */ spidey == goblin +/* : ┃ - */ } { +/* : : ┃ */ __incr_cov(to!("end of if block, if no earlier branch in this scope")); +/* : : ┃ */ // COVERAGE NOTE: Do we mark only the expression span (that may be trivial, as in this case), +/* : : ┃ */ // or associate it with the outer block, similar to how the `if` expression is associated with +/* : : ┃ */ // the outer block? (Although it is a continuation, in a sense, it is discontiguous in this case, +/* : : ┃ */ // so I think simpler to just make it its own coverage region.) +/* : : ┃ */ println!("it's a draw"); +/* : : ┃ */ +/* : ┃ - - - */ } else if { +/* : : ┃ */ __incr_cov(to!("end of `if true`")); +/* : ┃ - - - */ if true { +/* : : : ┃ */ __incr_cov(to!("end of `return Ok(())`")); +/* ┏-:---:-------:---< */ return Ok(()); +/* V : : ┃ - */ } else { +/* : : : ┃ */ // __incr_cov(to!("end of else block")); +/* : : : ┃ */ // computed counter expression +/* : : : ┃ */ false +/* : : : - */ } +/* : : - - - */ } { +/* : : ┃ */ __incr_cov(to!("end of if block")); +/* : : ┃ */ println!("wierd science"); +/* : ┃ - */ } else { +/* : : ┃ */ // __incr_cov(to!("end of `return Ok(())")); +/* : : ┃ */ // counter expression: (start of Green match arm) - (if spidey > goblin) - (previous `} else if {`) +/* : : ┃ */ println!("goblin wins"); +/* ┏-:---:---< */ return Ok(()); // THIS COUNTS LAST STATEMENT IN `else` BLOCK +/* V : : : */ // COVERAGE NOTE: When counting the span for `return`, +/* : : : */ // `break`, or `continue`, also report the outer spans +/* : : : */ // got this far--including this `else` block. Record +/* : : : */ // The start positions for those outer blocks, but: +/* : : : */ // * For the block containing the `return`, `break`, or +/* : : : */ // `continue`, end report the end position is the +/* : : : */ // start of the `return` span (or 1 char before it). +/* : : : */ // * Anything else? +/* : ┃ - */ } +/* : : */ // __incr_cov(to!("end of matched Green")); +/* : : */ // // DO NOT COUNT HERE IF NO STATEMENTS AFTER LAST `if` or `match` +/* : - */ }, +/* : - */ Blue => { +/* : ┃ */ __incr_cov(to!("end of matched Blue")); +/* : ┃ */ println!("violets"); +/* : - */ } +/* ┃ */ } +/* ┃ */ __incr_cov(to!("just before next branch: after `if condition1` (HIR: 'match condition1')")); +/* ┃ */ +/* ┃ */ let condition1 = true; +/* ┃ */ let condition2 = false; +/* ┃ */ let condition3 = true; +/* ┃ */ +/* ┃ */ println!("Called `various()` for TestStruct with field={}", self.field); +/* ┃ */ +/* ┃ - */ if condition1 { +/* : ┃ */ println!("before while loop"); +/* : ┃ */ let mut countdown = 10; +/* : ┃ */ // Must increment before repeated while text expression +/* : : I */ while countdown > 0 { // span is just the while test expression +/* : : ┃ */ println!("top of `while` loop"); +/* : : ┃ */ countdown -= 1; +/* : : ┃ */ // // Counter not needed, but span is computed as "while test" minus "block start" +/* : : ┃ */ // If test expression is 11, and the outer block runs only once, 11-1 = 10 +/* : ┃ - */ } +/* : ┃ */ println!("before for loop"); +/* : ┃ - */ for index in 0..10 { +/* : : ┃ */ println!("top of `for` loop"); +/* : : ┃ - */ if index == 8 { +/* : : : ┃ */ println!("before break"); +/* : : : ┃ */ // note the following is not legal here: +/* : : : ┃ */ // "can only break with a value inside `loop` or breakable block" +/* : : : ┃ */ // break +/* : : : ┃ */ +/* : : ┏-----< */ break; +/* : : V : : */ +/* : : : : */ // FIXME(richkadel): add examples with loop labels, breaking out of inner and outer loop to outer loop label, with expression. +/* : : : : */ // May want to record both the span and the start position after the broken out block depdnding on label +/* : : ┃ - */ } +/* : : ┃ */ println!("after `break` test"); +/* : : ┃ - */ if condition2 { +/* ┏-:---:---:---< */ return Ok(()); +/* V : : ┃ - */ } +/* : : ┃ */ +/* : : ┃ */ // BECAUSE THE PREVIOUS COVERAGE REGION HAS A `return`, THEN +/* : : ┃ */ // IF PREVIOUS COVERAGE REGION IS NOT COUNTED THEN OUTER REGION REACHED HERE. +/* : : ┃ */ // ADD A COVERAGE REGION FOR THE SPAN FROM JUST AFTER PREVIOUS REGION TO END +/* : : ┃ */ // OF OUTER SPAN, THEN TRUNCATE TO NEXT REGION NOT REACHED. +/* : : ┃ - */ if index % 3 == 2 { // NO __incr_cov() HERE BECAUSE NO STATEMENTS BETWEEN LAST CONDITIONAL BLOCK AND START OF THIS ONE +/* : : Λ : ┃ */ +/* : : ┗-----< */ continue; +/* : : ┃ - */ } +/* : : ┃ */ println!("after `continue` test"); +/* : : ┃ */ // maybe add a runtime flag for a possible `return` here? +/* : : ┃ */ +/* : ┃ - */ } +/* : ┃ */ println!("after for loop"); +/* : ┃ */ let result = if { // START OF NEW CONDITIONAL EXPRESSION. NEXT "GUARANTEED" COUNTER SHOULD COUNT FROM END OF LAST CONDITIONAL EXPRESSION +/* : ┃ */ // A "GUARANTEED" COUNTER CALL IS ONE THAT WILL BE CALLED REGARDLESS OF OTHER CONDITIONS. THIS INCLUDES: +/* : ┃ */ // * A CONDITIONAL EXPRESSION THAT IS NOT A BLOCK (OR ANOTHER CONDITIONAL STATEMENT, WHICH WOULD CONTAIN A BLOCK) +/* : ┃ */ // * OR IF THE NEXT CONDITIONAL EXPRESSION IS A BLOCK OR CONDITIONAL STATEMENT, THEN THE FIRST "GUARANTEED" COUNTER IN THAT BLOCK +/* : ┃ */ // * END OF BLOCK IF THE BLOCK DOES NOT HAVE INNER CONDITIONAL EXPRESSIONS +/* : ┃ */ // * BRANCHING STATEMENTS (`return`, `break`, `continue`) BY EITHER WRAPPING THE BRANCH STATEMENT NON-BLOCK EXPRESSION, +/* : ┃ */ // OR PREPENDING A COUNTER WITH EMPTY TUPLE IF NO EXPRESSION, OR IF EXPRESSION IS A BLOCK, THEN THE NEXT "GUARANTEED" +/* : ┃ */ // COUNTER CALL WITHIN THAT BLOCK. +/* : ┃ */ // BASICALLY, CARRY THE START OF COVERAGE SPAN FORWARD UNTIL THE GUARANTEED COUNTER IS FOUND +/* : ┃ */ println!("after result = if ..."); +/* : ┃ - */ if condition2 { +/* : : ┃ */ println!("before first return"); +/* ┏-:---:-------< */ return Ok(()); +/* V : : - */ } else if condition3 { +/* : : ┃ */ // THE ABOVE COUNTER IS _NOT_ REALLY NECESSARY IF EXPRESSION IS GUARANTEED TO EXECUTE. +/* : : ┃ */ // IF WE GET COUNTER IN `else if` BLOCK WE COVERED EXPRESSION. +/* : : ┃ */ // IF WE GET TO ANY REMAINING `else` or `else if` BLOCK WE KNOW WE EVALUATED THIS CONDITION +/* : : ┃ */ // AND ALL OTHERS UP TO THE EXECUTED BLOCK. BUT THE SPAN WOULD HAVE "HOLES" FOR UNEXECUTED BLOCKS. +/* : : ┃ */ println!("not second return"); +/* ┏-:---:-------< */ return Ok(()); +/* V : : - */ } else { +/* : : ┃ */ println!("not returning"); +/* : : ┃ */ false +/* : : - */ } +/* : ┃ */ // NO COUNTER HERE BECAUSE NO STATEMENTS AFTER CONDITIONAL BLOCK +/* : ┃ - */ } { +/* : : ┃ */ println!("branched condition returned true"); +/* : : ┃ */ Ok(()) +/* : ┃ - */ } else if self.call_closure( +/* : : - */ |closure_param| +/* : : ┃ - */ if condition3 { +/* : : : ┃ */ println!("in closure, captured condition said to print the param {}", closure_param); +/* : : : ┃ */ false +/* : : ┃ - */ } else { +/* : : : ┃ */ println!("in closure, captured condition was false"); +/* : : : ┃ */ true +/* : : ┃ - */ } +/* : : - */ +/* : : - */ ) { +/* : : ┃ */ println!("closure returned true"); +/* : : ┃ */ Err(Error::new(ErrorKind::Other, "Result is error if closure returned true")) +/* : ┃ - */ } else { +/* : : ┃ */ println!("closure returned false"); +/* : : ┃ */ Err(Error::new(ErrorKind::Other, "Result is error if closure returned false")) +/* : ┃ - */ }; +/* : ┃ */ println!("bottom of function might be skipped if early `return`"); +/* : ┃ */ result +/* ┃ - */ } else { +/* : ┃ */ println!("skipping everything in `various()`"); +/* : ┃ */ Ok(()) +/* ┃ - */ } +/* ┃ - */ // 0 // DO NOT COUNT IF NO STATEMENTS AFTER CONDITIONAL BLOCK. ALL COVERAGE IS ALREADY COUNTED +/* - */ } +/* */ } +/* */ +/* - */ fn main() -> Result<(), std::io::Error> { +/* ┃ */ //let mut status: u8 = 2; +/* ┃ */ let mut status: u8 = 1; +/* : - */ let result = if status < 2 && +/* : ┃ */ { +/* : ┃ */ status -= 1; +/* : ┃ */ status == 0 +/* : - - */ } { +/* : ┃ */ let test_struct = TestStruct::new_with_value(100); +/* : ┃ */ let _ = test_struct.various(); +/* ┏-:---< */ return __incr_cov_and_report(from!(""),Err(Error::new(ErrorKind::Other, format!("Error status {}", status)))) +/* V : - */ } else { +/* : ┃ */ let test_struct = TestStruct::new(); +/* : ┃ */ test_struct.various() +/* : - */ }; +/* ┃ */ println!("done"); +/* ┃ */ __incr_cov_and_report(from!(""),result) // function-scoped counter index = 0 +/* - */ } \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/drop_trait.rs b/src/test/codegen/coverage-experiments/src/drop_trait.rs new file mode 100644 index 0000000000000..75400e037e9f0 --- /dev/null +++ b/src/test/codegen/coverage-experiments/src/drop_trait.rs @@ -0,0 +1,25 @@ +#[inline(always)] +pub fn __incr_cov(_region_loc: &str, result: T) -> T { + result +} + +struct Firework { + _strength: i32, +} + +impl Drop for Firework { + fn drop(&mut self) { + __incr_cov("start of drop()", ()); + } +} + +fn main() -> Result<(),u8> { + let _firecracker = Firework { _strength: 1 }; + + if __incr_cov("start of main()", true) { + return __incr_cov("if true", { let _t = Err(1); _t }); + } + + let _tnt = Firework { _strength: 100 }; + Ok(()) +} \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/drop_trait_with_comments_prints.rs b/src/test/codegen/coverage-experiments/src/drop_trait_with_comments_prints.rs new file mode 100644 index 0000000000000..de9f5d5cb4647 --- /dev/null +++ b/src/test/codegen/coverage-experiments/src/drop_trait_with_comments_prints.rs @@ -0,0 +1,53 @@ +// +// +// +// It's interesting to speculate if there is a way to leverage the Drop trait functionality +// to increment counters when a scope is closed, but I don't think it would help "out of the box". +// +// A `return` or `break` with expression might not need a temp value expression wrapper +// such as `return { let _t = result_expression; __incr_counter(...); _t };` +// +// ... **if** the __incr_counter() was somehow called from a "drop()" trait function. +// +// The problem is, since the drop call is automatic, there is no way to have argument variants +// depending on where the drop() occurs (e.g., from a `return` statement vs. from the end of +// the function). We need 2 different code regions though. +// +// +// +// + +#[inline(always)] +pub fn __incr_cov(_region_loc: &str, /*index: u32,*/ result: T) -> T { + // println!("from: {}", _region_loc); + result +} + +struct Firework { + strength: i32, +} + +impl Drop for Firework { + fn drop(&mut self) { + println!("BOOM times {}!!!", self.strength); + __incr_cov("start of drop()", ()); + } +} + +fn main() -> Result<(),u8> { + let _firecracker = Firework { strength: 1 }; + + if __incr_cov("start of main()", true) { + return __incr_cov("if true", { let _t = Err(1); println!("computing return value"); _t }); + } + + let _tnt = Firework { strength: 100 }; + // __incr_cov("after if block", Ok(())) // CAN USE COUNTER EXPRESSION: "start of drop()" - "if true" + Ok(()) +} + +// OUTPUT WHEN RUNNING THIS PROGRAM IS AS EXPECTED: + +// computing return value +// BOOM times 1!!! +// Error: 1 diff --git a/src/test/codegen/coverage-experiments/src/for.rs b/src/test/codegen/coverage-experiments/src/for.rs new file mode 100644 index 0000000000000..3f44c382a1e3f --- /dev/null +++ b/src/test/codegen/coverage-experiments/src/for.rs @@ -0,0 +1,41 @@ +#[inline(always)] +pub fn __incr_cov(_region_loc: &str, /*index: u32,*/ result: T) -> T { + result +} + +fn main() { + for countdown in __incr_cov("start", 10..0) { + let _ = countdown; + __incr_cov("top of for", ()); + } +} + +// LOWERED TO HIR: +// +// fn main() { +// { +// let _t = +// match ::std::iter::IntoIterator::into_iter(__incr_cov("start", +// ::std::ops::Range{start: +// 10, +// end: +// 0,})) +// { +// mut iter => +// loop { +// let mut __next; +// match ::std::iter::Iterator::next(&mut iter) { +// ::std::option::Option::Some(val) => +// __next = val, +// ::std::option::Option::None => break , +// } +// let countdown = __next; +// { +// let _ = countdown; +// __incr_cov("top of for", ()); +// } +// }, +// }; +// _t +// } +// } \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/for_with_comments.rs b/src/test/codegen/coverage-experiments/src/for_with_comments.rs new file mode 100644 index 0000000000000..03d11b2c230ca --- /dev/null +++ b/src/test/codegen/coverage-experiments/src/for_with_comments.rs @@ -0,0 +1,24 @@ +/* */ #[inline(always)] +/* */ pub fn __incr_cov(_region_loc: &str, /*index: u32,*/ result: T) -> T { +/* */ result +/* */ } +/* */ +/* - */ fn main() { +/* : I */ for countdown in __incr_cov("start", 10..0) { // span is just the while test expression +/* : ┃ */ let _ = countdown; +/* : ┃ */ __incr_cov("top of for", ()); +/* ┃ - */ } +/* - */ } + + +// -Z unpretty=val -- present the input source, unstable (and less-pretty) variants; +// valid types are any of the types for `--pretty`, as well as: +// `expanded`, `expanded,identified`, +// `expanded,hygiene` (with internal representations), +// `everybody_loops` (all function bodies replaced with `loop {}`), +// `hir` (the HIR), `hir,identified`, +// `hir,typed` (HIR with types for each node), +// `hir-tree` (dump the raw HIR), +// `mir` (the MIR), or `mir-cfg` (graphviz formatted MIR) + +// argument to `pretty` must be one of `normal`, `expanded`, `identified`, or `expanded,identified` diff --git a/src/test/codegen/coverage-experiments/src/if.rs b/src/test/codegen/coverage-experiments/src/if.rs new file mode 100644 index 0000000000000..ad50f6be19004 --- /dev/null +++ b/src/test/codegen/coverage-experiments/src/if.rs @@ -0,0 +1,80 @@ +#![feature(core_intrinsics)] + +pub fn __llvm_incr_counter(_region_loc: &str) { +} + +#[inline(always)] +pub fn __incr_cov(region_loc: &str, result: T) -> T { + __llvm_incr_counter(region_loc); + result +} + +static TEST_FUNC_NAME: &'static [u8; 6] = b"main()"; + +fn main() { + let mut countdown = 10; + if __incr_cov("start", countdown > 0) { + + + // // TEST CALLING INTRINSIC: + unsafe { core::intrinsics::instrprof_increment(TEST_FUNC_NAME as *const u8, 1234 as u64, 314 as u32, 31 as u32) }; + // // Results in: + // // LLVM ERROR: Cannot select: intrinsic %llvm.instrprof.increment + // // I may need to pass one or more of the following flags (or equivalent opts) to LLVM to enable this: + // // -fprofile-instr-generate -fcoverage-mapping + + + countdown -= 1; + __incr_cov("if block",()); + } else if countdown > 5 { + countdown -= 2; + __incr_cov("else if block",()); + } else { + countdown -= 3; + } + + let mut countdown = 10; + if { let _tcov = countdown > 0; __llvm_incr_counter("start", ); _tcov } { + countdown -= 1; + __incr_cov("if block",()); + } else if countdown > 5 { + countdown -= 2; + __incr_cov("else if block",()); + } else { + countdown -= 3; + } +} + +// NOTE: hir REDUNDANTLY lowers the manually inlined counter in the second if block to: +// +// match { +// let _t = +// { +// let _tcov = countdown > 0; +// __llvm_incr_counter("start"); +// _tcov +// }; +// _t +// } { + +// I don't know if optimization phases will fix this or not. +// Otherwise, a more optimal (but definitely special case) way to handle this would be +// to inject the counter between the hir-introduced temp `_t` assignment and the block result +// line returning `_t`: +// +// match { +// let _t = countdown > 0; +// __llvm_incr_counter("start"); // <-- the only thing inserted for coverage here +// _t +// } +// +// UNFORTUNATELY THIS IS NOT A PATTERN WE CAN ALWAYS LEVERAGE, FOR EXPRESSIONS THAT HAVE VALUES +// WHERE WE NEED TO INJECT THE COUNTER AFTER THE EXPRESSION BUT BEFORE IT IS USED. +// +// IT DOES APPEAR TO BE THE CASE FOR WHILE EXPRESSIONS, (BECOMES loop { match { let _t = condition; _t} { true => {...} _ => break, }}) +// AND IS TRUE FOR IF EXPRESSIONS AS NOTED +// BUT NOT FOR RETURN STATEMENT (and I'm guessing not for loop { break value; } ? ) +// +// AND NOT FOR LAZY BOOLEAN EXPRESSIONS! +// +// AND NOT FOR MATCH EXPRESSIONS IN THE ORIGINAL SOURCE! \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/if_with_comments.rs b/src/test/codegen/coverage-experiments/src/if_with_comments.rs new file mode 100644 index 0000000000000..267e7bca2c5a2 --- /dev/null +++ b/src/test/codegen/coverage-experiments/src/if_with_comments.rs @@ -0,0 +1,39 @@ +/* */ #[inline(always)] +/* */ pub fn __incr_cov(_region_loc: &str, /*index: u32,*/ result: T) -> T { +/* */ result +/* */ } +/* */ +/* - */ fn main() { +/* ┃ */ let mut countdown = 10; +/* : I */ if __incr_cov("start", countdown > 0) { // span is from start of main() +/* : ┃ */ countdown -= 1; +/* : ┃ */ __incr_cov("if block",()); +/* ┃ - */ } + + let mut countdown = 10; + if __incr_cov("start", countdown > 0) { + countdown -= 1; + __incr_cov("if block",()); + } else if countdown > 5 { // counter expression "start" - "if block" + countdown -= 2; + __incr_cov("else if block",()); + } else { + countdown -= 3; + // __incr_cov("else block",()); // counter expression (countdown > 5 counter expression) - "else if block" + // PLACED AT END OF ELSE BLOCK OR START OF FIRST CONDITIONAL BLOCK, IF ANY (PRESUMING POSSIBLE EARLY EXIT). + // IF WE CAN GUARANTEE NO EARLY EXIT IN THIS BLOCK, THEN AT THE END IS FINE EVEN IF ELSE BLOCK CONTAINS OTHER CONDITIONS. + } + +/* - */ } + +// -Z unpretty=val -- present the input source, unstable (and less-pretty) variants; +// valid types are any of the types for `--pretty`, as well as: +// `expanded`, `expanded,identified`, +// `expanded,hygiene` (with internal representations), +// `everybody_loops` (all function bodies replaced with `loop {}`), +// `hir` (the HIR), `hir,identified`, +// `hir,typed` (HIR with types for each node), +// `hir-tree` (dump the raw HIR), +// `mir` (the MIR), or `mir-cfg` (graphviz formatted MIR) + +// argument to `pretty` must be one of `normal`, `expanded`, `identified`, or `expanded,identified` diff --git a/src/test/codegen/coverage-experiments/src/increment_intrinsic.rs b/src/test/codegen/coverage-experiments/src/increment_intrinsic.rs new file mode 100644 index 0000000000000..d4708cd367ff6 --- /dev/null +++ b/src/test/codegen/coverage-experiments/src/increment_intrinsic.rs @@ -0,0 +1,11 @@ +#![feature(core_intrinsics)] + +pub fn not_instrprof_increment(_hash: u64, _num_counters: u32, _index: u32) { +} + +fn main() { + // COMPARE THIS WITH INTRINSIC INSERTION + //not_instrprof_increment(1234 as u64, 314 as u32, 31 as u32); + + unsafe { core::intrinsics::instrprof_increment(1234 as u64, 314 as u32, 31 as u32) }; +} \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/just_main.rs b/src/test/codegen/coverage-experiments/src/just_main.rs new file mode 100644 index 0000000000000..081e5d72a6e0a --- /dev/null +++ b/src/test/codegen/coverage-experiments/src/just_main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("hello world! (should be covered)"); +} diff --git a/src/test/codegen/coverage-experiments/src/lazy_boolean.rs b/src/test/codegen/coverage-experiments/src/lazy_boolean.rs new file mode 100644 index 0000000000000..263277c7cdc4d --- /dev/null +++ b/src/test/codegen/coverage-experiments/src/lazy_boolean.rs @@ -0,0 +1,17 @@ +pub fn __llvm_incr_counter(_region_loc: &str) { +} + +#[inline(always)] +pub fn __incr_cov(region_loc: &str, result: T) -> T { + __llvm_incr_counter(region_loc); + result +} + +fn main() { + let a = 1; + let b = 10; + let c = 100; + let _result = __incr_cov("start", a < b) || __incr_cov("or", b < c); + + let _result = { let _t = a < b; __llvm_incr_counter("start"); _t } || { let _t = b < c; __llvm_incr_counter("start"); _t }; +} \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/loop_break_value.rs b/src/test/codegen/coverage-experiments/src/loop_break_value.rs new file mode 100644 index 0000000000000..76caa833ec4f8 --- /dev/null +++ b/src/test/codegen/coverage-experiments/src/loop_break_value.rs @@ -0,0 +1,15 @@ +pub fn __llvm_incr_counter(_region_loc: &str) { +} + +#[inline(always)] +pub fn __incr_cov(region_loc: &str, result: T) -> T { + __llvm_incr_counter(region_loc); + result +} + +fn main() { + __incr_cov("start", ()); + let _result = loop { + break __incr_cov("top of loop", true); + }; +} \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/match.rs b/src/test/codegen/coverage-experiments/src/match.rs new file mode 100644 index 0000000000000..afbb20888eab5 --- /dev/null +++ b/src/test/codegen/coverage-experiments/src/match.rs @@ -0,0 +1,22 @@ +pub fn __llvm_incr_counter(_region_loc: &str) { +} + +#[inline(always)] +pub fn __incr_cov(region_loc: &str, result: T) -> T { + __llvm_incr_counter(region_loc); + result +} + +fn main() { + let a = 1; + let b = 10; + let _result = match a < b { + true => true, + _ => false, + }; + + let _result = match __incr_cov("end of first match", a < b) { + true => __incr_cov("matched true", true), + _ => false, // counter expression "end of first match" - "matched true" + }; +} \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/match_with_increment.rs b/src/test/codegen/coverage-experiments/src/match_with_increment.rs new file mode 100644 index 0000000000000..f618b37ed5247 --- /dev/null +++ b/src/test/codegen/coverage-experiments/src/match_with_increment.rs @@ -0,0 +1,305 @@ +#![feature(core_intrinsics)] +//static TEST_FUNC_NAME: &'static [u8; 7] = b"main()\0"; + static TEST_FUNC_NAME: &'static [u8; 6] = b"main()"; +fn main() { + let a = 1; + let b = 10; + let _result = match { + let _t = a < b; + unsafe { core::intrinsics::instrprof_increment(TEST_FUNC_NAME as *const u8, 1234 as u64, 3 as u32, 0 as u32) }; + _t + } { + true => { + let _t = true; + unsafe { core::intrinsics::instrprof_increment(TEST_FUNC_NAME as *const u8, 1234 as u64, 3 as u32, 1 as u32) }; + _t + } + _ => false, + }; +} + +/* + +I NEED TO INSERT THE instrprof_increment() CALL: + + 1. JUST BEFORE THE switchInt(_4) (because we haven't counted entering the function main() yet, deferring that to "JUST BEFORE FIRST BRANCH") + 2. SOME TIME AFTER THE switchInt(_4), AND JUST BEFORE ANOTHER BRANCH (in this case, before "goto") + 2.a. NOT BEFORE BOTH GOTO'S AFTER switchInt(_4) (because one can be calculated by counter expression), BUT PERHAPS INSERT A noop PLACEHOLDER + AS A MARKER TO INCLUDE THE COVERAGE REGION AND REFERENCE THE COUNTERS TO BE SUBTRACTED (AND/OR SUMMED)? + + WHY DEFER INSERTING COUNTERS TO "JUST BEFORE FIRST BRANCH"? We can ignore panic/unwind() and only count if the coverage region ACTUALLY + executed in entirety. BUT IS THAT NECESSARY? IS IT MUCH EASIER TO INSERT COUNTERS AT THE TOP OF A REGION THAT MUST EXECUTE IN ENTIRETY IF + PANIC DOES NOT OCCUR? AND WHAT IF WE ADD SUPPORT FOR PANIC UNWIND (later)? + + IS THERE A BENEFIT OF THE DEFERRED APPROACH WHEN CONSIDERING EXPRESSIONS MAY HAVE EARLY RETURNS? (BECAUSE, WE STILL NEED TO COUNT THE REGION + LEADING UP TO THE EXPRESSION ANYWAY) + +================================================= +================================================= + +To inject an intrinsic after computing a final expression value of a coverage region: + +Replace the following basic block end (last statement plus terminator): + +... ... +StorageLive(_4) +StorageLive(_5) +_5 = _1 +StorageLive(_6) +_6 = _2 +_4 = Lt(move _5, move _6) +StorageDead(_6) +StorageDead(_5) + <------ to insert instrprof_increment() here +FakeRead(ForMatchedPlace, _4) +-------------------------------------------------------------------------------------- +switchInt(_4) + + +================================================= +Insert call to intrinsic with: + +StorageLive(_4) # _4 is now meant for deferred FakeRead(ForMatchdPlace, _4) in BasicBlock after increment() call +StorageLive(_5) # Unchanged except _4 is now _5 +StorageLive(_6) # Unchanged except _5 is now _6 +_6 = _1 # Unchanged except _5 is now _6 +StorageLive(_7) # Unchanged except _6 is now _7 +_7 = _2 # Unchanged except _6 is now _7 +_5 = Lt(move _6, move _7) # Unchanged except _4, _5, _6 is now _5, _6, _7 +StorageDead(_7) # Unchanged except _6 is now _7 +StorageDead(_6) # Unchanged except _5 is now _6 + +FakeRead(ForLet, _5) # CHANGED ForMatchedPlace to ForLet + +> # ALL NEW AND NECESSARY TO CALL instrprof_increment() +> StorageLive(_8) # ?? stores function pointer to instrprof_increment function? +> StorageLive(_9) +> StorageLive(_10) +> StorageLive(_11) +> _11 = const {alloc1+0: &&[u8; 6]} +> _10 = &raw const (*(*_11)) +> _9 = move _10 as *const u8 (Pointer(ArrayToPointer)) +> StorageDead(_10) +> StorageLive(_12) +> _12 = const 1234u64 +> StorageLive(_13) +> _13 = const 3u32 +> StorageLive(_14) +> _14 = const 0u32 +> -------------------------------------------------------------------------------------- +> _8 = const std::intrinsics::instrprof_increment(move _9, move _12, move _13, move _14) +> +> -> return +> +> StorageDead(_14) +> StorageDead(_13) +> StorageDead(_12) +> StorageDead(_9) +> StorageDead(_11) +> StorageDead(_8) + +_4 = _5 # ARE THESE LINES REDUNDANT? CAN I JUST PASS _5 DIRECTLY TO FakeRead()? +StorageDead(_5) # DROP "_t" temp result of `let _t = a < b` + # (NOTE THAT IF SO, I CAN REMOVE _5 altogether, and use _4, which coincidentally makes less changes) + # SEE BELOW + +FakeRead(ForMatchedPlace, _4) # Unchanged +-------------------------------------------------------------------------------------- +switchInt(_4) # Unchanged + + +================================================= +Can I skip the extra variable and insert call to intrinsic with: + +StorageLive(_4) # Unchanged +StorageLive(_5) # Unchanged +_5 = _1 # Unchanged +StorageLive(_6) # Unchanged +_6 = _2 # Unchanged +_4 = Lt(move _5, move _6) # Unchanged +StorageDead(_6) # Unchanged +StorageDead(_5) # Unchanged + +> # ALL NEW AND NECESSARY TO CALL instrprof_increment() +> FakeRead(ForLet, _4) # Save the post-increment result in temp "_t" +> StorageLive(_8) # ?? stores function pointer to instrprof_increment function? +> StorageLive(_9) +> StorageLive(_10) +> StorageLive(_11) +> _11 = const {alloc1+0: &&[u8; 6]} +> _10 = &raw const (*(*_11)) +> _9 = move _10 as *const u8 (Pointer(ArrayToPointer)) +> StorageDead(_10) +> StorageLive(_12) +> _12 = const 1234u64 +> StorageLive(_13) +> _13 = const 3u32 +> StorageLive(_14) +> _14 = const 0u32 +> -------------------------------------------------------------------------------------- +> _8 = const std::intrinsics::instrprof_increment(move _9, move _12, move _13, move _14) +> +> -> return +> +> StorageDead(_14) +> StorageDead(_13) +> StorageDead(_12) +> StorageDead(_9) +> StorageDead(_11) +> StorageDead(_8) + +FakeRead(ForMatchedPlace, _4) # Unchanged (PREVIOUSLY USED IN FakeRead(ForLet), is that OK?) +-------------------------------------------------------------------------------------- +switchInt(_4) # Unchanged + + + + + +================================================= +================================================= + +For the second inserted call to instrprof_increment, without that call we have: + +-------------------------------------------------------------------------------------- +switchInt(_4) # From above + +-> otherwise # that is, "NOT false" + +_3 = const true + <------ to insert instrprof_increment() here +-------------------------------------------------------------------------------------- +goto + +-> # No label. No condition, and not a "return" + +FakeRead(ForLet, _3) # NOTE: Unused result +StorageDead(_4) +_0 = () +StorageDead(_3) +StorageDead(_2) +StorageDead(_1) +-------------------------------------------------------------------------------------- +goto + +-> # No label. No condition, and not a "return" + +return # from main() + + +================================================= +With the call to increment(): + +-------------------------------------------------------------------------------------- +switchInt(_4) # From above + +-> otherwise # "NOT false" # UNCHANGED + +StorageLive(_15) # CHANGED! Allocated new storage (_15) for the result of match, if true. +_15 = const true # UNCHANGED except _3 is now _15 +FakeRead(ForLet, _15) # CHANGED! Assign value to temporary (to be assigned to _3 later) ... Do I need to do this? + +> # ALL NEW AND NECESSARY TO CALL instrprof_increment() +> StorageLive(_16) # pointer to instrprof_increment() function ? +> StorageLive(_17) +> StorageLive(_18) +> StorageLive(_19) +> _19 = const {alloc1+0: &&[u8; 6]} +> _18 = &raw const (*(*_19)) +> _17 = move _18 as *const u8 (Pointer(ArrayToPointer)) +> StorageDead(_18) +> StorageLive(_20) +> _20 = const 1234u64 +> StorageLive(_21) +> _21 = const 3u32 +> StorageLive(_22) +> _22 = const 1u32 +> -------------------------------------------------------------------------------------- +> _16 = const std::intrinsics::instrprof_increment(move _17, move _20, move _21, move _22) +> +> -> return +> +> StorageDead(_22) +> StorageDead(_21) +> StorageDead(_20) +> StorageDead(_17) +> StorageDead(_19) +> StorageDead(_16) +> _3 = _15 +> StorageDead(_15) + +--------------------------------# UNCHANGED------------------------------------------- +goto # UNCHANGED + +-> # UNCHANGED + +FakeRead(ForLet, _3) # UNCHANGED +StorageDead(_4) # UNCHANGED +_0 = () # UNCHANGED +StorageDead(_3) # UNCHANGED +StorageDead(_2) # UNCHANGED +StorageDead(_1) # UNCHANGED +-------------------------------------------------------------------------------------- +goto # UNCHANGED + +-> # UNCHANGED + +return # from main() # UNCHANGED + +================================================= +As before, can I skip the extra variable (_15) and insert the call to intrinsic with _3 directly?: + + +-------------------------------------------------------------------------------------- +switchInt(_4) # From above + +-> otherwise # "NOT false" # UNCHANGED + +_3 = const true # UNCHANGED? + +> # ALL NEW AND NECESSARY TO CALL instrprof_increment() +> StorageLive(_16) # pointer to instrprof_increment() function ? +> StorageLive(_17) +> StorageLive(_18) +> StorageLive(_19) +> _19 = const {alloc1+0: &&[u8; 6]} +> _18 = &raw const (*(*_19)) +> _17 = move _18 as *const u8 (Pointer(ArrayToPointer)) +> StorageDead(_18) +> StorageLive(_20) +> _20 = const 1234u64 +> StorageLive(_21) +> _21 = const 3u32 +> StorageLive(_22) +> _22 = const 1u32 +> -------------------------------------------------------------------------------------- +> _16 = const std::intrinsics::instrprof_increment(move _17, move _20, move _21, move _22) +> +> -> return +> +> StorageDead(_22) +> StorageDead(_21) +> StorageDead(_20) +> StorageDead(_17) +> StorageDead(_19) +> StorageDead(_16) + +--------------------------------# UNCHANGED------------------------------------------- +goto # UNCHANGED + +-> # UNCHANGED + +FakeRead(ForLet, _3) # UNCHANGED +StorageDead(_4) # UNCHANGED +_0 = () # UNCHANGED +StorageDead(_3) # UNCHANGED +StorageDead(_2) # UNCHANGED +StorageDead(_1) # UNCHANGED +-------------------------------------------------------------------------------------- +goto # UNCHANGED + +-> # UNCHANGED + +return # from main() # UNCHANGED + +*/ \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/match_with_increment_alt.rs b/src/test/codegen/coverage-experiments/src/match_with_increment_alt.rs new file mode 100644 index 0000000000000..60586967920cb --- /dev/null +++ b/src/test/codegen/coverage-experiments/src/match_with_increment_alt.rs @@ -0,0 +1,296 @@ +#![feature(core_intrinsics)] +//static TEST_FUNC_NAME: &'static [u8; 7] = b"main()\0"; + static TEST_FUNC_NAME: &'static [u8; 6] = b"main()"; +fn main() { + unsafe { core::intrinsics::instrprof_increment(TEST_FUNC_NAME as *const u8, 1234 as u64, 3 as u32, 0 as u32) }; + let a = 1; + let b = 10; + let _result = match a < b { + true => { + unsafe { core::intrinsics::instrprof_increment(TEST_FUNC_NAME as *const u8, 1234 as u64, 3 as u32, 1 as u32) }; + true + } + _ => false, + }; +} + +/* + +ALTERNATE APPROACH: + + IS IT MUCH EASIER TO INSERT COUNTERS AT THE TOP OF A REGION THAT MUST EXECUTE IN ENTIRETY IF + PANIC DOES NOT OCCUR? AND WHAT IF WE ADD SUPPORT FOR PANIC UNWIND (later)? + + IS THERE A DETRACTOR COMPARED TO THE DEFERRED APPROACH WHEN CONSIDERING EXPRESSIONS MAY HAVE EARLY RETURNS? + + (BECAUSE, WE STILL NEED TO COUNT THE REGION LEADING UP TO THE EXPRESSION ANYWAY) + +================================================= +================================================= + +To inject an intrinsic after computing a final expression value of a coverage region: + +Replace the following basic block end (last statement plus terminator): + +... ... +StorageLive(_4) +StorageLive(_5) +_5 = _1 +StorageLive(_6) +_6 = _2 +_4 = Lt(move _5, move _6) +StorageDead(_6) +StorageDead(_5) + <------ to insert instrprof_increment() here +FakeRead(ForMatchedPlace, _4) +-------------------------------------------------------------------------------------- +switchInt(_4) + + +================================================= +Insert call to intrinsic with: + +StorageLive(_4) # _4 is now meant for deferred FakeRead(ForMatchdPlace, _4) in BasicBlock after increment() call +StorageLive(_5) # Unchanged except _4 is now _5 +StorageLive(_6) # Unchanged except _5 is now _6 +_6 = _1 # Unchanged except _5 is now _6 +StorageLive(_7) # Unchanged except _6 is now _7 +_7 = _2 # Unchanged except _6 is now _7 +_5 = Lt(move _6, move _7) # Unchanged except _4, _5, _6 is now _5, _6, _7 +StorageDead(_7) # Unchanged except _6 is now _7 +StorageDead(_6) # Unchanged except _5 is now _6 + +FakeRead(ForLet, _5) # CHANGED ForMatchedPlace to ForLet + +> # ALL NEW AND NECESSARY TO CALL instrprof_increment() +> StorageLive(_8) # ?? stores function pointer to instrprof_increment function? +> StorageLive(_9) +> StorageLive(_10) +> StorageLive(_11) +> _11 = const {alloc1+0: &&[u8; 6]} +> _10 = &raw const (*(*_11)) +> _9 = move _10 as *const u8 (Pointer(ArrayToPointer)) +> StorageDead(_10) +> StorageLive(_12) +> _12 = const 1234u64 +> StorageLive(_13) +> _13 = const 3u32 +> StorageLive(_14) +> _14 = const 0u32 +> -------------------------------------------------------------------------------------- +> _8 = const std::intrinsics::instrprof_increment(move _9, move _12, move _13, move _14) +> +> -> return +> +> StorageDead(_14) +> StorageDead(_13) +> StorageDead(_12) +> StorageDead(_9) +> StorageDead(_11) +> StorageDead(_8) + +_4 = _5 # ARE THESE LINES REDUNDANT? CAN I JUST PASS _5 DIRECTLY TO FakeRead()? +StorageDead(_5) # DROP "_t" temp result of `let _t = a < b` + # (NOTE THAT IF SO, I CAN REMOVE _5 altogether, and use _4, which coincidentally makes less changes) + # SEE BELOW + +FakeRead(ForMatchedPlace, _4) # Unchanged +-------------------------------------------------------------------------------------- +switchInt(_4) # Unchanged + + +================================================= +Can I skip the extra variable and insert call to intrinsic with: + +StorageLive(_4) # Unchanged +StorageLive(_5) # Unchanged +_5 = _1 # Unchanged +StorageLive(_6) # Unchanged +_6 = _2 # Unchanged +_4 = Lt(move _5, move _6) # Unchanged +StorageDead(_6) # Unchanged +StorageDead(_5) # Unchanged + +> # ALL NEW AND NECESSARY TO CALL instrprof_increment() +> FakeRead(ForLet, _4) # Save the post-increment result in temp "_t" +> StorageLive(_8) # ?? stores function pointer to instrprof_increment function? +> StorageLive(_9) +> StorageLive(_10) +> StorageLive(_11) +> _11 = const {alloc1+0: &&[u8; 6]} +> _10 = &raw const (*(*_11)) +> _9 = move _10 as *const u8 (Pointer(ArrayToPointer)) +> StorageDead(_10) +> StorageLive(_12) +> _12 = const 1234u64 +> StorageLive(_13) +> _13 = const 3u32 +> StorageLive(_14) +> _14 = const 0u32 +> -------------------------------------------------------------------------------------- +> _8 = const std::intrinsics::instrprof_increment(move _9, move _12, move _13, move _14) +> +> -> return +> +> StorageDead(_14) +> StorageDead(_13) +> StorageDead(_12) +> StorageDead(_9) +> StorageDead(_11) +> StorageDead(_8) + +FakeRead(ForMatchedPlace, _4) # Unchanged (PREVIOUSLY USED IN FakeRead(ForLet), is that OK?) +-------------------------------------------------------------------------------------- +switchInt(_4) # Unchanged + + + + + +================================================= +================================================= + +For the second inserted call to instrprof_increment, without that call we have: + +-------------------------------------------------------------------------------------- +switchInt(_4) # From above + +-> otherwise # that is, "NOT false" + +_3 = const true + <------ to insert instrprof_increment() here +-------------------------------------------------------------------------------------- +goto + +-> # No label. No condition, and not a "return" + +FakeRead(ForLet, _3) # NOTE: Unused result +StorageDead(_4) +_0 = () +StorageDead(_3) +StorageDead(_2) +StorageDead(_1) +-------------------------------------------------------------------------------------- +goto + +-> # No label. No condition, and not a "return" + +return # from main() + + +================================================= +With the call to increment(): + +-------------------------------------------------------------------------------------- +switchInt(_4) # From above + +-> otherwise # "NOT false" # UNCHANGED + +StorageLive(_15) # CHANGED! Allocated new storage (_15) for the result of match, if true. +_15 = const true # UNCHANGED except _3 is now _15 +FakeRead(ForLet, _15) # CHANGED! Assign value to temporary (to be assigned to _3 later) ... Do I need to do this? + +> # ALL NEW AND NECESSARY TO CALL instrprof_increment() +> StorageLive(_16) # pointer to instrprof_increment() function ? +> StorageLive(_17) +> StorageLive(_18) +> StorageLive(_19) +> _19 = const {alloc1+0: &&[u8; 6]} +> _18 = &raw const (*(*_19)) +> _17 = move _18 as *const u8 (Pointer(ArrayToPointer)) +> StorageDead(_18) +> StorageLive(_20) +> _20 = const 1234u64 +> StorageLive(_21) +> _21 = const 3u32 +> StorageLive(_22) +> _22 = const 1u32 +> -------------------------------------------------------------------------------------- +> _16 = const std::intrinsics::instrprof_increment(move _17, move _20, move _21, move _22) +> +> -> return +> +> StorageDead(_22) +> StorageDead(_21) +> StorageDead(_20) +> StorageDead(_17) +> StorageDead(_19) +> StorageDead(_16) +> _3 = _15 +> StorageDead(_15) + +--------------------------------# UNCHANGED------------------------------------------- +goto # UNCHANGED + +-> # UNCHANGED + +FakeRead(ForLet, _3) # UNCHANGED +StorageDead(_4) # UNCHANGED +_0 = () # UNCHANGED +StorageDead(_3) # UNCHANGED +StorageDead(_2) # UNCHANGED +StorageDead(_1) # UNCHANGED +-------------------------------------------------------------------------------------- +goto # UNCHANGED + +-> # UNCHANGED + +return # from main() # UNCHANGED + +================================================= +As before, can I skip the extra variable (_15) and insert the call to intrinsic with _3 directly?: + + +-------------------------------------------------------------------------------------- +switchInt(_4) # From above + +-> otherwise # "NOT false" # UNCHANGED + +_3 = const true # UNCHANGED? + +> # ALL NEW AND NECESSARY TO CALL instrprof_increment() +> StorageLive(_16) # pointer to instrprof_increment() function ? +> StorageLive(_17) +> StorageLive(_18) +> StorageLive(_19) +> _19 = const {alloc1+0: &&[u8; 6]} +> _18 = &raw const (*(*_19)) +> _17 = move _18 as *const u8 (Pointer(ArrayToPointer)) +> StorageDead(_18) +> StorageLive(_20) +> _20 = const 1234u64 +> StorageLive(_21) +> _21 = const 3u32 +> StorageLive(_22) +> _22 = const 1u32 +> -------------------------------------------------------------------------------------- +> _16 = const std::intrinsics::instrprof_increment(move _17, move _20, move _21, move _22) +> +> -> return +> +> StorageDead(_22) +> StorageDead(_21) +> StorageDead(_20) +> StorageDead(_17) +> StorageDead(_19) +> StorageDead(_16) + +--------------------------------# UNCHANGED------------------------------------------- +goto # UNCHANGED + +-> # UNCHANGED + +FakeRead(ForLet, _3) # UNCHANGED +StorageDead(_4) # UNCHANGED +_0 = () # UNCHANGED +StorageDead(_3) # UNCHANGED +StorageDead(_2) # UNCHANGED +StorageDead(_1) # UNCHANGED +-------------------------------------------------------------------------------------- +goto # UNCHANGED + +-> # UNCHANGED + +return # from main() # UNCHANGED + +*/ \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/match_without_increment.mir b/src/test/codegen/coverage-experiments/src/match_without_increment.mir new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/test/codegen/coverage-experiments/src/match_without_increment.rs b/src/test/codegen/coverage-experiments/src/match_without_increment.rs new file mode 100644 index 0000000000000..fa85833e05434 --- /dev/null +++ b/src/test/codegen/coverage-experiments/src/match_without_increment.rs @@ -0,0 +1,5 @@ +fn main() { + let a = 1; + let b = 10; + let _result = match a < b { true => true, _ => false, }; +} \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/match_without_increment_alt.mir b/src/test/codegen/coverage-experiments/src/match_without_increment_alt.mir new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/test/codegen/coverage-experiments/src/question_mark_err_status_handling_with_comments.rs b/src/test/codegen/coverage-experiments/src/question_mark_err_status_handling_with_comments.rs new file mode 100644 index 0000000000000..03d11b2c230ca --- /dev/null +++ b/src/test/codegen/coverage-experiments/src/question_mark_err_status_handling_with_comments.rs @@ -0,0 +1,24 @@ +/* */ #[inline(always)] +/* */ pub fn __incr_cov(_region_loc: &str, /*index: u32,*/ result: T) -> T { +/* */ result +/* */ } +/* */ +/* - */ fn main() { +/* : I */ for countdown in __incr_cov("start", 10..0) { // span is just the while test expression +/* : ┃ */ let _ = countdown; +/* : ┃ */ __incr_cov("top of for", ()); +/* ┃ - */ } +/* - */ } + + +// -Z unpretty=val -- present the input source, unstable (and less-pretty) variants; +// valid types are any of the types for `--pretty`, as well as: +// `expanded`, `expanded,identified`, +// `expanded,hygiene` (with internal representations), +// `everybody_loops` (all function bodies replaced with `loop {}`), +// `hir` (the HIR), `hir,identified`, +// `hir,typed` (HIR with types for each node), +// `hir-tree` (dump the raw HIR), +// `mir` (the MIR), or `mir-cfg` (graphviz formatted MIR) + +// argument to `pretty` must be one of `normal`, `expanded`, `identified`, or `expanded,identified` diff --git a/src/test/codegen/coverage-experiments/src/while.rs b/src/test/codegen/coverage-experiments/src/while.rs new file mode 100644 index 0000000000000..3cb185eda544f --- /dev/null +++ b/src/test/codegen/coverage-experiments/src/while.rs @@ -0,0 +1,23 @@ +#[inline(always)] +pub fn __incr_cov(_region_loc: &str, result: T) -> T { + result +} + +fn main() { + let mut countdown = 10; + __incr_cov("block start",()); + while __incr_cov("while test", countdown > 0) { + countdown -= 1; + } + + let mut countdown = 10; + __incr_cov("after first while loop",()); + while __incr_cov("while test", countdown > 0) { + countdown -= 1; + if countdown < 5 { + __incr_cov("top of if countdown < 5",()); + break; + } + countdown -= 2; + } +} \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/while_clean.rs b/src/test/codegen/coverage-experiments/src/while_clean.rs new file mode 100644 index 0000000000000..e9ed1efc220d4 --- /dev/null +++ b/src/test/codegen/coverage-experiments/src/while_clean.rs @@ -0,0 +1,6 @@ +fn main() { + let mut countdown = 10; + while countdown > 0 { + countdown -= 1; + } +} \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/while_early_return.rs b/src/test/codegen/coverage-experiments/src/while_early_return.rs new file mode 100644 index 0000000000000..35709ffba3a04 --- /dev/null +++ b/src/test/codegen/coverage-experiments/src/while_early_return.rs @@ -0,0 +1,10 @@ +fn main() -> u8 { // this will lower to HIR but will not compile: `main` can only return types that implement `std::process::Termination` + let mut countdown = 10; + while countdown > 0 { + if false { + return if countdown > 8 { 1 } else { return 2; }; + } + countdown -= 1; + } + 0 +} \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/while_with_comments.rs b/src/test/codegen/coverage-experiments/src/while_with_comments.rs new file mode 100644 index 0000000000000..56417fedf00df --- /dev/null +++ b/src/test/codegen/coverage-experiments/src/while_with_comments.rs @@ -0,0 +1,51 @@ +/* */ #[inline(always)] +/* */ pub fn __incr_cov(_region_loc: &str, /*index: u32,*/ result: T) -> T { +/* */ result +/* */ } +/* */ +/* - */ fn main() { +/* ┃ */ let mut countdown = 10; +/* ┃ */ __incr_cov("block start",()); // Must increment before repeated while text expression +/* : I */ while __incr_cov("while test", countdown > 0) { // span is just the while test expression +/* : ┃ */ countdown -= 1; +/* : ┃ */ // __incr_cov("while loop",()); // Counter not needed, but span is computed as "while test" minus "block start" +/* : ┃ */ // If while criteria is tested 11 times, and the outer block runs only once, 11-1 = 10 +/* : ┃ */ // REMOVING COUNTER ASSUMES NO EARLY RETURN THOUGH. +/* : ┃ */ // I THINK WE CAN ONLY USE THE COUNTER EXPRESSION UP TO FIRST CONDITIONAL BLOCK, IF ANY (if, match, maybe any loop) +/* ┃ - */ } + + let mut countdown = 10; + __incr_cov("after first while loop",()); + while __incr_cov("while test", countdown > 0) { + countdown -= 1; + // if __incr_cov("top of while loop", countdown < 5) { + if countdown < 5 { // "top of while loop" = counter expression "while test" - "after first while loop" + __incr_cov("top of if countdown < 5",()); + break; + } + countdown -= 2; + // __incr_cov("after if countdown < 5 block", ()); + // "after if countdown < 5 block" = counter expression "top of while loop" - "top of if countdown < 5" + // HOWEVER, WE CAN ONLY REMOVE THE COUNTER AND USE COUNTER EXPRESSION IF WE **KNOW** THAT THE BODY OF THE IF + // WILL **ALWAYS** BREAK (OR RETURN, OR CONTINUE?) + // AND THUS WE TREAT THE STATEMENTS FOLLOWING THE IF BLOCK AS IF THEY WERE AN ELSE BLOCK. + // THAT'S A LOT TO ASK. + + // PERHAPS TREAT EARLY RETURNS AS A SPECIAL KIND OF COUNTER AND IF ANY ARE INVOKED BEFORE STATEMENTS AFTER THE BLOCK THAT CONTAINS THEM, + // THEN SUBTRACT THOSE COUNTS FROM THE COUNT BEFORE THE BLOCK (AS WE DO HERE)? (SO ONE SET OF EXPRESSIONS MUST SUM ALL OF THE EARLY + // RETURNS) + } +/* - */ } + + +// -Z unpretty=val -- present the input source, unstable (and less-pretty) variants; +// valid types are any of the types for `--pretty`, as well as: +// `expanded`, `expanded,identified`, +// `expanded,hygiene` (with internal representations), +// `everybody_loops` (all function bodies replaced with `loop {}`), +// `hir` (the HIR), `hir,identified`, +// `hir,typed` (HIR with types for each node), +// `hir-tree` (dump the raw HIR), +// `mir` (the MIR), or `mir-cfg` (graphviz formatted MIR) + +// argument to `pretty` must be one of `normal`, `expanded`, `identified`, or `expanded,identified` From 088037a04414dee0b3a792a6fad193e081ee8e37 Mon Sep 17 00:00:00 2001 From: Rich Kadel Date: Thu, 4 Jun 2020 17:47:21 -0700 Subject: [PATCH 02/14] explained lang_item function body (count_code_region) --- src/libcore/intrinsics.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libcore/intrinsics.rs b/src/libcore/intrinsics.rs index abb35e838ea28..0e5af35229ca0 100644 --- a/src/libcore/intrinsics.rs +++ b/src/libcore/intrinsics.rs @@ -1943,6 +1943,12 @@ extern "rust-intrinsic" { pub fn miri_start_panic(payload: *mut u8) -> !; } +// Since `count_code_region` is lang_item, it must have a function body that the compiler can use +// to register its DefId with the lang_item entry. This function body is never actually called +// (and is therefore implemented as an aborting stub) because it is replaced with the +// LLVM intrinsic `llvm.instrprof.increment` by +// `rustc_codegen_llvm::intrinsic::IntrinsicCallMethods::codegen_intrinsic_call()`. +#[doc(hidden)] #[cfg(not(bootstrap))] #[cfg_attr(not(bootstrap), lang = "count_code_region")] pub fn count_code_region(_index: u32) { From 2c5c2a6bc2f7023ee8ad252d2ee5a45fbfb2de22 Mon Sep 17 00:00:00 2001 From: Rich Kadel Date: Thu, 4 Jun 2020 17:52:27 -0700 Subject: [PATCH 03/14] removed experiments for cleaner github PR --- src/libcore/intrinsics.rs | 17 +- .../codegen/coverage-experiments/Cargo.lock | 5 - .../codegen/coverage-experiments/Cargo.toml | 103 ----- .../README-THIS-IS-TEMPORARY.md | 157 -------- .../src/coverage_injection_test.rs | 335 ---------------- .../src/coverage_injection_test2.rs | 320 ---------------- .../src/coverage_injection_test_alt.rs | 362 ------------------ .../coverage-experiments/src/drop_trait.rs | 25 -- .../src/drop_trait_with_comments_prints.rs | 53 --- .../codegen/coverage-experiments/src/for.rs | 41 -- .../src/for_with_comments.rs | 24 -- .../codegen/coverage-experiments/src/if.rs | 80 ---- .../src/if_with_comments.rs | 39 -- .../src/increment_intrinsic.rs | 11 - .../coverage-experiments/src/just_main.rs | 3 - .../coverage-experiments/src/lazy_boolean.rs | 17 - .../src/loop_break_value.rs | 15 - .../codegen/coverage-experiments/src/match.rs | 22 -- .../src/match_with_increment.rs | 305 --------------- .../src/match_with_increment_alt.rs | 296 -------------- .../src/match_without_increment.mir | 0 .../src/match_without_increment.rs | 5 - .../src/match_without_increment_alt.mir | 0 ..._mark_err_status_handling_with_comments.rs | 24 -- .../codegen/coverage-experiments/src/while.rs | 23 -- .../coverage-experiments/src/while_clean.rs | 6 - .../src/while_early_return.rs | 10 - .../src/while_with_comments.rs | 51 --- 28 files changed, 9 insertions(+), 2340 deletions(-) delete mode 100644 src/test/codegen/coverage-experiments/Cargo.lock delete mode 100644 src/test/codegen/coverage-experiments/Cargo.toml delete mode 100644 src/test/codegen/coverage-experiments/README-THIS-IS-TEMPORARY.md delete mode 100644 src/test/codegen/coverage-experiments/src/coverage_injection_test.rs delete mode 100644 src/test/codegen/coverage-experiments/src/coverage_injection_test2.rs delete mode 100644 src/test/codegen/coverage-experiments/src/coverage_injection_test_alt.rs delete mode 100644 src/test/codegen/coverage-experiments/src/drop_trait.rs delete mode 100644 src/test/codegen/coverage-experiments/src/drop_trait_with_comments_prints.rs delete mode 100644 src/test/codegen/coverage-experiments/src/for.rs delete mode 100644 src/test/codegen/coverage-experiments/src/for_with_comments.rs delete mode 100644 src/test/codegen/coverage-experiments/src/if.rs delete mode 100644 src/test/codegen/coverage-experiments/src/if_with_comments.rs delete mode 100644 src/test/codegen/coverage-experiments/src/increment_intrinsic.rs delete mode 100644 src/test/codegen/coverage-experiments/src/just_main.rs delete mode 100644 src/test/codegen/coverage-experiments/src/lazy_boolean.rs delete mode 100644 src/test/codegen/coverage-experiments/src/loop_break_value.rs delete mode 100644 src/test/codegen/coverage-experiments/src/match.rs delete mode 100644 src/test/codegen/coverage-experiments/src/match_with_increment.rs delete mode 100644 src/test/codegen/coverage-experiments/src/match_with_increment_alt.rs delete mode 100644 src/test/codegen/coverage-experiments/src/match_without_increment.mir delete mode 100644 src/test/codegen/coverage-experiments/src/match_without_increment.rs delete mode 100644 src/test/codegen/coverage-experiments/src/match_without_increment_alt.mir delete mode 100644 src/test/codegen/coverage-experiments/src/question_mark_err_status_handling_with_comments.rs delete mode 100644 src/test/codegen/coverage-experiments/src/while.rs delete mode 100644 src/test/codegen/coverage-experiments/src/while_clean.rs delete mode 100644 src/test/codegen/coverage-experiments/src/while_early_return.rs delete mode 100644 src/test/codegen/coverage-experiments/src/while_with_comments.rs diff --git a/src/libcore/intrinsics.rs b/src/libcore/intrinsics.rs index 0e5af35229ca0..06a432a26961e 100644 --- a/src/libcore/intrinsics.rs +++ b/src/libcore/intrinsics.rs @@ -1943,16 +1943,17 @@ extern "rust-intrinsic" { pub fn miri_start_panic(payload: *mut u8) -> !; } -// Since `count_code_region` is lang_item, it must have a function body that the compiler can use -// to register its DefId with the lang_item entry. This function body is never actually called -// (and is therefore implemented as an aborting stub) because it is replaced with the -// LLVM intrinsic `llvm.instrprof.increment` by -// `rustc_codegen_llvm::intrinsic::IntrinsicCallMethods::codegen_intrinsic_call()`. -#[doc(hidden)] +/// Defines the `count_code_region` intrinsic as a `LangItem`. `LangItem`s require a function body +/// to register its DefId with the LangItem entry. The function body is never actually called (and +/// is therefore implemented as an aborting stub) because it is replaced with the LLVM intrinsic +/// `llvm.instrprof.increment` by +/// `rustc_codegen_llvm::intrinsic::IntrinsicCallMethods::codegen_intrinsic_call()`. #[cfg(not(bootstrap))] #[cfg_attr(not(bootstrap), lang = "count_code_region")] -pub fn count_code_region(_index: u32) { - #[cfg_attr(not(bootstrap), allow(unused_unsafe))] // remove `unsafe` on bootstrap bump +fn count_code_region(_index: u32) { + // remove `unsafe` (and safety comment) on bootstrap bump + #[cfg_attr(not(bootstrap), allow(unused_unsafe))] + // SAFETY: the `abort` intrinsic has no requirements to be called. unsafe { abort() } diff --git a/src/test/codegen/coverage-experiments/Cargo.lock b/src/test/codegen/coverage-experiments/Cargo.lock deleted file mode 100644 index 132469cbb182c..0000000000000 --- a/src/test/codegen/coverage-experiments/Cargo.lock +++ /dev/null @@ -1,5 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "coverage_experiments" -version = "0.1.0" diff --git a/src/test/codegen/coverage-experiments/Cargo.toml b/src/test/codegen/coverage-experiments/Cargo.toml deleted file mode 100644 index 296a8d5c9af2d..0000000000000 --- a/src/test/codegen/coverage-experiments/Cargo.toml +++ /dev/null @@ -1,103 +0,0 @@ -[workspace] - -[package] -name = "coverage_experiments" -version = "0.1.0" -license = "BSD-3-Clause" -authors = ["rust-fuchsia@fuchsia.com"] -edition = "2018" - -[[bin]] - -name = "coverage_injection_test" -path = "src/coverage_injection_test.rs" - -[[bin]] - -name = "coverage_injection_test2" -path = "src/coverage_injection_test2.rs" - -[[bin]] - -name = "while" -path = "src/while.rs" - -[[bin]] - -name = "while_clean" -path = "src/while_clean.rs" - -[[bin]] - -name = "while_early_return" -path = "src/while_early_return.rs" - -[[bin]] - -name = "if_with_comments" -path = "src/if_with_comments.rs" - -[[bin]] - -name = "if" -path = "src/if.rs" - -[[bin]] - -name = "increment_intrinsic" -path = "src/increment_intrinsic.rs" - -[[bin]] - -name = "just_main" -path = "src/just_main.rs" - -[[bin]] - -name = "lazy_boolean" -path = "src/lazy_boolean.rs" - -[[bin]] - -name = "match" -path = "src/match.rs" - -[[bin]] - -name = "match_without_increment" -path = "src/match_without_increment.rs" # identical to -Zunpretty=hir output - -[[bin]] - -name = "match_with_increment" -path = "src/match_with_increment.rs" - -[[bin]] - -name = "match_with_increment_alt" -path = "src/match_with_increment_alt.rs" - -[[bin]] - -name = "loop_break_value" -path = "src/loop_break_value.rs" - -[[bin]] - -name = "for_with_comments" -path = "src/for_with_comments.rs" - -[[bin]] - -name = "for" -path = "src/for.rs" - -[[bin]] - -name = "drop_trait" -path = "src/drop_trait.rs" - -#[dependencies] # Should not need to manually add coverage dependencies -#version = "0.1.0" -#path = "../__builtin" # for mod __builtin::coverage - diff --git a/src/test/codegen/coverage-experiments/README-THIS-IS-TEMPORARY.md b/src/test/codegen/coverage-experiments/README-THIS-IS-TEMPORARY.md deleted file mode 100644 index 3b69c0a406594..0000000000000 --- a/src/test/codegen/coverage-experiments/README-THIS-IS-TEMPORARY.md +++ /dev/null @@ -1,157 +0,0 @@ -# codegen/coverage-experiments -*

THIS DIRECTORY IS TEMPORARY

* - -This directory contains some work-in-progress (WIP) code used for experimental development and -testing of Rust Coverage feature development. - -The code in this directory will be removed, or migrated into product tests, when the Rust -Coverage feature is complete. - -[TOC] - -## Development Notes - -### config.toml - -config.toml probably requires (I should verify that intrinsic `llvm.instrprof.increment` -code generation ONLY works with this config option): - - profiler = true - -## First build - -```shell -./x.py clean -./x.py build -i --stage 1 src/libstd -``` - -## Incremental builds *IF POSSIBLE!* - -```shell -./x.py build -i --stage 1 src/libstd --keep-stage 1 -``` - -*Note: Some changes made for Rust Coverage required the full build (without `--keep-stage 1`), and in some cases, required `./x.py clean` first!. Occassionally I would get errors when building or when compiling a test program with `--Zinstrument-coverage` that work correctly only after a full clean and build.* - -## Compile a test program with LLVM coverage instrumentation - -*Note: This PR is still a work in progress. At the time of this writing, the `llvm.instrprof.increment` intrinsic is injected, and recognized by the LLVM code generation stage, but it does not appear to be included in the final binary. This is not surprising since other steps are still to be implemented, such as generating the coverage map. See the suggested additional `llvm` flags for ways to verify the `llvm` passes at least get the right intrinsic.* - -Suggested debug configuration to confirm Rust coverage features: -```shell -$ export RUSTC_LOG=rustc_codegen_llvm::intrinsic,rustc_mir::transform::instrument_coverage=debug -``` - -Ensure the new compiled `rustc` is used (the path below, relative to the `rust` code repository root, is an example only): - -```shell -$ build/x86_64-unknown-linux-gnu/stage1/bin/rustc \ - src/test/codegen/coverage-experiments/just_main.rs \ - -Zinstrument-coverage -``` - -### About the test programs in coverage-experiments/src/ - -The `coverage-experiments/src/` directory contains some sample (and very simple) Rust programs used to analyze Rust compiler output at various stages, with or without the Rust code coverage compiler option. For now, these are only used for the in-progress development and will be removed at a future date. (These are *not* formal test programs.) - -The src director may also contain some snapshots of mir output from experimentation, particularly if the saved snapshots highlight results that are important to the future development, individually or when compared with other output files. - -Be aware that some of the files and/or comments may be outdated. - -### Additional `llvm` flags (append to the `rustc` command) - -These optional flags generate additional files and/or terminal output. LLVM's `-print-before=all` should show the `instrprof.increment` intrinsic with arguments computed by the experimental Rust coverage feature code: - -```shell - --emit llvm-ir \ - -Zverify-llvm-ir \ - -Zprint-llvm-passes \ - -Csave-temps \ - -Cllvm-args=-print-before-all -``` - -### Additional flags for MIR analysis and transforms - -These optional flags generate a directory with many files representing the MIR as text (`.mir` files) and as a visual graph (`.dot` files) rendered by `graphviz`. (**Some IDEs, such as `VSCode` have `graphviz` extensions.**) - -```shell - -Zdump-mir=main \ - -Zdump-mir-graphviz -``` - -### Flags I've used but appear to be irrelvant to `-Zinstrument-coverage` after all: -```shell - # -Zprofile - # -Ccodegen-units=1 - # -Cinline-threshold=0 - # -Clink-dead-code - # -Coverflow-checks=off -``` - -## Run the test program compiled with code coverage instrumentation (maybe): - -As stated above, at the time of this writing, this work-in-progress seems to generate `llvm.instrprof.increment` intrinsic calls correctly, and are visibile in early `llvm` code generation passes, but are eventually stripped. - -The test program should run as expected, currently does not generate any coverage output. - -*Example:* - -```shell - $ src/test/codegen/coverage-experiments/just_main - hello world! (should be covered) -``` - -### Running the coverage-enabled `rustc` compiler in the `lldb` debugger: - -For example, to verify the intrinsic is codegen'ed, set breakpoint in `lldb` where it validates a certain instruction is the `llvm.instrprof.increment` instruction. - -First, update config.toml for debugging: - -```toml - [llvm] - optimize = false - release-debuginfo = true - - [rust] - debug = true - debuginfo-level = 2 -``` - -*(Note, in case this is relevant after all, I also have the following changes; but I don't think I need them:)* - -```toml - # Add and uncomment these if relevant/useful: - # codegen-units = 0 - # python = '/usr/bin/python3.6' -``` - -Run the compiler with additional flags as needed: - -```shell -lldb \ - build/x86_64-unknown-linux-gnu/stage1/bin/rustc \ - -- \ - src/test/codegen/coverage-experiments/just_main.rs \ - -Zinstrument-coverage \ - -Zdump-mir=main \ - -Zdump-mir-graphviz -``` - -Note the specific line numbers may be different: - -```c++ -(lldb) b lib/Transforms/Instrumentation/InstrProfiling.cpp:418 -(lldb) r - -Process 93855 stopped -* thread #6, name = 'rustc', stop reason = breakpoint 2.1 - frame #0: 0x00007fffedff7738 librustc_driver-5a0990d8d18fb2b4.so`llvm::InstrProfiling::lowerIntrinsics(this=0x00007fffcc001d40, F=0x00007fffe4552198) at InstrProfiling.cpp:418:23 - 415 auto Instr = I++; - 416 InstrProfIncrementInst *Inc = castToIncrementInst(&*Instr); - 417 if (Inc) { --> 418 lowerIncrement(Inc); - 419 MadeChange = true; - 420 } else if (auto *Ind = dyn_cast(Instr)) { - 421 lowerValueProfileInst(Ind); -(lldb) -``` \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/coverage_injection_test.rs b/src/test/codegen/coverage-experiments/src/coverage_injection_test.rs deleted file mode 100644 index 231da1dc1a67f..0000000000000 --- a/src/test/codegen/coverage-experiments/src/coverage_injection_test.rs +++ /dev/null @@ -1,335 +0,0 @@ -/* */ use std::io::Error; -/* */ use std::io::ErrorKind; -/* */ -/* */ /// Align Rust counter increment with with: -/* */ /// [‘llvm.instrprof.increment’ Intrinsic](https://llvm.org/docs/LangRef.html#llvm-instrprof-increment-intrinsic) -/* */ /// -/* */ /// declare void @llvm.instrprof.increment(i8* , i64 , i32 , i32 ) -/* */ /// -/* */ /// The first argument is a pointer to a global variable containing the name of the entity -/* */ /// being instrumented. This should generally be the (mangled) function name for a set of -/* */ /// counters. -/* */ /// -/* */ /// The second argument is a hash value that can be used by the consumer of the profile data -/* */ /// to detect changes to the instrumented source, and the third is the number of counters -/* */ /// associated with name. It is an error if hash or num-counters differ between two -/* */ /// instances of instrprof.increment that refer to the same name. -/* */ /// -/* */ /// The last argument refers to which of the counters for name should be incremented. It -/* */ /// should be a value between 0 and num-counters. -/* */ /// -/* */ /// # Arguments -/* */ /// -/* */ /// `mangled_fn_name` - &'static ref to computed and injected static str, using: -/* */ /// -/* */ /// ``` -/* */ /// fn rustc_symbol_mangling::compute_symbol_name( -/* */ /// tcx: TyCtxt<'tcx>, -/* */ /// instance: Instance<'tcx>, -/* */ /// compute_instantiating_crate: impl FnOnce() -> CrateNum, -/* */ /// ) -> String -/* */ /// ``` -/* */ /// -/* */ /// `source_version_hash` - Compute hash based that only changes if there are "significant" -/* */ /// to control-flow inside the function. -/* */ /// -/* */ /// `num_counters` - The total number of counter calls [MAX(counter_index) + 1] within the -/* */ /// function. -/* */ /// -/* */ /// `counter_index` - zero-based counter index scoped by the function. (Ordering of -/* */ /// counters, relative to the source code location, is apparently not expected.) -/* */ /// -/* */ /// # Notes -/* */ /// -/* */ /// * The mangled_fn_name may not be computable until generics are monomorphized (see -/* */ /// parameters required by rustc_symbol_mangling::compute_symbol_name). -/* */ /// * The version hash may be computable from AST analysis, and may not benefit from further -/* */ /// lowering. -/* */ /// * num_counters depends on having already identified all counter insertion locations. -/* */ /// * counter_index can be computed at time of counter insertion (incrementally). -/* */ /// * Numeric parameters are signed to match the llvm increment intrinsic parameter types. -/* */ fn __lower_incr_cov(_mangled_fn_name: &'static str, _fn_version_hash: i64, _num_counters: i32, _counter_index: i32) { -/* */ } -/* */ -/* */ /// A coverage counter implementation that will work as both an intermediate coverage -/* */ /// counting and reporting implementation at the AST-level only--for debugging and -/* */ /// development--but also serves as a "marker" to be replaced by calls to LLVM -/* */ /// intrinsic coverage counter APIs during the lowering process. -/* */ /// -/* */ /// Calls to this function will be injected automatically into the AST. When LLVM intrinsics -/* */ /// are enabled, the counter function calls that were injected into the AST serve as -/* */ /// placeholders, to be replaced by an alternative, such as: -/* */ /// -/* */ /// * direct invocation of the `llvm.instrprof.increment()` intrinsic; or -/* */ /// * the `__lower_incr_cov()` function, defined above, that would invoke the -/* */ /// `llvm.instrprof.increment()` intrinsic; or -/* */ /// * a similar expression wrapper, with the additional parameters (as defined above -/* */ /// for `__lower_incr_cov()`, that invokes `llvm.instrprof.increment()` and returns the -/* */ /// result of the wrapped expression) -/* */ /// -/* */ /// The first two options would require replacing the inlined wrapper call with something -/* */ /// like: -/* */ /// -/* */ /// ``` -/* */ /// { let result = {expr}; __inlined_incr_cov(context, counter); result } -/* */ /// ``` -/* */ /// -/* */ /// But if the lowering process is already unwrapping the inlined call to `__incr_cov()`, then -/* */ /// it may be a perfect opportunity to replace the function with one of these more -/* */ /// direct methods. -/* */ /// -/* */ #[inline(always)] -/* */ pub fn __incr_cov(region_loc: &str, /*index: u32,*/ result: T) -> T { -/* */ // Either call the intermediate non-llvm coverage counter API or -/* */ // replace the call to this function with the expanded `__lower_incr_cov()` call. -/* */ -/* */ // let _lock = increment_counter(counter); -/* */ println!("{}", region_loc); -/* */ -/* */ result -/* */ } -/* */ -/* */ /// Write a report identifying each incremented counter and the number of times each counter -/* */ /// was incremented. -/* */ fn __report() { -/* */ println!("WRITE REPORT!"); -/* */ } -/* */ -/* */ /// Increment the counter after evaluating the wrapped expression (see `__incr_cov()`), then -/* */ /// write a report identifying each incremented counter and the number of times each counter -/* */ /// was incremented. -/* */ #[inline(always)] -/* */ pub fn __incr_cov_and_report(region_loc: &str, /*counter: u32,*/ result: T) -> T { -/* */ __incr_cov(region_loc, /*counter,*/ ()); -/* */ __report(); -/* */ result -/* */ } -/* */ -/* */ macro_rules! from { -/* */ ($from:expr) => { &format!("from: {}\n to: {}:{}:{}", $from, file!(), line!(), column!()) }; -/* */ } -/* */ -/* */ #[derive(Debug)] -/* */ enum TestEnum { -/* */ Red, -/* */ Green, -/* */ Blue, -/* */ } -/* */ -/* */ struct TestStruct { -/* */ field: i32, -/* */ } -/* */ -/* */ // IMPORTANT! IS WRAPPING main() ENOUGH? OR DO I ALSO NEED TO WRAP THREAD FUNCTIONS, ASSUMING -/* */ // THEY ARE STILL RUNNING WITH MAIN EXITS? (IF THEY CAN). NOT SURE HOW RUST HANDLES THAT. -/* */ -/* */ // I SUSPECT USING THREAD_LOCAL COUNTERS MAY NOT ACTUALLY BE AN OPTIMIZATION OVER MUTEX LOCKS, -/* */ // BUT MAYBE I SHOULD ASK. -/* */ -/* */ impl TestStruct { -/* - */ fn new() -> Self { -/* ┃ */ __incr_cov(from!("fn new()"),Self::new_with_value(31415)) // function-scoped counter index = 0 -/* - */ } -/* */ -/* - */ fn new_with_value(field: i32) -> Self { -/* ┃ */ __incr_cov(from!("fn new_with_value()"),Self { -/* ┃ */ field, -/* ┃ */ }) // function-scoped counter index = 0 -/* - */ } -/* */ -/* */ fn call_closure(&self, closure: F) -> bool -/* */ where -/* */ F: FnOnce( -/* */ i32, -/* */ ) -> bool, -/* - */ { -/* ┃ */ __incr_cov(from!("fn call_closure()"),closure(123)) // function-scoped counter index = 0 -/* - */ } -/* */ -/* - */ fn various(&self) -> Result<(),Error> { -/* ┃ */ use TestEnum::*; -/* ┃ */ let mut color = Red; -/* ┃ */ let _ = color; -/* ┃ */ color = Blue; -/* ┃ */ let _ = color; -/* ┃ */ color = Green; -/* ┃ */ match __incr_cov(from!("fn various"),color) { // function-scoped counter index = 0 -/* : */ -/* : */ // !!! RECORD SPAN FROM START OF INNERMOST CONTAINING BLOCK (THE FUNCTION IN THIS CASE) TO END OF MATCH EXPRESSION -/* : */ // If `match`, `while`, `loop`, `for`, `if`, etc. expression has a `return`, `break`, or `continue` -/* : */ // (if legal), then RECORD SPAN FROM START OF INNERMOST CONTAINING BLOCK TO END OF `return` EXPRESSION -/* : */ // If the expression includes lazy booleans, nest calls to `__incr_cov()`. -/* : I */ Red => __incr_cov(from!("Red => or end of MatchArmGuard expression inside pattern, if any"),println!("roses")), -/* : - */ Green => { -/* : ┃ */ let spidey = 100; -/* : ┃ */ let goblin = 50; -/* : ┃ */ // if spidey > goblin {__incr_cov(from!(""),{ -/* : ┃ */ // println!("what ev"); -/* : ┃ */ // })} -/* : ┃ */ // ACTUALLY, WRAPPING THE ENTIRE IF BLOCK IN `__incr_cov` IS NOT A GREAT GENERAL RULE. -/* : ┃ */ // JUST INSERTING A `return`, `break`, or `continue` IN THAT BLOCK (without an intermediate condition) -/* : ┃ */ // MAKES THE `__incr_cov()` CALL UNREACHABLE! -/* : ┃ */ // MY ORIGINAL SOLUTION WORKS BETTER (WRAP LAST EXPRESSION OR AFTER LAST SEMICOLON STATEMENT IN BLOCK) -/* : ┃ */ // UNLESS THE EXPRESSION IS NOT A BLOCK. -/* : ┃ - */ if __incr_cov(from!("Green => or end of MatchArmGuard expression inside pattern, if any"),spidey > goblin) { -/* : : ┃ */ println!("spidey beats goblin"); -/* : : ┃ */ __incr_cov(from!("block start"),()); -/* : ┃ - */ } else if __incr_cov(from!("`else if` on this line"),spidey == goblin) { -/* : : ┃ */ // COVERAGE NOTE: Do we mark only the expression span (that may be trivial, as in this case), -/* : : ┃ */ // or associate it with the outer block, similar to how the `if` expression is associated with -/* : : ┃ */ // the outer block? (Although it is a continuation, in a sense, it is discontiguous in this case, -/* : : ┃ */ // so I think simpler to just make it its own coverage region.) -/* : : ┃ */ println!("it's a draw"); -/* : : ┃ */ __incr_cov(from!("block start"),()); -/* : ┃ - - - */ } else if if __incr_cov(from!("`else if` on this line"),true) { -/* : : : ┃ */ // return __incr_cov(from!("after `if true`"),Ok(())); -/* : : : ┃ */ // ACTUALLY, BECAUSE OF `return`, WE DO NOT RECORD THE `if true` EVEN THOUGH WE COVERED IT. -/* : : : ┃ */ // IN FACT, IF THIS NESTED CONDITIONAL IN A CONDITIONAL EXPRESSION WAS AN `if` (WITHOUT PRECEDING ELSE) -/* : : : ┃ */ // WE WOULD NOT HAVE RECORDED THE COVERAGE OF STATEMENTS LEADING UP TO THE `if`, SO -/* : : : ┃ */ // IT SHOULD BE: -/* ┏-:---:-------:---< */ return __incr_cov(from!(""),Ok(())); -/* V : : : : */ // NOTE THE `from` STRING IS SAME FOR THE `else if`s `__incr_cov` AND THIS `return`. -/* : : : : */ // ONLY ONE OF THESE WILL EXECUTE, TO RECORD COVERAGE FROM THAT SPOT. -/* : : ┃ - */ } else { -/* : : : I */ __incr_cov(from!("`else`"),false) -/* : : - - */ } { -/* : : ┃ */ println!("wierd science"); -/* : : ┃ */ __incr_cov(from!("block start"),()); -/* : ┃ - */ } else { -/* : : ┃ */ println!("goblin wins"); -/* ┏-:---:---< */ return __incr_cov(from!("`else`"),Ok(())); // THIS COUNTS LAST STATEMENT IN `else` BLOCK -/* V : : : */ // COVERAGE NOTE: When counting the span for `return`, -/* : : : */ // `break`, or `continue`, also report the outer spans -/* : : : */ // got this far--including this `else` block. Record -/* : : : */ // The start positions for those outer blocks, but: -/* : : : */ // * For the block containing the `return`, `break`, or -/* : : : */ // `continue`, end report the end position is the -/* : : : */ // start of the `return` span (or 1 char before it). -/* : : : */ // * Anything else? -/* : ┃ - */ } -/* : ┃ - */ // __incr_cov(from!(""),()); // DO NOT COUNT HERE IF NO STATEMENTS AFTER LAST `if` or `match` -/* : - */ }, -/* : I */ Blue => __incr_cov(from!("Blue => or end of MatchArmGuard expression inside pattern, if any"),println!("violets")), -/* ┃ */ } -/* ┃ */ -/* ┃ */ let condition1 = true; -/* ┃ */ let condition2 = false; -/* ┃ */ let condition3 = true; -/* ┃ */ -/* ┃ */ println!("Called `various()` for TestStruct with field={}", self.field); -/* ┃ */ -/* ┃ - */ if __incr_cov(from!("after block end of prior `match` (or `if-else if-else`)"),condition1) { -/* : ┃ */ println!("before while loop"); -/* : ┃ */ let mut countdown = 10; -/* : ┃ */ __incr_cov(from!("block start"),()); // Must increment before repeated while text expression -/* : : I */ while __incr_cov(from!("while test"), countdown > 0) { // span is just the while test expression -/* : : ┃ */ println!("top of `while` loop"); -/* : : ┃ */ countdown -= 1; -/* : : ┃ */ // __incr_cov(from!("while loop"),()); // Counter not needed, but span is computed as "while test" minus "block start" -/* : : ┃ */ // If test expression is 11, and the outer block runs only once, 11-1 = 10 -/* : ┃ - */ } -/* : ┃ */ println!("before for loop"); -/* : ┃ - */ for index in __incr_cov(from!("end of while"),0..10) { -/* : : ┃ */ println!("top of `for` loop"); -/* : : ┃ - */ if __incr_cov(from!("block start"),index == 8) { -/* : : : ┃ */ println!("before break"); -/* : : : ┃ */ // note the following is not legal here: -/* : : : ┃ */ // "can only break with a value inside `loop` or breakable block" -/* : : : ┃ */ // break __incr_cov(from!(""),()); -/* : : : ┃ */ __incr_cov(from!("block start"),()); -/* : : ┏-----< */ break; -/* : : V : : */ -/* : : : : */ // FIXME(richkadel): add examples with loop labels, breaking out of inner and outer loop to outer loop label, with expression. -/* : : : : */ // May want to record both the span and the start position after the broken out block depdnding on label -/* : : ┃ - */ } -/* : : ┃ */ println!("after `break` test"); -/* : : ┃ - */ if __incr_cov(from!("block end of `if index == 8`"),condition2) { -/* ┏-:---:---:---< */ return __incr_cov(from!("block start"),Ok(())); -/* V : : ┃ - */ } -/* : : ┃ */ -/* : : ┃ */ // BECAUSE THE PREVIOUS COVERAGE REGION HAS A `return`, THEN -/* : : ┃ */ // IF PREVIOUS COVERAGE REGION IS NOT COUNTED THEN OUTER REGION REACHED HERE. -/* : : ┃ */ // ADD A COVERAGE REGION FOR THE SPAN FROM JUST AFTER PREVIOUS REGION TO END -/* : : ┃ */ // OF OUTER SPAN, THEN TRUNCATE TO NEXT REGION NOT REACHED. -/* : : ┃ - */ if index % 3 == 2 { // NO __incr_cov() HERE BECAUSE NO STATEMENTS BETWEEN LAST CONDITIONAL BLOCK AND START OF THIS ONE -/* : : Λ : ┃ */ __incr_cov(from!("block end of `if condition2`"),()); -/* : : ┗-----< */ continue; -/* : : ┃ - */ } -/* : : ┃ */ println!("after `continue` test"); -/* : : ┃ */ // maybe add a runtime flag for a possible `return` here? -/* : : ┃ */ __incr_cov(from!("for loop"),()); -/* : ┃ - */ } -/* : ┃ */ println!("after for loop"); -/* : ┃ */ let result = if { // START OF NEW CONDITIONAL EXPRESSION. NEXT "GUARANTEED" COUNTER SHOULD COUNT FROM END OF LAST CONDITIONAL EXPRESSION -/* : ┃ */ // A "GUARANTEED" COUNTER CALL IS ONE THAT WILL BE CALLED REGARDLESS OF OTHER CONDITIONS. THIS INCLUDES: -/* : ┃ */ // * A CONDITIONAL EXPRESSION THAT IS NOT A BLOCK (OR ANOTHER CONDITIONAL STATEMENT, WHICH WOULD CONTAIN A BLOCK) -/* : ┃ */ // * OR IF THE NEXT CONDITIONAL EXPRESSION IS A BLOCK OR CONDITIONAL STATEMENT, THEN THE FIRST "GUARANTEED" COUNTER IN THAT BLOCK -/* : ┃ */ // * END OF BLOCK IF THE BLOCK DOES NOT HAVE INNER CONDITIONAL EXPRESSIONS -/* : ┃ */ // * BRANCHING STATEMENTS (`return`, `break`, `continue`) BY EITHER WRAPPING THE BRANCH STATEMENT NON-BLOCK EXPRESSION, -/* : ┃ */ // OR PREPENDING A COUNTER WITH EMPTY TUPLE IF NO EXPRESSION, OR IF EXPRESSION IS A BLOCK, THEN THE NEXT "GUARANTEED" -/* : ┃ */ // COUNTER CALL WITHIN THAT BLOCK. -/* : ┃ */ // BASICALLY, CARRY THE START OF COVERAGE SPAN FORWARD UNTIL THE GUARANTEED COUNTER IS FOUND -/* : ┃ */ println!("after result = if ..."); -/* : ┃ - */ if __incr_cov(from!("block end of `for` loop"),condition2) { -/* : : ┃ */ println!("before first return"); -/* ┏-:---:-------< */ return __incr_cov(from!("block start"),Ok(())); -/* V : : - */ } else if __incr_cov(from!("`else`"),condition3) { -/* : : ┃ */ // THE ABOVE COUNTER IS _NOT_ REALLY NECESSARY IF EXPRESSION IS GUARANTEED TO EXECUTE. -/* : : ┃ */ // IF WE GET COUNTER IN `else if` BLOCK WE COVERED EXPRESSION. -/* : : ┃ */ // IF WE GET TO ANY REMAINING `else` or `else if` BLOCK WE KNOW WE EVALUATED THIS CONDITION -/* : : ┃ */ // AND ALL OTHERS UP TO THE EXECUTED BLOCK. BUT THE SPAN WOULD HAVE "HOLES" FOR UNEXECUTED BLOCKS. -/* : : ┃ */ println!("not second return"); -/* ┏-:---:-------< */ return __incr_cov(from!("block start"),Ok(())); -/* V : : - */ } else { -/* : : ┃ */ println!("not returning"); -/* : : ┃ */ __incr_cov(from!("block start"),false) -/* : : - */ } -/* : ┃ */ // NO COUNTER HERE BECAUSE NO STATEMENTS AFTER CONDITIONAL BLOCK -/* : ┃ - */ } { -/* : : ┃ */ println!("branched condition returned true"); -/* : : ┃ */ __incr_cov(from!(""),Ok(())) -/* : ┃ - */ } else if self.call_closure( -/* : : - */ |closure_param| __incr_cov(from!(""), -/* : : ┃ - */ if condition3 { -/* : : : ┃ */ println!("in closure, captured condition said to print the param {}", closure_param); -/* : : : ┃ */ __incr_cov(from!(""),false) -/* : : ┃ - */ } else { -/* : : : ┃ */ println!("in closure, captured condition was false"); -/* : : : ┃ */ __incr_cov(from!(""),true) -/* : : ┃ - */ } -/* : : - */ ) -/* : : - */ ) { -/* : : ┃ */ println!("closure returned true"); -/* : : ┃ */ __incr_cov(from!(""),Err(Error::new(ErrorKind::Other, "Result is error if closure returned true"))) -/* : ┃ - */ } else { -/* : : ┃ */ println!("closure returned false"); -/* : : ┃ */ __incr_cov(from!(""),Err(Error::new(ErrorKind::Other, "Result is error if closure returned false"))) -/* : ┃ - */ }; -/* : ┃ */ println!("bottom of function might be skipped if early `return`"); -/* : ┃ */ __incr_cov(from!("if condition1"),result) -/* ┃ - */ } else { -/* : ┃ */ println!("skipping everything in `various()`"); -/* : ┃ */ __incr_cov(from!(""),Ok(())) -/* ┃ - */ } -/* ┃ - */ // __incr_cov(from!(""),0) // DO NOT COUNT IF NO STATEMENTS AFTER CONDITIONAL BLOCK. ALL COVERAGE IS ALREADY COUNTED -/* - */ } -/* */ } -/* */ -/* - */ fn main() -> Result<(), std::io::Error> { -/* ┃ */ //let mut status: u8 = 2; -/* ┃ */ let mut status: u8 = 1; -/* : - */ let result = if status < 2 && -/* : ┃ */ __incr_cov(from!(""),{ -/* : ┃ */ status -= 1; -/* : ┃ */ status == 0 -/* : - - */ }) { -/* : ┃ */ let test_struct = TestStruct::new_with_value(100); -/* : ┃ */ let _ = test_struct.various(); -/* ┏-:---< */ return __incr_cov_and_report(from!(""),Err(Error::new(ErrorKind::Other, format!("Error status {}", status)))) -/* V : - */ } else { -/* : ┃ */ let test_struct = TestStruct::new(); -/* : ┃ */ __incr_cov(from!(""),test_struct.various()) -/* : - */ }; -/* ┃ */ println!("done"); -/* ┃ */ __incr_cov_and_report(from!(""),result) // function-scoped counter index = 0 -/* - */ } \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/coverage_injection_test2.rs b/src/test/codegen/coverage-experiments/src/coverage_injection_test2.rs deleted file mode 100644 index 8f4399ab51d09..0000000000000 --- a/src/test/codegen/coverage-experiments/src/coverage_injection_test2.rs +++ /dev/null @@ -1,320 +0,0 @@ -/* */ use std::io::Error; -/* */ use std::io::ErrorKind; -/* */ -/* */ /// Align Rust counter increment with with: -/* */ /// [‘llvm.instrprof.increment’ Intrinsic](https://llvm.org/docs/LangRef.html#llvm-instrprof-increment-intrinsic) -/* */ /// -/* */ /// declare void @llvm.instrprof.increment(i8* , i64 , i32 , i32 ) -/* */ /// -/* */ /// The first argument is a pointer to a global variable containing the name of the entity -/* */ /// being instrumented. This should generally be the (mangled) function name for a set of -/* */ /// counters. -/* */ /// -/* */ /// The second argument is a hash value that can be used by the consumer of the profile data -/* */ /// to detect changes to the instrumented source, and the third is the number of counters -/* */ /// associated with name. It is an error if hash or num-counters differ between two -/* */ /// instances of instrprof.increment that refer to the same name. -/* */ /// -/* */ /// The last argument refers to which of the counters for name should be incremented. It -/* */ /// should be a value between 0 and num-counters. -/* */ /// -/* */ /// # Arguments -/* */ /// -/* */ /// `mangled_fn_name` - &'static ref to computed and injected static str, using: -/* */ /// -/* */ /// ``` -/* */ /// fn rustc_symbol_mangling::compute_symbol_name( -/* */ /// tcx: TyCtxt<'tcx>, -/* */ /// instance: Instance<'tcx>, -/* */ /// compute_instantiating_crate: impl FnOnce() -> CrateNum, -/* */ /// ) -> String -/* */ /// ``` -/* */ /// -/* */ /// `source_version_hash` - Compute hash based that only changes if there are "significant" -/* */ /// to control-flow inside the function. -/* */ /// -/* */ /// `num_counters` - The total number of counter calls [MAX(counter_index) + 1] within the -/* */ /// function. -/* */ /// -/* */ /// `counter_index` - zero-based counter index scoped by the function. (Ordering of -/* */ /// counters, relative to the source code location, is apparently not expected.) -/* */ /// -/* */ /// # Notes -/* */ /// -/* */ /// * The mangled_fn_name may not be computable until generics are monomorphized (see -/* */ /// parameters required by rustc_symbol_mangling::compute_symbol_name). -/* */ /// * The version hash may be computable from AST analysis, and may not benefit from further -/* */ /// lowering. -/* */ /// * num_counters depends on having already identified all counter insertion locations. -/* */ /// * counter_index can be computed at time of counter insertion (incrementally). -/* */ /// * Numeric parameters are signed to match the llvm increment intrinsic parameter types. -/* */ fn __lower_incr_cov(_mangled_fn_name: &'static str, _fn_version_hash: i64, _num_counters: i32, _counter_index: i32) { -/* */ } -/* */ -/* */ /// A coverage counter implementation that will work as both an intermediate coverage -/* */ /// counting and reporting implementation at the AST-level only--for debugging and -/* */ /// development--but also serves as a "marker" to be replaced by calls to LLVM -/* */ /// intrinsic coverage counter APIs during the lowering process. -/* */ /// -/* */ /// Calls to this function will be injected automatically into the AST. When LLVM intrinsics -/* */ /// are enabled, the counter function calls that were injected into the AST serve as -/* */ /// placeholders, to be replaced by an alternative, such as: -/* */ /// -/* */ /// * direct invocation of the `llvm.instrprof.increment()` intrinsic; or -/* */ /// * the `__lower_incr_cov()` function, defined above, that would invoke the -/* */ /// `llvm.instrprof.increment()` intrinsic; or -/* */ /// * a similar expression wrapper, with the additional parameters (as defined above -/* */ /// for `__lower_incr_cov()`, that invokes `llvm.instrprof.increment()` and returns the -/* */ /// result of the wrapped expression) -/* */ /// -/* */ /// The first two options would require replacing the inlined wrapper call with something -/* */ /// like: -/* */ /// -/* */ /// ``` -/* */ /// { let result = {expr}; __inlined_incr_cov(context, counter); result } -/* */ /// ``` -/* */ /// -/* */ /// But if the lowering process is already unwrapping the inlined call to `__incr_cov()`, then -/* */ /// it may be a perfect opportunity to replace the function with one of these more -/* */ /// direct methods. -/* */ /// -/* */ #[inline(always)] -/* */ pub fn __incr_cov(region_loc: &str) { -/* */ // Either call the intermediate non-llvm coverage counter API or -/* */ // replace the call to this function with the expanded `__lower_incr_cov()` call. -/* */ -/* */ // let _lock = increment_counter(counter); -/* */ println!("{}", region_loc); -/* */ } -/* */ -/* */ /// Write a report identifying each incremented counter and the number of times each counter -/* */ /// was incremented. -/* */ fn __report() { -/* */ println!("WRITE REPORT!"); -/* */ } -/* */ -/* */ macro_rules! from { -/* */ ($from:expr) => { &format!("from: {}\n to: {}:{}:{}", $from, file!(), line!(), column!()) }; -/* */ } -/* */ -/* */ #[derive(Debug)] -/* */ enum TestEnum { -/* */ Red, -/* */ Green, -/* */ Blue, -/* */ } -/* */ -/* */ struct TestStruct { -/* */ field: i32, -/* */ } -/* */ -/* */ // IMPORTANT! IS WRAPPING main() ENOUGH? OR DO I ALSO NEED TO WRAP THREAD FUNCTIONS, ASSUMING -/* */ // THEY ARE STILL RUNNING WITH MAIN EXITS? (IF THEY CAN). NOT SURE HOW RUST HANDLES THAT. -/* */ -/* */ // I SUSPECT USING THREAD_LOCAL COUNTERS MAY NOT ACTUALLY BE AN OPTIMIZATION OVER MUTEX LOCKS, -/* */ // BUT MAYBE I SHOULD ASK. -/* */ -/* */ impl TestStruct { -/* - */ fn new() -> Self { -/* ┃ */ let __result = Self::new_with_value(31415); // function-scoped counter index = 0 -/* ┃ */ __incr_cov(from!("fn new()")); -/* ┃ */ __result -/* - */ } -/* */ -/* - */ fn new_with_value(field: i32) -> Self { -/* ┃ */ let __result = Self { -/* ┃ */ field, -/* ┃ */ }; -/* ┃ */ __incr_cov(from!("fn new_with_value()")); // function-scoped counter index = 0 -/* ┃ */ __result -/* - */ } -/* */ -/* */ fn call_closure(&self, closure: F) -> bool -/* */ where -/* */ F: FnOnce( -/* */ i32, -/* */ ) -> bool, -/* - */ { -/* ┃ */ let __result = closure(123); -/* ┃ */ __incr_cov(from!("fn call_closure()")); // function-scoped counter index = 0 -/* ┃ */ __result -/* - */ } -/* */ -/* - */ fn various(&self) -> Result<(),Error> { -/* ┃ */ use TestEnum::*; -/* ┃ */ let mut color = Red; -/* ┃ */ let _ = color; -/* ┃ */ color = Blue; -/* ┃ */ let _ = color; -/* ┃ */ color = Green; -/* ┃ */ match { let __result = color; __incr_cov(from!("fn various")); __result } { // function-scoped counter index = 0 -/* : */ -/* : */ // !!! RECORD SPAN FROM START OF INNERMOST CONTAINING BLOCK (THE FUNCTION IN THIS CASE) TO END OF MATCH EXPRESSION -/* : */ // If `match`, `while`, `loop`, `for`, `if`, etc. expression has a `return`, `break`, or `continue` -/* : */ // (if legal), then RECORD SPAN FROM START OF INNERMOST CONTAINING BLOCK TO END OF `return` EXPRESSION -/* : */ // If the expression includes lazy booleans, nest calls to `__incr_cov()`. -/* : I */ Red => {println!("roses"); __incr_cov(from!("Red => or end of MatchArmGuard expression inside pattern, if any"));} -/* : - */ Green => { -/* : ┃ */ let spidey = 100; -/* : ┃ */ let goblin = 50; -/* : ┃ */ // if spidey > goblin {__incr_cov(from!(""),{ -/* : ┃ */ // println!("what ev"); -/* : ┃ */ // })} -/* : ┃ */ // ACTUALLY, WRAPPING THE ENTIRE IF BLOCK IN `__incr_cov` IS NOT A GREAT GENERAL RULE. -/* : ┃ */ // JUST INSERTING A `return`, `break`, or `continue` IN THAT BLOCK (without an intermediate condition) -/* : ┃ */ // MAKES THE `__incr_cov()` CALL UNREACHABLE! -/* : ┃ */ // MY ORIGINAL SOLUTION WORKS BETTER (WRAP LAST EXPRESSION OR AFTER LAST SEMICOLON STATEMENT IN BLOCK) -/* : ┃ */ // UNLESS THE EXPRESSION IS NOT A BLOCK. -/* : ┃ - */ if { let __result = spidey > goblin; __incr_cov(from!("Green => or end of MatchArmGuard expression inside pattern, if any")); __result } { -/* : : ┃ */ println!("spidey beats goblin"); -/* : : ┃ */ __incr_cov(from!("block start")); -/* : ┃ - */ } else if { let __result = spidey == goblin; __incr_cov(from!("`else if` on this line")); __result } { -/* : : ┃ */ // COVERAGE NOTE: Do we mark only the expression span (that may be trivial, as in this case), -/* : : ┃ */ // or associate it with the outer block, similar to how the `if` expression is associated with -/* : : ┃ */ // the outer block? (Although it is a continuation, in a sense, it is discontiguous in this case, -/* : : ┃ */ // so I think simpler to just make it its own coverage region.) -/* : : ┃ */ println!("it's a draw"); -/* : : ┃ */ __incr_cov(from!("block start")); -/* : ┃ - - - */ } else if if { let __result = true; __incr_cov(from!("`else if` on this line")); __result } { -/* : : : ┃ */ // return __incr_cov(from!("after `if true`"),Ok(())); -/* : : : ┃ */ // ACTUALLY, BECAUSE OF `return`, WE DO NOT RECORD THE `if true` EVEN THOUGH WE COVERED IT. -/* : : : ┃ */ // IN FACT, IF THIS NESTED CONDITIONAL IN A CONDITIONAL EXPRESSION WAS AN `if` (WITHOUT PRECEDING ELSE) -/* : : : ┃ */ // WE WOULD NOT HAVE RECORDED THE COVERAGE OF STATEMENTS LEADING UP TO THE `if`, SO -/* : : : ┃ */ // IT SHOULD BE: -/* ┏-:---:-------:---< */ return { let __result = Ok(()); __incr_cov(from!("")); __result }; -/* V : : : : */ // NOTE THE `from` STRING IS SAME FOR THE `else if`s `__incr_cov` AND THIS `return`. -/* : : : : */ // ONLY ONE OF THESE WILL EXECUTE, TO RECORD COVERAGE FROM THAT SPOT. -/* : : ┃ - */ } else { -/* : : : I */ { let __result = false; __incr_cov(from!("`else`")); __result } -/* : : - - */ } { -/* : : ┃ */ println!("wierd science"); -/* : : ┃ */ __incr_cov(from!("block start")); -/* : ┃ - */ } else { -/* : : ┃ */ println!("goblin wins"); -/* ┏-:---:---< */ return { let __result = Ok(()); __incr_cov(from!("`else`")); __result }; // THIS COUNTS LAST STATEMENT IN `else` BLOCK -/* V : : : */ // COVERAGE NOTE: When counting the span for `return`, -/* : : : */ // `break`, or `continue`, also report the outer spans -/* : : : */ // got this far--including this `else` block. Record -/* : : : */ // The start positions for those outer blocks, but: -/* : : : */ // * For the block containing the `return`, `break`, or -/* : : : */ // `continue`, end report the end position is the -/* : : : */ // start of the `return` span (or 1 char before it). -/* : : : */ // * Anything else? -/* : ┃ - */ } -/* : ┃ - */ // __incr_cov(from!("")); // DO NOT COUNT HERE IF NO STATEMENTS AFTER LAST `if` or `match` -/* : - */ }, -/* : I */ Blue => { println!("violets"); __incr_cov(from!("Blue => or end of MatchArmGuard expression inside pattern, if any")); } -/* ┃ */ } -/* ┃ */ -/* ┃ */ let condition1 = true; -/* ┃ */ let condition2 = false; -/* ┃ */ let condition3 = true; -/* ┃ */ -/* ┃ */ println!("Called `various()` for TestStruct with field={}", self.field); -/* ┃ */ -/* ┃ - */ if { let __result = condition1; __incr_cov(from!("after block end of prior `match` (or `if-else if-else`)")); __result } { -/* : ┃ */ println!("before for loop"); -/* : ┃ - */ for index in { let __result = 0..10; __incr_cov(from!("block start")); __result } { -/* : : ┃ */ println!("top of `for` loop"); -/* : : ┃ - */ if { let __result = index == 8; __incr_cov(from!("block start")); __result } { -/* : : : ┃ */ println!("before break"); -/* : : : ┃ */ // note the following is not legal here: -/* : : : ┃ */ // "can only break with a value inside `loop` or breakable block" -/* : : : ┃ */ // break __incr_cov(from!("")); -/* : : : ┃ */ __incr_cov(from!("block start")); -/* : : ┏-----< */ break; -/* : : V : : */ -/* : : : : */ // FIXME(richkadel): add examples with loop labels, breaking out of inner and outer loop to outer loop label, with expression. -/* : : : : */ // May want to record both the span and the start position after the broken out block depdnding on label -/* : : ┃ - */ } -/* : : ┃ */ println!("after `break` test"); -/* : : ┃ - */ if { let __result = condition2; __incr_cov(from!("block end of `if index == 8`")); __result } { -/* ┏-:---:---:---< */ return { let __result = Ok(()); __incr_cov(from!("block start")); __result }; -/* V : : ┃ - */ } -/* : : ┃ */ -/* : : ┃ */ // BECAUSE THE PREVIOUS COVERAGE REGION HAS A `return`, THEN -/* : : ┃ */ // IF PREVIOUS COVERAGE REGION IS NOT COUNTED THEN OUTER REGION REACHED HERE. -/* : : ┃ */ // ADD A COVERAGE REGION FOR THE SPAN FROM JUST AFTER PREVIOUS REGION TO END -/* : : ┃ */ // OF OUTER SPAN, THEN TRUNCATE TO NEXT REGION NOT REACHED. -/* : : ┃ - */ if index % 3 == 2 { // NO __incr_cov() HERE BECAUSE NO STATEMENTS BETWEEN LAST CONDITIONAL BLOCK AND START OF THIS ONE -/* : : Λ : ┃ */ __incr_cov(from!("block end of `if condition2`")); -/* : : ┗-----< */ continue; -/* : : ┃ - */ } -/* : : ┃ */ println!("after `continue` test"); -/* : : ┃ */ // maybe add a runtime flag for a possible `return` here? -/* : : ┃ */ __incr_cov(from!("")); -/* : ┃ - */ } -/* : ┃ */ println!("after for loop"); -/* : ┃ */ let result = if { // START OF NEW CONDITIONAL EXPRESSION. NEXT "GUARANTEED" COUNTER SHOULD COUNT FROM END OF LAST CONDITIONAL EXPRESSION -/* : ┃ */ // A "GUARANTEED" COUNTER CALL IS ONE THAT WILL BE CALLED REGARDLESS OF OTHER CONDITIONS. THIS INCLUDES: -/* : ┃ */ // * A CONDITIONAL EXPRESSION THAT IS NOT A BLOCK (OR ANOTHER CONDITIONAL STATEMENT, WHICH WOULD CONTAIN A BLOCK) -/* : ┃ */ // * OR IF THE NEXT CONDITIONAL EXPRESSION IS A BLOCK OR CONDITIONAL STATEMENT, THEN THE FIRST "GUARANTEED" COUNTER IN THAT BLOCK -/* : ┃ */ // * END OF BLOCK IF THE BLOCK DOES NOT HAVE INNER CONDITIONAL EXPRESSIONS -/* : ┃ */ // * BRANCHING STATEMENTS (`return`, `break`, `continue`) BY EITHER WRAPPING THE BRANCH STATEMENT NON-BLOCK EXPRESSION, -/* : ┃ */ // OR PREPENDING A COUNTER WITH EMPTY TUPLE IF NO EXPRESSION, OR IF EXPRESSION IS A BLOCK, THEN THE NEXT "GUARANTEED" -/* : ┃ */ // COUNTER CALL WITHIN THAT BLOCK. -/* : ┃ */ // BASICALLY, CARRY THE START OF COVERAGE SPAN FORWARD UNTIL THE GUARANTEED COUNTER IS FOUND -/* : ┃ */ println!("after result = if ..."); -/* : ┃ - */ if { let __result = condition2; __incr_cov(from!("block end of `for` loop")); __result } { -/* : : ┃ */ println!("before first return"); -/* ┏-:---:-------< */ return { let __result = Ok(()); __incr_cov(from!("block start")); __result }; -/* V : : - */ } else if { let __result = condition3; __incr_cov(from!("`else`")); __result } { -/* : : ┃ */ // THE ABOVE COUNTER IS _NOT_ REALLY NECESSARY IF EXPRESSION IS GUARANTEED TO EXECUTE. -/* : : ┃ */ // IF WE GET COUNTER IN `else if` BLOCK WE COVERED EXPRESSION. -/* : : ┃ */ // IF WE GET TO ANY REMAINING `else` or `else if` BLOCK WE KNOW WE EVALUATED THIS CONDITION -/* : : ┃ */ // AND ALL OTHERS UP TO THE EXECUTED BLOCK. BUT THE SPAN WOULD HAVE "HOLES" FOR UNEXECUTED BLOCKS. -/* : : ┃ */ println!("not second return"); -/* ┏-:---:-------< */ return { let __result = Ok(()); __incr_cov(from!("block start")); __result }; -/* V : : - */ } else { -/* : : ┃ */ println!("not returning"); -/* : : ┃ */ { let __result = false; __incr_cov(from!("block start")); __result } -/* : : - */ } -/* : ┃ */ // NO COUNTER HERE BECAUSE NO STATEMENTS AFTER CONDITIONAL BLOCK -/* : ┃ - */ } { -/* : : ┃ */ println!("branched condition returned true"); -/* : : ┃ */ { let __result = Ok(()); __incr_cov(from!("")); __result } -/* : ┃ - */ } else if self.call_closure( -/* : : - */ |closure_param| { -/* : : ┃ - */ let __result = if condition3 { -/* : : : ┃ */ println!("in closure, captured condition said to print the param {}", closure_param); -/* : : : ┃ */ { let __result = false; __incr_cov(from!("")); __result } -/* : : ┃ - */ } else { -/* : : : ┃ */ println!("in closure, captured condition was false"); -/* : : : ┃ */ { let __result = true; __incr_cov(from!("")); __result } -/* : : ┃ - */ }; -/* : : - */ __incr_cov(from!("")); __result } -/* : : - */ ) { -/* : : ┃ */ println!("closure returned true"); -/* : : ┃ */ { let __result = Err(Error::new(ErrorKind::Other, "Result is error if closure returned true")); __incr_cov(from!("")); __result } -/* : ┃ - */ } else { -/* : : ┃ */ println!("closure returned false"); -/* : : ┃ */ { let __result = Err(Error::new(ErrorKind::Other, "Result is error if closure returned false")); __incr_cov(from!("")); __result } -/* : ┃ - */ }; -/* : ┃ */ println!("bottom of function might be skipped if early `return`"); -/* : ┃ */ { let __result = result; __incr_cov(from!("if condition1")); __result } -/* ┃ - */ } else { -/* : ┃ */ println!("skipping everything in `various()`"); -/* : ┃ */ { let __result = Ok(()); __incr_cov(from!("")); __result } -/* ┃ - */ } -/* ┃ - */ // __incr_cov(from!(""),0) // DO NOT COUNT IF NO STATEMENTS AFTER CONDITIONAL BLOCK. ALL COVERAGE IS ALREADY COUNTED -/* - */ } -/* */ } -/* */ -/* - */ fn main() -> Result<(), std::io::Error> { -/* ┃ */ //let mut status: u8 = 2; -/* ┃ */ let mut status: u8 = 1; -/* : - */ let result = if status < 2 && -/* : ┃ */ { let __result = { -/* : ┃ */ status -= 1; -/* : ┃ */ status == 0 -/* : - - */ }; __incr_cov(from!("")); __result } { -/* : ┃ */ let test_struct = TestStruct::new_with_value(100); -/* : ┃ */ let _ = test_struct.various(); -/* ┏-:---< */ return { let __result = Err(Error::new(ErrorKind::Other, format!("Error status {}", status))); __incr_cov(from!("")); __report(); __result } -/* V : - */ } else { -/* : ┃ */ let test_struct = TestStruct::new(); -/* : ┃ */ { let __result = test_struct.various(); __incr_cov(from!("")); __result } -/* : - */ }; -/* ┃ */ println!("done"); -/* ┃ */ { let __result = result; __incr_cov(from!("")); __report(); __result } -/* - */ } \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/coverage_injection_test_alt.rs b/src/test/codegen/coverage-experiments/src/coverage_injection_test_alt.rs deleted file mode 100644 index 20c4835dd882e..0000000000000 --- a/src/test/codegen/coverage-experiments/src/coverage_injection_test_alt.rs +++ /dev/null @@ -1,362 +0,0 @@ -/* */ use std::io::Error; -/* */ use std::io::ErrorKind; -/* */ -/* */ /// Align Rust counter increment with with: -/* */ /// [‘llvm.instrprof.increment’ Intrinsic](https://llvm.org/docs/LangRef.html#llvm-instrprof-increment-intrinsic) -/* */ /// -/* */ /// declare void @llvm.instrprof.increment(i8* , i64 , i32 , i32 ) -/* */ /// -/* */ /// The first argument is a pointer to a global variable containing the name of the entity -/* */ /// being instrumented. This should generally be the (mangled) function name for a set of -/* */ /// counters. -/* */ /// -/* */ /// The second argument is a hash value that can be used by the consumer of the profile data -/* */ /// to detect changes to the instrumented source, and the third is the number of counters -/* */ /// associated with name. It is an error if hash or num-counters differ between two -/* */ /// instances of instrprof.increment that refer to the same name. -/* */ /// -/* */ /// The last argument refers to which of the counters for name should be incremented. It -/* */ /// should be a value between 0 and num-counters. -/* */ /// -/* */ /// # Arguments -/* */ /// -/* */ /// `mangled_fn_name` - &'static ref to computed and injected static str, using: -/* */ /// -/* */ /// ``` -/* */ /// fn rustc_symbol_mangling::compute_symbol_name( -/* */ /// tcx: TyCtxt<'tcx>, -/* */ /// instance: Instance<'tcx>, -/* */ /// compute_instantiating_crate: impl FnOnce() -> CrateNum, -/* */ /// ) -> String -/* */ /// ``` -/* */ /// -/* */ /// `source_version_hash` - Compute hash based that only changes if there are "significant" -/* */ /// to control-flow inside the function. -/* */ /// -/* */ /// `num_counters` - The total number of counter calls [MAX(counter_index) + 1] within the -/* */ /// function. -/* */ /// -/* */ /// `counter_index` - zero-based counter index scoped by the function. (Ordering of -/* */ /// counters, relative to the source code location, is apparently not expected.) -/* */ /// -/* */ /// # Notes -/* */ /// -/* */ /// * The mangled_fn_name may not be computable until generics are monomorphized (see -/* */ /// parameters required by rustc_symbol_mangling::compute_symbol_name). -/* */ /// * The version hash may be computable from AST analysis, and may not benefit from further -/* */ /// lowering. -/* */ /// * num_counters depends on having already identified all counter insertion locations. -/* */ /// * counter_index can be computed at time of counter insertion (incrementally). -/* */ /// * Numeric parameters are signed to match the llvm increment intrinsic parameter types. -/* */ fn __lower_incr_cov(_mangled_fn_name: &'static str, _fn_version_hash: i64, _num_counters: i32, _counter_index: i32) { -/* */ } -/* */ -/* */ /// A coverage counter implementation that will work as both an intermediate coverage -/* */ /// counting and reporting implementation at the AST-level only--for debugging and -/* */ /// development--but also serves as a "marker" to be replaced by calls to LLVM -/* */ /// intrinsic coverage counter APIs during the lowering process. -/* */ /// -/* */ /// Calls to this function will be injected automatically into the AST. When LLVM intrinsics -/* */ /// are enabled, the counter function calls that were injected into the AST serve as -/* */ /// placeholders, to be replaced by an alternative, such as: -/* */ /// -/* */ /// * direct invocation of the `llvm.instrprof.increment()` intrinsic; or -/* */ /// * the `__lower_incr_cov()` function, defined above, that would invoke the -/* */ /// `llvm.instrprof.increment()` intrinsic; or -/* */ /// * a similar expression wrapper, with the additional parameters (as defined above -/* */ /// for `__lower_incr_cov()`, that invokes `llvm.instrprof.increment()` and returns the -/* */ /// result of the wrapped expression) -/* */ /// -/* */ /// The first two options would require replacing the inlined wrapper call with something -/* */ /// like: -/* */ /// -/* */ /// ``` -/* */ /// { let result = {expr}; __inlined_incr_cov(context, counter); result } -/* */ /// ``` -/* */ /// -/* */ /// But if the lowering process is already unwrapping the inlined call to `__incr_cov()`, then -/* */ /// it may be a perfect opportunity to replace the function with one of these more -/* */ /// direct methods. -/* */ /// -/* */ #[inline(always)] -/* */ pub fn __incr_cov(region_loc: &str, /*index: u32,*/) { -/* */ // Either call the intermediate non-llvm coverage counter API or -/* */ // replace the call to this function with the expanded `__lower_incr_cov()` call. -/* */ -/* */ // let _lock = increment_counter(counter); -/* */ println!("{}", region_loc); -/* */ } -/* */ -/* */ /// Write a report identifying each incremented counter and the number of times each counter -/* */ /// was incremented. -/* */ fn __report() { -/* */ println!("WRITE REPORT!"); -/* */ } -/* */ -/* */ /// Increment the counter after evaluating the wrapped expression (see `__incr_cov()`), then -/* */ /// write a report identifying each incremented counter and the number of times each counter -/* */ /// was incremented. -/* */ #[inline(always)] -/* */ pub fn __incr_cov_and_report(region_loc: &str, /*counter: u32,*/ result: T) -> T { -/* */ __incr_cov(region_loc, /*counter,*/); -/* */ __report(); -/* */ result -/* */ } -/* */ -/* */ macro_rules! from { -/* */ ($from:expr) => { &format!("from: {}\n to: {}:{}:{}", $from, file!(), line!(), column!()) }; -/* */ } -/* */ -/* */ macro_rules! to { -/* */ ($to:expr) => { &format!("to: {}\n to: {}:{}:{}", $to, file!(), line!(), column!()) }; -/* */ } -/* */ -/* */ #[derive(Debug)] -/* */ enum TestEnum { -/* */ Red, -/* */ Green, -/* */ Blue, -/* */ } -/* */ -/* */ struct TestStruct { -/* */ field: i32, -/* */ } -/* */ -/* */ // IMPORTANT! IS WRAPPING main() ENOUGH? OR DO I ALSO NEED TO WRAP THREAD FUNCTIONS, ASSUMING -/* */ // THEY ARE STILL RUNNING WITH MAIN EXITS? (IF THEY CAN). NOT SURE HOW RUST HANDLES THAT. -/* */ -/* */ // I SUSPECT USING THREAD_LOCAL COUNTERS MAY NOT ACTUALLY BE AN OPTIMIZATION OVER MUTEX LOCKS, -/* */ // BUT MAYBE I SHOULD ASK. -/* */ -/* */ impl TestStruct { -/* - */ fn new() -> Self { -/* ┃ */ __incr_cov(to!("end of fn new()")); // function-scoped counter index = 0 -/* ┃ */ Self::new_with_value(31415) -/* - */ } -/* */ -/* - */ fn new_with_value(field: i32) -> Self { -/* ┃ */ __incr_cov(to!("end of fn new_with_value()")); // function-scoped counter index = 0 -/* ┃ */ Self { -/* ┃ */ field, -/* ┃ */ } -/* - */ } -/* */ -/* */ fn call_closure(&self, closure: F) -> bool -/* */ where -/* */ F: FnOnce( -/* */ i32, -/* */ ) -> bool, -/* - */ { -/* ┃ */ __incr_cov(to!("end of fn call_closure()")); // function-scoped counter index = 0 -/* ┃ */ closure(123) -/* - */ } -/* */ -/* - */ fn various(&self) -> Result<(),Error> { -/* ┃ */ __incr_cov(to!("just before next branch: after `match color`: pattern selection")); -/* ┃ */ use TestEnum::*; -/* ┃ */ let mut color = Red; -/* ┃ */ let _ = color; -/* ┃ */ color = Blue; -/* ┃ */ let _ = color; -/* ┃ */ color = Green; -/* ┃ */ match color { // function-scoped counter index = 0 -/* : */ -/* : */ // !!! RECORD SPAN FROM START OF INNERMOST CONTAINING BLOCK (THE FUNCTION IN THIS CASE) TO END OF MATCH EXPRESSION -/* : */ // If `match`, `while`, `loop`, `for`, `if`, etc. expression has a `return`, `break`, or `continue` -/* : */ // (if legal), then RECORD SPAN FROM START OF INNERMOST CONTAINING BLOCK TO END OF `return` EXPRESSION -/* : */ // If the expression includes lazy booleans, nest calls to `__incr_cov()`. -/* : - */ Red => { -/* : ┃ */ __incr_cov(to!("end of matched Red")); -/* : ┃ */ println!("roses"); -/* : - */ } -/* : - */ Green => { -/* : ┃ */ __incr_cov(to!("just before next branch: after `if spidey > goblin`")); -/* : ┃ */ let spidey = 100; -/* : ┃ */ let goblin = 50; -/* : ┃ */ // if spidey > goblin {__incr_cov(from!(""),{ -/* : ┃ */ // println!("what ev"); -/* : ┃ */ // })} -/* : ┃ */ // ACTUALLY, WRAPPING THE ENTIRE IF BLOCK IN `__incr_cov` IS NOT A GREAT GENERAL RULE. -/* : ┃ */ // JUST INSERTING A `return`, `break`, or `continue` IN THAT BLOCK (without an intermediate condition) -/* : ┃ */ // MAKES THE `__incr_cov()` CALL UNREACHABLE! -/* : ┃ */ // MY ORIGINAL SOLUTION WORKS BETTER (WRAP LAST EXPRESSION OR AFTER LAST SEMICOLON STATEMENT IN BLOCK) -/* : ┃ */ // UNLESS THE EXPRESSION IS NOT A BLOCK. -/* : ┃ - */ if spidey > goblin { -/* : : ┃ */ __incr_cov(to!("end of if block, if no earlier branch in this scope")); -/* : : ┃ */ println!("spidey beats goblin"); -/* : : ┃ */ -/* : ┃ - */ } else if { -/* : : : */ // Make sure we can't compute the coverage count here. -/* : : : */ // We know the expression executed if the previous if block DID NOT -/* : : : */ // execute, and either this `else if` block does execute OR any subsequent -/* : : : */ // `else if` or `else` blocks execute, OR none of the blocks in the -/* : : : */ // `if`, `else if` or `else` blocks execute. -/* : : : */ // `if`, `else if` or `else` blocks execute. -/* : : ┃ */ __incr_cov(to!("end of `else if spidey == goblin` expression")); -/* : : ┃ */ spidey == goblin -/* : ┃ - */ } { -/* : : ┃ */ __incr_cov(to!("end of if block, if no earlier branch in this scope")); -/* : : ┃ */ // COVERAGE NOTE: Do we mark only the expression span (that may be trivial, as in this case), -/* : : ┃ */ // or associate it with the outer block, similar to how the `if` expression is associated with -/* : : ┃ */ // the outer block? (Although it is a continuation, in a sense, it is discontiguous in this case, -/* : : ┃ */ // so I think simpler to just make it its own coverage region.) -/* : : ┃ */ println!("it's a draw"); -/* : : ┃ */ -/* : ┃ - - - */ } else if { -/* : : ┃ */ __incr_cov(to!("end of `if true`")); -/* : ┃ - - - */ if true { -/* : : : ┃ */ __incr_cov(to!("end of `return Ok(())`")); -/* ┏-:---:-------:---< */ return Ok(()); -/* V : : ┃ - */ } else { -/* : : : ┃ */ // __incr_cov(to!("end of else block")); -/* : : : ┃ */ // computed counter expression -/* : : : ┃ */ false -/* : : : - */ } -/* : : - - - */ } { -/* : : ┃ */ __incr_cov(to!("end of if block")); -/* : : ┃ */ println!("wierd science"); -/* : ┃ - */ } else { -/* : : ┃ */ // __incr_cov(to!("end of `return Ok(())")); -/* : : ┃ */ // counter expression: (start of Green match arm) - (if spidey > goblin) - (previous `} else if {`) -/* : : ┃ */ println!("goblin wins"); -/* ┏-:---:---< */ return Ok(()); // THIS COUNTS LAST STATEMENT IN `else` BLOCK -/* V : : : */ // COVERAGE NOTE: When counting the span for `return`, -/* : : : */ // `break`, or `continue`, also report the outer spans -/* : : : */ // got this far--including this `else` block. Record -/* : : : */ // The start positions for those outer blocks, but: -/* : : : */ // * For the block containing the `return`, `break`, or -/* : : : */ // `continue`, end report the end position is the -/* : : : */ // start of the `return` span (or 1 char before it). -/* : : : */ // * Anything else? -/* : ┃ - */ } -/* : : */ // __incr_cov(to!("end of matched Green")); -/* : : */ // // DO NOT COUNT HERE IF NO STATEMENTS AFTER LAST `if` or `match` -/* : - */ }, -/* : - */ Blue => { -/* : ┃ */ __incr_cov(to!("end of matched Blue")); -/* : ┃ */ println!("violets"); -/* : - */ } -/* ┃ */ } -/* ┃ */ __incr_cov(to!("just before next branch: after `if condition1` (HIR: 'match condition1')")); -/* ┃ */ -/* ┃ */ let condition1 = true; -/* ┃ */ let condition2 = false; -/* ┃ */ let condition3 = true; -/* ┃ */ -/* ┃ */ println!("Called `various()` for TestStruct with field={}", self.field); -/* ┃ */ -/* ┃ - */ if condition1 { -/* : ┃ */ println!("before while loop"); -/* : ┃ */ let mut countdown = 10; -/* : ┃ */ // Must increment before repeated while text expression -/* : : I */ while countdown > 0 { // span is just the while test expression -/* : : ┃ */ println!("top of `while` loop"); -/* : : ┃ */ countdown -= 1; -/* : : ┃ */ // // Counter not needed, but span is computed as "while test" minus "block start" -/* : : ┃ */ // If test expression is 11, and the outer block runs only once, 11-1 = 10 -/* : ┃ - */ } -/* : ┃ */ println!("before for loop"); -/* : ┃ - */ for index in 0..10 { -/* : : ┃ */ println!("top of `for` loop"); -/* : : ┃ - */ if index == 8 { -/* : : : ┃ */ println!("before break"); -/* : : : ┃ */ // note the following is not legal here: -/* : : : ┃ */ // "can only break with a value inside `loop` or breakable block" -/* : : : ┃ */ // break -/* : : : ┃ */ -/* : : ┏-----< */ break; -/* : : V : : */ -/* : : : : */ // FIXME(richkadel): add examples with loop labels, breaking out of inner and outer loop to outer loop label, with expression. -/* : : : : */ // May want to record both the span and the start position after the broken out block depdnding on label -/* : : ┃ - */ } -/* : : ┃ */ println!("after `break` test"); -/* : : ┃ - */ if condition2 { -/* ┏-:---:---:---< */ return Ok(()); -/* V : : ┃ - */ } -/* : : ┃ */ -/* : : ┃ */ // BECAUSE THE PREVIOUS COVERAGE REGION HAS A `return`, THEN -/* : : ┃ */ // IF PREVIOUS COVERAGE REGION IS NOT COUNTED THEN OUTER REGION REACHED HERE. -/* : : ┃ */ // ADD A COVERAGE REGION FOR THE SPAN FROM JUST AFTER PREVIOUS REGION TO END -/* : : ┃ */ // OF OUTER SPAN, THEN TRUNCATE TO NEXT REGION NOT REACHED. -/* : : ┃ - */ if index % 3 == 2 { // NO __incr_cov() HERE BECAUSE NO STATEMENTS BETWEEN LAST CONDITIONAL BLOCK AND START OF THIS ONE -/* : : Λ : ┃ */ -/* : : ┗-----< */ continue; -/* : : ┃ - */ } -/* : : ┃ */ println!("after `continue` test"); -/* : : ┃ */ // maybe add a runtime flag for a possible `return` here? -/* : : ┃ */ -/* : ┃ - */ } -/* : ┃ */ println!("after for loop"); -/* : ┃ */ let result = if { // START OF NEW CONDITIONAL EXPRESSION. NEXT "GUARANTEED" COUNTER SHOULD COUNT FROM END OF LAST CONDITIONAL EXPRESSION -/* : ┃ */ // A "GUARANTEED" COUNTER CALL IS ONE THAT WILL BE CALLED REGARDLESS OF OTHER CONDITIONS. THIS INCLUDES: -/* : ┃ */ // * A CONDITIONAL EXPRESSION THAT IS NOT A BLOCK (OR ANOTHER CONDITIONAL STATEMENT, WHICH WOULD CONTAIN A BLOCK) -/* : ┃ */ // * OR IF THE NEXT CONDITIONAL EXPRESSION IS A BLOCK OR CONDITIONAL STATEMENT, THEN THE FIRST "GUARANTEED" COUNTER IN THAT BLOCK -/* : ┃ */ // * END OF BLOCK IF THE BLOCK DOES NOT HAVE INNER CONDITIONAL EXPRESSIONS -/* : ┃ */ // * BRANCHING STATEMENTS (`return`, `break`, `continue`) BY EITHER WRAPPING THE BRANCH STATEMENT NON-BLOCK EXPRESSION, -/* : ┃ */ // OR PREPENDING A COUNTER WITH EMPTY TUPLE IF NO EXPRESSION, OR IF EXPRESSION IS A BLOCK, THEN THE NEXT "GUARANTEED" -/* : ┃ */ // COUNTER CALL WITHIN THAT BLOCK. -/* : ┃ */ // BASICALLY, CARRY THE START OF COVERAGE SPAN FORWARD UNTIL THE GUARANTEED COUNTER IS FOUND -/* : ┃ */ println!("after result = if ..."); -/* : ┃ - */ if condition2 { -/* : : ┃ */ println!("before first return"); -/* ┏-:---:-------< */ return Ok(()); -/* V : : - */ } else if condition3 { -/* : : ┃ */ // THE ABOVE COUNTER IS _NOT_ REALLY NECESSARY IF EXPRESSION IS GUARANTEED TO EXECUTE. -/* : : ┃ */ // IF WE GET COUNTER IN `else if` BLOCK WE COVERED EXPRESSION. -/* : : ┃ */ // IF WE GET TO ANY REMAINING `else` or `else if` BLOCK WE KNOW WE EVALUATED THIS CONDITION -/* : : ┃ */ // AND ALL OTHERS UP TO THE EXECUTED BLOCK. BUT THE SPAN WOULD HAVE "HOLES" FOR UNEXECUTED BLOCKS. -/* : : ┃ */ println!("not second return"); -/* ┏-:---:-------< */ return Ok(()); -/* V : : - */ } else { -/* : : ┃ */ println!("not returning"); -/* : : ┃ */ false -/* : : - */ } -/* : ┃ */ // NO COUNTER HERE BECAUSE NO STATEMENTS AFTER CONDITIONAL BLOCK -/* : ┃ - */ } { -/* : : ┃ */ println!("branched condition returned true"); -/* : : ┃ */ Ok(()) -/* : ┃ - */ } else if self.call_closure( -/* : : - */ |closure_param| -/* : : ┃ - */ if condition3 { -/* : : : ┃ */ println!("in closure, captured condition said to print the param {}", closure_param); -/* : : : ┃ */ false -/* : : ┃ - */ } else { -/* : : : ┃ */ println!("in closure, captured condition was false"); -/* : : : ┃ */ true -/* : : ┃ - */ } -/* : : - */ -/* : : - */ ) { -/* : : ┃ */ println!("closure returned true"); -/* : : ┃ */ Err(Error::new(ErrorKind::Other, "Result is error if closure returned true")) -/* : ┃ - */ } else { -/* : : ┃ */ println!("closure returned false"); -/* : : ┃ */ Err(Error::new(ErrorKind::Other, "Result is error if closure returned false")) -/* : ┃ - */ }; -/* : ┃ */ println!("bottom of function might be skipped if early `return`"); -/* : ┃ */ result -/* ┃ - */ } else { -/* : ┃ */ println!("skipping everything in `various()`"); -/* : ┃ */ Ok(()) -/* ┃ - */ } -/* ┃ - */ // 0 // DO NOT COUNT IF NO STATEMENTS AFTER CONDITIONAL BLOCK. ALL COVERAGE IS ALREADY COUNTED -/* - */ } -/* */ } -/* */ -/* - */ fn main() -> Result<(), std::io::Error> { -/* ┃ */ //let mut status: u8 = 2; -/* ┃ */ let mut status: u8 = 1; -/* : - */ let result = if status < 2 && -/* : ┃ */ { -/* : ┃ */ status -= 1; -/* : ┃ */ status == 0 -/* : - - */ } { -/* : ┃ */ let test_struct = TestStruct::new_with_value(100); -/* : ┃ */ let _ = test_struct.various(); -/* ┏-:---< */ return __incr_cov_and_report(from!(""),Err(Error::new(ErrorKind::Other, format!("Error status {}", status)))) -/* V : - */ } else { -/* : ┃ */ let test_struct = TestStruct::new(); -/* : ┃ */ test_struct.various() -/* : - */ }; -/* ┃ */ println!("done"); -/* ┃ */ __incr_cov_and_report(from!(""),result) // function-scoped counter index = 0 -/* - */ } \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/drop_trait.rs b/src/test/codegen/coverage-experiments/src/drop_trait.rs deleted file mode 100644 index 75400e037e9f0..0000000000000 --- a/src/test/codegen/coverage-experiments/src/drop_trait.rs +++ /dev/null @@ -1,25 +0,0 @@ -#[inline(always)] -pub fn __incr_cov(_region_loc: &str, result: T) -> T { - result -} - -struct Firework { - _strength: i32, -} - -impl Drop for Firework { - fn drop(&mut self) { - __incr_cov("start of drop()", ()); - } -} - -fn main() -> Result<(),u8> { - let _firecracker = Firework { _strength: 1 }; - - if __incr_cov("start of main()", true) { - return __incr_cov("if true", { let _t = Err(1); _t }); - } - - let _tnt = Firework { _strength: 100 }; - Ok(()) -} \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/drop_trait_with_comments_prints.rs b/src/test/codegen/coverage-experiments/src/drop_trait_with_comments_prints.rs deleted file mode 100644 index de9f5d5cb4647..0000000000000 --- a/src/test/codegen/coverage-experiments/src/drop_trait_with_comments_prints.rs +++ /dev/null @@ -1,53 +0,0 @@ -// -// -// -// It's interesting to speculate if there is a way to leverage the Drop trait functionality -// to increment counters when a scope is closed, but I don't think it would help "out of the box". -// -// A `return` or `break` with expression might not need a temp value expression wrapper -// such as `return { let _t = result_expression; __incr_counter(...); _t };` -// -// ... **if** the __incr_counter() was somehow called from a "drop()" trait function. -// -// The problem is, since the drop call is automatic, there is no way to have argument variants -// depending on where the drop() occurs (e.g., from a `return` statement vs. from the end of -// the function). We need 2 different code regions though. -// -// -// -// - -#[inline(always)] -pub fn __incr_cov(_region_loc: &str, /*index: u32,*/ result: T) -> T { - // println!("from: {}", _region_loc); - result -} - -struct Firework { - strength: i32, -} - -impl Drop for Firework { - fn drop(&mut self) { - println!("BOOM times {}!!!", self.strength); - __incr_cov("start of drop()", ()); - } -} - -fn main() -> Result<(),u8> { - let _firecracker = Firework { strength: 1 }; - - if __incr_cov("start of main()", true) { - return __incr_cov("if true", { let _t = Err(1); println!("computing return value"); _t }); - } - - let _tnt = Firework { strength: 100 }; - // __incr_cov("after if block", Ok(())) // CAN USE COUNTER EXPRESSION: "start of drop()" - "if true" - Ok(()) -} - -// OUTPUT WHEN RUNNING THIS PROGRAM IS AS EXPECTED: - -// computing return value -// BOOM times 1!!! -// Error: 1 diff --git a/src/test/codegen/coverage-experiments/src/for.rs b/src/test/codegen/coverage-experiments/src/for.rs deleted file mode 100644 index 3f44c382a1e3f..0000000000000 --- a/src/test/codegen/coverage-experiments/src/for.rs +++ /dev/null @@ -1,41 +0,0 @@ -#[inline(always)] -pub fn __incr_cov(_region_loc: &str, /*index: u32,*/ result: T) -> T { - result -} - -fn main() { - for countdown in __incr_cov("start", 10..0) { - let _ = countdown; - __incr_cov("top of for", ()); - } -} - -// LOWERED TO HIR: -// -// fn main() { -// { -// let _t = -// match ::std::iter::IntoIterator::into_iter(__incr_cov("start", -// ::std::ops::Range{start: -// 10, -// end: -// 0,})) -// { -// mut iter => -// loop { -// let mut __next; -// match ::std::iter::Iterator::next(&mut iter) { -// ::std::option::Option::Some(val) => -// __next = val, -// ::std::option::Option::None => break , -// } -// let countdown = __next; -// { -// let _ = countdown; -// __incr_cov("top of for", ()); -// } -// }, -// }; -// _t -// } -// } \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/for_with_comments.rs b/src/test/codegen/coverage-experiments/src/for_with_comments.rs deleted file mode 100644 index 03d11b2c230ca..0000000000000 --- a/src/test/codegen/coverage-experiments/src/for_with_comments.rs +++ /dev/null @@ -1,24 +0,0 @@ -/* */ #[inline(always)] -/* */ pub fn __incr_cov(_region_loc: &str, /*index: u32,*/ result: T) -> T { -/* */ result -/* */ } -/* */ -/* - */ fn main() { -/* : I */ for countdown in __incr_cov("start", 10..0) { // span is just the while test expression -/* : ┃ */ let _ = countdown; -/* : ┃ */ __incr_cov("top of for", ()); -/* ┃ - */ } -/* - */ } - - -// -Z unpretty=val -- present the input source, unstable (and less-pretty) variants; -// valid types are any of the types for `--pretty`, as well as: -// `expanded`, `expanded,identified`, -// `expanded,hygiene` (with internal representations), -// `everybody_loops` (all function bodies replaced with `loop {}`), -// `hir` (the HIR), `hir,identified`, -// `hir,typed` (HIR with types for each node), -// `hir-tree` (dump the raw HIR), -// `mir` (the MIR), or `mir-cfg` (graphviz formatted MIR) - -// argument to `pretty` must be one of `normal`, `expanded`, `identified`, or `expanded,identified` diff --git a/src/test/codegen/coverage-experiments/src/if.rs b/src/test/codegen/coverage-experiments/src/if.rs deleted file mode 100644 index ad50f6be19004..0000000000000 --- a/src/test/codegen/coverage-experiments/src/if.rs +++ /dev/null @@ -1,80 +0,0 @@ -#![feature(core_intrinsics)] - -pub fn __llvm_incr_counter(_region_loc: &str) { -} - -#[inline(always)] -pub fn __incr_cov(region_loc: &str, result: T) -> T { - __llvm_incr_counter(region_loc); - result -} - -static TEST_FUNC_NAME: &'static [u8; 6] = b"main()"; - -fn main() { - let mut countdown = 10; - if __incr_cov("start", countdown > 0) { - - - // // TEST CALLING INTRINSIC: - unsafe { core::intrinsics::instrprof_increment(TEST_FUNC_NAME as *const u8, 1234 as u64, 314 as u32, 31 as u32) }; - // // Results in: - // // LLVM ERROR: Cannot select: intrinsic %llvm.instrprof.increment - // // I may need to pass one or more of the following flags (or equivalent opts) to LLVM to enable this: - // // -fprofile-instr-generate -fcoverage-mapping - - - countdown -= 1; - __incr_cov("if block",()); - } else if countdown > 5 { - countdown -= 2; - __incr_cov("else if block",()); - } else { - countdown -= 3; - } - - let mut countdown = 10; - if { let _tcov = countdown > 0; __llvm_incr_counter("start", ); _tcov } { - countdown -= 1; - __incr_cov("if block",()); - } else if countdown > 5 { - countdown -= 2; - __incr_cov("else if block",()); - } else { - countdown -= 3; - } -} - -// NOTE: hir REDUNDANTLY lowers the manually inlined counter in the second if block to: -// -// match { -// let _t = -// { -// let _tcov = countdown > 0; -// __llvm_incr_counter("start"); -// _tcov -// }; -// _t -// } { - -// I don't know if optimization phases will fix this or not. -// Otherwise, a more optimal (but definitely special case) way to handle this would be -// to inject the counter between the hir-introduced temp `_t` assignment and the block result -// line returning `_t`: -// -// match { -// let _t = countdown > 0; -// __llvm_incr_counter("start"); // <-- the only thing inserted for coverage here -// _t -// } -// -// UNFORTUNATELY THIS IS NOT A PATTERN WE CAN ALWAYS LEVERAGE, FOR EXPRESSIONS THAT HAVE VALUES -// WHERE WE NEED TO INJECT THE COUNTER AFTER THE EXPRESSION BUT BEFORE IT IS USED. -// -// IT DOES APPEAR TO BE THE CASE FOR WHILE EXPRESSIONS, (BECOMES loop { match { let _t = condition; _t} { true => {...} _ => break, }}) -// AND IS TRUE FOR IF EXPRESSIONS AS NOTED -// BUT NOT FOR RETURN STATEMENT (and I'm guessing not for loop { break value; } ? ) -// -// AND NOT FOR LAZY BOOLEAN EXPRESSIONS! -// -// AND NOT FOR MATCH EXPRESSIONS IN THE ORIGINAL SOURCE! \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/if_with_comments.rs b/src/test/codegen/coverage-experiments/src/if_with_comments.rs deleted file mode 100644 index 267e7bca2c5a2..0000000000000 --- a/src/test/codegen/coverage-experiments/src/if_with_comments.rs +++ /dev/null @@ -1,39 +0,0 @@ -/* */ #[inline(always)] -/* */ pub fn __incr_cov(_region_loc: &str, /*index: u32,*/ result: T) -> T { -/* */ result -/* */ } -/* */ -/* - */ fn main() { -/* ┃ */ let mut countdown = 10; -/* : I */ if __incr_cov("start", countdown > 0) { // span is from start of main() -/* : ┃ */ countdown -= 1; -/* : ┃ */ __incr_cov("if block",()); -/* ┃ - */ } - - let mut countdown = 10; - if __incr_cov("start", countdown > 0) { - countdown -= 1; - __incr_cov("if block",()); - } else if countdown > 5 { // counter expression "start" - "if block" - countdown -= 2; - __incr_cov("else if block",()); - } else { - countdown -= 3; - // __incr_cov("else block",()); // counter expression (countdown > 5 counter expression) - "else if block" - // PLACED AT END OF ELSE BLOCK OR START OF FIRST CONDITIONAL BLOCK, IF ANY (PRESUMING POSSIBLE EARLY EXIT). - // IF WE CAN GUARANTEE NO EARLY EXIT IN THIS BLOCK, THEN AT THE END IS FINE EVEN IF ELSE BLOCK CONTAINS OTHER CONDITIONS. - } - -/* - */ } - -// -Z unpretty=val -- present the input source, unstable (and less-pretty) variants; -// valid types are any of the types for `--pretty`, as well as: -// `expanded`, `expanded,identified`, -// `expanded,hygiene` (with internal representations), -// `everybody_loops` (all function bodies replaced with `loop {}`), -// `hir` (the HIR), `hir,identified`, -// `hir,typed` (HIR with types for each node), -// `hir-tree` (dump the raw HIR), -// `mir` (the MIR), or `mir-cfg` (graphviz formatted MIR) - -// argument to `pretty` must be one of `normal`, `expanded`, `identified`, or `expanded,identified` diff --git a/src/test/codegen/coverage-experiments/src/increment_intrinsic.rs b/src/test/codegen/coverage-experiments/src/increment_intrinsic.rs deleted file mode 100644 index d4708cd367ff6..0000000000000 --- a/src/test/codegen/coverage-experiments/src/increment_intrinsic.rs +++ /dev/null @@ -1,11 +0,0 @@ -#![feature(core_intrinsics)] - -pub fn not_instrprof_increment(_hash: u64, _num_counters: u32, _index: u32) { -} - -fn main() { - // COMPARE THIS WITH INTRINSIC INSERTION - //not_instrprof_increment(1234 as u64, 314 as u32, 31 as u32); - - unsafe { core::intrinsics::instrprof_increment(1234 as u64, 314 as u32, 31 as u32) }; -} \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/just_main.rs b/src/test/codegen/coverage-experiments/src/just_main.rs deleted file mode 100644 index 081e5d72a6e0a..0000000000000 --- a/src/test/codegen/coverage-experiments/src/just_main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("hello world! (should be covered)"); -} diff --git a/src/test/codegen/coverage-experiments/src/lazy_boolean.rs b/src/test/codegen/coverage-experiments/src/lazy_boolean.rs deleted file mode 100644 index 263277c7cdc4d..0000000000000 --- a/src/test/codegen/coverage-experiments/src/lazy_boolean.rs +++ /dev/null @@ -1,17 +0,0 @@ -pub fn __llvm_incr_counter(_region_loc: &str) { -} - -#[inline(always)] -pub fn __incr_cov(region_loc: &str, result: T) -> T { - __llvm_incr_counter(region_loc); - result -} - -fn main() { - let a = 1; - let b = 10; - let c = 100; - let _result = __incr_cov("start", a < b) || __incr_cov("or", b < c); - - let _result = { let _t = a < b; __llvm_incr_counter("start"); _t } || { let _t = b < c; __llvm_incr_counter("start"); _t }; -} \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/loop_break_value.rs b/src/test/codegen/coverage-experiments/src/loop_break_value.rs deleted file mode 100644 index 76caa833ec4f8..0000000000000 --- a/src/test/codegen/coverage-experiments/src/loop_break_value.rs +++ /dev/null @@ -1,15 +0,0 @@ -pub fn __llvm_incr_counter(_region_loc: &str) { -} - -#[inline(always)] -pub fn __incr_cov(region_loc: &str, result: T) -> T { - __llvm_incr_counter(region_loc); - result -} - -fn main() { - __incr_cov("start", ()); - let _result = loop { - break __incr_cov("top of loop", true); - }; -} \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/match.rs b/src/test/codegen/coverage-experiments/src/match.rs deleted file mode 100644 index afbb20888eab5..0000000000000 --- a/src/test/codegen/coverage-experiments/src/match.rs +++ /dev/null @@ -1,22 +0,0 @@ -pub fn __llvm_incr_counter(_region_loc: &str) { -} - -#[inline(always)] -pub fn __incr_cov(region_loc: &str, result: T) -> T { - __llvm_incr_counter(region_loc); - result -} - -fn main() { - let a = 1; - let b = 10; - let _result = match a < b { - true => true, - _ => false, - }; - - let _result = match __incr_cov("end of first match", a < b) { - true => __incr_cov("matched true", true), - _ => false, // counter expression "end of first match" - "matched true" - }; -} \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/match_with_increment.rs b/src/test/codegen/coverage-experiments/src/match_with_increment.rs deleted file mode 100644 index f618b37ed5247..0000000000000 --- a/src/test/codegen/coverage-experiments/src/match_with_increment.rs +++ /dev/null @@ -1,305 +0,0 @@ -#![feature(core_intrinsics)] -//static TEST_FUNC_NAME: &'static [u8; 7] = b"main()\0"; - static TEST_FUNC_NAME: &'static [u8; 6] = b"main()"; -fn main() { - let a = 1; - let b = 10; - let _result = match { - let _t = a < b; - unsafe { core::intrinsics::instrprof_increment(TEST_FUNC_NAME as *const u8, 1234 as u64, 3 as u32, 0 as u32) }; - _t - } { - true => { - let _t = true; - unsafe { core::intrinsics::instrprof_increment(TEST_FUNC_NAME as *const u8, 1234 as u64, 3 as u32, 1 as u32) }; - _t - } - _ => false, - }; -} - -/* - -I NEED TO INSERT THE instrprof_increment() CALL: - - 1. JUST BEFORE THE switchInt(_4) (because we haven't counted entering the function main() yet, deferring that to "JUST BEFORE FIRST BRANCH") - 2. SOME TIME AFTER THE switchInt(_4), AND JUST BEFORE ANOTHER BRANCH (in this case, before "goto") - 2.a. NOT BEFORE BOTH GOTO'S AFTER switchInt(_4) (because one can be calculated by counter expression), BUT PERHAPS INSERT A noop PLACEHOLDER - AS A MARKER TO INCLUDE THE COVERAGE REGION AND REFERENCE THE COUNTERS TO BE SUBTRACTED (AND/OR SUMMED)? - - WHY DEFER INSERTING COUNTERS TO "JUST BEFORE FIRST BRANCH"? We can ignore panic/unwind() and only count if the coverage region ACTUALLY - executed in entirety. BUT IS THAT NECESSARY? IS IT MUCH EASIER TO INSERT COUNTERS AT THE TOP OF A REGION THAT MUST EXECUTE IN ENTIRETY IF - PANIC DOES NOT OCCUR? AND WHAT IF WE ADD SUPPORT FOR PANIC UNWIND (later)? - - IS THERE A BENEFIT OF THE DEFERRED APPROACH WHEN CONSIDERING EXPRESSIONS MAY HAVE EARLY RETURNS? (BECAUSE, WE STILL NEED TO COUNT THE REGION - LEADING UP TO THE EXPRESSION ANYWAY) - -================================================= -================================================= - -To inject an intrinsic after computing a final expression value of a coverage region: - -Replace the following basic block end (last statement plus terminator): - -... ... -StorageLive(_4) -StorageLive(_5) -_5 = _1 -StorageLive(_6) -_6 = _2 -_4 = Lt(move _5, move _6) -StorageDead(_6) -StorageDead(_5) - <------ to insert instrprof_increment() here -FakeRead(ForMatchedPlace, _4) --------------------------------------------------------------------------------------- -switchInt(_4) - - -================================================= -Insert call to intrinsic with: - -StorageLive(_4) # _4 is now meant for deferred FakeRead(ForMatchdPlace, _4) in BasicBlock after increment() call -StorageLive(_5) # Unchanged except _4 is now _5 -StorageLive(_6) # Unchanged except _5 is now _6 -_6 = _1 # Unchanged except _5 is now _6 -StorageLive(_7) # Unchanged except _6 is now _7 -_7 = _2 # Unchanged except _6 is now _7 -_5 = Lt(move _6, move _7) # Unchanged except _4, _5, _6 is now _5, _6, _7 -StorageDead(_7) # Unchanged except _6 is now _7 -StorageDead(_6) # Unchanged except _5 is now _6 - -FakeRead(ForLet, _5) # CHANGED ForMatchedPlace to ForLet - -> # ALL NEW AND NECESSARY TO CALL instrprof_increment() -> StorageLive(_8) # ?? stores function pointer to instrprof_increment function? -> StorageLive(_9) -> StorageLive(_10) -> StorageLive(_11) -> _11 = const {alloc1+0: &&[u8; 6]} -> _10 = &raw const (*(*_11)) -> _9 = move _10 as *const u8 (Pointer(ArrayToPointer)) -> StorageDead(_10) -> StorageLive(_12) -> _12 = const 1234u64 -> StorageLive(_13) -> _13 = const 3u32 -> StorageLive(_14) -> _14 = const 0u32 -> -------------------------------------------------------------------------------------- -> _8 = const std::intrinsics::instrprof_increment(move _9, move _12, move _13, move _14) -> -> -> return -> -> StorageDead(_14) -> StorageDead(_13) -> StorageDead(_12) -> StorageDead(_9) -> StorageDead(_11) -> StorageDead(_8) - -_4 = _5 # ARE THESE LINES REDUNDANT? CAN I JUST PASS _5 DIRECTLY TO FakeRead()? -StorageDead(_5) # DROP "_t" temp result of `let _t = a < b` - # (NOTE THAT IF SO, I CAN REMOVE _5 altogether, and use _4, which coincidentally makes less changes) - # SEE BELOW - -FakeRead(ForMatchedPlace, _4) # Unchanged --------------------------------------------------------------------------------------- -switchInt(_4) # Unchanged - - -================================================= -Can I skip the extra variable and insert call to intrinsic with: - -StorageLive(_4) # Unchanged -StorageLive(_5) # Unchanged -_5 = _1 # Unchanged -StorageLive(_6) # Unchanged -_6 = _2 # Unchanged -_4 = Lt(move _5, move _6) # Unchanged -StorageDead(_6) # Unchanged -StorageDead(_5) # Unchanged - -> # ALL NEW AND NECESSARY TO CALL instrprof_increment() -> FakeRead(ForLet, _4) # Save the post-increment result in temp "_t" -> StorageLive(_8) # ?? stores function pointer to instrprof_increment function? -> StorageLive(_9) -> StorageLive(_10) -> StorageLive(_11) -> _11 = const {alloc1+0: &&[u8; 6]} -> _10 = &raw const (*(*_11)) -> _9 = move _10 as *const u8 (Pointer(ArrayToPointer)) -> StorageDead(_10) -> StorageLive(_12) -> _12 = const 1234u64 -> StorageLive(_13) -> _13 = const 3u32 -> StorageLive(_14) -> _14 = const 0u32 -> -------------------------------------------------------------------------------------- -> _8 = const std::intrinsics::instrprof_increment(move _9, move _12, move _13, move _14) -> -> -> return -> -> StorageDead(_14) -> StorageDead(_13) -> StorageDead(_12) -> StorageDead(_9) -> StorageDead(_11) -> StorageDead(_8) - -FakeRead(ForMatchedPlace, _4) # Unchanged (PREVIOUSLY USED IN FakeRead(ForLet), is that OK?) --------------------------------------------------------------------------------------- -switchInt(_4) # Unchanged - - - - - -================================================= -================================================= - -For the second inserted call to instrprof_increment, without that call we have: - --------------------------------------------------------------------------------------- -switchInt(_4) # From above - --> otherwise # that is, "NOT false" - -_3 = const true - <------ to insert instrprof_increment() here --------------------------------------------------------------------------------------- -goto - --> # No label. No condition, and not a "return" - -FakeRead(ForLet, _3) # NOTE: Unused result -StorageDead(_4) -_0 = () -StorageDead(_3) -StorageDead(_2) -StorageDead(_1) --------------------------------------------------------------------------------------- -goto - --> # No label. No condition, and not a "return" - -return # from main() - - -================================================= -With the call to increment(): - --------------------------------------------------------------------------------------- -switchInt(_4) # From above - --> otherwise # "NOT false" # UNCHANGED - -StorageLive(_15) # CHANGED! Allocated new storage (_15) for the result of match, if true. -_15 = const true # UNCHANGED except _3 is now _15 -FakeRead(ForLet, _15) # CHANGED! Assign value to temporary (to be assigned to _3 later) ... Do I need to do this? - -> # ALL NEW AND NECESSARY TO CALL instrprof_increment() -> StorageLive(_16) # pointer to instrprof_increment() function ? -> StorageLive(_17) -> StorageLive(_18) -> StorageLive(_19) -> _19 = const {alloc1+0: &&[u8; 6]} -> _18 = &raw const (*(*_19)) -> _17 = move _18 as *const u8 (Pointer(ArrayToPointer)) -> StorageDead(_18) -> StorageLive(_20) -> _20 = const 1234u64 -> StorageLive(_21) -> _21 = const 3u32 -> StorageLive(_22) -> _22 = const 1u32 -> -------------------------------------------------------------------------------------- -> _16 = const std::intrinsics::instrprof_increment(move _17, move _20, move _21, move _22) -> -> -> return -> -> StorageDead(_22) -> StorageDead(_21) -> StorageDead(_20) -> StorageDead(_17) -> StorageDead(_19) -> StorageDead(_16) -> _3 = _15 -> StorageDead(_15) - ---------------------------------# UNCHANGED------------------------------------------- -goto # UNCHANGED - --> # UNCHANGED - -FakeRead(ForLet, _3) # UNCHANGED -StorageDead(_4) # UNCHANGED -_0 = () # UNCHANGED -StorageDead(_3) # UNCHANGED -StorageDead(_2) # UNCHANGED -StorageDead(_1) # UNCHANGED --------------------------------------------------------------------------------------- -goto # UNCHANGED - --> # UNCHANGED - -return # from main() # UNCHANGED - -================================================= -As before, can I skip the extra variable (_15) and insert the call to intrinsic with _3 directly?: - - --------------------------------------------------------------------------------------- -switchInt(_4) # From above - --> otherwise # "NOT false" # UNCHANGED - -_3 = const true # UNCHANGED? - -> # ALL NEW AND NECESSARY TO CALL instrprof_increment() -> StorageLive(_16) # pointer to instrprof_increment() function ? -> StorageLive(_17) -> StorageLive(_18) -> StorageLive(_19) -> _19 = const {alloc1+0: &&[u8; 6]} -> _18 = &raw const (*(*_19)) -> _17 = move _18 as *const u8 (Pointer(ArrayToPointer)) -> StorageDead(_18) -> StorageLive(_20) -> _20 = const 1234u64 -> StorageLive(_21) -> _21 = const 3u32 -> StorageLive(_22) -> _22 = const 1u32 -> -------------------------------------------------------------------------------------- -> _16 = const std::intrinsics::instrprof_increment(move _17, move _20, move _21, move _22) -> -> -> return -> -> StorageDead(_22) -> StorageDead(_21) -> StorageDead(_20) -> StorageDead(_17) -> StorageDead(_19) -> StorageDead(_16) - ---------------------------------# UNCHANGED------------------------------------------- -goto # UNCHANGED - --> # UNCHANGED - -FakeRead(ForLet, _3) # UNCHANGED -StorageDead(_4) # UNCHANGED -_0 = () # UNCHANGED -StorageDead(_3) # UNCHANGED -StorageDead(_2) # UNCHANGED -StorageDead(_1) # UNCHANGED --------------------------------------------------------------------------------------- -goto # UNCHANGED - --> # UNCHANGED - -return # from main() # UNCHANGED - -*/ \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/match_with_increment_alt.rs b/src/test/codegen/coverage-experiments/src/match_with_increment_alt.rs deleted file mode 100644 index 60586967920cb..0000000000000 --- a/src/test/codegen/coverage-experiments/src/match_with_increment_alt.rs +++ /dev/null @@ -1,296 +0,0 @@ -#![feature(core_intrinsics)] -//static TEST_FUNC_NAME: &'static [u8; 7] = b"main()\0"; - static TEST_FUNC_NAME: &'static [u8; 6] = b"main()"; -fn main() { - unsafe { core::intrinsics::instrprof_increment(TEST_FUNC_NAME as *const u8, 1234 as u64, 3 as u32, 0 as u32) }; - let a = 1; - let b = 10; - let _result = match a < b { - true => { - unsafe { core::intrinsics::instrprof_increment(TEST_FUNC_NAME as *const u8, 1234 as u64, 3 as u32, 1 as u32) }; - true - } - _ => false, - }; -} - -/* - -ALTERNATE APPROACH: - - IS IT MUCH EASIER TO INSERT COUNTERS AT THE TOP OF A REGION THAT MUST EXECUTE IN ENTIRETY IF - PANIC DOES NOT OCCUR? AND WHAT IF WE ADD SUPPORT FOR PANIC UNWIND (later)? - - IS THERE A DETRACTOR COMPARED TO THE DEFERRED APPROACH WHEN CONSIDERING EXPRESSIONS MAY HAVE EARLY RETURNS? - - (BECAUSE, WE STILL NEED TO COUNT THE REGION LEADING UP TO THE EXPRESSION ANYWAY) - -================================================= -================================================= - -To inject an intrinsic after computing a final expression value of a coverage region: - -Replace the following basic block end (last statement plus terminator): - -... ... -StorageLive(_4) -StorageLive(_5) -_5 = _1 -StorageLive(_6) -_6 = _2 -_4 = Lt(move _5, move _6) -StorageDead(_6) -StorageDead(_5) - <------ to insert instrprof_increment() here -FakeRead(ForMatchedPlace, _4) --------------------------------------------------------------------------------------- -switchInt(_4) - - -================================================= -Insert call to intrinsic with: - -StorageLive(_4) # _4 is now meant for deferred FakeRead(ForMatchdPlace, _4) in BasicBlock after increment() call -StorageLive(_5) # Unchanged except _4 is now _5 -StorageLive(_6) # Unchanged except _5 is now _6 -_6 = _1 # Unchanged except _5 is now _6 -StorageLive(_7) # Unchanged except _6 is now _7 -_7 = _2 # Unchanged except _6 is now _7 -_5 = Lt(move _6, move _7) # Unchanged except _4, _5, _6 is now _5, _6, _7 -StorageDead(_7) # Unchanged except _6 is now _7 -StorageDead(_6) # Unchanged except _5 is now _6 - -FakeRead(ForLet, _5) # CHANGED ForMatchedPlace to ForLet - -> # ALL NEW AND NECESSARY TO CALL instrprof_increment() -> StorageLive(_8) # ?? stores function pointer to instrprof_increment function? -> StorageLive(_9) -> StorageLive(_10) -> StorageLive(_11) -> _11 = const {alloc1+0: &&[u8; 6]} -> _10 = &raw const (*(*_11)) -> _9 = move _10 as *const u8 (Pointer(ArrayToPointer)) -> StorageDead(_10) -> StorageLive(_12) -> _12 = const 1234u64 -> StorageLive(_13) -> _13 = const 3u32 -> StorageLive(_14) -> _14 = const 0u32 -> -------------------------------------------------------------------------------------- -> _8 = const std::intrinsics::instrprof_increment(move _9, move _12, move _13, move _14) -> -> -> return -> -> StorageDead(_14) -> StorageDead(_13) -> StorageDead(_12) -> StorageDead(_9) -> StorageDead(_11) -> StorageDead(_8) - -_4 = _5 # ARE THESE LINES REDUNDANT? CAN I JUST PASS _5 DIRECTLY TO FakeRead()? -StorageDead(_5) # DROP "_t" temp result of `let _t = a < b` - # (NOTE THAT IF SO, I CAN REMOVE _5 altogether, and use _4, which coincidentally makes less changes) - # SEE BELOW - -FakeRead(ForMatchedPlace, _4) # Unchanged --------------------------------------------------------------------------------------- -switchInt(_4) # Unchanged - - -================================================= -Can I skip the extra variable and insert call to intrinsic with: - -StorageLive(_4) # Unchanged -StorageLive(_5) # Unchanged -_5 = _1 # Unchanged -StorageLive(_6) # Unchanged -_6 = _2 # Unchanged -_4 = Lt(move _5, move _6) # Unchanged -StorageDead(_6) # Unchanged -StorageDead(_5) # Unchanged - -> # ALL NEW AND NECESSARY TO CALL instrprof_increment() -> FakeRead(ForLet, _4) # Save the post-increment result in temp "_t" -> StorageLive(_8) # ?? stores function pointer to instrprof_increment function? -> StorageLive(_9) -> StorageLive(_10) -> StorageLive(_11) -> _11 = const {alloc1+0: &&[u8; 6]} -> _10 = &raw const (*(*_11)) -> _9 = move _10 as *const u8 (Pointer(ArrayToPointer)) -> StorageDead(_10) -> StorageLive(_12) -> _12 = const 1234u64 -> StorageLive(_13) -> _13 = const 3u32 -> StorageLive(_14) -> _14 = const 0u32 -> -------------------------------------------------------------------------------------- -> _8 = const std::intrinsics::instrprof_increment(move _9, move _12, move _13, move _14) -> -> -> return -> -> StorageDead(_14) -> StorageDead(_13) -> StorageDead(_12) -> StorageDead(_9) -> StorageDead(_11) -> StorageDead(_8) - -FakeRead(ForMatchedPlace, _4) # Unchanged (PREVIOUSLY USED IN FakeRead(ForLet), is that OK?) --------------------------------------------------------------------------------------- -switchInt(_4) # Unchanged - - - - - -================================================= -================================================= - -For the second inserted call to instrprof_increment, without that call we have: - --------------------------------------------------------------------------------------- -switchInt(_4) # From above - --> otherwise # that is, "NOT false" - -_3 = const true - <------ to insert instrprof_increment() here --------------------------------------------------------------------------------------- -goto - --> # No label. No condition, and not a "return" - -FakeRead(ForLet, _3) # NOTE: Unused result -StorageDead(_4) -_0 = () -StorageDead(_3) -StorageDead(_2) -StorageDead(_1) --------------------------------------------------------------------------------------- -goto - --> # No label. No condition, and not a "return" - -return # from main() - - -================================================= -With the call to increment(): - --------------------------------------------------------------------------------------- -switchInt(_4) # From above - --> otherwise # "NOT false" # UNCHANGED - -StorageLive(_15) # CHANGED! Allocated new storage (_15) for the result of match, if true. -_15 = const true # UNCHANGED except _3 is now _15 -FakeRead(ForLet, _15) # CHANGED! Assign value to temporary (to be assigned to _3 later) ... Do I need to do this? - -> # ALL NEW AND NECESSARY TO CALL instrprof_increment() -> StorageLive(_16) # pointer to instrprof_increment() function ? -> StorageLive(_17) -> StorageLive(_18) -> StorageLive(_19) -> _19 = const {alloc1+0: &&[u8; 6]} -> _18 = &raw const (*(*_19)) -> _17 = move _18 as *const u8 (Pointer(ArrayToPointer)) -> StorageDead(_18) -> StorageLive(_20) -> _20 = const 1234u64 -> StorageLive(_21) -> _21 = const 3u32 -> StorageLive(_22) -> _22 = const 1u32 -> -------------------------------------------------------------------------------------- -> _16 = const std::intrinsics::instrprof_increment(move _17, move _20, move _21, move _22) -> -> -> return -> -> StorageDead(_22) -> StorageDead(_21) -> StorageDead(_20) -> StorageDead(_17) -> StorageDead(_19) -> StorageDead(_16) -> _3 = _15 -> StorageDead(_15) - ---------------------------------# UNCHANGED------------------------------------------- -goto # UNCHANGED - --> # UNCHANGED - -FakeRead(ForLet, _3) # UNCHANGED -StorageDead(_4) # UNCHANGED -_0 = () # UNCHANGED -StorageDead(_3) # UNCHANGED -StorageDead(_2) # UNCHANGED -StorageDead(_1) # UNCHANGED --------------------------------------------------------------------------------------- -goto # UNCHANGED - --> # UNCHANGED - -return # from main() # UNCHANGED - -================================================= -As before, can I skip the extra variable (_15) and insert the call to intrinsic with _3 directly?: - - --------------------------------------------------------------------------------------- -switchInt(_4) # From above - --> otherwise # "NOT false" # UNCHANGED - -_3 = const true # UNCHANGED? - -> # ALL NEW AND NECESSARY TO CALL instrprof_increment() -> StorageLive(_16) # pointer to instrprof_increment() function ? -> StorageLive(_17) -> StorageLive(_18) -> StorageLive(_19) -> _19 = const {alloc1+0: &&[u8; 6]} -> _18 = &raw const (*(*_19)) -> _17 = move _18 as *const u8 (Pointer(ArrayToPointer)) -> StorageDead(_18) -> StorageLive(_20) -> _20 = const 1234u64 -> StorageLive(_21) -> _21 = const 3u32 -> StorageLive(_22) -> _22 = const 1u32 -> -------------------------------------------------------------------------------------- -> _16 = const std::intrinsics::instrprof_increment(move _17, move _20, move _21, move _22) -> -> -> return -> -> StorageDead(_22) -> StorageDead(_21) -> StorageDead(_20) -> StorageDead(_17) -> StorageDead(_19) -> StorageDead(_16) - ---------------------------------# UNCHANGED------------------------------------------- -goto # UNCHANGED - --> # UNCHANGED - -FakeRead(ForLet, _3) # UNCHANGED -StorageDead(_4) # UNCHANGED -_0 = () # UNCHANGED -StorageDead(_3) # UNCHANGED -StorageDead(_2) # UNCHANGED -StorageDead(_1) # UNCHANGED --------------------------------------------------------------------------------------- -goto # UNCHANGED - --> # UNCHANGED - -return # from main() # UNCHANGED - -*/ \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/match_without_increment.mir b/src/test/codegen/coverage-experiments/src/match_without_increment.mir deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/src/test/codegen/coverage-experiments/src/match_without_increment.rs b/src/test/codegen/coverage-experiments/src/match_without_increment.rs deleted file mode 100644 index fa85833e05434..0000000000000 --- a/src/test/codegen/coverage-experiments/src/match_without_increment.rs +++ /dev/null @@ -1,5 +0,0 @@ -fn main() { - let a = 1; - let b = 10; - let _result = match a < b { true => true, _ => false, }; -} \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/match_without_increment_alt.mir b/src/test/codegen/coverage-experiments/src/match_without_increment_alt.mir deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/src/test/codegen/coverage-experiments/src/question_mark_err_status_handling_with_comments.rs b/src/test/codegen/coverage-experiments/src/question_mark_err_status_handling_with_comments.rs deleted file mode 100644 index 03d11b2c230ca..0000000000000 --- a/src/test/codegen/coverage-experiments/src/question_mark_err_status_handling_with_comments.rs +++ /dev/null @@ -1,24 +0,0 @@ -/* */ #[inline(always)] -/* */ pub fn __incr_cov(_region_loc: &str, /*index: u32,*/ result: T) -> T { -/* */ result -/* */ } -/* */ -/* - */ fn main() { -/* : I */ for countdown in __incr_cov("start", 10..0) { // span is just the while test expression -/* : ┃ */ let _ = countdown; -/* : ┃ */ __incr_cov("top of for", ()); -/* ┃ - */ } -/* - */ } - - -// -Z unpretty=val -- present the input source, unstable (and less-pretty) variants; -// valid types are any of the types for `--pretty`, as well as: -// `expanded`, `expanded,identified`, -// `expanded,hygiene` (with internal representations), -// `everybody_loops` (all function bodies replaced with `loop {}`), -// `hir` (the HIR), `hir,identified`, -// `hir,typed` (HIR with types for each node), -// `hir-tree` (dump the raw HIR), -// `mir` (the MIR), or `mir-cfg` (graphviz formatted MIR) - -// argument to `pretty` must be one of `normal`, `expanded`, `identified`, or `expanded,identified` diff --git a/src/test/codegen/coverage-experiments/src/while.rs b/src/test/codegen/coverage-experiments/src/while.rs deleted file mode 100644 index 3cb185eda544f..0000000000000 --- a/src/test/codegen/coverage-experiments/src/while.rs +++ /dev/null @@ -1,23 +0,0 @@ -#[inline(always)] -pub fn __incr_cov(_region_loc: &str, result: T) -> T { - result -} - -fn main() { - let mut countdown = 10; - __incr_cov("block start",()); - while __incr_cov("while test", countdown > 0) { - countdown -= 1; - } - - let mut countdown = 10; - __incr_cov("after first while loop",()); - while __incr_cov("while test", countdown > 0) { - countdown -= 1; - if countdown < 5 { - __incr_cov("top of if countdown < 5",()); - break; - } - countdown -= 2; - } -} \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/while_clean.rs b/src/test/codegen/coverage-experiments/src/while_clean.rs deleted file mode 100644 index e9ed1efc220d4..0000000000000 --- a/src/test/codegen/coverage-experiments/src/while_clean.rs +++ /dev/null @@ -1,6 +0,0 @@ -fn main() { - let mut countdown = 10; - while countdown > 0 { - countdown -= 1; - } -} \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/while_early_return.rs b/src/test/codegen/coverage-experiments/src/while_early_return.rs deleted file mode 100644 index 35709ffba3a04..0000000000000 --- a/src/test/codegen/coverage-experiments/src/while_early_return.rs +++ /dev/null @@ -1,10 +0,0 @@ -fn main() -> u8 { // this will lower to HIR but will not compile: `main` can only return types that implement `std::process::Termination` - let mut countdown = 10; - while countdown > 0 { - if false { - return if countdown > 8 { 1 } else { return 2; }; - } - countdown -= 1; - } - 0 -} \ No newline at end of file diff --git a/src/test/codegen/coverage-experiments/src/while_with_comments.rs b/src/test/codegen/coverage-experiments/src/while_with_comments.rs deleted file mode 100644 index 56417fedf00df..0000000000000 --- a/src/test/codegen/coverage-experiments/src/while_with_comments.rs +++ /dev/null @@ -1,51 +0,0 @@ -/* */ #[inline(always)] -/* */ pub fn __incr_cov(_region_loc: &str, /*index: u32,*/ result: T) -> T { -/* */ result -/* */ } -/* */ -/* - */ fn main() { -/* ┃ */ let mut countdown = 10; -/* ┃ */ __incr_cov("block start",()); // Must increment before repeated while text expression -/* : I */ while __incr_cov("while test", countdown > 0) { // span is just the while test expression -/* : ┃ */ countdown -= 1; -/* : ┃ */ // __incr_cov("while loop",()); // Counter not needed, but span is computed as "while test" minus "block start" -/* : ┃ */ // If while criteria is tested 11 times, and the outer block runs only once, 11-1 = 10 -/* : ┃ */ // REMOVING COUNTER ASSUMES NO EARLY RETURN THOUGH. -/* : ┃ */ // I THINK WE CAN ONLY USE THE COUNTER EXPRESSION UP TO FIRST CONDITIONAL BLOCK, IF ANY (if, match, maybe any loop) -/* ┃ - */ } - - let mut countdown = 10; - __incr_cov("after first while loop",()); - while __incr_cov("while test", countdown > 0) { - countdown -= 1; - // if __incr_cov("top of while loop", countdown < 5) { - if countdown < 5 { // "top of while loop" = counter expression "while test" - "after first while loop" - __incr_cov("top of if countdown < 5",()); - break; - } - countdown -= 2; - // __incr_cov("after if countdown < 5 block", ()); - // "after if countdown < 5 block" = counter expression "top of while loop" - "top of if countdown < 5" - // HOWEVER, WE CAN ONLY REMOVE THE COUNTER AND USE COUNTER EXPRESSION IF WE **KNOW** THAT THE BODY OF THE IF - // WILL **ALWAYS** BREAK (OR RETURN, OR CONTINUE?) - // AND THUS WE TREAT THE STATEMENTS FOLLOWING THE IF BLOCK AS IF THEY WERE AN ELSE BLOCK. - // THAT'S A LOT TO ASK. - - // PERHAPS TREAT EARLY RETURNS AS A SPECIAL KIND OF COUNTER AND IF ANY ARE INVOKED BEFORE STATEMENTS AFTER THE BLOCK THAT CONTAINS THEM, - // THEN SUBTRACT THOSE COUNTS FROM THE COUNT BEFORE THE BLOCK (AS WE DO HERE)? (SO ONE SET OF EXPRESSIONS MUST SUM ALL OF THE EARLY - // RETURNS) - } -/* - */ } - - -// -Z unpretty=val -- present the input source, unstable (and less-pretty) variants; -// valid types are any of the types for `--pretty`, as well as: -// `expanded`, `expanded,identified`, -// `expanded,hygiene` (with internal representations), -// `everybody_loops` (all function bodies replaced with `loop {}`), -// `hir` (the HIR), `hir,identified`, -// `hir,typed` (HIR with types for each node), -// `hir-tree` (dump the raw HIR), -// `mir` (the MIR), or `mir-cfg` (graphviz formatted MIR) - -// argument to `pretty` must be one of `normal`, `expanded`, `identified`, or `expanded,identified` From d2cd59a0315809afa58df0196c34b33ee0a8c161 Mon Sep 17 00:00:00 2001 From: Rich Kadel Date: Fri, 5 Jun 2020 09:14:45 -0700 Subject: [PATCH 04/14] Add case for count_code_region() extern lang_item As suggested in PR feedback: https://github.com/rust-lang/rust/pull/73011#discussion_r435728923 This allows count_code_region() to be handled like a normal intrinsic so the InstanceDef::InjectedCode variant is no longer needed. --- src/libcore/intrinsics.rs | 21 ++++++------------- src/librustc_codegen_ssa/mir/block.rs | 5 +---- src/librustc_middle/mir/mono.rs | 1 - src/librustc_middle/ty/instance.rs | 7 ------- src/librustc_middle/ty/mod.rs | 1 - src/librustc_middle/ty/structural_impls.rs | 11 +++------- src/librustc_mir/interpret/terminator.rs | 3 --- src/librustc_mir/monomorphize/collector.rs | 5 +---- src/librustc_mir/monomorphize/partitioning.rs | 2 -- src/librustc_mir/shim.rs | 3 --- src/librustc_passes/weak_lang_items.rs | 16 ++++++++++++-- src/librustc_ty/instance.rs | 4 ---- src/librustc_typeck/check/intrinsic.rs | 2 ++ 13 files changed, 27 insertions(+), 54 deletions(-) diff --git a/src/libcore/intrinsics.rs b/src/libcore/intrinsics.rs index 06a432a26961e..7ce5814d39a02 100644 --- a/src/libcore/intrinsics.rs +++ b/src/libcore/intrinsics.rs @@ -1941,22 +1941,13 @@ extern "rust-intrinsic" { /// /// Perma-unstable: do not use. pub fn miri_start_panic(payload: *mut u8) -> !; -} -/// Defines the `count_code_region` intrinsic as a `LangItem`. `LangItem`s require a function body -/// to register its DefId with the LangItem entry. The function body is never actually called (and -/// is therefore implemented as an aborting stub) because it is replaced with the LLVM intrinsic -/// `llvm.instrprof.increment` by -/// `rustc_codegen_llvm::intrinsic::IntrinsicCallMethods::codegen_intrinsic_call()`. -#[cfg(not(bootstrap))] -#[cfg_attr(not(bootstrap), lang = "count_code_region")] -fn count_code_region(_index: u32) { - // remove `unsafe` (and safety comment) on bootstrap bump - #[cfg_attr(not(bootstrap), allow(unused_unsafe))] - // SAFETY: the `abort` intrinsic has no requirements to be called. - unsafe { - abort() - } + /// Internal placeholder for injecting code coverage counters when the "instrument-coverage" + /// option is enabled. The placeholder is replaced with `llvm.instrprof.increment` during code + /// generation. + #[cfg(not(bootstrap))] + #[cfg_attr(not(bootstrap), lang = "count_code_region")] + pub fn count_code_region(_index: u32); } // Some functions are defined here because they accidentally got made diff --git a/src/librustc_codegen_ssa/mir/block.rs b/src/librustc_codegen_ssa/mir/block.rs index d7db657154993..665ef77090987 100644 --- a/src/librustc_codegen_ssa/mir/block.rs +++ b/src/librustc_codegen_ssa/mir/block.rs @@ -566,10 +566,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // Handle intrinsics old codegen wants Expr's for, ourselves. let intrinsic = match def { - Some(ty::InstanceDef::Intrinsic(def_id)) - | Some(ty::InstanceDef::InjectedCode(def_id)) => { - Some(bx.tcx().item_name(def_id).as_str()) - } + Some(ty::InstanceDef::Intrinsic(def_id)) => Some(bx.tcx().item_name(def_id).as_str()), _ => None, }; let intrinsic = intrinsic.as_ref().map(|s| &s[..]); diff --git a/src/librustc_middle/mir/mono.rs b/src/librustc_middle/mir/mono.rs index b2c00849d9f83..c889dbc0a4498 100644 --- a/src/librustc_middle/mir/mono.rs +++ b/src/librustc_middle/mir/mono.rs @@ -352,7 +352,6 @@ impl<'tcx> CodegenUnit<'tcx> { InstanceDef::VtableShim(..) | InstanceDef::ReifyShim(..) | InstanceDef::Intrinsic(..) - | InstanceDef::InjectedCode(..) | InstanceDef::FnPtrShim(..) | InstanceDef::Virtual(..) | InstanceDef::ClosureOnceShim { .. } diff --git a/src/librustc_middle/ty/instance.rs b/src/librustc_middle/ty/instance.rs index 4f88e64c5039a..1ce079821a22e 100644 --- a/src/librustc_middle/ty/instance.rs +++ b/src/librustc_middle/ty/instance.rs @@ -21,10 +21,6 @@ pub enum InstanceDef<'tcx> { Item(DefId), Intrinsic(DefId), - /// Injected call to a placeholder function that is replaced with - /// For example: `core::intrinsic::count_code_region()` for code coverage. - InjectedCode(DefId), - /// `::method` where `method` receives unsizeable `self: Self`. VtableShim(DefId), @@ -153,7 +149,6 @@ impl<'tcx> InstanceDef<'tcx> { | InstanceDef::FnPtrShim(def_id, _) | InstanceDef::Virtual(def_id, _) | InstanceDef::Intrinsic(def_id) - | InstanceDef::InjectedCode(def_id) | InstanceDef::ClosureOnceShim { call_once: def_id } | InstanceDef::DropGlue(def_id, _) | InstanceDef::CloneShim(def_id, _) => def_id, @@ -241,7 +236,6 @@ impl<'tcx> fmt::Display for Instance<'tcx> { InstanceDef::VtableShim(_) => write!(f, " - shim(vtable)"), InstanceDef::ReifyShim(_) => write!(f, " - shim(reify)"), InstanceDef::Intrinsic(_) => write!(f, " - intrinsic"), - InstanceDef::InjectedCode(_) => write!(f, " - injected-code"), InstanceDef::Virtual(_, num) => write!(f, " - virtual#{}", num), InstanceDef::FnPtrShim(_, ty) => write!(f, " - shim({:?})", ty), InstanceDef::ClosureOnceShim { .. } => write!(f, " - shim"), @@ -421,7 +415,6 @@ impl<'tcx> Instance<'tcx> { | InstanceDef::FnPtrShim(..) | InstanceDef::Item(_) | InstanceDef::Intrinsic(..) - | InstanceDef::InjectedCode(..) | InstanceDef::ReifyShim(..) | InstanceDef::Virtual(..) | InstanceDef::VtableShim(..) => Some(self.substs), diff --git a/src/librustc_middle/ty/mod.rs b/src/librustc_middle/ty/mod.rs index 9b1e717731e82..93ef73171993c 100644 --- a/src/librustc_middle/ty/mod.rs +++ b/src/librustc_middle/ty/mod.rs @@ -2717,7 +2717,6 @@ impl<'tcx> TyCtxt<'tcx> { ty::InstanceDef::VtableShim(..) | ty::InstanceDef::ReifyShim(..) | ty::InstanceDef::Intrinsic(..) - | ty::InstanceDef::InjectedCode(..) | ty::InstanceDef::FnPtrShim(..) | ty::InstanceDef::Virtual(..) | ty::InstanceDef::ClosureOnceShim { .. } diff --git a/src/librustc_middle/ty/structural_impls.rs b/src/librustc_middle/ty/structural_impls.rs index b6cbd2082a518..f6f5dfd651612 100644 --- a/src/librustc_middle/ty/structural_impls.rs +++ b/src/librustc_middle/ty/structural_impls.rs @@ -674,7 +674,6 @@ impl<'a, 'tcx> Lift<'tcx> for ty::InstanceDef<'a> { ty::InstanceDef::VtableShim(def_id) => Some(ty::InstanceDef::VtableShim(def_id)), ty::InstanceDef::ReifyShim(def_id) => Some(ty::InstanceDef::ReifyShim(def_id)), ty::InstanceDef::Intrinsic(def_id) => Some(ty::InstanceDef::Intrinsic(def_id)), - ty::InstanceDef::InjectedCode(def_id) => Some(ty::InstanceDef::Intrinsic(def_id)), ty::InstanceDef::FnPtrShim(def_id, ref ty) => { Some(ty::InstanceDef::FnPtrShim(def_id, tcx.lift(ty)?)) } @@ -847,7 +846,6 @@ impl<'tcx> TypeFoldable<'tcx> for ty::instance::Instance<'tcx> { VtableShim(did) => VtableShim(did.fold_with(folder)), ReifyShim(did) => ReifyShim(did.fold_with(folder)), Intrinsic(did) => Intrinsic(did.fold_with(folder)), - InjectedCode(did) => InjectedCode(did.fold_with(folder)), FnPtrShim(did, ty) => FnPtrShim(did.fold_with(folder), ty.fold_with(folder)), Virtual(did, i) => Virtual(did.fold_with(folder), i), ClosureOnceShim { call_once } => { @@ -863,12 +861,9 @@ impl<'tcx> TypeFoldable<'tcx> for ty::instance::Instance<'tcx> { use crate::ty::InstanceDef::*; self.substs.visit_with(visitor) || match self.def { - Item(did) - | VtableShim(did) - | ReifyShim(did) - | Intrinsic(did) - | InjectedCode(did) - | Virtual(did, _) => did.visit_with(visitor), + Item(did) | VtableShim(did) | ReifyShim(did) | Intrinsic(did) | Virtual(did, _) => { + did.visit_with(visitor) + } FnPtrShim(did, ty) | CloneShim(did, ty) => { did.visit_with(visitor) || ty.visit_with(visitor) } diff --git a/src/librustc_mir/interpret/terminator.rs b/src/librustc_mir/interpret/terminator.rs index 82fa471b54d73..cd7621ea9752b 100644 --- a/src/librustc_mir/interpret/terminator.rs +++ b/src/librustc_mir/interpret/terminator.rs @@ -257,9 +257,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { assert!(caller_abi == Abi::RustIntrinsic || caller_abi == Abi::PlatformIntrinsic); M::call_intrinsic(self, instance, args, ret, unwind) } - ty::InstanceDef::InjectedCode(..) => { - M::call_intrinsic(self, instance, args, ret, unwind) - } ty::InstanceDef::VtableShim(..) | ty::InstanceDef::ReifyShim(..) | ty::InstanceDef::ClosureOnceShim { .. } diff --git a/src/librustc_mir/monomorphize/collector.rs b/src/librustc_mir/monomorphize/collector.rs index 24c4226bb4e94..994d1e69f2e3e 100644 --- a/src/librustc_mir/monomorphize/collector.rs +++ b/src/librustc_mir/monomorphize/collector.rs @@ -714,9 +714,7 @@ fn visit_instance_use<'tcx>( } match instance.def { - ty::InstanceDef::Virtual(..) - | ty::InstanceDef::Intrinsic(_) - | ty::InstanceDef::InjectedCode(_) => { + ty::InstanceDef::Virtual(..) | ty::InstanceDef::Intrinsic(_) => { if !is_direct_call { bug!("{:?} being reified", instance); } @@ -753,7 +751,6 @@ fn should_monomorphize_locally<'tcx>(tcx: TyCtxt<'tcx>, instance: &Instance<'tcx | ty::InstanceDef::FnPtrShim(..) | ty::InstanceDef::DropGlue(..) | ty::InstanceDef::Intrinsic(_) - | ty::InstanceDef::InjectedCode(_) | ty::InstanceDef::CloneShim(..) => return true, }; diff --git a/src/librustc_mir/monomorphize/partitioning.rs b/src/librustc_mir/monomorphize/partitioning.rs index 7c97b9d611e15..db1ea72c0a531 100644 --- a/src/librustc_mir/monomorphize/partitioning.rs +++ b/src/librustc_mir/monomorphize/partitioning.rs @@ -322,7 +322,6 @@ fn mono_item_visibility( | InstanceDef::FnPtrShim(..) | InstanceDef::Virtual(..) | InstanceDef::Intrinsic(..) - | InstanceDef::InjectedCode(..) | InstanceDef::ClosureOnceShim { .. } | InstanceDef::DropGlue(..) | InstanceDef::CloneShim(..) => return Visibility::Hidden, @@ -718,7 +717,6 @@ fn characteristic_def_id_of_mono_item<'tcx>( | ty::InstanceDef::FnPtrShim(..) | ty::InstanceDef::ClosureOnceShim { .. } | ty::InstanceDef::Intrinsic(..) - | ty::InstanceDef::InjectedCode(..) | ty::InstanceDef::DropGlue(..) | ty::InstanceDef::Virtual(..) | ty::InstanceDef::CloneShim(..) => return None, diff --git a/src/librustc_mir/shim.rs b/src/librustc_mir/shim.rs index b4477d9c86d43..f95fd9b9e90c5 100644 --- a/src/librustc_mir/shim.rs +++ b/src/librustc_mir/shim.rs @@ -109,9 +109,6 @@ fn make_shim<'tcx>(tcx: TyCtxt<'tcx>, instance: ty::InstanceDef<'tcx>) -> Body<' ty::InstanceDef::Intrinsic(_) => { bug!("creating shims from intrinsics ({:?}) is unsupported", instance) } - ty::InstanceDef::InjectedCode(_) => { - bug!("creating shims from injected code ({:?}) is unsupported", instance) - } }; debug!("make_shim({:?}) = untransformed {:?}", instance, result); diff --git a/src/librustc_passes/weak_lang_items.rs b/src/librustc_passes/weak_lang_items.rs index 96ec23692df51..f2f07b5d4fb26 100644 --- a/src/librustc_passes/weak_lang_items.rs +++ b/src/librustc_passes/weak_lang_items.rs @@ -5,10 +5,12 @@ use rustc_errors::struct_span_err; use rustc_hir as hir; use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor}; use rustc_hir::lang_items; +use rustc_hir::lang_items::ITEM_REFS; use rustc_hir::weak_lang_items::WEAK_ITEMS_REFS; use rustc_middle::middle::lang_items::whitelisted; use rustc_middle::ty::TyCtxt; use rustc_session::config::CrateType; +use rustc_span::symbol::sym; use rustc_span::symbol::Symbol; use rustc_span::Span; @@ -70,11 +72,21 @@ fn verify<'tcx>(tcx: TyCtxt<'tcx>, items: &lang_items::LanguageItems) { } impl<'a, 'tcx> Context<'a, 'tcx> { - fn register(&mut self, name: Symbol, span: Span) { + fn register(&mut self, name: Symbol, span: Span, hir_id: hir::HirId) { if let Some(&item) = WEAK_ITEMS_REFS.get(&name) { if self.items.require(item).is_err() { self.items.missing.push(item); } + } else if name == sym::count_code_region { + // `core::intrinsics::code_count_region()` is (currently) the only `extern` lang item + // that is never actually linked. It is not a `weak_lang_item` that can be registered + // when used, and should be registered here instead. + if let Some((item_index, _)) = ITEM_REFS.get(&*name.as_str()).cloned() { + if self.items.items[item_index].is_none() { + let item_def_id = self.tcx.hir().local_def_id(hir_id).to_def_id(); + self.items.items[item_index] = Some(item_def_id); + } + } } else { struct_span_err!(self.tcx.sess, span, E0264, "unknown external lang item: `{}`", name) .emit(); @@ -91,7 +103,7 @@ impl<'a, 'tcx, 'v> Visitor<'v> for Context<'a, 'tcx> { fn visit_foreign_item(&mut self, i: &hir::ForeignItem<'_>) { if let Some((lang_item, _)) = hir::lang_items::extract(&i.attrs) { - self.register(lang_item, i.span); + self.register(lang_item, i.span, i.hir_id); } intravisit::walk_foreign_item(self, i) } diff --git a/src/librustc_ty/instance.rs b/src/librustc_ty/instance.rs index d4ceeff324450..0acf769168137 100644 --- a/src/librustc_ty/instance.rs +++ b/src/librustc_ty/instance.rs @@ -35,10 +35,6 @@ fn resolve_instance<'tcx>( debug!(" => intrinsic"); ty::InstanceDef::Intrinsic(def_id) } - ty::FnDef(def_id, _) if Some(def_id) == tcx.lang_items().count_code_region_fn() => { - debug!(" => injected placeholder function to be replaced"); - ty::InstanceDef::InjectedCode(def_id) - } ty::FnDef(def_id, substs) if Some(def_id) == tcx.lang_items().drop_in_place_fn() => { let ty = substs.type_at(0); diff --git a/src/librustc_typeck/check/intrinsic.rs b/src/librustc_typeck/check/intrinsic.rs index bded2c695c9db..3ec6973a17d56 100644 --- a/src/librustc_typeck/check/intrinsic.rs +++ b/src/librustc_typeck/check/intrinsic.rs @@ -347,6 +347,8 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) { return; } + "count_code_region" => (0, vec![tcx.types.u32], tcx.mk_unit()), + ref other => { struct_span_err!( tcx.sess, From e4df7e70466611a49d3ff6c49d162b2045173449 Mon Sep 17 00:00:00 2001 From: Rich Kadel Date: Fri, 5 Jun 2020 09:49:31 -0700 Subject: [PATCH 05/14] Update src/libcore/intrinsics.rs Co-authored-by: bjorn3 --- src/libcore/intrinsics.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libcore/intrinsics.rs b/src/libcore/intrinsics.rs index 7ce5814d39a02..3806d3ae25487 100644 --- a/src/libcore/intrinsics.rs +++ b/src/libcore/intrinsics.rs @@ -1946,7 +1946,7 @@ extern "rust-intrinsic" { /// option is enabled. The placeholder is replaced with `llvm.instrprof.increment` during code /// generation. #[cfg(not(bootstrap))] - #[cfg_attr(not(bootstrap), lang = "count_code_region")] + #[lang = "count_code_region"] pub fn count_code_region(_index: u32); } From 7e49a9ec59f7950efa9950b65c10f9b3f3a4b6b2 Mon Sep 17 00:00:00 2001 From: Rich Kadel Date: Sun, 7 Jun 2020 19:35:15 -0700 Subject: [PATCH 06/14] moved to post_borrowck_cleanup & used MirPatch --- src/librustc_mir/interpret/intrinsics.rs | 1 + .../transform/instrument_coverage.rs | 112 +++++++++--------- src/librustc_mir/transform/mod.rs | 6 +- src/librustc_session/options.rs | 4 +- 4 files changed, 65 insertions(+), 58 deletions(-) diff --git a/src/librustc_mir/interpret/intrinsics.rs b/src/librustc_mir/interpret/intrinsics.rs index 47e5b8b4fcec4..4d8120794f885 100644 --- a/src/librustc_mir/interpret/intrinsics.rs +++ b/src/librustc_mir/interpret/intrinsics.rs @@ -389,6 +389,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { ); self.copy_op(self.operand_index(args[0], index)?, dest)?; } + sym::count_code_region => (), _ => return Ok(false), } diff --git a/src/librustc_mir/transform/instrument_coverage.rs b/src/librustc_mir/transform/instrument_coverage.rs index 045cd03d1f7da..0604caadaea38 100644 --- a/src/librustc_mir/transform/instrument_coverage.rs +++ b/src/librustc_mir/transform/instrument_coverage.rs @@ -1,8 +1,7 @@ use crate::transform::{MirPass, MirSource}; -use rustc_index::vec::Idx; +use crate::util::patch::MirPatch; use rustc_middle::mir::interpret::Scalar; use rustc_middle::mir::*; -use rustc_middle::mir::{Local, LocalDecl}; use rustc_middle::ty; use rustc_middle::ty::Ty; use rustc_middle::ty::TyCtxt; @@ -16,69 +15,62 @@ pub struct InstrumentCoverage; * the intrinsic llvm.instrprof.increment. */ -// 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. - impl<'tcx> MirPass<'tcx> for InstrumentCoverage { fn run_pass(&self, tcx: TyCtxt<'tcx>, src: MirSource<'tcx>, body: &mut Body<'tcx>) { if tcx.sess.opts.debugging_opts.instrument_coverage { - if let Some(callee_fn_def_id) = tcx.lang_items().count_code_region_fn() { - debug!("instrumenting {:?}", src.def_id()); - instrument_coverage(tcx, callee_fn_def_id, body); - } + debug!("instrumenting {:?}", src.def_id()); + instrument_coverage(tcx, body); } } } -pub fn instrument_coverage<'tcx>( - tcx: TyCtxt<'tcx>, - callee_fn_def_id: DefId, - body: &mut Body<'tcx>, -) { +// The first counter (start of the function) is index zero. +const INIT_FUNCTION_COUNTER: u128 = 0; + +/// Injects calls to placeholder function `count_code_region()`. +// 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. +pub fn instrument_coverage<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { let span = body.span.shrink_to_lo(); - let ret_ty = tcx.fn_sig(callee_fn_def_id).output(); + let count_code_region_fn = + function_handle(tcx, span, tcx.lang_items().count_code_region_fn().unwrap()); + let counter_index = const_int_operand(tcx, span, tcx.types.u32, INIT_FUNCTION_COUNTER); + + let mut patch = MirPatch::new(body); + + let new_block = patch.new_block(placeholder_block(SourceInfo::outermost(body.span))); + let next_block = START_BLOCK; + + let temp = patch.new_temp(tcx.mk_unit(), body.span); + patch.patch_terminator( + new_block, + TerminatorKind::Call { + func: count_code_region_fn, + args: vec![counter_index], + // new_block will swapped with the next_block, after applying patch + destination: Some((Place::from(temp), new_block)), + cleanup: None, + from_hir_call: false, + }, + ); + + patch.add_statement(new_block.start_location(), StatementKind::StorageLive(temp)); + patch.add_statement(next_block.start_location(), StatementKind::StorageDead(temp)); + + patch.apply(body); + + // To insert the `new_block` in front of the first block in the counted branch (for example, + // the START_BLOCK, at the top of the function), just swap the indexes, leaving the rest of the + // graph unchanged. + body.basic_blocks_mut().swap(next_block, new_block); +} + +fn function_handle<'tcx>(tcx: TyCtxt<'tcx>, span: Span, fn_def_id: DefId) -> Operand<'tcx> { + let ret_ty = tcx.fn_sig(fn_def_id).output(); let ret_ty = ret_ty.no_bound_vars().unwrap(); let substs = tcx.mk_substs(::std::iter::once(ty::subst::GenericArg::from(ret_ty))); - - let count_code_region_fn: Operand<'_> = - Operand::function_handle(tcx, callee_fn_def_id, substs, span); - - let index = const_int_operand(tcx, span.clone(), tcx.types.u32, 0); - - let args = vec![index]; - - let source_info = SourceInfo { span: span, scope: OUTERMOST_SOURCE_SCOPE }; - - let new_block = START_BLOCK + body.basic_blocks().len(); - - let next_local = body.local_decls.len(); - let new_temp = Local::new(next_local); - let unit_temp = Place::from(new_temp); - - let storage_live = Statement { source_info, kind: StatementKind::StorageLive(new_temp) }; - let storage_dead = Statement { source_info, kind: StatementKind::StorageDead(new_temp) }; - - let count_code_region_call = TerminatorKind::Call { - func: count_code_region_fn, - args, - destination: Some((unit_temp, new_block)), - cleanup: None, - from_hir_call: false, - }; - - body.local_decls.push(LocalDecl::new(tcx.mk_unit(), body.span)); - body.basic_blocks_mut().push(BasicBlockData { - statements: vec![storage_live], - is_cleanup: false, - terminator: Some(Terminator { source_info, kind: count_code_region_call }), - }); - - body.basic_blocks_mut().swap(START_BLOCK, new_block); - body[new_block].statements.push(storage_dead); - - // FIXME(richkadel): ALSO add each computed Span for each conditional branch to the coverage map - // and provide that map to LLVM to encode in the final binary. + Operand::function_handle(tcx, fn_def_id, substs, span) } fn const_int_operand<'tcx>( @@ -98,3 +90,15 @@ fn const_int_operand<'tcx>( literal: ty::Const::from_scalar(tcx, Scalar::from_uint(val, size), ty), }) } + +fn placeholder_block<'tcx>(source_info: SourceInfo) -> BasicBlockData<'tcx> { + BasicBlockData { + statements: vec![], + terminator: Some(Terminator { + source_info, + // this gets overwritten by the counter Call + kind: TerminatorKind::Unreachable, + }), + is_cleanup: false, + } +} diff --git a/src/librustc_mir/transform/mod.rs b/src/librustc_mir/transform/mod.rs index e03ef48f74838..956ddd2051bac 100644 --- a/src/librustc_mir/transform/mod.rs +++ b/src/librustc_mir/transform/mod.rs @@ -288,8 +288,6 @@ fn mir_validated( &[&[ // What we need to run borrowck etc. &promote_pass, - // FIXME(richkadel): is this the best place for the InstrumentCoverage pass? - &instrument_coverage::InstrumentCoverage, &simplify::SimplifyCfg::new("qualify-consts"), ]], ); @@ -340,6 +338,10 @@ fn run_post_borrowck_cleanup_passes<'tcx>( // `AddRetag` needs to run after `ElaborateDrops`. Otherwise it should run fairly late, // but before optimizations begin. &add_retag::AddRetag, + // If the `instrument-coverage` option is enabled, analyze the CFG, identify each + // conditional branch, construct a coverage map to be passed to LLVM, and inject counters + // where needed. + &instrument_coverage::InstrumentCoverage, &simplify::SimplifyCfg::new("elaborate-drops"), ]; diff --git a/src/librustc_session/options.rs b/src/librustc_session/options.rs index 599ce595e1314..2d231359057fd 100644 --- a/src/librustc_session/options.rs +++ b/src/librustc_session/options.rs @@ -877,8 +877,8 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options, (such as entering an empty infinite loop) by inserting llvm.sideeffect \ (default: no)"), instrument_coverage: bool = (false, parse_bool, [TRACKED], - "instrument the generated code with LLVM code region counters for \ - generating coverage reports (default: no)"), + "instrument the generated code with LLVM code region counters to \ + (in the future) generate coverage reports (experimental; 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], From 46ebd57c42439b3aedcb160f70b022a4f59f4afa Mon Sep 17 00:00:00 2001 From: Rich Kadel Date: Mon, 8 Jun 2020 16:20:26 -0700 Subject: [PATCH 07/14] moved instrument_coverage pass, optimized scalar, added FIXME --- src/librustc_codegen_llvm/intrinsic.rs | 5 ++++ .../transform/instrument_coverage.rs | 27 ++++++++++++------- src/librustc_mir/transform/mod.rs | 8 +++--- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/librustc_codegen_llvm/intrinsic.rs b/src/librustc_codegen_llvm/intrinsic.rs index 7fddda99185b4..95465939070a0 100644 --- a/src/librustc_codegen_llvm/intrinsic.rs +++ b/src/librustc_codegen_llvm/intrinsic.rs @@ -148,6 +148,11 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> { caller_fn_path ); + // FIXME(richkadel): (1) Replace raw function name with mangled function name; + // (2) Replace hardcoded `1234` in `hash` with a computed hash (as discussed in) + // the MCP (compiler-team/issues/278); and replace the hardcoded `1` for + // `num_counters` with the actual number of counters per function (when the + // changes are made to inject more than one counter per function). let (fn_name, _len_val) = self.const_str(Symbol::intern(&caller_fn_path)); let index = args[0].immediate(); let hash = self.const_u64(1234); diff --git a/src/librustc_mir/transform/instrument_coverage.rs b/src/librustc_mir/transform/instrument_coverage.rs index 0604caadaea38..27abe813b067d 100644 --- a/src/librustc_mir/transform/instrument_coverage.rs +++ b/src/librustc_mir/transform/instrument_coverage.rs @@ -7,6 +7,7 @@ use rustc_middle::ty::Ty; use rustc_middle::ty::TyCtxt; use rustc_span::def_id::DefId; use rustc_span::Span; +use rustc_target::abi; pub struct InstrumentCoverage; @@ -25,7 +26,7 @@ impl<'tcx> MirPass<'tcx> for InstrumentCoverage { } // The first counter (start of the function) is index zero. -const INIT_FUNCTION_COUNTER: u128 = 0; +const INIT_FUNCTION_COUNTER: u32 = 0; /// Injects calls to placeholder function `count_code_region()`. // FIXME(richkadel): As a first step, counters are only injected at the top of each function. @@ -35,7 +36,8 @@ pub fn instrument_coverage<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { let count_code_region_fn = function_handle(tcx, span, tcx.lang_items().count_code_region_fn().unwrap()); - let counter_index = const_int_operand(tcx, span, tcx.types.u32, INIT_FUNCTION_COUNTER); + let counter_index = + const_int_operand(tcx, span, tcx.types.u32, Scalar::from_u32(INIT_FUNCTION_COUNTER)); let mut patch = MirPatch::new(body); @@ -77,17 +79,24 @@ fn const_int_operand<'tcx>( tcx: TyCtxt<'tcx>, span: Span, ty: Ty<'tcx>, - val: u128, + val: Scalar, ) -> Operand<'tcx> { - let param_env_and_ty = ty::ParamEnv::empty().and(ty); - let size = tcx - .layout_of(param_env_and_ty) - .unwrap_or_else(|e| panic!("could not compute layout for {:?}: {:?}", ty, e)) - .size; + debug_assert!({ + let param_env_and_ty = ty::ParamEnv::empty().and(ty); + let type_size = tcx + .layout_of(param_env_and_ty) + .unwrap_or_else(|e| panic!("could not compute layout for {:?}: {:?}", ty, e)) + .size; + let scalar_size = abi::Size::from_bytes(match val { + Scalar::Raw { size, .. } => size, + _ => panic!("Invalid scalar type {:?}", val), + }); + scalar_size == type_size + }); Operand::Constant(box Constant { span, user_ty: None, - literal: ty::Const::from_scalar(tcx, Scalar::from_uint(val, size), ty), + literal: ty::Const::from_scalar(tcx, val, ty), }) } diff --git a/src/librustc_mir/transform/mod.rs b/src/librustc_mir/transform/mod.rs index 956ddd2051bac..846ed1f86d8d6 100644 --- a/src/librustc_mir/transform/mod.rs +++ b/src/librustc_mir/transform/mod.rs @@ -289,6 +289,10 @@ fn mir_validated( // What we need to run borrowck etc. &promote_pass, &simplify::SimplifyCfg::new("qualify-consts"), + // If the `instrument-coverage` option is enabled, analyze the CFG, identify each + // conditional branch, construct a coverage map to be passed to LLVM, and inject counters + // where needed. + &instrument_coverage::InstrumentCoverage, ]], ); @@ -338,10 +342,6 @@ fn run_post_borrowck_cleanup_passes<'tcx>( // `AddRetag` needs to run after `ElaborateDrops`. Otherwise it should run fairly late, // but before optimizations begin. &add_retag::AddRetag, - // If the `instrument-coverage` option is enabled, analyze the CFG, identify each - // conditional branch, construct a coverage map to be passed to LLVM, and inject counters - // where needed. - &instrument_coverage::InstrumentCoverage, &simplify::SimplifyCfg::new("elaborate-drops"), ]; From 20aba8f634c13fa2bb1b043b51a074769dc06f66 Mon Sep 17 00:00:00 2001 From: Rich Kadel Date: Wed, 10 Jun 2020 09:54:02 -0700 Subject: [PATCH 08/14] added test, Operand::const_from_scalar, require_lang_item, & comments Addresses feedback from @oli-obk (Thanks!) --- src/librustc_middle/mir/mod.rs | 28 +++++++ src/librustc_mir/interpret/intrinsics.rs | 1 + .../transform/instrument_coverage.rs | 52 ++++-------- src/test/mir-opt/instrument_coverage.rs | 19 +++++ .../rustc.bar.InstrumentCoverage.diff | 41 ++++++++++ .../rustc.main.InstrumentCoverage.diff | 82 +++++++++++++++++++ 6 files changed, 186 insertions(+), 37 deletions(-) create mode 100644 src/test/mir-opt/instrument_coverage.rs create mode 100644 src/test/mir-opt/instrument_coverage/rustc.bar.InstrumentCoverage.diff create mode 100644 src/test/mir-opt/instrument_coverage/rustc.main.InstrumentCoverage.diff diff --git a/src/librustc_middle/mir/mod.rs b/src/librustc_middle/mir/mod.rs index 27848684706d6..11ae2cf72c462 100644 --- a/src/librustc_middle/mir/mod.rs +++ b/src/librustc_middle/mir/mod.rs @@ -29,6 +29,7 @@ use rustc_macros::HashStable; use rustc_serialize::{Decodable, Encodable}; use rustc_span::symbol::Symbol; use rustc_span::{Span, DUMMY_SP}; +use rustc_target::abi; use rustc_target::asm::InlineAsmRegOrRegClass; use std::borrow::Cow; use std::fmt::{self, Debug, Display, Formatter, Write}; @@ -2218,6 +2219,33 @@ impl<'tcx> Operand<'tcx> { }) } + /// Convenience helper to make a literal-like constant from a given scalar value. + /// Since this is used to synthesize MIR, assumes `user_ty` is None. + pub fn const_from_scalar( + tcx: TyCtxt<'tcx>, + ty: Ty<'tcx>, + val: Scalar, + span: Span, + ) -> Operand<'tcx> { + debug_assert!({ + let param_env_and_ty = ty::ParamEnv::empty().and(ty); + let type_size = tcx + .layout_of(param_env_and_ty) + .unwrap_or_else(|e| panic!("could not compute layout for {:?}: {:?}", ty, e)) + .size; + let scalar_size = abi::Size::from_bytes(match val { + Scalar::Raw { size, .. } => size, + _ => panic!("Invalid scalar type {:?}", val), + }); + scalar_size == type_size + }); + Operand::Constant(box Constant { + span, + user_ty: None, + literal: ty::Const::from_scalar(tcx, val, ty), + }) + } + pub fn to_copy(&self) -> Self { match *self { Operand::Copy(_) | Operand::Constant(_) => self.clone(), diff --git a/src/librustc_mir/interpret/intrinsics.rs b/src/librustc_mir/interpret/intrinsics.rs index 4d8120794f885..ac28ccd181520 100644 --- a/src/librustc_mir/interpret/intrinsics.rs +++ b/src/librustc_mir/interpret/intrinsics.rs @@ -389,6 +389,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { ); self.copy_op(self.operand_index(args[0], index)?, dest)?; } + // FIXME(#73156): Handle source code coverage in const eval sym::count_code_region => (), _ => return Ok(false), } diff --git a/src/librustc_mir/transform/instrument_coverage.rs b/src/librustc_mir/transform/instrument_coverage.rs index 27abe813b067d..fda7ad731fa27 100644 --- a/src/librustc_mir/transform/instrument_coverage.rs +++ b/src/librustc_mir/transform/instrument_coverage.rs @@ -1,21 +1,17 @@ use crate::transform::{MirPass, MirSource}; use crate::util::patch::MirPatch; +use rustc_hir::lang_items; use rustc_middle::mir::interpret::Scalar; use rustc_middle::mir::*; use rustc_middle::ty; -use rustc_middle::ty::Ty; use rustc_middle::ty::TyCtxt; use rustc_span::def_id::DefId; use rustc_span::Span; -use rustc_target::abi; +/// Inserts call to count_code_region() as a placeholder to be replaced during code generation with +/// the intrinsic llvm.instrprof.increment. pub struct InstrumentCoverage; -/** - * Inserts call to count_code_region() as a placeholder to be replaced during code generation with - * the intrinsic llvm.instrprof.increment. - */ - impl<'tcx> MirPass<'tcx> for InstrumentCoverage { fn run_pass(&self, tcx: TyCtxt<'tcx>, src: MirSource<'tcx>, body: &mut Body<'tcx>) { if tcx.sess.opts.debugging_opts.instrument_coverage { @@ -34,10 +30,17 @@ const INIT_FUNCTION_COUNTER: u32 = 0; pub fn instrument_coverage<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { let span = body.span.shrink_to_lo(); - let count_code_region_fn = - function_handle(tcx, span, tcx.lang_items().count_code_region_fn().unwrap()); - let counter_index = - const_int_operand(tcx, span, tcx.types.u32, Scalar::from_u32(INIT_FUNCTION_COUNTER)); + let count_code_region_fn = function_handle( + tcx, + tcx.require_lang_item(lang_items::CountCodeRegionFnLangItem, None), + span, + ); + let counter_index = Operand::const_from_scalar( + tcx, + tcx.types.u32, + Scalar::from_u32(INIT_FUNCTION_COUNTER), + span, + ); let mut patch = MirPatch::new(body); @@ -68,38 +71,13 @@ pub fn instrument_coverage<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { body.basic_blocks_mut().swap(next_block, new_block); } -fn function_handle<'tcx>(tcx: TyCtxt<'tcx>, span: Span, fn_def_id: DefId) -> Operand<'tcx> { +fn function_handle<'tcx>(tcx: TyCtxt<'tcx>, fn_def_id: DefId, span: Span) -> Operand<'tcx> { let ret_ty = tcx.fn_sig(fn_def_id).output(); let ret_ty = ret_ty.no_bound_vars().unwrap(); let substs = tcx.mk_substs(::std::iter::once(ty::subst::GenericArg::from(ret_ty))); Operand::function_handle(tcx, fn_def_id, substs, span) } -fn const_int_operand<'tcx>( - tcx: TyCtxt<'tcx>, - span: Span, - ty: Ty<'tcx>, - val: Scalar, -) -> Operand<'tcx> { - debug_assert!({ - let param_env_and_ty = ty::ParamEnv::empty().and(ty); - let type_size = tcx - .layout_of(param_env_and_ty) - .unwrap_or_else(|e| panic!("could not compute layout for {:?}: {:?}", ty, e)) - .size; - let scalar_size = abi::Size::from_bytes(match val { - Scalar::Raw { size, .. } => size, - _ => panic!("Invalid scalar type {:?}", val), - }); - scalar_size == type_size - }); - Operand::Constant(box Constant { - span, - user_ty: None, - literal: ty::Const::from_scalar(tcx, val, ty), - }) -} - fn placeholder_block<'tcx>(source_info: SourceInfo) -> BasicBlockData<'tcx> { BasicBlockData { statements: vec![], diff --git a/src/test/mir-opt/instrument_coverage.rs b/src/test/mir-opt/instrument_coverage.rs new file mode 100644 index 0000000000000..e8c723b528a1a --- /dev/null +++ b/src/test/mir-opt/instrument_coverage.rs @@ -0,0 +1,19 @@ +// Test that the initial version of Rust coverage injects count_code_region() placeholder calls, +// at the top of each function. The placeholders are later converted into LLVM instrprof.increment +// intrinsics, during codegen. + +// compile-flags: -Zinstrument-coverage +// EMIT_MIR rustc.main.InstrumentCoverage.diff +// EMIT_MIR rustc.bar.InstrumentCoverage.diff +fn main() { + loop { + if bar() { + break; + } + } +} + +#[inline(never)] +fn bar() -> bool { + true +} diff --git a/src/test/mir-opt/instrument_coverage/rustc.bar.InstrumentCoverage.diff b/src/test/mir-opt/instrument_coverage/rustc.bar.InstrumentCoverage.diff new file mode 100644 index 0000000000000..d23bb93d951dc --- /dev/null +++ b/src/test/mir-opt/instrument_coverage/rustc.bar.InstrumentCoverage.diff @@ -0,0 +1,41 @@ +- // MIR for `bar` before InstrumentCoverage ++ // MIR for `bar` after InstrumentCoverage + + fn bar() -> bool { + let mut _0: bool; // return place in scope 0 at $DIR/instrument_coverage.rs:17:13: 17:17 ++ let mut _1: (); // in scope 0 at $DIR/instrument_coverage.rs:17:1: 19:2 + + bb0: { ++ StorageLive(_1); // scope 0 at $DIR/instrument_coverage.rs:17:1: 19:2 ++ _1 = const std::intrinsics::count_code_region(const 0u32) -> bb2; // scope 0 at $DIR/instrument_coverage.rs:17:1: 19:2 ++ // ty::Const ++ // + ty: unsafe extern "rust-intrinsic" fn(u32) {std::intrinsics::count_code_region} ++ // + val: Value(Scalar()) ++ // mir::Constant ++ // + span: $DIR/instrument_coverage.rs:17:1: 17:1 ++ // + literal: Const { ty: unsafe extern "rust-intrinsic" fn(u32) {std::intrinsics::count_code_region}, val: Value(Scalar()) } ++ // ty::Const ++ // + ty: u32 ++ // + val: Value(Scalar(0x00000000)) ++ // mir::Constant ++ // + span: $DIR/instrument_coverage.rs:17:1: 17:1 ++ // + literal: Const { ty: u32, val: Value(Scalar(0x00000000)) } ++ } ++ ++ bb1 (cleanup): { ++ resume; // scope 0 at $DIR/instrument_coverage.rs:17:1: 19:2 ++ } ++ ++ bb2: { ++ StorageDead(_1); // scope 0 at $DIR/instrument_coverage.rs:18:5: 18:9 + _0 = const true; // scope 0 at $DIR/instrument_coverage.rs:18:5: 18:9 + // ty::Const + // + ty: bool + // + val: Value(Scalar(0x01)) + // mir::Constant + // + span: $DIR/instrument_coverage.rs:18:5: 18:9 + // + literal: Const { ty: bool, val: Value(Scalar(0x01)) } + return; // scope 0 at $DIR/instrument_coverage.rs:19:2: 19:2 + } + } + diff --git a/src/test/mir-opt/instrument_coverage/rustc.main.InstrumentCoverage.diff b/src/test/mir-opt/instrument_coverage/rustc.main.InstrumentCoverage.diff new file mode 100644 index 0000000000000..d5d0f82495d1a --- /dev/null +++ b/src/test/mir-opt/instrument_coverage/rustc.main.InstrumentCoverage.diff @@ -0,0 +1,82 @@ +- // MIR for `main` before InstrumentCoverage ++ // MIR for `main` after InstrumentCoverage + + fn main() -> () { + let mut _0: (); // return place in scope 0 at $DIR/instrument_coverage.rs:8:11: 8:11 + let mut _1: (); // in scope 0 at $DIR/instrument_coverage.rs:8:1: 14:2 + let mut _2: bool; // in scope 0 at $DIR/instrument_coverage.rs:10:12: 10:17 + let mut _3: !; // in scope 0 at $DIR/instrument_coverage.rs:10:18: 12:10 ++ let mut _4: (); // in scope 0 at $DIR/instrument_coverage.rs:8:1: 14:2 + + bb0: { +- falseUnwind -> [real: bb1, cleanup: bb6]; // scope 0 at $DIR/instrument_coverage.rs:9:5: 13:6 ++ StorageLive(_4); // scope 0 at $DIR/instrument_coverage.rs:8:1: 14:2 ++ _4 = const std::intrinsics::count_code_region(const 0u32) -> bb7; // scope 0 at $DIR/instrument_coverage.rs:8:1: 14:2 ++ // ty::Const ++ // + ty: unsafe extern "rust-intrinsic" fn(u32) {std::intrinsics::count_code_region} ++ // + val: Value(Scalar()) ++ // mir::Constant ++ // + span: $DIR/instrument_coverage.rs:8:1: 8:1 ++ // + literal: Const { ty: unsafe extern "rust-intrinsic" fn(u32) {std::intrinsics::count_code_region}, val: Value(Scalar()) } ++ // ty::Const ++ // + ty: u32 ++ // + val: Value(Scalar(0x00000000)) ++ // mir::Constant ++ // + span: $DIR/instrument_coverage.rs:8:1: 8:1 ++ // + literal: Const { ty: u32, val: Value(Scalar(0x00000000)) } + } + + bb1: { + StorageLive(_2); // scope 0 at $DIR/instrument_coverage.rs:10:12: 10:17 + _2 = const bar() -> [return: bb2, unwind: bb6]; // scope 0 at $DIR/instrument_coverage.rs:10:12: 10:17 + // ty::Const + // + ty: fn() -> bool {bar} + // + val: Value(Scalar()) + // mir::Constant + // + span: $DIR/instrument_coverage.rs:10:12: 10:15 + // + literal: Const { ty: fn() -> bool {bar}, val: Value(Scalar()) } + } + + bb2: { + FakeRead(ForMatchedPlace, _2); // scope 0 at $DIR/instrument_coverage.rs:10:12: 10:17 + switchInt(_2) -> [false: bb4, otherwise: bb3]; // scope 0 at $DIR/instrument_coverage.rs:10:9: 12:10 + } + + bb3: { + falseEdges -> [real: bb5, imaginary: bb4]; // scope 0 at $DIR/instrument_coverage.rs:10:9: 12:10 + } + + bb4: { + _1 = const (); // scope 0 at $DIR/instrument_coverage.rs:10:9: 12:10 + // ty::Const + // + ty: () + // + val: Value(Scalar()) + // mir::Constant + // + span: $DIR/instrument_coverage.rs:10:9: 12:10 + // + literal: Const { ty: (), val: Value(Scalar()) } + StorageDead(_2); // scope 0 at $DIR/instrument_coverage.rs:13:5: 13:6 + goto -> bb0; // scope 0 at $DIR/instrument_coverage.rs:9:5: 13:6 + } + + bb5: { + _0 = const (); // scope 0 at $DIR/instrument_coverage.rs:11:13: 11:18 + // ty::Const + // + ty: () + // + val: Value(Scalar()) + // mir::Constant + // + span: $DIR/instrument_coverage.rs:11:13: 11:18 + // + literal: Const { ty: (), val: Value(Scalar()) } + StorageDead(_2); // scope 0 at $DIR/instrument_coverage.rs:13:5: 13:6 + return; // scope 0 at $DIR/instrument_coverage.rs:14:2: 14:2 + } + + bb6 (cleanup): { + resume; // scope 0 at $DIR/instrument_coverage.rs:8:1: 14:2 ++ } ++ ++ bb7: { ++ StorageDead(_4); // scope 0 at $DIR/instrument_coverage.rs:9:5: 13:6 ++ falseUnwind -> [real: bb1, cleanup: bb6]; // scope 0 at $DIR/instrument_coverage.rs:9:5: 13:6 + } + } + From 163e5854562f5274f092d66318a5c805e18d83c5 Mon Sep 17 00:00:00 2001 From: Rich Kadel Date: Wed, 10 Jun 2020 12:48:30 -0700 Subject: [PATCH 09/14] updated mir-opt test due to other recent changes to MIR --- .../rustc.main.InstrumentCoverage.diff | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/test/mir-opt/instrument_coverage/rustc.main.InstrumentCoverage.diff b/src/test/mir-opt/instrument_coverage/rustc.main.InstrumentCoverage.diff index d5d0f82495d1a..095246580409e 100644 --- a/src/test/mir-opt/instrument_coverage/rustc.main.InstrumentCoverage.diff +++ b/src/test/mir-opt/instrument_coverage/rustc.main.InstrumentCoverage.diff @@ -9,7 +9,7 @@ + let mut _4: (); // in scope 0 at $DIR/instrument_coverage.rs:8:1: 14:2 bb0: { -- falseUnwind -> [real: bb1, cleanup: bb6]; // scope 0 at $DIR/instrument_coverage.rs:9:5: 13:6 +- falseUnwind -> [real: bb1, cleanup: bb2]; // scope 0 at $DIR/instrument_coverage.rs:9:5: 13:6 + StorageLive(_4); // scope 0 at $DIR/instrument_coverage.rs:8:1: 14:2 + _4 = const std::intrinsics::count_code_region(const 0u32) -> bb7; // scope 0 at $DIR/instrument_coverage.rs:8:1: 14:2 + // ty::Const @@ -28,7 +28,7 @@ bb1: { StorageLive(_2); // scope 0 at $DIR/instrument_coverage.rs:10:12: 10:17 - _2 = const bar() -> [return: bb2, unwind: bb6]; // scope 0 at $DIR/instrument_coverage.rs:10:12: 10:17 + _2 = const bar() -> [return: bb3, unwind: bb2]; // scope 0 at $DIR/instrument_coverage.rs:10:12: 10:17 // ty::Const // + ty: fn() -> bool {bar} // + val: Value(Scalar()) @@ -37,16 +37,20 @@ // + literal: Const { ty: fn() -> bool {bar}, val: Value(Scalar()) } } - bb2: { - FakeRead(ForMatchedPlace, _2); // scope 0 at $DIR/instrument_coverage.rs:10:12: 10:17 - switchInt(_2) -> [false: bb4, otherwise: bb3]; // scope 0 at $DIR/instrument_coverage.rs:10:9: 12:10 + bb2 (cleanup): { + resume; // scope 0 at $DIR/instrument_coverage.rs:8:1: 14:2 } bb3: { - falseEdges -> [real: bb5, imaginary: bb4]; // scope 0 at $DIR/instrument_coverage.rs:10:9: 12:10 + FakeRead(ForMatchedPlace, _2); // scope 0 at $DIR/instrument_coverage.rs:10:12: 10:17 + switchInt(_2) -> [false: bb5, otherwise: bb4]; // scope 0 at $DIR/instrument_coverage.rs:10:9: 12:10 } bb4: { + falseEdge -> [real: bb6, imaginary: bb5]; // scope 0 at $DIR/instrument_coverage.rs:10:9: 12:10 + } + + bb5: { _1 = const (); // scope 0 at $DIR/instrument_coverage.rs:10:9: 12:10 // ty::Const // + ty: () @@ -58,7 +62,7 @@ goto -> bb0; // scope 0 at $DIR/instrument_coverage.rs:9:5: 13:6 } - bb5: { + bb6: { _0 = const (); // scope 0 at $DIR/instrument_coverage.rs:11:13: 11:18 // ty::Const // + ty: () @@ -68,15 +72,11 @@ // + literal: Const { ty: (), val: Value(Scalar()) } StorageDead(_2); // scope 0 at $DIR/instrument_coverage.rs:13:5: 13:6 return; // scope 0 at $DIR/instrument_coverage.rs:14:2: 14:2 - } - - bb6 (cleanup): { - resume; // scope 0 at $DIR/instrument_coverage.rs:8:1: 14:2 + } + + bb7: { + StorageDead(_4); // scope 0 at $DIR/instrument_coverage.rs:9:5: 13:6 -+ falseUnwind -> [real: bb1, cleanup: bb6]; // scope 0 at $DIR/instrument_coverage.rs:9:5: 13:6 ++ falseUnwind -> [real: bb1, cleanup: bb2]; // scope 0 at $DIR/instrument_coverage.rs:9:5: 13:6 } } From 98685a4bf2ef50c6d6a64ef3867a29994d5a4a25 Mon Sep 17 00:00:00 2001 From: Rich Kadel Date: Mon, 15 Jun 2020 17:08:13 -0700 Subject: [PATCH 10/14] Add new `fn_span` to TerminatorKind::Call instance --- src/librustc_mir/transform/instrument_coverage.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/librustc_mir/transform/instrument_coverage.rs b/src/librustc_mir/transform/instrument_coverage.rs index fda7ad731fa27..c36614938e10f 100644 --- a/src/librustc_mir/transform/instrument_coverage.rs +++ b/src/librustc_mir/transform/instrument_coverage.rs @@ -57,6 +57,7 @@ pub fn instrument_coverage<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { destination: Some((Place::from(temp), new_block)), cleanup: None, from_hir_call: false, + fn_span: span, }, ); From 1db44afecd892351ae91499b1baefee433bbc04b Mon Sep 17 00:00:00 2001 From: Rich Kadel Date: Tue, 16 Jun 2020 18:48:46 -0700 Subject: [PATCH 11/14] Ensure profiling runtime for -Zinstrument-coverage If config.toml `profiler = false`, the test/mir-opt/instrument_coverage test is ignored. Otherwise, this patch ensures the profiler_runtime is loaded when -Zinstrument-coverage is enabled. Confirmed that this works for MacOS. --- config.toml.example | 3 +- src/librustc_metadata/creader.rs | 4 +- src/test/mir-opt/instrument_coverage.rs | 1 + .../rustc.bar.InstrumentCoverage.diff | 22 ++++---- .../rustc.main.InstrumentCoverage.diff | 54 +++++++++---------- 5 files changed, 44 insertions(+), 40 deletions(-) diff --git a/config.toml.example b/config.toml.example index d995554913f84..bc6760334170b 100644 --- a/config.toml.example +++ b/config.toml.example @@ -209,7 +209,8 @@ # Build the sanitizer runtimes #sanitizers = false -# Build the profiler runtime +# Build the profiler runtime (required when compiling with options that depend +# on this runtime, such as `-C profile-generate` or `-Z instrument-coverage`). #profiler = false # Indicates whether the native libraries linked into Cargo will be statically diff --git a/src/librustc_metadata/creader.rs b/src/librustc_metadata/creader.rs index b8ebcd6c8a8ff..f3e4f40bd5a18 100644 --- a/src/librustc_metadata/creader.rs +++ b/src/librustc_metadata/creader.rs @@ -698,7 +698,9 @@ impl<'a> CrateLoader<'a> { } fn inject_profiler_runtime(&mut self) { - if (self.sess.opts.debugging_opts.profile || self.sess.opts.cg.profile_generate.enabled()) + if (self.sess.opts.debugging_opts.instrument_coverage + || self.sess.opts.debugging_opts.profile + || self.sess.opts.cg.profile_generate.enabled()) && !self.sess.opts.debugging_opts.no_profiler_runtime { info!("loading profiler"); diff --git a/src/test/mir-opt/instrument_coverage.rs b/src/test/mir-opt/instrument_coverage.rs index e8c723b528a1a..3fe010ef68fc3 100644 --- a/src/test/mir-opt/instrument_coverage.rs +++ b/src/test/mir-opt/instrument_coverage.rs @@ -2,6 +2,7 @@ // at the top of each function. The placeholders are later converted into LLVM instrprof.increment // intrinsics, during codegen. +// needs-profiler-support // compile-flags: -Zinstrument-coverage // EMIT_MIR rustc.main.InstrumentCoverage.diff // EMIT_MIR rustc.bar.InstrumentCoverage.diff diff --git a/src/test/mir-opt/instrument_coverage/rustc.bar.InstrumentCoverage.diff b/src/test/mir-opt/instrument_coverage/rustc.bar.InstrumentCoverage.diff index d23bb93d951dc..1e64379aa0e4b 100644 --- a/src/test/mir-opt/instrument_coverage/rustc.bar.InstrumentCoverage.diff +++ b/src/test/mir-opt/instrument_coverage/rustc.bar.InstrumentCoverage.diff @@ -2,40 +2,40 @@ + // MIR for `bar` after InstrumentCoverage fn bar() -> bool { - let mut _0: bool; // return place in scope 0 at $DIR/instrument_coverage.rs:17:13: 17:17 -+ let mut _1: (); // in scope 0 at $DIR/instrument_coverage.rs:17:1: 19:2 + let mut _0: bool; // return place in scope 0 at $DIR/instrument_coverage.rs:18:13: 18:17 ++ let mut _1: (); // in scope 0 at $DIR/instrument_coverage.rs:18:1: 20:2 bb0: { -+ StorageLive(_1); // scope 0 at $DIR/instrument_coverage.rs:17:1: 19:2 -+ _1 = const std::intrinsics::count_code_region(const 0u32) -> bb2; // scope 0 at $DIR/instrument_coverage.rs:17:1: 19:2 ++ StorageLive(_1); // scope 0 at $DIR/instrument_coverage.rs:18:1: 20:2 ++ _1 = const std::intrinsics::count_code_region(const 0u32) -> bb2; // scope 0 at $DIR/instrument_coverage.rs:18:1: 20:2 + // ty::Const + // + ty: unsafe extern "rust-intrinsic" fn(u32) {std::intrinsics::count_code_region} + // + val: Value(Scalar()) + // mir::Constant -+ // + span: $DIR/instrument_coverage.rs:17:1: 17:1 ++ // + span: $DIR/instrument_coverage.rs:18:1: 18:1 + // + literal: Const { ty: unsafe extern "rust-intrinsic" fn(u32) {std::intrinsics::count_code_region}, val: Value(Scalar()) } + // ty::Const + // + ty: u32 + // + val: Value(Scalar(0x00000000)) + // mir::Constant -+ // + span: $DIR/instrument_coverage.rs:17:1: 17:1 ++ // + span: $DIR/instrument_coverage.rs:18:1: 18:1 + // + literal: Const { ty: u32, val: Value(Scalar(0x00000000)) } + } + + bb1 (cleanup): { -+ resume; // scope 0 at $DIR/instrument_coverage.rs:17:1: 19:2 ++ resume; // scope 0 at $DIR/instrument_coverage.rs:18:1: 20:2 + } + + bb2: { -+ StorageDead(_1); // scope 0 at $DIR/instrument_coverage.rs:18:5: 18:9 - _0 = const true; // scope 0 at $DIR/instrument_coverage.rs:18:5: 18:9 ++ StorageDead(_1); // scope 0 at $DIR/instrument_coverage.rs:19:5: 19:9 + _0 = const true; // scope 0 at $DIR/instrument_coverage.rs:19:5: 19:9 // ty::Const // + ty: bool // + val: Value(Scalar(0x01)) // mir::Constant - // + span: $DIR/instrument_coverage.rs:18:5: 18:9 + // + span: $DIR/instrument_coverage.rs:19:5: 19:9 // + literal: Const { ty: bool, val: Value(Scalar(0x01)) } - return; // scope 0 at $DIR/instrument_coverage.rs:19:2: 19:2 + return; // scope 0 at $DIR/instrument_coverage.rs:20:2: 20:2 } } diff --git a/src/test/mir-opt/instrument_coverage/rustc.main.InstrumentCoverage.diff b/src/test/mir-opt/instrument_coverage/rustc.main.InstrumentCoverage.diff index 095246580409e..82d21467827eb 100644 --- a/src/test/mir-opt/instrument_coverage/rustc.main.InstrumentCoverage.diff +++ b/src/test/mir-opt/instrument_coverage/rustc.main.InstrumentCoverage.diff @@ -2,81 +2,81 @@ + // MIR for `main` after InstrumentCoverage fn main() -> () { - let mut _0: (); // return place in scope 0 at $DIR/instrument_coverage.rs:8:11: 8:11 - let mut _1: (); // in scope 0 at $DIR/instrument_coverage.rs:8:1: 14:2 - let mut _2: bool; // in scope 0 at $DIR/instrument_coverage.rs:10:12: 10:17 - let mut _3: !; // in scope 0 at $DIR/instrument_coverage.rs:10:18: 12:10 -+ let mut _4: (); // in scope 0 at $DIR/instrument_coverage.rs:8:1: 14:2 + let mut _0: (); // return place in scope 0 at $DIR/instrument_coverage.rs:9:11: 9:11 + let mut _1: (); // in scope 0 at $DIR/instrument_coverage.rs:9:1: 15:2 + let mut _2: bool; // in scope 0 at $DIR/instrument_coverage.rs:11:12: 11:17 + let mut _3: !; // in scope 0 at $DIR/instrument_coverage.rs:11:18: 13:10 ++ let mut _4: (); // in scope 0 at $DIR/instrument_coverage.rs:9:1: 15:2 bb0: { -- falseUnwind -> [real: bb1, cleanup: bb2]; // scope 0 at $DIR/instrument_coverage.rs:9:5: 13:6 -+ StorageLive(_4); // scope 0 at $DIR/instrument_coverage.rs:8:1: 14:2 -+ _4 = const std::intrinsics::count_code_region(const 0u32) -> bb7; // scope 0 at $DIR/instrument_coverage.rs:8:1: 14:2 +- falseUnwind -> [real: bb1, cleanup: bb2]; // scope 0 at $DIR/instrument_coverage.rs:10:5: 14:6 ++ StorageLive(_4); // scope 0 at $DIR/instrument_coverage.rs:9:1: 15:2 ++ _4 = const std::intrinsics::count_code_region(const 0u32) -> bb7; // scope 0 at $DIR/instrument_coverage.rs:9:1: 15:2 + // ty::Const + // + ty: unsafe extern "rust-intrinsic" fn(u32) {std::intrinsics::count_code_region} + // + val: Value(Scalar()) + // mir::Constant -+ // + span: $DIR/instrument_coverage.rs:8:1: 8:1 ++ // + span: $DIR/instrument_coverage.rs:9:1: 9:1 + // + literal: Const { ty: unsafe extern "rust-intrinsic" fn(u32) {std::intrinsics::count_code_region}, val: Value(Scalar()) } + // ty::Const + // + ty: u32 + // + val: Value(Scalar(0x00000000)) + // mir::Constant -+ // + span: $DIR/instrument_coverage.rs:8:1: 8:1 ++ // + span: $DIR/instrument_coverage.rs:9:1: 9:1 + // + literal: Const { ty: u32, val: Value(Scalar(0x00000000)) } } bb1: { - StorageLive(_2); // scope 0 at $DIR/instrument_coverage.rs:10:12: 10:17 - _2 = const bar() -> [return: bb3, unwind: bb2]; // scope 0 at $DIR/instrument_coverage.rs:10:12: 10:17 + StorageLive(_2); // scope 0 at $DIR/instrument_coverage.rs:11:12: 11:17 + _2 = const bar() -> [return: bb3, unwind: bb2]; // scope 0 at $DIR/instrument_coverage.rs:11:12: 11:17 // ty::Const // + ty: fn() -> bool {bar} // + val: Value(Scalar()) // mir::Constant - // + span: $DIR/instrument_coverage.rs:10:12: 10:15 + // + span: $DIR/instrument_coverage.rs:11:12: 11:15 // + literal: Const { ty: fn() -> bool {bar}, val: Value(Scalar()) } } bb2 (cleanup): { - resume; // scope 0 at $DIR/instrument_coverage.rs:8:1: 14:2 + resume; // scope 0 at $DIR/instrument_coverage.rs:9:1: 15:2 } bb3: { - FakeRead(ForMatchedPlace, _2); // scope 0 at $DIR/instrument_coverage.rs:10:12: 10:17 - switchInt(_2) -> [false: bb5, otherwise: bb4]; // scope 0 at $DIR/instrument_coverage.rs:10:9: 12:10 + FakeRead(ForMatchedPlace, _2); // scope 0 at $DIR/instrument_coverage.rs:11:12: 11:17 + switchInt(_2) -> [false: bb5, otherwise: bb4]; // scope 0 at $DIR/instrument_coverage.rs:11:9: 13:10 } bb4: { - falseEdge -> [real: bb6, imaginary: bb5]; // scope 0 at $DIR/instrument_coverage.rs:10:9: 12:10 + falseEdge -> [real: bb6, imaginary: bb5]; // scope 0 at $DIR/instrument_coverage.rs:11:9: 13:10 } bb5: { - _1 = const (); // scope 0 at $DIR/instrument_coverage.rs:10:9: 12:10 + _1 = const (); // scope 0 at $DIR/instrument_coverage.rs:11:9: 13:10 // ty::Const // + ty: () // + val: Value(Scalar()) // mir::Constant - // + span: $DIR/instrument_coverage.rs:10:9: 12:10 + // + span: $DIR/instrument_coverage.rs:11:9: 13:10 // + literal: Const { ty: (), val: Value(Scalar()) } - StorageDead(_2); // scope 0 at $DIR/instrument_coverage.rs:13:5: 13:6 - goto -> bb0; // scope 0 at $DIR/instrument_coverage.rs:9:5: 13:6 + StorageDead(_2); // scope 0 at $DIR/instrument_coverage.rs:14:5: 14:6 + goto -> bb0; // scope 0 at $DIR/instrument_coverage.rs:10:5: 14:6 } bb6: { - _0 = const (); // scope 0 at $DIR/instrument_coverage.rs:11:13: 11:18 + _0 = const (); // scope 0 at $DIR/instrument_coverage.rs:12:13: 12:18 // ty::Const // + ty: () // + val: Value(Scalar()) // mir::Constant - // + span: $DIR/instrument_coverage.rs:11:13: 11:18 + // + span: $DIR/instrument_coverage.rs:12:13: 12:18 // + literal: Const { ty: (), val: Value(Scalar()) } - StorageDead(_2); // scope 0 at $DIR/instrument_coverage.rs:13:5: 13:6 - return; // scope 0 at $DIR/instrument_coverage.rs:14:2: 14:2 + StorageDead(_2); // scope 0 at $DIR/instrument_coverage.rs:14:5: 14:6 + return; // scope 0 at $DIR/instrument_coverage.rs:15:2: 15:2 + } + + bb7: { -+ StorageDead(_4); // scope 0 at $DIR/instrument_coverage.rs:9:5: 13:6 -+ falseUnwind -> [real: bb1, cleanup: bb2]; // scope 0 at $DIR/instrument_coverage.rs:9:5: 13:6 ++ StorageDead(_4); // scope 0 at $DIR/instrument_coverage.rs:10:5: 14:6 ++ falseUnwind -> [real: bb1, cleanup: bb2]; // scope 0 at $DIR/instrument_coverage.rs:10:5: 14:6 } } From c3387293d4ff050cce2c7f3a79c8e7b040515f8c Mon Sep 17 00:00:00 2001 From: Rich Kadel Date: Wed, 17 Jun 2020 10:29:00 -0700 Subject: [PATCH 12/14] Update src/libcore/intrinsics.rs Co-authored-by: bjorn3 --- src/libcore/intrinsics.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libcore/intrinsics.rs b/src/libcore/intrinsics.rs index 3806d3ae25487..2d3e181466105 100644 --- a/src/libcore/intrinsics.rs +++ b/src/libcore/intrinsics.rs @@ -1947,7 +1947,7 @@ extern "rust-intrinsic" { /// generation. #[cfg(not(bootstrap))] #[lang = "count_code_region"] - pub fn count_code_region(_index: u32); + pub fn count_code_region(index: u32); } // Some functions are defined here because they accidentally got made From b9f0304af8b74bdf01cd39f1603d73bdc599de79 Mon Sep 17 00:00:00 2001 From: Rich Kadel Date: Wed, 17 Jun 2020 11:24:43 -0700 Subject: [PATCH 13/14] temporarily enable mac and windows tests on bors try testing platform-specific changes --- src/ci/azure-pipelines/try.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/ci/azure-pipelines/try.yml b/src/ci/azure-pipelines/try.yml index 38a0685e0f75a..b57b691641ca5 100644 --- a/src/ci/azure-pipelines/try.yml +++ b/src/ci/azure-pipelines/try.yml @@ -82,3 +82,33 @@ jobs: # INITIAL_RUST_CONFIGURE_ARGS: --build=x86_64-pc-windows-msvc --enable-extended --enable-profiler # SCRIPT: python x.py dist # DEPLOY_ALT: 1 + +- job: macOS + timeoutInMinutes: 600 + pool: + vmImage: macos-10.15 + steps: + - template: steps/run.yml + strategy: + matrix: + x86_64-apple: + SCRIPT: ./x.py test + INITIAL_RUST_CONFIGURE_ARGS: --build=x86_64-apple-darwin --enable-sanitizers --enable-profiler --set rust.jemalloc + RUSTC_RETRY_LINKER_ON_SEGFAULT: 1 + MACOSX_DEPLOYMENT_TARGET: 10.8 + MACOSX_STD_DEPLOYMENT_TARGET: 10.7 + NO_LLVM_ASSERTIONS: 1 + NO_DEBUG_ASSERTIONS: 1 +- job: Windows + timeoutInMinutes: 600 + pool: + vmImage: 'vs2017-win2016' + steps: + - template: steps/run.yml + strategy: + matrix: + x86_64-msvc: + INITIAL_RUST_CONFIGURE_ARGS: > + --build=x86_64-pc-windows-msvc + --enable-profiler + SCRIPT: python x.py test From 36c9014ddd3e2ac6b6a0e9f623e791281c40473d Mon Sep 17 00:00:00 2001 From: Rich Kadel Date: Wed, 17 Jun 2020 14:50:28 -0700 Subject: [PATCH 14/14] removed try config to test mac & windows (passed) https://dev.azure.com/rust-lang/rust/_build/results?buildId=32224&view=results --- src/ci/azure-pipelines/try.yml | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/src/ci/azure-pipelines/try.yml b/src/ci/azure-pipelines/try.yml index b57b691641ca5..38a0685e0f75a 100644 --- a/src/ci/azure-pipelines/try.yml +++ b/src/ci/azure-pipelines/try.yml @@ -82,33 +82,3 @@ jobs: # INITIAL_RUST_CONFIGURE_ARGS: --build=x86_64-pc-windows-msvc --enable-extended --enable-profiler # SCRIPT: python x.py dist # DEPLOY_ALT: 1 - -- job: macOS - timeoutInMinutes: 600 - pool: - vmImage: macos-10.15 - steps: - - template: steps/run.yml - strategy: - matrix: - x86_64-apple: - SCRIPT: ./x.py test - INITIAL_RUST_CONFIGURE_ARGS: --build=x86_64-apple-darwin --enable-sanitizers --enable-profiler --set rust.jemalloc - RUSTC_RETRY_LINKER_ON_SEGFAULT: 1 - MACOSX_DEPLOYMENT_TARGET: 10.8 - MACOSX_STD_DEPLOYMENT_TARGET: 10.7 - NO_LLVM_ASSERTIONS: 1 - NO_DEBUG_ASSERTIONS: 1 -- job: Windows - timeoutInMinutes: 600 - pool: - vmImage: 'vs2017-win2016' - steps: - - template: steps/run.yml - strategy: - matrix: - x86_64-msvc: - INITIAL_RUST_CONFIGURE_ARGS: > - --build=x86_64-pc-windows-msvc - --enable-profiler - SCRIPT: python x.py test