Skip to main content

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}