cranelift_codegen/
timing.rs1use alloc::boxed::Box;
6use core::any::Any;
7use core::fmt;
8
9macro_rules! define_passes {
19 ($($pass:ident: $desc:expr,)+) => {
20 #[allow(non_camel_case_types)]
22 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
23 pub enum Pass {
24 $(#[doc=$desc] $pass,)+
25 None,
27 }
28
29 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
44define_passes! {
46 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 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
99pub trait Profiler {
101 fn start_pass(&self, pass: Pass) -> Box<dyn Any>;
108}
109
110pub 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 thread_local! {
131 static PROFILER: RefCell<Box<dyn Profiler>> = RefCell::new(Box::new(DefaultProfiler));
132 }
133
134 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 pub fn start_pass(pass: Pass) -> Box<dyn Any> {
145 PROFILER.with(|profiler| profiler.borrow().start_pass(pass))
146 }
147
148 #[derive(Default, Copy, Clone)]
150 struct PassTime {
151 total: Duration,
153
154 child: Duration,
156 }
157
158 pub struct PassTimes {
160 pass: [PassTime; NUM_PASSES],
161 }
162
163 impl PassTimes {
164 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 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 if time.total == Duration::default() {
194 continue;
195 }
196
197 fn fmtdur(mut dur: Duration, f: &mut fmt::Formatter) -> fmt::Result {
199 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 thread_local! {
217 static PASS_TIME: RefCell<PassTimes> = RefCell::new(Default::default());
218 }
219
220 pub fn take_current() -> PassTimes {
224 PASS_TIME.with(|rc| mem::take(&mut *rc.borrow_mut()))
225 }
226
227 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 struct DefaultTimingToken {
250 start: Instant,
252
253 pass: Pass,
255
256 prev: Pass,
258 }
259
260 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}