calyx_opt/
pass_manager.rs1use 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
12pub type PassClosure = Box<dyn Fn(&mut ir::Context) -> PassResult<()>>;
14
15#[derive(Default)]
17pub struct PassManager {
18 passes: HashMap<String, PassClosure>,
20 aliases: HashMap<String, Vec<String>>,
22 help: HashMap<String, String>,
24}
25
26impl PassManager {
27 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 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 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 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 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 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 pub fn complete_help(&self) -> String {
152 let mut ret = String::with_capacity(1000);
153
154 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 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 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 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 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 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 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 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 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 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 let pass = &self.passes[&name];
277
278 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 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#[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}