Skip to content

Commit 2529c49

Browse files
committed
coverage: Collect HIR info during MIR building
The coverage instrumentor pass operates on MIR, but it needs access to some source-related information that is normally not present in MIR. Historically, that information was obtained by looking back at HIR during MIR transformation. This patch arranges for the necessary information to be collected ahead of time during MIR building instead.
1 parent d5b3238 commit 2529c49

File tree

7 files changed

+148
-108
lines changed

7 files changed

+148
-108
lines changed

compiler/rustc_const_eval/src/transform/promote_consts.rs

+1
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,7 @@ pub fn promote_candidates<'tcx>(
971971
body.span,
972972
body.coroutine_kind(),
973973
body.tainted_by_errors,
974+
None,
974975
);
975976
promoted.phase = MirPhase::Analysis(AnalysisPhase::Initial);
976977

compiler/rustc_middle/src/mir/mod.rs

+10
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,13 @@ pub struct Body<'tcx> {
346346

347347
pub tainted_by_errors: Option<ErrorGuaranteed>,
348348

349+
/// Extra information about this function's source code captured during MIR
350+
/// building, for use by coverage instrumentation.
351+
///
352+
/// If `-Cinstrument-coverage` is not active, or if an individual function
353+
/// is not eligible for coverage, then this should always be `None`.
354+
pub coverage_hir_info: Option<Box<coverage::HirInfo>>,
355+
349356
/// Per-function coverage information added by the `InstrumentCoverage`
350357
/// pass, to be used in conjunction with the coverage statements injected
351358
/// into this body's blocks.
@@ -367,6 +374,7 @@ impl<'tcx> Body<'tcx> {
367374
span: Span,
368375
coroutine_kind: Option<CoroutineKind>,
369376
tainted_by_errors: Option<ErrorGuaranteed>,
377+
coverage_hir_info: Option<Box<coverage::HirInfo>>,
370378
) -> Self {
371379
// We need `arg_count` locals, and one for the return place.
372380
assert!(
@@ -400,6 +408,7 @@ impl<'tcx> Body<'tcx> {
400408
is_polymorphic: false,
401409
injection_phase: None,
402410
tainted_by_errors,
411+
coverage_hir_info,
403412
function_coverage_info: None,
404413
};
405414
body.is_polymorphic = body.has_non_region_param();
@@ -429,6 +438,7 @@ impl<'tcx> Body<'tcx> {
429438
is_polymorphic: false,
430439
injection_phase: None,
431440
tainted_by_errors: None,
441+
coverage_hir_info: None,
432442
function_coverage_info: None,
433443
};
434444
body.is_polymorphic = body.has_non_region_param();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use rustc_middle::hir;
2+
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
3+
use rustc_middle::mir;
4+
use rustc_middle::ty::TyCtxt;
5+
use rustc_span::def_id::LocalDefId;
6+
use rustc_span::{ExpnKind, Span};
7+
8+
/// If the given item is eligible for coverage instrumentation, collect relevant
9+
/// HIR information that will be needed by the instrumentor pass.
10+
pub(crate) fn make_coverage_hir_info_if_eligible(
11+
tcx: TyCtxt<'_>,
12+
def_id: LocalDefId,
13+
) -> Option<Box<mir::coverage::HirInfo>> {
14+
assert!(tcx.sess.instrument_coverage());
15+
16+
is_eligible_for_coverage(tcx, def_id).then(|| Box::new(make_coverage_hir_info(tcx, def_id)))
17+
}
18+
19+
fn is_eligible_for_coverage(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
20+
let is_fn_like = tcx.hir().get_by_def_id(def_id).fn_kind().is_some();
21+
22+
// Only instrument functions, methods, and closures (not constants since they are evaluated
23+
// at compile time by Miri).
24+
// FIXME(#73156): Handle source code coverage in const eval, but note, if and when const
25+
// expressions get coverage spans, we will probably have to "carve out" space for const
26+
// expressions from coverage spans in enclosing MIR's, like we do for closures. (That might
27+
// be tricky if const expressions have no corresponding statements in the enclosing MIR.
28+
// Closures are carved out by their initial `Assign` statement.)
29+
if !is_fn_like {
30+
return false;
31+
}
32+
33+
let codegen_fn_attrs = tcx.codegen_fn_attrs(def_id);
34+
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NO_COVERAGE) {
35+
return false;
36+
}
37+
38+
true
39+
}
40+
41+
fn make_coverage_hir_info(tcx: TyCtxt<'_>, def_id: LocalDefId) -> mir::coverage::HirInfo {
42+
let (maybe_fn_sig, hir_body) = fn_sig_and_body(tcx, def_id);
43+
44+
let function_source_hash = hash_mir_source(tcx, hir_body);
45+
let body_span = get_body_span(tcx, hir_body, def_id);
46+
47+
let spans_are_compatible = {
48+
let source_map = tcx.sess.source_map();
49+
|a: Span, b: Span| {
50+
a.eq_ctxt(b)
51+
&& source_map.lookup_source_file_idx(a.lo())
52+
== source_map.lookup_source_file_idx(b.lo())
53+
}
54+
};
55+
56+
let fn_sig_span = if let Some(fn_sig) = maybe_fn_sig
57+
&& spans_are_compatible(fn_sig.span, body_span)
58+
&& fn_sig.span.lo() <= body_span.lo()
59+
{
60+
fn_sig.span.with_hi(body_span.lo())
61+
} else {
62+
body_span.shrink_to_lo()
63+
};
64+
65+
mir::coverage::HirInfo { function_source_hash, fn_sig_span, body_span }
66+
}
67+
68+
fn fn_sig_and_body(
69+
tcx: TyCtxt<'_>,
70+
def_id: LocalDefId,
71+
) -> (Option<&rustc_hir::FnSig<'_>>, &rustc_hir::Body<'_>) {
72+
// FIXME(#79625): Consider improving MIR to provide the information needed, to avoid going back
73+
// to HIR for it.
74+
let hir_node = tcx.hir().get_by_def_id(def_id);
75+
let (_, fn_body_id) =
76+
hir::map::associated_body(hir_node).expect("HIR node is a function with body");
77+
(hir_node.fn_sig(), tcx.hir().body(fn_body_id))
78+
}
79+
80+
fn get_body_span<'tcx>(
81+
tcx: TyCtxt<'tcx>,
82+
hir_body: &rustc_hir::Body<'tcx>,
83+
def_id: LocalDefId,
84+
) -> Span {
85+
let mut body_span = hir_body.value.span;
86+
87+
if tcx.is_closure(def_id.to_def_id()) {
88+
// If the MIR function is a closure, and if the closure body span
89+
// starts from a macro, but it's content is not in that macro, try
90+
// to find a non-macro callsite, and instrument the spans there
91+
// instead.
92+
loop {
93+
let expn_data = body_span.ctxt().outer_expn_data();
94+
if expn_data.is_root() {
95+
break;
96+
}
97+
if let ExpnKind::Macro { .. } = expn_data.kind {
98+
body_span = expn_data.call_site;
99+
} else {
100+
break;
101+
}
102+
}
103+
}
104+
105+
body_span
106+
}
107+
108+
fn hash_mir_source<'tcx>(tcx: TyCtxt<'tcx>, hir_body: &'tcx rustc_hir::Body<'tcx>) -> u64 {
109+
// FIXME(cjgillot) Stop hashing HIR manually here.
110+
let owner = hir_body.id().hir_id.owner;
111+
tcx.hir_owner_nodes(owner)
112+
.unwrap()
113+
.opt_hash_including_bodies
114+
.unwrap()
115+
.to_smaller_hash()
116+
.as_u64()
117+
}

compiler/rustc_mir_build/src/build/custom/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ pub(super) fn build_custom_mir<'tcx>(
6060
tainted_by_errors: None,
6161
injection_phase: None,
6262
pass_count: 0,
63+
coverage_hir_info: None,
6364
function_coverage_info: None,
6465
};
6566

compiler/rustc_mir_build/src/build/mod.rs

+9
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,7 @@ fn construct_error(tcx: TyCtxt<'_>, def_id: LocalDefId, guar: ErrorGuaranteed) -
693693
span,
694694
coroutine_kind,
695695
Some(guar),
696+
None,
696697
);
697698

698699
body.coroutine.as_mut().map(|gen| gen.yield_ty = yield_ty);
@@ -775,6 +776,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
775776
}
776777
}
777778

779+
let coverage_hir_info = if self.tcx.sess.instrument_coverage() {
780+
coverageinfo::make_coverage_hir_info_if_eligible(self.tcx, self.def_id)
781+
} else {
782+
None
783+
};
784+
778785
Body::new(
779786
MirSource::item(self.def_id.to_def_id()),
780787
self.cfg.basic_blocks,
@@ -786,6 +793,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
786793
self.fn_span,
787794
self.coroutine_kind,
788795
None,
796+
coverage_hir_info,
789797
)
790798
}
791799

@@ -1056,6 +1064,7 @@ pub(crate) fn parse_float_into_scalar(
10561064

10571065
mod block;
10581066
mod cfg;
1067+
mod coverageinfo;
10591068
mod custom;
10601069
mod expr;
10611070
mod matches;

compiler/rustc_mir_transform/src/coverage/mod.rs

+9-108
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,14 @@ use self::spans::CoverageSpans;
1414
use crate::MirPass;
1515

1616
use rustc_data_structures::sync::Lrc;
17-
use rustc_middle::hir;
18-
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
1917
use rustc_middle::mir::coverage::*;
2018
use rustc_middle::mir::{
2119
self, BasicBlock, BasicBlockData, Coverage, SourceInfo, Statement, StatementKind, Terminator,
2220
TerminatorKind,
2321
};
2422
use rustc_middle::ty::TyCtxt;
25-
use rustc_span::def_id::LocalDefId;
2623
use rustc_span::source_map::SourceMap;
27-
use rustc_span::{ExpnKind, SourceFile, Span, Symbol};
24+
use rustc_span::{SourceFile, Span, Symbol};
2825

2926
/// Inserts `StatementKind::Coverage` statements that either instrument the binary with injected
3027
/// counters, via intrinsic `llvm.instrprof.increment`, and/or inject metadata used during codegen
@@ -43,8 +40,10 @@ impl<'tcx> MirPass<'tcx> for InstrumentCoverage {
4340
// be transformed, so it should never see promoted MIR.
4441
assert!(mir_source.promoted.is_none());
4542

46-
let def_id = mir_source.def_id().expect_local();
47-
if !is_eligible_for_coverage(tcx, def_id) {
43+
let def_id = mir_source.def_id();
44+
if mir_body.coverage_hir_info.is_none() {
45+
// If we didn't capture HIR info during MIR building, this MIR
46+
// wasn't eligible for coverage instrumentation, so skip it.
4847
trace!("InstrumentCoverage skipped for {def_id:?} (not eligible)");
4948
return;
5049
}
@@ -79,8 +78,10 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
7978
let source_map = tcx.sess.source_map();
8079

8180
let def_id = mir_body.source.def_id().expect_local();
82-
let mir::coverage::HirInfo { function_source_hash, fn_sig_span, body_span, .. } =
83-
make_coverage_hir_info(tcx, def_id);
81+
let &mir::coverage::HirInfo { function_source_hash, fn_sig_span, body_span, .. } = mir_body
82+
.coverage_hir_info
83+
.as_deref()
84+
.expect("functions without HIR info have already been skipped");
8485

8586
let source_file = source_map.lookup_source_file(body_span.lo());
8687

@@ -290,103 +291,3 @@ fn make_code_region(
290291
end_col: end_col as u32,
291292
}
292293
}
293-
294-
fn is_eligible_for_coverage(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
295-
let is_fn_like = tcx.hir().get_by_def_id(def_id).fn_kind().is_some();
296-
297-
// Only instrument functions, methods, and closures (not constants since they are evaluated
298-
// at compile time by Miri).
299-
// FIXME(#73156): Handle source code coverage in const eval, but note, if and when const
300-
// expressions get coverage spans, we will probably have to "carve out" space for const
301-
// expressions from coverage spans in enclosing MIR's, like we do for closures. (That might
302-
// be tricky if const expressions have no corresponding statements in the enclosing MIR.
303-
// Closures are carved out by their initial `Assign` statement.)
304-
if !is_fn_like {
305-
return false;
306-
}
307-
308-
let codegen_fn_attrs = tcx.codegen_fn_attrs(def_id);
309-
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NO_COVERAGE) {
310-
return false;
311-
}
312-
313-
true
314-
}
315-
316-
fn make_coverage_hir_info(tcx: TyCtxt<'_>, def_id: LocalDefId) -> mir::coverage::HirInfo {
317-
let (maybe_fn_sig, hir_body) = fn_sig_and_body(tcx, def_id);
318-
319-
let function_source_hash = hash_mir_source(tcx, hir_body);
320-
let body_span = get_body_span(tcx, hir_body, def_id);
321-
322-
let spans_are_compatible = {
323-
let source_map = tcx.sess.source_map();
324-
|a: Span, b: Span| {
325-
a.eq_ctxt(b)
326-
&& source_map.lookup_source_file_idx(a.lo())
327-
== source_map.lookup_source_file_idx(b.lo())
328-
}
329-
};
330-
331-
let fn_sig_span = if let Some(fn_sig) = maybe_fn_sig
332-
&& spans_are_compatible(fn_sig.span, body_span)
333-
&& fn_sig.span.lo() <= body_span.lo()
334-
{
335-
fn_sig.span.with_hi(body_span.lo())
336-
} else {
337-
body_span.shrink_to_lo()
338-
};
339-
340-
mir::coverage::HirInfo { function_source_hash, fn_sig_span, body_span }
341-
}
342-
343-
fn fn_sig_and_body(
344-
tcx: TyCtxt<'_>,
345-
def_id: LocalDefId,
346-
) -> (Option<&rustc_hir::FnSig<'_>>, &rustc_hir::Body<'_>) {
347-
// FIXME(#79625): Consider improving MIR to provide the information needed, to avoid going back
348-
// to HIR for it.
349-
let hir_node = tcx.hir().get_by_def_id(def_id);
350-
let (_, fn_body_id) =
351-
hir::map::associated_body(hir_node).expect("HIR node is a function with body");
352-
(hir_node.fn_sig(), tcx.hir().body(fn_body_id))
353-
}
354-
355-
fn get_body_span<'tcx>(
356-
tcx: TyCtxt<'tcx>,
357-
hir_body: &rustc_hir::Body<'tcx>,
358-
def_id: LocalDefId,
359-
) -> Span {
360-
let mut body_span = hir_body.value.span;
361-
362-
if tcx.is_closure(def_id.to_def_id()) {
363-
// If the MIR function is a closure, and if the closure body span
364-
// starts from a macro, but it's content is not in that macro, try
365-
// to find a non-macro callsite, and instrument the spans there
366-
// instead.
367-
loop {
368-
let expn_data = body_span.ctxt().outer_expn_data();
369-
if expn_data.is_root() {
370-
break;
371-
}
372-
if let ExpnKind::Macro { .. } = expn_data.kind {
373-
body_span = expn_data.call_site;
374-
} else {
375-
break;
376-
}
377-
}
378-
}
379-
380-
body_span
381-
}
382-
383-
fn hash_mir_source<'tcx>(tcx: TyCtxt<'tcx>, hir_body: &'tcx rustc_hir::Body<'tcx>) -> u64 {
384-
// FIXME(cjgillot) Stop hashing HIR manually here.
385-
let owner = hir_body.id().hir_id.owner;
386-
tcx.hir_owner_nodes(owner)
387-
.unwrap()
388-
.opt_hash_including_bodies
389-
.unwrap()
390-
.to_smaller_hash()
391-
.as_u64()
392-
}

compiler/rustc_mir_transform/src/shim.rs

+1
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ fn new_body<'tcx>(
283283
None,
284284
// FIXME(compiler-errors): is this correct?
285285
None,
286+
None,
286287
)
287288
}
288289

0 commit comments

Comments
 (0)