opt_lang/stats.rs
1//! What an optimization run did.
2
3use crate::pipeline::PassKind;
4
5/// How often one pass ran and how often it changed something.
6///
7/// # Examples
8///
9/// ```
10/// use ir_lang::{Linkage, Module, Signature, Type};
11/// use opt_lang::PassKind;
12///
13/// let mut m = Module::new("m");
14/// let f = m.declare_function("f", &Signature::new(&[], &[Type::I32]), Linkage::Export)?;
15/// let mut b = m.build(f)?;
16/// let dead = b.iconst(Type::I32, 1)?;
17/// let live = b.iconst(Type::I32, 2)?;
18/// b.ret(&[live])?;
19/// # let _ = dead;
20/// let stats = opt_lang::optimize(&mut m)?;
21/// let dce = stats.pass(PassKind::Dce);
22/// assert!(dce.runs() >= 1 && dce.changes() >= 1);
23/// # Ok::<(), Box<dyn std::error::Error>>(())
24/// ```
25#[derive(Clone, Copy, PartialEq, Eq, Debug)]
26pub struct PassStats {
27 kind: PassKind,
28 runs: u64,
29 changes: u64,
30}
31
32impl PassStats {
33 /// The pass.
34 ///
35 /// # Examples
36 ///
37 /// ```
38 /// use opt_lang::{PassKind, Stats};
39 ///
40 /// assert_eq!(Stats::default().pass(PassKind::Gvn).kind(), PassKind::Gvn);
41 /// ```
42 #[must_use]
43 pub const fn kind(&self) -> PassKind {
44 self.kind
45 }
46
47 /// How many times it ran (over all functions and iterations).
48 ///
49 /// # Examples
50 ///
51 /// ```
52 /// use opt_lang::{PassKind, Stats};
53 ///
54 /// assert_eq!(Stats::default().pass(PassKind::Sccp).runs(), 0);
55 /// ```
56 #[must_use]
57 pub const fn runs(&self) -> u64 {
58 self.runs
59 }
60
61 /// How many of those runs changed the function.
62 ///
63 /// # Examples
64 ///
65 /// ```
66 /// use opt_lang::{PassKind, Stats};
67 ///
68 /// assert_eq!(Stats::default().pass(PassKind::Licm).changes(), 0);
69 /// ```
70 #[must_use]
71 pub const fn changes(&self) -> u64 {
72 self.changes
73 }
74}
75
76/// The result of an optimization run: sizes before and after, iterations, and
77/// per-pass counts.
78///
79/// Sizes count live instructions (terminators excluded) and live blocks.
80///
81/// # Examples
82///
83/// ```
84/// use ir_lang::{CmpOp, Linkage, Module, Signature, Type};
85///
86/// // if 1 < 2 { ret 10 } else { ret 20 }: folds to `ret 10`.
87/// let mut m = Module::new("m");
88/// let f = m.declare_function("f", &Signature::new(&[], &[Type::I32]), Linkage::Export)?;
89/// let mut b = m.build(f)?;
90/// let (one, two) = (b.iconst(Type::I32, 1)?, b.iconst(Type::I32, 2)?);
91/// let c = b.compare(CmpOp::Lt, one, two)?;
92/// let (t, e) = (b.create_block(&[])?, b.create_block(&[])?);
93/// b.branch(c, t, &[], e, &[])?;
94/// b.switch_to(t)?;
95/// let ten = b.iconst(Type::I32, 10)?;
96/// b.ret(&[ten])?;
97/// b.switch_to(e)?;
98/// let twenty = b.iconst(Type::I32, 20)?;
99/// b.ret(&[twenty])?;
100///
101/// let stats = opt_lang::optimize(&mut m)?;
102/// assert_eq!(stats.functions(), 1);
103/// assert_eq!((stats.insts_before(), stats.blocks_before()), (5, 3));
104/// assert_eq!((stats.insts_after(), stats.blocks_after()), (1, 1));
105/// assert_eq!(stats.converged(), 1);
106/// # Ok::<(), Box<dyn std::error::Error>>(())
107/// ```
108#[derive(Clone, PartialEq, Eq, Debug)]
109pub struct Stats {
110 pub(crate) functions: usize,
111 pub(crate) insts_before: usize,
112 pub(crate) insts_after: usize,
113 pub(crate) blocks_before: usize,
114 pub(crate) blocks_after: usize,
115 pub(crate) iterations: usize,
116 pub(crate) converged: usize,
117 pub(crate) budget_exhausted: usize,
118 pub(crate) passes: [PassStats; PassKind::ALL.len()],
119}
120
121impl Default for Stats {
122 fn default() -> Self {
123 let mut passes = [PassStats {
124 kind: PassKind::Dce,
125 runs: 0,
126 changes: 0,
127 }; PassKind::ALL.len()];
128 for (slot, kind) in passes.iter_mut().zip(PassKind::ALL) {
129 slot.kind = kind;
130 }
131 Stats {
132 functions: 0,
133 insts_before: 0,
134 insts_after: 0,
135 blocks_before: 0,
136 blocks_after: 0,
137 iterations: 0,
138 converged: 0,
139 budget_exhausted: 0,
140 passes,
141 }
142 }
143}
144
145impl Stats {
146 pub(crate) fn record(&mut self, kind: PassKind, changed: bool) {
147 if let Some(p) = self.passes.iter_mut().find(|p| p.kind == kind) {
148 p.runs += 1;
149 p.changes += u64::from(changed);
150 }
151 }
152
153 pub(crate) fn merge(&mut self, other: &Stats) {
154 self.functions += other.functions;
155 self.insts_before += other.insts_before;
156 self.insts_after += other.insts_after;
157 self.blocks_before += other.blocks_before;
158 self.blocks_after += other.blocks_after;
159 self.iterations += other.iterations;
160 self.converged += other.converged;
161 self.budget_exhausted += other.budget_exhausted;
162 for (a, b) in self.passes.iter_mut().zip(other.passes.iter()) {
163 a.runs += b.runs;
164 a.changes += b.changes;
165 }
166 }
167
168 /// The number of function bodies optimized.
169 ///
170 /// See [`Stats`] for an example.
171 #[must_use]
172 pub const fn functions(&self) -> usize {
173 self.functions
174 }
175
176 /// Live instructions before optimization (terminators excluded).
177 ///
178 /// See [`Stats`] for an example.
179 #[must_use]
180 pub const fn insts_before(&self) -> usize {
181 self.insts_before
182 }
183
184 /// Live instructions after optimization (terminators excluded).
185 ///
186 /// See [`Stats`] for an example.
187 #[must_use]
188 pub const fn insts_after(&self) -> usize {
189 self.insts_after
190 }
191
192 /// Live blocks before optimization.
193 ///
194 /// See [`Stats`] for an example.
195 #[must_use]
196 pub const fn blocks_before(&self) -> usize {
197 self.blocks_before
198 }
199
200 /// Live blocks after optimization.
201 ///
202 /// See [`Stats`] for an example.
203 #[must_use]
204 pub const fn blocks_after(&self) -> usize {
205 self.blocks_after
206 }
207
208 /// Pipeline sweeps run, summed over functions (a sweep runs every pass once).
209 ///
210 /// # Examples
211 ///
212 /// ```
213 /// assert_eq!(opt_lang::Stats::default().iterations(), 0);
214 /// ```
215 #[must_use]
216 pub const fn iterations(&self) -> usize {
217 self.iterations
218 }
219
220 /// Functions whose last sweep changed nothing (they reached the pipeline's
221 /// fixpoint within the iteration limit).
222 ///
223 /// See [`Stats`] for an example.
224 #[must_use]
225 pub const fn converged(&self) -> usize {
226 self.converged
227 }
228
229 /// Functions where a pass was skipped or cut short because the budget ran
230 /// out. Those functions are valid but may be less optimized.
231 ///
232 /// # Examples
233 ///
234 /// ```
235 /// assert_eq!(opt_lang::Stats::default().budget_exhausted(), 0);
236 /// ```
237 #[must_use]
238 pub const fn budget_exhausted(&self) -> usize {
239 self.budget_exhausted
240 }
241
242 /// The counts of one pass.
243 ///
244 /// See [`PassStats`] for an example.
245 #[must_use]
246 pub fn pass(&self, kind: PassKind) -> PassStats {
247 self.passes
248 .iter()
249 .copied()
250 .find(|p| p.kind == kind)
251 .unwrap_or(PassStats {
252 kind,
253 runs: 0,
254 changes: 0,
255 })
256 }
257
258 /// The counts of every pass, in [`PassKind::ALL`] order.
259 ///
260 /// # Examples
261 ///
262 /// ```
263 /// assert_eq!(opt_lang::Stats::default().passes().len(), opt_lang::PassKind::ALL.len());
264 /// ```
265 #[must_use]
266 pub fn passes(&self) -> &[PassStats] {
267 &self.passes
268 }
269}