Skip to main content

pass_lang/
report.rs

1//! The record a [`PassManager`](crate::PassManager) run produces.
2
3use alloc::vec::Vec;
4
5use crate::pass::Outcome;
6
7/// One entry in a [`Report`]: a pass that ran and what it did.
8///
9/// Entries appear in execution order. When a pipeline is run to a fixpoint, a
10/// pass that runs on three sweeps contributes three entries, one per sweep.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize))]
13pub struct PassRun {
14    name: &'static str,
15    outcome: Outcome,
16}
17
18impl PassRun {
19    /// The [`name`](crate::Pass::name) of the pass that ran.
20    ///
21    /// # Examples
22    ///
23    /// ```
24    /// use pass_lang::{Outcome, Pass, PassError, PassManager};
25    ///
26    /// struct Touch;
27    /// impl Pass<i64> for Touch {
28    ///     fn name(&self) -> &'static str { "touch" }
29    ///     fn run(&mut self, u: &mut i64) -> Result<Outcome, PassError> {
30    ///         *u += 1;
31    ///         Ok(Outcome::Changed)
32    ///     }
33    /// }
34    ///
35    /// let mut pm = PassManager::new();
36    /// pm.add(Touch);
37    /// let mut unit = 0;
38    /// let report = pm.run(&mut unit).unwrap();
39    /// assert_eq!(report.runs()[0].name(), "touch");
40    /// ```
41    #[must_use]
42    #[inline]
43    pub fn name(&self) -> &'static str {
44        self.name
45    }
46
47    /// What the pass reported on that run.
48    #[must_use]
49    #[inline]
50    pub fn outcome(&self) -> Outcome {
51        self.outcome
52    }
53}
54
55/// A record of one [`PassManager`](crate::PassManager) run.
56///
57/// A `Report` is the observability hook: it lists every pass that ran, in order,
58/// with the [`Outcome`] each reported, and summarizes the run — how many sweeps
59/// it took, whether it reached a fixpoint, and how many passes changed the unit.
60/// It is produced by [`PassManager::run`](crate::PassManager::run) and
61/// [`PassManager::run_to_fixpoint`](crate::PassManager::run_to_fixpoint); you
62/// read it, you do not build it.
63///
64/// # Examples
65///
66/// ```
67/// use pass_lang::{Outcome, Pass, PassError, PassManager};
68///
69/// struct Halve;
70/// impl Pass<i64> for Halve {
71///     fn name(&self) -> &'static str { "halve" }
72///     fn run(&mut self, u: &mut i64) -> Result<Outcome, PassError> {
73///         if *u <= 1 { return Ok(Outcome::Unchanged); }
74///         *u /= 2;
75///         Ok(Outcome::Changed)
76///     }
77/// }
78///
79/// let mut pm = PassManager::new();
80/// pm.add(Halve);
81///
82/// let mut unit = 8;
83/// let report = pm.run_to_fixpoint(&mut unit, 16).unwrap();
84///
85/// assert_eq!(unit, 1);                 // 8 -> 4 -> 2 -> 1
86/// assert!(report.converged());         // a final sweep changed nothing
87/// assert_eq!(report.iterations(), 4);  // three halving sweeps + one confirming sweep
88/// assert_eq!(report.changes(), 3);     // three sweeps reported Changed
89/// ```
90#[derive(Debug, Clone, PartialEq, Eq, Default)]
91#[cfg_attr(feature = "serde", derive(serde::Serialize))]
92pub struct Report {
93    runs: Vec<PassRun>,
94    iterations: usize,
95    converged: bool,
96}
97
98impl Report {
99    /// Start a report sized for at least `capacity` pass executions, so a single
100    /// sweep records without reallocating its run list.
101    pub(crate) fn with_capacity(capacity: usize) -> Self {
102        Self {
103            runs: Vec::with_capacity(capacity),
104            iterations: 0,
105            converged: false,
106        }
107    }
108
109    /// Append the outcome of one pass execution.
110    #[inline]
111    pub(crate) fn record(&mut self, name: &'static str, outcome: Outcome) {
112        self.runs.push(PassRun { name, outcome });
113    }
114
115    /// Stamp the aggregate fields once the run is complete.
116    pub(crate) fn finalize(&mut self, iterations: usize, converged: bool) {
117        self.iterations = iterations;
118        self.converged = converged;
119    }
120
121    /// Every pass execution, in the order it happened.
122    ///
123    /// # Examples
124    ///
125    /// ```
126    /// use pass_lang::{Outcome, Pass, PassError, PassManager};
127    ///
128    /// struct A;
129    /// impl Pass<i64> for A {
130    ///     fn name(&self) -> &'static str { "a" }
131    ///     fn run(&mut self, _: &mut i64) -> Result<Outcome, PassError> { Ok(Outcome::Unchanged) }
132    /// }
133    /// struct B;
134    /// impl Pass<i64> for B {
135    ///     fn name(&self) -> &'static str { "b" }
136    ///     fn run(&mut self, _: &mut i64) -> Result<Outcome, PassError> { Ok(Outcome::Unchanged) }
137    /// }
138    ///
139    /// let mut pm = PassManager::new();
140    /// pm.add(A).add(B);
141    /// let mut unit = 0;
142    /// let report = pm.run(&mut unit).unwrap();
143    ///
144    /// let names: Vec<_> = report.runs().iter().map(|r| r.name()).collect();
145    /// assert_eq!(names, ["a", "b"]);
146    /// ```
147    #[must_use]
148    #[inline]
149    pub fn runs(&self) -> &[PassRun] {
150        &self.runs
151    }
152
153    /// How many pass executions reported [`Outcome::Changed`].
154    ///
155    /// # Examples
156    ///
157    /// ```
158    /// use pass_lang::{Outcome, Pass, PassError, PassManager};
159    ///
160    /// struct Inc;
161    /// impl Pass<i64> for Inc {
162    ///     fn name(&self) -> &'static str { "inc" }
163    ///     fn run(&mut self, u: &mut i64) -> Result<Outcome, PassError> {
164    ///         *u += 1;
165    ///         Ok(Outcome::Changed)
166    ///     }
167    /// }
168    ///
169    /// let mut pm = PassManager::new();
170    /// pm.add(Inc);
171    /// let mut unit = 0;
172    /// assert_eq!(pm.run(&mut unit).unwrap().changes(), 1);
173    /// ```
174    #[must_use]
175    pub fn changes(&self) -> usize {
176        self.runs.iter().filter(|r| r.outcome.changed()).count()
177    }
178
179    /// The number of full sweeps over the pipeline.
180    ///
181    /// [`PassManager::run`](crate::PassManager::run) always reports `1`.
182    /// [`PassManager::run_to_fixpoint`](crate::PassManager::run_to_fixpoint)
183    /// reports how many sweeps it actually performed.
184    #[must_use]
185    #[inline]
186    pub fn iterations(&self) -> usize {
187        self.iterations
188    }
189
190    /// Whether the final sweep made no change — the pipeline reached a fixpoint.
191    ///
192    /// For [`run`](crate::PassManager::run) this is `true` when the single sweep
193    /// changed nothing. For
194    /// [`run_to_fixpoint`](crate::PassManager::run_to_fixpoint) it is `true` when
195    /// a sweep settled before the iteration bound was hit, and `false` when the
196    /// bound was reached with the unit still changing.
197    #[must_use]
198    #[inline]
199    pub fn converged(&self) -> bool {
200        self.converged
201    }
202}
203
204#[cfg(test)]
205#[allow(clippy::expect_used, clippy::unwrap_used)]
206mod tests {
207    use super::*;
208
209    #[test]
210    fn test_empty_report_defaults() {
211        let report = Report::with_capacity(0);
212        assert!(report.runs().is_empty());
213        assert_eq!(report.changes(), 0);
214        assert_eq!(report.iterations(), 0);
215        assert!(!report.converged());
216    }
217
218    #[test]
219    fn test_record_appends_in_order() {
220        let mut report = Report::with_capacity(0);
221        report.record("a", Outcome::Changed);
222        report.record("b", Outcome::Unchanged);
223        let names: alloc::vec::Vec<_> = report.runs().iter().map(PassRun::name).collect();
224        assert_eq!(names, ["a", "b"]);
225    }
226
227    #[test]
228    fn test_changes_counts_only_changed() {
229        let mut report = Report::with_capacity(0);
230        report.record("a", Outcome::Changed);
231        report.record("b", Outcome::Unchanged);
232        report.record("c", Outcome::Changed);
233        assert_eq!(report.changes(), 2);
234    }
235
236    #[test]
237    fn test_finalize_sets_aggregate() {
238        let mut report = Report::with_capacity(0);
239        report.finalize(3, true);
240        assert_eq!(report.iterations(), 3);
241        assert!(report.converged());
242    }
243
244    #[test]
245    fn test_pass_run_accessors() {
246        let mut report = Report::with_capacity(0);
247        report.record("only", Outcome::Changed);
248        let run = report.runs()[0];
249        assert_eq!(run.name(), "only");
250        assert_eq!(run.outcome(), Outcome::Changed);
251    }
252}