cairo_lang_lowering/optimizations/
variable_forwarding.rs1#[cfg(test)]
2#[path = "variable_forwarding_test.rs"]
3mod test;
4
5use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
6use cairo_lang_utils::ordered_hash_set::OrderedHashSet;
7use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
8use cairo_lang_utils::unordered_hash_set::UnorderedHashSet;
9use itertools::Itertools;
10use salsa::Database;
11
12use crate::analysis::def_site::DefSiteAnalysis;
13use crate::analysis::dominator::Dominators;
14use crate::analysis::equality_analysis::{EqualityAnalysis, EqualityState};
15use crate::analysis::topological_order::TopologicalOrder;
16use crate::analysis::use_sites::UseSites;
17use crate::analysis::{DefLocation, UseLocation};
18use crate::objects::blocks::Blocks;
19use crate::{BlockEnd, BlockId, Lowered, Statement, VariableArena, VariableId};
20
21struct RenameDep {
23 loc: UseLocation,
24 from: VariableId,
25 to: VariableId,
26 count: usize,
29}
30
31#[derive(Default)]
33struct CommittedState {
34 removed: OrderedHashMap<BlockId, UnorderedHashSet<usize>>,
36 renames: Vec<RenameDep>,
38 freed_count: UnorderedHashMap<VariableId, usize>,
40 added_count: UnorderedHashMap<VariableId, usize>,
42}
43
44impl CommittedState {
45 fn is_stmt_removed(&self, block_id: BlockId, stmt_idx: usize) -> bool {
46 self.removed.get(&block_id).is_some_and(|indices| indices.contains(&stmt_idx))
47 }
48
49 fn merge(&mut self, txn: Transaction) {
50 for def in txn.removed {
51 if let DefLocation::Statement((block_id, stmt_idx)) = def {
53 self.removed.entry(block_id).or_default().insert(stmt_idx);
54 }
55 }
56 self.renames.extend(txn.renames);
57 for (v, delta) in txn.freed_delta {
58 *self.freed_count.entry(v).or_default() += delta;
59 }
60 for (v, delta) in txn.added_delta {
61 *self.added_count.entry(v).or_default() += delta;
62 }
63 }
64}
65
66#[derive(Default)]
69struct Transaction {
70 removed: OrderedHashSet<DefLocation>,
72 renames: Vec<RenameDep>,
74 freed_delta: OrderedHashMap<VariableId, usize>,
76 added_delta: OrderedHashMap<VariableId, usize>,
78}
79
80pub fn variable_forwarding<'db>(db: &'db dyn Database, lowered: &mut Lowered<'db>) {
87 if lowered.blocks.is_empty() {
88 return;
89 }
90
91 let equalities = EqualityAnalysis::analyze(db, lowered);
92 let dominators = Dominators::analyze(lowered);
93 let def_sites = DefSiteAnalysis::analyze(lowered).0;
94 let use_sites = UseSites::analyze(lowered);
95 let topological_order = TopologicalOrder::analyze(lowered);
96 let ctx = ForwardingCtx {
97 variables: &lowered.variables,
98 blocks: &mut lowered.blocks,
99 dominators,
100 def_sites,
101 use_sites,
102 equalities: &equalities,
103 };
104 ctx.forward_all(topological_order);
105}
106
107struct ForwardingCtx<'a, 'db> {
109 variables: &'a VariableArena<'db>,
111 blocks: &'a mut Blocks<'db>,
113 dominators: Dominators,
115 def_sites: Vec<Option<DefLocation>>,
117 use_sites: UseSites,
119 equalities: &'a [Option<EqualityState<'db>>],
121}
122
123impl<'a, 'db> ForwardingCtx<'a, 'db> {
124 fn forward_all(mut self, topological_order: TopologicalOrder) {
125 self.prefill();
127
128 let mut committed = CommittedState::default();
134 let mut tried: UnorderedHashSet<DefLocation> = UnorderedHashSet::default();
135 for block_id in topological_order.iter().rev() {
136 for stmt_idx in (0..self.blocks[*block_id].statements.len()).rev() {
137 if self.blocks[*block_id].statements[stmt_idx].outputs().is_empty() {
138 continue;
139 }
140 let mut txn = Transaction::default();
141 if self.try_remove(
142 DefLocation::Statement((*block_id, stmt_idx)),
143 &mut tried,
144 &committed,
145 &mut txn,
146 ) && self.verify_non_copy_safety(&committed, &txn)
147 {
148 committed.merge(txn);
149 }
150 }
151 }
152
153 for r in &committed.renames {
155 if let UseLocation::Statement((block_id, stmt_idx)) = r.loc
156 && committed.is_stmt_removed(block_id, stmt_idx)
157 {
158 continue;
159 }
160 self.rename_var(r.loc, r.from, r.to);
161 }
162 for (block_id, indices) in committed.removed.iter() {
163 let mut i = 0;
164 self.blocks[*block_id].statements.retain(|_| {
165 let keep = !indices.contains(&i);
166 i += 1;
167 keep
168 });
169 }
170 }
171
172 fn prefill(&mut self) {
174 for (var_id, var) in self.variables {
175 let Some(def) = self.definition(var_id) else {
176 continue;
177 };
178 if !self.removable(def) || var.info.copyable.is_err() || var.info.droppable.is_err() {
179 continue;
180 }
181 for (loc, _count) in self.use_sites.use_locs(var_id).collect_vec() {
183 let Some(eq_state) = self.equalities[loc.block().0].as_ref() else {
184 continue;
185 };
186 let lead = eq_state.find_immut(var_id);
187 if lead != var_id && self.is_visible(loc, lead) {
188 self.rename_var(loc, var_id, lead);
189 self.use_sites.move_uses(var_id, lead, loc);
191 }
192 }
193 }
194 }
195
196 fn try_remove(
201 &self,
202 def: DefLocation,
203 tried: &mut UnorderedHashSet<DefLocation>,
204 committed: &CommittedState,
205 txn: &mut Transaction,
206 ) -> bool {
207 if self.is_removed(def, committed, txn) {
208 return true;
209 }
210 if !tried.insert(def) {
212 return false;
213 }
214
215 let Some(deps) = self.try_forward_all_outputs(def, committed, txn) else {
216 return false;
217 };
218
219 txn.removed.insert(def);
221 for r in &deps {
222 *txn.added_delta.entry(r.to).or_default() += r.count;
223 }
224 txn.renames.extend(deps);
225
226 let DefLocation::Statement(stmt_loc) = def else { return true };
228 for var_usage in self.blocks[stmt_loc].inputs() {
232 let var_id = var_usage.var_id;
233 *txn.freed_delta.entry(var_id).or_default() += 1;
234
235 if self.variables[var_id].info.copyable.is_err()
236 && self.effective_use_count(var_id, committed, txn) == 0
237 {
238 let Some(producer) = self.definition(var_id) else {
245 continue;
246 };
247 if !self.try_remove(producer, tried, committed, txn) {
248 return false;
249 }
250 }
251 }
252 true
253 }
254
255 fn forwarding_dependencies(
259 &self,
260 v: VariableId,
261 committed: &CommittedState,
262 txn: &Transaction,
263 ) -> Option<Vec<RenameDep>> {
264 let mut deps = Vec::new();
265 for (loc, count) in self.use_sites.use_locs(v) {
266 if let UseLocation::Statement(sl) = loc
267 && self.is_removed(DefLocation::Statement(sl), committed, txn)
268 {
269 continue;
270 }
271 let eq_state = self.equalities[loc.block().0].as_ref()?;
272 let rep = eq_state.find_immut(v);
274 if rep == v || !self.is_visible(loc, rep) {
275 return None;
276 }
277 deps.push(RenameDep { loc, from: v, to: rep, count });
278 }
279 if self.has_added_uses(v, committed, txn) {
283 return None;
284 }
285 Some(deps)
286 }
287
288 fn try_forward_all_outputs(
290 &self,
291 def: DefLocation,
292 committed: &CommittedState,
293 txn: &Transaction,
294 ) -> Option<Vec<RenameDep>> {
295 if !self.removable(def) {
296 return None;
297 }
298 let DefLocation::Statement(sl) = def else { return None };
300 let outputs: Vec<VariableId> = self.blocks[sl].outputs().to_vec();
301 let mut all_renames = Vec::new();
302 for v in outputs {
303 all_renames.extend(self.forwarding_dependencies(v, committed, txn)?);
304 }
305 Some(all_renames)
306 }
307
308 fn effective_use_count(
310 &self,
311 v: VariableId,
312 committed: &CommittedState,
313 txn: &Transaction,
314 ) -> usize {
315 self.use_sites.use_count(v)
316 + committed.added_count.get(&v).copied().unwrap_or(0)
317 + txn.added_delta.get(&v).copied().unwrap_or(0)
318 - committed.freed_count.get(&v).copied().unwrap_or(0)
319 - txn.freed_delta.get(&v).copied().unwrap_or(0)
320 }
321
322 fn verify_non_copy_safety(&self, committed: &CommittedState, txn: &Transaction) -> bool {
331 txn.added_delta.iter().all(|(&v, _)| {
332 self.variables[v].info.copyable.is_ok()
333 || self.effective_use_count(v, committed, txn) <= self.use_sites.use_count(v)
334 })
335 }
336
337 fn rename_var(&mut self, loc: UseLocation, from: VariableId, to: VariableId) {
339 match loc {
340 UseLocation::Statement((block_id, stmt_idx)) => {
341 for input in self.blocks[block_id].statements[stmt_idx].inputs_mut() {
342 if input.var_id == from {
343 input.var_id = to;
344 }
345 }
346 }
347 UseLocation::BlockEnd(block_id) => match &mut self.blocks[block_id].end {
348 BlockEnd::Return(inputs, _) => {
349 for input in inputs {
350 if input.var_id == from {
351 input.var_id = to;
352 }
353 }
354 }
355 BlockEnd::Panic(input) => {
356 if input.var_id == from {
357 input.var_id = to;
358 }
359 }
360 BlockEnd::Match { info } => {
361 for input in info.inputs_mut() {
362 if input.var_id == from {
363 input.var_id = to;
364 }
365 }
366 }
367 BlockEnd::Goto(_, remapping) => {
368 for src in remapping.values_mut() {
369 if src.var_id == from {
370 src.var_id = to;
371 }
372 }
373 }
374 BlockEnd::NotSet => {}
375 },
376 }
377 }
378
379 fn is_visible(&self, use_loc: UseLocation, var_id: VariableId) -> bool {
380 let Some(def) = self.definition(var_id) else {
381 return false;
382 };
383 let def_block = def.block();
384 let use_block = use_loc.block();
385 if def_block != use_block {
386 return self.dominators.dominates(def_block, use_block);
387 }
388 match (def, use_loc) {
389 (DefLocation::BlockEntry(_), _) => true,
390 (DefLocation::Statement(_), UseLocation::BlockEnd(_)) => true,
391 (DefLocation::Statement((_, di)), UseLocation::Statement((_, ui))) => ui > di,
392 }
393 }
394
395 fn is_committed(&self, def: DefLocation, committed: &CommittedState) -> bool {
396 match def {
397 DefLocation::Statement((block_id, stmt_idx)) => {
398 committed.is_stmt_removed(block_id, stmt_idx)
399 }
400 DefLocation::BlockEntry(_) => false,
401 }
402 }
403
404 fn is_removed(&self, def: DefLocation, committed: &CommittedState, txn: &Transaction) -> bool {
405 self.is_committed(def, committed) || txn.removed.contains(&def)
406 }
407
408 fn has_added_uses(&self, v: VariableId, committed: &CommittedState, txn: &Transaction) -> bool {
409 committed.added_count.contains_key(&v) || txn.added_delta.contains_key(&v)
410 }
411
412 fn definition(&self, var: VariableId) -> Option<DefLocation> {
413 self.def_sites[var.index()]
414 }
415
416 fn removable(&self, def: DefLocation) -> bool {
417 match def {
418 DefLocation::Statement(stmt_loc) => match self.blocks[stmt_loc] {
419 Statement::Const(_)
420 | Statement::StructConstruct(_)
421 | Statement::StructDestructure(_)
422 | Statement::EnumConstruct(_)
423 | Statement::Snapshot(_)
424 | Statement::Desnap(_)
425 | Statement::IntoBox(_)
426 | Statement::Unbox(_) => true,
427 Statement::Call(_) => false,
428 },
429 DefLocation::BlockEntry(_) => true,
430 }
431 }
432}