Skip to content

Commit 7a3e5cd

Browse files
committed
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.
1 parent 1f423a6 commit 7a3e5cd

3 files changed

Lines changed: 57 additions & 11 deletions

File tree

compiler/rustc_mir_transform/src/coverage/expansion.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use rustc_span::{ExpnId, ExpnKind, Span};
66
use crate::coverage::from_mir;
77
use crate::coverage::graph::CoverageGraph;
88
use crate::coverage::hir_info::ExtractedHirInfo;
9+
use crate::coverage::mappings::MappingsError;
910

1011
#[derive(Clone, Copy, Debug)]
1112
pub(crate) struct SpanWithBcb {
@@ -62,6 +63,8 @@ pub(crate) struct ExpnNode {
6263
/// but is helpful for debugging and might be useful later.
6364
#[expect(dead_code)]
6465
pub(crate) expn_id: ExpnId,
66+
/// Index of this node in a depth-first traversal from the root.
67+
pub(crate) dfs_rank: usize,
6568

6669
// Useful info extracted from `ExpnData`.
6770
pub(crate) expn_kind: ExpnKind,
@@ -100,6 +103,7 @@ impl ExpnNode {
100103

101104
Self {
102105
expn_id,
106+
dfs_rank: usize::MAX,
103107

104108
expn_kind: expn_data.kind,
105109
call_site,
@@ -124,7 +128,7 @@ pub(crate) fn build_expn_tree(
124128
mir_body: &mir::Body<'_>,
125129
hir_info: &ExtractedHirInfo,
126130
graph: &CoverageGraph,
127-
) -> ExpnTree {
131+
) -> Result<ExpnTree, MappingsError> {
128132
let raw_spans = from_mir::extract_raw_spans_from_mir(mir_body, graph);
129133

130134
let mut nodes = FxIndexMap::default();
@@ -157,6 +161,9 @@ pub(crate) fn build_expn_tree(
157161
}
158162
}
159163

164+
// Sort the tree nodes into depth-first order.
165+
sort_nodes_depth_first(&mut nodes)?;
166+
160167
// If we have a span for the function signature, associate it with the
161168
// corresponding expansion tree node.
162169
if let Some(fn_sig_span) = hir_info.fn_sig_span
@@ -189,5 +196,32 @@ pub(crate) fn build_expn_tree(
189196
}
190197
}
191198

192-
ExpnTree { nodes }
199+
Ok(ExpnTree { nodes })
200+
}
201+
202+
/// Sorts the tree nodes in the map into depth-first order.
203+
///
204+
/// This allows subsequent operations to iterate over all nodes, while assuming
205+
/// that every node occurs before all of its descendants.
206+
fn sort_nodes_depth_first(nodes: &mut FxIndexMap<ExpnId, ExpnNode>) -> Result<(), MappingsError> {
207+
let mut dfs_stack = vec![ExpnId::root()];
208+
let mut next_dfs_rank = 0usize;
209+
while let Some(expn_id) = dfs_stack.pop() {
210+
if let Some(node) = nodes.get_mut(&expn_id) {
211+
node.dfs_rank = next_dfs_rank;
212+
next_dfs_rank += 1;
213+
dfs_stack.extend(node.child_expn_ids.iter().rev().copied());
214+
}
215+
}
216+
nodes.sort_by_key(|_expn_id, node| node.dfs_rank);
217+
218+
// Verify that the depth-first search visited each node exactly once.
219+
for (i, &ExpnNode { dfs_rank, .. }) in nodes.values().enumerate() {
220+
if dfs_rank != i {
221+
tracing::debug!(dfs_rank, i, "expansion tree node's rank does not match its index");
222+
return Err(MappingsError::TreeSortFailure);
223+
}
224+
}
225+
226+
Ok(())
193227
}

compiler/rustc_mir_transform/src/coverage/mappings.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ use crate::coverage::graph::CoverageGraph;
1111
use crate::coverage::hir_info::ExtractedHirInfo;
1212
use crate::coverage::spans::extract_refined_covspans;
1313

14+
/// Indicates why mapping extraction failed, for debug-logging purposes.
15+
#[derive(Debug)]
16+
pub(crate) enum MappingsError {
17+
NoMappings,
18+
TreeSortFailure,
19+
}
20+
1421
#[derive(Default)]
1522
pub(crate) struct ExtractedMappings {
1623
pub(crate) mappings: Vec<Mapping>,
@@ -23,8 +30,8 @@ pub(crate) fn extract_mappings_from_mir<'tcx>(
2330
mir_body: &mir::Body<'tcx>,
2431
hir_info: &ExtractedHirInfo,
2532
graph: &CoverageGraph,
26-
) -> ExtractedMappings {
27-
let expn_tree = expansion::build_expn_tree(mir_body, hir_info, graph);
33+
) -> Result<ExtractedMappings, MappingsError> {
34+
let expn_tree = expansion::build_expn_tree(mir_body, hir_info, graph)?;
2835

2936
let mut mappings = vec![];
3037

@@ -33,7 +40,11 @@ pub(crate) fn extract_mappings_from_mir<'tcx>(
3340

3441
extract_branch_mappings(mir_body, hir_info, graph, &expn_tree, &mut mappings);
3542

36-
ExtractedMappings { mappings }
43+
if mappings.is_empty() {
44+
tracing::debug!("no mappings were extracted");
45+
return Err(MappingsError::NoMappings);
46+
}
47+
Ok(ExtractedMappings { mappings })
3748
}
3849

3950
fn resolve_block_markers(

compiler/rustc_mir_transform/src/coverage/mod.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,13 @@ fn instrument_function_for_coverage<'tcx>(tcx: TyCtxt<'tcx>, mir_body: &mut mir:
7373
////////////////////////////////////////////////////
7474
// Extract coverage spans and other mapping info from MIR.
7575
let ExtractedMappings { mappings } =
76-
mappings::extract_mappings_from_mir(tcx, mir_body, &hir_info, &graph);
77-
if mappings.is_empty() {
78-
// No spans could be converted into valid mappings, so skip this function.
79-
debug!("no spans could be converted into valid mappings; skipping");
80-
return;
81-
}
76+
match mappings::extract_mappings_from_mir(tcx, mir_body, &hir_info, &graph) {
77+
Ok(m) => m,
78+
Err(error) => {
79+
tracing::debug!(?error, "mapping extraction failed; skipping this function");
80+
return;
81+
}
82+
};
8283

8384
// Use the coverage graph to prepare intermediate data that will eventually
8485
// be used to assign physical counters and counter expressions to points in

0 commit comments

Comments
 (0)