cairo_lang_lowering/optimizations/
reboxing.rs1#[cfg(test)]
2#[path = "reboxing_test.rs"]
3mod reboxing_test;
4
5use std::rc::Rc;
6
7use cairo_lang_diagnostics::Maybe;
8use cairo_lang_semantic::corelib::core_box_ty;
9use cairo_lang_utils::ordered_hash_map::{Entry, OrderedHashMap};
10use cairo_lang_utils::ordered_hash_set::OrderedHashSet;
11use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
12use itertools::Itertools;
13use salsa::Database;
14
15use super::var_renamer::VarRenamer;
16use crate::analysis::StatementLocation;
17use crate::blocks::Blocks;
18use crate::utils::RebuilderEx;
19use crate::{
20 BlockEnd, Lowered, Statement, StatementStructDestructure, VarUsage, Variable, VariableArena,
21 VariableId,
22};
23
24#[derive(Debug, Clone, PartialEq, Eq, Hash)]
26pub struct MemberOfUnboxedData {
27 pub source: Rc<ReboxingValue>,
29 pub member: usize,
31 pub deconstruct_location: StatementLocation,
33}
34
35#[derive(Debug, Clone, PartialEq, Eq, Hash)]
37pub enum ReboxingValue {
38 Revoked,
40 Unboxed(VariableId),
42 MemberOfUnboxed(MemberOfUnboxedData),
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Hash)]
48pub struct ReboxCandidate {
49 pub source: ReboxingValue,
51 pub reboxed_var: VariableId,
53 pub into_box_location: StatementLocation,
55}
56
57pub fn find_reboxing_candidates<'db>(lowered: &Lowered<'db>) -> OrderedHashSet<ReboxCandidate> {
66 if lowered.blocks.is_empty() {
67 return Default::default();
68 }
69
70 trace!("Running reboxing analysis...");
71
72 let mut current_state: OrderedHashMap<VariableId, ReboxingValue> = Default::default();
81 let mut candidates: OrderedHashSet<ReboxCandidate> = Default::default();
82
83 for (block_id, block) in lowered.blocks.iter() {
84 for (stmt_idx, stmt) in block.statements.iter().enumerate() {
85 match stmt {
86 Statement::Unbox(unbox_stmt) => {
87 let res = ReboxingValue::Unboxed(unbox_stmt.input.var_id);
88 current_state.insert(unbox_stmt.output, res);
89 }
90 Statement::IntoBox(into_box_stmt) => {
91 let source = current_state
92 .get(&into_box_stmt.input.var_id)
93 .unwrap_or(&ReboxingValue::Revoked);
94 if !matches!(source, ReboxingValue::Revoked) {
95 candidates.insert(ReboxCandidate {
96 source: source.clone(),
97 reboxed_var: into_box_stmt.output,
98 into_box_location: (block_id, stmt_idx),
99 });
100 }
101 }
102 Statement::StructDestructure(destructure_stmt) => {
103 let info = &lowered.variables[destructure_stmt.input.var_id].info;
104 if info.copyable.is_err() || info.droppable.is_err() {
105 continue;
106 }
107 let input_state = current_state
108 .get(&destructure_stmt.input.var_id)
109 .cloned()
110 .unwrap_or(ReboxingValue::Revoked);
111 match input_state {
112 ReboxingValue::Revoked => {}
113 ReboxingValue::MemberOfUnboxed { .. } | ReboxingValue::Unboxed(_) => {
114 let input_state_rc = Rc::new(input_state);
115 for (member_idx, output_var) in
116 destructure_stmt.outputs.iter().enumerate()
117 {
118 let res = ReboxingValue::MemberOfUnboxed(MemberOfUnboxedData {
119 source: Rc::clone(&input_state_rc),
120 deconstruct_location: (block_id, stmt_idx),
121 member: member_idx,
122 });
123
124 current_state.insert(*output_var, res);
125 }
126 }
127 }
128 }
129 _ => {}
130 }
131 }
132
133 if let BlockEnd::Goto(_, remapping) = &block.end {
135 for (dst, src_usage) in remapping.iter() {
136 let src_state =
137 current_state.get(&src_usage.var_id).cloned().unwrap_or(ReboxingValue::Revoked);
138 update_reboxing_variable_join(&mut current_state, *dst, src_state);
139 }
140 }
141 }
142
143 trace!("Found {} reboxing candidate(s).", candidates.len());
144 candidates
145}
146
147fn update_reboxing_variable_join(
150 current_state: &mut OrderedHashMap<id_arena::Id<crate::VariableMarker>, ReboxingValue>,
151 var: VariableId,
152 res: ReboxingValue,
153) {
154 match current_state.entry(var) {
155 Entry::Vacant(entry) => {
156 entry.insert(res);
157 }
158 Entry::Occupied(mut entry) => {
159 if entry.get() != &res {
160 entry.insert(ReboxingValue::Revoked);
161 }
162 }
163 }
164}
165
166#[derive(Debug)]
168enum ReboxingOperation<'db> {
169 Add { location: StatementLocation, statement: Statement<'db> },
170 Remove { location: StatementLocation },
171}
172
173impl<'db> ReboxingOperation<'db> {
174 fn apply(self, lowered: &mut Lowered<'db>) {
178 match self {
179 ReboxingOperation::Add { location: (block_id, stmt_idx), statement } => {
180 lowered.blocks[block_id].statements.insert(stmt_idx, statement);
181 }
182 ReboxingOperation::Remove { location: (block_id, stmt_idx) } => {
183 lowered.blocks[block_id].statements.remove(stmt_idx);
184 }
185 }
186 }
187
188 fn location(&self) -> StatementLocation {
190 match self {
191 ReboxingOperation::Add { location, .. } => *location,
192 ReboxingOperation::Remove { location } => *location,
193 }
194 }
195}
196
197pub fn apply_reboxing_candidates<'db>(
199 db: &'db dyn Database,
200 lowered: &mut Lowered<'db>,
201 candidates: &OrderedHashSet<ReboxCandidate>,
202) -> Maybe<()> {
203 if candidates.is_empty() {
204 trace!("No reboxing candidates to apply.");
205 return Ok(());
206 }
207
208 trace!("Applying {} reboxing optimization(s).", candidates.len());
209
210 let mut renamer = VarRenamer::default();
211 let mut operations = Vec::new();
212 let mut added_boxes = UnorderedHashMap::default();
213
214 for candidate in candidates.iter() {
215 let box_var_to_use = match &candidate.source {
216 ReboxingValue::Revoked => unreachable!("Revoked reboxing candidate should not exist"),
217 ReboxingValue::Unboxed(id) => *id,
218 ReboxingValue::MemberOfUnboxed(data) => {
219 create_deconstruct_statements(
222 db,
223 &mut lowered.variables,
224 &lowered.blocks,
225 &mut operations,
226 &mut added_boxes,
227 data,
228 )
229 }
230 };
231 renamer.renamed_vars.insert(candidate.reboxed_var, box_var_to_use);
232 operations.push(ReboxingOperation::Remove { location: candidate.into_box_location });
233 }
234
235 operations.sort_by_key(|op| {
238 let (block_id, stmt_idx) = op.location();
239 (std::cmp::Reverse(block_id.0), std::cmp::Reverse(stmt_idx))
240 });
241
242 operations.into_iter().for_each(|operation| operation.apply(lowered));
243
244 for block in lowered.blocks.iter_mut() {
245 *block = renamer.rebuild_block(block);
246 }
247 Ok(())
248}
249
250#[allow(clippy::too_many_arguments)]
253fn create_deconstruct_statements<'db>(
254 db: &'db dyn Database,
255 variables: &mut VariableArena<'db>,
256 blocks: &Blocks<'db>,
257 operations: &mut Vec<ReboxingOperation<'db>>,
258 added_boxes: &mut UnorderedHashMap<VariableId, Vec<VariableId>>,
259 data: &MemberOfUnboxedData,
260) -> VariableId {
261 let deconstruct_statement = &blocks[data.deconstruct_location];
262 let deconstruct_outputs = deconstruct_statement.outputs();
263 let input_var = deconstruct_statement.inputs()[0];
264 if let Some(boxed_vars) = added_boxes.get(&input_var.var_id) {
265 return boxed_vars[data.member];
266 }
267
268 let source_box = match data.source.as_ref() {
269 ReboxingValue::Revoked => {
270 unreachable!("A Revoked reboxing state should be blocked before.")
271 }
272 ReboxingValue::Unboxed(id) => {
273 *id
275 }
276 ReboxingValue::MemberOfUnboxed(data) => {
277 create_deconstruct_statements(db, variables, blocks, operations, added_boxes, data)
279 }
280 };
281 let outputs = deconstruct_outputs
282 .iter()
283 .map(|out_var_id| {
284 let out_var = &variables[*out_var_id];
285 let box_ty = core_box_ty(db, out_var.ty);
286 let out_location = out_var.location;
287 variables.alloc(Variable::with_default_context(db, box_ty, out_location))
288 })
289 .collect_vec();
290
291 operations.push(ReboxingOperation::Add {
292 location: data.deconstruct_location,
293 statement: Statement::StructDestructure(StatementStructDestructure {
294 input: VarUsage { var_id: source_box, location: input_var.location },
295 outputs: outputs.clone(),
296 }),
297 });
298 added_boxes.insert(input_var.var_id, outputs);
299 added_boxes[&input_var.var_id][data.member]
300}
301
302pub fn apply_reboxing<'db>(db: &'db dyn Database, lowered: &mut Lowered<'db>) -> Maybe<()> {
311 let candidates = find_reboxing_candidates(lowered);
312 apply_reboxing_candidates(db, lowered, &candidates)
313}