Skip to content

Commit 1dc6c3c

Browse files
authored
Rollup merge of #73011 - richkadel:llvm-count-from-mir-pass, r=tmandry
first stage of implementing LLVM code coverage This PR replaces #70680 (WIP toward LLVM Code Coverage for Rust) since I am re-implementing the Rust LLVM code coverage feature in a different part of the compiler (in MIR pass(es) vs AST). This PR updates rustc with `-Zinstrument-coverage` option that injects the llvm intrinsic `instrprof.increment()` for code generation. This initial version only injects counters at the top of each function, and does not yet implement the required coverage map. Upcoming PRs will add the coverage map, and add more counters and/or counter expressions for each conditional code branch. Rust compiler MCP rust-lang/compiler-team#278 Relevant issue: #34701 - Implement support for LLVMs code coverage instrumentation ***[I put together some development notes here, under a separate branch.](https://github.com/richkadel/rust/blob/cfa0b21d34ee64e4ebee226101bd2ef0c6757865/src/test/codegen/coverage-experiments/README-THIS-IS-TEMPORARY.md)***
2 parents 7cc4518 + 36c9014 commit 1dc6c3c

File tree

25 files changed

+383
-4
lines changed

25 files changed

+383
-4
lines changed

config.toml.example

+2-1
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,8 @@
209209
# Build the sanitizer runtimes
210210
#sanitizers = false
211211

212-
# Build the profiler runtime
212+
# Build the profiler runtime (required when compiling with options that depend
213+
# on this runtime, such as `-C profile-generate` or `-Z instrument-coverage`).
213214
#profiler = false
214215

215216
# Indicates whether the native libraries linked into Cargo will be statically

src/libcore/intrinsics.rs

+7
Original file line numberDiff line numberDiff line change
@@ -1941,6 +1941,13 @@ extern "rust-intrinsic" {
19411941
///
19421942
/// Perma-unstable: do not use.
19431943
pub fn miri_start_panic(payload: *mut u8) -> !;
1944+
1945+
/// Internal placeholder for injecting code coverage counters when the "instrument-coverage"
1946+
/// option is enabled. The placeholder is replaced with `llvm.instrprof.increment` during code
1947+
/// generation.
1948+
#[cfg(not(bootstrap))]
1949+
#[lang = "count_code_region"]
1950+
pub fn count_code_region(index: u32);
19441951
}
19451952

19461953
// Some functions are defined here because they accidentally got made

src/librustc_codegen_llvm/builder.rs

+27
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,33 @@ impl BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
997997
self.call_lifetime_intrinsic("llvm.lifetime.end.p0i8", ptr, size);
998998
}
999999

1000+
fn instrprof_increment(
1001+
&mut self,
1002+
fn_name: &'ll Value,
1003+
hash: &'ll Value,
1004+
num_counters: &'ll Value,
1005+
index: &'ll Value,
1006+
) -> &'ll Value {
1007+
debug!(
1008+
"instrprof_increment() with args ({:?}, {:?}, {:?}, {:?})",
1009+
fn_name, hash, num_counters, index
1010+
);
1011+
1012+
let llfn = unsafe { llvm::LLVMRustGetInstrprofIncrementIntrinsic(self.cx().llmod) };
1013+
let args = &[fn_name, hash, num_counters, index];
1014+
let args = self.check_call("call", llfn, args);
1015+
1016+
unsafe {
1017+
llvm::LLVMRustBuildCall(
1018+
self.llbuilder,
1019+
llfn,
1020+
args.as_ptr() as *const &llvm::Value,
1021+
args.len() as c_uint,
1022+
None,
1023+
)
1024+
}
1025+
}
1026+
10001027
fn call(
10011028
&mut self,
10021029
llfn: &'ll Value,

src/librustc_codegen_llvm/context.rs

+2
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,8 @@ impl CodegenCx<'b, 'tcx> {
749749
ifn!("llvm.lifetime.start.p0i8", fn(t_i64, i8p) -> void);
750750
ifn!("llvm.lifetime.end.p0i8", fn(t_i64, i8p) -> void);
751751

752+
ifn!("llvm.instrprof.increment", fn(i8p, t_i64, t_i32, t_i32) -> void);
753+
752754
ifn!("llvm.expect.i1", fn(i1, i1) -> i1);
753755
ifn!("llvm.eh.typeid.for", fn(i8p) -> t_i32);
754756
ifn!("llvm.localescape", fn(...) -> void);

src/librustc_codegen_llvm/intrinsic.rs

+26
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use crate::type_of::LayoutLlvmExt;
77
use crate::va_arg::emit_va_arg;
88
use crate::value::Value;
99

10+
use log::debug;
11+
1012
use rustc_ast::ast;
1113
use rustc_codegen_ssa::base::{compare_simd_types, to_immediate, wants_msvc_seh};
1214
use rustc_codegen_ssa::common::span_invalid_monomorphization_error;
@@ -21,6 +23,7 @@ use rustc_middle::ty::layout::{FnAbiExt, HasTyCtxt};
2123
use rustc_middle::ty::{self, Ty};
2224
use rustc_middle::{bug, span_bug};
2325
use rustc_span::Span;
26+
use rustc_span::Symbol;
2427
use rustc_target::abi::{self, HasDataLayout, LayoutOf, Primitive};
2528
use rustc_target::spec::PanicStrategy;
2629

@@ -86,6 +89,7 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> {
8689
args: &[OperandRef<'tcx, &'ll Value>],
8790
llresult: &'ll Value,
8891
span: Span,
92+
caller_instance: ty::Instance<'tcx>,
8993
) {
9094
let tcx = self.tcx;
9195
let callee_ty = instance.monomorphic_ty(tcx);
@@ -136,6 +140,28 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> {
136140
let llfn = self.get_intrinsic(&("llvm.debugtrap"));
137141
self.call(llfn, &[], None)
138142
}
143+
"count_code_region" => {
144+
if let ty::InstanceDef::Item(fn_def_id) = caller_instance.def {
145+
let caller_fn_path = tcx.def_path_str(fn_def_id);
146+
debug!(
147+
"count_code_region to llvm.instrprof.increment(fn_name={})",
148+
caller_fn_path
149+
);
150+
151+
// FIXME(richkadel): (1) Replace raw function name with mangled function name;
152+
// (2) Replace hardcoded `1234` in `hash` with a computed hash (as discussed in)
153+
// the MCP (compiler-team/issues/278); and replace the hardcoded `1` for
154+
// `num_counters` with the actual number of counters per function (when the
155+
// changes are made to inject more than one counter per function).
156+
let (fn_name, _len_val) = self.const_str(Symbol::intern(&caller_fn_path));
157+
let index = args[0].immediate();
158+
let hash = self.const_u64(1234);
159+
let num_counters = self.const_u32(1);
160+
self.instrprof_increment(fn_name, hash, num_counters, index)
161+
} else {
162+
bug!("intrinsic count_code_region: no src.instance");
163+
}
164+
}
139165
"va_start" => self.va_start(args[0].immediate()),
140166
"va_end" => self.va_end(args[0].immediate()),
141167
"va_copy" => {

src/librustc_codegen_llvm/llvm/ffi.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1360,6 +1360,7 @@ extern "C" {
13601360

13611361
// Miscellaneous instructions
13621362
pub fn LLVMBuildPhi(B: &Builder<'a>, Ty: &'a Type, Name: *const c_char) -> &'a Value;
1363+
pub fn LLVMRustGetInstrprofIncrementIntrinsic(M: &Module) -> &'a Value;
13631364
pub fn LLVMRustBuildCall(
13641365
B: &Builder<'a>,
13651366
Fn: &'a Value,

src/librustc_codegen_ssa/back/write.rs

+6
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,12 @@ impl ModuleConfig {
175175
if sess.opts.debugging_opts.profile && !is_compiler_builtins {
176176
passes.push("insert-gcov-profiling".to_owned());
177177
}
178+
179+
// The rustc option `-Zinstrument_coverage` injects intrinsic calls to
180+
// `llvm.instrprof.increment()`, which requires the LLVM `instrprof` pass.
181+
if sess.opts.debugging_opts.instrument_coverage {
182+
passes.push("instrprof".to_owned());
183+
}
178184
passes
179185
},
180186
vec![]

src/librustc_codegen_ssa/mir/block.rs

+1
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
693693
&args,
694694
dest,
695695
terminator.source_info.span,
696+
self.instance,
696697
);
697698

698699
if let ReturnDest::IndirectOperand(dst, _) = ret_dest {

src/librustc_codegen_ssa/traits/builder.rs

+8
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,14 @@ pub trait BuilderMethods<'a, 'tcx>:
260260
/// Called for `StorageDead`
261261
fn lifetime_end(&mut self, ptr: Self::Value, size: Size);
262262

263+
fn instrprof_increment(
264+
&mut self,
265+
fn_name: Self::Value,
266+
hash: Self::Value,
267+
num_counters: Self::Value,
268+
index: Self::Value,
269+
) -> Self::Value;
270+
263271
fn call(
264272
&mut self,
265273
llfn: Self::Value,

src/librustc_codegen_ssa/traits/intrinsic.rs

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub trait IntrinsicCallMethods<'tcx>: BackendTypes {
1515
args: &[OperandRef<'tcx, Self::Value>],
1616
llresult: Self::Value,
1717
span: Span,
18+
caller_instance: ty::Instance<'tcx>,
1819
);
1920

2021
fn abort(&mut self);

src/librustc_hir/lang_items.rs

+2
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,8 @@ language_item_table! {
242242

243243
StartFnLangItem, "start", start_fn, Target::Fn;
244244

245+
CountCodeRegionFnLangItem, "count_code_region", count_code_region_fn, Target::Fn;
246+
245247
EhPersonalityLangItem, "eh_personality", eh_personality, Target::Fn;
246248
EhCatchTypeinfoLangItem, "eh_catch_typeinfo", eh_catch_typeinfo, Target::Static;
247249

src/librustc_interface/tests.rs

+1
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,7 @@ fn test_debugging_options_tracking_hash() {
548548
tracked!(human_readable_cgu_names, true);
549549
tracked!(inline_in_all_cgus, Some(true));
550550
tracked!(insert_sideeffect, true);
551+
tracked!(instrument_coverage, true);
551552
tracked!(instrument_mcount, true);
552553
tracked!(link_only, true);
553554
tracked!(merge_functions, Some(MergeFunctions::Disabled));

src/librustc_metadata/creader.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -706,7 +706,9 @@ impl<'a> CrateLoader<'a> {
706706
}
707707

708708
fn inject_profiler_runtime(&mut self) {
709-
if (self.sess.opts.debugging_opts.profile || self.sess.opts.cg.profile_generate.enabled())
709+
if (self.sess.opts.debugging_opts.instrument_coverage
710+
|| self.sess.opts.debugging_opts.profile
711+
|| self.sess.opts.cg.profile_generate.enabled())
710712
&& !self.sess.opts.debugging_opts.no_profiler_runtime
711713
{
712714
info!("loading profiler");

src/librustc_middle/mir/mod.rs

+28
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use rustc_macros::HashStable;
2929
use rustc_serialize::{Decodable, Encodable};
3030
use rustc_span::symbol::Symbol;
3131
use rustc_span::{Span, DUMMY_SP};
32+
use rustc_target::abi;
3233
use rustc_target::asm::InlineAsmRegOrRegClass;
3334
use std::borrow::Cow;
3435
use std::fmt::{self, Debug, Display, Formatter, Write};
@@ -2218,6 +2219,33 @@ impl<'tcx> Operand<'tcx> {
22182219
})
22192220
}
22202221

2222+
/// Convenience helper to make a literal-like constant from a given scalar value.
2223+
/// Since this is used to synthesize MIR, assumes `user_ty` is None.
2224+
pub fn const_from_scalar(
2225+
tcx: TyCtxt<'tcx>,
2226+
ty: Ty<'tcx>,
2227+
val: Scalar,
2228+
span: Span,
2229+
) -> Operand<'tcx> {
2230+
debug_assert!({
2231+
let param_env_and_ty = ty::ParamEnv::empty().and(ty);
2232+
let type_size = tcx
2233+
.layout_of(param_env_and_ty)
2234+
.unwrap_or_else(|e| panic!("could not compute layout for {:?}: {:?}", ty, e))
2235+
.size;
2236+
let scalar_size = abi::Size::from_bytes(match val {
2237+
Scalar::Raw { size, .. } => size,
2238+
_ => panic!("Invalid scalar type {:?}", val),
2239+
});
2240+
scalar_size == type_size
2241+
});
2242+
Operand::Constant(box Constant {
2243+
span,
2244+
user_ty: None,
2245+
literal: ty::Const::from_scalar(tcx, val, ty),
2246+
})
2247+
}
2248+
22212249
pub fn to_copy(&self) -> Self {
22222250
match *self {
22232251
Operand::Copy(_) | Operand::Constant(_) => self.clone(),

src/librustc_mir/interpret/intrinsics.rs

+2
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
389389
);
390390
self.copy_op(self.operand_index(args[0], index)?, dest)?;
391391
}
392+
// FIXME(#73156): Handle source code coverage in const eval
393+
sym::count_code_region => (),
392394
_ => return Ok(false),
393395
}
394396

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use crate::transform::{MirPass, MirSource};
2+
use crate::util::patch::MirPatch;
3+
use rustc_hir::lang_items;
4+
use rustc_middle::mir::interpret::Scalar;
5+
use rustc_middle::mir::*;
6+
use rustc_middle::ty;
7+
use rustc_middle::ty::TyCtxt;
8+
use rustc_span::def_id::DefId;
9+
use rustc_span::Span;
10+
11+
/// Inserts call to count_code_region() as a placeholder to be replaced during code generation with
12+
/// the intrinsic llvm.instrprof.increment.
13+
pub struct InstrumentCoverage;
14+
15+
impl<'tcx> MirPass<'tcx> for InstrumentCoverage {
16+
fn run_pass(&self, tcx: TyCtxt<'tcx>, src: MirSource<'tcx>, body: &mut Body<'tcx>) {
17+
if tcx.sess.opts.debugging_opts.instrument_coverage {
18+
debug!("instrumenting {:?}", src.def_id());
19+
instrument_coverage(tcx, body);
20+
}
21+
}
22+
}
23+
24+
// The first counter (start of the function) is index zero.
25+
const INIT_FUNCTION_COUNTER: u32 = 0;
26+
27+
/// Injects calls to placeholder function `count_code_region()`.
28+
// FIXME(richkadel): As a first step, counters are only injected at the top of each function.
29+
// The complete solution will inject counters at each conditional code branch.
30+
pub fn instrument_coverage<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
31+
let span = body.span.shrink_to_lo();
32+
33+
let count_code_region_fn = function_handle(
34+
tcx,
35+
tcx.require_lang_item(lang_items::CountCodeRegionFnLangItem, None),
36+
span,
37+
);
38+
let counter_index = Operand::const_from_scalar(
39+
tcx,
40+
tcx.types.u32,
41+
Scalar::from_u32(INIT_FUNCTION_COUNTER),
42+
span,
43+
);
44+
45+
let mut patch = MirPatch::new(body);
46+
47+
let new_block = patch.new_block(placeholder_block(SourceInfo::outermost(body.span)));
48+
let next_block = START_BLOCK;
49+
50+
let temp = patch.new_temp(tcx.mk_unit(), body.span);
51+
patch.patch_terminator(
52+
new_block,
53+
TerminatorKind::Call {
54+
func: count_code_region_fn,
55+
args: vec![counter_index],
56+
// new_block will swapped with the next_block, after applying patch
57+
destination: Some((Place::from(temp), new_block)),
58+
cleanup: None,
59+
from_hir_call: false,
60+
fn_span: span,
61+
},
62+
);
63+
64+
patch.add_statement(new_block.start_location(), StatementKind::StorageLive(temp));
65+
patch.add_statement(next_block.start_location(), StatementKind::StorageDead(temp));
66+
67+
patch.apply(body);
68+
69+
// To insert the `new_block` in front of the first block in the counted branch (for example,
70+
// the START_BLOCK, at the top of the function), just swap the indexes, leaving the rest of the
71+
// graph unchanged.
72+
body.basic_blocks_mut().swap(next_block, new_block);
73+
}
74+
75+
fn function_handle<'tcx>(tcx: TyCtxt<'tcx>, fn_def_id: DefId, span: Span) -> Operand<'tcx> {
76+
let ret_ty = tcx.fn_sig(fn_def_id).output();
77+
let ret_ty = ret_ty.no_bound_vars().unwrap();
78+
let substs = tcx.mk_substs(::std::iter::once(ty::subst::GenericArg::from(ret_ty)));
79+
Operand::function_handle(tcx, fn_def_id, substs, span)
80+
}
81+
82+
fn placeholder_block<'tcx>(source_info: SourceInfo) -> BasicBlockData<'tcx> {
83+
BasicBlockData {
84+
statements: vec![],
85+
terminator: Some(Terminator {
86+
source_info,
87+
// this gets overwritten by the counter Call
88+
kind: TerminatorKind::Unreachable,
89+
}),
90+
is_cleanup: false,
91+
}
92+
}

src/librustc_mir/transform/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub mod elaborate_drops;
2828
pub mod generator;
2929
pub mod inline;
3030
pub mod instcombine;
31+
pub mod instrument_coverage;
3132
pub mod no_landing_pads;
3233
pub mod nrvo;
3334
pub mod promote_consts;
@@ -288,6 +289,10 @@ fn mir_validated(
288289
// What we need to run borrowck etc.
289290
&promote_pass,
290291
&simplify::SimplifyCfg::new("qualify-consts"),
292+
// If the `instrument-coverage` option is enabled, analyze the CFG, identify each
293+
// conditional branch, construct a coverage map to be passed to LLVM, and inject counters
294+
// where needed.
295+
&instrument_coverage::InstrumentCoverage,
291296
]],
292297
);
293298

0 commit comments

Comments
 (0)