Skip to content

Commit 595c222

Browse files
authored
Unrolled build for rust-lang#119155
Rollup merge of rust-lang#119155 - Zalathar:async-fn, r=compiler-errors coverage: Check for `async fn` explicitly, without needing a heuristic The old code used a heuristic to detect async functions and adjust their coverage spans to produce better output. But there's no need to resort to a heuristic when we can just look back at the original definition and check whether the current function is actually an `async fn`. In addition to being generally nicer, this also gets rid of the one piece of code that specifically cares about `CoverageSpan::is_closure` representing an actual closure. All remaining code that inspects that field just uses it as an indication that the span is a hole that should be carved out of other spans, and then discarded. That opens up the possibility of introducing other kinds of “hole” spans, e.g. for nested functions/types/macros, and having them all behave uniformly. --- `@rustbot` label +A-code-coverage
2 parents 8fca829 + cf6dc7a commit 595c222

File tree

6 files changed

+130
-40
lines changed

6 files changed

+130
-40
lines changed

compiler/rustc_mir_transform/src/coverage/mod.rs

+9-19
Original file line numberDiff line numberDiff line change
@@ -68,41 +68,29 @@ impl<'tcx> MirPass<'tcx> for InstrumentCoverage {
6868
struct Instrumentor<'a, 'tcx> {
6969
tcx: TyCtxt<'tcx>,
7070
mir_body: &'a mut mir::Body<'tcx>,
71-
fn_sig_span: Span,
72-
body_span: Span,
73-
function_source_hash: u64,
71+
hir_info: ExtractedHirInfo,
7472
basic_coverage_blocks: CoverageGraph,
7573
coverage_counters: CoverageCounters,
7674
}
7775

7876
impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
7977
fn new(tcx: TyCtxt<'tcx>, mir_body: &'a mut mir::Body<'tcx>) -> Self {
80-
let hir_info @ ExtractedHirInfo { function_source_hash, fn_sig_span, body_span } =
81-
extract_hir_info(tcx, mir_body.source.def_id().expect_local());
78+
let hir_info = extract_hir_info(tcx, mir_body.source.def_id().expect_local());
8279

8380
debug!(?hir_info, "instrumenting {:?}", mir_body.source.def_id());
8481

8582
let basic_coverage_blocks = CoverageGraph::from_mir(mir_body);
8683
let coverage_counters = CoverageCounters::new(&basic_coverage_blocks);
8784

88-
Self {
89-
tcx,
90-
mir_body,
91-
fn_sig_span,
92-
body_span,
93-
function_source_hash,
94-
basic_coverage_blocks,
95-
coverage_counters,
96-
}
85+
Self { tcx, mir_body, hir_info, basic_coverage_blocks, coverage_counters }
9786
}
9887

9988
fn inject_counters(&'a mut self) {
10089
////////////////////////////////////////////////////
10190
// Compute coverage spans from the `CoverageGraph`.
10291
let Some(coverage_spans) = CoverageSpans::generate_coverage_spans(
10392
self.mir_body,
104-
self.fn_sig_span,
105-
self.body_span,
93+
&self.hir_info,
10694
&self.basic_coverage_blocks,
10795
) else {
10896
// No relevant spans were found in MIR, so skip instrumenting this function.
@@ -121,7 +109,7 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
121109
let mappings = self.create_mappings_and_inject_coverage_statements(&coverage_spans);
122110

123111
self.mir_body.function_coverage_info = Some(Box::new(FunctionCoverageInfo {
124-
function_source_hash: self.function_source_hash,
112+
function_source_hash: self.hir_info.function_source_hash,
125113
num_counters: self.coverage_counters.num_counters(),
126114
expressions: self.coverage_counters.take_expressions(),
127115
mappings,
@@ -136,7 +124,7 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
136124
coverage_spans: &CoverageSpans,
137125
) -> Vec<Mapping> {
138126
let source_map = self.tcx.sess.source_map();
139-
let body_span = self.body_span;
127+
let body_span = self.hir_info.body_span;
140128

141129
let source_file = source_map.lookup_source_file(body_span.lo());
142130
use rustc_session::RemapFileNameExt;
@@ -311,6 +299,7 @@ fn is_eligible_for_coverage(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
311299
#[derive(Debug)]
312300
struct ExtractedHirInfo {
313301
function_source_hash: u64,
302+
is_async_fn: bool,
314303
fn_sig_span: Span,
315304
body_span: Span,
316305
}
@@ -324,6 +313,7 @@ fn extract_hir_info<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> ExtractedHir
324313
hir::map::associated_body(hir_node).expect("HIR node is a function with body");
325314
let hir_body = tcx.hir().body(fn_body_id);
326315

316+
let is_async_fn = hir_node.fn_sig().is_some_and(|fn_sig| fn_sig.header.is_async());
327317
let body_span = get_body_span(tcx, hir_body, def_id);
328318

329319
// The actual signature span is only used if it has the same context and
@@ -345,7 +335,7 @@ fn extract_hir_info<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> ExtractedHir
345335

346336
let function_source_hash = hash_mir_source(tcx, hir_body);
347337

348-
ExtractedHirInfo { function_source_hash, fn_sig_span, body_span }
338+
ExtractedHirInfo { function_source_hash, is_async_fn, fn_sig_span, body_span }
349339
}
350340

351341
fn get_body_span<'tcx>(

compiler/rustc_mir_transform/src/coverage/spans.rs

+6-9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use rustc_middle::mir;
66
use rustc_span::{BytePos, ExpnKind, MacroKind, Span, Symbol, DUMMY_SP};
77

88
use super::graph::{BasicCoverageBlock, CoverageGraph, START_BCB};
9+
use crate::coverage::ExtractedHirInfo;
910

1011
mod from_mir;
1112

@@ -21,14 +22,12 @@ impl CoverageSpans {
2122
/// Returns `None` if no coverage-relevant spans could be extracted.
2223
pub(super) fn generate_coverage_spans(
2324
mir_body: &mir::Body<'_>,
24-
fn_sig_span: Span,
25-
body_span: Span,
25+
hir_info: &ExtractedHirInfo,
2626
basic_coverage_blocks: &CoverageGraph,
2727
) -> Option<Self> {
2828
let coverage_spans = CoverageSpansGenerator::generate_coverage_spans(
2929
mir_body,
30-
fn_sig_span,
31-
body_span,
30+
hir_info,
3231
basic_coverage_blocks,
3332
);
3433

@@ -230,19 +229,17 @@ impl<'a> CoverageSpansGenerator<'a> {
230229
/// to be).
231230
pub(super) fn generate_coverage_spans(
232231
mir_body: &mir::Body<'_>,
233-
fn_sig_span: Span, // Ensured to be same SourceFile and SyntaxContext as `body_span`
234-
body_span: Span,
232+
hir_info: &ExtractedHirInfo,
235233
basic_coverage_blocks: &'a CoverageGraph,
236234
) -> Vec<CoverageSpan> {
237235
let sorted_spans = from_mir::mir_to_initial_sorted_coverage_spans(
238236
mir_body,
239-
fn_sig_span,
240-
body_span,
237+
hir_info,
241238
basic_coverage_blocks,
242239
);
243240

244241
let coverage_spans = Self {
245-
body_span,
242+
body_span: hir_info.body_span,
246243
basic_coverage_blocks,
247244
sorted_spans_iter: sorted_spans.into_iter(),
248245
some_curr: None,

compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs

+11-12
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,22 @@ use rustc_span::Span;
77

88
use crate::coverage::graph::{BasicCoverageBlock, BasicCoverageBlockData, CoverageGraph};
99
use crate::coverage::spans::CoverageSpan;
10+
use crate::coverage::ExtractedHirInfo;
1011

1112
pub(super) fn mir_to_initial_sorted_coverage_spans(
1213
mir_body: &mir::Body<'_>,
13-
fn_sig_span: Span,
14-
body_span: Span,
14+
hir_info: &ExtractedHirInfo,
1515
basic_coverage_blocks: &CoverageGraph,
1616
) -> Vec<CoverageSpan> {
17+
let &ExtractedHirInfo { is_async_fn, fn_sig_span, body_span, .. } = hir_info;
18+
if is_async_fn {
19+
// An async function desugars into a function that returns a future,
20+
// with the user code wrapped in a closure. Any spans in the desugared
21+
// outer function will be unhelpful, so just produce a single span
22+
// associating the function signature with its entry BCB.
23+
return vec![CoverageSpan::for_fn_sig(fn_sig_span)];
24+
}
25+
1726
let mut initial_spans = Vec::with_capacity(mir_body.basic_blocks.len() * 2);
1827
for (bcb, bcb_data) in basic_coverage_blocks.iter_enumerated() {
1928
initial_spans.extend(bcb_to_initial_coverage_spans(mir_body, body_span, bcb, bcb_data));
@@ -44,16 +53,6 @@ pub(super) fn mir_to_initial_sorted_coverage_spans(
4453
.then_with(|| Ord::cmp(&a.is_closure, &b.is_closure).reverse())
4554
});
4655

47-
// The desugaring of an async function includes a closure containing the
48-
// original function body, and a terminator that returns the `impl Future`.
49-
// That terminator will cause a confusing coverage count for the function's
50-
// closing brace, so discard everything after the body closure span.
51-
if let Some(body_closure_index) =
52-
initial_spans.iter().rposition(|covspan| covspan.is_closure && covspan.span == body_span)
53-
{
54-
initial_spans.truncate(body_closure_index + 1);
55-
}
56-
5756
initial_spans
5857
}
5958

tests/coverage/async_block.cov-map

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
Function name: async_block::main
2+
Raw bytes (38): 0x[01, 01, 02, 01, 05, 03, 05, 06, 01, 05, 01, 00, 0b, 05, 01, 09, 00, 0a, 03, 00, 0e, 00, 13, 05, 00, 14, 01, 16, 05, 07, 0a, 02, 06, 06, 03, 01, 00, 02]
3+
Number of files: 1
4+
- file 0 => global file 1
5+
Number of expressions: 2
6+
- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
7+
- expression 1 operands: lhs = Expression(0, Add), rhs = Counter(1)
8+
Number of file 0 mappings: 6
9+
- Code(Counter(0)) at (prev + 5, 1) to (start + 0, 11)
10+
- Code(Counter(1)) at (prev + 1, 9) to (start + 0, 10)
11+
- Code(Expression(0, Add)) at (prev + 0, 14) to (start + 0, 19)
12+
= (c0 + c1)
13+
- Code(Counter(1)) at (prev + 0, 20) to (start + 1, 22)
14+
- Code(Counter(1)) at (prev + 7, 10) to (start + 2, 6)
15+
- Code(Expression(1, Sub)) at (prev + 3, 1) to (start + 0, 2)
16+
= ((c0 + c1) - c1)
17+
18+
Function name: async_block::main::{closure#0}
19+
Raw bytes (28): 0x[01, 01, 02, 01, 05, 05, 02, 04, 01, 07, 1c, 01, 17, 05, 01, 18, 02, 0e, 02, 02, 14, 02, 0e, 07, 03, 09, 00, 0a]
20+
Number of files: 1
21+
- file 0 => global file 1
22+
Number of expressions: 2
23+
- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
24+
- expression 1 operands: lhs = Counter(1), rhs = Expression(0, Sub)
25+
Number of file 0 mappings: 4
26+
- Code(Counter(0)) at (prev + 7, 28) to (start + 1, 23)
27+
- Code(Counter(1)) at (prev + 1, 24) to (start + 2, 14)
28+
- Code(Expression(0, Sub)) at (prev + 2, 20) to (start + 2, 14)
29+
= (c0 - c1)
30+
- Code(Expression(1, Add)) at (prev + 3, 9) to (start + 0, 10)
31+
= (c1 + (c0 - c1))
32+

tests/coverage/async_block.coverage

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
LL| |#![feature(coverage_attribute)]
2+
LL| |#![feature(noop_waker)]
3+
LL| |// edition: 2021
4+
LL| |
5+
LL| 1|fn main() {
6+
LL| 17| for i in 0..16 {
7+
^16
8+
LL| 16| let future = async {
9+
LL| 16| if i >= 12 {
10+
LL| 4| println!("big");
11+
LL| 12| } else {
12+
LL| 12| println!("small");
13+
LL| 12| }
14+
LL| 16| };
15+
LL| 16| executor::block_on(future);
16+
LL| 16| }
17+
LL| 1|}
18+
LL| |
19+
LL| |mod executor {
20+
LL| | use core::future::Future;
21+
LL| | use core::pin::pin;
22+
LL| | use core::task::{Context, Poll, Waker};
23+
LL| |
24+
LL| | #[coverage(off)]
25+
LL| | pub fn block_on<F: Future>(mut future: F) -> F::Output {
26+
LL| | let mut future = pin!(future);
27+
LL| | let waker = Waker::noop();
28+
LL| | let mut context = Context::from_waker(&waker);
29+
LL| |
30+
LL| | loop {
31+
LL| | if let Poll::Ready(val) = future.as_mut().poll(&mut context) {
32+
LL| | break val;
33+
LL| | }
34+
LL| | }
35+
LL| | }
36+
LL| |}
37+

tests/coverage/async_block.rs

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#![feature(coverage_attribute)]
2+
#![feature(noop_waker)]
3+
// edition: 2021
4+
5+
fn main() {
6+
for i in 0..16 {
7+
let future = async {
8+
if i >= 12 {
9+
println!("big");
10+
} else {
11+
println!("small");
12+
}
13+
};
14+
executor::block_on(future);
15+
}
16+
}
17+
18+
mod executor {
19+
use core::future::Future;
20+
use core::pin::pin;
21+
use core::task::{Context, Poll, Waker};
22+
23+
#[coverage(off)]
24+
pub fn block_on<F: Future>(mut future: F) -> F::Output {
25+
let mut future = pin!(future);
26+
let waker = Waker::noop();
27+
let mut context = Context::from_waker(&waker);
28+
29+
loop {
30+
if let Poll::Ready(val) = future.as_mut().poll(&mut context) {
31+
break val;
32+
}
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)