1747ad3c4Slazypassion //! Pass timing. 2747ad3c4Slazypassion //! 3747ad3c4Slazypassion //! This modules provides facilities for timing the execution of individual compilation passes. 4747ad3c4Slazypassion 54ab0fc48SJonas Kruckenberg use alloc::boxed::Box; 64ab0fc48SJonas Kruckenberg use core::any::Any; 7747ad3c4Slazypassion use core::fmt; 8747ad3c4Slazypassion 9747ad3c4Slazypassion // Each pass that can be timed is predefined with the `define_passes!` macro. Each pass has a 10747ad3c4Slazypassion // snake_case name and a plain text description used when printing out the timing report. 11747ad3c4Slazypassion // 12747ad3c4Slazypassion // This macro defines: 13747ad3c4Slazypassion // 14747ad3c4Slazypassion // - A C-style enum containing all the pass names and a `None` variant. 15747ad3c4Slazypassion // - A usize constant with the number of defined passes. 16747ad3c4Slazypassion // - A const array of pass descriptions. 17747ad3c4Slazypassion // - A public function per pass used to start the timing of that pass. 18747ad3c4Slazypassion macro_rules! define_passes { 19108f7917Sbjorn3 ($($pass:ident: $desc:expr,)+) => { 20108f7917Sbjorn3 /// A single profiled pass. 21099102d9SAlex Crichton #[expect(non_camel_case_types, reason = "macro-generated code")] 22747ad3c4Slazypassion #[derive(Clone, Copy, Debug, PartialEq, Eq)] 23108f7917Sbjorn3 pub enum Pass { 24108f7917Sbjorn3 $(#[doc=$desc] $pass,)+ 25108f7917Sbjorn3 /// No active pass. 26108f7917Sbjorn3 None, 27108f7917Sbjorn3 } 28747ad3c4Slazypassion 29108f7917Sbjorn3 /// The amount of profiled passes. 30108f7917Sbjorn3 pub const NUM_PASSES: usize = Pass::None as usize; 31747ad3c4Slazypassion 32108f7917Sbjorn3 const DESCRIPTIONS: [&str; NUM_PASSES] = [ $($desc),+ ]; 33747ad3c4Slazypassion 34747ad3c4Slazypassion $( 35747ad3c4Slazypassion #[doc=$desc] 368bd507dbSBenjamin Bouvier #[must_use] 37108f7917Sbjorn3 pub fn $pass() -> Box<dyn Any> { 38108f7917Sbjorn3 start_pass(Pass::$pass) 39747ad3c4Slazypassion } 40747ad3c4Slazypassion )+ 41747ad3c4Slazypassion } 42747ad3c4Slazypassion } 43747ad3c4Slazypassion 44747ad3c4Slazypassion // Pass definitions. 45747ad3c4Slazypassion define_passes! { 468bd507dbSBenjamin Bouvier // All these are used in other crates but defined here so they appear in the unified 478bd507dbSBenjamin Bouvier // `PassTimes` output. 48747ad3c4Slazypassion process_file: "Processing test file", 49747ad3c4Slazypassion parse_text: "Parsing textual Cranelift IR", 50747ad3c4Slazypassion wasm_translate_module: "Translate WASM module", 51747ad3c4Slazypassion wasm_translate_function: "Translate WASM function", 52747ad3c4Slazypassion 53747ad3c4Slazypassion verifier: "Verify Cranelift IR", 54747ad3c4Slazypassion 55747ad3c4Slazypassion compile: "Compilation passes", 568a9b1a90SBenjamin Bouvier try_incremental_cache: "Try loading from incremental cache", 578a9b1a90SBenjamin Bouvier store_incremental_cache: "Store in incremental cache", 58747ad3c4Slazypassion flowgraph: "Control flow graph", 59747ad3c4Slazypassion domtree: "Dominator tree", 60747ad3c4Slazypassion loop_analysis: "Loop analysis", 61747ad3c4Slazypassion preopt: "Pre-legalization rewriting", 62108f7917Sbjorn3 egraph: "Egraph based optimizations", 63747ad3c4Slazypassion gvn: "Global value numbering", 64747ad3c4Slazypassion licm: "Loop invariant code motion", 65747ad3c4Slazypassion unreachable_code: "Remove unreachable blocks", 660bc0503fSJulian Seward remove_constant_phis: "Remove constant phi-nodes", 67747ad3c4Slazypassion 682b9fefe8SChris Fallin vcode_lower: "VCode lowering", 692b9fefe8SChris Fallin vcode_emit: "VCode emission", 702b9fefe8SChris Fallin vcode_emit_finish: "VCode emission finalization", 712b9fefe8SChris Fallin 72747ad3c4Slazypassion regalloc: "Register allocation", 730af8737eSChris Fallin regalloc_checker: "Register allocation symbolic verification", 74747ad3c4Slazypassion layout_renumber: "Layout full renumbering", 75747ad3c4Slazypassion 76747ad3c4Slazypassion canonicalize_nans: "Canonicalization of NaNs", 77747ad3c4Slazypassion } 78747ad3c4Slazypassion 79747ad3c4Slazypassion impl Pass { idx(self) -> usize80108f7917Sbjorn3 fn idx(self) -> usize { 81747ad3c4Slazypassion self as usize 82747ad3c4Slazypassion } 83108f7917Sbjorn3 84108f7917Sbjorn3 /// Description of the pass. description(self) -> &'static str85108f7917Sbjorn3 pub fn description(self) -> &'static str { 86108f7917Sbjorn3 match DESCRIPTIONS.get(self.idx()) { 87108f7917Sbjorn3 Some(s) => s, 88108f7917Sbjorn3 None => "<no pass>", 89108f7917Sbjorn3 } 90108f7917Sbjorn3 } 91747ad3c4Slazypassion } 92747ad3c4Slazypassion 93747ad3c4Slazypassion impl fmt::Display for Pass { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result94747ad3c4Slazypassion fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 95108f7917Sbjorn3 f.write_str(self.description()) 96747ad3c4Slazypassion } 97747ad3c4Slazypassion } 98747ad3c4Slazypassion 99108f7917Sbjorn3 /// A profiler. 100108f7917Sbjorn3 pub trait Profiler { 101108f7917Sbjorn3 /// Start a profiling pass. 102747ad3c4Slazypassion /// 103108f7917Sbjorn3 /// Will return a token which when dropped indicates the end of the pass. 104108f7917Sbjorn3 /// 105108f7917Sbjorn3 /// Multiple passes can be active at the same time, but they must be started and stopped in a 106108f7917Sbjorn3 /// LIFO fashion. start_pass(&self, pass: Pass) -> Box<dyn Any>107108f7917Sbjorn3 fn start_pass(&self, pass: Pass) -> Box<dyn Any>; 108108f7917Sbjorn3 } 109108f7917Sbjorn3 1104ab0fc48SJonas Kruckenberg /// The default profiler. You can get the results using [`take_current`]. 1114ab0fc48SJonas Kruckenberg pub struct DefaultProfiler; 1124ab0fc48SJonas Kruckenberg 1134ab0fc48SJonas Kruckenberg #[cfg(not(feature = "timing"))] 1144ab0fc48SJonas Kruckenberg pub(crate) use disabled::*; 1154ab0fc48SJonas Kruckenberg #[cfg(feature = "timing")] 1164ab0fc48SJonas Kruckenberg pub use enabled::*; 1174ab0fc48SJonas Kruckenberg 1184ab0fc48SJonas Kruckenberg #[cfg(feature = "timing")] 1194ab0fc48SJonas Kruckenberg mod enabled { 12090ac295eSAlex Crichton use super::{DESCRIPTIONS, DefaultProfiler, NUM_PASSES, Pass, Profiler}; 1210889323aSSSD use alloc::boxed::Box; 1220889323aSSSD use core::any::Any; 1230889323aSSSD use core::cell::{Cell, RefCell}; 1240889323aSSSD use core::fmt; 1250889323aSSSD use core::mem; 1260889323aSSSD use core::time::Duration; 1274ab0fc48SJonas Kruckenberg use std::time::Instant; 1284ab0fc48SJonas Kruckenberg 129108f7917Sbjorn3 // Information about passes in a single thread. 130108f7917Sbjorn3 thread_local! { 131108f7917Sbjorn3 static PROFILER: RefCell<Box<dyn Profiler>> = RefCell::new(Box::new(DefaultProfiler)); 132108f7917Sbjorn3 } 133108f7917Sbjorn3 134108f7917Sbjorn3 /// Set the profiler for the current thread. 135108f7917Sbjorn3 /// 136108f7917Sbjorn3 /// Returns the old profiler. set_thread_profiler(new_profiler: Box<dyn Profiler>) -> Box<dyn Profiler>137108f7917Sbjorn3 pub fn set_thread_profiler(new_profiler: Box<dyn Profiler>) -> Box<dyn Profiler> { 1380889323aSSSD PROFILER.with(|profiler| core::mem::replace(&mut *profiler.borrow_mut(), new_profiler)) 139108f7917Sbjorn3 } 140108f7917Sbjorn3 141108f7917Sbjorn3 /// Start timing `pass` as a child of the currently running pass, if any. 142108f7917Sbjorn3 /// 143108f7917Sbjorn3 /// This function is called by the publicly exposed pass functions. start_pass(pass: Pass) -> Box<dyn Any>1444ab0fc48SJonas Kruckenberg pub fn start_pass(pass: Pass) -> Box<dyn Any> { 145108f7917Sbjorn3 PROFILER.with(|profiler| profiler.borrow().start_pass(pass)) 146108f7917Sbjorn3 } 147747ad3c4Slazypassion 148747ad3c4Slazypassion /// Accumulated timing information for a single pass. 149747ad3c4Slazypassion #[derive(Default, Copy, Clone)] 150747ad3c4Slazypassion struct PassTime { 151747ad3c4Slazypassion /// Total time spent running this pass including children. 152747ad3c4Slazypassion total: Duration, 153747ad3c4Slazypassion 154747ad3c4Slazypassion /// Time spent running in child passes. 155747ad3c4Slazypassion child: Duration, 156747ad3c4Slazypassion } 157747ad3c4Slazypassion 158747ad3c4Slazypassion /// Accumulated timing for all passes. 159747ad3c4Slazypassion pub struct PassTimes { 160747ad3c4Slazypassion pass: [PassTime; NUM_PASSES], 161747ad3c4Slazypassion } 162747ad3c4Slazypassion 163713ce07dSAlex Crichton impl PassTimes { 164108f7917Sbjorn3 /// Add `other` to the timings of this `PassTimes`. add(&mut self, other: &Self)165108f7917Sbjorn3 pub fn add(&mut self, other: &Self) { 166108f7917Sbjorn3 for (a, b) in self.pass.iter_mut().zip(&other.pass[..]) { 167108f7917Sbjorn3 a.total += b.total; 168108f7917Sbjorn3 a.child += b.child; 169108f7917Sbjorn3 } 170108f7917Sbjorn3 } 171108f7917Sbjorn3 172713ce07dSAlex Crichton /// Returns the total amount of time taken by all the passes measured. total(&self) -> Duration173713ce07dSAlex Crichton pub fn total(&self) -> Duration { 174713ce07dSAlex Crichton self.pass.iter().map(|p| p.total - p.child).sum() 175713ce07dSAlex Crichton } 176713ce07dSAlex Crichton } 177713ce07dSAlex Crichton 178747ad3c4Slazypassion impl Default for PassTimes { default() -> Self179747ad3c4Slazypassion fn default() -> Self { 1809f506692SPeter Huene Self { 181747ad3c4Slazypassion pass: [Default::default(); NUM_PASSES], 182747ad3c4Slazypassion } 183747ad3c4Slazypassion } 184747ad3c4Slazypassion } 185747ad3c4Slazypassion 186747ad3c4Slazypassion impl fmt::Display for PassTimes { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result187747ad3c4Slazypassion fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 188747ad3c4Slazypassion writeln!(f, "======== ======== ==================================")?; 189747ad3c4Slazypassion writeln!(f, " Total Self Pass")?; 190747ad3c4Slazypassion writeln!(f, "-------- -------- ----------------------------------")?; 191747ad3c4Slazypassion for (time, desc) in self.pass.iter().zip(&DESCRIPTIONS[..]) { 192747ad3c4Slazypassion // Omit passes that haven't run. 193747ad3c4Slazypassion if time.total == Duration::default() { 194747ad3c4Slazypassion continue; 195747ad3c4Slazypassion } 196747ad3c4Slazypassion 197747ad3c4Slazypassion // Write a duration as secs.millis, trailing space. 198747ad3c4Slazypassion fn fmtdur(mut dur: Duration, f: &mut fmt::Formatter) -> fmt::Result { 199747ad3c4Slazypassion // Round to nearest ms by adding 500us. 200747ad3c4Slazypassion dur += Duration::new(0, 500_000); 201747ad3c4Slazypassion let ms = dur.subsec_millis(); 202747ad3c4Slazypassion write!(f, "{:4}.{:03} ", dur.as_secs(), ms) 203747ad3c4Slazypassion } 204747ad3c4Slazypassion 205747ad3c4Slazypassion fmtdur(time.total, f)?; 206747ad3c4Slazypassion if let Some(s) = time.total.checked_sub(time.child) { 207747ad3c4Slazypassion fmtdur(s, f)?; 208747ad3c4Slazypassion } 209a0442ea0SHamir Mahal writeln!(f, " {desc}")?; 210747ad3c4Slazypassion } 211747ad3c4Slazypassion writeln!(f, "======== ======== ==================================") 212747ad3c4Slazypassion } 213747ad3c4Slazypassion } 214747ad3c4Slazypassion 2156b854381SDan Gohman // Information about passes in a single thread. 216747ad3c4Slazypassion thread_local! { 217747ad3c4Slazypassion static PASS_TIME: RefCell<PassTimes> = RefCell::new(Default::default()); 218747ad3c4Slazypassion } 219747ad3c4Slazypassion 220494e2b89SAlex Crichton /// Take the current accumulated pass timings and reset the timings for the current thread. 221494e2b89SAlex Crichton /// 222494e2b89SAlex Crichton /// Only applies when [`DefaultProfiler`] is used. 223*94a5e2cdSChris Fallin /// 224*94a5e2cdSChris Fallin /// # Panics 225*94a5e2cdSChris Fallin /// 226*94a5e2cdSChris Fallin /// Panics when any pass timing token is currently active. take_current() -> PassTimes227494e2b89SAlex Crichton pub fn take_current() -> PassTimes { 228*94a5e2cdSChris Fallin assert_eq!( 229*94a5e2cdSChris Fallin CURRENT_PASS.with(|p| p.get()), 230*94a5e2cdSChris Fallin Pass::None, 231*94a5e2cdSChris Fallin "Cannot take_current() on profiler while a pass is active" 232*94a5e2cdSChris Fallin ); 233494e2b89SAlex Crichton PASS_TIME.with(|rc| mem::take(&mut *rc.borrow_mut())) 234494e2b89SAlex Crichton } 235494e2b89SAlex Crichton 236494e2b89SAlex Crichton // Information about passes in a single thread. 237494e2b89SAlex Crichton thread_local! { 238494e2b89SAlex Crichton static CURRENT_PASS: Cell<Pass> = const { Cell::new(Pass::None) }; 239*94a5e2cdSChris Fallin static LAST_INSTANT: Cell<Option<Instant>> = Cell::new(None); 240*94a5e2cdSChris Fallin } 241*94a5e2cdSChris Fallin 242*94a5e2cdSChris Fallin /// Provides a guaranteed-never-decreasing `Instant`. This is 243*94a5e2cdSChris Fallin /// supposed to be guaranteed by the operating system APIs that 244*94a5e2cdSChris Fallin /// are used to implement `Instant::now()`, but apparently 245*94a5e2cdSChris Fallin /// sometimes kernel bugs may result in this moving backward, and 246*94a5e2cdSChris Fallin /// if it does, our invariants here are violated, possibly 247*94a5e2cdSChris Fallin /// resulting in panics when we process nested time spans. So we 248*94a5e2cdSChris Fallin /// backstop against any bugs by tracking the last-returned 249*94a5e2cdSChris Fallin /// `Instant` and never regressing before it. monotonic_instant() -> Instant250*94a5e2cdSChris Fallin fn monotonic_instant() -> Instant { 251*94a5e2cdSChris Fallin let now = match LAST_INSTANT.get() { 252*94a5e2cdSChris Fallin None => Instant::now(), 253*94a5e2cdSChris Fallin Some(prev) => { 254*94a5e2cdSChris Fallin let system_now = Instant::now(); 255*94a5e2cdSChris Fallin core::cmp::max(prev, system_now) 256*94a5e2cdSChris Fallin } 257*94a5e2cdSChris Fallin }; 258*94a5e2cdSChris Fallin LAST_INSTANT.set(Some(now)); 259*94a5e2cdSChris Fallin now 260494e2b89SAlex Crichton } 261494e2b89SAlex Crichton 262108f7917Sbjorn3 impl Profiler for DefaultProfiler { start_pass(&self, pass: Pass) -> Box<dyn Any>263108f7917Sbjorn3 fn start_pass(&self, pass: Pass) -> Box<dyn Any> { 264747ad3c4Slazypassion let prev = CURRENT_PASS.with(|p| p.replace(pass)); 2652bac6574SAlex Crichton log::debug!("timing: Starting {pass}, (during {prev})"); 266108f7917Sbjorn3 Box::new(DefaultTimingToken { 267*94a5e2cdSChris Fallin start: monotonic_instant(), 268747ad3c4Slazypassion pass, 269747ad3c4Slazypassion prev, 270108f7917Sbjorn3 }) 271747ad3c4Slazypassion } 272747ad3c4Slazypassion } 273747ad3c4Slazypassion 274494e2b89SAlex Crichton /// A timing token is responsible for timing the currently running pass. Timing starts when it 275494e2b89SAlex Crichton /// is created and ends when it is dropped. 276494e2b89SAlex Crichton /// 277494e2b89SAlex Crichton /// Multiple passes can be active at the same time, but they must be started and stopped in a 278494e2b89SAlex Crichton /// LIFO fashion. 279494e2b89SAlex Crichton struct DefaultTimingToken { 280494e2b89SAlex Crichton /// Start time for this pass. 281494e2b89SAlex Crichton start: Instant, 282494e2b89SAlex Crichton 283494e2b89SAlex Crichton // Pass being timed by this token. 284494e2b89SAlex Crichton pass: Pass, 285494e2b89SAlex Crichton 286494e2b89SAlex Crichton // The previously active pass which will be restored when this token is dropped. 287494e2b89SAlex Crichton prev: Pass, 288494e2b89SAlex Crichton } 289494e2b89SAlex Crichton 290747ad3c4Slazypassion /// Dropping a timing token indicated the end of the pass. 291108f7917Sbjorn3 impl Drop for DefaultTimingToken { drop(&mut self)292747ad3c4Slazypassion fn drop(&mut self) { 293*94a5e2cdSChris Fallin let now = monotonic_instant(); 294*94a5e2cdSChris Fallin let duration = self.start.duration_since(now); 295dd6adfdbSNick Fitzgerald log::debug!("timing: Ending {}: {}ms", self.pass, duration.as_millis()); 296747ad3c4Slazypassion let old_cur = CURRENT_PASS.with(|p| p.replace(self.prev)); 297*94a5e2cdSChris Fallin assert_eq!(self.pass, old_cur, "Timing tokens dropped out of order"); 298747ad3c4Slazypassion PASS_TIME.with(|rc| { 299747ad3c4Slazypassion let mut table = rc.borrow_mut(); 300747ad3c4Slazypassion table.pass[self.pass.idx()].total += duration; 301747ad3c4Slazypassion if let Some(parent) = table.pass.get_mut(self.prev.idx()) { 302747ad3c4Slazypassion parent.child += duration; 303747ad3c4Slazypassion } 304747ad3c4Slazypassion }) 305747ad3c4Slazypassion } 306747ad3c4Slazypassion } 307494e2b89SAlex Crichton } 308747ad3c4Slazypassion 309494e2b89SAlex Crichton #[cfg(not(feature = "timing"))] 310494e2b89SAlex Crichton mod disabled { 311494e2b89SAlex Crichton use super::{DefaultProfiler, Pass, Profiler}; 3124ab0fc48SJonas Kruckenberg use alloc::boxed::Box; 3134ab0fc48SJonas Kruckenberg use core::any::Any; 314494e2b89SAlex Crichton 315494e2b89SAlex Crichton impl Profiler for DefaultProfiler { start_pass(&self, _pass: Pass) -> Box<dyn Any>316494e2b89SAlex Crichton fn start_pass(&self, _pass: Pass) -> Box<dyn Any> { 317494e2b89SAlex Crichton Box::new(()) 318494e2b89SAlex Crichton } 319494e2b89SAlex Crichton } 3204ab0fc48SJonas Kruckenberg start_pass(_pass: Pass) -> Box<dyn Any>3214ab0fc48SJonas Kruckenberg pub(crate) fn start_pass(_pass: Pass) -> Box<dyn Any> { 3224ab0fc48SJonas Kruckenberg Box::new(()) 3234ab0fc48SJonas Kruckenberg } 324747ad3c4Slazypassion } 325747ad3c4Slazypassion 326747ad3c4Slazypassion #[cfg(test)] 327747ad3c4Slazypassion mod tests { 328747ad3c4Slazypassion use super::*; 32910e226f9Sbjorn3 use alloc::string::ToString; 330747ad3c4Slazypassion 331747ad3c4Slazypassion #[test] display()332747ad3c4Slazypassion fn display() { 333747ad3c4Slazypassion assert_eq!(Pass::None.to_string(), "<no pass>"); 334747ad3c4Slazypassion assert_eq!(Pass::regalloc.to_string(), "Register allocation"); 335747ad3c4Slazypassion } 336747ad3c4Slazypassion } 337