Skip to content

Commit b49b18b

Browse files
authored
Rollup merge of #149471 - Zalathar:tree, r=oli-obk
coverage: Store signature/body spans and branch spans in the expansion tree In order to support coverage instrumentation of expansion regions, we need to reduce the amount of code that assumes we're only instrumenting a flat function body. Moving more data into expansion tree nodes is an incremental step in that direction. There should be no change to compiler output.
2 parents 97b5b9b + ac43716 commit b49b18b

File tree

11 files changed

+372
-50
lines changed

11 files changed

+372
-50
lines changed

compiler/rustc_mir_transform/src/coverage/expansion.rs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet, IndexEntry};
22
use rustc_middle::mir;
3-
use rustc_middle::mir::coverage::BasicCoverageBlock;
3+
use rustc_middle::mir::coverage::{BasicCoverageBlock, BranchSpan};
44
use rustc_span::{ExpnId, ExpnKind, Span};
55

66
use crate::coverage::from_mir;
@@ -71,11 +71,21 @@ pub(crate) struct ExpnNode {
7171
/// This links an expansion node to its parent in the tree.
7272
pub(crate) call_site_expn_id: Option<ExpnId>,
7373

74+
/// Holds the function signature span, if it belongs to this expansion.
75+
/// Used by special-case code in span refinement.
76+
pub(crate) fn_sig_span: Option<Span>,
77+
/// Holds the function body span, if it belongs to this expansion.
78+
/// Used by special-case code in span refinement.
79+
pub(crate) body_span: Option<Span>,
80+
7481
/// Spans (and their associated BCBs) belonging to this expansion.
7582
pub(crate) spans: Vec<SpanWithBcb>,
7683
/// Expansions whose call-site is in this expansion.
7784
pub(crate) child_expn_ids: FxIndexSet<ExpnId>,
7885

86+
/// Branch spans (recorded during MIR building) belonging to this expansion.
87+
pub(crate) branch_spans: Vec<BranchSpan>,
88+
7989
/// Hole spans belonging to this expansion, to be carved out from the
8090
/// code spans during span refinement.
8191
pub(crate) hole_spans: Vec<Span>,
@@ -95,9 +105,14 @@ impl ExpnNode {
95105
call_site,
96106
call_site_expn_id,
97107

108+
fn_sig_span: None,
109+
body_span: None,
110+
98111
spans: vec![],
99112
child_expn_ids: FxIndexSet::default(),
100113

114+
branch_spans: vec![],
115+
101116
hole_spans: vec![],
102117
}
103118
}
@@ -142,6 +157,20 @@ pub(crate) fn build_expn_tree(
142157
}
143158
}
144159

160+
// If we have a span for the function signature, associate it with the
161+
// corresponding expansion tree node.
162+
if let Some(fn_sig_span) = hir_info.fn_sig_span
163+
&& let Some(node) = nodes.get_mut(&fn_sig_span.ctxt().outer_expn())
164+
{
165+
node.fn_sig_span = Some(fn_sig_span);
166+
}
167+
168+
// Also associate the body span with its expansion tree node.
169+
let body_span = hir_info.body_span;
170+
if let Some(node) = nodes.get_mut(&body_span.ctxt().outer_expn()) {
171+
node.body_span = Some(body_span);
172+
}
173+
145174
// Associate each hole span (extracted from HIR) with its corresponding
146175
// expansion tree node.
147176
for &hole_span in &hir_info.hole_spans {
@@ -150,5 +179,15 @@ pub(crate) fn build_expn_tree(
150179
node.hole_spans.push(hole_span);
151180
}
152181

182+
// Associate each branch span (recorded during MIR building) with its
183+
// corresponding expansion tree node.
184+
if let Some(coverage_info_hi) = mir_body.coverage_info_hi.as_deref() {
185+
for branch_span in &coverage_info_hi.branch_spans {
186+
if let Some(node) = nodes.get_mut(&branch_span.span.ctxt().outer_expn()) {
187+
node.branch_spans.push(BranchSpan::clone(branch_span));
188+
}
189+
}
190+
}
191+
153192
ExpnTree { nodes }
154193
}

compiler/rustc_mir_transform/src/coverage/mappings.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ use rustc_middle::mir::coverage::{
44
};
55
use rustc_middle::mir::{self, BasicBlock, StatementKind};
66
use rustc_middle::ty::TyCtxt;
7+
use rustc_span::ExpnKind;
78

8-
use crate::coverage::expansion;
9+
use crate::coverage::expansion::{self, ExpnTree};
910
use crate::coverage::graph::CoverageGraph;
1011
use crate::coverage::hir_info::ExtractedHirInfo;
1112
use crate::coverage::spans::extract_refined_covspans;
12-
use crate::coverage::unexpand::unexpand_into_body_span;
1313

1414
#[derive(Default)]
1515
pub(crate) struct ExtractedMappings {
@@ -31,7 +31,7 @@ pub(crate) fn extract_mappings_from_mir<'tcx>(
3131
// Extract ordinary code mappings from MIR statement/terminator spans.
3232
extract_refined_covspans(tcx, hir_info, graph, &expn_tree, &mut mappings);
3333

34-
extract_branch_mappings(mir_body, hir_info, graph, &mut mappings);
34+
extract_branch_mappings(mir_body, hir_info, graph, &expn_tree, &mut mappings);
3535

3636
ExtractedMappings { mappings }
3737
}
@@ -57,25 +57,25 @@ fn resolve_block_markers(
5757
block_markers
5858
}
5959

60-
pub(super) fn extract_branch_mappings(
60+
fn extract_branch_mappings(
6161
mir_body: &mir::Body<'_>,
6262
hir_info: &ExtractedHirInfo,
6363
graph: &CoverageGraph,
64+
expn_tree: &ExpnTree,
6465
mappings: &mut Vec<Mapping>,
6566
) {
6667
let Some(coverage_info_hi) = mir_body.coverage_info_hi.as_deref() else { return };
67-
6868
let block_markers = resolve_block_markers(coverage_info_hi, mir_body);
6969

70-
mappings.extend(coverage_info_hi.branch_spans.iter().filter_map(
71-
|&BranchSpan { span: raw_span, true_marker, false_marker }| try {
72-
// For now, ignore any branch span that was introduced by
73-
// expansion. This makes things like assert macros less noisy.
74-
if !raw_span.ctxt().outer_expn_data().is_root() {
75-
return None;
76-
}
77-
let span = unexpand_into_body_span(raw_span, hir_info.body_span)?;
70+
// For now, ignore any branch span that was introduced by
71+
// expansion. This makes things like assert macros less noisy.
72+
let Some(node) = expn_tree.get(hir_info.body_span.ctxt().outer_expn()) else { return };
73+
if node.expn_kind != ExpnKind::Root {
74+
return;
75+
}
7876

77+
mappings.extend(node.branch_spans.iter().filter_map(
78+
|&BranchSpan { span, true_marker, false_marker }| try {
7979
let bcb_from_marker = |marker: BlockMarkerId| graph.bcb_from_bb(block_markers[marker]?);
8080

8181
let true_bcb = bcb_from_marker(true_marker)?;

compiler/rustc_mir_transform/src/coverage/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ pub(super) mod query;
1717
mod spans;
1818
#[cfg(test)]
1919
mod tests;
20-
mod unexpand;
2120

2221
/// Inserts `StatementKind::Coverage` statements that either instrument the binary with injected
2322
/// counters, via intrinsic `llvm.instrprof.increment`, and/or inject metadata used during codegen

compiler/rustc_mir_transform/src/coverage/spans.rs

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,9 @@ pub(super) fn extract_refined_covspans<'tcx>(
2626
return;
2727
}
2828

29-
let &ExtractedHirInfo { body_span, .. } = hir_info;
30-
3129
// If there somehow isn't an expansion tree node corresponding to the
3230
// body span, return now and don't create any mappings.
33-
let Some(node) = expn_tree.get(body_span.ctxt().outer_expn()) else { return };
31+
let Some(node) = expn_tree.get(hir_info.body_span.ctxt().outer_expn()) else { return };
3432

3533
let mut covspans = vec![];
3634

@@ -47,27 +45,29 @@ pub(super) fn extract_refined_covspans<'tcx>(
4745
}
4846
}
4947

50-
covspans.retain(|covspan: &Covspan| {
51-
let covspan_span = covspan.span;
52-
// Discard any spans not contained within the function body span.
53-
// Also discard any spans that fill the entire body, because they tend
54-
// to represent compiler-inserted code, e.g. implicitly returning `()`.
55-
if !body_span.contains(covspan_span) || body_span.source_equal(covspan_span) {
56-
return false;
57-
}
48+
if let Some(body_span) = node.body_span {
49+
covspans.retain(|covspan: &Covspan| {
50+
let covspan_span = covspan.span;
51+
// Discard any spans not contained within the function body span.
52+
// Also discard any spans that fill the entire body, because they tend
53+
// to represent compiler-inserted code, e.g. implicitly returning `()`.
54+
if !body_span.contains(covspan_span) || body_span.source_equal(covspan_span) {
55+
return false;
56+
}
5857

59-
// Each pushed covspan should have the same context as the body span.
60-
// If it somehow doesn't, discard the covspan, or panic in debug builds.
61-
if !body_span.eq_ctxt(covspan_span) {
62-
debug_assert!(
63-
false,
64-
"span context mismatch: body_span={body_span:?}, covspan.span={covspan_span:?}"
65-
);
66-
return false;
67-
}
58+
// Each pushed covspan should have the same context as the body span.
59+
// If it somehow doesn't, discard the covspan, or panic in debug builds.
60+
if !body_span.eq_ctxt(covspan_span) {
61+
debug_assert!(
62+
false,
63+
"span context mismatch: body_span={body_span:?}, covspan.span={covspan_span:?}"
64+
);
65+
return false;
66+
}
6867

69-
true
70-
});
68+
true
69+
});
70+
}
7171

7272
// Only proceed if we found at least one usable span.
7373
if covspans.is_empty() {
@@ -78,10 +78,9 @@ pub(super) fn extract_refined_covspans<'tcx>(
7878
// Otherwise, add a fake span at the start of the body, to avoid an ugly
7979
// gap between the start of the body and the first real span.
8080
// FIXME: Find a more principled way to solve this problem.
81-
covspans.push(Covspan {
82-
span: hir_info.fn_sig_span.unwrap_or_else(|| body_span.shrink_to_lo()),
83-
bcb: START_BCB,
84-
});
81+
if let Some(span) = node.fn_sig_span.or_else(|| try { node.body_span?.shrink_to_lo() }) {
82+
covspans.push(Covspan { span, bcb: START_BCB });
83+
}
8584

8685
let compare_covspans = |a: &Covspan, b: &Covspan| {
8786
compare_spans(a.span, b.span)

compiler/rustc_mir_transform/src/coverage/unexpand.rs

Lines changed: 0 additions & 9 deletions
This file was deleted.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
Function name: fn_in_macro::branch_in_macro
2+
Raw bytes (19): 0x[01, 01, 00, 03, 01, 22, 01, 00, 15, 01, 0b, 05, 00, 17, 01, 01, 01, 00, 02]
3+
Number of files: 1
4+
- file 0 => $DIR/fn-in-macro.rs
5+
Number of expressions: 0
6+
Number of file 0 mappings: 3
7+
- Code(Counter(0)) at (prev + 34, 1) to (start + 0, 21)
8+
- Code(Counter(0)) at (prev + 11, 5) to (start + 0, 23)
9+
- Code(Counter(0)) at (prev + 1, 1) to (start + 0, 2)
10+
Highest counter ID seen: c0
11+
12+
Function name: fn_in_macro::fn_in_macro
13+
Raw bytes (31): 0x[01, 01, 01, 01, 05, 05, 01, 0c, 09, 00, 19, 01, 01, 10, 00, 25, 05, 00, 2c, 02, 0e, 02, 02, 14, 02, 0e, 01, 03, 09, 00, 0a]
14+
Number of files: 1
15+
- file 0 => $DIR/fn-in-macro.rs
16+
Number of expressions: 1
17+
- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
18+
Number of file 0 mappings: 5
19+
- Code(Counter(0)) at (prev + 12, 9) to (start + 0, 25)
20+
- Code(Counter(0)) at (prev + 1, 16) to (start + 0, 37)
21+
- Code(Counter(1)) at (prev + 0, 44) to (start + 2, 14)
22+
- Code(Expression(0, Sub)) at (prev + 2, 20) to (start + 2, 14)
23+
= (c0 - c1)
24+
- Code(Counter(0)) at (prev + 3, 9) to (start + 0, 10)
25+
Highest counter ID seen: c1
26+
27+
Function name: fn_in_macro::fn_not_in_macro
28+
Raw bytes (38): 0x[01, 01, 01, 01, 05, 06, 01, 19, 01, 00, 15, 01, 01, 08, 00, 1d, 20, 05, 02, 00, 08, 00, 23, 05, 00, 24, 02, 06, 02, 02, 0c, 02, 06, 01, 03, 01, 00, 02]
29+
Number of files: 1
30+
- file 0 => $DIR/fn-in-macro.rs
31+
Number of expressions: 1
32+
- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
33+
Number of file 0 mappings: 6
34+
- Code(Counter(0)) at (prev + 25, 1) to (start + 0, 21)
35+
- Code(Counter(0)) at (prev + 1, 8) to (start + 0, 29)
36+
- Branch { true: Counter(1), false: Expression(0, Sub) } at (prev + 0, 8) to (start + 0, 35)
37+
true = c1
38+
false = (c0 - c1)
39+
- Code(Counter(1)) at (prev + 0, 36) to (start + 2, 6)
40+
- Code(Expression(0, Sub)) at (prev + 2, 12) to (start + 2, 6)
41+
= (c0 - c1)
42+
- Code(Counter(0)) at (prev + 3, 1) to (start + 0, 2)
43+
Highest counter ID seen: c1
44+
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
LL| |#![feature(coverage_attribute)]
2+
LL| |//@ edition: 2024
3+
LL| |//@ compile-flags: -Zcoverage-options=branch
4+
LL| |//@ llvm-cov-flags: --show-branches=count
5+
LL| |
6+
LL| |// Snapshot test demonstrating how branch coverage interacts with code in macros.
7+
LL| |// This test captures current behavior, which is not necessarily "correct".
8+
LL| |
9+
LL| |macro_rules! define_fn {
10+
LL| | () => {
11+
LL| | /// Function defined entirely within a macro.
12+
LL| 1| fn fn_in_macro() {
13+
LL| 1| if core::hint::black_box(true) {
14+
LL| 1| say("true");
15+
LL| 1| } else {
16+
LL| 0| say("false");
17+
LL| 0| }
18+
LL| 1| }
19+
LL| | };
20+
LL| |}
21+
LL| |
22+
LL| |define_fn!();
23+
LL| |
24+
LL| |/// Function not in a macro at all, for comparison.
25+
LL| 1|fn fn_not_in_macro() {
26+
LL| 1| if core::hint::black_box(true) {
27+
------------------
28+
| Branch (LL:8): [True: 1, False: 0]
29+
------------------
30+
LL| 1| say("true");
31+
LL| 1| } else {
32+
LL| 0| say("false");
33+
LL| 0| }
34+
LL| 1|}
35+
LL| |
36+
LL| |/// Function that is not in a macro, containing a branch that is in a macro.
37+
LL| 1|fn branch_in_macro() {
38+
LL| | macro_rules! macro_with_branch {
39+
LL| | () => {{
40+
LL| | if core::hint::black_box(true) {
41+
LL| | say("true");
42+
LL| | } else {
43+
LL| | say("false");
44+
LL| | }
45+
LL| | }};
46+
LL| | }
47+
LL| |
48+
LL| 1| macro_with_branch!();
49+
LL| 1|}
50+
LL| |
51+
LL| |#[coverage(off)]
52+
LL| |fn main() {
53+
LL| | fn_in_macro();
54+
LL| | fn_not_in_macro();
55+
LL| | branch_in_macro();
56+
LL| |}
57+
LL| |
58+
LL| |#[coverage(off)]
59+
LL| |fn say(message: &str) {
60+
LL| | println!("{message}");
61+
LL| |}
62+

0 commit comments

Comments
 (0)