Rust · 6011 bytes Raw Blame History
1 //! Pass trait + PassManager.
2 //!
3 //! Every optimization pass implements `Pass` and is registered with a
4 //! `PassManager`. The manager runs passes to fixpoint, verifying the IR
5 //! after every pass so that any miscompile is caught immediately.
6
7 use crate::ir::inst::Module;
8 use crate::ir::verify::{verify_module, VerifyError};
9
10 /// A single optimization pass.
11 pub trait Pass {
12 /// Human-readable pass name (used in diagnostics + IR dumps).
13 fn name(&self) -> &'static str;
14
15 /// Run the pass over the module. Return `true` if the IR was modified.
16 fn run(&self, module: &mut Module) -> bool;
17 }
18
19 /// Result of a pass-manager run.
20 #[derive(Debug)]
21 pub struct PassRunResult {
22 /// Total number of times any pass reported "changed".
23 pub change_count: usize,
24 /// Number of fixpoint iterations performed.
25 pub iterations: usize,
26 }
27
28 /// A linearly-ordered collection of passes that are run to fixpoint.
29 pub struct PassManager {
30 passes: Vec<Box<dyn Pass>>,
31 /// Run `verify_module` after every pass and panic on failure.
32 /// Always-on in debug builds; can be disabled for benchmarking.
33 pub verify_after_each: bool,
34 /// Maximum fixpoint iterations before bailing out (defensive).
35 pub max_iterations: usize,
36 }
37
38 impl PassManager {
39 pub fn new() -> Self {
40 Self {
41 passes: Vec::new(),
42 verify_after_each: true,
43 max_iterations: 32,
44 }
45 }
46
47 /// Add a pass to the end of the pipeline.
48 pub fn add(&mut self, pass: Box<dyn Pass>) {
49 self.passes.push(pass);
50 }
51
52 /// Number of passes registered.
53 pub fn len(&self) -> usize {
54 self.passes.len()
55 }
56
57 #[cfg(test)]
58 pub(crate) fn pass_names(&self) -> Vec<&'static str> {
59 self.passes.iter().map(|pass| pass.name()).collect()
60 }
61
62 pub fn is_empty(&self) -> bool {
63 self.passes.is_empty()
64 }
65
66 /// Run all passes to fixpoint over the module.
67 ///
68 /// Each iteration runs every pass once. We keep iterating as long as
69 /// at least one pass reported a change in the previous round, up to
70 /// `max_iterations`.
71 pub fn run(&self, module: &mut Module) -> PassRunResult {
72 let mut change_count = 0usize;
73 let mut iterations = 0usize;
74
75 // Verify on entry — bad input shouldn't be blamed on a pass.
76 self.verify_or_panic(module, "<entry>");
77
78 let mut changed = true;
79 while changed && iterations < self.max_iterations {
80 changed = false;
81 iterations += 1;
82 for pass in &self.passes {
83 let pass_changed = pass.run(module);
84 if pass_changed {
85 change_count += 1;
86 changed = true;
87 // Rebuild O(1) type caches after IR mutations.
88 for func in &mut module.functions {
89 func.rebuild_type_cache();
90 }
91 }
92 if self.verify_after_each {
93 self.verify_or_panic(module, pass.name());
94 }
95 }
96 }
97
98 PassRunResult {
99 change_count,
100 iterations,
101 }
102 }
103
104 fn verify_or_panic(&self, module: &Module, after: &str) {
105 if !self.verify_after_each {
106 return;
107 }
108 let errors: Vec<VerifyError> = verify_module(module);
109 if !errors.is_empty() {
110 let mut msg = format!("IR verifier failed after pass `{}`:\n", after);
111 for e in &errors {
112 msg.push_str(" - ");
113 msg.push_str(&e.msg);
114 msg.push('\n');
115 }
116 panic!("{}", msg);
117 }
118 }
119 }
120
121 impl Default for PassManager {
122 fn default() -> Self {
123 Self::new()
124 }
125 }
126
127 #[cfg(test)]
128 mod tests {
129 use super::*;
130 use crate::ir::inst::*;
131 use crate::ir::types::IrType;
132
133 /// A pass that does nothing — used to test infrastructure plumbing.
134 struct NoopPass;
135 impl Pass for NoopPass {
136 fn name(&self) -> &'static str {
137 "noop"
138 }
139 fn run(&self, _module: &mut Module) -> bool {
140 false
141 }
142 }
143
144 /// A pass that claims it changed once, then is idle.
145 struct OneShotPass {
146 fired: std::cell::Cell<bool>,
147 }
148 impl Pass for OneShotPass {
149 fn name(&self) -> &'static str {
150 "oneshot"
151 }
152 fn run(&self, _module: &mut Module) -> bool {
153 if self.fired.get() {
154 false
155 } else {
156 self.fired.set(true);
157 true
158 }
159 }
160 }
161
162 fn empty_module() -> Module {
163 let mut m = Module::new("t".into());
164 let mut f = Function::new("f".into(), vec![], IrType::Void);
165 // Entry block needs a terminator to be valid.
166 let entry = f.entry;
167 f.block_mut(entry).terminator = Some(Terminator::Return(None));
168 m.add_function(f);
169 m
170 }
171
172 #[test]
173 fn empty_pipeline_runs_one_iteration() {
174 let pm = PassManager::new();
175 let mut m = empty_module();
176 let r = pm.run(&mut m);
177 assert_eq!(r.change_count, 0);
178 // One iteration to discover nothing changed.
179 assert_eq!(r.iterations, 1);
180 }
181
182 #[test]
183 fn noop_pass_terminates() {
184 let mut pm = PassManager::new();
185 pm.add(Box::new(NoopPass));
186 let mut m = empty_module();
187 let r = pm.run(&mut m);
188 assert_eq!(r.change_count, 0);
189 // First iteration runs every pass; since none change, we're done.
190 assert_eq!(r.iterations, 1);
191 }
192
193 #[test]
194 fn oneshot_runs_then_terminates() {
195 let mut pm = PassManager::new();
196 pm.add(Box::new(OneShotPass {
197 fired: std::cell::Cell::new(false),
198 }));
199 let mut m = empty_module();
200 let r = pm.run(&mut m);
201 assert_eq!(r.change_count, 1);
202 // Iter 1 changed → iter 2 stable.
203 assert_eq!(r.iterations, 2);
204 }
205 }
206