Path: blob/main/cranelift/codegen/src/timing.rs
1693 views
//! Pass timing.1//!2//! This modules provides facilities for timing the execution of individual compilation passes.34use alloc::boxed::Box;5use core::any::Any;6use core::fmt;78// Each pass that can be timed is predefined with the `define_passes!` macro. Each pass has a9// snake_case name and a plain text description used when printing out the timing report.10//11// This macro defines:12//13// - A C-style enum containing all the pass names and a `None` variant.14// - A usize constant with the number of defined passes.15// - A const array of pass descriptions.16// - A public function per pass used to start the timing of that pass.17macro_rules! define_passes {18($($pass:ident: $desc:expr,)+) => {19/// A single profiled pass.20#[expect(non_camel_case_types, reason = "macro-generated code")]21#[derive(Clone, Copy, Debug, PartialEq, Eq)]22pub enum Pass {23$(#[doc=$desc] $pass,)+24/// No active pass.25None,26}2728/// The amount of profiled passes.29pub const NUM_PASSES: usize = Pass::None as usize;3031const DESCRIPTIONS: [&str; NUM_PASSES] = [ $($desc),+ ];3233$(34#[doc=$desc]35#[must_use]36pub fn $pass() -> Box<dyn Any> {37start_pass(Pass::$pass)38}39)+40}41}4243// Pass definitions.44define_passes! {45// All these are used in other crates but defined here so they appear in the unified46// `PassTimes` output.47process_file: "Processing test file",48parse_text: "Parsing textual Cranelift IR",49wasm_translate_module: "Translate WASM module",50wasm_translate_function: "Translate WASM function",5152verifier: "Verify Cranelift IR",5354compile: "Compilation passes",55try_incremental_cache: "Try loading from incremental cache",56store_incremental_cache: "Store in incremental cache",57flowgraph: "Control flow graph",58domtree: "Dominator tree",59loop_analysis: "Loop analysis",60preopt: "Pre-legalization rewriting",61egraph: "Egraph based optimizations",62gvn: "Global value numbering",63licm: "Loop invariant code motion",64unreachable_code: "Remove unreachable blocks",65remove_constant_phis: "Remove constant phi-nodes",6667vcode_lower: "VCode lowering",68vcode_emit: "VCode emission",69vcode_emit_finish: "VCode emission finalization",7071regalloc: "Register allocation",72regalloc_checker: "Register allocation symbolic verification",73layout_renumber: "Layout full renumbering",7475canonicalize_nans: "Canonicalization of NaNs",76}7778impl Pass {79fn idx(self) -> usize {80self as usize81}8283/// Description of the pass.84pub fn description(self) -> &'static str {85match DESCRIPTIONS.get(self.idx()) {86Some(s) => s,87None => "<no pass>",88}89}90}9192impl fmt::Display for Pass {93fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {94f.write_str(self.description())95}96}9798/// A profiler.99pub trait Profiler {100/// Start a profiling pass.101///102/// Will return a token which when dropped indicates the end of the pass.103///104/// Multiple passes can be active at the same time, but they must be started and stopped in a105/// LIFO fashion.106fn start_pass(&self, pass: Pass) -> Box<dyn Any>;107}108109/// The default profiler. You can get the results using [`take_current`].110pub struct DefaultProfiler;111112#[cfg(not(feature = "timing"))]113pub(crate) use disabled::*;114#[cfg(feature = "timing")]115pub use enabled::*;116117#[cfg(feature = "timing")]118mod enabled {119use super::{DESCRIPTIONS, DefaultProfiler, NUM_PASSES, Pass, Profiler};120use std::any::Any;121use std::boxed::Box;122use std::cell::{Cell, RefCell};123use std::fmt;124use std::mem;125use std::time::Duration;126use std::time::Instant;127128// Information about passes in a single thread.129thread_local! {130static PROFILER: RefCell<Box<dyn Profiler>> = RefCell::new(Box::new(DefaultProfiler));131}132133/// Set the profiler for the current thread.134///135/// Returns the old profiler.136pub fn set_thread_profiler(new_profiler: Box<dyn Profiler>) -> Box<dyn Profiler> {137PROFILER.with(|profiler| std::mem::replace(&mut *profiler.borrow_mut(), new_profiler))138}139140/// Start timing `pass` as a child of the currently running pass, if any.141///142/// This function is called by the publicly exposed pass functions.143pub fn start_pass(pass: Pass) -> Box<dyn Any> {144PROFILER.with(|profiler| profiler.borrow().start_pass(pass))145}146147/// Accumulated timing information for a single pass.148#[derive(Default, Copy, Clone)]149struct PassTime {150/// Total time spent running this pass including children.151total: Duration,152153/// Time spent running in child passes.154child: Duration,155}156157/// Accumulated timing for all passes.158pub struct PassTimes {159pass: [PassTime; NUM_PASSES],160}161162impl PassTimes {163/// Add `other` to the timings of this `PassTimes`.164pub fn add(&mut self, other: &Self) {165for (a, b) in self.pass.iter_mut().zip(&other.pass[..]) {166a.total += b.total;167a.child += b.child;168}169}170171/// Returns the total amount of time taken by all the passes measured.172pub fn total(&self) -> Duration {173self.pass.iter().map(|p| p.total - p.child).sum()174}175}176177impl Default for PassTimes {178fn default() -> Self {179Self {180pass: [Default::default(); NUM_PASSES],181}182}183}184185impl fmt::Display for PassTimes {186fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {187writeln!(f, "======== ======== ==================================")?;188writeln!(f, " Total Self Pass")?;189writeln!(f, "-------- -------- ----------------------------------")?;190for (time, desc) in self.pass.iter().zip(&DESCRIPTIONS[..]) {191// Omit passes that haven't run.192if time.total == Duration::default() {193continue;194}195196// Write a duration as secs.millis, trailing space.197fn fmtdur(mut dur: Duration, f: &mut fmt::Formatter) -> fmt::Result {198// Round to nearest ms by adding 500us.199dur += Duration::new(0, 500_000);200let ms = dur.subsec_millis();201write!(f, "{:4}.{:03} ", dur.as_secs(), ms)202}203204fmtdur(time.total, f)?;205if let Some(s) = time.total.checked_sub(time.child) {206fmtdur(s, f)?;207}208writeln!(f, " {desc}")?;209}210writeln!(f, "======== ======== ==================================")211}212}213214// Information about passes in a single thread.215thread_local! {216static PASS_TIME: RefCell<PassTimes> = RefCell::new(Default::default());217}218219/// Take the current accumulated pass timings and reset the timings for the current thread.220///221/// Only applies when [`DefaultProfiler`] is used.222pub fn take_current() -> PassTimes {223PASS_TIME.with(|rc| mem::take(&mut *rc.borrow_mut()))224}225226// Information about passes in a single thread.227thread_local! {228static CURRENT_PASS: Cell<Pass> = const { Cell::new(Pass::None) };229}230231impl Profiler for DefaultProfiler {232fn start_pass(&self, pass: Pass) -> Box<dyn Any> {233let prev = CURRENT_PASS.with(|p| p.replace(pass));234log::debug!("timing: Starting {pass}, (during {prev})");235Box::new(DefaultTimingToken {236start: Instant::now(),237pass,238prev,239})240}241}242243/// A timing token is responsible for timing the currently running pass. Timing starts when it244/// is created and ends when it is dropped.245///246/// Multiple passes can be active at the same time, but they must be started and stopped in a247/// LIFO fashion.248struct DefaultTimingToken {249/// Start time for this pass.250start: Instant,251252// Pass being timed by this token.253pass: Pass,254255// The previously active pass which will be restored when this token is dropped.256prev: Pass,257}258259/// Dropping a timing token indicated the end of the pass.260impl Drop for DefaultTimingToken {261fn drop(&mut self) {262let duration = self.start.elapsed();263log::debug!("timing: Ending {}: {}ms", self.pass, duration.as_millis());264let old_cur = CURRENT_PASS.with(|p| p.replace(self.prev));265debug_assert_eq!(self.pass, old_cur, "Timing tokens dropped out of order");266PASS_TIME.with(|rc| {267let mut table = rc.borrow_mut();268table.pass[self.pass.idx()].total += duration;269if let Some(parent) = table.pass.get_mut(self.prev.idx()) {270parent.child += duration;271}272})273}274}275}276277#[cfg(not(feature = "timing"))]278mod disabled {279use super::{DefaultProfiler, Pass, Profiler};280use alloc::boxed::Box;281use core::any::Any;282283impl Profiler for DefaultProfiler {284fn start_pass(&self, _pass: Pass) -> Box<dyn Any> {285Box::new(())286}287}288289pub(crate) fn start_pass(_pass: Pass) -> Box<dyn Any> {290Box::new(())291}292}293294#[cfg(test)]295mod tests {296use super::*;297use alloc::string::ToString;298299#[test]300fn display() {301assert_eq!(Pass::None.to_string(), "<no pass>");302assert_eq!(Pass::regalloc.to_string(), "Register allocation");303}304}305306307