1use 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#[derive(Clone, Debug, Default)]
26pub struct LoopCanonicalizeStats {
27 pub preheaders_inserted: usize,
29 pub header_phis_rewritten: usize,
31 pub preheader_phis_inserted: usize,
33}
34
35impl LoopCanonicalizeStats {
36 #[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#[derive(Debug, Default)]
45pub struct LoopCanonicalizer {
46 stats: LoopCanonicalizeStats,
47}
48
49pub 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 #[must_use]
65 pub fn new() -> Self {
66 Self::default()
67 }
68
69 #[must_use]
71 pub const fn stats(&self) -> &LoopCanonicalizeStats {
72 &self.stats
73 }
74
75 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}