cranelift_codegen/
timing.rs1use core::fmt;
6use std::any::Any;
7use std::boxed::Box;
8use std::cell::RefCell;
9use std::mem;
10use std::time::Duration;
11
12macro_rules! define_passes {
22 ($($pass:ident: $desc:expr,)+) => {
23 #[allow(non_camel_case_types)]
25 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
26 pub enum Pass {
27 $(#[doc=$desc] $pass,)+
28 None,
30 }
31
32 pub const NUM_PASSES: usize = Pass::None as usize;
34
35 const DESCRIPTIONS: [&str; NUM_PASSES] = [ $($desc),+ ];
36
37 $(
38 #[doc=$desc]
39 #[must_use]
40 pub fn $pass() -> Box<dyn Any> {
41 start_pass(Pass::$pass)
42 }
43 )+
44 }
45}
46
47define_passes! {
49 process_file: "Processing test file",
52 parse_text: "Parsing textual Cranelift IR",
53 wasm_translate_module: "Translate WASM module",
54 wasm_translate_function: "Translate WASM function",
55
56 verifier: "Verify Cranelift IR",
57
58 compile: "Compilation passes",
59 try_incremental_cache: "Try loading from incremental cache",
60 store_incremental_cache: "Store in incremental cache",
61 flowgraph: "Control flow graph",
62 domtree: "Dominator tree",
63 loop_analysis: "Loop analysis",
64 preopt: "Pre-legalization rewriting",
65 egraph: "Egraph based optimizations",
66 gvn: "Global value numbering",
67 licm: "Loop invariant code motion",
68 unreachable_code: "Remove unreachable blocks",
69 remove_constant_phis: "Remove constant phi-nodes",
70
71 vcode_lower: "VCode lowering",
72 vcode_emit: "VCode emission",
73 vcode_emit_finish: "VCode emission finalization",
74
75 regalloc: "Register allocation",
76 regalloc_checker: "Register allocation symbolic verification",
77 layout_renumber: "Layout full renumbering",
78
79 canonicalize_nans: "Canonicalization of NaNs",
80}
81
82impl Pass {
83 fn idx(self) -> usize {
84 self as usize
85 }
86
87 pub fn description(self) -> &'static str {
89 match DESCRIPTIONS.get(self.idx()) {
90 Some(s) => s,
91 None => "<no pass>",
92 }
93 }
94}
95
96impl fmt::Display for Pass {
97 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
98 f.write_str(self.description())
99 }
100}
101
102pub trait Profiler {
104 fn start_pass(&self, pass: Pass) -> Box<dyn Any>;
111}
112
113thread_local! {
115 static PROFILER: RefCell<Box<dyn Profiler>> = RefCell::new(Box::new(DefaultProfiler));
116}
117
118pub fn set_thread_profiler(new_profiler: Box<dyn Profiler>) -> Box<dyn Profiler> {
122 PROFILER.with(|profiler| std::mem::replace(&mut *profiler.borrow_mut(), new_profiler))
123}
124
125fn start_pass(pass: Pass) -> Box<dyn Any> {
129 PROFILER.with(|profiler| profiler.borrow().start_pass(pass))
130}
131
132#[derive(Default, Copy, Clone)]
134struct PassTime {
135 total: Duration,
137
138 child: Duration,
140}
141
142pub struct PassTimes {
144 pass: [PassTime; NUM_PASSES],
145}
146
147impl PassTimes {
148 pub fn add(&mut self, other: &Self) {
150 for (a, b) in self.pass.iter_mut().zip(&other.pass[..]) {
151 a.total += b.total;
152 a.child += b.child;
153 }
154 }
155
156 pub fn total(&self) -> Duration {
158 self.pass.iter().map(|p| p.total - p.child).sum()
159 }
160}
161
162impl Default for PassTimes {
163 fn default() -> Self {
164 Self {
165 pass: [Default::default(); NUM_PASSES],
166 }
167 }
168}
169
170impl fmt::Display for PassTimes {
171 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
172 writeln!(f, "======== ======== ==================================")?;
173 writeln!(f, " Total Self Pass")?;
174 writeln!(f, "-------- -------- ----------------------------------")?;
175 for (time, desc) in self.pass.iter().zip(&DESCRIPTIONS[..]) {
176 if time.total == Duration::default() {
178 continue;
179 }
180
181 fn fmtdur(mut dur: Duration, f: &mut fmt::Formatter) -> fmt::Result {
183 dur += Duration::new(0, 500_000);
185 let ms = dur.subsec_millis();
186 write!(f, "{:4}.{:03} ", dur.as_secs(), ms)
187 }
188
189 fmtdur(time.total, f)?;
190 if let Some(s) = time.total.checked_sub(time.child) {
191 fmtdur(s, f)?;
192 }
193 writeln!(f, " {desc}")?;
194 }
195 writeln!(f, "======== ======== ==================================")
196 }
197}
198
199thread_local! {
201 static PASS_TIME: RefCell<PassTimes> = RefCell::new(Default::default());
202}
203
204pub struct DefaultProfiler;
206
207pub fn take_current() -> PassTimes {
211 PASS_TIME.with(|rc| mem::take(&mut *rc.borrow_mut()))
212}
213
214#[cfg(feature = "timing")]
215mod enabled {
216 use super::{DefaultProfiler, Pass, Profiler, PASS_TIME};
217 use std::any::Any;
218 use std::boxed::Box;
219 use std::cell::Cell;
220 use std::time::Instant;
221
222 thread_local! {
224 static CURRENT_PASS: Cell<Pass> = const { Cell::new(Pass::None) };
225 }
226
227 impl Profiler for DefaultProfiler {
228 fn start_pass(&self, pass: Pass) -> Box<dyn Any> {
229 let prev = CURRENT_PASS.with(|p| p.replace(pass));
230 log::debug!("timing: Starting {}, (during {})", pass, prev);
231 Box::new(DefaultTimingToken {
232 start: Instant::now(),
233 pass,
234 prev,
235 })
236 }
237 }
238
239 struct DefaultTimingToken {
245 start: Instant,
247
248 pass: Pass,
250
251 prev: Pass,
253 }
254
255 impl Drop for DefaultTimingToken {
257 fn drop(&mut self) {
258 let duration = self.start.elapsed();
259 log::debug!("timing: Ending {}: {}ms", self.pass, duration.as_millis());
260 let old_cur = CURRENT_PASS.with(|p| p.replace(self.prev));
261 debug_assert_eq!(self.pass, old_cur, "Timing tokens dropped out of order");
262 PASS_TIME.with(|rc| {
263 let mut table = rc.borrow_mut();
264 table.pass[self.pass.idx()].total += duration;
265 if let Some(parent) = table.pass.get_mut(self.prev.idx()) {
266 parent.child += duration;
267 }
268 })
269 }
270 }
271}
272
273#[cfg(not(feature = "timing"))]
274mod disabled {
275 use super::{DefaultProfiler, Pass, Profiler};
276 use std::any::Any;
277 use std::boxed::Box;
278
279 impl Profiler for DefaultProfiler {
280 fn start_pass(&self, _pass: Pass) -> Box<dyn Any> {
281 Box::new(())
282 }
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289 use alloc::string::ToString;
290
291 #[test]
292 fn display() {
293 assert_eq!(Pass::None.to_string(), "<no pass>");
294 assert_eq!(Pass::regalloc.to_string(), "Register allocation");
295 }
296}