cranelift_codegen/
timing.rs

1//! Pass timing.
2//!
3//! This modules provides facilities for timing the execution of individual compilation passes.
4
5use alloc::boxed::Box;
6use core::any::Any;
7use core::fmt;
8
9// Each pass that can be timed is predefined with the `define_passes!` macro. Each pass has a
10// snake_case name and a plain text description used when printing out the timing report.
11//
12// This macro defines:
13//
14// - A C-style enum containing all the pass names and a `None` variant.
15// - A usize constant with the number of defined passes.
16// - A const array of pass descriptions.
17// - A public function per pass used to start the timing of that pass.
18macro_rules! define_passes {
19    ($($pass:ident: $desc:expr,)+) => {
20        /// A single profiled pass.
21        #[allow(non_camel_case_types)]
22        #[derive(Clone, Copy, Debug, PartialEq, Eq)]
23        pub enum Pass {
24            $(#[doc=$desc] $pass,)+
25            /// No active pass.
26            None,
27        }
28
29        /// The amount of profiled passes.
30        pub const NUM_PASSES: usize = Pass::None as usize;
31
32        const DESCRIPTIONS: [&str; NUM_PASSES] = [ $($desc),+ ];
33
34        $(
35            #[doc=$desc]
36            #[must_use]
37            pub fn $pass() -> Box<dyn Any> {
38                start_pass(Pass::$pass)
39            }
40        )+
41    }
42}
43
44// Pass definitions.
45define_passes! {
46    // All these are used in other crates but defined here so they appear in the unified
47    // `PassTimes` output.
48    process_file: "Processing test file",
49    parse_text: "Parsing textual Cranelift IR",
50    wasm_translate_module: "Translate WASM module",
51    wasm_translate_function: "Translate WASM function",
52
53    verifier: "Verify Cranelift IR",
54
55    compile: "Compilation passes",
56    try_incremental_cache: "Try loading from incremental cache",
57    store_incremental_cache: "Store in incremental cache",
58    flowgraph: "Control flow graph",
59    domtree: "Dominator tree",
60    loop_analysis: "Loop analysis",
61    preopt: "Pre-legalization rewriting",
62    egraph: "Egraph based optimizations",
63    gvn: "Global value numbering",
64    licm: "Loop invariant code motion",
65    unreachable_code: "Remove unreachable blocks",
66    remove_constant_phis: "Remove constant phi-nodes",
67
68    vcode_lower: "VCode lowering",
69    vcode_emit: "VCode emission",
70    vcode_emit_finish: "VCode emission finalization",
71
72    regalloc: "Register allocation",
73    regalloc_checker: "Register allocation symbolic verification",
74    layout_renumber: "Layout full renumbering",
75
76    canonicalize_nans: "Canonicalization of NaNs",
77}
78
79impl Pass {
80    fn idx(self) -> usize {
81        self as usize
82    }
83
84    /// Description of the pass.
85    pub fn description(self) -> &'static str {
86        match DESCRIPTIONS.get(self.idx()) {
87            Some(s) => s,
88            None => "<no pass>",
89        }
90    }
91}
92
93impl fmt::Display for Pass {
94    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
95        f.write_str(self.description())
96    }
97}
98
99/// A profiler.
100pub trait Profiler {
101    /// Start a profiling pass.
102    ///
103    /// Will return a token which when dropped indicates the end of the pass.
104    ///
105    /// Multiple passes can be active at the same time, but they must be started and stopped in a
106    /// LIFO fashion.
107    fn start_pass(&self, pass: Pass) -> Box<dyn Any>;
108}
109
110/// The default profiler. You can get the results using [`take_current`].
111pub struct DefaultProfiler;
112
113#[cfg(not(feature = "timing"))]
114pub(crate) use disabled::*;
115#[cfg(feature = "timing")]
116pub use enabled::*;
117
118#[cfg(feature = "timing")]
119mod enabled {
120    use super::{DefaultProfiler, Pass, Profiler, DESCRIPTIONS, NUM_PASSES};
121    use std::any::Any;
122    use std::boxed::Box;
123    use std::cell::{Cell, RefCell};
124    use std::fmt;
125    use std::mem;
126    use std::time::Duration;
127    use std::time::Instant;
128
129    // Information about passes in a single thread.
130    thread_local! {
131        static PROFILER: RefCell<Box<dyn Profiler>> = RefCell::new(Box::new(DefaultProfiler));
132    }
133
134    /// Set the profiler for the current thread.
135    ///
136    /// Returns the old profiler.
137    pub fn set_thread_profiler(new_profiler: Box<dyn Profiler>) -> Box<dyn Profiler> {
138        PROFILER.with(|profiler| std::mem::replace(&mut *profiler.borrow_mut(), new_profiler))
139    }
140
141    /// Start timing `pass` as a child of the currently running pass, if any.
142    ///
143    /// This function is called by the publicly exposed pass functions.
144    pub fn start_pass(pass: Pass) -> Box<dyn Any> {
145        PROFILER.with(|profiler| profiler.borrow().start_pass(pass))
146    }
147
148    /// Accumulated timing information for a single pass.
149    #[derive(Default, Copy, Clone)]
150    struct PassTime {
151        /// Total time spent running this pass including children.
152        total: Duration,
153
154        /// Time spent running in child passes.
155        child: Duration,
156    }
157
158    /// Accumulated timing for all passes.
159    pub struct PassTimes {
160        pass: [PassTime; NUM_PASSES],
161    }
162
163    impl PassTimes {
164        /// Add `other` to the timings of this `PassTimes`.
165        pub fn add(&mut self, other: &Self) {
166            for (a, b) in self.pass.iter_mut().zip(&other.pass[..]) {
167                a.total += b.total;
168                a.child += b.child;
169            }
170        }
171
172        /// Returns the total amount of time taken by all the passes measured.
173        pub fn total(&self) -> Duration {
174            self.pass.iter().map(|p| p.total - p.child).sum()
175        }
176    }
177
178    impl Default for PassTimes {
179        fn default() -> Self {
180            Self {
181                pass: [Default::default(); NUM_PASSES],
182            }
183        }
184    }
185
186    impl fmt::Display for PassTimes {
187        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
188            writeln!(f, "======== ========  ==================================")?;
189            writeln!(f, "   Total     Self  Pass")?;
190            writeln!(f, "-------- --------  ----------------------------------")?;
191            for (time, desc) in self.pass.iter().zip(&DESCRIPTIONS[..]) {
192                // Omit passes that haven't run.
193                if time.total == Duration::default() {
194                    continue;
195                }
196
197                // Write a duration as secs.millis, trailing space.
198                fn fmtdur(mut dur: Duration, f: &mut fmt::Formatter) -> fmt::Result {
199                    // Round to nearest ms by adding 500us.
200                    dur += Duration::new(0, 500_000);
201                    let ms = dur.subsec_millis();
202                    write!(f, "{:4}.{:03} ", dur.as_secs(), ms)
203                }
204
205                fmtdur(time.total, f)?;
206                if let Some(s) = time.total.checked_sub(time.child) {
207                    fmtdur(s, f)?;
208                }
209                writeln!(f, " {desc}")?;
210            }
211            writeln!(f, "======== ========  ==================================")
212        }
213    }
214
215    // Information about passes in a single thread.
216    thread_local! {
217        static PASS_TIME: RefCell<PassTimes> = RefCell::new(Default::default());
218    }
219
220    /// Take the current accumulated pass timings and reset the timings for the current thread.
221    ///
222    /// Only applies when [`DefaultProfiler`] is used.
223    pub fn take_current() -> PassTimes {
224        PASS_TIME.with(|rc| mem::take(&mut *rc.borrow_mut()))
225    }
226
227    // Information about passes in a single thread.
228    thread_local! {
229        static CURRENT_PASS: Cell<Pass> = const { Cell::new(Pass::None) };
230    }
231
232    impl Profiler for DefaultProfiler {
233        fn start_pass(&self, pass: Pass) -> Box<dyn Any> {
234            let prev = CURRENT_PASS.with(|p| p.replace(pass));
235            log::debug!("timing: Starting {}, (during {})", pass, prev);
236            Box::new(DefaultTimingToken {
237                start: Instant::now(),
238                pass,
239                prev,
240            })
241        }
242    }
243
244    /// A timing token is responsible for timing the currently running pass. Timing starts when it
245    /// is created and ends when it is dropped.
246    ///
247    /// Multiple passes can be active at the same time, but they must be started and stopped in a
248    /// LIFO fashion.
249    struct DefaultTimingToken {
250        /// Start time for this pass.
251        start: Instant,
252
253        // Pass being timed by this token.
254        pass: Pass,
255
256        // The previously active pass which will be restored when this token is dropped.
257        prev: Pass,
258    }
259
260    /// Dropping a timing token indicated the end of the pass.
261    impl Drop for DefaultTimingToken {
262        fn drop(&mut self) {
263            let duration = self.start.elapsed();
264            log::debug!("timing: Ending {}: {}ms", self.pass, duration.as_millis());
265            let old_cur = CURRENT_PASS.with(|p| p.replace(self.prev));
266            debug_assert_eq!(self.pass, old_cur, "Timing tokens dropped out of order");
267            PASS_TIME.with(|rc| {
268                let mut table = rc.borrow_mut();
269                table.pass[self.pass.idx()].total += duration;
270                if let Some(parent) = table.pass.get_mut(self.prev.idx()) {
271                    parent.child += duration;
272                }
273            })
274        }
275    }
276}
277
278#[cfg(not(feature = "timing"))]
279mod disabled {
280    use super::{DefaultProfiler, Pass, Profiler};
281    use alloc::boxed::Box;
282    use core::any::Any;
283
284    impl Profiler for DefaultProfiler {
285        fn start_pass(&self, _pass: Pass) -> Box<dyn Any> {
286            Box::new(())
287        }
288    }
289
290    pub(crate) fn start_pass(_pass: Pass) -> Box<dyn Any> {
291        Box::new(())
292    }
293}
294
295#[cfg(test)]
296mod tests {
297    use super::*;
298    use alloc::string::ToString;
299
300    #[test]
301    fn display() {
302        assert_eq!(Pass::None.to_string(), "<no pass>");
303        assert_eq!(Pass::regalloc.to_string(), "Register allocation");
304    }
305}