pub struct PassManager<T> { /* private fields */ }Expand description
An ordered pipeline of passes over a unit of type T.
A PassManager holds passes in the order they were registered and runs them
over a unit. It is the scheduler, and scheduling is its only job: it never
reads or rewrites the unit itself — it only calls the passes, in order, and
records what they report. Registering a pass with add is the
plugin seam: any crate can contribute a transform by implementing
Pass<T> and adding it here.
Two ways to run a pipeline:
runmakes a single sweep — every pass once, in order. This is the common case.run_to_fixpointrepeats the sweep until a full pass over the pipeline changes nothing, or an iteration bound is reached. Use it when passes feed one another and a transform can expose further work (constant folding exposing dead code, which exposes more folding).
The manager is single-threaded by design: a pipeline is an inherently ordered sequence of mutations, so it carries no atomic overhead. Dispatch to a pass is one indirect call amortised over the whole unit that pass rewrites.
§Examples
use pass_lang::{Outcome, Pass, PassError, PassManager};
struct Double;
impl Pass<i64> for Double {
fn name(&self) -> &'static str { "double" }
fn run(&mut self, u: &mut i64) -> Result<Outcome, PassError> {
*u *= 2;
Ok(Outcome::Changed)
}
}
let mut pm = PassManager::new();
pm.add(Double).add(Double);
let mut unit = 3;
let report = pm.run(&mut unit).unwrap();
assert_eq!(unit, 12); // 3 -> 6 -> 12
assert_eq!(report.runs().len(), 2);Implementations§
Source§impl<T> PassManager<T>
impl<T> PassManager<T>
Sourcepub fn new() -> Self
pub fn new() -> Self
Create an empty pipeline.
§Examples
use pass_lang::PassManager;
let pm = PassManager::<i64>::new();
assert!(pm.is_empty());Sourcepub fn add(&mut self, pass: impl Pass<T> + 'static) -> &mut Self
pub fn add(&mut self, pass: impl Pass<T> + 'static) -> &mut Self
Register a pass at the end of the pipeline.
Returns &mut Self so registrations can be chained. This is the plugin
seam: the pass must be 'static, and it runs after every pass already
registered.
§Examples
use pass_lang::{Outcome, Pass, PassError, PassManager};
struct Noop(&'static str);
impl Pass<i64> for Noop {
fn name(&self) -> &'static str { self.0 }
fn run(&mut self, _: &mut i64) -> Result<Outcome, PassError> { Ok(Outcome::Unchanged) }
}
let mut pm = PassManager::new();
pm.add(Noop("first")).add(Noop("second"));
assert_eq!(pm.len(), 2);Sourcepub fn run(&mut self, unit: &mut T) -> Result<Report, PassError>
pub fn run(&mut self, unit: &mut T) -> Result<Report, PassError>
Run every pass once, in registration order.
Each pass transforms unit in place; the returned Report lists every
pass that ran with the Outcome it reported. If a pass
returns a PassError, the pipeline stops at that pass — later passes do
not run — and the error is returned with the failing pass’s name stamped
in.
The report’s converged is true when the
sweep changed nothing (the unit is already at a fixpoint for this
pipeline), and iterations is always 1.
§Errors
Returns the PassError of the first pass that fails.
§Examples
use pass_lang::{Outcome, Pass, PassError, PassManager};
struct FailIfNegative;
impl Pass<i64> for FailIfNegative {
fn name(&self) -> &'static str { "fail-if-negative" }
fn run(&mut self, u: &mut i64) -> Result<Outcome, PassError> {
if *u < 0 { return Err(PassError::new("unit went negative")); }
Ok(Outcome::Unchanged)
}
}
let mut pm = PassManager::new();
pm.add(FailIfNegative);
let mut unit = -1;
let err = pm.run(&mut unit).unwrap_err();
assert_eq!(err.pass(), "fail-if-negative");
assert_eq!(err.message(), "unit went negative");Sourcepub fn run_to_fixpoint(
&mut self,
unit: &mut T,
max_iters: usize,
) -> Result<Report, PassError>
pub fn run_to_fixpoint( &mut self, unit: &mut T, max_iters: usize, ) -> Result<Report, PassError>
Repeat the pipeline until it settles, or until max_iters sweeps run.
Each sweep runs every pass once, in order. After a sweep in which no pass
reported Changed, the unit is at a fixpoint
and the loop stops with converged true. If
max_iters sweeps run while the unit is still changing, the loop stops
with converged false — the bound guarantees termination even if a pass
oscillates. Passing max_iters == 0 performs no sweeps.
The Report accumulates every pass execution across every sweep, so its
runs length is the sum over sweeps (a short final
sweep on error aside).
§Errors
Returns the PassError of the first pass that fails, on whichever sweep
it fails.
§Examples
use pass_lang::{Outcome, Pass, PassError, PassManager};
// Halve toward 1; idempotent once it reaches 1.
struct Halve;
impl Pass<i64> for Halve {
fn name(&self) -> &'static str { "halve" }
fn run(&mut self, u: &mut i64) -> Result<Outcome, PassError> {
if *u <= 1 { return Ok(Outcome::Unchanged); }
*u /= 2;
Ok(Outcome::Changed)
}
}
let mut pm = PassManager::new();
pm.add(Halve);
let mut unit = 16;
let report = pm.run_to_fixpoint(&mut unit, 32).unwrap();
assert_eq!(unit, 1);
assert!(report.converged());