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