analyssa/scheduling/pass.rs
1//! [`SsaPass`] trait and supporting types ([`ModificationScope`],
2//! [`SsaPassHost`]) for declaring and implementing SSA transformation
3//! passes.
4//!
5//! A pass is a target-agnostic transformation that runs on a single
6//! method's SSA (or globally across all methods) and reports whether
7//! it made changes. The scheduler in [`super::scheduler`] orchestrates
8//! execution: layer assignment from capability dependencies, per-layer
9//! fixpoint, parallel per-method dispatch via rayon, and
10//! modification-scope-driven repair after each pass.
11
12use crate::{
13 error::Result,
14 events::EventListener,
15 host::{DirtySet, SsaStore},
16 ir::function::SsaFunction,
17 pointer::PointerSize,
18 target::Target,
19 world::World,
20};
21
22/// Combined host surface that the scheduler exposes to passes.
23///
24/// Scheduler hosts implement [`World<T>`], [`SsaStore<T>`], and
25/// [`DirtySet<T>`] separately, plus this trait to surface the event
26/// sink and pointer size. The scheduler hands passes a
27/// `&dyn SsaPassHost<T>` so they can read the call graph, look up peer
28/// methods' SSA, mark methods dirty, and record events without knowing
29/// the concrete host type.
30pub trait SsaPassHost<T: Target>: World<T> + SsaStore<T> + DirtySet<T> + Sync {
31 /// Returns the event sink for transformation events recorded by passes.
32 ///
33 /// A trait object rather than a concrete [`EventLog<T>`], so a host can
34 /// choose its sink. [`EventLog<T>`] is an append-only `boxcar::Vec` with no
35 /// bound and no way to drain it, so naming it concretely would force a host
36 /// that never consumes events to retain every one for the process lifetime.
37 /// [`NullListener`] discards them instead.
38 ///
39 /// [`EventLog<T>`]: crate::events::EventLog
40 /// [`NullListener`]: crate::events::NullListener
41 fn events(&self) -> &dyn EventListener<T>;
42
43 /// Returns the pointer width of the target's runtime.
44 ///
45 /// Used by passes that need to know the host's address size (e.g.,
46 /// predicate evaluation, reassociation, jump threading). Hosts that
47 /// do not care can accept the default of [`PointerSize::Bit64`].
48 fn ptr_size(&self) -> PointerSize {
49 PointerSize::Bit64
50 }
51}
52
53/// Describes the extent of modifications a pass makes to the SSA function.
54///
55/// The scheduler uses this to select the minimum repair necessary after a
56/// pass runs, avoiding expensive full SSA reconstruction when it is not
57/// needed. Passes should declare the **tightest** scope that covers all
58/// their modifications.
59#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
60pub enum ModificationScope {
61 /// The pass only replaces uses of variables with other existing
62 /// variables.
63 ///
64 /// SSA invariants are preserved automatically — no repair is needed
65 /// after the pass. Example: GVN forwarding redundant uses to earlier
66 /// defs.
67 UsesOnly,
68
69 /// The pass replaces or removes instructions but does not change
70 /// the CFG.
71 ///
72 /// After this scope, the scheduler runs a lightweight repair to strip
73 /// `Nop`s, recompute variable metadata, and eliminate trivial phis.
74 /// No dominator or dominance-frontier recomputation is needed.
75 /// Examples: constant propagation, copy propagation, dead-code
76 /// elimination, algebraic simplification, strength reduction.
77 InstructionsOnly,
78
79 /// The pass may add or remove blocks, change predecessors or
80 /// successors, or otherwise modify the control-flow graph.
81 ///
82 /// After this scope, the scheduler runs full SSA reconstruction
83 /// (recompute dominators, place phis, rename variables). Examples:
84 /// control flow unflattening, jump threading, block merging, loop
85 /// canonicalization, inlining.
86 CfgModifying,
87}
88
89/// An SSA transformation pass.
90///
91/// Generic over both the target `T` and the host adapter `H`. The host
92/// type must implement [`SsaPassHost<T>`] and is fixed by the
93/// [`PassScheduler`](crate::scheduling::PassScheduler) instance — all
94/// passes registered with that scheduler see the same host type.
95///
96/// # Concurrency
97///
98/// Passes must be `Send + Sync` so the scheduler can run them in
99/// parallel across methods via rayon. Mutation of pass state happens
100/// only in [`initialize`](Self::initialize) /
101/// [`finalize`](Self::finalize) (single-threaded boundary calls);
102/// per-method work uses `&self` and must rely on interior mutability
103/// for any cross-method state.
104///
105/// # Implementing a pass
106///
107/// Most analyssa-side passes have a pure-function body in `crate::passes`
108/// and a one-line trait impl in the scheduling sub-module:
109///
110/// ```text
111/// impl<T: Target, H: SsaPassHost<T>> SsaPass<T, H> for MyPass {
112/// fn name(&self) -> &'static str { "my-pass" }
113/// fn run_on_method(&self, ssa: &mut SsaFunction<T>, method: &T::MethodRef, host: &H) -> Result<bool> {
114/// Ok(passes::my_pass::run(ssa, method, host.events()))
115/// }
116/// }
117/// ```
118///
119/// Hosts that ship target-specific passes (e.g., CIL inlining) write
120/// impls bounded on a host extension trait, giving the impl access to
121/// host-specific methods while still being storable in a
122/// `Box<dyn SsaPass<CilTarget, ConcreteCilHost>>`.
123pub trait SsaPass<T: Target, H: SsaPassHost<T>>: Send + Sync {
124 /// Returns a unique short name for logging and debugging.
125 fn name(&self) -> &'static str;
126
127 /// Returns a human-readable description of the pass.
128 fn description(&self) -> &'static str {
129 "No description available"
130 }
131
132 /// Determines whether this pass should run on a specific method.
133 ///
134 /// Called before [`run_on_method`](Self::run_on_method). Override to
135 /// skip methods that do not need this pass (e.g., already processed,
136 /// too small to be interesting). Default implementation returns `true`.
137 fn should_run(&self, _method: &T::MethodRef, _host: &H) -> bool {
138 true
139 }
140
141 /// Run the pass on a single method's SSA.
142 ///
143 /// Events are recorded directly to `host.events()`.
144 ///
145 /// # Arguments
146 ///
147 /// * `ssa` — The SSA function to transform in place.
148 /// * `method` — Opaque reference identifying the method being processed.
149 /// * `host` — The host adapter for reading the call graph, looking up
150 /// peer SSA, marking methods dirty, and recording events.
151 ///
152 /// # Returns
153 ///
154 /// `Ok(true)` if any changes were made, `Ok(false)` if no changes.
155 ///
156 /// # Errors
157 ///
158 /// Returns `Err` if the pass fails to process the method (e.g.,
159 /// malformed SSA, unexpected opcode).
160 fn run_on_method(
161 &self,
162 ssa: &mut SsaFunction<T>,
163 method: &T::MethodRef,
164 host: &H,
165 ) -> Result<bool>;
166
167 /// Run the pass on the entire program (interprocedural passes).
168 ///
169 /// Override for passes that need to see all methods simultaneously,
170 /// such as dead-method elimination or whole-program constant
171 /// propagation.
172 ///
173 /// # Arguments
174 ///
175 /// * `host` — The host adapter providing program-wide state.
176 ///
177 /// # Returns
178 ///
179 /// `Ok(true)` if any changes were made, `Ok(false)` if no changes.
180 ///
181 /// # Errors
182 ///
183 /// Returns `Err` if the pass fails.
184 fn run_global(&self, _host: &H) -> Result<bool> {
185 Ok(false)
186 }
187
188 /// Returns whether this pass operates globally (across all methods).
189 ///
190 /// When `true`, the scheduler invokes [`run_global`](Self::run_global)
191 /// instead of iterating per-method with
192 /// [`run_on_method`](Self::run_on_method).
193 fn is_global(&self) -> bool {
194 false
195 }
196
197 /// Called once before the pass runs in a phase.
198 ///
199 /// Use this to initialize pass-specific state or caches. The default
200 /// implementation is a no-op.
201 ///
202 /// # Errors
203 ///
204 /// Returns an error if initialization fails.
205 fn initialize(&mut self, _host: &H) -> Result<()> {
206 Ok(())
207 }
208
209 /// Called once after the pass completes in a phase.
210 ///
211 /// Use this to clean up pass-specific state. The default
212 /// implementation is a no-op.
213 ///
214 /// # Errors
215 ///
216 /// Returns an error if finalization fails.
217 fn finalize(&mut self, _host: &H) -> Result<()> {
218 Ok(())
219 }
220
221 /// Declares the extent of modifications this pass makes.
222 ///
223 /// Default is [`ModificationScope::CfgModifying`] (conservative).
224 /// Override for passes that do not modify the CFG so the scheduler
225 /// can apply a lighter-weight repair.
226 fn modification_scope(&self) -> ModificationScope {
227 ModificationScope::CfgModifying
228 }
229
230 /// Returns whether the pass performs its own SSA boundary repair.
231 ///
232 /// Passes that wrap their mutation body in
233 /// [`SsaFunction::edit`](crate::ir::function::SsaFunction::edit) should
234 /// return `true` so the scheduler does not run a second repair step on
235 /// the same method. The pass must still report an accurate
236 /// [`modification_scope`](Self::modification_scope) for ordering,
237 /// documentation, and hosts that inspect pass metadata.
238 fn repairs_ssa(&self) -> bool {
239 false
240 }
241
242 /// Returns the capabilities this pass provides after execution.
243 ///
244 /// The scheduler ensures that consumers of a capability are placed in
245 /// later layers than providers.
246 fn provides(&self) -> &[T::Capability] {
247 &[]
248 }
249
250 /// Returns the capabilities this pass requires before it can run.
251 ///
252 /// Unsatisfied requirements (no provider registered) fall back to
253 /// phase-based ordering rather than blocking the pass.
254 fn requires(&self) -> &[T::Capability] {
255 &[]
256 }
257
258 /// Returns whether this pass reads other methods' SSA during
259 /// [`run_on_method`](Self::run_on_method).
260 ///
261 /// During parallel execution, the scheduler must keep each method's
262 /// SSA visible in the store so other threads can read it. When this
263 /// returns `true`, the scheduler clones the SSA before processing
264 /// (instead of removing it) so concurrent visibility is preserved.
265 /// Passes that only modify their own method should keep the default
266 /// `false` to avoid the clone overhead.
267 fn reads_peer_ssa(&self) -> bool {
268 false
269 }
270
271 /// Returns whether this pass requires a full scan of all methods
272 /// every iteration.
273 ///
274 /// If `true`, the scheduler dispatches to every method with SSA,
275 /// regardless of dirty-tracking state. If `false` (default), only
276 /// dirty methods are processed.
277 fn requires_full_scan(&self) -> bool {
278 false
279 }
280
281 /// Returns the fallback execution layer for this pass.
282 ///
283 /// Used when capability dependencies do not constrain the pass's
284 /// position. Hosts assign meaningful numbers (e.g., CIL uses
285 /// Structure=0, Value=1, Simplify=2, Inline=3 by convention).
286 ///
287 /// Normalize-style passes (DCE, GVN, constant propagation) typically
288 /// use layer 0 and are registered via the scheduler's `add_normalize`
289 /// API, which takes them out of the layered execution and runs them
290 /// between every layer's fixpoint iterations.
291 fn fallback_layer(&self) -> usize {
292 0
293 }
294}