Skip to content

Commit c833057

Browse files
committed
Collect statistics about MIR optimizations
1 parent d65c08e commit c833057

21 files changed

+347
-6
lines changed

compiler/rustc_data_structures/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ pub mod fingerprint;
9292
pub mod profiling;
9393
pub mod sharded;
9494
pub mod stack;
95+
pub mod statistics;
9596
pub mod sync;
9697
pub mod thin_vec;
9798
pub mod tiny_list;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
//! # Support for collecting simple statistics
2+
//!
3+
//! Statistics are useful for collecting metrics from optimization passes, like
4+
//! the number of simplifications performed. To avoid introducing overhead, the
5+
//! collection of statistics is enabled only when rustc is compiled with
6+
//! debug-assertions.
7+
//!
8+
//! Statistics are static variables defined in the module they are used, and
9+
//! lazy registered in the global collector on the first use. Once registered,
10+
//! the collector will obtain their values at the end of compilation process
11+
//! when requested with -Zmir-opt-stats option.
12+
13+
use parking_lot::{const_mutex, Mutex};
14+
use std::io::{self, stdout, Write as _};
15+
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
16+
17+
static COLLECTOR: Collector = Collector::new();
18+
19+
/// Enables the collection of statistics.
20+
/// To be effective it has to be called before the first use of a statistics.
21+
pub fn try_enable() -> Result<(), ()> {
22+
COLLECTOR.try_enable()
23+
}
24+
25+
/// Prints all statistics collected so far.
26+
pub fn print() {
27+
COLLECTOR.print();
28+
}
29+
30+
pub struct Statistic {
31+
category: &'static str,
32+
name: &'static str,
33+
initialized: AtomicBool,
34+
value: AtomicUsize,
35+
}
36+
37+
struct Collector(Mutex<State>);
38+
39+
struct State {
40+
enabled: bool,
41+
stats: Vec<&'static Statistic>,
42+
}
43+
44+
#[derive(Eq, PartialEq, Ord, PartialOrd)]
45+
struct Snapshot {
46+
category: &'static str,
47+
name: &'static str,
48+
value: usize,
49+
}
50+
51+
impl Statistic {
52+
pub const fn new(category: &'static str, name: &'static str) -> Self {
53+
Statistic {
54+
category,
55+
name,
56+
initialized: AtomicBool::new(false),
57+
value: AtomicUsize::new(0),
58+
}
59+
}
60+
61+
pub fn name(&self) -> &'static str {
62+
self.name
63+
}
64+
65+
pub fn category(&self) -> &'static str {
66+
self.category.rsplit("::").next().unwrap()
67+
}
68+
69+
#[inline]
70+
pub fn register(&'static self) {
71+
if cfg!(debug_assertions) {
72+
if !self.initialized.load(Ordering::Acquire) {
73+
COLLECTOR.register(self);
74+
}
75+
}
76+
}
77+
78+
#[inline]
79+
pub fn increment(&'static self, value: usize) {
80+
if cfg!(debug_assertions) {
81+
self.value.fetch_add(value, Ordering::Relaxed);
82+
self.register();
83+
}
84+
}
85+
86+
#[inline]
87+
pub fn update_max(&'static self, value: usize) {
88+
if cfg!(debug_assertions) {
89+
self.value.fetch_max(value, Ordering::Relaxed);
90+
self.register();
91+
}
92+
}
93+
94+
fn snapshot(&'static self) -> Snapshot {
95+
Snapshot {
96+
name: self.name(),
97+
category: self.category(),
98+
value: self.value.load(Ordering::Relaxed),
99+
}
100+
}
101+
}
102+
103+
impl Collector {
104+
const fn new() -> Self {
105+
Collector(const_mutex(State { enabled: false, stats: Vec::new() }))
106+
}
107+
108+
fn try_enable(&self) -> Result<(), ()> {
109+
if cfg!(debug_assertions) {
110+
self.0.lock().enabled = true;
111+
Ok(())
112+
} else {
113+
Err(())
114+
}
115+
}
116+
117+
fn snapshot(&self) -> Vec<Snapshot> {
118+
self.0.lock().stats.iter().copied().map(Statistic::snapshot).collect()
119+
}
120+
121+
fn register(&self, s: &'static Statistic) {
122+
let mut state = self.0.lock();
123+
if !s.initialized.load(Ordering::Relaxed) {
124+
if state.enabled {
125+
state.stats.push(s);
126+
}
127+
s.initialized.store(true, Ordering::Release);
128+
}
129+
}
130+
131+
fn print(&self) {
132+
let mut stats = self.snapshot();
133+
stats.sort();
134+
match self.write(&stats) {
135+
Ok(_) => {}
136+
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => {}
137+
Err(e) => panic!(e),
138+
}
139+
}
140+
141+
fn write(&self, stats: &[Snapshot]) -> io::Result<()> {
142+
let mut cat_width = 0;
143+
let mut val_width = 0;
144+
145+
for s in stats {
146+
cat_width = cat_width.max(s.category.len());
147+
val_width = val_width.max(s.value.to_string().len());
148+
}
149+
150+
let mut out = Vec::new();
151+
for s in stats {
152+
write!(
153+
&mut out,
154+
"{val:val_width$} {cat:cat_width$} {name}\n",
155+
val = s.value,
156+
val_width = val_width,
157+
cat = s.category,
158+
cat_width = cat_width,
159+
name = s.name,
160+
)?;
161+
}
162+
163+
stdout().write_all(&out)
164+
}
165+
}

compiler/rustc_driver/src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ fn run_compiler(
312312

313313
interface::run_compiler(config, |compiler| {
314314
let sess = compiler.session();
315+
315316
let should_stop = RustcDefaultCalls::print_crate_info(
316317
&***compiler.codegen_backend(),
317318
sess,
@@ -453,6 +454,10 @@ fn run_compiler(
453454
linker.link()?
454455
}
455456

457+
if sess.opts.debugging_opts.mir_opt_stats {
458+
rustc_data_structures::statistics::print();
459+
}
460+
456461
if sess.opts.debugging_opts.perf_stats {
457462
sess.print_perf_stats();
458463
}

compiler/rustc_mir/src/transform/const_prop.rs

+9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::cell::Cell;
55

66
use rustc_ast::Mutability;
77
use rustc_data_structures::fx::FxHashSet;
8+
use rustc_data_structures::statistics::Statistic;
89
use rustc_hir::def::DefKind;
910
use rustc_hir::HirId;
1011
use rustc_index::bit_set::BitSet;
@@ -59,6 +60,10 @@ macro_rules! throw_machine_stop_str {
5960

6061
pub struct ConstProp;
6162

63+
static NUM_PROPAGATED: Statistic = Statistic::new(module_path!(), "Number of const propagations");
64+
static NUM_VALIDATION_ERRORS: Statistic =
65+
Statistic::new(module_path!(), "Number of validation errors during const propagation");
66+
6267
impl<'tcx> MirPass<'tcx> for ConstProp {
6368
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
6469
// will be evaluated by miri and produce its errors there
@@ -622,6 +627,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
622627
ScalarMaybeUninit::Scalar(scalar),
623628
)) = *value
624629
{
630+
NUM_PROPAGATED.increment(1);
625631
*operand = self.operand_from_scalar(
626632
scalar,
627633
value.layout.ty,
@@ -809,6 +815,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
809815
/*may_ref_to_static*/ true,
810816
) {
811817
trace!("validation error, attempt failed: {:?}", e);
818+
NUM_VALIDATION_ERRORS.increment(1);
812819
return;
813820
}
814821

@@ -818,6 +825,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
818825
if let Some(Ok(imm)) = imm {
819826
match *imm {
820827
interpret::Immediate::Scalar(ScalarMaybeUninit::Scalar(scalar)) => {
828+
NUM_PROPAGATED.increment(1);
821829
*rval = Rvalue::Use(self.operand_from_scalar(
822830
scalar,
823831
value.layout.ty,
@@ -859,6 +867,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
859867
if let Some(Some(alloc)) = alloc {
860868
// Assign entire constant in a single statement.
861869
// We can't use aggregates, as we run after the aggregate-lowering `MirPhase`.
870+
NUM_PROPAGATED.increment(1);
862871
*rval = Rvalue::Use(Operand::Constant(Box::new(Constant {
863872
span: source_info.span,
864873
user_ty: None,

compiler/rustc_mir/src/transform/copy_prop.rs

+22-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
2222
use crate::transform::MirPass;
2323
use crate::util::def_use::DefUseAnalysis;
24+
use rustc_data_structures::statistics::Statistic;
2425
use rustc_middle::mir::visit::MutVisitor;
2526
use rustc_middle::mir::{
2627
Body, Constant, Local, LocalKind, Location, Operand, Place, Rvalue, StatementKind,
@@ -29,6 +30,13 @@ use rustc_middle::ty::TyCtxt;
2930

3031
pub struct CopyPropagation;
3132

33+
static NUM_PROPAGATED_LOCAL: Statistic =
34+
Statistic::new(module_path!(), "Number of locals copy propagated");
35+
static NUM_PROPAGATED_CONST: Statistic =
36+
Statistic::new(module_path!(), "Number of constants copy propagated");
37+
static MAX_PROPAGATED: Statistic =
38+
Statistic::new(module_path!(), "Maximum number of copy propagations in a function");
39+
3240
impl<'tcx> MirPass<'tcx> for CopyPropagation {
3341
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
3442
let opts = &tcx.sess.opts.debugging_opts;
@@ -39,6 +47,8 @@ impl<'tcx> MirPass<'tcx> for CopyPropagation {
3947
return;
4048
}
4149

50+
let mut propagated_local = 0;
51+
let mut propagated_const = 0;
4252
let mut def_use_analysis = DefUseAnalysis::new(body);
4353
loop {
4454
def_use_analysis.analyze(body);
@@ -139,8 +149,13 @@ impl<'tcx> MirPass<'tcx> for CopyPropagation {
139149
}
140150
}
141151

142-
changed =
143-
action.perform(body, &def_use_analysis, dest_local, location, tcx) || changed;
152+
if action.perform(body, &def_use_analysis, dest_local, location, tcx) {
153+
match action {
154+
Action::PropagateLocalCopy(_) => propagated_local += 1,
155+
Action::PropagateConstant(_) => propagated_const += 1,
156+
}
157+
changed = true;
158+
}
144159
// FIXME(pcwalton): Update the use-def chains to delete the instructions instead of
145160
// regenerating the chains.
146161
break;
@@ -149,6 +164,10 @@ impl<'tcx> MirPass<'tcx> for CopyPropagation {
149164
break;
150165
}
151166
}
167+
168+
NUM_PROPAGATED_LOCAL.increment(propagated_const);
169+
NUM_PROPAGATED_CONST.increment(propagated_local);
170+
MAX_PROPAGATED.update_max(propagated_local + propagated_const);
152171
}
153172
}
154173

@@ -193,6 +212,7 @@ fn eliminate_self_assignments(body: &mut Body<'_>, def_use_analysis: &DefUseAnal
193212
changed
194213
}
195214

215+
#[derive(Copy, Clone)]
196216
enum Action<'tcx> {
197217
PropagateLocalCopy(Local),
198218
PropagateConstant(Constant<'tcx>),

compiler/rustc_mir/src/transform/dest_prop.rs

+11
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ use crate::{
103103
util::{dump_mir, PassWhere},
104104
};
105105
use itertools::Itertools;
106+
use rustc_data_structures::statistics::Statistic;
106107
use rustc_data_structures::unify::{InPlaceUnificationTable, UnifyKey};
107108
use rustc_index::{
108109
bit_set::{BitMatrix, BitSet},
@@ -125,6 +126,13 @@ const MAX_BLOCKS: usize = 250;
125126

126127
pub struct DestinationPropagation;
127128

129+
static NUM_TOO_MANY_LOCALS: Statistic =
130+
Statistic::new(module_path!(), "Number of functions with too many locals to optimize");
131+
static NUM_TOO_MANY_BLOCKS: Statistic =
132+
Statistic::new(module_path!(), "Number of functions with too many blocks to optimize");
133+
static NUM_PROPAGATED: Statistic =
134+
Statistic::new(module_path!(), "Number of destination propagations");
135+
128136
impl<'tcx> MirPass<'tcx> for DestinationPropagation {
129137
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
130138
// Only run at mir-opt-level=2 or higher for now (we don't fix up debuginfo and remove
@@ -164,6 +172,7 @@ impl<'tcx> MirPass<'tcx> for DestinationPropagation {
164172
"too many candidate locals in {:?} ({}, max is {}), not optimizing",
165173
def_id, relevant, MAX_LOCALS
166174
);
175+
NUM_TOO_MANY_LOCALS.increment(1);
167176
return;
168177
}
169178
if body.basic_blocks().len() > MAX_BLOCKS {
@@ -173,6 +182,7 @@ impl<'tcx> MirPass<'tcx> for DestinationPropagation {
173182
body.basic_blocks().len(),
174183
MAX_BLOCKS
175184
);
185+
NUM_TOO_MANY_BLOCKS.increment(1);
176186
return;
177187
}
178188

@@ -197,6 +207,7 @@ impl<'tcx> MirPass<'tcx> for DestinationPropagation {
197207
break;
198208
}
199209

210+
NUM_PROPAGATED.increment(1);
200211
replacements.push(candidate);
201212
conflicts.unify(candidate.src, candidate.dest.local);
202213
}

compiler/rustc_mir/src/transform/inline.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Inlining pass for MIR functions
22
33
use rustc_attr as attr;
4+
use rustc_data_structures::statistics::Statistic;
45
use rustc_hir::def_id::DefId;
56
use rustc_index::bit_set::BitSet;
67
use rustc_index::vec::{Idx, IndexVec};
@@ -28,6 +29,8 @@ const UNKNOWN_SIZE_COST: usize = 10;
2829

2930
pub struct Inline;
3031

32+
static NUM_INLINED: Statistic = Statistic::new(module_path!(), "Number of functions inlined");
33+
3134
#[derive(Copy, Clone, Debug)]
3235
struct CallSite<'tcx> {
3336
callee: DefId,
@@ -156,6 +159,7 @@ impl Inliner<'tcx> {
156159
continue;
157160
}
158161
debug!("attempting to inline callsite {:?} - success", callsite);
162+
NUM_INLINED.increment(1);
159163

160164
// Add callsites from inlined function
161165
for (bb, bb_data) in caller_body.basic_blocks().iter_enumerated().skip(start) {

0 commit comments

Comments
 (0)