2024-04-29 22:25:09 +10:00
use std ::collections ::BTreeSet ;
2024-07-25 15:23:35 +08:00
use rustc_data_structures ::fx ::FxIndexMap ;
2024-04-29 22:25:09 +10:00
use rustc_data_structures ::graph ::DirectedGraph ;
2024-05-02 12:30:07 +10:00
use rustc_index ::IndexVec ;
2024-04-29 22:25:09 +10:00
use rustc_index ::bit_set ::BitSet ;
2024-07-04 23:52:49 +10:00
use rustc_middle ::mir ::coverage ::{
2024-07-25 15:23:35 +08:00
BlockMarkerId , BranchSpan , ConditionId , ConditionInfo , CoverageInfoHi , CoverageKind ,
2024-07-04 23:52:49 +10:00
} ;
2024-04-29 22:25:09 +10:00
use rustc_middle ::mir ::{ self , BasicBlock , StatementKind } ;
2024-06-17 20:09:45 +10:00
use rustc_middle ::ty ::TyCtxt ;
2024-04-29 22:25:09 +10:00
use rustc_span ::Span ;
use crate ::coverage ::ExtractedHirInfo ;
use crate ::coverage ::graph ::{ BasicCoverageBlock , CoverageGraph , START_BCB } ;
2024-06-30 17:36:16 +10:00
use crate ::coverage ::spans ::extract_refined_covspans ;
2024-06-30 18:20:45 +10:00
use crate ::coverage ::unexpand ::unexpand_into_body_span ;
2024-04-29 22:25:09 +10:00
2024-05-02 14:18:22 +10:00
/// Associates an ordinary executable code span with its corresponding BCB.
2024-04-29 22:25:09 +10:00
#[ derive(Debug) ]
2024-05-02 14:18:22 +10:00
pub ( super ) struct CodeMapping {
2024-04-29 22:25:09 +10:00
pub ( super ) span : Span ,
2024-05-02 14:18:22 +10:00
pub ( super ) bcb : BasicCoverageBlock ,
2024-04-29 22:25:09 +10:00
}
2024-05-02 14:18:22 +10:00
/// This is separate from [`MCDCBranch`] to help prepare for larger changes
2024-04-29 22:25:09 +10:00
/// that will be needed for improved branch coverage in the future.
/// (See <https://github.com/rust-lang/rust/pull/124217>.)
#[ derive(Debug) ]
2024-05-02 14:39:24 +10:00
pub ( super ) struct BranchPair {
2024-04-29 22:25:09 +10:00
pub ( super ) span : Span ,
pub ( super ) true_bcb : BasicCoverageBlock ,
pub ( super ) false_bcb : BasicCoverageBlock ,
}
2024-05-02 12:46:23 +10:00
/// Associates an MC/DC branch span with condition info besides fields for normal branch.
#[ derive(Debug) ]
pub ( super ) struct MCDCBranch {
pub ( super ) span : Span ,
pub ( super ) true_bcb : BasicCoverageBlock ,
pub ( super ) false_bcb : BasicCoverageBlock ,
2024-07-25 15:23:35 +08:00
pub ( super ) condition_info : ConditionInfo ,
// Offset added to test vector idx if this branch is evaluated to true.
pub ( super ) true_index : usize ,
// Offset added to test vector idx if this branch is evaluated to false.
pub ( super ) false_index : usize ,
2024-05-02 12:46:23 +10:00
}
2024-05-02 12:30:07 +10:00
/// Associates an MC/DC decision with its join BCBs.
#[ derive(Debug) ]
pub ( super ) struct MCDCDecision {
pub ( super ) span : Span ,
pub ( super ) end_bcbs : BTreeSet < BasicCoverageBlock > ,
2024-07-25 15:23:35 +08:00
pub ( super ) bitmap_idx : usize ,
pub ( super ) num_test_vectors : usize ,
2024-05-02 12:30:07 +10:00
pub ( super ) decision_depth : u16 ,
}
2024-07-25 15:23:35 +08:00
// LLVM uses `i32` to index the bitmap. Thus `i32::MAX` is the hard limit for number of all test vectors
// in a function.
const MCDC_MAX_BITMAP_SIZE : usize = i32 ::MAX as usize ;
2024-05-02 20:24:23 +10:00
#[ derive(Default) ]
2024-05-02 17:05:32 +10:00
pub ( super ) struct ExtractedMappings {
2024-07-15 20:37:14 +10:00
/// Store our own copy of [`CoverageGraph::num_nodes`], so that we don't
/// need access to the whole graph when allocating per-BCB data. This is
/// only public so that other code can still use exhaustive destructuring.
pub ( super ) num_bcbs : usize ,
2024-05-02 14:18:22 +10:00
pub ( super ) code_mappings : Vec < CodeMapping > ,
2024-05-02 14:39:24 +10:00
pub ( super ) branch_pairs : Vec < BranchPair > ,
2024-07-25 15:23:35 +08:00
pub ( super ) mcdc_bitmap_bits : usize ,
pub ( super ) mcdc_degraded_branches : Vec < MCDCBranch > ,
pub ( super ) mcdc_mappings : Vec < ( MCDCDecision , Vec < MCDCBranch > ) > ,
2024-04-29 22:25:09 +10:00
}
/// Extracts coverage-relevant spans from MIR, and associates them with
/// their corresponding BCBs.
2024-06-17 20:09:45 +10:00
pub ( super ) fn extract_all_mapping_info_from_mir < ' tcx > (
tcx : TyCtxt < ' tcx > ,
mir_body : & mir ::Body < ' tcx > ,
2024-04-29 22:25:09 +10:00
hir_info : & ExtractedHirInfo ,
basic_coverage_blocks : & CoverageGraph ,
2024-05-02 17:05:32 +10:00
) -> ExtractedMappings {
2024-06-17 20:09:45 +10:00
let mut code_mappings = vec! [ ] ;
let mut branch_pairs = vec! [ ] ;
2024-07-25 15:23:35 +08:00
let mut mcdc_bitmap_bits = 0 ;
let mut mcdc_degraded_branches = vec! [ ] ;
let mut mcdc_mappings = vec! [ ] ;
2024-06-17 20:09:45 +10:00
if hir_info . is_async_fn | | tcx . sess . coverage_no_mir_spans ( ) {
2024-04-29 22:25:09 +10:00
// An async function desugars into a function that returns a future,
// with the user code wrapped in a closure. Any spans in the desugared
// outer function will be unhelpful, so just keep the signature span
// and ignore all of the spans in the MIR body.
2024-06-17 20:09:45 +10:00
//
// When debugging flag `-Zcoverage-options=no-mir-spans` is set, we need
// to give the same treatment to _all_ functions, because `llvm-cov`
// seems to ignore functions that don't have any ordinary code spans.
2024-04-29 22:25:09 +10:00
if let Some ( span ) = hir_info . fn_sig_span_extended {
2024-06-17 20:09:45 +10:00
code_mappings . push ( CodeMapping { span , bcb : START_BCB } ) ;
2024-04-29 22:25:09 +10:00
}
2024-06-17 20:09:45 +10:00
} else {
// Extract coverage spans from MIR statements/terminators as normal.
extract_refined_covspans ( mir_body , hir_info , basic_coverage_blocks , & mut code_mappings ) ;
2024-04-29 22:25:09 +10:00
}
2024-05-02 20:24:23 +10:00
branch_pairs . extend ( extract_branch_pairs ( mir_body , hir_info , basic_coverage_blocks ) ) ;
extract_mcdc_mappings (
mir_body ,
hir_info . body_span ,
basic_coverage_blocks ,
2024-07-25 15:23:35 +08:00
& mut mcdc_bitmap_bits ,
& mut mcdc_degraded_branches ,
& mut mcdc_mappings ,
2024-05-02 20:24:23 +10:00
) ;
2024-05-02 17:05:32 +10:00
ExtractedMappings {
2024-07-15 20:37:14 +10:00
num_bcbs : basic_coverage_blocks . num_nodes ( ) ,
2024-05-02 17:05:32 +10:00
code_mappings ,
branch_pairs ,
2024-07-25 15:23:35 +08:00
mcdc_bitmap_bits ,
mcdc_degraded_branches ,
mcdc_mappings ,
2024-05-02 17:05:32 +10:00
}
2024-05-02 16:28:49 +10:00
}
2024-05-02 17:05:32 +10:00
impl ExtractedMappings {
2024-07-15 20:37:14 +10:00
pub ( super ) fn all_bcbs_with_counter_mappings ( & self ) -> BitSet < BasicCoverageBlock > {
2024-05-02 16:28:49 +10:00
// Fully destructure self to make sure we don't miss any fields that have mappings.
let Self {
2024-07-15 20:37:14 +10:00
num_bcbs ,
2024-05-02 16:28:49 +10:00
code_mappings ,
branch_pairs ,
2024-07-25 15:23:35 +08:00
mcdc_bitmap_bits : _ ,
mcdc_degraded_branches ,
mcdc_mappings ,
2024-05-02 16:28:49 +10:00
} = self ;
// Identify which BCBs have one or more mappings.
2024-07-15 20:37:14 +10:00
let mut bcbs_with_counter_mappings = BitSet ::new_empty ( * num_bcbs ) ;
2024-05-02 16:28:49 +10:00
let mut insert = | bcb | {
bcbs_with_counter_mappings . insert ( bcb ) ;
} ;
for & CodeMapping { span : _ , bcb } in code_mappings {
insert ( bcb ) ;
}
for & BranchPair { true_bcb , false_bcb , .. } in branch_pairs {
insert ( true_bcb ) ;
insert ( false_bcb ) ;
}
2024-07-25 15:23:35 +08:00
for & MCDCBranch { true_bcb , false_bcb , .. } in mcdc_degraded_branches
. iter ( )
. chain ( mcdc_mappings . iter ( ) . map ( | ( _ , branches ) | branches . into_iter ( ) ) . flatten ( ) )
{
2024-05-02 16:28:49 +10:00
insert ( true_bcb ) ;
insert ( false_bcb ) ;
}
// MC/DC decisions refer to BCBs, but don't require those BCBs to have counters.
if bcbs_with_counter_mappings . is_empty ( ) {
debug_assert! (
2024-07-25 15:23:35 +08:00
mcdc_mappings . is_empty ( ) ,
" A function with no counter mappings shouldn't have any decisions: {mcdc_mappings:?} " ,
2024-05-02 16:28:49 +10:00
) ;
}
bcbs_with_counter_mappings
}
2024-07-15 20:32:14 +10:00
/// Returns the set of BCBs that have one or more `Code` mappings.
pub ( super ) fn bcbs_with_ordinary_code_mappings ( & self ) -> BitSet < BasicCoverageBlock > {
let mut bcbs = BitSet ::new_empty ( self . num_bcbs ) ;
for & CodeMapping { span : _ , bcb } in & self . code_mappings {
bcbs . insert ( bcb ) ;
}
bcbs
}
2024-04-29 22:25:09 +10:00
}
fn resolve_block_markers (
2024-07-04 23:52:49 +10:00
coverage_info_hi : & CoverageInfoHi ,
2024-04-29 22:25:09 +10:00
mir_body : & mir ::Body < '_ > ,
) -> IndexVec < BlockMarkerId , Option < BasicBlock > > {
let mut block_markers = IndexVec ::< BlockMarkerId , Option < BasicBlock > > ::from_elem_n (
None ,
2024-07-04 23:52:49 +10:00
coverage_info_hi . num_block_markers ,
2024-04-29 22:25:09 +10:00
) ;
// 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 ( CoverageKind ::BlockMarker { id } ) = statement . kind {
block_markers [ id ] = Some ( bb ) ;
}
}
}
block_markers
}
// FIXME: There is currently a lot of redundancy between
// `extract_branch_pairs` and `extract_mcdc_mappings`. This is needed so
// that they can each be modified without interfering with the other, but in
// the long term we should try to bring them together again when branch coverage
// and MC/DC coverage support are more mature.
pub ( super ) fn extract_branch_pairs (
mir_body : & mir ::Body < '_ > ,
hir_info : & ExtractedHirInfo ,
basic_coverage_blocks : & CoverageGraph ,
2024-05-02 14:39:24 +10:00
) -> Vec < BranchPair > {
2024-07-04 23:52:49 +10:00
let Some ( coverage_info_hi ) = mir_body . coverage_info_hi . as_deref ( ) else { return vec! [ ] } ;
2024-04-29 22:25:09 +10:00
2024-07-04 23:52:49 +10:00
let block_markers = resolve_block_markers ( coverage_info_hi , mir_body ) ;
2024-04-29 22:25:09 +10:00
2024-07-04 23:52:49 +10:00
coverage_info_hi
2024-04-29 22:25:09 +10:00
. 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 ;
}
2024-06-30 18:20:45 +10:00
let span = unexpand_into_body_span ( raw_span , hir_info . body_span ) ? ;
2024-04-29 22:25:09 +10:00
let bcb_from_marker =
| marker : BlockMarkerId | 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 ) ? ;
2024-05-02 14:39:24 +10:00
Some ( BranchPair { span , true_bcb , false_bcb } )
2024-04-29 22:25:09 +10:00
} )
. collect ::< Vec < _ > > ( )
}
pub ( super ) fn extract_mcdc_mappings (
mir_body : & mir ::Body < '_ > ,
body_span : Span ,
basic_coverage_blocks : & CoverageGraph ,
2024-07-25 15:23:35 +08:00
mcdc_bitmap_bits : & mut usize ,
mcdc_degraded_branches : & mut impl Extend < MCDCBranch > ,
mcdc_mappings : & mut impl Extend < ( MCDCDecision , Vec < MCDCBranch > ) > ,
2024-05-02 12:46:23 +10:00
) {
2024-07-04 23:52:49 +10:00
let Some ( coverage_info_hi ) = mir_body . coverage_info_hi . as_deref ( ) else { return } ;
2024-04-29 22:25:09 +10:00
2024-07-04 23:52:49 +10:00
let block_markers = resolve_block_markers ( coverage_info_hi , mir_body ) ;
2024-04-29 22:25:09 +10:00
let bcb_from_marker =
| marker : BlockMarkerId | basic_coverage_blocks . bcb_from_bb ( block_markers [ marker ] ? ) ;
let check_branch_bcb =
| raw_span : Span , true_marker : BlockMarkerId , false_marker : BlockMarkerId | {
// 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 ;
}
2024-06-30 18:20:45 +10:00
let span = unexpand_into_body_span ( raw_span , body_span ) ? ;
2024-04-29 22:25:09 +10:00
let true_bcb = bcb_from_marker ( true_marker ) ? ;
let false_bcb = bcb_from_marker ( false_marker ) ? ;
Some ( ( span , true_bcb , false_bcb ) )
} ;
2024-07-25 15:23:35 +08:00
let to_mcdc_branch = | & mir ::coverage ::MCDCBranchSpan {
span : raw_span ,
condition_info ,
true_marker ,
false_marker ,
} | {
let ( span , true_bcb , false_bcb ) = check_branch_bcb ( raw_span , true_marker , false_marker ) ? ;
Some ( MCDCBranch {
span ,
true_bcb ,
false_bcb ,
condition_info ,
true_index : usize ::MAX ,
false_index : usize ::MAX ,
} )
} ;
let mut get_bitmap_idx = | num_test_vectors : usize | -> Option < usize > {
let bitmap_idx = * mcdc_bitmap_bits ;
let next_bitmap_bits = bitmap_idx . saturating_add ( num_test_vectors ) ;
( next_bitmap_bits < = MCDC_MAX_BITMAP_SIZE ) . then ( | | {
* mcdc_bitmap_bits = next_bitmap_bits ;
bitmap_idx
} )
} ;
mcdc_degraded_branches
. extend ( coverage_info_hi . mcdc_degraded_branch_spans . iter ( ) . filter_map ( to_mcdc_branch ) ) ;
mcdc_mappings . extend ( coverage_info_hi . mcdc_spans . iter ( ) . filter_map ( | ( decision , branches ) | {
if branches . len ( ) = = 0 {
return None ;
}
let decision_span = unexpand_into_body_span ( decision . span , body_span ) ? ;
let end_bcbs = decision
. end_markers
. iter ( )
. map ( | & marker | bcb_from_marker ( marker ) )
. collect ::< Option < _ > > ( ) ? ;
let mut branch_mappings : Vec < _ > = branches . into_iter ( ) . filter_map ( to_mcdc_branch ) . collect ( ) ;
if branch_mappings . len ( ) ! = branches . len ( ) {
mcdc_degraded_branches . extend ( branch_mappings ) ;
return None ;
}
let num_test_vectors = calc_test_vectors_index ( & mut branch_mappings ) ;
let Some ( bitmap_idx ) = get_bitmap_idx ( num_test_vectors ) else {
// TODO warn about bitmap
mcdc_degraded_branches . extend ( branch_mappings ) ;
return None ;
} ;
// LLVM requires span of the decision contains all spans of its conditions.
// Usually the decision span meets the requirement well but in cases like macros it may not.
let span = branch_mappings
. iter ( )
. map ( | branch | branch . span )
. reduce ( | lhs , rhs | lhs . to ( rhs ) )
. map (
| joint_span | {
if decision_span . contains ( joint_span ) { decision_span } else { joint_span }
} ,
)
. expect ( " branch mappings are ensured to be non-empty as checked above " ) ;
Some ( (
MCDCDecision {
2024-05-02 12:30:07 +10:00
span ,
2024-04-29 22:25:09 +10:00
end_bcbs ,
bitmap_idx ,
2024-07-25 15:23:35 +08:00
num_test_vectors ,
2024-04-29 22:25:09 +10:00
decision_depth : decision . decision_depth ,
2024-07-25 15:23:35 +08:00
} ,
branch_mappings ,
) )
} ) ) ;
}
// LLVM checks the executed test vector by accumulating indices of tested branches.
// We calculate number of all possible test vectors of the decision and assign indices
// to branches here.
// See [the rfc](https://discourse.llvm.org/t/rfc-coverage-new-algorithm-and-file-format-for-mc-dc/76798/)
// for more details about the algorithm.
// This function is mostly like [`TVIdxBuilder::TvIdxBuilder`](https://github.com/llvm/llvm-project/blob/d594d9f7f4dc6eb748b3261917db689fdc348b96/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp#L226)
fn calc_test_vectors_index ( conditions : & mut Vec < MCDCBranch > ) -> usize {
let mut indegree_stats = IndexVec ::< ConditionId , usize > ::from_elem_n ( 0 , conditions . len ( ) ) ;
// `num_paths` is `width` described at the llvm rfc, which indicates how many paths reaching the condition node.
let mut num_paths_stats = IndexVec ::< ConditionId , usize > ::from_elem_n ( 0 , conditions . len ( ) ) ;
let mut next_conditions = conditions
. iter_mut ( )
. map ( | branch | {
let ConditionInfo { condition_id , true_next_id , false_next_id } = branch . condition_info ;
[ true_next_id , false_next_id ]
. into_iter ( )
. filter_map ( std ::convert ::identity )
. for_each ( | next_id | indegree_stats [ next_id ] + = 1 ) ;
( condition_id , branch )
} )
. collect ::< FxIndexMap < _ , _ > > ( ) ;
let mut queue = std ::collections ::VecDeque ::from_iter (
next_conditions . swap_remove ( & ConditionId ::START ) . into_iter ( ) ,
) ;
num_paths_stats [ ConditionId ::START ] = 1 ;
let mut decision_end_nodes = Vec ::new ( ) ;
while let Some ( branch ) = queue . pop_front ( ) {
let ConditionInfo { condition_id , true_next_id , false_next_id } = branch . condition_info ;
let ( false_index , true_index ) = ( & mut branch . false_index , & mut branch . true_index ) ;
let this_paths_count = num_paths_stats [ condition_id ] ;
// Note. First check the false next to ensure conditions are touched in same order with llvm-cov.
for ( next , index ) in [ ( false_next_id , false_index ) , ( true_next_id , true_index ) ] {
if let Some ( next_id ) = next {
let next_paths_count = & mut num_paths_stats [ next_id ] ;
* index = * next_paths_count ;
* next_paths_count = next_paths_count . saturating_add ( this_paths_count ) ;
let next_indegree = & mut indegree_stats [ next_id ] ;
* next_indegree - = 1 ;
if * next_indegree = = 0 {
queue . push_back ( next_conditions . swap_remove ( & next_id ) . expect (
" conditions with non-zero indegree before must be in next_conditions " ,
) ) ;
}
} else {
decision_end_nodes . push ( ( this_paths_count , condition_id , index ) ) ;
}
}
}
assert! ( next_conditions . is_empty ( ) , " the decision tree has untouched nodes " ) ;
let mut cur_idx = 0 ;
// LLVM hopes the end nodes are sorted in descending order by `num_paths` so that it can
// optimize bitmap size for decisions in tree form such as `a && b && c && d && ...`.
decision_end_nodes . sort_by_key ( | ( num_paths , _ , _ ) | usize ::MAX - * num_paths ) ;
for ( num_paths , condition_id , index ) in decision_end_nodes {
assert_eq! (
num_paths , num_paths_stats [ condition_id ] ,
" end nodes should not be updated since they were visited "
) ;
assert_eq! ( * index , usize ::MAX , " end nodes should not be assigned index before " ) ;
* index = cur_idx ;
cur_idx + = num_paths ;
}
cur_idx
2024-04-29 22:25:09 +10:00
}