Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/cranelift/codegen/src/timing.rs
1693 views
1
//! Pass timing.
2
//!
3
//! This modules provides facilities for timing the execution of individual compilation passes.
4
5
use alloc::boxed::Box;
6
use core::any::Any;
7
use 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.
18
macro_rules! define_passes {
19
($($pass:ident: $desc:expr,)+) => {
20
/// A single profiled pass.
21
#[expect(non_camel_case_types, reason = "macro-generated code")]
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.
45
define_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
79
impl 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
93
impl 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.
100
pub 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`].
111
pub struct DefaultProfiler;
112
113
#[cfg(not(feature = "timing"))]
114
pub(crate) use disabled::*;
115
#[cfg(feature = "timing")]
116
pub use enabled::*;
117
118
#[cfg(feature = "timing")]
119
mod enabled {
120
use super::{DESCRIPTIONS, DefaultProfiler, NUM_PASSES, Pass, Profiler};
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 {pass}, (during {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"))]
279
mod 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)]
296
mod 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
}
306
307