Skip to main content

solar_codegen/transform/
loop_canonicalize.rs

1//! Loop canonicalization for MIR.
2//!
3//! This pass currently implements the first LoopSimplify building block: every
4//! natural loop gets a dedicated preheader when its incoming edge does not
5//! already come from a non-loop predecessor that jumps directly to the header.
6//! Later loop passes can rely on that shape for LICM, storage promotion, and
7//! ScalarEvolution-style reasoning.
8//!
9//! Safety contract:
10//! - only split header edges whose terminators explicitly target the header
11//! - preserve header phi semantics by moving outside incoming values through the inserted preheader
12//! - repair reachability-dependent phis after CFG rewrites
13
14use crate::{
15    analysis::LoopAnalyzer,
16    mir::{
17        BlockId, Function, InstId, InstKind, Instruction, Terminator, Value, ValueId,
18        utils::repair_reachability_phis,
19    },
20    pass::FunctionPass,
21};
22use solar_data_structures::map::FxHashSet;
23
24/// Statistics from loop canonicalization.
25#[derive(Clone, Debug, Default)]
26pub struct LoopCanonicalizeStats {
27    /// Number of preheader blocks inserted.
28    pub preheaders_inserted: usize,
29    /// Number of header phi nodes rewritten to use a preheader incoming value.
30    pub header_phis_rewritten: usize,
31    /// Number of new preheader phi nodes inserted.
32    pub preheader_phis_inserted: usize,
33}
34
35impl LoopCanonicalizeStats {
36    /// Returns total canonicalization changes performed.
37    #[must_use]
38    pub const fn total(&self) -> usize {
39        self.preheaders_inserted + self.header_phis_rewritten + self.preheader_phis_inserted
40    }
41}
42
43/// Canonicalizes natural loops into a form expected by loop optimizers.
44#[derive(Debug, Default)]
45pub struct LoopCanonicalizer {
46    stats: LoopCanonicalizeStats,
47}
48
49/// Function pass for loop canonicalization.
50pub struct LoopCanonicalizePass;
51
52impl FunctionPass for LoopCanonicalizePass {
53    fn name(&self) -> &str {
54        "loop-canonicalize"
55    }
56
57    fn run_on_function(&mut self, func: &mut Function) -> bool {
58        LoopCanonicalizer::new().run(func).total() != 0
59    }
60}
61
62impl LoopCanonicalizer {
63    /// Creates a new loop canonicalizer.
64    #[must_use]
65    pub fn new() -> Self {
66        Self::default()
67    }
68
69    /// Returns statistics from the last run.
70    #[must_use]
71    pub const fn stats(&self) -> &LoopCanonicalizeStats {
72        &self.stats
73    }
74
75    /// Runs loop canonicalization to a fixed point.
76    pub fn run(&mut self, func: &mut Function) -> &LoopCanonicalizeStats {
77        self.stats = LoopCanonicalizeStats::default();
78
79        loop {
80            let mut analyzer = LoopAnalyzer::new();
81            let loop_info = analyzer.analyze(func);
82            let mut headers: Vec<_> = loop_info.loops.keys().copied().collect();
83            headers.sort_by_key(|header| header.index());
84
85            let Some(header) = headers.into_iter().find(|&header| {
86                let loop_data = &loop_info.loops[&header];
87                let outside_preds = self.outside_predecessors(func, loop_data);
88                loop_data.preheader.is_none()
89                    && !outside_preds.is_empty()
90                    && self.can_split_preheader(func, header, &outside_preds)
91            }) else {
92                break;
93            };
94
95            let loop_data = &loop_info.loops[&header];
96            let outside_preds = self.outside_predecessors(func, loop_data);
97            self.insert_preheader(func, header, &outside_preds);
98        }
99
100        &self.stats
101    }
102
103    fn outside_predecessors(
104        &self,
105        func: &Function,
106        loop_data: &crate::analysis::Loop,
107    ) -> Vec<BlockId> {
108        let mut seen = FxHashSet::default();
109        func.blocks[loop_data.header]
110            .predecessors
111            .iter()
112            .copied()
113            .filter(|pred| !loop_data.blocks.contains(pred))
114            .filter(|pred| seen.insert(*pred))
115            .collect()
116    }
117
118    fn can_split_preheader(
119        &self,
120        func: &Function,
121        header: BlockId,
122        outside_preds: &[BlockId],
123    ) -> bool {
124        for &pred in outside_preds {
125            if !self.terminator_targets(func, pred, header) {
126                return false;
127            }
128        }
129
130        for &inst_id in &func.blocks[header].instructions {
131            let InstKind::Phi(incoming) = &func.instructions[inst_id].kind else {
132                continue;
133            };
134            if outside_preds
135                .iter()
136                .any(|pred| !incoming.iter().any(|(incoming_pred, _)| incoming_pred == pred))
137            {
138                return false;
139            }
140        }
141
142        true
143    }
144
145    fn terminator_targets(&self, func: &Function, block: BlockId, target: BlockId) -> bool {
146        func.blocks[block]
147            .terminator
148            .as_ref()
149            .is_some_and(|term| term.successors().contains(&target))
150    }
151
152    fn insert_preheader(
153        &mut self,
154        func: &mut Function,
155        header: BlockId,
156        outside_preds: &[BlockId],
157    ) {
158        let preheader = func.alloc_block();
159        func.blocks[preheader].terminator = Some(Terminator::Jump(header));
160
161        for &pred in outside_preds {
162            self.redirect_terminator(func, pred, header, preheader);
163        }
164
165        self.rewrite_header_phis(func, header, preheader, outside_preds);
166        repair_reachability_phis(func);
167        self.stats.preheaders_inserted += 1;
168    }
169
170    fn rewrite_header_phis(
171        &mut self,
172        func: &mut Function,
173        header: BlockId,
174        preheader: BlockId,
175        outside_preds: &[BlockId],
176    ) {
177        let phi_insts: Vec<_> = func.blocks[header]
178            .instructions
179            .iter()
180            .copied()
181            .take_while(|&inst_id| matches!(func.instructions[inst_id].kind, InstKind::Phi(_)))
182            .collect();
183
184        let mut preheader_insert_pos = 0;
185        for inst_id in phi_insts {
186            let external_incoming = self.external_phi_incoming(func, inst_id, outside_preds);
187            let preheader_value =
188                self.canonical_preheader_value(func, inst_id, external_incoming, preheader);
189
190            let InstKind::Phi(incoming) = &mut func.instructions[inst_id].kind else {
191                continue;
192            };
193            incoming.retain(|(pred, _)| !outside_preds.contains(pred));
194            incoming.push((preheader, preheader_value));
195            self.stats.header_phis_rewritten += 1;
196
197            if let Value::Inst(phi_inst) = func.values[preheader_value]
198                && func.blocks[preheader].instructions.contains(&phi_inst)
199                && let Some(pos) = func.blocks[preheader]
200                    .instructions
201                    .iter()
202                    .position(|&existing| existing == phi_inst)
203            {
204                let phi_inst = func.blocks[preheader].instructions.remove(pos);
205                func.blocks[preheader].instructions.insert(preheader_insert_pos, phi_inst);
206                preheader_insert_pos += 1;
207            }
208        }
209    }
210
211    fn external_phi_incoming(
212        &self,
213        func: &Function,
214        inst_id: InstId,
215        outside_preds: &[BlockId],
216    ) -> Vec<(BlockId, ValueId)> {
217        let InstKind::Phi(incoming) = &func.instructions[inst_id].kind else {
218            return Vec::new();
219        };
220        outside_preds
221            .iter()
222            .filter_map(|pred| {
223                incoming
224                    .iter()
225                    .find(|(incoming_pred, _)| incoming_pred == pred)
226                    .map(|(_, value)| (*pred, *value))
227            })
228            .collect()
229    }
230
231    fn canonical_preheader_value(
232        &mut self,
233        func: &mut Function,
234        header_phi: InstId,
235        incoming: Vec<(BlockId, ValueId)>,
236        preheader: BlockId,
237    ) -> ValueId {
238        debug_assert!(!incoming.is_empty());
239        let first_value = incoming[0].1;
240        if incoming.iter().all(|(_, value)| *value == first_value) {
241            return first_value;
242        }
243
244        let result_ty =
245            func.instructions[header_phi].result_ty.expect("header phi should have result type");
246        let preheader_phi =
247            func.alloc_inst(Instruction::new(InstKind::Phi(incoming), Some(result_ty)));
248        func.blocks[preheader].instructions.push(preheader_phi);
249        self.stats.preheader_phis_inserted += 1;
250        func.alloc_value(Value::Inst(preheader_phi))
251    }
252
253    fn redirect_terminator(
254        &self,
255        func: &mut Function,
256        block_id: BlockId,
257        old_target: BlockId,
258        new_target: BlockId,
259    ) {
260        let Some(term) = &mut func.blocks[block_id].terminator else {
261            return;
262        };
263        match term {
264            Terminator::Jump(target) => {
265                if *target == old_target {
266                    *target = new_target;
267                }
268            }
269            Terminator::Branch { then_block, else_block, .. } => {
270                if *then_block == old_target {
271                    *then_block = new_target;
272                }
273                if *else_block == old_target {
274                    *else_block = new_target;
275                }
276            }
277            Terminator::Switch { default, cases, .. } => {
278                if *default == old_target {
279                    *default = new_target;
280                }
281                for (_, target) in cases {
282                    if *target == old_target {
283                        *target = new_target;
284                    }
285                }
286            }
287            Terminator::Return { .. }
288            | Terminator::Revert { .. }
289            | Terminator::ReturnData { .. }
290            | Terminator::Stop
291            | Terminator::SelfDestruct { .. }
292            | Terminator::Invalid => {}
293        }
294    }
295}