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 let info = &self.variables[var_id].info;
236 if (info.copyable.is_err() || info.droppable.is_err())
237 && self.effective_use_count(var_id, committed, txn) == 0
238 {
239 let Some(producer) = self.definition(var_id) else {
247 continue;
248 };
249 if !self.try_remove(producer, tried, committed, txn) {
250 return false;
251 }
252 }
253 }
254 true
255 }
256
257 fn forwarding_dependencies(
261 &self,
262 v: VariableId,
263 committed: &CommittedState,
264 txn: &Transaction,
265 ) -> Option<Vec<RenameDep>> {
266 let mut deps = Vec::new();
267 for (loc, count) in self.use_sites.use_locs(v) {
268 if let UseLocation::Statement(sl) = loc
269 && self.is_removed(DefLocation::Statement(sl), committed, txn)
270 {
271 continue;
272 }
273 let eq_state = self.equalities[loc.block().0].as_ref()?;
274 let rep = eq_state.find_immut(v);
276 if rep == v || !self.is_visible(loc, rep) {
277 return None;
278 }
279 deps.push(RenameDep { loc, from: v, to: rep, count });
280 }
281 if self.has_added_uses(v, committed, txn) {
285 return None;
286 }
287 Some(deps)
288 }
289
290 fn try_forward_all_outputs(
292 &self,
293 def: DefLocation,
294 committed: &CommittedState,
295 txn: &Transaction,
296 ) -> Option<Vec<RenameDep>> {
297 if !self.removable(def) {
298 return None;
299 }
300 let DefLocation::Statement(sl) = def else { return None };
302 let outputs: Vec<VariableId> = self.blocks[sl].outputs().to_vec();
303 let mut all_renames = Vec::new();
304 for v in outputs {
305 all_renames.extend(self.forwarding_dependencies(v, committed, txn)?);
306 }
307 Some(all_renames)
308 }
309
310 fn effective_use_count(
312 &self,
313 v: VariableId,
314 committed: &CommittedState,
315 txn: &Transaction,
316 ) -> usize {
317 self.use_sites.use_count(v)
318 + committed.added_count.get(&v).copied().unwrap_or(0)
319 + txn.added_delta.get(&v).copied().unwrap_or(0)
320 - committed.freed_count.get(&v).copied().unwrap_or(0)
321 - txn.freed_delta.get(&v).copied().unwrap_or(0)
322 }
323
324 fn verify_non_copy_safety(&self, committed: &CommittedState, txn: &Transaction) -> bool {
333 txn.added_delta.iter().all(|(&v, _)| {
334 self.variables[v].info.copyable.is_ok()
335 || self.effective_use_count(v, committed, txn) <= self.use_sites.use_count(v)
336 })
337 }
338
339 fn rename_var(&mut self, loc: UseLocation, from: VariableId, to: VariableId) {
341 match loc {
342 UseLocation::Statement((block_id, stmt_idx)) => {
343 for input in self.blocks[block_id].statements[stmt_idx].inputs_mut() {
344 if input.var_id == from {
345 input.var_id = to;
346 }
347 }
348 }
349 UseLocation::BlockEnd(block_id) => match &mut self.blocks[block_id].end {
350 BlockEnd::Return(inputs, _) => {
351 for input in inputs {
352 if input.var_id == from {
353 input.var_id = to;
354 }
355 }
356 }
357 BlockEnd::Panic(input) => {
358 if input.var_id == from {
359 input.var_id = to;
360 }
361 }
362 BlockEnd::Match { info } => {
363 for input in info.inputs_mut() {
364 if input.var_id == from {
365 input.var_id = to;
366 }
367 }
368 }
369 BlockEnd::Goto(_, remapping) => {
370 for src in remapping.values_mut() {
371 if src.var_id == from {
372 src.var_id = to;
373 }
374 }
375 }
376 BlockEnd::NotSet => {}
377 },
378 }
379 }
380
381 fn is_visible(&self, use_loc: UseLocation, var_id: VariableId) -> bool {
382 let Some(def) = self.definition(var_id) else {
383 return false;
384 };
385 let def_block = def.block();
386 let use_block = use_loc.block();
387 if def_block != use_block {
388 return self.dominators.dominates(def_block, use_block);
389 }
390 match (def, use_loc) {
391 (DefLocation::BlockEntry(_), _) => true,
392 (DefLocation::Statement(_), UseLocation::BlockEnd(_)) => true,
393 (DefLocation::Statement((_, di)), UseLocation::Statement((_, ui))) => ui > di,
394 }
395 }
396
397 fn is_committed(&self, def: DefLocation, committed: &CommittedState) -> bool {
398 match def {
399 DefLocation::Statement((block_id, stmt_idx)) => {
400 committed.is_stmt_removed(block_id, stmt_idx)
401 }
402 DefLocation::BlockEntry(_) => false,
403 }
404 }
405
406 fn is_removed(&self, def: DefLocation, committed: &CommittedState, txn: &Transaction) -> bool {
407 self.is_committed(def, committed) || txn.removed.contains(&def)
408 }
409
410 fn has_added_uses(&self, v: VariableId, committed: &CommittedState, txn: &Transaction) -> bool {
411 committed.added_count.contains_key(&v) || txn.added_delta.contains_key(&v)
412 }
413
414 fn definition(&self, var: VariableId) -> Option<DefLocation> {
415 self.def_sites[var.index()]
416 }
417
418 fn removable(&self, def: DefLocation) -> bool {
419 match def {
420 DefLocation::Statement(stmt_loc) => match self.blocks[stmt_loc] {
421 Statement::Const(_)
422 | Statement::StructConstruct(_)
423 | Statement::StructDestructure(_)
424 | Statement::EnumConstruct(_)
425 | Statement::Snapshot(_)
426 | Statement::Desnap(_)
427 | Statement::IntoBox(_)
428 | Statement::Unbox(_) => true,
429 Statement::Call(_) => false,
430 },
431 DefLocation::BlockEntry(_) => true,
432 }
433 }
434}