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::{CalyxResult, Error};
6use std::collections::{HashMap, HashSet};
7use std::fmt::Write as _;
8use std::time::Instant;
9
10/// Top-level type for all passes that transform an [ir::Context]
11pub type PassClosure = Box<dyn Fn(&mut ir::Context) -> CalyxResult<()>>;
12
13/// Structure that tracks all registered passes for the compiler.
14#[derive(Default)]
15pub struct PassManager {
16    /// All registered passes
17    passes: HashMap<String, PassClosure>,
18    /// Tracks alias for groups of passes that run together.
19    aliases: HashMap<String, Vec<String>>,
20    // Track the help information for passes
21    help: HashMap<String, String>,
22}
23
24impl PassManager {
25    /// Register a new Calyx pass and return an error if another pass with the
26    /// same name has already been registered.
27    ///
28    /// ## Example
29    /// ```rust
30    /// let pm = PassManager::default();
31    /// pm.register_pass::<WellFormed>()?;
32    /// ```
33    pub fn register_pass<Pass>(&mut self) -> CalyxResult<()>
34    where
35        Pass:
36            traversal::Visitor + traversal::ConstructVisitor + traversal::Named,
37    {
38        let name = Pass::name().to_string();
39        if self.passes.contains_key(&name) {
40            return Err(Error::misc(format!(
41                "Pass with name '{}' is already registered.",
42                name
43            )));
44        }
45        let pass_closure: PassClosure = Box::new(|ir| {
46            Pass::do_pass_default(ir)?;
47            Ok(())
48        });
49        self.passes.insert(name.clone(), pass_closure);
50        let mut help = format!("- {}: {}", name, Pass::description());
51        for opt in Pass::opts() {
52            write!(
53                &mut help,
54                "\n  * {}: {} (default: {})",
55                opt.name(),
56                opt.description(),
57                opt.default()
58            )
59            .unwrap();
60        }
61        self.help.insert(name, help);
62        Ok(())
63    }
64
65    /// Adds a new alias for groups of passes. An alias is a list of strings
66    /// that represent valid pass names OR an alias.
67    /// The passes and aliases are executed in the order of specification.
68    pub fn add_alias(
69        &mut self,
70        name: String,
71        passes: Vec<String>,
72    ) -> CalyxResult<()> {
73        if self.aliases.contains_key(&name) {
74            return Err(Error::misc(format!(
75                "Alias with name '{}'  already registered.",
76                name
77            )));
78        }
79        // Expand any aliases used in defining this alias.
80        let all_passes = passes
81            .into_iter()
82            .flat_map(|pass| {
83                if self.aliases.contains_key(&pass) {
84                    self.aliases[&pass].clone()
85                } else if self.passes.contains_key(&pass) {
86                    vec![pass]
87                } else {
88                    panic!("No pass or alias named: {}", pass)
89                }
90            })
91            .collect();
92        self.aliases.insert(name, all_passes);
93        Ok(())
94    }
95
96    /// Return the help string for a specific pass.
97    pub fn specific_help(&self, pass: &str) -> Option<String> {
98        self.help.get(pass).cloned().or_else(|| {
99            self.aliases.get(pass).map(|passes| {
100                let pass_str = passes
101                    .iter()
102                    .map(|p| format!("- {p}"))
103                    .collect::<Vec<String>>()
104                    .join("\n");
105                format!("`{pass}' is an alias for pass pipeline:\n{}", pass_str)
106            })
107        })
108    }
109
110    /// Return a string representation to show all available passes and aliases.
111    /// Appropriate for help text.
112    pub fn complete_help(&self) -> String {
113        let mut ret = String::with_capacity(1000);
114
115        // Push all passes.
116        let mut pass_names = self.passes.keys().collect::<Vec<_>>();
117        pass_names.sort();
118        ret.push_str("Passes:\n");
119        pass_names.iter().for_each(|&pass| {
120            writeln!(ret, "{}", self.help[pass]).unwrap();
121        });
122
123        // Push all aliases
124        let mut aliases = self.aliases.iter().collect::<Vec<_>>();
125        aliases.sort_by(|kv1, kv2| kv1.0.cmp(kv2.0));
126        ret.push_str("\nAliases:\n");
127        aliases.iter().for_each(|(alias, passes)| {
128            let pass_str = passes
129                .iter()
130                .map(|p| p.to_string())
131                .collect::<Vec<String>>()
132                .join(", ");
133            writeln!(ret, "- {}: {}", alias, pass_str).unwrap();
134        });
135        ret
136    }
137
138    /// Attempts to resolve the alias name. If there is no alias with this name,
139    /// assumes that this is a pass instead.
140    fn resolve_alias(&self, maybe_alias: &str) -> Vec<String> {
141        self.aliases
142            .get(maybe_alias)
143            .cloned()
144            .unwrap_or_else(|| vec![maybe_alias.to_string()])
145    }
146
147    /// Creates a plan using an inclusion and exclusion list which might contain
148    /// aliases.
149    fn create_plan(
150        &self,
151        incls: &[String],
152        excls: &[String],
153    ) -> CalyxResult<(Vec<String>, HashSet<String>)> {
154        // Incls and excls can both have aliases in them. Resolve them.
155        let passes = incls
156            .iter()
157            .flat_map(|maybe_alias| self.resolve_alias(maybe_alias))
158            .collect::<Vec<_>>();
159
160        let excl_set = excls
161            .iter()
162            .flat_map(|maybe_alias| self.resolve_alias(maybe_alias))
163            .collect::<HashSet<String>>();
164
165        // Validate that names of passes in incl and excl sets are known
166        passes.iter().chain(excl_set.iter()).try_for_each(|pass| {
167            if !self.passes.contains_key(pass) {
168                Err(Error::misc(format!(
169                    "Unknown pass: {pass}. Run compiler with pass-help subcommand to view registered passes."
170                )))
171            } else {
172                Ok(())
173            }
174        })?;
175
176        Ok((passes, excl_set))
177    }
178
179    /// Executes a given "plan" constructed using the incl and excl lists.
180    pub fn execute_plan(
181        &self,
182        ctx: &mut ir::Context,
183        incl: &[String],
184        excl: &[String],
185        dump_ir: bool,
186    ) -> CalyxResult<()> {
187        let (passes, excl_set) = self.create_plan(incl, excl)?;
188
189        for name in passes {
190            // Pass is known to exist because create_plan validates the
191            // names of passes.
192            let pass = &self.passes[&name];
193
194            // Conditional compilation for WASM target because Instant::now
195            // is not supported.
196            if cfg!(not(target_family = "wasm")) {
197                if !excl_set.contains(&name) {
198                    let start = Instant::now();
199                    pass(ctx)?;
200                    if dump_ir {
201                        ir::Printer::write_context(
202                            ctx,
203                            true,
204                            &mut std::io::stdout(),
205                        )?;
206                    }
207                    let elapsed = start.elapsed();
208                    // Warn if pass takes more than 3 seconds.
209                    if elapsed.as_secs() > 5 {
210                        log::warn!("{name}: {}ms", elapsed.as_millis());
211                    } else {
212                        log::info!("{name}: {}ms", start.elapsed().as_millis());
213                    }
214                } else {
215                    log::info!("{name}: Ignored")
216                }
217            } else if !excl_set.contains(&name) {
218                pass(ctx)?;
219            }
220        }
221
222        Ok(())
223    }
224}
225
226/// Simple macro to register an alias with a pass manager.
227///
228/// ## Example
229/// ```
230/// let pm = PassManager::default();
231/// // Register passes WellFormed, Papercut, and Canonicalize.
232/// register_alias!(pm, "validate", [WellFormed, Papercut, Canonicalize]);
233/// ```
234#[macro_export]
235macro_rules! register_alias {
236    (@unwrap_name $pass:ident) => {
237        $pass::name().to_string()
238    };
239
240    (@unwrap_name $pass:literal) => {
241        $pass.to_string()
242    };
243
244    ($manager:expr, $alias:literal, [ $($pass:tt),* $(,)? ]) => {
245        $manager.add_alias($alias.to_string(), vec![
246            $(register_alias!(@unwrap_name $pass)),*
247        ])?;
248    };
249}