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