sonatina_codegen/optim/
licm.rs

1// TODO: Add control flow hoisting.
2use fxhash::{FxHashMap, FxHashSet};
3
4use crate::{
5    cfg::ControlFlowGraph,
6    loop_analysis::{Loop, LoopTree},
7};
8
9use sonatina_ir::{
10    func_cursor::{CursorLocation, FuncCursor, InsnInserter},
11    Block, Function, Insn, InsnData, Value,
12};
13
14#[derive(Debug)]
15pub struct LicmSolver {
16    invariants: Vec<Insn>,
17}
18
19impl LicmSolver {
20    pub fn new() -> Self {
21        Self {
22            invariants: Vec::default(),
23        }
24    }
25
26    pub fn clear(&mut self) {
27        self.invariants.clear();
28    }
29
30    /// Run loop invariant code motion ont the function.
31    /// This method also modifies `cfg` and `lpt` htt
32    pub fn run(&mut self, func: &mut Function, cfg: &mut ControlFlowGraph, lpt: &mut LoopTree) {
33        for lp in lpt.loops() {
34            self.collect_invaliants(func, cfg, lpt, lp);
35
36            if !self.invariants.is_empty() {
37                let preheader = self.create_preheader(func, cfg, lpt, lp);
38                self.hoist_invariants(func, preheader);
39                self.invariants.clear();
40            }
41        }
42    }
43
44    /// Collect loop invariants int the `lp`.
45    /// The found invariants are inserted to `Self::invariants`.
46    fn collect_invaliants(
47        &mut self,
48        func: &Function,
49        cfg: &ControlFlowGraph,
50        lpt: &LoopTree,
51        lp: Loop,
52    ) {
53        let mut block_in_loop_rpo: Vec<_> = lpt.iter_blocks_post_order(cfg, lp).collect();
54        block_in_loop_rpo.reverse();
55
56        let mut loop_var = FxHashSet::default();
57        for block in block_in_loop_rpo {
58            for insn in func.layout.iter_insn(block) {
59                if self.is_invariant(func, &loop_var, insn) {
60                    self.invariants.push(insn);
61                } else if let Some(result) = func.dfg.insn_result(insn) {
62                    loop_var.insert(result);
63                }
64            }
65        }
66    }
67
68    /// Returns `true` if the insn is loop invariant.
69    fn is_invariant(&self, func: &Function, loop_var: &FxHashSet<Value>, insn: Insn) -> bool {
70        if !self.is_safe_to_hoist(func, insn) {
71            return false;
72        }
73
74        for arg in func.dfg.insn_args(insn) {
75            if loop_var.contains(arg) {
76                return false;
77            }
78        }
79
80        true
81    }
82
83    /// Returns `true` if the `insn` is safe to hoist.
84    fn is_safe_to_hoist(&self, func: &Function, insn: Insn) -> bool {
85        !(func.dfg.has_side_effect(insn)
86            || func.dfg.is_branch(insn)
87            || func.dfg.may_trap(insn)
88            || func.dfg.is_phi(insn))
89    }
90
91    /// Returns preheader of the loop.
92    /// 1. If there is natural preheader for the loop, then returns it without any modification of
93    /// function.
94    /// 2. If no natural preheader for the loop, then create the preheader and modify function
95    ///    layout, `cfg`, and `lpt`.
96    fn create_preheader(
97        &self,
98        func: &mut Function,
99        cfg: &mut ControlFlowGraph,
100        lpt: &mut LoopTree,
101        lp: Loop,
102    ) -> Block {
103        let lp_header = lpt.loop_header(lp);
104        let original_preheaders: Vec<Block> = cfg
105            .preds_of(lp_header)
106            .copied()
107            .filter(|block| !lpt.is_in_loop(*block, lp))
108            .collect();
109
110        // If the loop header already has a single preheader and the edge is not a critical edge,
111        // then it's possible to use the preheader as is.
112        if original_preheaders.len() == 1 && cfg.succs_of(original_preheaders[0]).count() == 1 {
113            return original_preheaders[0];
114        }
115
116        // Create preheader and insert it before the loop header.
117        let new_preheader = func.dfg.make_block();
118        let mut inserter = InsnInserter::new(func, CursorLocation::BlockTop(lp_header));
119        inserter.insert_block_before(new_preheader);
120
121        // Insert jump insn of which destination is the loop header.
122        inserter.set_loc(CursorLocation::BlockTop(new_preheader));
123        inserter.insert_insn_data(InsnData::jump(lp_header));
124        cfg.add_edge(new_preheader, lp_header);
125
126        // Rewrite branch destination of original preheaders and modify cfg.
127        for block in original_preheaders.iter().copied() {
128            let last_insn = func.layout.last_insn_of(block).unwrap();
129            func.dfg
130                .rewrite_branch_dest(last_insn, lp_header, new_preheader);
131            cfg.remove_edge(block, lp_header);
132            cfg.add_edge(block, new_preheader);
133        }
134
135        self.modify_phi_insn(func, lp_header, &original_preheaders, new_preheader);
136
137        // Map new preheader to the parent loop if exists.
138        if let Some(parent_lp) = lpt.parent_loop(lp) {
139            lpt.map_block(new_preheader, parent_lp);
140        }
141
142        new_preheader
143    }
144
145    /// Hoist invariants to the preheader.
146    fn hoist_invariants(&self, func: &mut Function, preheader: Block) {
147        let last_insn = func.layout.last_insn_of(preheader).unwrap();
148        for invariant in self.invariants.iter().copied() {
149            func.layout.remove_insn(invariant);
150            func.layout.insert_insn_before(invariant, last_insn);
151        }
152    }
153
154    // Modify phi insns in loop header.
155    fn modify_phi_insn(
156        &self,
157        func: &mut Function,
158        lp_header: Block,
159        original_preheaders: &[Block],
160        new_preheader: Block,
161    ) {
162        // Record inserted phis to avoid duplication of the same phi.
163        let mut inserted_phis = FxHashMap::default();
164
165        let mut next_insn = func.layout.first_insn_of(lp_header);
166        while let Some(insn) = next_insn {
167            if !func.dfg.is_phi(insn) {
168                break;
169            }
170
171            // Create new phi insn that should be inserted to the preheader, and remove insn
172            // arguments passing through original preheaders.
173            let mut phi_insn_data = InsnData::phi(func.dfg.insn_result_ty(insn).unwrap());
174            for &block in original_preheaders {
175                // Remove an argument.
176                let value = func.dfg.remove_phi_arg(insn, block);
177                // Add an argument to newly inserted phi insn.
178                phi_insn_data.append_phi_arg(value, block);
179            }
180
181            let phi_result = match inserted_phis.get(&phi_insn_data) {
182                // If the same phi is already inserted in the preheader, reuse its result.
183                Some(&value) => value,
184
185                // Insert new phi to the preheader if there is no same phi in the preheader.
186                None => {
187                    // Insert new phi insn to the preheader.
188                    let mut inserter =
189                        InsnInserter::new(func, CursorLocation::BlockTop(new_preheader));
190                    let new_phi_insn = inserter.insert_insn_data(phi_insn_data.clone());
191                    let result = inserter.make_result(new_phi_insn).unwrap();
192                    inserter.attach_result(new_phi_insn, result);
193
194                    // Add phi_insn_data to `inserted_phis` for reusing.
195                    inserted_phis.insert(phi_insn_data, result);
196
197                    result
198                }
199            };
200
201            // Append the result of new phi insn.
202            func.dfg.append_phi_arg(insn, phi_result, new_preheader);
203
204            next_insn = func.layout.next_insn_of(insn);
205        }
206    }
207}
208
209impl Default for LicmSolver {
210    fn default() -> Self {
211        Self::new()
212    }
213}