1#[cfg(test)]
2#[path = "const_folding_test.rs"]
3mod test;
4
5use std::rc::Rc;
6use std::sync::Arc;
7
8use cairo_lang_defs::ids::{ExternFunctionId, FreeFunctionId};
9use cairo_lang_filesystem::flag::FlagsGroup;
10use cairo_lang_filesystem::ids::SmolStrId;
11use cairo_lang_semantic::corelib::CorelibSemantic;
12use cairo_lang_semantic::helper::ModuleHelper;
13use cairo_lang_semantic::items::constant::{
14 ConstCalcInfo, ConstValue, ConstValueId, ConstantSemantic, TypeRange, canonical_felt252,
15 felt252_for_downcast,
16};
17use cairo_lang_semantic::items::functions::{GenericFunctionId, GenericFunctionWithBodyId};
18use cairo_lang_semantic::items::structure::StructSemantic;
19use cairo_lang_semantic::types::{TypeSizeInformation, TypesSemantic};
20use cairo_lang_semantic::{
21 ConcreteTypeId, ConcreteVariant, GenericArgumentId, MatchArmSelector, TypeId, TypeLongId,
22 corelib,
23};
24use cairo_lang_utils::byte_array::BYTE_ARRAY_MAGIC;
25use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
26use cairo_lang_utils::ordered_hash_set::OrderedHashSet;
27use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
28use cairo_lang_utils::{Intern, extract_matches, require, try_extract_matches};
29use itertools::{Itertools, chain, zip_eq};
30use num_bigint::BigInt;
31use num_integer::Integer;
32use num_traits::cast::ToPrimitive;
33use num_traits::{Num, One, Zero};
34use salsa::Database;
35use starknet_types_core::felt::Felt as Felt252;
36
37use crate::db::LoweringGroup;
38use crate::ids::{
39 ConcreteFunctionWithBodyId, ConcreteFunctionWithBodyLongId, FunctionId, SemanticFunctionIdEx,
40 SpecializedFunction,
41};
42use crate::specialization::SpecializationArg;
43use crate::utils::InliningStrategy;
44use crate::{
45 Block, BlockEnd, BlockId, DependencyType, Lowered, LoweringStage, MatchArm, MatchEnumInfo,
46 MatchExternInfo, MatchInfo, Statement, StatementCall, StatementConst, StatementDesnap,
47 StatementEnumConstruct, StatementIntoBox, StatementSnapshot, StatementStructConstruct,
48 StatementStructDestructure, StatementUnbox, VarRemapping, VarUsage, Variable, VariableArena,
49 VariableId,
50};
51
52fn const_to_specialization_arg<'db>(
56 db: &'db dyn Database,
57 value: ConstValueId<'db>,
58 boxed: bool,
59) -> SpecializationArg<'db> {
60 match value.long(db) {
61 ConstValue::Struct(members, ty) => {
62 if matches!(
65 ty.long(db),
66 TypeLongId::Concrete(ConcreteTypeId::Struct(_))
67 | TypeLongId::Tuple(_)
68 | TypeLongId::FixedSizeArray { .. }
69 ) {
70 let args = members
71 .iter()
72 .map(|member| const_to_specialization_arg(db, *member, false))
73 .collect();
74 SpecializationArg::Struct(args)
75 } else {
76 SpecializationArg::Const { value, boxed }
77 }
78 }
79 ConstValue::Enum(variant, payload) => SpecializationArg::Enum {
80 variant: *variant,
81 payload: Box::new(const_to_specialization_arg(db, *payload, false)),
82 },
83 _ => SpecializationArg::Const { value, boxed },
84 }
85}
86
87#[derive(Debug, Clone)]
90enum VarInfo<'db> {
91 Const(ConstValueId<'db>),
93 Var(VarUsage<'db>),
95 Snapshot(Rc<VarInfo<'db>>),
97 Struct(Vec<Option<Rc<VarInfo<'db>>>>),
100 Enum { variant: ConcreteVariant<'db>, payload: Rc<VarInfo<'db>> },
102 Box(Rc<VarInfo<'db>>),
104 Array(Vec<Option<Rc<VarInfo<'db>>>>),
107}
108impl<'db> VarInfo<'db> {
109 fn peel_snapshots(mut self: Rc<Self>) -> (usize, Rc<VarInfo<'db>>) {
111 let mut n_snapshots = 0;
112 while let VarInfo::Snapshot(inner) = self.as_ref() {
113 self = inner.clone();
114 n_snapshots += 1;
115 }
116 (n_snapshots, self)
117 }
118 fn wrap_with_snapshots(mut self: Rc<Self>, n_snapshots: usize) -> Rc<VarInfo<'db>> {
120 for _ in 0..n_snapshots {
121 self = VarInfo::Snapshot(self).into();
122 }
123 self
124 }
125}
126
127#[derive(Debug, Clone, Copy, PartialEq)]
128enum Reachability {
129 FromSingleGoto(BlockId),
132 Any,
135}
136
137pub fn const_folding<'db>(
140 db: &'db dyn Database,
141 function_id: ConcreteFunctionWithBodyId<'db>,
142 lowered: &mut Lowered<'db>,
143) {
144 if lowered.blocks.is_empty() {
145 return;
146 }
147
148 let mut ctx = ConstFoldingContext::new(db, function_id, &mut lowered.variables);
151
152 if ctx.should_skip_const_folding(db) {
153 return;
154 }
155
156 for block_id in (0..lowered.blocks.len()).map(BlockId) {
157 if !ctx.visit_block_start(block_id, |block_id| &lowered.blocks[block_id]) {
158 continue;
159 }
160
161 let block = &mut lowered.blocks[block_id];
162 for stmt in block.statements.iter_mut() {
163 ctx.visit_statement(stmt);
164 }
165 ctx.visit_block_end(block_id, block);
166 }
167}
168
169pub struct ConstFoldingContext<'db, 'mt> {
170 db: &'db dyn Database,
172 pub variables: &'mt mut VariableArena<'db>,
174 var_info: UnorderedHashMap<VariableId, Rc<VarInfo<'db>>>,
176 libfunc_info: &'db ConstFoldingLibfuncInfo<'db>,
178 caller_function: ConcreteFunctionWithBodyId<'db>,
181 reachability: UnorderedHashMap<BlockId, Reachability>,
185 additional_stmts: Vec<Statement<'db>>,
187}
188
189impl<'db, 'mt> ConstFoldingContext<'db, 'mt> {
190 pub fn new(
191 db: &'db dyn Database,
192 function_id: ConcreteFunctionWithBodyId<'db>,
193 variables: &'mt mut VariableArena<'db>,
194 ) -> Self {
195 Self {
196 db,
197 var_info: UnorderedHashMap::default(),
198 variables,
199 libfunc_info: priv_const_folding_info(db),
200 caller_function: function_id,
201 reachability: UnorderedHashMap::from_iter([(BlockId::root(), Reachability::Any)]),
202 additional_stmts: vec![],
203 }
204 }
205
206 pub fn visit_block_start<'r, 'get>(
209 &'r mut self,
210 block_id: BlockId,
211 get_block: impl FnOnce(BlockId) -> &'get Block<'db>,
212 ) -> bool
213 where
214 'db: 'get,
215 {
216 let Some(reachability) = self.reachability.remove(&block_id) else {
217 return false;
218 };
219 match reachability {
220 Reachability::Any => {}
221 Reachability::FromSingleGoto(from_block) => match &get_block(from_block).end {
222 BlockEnd::Goto(_, remapping) => {
223 for (dst, src) in remapping.iter() {
224 if let Some(v) = self.as_const(src.var_id) {
225 self.var_info.insert(*dst, VarInfo::Const(v).into());
226 }
227 }
228 }
229 _ => unreachable!("Expected a goto end"),
230 },
231 }
232 true
233 }
234
235 pub fn visit_statement(&mut self, stmt: &mut Statement<'db>) {
245 self.maybe_replace_inputs(stmt.inputs_mut());
246 match stmt {
247 Statement::Const(StatementConst { value, output, boxed }) if *boxed => {
248 self.var_info.insert(*output, VarInfo::Box(VarInfo::Const(*value).into()).into());
249 }
250 Statement::Const(StatementConst { value, output, .. }) => match value.long(self.db) {
251 ConstValue::Int(..)
252 | ConstValue::Struct(..)
253 | ConstValue::Enum(..)
254 | ConstValue::NonZero(..) => {
255 self.var_info.insert(*output, VarInfo::Const(*value).into());
256 }
257 ConstValue::Generic(_)
258 | ConstValue::ImplConstant(_)
259 | ConstValue::Var(..)
260 | ConstValue::Missing(_) => {}
261 },
262 Statement::Snapshot(stmt) => {
263 if let Some(info) = self.var_info.get(&stmt.input.var_id) {
264 let info = info.clone();
265 self.var_info.insert(stmt.original(), info.clone());
266 self.var_info.insert(stmt.snapshot(), VarInfo::Snapshot(info).into());
267 }
268 }
269 Statement::Desnap(StatementDesnap { input, output }) => {
270 if let Some(info) = self.var_info.get(&input.var_id)
271 && let VarInfo::Snapshot(info) = info.as_ref()
272 {
273 self.var_info.insert(*output, info.clone());
274 }
275 }
276 Statement::Call(call_stmt) => {
277 if let Some(updated_stmt) = self.handle_statement_call(call_stmt) {
278 *stmt = updated_stmt;
279 } else if let Some(updated_stmt) = self.try_specialize_call(call_stmt) {
280 *stmt = updated_stmt;
281 }
282 }
283 Statement::StructConstruct(StatementStructConstruct { inputs, output }) => {
284 let mut const_args = vec![];
285 let mut all_args = vec![];
286 let mut contains_info = false;
287 for input in inputs.iter() {
288 let Some(info) = self.var_info.get(&input.var_id) else {
289 all_args.push(var_info_if_copy(self.variables, *input));
290 continue;
291 };
292 contains_info = true;
293 if let VarInfo::Const(value) = info.as_ref() {
294 const_args.push(*value);
295 }
296 all_args.push(Some(info.clone()));
297 }
298 if const_args.len() == inputs.len() {
299 let value =
300 ConstValue::Struct(const_args, self.variables[*output].ty).intern(self.db);
301 self.var_info.insert(*output, VarInfo::Const(value).into());
302 } else if contains_info {
303 self.var_info.insert(*output, VarInfo::Struct(all_args).into());
304 }
305 }
306 Statement::StructDestructure(StatementStructDestructure { input, outputs }) => {
307 if let Some(info) = self.var_info.get(&input.var_id) {
308 let (n_snapshots, info) = info.clone().peel_snapshots();
309 match info.as_ref() {
310 VarInfo::Const(const_value) => {
311 if let ConstValue::Struct(member_values, _) = const_value.long(self.db)
312 {
313 for (output, value) in zip_eq(outputs, member_values) {
314 self.var_info.insert(
315 *output,
316 Rc::new(VarInfo::Const(*value))
317 .wrap_with_snapshots(n_snapshots),
318 );
319 }
320 }
321 }
322 VarInfo::Struct(members) => {
323 for (output, member) in zip_eq(outputs, members.clone()) {
324 if let Some(member) = member {
325 self.var_info
326 .insert(*output, member.wrap_with_snapshots(n_snapshots));
327 }
328 }
329 }
330 _ => {}
331 }
332 }
333 }
334 Statement::EnumConstruct(StatementEnumConstruct { variant, input, output }) => {
335 let value = if let Some(info) = self.var_info.get(&input.var_id) {
336 if let VarInfo::Const(val) = info.as_ref() {
337 VarInfo::Const(ConstValue::Enum(*variant, *val).intern(self.db))
338 } else {
339 VarInfo::Enum { variant: *variant, payload: info.clone() }
340 }
341 } else {
342 VarInfo::Enum { variant: *variant, payload: VarInfo::Var(*input).into() }
343 };
344 self.var_info.insert(*output, value.into());
345 }
346 Statement::IntoBox(StatementIntoBox { input, output }) => {
347 let var_info = self.var_info.get(&input.var_id);
348 let const_value = var_info.and_then(|var_info| match var_info.as_ref() {
349 VarInfo::Const(val) => Some(*val),
350 VarInfo::Snapshot(info) => {
351 try_extract_matches!(info.as_ref(), VarInfo::Const).copied()
352 }
353 _ => None,
354 });
355 let var_info =
356 var_info.cloned().or_else(|| var_info_if_copy(self.variables, *input));
357 if let Some(var_info) = var_info {
358 self.var_info.insert(*output, VarInfo::Box(var_info).into());
359 }
360
361 if let Some(const_value) = const_value {
362 *stmt = Statement::Const(StatementConst::new_boxed(const_value, *output));
363 }
364 }
365 Statement::Unbox(StatementUnbox { input, output }) => {
366 if let Some(inner) = self.var_info.get(&input.var_id)
367 && let VarInfo::Box(inner) = inner.as_ref()
368 {
369 let inner = inner.clone();
370 if let VarInfo::Const(inner) =
371 self.var_info.entry(*output).insert_entry(inner).get().as_ref()
372 {
373 *stmt = Statement::Const(StatementConst::new_flat(*inner, *output));
374 }
375 }
376 }
377 }
378 }
379
380 pub fn visit_block_end(&mut self, block_id: BlockId, block: &mut Block<'db>) {
387 let statements = &mut block.statements;
388 statements.splice(0..0, self.additional_stmts.drain(..));
389
390 match &mut block.end {
391 BlockEnd::Goto(_, remappings) => {
392 for (_, v) in remappings.iter_mut() {
393 self.maybe_replace_input(v);
394 }
395 }
396 BlockEnd::Match { info } => {
397 self.maybe_replace_inputs(info.inputs_mut());
398 match info {
399 MatchInfo::Enum(info) => {
400 if let Some(updated_end) = self.handle_enum_block_end(info, statements) {
401 block.end = updated_end;
402 }
403 }
404 MatchInfo::Extern(info) => {
405 if let Some(updated_end) = self.handle_extern_block_end(info, statements) {
406 block.end = updated_end;
407 }
408 }
409 MatchInfo::Value(info) => {
410 if let Some(value) =
411 self.as_int(info.input.var_id).and_then(|x| x.to_usize())
412 && let Some(arm) = info.arms.iter().find(|arm| {
413 matches!(
414 &arm.arm_selector,
415 MatchArmSelector::Value(v) if v.value == value
416 )
417 })
418 {
419 statements.push(Statement::StructConstruct(StatementStructConstruct {
421 inputs: vec![],
422 output: arm.var_ids[0],
423 }));
424 block.end = BlockEnd::Goto(arm.block_id, Default::default());
425 }
426 }
427 }
428 }
429 BlockEnd::Return(inputs, _) => self.maybe_replace_inputs(inputs),
430 BlockEnd::Panic(_) | BlockEnd::NotSet => unreachable!(),
431 }
432 match &block.end {
433 BlockEnd::Goto(dst_block_id, _) => {
434 match self.reachability.entry(*dst_block_id) {
435 std::collections::hash_map::Entry::Occupied(mut e) => {
436 e.insert(Reachability::Any)
437 }
438 std::collections::hash_map::Entry::Vacant(e) => {
439 *e.insert(Reachability::FromSingleGoto(block_id))
440 }
441 };
442 }
443 BlockEnd::Match { info } => {
444 for arm in info.arms() {
445 assert!(self.reachability.insert(arm.block_id, Reachability::Any).is_none());
446 }
447 }
448 BlockEnd::NotSet | BlockEnd::Return(..) | BlockEnd::Panic(..) => {}
449 }
450 }
451
452 fn handle_statement_call(&mut self, stmt: &mut StatementCall<'db>) -> Option<Statement<'db>> {
460 let db = self.db;
461 if stmt.function == self.panic_with_felt252 {
462 let val = self.as_const(stmt.inputs[0].var_id)?;
463 stmt.inputs.clear();
464 stmt.function = GenericFunctionId::Free(self.panic_with_const_felt252)
465 .concretize(db, vec![GenericArgumentId::Constant(val)])
466 .lowered(db);
467 return None;
468 } else if stmt.function == self.panic_with_byte_array && !db.flag_unsafe_panic() {
469 let snap = self.var_info.get(&stmt.inputs[0].var_id)?;
470 let bytearray = try_extract_matches!(snap.as_ref(), VarInfo::Snapshot)?;
471 let [Some(data), Some(pending_word), Some(pending_len)] =
472 &try_extract_matches!(bytearray.as_ref(), VarInfo::Struct)?[..]
473 else {
474 return None;
475 };
476 let data = try_extract_matches!(data.as_ref(), VarInfo::Array)?;
477 let pending_word = try_extract_matches!(pending_word.as_ref(), VarInfo::Const)?;
478 let pending_len = try_extract_matches!(pending_len.as_ref(), VarInfo::Const)?;
479 let mut panic_data =
480 vec![BigInt::from_str_radix(BYTE_ARRAY_MAGIC, 16).unwrap(), data.len().into()];
481 for word in data {
482 let VarInfo::Const(word) = word.as_ref()?.as_ref() else {
483 return None;
484 };
485 panic_data.push(word.to_int(db)?.clone());
486 }
487 panic_data.extend([pending_word.to_int(db)?.clone(), pending_len.to_int(db)?.clone()]);
488 let felt252_ty = self.felt252;
489 let location = stmt.location;
490 let new_var = |ty| Variable::with_default_context(db, ty, location);
491 let as_usage = |var_id| VarUsage { var_id, location };
492 let array_fn = |extern_id| {
493 let args = vec![GenericArgumentId::Type(felt252_ty)];
494 GenericFunctionId::Extern(extern_id).concretize(db, args).lowered(db)
495 };
496 let call_stmt = |function, inputs, outputs| {
497 let with_coupon = false;
498 Statement::Call(StatementCall {
499 function,
500 inputs,
501 with_coupon,
502 outputs,
503 location,
504 is_specialization_base_call: false,
505 })
506 };
507 let arr_var = new_var(corelib::core_array_felt252_ty(db));
508 let mut arr = self.variables.alloc(arr_var.clone());
509 self.additional_stmts.push(call_stmt(array_fn(self.array_new), vec![], vec![arr]));
510 let felt252_var = new_var(felt252_ty);
511 let arr_append_fn = array_fn(self.array_append);
512 for word in panic_data {
513 let to_append = self.variables.alloc(felt252_var.clone());
514 let new_arr = self.variables.alloc(arr_var.clone());
515 self.additional_stmts.push(Statement::Const(StatementConst::new_flat(
516 ConstValue::Int(word, felt252_ty).intern(db),
517 to_append,
518 )));
519 self.additional_stmts.push(call_stmt(
520 arr_append_fn,
521 vec![as_usage(arr), as_usage(to_append)],
522 vec![new_arr],
523 ));
524 arr = new_arr;
525 }
526 let panic_ty = corelib::get_core_ty_by_name(db, SmolStrId::from(db, "Panic"), vec![]);
527 let panic_var = self.variables.alloc(new_var(panic_ty));
528 self.additional_stmts.push(Statement::StructConstruct(StatementStructConstruct {
529 inputs: vec![],
530 output: panic_var,
531 }));
532 return Some(Statement::StructConstruct(StatementStructConstruct {
533 inputs: vec![as_usage(panic_var), as_usage(arr)],
534 output: stmt.outputs[0],
535 }));
536 }
537 let (id, _generic_args) = stmt.function.get_extern(db)?;
538 if id == self.felt_sub {
539 if let Some(rhs) = self.as_int(stmt.inputs[1].var_id)
540 && rhs.is_zero()
541 {
542 self.var_info.insert(stmt.outputs[0], VarInfo::Var(stmt.inputs[0]).into());
543 None
544 } else if let Some(lhs) = self.as_int(stmt.inputs[0].var_id)
545 && let Some(rhs) = self.as_int(stmt.inputs[1].var_id)
546 {
547 let value = canonical_felt252(&(lhs - rhs));
548 Some(self.propagate_const_and_get_statement(value, stmt.outputs[0]))
549 } else {
550 None
551 }
552 } else if id == self.felt_add {
553 if let Some(lhs) = self.as_int(stmt.inputs[0].var_id)
554 && lhs.is_zero()
555 {
556 self.var_info.insert(stmt.outputs[0], VarInfo::Var(stmt.inputs[1]).into());
557 None
558 } else if let Some(rhs) = self.as_int(stmt.inputs[1].var_id)
559 && rhs.is_zero()
560 {
561 self.var_info.insert(stmt.outputs[0], VarInfo::Var(stmt.inputs[0]).into());
562 None
563 } else if let Some(lhs) = self.as_int(stmt.inputs[0].var_id)
564 && let Some(rhs) = self.as_int(stmt.inputs[1].var_id)
565 {
566 let value = canonical_felt252(&(lhs + rhs));
567 Some(self.propagate_const_and_get_statement(value, stmt.outputs[0]))
568 } else {
569 None
570 }
571 } else if id == self.felt_mul {
572 let lhs = self.as_int(stmt.inputs[0].var_id);
573 let rhs = self.as_int(stmt.inputs[1].var_id);
574 if lhs.map(Zero::is_zero).unwrap_or_default()
575 || rhs.map(Zero::is_zero).unwrap_or_default()
576 {
577 Some(self.propagate_zero_and_get_statement(stmt.outputs[0]))
578 } else if let Some(rhs) = self.as_int(stmt.inputs[1].var_id)
579 && rhs.is_one()
580 {
581 self.var_info.insert(stmt.outputs[0], VarInfo::Var(stmt.inputs[0]).into());
582 None
583 } else if let Some(lhs) = self.as_int(stmt.inputs[0].var_id)
584 && lhs.is_one()
585 {
586 self.var_info.insert(stmt.outputs[0], VarInfo::Var(stmt.inputs[1]).into());
587 None
588 } else if let Some(lhs) = lhs
589 && let Some(rhs) = rhs
590 {
591 let value = canonical_felt252(&(lhs * rhs));
592 Some(self.propagate_const_and_get_statement(value, stmt.outputs[0]))
593 } else {
594 None
595 }
596 } else if id == self.felt_div {
597 if let Some(rhs) = self.as_int(stmt.inputs[1].var_id)
599 && rhs.is_one()
601 {
602 self.var_info.insert(stmt.outputs[0], VarInfo::Var(stmt.inputs[0]).into());
603 None
604 } else if let Some(lhs) = self.as_int(stmt.inputs[0].var_id)
605 && lhs.is_zero()
607 {
608 Some(self.propagate_zero_and_get_statement(stmt.outputs[0]))
609 } else if let Some(lhs) = self.as_int(stmt.inputs[0].var_id)
610 && let Some(rhs) = self.as_int(stmt.inputs[1].var_id)
611 && let Ok(rhs_nonzero) = Felt252::from(rhs).try_into()
612 {
613 let lhs_felt = Felt252::from(lhs);
617 let value = lhs_felt.field_div(&rhs_nonzero).to_bigint();
618 Some(self.propagate_const_and_get_statement(value, stmt.outputs[0]))
619 } else {
620 None
621 }
622 } else if self.wide_mul_fns.contains(&id) {
623 let lhs = self.as_int(stmt.inputs[0].var_id);
624 let rhs = self.as_int(stmt.inputs[1].var_id);
625 let output = stmt.outputs[0];
626 if lhs.map(Zero::is_zero).unwrap_or_default()
627 || rhs.map(Zero::is_zero).unwrap_or_default()
628 {
629 return Some(self.propagate_zero_and_get_statement(output));
630 }
631 let lhs = lhs?;
632 Some(self.propagate_const_and_get_statement(lhs * rhs?, stmt.outputs[0]))
633 } else if id == self.bounded_int_add || id == self.bounded_int_sub {
634 let lhs = self.as_int(stmt.inputs[0].var_id)?;
635 let rhs = self.as_int(stmt.inputs[1].var_id)?;
636 let value = if id == self.bounded_int_add { lhs + rhs } else { lhs - rhs };
637 Some(self.propagate_const_and_get_statement(value, stmt.outputs[0]))
638 } else if self.div_rem_fns.contains(&id) {
639 let lhs = self.as_int(stmt.inputs[0].var_id);
640 if lhs.map(Zero::is_zero).unwrap_or_default() {
641 let additional_stmt = self.propagate_zero_and_get_statement(stmt.outputs[1]);
642 self.additional_stmts.push(additional_stmt);
643 return Some(self.propagate_zero_and_get_statement(stmt.outputs[0]));
644 }
645 let rhs = self.as_int(stmt.inputs[1].var_id)?;
646 let (q, r) = lhs?.div_rem(rhs);
647 let q_output = stmt.outputs[0];
648 let q_value = ConstValue::Int(q, self.variables[q_output].ty).intern(db);
649 self.var_info.insert(q_output, VarInfo::Const(q_value).into());
650 let r_output = stmt.outputs[1];
651 let r_value = ConstValue::Int(r, self.variables[r_output].ty).intern(db);
652 self.var_info.insert(r_output, VarInfo::Const(r_value).into());
653 self.additional_stmts
654 .push(Statement::Const(StatementConst::new_flat(r_value, r_output)));
655 Some(Statement::Const(StatementConst::new_flat(q_value, q_output)))
656 } else if id == self.storage_base_address_from_felt252 {
657 let input_var = stmt.inputs[0].var_id;
658 if let Some(const_value) = self.as_const(input_var)
659 && let ConstValue::Int(val, ty) = const_value.long(db)
660 {
661 const ADDR_BOUND: Felt252 = Felt252::from_hex_unchecked(
662 "0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00",
663 );
664 let as_felt252 = Felt252::from(val);
665 let normalized =
666 if as_felt252 >= ADDR_BOUND { as_felt252 - ADDR_BOUND } else { as_felt252 };
667 stmt.inputs.clear();
668 let arg = GenericArgumentId::Constant(
669 ConstValue::Int(normalized.to_bigint(), *ty).intern(db),
670 );
671 stmt.function =
672 self.storage_base_address_const.concretize(db, vec![arg]).lowered(db);
673 }
674 None
675 } else if self.upcast_fns.contains(&id) {
676 let int_value = self.as_int(stmt.inputs[0].var_id)?;
677 let output = stmt.outputs[0];
678 let value = ConstValue::Int(int_value.clone(), self.variables[output].ty).intern(db);
679 self.var_info.insert(output, VarInfo::Const(value).into());
680 Some(Statement::Const(StatementConst::new_flat(value, output)))
681 } else if id == self.array_new {
682 self.var_info.insert(stmt.outputs[0], VarInfo::Array(vec![]).into());
683 None
684 } else if id == self.array_append {
685 let mut var_infos = if let VarInfo::Array(var_infos) =
686 self.var_info.get(&stmt.inputs[0].var_id)?.as_ref()
687 {
688 var_infos.clone()
689 } else {
690 return None;
691 };
692 let appended = stmt.inputs[1];
693 var_infos.push(match self.var_info.get(&appended.var_id) {
694 Some(var_info) => Some(var_info.clone()),
695 None => var_info_if_copy(self.variables, appended),
696 });
697 self.var_info.insert(stmt.outputs[0], VarInfo::Array(var_infos).into());
698 None
699 } else if id == self.array_len {
700 let info = self.var_info.get(&stmt.inputs[0].var_id)?;
701 let desnapped = try_extract_matches!(info.as_ref(), VarInfo::Snapshot)?;
702 let length = try_extract_matches!(desnapped.as_ref(), VarInfo::Array)?.len();
703 Some(self.propagate_const_and_get_statement(length.into(), stmt.outputs[0]))
704 } else {
705 None
706 }
707 }
708
709 fn try_specialize_call(&self, call_stmt: &mut StatementCall<'db>) -> Option<Statement<'db>> {
716 if call_stmt.with_coupon {
717 return None;
718 }
719 if matches!(self.db.optimizations().inlining_strategy(), InliningStrategy::Avoid) {
721 return None;
722 }
723
724 let Ok(Some(mut called_function)) = call_stmt.function.body(self.db) else {
725 return None;
726 };
727
728 let extract_base = |function: ConcreteFunctionWithBodyId<'db>| match function.long(self.db)
729 {
730 ConcreteFunctionWithBodyLongId::Specialized(specialized) => {
731 specialized.long(self.db).base
732 }
733 _ => function,
734 };
735 let called_base = extract_base(called_function);
736 let caller_base = extract_base(self.caller_function);
737
738 if self.db.priv_never_inline(called_base).ok()? {
739 return None;
740 }
741
742 if call_stmt.is_specialization_base_call {
744 return None;
745 }
746
747 if called_base == caller_base && called_function != called_base {
749 return None;
750 }
751
752 let scc =
755 self.db.lowered_scc(called_base, DependencyType::Call, LoweringStage::Monomorphized);
756 if scc.len() > 1 && scc.contains(&caller_base) {
757 return None;
758 }
759
760 if call_stmt.inputs.iter().all(|arg| self.var_info.get(&arg.var_id).is_none()) {
761 return None;
763 }
764
765 let self_specializition = if let ConcreteFunctionWithBodyLongId::Specialized(specialized) =
767 self.caller_function.long(self.db)
768 && caller_base == called_base
769 {
770 specialized.long(self.db).args.iter().map(Some).collect()
771 } else {
772 vec![None; call_stmt.inputs.len()]
773 };
774
775 let mut specialization_args = vec![];
776 let mut new_args = vec![];
777 for (arg, coerce) in zip_eq(&call_stmt.inputs, &self_specializition) {
778 if let Some(var_info) = self.var_info.get(&arg.var_id)
779 && self.variables[arg.var_id].info.droppable.is_ok()
780 && let Some(specialization_arg) = self.try_get_specialization_arg(
781 var_info.clone(),
782 self.variables[arg.var_id].ty,
783 &mut new_args,
784 *coerce,
785 )
786 {
787 specialization_args.push(specialization_arg);
788 } else {
789 specialization_args.push(SpecializationArg::NotSpecialized);
790 new_args.push(*arg);
791 continue;
792 };
793 }
794
795 if specialization_args.iter().all(|arg| matches!(arg, SpecializationArg::NotSpecialized)) {
796 return None;
798 }
799 if let ConcreteFunctionWithBodyLongId::Specialized(specialized_function) =
800 called_function.long(self.db)
801 {
802 let specialized_function = specialized_function.long(self.db);
803 called_function = specialized_function.base;
806 let mut new_args_iter = specialization_args.into_iter();
807 let mut old_args = specialized_function.args.clone();
808 let mut stack = vec![];
809 for arg in old_args.iter_mut().rev() {
810 stack.push(arg);
811 }
812 while let Some(arg) = stack.pop() {
813 match arg {
814 SpecializationArg::Const { .. } => {}
815 SpecializationArg::Snapshot(inner) => {
816 stack.push(inner.as_mut());
817 }
818 SpecializationArg::Enum { payload, .. } => {
819 stack.push(payload.as_mut());
820 }
821 SpecializationArg::Array(_, values) | SpecializationArg::Struct(values) => {
822 for value in values.iter_mut().rev() {
823 stack.push(value);
824 }
825 }
826 SpecializationArg::NotSpecialized => {
827 *arg = new_args_iter.next().unwrap_or(SpecializationArg::NotSpecialized);
828 }
829 }
830 }
831 specialization_args = old_args;
832 }
833 let specialized = SpecializedFunction { base: called_function, args: specialization_args }
834 .intern(self.db);
835 let specialized_func_id =
836 ConcreteFunctionWithBodyLongId::Specialized(specialized).intern(self.db);
837
838 if caller_base != called_base
839 && self.db.priv_should_specialize(specialized_func_id) == Ok(false)
840 {
841 return None;
842 }
843
844 Some(Statement::Call(StatementCall {
845 function: specialized_func_id.function_id(self.db).unwrap(),
846 inputs: new_args,
847 with_coupon: call_stmt.with_coupon,
848 outputs: std::mem::take(&mut call_stmt.outputs),
849 location: call_stmt.location,
850 is_specialization_base_call: false,
851 }))
852 }
853
854 fn propagate_const_and_get_statement(
856 &mut self,
857 value: BigInt,
858 output: VariableId,
859 ) -> Statement<'db> {
860 let ty = self.variables[output].ty;
861 let value = ConstValueId::from_int(self.db, ty, &value);
862 self.var_info.insert(output, VarInfo::Const(value).into());
863 Statement::Const(StatementConst::new_flat(value, output))
864 }
865
866 fn propagate_zero_and_get_statement(&mut self, output: VariableId) -> Statement<'db> {
868 self.propagate_const_and_get_statement(BigInt::zero(), output)
869 }
870
871 fn try_generate_const_statement(
873 &self,
874 value: ConstValueId<'db>,
875 output: VariableId,
876 ) -> Option<Statement<'db>> {
877 if self.db.type_size_info(self.variables[output].ty) == Ok(TypeSizeInformation::Other) {
878 Some(Statement::Const(StatementConst::new_flat(value, output)))
879 } else if matches!(value.long(self.db), ConstValue::Struct(members, _) if members.is_empty())
880 {
881 Some(Statement::StructConstruct(StatementStructConstruct { inputs: vec![], output }))
883 } else {
884 None
885 }
886 }
887
888 fn handle_enum_block_end(
893 &mut self,
894 info: &mut MatchEnumInfo<'db>,
895 statements: &mut Vec<Statement<'db>>,
896 ) -> Option<BlockEnd<'db>> {
897 let input = info.input.var_id;
898 let (n_snapshots, var_info) = self.var_info.get(&input)?.clone().peel_snapshots();
899 let location = info.location;
900 let as_usage = |var_id| VarUsage { var_id, location };
901 let db = self.db;
902 let snapshot_stmt = |vars: &mut VariableArena<'_>, pre_snap, post_snap| {
903 let ignored = vars.alloc(vars[pre_snap].clone());
904 Statement::Snapshot(StatementSnapshot::new(as_usage(pre_snap), ignored, post_snap))
905 };
906 if let VarInfo::Const(const_value) = var_info.as_ref()
908 && let ConstValue::Enum(variant, value) = const_value.long(db)
909 {
910 let arm = &info.arms[variant.idx];
911 let output = arm.var_ids[0];
912 self.var_info
914 .insert(output, Rc::new(VarInfo::Const(*value)).wrap_with_snapshots(n_snapshots));
915 if self.variables[input].info.droppable.is_ok()
916 && self.variables[output].info.copyable.is_ok()
917 && let Ok(mut ty) = value.ty(db)
918 && let Some(mut stmt) = self.try_generate_const_statement(*value, output)
919 {
920 let snapshot_vars = (0..n_snapshots)
921 .map(|_| {
922 let old_ty = ty;
923 ty = TypeLongId::Snapshot(ty).intern(db);
924 self.variables.alloc(Variable::with_default_context(db, old_ty, location))
925 })
926 .chain([output])
927 .collect::<Vec<_>>();
928 stmt.outputs_mut()[0] = snapshot_vars[0];
930 statements.push(stmt);
931 statements.extend(snapshot_vars.into_iter().tuple_windows().map(
933 |(pre_snap, post_snap)| snapshot_stmt(self.variables, pre_snap, post_snap),
934 ));
935
936 return Some(BlockEnd::Goto(arm.block_id, Default::default()));
937 }
938 } else if let VarInfo::Enum { variant, payload } = var_info.as_ref() {
939 let arm = &info.arms[variant.idx];
940 let variant_ty = variant.ty;
941 let output = arm.var_ids[0];
942 let payload = payload.clone();
943 let unwrapped =
944 self.variables[input].info.droppable.is_ok().then_some(()).and_then(|_| {
945 let (extra_snapshots, inner) = payload.clone().peel_snapshots();
946 match inner.as_ref() {
947 VarInfo::Var(var) if self.variables[var.var_id].info.copyable.is_ok() => {
948 Some((var.var_id, extra_snapshots))
949 }
950 VarInfo::Const(value) => {
951 let const_var = self
952 .variables
953 .alloc(Variable::with_default_context(db, variant_ty, location));
954 statements.push(self.try_generate_const_statement(*value, const_var)?);
955 Some((const_var, extra_snapshots))
956 }
957 _ => None,
958 }
959 });
960 self.var_info.insert(output, payload.wrap_with_snapshots(n_snapshots));
962 if let Some((mut unwrapped, extra_snapshots)) = unwrapped {
963 let total_snapshots = n_snapshots + extra_snapshots;
964 if total_snapshots != 0 {
965 for _ in 1..total_snapshots {
967 let ty = TypeLongId::Snapshot(self.variables[unwrapped].ty).intern(db);
968 let non_snap_var = Variable::with_default_context(self.db, ty, location);
969 let snapped = self.variables.alloc(non_snap_var);
970 statements.push(snapshot_stmt(self.variables, unwrapped, snapped));
971 unwrapped = snapped;
972 }
973 statements.push(snapshot_stmt(self.variables, unwrapped, output));
974 };
975 return Some(BlockEnd::Goto(arm.block_id, Default::default()));
976 }
977 }
978 None
979 }
980
981 fn handle_extern_block_end(
986 &mut self,
987 info: &mut MatchExternInfo<'db>,
988 statements: &mut Vec<Statement<'db>>,
989 ) -> Option<BlockEnd<'db>> {
990 let db = self.db;
991 let (id, generic_args) = info.function.get_extern(db)?;
992 if self.nz_fns.contains(&id) {
993 let val = self.as_const(info.inputs[0].var_id)?;
994 let is_zero = match val.long(db) {
995 ConstValue::Int(v, _) => v.is_zero(),
996 ConstValue::Struct(s, _) => s
997 .iter()
998 .all(|v| v.to_int(db).expect("Expected ConstValue::Int for size").is_zero()),
999 _ => unreachable!(),
1000 };
1001 Some(if is_zero {
1002 BlockEnd::Goto(info.arms[0].block_id, Default::default())
1003 } else {
1004 let arm = &info.arms[1];
1005 let nz_var = arm.var_ids[0];
1006 let nz_val = ConstValue::NonZero(val).intern(db);
1007 self.var_info.insert(nz_var, VarInfo::Const(nz_val).into());
1008 statements.push(Statement::Const(StatementConst::new_flat(nz_val, nz_var)));
1009 BlockEnd::Goto(arm.block_id, Default::default())
1010 })
1011 } else if self.eq_fns.contains(&id) {
1012 let lhs = self.as_int(info.inputs[0].var_id);
1013 let rhs = self.as_int(info.inputs[1].var_id);
1014 if (lhs.map(Zero::is_zero).unwrap_or_default() && rhs.is_none())
1015 || (rhs.map(Zero::is_zero).unwrap_or_default() && lhs.is_none())
1016 {
1017 let nz_input = info.inputs[if lhs.is_some() { 1 } else { 0 }];
1018 let var = &self.variables[nz_input.var_id].clone();
1019 let function = self.type_info.get(&var.ty)?.is_zero;
1020 let unused_nz_var = Variable::with_default_context(
1021 db,
1022 corelib::core_nonzero_ty(db, var.ty),
1023 var.location,
1024 );
1025 let unused_nz_var = self.variables.alloc(unused_nz_var);
1026 return Some(BlockEnd::Match {
1027 info: MatchInfo::Extern(MatchExternInfo {
1028 function,
1029 inputs: vec![nz_input],
1030 arms: vec![
1031 MatchArm {
1032 arm_selector: MatchArmSelector::VariantId(
1033 corelib::jump_nz_zero_variant(db, var.ty),
1034 ),
1035 block_id: info.arms[1].block_id,
1036 var_ids: vec![],
1037 },
1038 MatchArm {
1039 arm_selector: MatchArmSelector::VariantId(
1040 corelib::jump_nz_nonzero_variant(db, var.ty),
1041 ),
1042 block_id: info.arms[0].block_id,
1043 var_ids: vec![unused_nz_var],
1044 },
1045 ],
1046 location: info.location,
1047 }),
1048 });
1049 }
1050 Some(BlockEnd::Goto(
1051 info.arms[if lhs? == rhs? { 1 } else { 0 }].block_id,
1052 Default::default(),
1053 ))
1054 } else if self.uadd_fns.contains(&id)
1055 || self.usub_fns.contains(&id)
1056 || self.diff_fns.contains(&id)
1057 || self.iadd_fns.contains(&id)
1058 || self.isub_fns.contains(&id)
1059 {
1060 let rhs = self.as_int(info.inputs[1].var_id);
1061 let lhs = self.as_int(info.inputs[0].var_id);
1062 if let (Some(lhs), Some(rhs)) = (lhs, rhs) {
1063 let ty = self.variables[info.arms[0].var_ids[0]].ty;
1064 let range = self.type_value_ranges.get(&ty)?;
1065 let value = if self.uadd_fns.contains(&id) || self.iadd_fns.contains(&id) {
1066 lhs + rhs
1067 } else {
1068 lhs - rhs
1069 };
1070 let (arm_index, value) = match range.normalized(value) {
1071 NormalizedResult::InRange(value) => (0, value),
1072 NormalizedResult::Under(value) => (1, value),
1073 NormalizedResult::Over(value) => (
1074 if self.iadd_fns.contains(&id) || self.isub_fns.contains(&id) {
1075 2
1076 } else {
1077 1
1078 },
1079 value,
1080 ),
1081 };
1082 let arm = &info.arms[arm_index];
1083 let actual_output = arm.var_ids[0];
1084 let value = ConstValue::Int(value, ty).intern(db);
1085 self.var_info.insert(actual_output, VarInfo::Const(value).into());
1086 statements.push(Statement::Const(StatementConst::new_flat(value, actual_output)));
1087 return Some(BlockEnd::Goto(arm.block_id, Default::default()));
1088 }
1089 if let Some(rhs) = rhs {
1090 if rhs.is_zero() && !self.diff_fns.contains(&id) {
1091 let arm = &info.arms[0];
1092 self.var_info.insert(arm.var_ids[0], VarInfo::Var(info.inputs[0]).into());
1093 return Some(BlockEnd::Goto(arm.block_id, Default::default()));
1094 }
1095 if rhs.is_one() && !self.diff_fns.contains(&id) {
1096 let ty = self.variables[info.arms[0].var_ids[0]].ty;
1097 let ty_info = self.type_info.get(&ty)?;
1098 let function = if self.uadd_fns.contains(&id) || self.iadd_fns.contains(&id) {
1099 ty_info.inc?
1100 } else {
1101 ty_info.dec?
1102 };
1103 let enum_ty = function.signature(db).ok()?.return_type;
1104 let TypeLongId::Concrete(ConcreteTypeId::Enum(concrete_enum_id)) =
1105 enum_ty.long(db)
1106 else {
1107 return None;
1108 };
1109 let result = self.variables.alloc(Variable::with_default_context(
1110 db,
1111 function.signature(db).unwrap().return_type,
1112 info.location,
1113 ));
1114 statements.push(Statement::Call(StatementCall {
1115 function,
1116 inputs: vec![info.inputs[0]],
1117 with_coupon: false,
1118 outputs: vec![result],
1119 location: info.location,
1120 is_specialization_base_call: false,
1121 }));
1122 return Some(BlockEnd::Match {
1123 info: MatchInfo::Enum(MatchEnumInfo {
1124 concrete_enum_id: *concrete_enum_id,
1125 input: VarUsage { var_id: result, location: info.location },
1126 arms: core::mem::take(&mut info.arms),
1127 location: info.location,
1128 }),
1129 });
1130 }
1131 }
1132 if let Some(lhs) = lhs
1133 && lhs.is_zero()
1134 && (self.uadd_fns.contains(&id) || self.iadd_fns.contains(&id))
1135 {
1136 let arm = &info.arms[0];
1137 self.var_info.insert(arm.var_ids[0], VarInfo::Var(info.inputs[1]).into());
1138 return Some(BlockEnd::Goto(arm.block_id, Default::default()));
1139 }
1140 None
1141 } else if let Some(reversed) = self.downcast_fns.get(&id) {
1142 let range = |ty: TypeId<'_>| {
1143 Some(if let Some(range) = self.type_value_ranges.get(&ty) {
1144 range.clone()
1145 } else {
1146 let (min, max) = corelib::try_extract_bounded_int_type(db, ty)?;
1147 TypeRange::new(min.to_int(db)?.clone(), max.to_int(db)?.clone())
1148 })
1149 };
1150 let (success_arm, failure_arm) = if *reversed { (1, 0) } else { (0, 1) };
1151 let input_var = info.inputs[0].var_id;
1152 let in_ty = self.variables[input_var].ty;
1153 let success_output = info.arms[success_arm].var_ids[0];
1154 let out_ty = self.variables[success_output].ty;
1155 let out_range = range(out_ty)?;
1156 let Some(value) = self.as_int(input_var) else {
1157 let in_range = range(in_ty)?;
1158 return if in_range.min < out_range.min || in_range.max > out_range.max {
1159 None
1160 } else {
1161 let generic_args = [in_ty, out_ty].map(GenericArgumentId::Type).to_vec();
1162 let function = db.core_info().upcast_fn.concretize(db, generic_args);
1163 statements.push(Statement::Call(StatementCall {
1164 function: function.lowered(db),
1165 inputs: vec![info.inputs[0]],
1166 with_coupon: false,
1167 outputs: vec![success_output],
1168 location: info.location,
1169 is_specialization_base_call: false,
1170 }));
1171 return Some(BlockEnd::Goto(
1172 info.arms[success_arm].block_id,
1173 Default::default(),
1174 ));
1175 };
1176 };
1177 let value = if in_ty == self.felt252 {
1178 felt252_for_downcast(value, &out_range.min)
1179 } else {
1180 value.clone()
1181 };
1182 Some(if let NormalizedResult::InRange(value) = out_range.normalized(value) {
1183 let value = ConstValue::Int(value, out_ty).intern(db);
1184 self.var_info.insert(success_output, VarInfo::Const(value).into());
1185 statements.push(Statement::Const(StatementConst::new_flat(value, success_output)));
1186 BlockEnd::Goto(info.arms[success_arm].block_id, Default::default())
1187 } else {
1188 BlockEnd::Goto(info.arms[failure_arm].block_id, Default::default())
1189 })
1190 } else if id == self.bounded_int_constrain {
1191 let input_var = info.inputs[0].var_id;
1192 let value = self.as_int(input_var)?;
1193 let generic_arg = generic_args[1];
1194 let constrain_value = extract_matches!(generic_arg, GenericArgumentId::Constant)
1195 .to_int(db)
1196 .expect("Expected ConstValue::Int for size");
1197 let arm_idx = if value < constrain_value { 0 } else { 1 };
1198 let output = info.arms[arm_idx].var_ids[0];
1199 statements.push(self.propagate_const_and_get_statement(value.clone(), output));
1200 Some(BlockEnd::Goto(info.arms[arm_idx].block_id, Default::default()))
1201 } else if id == self.bounded_int_trim_min {
1202 let input_var = info.inputs[0].var_id;
1203 let ConstValue::Int(value, ty) = self.as_const(input_var)?.long(self.db) else {
1204 return None;
1205 };
1206 let is_trimmed = if let Some(range) = self.type_value_ranges.get(ty) {
1207 range.min == *value
1208 } else {
1209 corelib::try_extract_bounded_int_type(db, *ty)?.0.to_int(db)? == value
1210 };
1211 let arm_idx = if is_trimmed {
1212 0
1213 } else {
1214 let output = info.arms[1].var_ids[0];
1215 statements.push(self.propagate_const_and_get_statement(value.clone(), output));
1216 1
1217 };
1218 Some(BlockEnd::Goto(info.arms[arm_idx].block_id, Default::default()))
1219 } else if id == self.bounded_int_trim_max {
1220 let input_var = info.inputs[0].var_id;
1221 let ConstValue::Int(value, ty) = self.as_const(input_var)?.long(self.db) else {
1222 return None;
1223 };
1224 let is_trimmed = if let Some(range) = self.type_value_ranges.get(ty) {
1225 range.max == *value
1226 } else {
1227 corelib::try_extract_bounded_int_type(db, *ty)?.1.to_int(db)? == value
1228 };
1229 let arm_idx = if is_trimmed {
1230 0
1231 } else {
1232 let output = info.arms[1].var_ids[0];
1233 statements.push(self.propagate_const_and_get_statement(value.clone(), output));
1234 1
1235 };
1236 Some(BlockEnd::Goto(info.arms[arm_idx].block_id, Default::default()))
1237 } else if id == self.array_get {
1238 let index = self.as_int(info.inputs[1].var_id)?.to_usize()?;
1239 if let Some(arr_info) = self.var_info.get(&info.inputs[0].var_id)
1240 && let VarInfo::Snapshot(arr_info) = arr_info.as_ref()
1241 && let VarInfo::Array(infos) = arr_info.as_ref()
1242 {
1243 match infos.get(index) {
1244 Some(Some(output_var_info)) => {
1245 let arm = &info.arms[0];
1246 let output_var_info = output_var_info.clone();
1247 self.var_info.insert(
1248 arm.var_ids[0],
1249 VarInfo::Box(VarInfo::Snapshot(output_var_info.clone()).into()).into(),
1250 );
1251 if let VarInfo::Const(value) = output_var_info.as_ref() {
1252 let value_ty = value.ty(db).ok()?;
1253 let value_box_ty = corelib::core_box_ty(db, value_ty);
1254 let location = info.location;
1255 let boxed_var =
1256 Variable::with_default_context(db, value_box_ty, location);
1257 let boxed = self.variables.alloc(boxed_var.clone());
1258 let unused_boxed = self.variables.alloc(boxed_var);
1259 let snapped = self.variables.alloc(Variable::with_default_context(
1260 db,
1261 TypeLongId::Snapshot(value_box_ty).intern(db),
1262 location,
1263 ));
1264 statements.extend([
1265 Statement::Const(StatementConst::new_boxed(*value, boxed)),
1266 Statement::Snapshot(StatementSnapshot {
1267 input: VarUsage { var_id: boxed, location },
1268 outputs: [unused_boxed, snapped],
1269 }),
1270 Statement::Call(StatementCall {
1271 function: self
1272 .box_forward_snapshot
1273 .concretize(db, vec![GenericArgumentId::Type(value_ty)])
1274 .lowered(db),
1275 inputs: vec![VarUsage { var_id: snapped, location }],
1276 with_coupon: false,
1277 outputs: vec![arm.var_ids[0]],
1278 location: info.location,
1279 is_specialization_base_call: false,
1280 }),
1281 ]);
1282 return Some(BlockEnd::Goto(arm.block_id, Default::default()));
1283 }
1284 }
1285 None => {
1286 return Some(BlockEnd::Goto(info.arms[1].block_id, Default::default()));
1287 }
1288 Some(None) => {}
1289 }
1290 }
1291 if index.is_zero()
1292 && let [success, failure] = info.arms.as_mut_slice()
1293 {
1294 let arr = info.inputs[0].var_id;
1295 let unused_arr_output0 = self.variables.alloc(self.variables[arr].clone());
1296 let unused_arr_output1 = self.variables.alloc(self.variables[arr].clone());
1297 info.inputs.truncate(1);
1298 info.function = GenericFunctionId::Extern(self.array_snapshot_pop_front)
1299 .concretize(db, generic_args)
1300 .lowered(db);
1301 success.var_ids.insert(0, unused_arr_output0);
1302 failure.var_ids.insert(0, unused_arr_output1);
1303 }
1304 None
1305 } else if id == self.array_pop_front {
1306 let VarInfo::Array(var_infos) = self.var_info.get(&info.inputs[0].var_id)?.as_ref()
1307 else {
1308 return None;
1309 };
1310 if let Some(first) = var_infos.first() {
1311 if let Some(first) = first.as_ref().cloned() {
1312 let arm = &info.arms[0];
1313 self.var_info
1314 .insert(arm.var_ids[0], VarInfo::Array(var_infos[1..].to_vec()).into());
1315 self.var_info.insert(arm.var_ids[1], VarInfo::Box(first).into());
1316 }
1317 None
1318 } else {
1319 let arm = &info.arms[1];
1320 self.var_info.insert(arm.var_ids[0], VarInfo::Array(vec![]).into());
1321 Some(BlockEnd::Goto(
1322 arm.block_id,
1323 VarRemapping {
1324 remapping: FromIterator::from_iter([(arm.var_ids[0], info.inputs[0])]),
1325 },
1326 ))
1327 }
1328 } else if id == self.array_snapshot_pop_back || id == self.array_snapshot_pop_front {
1329 let var_info = self.var_info.get(&info.inputs[0].var_id)?;
1330 let desnapped = try_extract_matches!(var_info.as_ref(), VarInfo::Snapshot)?;
1331 let element_var_infos = try_extract_matches!(desnapped.as_ref(), VarInfo::Array)?;
1332 if element_var_infos.is_empty() {
1334 let arm = &info.arms[1];
1335 self.var_info.insert(arm.var_ids[0], VarInfo::Array(vec![]).into());
1336 Some(BlockEnd::Goto(
1337 arm.block_id,
1338 VarRemapping {
1339 remapping: FromIterator::from_iter([(arm.var_ids[0], info.inputs[0])]),
1340 },
1341 ))
1342 } else {
1343 None
1344 }
1345 } else {
1346 None
1347 }
1348 }
1349
1350 fn as_const(&self, var_id: VariableId) -> Option<ConstValueId<'db>> {
1352 try_extract_matches!(self.var_info.get(&var_id)?.as_ref(), VarInfo::Const).copied()
1353 }
1354
1355 fn as_int(&self, var_id: VariableId) -> Option<&BigInt> {
1357 match self.as_const(var_id)?.long(self.db) {
1358 ConstValue::Int(value, _) => Some(value),
1359 ConstValue::NonZero(const_value) => {
1360 if let ConstValue::Int(value, _) = const_value.long(self.db) {
1361 Some(value)
1362 } else {
1363 None
1364 }
1365 }
1366 _ => None,
1367 }
1368 }
1369
1370 fn maybe_replace_inputs(&self, inputs: &mut [VarUsage<'db>]) {
1372 for input in inputs {
1373 self.maybe_replace_input(input);
1374 }
1375 }
1376
1377 fn maybe_replace_input(&self, input: &mut VarUsage<'db>) {
1379 if let Some(info) = self.var_info.get(&input.var_id)
1380 && let VarInfo::Var(new_var) = info.as_ref()
1381 {
1382 *input = *new_var;
1383 }
1384 }
1385
1386 fn try_get_specialization_arg(
1392 &self,
1393 var_info: Rc<VarInfo<'db>>,
1394 ty: TypeId<'db>,
1395 unknown_vars: &mut Vec<VarUsage<'db>>,
1396 coerce: Option<&SpecializationArg<'db>>,
1397 ) -> Option<SpecializationArg<'db>> {
1398 require(self.db.type_size_info(ty).ok()? != TypeSizeInformation::ZeroSized)?;
1400 require(!matches!(coerce, Some(SpecializationArg::NotSpecialized)))?;
1402
1403 match var_info.as_ref() {
1404 VarInfo::Const(value) => {
1405 let res = const_to_specialization_arg(self.db, *value, false);
1406 let Some(coerce) = coerce else {
1407 return Some(res);
1408 };
1409 if *coerce == res { Some(res) } else { None }
1410 }
1411 VarInfo::Box(info) => {
1412 let res = try_extract_matches!(info.as_ref(), VarInfo::Const)
1413 .map(|value| SpecializationArg::Const { value: *value, boxed: true });
1414 let Some(coerce) = coerce else {
1415 return res;
1416 };
1417 if Some(coerce.clone()) == res { res } else { None }
1418 }
1419 VarInfo::Snapshot(info) => {
1420 let desnap_ty = *extract_matches!(ty.long(self.db), TypeLongId::Snapshot);
1421 let mut local_unknown_vars: Vec<VarUsage<'db>> = Vec::new();
1423 let inner = self.try_get_specialization_arg(
1424 info.clone(),
1425 desnap_ty,
1426 &mut local_unknown_vars,
1427 coerce.map(|coerce| {
1428 extract_matches!(coerce, SpecializationArg::Snapshot).as_ref()
1429 }),
1430 )?;
1431 unknown_vars.extend(local_unknown_vars);
1432 Some(SpecializationArg::Snapshot(Box::new(inner)))
1433 }
1434 VarInfo::Array(infos) => {
1435 let TypeLongId::Concrete(concrete_ty) = ty.long(self.db) else {
1436 unreachable!("Expected a concrete type");
1437 };
1438 let [GenericArgumentId::Type(inner_ty)] = &concrete_ty.generic_args(self.db)[..]
1439 else {
1440 unreachable!("Expected a single type generic argument");
1441 };
1442 let coerces = match coerce {
1443 Some(coerce) => {
1444 let SpecializationArg::Array(ty, specialization_args) = coerce else {
1445 unreachable!("Expected an array specialization argument");
1446 };
1447 assert_eq!(ty, inner_ty);
1448 if specialization_args.len() != infos.len() {
1449 return None;
1450 }
1451
1452 specialization_args.iter().map(Some).collect()
1453 }
1454 None => vec![None; infos.len()],
1455 };
1456 let mut vars = vec![];
1458 let mut args = vec![];
1459 for (info, coerce) in zip_eq(infos, coerces) {
1460 let info = info.as_ref()?.clone();
1461 let arg =
1462 self.try_get_specialization_arg(info, *inner_ty, &mut vars, coerce)?;
1463 args.push(arg);
1464 }
1465 if !args.is_empty()
1466 && args.iter().all(|arg| matches!(arg, SpecializationArg::NotSpecialized))
1467 {
1468 return None;
1469 }
1470 unknown_vars.extend(vars);
1471 Some(SpecializationArg::Array(*inner_ty, args))
1472 }
1473 VarInfo::Struct(infos) => {
1474 let element_types: Vec<TypeId<'db>> = match ty.long(self.db) {
1476 TypeLongId::Concrete(ConcreteTypeId::Struct(concrete_struct)) => {
1477 let members = self.db.concrete_struct_members(*concrete_struct).unwrap();
1478 members.values().map(|member| member.ty).collect()
1479 }
1480 TypeLongId::Tuple(element_types) => element_types.clone(),
1481 TypeLongId::FixedSizeArray { type_id, .. } => vec![*type_id; infos.len()],
1482 _ => return None,
1484 };
1485
1486 let coerces = match coerce {
1487 Some(SpecializationArg::Struct(specialization_args)) => {
1488 assert_eq!(specialization_args.len(), infos.len());
1489 specialization_args.iter().map(Some).collect()
1490 }
1491 Some(_) => unreachable!("Expected a struct specialization argument"),
1492 None => vec![None; infos.len()],
1493 };
1494
1495 let mut struct_args = Vec::new();
1496 let mut vars = vec![];
1498 for ((elem_ty, opt_var_info), coerce) in
1499 zip_eq(zip_eq(element_types, infos), coerces)
1500 {
1501 let var_info = opt_var_info.as_ref()?.clone();
1502 let arg =
1503 self.try_get_specialization_arg(var_info, elem_ty, &mut vars, coerce)?;
1504 struct_args.push(arg);
1505 }
1506 if !struct_args.is_empty()
1507 && struct_args
1508 .iter()
1509 .all(|arg| matches!(arg, SpecializationArg::NotSpecialized))
1510 {
1511 return None;
1512 }
1513 unknown_vars.extend(vars);
1514 Some(SpecializationArg::Struct(struct_args))
1515 }
1516 VarInfo::Enum { variant, payload } => {
1517 let coerce = match coerce {
1518 Some(coerce) => {
1519 let SpecializationArg::Enum { variant: coercion_variant, payload } = coerce
1520 else {
1521 unreachable!("Expected an enum specialization argument");
1522 };
1523 if coercion_variant != variant {
1524 return None;
1525 }
1526 Some(payload.as_ref())
1527 }
1528 None => None,
1529 };
1530 let mut local_unknown_vars = vec![];
1531 let payload_arg = self.try_get_specialization_arg(
1532 payload.clone(),
1533 variant.ty,
1534 &mut local_unknown_vars,
1535 coerce,
1536 )?;
1537
1538 unknown_vars.extend(local_unknown_vars);
1539 Some(SpecializationArg::Enum { variant: *variant, payload: Box::new(payload_arg) })
1540 }
1541 VarInfo::Var(var_usage) => {
1542 unknown_vars.push(*var_usage);
1543 Some(SpecializationArg::NotSpecialized)
1544 }
1545 }
1546 }
1547
1548 pub fn should_skip_const_folding(&self, db: &'db dyn Database) -> bool {
1550 if db.optimizations().skip_const_folding() {
1551 return true;
1552 }
1553
1554 if self.caller_function.base_semantic_function(db).generic_function(db)
1557 == GenericFunctionWithBodyId::Free(self.libfunc_info.panic_with_const_felt252)
1558 {
1559 return true;
1560 }
1561 false
1562 }
1563}
1564
1565fn var_info_if_copy<'db>(
1567 variables: &VariableArena<'db>,
1568 input: VarUsage<'db>,
1569) -> Option<Rc<VarInfo<'db>>> {
1570 variables[input.var_id].info.copyable.is_ok().then(|| VarInfo::Var(input).into())
1571}
1572
1573#[salsa::tracked(returns(ref))]
1575fn priv_const_folding_info<'db>(
1576 db: &'db dyn Database,
1577) -> crate::optimizations::const_folding::ConstFoldingLibfuncInfo<'db> {
1578 ConstFoldingLibfuncInfo::new(db)
1579}
1580
1581#[derive(Debug, PartialEq, Eq, salsa::Update)]
1583pub struct ConstFoldingLibfuncInfo<'db> {
1584 felt_sub: ExternFunctionId<'db>,
1586 felt_add: ExternFunctionId<'db>,
1588 felt_mul: ExternFunctionId<'db>,
1590 felt_div: ExternFunctionId<'db>,
1592 box_forward_snapshot: GenericFunctionId<'db>,
1594 eq_fns: OrderedHashSet<ExternFunctionId<'db>>,
1596 uadd_fns: OrderedHashSet<ExternFunctionId<'db>>,
1598 usub_fns: OrderedHashSet<ExternFunctionId<'db>>,
1600 diff_fns: OrderedHashSet<ExternFunctionId<'db>>,
1602 iadd_fns: OrderedHashSet<ExternFunctionId<'db>>,
1604 isub_fns: OrderedHashSet<ExternFunctionId<'db>>,
1606 wide_mul_fns: OrderedHashSet<ExternFunctionId<'db>>,
1608 div_rem_fns: OrderedHashSet<ExternFunctionId<'db>>,
1610 bounded_int_add: ExternFunctionId<'db>,
1612 bounded_int_sub: ExternFunctionId<'db>,
1614 bounded_int_constrain: ExternFunctionId<'db>,
1616 bounded_int_trim_min: ExternFunctionId<'db>,
1618 bounded_int_trim_max: ExternFunctionId<'db>,
1620 array_get: ExternFunctionId<'db>,
1622 array_snapshot_pop_front: ExternFunctionId<'db>,
1624 array_snapshot_pop_back: ExternFunctionId<'db>,
1626 array_len: ExternFunctionId<'db>,
1628 array_new: ExternFunctionId<'db>,
1630 array_append: ExternFunctionId<'db>,
1632 array_pop_front: ExternFunctionId<'db>,
1634 storage_base_address_from_felt252: ExternFunctionId<'db>,
1636 storage_base_address_const: GenericFunctionId<'db>,
1638 panic_with_felt252: FunctionId<'db>,
1640 pub panic_with_const_felt252: FreeFunctionId<'db>,
1642 panic_with_byte_array: FunctionId<'db>,
1644 type_info: OrderedHashMap<TypeId<'db>, TypeInfo<'db>>,
1646 const_calculation_info: Arc<ConstCalcInfo<'db>>,
1648}
1649impl<'db> ConstFoldingLibfuncInfo<'db> {
1650 fn new(db: &'db dyn Database) -> Self {
1651 let core = ModuleHelper::core(db);
1652 let box_module = core.submodule("box");
1653 let integer_module = core.submodule("integer");
1654 let internal_module = core.submodule("internal");
1655 let bounded_int_module = internal_module.submodule("bounded_int");
1656 let num_module = internal_module.submodule("num");
1657 let array_module = core.submodule("array");
1658 let starknet_module = core.submodule("starknet");
1659 let storage_access_module = starknet_module.submodule("storage_access");
1660 let utypes = ["u8", "u16", "u32", "u64", "u128"];
1661 let itypes = ["i8", "i16", "i32", "i64", "i128"];
1662 let eq_fns = OrderedHashSet::<_>::from_iter(
1663 chain!(utypes, itypes).map(|ty| integer_module.extern_function_id(&format!("{ty}_eq"))),
1664 );
1665 let uadd_fns = OrderedHashSet::<_>::from_iter(
1666 utypes.map(|ty| integer_module.extern_function_id(&format!("{ty}_overflowing_add"))),
1667 );
1668 let usub_fns = OrderedHashSet::<_>::from_iter(
1669 utypes.map(|ty| integer_module.extern_function_id(&format!("{ty}_overflowing_sub"))),
1670 );
1671 let diff_fns = OrderedHashSet::<_>::from_iter(
1672 itypes.map(|ty| integer_module.extern_function_id(&format!("{ty}_diff"))),
1673 );
1674 let iadd_fns =
1675 OrderedHashSet::<_>::from_iter(itypes.map(|ty| {
1676 integer_module.extern_function_id(&format!("{ty}_overflowing_add_impl"))
1677 }));
1678 let isub_fns =
1679 OrderedHashSet::<_>::from_iter(itypes.map(|ty| {
1680 integer_module.extern_function_id(&format!("{ty}_overflowing_sub_impl"))
1681 }));
1682 let wide_mul_fns = OrderedHashSet::<_>::from_iter(chain!(
1683 [bounded_int_module.extern_function_id("bounded_int_mul")],
1684 ["u8", "u16", "u32", "u64", "i8", "i16", "i32", "i64"]
1685 .map(|ty| integer_module.extern_function_id(&format!("{ty}_wide_mul"))),
1686 ));
1687 let div_rem_fns = OrderedHashSet::<_>::from_iter(chain!(
1688 [bounded_int_module.extern_function_id("bounded_int_div_rem")],
1689 utypes.map(|ty| integer_module.extern_function_id(&format!("{ty}_safe_divmod"))),
1690 ));
1691 let type_info: OrderedHashMap<TypeId<'db>, TypeInfo<'db>> = OrderedHashMap::from_iter(
1692 [
1693 ("u8", false, true),
1694 ("u16", false, true),
1695 ("u32", false, true),
1696 ("u64", false, true),
1697 ("u128", false, true),
1698 ("u256", false, false),
1699 ("i8", true, true),
1700 ("i16", true, true),
1701 ("i32", true, true),
1702 ("i64", true, true),
1703 ("i128", true, true),
1704 ]
1705 .map(|(ty_name, as_bounded_int, inc_dec): (&'static str, bool, bool)| {
1706 let ty = corelib::get_core_ty_by_name(db, SmolStrId::from(db, ty_name), vec![]);
1707 let is_zero = if as_bounded_int {
1708 bounded_int_module
1709 .function_id("bounded_int_is_zero", vec![GenericArgumentId::Type(ty)])
1710 } else {
1711 integer_module.function_id(
1712 SmolStrId::from(db, format!("{ty_name}_is_zero")).long(db).as_str(),
1713 vec![],
1714 )
1715 }
1716 .lowered(db);
1717 let (inc, dec) = if inc_dec {
1718 (
1719 Some(
1720 num_module
1721 .function_id(
1722 SmolStrId::from(db, format!("{ty_name}_inc")).long(db).as_str(),
1723 vec![],
1724 )
1725 .lowered(db),
1726 ),
1727 Some(
1728 num_module
1729 .function_id(
1730 SmolStrId::from(db, format!("{ty_name}_dec")).long(db).as_str(),
1731 vec![],
1732 )
1733 .lowered(db),
1734 ),
1735 )
1736 } else {
1737 (None, None)
1738 };
1739 let info = TypeInfo { is_zero, inc, dec };
1740 (ty, info)
1741 }),
1742 );
1743 Self {
1744 felt_sub: core.extern_function_id("felt252_sub"),
1745 felt_add: core.extern_function_id("felt252_add"),
1746 felt_mul: core.extern_function_id("felt252_mul"),
1747 felt_div: core.extern_function_id("felt252_div"),
1748 box_forward_snapshot: box_module.generic_function_id("box_forward_snapshot"),
1749 eq_fns,
1750 uadd_fns,
1751 usub_fns,
1752 diff_fns,
1753 iadd_fns,
1754 isub_fns,
1755 wide_mul_fns,
1756 div_rem_fns,
1757 bounded_int_add: bounded_int_module.extern_function_id("bounded_int_add"),
1758 bounded_int_sub: bounded_int_module.extern_function_id("bounded_int_sub"),
1759 bounded_int_constrain: bounded_int_module.extern_function_id("bounded_int_constrain"),
1760 bounded_int_trim_min: bounded_int_module.extern_function_id("bounded_int_trim_min"),
1761 bounded_int_trim_max: bounded_int_module.extern_function_id("bounded_int_trim_max"),
1762 array_get: array_module.extern_function_id("array_get"),
1763 array_snapshot_pop_front: array_module.extern_function_id("array_snapshot_pop_front"),
1764 array_snapshot_pop_back: array_module.extern_function_id("array_snapshot_pop_back"),
1765 array_len: array_module.extern_function_id("array_len"),
1766 array_new: array_module.extern_function_id("array_new"),
1767 array_append: array_module.extern_function_id("array_append"),
1768 array_pop_front: array_module.extern_function_id("array_pop_front"),
1769 storage_base_address_from_felt252: storage_access_module
1770 .extern_function_id("storage_base_address_from_felt252"),
1771 storage_base_address_const: storage_access_module
1772 .generic_function_id("storage_base_address_const"),
1773 panic_with_felt252: core.function_id("panic_with_felt252", vec![]).lowered(db),
1774 panic_with_const_felt252: core.free_function_id("panic_with_const_felt252"),
1775 panic_with_byte_array: core
1776 .submodule("panics")
1777 .function_id("panic_with_byte_array", vec![])
1778 .lowered(db),
1779 type_info,
1780 const_calculation_info: db.const_calc_info(),
1781 }
1782 }
1783}
1784
1785impl<'db> std::ops::Deref for ConstFoldingContext<'db, '_> {
1786 type Target = ConstFoldingLibfuncInfo<'db>;
1787 fn deref(&self) -> &ConstFoldingLibfuncInfo<'db> {
1788 self.libfunc_info
1789 }
1790}
1791
1792impl<'a> std::ops::Deref for ConstFoldingLibfuncInfo<'a> {
1793 type Target = ConstCalcInfo<'a>;
1794 fn deref(&self) -> &ConstCalcInfo<'a> {
1795 &self.const_calculation_info
1796 }
1797}
1798
1799#[derive(Debug, PartialEq, Eq, salsa::Update)]
1801struct TypeInfo<'db> {
1802 is_zero: FunctionId<'db>,
1804 inc: Option<FunctionId<'db>>,
1806 dec: Option<FunctionId<'db>>,
1808}
1809
1810trait TypeRangeNormalizer {
1811 fn normalized(&self, value: BigInt) -> NormalizedResult;
1814}
1815impl TypeRangeNormalizer for TypeRange {
1816 fn normalized(&self, value: BigInt) -> NormalizedResult {
1817 if value < self.min {
1818 NormalizedResult::Under(value - &self.min + &self.max + 1)
1819 } else if value > self.max {
1820 NormalizedResult::Over(value + &self.min - &self.max - 1)
1821 } else {
1822 NormalizedResult::InRange(value)
1823 }
1824 }
1825}
1826
1827enum NormalizedResult {
1829 InRange(BigInt),
1831 Over(BigInt),
1833 Under(BigInt),
1835}