Skip to main content

sway_ir/
pass_manager.rs

1use crate::{
2    create_arg_demotion_pass, create_arg_pointee_mutability_tagger_pass, create_ccp_pass,
3    create_const_demotion_pass, create_const_folding_pass, create_cse_pass, create_dce_pass,
4    create_dom_fronts_pass, create_dominators_pass, create_escaped_symbols_pass,
5    create_fn_dedup_debug_profile_pass, create_fn_dedup_release_profile_pass,
6    create_fn_inline_pass, create_globals_dce_pass, create_init_aggr_lowering_pass,
7    create_mem2reg_pass, create_memcpyopt_pass, create_memcpyprop_reverse_pass,
8    create_misc_demotion_pass, create_module_printer_pass, create_module_verifier_pass,
9    create_postorder_pass, create_ret_demotion_pass, create_simplify_cfg_pass, create_sroa_pass,
10    Context, Function, IrError, Module, ARG_DEMOTION_NAME, ARG_POINTEE_MUTABILITY_TAGGER_NAME,
11    CCP_NAME, CONST_DEMOTION_NAME, CONST_FOLDING_NAME, CSE_NAME, DCE_NAME,
12    FN_DEDUP_DEBUG_PROFILE_NAME, FN_DEDUP_RELEASE_PROFILE_NAME, FN_INLINE_NAME, GLOBALS_DCE_NAME,
13    INIT_AGGR_LOWERING_NAME, MEM2REG_NAME, MEMCPYOPT_NAME, MEMCPYPROP_REVERSE_NAME,
14    MISC_DEMOTION_NAME, RET_DEMOTION_NAME, SIMPLIFY_CFG_NAME, SROA_NAME,
15};
16use downcast_rs::{impl_downcast, Downcast};
17use rustc_hash::FxHashMap;
18use std::{
19    any::{type_name, TypeId},
20    cell::RefCell,
21    collections::{hash_map, HashSet},
22    ops::DerefMut,
23};
24
25/// Result of an analysis. Specific result must be downcasted to.
26pub trait AnalysisResultT: Downcast {}
27impl_downcast!(AnalysisResultT);
28pub type AnalysisResult = Box<dyn AnalysisResultT>;
29
30/// Program scope over which a pass executes.
31pub trait PassScope {
32    fn get_arena_idx(&self) -> slotmap::DefaultKey;
33}
34impl PassScope for Module {
35    fn get_arena_idx(&self) -> slotmap::DefaultKey {
36        self.0
37    }
38}
39impl PassScope for Function {
40    fn get_arena_idx(&self) -> slotmap::DefaultKey {
41        self.0
42    }
43}
44
45/// Is a pass an Analysis or a Transformation over the IR?
46#[derive(Clone)]
47pub enum PassMutability<S: PassScope> {
48    /// An analysis pass, producing an analysis result.
49    Analysis(fn(&Context, analyses: &AnalysisResults, S) -> Result<AnalysisResult, IrError>),
50    /// A pass over the IR that can possibly modify it.
51    Transform(fn(&mut Context, analyses: &AnalysisResults, S) -> Result<bool, IrError>),
52}
53
54/// A concrete version of [PassScope].
55#[derive(Clone)]
56pub enum ScopedPass {
57    ModulePass(PassMutability<Module>),
58    FunctionPass(PassMutability<Function>),
59}
60
61/// An analysis or transformation pass.
62pub struct Pass {
63    /// Pass identifier.
64    pub name: &'static str,
65    /// A short description.
66    pub descr: &'static str,
67    /// Other passes that this pass depends on.
68    pub deps: Vec<&'static str>,
69    /// The executor.
70    pub runner: ScopedPass,
71}
72
73impl Pass {
74    pub fn is_analysis(&self) -> bool {
75        match &self.runner {
76            ScopedPass::ModulePass(pm) => matches!(pm, PassMutability::Analysis(_)),
77            ScopedPass::FunctionPass(pm) => matches!(pm, PassMutability::Analysis(_)),
78        }
79    }
80
81    pub fn is_transform(&self) -> bool {
82        !self.is_analysis()
83    }
84
85    pub fn is_module_pass(&self) -> bool {
86        matches!(self.runner, ScopedPass::ModulePass(_))
87    }
88
89    pub fn is_function_pass(&self) -> bool {
90        matches!(self.runner, ScopedPass::FunctionPass(_))
91    }
92}
93
94#[derive(Default)]
95pub struct AnalysisResults {
96    // Hash from (AnalysisResultT, (PassScope, Scope Identity)) to an actual result.
97    results: FxHashMap<(TypeId, (TypeId, slotmap::DefaultKey)), AnalysisResult>,
98    name_typeid_map: FxHashMap<&'static str, TypeId>,
99    pub is_log_enabled: bool,
100    /// Amalgamated debug log from all passes
101    log_string: RefCell<String>,
102}
103
104impl AnalysisResults {
105    pub fn push_log(&self, log: impl AsRef<str>) {
106        if self.is_log_enabled {
107            self.log_string.borrow_mut().push_str(log.as_ref());
108        }
109    }
110
111    /// Get the results of an analysis.
112    /// Example analyses.get_analysis_result::<DomTreeAnalysis>(foo).
113    pub fn get_analysis_result<T: AnalysisResultT, S: PassScope + 'static>(&self, scope: S) -> &T {
114        self.results
115            .get(&(
116                TypeId::of::<T>(),
117                (TypeId::of::<S>(), scope.get_arena_idx()),
118            ))
119            .unwrap_or_else(|| {
120                panic!(
121                    "Internal error. Analysis result {} unavailable for {} with idx {:?}",
122                    type_name::<T>(),
123                    type_name::<S>(),
124                    scope.get_arena_idx()
125                )
126            })
127            .downcast_ref()
128            .expect("AnalysisResult: Incorrect type")
129    }
130
131    /// Is an analysis result available at the given scope?
132    fn is_analysis_result_available<S: PassScope + 'static>(
133        &self,
134        name: &'static str,
135        scope: S,
136    ) -> bool {
137        self.name_typeid_map
138            .get(name)
139            .and_then(|result_typeid| {
140                self.results
141                    .get(&(*result_typeid, (TypeId::of::<S>(), scope.get_arena_idx())))
142            })
143            .is_some()
144    }
145
146    /// Add a new result.
147    fn add_result<S: PassScope + 'static>(
148        &mut self,
149        name: &'static str,
150        scope: S,
151        result: AnalysisResult,
152    ) {
153        let result_typeid = (*result).type_id();
154        self.results.insert(
155            (result_typeid, (TypeId::of::<S>(), scope.get_arena_idx())),
156            result,
157        );
158        self.name_typeid_map.insert(name, result_typeid);
159    }
160
161    /// Invalidate all results at a given scope.
162    fn invalidate_all_results_at_scope<S: PassScope + 'static>(&mut self, scope: S) {
163        self.results
164            .retain(|(_result_typeid, (scope_typeid, scope_idx)), _v| {
165                (*scope_typeid, *scope_idx) != (TypeId::of::<S>(), scope.get_arena_idx())
166            });
167    }
168}
169
170/// Options when running the `PassManager`.
171///
172/// # Printing Options
173///
174/// Note that states of IR can always be printed by injecting the module printer pass
175/// and just running the passes. That approach however offers less control over the
176/// printing. E.g., requiring the printing to happen only if the previous passes
177/// modified the IR cannot be done by simply injecting a module printer.
178#[derive(Debug)]
179pub struct Options {
180    pub print_initial: bool,
181    pub print_final: bool,
182    pub print_modified_only: bool,
183    pub print_metadata: bool,
184    pub print_passes: HashSet<String>,
185    pub force_verify_ir: bool,
186    pub rounds: usize,
187    pub log: bool,
188}
189
190impl Default for Options {
191    fn default() -> Self {
192        Self {
193            print_initial: false,
194            print_final: false,
195            print_modified_only: false,
196            print_metadata: false,
197            print_passes: HashSet::default(),
198            force_verify_ir: false,
199            rounds: 2,
200            log: false,
201        }
202    }
203}
204
205#[derive(Default)]
206pub struct PassManager {
207    passes: FxHashMap<&'static str, Pass>,
208    analyses: AnalysisResults,
209}
210
211impl PassManager {
212    pub const OPTIMIZATION_PASSES: [&'static str; 19] = [
213        ARG_DEMOTION_NAME,
214        ARG_POINTEE_MUTABILITY_TAGGER_NAME,
215        CCP_NAME,
216        CONST_DEMOTION_NAME,
217        CONST_FOLDING_NAME,
218        CSE_NAME,
219        DCE_NAME,
220        FN_DEDUP_DEBUG_PROFILE_NAME,
221        FN_DEDUP_RELEASE_PROFILE_NAME,
222        FN_INLINE_NAME,
223        GLOBALS_DCE_NAME,
224        INIT_AGGR_LOWERING_NAME,
225        MEM2REG_NAME,
226        MEMCPYOPT_NAME,
227        MEMCPYPROP_REVERSE_NAME,
228        MISC_DEMOTION_NAME,
229        RET_DEMOTION_NAME,
230        SIMPLIFY_CFG_NAME,
231        SROA_NAME,
232    ];
233
234    /// Register a pass. Should be called only once for each pass.
235    pub fn register(&mut self, pass: Pass) -> &'static str {
236        for dep in &pass.deps {
237            if let Some(dep_t) = self.lookup_registered_pass(dep) {
238                if dep_t.is_transform() {
239                    panic!(
240                        "Pass {} cannot depend on a transformation pass {}",
241                        pass.name, dep
242                    );
243                }
244                if pass.is_function_pass() && dep_t.is_module_pass() {
245                    panic!(
246                        "Function pass {} cannot depend on module pass {}",
247                        pass.name, dep
248                    );
249                }
250            } else {
251                panic!(
252                    "Pass {} depends on a (yet) unregistered pass {}",
253                    pass.name, dep
254                );
255            }
256        }
257        let pass_name = pass.name;
258        match self.passes.entry(pass.name) {
259            hash_map::Entry::Occupied(_) => {
260                panic!("Trying to register an already registered pass");
261            }
262            hash_map::Entry::Vacant(entry) => {
263                entry.insert(pass);
264            }
265        }
266        pass_name
267    }
268
269    fn actually_run(&mut self, ir: &mut Context, pass: &'static str) -> Result<bool, IrError> {
270        let mut modified = false;
271
272        fn run_module_pass(
273            pm: &mut PassManager,
274            ir: &mut Context,
275            pass: &'static str,
276            module: Module,
277        ) -> Result<bool, IrError> {
278            let mut modified = false;
279            let pass_t = pm.passes.get(pass).expect("Unregistered pass");
280            for dep in pass_t.deps.clone() {
281                let dep_t = pm.passes.get(dep).expect("Unregistered dependent pass");
282                // If pass registration allows transformations as dependents, we could remove this I guess.
283                assert!(dep_t.is_analysis());
284                match dep_t.runner {
285                    ScopedPass::ModulePass(_) => {
286                        if !pm.analyses.is_analysis_result_available(dep, module) {
287                            run_module_pass(pm, ir, dep, module)?;
288                        }
289                    }
290                    ScopedPass::FunctionPass(_) => {
291                        for f in module.function_iter(ir) {
292                            if !pm.analyses.is_analysis_result_available(dep, f) {
293                                run_function_pass(pm, ir, dep, f)?;
294                            }
295                        }
296                    }
297                }
298            }
299
300            // Get the pass again to satisfy the borrow checker.
301            let pass_t = pm.passes.get(pass).expect("Unregistered pass");
302            let ScopedPass::ModulePass(mp) = pass_t.runner.clone() else {
303                panic!("Expected a module pass");
304            };
305            match mp {
306                PassMutability::Analysis(analysis) => {
307                    let result = analysis(ir, &pm.analyses, module)?;
308                    pm.analyses.add_result(pass, module, result);
309                }
310                PassMutability::Transform(transform) => {
311                    if transform(ir, &pm.analyses, module)? {
312                        pm.analyses.invalidate_all_results_at_scope(module);
313                        for f in module.function_iter(ir) {
314                            pm.analyses.invalidate_all_results_at_scope(f);
315                        }
316                        modified = true;
317                    }
318                }
319            }
320
321            Ok(modified)
322        }
323
324        fn run_function_pass(
325            pm: &mut PassManager,
326            ir: &mut Context,
327            pass: &'static str,
328            function: Function,
329        ) -> Result<bool, IrError> {
330            let mut modified = false;
331            let pass_t = pm.passes.get(pass).expect("Unregistered pass");
332            for dep in pass_t.deps.clone() {
333                let dep_t = pm.passes.get(dep).expect("Unregistered dependent pass");
334                // If pass registration allows transformations as dependents, we could remove this I guess.
335                assert!(dep_t.is_analysis());
336                match dep_t.runner {
337                    ScopedPass::ModulePass(_) => {
338                        panic!("Function pass {pass} cannot depend on module pass {dep}")
339                    }
340                    ScopedPass::FunctionPass(_) => {
341                        if !pm.analyses.is_analysis_result_available(dep, function) {
342                            run_function_pass(pm, ir, dep, function)?;
343                        };
344                    }
345                }
346            }
347
348            // Get the pass again to satisfy the borrow checker.
349            let pass_t = pm.passes.get(pass).expect("Unregistered pass");
350            let ScopedPass::FunctionPass(fp) = pass_t.runner.clone() else {
351                panic!("Expected a function pass");
352            };
353            match fp {
354                PassMutability::Analysis(analysis) => {
355                    let result = analysis(ir, &pm.analyses, function)?;
356                    pm.analyses.add_result(pass, function, result);
357                }
358                PassMutability::Transform(transform) => {
359                    if transform(ir, &pm.analyses, function)? {
360                        pm.analyses.invalidate_all_results_at_scope(function);
361                        modified = true;
362                    }
363                }
364            }
365
366            Ok(modified)
367        }
368
369        for m in ir.module_iter() {
370            let pass_t = self.passes.get(pass).expect("Unregistered pass");
371            let pass_runner = pass_t.runner.clone();
372            match pass_runner {
373                ScopedPass::ModulePass(_) => {
374                    modified |= run_module_pass(self, ir, pass, m)?;
375                }
376                ScopedPass::FunctionPass(_) => {
377                    for f in m.function_iter(ir) {
378                        modified |= run_function_pass(self, ir, pass, f)?;
379                    }
380                }
381            }
382        }
383        Ok(modified)
384    }
385
386    /// Run the `passes` and return true if the `passes` modify the initial `ir`.
387    /// The IR states are printed according to the options provided and verified.
388    pub fn run(
389        &mut self,
390        ir: &mut Context,
391        passes: &PassGroup,
392        options: &Options,
393    ) -> Result<bool, IrError> {
394        if options.print_initial {
395            print_initial_or_final_ir(ir, "Initial", options.print_metadata);
396        }
397
398        self.analyses.is_log_enabled = options.log;
399        self.analyses.log_string.borrow_mut().clear();
400
401        // Verify before we start
402        ir.verify()?;
403
404        let mut global_modified = false;
405
406        for _ in 0..options.rounds {
407            let mut iter_modified = false;
408
409            for pass in passes.flatten_pass_group() {
410                // Save IR before optimisation only when forcing verification
411                let ir_before = if options.force_verify_ir {
412                    ir.to_string()
413                } else {
414                    String::new()
415                };
416
417                // run the pass
418                let modified = self.actually_run(ir, pass)?;
419
420                // Save IR after optimisation only when forcing verification
421                let ir_after = if options.force_verify_ir {
422                    ir.to_string()
423                } else {
424                    String::new()
425                };
426
427                iter_modified |= modified;
428
429                if options.print_passes.contains(pass) && (!options.print_modified_only || modified)
430                {
431                    print_ir_after_pass(
432                        ir,
433                        self.lookup_registered_pass(pass).unwrap(),
434                        options.print_metadata,
435                    );
436                }
437
438                ir.verify()?;
439
440                if options.force_verify_ir {
441                    // Verify pass correctly return modified
442                    let ir_modified = ir_before != ir_after;
443                    if modified != ir_modified {
444                        return Err(IrError::InvalidPassModified {
445                            pass: pass.to_string(),
446                            returned: modified,
447                            comparison: ir_modified,
448                        });
449                    }
450                }
451            }
452
453            global_modified |= iter_modified;
454            if !iter_modified {
455                break;
456            }
457        }
458
459        if options.print_final {
460            print_initial_or_final_ir(ir, "Final", options.print_metadata);
461        }
462
463        Ok(global_modified)
464    }
465
466    /// Get reference to a registered pass.
467    pub fn lookup_registered_pass(&self, name: &str) -> Option<&Pass> {
468        self.passes.get(name)
469    }
470
471    pub fn take_log(&self) -> String {
472        let mut log = self.analyses.log_string.borrow_mut();
473        std::mem::take(log.deref_mut())
474    }
475
476    pub fn help_text(&self) -> String {
477        let summary = self
478            .passes
479            .iter()
480            .map(|(name, pass)| format!("  {name:16} - {}", pass.descr))
481            .collect::<Vec<_>>()
482            .join("\n");
483
484        format!("Valid pass names are:\n\n{summary}",)
485    }
486}
487
488// Empty IRs are result of compiling dependencies. We don't want to print those.
489fn ir_is_empty(ir: &Context) -> bool {
490    ir.functions.is_empty()
491        && ir.blocks.is_empty()
492        && ir.values.is_empty()
493        && ir.local_vars.is_empty()
494}
495
496fn print_ir_after_pass(ir: &Context, pass: &Pass, print_metadata: bool) {
497    if !ir_is_empty(ir) {
498        println!("// IR: [{}] {}", pass.name, pass.descr);
499        println!(
500            "{}",
501            crate::printer::to_string_with_metadata(ir, print_metadata)
502        );
503    }
504}
505
506fn print_initial_or_final_ir(ir: &Context, initial_or_final: &'static str, print_metadata: bool) {
507    if !ir_is_empty(ir) {
508        println!("// IR: {initial_or_final}");
509        println!(
510            "{}",
511            crate::printer::to_string_with_metadata(ir, print_metadata)
512        );
513    }
514}
515
516/// A group of passes.
517/// Can contain sub-groups.
518#[derive(Default)]
519pub struct PassGroup(Vec<PassOrGroup>);
520
521/// An individual pass, or a group (with possible subgroup) of passes.
522pub enum PassOrGroup {
523    Pass(&'static str),
524    Group(PassGroup),
525}
526
527impl PassGroup {
528    // Flatten a group of passes into an ordered list.
529    fn flatten_pass_group(&self) -> Vec<&'static str> {
530        let mut output = Vec::<&str>::new();
531        fn inner(output: &mut Vec<&str>, input: &PassGroup) {
532            for pass_or_group in &input.0 {
533                match pass_or_group {
534                    PassOrGroup::Pass(pass) => output.push(pass),
535                    PassOrGroup::Group(pg) => inner(output, pg),
536                }
537            }
538        }
539        inner(&mut output, self);
540        output
541    }
542
543    /// Append a pass to this group.
544    pub fn append_pass(&mut self, pass: &'static str) {
545        self.0.push(PassOrGroup::Pass(pass));
546    }
547
548    /// Append a pass group.
549    pub fn append_group(&mut self, group: PassGroup) {
550        self.0.push(PassOrGroup::Group(group));
551    }
552}
553
554/// A convenience utility to register known passes.
555pub fn register_known_passes(pm: &mut PassManager) {
556    // Analysis passes.
557    pm.register(create_postorder_pass());
558    pm.register(create_dominators_pass());
559    pm.register(create_dom_fronts_pass());
560    pm.register(create_escaped_symbols_pass());
561    pm.register(create_module_printer_pass());
562    pm.register(create_module_verifier_pass());
563
564    // Lowering passes.
565    pm.register(create_init_aggr_lowering_pass());
566
567    // Optimization passes.
568    pm.register(create_arg_pointee_mutability_tagger_pass());
569    pm.register(create_fn_dedup_release_profile_pass());
570    pm.register(create_fn_dedup_debug_profile_pass());
571    pm.register(create_mem2reg_pass());
572    pm.register(create_sroa_pass());
573    pm.register(create_fn_inline_pass());
574    pm.register(create_const_folding_pass());
575    pm.register(create_ccp_pass());
576    pm.register(create_simplify_cfg_pass());
577    pm.register(create_globals_dce_pass());
578    pm.register(create_dce_pass());
579    pm.register(create_cse_pass());
580    pm.register(create_arg_demotion_pass());
581    pm.register(create_const_demotion_pass());
582    pm.register(create_ret_demotion_pass());
583    pm.register(create_misc_demotion_pass());
584    pm.register(create_memcpyopt_pass());
585    pm.register(create_memcpyprop_reverse_pass());
586}
587
588pub fn create_o1_pass_group() -> PassGroup {
589    let mut o1 = PassGroup::default();
590    o1.append_pass(MEM2REG_NAME);
591    o1.append_pass(FN_DEDUP_RELEASE_PROFILE_NAME);
592    o1.append_pass(FN_INLINE_NAME);
593    o1.append_pass(ARG_POINTEE_MUTABILITY_TAGGER_NAME);
594    o1.append_pass(SIMPLIFY_CFG_NAME);
595    o1.append_pass(GLOBALS_DCE_NAME);
596    o1.append_pass(DCE_NAME);
597    o1.append_pass(FN_INLINE_NAME);
598    o1.append_pass(ARG_POINTEE_MUTABILITY_TAGGER_NAME);
599    o1.append_pass(CCP_NAME);
600    o1.append_pass(CONST_FOLDING_NAME);
601    o1.append_pass(SIMPLIFY_CFG_NAME);
602    o1.append_pass(CSE_NAME);
603    o1.append_pass(CONST_FOLDING_NAME);
604    o1.append_pass(SIMPLIFY_CFG_NAME);
605    o1.append_pass(GLOBALS_DCE_NAME);
606    o1.append_pass(DCE_NAME);
607    o1.append_pass(FN_DEDUP_RELEASE_PROFILE_NAME);
608
609    o1
610}
611
612/// Utility to insert a pass after every pass in the given group `pg`.
613/// It preserves the `pg` group's structure. This means if `pg` has subgroups
614/// and those have subgroups, the resulting [PassGroup] will have the
615/// same subgroups, but with the `pass` inserted after every pass in every
616/// subgroup, as well as all passes outside of any groups.
617pub fn insert_after_each(pg: PassGroup, pass: &'static str) -> PassGroup {
618    fn insert_after_each_rec(pg: PassGroup, pass: &'static str) -> Vec<PassOrGroup> {
619        pg.0.into_iter()
620            .flat_map(|p_o_g| match p_o_g {
621                PassOrGroup::Group(group) => vec![PassOrGroup::Group(PassGroup(
622                    insert_after_each_rec(group, pass),
623                ))],
624                PassOrGroup::Pass(_) => vec![p_o_g, PassOrGroup::Pass(pass)],
625            })
626            .collect()
627    }
628
629    PassGroup(insert_after_each_rec(pg, pass))
630}