coverage: Sort the expansion tree in depth-first order

This makes it possible for subsequent operations to iterate over all nodes,
while assuming that every node occurs before all of its descendants.
This commit is contained in:
Zalathar 2025-12-02 13:11:33 +11:00
parent 1f423a6e42
commit 7a3e5cd57e
3 changed files with 57 additions and 11 deletions

View file

@ -6,6 +6,7 @@ use rustc_span::{ExpnId, ExpnKind, Span};
use crate::coverage::from_mir;
use crate::coverage::graph::CoverageGraph;
use crate::coverage::hir_info::ExtractedHirInfo;
use crate::coverage::mappings::MappingsError;
#[derive(Clone, Copy, Debug)]
pub(crate) struct SpanWithBcb {
@ -62,6 +63,8 @@ pub(crate) struct ExpnNode {
/// but is helpful for debugging and might be useful later.
#[expect(dead_code)]
pub(crate) expn_id: ExpnId,
/// Index of this node in a depth-first traversal from the root.
pub(crate) dfs_rank: usize,
// Useful info extracted from `ExpnData`.
pub(crate) expn_kind: ExpnKind,
@ -100,6 +103,7 @@ impl ExpnNode {
Self {
expn_id,
dfs_rank: usize::MAX,
expn_kind: expn_data.kind,
call_site,
@ -124,7 +128,7 @@ pub(crate) fn build_expn_tree(
mir_body: &mir::Body<'_>,
hir_info: &ExtractedHirInfo,
graph: &CoverageGraph,
) -> ExpnTree {
) -> Result<ExpnTree, MappingsError> {
let raw_spans = from_mir::extract_raw_spans_from_mir(mir_body, graph);
let mut nodes = FxIndexMap::default();
@ -157,6 +161,9 @@ pub(crate) fn build_expn_tree(
}
}
// Sort the tree nodes into depth-first order.
sort_nodes_depth_first(&mut nodes)?;
// If we have a span for the function signature, associate it with the
// corresponding expansion tree node.
if let Some(fn_sig_span) = hir_info.fn_sig_span
@ -189,5 +196,32 @@ pub(crate) fn build_expn_tree(
}
}
ExpnTree { nodes }
Ok(ExpnTree { nodes })
}
/// Sorts the tree nodes in the map into depth-first order.
///
/// This allows subsequent operations to iterate over all nodes, while assuming
/// that every node occurs before all of its descendants.
fn sort_nodes_depth_first(nodes: &mut FxIndexMap<ExpnId, ExpnNode>) -> Result<(), MappingsError> {
let mut dfs_stack = vec![ExpnId::root()];
let mut next_dfs_rank = 0usize;
while let Some(expn_id) = dfs_stack.pop() {
if let Some(node) = nodes.get_mut(&expn_id) {
node.dfs_rank = next_dfs_rank;
next_dfs_rank += 1;
dfs_stack.extend(node.child_expn_ids.iter().rev().copied());
}
}
nodes.sort_by_key(|_expn_id, node| node.dfs_rank);
// Verify that the depth-first search visited each node exactly once.
for (i, &ExpnNode { dfs_rank, .. }) in nodes.values().enumerate() {
if dfs_rank != i {
tracing::debug!(dfs_rank, i, "expansion tree node's rank does not match its index");
return Err(MappingsError::TreeSortFailure);
}
}
Ok(())
}

View file

@ -11,6 +11,13 @@ use crate::coverage::graph::CoverageGraph;
use crate::coverage::hir_info::ExtractedHirInfo;
use crate::coverage::spans::extract_refined_covspans;
/// Indicates why mapping extraction failed, for debug-logging purposes.
#[derive(Debug)]
pub(crate) enum MappingsError {
NoMappings,
TreeSortFailure,
}
#[derive(Default)]
pub(crate) struct ExtractedMappings {
pub(crate) mappings: Vec<Mapping>,
@ -23,8 +30,8 @@ pub(crate) fn extract_mappings_from_mir<'tcx>(
mir_body: &mir::Body<'tcx>,
hir_info: &ExtractedHirInfo,
graph: &CoverageGraph,
) -> ExtractedMappings {
let expn_tree = expansion::build_expn_tree(mir_body, hir_info, graph);
) -> Result<ExtractedMappings, MappingsError> {
let expn_tree = expansion::build_expn_tree(mir_body, hir_info, graph)?;
let mut mappings = vec![];
@ -33,7 +40,11 @@ pub(crate) fn extract_mappings_from_mir<'tcx>(
extract_branch_mappings(mir_body, hir_info, graph, &expn_tree, &mut mappings);
ExtractedMappings { mappings }
if mappings.is_empty() {
tracing::debug!("no mappings were extracted");
return Err(MappingsError::NoMappings);
}
Ok(ExtractedMappings { mappings })
}
fn resolve_block_markers(

View file

@ -73,12 +73,13 @@ fn instrument_function_for_coverage<'tcx>(tcx: TyCtxt<'tcx>, mir_body: &mut mir:
////////////////////////////////////////////////////
// Extract coverage spans and other mapping info from MIR.
let ExtractedMappings { mappings } =
mappings::extract_mappings_from_mir(tcx, mir_body, &hir_info, &graph);
if mappings.is_empty() {
// No spans could be converted into valid mappings, so skip this function.
debug!("no spans could be converted into valid mappings; skipping");
return;
}
match mappings::extract_mappings_from_mir(tcx, mir_body, &hir_info, &graph) {
Ok(m) => m,
Err(error) => {
tracing::debug!(?error, "mapping extraction failed; skipping this function");
return;
}
};
// Use the coverage graph to prepare intermediate data that will eventually
// be used to assign physical counters and counter expressions to points in