diff --git a/compiler/rustc_data_structures/src/lib.rs b/compiler/rustc_data_structures/src/lib.rs index 9958e5dd5e0ee..2724164be9404 100644 --- a/compiler/rustc_data_structures/src/lib.rs +++ b/compiler/rustc_data_structures/src/lib.rs @@ -92,6 +92,7 @@ pub mod fingerprint; pub mod profiling; pub mod sharded; pub mod stack; +pub mod statistics; pub mod sync; pub mod thin_vec; pub mod tiny_list; diff --git a/compiler/rustc_data_structures/src/statistics.rs b/compiler/rustc_data_structures/src/statistics.rs new file mode 100644 index 0000000000000..783e787021987 --- /dev/null +++ b/compiler/rustc_data_structures/src/statistics.rs @@ -0,0 +1,165 @@ +//! # Support for collecting simple statistics +//! +//! Statistics are useful for collecting metrics from optimization passes, like +//! the number of simplifications performed. To avoid introducing overhead, the +//! collection of statistics is enabled only when rustc is compiled with +//! debug-assertions. +//! +//! Statistics are static variables defined in the module they are used, and +//! lazy registered in the global collector on the first use. Once registered, +//! the collector will obtain their values at the end of compilation process +//! when requested with -Zmir-opt-stats option. + +use parking_lot::{const_mutex, Mutex}; +use std::io::{self, stdout, Write as _}; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; + +static COLLECTOR: Collector = Collector::new(); + +/// Enables the collection of statistics. +/// To be effective it has to be called before the first use of a statistics. +pub fn try_enable() -> Result<(), ()> { + COLLECTOR.try_enable() +} + +/// Prints all statistics collected so far. +pub fn print() { + COLLECTOR.print(); +} + +pub struct Statistic { + category: &'static str, + name: &'static str, + initialized: AtomicBool, + value: AtomicUsize, +} + +struct Collector(Mutex); + +struct State { + enabled: bool, + stats: Vec<&'static Statistic>, +} + +#[derive(Eq, PartialEq, Ord, PartialOrd)] +struct Snapshot { + category: &'static str, + name: &'static str, + value: usize, +} + +impl Statistic { + pub const fn new(category: &'static str, name: &'static str) -> Self { + Statistic { + category, + name, + initialized: AtomicBool::new(false), + value: AtomicUsize::new(0), + } + } + + pub fn name(&self) -> &'static str { + self.name + } + + pub fn category(&self) -> &'static str { + self.category.rsplit("::").next().unwrap() + } + + #[inline] + pub fn register(&'static self) { + if cfg!(debug_assertions) { + if !self.initialized.load(Ordering::Acquire) { + COLLECTOR.register(self); + } + } + } + + #[inline] + pub fn increment(&'static self, value: usize) { + if cfg!(debug_assertions) { + self.value.fetch_add(value, Ordering::Relaxed); + self.register(); + } + } + + #[inline] + pub fn update_max(&'static self, value: usize) { + if cfg!(debug_assertions) { + self.value.fetch_max(value, Ordering::Relaxed); + self.register(); + } + } + + fn snapshot(&'static self) -> Snapshot { + Snapshot { + name: self.name(), + category: self.category(), + value: self.value.load(Ordering::Relaxed), + } + } +} + +impl Collector { + const fn new() -> Self { + Collector(const_mutex(State { enabled: false, stats: Vec::new() })) + } + + fn try_enable(&self) -> Result<(), ()> { + if cfg!(debug_assertions) { + self.0.lock().enabled = true; + Ok(()) + } else { + Err(()) + } + } + + fn snapshot(&self) -> Vec { + self.0.lock().stats.iter().copied().map(Statistic::snapshot).collect() + } + + fn register(&self, s: &'static Statistic) { + let mut state = self.0.lock(); + if !s.initialized.load(Ordering::Relaxed) { + if state.enabled { + state.stats.push(s); + } + s.initialized.store(true, Ordering::Release); + } + } + + fn print(&self) { + let mut stats = self.snapshot(); + stats.sort(); + match self.write(&stats) { + Ok(_) => {} + Err(e) if e.kind() == io::ErrorKind::BrokenPipe => {} + Err(e) => panic!(e), + } + } + + fn write(&self, stats: &[Snapshot]) -> io::Result<()> { + let mut cat_width = 0; + let mut val_width = 0; + + for s in stats { + cat_width = cat_width.max(s.category.len()); + val_width = val_width.max(s.value.to_string().len()); + } + + let mut out = Vec::new(); + for s in stats { + write!( + &mut out, + "{val:val_width$} {cat:cat_width$} {name}\n", + val = s.value, + val_width = val_width, + cat = s.category, + cat_width = cat_width, + name = s.name, + )?; + } + + stdout().write_all(&out) + } +} diff --git a/compiler/rustc_driver/src/lib.rs b/compiler/rustc_driver/src/lib.rs index 575c2e627ed73..5b7ce0a6aa0f2 100644 --- a/compiler/rustc_driver/src/lib.rs +++ b/compiler/rustc_driver/src/lib.rs @@ -312,6 +312,7 @@ fn run_compiler( interface::run_compiler(config, |compiler| { let sess = compiler.session(); + let should_stop = RustcDefaultCalls::print_crate_info( &***compiler.codegen_backend(), sess, @@ -453,6 +454,10 @@ fn run_compiler( linker.link()? } + if sess.opts.debugging_opts.mir_opt_stats { + rustc_data_structures::statistics::print(); + } + if sess.opts.debugging_opts.perf_stats { sess.print_perf_stats(); } diff --git a/compiler/rustc_mir/src/transform/const_prop.rs b/compiler/rustc_mir/src/transform/const_prop.rs index 14b310cda939e..b89ad4c0b47d9 100644 --- a/compiler/rustc_mir/src/transform/const_prop.rs +++ b/compiler/rustc_mir/src/transform/const_prop.rs @@ -5,6 +5,7 @@ use std::cell::Cell; use rustc_ast::Mutability; use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::statistics::Statistic; use rustc_hir::def::DefKind; use rustc_hir::HirId; use rustc_index::bit_set::BitSet; @@ -59,6 +60,10 @@ macro_rules! throw_machine_stop_str { pub struct ConstProp; +static NUM_PROPAGATED: Statistic = Statistic::new(module_path!(), "Number of const propagations"); +static NUM_VALIDATION_ERRORS: Statistic = + Statistic::new(module_path!(), "Number of validation errors during const propagation"); + impl<'tcx> MirPass<'tcx> for ConstProp { fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { // will be evaluated by miri and produce its errors there @@ -622,6 +627,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { ScalarMaybeUninit::Scalar(scalar), )) = *value { + NUM_PROPAGATED.increment(1); *operand = self.operand_from_scalar( scalar, value.layout.ty, @@ -809,6 +815,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { /*may_ref_to_static*/ true, ) { trace!("validation error, attempt failed: {:?}", e); + NUM_VALIDATION_ERRORS.increment(1); return; } @@ -818,6 +825,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { if let Some(Ok(imm)) = imm { match *imm { interpret::Immediate::Scalar(ScalarMaybeUninit::Scalar(scalar)) => { + NUM_PROPAGATED.increment(1); *rval = Rvalue::Use(self.operand_from_scalar( scalar, value.layout.ty, @@ -859,6 +867,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { if let Some(Some(alloc)) = alloc { // Assign entire constant in a single statement. // We can't use aggregates, as we run after the aggregate-lowering `MirPhase`. + NUM_PROPAGATED.increment(1); *rval = Rvalue::Use(Operand::Constant(Box::new(Constant { span: source_info.span, user_ty: None, diff --git a/compiler/rustc_mir/src/transform/copy_prop.rs b/compiler/rustc_mir/src/transform/copy_prop.rs index 4f44bb7b20476..c7981cf4a1050 100644 --- a/compiler/rustc_mir/src/transform/copy_prop.rs +++ b/compiler/rustc_mir/src/transform/copy_prop.rs @@ -21,6 +21,7 @@ use crate::transform::MirPass; use crate::util::def_use::DefUseAnalysis; +use rustc_data_structures::statistics::Statistic; use rustc_middle::mir::visit::MutVisitor; use rustc_middle::mir::{ Body, Constant, Local, LocalKind, Location, Operand, Place, Rvalue, StatementKind, @@ -29,6 +30,13 @@ use rustc_middle::ty::TyCtxt; pub struct CopyPropagation; +static NUM_PROPAGATED_LOCAL: Statistic = + Statistic::new(module_path!(), "Number of locals copy propagated"); +static NUM_PROPAGATED_CONST: Statistic = + Statistic::new(module_path!(), "Number of constants copy propagated"); +static MAX_PROPAGATED: Statistic = + Statistic::new(module_path!(), "Maximum number of copy propagations in a function"); + impl<'tcx> MirPass<'tcx> for CopyPropagation { fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { let opts = &tcx.sess.opts.debugging_opts; @@ -39,6 +47,8 @@ impl<'tcx> MirPass<'tcx> for CopyPropagation { return; } + let mut propagated_local = 0; + let mut propagated_const = 0; let mut def_use_analysis = DefUseAnalysis::new(body); loop { def_use_analysis.analyze(body); @@ -139,8 +149,13 @@ impl<'tcx> MirPass<'tcx> for CopyPropagation { } } - changed = - action.perform(body, &def_use_analysis, dest_local, location, tcx) || changed; + if action.perform(body, &def_use_analysis, dest_local, location, tcx) { + match action { + Action::PropagateLocalCopy(_) => propagated_local += 1, + Action::PropagateConstant(_) => propagated_const += 1, + } + changed = true; + } // FIXME(pcwalton): Update the use-def chains to delete the instructions instead of // regenerating the chains. break; @@ -149,6 +164,10 @@ impl<'tcx> MirPass<'tcx> for CopyPropagation { break; } } + + NUM_PROPAGATED_LOCAL.increment(propagated_const); + NUM_PROPAGATED_CONST.increment(propagated_local); + MAX_PROPAGATED.update_max(propagated_local + propagated_const); } } @@ -193,6 +212,7 @@ fn eliminate_self_assignments(body: &mut Body<'_>, def_use_analysis: &DefUseAnal changed } +#[derive(Copy, Clone)] enum Action<'tcx> { PropagateLocalCopy(Local), PropagateConstant(Constant<'tcx>), diff --git a/compiler/rustc_mir/src/transform/dest_prop.rs b/compiler/rustc_mir/src/transform/dest_prop.rs index 410f462ed469f..1ea0d0b9fcab2 100644 --- a/compiler/rustc_mir/src/transform/dest_prop.rs +++ b/compiler/rustc_mir/src/transform/dest_prop.rs @@ -103,6 +103,7 @@ use crate::{ util::{dump_mir, PassWhere}, }; use itertools::Itertools; +use rustc_data_structures::statistics::Statistic; use rustc_data_structures::unify::{InPlaceUnificationTable, UnifyKey}; use rustc_index::{ bit_set::{BitMatrix, BitSet}, @@ -125,6 +126,13 @@ const MAX_BLOCKS: usize = 250; pub struct DestinationPropagation; +static NUM_TOO_MANY_LOCALS: Statistic = + Statistic::new(module_path!(), "Number of functions with too many locals to optimize"); +static NUM_TOO_MANY_BLOCKS: Statistic = + Statistic::new(module_path!(), "Number of functions with too many blocks to optimize"); +static NUM_PROPAGATED: Statistic = + Statistic::new(module_path!(), "Number of destination propagations"); + impl<'tcx> MirPass<'tcx> for DestinationPropagation { fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { // 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 { "too many candidate locals in {:?} ({}, max is {}), not optimizing", def_id, relevant, MAX_LOCALS ); + NUM_TOO_MANY_LOCALS.increment(1); return; } if body.basic_blocks().len() > MAX_BLOCKS { @@ -173,6 +182,7 @@ impl<'tcx> MirPass<'tcx> for DestinationPropagation { body.basic_blocks().len(), MAX_BLOCKS ); + NUM_TOO_MANY_BLOCKS.increment(1); return; } @@ -197,6 +207,7 @@ impl<'tcx> MirPass<'tcx> for DestinationPropagation { break; } + NUM_PROPAGATED.increment(1); replacements.push(candidate); conflicts.unify(candidate.src, candidate.dest.local); } diff --git a/compiler/rustc_mir/src/transform/inline.rs b/compiler/rustc_mir/src/transform/inline.rs index 34aaefdcbeab6..d9616417ca4f8 100644 --- a/compiler/rustc_mir/src/transform/inline.rs +++ b/compiler/rustc_mir/src/transform/inline.rs @@ -1,6 +1,7 @@ //! Inlining pass for MIR functions use rustc_attr as attr; +use rustc_data_structures::statistics::Statistic; use rustc_hir::def_id::DefId; use rustc_index::bit_set::BitSet; use rustc_index::vec::{Idx, IndexVec}; @@ -28,6 +29,8 @@ const UNKNOWN_SIZE_COST: usize = 10; pub struct Inline; +static NUM_INLINED: Statistic = Statistic::new(module_path!(), "Number of functions inlined"); + #[derive(Copy, Clone, Debug)] struct CallSite<'tcx> { callee: DefId, @@ -156,6 +159,7 @@ impl Inliner<'tcx> { continue; } debug!("attempting to inline callsite {:?} - success", callsite); + NUM_INLINED.increment(1); // Add callsites from inlined function for (bb, bb_data) in caller_body.basic_blocks().iter_enumerated().skip(start) { diff --git a/compiler/rustc_mir/src/transform/instcombine.rs b/compiler/rustc_mir/src/transform/instcombine.rs index 1a8e281d417df..4adb19d39b0f9 100644 --- a/compiler/rustc_mir/src/transform/instcombine.rs +++ b/compiler/rustc_mir/src/transform/instcombine.rs @@ -2,6 +2,7 @@ use crate::transform::MirPass; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::statistics::Statistic; use rustc_hir::Mutability; use rustc_index::vec::Idx; use rustc_middle::mir::{ @@ -18,6 +19,15 @@ use std::mem; pub struct InstCombine; +static NUM_REF_DEREF_SIMPLIFIED: Statistic = + Statistic::new(module_path!(), "Number of ref-derefs operations simplified"); +static NUM_EQ_BOOL_COMPARE_SIMPLIFIED: Statistic = + Statistic::new(module_path!(), "Number of equality comparisons with a const bool simplified"); +static NUM_ARRAY_LEN_SIMPLIFIED: Statistic = + Statistic::new(module_path!(), "Number of array length operations simplified"); +static NUM_DEREF_REF_SIMPLIFIED: Statistic = + Statistic::new(module_path!(), "Number of derer-refs operations simplified"); + impl<'tcx> MirPass<'tcx> for InstCombine { fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { // First, find optimization opportunities. This is done in a pre-pass to keep the MIR @@ -63,21 +73,25 @@ impl<'tcx> MutVisitor<'tcx> for InstCombineVisitor<'tcx> { } _ => bug!("Detected `&*` but didn't find `&*`!"), }; + NUM_REF_DEREF_SIMPLIFIED.increment(1); *rvalue = Rvalue::Use(Operand::Copy(new_place)) } if let Some(constant) = self.optimizations.arrays_lengths.remove(&location) { debug!("replacing `Len([_; N])`: {:?}", rvalue); + NUM_ARRAY_LEN_SIMPLIFIED.increment(1); *rvalue = Rvalue::Use(Operand::Constant(box constant)); } if let Some(operand) = self.optimizations.unneeded_equality_comparison.remove(&location) { debug!("replacing {:?} with {:?}", rvalue, operand); + NUM_EQ_BOOL_COMPARE_SIMPLIFIED.increment(1); *rvalue = Rvalue::Use(operand); } if let Some(place) = self.optimizations.unneeded_deref.remove(&location) { debug!("unneeded_deref: replacing {:?} with {:?}", rvalue, place); + NUM_DEREF_REF_SIMPLIFIED.increment(1); *rvalue = Rvalue::Use(Operand::Copy(place)); } diff --git a/compiler/rustc_mir/src/transform/match_branches.rs b/compiler/rustc_mir/src/transform/match_branches.rs index 8b2d6b09aa830..271aca039f91f 100644 --- a/compiler/rustc_mir/src/transform/match_branches.rs +++ b/compiler/rustc_mir/src/transform/match_branches.rs @@ -1,4 +1,5 @@ use crate::transform::MirPass; +use rustc_data_structures::statistics::Statistic; use rustc_middle::mir::*; use rustc_middle::ty::TyCtxt; @@ -36,6 +37,9 @@ pub struct MatchBranchSimplification; /// } /// ``` +static NUM_MERGED: Statistic = + Statistic::new(module_path!(), "Number of similar basic blocks merged together"); + impl<'tcx> MirPass<'tcx> for MatchBranchSimplification { fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { let param_env = tcx.param_env(body.source.def_id()); @@ -133,6 +137,7 @@ impl<'tcx> MirPass<'tcx> for MatchBranchSimplification { }); from.statements.extend(new_stmts); from.terminator_mut().kind = first.terminator().kind.clone(); + NUM_MERGED.increment(1); } } } diff --git a/compiler/rustc_mir/src/transform/multiple_return_terminators.rs b/compiler/rustc_mir/src/transform/multiple_return_terminators.rs index c37b54a3190f8..3bb84bfa269f2 100644 --- a/compiler/rustc_mir/src/transform/multiple_return_terminators.rs +++ b/compiler/rustc_mir/src/transform/multiple_return_terminators.rs @@ -2,12 +2,16 @@ //! return instead. use crate::transform::{simplify, MirPass}; +use rustc_data_structures::statistics::Statistic; use rustc_index::bit_set::BitSet; use rustc_middle::mir::*; use rustc_middle::ty::TyCtxt; pub struct MultipleReturnTerminators; +static NUM_REPLACED: Statistic = + Statistic::new(module_path!(), "Number of goto terminators replaced with a return"); + impl<'tcx> MirPass<'tcx> for MultipleReturnTerminators { fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { if tcx.sess.opts.debugging_opts.mir_opt_level < 3 { @@ -28,6 +32,7 @@ impl<'tcx> MirPass<'tcx> for MultipleReturnTerminators { for bb in bbs { if let TerminatorKind::Goto { target } = bb.terminator().kind { if bbs_simple_returns.contains(target) { + NUM_REPLACED.increment(1); bb.terminator_mut().kind = TerminatorKind::Return; } } diff --git a/compiler/rustc_mir/src/transform/nrvo.rs b/compiler/rustc_mir/src/transform/nrvo.rs index 7e05d66074bb1..37139b73116ed 100644 --- a/compiler/rustc_mir/src/transform/nrvo.rs +++ b/compiler/rustc_mir/src/transform/nrvo.rs @@ -1,3 +1,4 @@ +use rustc_data_structures::statistics::Statistic; use rustc_hir::Mutability; use rustc_index::bit_set::HybridBitSet; use rustc_middle::mir::visit::{MutVisitor, NonUseContext, PlaceContext, Visitor}; @@ -30,6 +31,11 @@ use crate::transform::MirPass; /// [#71003]: https://github.com/rust-lang/rust/pull/71003 pub struct RenameReturnPlace; +static NUM_OPTIMIZED: Statistic = Statistic::new( + module_path!(), + "Number of functions eligible for named return value optimization", +); + impl<'tcx> MirPass<'tcx> for RenameReturnPlace { fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut mir::Body<'tcx>) { if tcx.sess.opts.debugging_opts.mir_opt_level == 0 { @@ -70,6 +76,7 @@ impl<'tcx> MirPass<'tcx> for RenameReturnPlace { // The return place is always mutable. ret_decl.mutability = Mutability::Mut; + NUM_OPTIMIZED.increment(1); } } diff --git a/compiler/rustc_mir/src/transform/remove_noop_landing_pads.rs b/compiler/rustc_mir/src/transform/remove_noop_landing_pads.rs index 31e201c3a5bbe..bb8e39a0e0cc9 100644 --- a/compiler/rustc_mir/src/transform/remove_noop_landing_pads.rs +++ b/compiler/rustc_mir/src/transform/remove_noop_landing_pads.rs @@ -1,5 +1,6 @@ use crate::transform::MirPass; use crate::util::patch::MirPatch; +use rustc_data_structures::statistics::Statistic; use rustc_index::bit_set::BitSet; use rustc_middle::mir::*; use rustc_middle::ty::TyCtxt; @@ -10,6 +11,10 @@ use rustc_target::spec::PanicStrategy; /// code for these. pub struct RemoveNoopLandingPads; +static NUM_FOLDED: Statistic = Statistic::new(module_path!(), "Number of no-op jumps folded"); +static NUM_REMOVED: Statistic = + Statistic::new(module_path!(), "Number of no-op landing pads removed"); + pub fn remove_noop_landing_pads<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { if tcx.sess.panic_strategy() == PanicStrategy::Abort { return; @@ -128,5 +133,7 @@ impl RemoveNoopLandingPads { } debug!("removed {:?} jumps and {:?} landing pads", jumps_folded, landing_pads_removed); + NUM_FOLDED.increment(jumps_folded); + NUM_REMOVED.increment(landing_pads_removed); } } diff --git a/compiler/rustc_mir/src/transform/remove_unneeded_drops.rs b/compiler/rustc_mir/src/transform/remove_unneeded_drops.rs index aaf3ecab4dca7..2f9812f909499 100644 --- a/compiler/rustc_mir/src/transform/remove_unneeded_drops.rs +++ b/compiler/rustc_mir/src/transform/remove_unneeded_drops.rs @@ -1,6 +1,7 @@ //! This pass replaces a drop of a type that does not need dropping, with a goto use crate::transform::MirPass; +use rustc_data_structures::statistics::Statistic; use rustc_middle::mir::visit::Visitor; use rustc_middle::mir::*; use rustc_middle::ty::{ParamEnv, TyCtxt}; @@ -9,6 +10,9 @@ use super::simplify::simplify_cfg; pub struct RemoveUnneededDrops; +static NUM_REMOVED: Statistic = + Statistic::new(module_path!(), "Number of unnecessary drop terminators removed"); + impl<'tcx> MirPass<'tcx> for RemoveUnneededDrops { fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { trace!("Running RemoveUnneededDrops on {:?}", body.source); @@ -23,6 +27,7 @@ impl<'tcx> MirPass<'tcx> for RemoveUnneededDrops { for (loc, target) in opt_finder.optimizations { let terminator = body.basic_blocks_mut()[loc.block].terminator_mut(); debug!("SUCCESS: replacing `drop` with goto({:?})", target); + NUM_REMOVED.increment(1); terminator.kind = TerminatorKind::Goto { target }; } diff --git a/compiler/rustc_mir/src/transform/simplify.rs b/compiler/rustc_mir/src/transform/simplify.rs index f0c87bcf513cd..e189ddd7ce8da 100644 --- a/compiler/rustc_mir/src/transform/simplify.rs +++ b/compiler/rustc_mir/src/transform/simplify.rs @@ -28,6 +28,7 @@ //! return. use crate::transform::MirPass; +use rustc_data_structures::statistics::Statistic; use rustc_index::bit_set::BitSet; use rustc_index::vec::{Idx, IndexVec}; use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor}; @@ -36,6 +37,21 @@ use rustc_middle::ty::TyCtxt; use smallvec::SmallVec; use std::borrow::Cow; +static NUM_DEAD_BLOCKS_REMOVED: Statistic = + Statistic::new(module_path!(), "Number of dead basic blocks removed"); +static NUM_SWITCH_INT_SIMPLIFIED: Statistic = Statistic::new( + module_path!(), + "Number of switch int terminators with identical successors replaced with a goto", +); +static NUM_BLOCKS_MERGED: Statistic = + Statistic::new(module_path!(), "Number of basic blocks merged with their only predecessor"); +static MAX_CFG_ITERATIONS: Statistic = + Statistic::new(module_path!(), "Maximum number of iterations of control-flow-graph simplifier"); +static NUM_LOCALS_REMOVED: Statistic = + Statistic::new(module_path!(), "Number of unused locals removed"); +static MAX_LOCALS_ITERATIONS: Statistic = + Statistic::new(module_path!(), "Maximum number of iterations of locals simplifier"); + pub struct SimplifyCfg { label: String, } @@ -101,7 +117,7 @@ impl<'a, 'tcx> CfgSimplifier<'a, 'tcx> { // We do not push the statements directly into the target block (`bb`) as that is slower // due to additional reallocations let mut merged_blocks = Vec::new(); - loop { + for iterations in 1.. { let mut changed = false; self.collapse_goto_chain(&mut start, &mut changed); @@ -145,6 +161,7 @@ impl<'a, 'tcx> CfgSimplifier<'a, 'tcx> { } if !changed { + MAX_CFG_ITERATIONS.update_max(iterations); break; } } @@ -238,6 +255,8 @@ impl<'a, 'tcx> CfgSimplifier<'a, 'tcx> { }; debug!("merging block {:?} into {:?}", target, terminator); + NUM_BLOCKS_MERGED.increment(1); + *terminator = match self.basic_blocks[target].terminator.take() { Some(terminator) => terminator, None => { @@ -275,6 +294,7 @@ impl<'a, 'tcx> CfgSimplifier<'a, 'tcx> { }; debug!("simplifying branch {:?}", terminator); + NUM_SWITCH_INT_SIMPLIFIED.increment(1); terminator.kind = TerminatorKind::Goto { target: first_succ }; true } @@ -306,7 +326,9 @@ pub fn remove_dead_blocks(body: &mut Body<'_>) { } used_blocks += 1; } - basic_blocks.raw.truncate(used_blocks); + let removed_blocks = basic_blocks.len() - used_blocks; + basic_blocks.truncate(used_blocks); + NUM_DEAD_BLOCKS_REMOVED.increment(removed_blocks); for block in basic_blocks { for target in block.terminator_mut().successors_mut() { @@ -337,11 +359,12 @@ impl<'tcx> MirPass<'tcx> for SimplifyLocals { // count. For example, if we removed `_2 = discriminant(_1)`, then we'll subtract one from // `use_counts[_1]`. That in turn might make `_1` unused, so we loop until we hit a // fixedpoint where there are no more unused locals. - loop { + for iterations in 1.. { let mut remove_statements = RemoveStatements::new(&mut used_locals, arg_count, tcx); remove_statements.visit_body(body); if !remove_statements.modified { + MAX_LOCALS_ITERATIONS.update_max(iterations); break; } } @@ -380,6 +403,8 @@ fn make_local_map( } used.increment_by(1); } + let unused = local_decls.len() - used.index(); + NUM_LOCALS_REMOVED.increment(unused); local_decls.truncate(used.index()); map } diff --git a/compiler/rustc_mir/src/transform/simplify_branches.rs b/compiler/rustc_mir/src/transform/simplify_branches.rs index 5f63c03993d3a..d27464566956f 100644 --- a/compiler/rustc_mir/src/transform/simplify_branches.rs +++ b/compiler/rustc_mir/src/transform/simplify_branches.rs @@ -1,6 +1,7 @@ //! A pass that simplifies branches when their condition is known. use crate::transform::MirPass; +use rustc_data_structures::statistics::Statistic; use rustc_middle::mir::*; use rustc_middle::ty::TyCtxt; @@ -10,6 +11,13 @@ pub struct SimplifyBranches { label: String, } +static NUM_TRUE_ASSERT_SIMPLIFIED: Statistic = + Statistic::new(module_path!(), "Number of true assertions simplified to a goto"); +static NUM_FALSE_EDGE_SIMPLIFIED: Statistic = + Statistic::new(module_path!(), "Number of false edges simplified to a goto"); +static NUM_FALSE_UNWIND_SIMPLIFIED: Statistic = + Statistic::new(module_path!(), "Number of false unwinds simplified to a goto"); + impl SimplifyBranches { pub fn new(label: &str) -> Self { SimplifyBranches { label: format!("SimplifyBranches-{}", label) } @@ -50,12 +58,15 @@ impl<'tcx> MirPass<'tcx> for SimplifyBranches { TerminatorKind::Assert { target, cond: Operand::Constant(ref c), expected, .. } if (c.literal.try_eval_bool(tcx, param_env) == Some(true)) == expected => { + NUM_TRUE_ASSERT_SIMPLIFIED.increment(1); TerminatorKind::Goto { target } } TerminatorKind::FalseEdge { real_target, .. } => { + NUM_FALSE_EDGE_SIMPLIFIED.increment(1); TerminatorKind::Goto { target: real_target } } TerminatorKind::FalseUnwind { real_target, .. } => { + NUM_FALSE_UNWIND_SIMPLIFIED.increment(1); TerminatorKind::Goto { target: real_target } } _ => continue, diff --git a/compiler/rustc_mir/src/transform/simplify_comparison_integral.rs b/compiler/rustc_mir/src/transform/simplify_comparison_integral.rs index 6372f8960ddb6..8ee156a64d16e 100644 --- a/compiler/rustc_mir/src/transform/simplify_comparison_integral.rs +++ b/compiler/rustc_mir/src/transform/simplify_comparison_integral.rs @@ -1,6 +1,7 @@ use std::iter; use super::MirPass; +use rustc_data_structures::statistics::Statistic; use rustc_middle::{ mir::{ interpret::Scalar, BasicBlock, BinOp, Body, Operand, Place, Rvalue, Statement, @@ -25,6 +26,11 @@ use rustc_middle::{ /// ``` pub struct SimplifyComparisonIntegral; +static NUM_SIMPLIFIED: Statistic = Statistic::new( + module_path!(), + "Number of switches on a result of integer comparison simplified to switches on integers", +); + impl<'tcx> MirPass<'tcx> for SimplifyComparisonIntegral { fn run_pass(&self, _: TyCtxt<'tcx>, body: &mut Body<'tcx>) { trace!("Running SimplifyComparisonIntegral on {:?}", body.source); @@ -65,6 +71,7 @@ impl<'tcx> MirPass<'tcx> for SimplifyComparisonIntegral { _ => unreachable!(), } + NUM_SIMPLIFIED.increment(1); // delete comparison statement if it the value being switched on was moved, which means it can not be user later on if opt.can_remove_bin_op_stmt { bb.statements[opt.bin_op_stmt_idx].make_nop(); diff --git a/compiler/rustc_mir/src/transform/simplify_try.rs b/compiler/rustc_mir/src/transform/simplify_try.rs index 27bb1def726e1..44a047df5c982 100644 --- a/compiler/rustc_mir/src/transform/simplify_try.rs +++ b/compiler/rustc_mir/src/transform/simplify_try.rs @@ -11,6 +11,7 @@ use crate::transform::{simplify, MirPass}; use itertools::Itertools as _; +use rustc_data_structures::statistics::Statistic; use rustc_index::{bit_set::BitSet, vec::IndexVec}; use rustc_middle::mir::visit::{NonUseContext, PlaceContext, Visitor}; use rustc_middle::mir::*; @@ -37,6 +38,13 @@ use std::slice::Iter; /// ``` pub struct SimplifyArmIdentity; +static NUM_ARM_IDENTITY_SIMPLIFIED: Statistic = + Statistic::new(module_path!(), "Number of arm identities simplified to a move"); +static NUM_SAME_BRANCH_SIMPLIFIED: Statistic = Statistic::new( + module_path!(), + "Number of switches with equivalent targets replaced with a goto to the first target", +); + #[derive(Debug)] struct ArmIdentityInfo<'tcx> { /// Storage location for the variant's field @@ -429,6 +437,7 @@ impl<'tcx> MirPass<'tcx> for SimplifyArmIdentity { } trace!("block is now {:?}", bb.statements); + NUM_ARM_IDENTITY_SIMPLIFIED.increment(1); } } } @@ -544,6 +553,7 @@ impl<'tcx> MirPass<'tcx> for SimplifyBranchSame { } if did_remove_blocks { + NUM_SAME_BRANCH_SIMPLIFIED.increment(opts.len()); // We have dead blocks now, so remove those. simplify::remove_dead_blocks(body); } diff --git a/compiler/rustc_mir/src/transform/uninhabited_enum_branching.rs b/compiler/rustc_mir/src/transform/uninhabited_enum_branching.rs index 465832c89fd00..48fe46f9a7007 100644 --- a/compiler/rustc_mir/src/transform/uninhabited_enum_branching.rs +++ b/compiler/rustc_mir/src/transform/uninhabited_enum_branching.rs @@ -2,6 +2,7 @@ use crate::transform::MirPass; use rustc_data_structures::stable_set::FxHashSet; +use rustc_data_structures::statistics::Statistic; use rustc_middle::mir::{ BasicBlock, BasicBlockData, Body, Local, Operand, Rvalue, StatementKind, SwitchTargets, TerminatorKind, @@ -12,6 +13,11 @@ use rustc_target::abi::{Abi, Variants}; pub struct UninhabitedEnumBranching; +static NUM_REMOVED: Statistic = Statistic::new( + module_path!(), + "Number of branches corresponding to uninhabited enum variants removed", +); + fn get_discriminant_local(terminator: &TerminatorKind<'_>) -> Option { if let TerminatorKind::SwitchInt { discr: Operand::Move(p), .. } = terminator { p.as_local() @@ -109,7 +115,8 @@ impl<'tcx> MirPass<'tcx> for UninhabitedEnumBranching { targets.iter().filter(|(val, _)| allowed_variants.contains(val)), targets.otherwise(), ); - + NUM_REMOVED + .increment(targets.all_targets().len() - new_targets.all_targets().len()); *targets = new_targets; } else { unreachable!() diff --git a/compiler/rustc_mir/src/transform/unreachable_prop.rs b/compiler/rustc_mir/src/transform/unreachable_prop.rs index f6d39dae3429e..351c1daac27fa 100644 --- a/compiler/rustc_mir/src/transform/unreachable_prop.rs +++ b/compiler/rustc_mir/src/transform/unreachable_prop.rs @@ -5,11 +5,17 @@ use crate::transform::simplify; use crate::transform::MirPass; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::statistics::Statistic; use rustc_middle::mir::*; use rustc_middle::ty::TyCtxt; pub struct UnreachablePropagation; +static NUM_PROPAGATED: Statistic = + Statistic::new(module_path!(), "Number of unreachable terminators propagated"); +static NUM_TARGETS_REMOVED: Statistic = + Statistic::new(module_path!(), "Number of blocks with some of targets removed"); + impl MirPass<'_> for UnreachablePropagation { fn run_pass<'tcx>(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { if tcx.sess.opts.debugging_opts.mir_opt_level < 3 { @@ -43,6 +49,13 @@ impl MirPass<'_> for UnreachablePropagation { if terminator_kind == TerminatorKind::Unreachable && !asm_stmt_in_block() { unreachable_blocks.insert(bb); } + + if terminator_kind == TerminatorKind::Unreachable { + NUM_PROPAGATED.increment(1); + } else { + NUM_TARGETS_REMOVED.increment(1); + } + replacements.insert(bb, terminator_kind); } } diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 627adcceb3f4a..7ace79347e9bf 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -961,6 +961,8 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options, (default: no)"), mir_opt_level: usize = (1, parse_uint, [TRACKED], "MIR optimization level (0-3; default: 1)"), + mir_opt_stats: bool = (false, parse_bool, [UNTRACKED], + "print MIR optimization statistics"), mutable_noalias: bool = (false, parse_bool, [TRACKED], "emit noalias metadata for mutable references (default: no)"), new_llvm_pass_manager: bool = (false, parse_bool, [TRACKED], diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index ff5e6156d846b..b184eedf2dc6b 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -1538,6 +1538,14 @@ fn validate_commandline_args_with_session_available(sess: &Session) { break; } } + + if sess.opts.debugging_opts.mir_opt_stats { + if let Err(_) = rustc_data_structures::statistics::try_enable() { + sess.err(&format!( + "-Zmir-opt-stats is only available when rustc is compiled with debug-assertions" + )); + } + } } /// Holds data on the current incremental compilation session, if there is one.