Rollup merge of #122322 - Zalathar:branch, r=oli-obk
coverage: Initial support for branch coverage instrumentation (This is a review-ready version of the changes that were drafted in #118305.) This PR adds support for branch coverage instrumentation, gated behind the unstable flag value `-Zcoverage-options=branch`. (Coverage instrumentation must also be enabled with `-Cinstrument-coverage`.) During THIR-to-MIR lowering (MIR building), if branch coverage is enabled, we collect additional information about branch conditions and their corresponding then/else blocks. We inject special marker statements into those blocks, so that the `InstrumentCoverage` MIR pass can reliably identify them even after the initially-built MIR has been simplified and renumbered. The rest of the changes are mostly just plumbing needed to gather up the information that was collected during MIR building, and include it in the coverage metadata that we embed in the final binary. Note that `llvm-cov show` doesn't print branch coverage information in its source views by default; that needs to be explicitly enabled with `--show-branches=count` or similar. --- The current implementation doesn't have any support for instrumenting `if let` or let-chains. I think it's still useful without that, and adding it would be non-trivial, so I'm happy to leave that for future work.
This commit is contained in:
commit
54a5a49af0
31 changed files with 1250 additions and 59 deletions
|
@ -164,6 +164,15 @@ impl CounterMappingRegion {
|
|||
end_line,
|
||||
end_col,
|
||||
),
|
||||
MappingKind::Branch { true_term, false_term } => Self::branch_region(
|
||||
Counter::from_term(true_term),
|
||||
Counter::from_term(false_term),
|
||||
local_file_id,
|
||||
start_line,
|
||||
start_col,
|
||||
end_line,
|
||||
end_col,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -188,9 +197,6 @@ impl CounterMappingRegion {
|
|||
}
|
||||
}
|
||||
|
||||
// This function might be used in the future; the LLVM API is still evolving, as is coverage
|
||||
// support.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn branch_region(
|
||||
counter: Counter,
|
||||
false_counter: Counter,
|
||||
|
|
|
@ -88,7 +88,7 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> {
|
|||
match coverage.kind {
|
||||
// Marker statements have no effect during codegen,
|
||||
// so return early and don't create `func_coverage`.
|
||||
CoverageKind::SpanMarker => return,
|
||||
CoverageKind::SpanMarker | CoverageKind::BlockMarker { .. } => return,
|
||||
// Match exhaustively to ensure that newly-added kinds are classified correctly.
|
||||
CoverageKind::CounterIncrement { .. } | CoverageKind::ExpressionUsed { .. } => {}
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> {
|
|||
|
||||
let Coverage { kind } = coverage;
|
||||
match *kind {
|
||||
CoverageKind::SpanMarker => unreachable!(
|
||||
CoverageKind::SpanMarker | CoverageKind::BlockMarker { .. } => unreachable!(
|
||||
"unexpected marker statement {kind:?} should have caused an early return"
|
||||
),
|
||||
CoverageKind::CounterIncrement { id } => {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
use crate::mir;
|
||||
use crate::query::TyCtxtAt;
|
||||
use crate::ty::{Ty, TyCtxt};
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::DUMMY_SP;
|
||||
|
||||
macro_rules! declare_hooks {
|
||||
|
@ -70,4 +71,10 @@ declare_hooks! {
|
|||
|
||||
/// Getting a &core::panic::Location referring to a span.
|
||||
hook const_caller_location(file: rustc_span::Symbol, line: u32, col: u32) -> mir::ConstValue<'tcx>;
|
||||
|
||||
/// Returns `true` if this def is a function-like thing that is eligible for
|
||||
/// coverage instrumentation under `-Cinstrument-coverage`.
|
||||
///
|
||||
/// (Eligible functions might nevertheless be skipped for other reasons.)
|
||||
hook is_eligible_for_coverage(key: LocalDefId) -> bool;
|
||||
}
|
||||
|
|
|
@ -2,10 +2,19 @@
|
|||
|
||||
use rustc_index::IndexVec;
|
||||
use rustc_macros::HashStable;
|
||||
use rustc_span::Symbol;
|
||||
use rustc_span::{Span, Symbol};
|
||||
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
|
||||
rustc_index::newtype_index! {
|
||||
/// Used by [`CoverageKind::BlockMarker`] to mark blocks during THIR-to-MIR
|
||||
/// lowering, so that those blocks can be identified later.
|
||||
#[derive(HashStable)]
|
||||
#[encodable]
|
||||
#[debug_format = "BlockMarkerId({})"]
|
||||
pub struct BlockMarkerId {}
|
||||
}
|
||||
|
||||
rustc_index::newtype_index! {
|
||||
/// ID of a coverage counter. Values ascend from 0.
|
||||
///
|
||||
|
@ -83,6 +92,12 @@ pub enum CoverageKind {
|
|||
/// codegen.
|
||||
SpanMarker,
|
||||
|
||||
/// Marks its enclosing basic block with an ID that can be referred to by
|
||||
/// side data in [`BranchInfo`].
|
||||
///
|
||||
/// Has no effect during codegen.
|
||||
BlockMarker { id: BlockMarkerId },
|
||||
|
||||
/// Marks the point in MIR control flow represented by a coverage counter.
|
||||
///
|
||||
/// This is eventually lowered to `llvm.instrprof.increment` in LLVM IR.
|
||||
|
@ -107,6 +122,7 @@ impl Debug for CoverageKind {
|
|||
use CoverageKind::*;
|
||||
match self {
|
||||
SpanMarker => write!(fmt, "SpanMarker"),
|
||||
BlockMarker { id } => write!(fmt, "BlockMarker({:?})", id.index()),
|
||||
CounterIncrement { id } => write!(fmt, "CounterIncrement({:?})", id.index()),
|
||||
ExpressionUsed { id } => write!(fmt, "ExpressionUsed({:?})", id.index()),
|
||||
}
|
||||
|
@ -163,14 +179,18 @@ pub struct Expression {
|
|||
pub enum MappingKind {
|
||||
/// Associates a normal region of code with a counter/expression/zero.
|
||||
Code(CovTerm),
|
||||
/// Associates a branch region with separate counters for true and false.
|
||||
Branch { true_term: CovTerm, false_term: CovTerm },
|
||||
}
|
||||
|
||||
impl MappingKind {
|
||||
/// Iterator over all coverage terms in this mapping kind.
|
||||
pub fn terms(&self) -> impl Iterator<Item = CovTerm> {
|
||||
let one = |a| std::iter::once(a);
|
||||
let one = |a| std::iter::once(a).chain(None);
|
||||
let two = |a, b| std::iter::once(a).chain(Some(b));
|
||||
match *self {
|
||||
Self::Code(term) => one(term),
|
||||
Self::Branch { true_term, false_term } => two(true_term, false_term),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,6 +199,9 @@ impl MappingKind {
|
|||
pub fn map_terms(&self, map_fn: impl Fn(CovTerm) -> CovTerm) -> Self {
|
||||
match *self {
|
||||
Self::Code(term) => Self::Code(map_fn(term)),
|
||||
Self::Branch { true_term, false_term } => {
|
||||
Self::Branch { true_term: map_fn(true_term), false_term: map_fn(false_term) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -202,3 +225,22 @@ pub struct FunctionCoverageInfo {
|
|||
pub expressions: IndexVec<ExpressionId, Expression>,
|
||||
pub mappings: Vec<Mapping>,
|
||||
}
|
||||
|
||||
/// Branch information recorded during THIR-to-MIR lowering, and stored in MIR.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
|
||||
pub struct BranchInfo {
|
||||
/// 1 more than the highest-numbered [`CoverageKind::BlockMarker`] that was
|
||||
/// injected into the MIR body. This makes it possible to allocate per-ID
|
||||
/// data structures without having to scan the entire body first.
|
||||
pub num_block_markers: usize,
|
||||
pub branch_spans: Vec<BranchSpan>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
|
||||
pub struct BranchSpan {
|
||||
pub span: Span,
|
||||
pub true_marker: BlockMarkerId,
|
||||
pub false_marker: BlockMarkerId,
|
||||
}
|
||||
|
|
|
@ -403,6 +403,12 @@ pub struct Body<'tcx> {
|
|||
|
||||
pub tainted_by_errors: Option<ErrorGuaranteed>,
|
||||
|
||||
/// Branch coverage information collected during MIR building, to be used by
|
||||
/// the `InstrumentCoverage` pass.
|
||||
///
|
||||
/// Only present if branch coverage is enabled and this function is eligible.
|
||||
pub coverage_branch_info: Option<Box<coverage::BranchInfo>>,
|
||||
|
||||
/// Per-function coverage information added by the `InstrumentCoverage`
|
||||
/// pass, to be used in conjunction with the coverage statements injected
|
||||
/// into this body's blocks.
|
||||
|
@ -450,6 +456,7 @@ impl<'tcx> Body<'tcx> {
|
|||
is_polymorphic: false,
|
||||
injection_phase: None,
|
||||
tainted_by_errors,
|
||||
coverage_branch_info: None,
|
||||
function_coverage_info: None,
|
||||
};
|
||||
body.is_polymorphic = body.has_non_region_param();
|
||||
|
@ -479,6 +486,7 @@ impl<'tcx> Body<'tcx> {
|
|||
is_polymorphic: false,
|
||||
injection_phase: None,
|
||||
tainted_by_errors: None,
|
||||
coverage_branch_info: None,
|
||||
function_coverage_info: None,
|
||||
};
|
||||
body.is_polymorphic = body.has_non_region_param();
|
||||
|
|
|
@ -461,6 +461,9 @@ pub fn write_mir_intro<'tcx>(
|
|||
// Add an empty line before the first block is printed.
|
||||
writeln!(w)?;
|
||||
|
||||
if let Some(branch_info) = &body.coverage_branch_info {
|
||||
write_coverage_branch_info(branch_info, w)?;
|
||||
}
|
||||
if let Some(function_coverage_info) = &body.function_coverage_info {
|
||||
write_function_coverage_info(function_coverage_info, w)?;
|
||||
}
|
||||
|
@ -468,6 +471,25 @@ pub fn write_mir_intro<'tcx>(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn write_coverage_branch_info(
|
||||
branch_info: &coverage::BranchInfo,
|
||||
w: &mut dyn io::Write,
|
||||
) -> io::Result<()> {
|
||||
let coverage::BranchInfo { branch_spans, .. } = branch_info;
|
||||
|
||||
for coverage::BranchSpan { span, true_marker, false_marker } in branch_spans {
|
||||
writeln!(
|
||||
w,
|
||||
"{INDENT}coverage branch {{ true: {true_marker:?}, false: {false_marker:?} }} => {span:?}",
|
||||
)?;
|
||||
}
|
||||
if !branch_spans.is_empty() {
|
||||
writeln!(w)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_function_coverage_info(
|
||||
function_coverage_info: &coverage::FunctionCoverageInfo,
|
||||
w: &mut dyn io::Write,
|
||||
|
|
|
@ -405,6 +405,7 @@ TrivialTypeTraversalImpls! {
|
|||
::rustc_hir::HirId,
|
||||
::rustc_hir::MatchSource,
|
||||
::rustc_target::asm::InlineAsmRegOrRegClass,
|
||||
crate::mir::coverage::BlockMarkerId,
|
||||
crate::mir::coverage::CounterId,
|
||||
crate::mir::coverage::ExpressionId,
|
||||
crate::mir::Local,
|
||||
|
|
148
compiler/rustc_mir_build/src/build/coverageinfo.rs
Normal file
148
compiler/rustc_mir_build/src/build/coverageinfo.rs
Normal file
|
@ -0,0 +1,148 @@
|
|||
use std::assert_matches::assert_matches;
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_middle::mir::coverage::{BlockMarkerId, BranchSpan, CoverageKind};
|
||||
use rustc_middle::mir::{self, BasicBlock, UnOp};
|
||||
use rustc_middle::thir::{ExprId, ExprKind, Thir};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
|
||||
use crate::build::Builder;
|
||||
|
||||
pub(crate) struct BranchInfoBuilder {
|
||||
/// Maps condition expressions to their enclosing `!`, for better instrumentation.
|
||||
nots: FxHashMap<ExprId, NotInfo>,
|
||||
|
||||
num_block_markers: usize,
|
||||
branch_spans: Vec<BranchSpan>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct NotInfo {
|
||||
/// When visiting the associated expression as a branch condition, treat this
|
||||
/// enclosing `!` as the branch condition instead.
|
||||
enclosing_not: ExprId,
|
||||
/// True if the associated expression is nested within an odd number of `!`
|
||||
/// expressions relative to `enclosing_not` (inclusive of `enclosing_not`).
|
||||
is_flipped: bool,
|
||||
}
|
||||
|
||||
impl BranchInfoBuilder {
|
||||
/// Creates a new branch info builder, but only if branch coverage instrumentation
|
||||
/// is enabled and `def_id` represents a function that is eligible for coverage.
|
||||
pub(crate) fn new_if_enabled(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<Self> {
|
||||
if tcx.sess.instrument_coverage_branch() && tcx.is_eligible_for_coverage(def_id) {
|
||||
Some(Self { nots: FxHashMap::default(), num_block_markers: 0, branch_spans: vec![] })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Unary `!` expressions inside an `if` condition are lowered by lowering
|
||||
/// their argument instead, and then reversing the then/else arms of that `if`.
|
||||
///
|
||||
/// That's awkward for branch coverage instrumentation, so to work around that
|
||||
/// we pre-emptively visit any affected `!` expressions, and record extra
|
||||
/// information that [`Builder::visit_coverage_branch_condition`] can use to
|
||||
/// synthesize branch instrumentation for the enclosing `!`.
|
||||
pub(crate) fn visit_unary_not(&mut self, thir: &Thir<'_>, unary_not: ExprId) {
|
||||
assert_matches!(thir[unary_not].kind, ExprKind::Unary { op: UnOp::Not, .. });
|
||||
|
||||
self.visit_with_not_info(
|
||||
thir,
|
||||
unary_not,
|
||||
// Set `is_flipped: false` for the `!` itself, so that its enclosed
|
||||
// expression will have `is_flipped: true`.
|
||||
NotInfo { enclosing_not: unary_not, is_flipped: false },
|
||||
);
|
||||
}
|
||||
|
||||
fn visit_with_not_info(&mut self, thir: &Thir<'_>, expr_id: ExprId, not_info: NotInfo) {
|
||||
match self.nots.entry(expr_id) {
|
||||
// This expression has already been marked by an enclosing `!`.
|
||||
Entry::Occupied(_) => return,
|
||||
Entry::Vacant(entry) => entry.insert(not_info),
|
||||
};
|
||||
|
||||
match thir[expr_id].kind {
|
||||
ExprKind::Unary { op: UnOp::Not, arg } => {
|
||||
// Invert the `is_flipped` flag for the contents of this `!`.
|
||||
let not_info = NotInfo { is_flipped: !not_info.is_flipped, ..not_info };
|
||||
self.visit_with_not_info(thir, arg, not_info);
|
||||
}
|
||||
ExprKind::Scope { value, .. } => self.visit_with_not_info(thir, value, not_info),
|
||||
ExprKind::Use { source } => self.visit_with_not_info(thir, source, not_info),
|
||||
// All other expressions (including `&&` and `||`) don't need any
|
||||
// special handling of their contents, so stop visiting.
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn next_block_marker_id(&mut self) -> BlockMarkerId {
|
||||
let id = BlockMarkerId::from_usize(self.num_block_markers);
|
||||
self.num_block_markers += 1;
|
||||
id
|
||||
}
|
||||
|
||||
pub(crate) fn into_done(self) -> Option<Box<mir::coverage::BranchInfo>> {
|
||||
let Self { nots: _, num_block_markers, branch_spans } = self;
|
||||
|
||||
if num_block_markers == 0 {
|
||||
assert!(branch_spans.is_empty());
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Box::new(mir::coverage::BranchInfo { num_block_markers, branch_spans }))
|
||||
}
|
||||
}
|
||||
|
||||
impl Builder<'_, '_> {
|
||||
/// If branch coverage is enabled, inject marker statements into `then_block`
|
||||
/// and `else_block`, and record their IDs in the table of branch spans.
|
||||
pub(crate) fn visit_coverage_branch_condition(
|
||||
&mut self,
|
||||
mut expr_id: ExprId,
|
||||
mut then_block: BasicBlock,
|
||||
mut else_block: BasicBlock,
|
||||
) {
|
||||
// Bail out if branch coverage is not enabled for this function.
|
||||
let Some(branch_info) = self.coverage_branch_info.as_ref() else { return };
|
||||
|
||||
// If this condition expression is nested within one or more `!` expressions,
|
||||
// replace it with the enclosing `!` collected by `visit_unary_not`.
|
||||
if let Some(&NotInfo { enclosing_not, is_flipped }) = branch_info.nots.get(&expr_id) {
|
||||
expr_id = enclosing_not;
|
||||
if is_flipped {
|
||||
std::mem::swap(&mut then_block, &mut else_block);
|
||||
}
|
||||
}
|
||||
let source_info = self.source_info(self.thir[expr_id].span);
|
||||
|
||||
// Now that we have `source_info`, we can upgrade to a &mut reference.
|
||||
let branch_info = self.coverage_branch_info.as_mut().expect("upgrading & to &mut");
|
||||
|
||||
let mut inject_branch_marker = |block: BasicBlock| {
|
||||
let id = branch_info.next_block_marker_id();
|
||||
|
||||
let marker_statement = mir::Statement {
|
||||
source_info,
|
||||
kind: mir::StatementKind::Coverage(Box::new(mir::Coverage {
|
||||
kind: CoverageKind::BlockMarker { id },
|
||||
})),
|
||||
};
|
||||
self.cfg.push(block, marker_statement);
|
||||
|
||||
id
|
||||
};
|
||||
|
||||
let true_marker = inject_branch_marker(then_block);
|
||||
let false_marker = inject_branch_marker(else_block);
|
||||
|
||||
branch_info.branch_spans.push(BranchSpan {
|
||||
span: source_info.span,
|
||||
true_marker,
|
||||
false_marker,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -60,6 +60,7 @@ pub(super) fn build_custom_mir<'tcx>(
|
|||
tainted_by_errors: None,
|
||||
injection_phase: None,
|
||||
pass_count: 0,
|
||||
coverage_branch_info: None,
|
||||
function_coverage_info: None,
|
||||
};
|
||||
|
||||
|
|
|
@ -105,6 +105,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
|||
success_block.unit()
|
||||
}
|
||||
ExprKind::Unary { op: UnOp::Not, arg } => {
|
||||
// Improve branch coverage instrumentation by noting conditions
|
||||
// nested within one or more `!` expressions.
|
||||
// (Skipped if branch coverage is not enabled.)
|
||||
if let Some(branch_info) = this.coverage_branch_info.as_mut() {
|
||||
branch_info.visit_unary_not(this.thir, expr_id);
|
||||
}
|
||||
|
||||
let local_scope = this.local_scope();
|
||||
let (success_block, failure_block) =
|
||||
this.in_if_then_scope(local_scope, expr_span, |this| {
|
||||
|
@ -149,6 +156,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
|||
let else_block = this.cfg.start_new_block();
|
||||
let term = TerminatorKind::if_(operand, then_block, else_block);
|
||||
|
||||
// Record branch coverage info for this condition.
|
||||
// (Does nothing if branch coverage is not enabled.)
|
||||
this.visit_coverage_branch_condition(expr_id, then_block, else_block);
|
||||
|
||||
let source_info = this.source_info(expr_span);
|
||||
this.cfg.terminate(block, source_info, term);
|
||||
this.break_for_else(else_block, source_info);
|
||||
|
|
|
@ -234,6 +234,10 @@ struct Builder<'a, 'tcx> {
|
|||
// the root (most of them do) and saves us from retracing many sub-paths
|
||||
// many times, and rechecking many nodes.
|
||||
lint_level_roots_cache: GrowableBitSet<hir::ItemLocalId>,
|
||||
|
||||
/// Collects additional coverage information during MIR building.
|
||||
/// Only present if branch coverage is enabled and this function is eligible.
|
||||
coverage_branch_info: Option<coverageinfo::BranchInfoBuilder>,
|
||||
}
|
||||
|
||||
type CaptureMap<'tcx> = SortedIndexMultiMap<usize, hir::HirId, Capture<'tcx>>;
|
||||
|
@ -807,6 +811,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
|||
unit_temp: None,
|
||||
var_debug_info: vec![],
|
||||
lint_level_roots_cache: GrowableBitSet::new_empty(),
|
||||
coverage_branch_info: coverageinfo::BranchInfoBuilder::new_if_enabled(tcx, def),
|
||||
};
|
||||
|
||||
assert_eq!(builder.cfg.start_new_block(), START_BLOCK);
|
||||
|
@ -826,7 +831,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
Body::new(
|
||||
let mut body = Body::new(
|
||||
MirSource::item(self.def_id.to_def_id()),
|
||||
self.cfg.basic_blocks,
|
||||
self.source_scopes,
|
||||
|
@ -837,7 +842,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
|||
self.fn_span,
|
||||
self.coroutine,
|
||||
None,
|
||||
)
|
||||
);
|
||||
body.coverage_branch_info = self.coverage_branch_info.and_then(|b| b.into_done());
|
||||
body
|
||||
}
|
||||
|
||||
fn insert_upvar_arg(&mut self) {
|
||||
|
@ -1111,6 +1118,7 @@ pub(crate) fn parse_float_into_scalar(
|
|||
|
||||
mod block;
|
||||
mod cfg;
|
||||
mod coverageinfo;
|
||||
mod custom;
|
||||
mod expr;
|
||||
mod matches;
|
||||
|
|
|
@ -14,7 +14,6 @@ use self::spans::{BcbMapping, BcbMappingKind, CoverageSpans};
|
|||
use crate::MirPass;
|
||||
|
||||
use rustc_middle::hir;
|
||||
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
|
||||
use rustc_middle::mir::coverage::*;
|
||||
use rustc_middle::mir::{
|
||||
self, BasicBlock, BasicBlockData, Coverage, SourceInfo, Statement, StatementKind, Terminator,
|
||||
|
@ -44,7 +43,7 @@ impl<'tcx> MirPass<'tcx> for InstrumentCoverage {
|
|||
|
||||
let def_id = mir_source.def_id().expect_local();
|
||||
|
||||
if !is_eligible_for_coverage(tcx, def_id) {
|
||||
if !tcx.is_eligible_for_coverage(def_id) {
|
||||
trace!("InstrumentCoverage skipped for {def_id:?} (not eligible)");
|
||||
return;
|
||||
}
|
||||
|
@ -140,6 +139,10 @@ fn create_mappings<'tcx>(
|
|||
.filter_map(|&BcbMapping { kind: bcb_mapping_kind, span }| {
|
||||
let kind = match bcb_mapping_kind {
|
||||
BcbMappingKind::Code(bcb) => MappingKind::Code(term_for_bcb(bcb)),
|
||||
BcbMappingKind::Branch { true_bcb, false_bcb } => MappingKind::Branch {
|
||||
true_term: term_for_bcb(true_bcb),
|
||||
false_term: term_for_bcb(false_bcb),
|
||||
},
|
||||
};
|
||||
let code_region = make_code_region(source_map, file_name, span, body_span)?;
|
||||
Some(Mapping { kind, code_region })
|
||||
|
@ -349,37 +352,6 @@ fn check_code_region(code_region: CodeRegion) -> Option<CodeRegion> {
|
|||
}
|
||||
}
|
||||
|
||||
fn is_eligible_for_coverage(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
|
||||
// Only instrument functions, methods, and closures (not constants since they are evaluated
|
||||
// at compile time by Miri).
|
||||
// FIXME(#73156): Handle source code coverage in const eval, but note, if and when const
|
||||
// expressions get coverage spans, we will probably have to "carve out" space for const
|
||||
// expressions from coverage spans in enclosing MIR's, like we do for closures. (That might
|
||||
// be tricky if const expressions have no corresponding statements in the enclosing MIR.
|
||||
// Closures are carved out by their initial `Assign` statement.)
|
||||
if !tcx.def_kind(def_id).is_fn_like() {
|
||||
trace!("InstrumentCoverage skipped for {def_id:?} (not an fn-like)");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't instrument functions with `#[automatically_derived]` on their
|
||||
// enclosing impl block, on the assumption that most users won't care about
|
||||
// coverage for derived impls.
|
||||
if let Some(impl_of) = tcx.impl_of_method(def_id.to_def_id())
|
||||
&& tcx.is_automatically_derived(impl_of)
|
||||
{
|
||||
trace!("InstrumentCoverage skipped for {def_id:?} (automatically derived)");
|
||||
return false;
|
||||
}
|
||||
|
||||
if tcx.codegen_fn_attrs(def_id).flags.contains(CodegenFnAttrFlags::NO_COVERAGE) {
|
||||
trace!("InstrumentCoverage skipped for {def_id:?} (`#[coverage(off)]`)");
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Function information extracted from HIR by the coverage instrumentor.
|
||||
#[derive(Debug)]
|
||||
struct ExtractedHirInfo {
|
||||
|
|
|
@ -1,14 +1,49 @@
|
|||
use super::*;
|
||||
|
||||
use rustc_data_structures::captures::Captures;
|
||||
use rustc_middle::mir::coverage::*;
|
||||
use rustc_middle::mir::{Body, CoverageIdsInfo};
|
||||
use rustc_middle::query::Providers;
|
||||
use rustc_middle::ty::{self};
|
||||
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
|
||||
use rustc_middle::mir::coverage::{CounterId, CoverageKind};
|
||||
use rustc_middle::mir::{Body, Coverage, CoverageIdsInfo, Statement, StatementKind};
|
||||
use rustc_middle::query::TyCtxtAt;
|
||||
use rustc_middle::ty::{self, TyCtxt};
|
||||
use rustc_middle::util::Providers;
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
|
||||
/// A `query` provider for retrieving coverage information injected into MIR.
|
||||
/// Registers query/hook implementations related to coverage.
|
||||
pub(crate) fn provide(providers: &mut Providers) {
|
||||
providers.coverage_ids_info = |tcx, def_id| coverage_ids_info(tcx, def_id);
|
||||
providers.hooks.is_eligible_for_coverage =
|
||||
|TyCtxtAt { tcx, .. }, def_id| is_eligible_for_coverage(tcx, def_id);
|
||||
providers.queries.coverage_ids_info = coverage_ids_info;
|
||||
}
|
||||
|
||||
/// Hook implementation for [`TyCtxt::is_eligible_for_coverage`].
|
||||
fn is_eligible_for_coverage(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
|
||||
// Only instrument functions, methods, and closures (not constants since they are evaluated
|
||||
// at compile time by Miri).
|
||||
// FIXME(#73156): Handle source code coverage in const eval, but note, if and when const
|
||||
// expressions get coverage spans, we will probably have to "carve out" space for const
|
||||
// expressions from coverage spans in enclosing MIR's, like we do for closures. (That might
|
||||
// be tricky if const expressions have no corresponding statements in the enclosing MIR.
|
||||
// Closures are carved out by their initial `Assign` statement.)
|
||||
if !tcx.def_kind(def_id).is_fn_like() {
|
||||
trace!("InstrumentCoverage skipped for {def_id:?} (not an fn-like)");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't instrument functions with `#[automatically_derived]` on their
|
||||
// enclosing impl block, on the assumption that most users won't care about
|
||||
// coverage for derived impls.
|
||||
if let Some(impl_of) = tcx.impl_of_method(def_id.to_def_id())
|
||||
&& tcx.is_automatically_derived(impl_of)
|
||||
{
|
||||
trace!("InstrumentCoverage skipped for {def_id:?} (automatically derived)");
|
||||
return false;
|
||||
}
|
||||
|
||||
if tcx.codegen_fn_attrs(def_id).flags.contains(CodegenFnAttrFlags::NO_COVERAGE) {
|
||||
trace!("InstrumentCoverage skipped for {def_id:?} (`#[coverage(off)]`)");
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Query implementation for `coverage_ids_info`.
|
||||
|
|
|
@ -13,6 +13,8 @@ mod from_mir;
|
|||
pub(super) enum BcbMappingKind {
|
||||
/// Associates an ordinary executable code span with its corresponding BCB.
|
||||
Code(BasicCoverageBlock),
|
||||
/// Associates a branch span with BCBs for its true and false arms.
|
||||
Branch { true_bcb: BasicCoverageBlock, false_bcb: BasicCoverageBlock },
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -66,6 +68,12 @@ pub(super) fn generate_coverage_spans(
|
|||
// Each span produced by the generator represents an ordinary code region.
|
||||
BcbMapping { kind: BcbMappingKind::Code(bcb), span }
|
||||
}));
|
||||
|
||||
mappings.extend(from_mir::extract_branch_mappings(
|
||||
mir_body,
|
||||
hir_info.body_span,
|
||||
basic_coverage_blocks,
|
||||
));
|
||||
}
|
||||
|
||||
if mappings.is_empty() {
|
||||
|
@ -80,6 +88,10 @@ pub(super) fn generate_coverage_spans(
|
|||
for &BcbMapping { kind, span: _ } in &mappings {
|
||||
match kind {
|
||||
BcbMappingKind::Code(bcb) => insert(bcb),
|
||||
BcbMappingKind::Branch { true_bcb, false_bcb } => {
|
||||
insert(true_bcb);
|
||||
insert(false_bcb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use rustc_data_structures::captures::Captures;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_index::IndexVec;
|
||||
use rustc_middle::mir::coverage::{BlockMarkerId, BranchSpan, CoverageKind};
|
||||
use rustc_middle::mir::{
|
||||
self, AggregateKind, FakeReadCause, Rvalue, Statement, StatementKind, Terminator,
|
||||
self, AggregateKind, BasicBlock, FakeReadCause, Rvalue, Statement, StatementKind, Terminator,
|
||||
TerminatorKind,
|
||||
};
|
||||
use rustc_span::{ExpnKind, MacroKind, Span, Symbol};
|
||||
|
@ -9,6 +11,7 @@ use rustc_span::{ExpnKind, MacroKind, Span, Symbol};
|
|||
use crate::coverage::graph::{
|
||||
BasicCoverageBlock, BasicCoverageBlockData, CoverageGraph, START_BCB,
|
||||
};
|
||||
use crate::coverage::spans::{BcbMapping, BcbMappingKind};
|
||||
use crate::coverage::ExtractedHirInfo;
|
||||
|
||||
/// Traverses the MIR body to produce an initial collection of coverage-relevant
|
||||
|
@ -179,8 +182,6 @@ fn is_closure_like(statement: &Statement<'_>) -> bool {
|
|||
/// If the MIR `Statement` has a span contributive to computing coverage spans,
|
||||
/// return it; otherwise return `None`.
|
||||
fn filtered_statement_span(statement: &Statement<'_>) -> Option<Span> {
|
||||
use mir::coverage::CoverageKind;
|
||||
|
||||
match statement.kind {
|
||||
// These statements have spans that are often outside the scope of the executed source code
|
||||
// for their parent `BasicBlock`.
|
||||
|
@ -225,6 +226,11 @@ fn filtered_statement_span(statement: &Statement<'_>) -> Option<Span> {
|
|||
Some(statement.source_info.span)
|
||||
}
|
||||
|
||||
StatementKind::Coverage(box mir::Coverage {
|
||||
// Block markers are used for branch coverage, so ignore them here.
|
||||
kind: CoverageKind::BlockMarker {..}
|
||||
}) => None,
|
||||
|
||||
StatementKind::Coverage(box mir::Coverage {
|
||||
// These coverage statements should not exist prior to coverage instrumentation.
|
||||
kind: CoverageKind::CounterIncrement { .. } | CoverageKind::ExpressionUsed { .. }
|
||||
|
@ -358,3 +364,51 @@ impl SpanFromMir {
|
|||
Self { span, visible_macro, bcb, is_hole }
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn extract_branch_mappings(
|
||||
mir_body: &mir::Body<'_>,
|
||||
body_span: Span,
|
||||
basic_coverage_blocks: &CoverageGraph,
|
||||
) -> Vec<BcbMapping> {
|
||||
let Some(branch_info) = mir_body.coverage_branch_info.as_deref() else {
|
||||
return vec![];
|
||||
};
|
||||
|
||||
let mut block_markers = IndexVec::<BlockMarkerId, Option<BasicBlock>>::from_elem_n(
|
||||
None,
|
||||
branch_info.num_block_markers,
|
||||
);
|
||||
|
||||
// Fill out the mapping from block marker IDs to their enclosing blocks.
|
||||
for (bb, data) in mir_body.basic_blocks.iter_enumerated() {
|
||||
for statement in &data.statements {
|
||||
if let StatementKind::Coverage(coverage) = &statement.kind
|
||||
&& let CoverageKind::BlockMarker { id } = coverage.kind
|
||||
{
|
||||
block_markers[id] = Some(bb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
branch_info
|
||||
.branch_spans
|
||||
.iter()
|
||||
.filter_map(|&BranchSpan { span: raw_span, true_marker, false_marker }| {
|
||||
// For now, ignore any branch span that was introduced by
|
||||
// expansion. This makes things like assert macros less noisy.
|
||||
if !raw_span.ctxt().outer_expn_data().is_root() {
|
||||
return None;
|
||||
}
|
||||
let (span, _) = unexpand_into_body_span_with_visible_macro(raw_span, body_span)?;
|
||||
|
||||
let bcb_from_marker = |marker: BlockMarkerId| {
|
||||
Some(basic_coverage_blocks.bcb_from_bb(block_markers[marker]?)?)
|
||||
};
|
||||
|
||||
let true_bcb = bcb_from_marker(true_marker)?;
|
||||
let false_bcb = bcb_from_marker(false_marker)?;
|
||||
|
||||
Some(BcbMapping { kind: BcbMappingKind::Branch { true_bcb, false_bcb }, span })
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
|
|
@ -37,8 +37,9 @@ use rustc_middle::mir::{
|
|||
LocalDecl, MirPass, MirPhase, Operand, Place, ProjectionElem, Promoted, RuntimePhase, Rvalue,
|
||||
SourceInfo, Statement, StatementKind, TerminatorKind, START_BLOCK,
|
||||
};
|
||||
use rustc_middle::query::Providers;
|
||||
use rustc_middle::query;
|
||||
use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt};
|
||||
use rustc_middle::util::Providers;
|
||||
use rustc_span::{source_map::Spanned, sym, DUMMY_SP};
|
||||
use rustc_trait_selection::traits;
|
||||
|
||||
|
@ -124,7 +125,7 @@ pub fn provide(providers: &mut Providers) {
|
|||
ffi_unwind_calls::provide(providers);
|
||||
shim::provide(providers);
|
||||
cross_crate_inline::provide(providers);
|
||||
*providers = Providers {
|
||||
providers.queries = query::Providers {
|
||||
mir_keys,
|
||||
mir_const,
|
||||
mir_const_qualif,
|
||||
|
@ -139,7 +140,7 @@ pub fn provide(providers: &mut Providers) {
|
|||
mir_inliner_callees: inline::cycle::mir_inliner_callees,
|
||||
promoted_mir,
|
||||
deduced_param_attrs: deduce_param_attrs::deduced_param_attrs,
|
||||
..*providers
|
||||
..providers.queries
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -146,7 +146,7 @@ pub enum InstrumentCoverage {
|
|||
/// Individual flag values controlled by `-Z coverage-options`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct CoverageOptions {
|
||||
/// Add branch coverage instrumentation (placeholder flag; not yet implemented).
|
||||
/// Add branch coverage instrumentation.
|
||||
pub branch: bool,
|
||||
}
|
||||
|
||||
|
|
|
@ -352,7 +352,7 @@ This unstable option provides finer control over some aspects of coverage
|
|||
instrumentation. Pass one or more of the following values, separated by commas.
|
||||
|
||||
- `branch` or `no-branch`
|
||||
- Placeholder for potential branch coverage support in the future.
|
||||
- Enables or disables branch coverage instrumentation.
|
||||
|
||||
## Other references
|
||||
|
||||
|
|
|
@ -5,4 +5,4 @@ This option controls details of the coverage instrumentation performed by
|
|||
|
||||
Multiple options can be passed, separated by commas. Valid options are:
|
||||
|
||||
- `branch` or `no-branch`: Placeholder for future branch coverage support.
|
||||
- `branch` or `no-branch`: Enables or disables branch coverage instrumentation.
|
||||
|
|
54
tests/coverage/branch_generics.cov-map
Normal file
54
tests/coverage/branch_generics.cov-map
Normal file
|
@ -0,0 +1,54 @@
|
|||
Function name: branch_generics::print_size::<()>
|
||||
Raw bytes (35): 0x[01, 01, 02, 01, 05, 05, 02, 05, 01, 06, 01, 01, 24, 20, 05, 02, 01, 08, 00, 24, 05, 00, 25, 02, 06, 02, 02, 0c, 02, 06, 07, 03, 01, 00, 02]
|
||||
Number of files: 1
|
||||
- file 0 => global file 1
|
||||
Number of expressions: 2
|
||||
- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
|
||||
- expression 1 operands: lhs = Counter(1), rhs = Expression(0, Sub)
|
||||
Number of file 0 mappings: 5
|
||||
- Code(Counter(0)) at (prev + 6, 1) to (start + 1, 36)
|
||||
- Branch { true: Counter(1), false: Expression(0, Sub) } at (prev + 1, 8) to (start + 0, 36)
|
||||
true = c1
|
||||
false = (c0 - c1)
|
||||
- Code(Counter(1)) at (prev + 0, 37) to (start + 2, 6)
|
||||
- Code(Expression(0, Sub)) at (prev + 2, 12) to (start + 2, 6)
|
||||
= (c0 - c1)
|
||||
- Code(Expression(1, Add)) at (prev + 3, 1) to (start + 0, 2)
|
||||
= (c1 + (c0 - c1))
|
||||
|
||||
Function name: branch_generics::print_size::<u32>
|
||||
Raw bytes (35): 0x[01, 01, 02, 01, 05, 05, 02, 05, 01, 06, 01, 01, 24, 20, 05, 02, 01, 08, 00, 24, 05, 00, 25, 02, 06, 02, 02, 0c, 02, 06, 07, 03, 01, 00, 02]
|
||||
Number of files: 1
|
||||
- file 0 => global file 1
|
||||
Number of expressions: 2
|
||||
- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
|
||||
- expression 1 operands: lhs = Counter(1), rhs = Expression(0, Sub)
|
||||
Number of file 0 mappings: 5
|
||||
- Code(Counter(0)) at (prev + 6, 1) to (start + 1, 36)
|
||||
- Branch { true: Counter(1), false: Expression(0, Sub) } at (prev + 1, 8) to (start + 0, 36)
|
||||
true = c1
|
||||
false = (c0 - c1)
|
||||
- Code(Counter(1)) at (prev + 0, 37) to (start + 2, 6)
|
||||
- Code(Expression(0, Sub)) at (prev + 2, 12) to (start + 2, 6)
|
||||
= (c0 - c1)
|
||||
- Code(Expression(1, Add)) at (prev + 3, 1) to (start + 0, 2)
|
||||
= (c1 + (c0 - c1))
|
||||
|
||||
Function name: branch_generics::print_size::<u64>
|
||||
Raw bytes (35): 0x[01, 01, 02, 01, 05, 05, 02, 05, 01, 06, 01, 01, 24, 20, 05, 02, 01, 08, 00, 24, 05, 00, 25, 02, 06, 02, 02, 0c, 02, 06, 07, 03, 01, 00, 02]
|
||||
Number of files: 1
|
||||
- file 0 => global file 1
|
||||
Number of expressions: 2
|
||||
- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
|
||||
- expression 1 operands: lhs = Counter(1), rhs = Expression(0, Sub)
|
||||
Number of file 0 mappings: 5
|
||||
- Code(Counter(0)) at (prev + 6, 1) to (start + 1, 36)
|
||||
- Branch { true: Counter(1), false: Expression(0, Sub) } at (prev + 1, 8) to (start + 0, 36)
|
||||
true = c1
|
||||
false = (c0 - c1)
|
||||
- Code(Counter(1)) at (prev + 0, 37) to (start + 2, 6)
|
||||
- Code(Expression(0, Sub)) at (prev + 2, 12) to (start + 2, 6)
|
||||
= (c0 - c1)
|
||||
- Code(Expression(1, Add)) at (prev + 3, 1) to (start + 0, 2)
|
||||
= (c1 + (c0 - c1))
|
||||
|
62
tests/coverage/branch_generics.coverage
Normal file
62
tests/coverage/branch_generics.coverage
Normal file
|
@ -0,0 +1,62 @@
|
|||
LL| |#![feature(coverage_attribute)]
|
||||
LL| |//@ edition: 2021
|
||||
LL| |//@ compile-flags: -Zcoverage-options=branch
|
||||
LL| |//@ llvm-cov-flags: --show-branches=count
|
||||
LL| |
|
||||
LL| 3|fn print_size<T>() {
|
||||
LL| 3| if std::mem::size_of::<T>() > 4 {
|
||||
------------------
|
||||
| Branch (LL:8): [True: 0, False: 1]
|
||||
| Branch (LL:8): [True: 0, False: 1]
|
||||
| Branch (LL:8): [True: 1, False: 0]
|
||||
------------------
|
||||
LL| 1| println!("size > 4");
|
||||
LL| 2| } else {
|
||||
LL| 2| println!("size <= 4");
|
||||
LL| 2| }
|
||||
LL| 3|}
|
||||
------------------
|
||||
| branch_generics::print_size::<()>:
|
||||
| LL| 1|fn print_size<T>() {
|
||||
| LL| 1| if std::mem::size_of::<T>() > 4 {
|
||||
| ------------------
|
||||
| | Branch (LL:8): [True: 0, False: 1]
|
||||
| ------------------
|
||||
| LL| 0| println!("size > 4");
|
||||
| LL| 1| } else {
|
||||
| LL| 1| println!("size <= 4");
|
||||
| LL| 1| }
|
||||
| LL| 1|}
|
||||
------------------
|
||||
| branch_generics::print_size::<u32>:
|
||||
| LL| 1|fn print_size<T>() {
|
||||
| LL| 1| if std::mem::size_of::<T>() > 4 {
|
||||
| ------------------
|
||||
| | Branch (LL:8): [True: 0, False: 1]
|
||||
| ------------------
|
||||
| LL| 0| println!("size > 4");
|
||||
| LL| 1| } else {
|
||||
| LL| 1| println!("size <= 4");
|
||||
| LL| 1| }
|
||||
| LL| 1|}
|
||||
------------------
|
||||
| branch_generics::print_size::<u64>:
|
||||
| LL| 1|fn print_size<T>() {
|
||||
| LL| 1| if std::mem::size_of::<T>() > 4 {
|
||||
| ------------------
|
||||
| | Branch (LL:8): [True: 1, False: 0]
|
||||
| ------------------
|
||||
| LL| 1| println!("size > 4");
|
||||
| LL| 1| } else {
|
||||
| LL| 0| println!("size <= 4");
|
||||
| LL| 0| }
|
||||
| LL| 1|}
|
||||
------------------
|
||||
LL| |
|
||||
LL| |#[coverage(off)]
|
||||
LL| |fn main() {
|
||||
LL| | print_size::<()>();
|
||||
LL| | print_size::<u32>();
|
||||
LL| | print_size::<u64>();
|
||||
LL| |}
|
||||
|
19
tests/coverage/branch_generics.rs
Normal file
19
tests/coverage/branch_generics.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
#![feature(coverage_attribute)]
|
||||
//@ edition: 2021
|
||||
//@ compile-flags: -Zcoverage-options=branch
|
||||
//@ llvm-cov-flags: --show-branches=count
|
||||
|
||||
fn print_size<T>() {
|
||||
if std::mem::size_of::<T>() > 4 {
|
||||
println!("size > 4");
|
||||
} else {
|
||||
println!("size <= 4");
|
||||
}
|
||||
}
|
||||
|
||||
#[coverage(off)]
|
||||
fn main() {
|
||||
print_size::<()>();
|
||||
print_size::<u32>();
|
||||
print_size::<u64>();
|
||||
}
|
32
tests/coverage/branch_guard.cov-map
Normal file
32
tests/coverage/branch_guard.cov-map
Normal file
|
@ -0,0 +1,32 @@
|
|||
Function name: branch_guard::branch_match_guard
|
||||
Raw bytes (85): 0x[01, 01, 06, 19, 0d, 05, 09, 0f, 15, 13, 11, 17, 0d, 05, 09, 0d, 01, 0c, 01, 01, 10, 1d, 03, 0b, 00, 0c, 15, 01, 14, 02, 0a, 0d, 03, 0e, 00, 0f, 19, 00, 14, 00, 19, 20, 0d, 02, 00, 14, 00, 1e, 0d, 00, 1d, 02, 0a, 11, 03, 0e, 00, 0f, 1d, 00, 14, 00, 19, 20, 11, 09, 00, 14, 00, 1e, 11, 00, 1d, 02, 0a, 17, 03, 0e, 02, 0a, 0b, 04, 01, 00, 02]
|
||||
Number of files: 1
|
||||
- file 0 => global file 1
|
||||
Number of expressions: 6
|
||||
- expression 0 operands: lhs = Counter(6), rhs = Counter(3)
|
||||
- expression 1 operands: lhs = Counter(1), rhs = Counter(2)
|
||||
- expression 2 operands: lhs = Expression(3, Add), rhs = Counter(5)
|
||||
- expression 3 operands: lhs = Expression(4, Add), rhs = Counter(4)
|
||||
- expression 4 operands: lhs = Expression(5, Add), rhs = Counter(3)
|
||||
- expression 5 operands: lhs = Counter(1), rhs = Counter(2)
|
||||
Number of file 0 mappings: 13
|
||||
- Code(Counter(0)) at (prev + 12, 1) to (start + 1, 16)
|
||||
- Code(Counter(7)) at (prev + 3, 11) to (start + 0, 12)
|
||||
- Code(Counter(5)) at (prev + 1, 20) to (start + 2, 10)
|
||||
- Code(Counter(3)) at (prev + 3, 14) to (start + 0, 15)
|
||||
- Code(Counter(6)) at (prev + 0, 20) to (start + 0, 25)
|
||||
- Branch { true: Counter(3), false: Expression(0, Sub) } at (prev + 0, 20) to (start + 0, 30)
|
||||
true = c3
|
||||
false = (c6 - c3)
|
||||
- Code(Counter(3)) at (prev + 0, 29) to (start + 2, 10)
|
||||
- Code(Counter(4)) at (prev + 3, 14) to (start + 0, 15)
|
||||
- Code(Counter(7)) at (prev + 0, 20) to (start + 0, 25)
|
||||
- Branch { true: Counter(4), false: Counter(2) } at (prev + 0, 20) to (start + 0, 30)
|
||||
true = c4
|
||||
false = c2
|
||||
- Code(Counter(4)) at (prev + 0, 29) to (start + 2, 10)
|
||||
- Code(Expression(5, Add)) at (prev + 3, 14) to (start + 2, 10)
|
||||
= (c1 + c2)
|
||||
- Code(Expression(2, Add)) at (prev + 4, 1) to (start + 0, 2)
|
||||
= ((((c1 + c2) + c3) + c4) + c5)
|
||||
|
45
tests/coverage/branch_guard.coverage
Normal file
45
tests/coverage/branch_guard.coverage
Normal file
|
@ -0,0 +1,45 @@
|
|||
LL| |#![feature(coverage_attribute)]
|
||||
LL| |//@ edition: 2021
|
||||
LL| |//@ compile-flags: -Zcoverage-options=branch
|
||||
LL| |//@ llvm-cov-flags: --show-branches=count
|
||||
LL| |
|
||||
LL| |macro_rules! no_merge {
|
||||
LL| | () => {
|
||||
LL| | for _ in 0..1 {}
|
||||
LL| | };
|
||||
LL| |}
|
||||
LL| |
|
||||
LL| 4|fn branch_match_guard(x: Option<u32>) {
|
||||
LL| 4| no_merge!();
|
||||
LL| |
|
||||
LL| 1| match x {
|
||||
LL| 1| Some(0) => {
|
||||
LL| 1| println!("zero");
|
||||
LL| 1| }
|
||||
LL| 3| Some(x) if x % 2 == 0 => {
|
||||
^2
|
||||
------------------
|
||||
| Branch (LL:20): [True: 2, False: 1]
|
||||
------------------
|
||||
LL| 2| println!("is nonzero and even");
|
||||
LL| 2| }
|
||||
LL| 1| Some(x) if x % 3 == 0 => {
|
||||
------------------
|
||||
| Branch (LL:20): [True: 1, False: 0]
|
||||
------------------
|
||||
LL| 1| println!("is nonzero and odd, but divisible by 3");
|
||||
LL| 1| }
|
||||
LL| 0| _ => {
|
||||
LL| 0| println!("something else");
|
||||
LL| 0| }
|
||||
LL| | }
|
||||
LL| 4|}
|
||||
LL| |
|
||||
LL| |#[coverage(off)]
|
||||
LL| |fn main() {
|
||||
LL| | branch_match_guard(Some(0));
|
||||
LL| | branch_match_guard(Some(2));
|
||||
LL| | branch_match_guard(Some(6));
|
||||
LL| | branch_match_guard(Some(3));
|
||||
LL| |}
|
||||
|
37
tests/coverage/branch_guard.rs
Normal file
37
tests/coverage/branch_guard.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
#![feature(coverage_attribute)]
|
||||
//@ edition: 2021
|
||||
//@ compile-flags: -Zcoverage-options=branch
|
||||
//@ llvm-cov-flags: --show-branches=count
|
||||
|
||||
macro_rules! no_merge {
|
||||
() => {
|
||||
for _ in 0..1 {}
|
||||
};
|
||||
}
|
||||
|
||||
fn branch_match_guard(x: Option<u32>) {
|
||||
no_merge!();
|
||||
|
||||
match x {
|
||||
Some(0) => {
|
||||
println!("zero");
|
||||
}
|
||||
Some(x) if x % 2 == 0 => {
|
||||
println!("is nonzero and even");
|
||||
}
|
||||
Some(x) if x % 3 == 0 => {
|
||||
println!("is nonzero and odd, but divisible by 3");
|
||||
}
|
||||
_ => {
|
||||
println!("something else");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[coverage(off)]
|
||||
fn main() {
|
||||
branch_match_guard(Some(0));
|
||||
branch_match_guard(Some(2));
|
||||
branch_match_guard(Some(6));
|
||||
branch_match_guard(Some(3));
|
||||
}
|
188
tests/coverage/branch_if.cov-map
Normal file
188
tests/coverage/branch_if.cov-map
Normal file
|
@ -0,0 +1,188 @@
|
|||
Function name: branch_if::branch_and
|
||||
Raw bytes (56): 0x[01, 01, 04, 05, 09, 0d, 02, 11, 0f, 0d, 02, 08, 01, 2b, 01, 01, 10, 05, 03, 08, 00, 09, 20, 09, 02, 00, 08, 00, 09, 09, 00, 0d, 00, 0e, 20, 11, 0d, 00, 0d, 00, 0e, 11, 00, 0f, 02, 06, 0f, 02, 0c, 02, 06, 0b, 03, 01, 00, 02]
|
||||
Number of files: 1
|
||||
- file 0 => global file 1
|
||||
Number of expressions: 4
|
||||
- expression 0 operands: lhs = Counter(1), rhs = Counter(2)
|
||||
- expression 1 operands: lhs = Counter(3), rhs = Expression(0, Sub)
|
||||
- expression 2 operands: lhs = Counter(4), rhs = Expression(3, Add)
|
||||
- expression 3 operands: lhs = Counter(3), rhs = Expression(0, Sub)
|
||||
Number of file 0 mappings: 8
|
||||
- Code(Counter(0)) at (prev + 43, 1) to (start + 1, 16)
|
||||
- Code(Counter(1)) at (prev + 3, 8) to (start + 0, 9)
|
||||
- Branch { true: Counter(2), false: Expression(0, Sub) } at (prev + 0, 8) to (start + 0, 9)
|
||||
true = c2
|
||||
false = (c1 - c2)
|
||||
- Code(Counter(2)) at (prev + 0, 13) to (start + 0, 14)
|
||||
- Branch { true: Counter(4), false: Counter(3) } at (prev + 0, 13) to (start + 0, 14)
|
||||
true = c4
|
||||
false = c3
|
||||
- Code(Counter(4)) at (prev + 0, 15) to (start + 2, 6)
|
||||
- Code(Expression(3, Add)) at (prev + 2, 12) to (start + 2, 6)
|
||||
= (c3 + (c1 - c2))
|
||||
- Code(Expression(2, Add)) at (prev + 3, 1) to (start + 0, 2)
|
||||
= (c4 + (c3 + (c1 - c2)))
|
||||
|
||||
Function name: branch_if::branch_not
|
||||
Raw bytes (224): 0x[01, 01, 29, 05, 09, 09, 02, a3, 01, 0d, 09, 02, a3, 01, 0d, 09, 02, 0d, 9e, 01, a3, 01, 0d, 09, 02, 9b, 01, 11, 0d, 9e, 01, a3, 01, 0d, 09, 02, 9b, 01, 11, 0d, 9e, 01, a3, 01, 0d, 09, 02, 11, 96, 01, 9b, 01, 11, 0d, 9e, 01, a3, 01, 0d, 09, 02, 93, 01, 15, 11, 96, 01, 9b, 01, 11, 0d, 9e, 01, a3, 01, 0d, 09, 02, 93, 01, 15, 11, 96, 01, 9b, 01, 11, 0d, 9e, 01, a3, 01, 0d, 09, 02, 15, 8e, 01, 93, 01, 15, 11, 96, 01, 9b, 01, 11, 0d, 9e, 01, a3, 01, 0d, 09, 02, 12, 01, 0c, 01, 01, 10, 05, 03, 08, 00, 09, 20, 09, 02, 00, 08, 00, 09, 09, 01, 09, 00, 11, 02, 01, 06, 00, 07, a3, 01, 01, 08, 00, 0a, 20, 9e, 01, 0d, 00, 08, 00, 0a, 9e, 01, 00, 0b, 02, 06, 0d, 02, 06, 00, 07, 9b, 01, 01, 08, 00, 0b, 20, 11, 96, 01, 00, 08, 00, 0b, 11, 00, 0c, 02, 06, 96, 01, 02, 06, 00, 07, 93, 01, 01, 08, 00, 0c, 20, 8e, 01, 15, 00, 08, 00, 0c, 8e, 01, 00, 0d, 02, 06, 15, 02, 06, 00, 07, 8b, 01, 01, 01, 00, 02]
|
||||
Number of files: 1
|
||||
- file 0 => global file 1
|
||||
Number of expressions: 41
|
||||
- expression 0 operands: lhs = Counter(1), rhs = Counter(2)
|
||||
- expression 1 operands: lhs = Counter(2), rhs = Expression(0, Sub)
|
||||
- expression 2 operands: lhs = Expression(40, Add), rhs = Counter(3)
|
||||
- expression 3 operands: lhs = Counter(2), rhs = Expression(0, Sub)
|
||||
- expression 4 operands: lhs = Expression(40, Add), rhs = Counter(3)
|
||||
- expression 5 operands: lhs = Counter(2), rhs = Expression(0, Sub)
|
||||
- expression 6 operands: lhs = Counter(3), rhs = Expression(39, Sub)
|
||||
- expression 7 operands: lhs = Expression(40, Add), rhs = Counter(3)
|
||||
- expression 8 operands: lhs = Counter(2), rhs = Expression(0, Sub)
|
||||
- expression 9 operands: lhs = Expression(38, Add), rhs = Counter(4)
|
||||
- expression 10 operands: lhs = Counter(3), rhs = Expression(39, Sub)
|
||||
- expression 11 operands: lhs = Expression(40, Add), rhs = Counter(3)
|
||||
- expression 12 operands: lhs = Counter(2), rhs = Expression(0, Sub)
|
||||
- expression 13 operands: lhs = Expression(38, Add), rhs = Counter(4)
|
||||
- expression 14 operands: lhs = Counter(3), rhs = Expression(39, Sub)
|
||||
- expression 15 operands: lhs = Expression(40, Add), rhs = Counter(3)
|
||||
- expression 16 operands: lhs = Counter(2), rhs = Expression(0, Sub)
|
||||
- expression 17 operands: lhs = Counter(4), rhs = Expression(37, Sub)
|
||||
- expression 18 operands: lhs = Expression(38, Add), rhs = Counter(4)
|
||||
- expression 19 operands: lhs = Counter(3), rhs = Expression(39, Sub)
|
||||
- expression 20 operands: lhs = Expression(40, Add), rhs = Counter(3)
|
||||
- expression 21 operands: lhs = Counter(2), rhs = Expression(0, Sub)
|
||||
- expression 22 operands: lhs = Expression(36, Add), rhs = Counter(5)
|
||||
- expression 23 operands: lhs = Counter(4), rhs = Expression(37, Sub)
|
||||
- expression 24 operands: lhs = Expression(38, Add), rhs = Counter(4)
|
||||
- expression 25 operands: lhs = Counter(3), rhs = Expression(39, Sub)
|
||||
- expression 26 operands: lhs = Expression(40, Add), rhs = Counter(3)
|
||||
- expression 27 operands: lhs = Counter(2), rhs = Expression(0, Sub)
|
||||
- expression 28 operands: lhs = Expression(36, Add), rhs = Counter(5)
|
||||
- expression 29 operands: lhs = Counter(4), rhs = Expression(37, Sub)
|
||||
- expression 30 operands: lhs = Expression(38, Add), rhs = Counter(4)
|
||||
- expression 31 operands: lhs = Counter(3), rhs = Expression(39, Sub)
|
||||
- expression 32 operands: lhs = Expression(40, Add), rhs = Counter(3)
|
||||
- expression 33 operands: lhs = Counter(2), rhs = Expression(0, Sub)
|
||||
- expression 34 operands: lhs = Counter(5), rhs = Expression(35, Sub)
|
||||
- expression 35 operands: lhs = Expression(36, Add), rhs = Counter(5)
|
||||
- expression 36 operands: lhs = Counter(4), rhs = Expression(37, Sub)
|
||||
- expression 37 operands: lhs = Expression(38, Add), rhs = Counter(4)
|
||||
- expression 38 operands: lhs = Counter(3), rhs = Expression(39, Sub)
|
||||
- expression 39 operands: lhs = Expression(40, Add), rhs = Counter(3)
|
||||
- expression 40 operands: lhs = Counter(2), rhs = Expression(0, Sub)
|
||||
Number of file 0 mappings: 18
|
||||
- Code(Counter(0)) at (prev + 12, 1) to (start + 1, 16)
|
||||
- Code(Counter(1)) at (prev + 3, 8) to (start + 0, 9)
|
||||
- Branch { true: Counter(2), false: Expression(0, Sub) } at (prev + 0, 8) to (start + 0, 9)
|
||||
true = c2
|
||||
false = (c1 - c2)
|
||||
- Code(Counter(2)) at (prev + 1, 9) to (start + 0, 17)
|
||||
- Code(Expression(0, Sub)) at (prev + 1, 6) to (start + 0, 7)
|
||||
= (c1 - c2)
|
||||
- Code(Expression(40, Add)) at (prev + 1, 8) to (start + 0, 10)
|
||||
= (c2 + (c1 - c2))
|
||||
- Branch { true: Expression(39, Sub), false: Counter(3) } at (prev + 0, 8) to (start + 0, 10)
|
||||
true = ((c2 + (c1 - c2)) - c3)
|
||||
false = c3
|
||||
- Code(Expression(39, Sub)) at (prev + 0, 11) to (start + 2, 6)
|
||||
= ((c2 + (c1 - c2)) - c3)
|
||||
- Code(Counter(3)) at (prev + 2, 6) to (start + 0, 7)
|
||||
- Code(Expression(38, Add)) at (prev + 1, 8) to (start + 0, 11)
|
||||
= (c3 + ((c2 + (c1 - c2)) - c3))
|
||||
- Branch { true: Counter(4), false: Expression(37, Sub) } at (prev + 0, 8) to (start + 0, 11)
|
||||
true = c4
|
||||
false = ((c3 + ((c2 + (c1 - c2)) - c3)) - c4)
|
||||
- Code(Counter(4)) at (prev + 0, 12) to (start + 2, 6)
|
||||
- Code(Expression(37, Sub)) at (prev + 2, 6) to (start + 0, 7)
|
||||
= ((c3 + ((c2 + (c1 - c2)) - c3)) - c4)
|
||||
- Code(Expression(36, Add)) at (prev + 1, 8) to (start + 0, 12)
|
||||
= (c4 + ((c3 + ((c2 + (c1 - c2)) - c3)) - c4))
|
||||
- Branch { true: Expression(35, Sub), false: Counter(5) } at (prev + 0, 8) to (start + 0, 12)
|
||||
true = ((c4 + ((c3 + ((c2 + (c1 - c2)) - c3)) - c4)) - c5)
|
||||
false = c5
|
||||
- Code(Expression(35, Sub)) at (prev + 0, 13) to (start + 2, 6)
|
||||
= ((c4 + ((c3 + ((c2 + (c1 - c2)) - c3)) - c4)) - c5)
|
||||
- Code(Counter(5)) at (prev + 2, 6) to (start + 0, 7)
|
||||
- Code(Expression(34, Add)) at (prev + 1, 1) to (start + 0, 2)
|
||||
= (c5 + ((c4 + ((c3 + ((c2 + (c1 - c2)) - c3)) - c4)) - c5))
|
||||
|
||||
Function name: branch_if::branch_not_as
|
||||
Raw bytes (124): 0x[01, 01, 16, 05, 09, 09, 02, 57, 0d, 09, 02, 57, 0d, 09, 02, 0d, 52, 57, 0d, 09, 02, 4f, 11, 0d, 52, 57, 0d, 09, 02, 4f, 11, 0d, 52, 57, 0d, 09, 02, 11, 4a, 4f, 11, 0d, 52, 57, 0d, 09, 02, 0e, 01, 1d, 01, 01, 10, 05, 03, 08, 00, 14, 20, 02, 09, 00, 08, 00, 14, 02, 00, 15, 02, 06, 09, 02, 06, 00, 07, 57, 01, 08, 00, 15, 20, 0d, 52, 00, 08, 00, 15, 0d, 00, 16, 02, 06, 52, 02, 06, 00, 07, 4f, 01, 08, 00, 16, 20, 4a, 11, 00, 08, 00, 16, 4a, 00, 17, 02, 06, 11, 02, 06, 00, 07, 47, 01, 01, 00, 02]
|
||||
Number of files: 1
|
||||
- file 0 => global file 1
|
||||
Number of expressions: 22
|
||||
- expression 0 operands: lhs = Counter(1), rhs = Counter(2)
|
||||
- expression 1 operands: lhs = Counter(2), rhs = Expression(0, Sub)
|
||||
- expression 2 operands: lhs = Expression(21, Add), rhs = Counter(3)
|
||||
- expression 3 operands: lhs = Counter(2), rhs = Expression(0, Sub)
|
||||
- expression 4 operands: lhs = Expression(21, Add), rhs = Counter(3)
|
||||
- expression 5 operands: lhs = Counter(2), rhs = Expression(0, Sub)
|
||||
- expression 6 operands: lhs = Counter(3), rhs = Expression(20, Sub)
|
||||
- expression 7 operands: lhs = Expression(21, Add), rhs = Counter(3)
|
||||
- expression 8 operands: lhs = Counter(2), rhs = Expression(0, Sub)
|
||||
- expression 9 operands: lhs = Expression(19, Add), rhs = Counter(4)
|
||||
- expression 10 operands: lhs = Counter(3), rhs = Expression(20, Sub)
|
||||
- expression 11 operands: lhs = Expression(21, Add), rhs = Counter(3)
|
||||
- expression 12 operands: lhs = Counter(2), rhs = Expression(0, Sub)
|
||||
- expression 13 operands: lhs = Expression(19, Add), rhs = Counter(4)
|
||||
- expression 14 operands: lhs = Counter(3), rhs = Expression(20, Sub)
|
||||
- expression 15 operands: lhs = Expression(21, Add), rhs = Counter(3)
|
||||
- expression 16 operands: lhs = Counter(2), rhs = Expression(0, Sub)
|
||||
- expression 17 operands: lhs = Counter(4), rhs = Expression(18, Sub)
|
||||
- expression 18 operands: lhs = Expression(19, Add), rhs = Counter(4)
|
||||
- expression 19 operands: lhs = Counter(3), rhs = Expression(20, Sub)
|
||||
- expression 20 operands: lhs = Expression(21, Add), rhs = Counter(3)
|
||||
- expression 21 operands: lhs = Counter(2), rhs = Expression(0, Sub)
|
||||
Number of file 0 mappings: 14
|
||||
- Code(Counter(0)) at (prev + 29, 1) to (start + 1, 16)
|
||||
- Code(Counter(1)) at (prev + 3, 8) to (start + 0, 20)
|
||||
- Branch { true: Expression(0, Sub), false: Counter(2) } at (prev + 0, 8) to (start + 0, 20)
|
||||
true = (c1 - c2)
|
||||
false = c2
|
||||
- Code(Expression(0, Sub)) at (prev + 0, 21) to (start + 2, 6)
|
||||
= (c1 - c2)
|
||||
- Code(Counter(2)) at (prev + 2, 6) to (start + 0, 7)
|
||||
- Code(Expression(21, Add)) at (prev + 1, 8) to (start + 0, 21)
|
||||
= (c2 + (c1 - c2))
|
||||
- Branch { true: Counter(3), false: Expression(20, Sub) } at (prev + 0, 8) to (start + 0, 21)
|
||||
true = c3
|
||||
false = ((c2 + (c1 - c2)) - c3)
|
||||
- Code(Counter(3)) at (prev + 0, 22) to (start + 2, 6)
|
||||
- Code(Expression(20, Sub)) at (prev + 2, 6) to (start + 0, 7)
|
||||
= ((c2 + (c1 - c2)) - c3)
|
||||
- Code(Expression(19, Add)) at (prev + 1, 8) to (start + 0, 22)
|
||||
= (c3 + ((c2 + (c1 - c2)) - c3))
|
||||
- Branch { true: Expression(18, Sub), false: Counter(4) } at (prev + 0, 8) to (start + 0, 22)
|
||||
true = ((c3 + ((c2 + (c1 - c2)) - c3)) - c4)
|
||||
false = c4
|
||||
- Code(Expression(18, Sub)) at (prev + 0, 23) to (start + 2, 6)
|
||||
= ((c3 + ((c2 + (c1 - c2)) - c3)) - c4)
|
||||
- Code(Counter(4)) at (prev + 2, 6) to (start + 0, 7)
|
||||
- Code(Expression(17, Add)) at (prev + 1, 1) to (start + 0, 2)
|
||||
= (c4 + ((c3 + ((c2 + (c1 - c2)) - c3)) - c4))
|
||||
|
||||
Function name: branch_if::branch_or
|
||||
Raw bytes (56): 0x[01, 01, 04, 05, 09, 09, 0d, 0f, 11, 09, 0d, 08, 01, 35, 01, 01, 10, 05, 03, 08, 00, 09, 20, 09, 02, 00, 08, 00, 09, 02, 00, 0d, 00, 0e, 20, 0d, 11, 00, 0d, 00, 0e, 0f, 00, 0f, 02, 06, 11, 02, 0c, 02, 06, 0b, 03, 01, 00, 02]
|
||||
Number of files: 1
|
||||
- file 0 => global file 1
|
||||
Number of expressions: 4
|
||||
- expression 0 operands: lhs = Counter(1), rhs = Counter(2)
|
||||
- expression 1 operands: lhs = Counter(2), rhs = Counter(3)
|
||||
- expression 2 operands: lhs = Expression(3, Add), rhs = Counter(4)
|
||||
- expression 3 operands: lhs = Counter(2), rhs = Counter(3)
|
||||
Number of file 0 mappings: 8
|
||||
- Code(Counter(0)) at (prev + 53, 1) to (start + 1, 16)
|
||||
- Code(Counter(1)) at (prev + 3, 8) to (start + 0, 9)
|
||||
- Branch { true: Counter(2), false: Expression(0, Sub) } at (prev + 0, 8) to (start + 0, 9)
|
||||
true = c2
|
||||
false = (c1 - c2)
|
||||
- Code(Expression(0, Sub)) at (prev + 0, 13) to (start + 0, 14)
|
||||
= (c1 - c2)
|
||||
- Branch { true: Counter(3), false: Counter(4) } at (prev + 0, 13) to (start + 0, 14)
|
||||
true = c3
|
||||
false = c4
|
||||
- Code(Expression(3, Add)) at (prev + 0, 15) to (start + 2, 6)
|
||||
= (c2 + c3)
|
||||
- Code(Counter(4)) at (prev + 2, 12) to (start + 2, 6)
|
||||
- Code(Expression(2, Add)) at (prev + 3, 1) to (start + 0, 2)
|
||||
= ((c2 + c3) + c4)
|
||||
|
115
tests/coverage/branch_if.coverage
Normal file
115
tests/coverage/branch_if.coverage
Normal file
|
@ -0,0 +1,115 @@
|
|||
LL| |#![feature(coverage_attribute)]
|
||||
LL| |//@ edition: 2021
|
||||
LL| |//@ compile-flags: -Zcoverage-options=branch
|
||||
LL| |//@ llvm-cov-flags: --show-branches=count
|
||||
LL| |
|
||||
LL| |macro_rules! no_merge {
|
||||
LL| | () => {
|
||||
LL| | for _ in 0..1 {}
|
||||
LL| | };
|
||||
LL| |}
|
||||
LL| |
|
||||
LL| 3|fn branch_not(a: bool) {
|
||||
LL| 3| no_merge!();
|
||||
LL| |
|
||||
LL| 3| if a {
|
||||
------------------
|
||||
| Branch (LL:8): [True: 2, False: 1]
|
||||
------------------
|
||||
LL| 2| say("a")
|
||||
LL| 1| }
|
||||
LL| 3| if !a {
|
||||
------------------
|
||||
| Branch (LL:8): [True: 1, False: 2]
|
||||
------------------
|
||||
LL| 1| say("not a");
|
||||
LL| 2| }
|
||||
LL| 3| if !!a {
|
||||
------------------
|
||||
| Branch (LL:8): [True: 2, False: 1]
|
||||
------------------
|
||||
LL| 2| say("not not a");
|
||||
LL| 2| }
|
||||
^1
|
||||
LL| 3| if !!!a {
|
||||
------------------
|
||||
| Branch (LL:8): [True: 1, False: 2]
|
||||
------------------
|
||||
LL| 1| say("not not not a");
|
||||
LL| 2| }
|
||||
LL| 3|}
|
||||
LL| |
|
||||
LL| 3|fn branch_not_as(a: bool) {
|
||||
LL| 3| no_merge!();
|
||||
LL| |
|
||||
LL| 3| if !(a as bool) {
|
||||
------------------
|
||||
| Branch (LL:8): [True: 1, False: 2]
|
||||
------------------
|
||||
LL| 1| say("not (a as bool)");
|
||||
LL| 2| }
|
||||
LL| 3| if !!(a as bool) {
|
||||
------------------
|
||||
| Branch (LL:8): [True: 2, False: 1]
|
||||
------------------
|
||||
LL| 2| say("not not (a as bool)");
|
||||
LL| 2| }
|
||||
^1
|
||||
LL| 3| if !!!(a as bool) {
|
||||
------------------
|
||||
| Branch (LL:8): [True: 1, False: 2]
|
||||
------------------
|
||||
LL| 1| say("not not (a as bool)");
|
||||
LL| 2| }
|
||||
LL| 3|}
|
||||
LL| |
|
||||
LL| 15|fn branch_and(a: bool, b: bool) {
|
||||
LL| 15| no_merge!();
|
||||
LL| |
|
||||
LL| 15| if a && b {
|
||||
^12
|
||||
------------------
|
||||
| Branch (LL:8): [True: 12, False: 3]
|
||||
| Branch (LL:13): [True: 8, False: 4]
|
||||
------------------
|
||||
LL| 8| say("both");
|
||||
LL| 8| } else {
|
||||
LL| 7| say("not both");
|
||||
LL| 7| }
|
||||
LL| 15|}
|
||||
LL| |
|
||||
LL| 15|fn branch_or(a: bool, b: bool) {
|
||||
LL| 15| no_merge!();
|
||||
LL| |
|
||||
LL| 15| if a || b {
|
||||
^3
|
||||
------------------
|
||||
| Branch (LL:8): [True: 12, False: 3]
|
||||
| Branch (LL:13): [True: 2, False: 1]
|
||||
------------------
|
||||
LL| 14| say("either");
|
||||
LL| 14| } else {
|
||||
LL| 1| say("neither");
|
||||
LL| 1| }
|
||||
LL| 15|}
|
||||
LL| |
|
||||
LL| |#[coverage(off)]
|
||||
LL| |fn say(message: &str) {
|
||||
LL| | core::hint::black_box(message);
|
||||
LL| |}
|
||||
LL| |
|
||||
LL| |#[coverage(off)]
|
||||
LL| |fn main() {
|
||||
LL| | for a in [false, true, true] {
|
||||
LL| | branch_not(a);
|
||||
LL| | branch_not_as(a);
|
||||
LL| | }
|
||||
LL| |
|
||||
LL| | for a in [false, true, true, true, true] {
|
||||
LL| | for b in [false, true, true] {
|
||||
LL| | branch_and(a, b);
|
||||
LL| | branch_or(a, b);
|
||||
LL| | }
|
||||
LL| | }
|
||||
LL| |}
|
||||
|
81
tests/coverage/branch_if.rs
Normal file
81
tests/coverage/branch_if.rs
Normal file
|
@ -0,0 +1,81 @@
|
|||
#![feature(coverage_attribute)]
|
||||
//@ edition: 2021
|
||||
//@ compile-flags: -Zcoverage-options=branch
|
||||
//@ llvm-cov-flags: --show-branches=count
|
||||
|
||||
macro_rules! no_merge {
|
||||
() => {
|
||||
for _ in 0..1 {}
|
||||
};
|
||||
}
|
||||
|
||||
fn branch_not(a: bool) {
|
||||
no_merge!();
|
||||
|
||||
if a {
|
||||
say("a")
|
||||
}
|
||||
if !a {
|
||||
say("not a");
|
||||
}
|
||||
if !!a {
|
||||
say("not not a");
|
||||
}
|
||||
if !!!a {
|
||||
say("not not not a");
|
||||
}
|
||||
}
|
||||
|
||||
fn branch_not_as(a: bool) {
|
||||
no_merge!();
|
||||
|
||||
if !(a as bool) {
|
||||
say("not (a as bool)");
|
||||
}
|
||||
if !!(a as bool) {
|
||||
say("not not (a as bool)");
|
||||
}
|
||||
if !!!(a as bool) {
|
||||
say("not not (a as bool)");
|
||||
}
|
||||
}
|
||||
|
||||
fn branch_and(a: bool, b: bool) {
|
||||
no_merge!();
|
||||
|
||||
if a && b {
|
||||
say("both");
|
||||
} else {
|
||||
say("not both");
|
||||
}
|
||||
}
|
||||
|
||||
fn branch_or(a: bool, b: bool) {
|
||||
no_merge!();
|
||||
|
||||
if a || b {
|
||||
say("either");
|
||||
} else {
|
||||
say("neither");
|
||||
}
|
||||
}
|
||||
|
||||
#[coverage(off)]
|
||||
fn say(message: &str) {
|
||||
core::hint::black_box(message);
|
||||
}
|
||||
|
||||
#[coverage(off)]
|
||||
fn main() {
|
||||
for a in [false, true, true] {
|
||||
branch_not(a);
|
||||
branch_not_as(a);
|
||||
}
|
||||
|
||||
for a in [false, true, true, true, true] {
|
||||
for b in [false, true, true] {
|
||||
branch_and(a, b);
|
||||
branch_or(a, b);
|
||||
}
|
||||
}
|
||||
}
|
98
tests/coverage/branch_while.cov-map
Normal file
98
tests/coverage/branch_while.cov-map
Normal file
|
@ -0,0 +1,98 @@
|
|||
Function name: branch_while::while_cond
|
||||
Raw bytes (42): 0x[01, 01, 03, 05, 09, 03, 09, 03, 09, 06, 01, 0c, 01, 01, 10, 05, 03, 09, 00, 12, 03, 01, 0b, 00, 10, 20, 09, 0a, 00, 0b, 00, 10, 09, 00, 11, 02, 06, 0a, 03, 01, 00, 02]
|
||||
Number of files: 1
|
||||
- file 0 => global file 1
|
||||
Number of expressions: 3
|
||||
- expression 0 operands: lhs = Counter(1), rhs = Counter(2)
|
||||
- expression 1 operands: lhs = Expression(0, Add), rhs = Counter(2)
|
||||
- expression 2 operands: lhs = Expression(0, Add), rhs = Counter(2)
|
||||
Number of file 0 mappings: 6
|
||||
- Code(Counter(0)) at (prev + 12, 1) to (start + 1, 16)
|
||||
- Code(Counter(1)) at (prev + 3, 9) to (start + 0, 18)
|
||||
- Code(Expression(0, Add)) at (prev + 1, 11) to (start + 0, 16)
|
||||
= (c1 + c2)
|
||||
- Branch { true: Counter(2), false: Expression(2, Sub) } at (prev + 0, 11) to (start + 0, 16)
|
||||
true = c2
|
||||
false = ((c1 + c2) - c2)
|
||||
- Code(Counter(2)) at (prev + 0, 17) to (start + 2, 6)
|
||||
- Code(Expression(2, Sub)) at (prev + 3, 1) to (start + 0, 2)
|
||||
= ((c1 + c2) - c2)
|
||||
|
||||
Function name: branch_while::while_cond_not
|
||||
Raw bytes (42): 0x[01, 01, 03, 05, 09, 03, 09, 03, 09, 06, 01, 15, 01, 01, 10, 05, 03, 09, 00, 12, 03, 01, 0b, 00, 14, 20, 09, 0a, 00, 0b, 00, 14, 09, 00, 15, 02, 06, 0a, 03, 01, 00, 02]
|
||||
Number of files: 1
|
||||
- file 0 => global file 1
|
||||
Number of expressions: 3
|
||||
- expression 0 operands: lhs = Counter(1), rhs = Counter(2)
|
||||
- expression 1 operands: lhs = Expression(0, Add), rhs = Counter(2)
|
||||
- expression 2 operands: lhs = Expression(0, Add), rhs = Counter(2)
|
||||
Number of file 0 mappings: 6
|
||||
- Code(Counter(0)) at (prev + 21, 1) to (start + 1, 16)
|
||||
- Code(Counter(1)) at (prev + 3, 9) to (start + 0, 18)
|
||||
- Code(Expression(0, Add)) at (prev + 1, 11) to (start + 0, 20)
|
||||
= (c1 + c2)
|
||||
- Branch { true: Counter(2), false: Expression(2, Sub) } at (prev + 0, 11) to (start + 0, 20)
|
||||
true = c2
|
||||
false = ((c1 + c2) - c2)
|
||||
- Code(Counter(2)) at (prev + 0, 21) to (start + 2, 6)
|
||||
- Code(Expression(2, Sub)) at (prev + 3, 1) to (start + 0, 2)
|
||||
= ((c1 + c2) - c2)
|
||||
|
||||
Function name: branch_while::while_op_and
|
||||
Raw bytes (56): 0x[01, 01, 04, 05, 09, 03, 0d, 03, 0d, 11, 0d, 08, 01, 1e, 01, 01, 10, 05, 03, 09, 01, 12, 03, 02, 0b, 00, 10, 20, 0a, 0d, 00, 0b, 00, 10, 0a, 00, 14, 00, 19, 20, 09, 11, 00, 14, 00, 19, 09, 00, 1a, 03, 06, 0f, 04, 01, 00, 02]
|
||||
Number of files: 1
|
||||
- file 0 => global file 1
|
||||
Number of expressions: 4
|
||||
- expression 0 operands: lhs = Counter(1), rhs = Counter(2)
|
||||
- expression 1 operands: lhs = Expression(0, Add), rhs = Counter(3)
|
||||
- expression 2 operands: lhs = Expression(0, Add), rhs = Counter(3)
|
||||
- expression 3 operands: lhs = Counter(4), rhs = Counter(3)
|
||||
Number of file 0 mappings: 8
|
||||
- Code(Counter(0)) at (prev + 30, 1) to (start + 1, 16)
|
||||
- Code(Counter(1)) at (prev + 3, 9) to (start + 1, 18)
|
||||
- Code(Expression(0, Add)) at (prev + 2, 11) to (start + 0, 16)
|
||||
= (c1 + c2)
|
||||
- Branch { true: Expression(2, Sub), false: Counter(3) } at (prev + 0, 11) to (start + 0, 16)
|
||||
true = ((c1 + c2) - c3)
|
||||
false = c3
|
||||
- Code(Expression(2, Sub)) at (prev + 0, 20) to (start + 0, 25)
|
||||
= ((c1 + c2) - c3)
|
||||
- Branch { true: Counter(2), false: Counter(4) } at (prev + 0, 20) to (start + 0, 25)
|
||||
true = c2
|
||||
false = c4
|
||||
- Code(Counter(2)) at (prev + 0, 26) to (start + 3, 6)
|
||||
- Code(Expression(3, Add)) at (prev + 4, 1) to (start + 0, 2)
|
||||
= (c4 + c3)
|
||||
|
||||
Function name: branch_while::while_op_or
|
||||
Raw bytes (66): 0x[01, 01, 09, 05, 1b, 09, 0d, 03, 09, 03, 09, 22, 0d, 03, 09, 09, 0d, 22, 0d, 03, 09, 08, 01, 29, 01, 01, 10, 05, 03, 09, 01, 12, 03, 02, 0b, 00, 10, 20, 09, 22, 00, 0b, 00, 10, 22, 00, 14, 00, 19, 20, 0d, 1e, 00, 14, 00, 19, 1b, 00, 1a, 03, 06, 1e, 04, 01, 00, 02]
|
||||
Number of files: 1
|
||||
- file 0 => global file 1
|
||||
Number of expressions: 9
|
||||
- expression 0 operands: lhs = Counter(1), rhs = Expression(6, Add)
|
||||
- expression 1 operands: lhs = Counter(2), rhs = Counter(3)
|
||||
- expression 2 operands: lhs = Expression(0, Add), rhs = Counter(2)
|
||||
- expression 3 operands: lhs = Expression(0, Add), rhs = Counter(2)
|
||||
- expression 4 operands: lhs = Expression(8, Sub), rhs = Counter(3)
|
||||
- expression 5 operands: lhs = Expression(0, Add), rhs = Counter(2)
|
||||
- expression 6 operands: lhs = Counter(2), rhs = Counter(3)
|
||||
- expression 7 operands: lhs = Expression(8, Sub), rhs = Counter(3)
|
||||
- expression 8 operands: lhs = Expression(0, Add), rhs = Counter(2)
|
||||
Number of file 0 mappings: 8
|
||||
- Code(Counter(0)) at (prev + 41, 1) to (start + 1, 16)
|
||||
- Code(Counter(1)) at (prev + 3, 9) to (start + 1, 18)
|
||||
- Code(Expression(0, Add)) at (prev + 2, 11) to (start + 0, 16)
|
||||
= (c1 + (c2 + c3))
|
||||
- Branch { true: Counter(2), false: Expression(8, Sub) } at (prev + 0, 11) to (start + 0, 16)
|
||||
true = c2
|
||||
false = ((c1 + (c2 + c3)) - c2)
|
||||
- Code(Expression(8, Sub)) at (prev + 0, 20) to (start + 0, 25)
|
||||
= ((c1 + (c2 + c3)) - c2)
|
||||
- Branch { true: Counter(3), false: Expression(7, Sub) } at (prev + 0, 20) to (start + 0, 25)
|
||||
true = c3
|
||||
false = (((c1 + (c2 + c3)) - c2) - c3)
|
||||
- Code(Expression(6, Add)) at (prev + 0, 26) to (start + 3, 6)
|
||||
= (c2 + c3)
|
||||
- Code(Expression(7, Sub)) at (prev + 4, 1) to (start + 0, 2)
|
||||
= (((c1 + (c2 + c3)) - c2) - c3)
|
||||
|
74
tests/coverage/branch_while.coverage
Normal file
74
tests/coverage/branch_while.coverage
Normal file
|
@ -0,0 +1,74 @@
|
|||
LL| |#![feature(coverage_attribute)]
|
||||
LL| |//@ edition: 2021
|
||||
LL| |//@ compile-flags: -Zcoverage-options=branch
|
||||
LL| |//@ llvm-cov-flags: --show-branches=count
|
||||
LL| |
|
||||
LL| |macro_rules! no_merge {
|
||||
LL| | () => {
|
||||
LL| | for _ in 0..1 {}
|
||||
LL| | };
|
||||
LL| |}
|
||||
LL| |
|
||||
LL| 1|fn while_cond() {
|
||||
LL| 1| no_merge!();
|
||||
LL| |
|
||||
LL| 1| let mut a = 8;
|
||||
LL| 9| while a > 0 {
|
||||
------------------
|
||||
| Branch (LL:11): [True: 8, False: 1]
|
||||
------------------
|
||||
LL| 8| a -= 1;
|
||||
LL| 8| }
|
||||
LL| 1|}
|
||||
LL| |
|
||||
LL| 1|fn while_cond_not() {
|
||||
LL| 1| no_merge!();
|
||||
LL| |
|
||||
LL| 1| let mut a = 8;
|
||||
LL| 9| while !(a == 0) {
|
||||
------------------
|
||||
| Branch (LL:11): [True: 8, False: 1]
|
||||
------------------
|
||||
LL| 8| a -= 1;
|
||||
LL| 8| }
|
||||
LL| 1|}
|
||||
LL| |
|
||||
LL| 1|fn while_op_and() {
|
||||
LL| 1| no_merge!();
|
||||
LL| |
|
||||
LL| 1| let mut a = 8;
|
||||
LL| 1| let mut b = 4;
|
||||
LL| 5| while a > 0 && b > 0 {
|
||||
------------------
|
||||
| Branch (LL:11): [True: 5, False: 0]
|
||||
| Branch (LL:20): [True: 4, False: 1]
|
||||
------------------
|
||||
LL| 4| a -= 1;
|
||||
LL| 4| b -= 1;
|
||||
LL| 4| }
|
||||
LL| 1|}
|
||||
LL| |
|
||||
LL| 1|fn while_op_or() {
|
||||
LL| 1| no_merge!();
|
||||
LL| |
|
||||
LL| 1| let mut a = 4;
|
||||
LL| 1| let mut b = 8;
|
||||
LL| 9| while a > 0 || b > 0 {
|
||||
^5
|
||||
------------------
|
||||
| Branch (LL:11): [True: 4, False: 5]
|
||||
| Branch (LL:20): [True: 4, False: 1]
|
||||
------------------
|
||||
LL| 8| a -= 1;
|
||||
LL| 8| b -= 1;
|
||||
LL| 8| }
|
||||
LL| 1|}
|
||||
LL| |
|
||||
LL| |#[coverage(off)]
|
||||
LL| |fn main() {
|
||||
LL| | while_cond();
|
||||
LL| | while_cond_not();
|
||||
LL| | while_op_and();
|
||||
LL| | while_op_or();
|
||||
LL| |}
|
||||
|
58
tests/coverage/branch_while.rs
Normal file
58
tests/coverage/branch_while.rs
Normal file
|
@ -0,0 +1,58 @@
|
|||
#![feature(coverage_attribute)]
|
||||
//@ edition: 2021
|
||||
//@ compile-flags: -Zcoverage-options=branch
|
||||
//@ llvm-cov-flags: --show-branches=count
|
||||
|
||||
macro_rules! no_merge {
|
||||
() => {
|
||||
for _ in 0..1 {}
|
||||
};
|
||||
}
|
||||
|
||||
fn while_cond() {
|
||||
no_merge!();
|
||||
|
||||
let mut a = 8;
|
||||
while a > 0 {
|
||||
a -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn while_cond_not() {
|
||||
no_merge!();
|
||||
|
||||
let mut a = 8;
|
||||
while !(a == 0) {
|
||||
a -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn while_op_and() {
|
||||
no_merge!();
|
||||
|
||||
let mut a = 8;
|
||||
let mut b = 4;
|
||||
while a > 0 && b > 0 {
|
||||
a -= 1;
|
||||
b -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn while_op_or() {
|
||||
no_merge!();
|
||||
|
||||
let mut a = 4;
|
||||
let mut b = 8;
|
||||
while a > 0 || b > 0 {
|
||||
a -= 1;
|
||||
b -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
#[coverage(off)]
|
||||
fn main() {
|
||||
while_cond();
|
||||
while_cond_not();
|
||||
while_op_and();
|
||||
while_op_or();
|
||||
}
|
Loading…
Add table
Reference in a new issue