calyx_opt/
pass_manager.rs

1//! Define the PassManager structure that is used to construct and run pass
2//! passes.
3use crate::traversal;
4use calyx_ir as ir;
5use calyx_utils::{Error, MultiError};
6use std::collections::{HashMap, HashSet};
7use std::fmt::Write as _;
8use std::time::Instant;
9
10pub type PassResult<T> = std::result::Result<T, MultiError>;
11
12/// Top-level type for all passes that transform an [ir::Context]
13pub type PassClosure = Box<dyn Fn(&mut ir::Context) -> PassResult<()>>;
14
15/// Structure that tracks all registered passes for the compiler.
16#[derive(Default)]
17pub struct PassManager {
18    /// All registered passes
19    passes: HashMap<String, PassClosure>,
20    /// Tracks alias for groups of passes that run together.
21    aliases: HashMap<String, Vec<String>>,
22    // Track the help information for passes
23    help: HashMap<String, String>,
24}
25
26impl PassManager {
27    /// Register a new Calyx pass and return an error if another pass with the
28    /// same name has already been registered.
29    ///
30    /// ## Example
31    /// ```rust
32    /// let pm = PassManager::default();
33    /// pm.register_pass::<WellFormed>()?;
34    /// ```
35    pub fn register_pass<Pass>(&mut self) -> PassResult<()>
36    where
37        Pass:
38            traversal::Visitor + traversal::ConstructVisitor + traversal::Named,
39    {
40        self.register_generic_pass::<Pass>(Box::new(|ir| {
41            Pass::do_pass_default(ir)?;
42            Ok(())
43        }))
44    }
45
46    /// Registers a diagnostic pass as a normal pass. If there is an error,
47    /// this will report the first error gathered by the pass.
48    pub fn register_diagnostic<Pass>(&mut self) -> PassResult<()>
49    where
50        Pass: traversal::Visitor
51            + traversal::ConstructVisitor
52            + traversal::Named
53            + traversal::DiagnosticPass,
54    {
55        self.register_generic_pass::<Pass>(Box::new(|ir| {
56            let mut visitor = Pass::from(ir)?;
57            visitor.do_pass(ir)?;
58
59            let errors: Vec<_> =
60                visitor.diagnostics().errors_iter().cloned().collect();
61            if !errors.is_empty() {
62                Err(MultiError::from(errors))
63            } else {
64                // only show warnings, if there are no errors
65                visitor.diagnostics().warning_iter().for_each(
66                    |warning| log::warn!(target: Pass::name(), "{warning:?}"),
67                );
68                Ok(())
69            }
70        }))
71    }
72
73    fn register_generic_pass<Pass>(
74        &mut self,
75        pass_closure: PassClosure,
76    ) -> PassResult<()>
77    where
78        Pass:
79            traversal::Visitor + traversal::ConstructVisitor + traversal::Named,
80    {
81        let name = Pass::name().to_string();
82        if self.passes.contains_key(&name) {
83            return Err(Error::misc(format!(
84                "Pass with name '{name}' is already registered."
85            ))
86            .into());
87        }
88        self.passes.insert(name.clone(), pass_closure);
89        let mut help = format!("- {}: {}", name, Pass::description());
90        for opt in Pass::opts() {
91            write!(
92                &mut help,
93                "\n  * {}: {} (default: {})",
94                opt.name(),
95                opt.description(),
96                opt.default()
97            )
98            .unwrap();
99        }
100        self.help.insert(name, help);
101        Ok(())
102    }
103
104    /// Adds a new alias for groups of passes. An alias is a list of strings
105    /// that represent valid pass names OR an alias.
106    /// The passes and aliases are executed in the order of specification.
107    pub fn add_alias(
108        &mut self,
109        name: String,
110        passes: Vec<String>,
111    ) -> PassResult<()> {
112        if self.aliases.contains_key(&name) {
113            return Err(Error::misc(format!(
114                "Alias with name '{name}'  already registered."
115            ))
116            .into());
117        }
118        // Expand any aliases used in defining this alias.
119        let all_passes = passes
120            .into_iter()
121            .flat_map(|pass| {
122                if self.aliases.contains_key(&pass) {
123                    self.aliases[&pass].clone()
124                } else if self.passes.contains_key(&pass) {
125                    vec![pass]
126                } else {
127                    panic!("No pass or alias named: {pass}")
128                }
129            })
130            .collect();
131        self.aliases.insert(name, all_passes);
132        Ok(())
133    }
134
135    /// Return the help string for a specific pass.
136    pub fn specific_help(&self, pass: &str) -> Option<String> {
137        self.help.get(pass).cloned().or_else(|| {
138            self.aliases.get(pass).map(|passes| {
139                let pass_str = passes
140                    .iter()
141                    .map(|p| format!("- {p}"))
142                    .collect::<Vec<String>>()
143                    .join("\n");
144                format!("`{pass}' is an alias for pass pipeline:\n{pass_str}")
145            })
146        })
147    }
148
149    /// Return a string representation to show all available passes and aliases.
150    /// Appropriate for help text.
151    pub fn complete_help(&self) -> String {
152        let mut ret = String::with_capacity(1000);
153
154        // Push all passes.
155        let mut pass_names = self.passes.keys().collect::<Vec<_>>();
156        pass_names.sort();
157        ret.push_str("Passes:\n");
158        pass_names.iter().for_each(|&pass| {
159            writeln!(ret, "{}", self.help[pass]).unwrap();
160        });
161
162        // Push all aliases
163        let mut aliases = self.aliases.iter().collect::<Vec<_>>();
164        aliases.sort_by(|kv1, kv2| kv1.0.cmp(kv2.0));
165        ret.push_str("\nAliases:\n");
166        aliases.iter().for_each(|(alias, passes)| {
167            let pass_str = passes
168                .iter()
169                .map(|p| p.to_string())
170                .collect::<Vec<String>>()
171                .join(", ");
172            writeln!(ret, "- {alias}: {pass_str}").unwrap();
173        });
174        ret
175    }
176
177    /// Attempts to resolve the alias name. If there is no alias with this name,
178    /// assumes that this is a pass instead.
179    fn resolve_alias(&self, maybe_alias: &str) -> Vec<String> {
180        self.aliases
181            .get(maybe_alias)
182            .cloned()
183            .unwrap_or_else(|| vec![maybe_alias.to_string()])
184    }
185
186    /// Creates a plan using an inclusion and exclusion list which might contain
187    /// aliases.
188    fn create_plan(
189        &self,
190        incls: &[String],
191        excls: &[String],
192        insns: &[String],
193    ) -> PassResult<(Vec<String>, HashSet<String>)> {
194        let mut insertions = insns
195            .iter()
196            .filter_map(|str| match str.split_once(':') {
197                Some((before, after)) => {
198                    Some((before.to_string(), after.to_string()))
199                }
200                None => {
201                    log::warn!("No ':' in {str}. Ignoring this option.");
202                    None
203                }
204            })
205            .collect::<Vec<_>>();
206        // Incls and excls can have aliases in them. Resolve them.
207        let mut passes = incls
208            .iter()
209            .flat_map(|maybe_alias| self.resolve_alias(maybe_alias))
210            .collect::<Vec<_>>();
211
212        let excl_set = excls
213            .iter()
214            .flat_map(|maybe_alias| self.resolve_alias(maybe_alias))
215            .collect::<HashSet<String>>();
216
217        // Validate that names of passes in incl and excl sets are known
218        passes.iter().chain(excl_set.iter().chain(insertions.iter().flat_map(|(pass1, pass2)| vec![pass1, pass2]))).try_for_each(|pass| {
219            if !self.passes.contains_key(pass) {
220                Err(Error::misc(format!(
221                    "Unknown pass: {pass}. Run compiler with pass-help subcommand to view registered passes."
222                )))
223            } else {
224                Ok(())
225            }
226        })?;
227
228        // Remove passes from `insertions` that are not slated to run.
229        insertions.retain(|(pass1, pass2)|
230            if !passes.contains(pass1) || excl_set.contains(pass1) {
231                log::warn!("Pass {pass1} is not slated to run. Reordering will have no effect.");
232                false
233            }
234            else if !passes.contains(pass2) || excl_set.contains(pass2) {
235                log::warn!("Pass {pass2} is not slated to run. Reordering will have no effect.");
236                false
237            }
238            else {
239                true
240            }
241        );
242
243        // Perform re-insertion.
244        // Insert `after` right after `before`. If `after` already appears after
245        // before, do nothing.
246        for (before, after) in insertions {
247            let before_idx =
248                passes.iter().position(|pass| *pass == before).unwrap();
249            let after_idx =
250                passes.iter().position(|pass| *pass == after).unwrap();
251            // Only need to perform re-insertion if it is actually out of order.
252            if before_idx > after_idx {
253                passes.insert(before_idx + 1, after);
254                passes.remove(after_idx);
255            }
256        }
257
258        Ok((passes, excl_set))
259    }
260
261    /// Executes a given "plan" constructed using the incl and excl lists.
262    /// ord is a relative ordering that should be enforced.
263    pub fn execute_plan(
264        &self,
265        ctx: &mut ir::Context,
266        incl: &[String],
267        excl: &[String],
268        insn: &[String],
269        dump_ir: bool,
270    ) -> PassResult<()> {
271        let (passes, excl_set) = self.create_plan(incl, excl, insn)?;
272
273        for name in passes {
274            // Pass is known to exist because create_plan validates the
275            // names of passes.
276            let pass = &self.passes[&name];
277
278            // Conditional compilation for WASM target because Instant::now
279            // is not supported.
280            if cfg!(not(target_family = "wasm")) {
281                if !excl_set.contains(&name) {
282                    let start = Instant::now();
283                    pass(ctx)?;
284                    if dump_ir {
285                        ir::Printer::write_context(
286                            ctx,
287                            true,
288                            &mut std::io::stdout(),
289                        )?;
290                    }
291                    let elapsed = start.elapsed();
292                    // Warn if pass takes more than 3 seconds.
293                    if elapsed.as_secs() > 5 {
294                        log::warn!("{name}: {}ms", elapsed.as_millis());
295                    } else {
296                        log::info!("{name}: {}ms", start.elapsed().as_millis());
297                    }
298                } else {
299                    log::info!("{name}: Ignored")
300                }
301            } else if !excl_set.contains(&name) {
302                pass(ctx)?;
303            }
304        }
305
306        Ok(())
307    }
308}
309
310/// Simple macro to register an alias with a pass manager.
311///
312/// ## Example
313/// ```
314/// let pm = PassManager::default();
315/// // Register passes WellFormed, Papercut, and Canonicalize.
316/// register_alias!(pm, "validate", [WellFormed, Papercut, Canonicalize]);
317/// ```
318#[macro_export]
319macro_rules! register_alias {
320    (@unwrap_name $pass:ident) => {
321        $pass::name().to_string()
322    };
323
324    (@unwrap_name $pass:literal) => {
325        $pass.to_string()
326    };
327
328    ($manager:expr, $alias:literal, [ $($pass:tt),* $(,)? ]) => {
329        $manager.add_alias($alias.to_string(), vec![
330            $(register_alias!(@unwrap_name $pass)),*
331        ])?;
332    };
333}