1#[cfg(test)]
2#[path = "const_folding_test.rs"]
3mod test;
4
5use std::sync::Arc;
6
7use cairo_lang_defs::ids::{ExternFunctionId, ModuleId};
8use cairo_lang_semantic::helper::ModuleHelper;
9use cairo_lang_semantic::items::constant::ConstValue;
10use cairo_lang_semantic::items::imp::ImplLookupContext;
11use cairo_lang_semantic::{GenericArgumentId, MatchArmSelector, TypeId, corelib};
12use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
13use cairo_lang_utils::ordered_hash_set::OrderedHashSet;
14use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
15use cairo_lang_utils::{Intern, LookupIntern, extract_matches, try_extract_matches};
16use id_arena::Arena;
17use itertools::{chain, zip_eq};
18use num_bigint::BigInt;
19use num_integer::Integer;
20use num_traits::Zero;
21
22use crate::db::LoweringGroup;
23use crate::ids::{FunctionId, SemanticFunctionIdEx};
24use crate::{
25 BlockId, FlatBlockEnd, FlatLowered, MatchArm, MatchEnumInfo, MatchExternInfo, MatchInfo,
26 Statement, StatementCall, StatementConst, StatementDesnap, StatementEnumConstruct,
27 StatementStructConstruct, StatementStructDestructure, VarUsage, Variable, VariableId,
28};
29
30#[derive(Debug, Clone)]
33enum VarInfo {
34 Const(ConstValue),
36 Var(VarUsage),
38 Snapshot(Box<VarInfo>),
40 Struct(Vec<Option<VarInfo>>),
43}
44
45pub fn const_folding(db: &dyn LoweringGroup, lowered: &mut FlatLowered) {
48 if db.optimization_config().skip_const_folding || lowered.blocks.is_empty() {
49 return;
50 }
51 let libfunc_info = priv_const_folding_info(db);
52 let mut ctx = ConstFoldingContext {
55 db,
56 var_info: UnorderedHashMap::default(),
57 variables: &mut lowered.variables,
58 libfunc_info: &libfunc_info,
59 };
60 let mut stack = vec![BlockId::root()];
61 let mut visited = vec![false; lowered.blocks.len()];
62 while let Some(block_id) = stack.pop() {
63 if visited[block_id.0] {
64 continue;
65 }
66 visited[block_id.0] = true;
67
68 let block = &mut lowered.blocks[block_id];
69 let mut additional_consts = vec![];
70 for stmt in block.statements.iter_mut() {
71 ctx.maybe_replace_inputs(stmt.inputs_mut());
72 match stmt {
73 Statement::Const(StatementConst { value, output }) => {
74 if matches!(
77 value,
78 ConstValue::Int(..)
79 | ConstValue::Struct(..)
80 | ConstValue::Enum(..)
81 | ConstValue::NonZero(..)
82 ) {
83 ctx.var_info.insert(*output, VarInfo::Const(value.clone()));
84 }
85 }
86 Statement::Snapshot(stmt) => {
87 if let Some(info) = ctx.var_info.get(&stmt.input.var_id).cloned() {
88 ctx.var_info.insert(stmt.original(), info.clone());
89 ctx.var_info.insert(stmt.snapshot(), VarInfo::Snapshot(info.into()));
90 }
91 }
92 Statement::Desnap(StatementDesnap { input, output }) => {
93 if let Some(VarInfo::Snapshot(info)) = ctx.var_info.get(&input.var_id) {
94 ctx.var_info.insert(*output, info.as_ref().clone());
95 }
96 }
97 Statement::Call(call_stmt) => {
98 if let Some(updated_stmt) =
99 ctx.handle_statement_call(call_stmt, &mut additional_consts)
100 {
101 *stmt = Statement::Const(updated_stmt);
102 }
103 }
104 Statement::StructConstruct(StatementStructConstruct { inputs, output }) => {
105 let mut const_args = vec![];
106 let mut all_args = vec![];
107 let mut contains_info = false;
108 for input in inputs.iter() {
109 let Some(info) = ctx.var_info.get(&input.var_id) else {
110 all_args.push(
111 ctx.variables[input.var_id]
112 .copyable
113 .is_ok()
114 .then_some(VarInfo::Var(*input)),
115 );
116 continue;
117 };
118 contains_info = true;
119 if let VarInfo::Const(value) = info {
120 const_args.push(value.clone());
121 }
122 all_args.push(Some(info.clone()));
123 }
124 if const_args.len() == inputs.len() {
125 let value = ConstValue::Struct(const_args, ctx.variables[*output].ty);
126 ctx.var_info.insert(*output, VarInfo::Const(value));
127 } else if contains_info {
128 ctx.var_info.insert(*output, VarInfo::Struct(all_args));
129 }
130 }
131 Statement::StructDestructure(StatementStructDestructure { input, outputs }) => {
132 if let Some(mut info) = ctx.var_info.get(&input.var_id) {
133 let mut n_snapshot = 0;
134 while let VarInfo::Snapshot(inner) = info {
135 info = inner.as_ref();
136 n_snapshot += 1;
137 }
138 let wrap_with_snapshots = |mut info| {
139 for _ in 0..n_snapshot {
140 info = VarInfo::Snapshot(Box::new(info));
141 }
142 info
143 };
144 match info {
145 VarInfo::Const(ConstValue::Struct(member_values, _)) => {
146 for (output, value) in zip_eq(outputs, member_values.clone()) {
147 ctx.var_info.insert(
148 *output,
149 wrap_with_snapshots(VarInfo::Const(value)),
150 );
151 }
152 }
153 VarInfo::Struct(members) => {
154 for (output, member) in zip_eq(outputs, members.clone()) {
155 if let Some(member) = member {
156 ctx.var_info.insert(*output, wrap_with_snapshots(member));
157 }
158 }
159 }
160 _ => {}
161 }
162 }
163 }
164 Statement::EnumConstruct(StatementEnumConstruct { variant, input, output }) => {
165 if let Some(VarInfo::Const(val)) = ctx.var_info.get(&input.var_id) {
166 let value = ConstValue::Enum(variant.clone(), val.clone().into());
167 ctx.var_info.insert(*output, VarInfo::Const(value.clone()));
168 }
169 }
170 }
171 }
172 block.statements.splice(0..0, additional_consts.into_iter().map(Statement::Const));
173
174 match &mut block.end {
175 FlatBlockEnd::Goto(block_id, remappings) => {
176 stack.push(*block_id);
177 for (_, v) in remappings.iter_mut() {
178 ctx.maybe_replace_input(v);
179 }
180 }
181 FlatBlockEnd::Match { info } => {
182 stack.extend(info.arms().iter().map(|arm| arm.block_id));
183 ctx.maybe_replace_inputs(info.inputs_mut());
184 match info {
185 MatchInfo::Enum(MatchEnumInfo { input, arms, .. }) => {
186 if let Some(VarInfo::Const(ConstValue::Enum(variant, value))) =
187 ctx.var_info.get(&input.var_id)
188 {
189 let arm = &arms[variant.idx];
190 ctx.var_info
191 .insert(arm.var_ids[0], VarInfo::Const(value.as_ref().clone()));
192 }
193 }
194 MatchInfo::Extern(info) => {
195 if let Some((extra_stmt, updated_end)) = ctx.handle_extern_block_end(info) {
196 if let Some(stmt) = extra_stmt {
197 block.statements.push(Statement::Const(stmt));
198 }
199 block.end = updated_end;
200 }
201 }
202 MatchInfo::Value(..) => {}
203 }
204 }
205 FlatBlockEnd::Return(ref mut inputs, _) => ctx.maybe_replace_inputs(inputs),
206 FlatBlockEnd::Panic(_) | FlatBlockEnd::NotSet => unreachable!(),
207 }
208 }
209}
210
211struct ConstFoldingContext<'a> {
212 db: &'a dyn LoweringGroup,
214 variables: &'a mut Arena<Variable>,
216 var_info: UnorderedHashMap<VariableId, VarInfo>,
218 libfunc_info: &'a ConstFoldingLibfuncInfo,
220}
221
222impl ConstFoldingContext<'_> {
223 fn handle_statement_call(
231 &mut self,
232 stmt: &mut StatementCall,
233 additional_consts: &mut Vec<StatementConst>,
234 ) -> Option<StatementConst> {
235 if stmt.function == self.panic_with_felt252 {
236 let val = self.as_const(stmt.inputs[0].var_id)?;
237 stmt.inputs.clear();
238 stmt.function = ModuleHelper::core(self.db.upcast())
239 .function_id(
240 "panic_with_const_felt252",
241 vec![GenericArgumentId::Constant(val.clone().intern(self.db))],
242 )
243 .lowered(self.db);
244 return None;
245 }
246 let (id, _generic_args) = stmt.function.get_extern(self.db)?;
247 if id == self.felt_sub {
248 let val = self.as_int(stmt.inputs[1].var_id)?;
250 if val.is_zero() {
251 self.var_info.insert(stmt.outputs[0], VarInfo::Var(stmt.inputs[0]));
252 }
253 None
254 } else if self.wide_mul_fns.contains(&id) {
255 let lhs = self.as_int_ex(stmt.inputs[0].var_id);
256 let rhs = self.as_int(stmt.inputs[1].var_id);
257 let output = stmt.outputs[0];
258 if lhs.map(|(v, _)| v.is_zero()).unwrap_or_default()
259 || rhs.map(Zero::is_zero).unwrap_or_default()
260 {
261 return Some(self.propagate_zero_and_get_statement(output));
262 }
263 let (lhs, nz_ty) = lhs?;
264 Some(self.propagate_const_and_get_statement(lhs * rhs?, stmt.outputs[0], nz_ty))
265 } else if id == self.bounded_int_add || id == self.bounded_int_sub {
266 let lhs = self.as_int(stmt.inputs[0].var_id)?;
267 let rhs = self.as_int(stmt.inputs[1].var_id)?;
268 let value = if id == self.bounded_int_add { lhs + rhs } else { lhs - rhs };
269 Some(self.propagate_const_and_get_statement(value, stmt.outputs[0], false))
270 } else if self.div_rem_fns.contains(&id) {
271 let lhs = self.as_int(stmt.inputs[0].var_id);
272 if lhs.map(Zero::is_zero).unwrap_or_default() {
273 additional_consts.push(self.propagate_zero_and_get_statement(stmt.outputs[1]));
274 return Some(self.propagate_zero_and_get_statement(stmt.outputs[0]));
275 }
276 let rhs = self.as_int(stmt.inputs[1].var_id)?;
277 let (q, r) = lhs?.div_rem(rhs);
278 let q_output = stmt.outputs[0];
279 let q_value = ConstValue::Int(q, self.variables[q_output].ty);
280 self.var_info.insert(q_output, VarInfo::Const(q_value.clone()));
281 let r_output = stmt.outputs[1];
282 let r_value = ConstValue::Int(r, self.variables[r_output].ty);
283 self.var_info.insert(r_output, VarInfo::Const(r_value.clone()));
284 additional_consts.push(StatementConst { value: r_value, output: r_output });
285 Some(StatementConst { value: q_value, output: q_output })
286 } else if id == self.storage_base_address_from_felt252 {
287 let input_var = stmt.inputs[0].var_id;
288 if let Some(ConstValue::Int(val, ty)) = self.as_const(input_var) {
289 stmt.inputs.clear();
290 stmt.function =
291 ModuleHelper { db: self.db.upcast(), id: self.storage_access_module }
292 .function_id(
293 "storage_base_address_const",
294 vec![GenericArgumentId::Constant(
295 ConstValue::Int(val.clone(), *ty).intern(self.db),
296 )],
297 )
298 .lowered(self.db);
299 }
300 None
301 } else if id == self.into_box {
302 let const_value = match self.var_info.get(&stmt.inputs[0].var_id)? {
303 VarInfo::Const(val) => val,
304 VarInfo::Snapshot(info) => try_extract_matches!(info.as_ref(), VarInfo::Const)?,
305 _ => return None,
306 };
307 let value = ConstValue::Boxed(const_value.clone().into());
308 Some(StatementConst { value, output: stmt.outputs[0] })
311 } else if id == self.upcast {
312 let int_value = self.as_int(stmt.inputs[0].var_id)?;
313 let output = stmt.outputs[0];
314 let value = ConstValue::Int(int_value.clone(), self.variables[output].ty);
315 self.var_info.insert(output, VarInfo::Const(value.clone()));
316 Some(StatementConst { value, output })
317 } else {
318 None
319 }
320 }
321
322 fn propagate_const_and_get_statement(
324 &mut self,
325 value: BigInt,
326 output: VariableId,
327 nz_ty: bool,
328 ) -> StatementConst {
329 let mut value = ConstValue::Int(value, self.variables[output].ty);
330 if nz_ty {
331 value = ConstValue::NonZero(Box::new(value));
332 }
333 self.var_info.insert(output, VarInfo::Const(value.clone()));
334 StatementConst { value, output }
335 }
336
337 fn propagate_zero_and_get_statement(&mut self, output: VariableId) -> StatementConst {
339 self.propagate_const_and_get_statement(BigInt::zero(), output, false)
340 }
341
342 fn handle_extern_block_end(
347 &mut self,
348 info: &mut MatchExternInfo,
349 ) -> Option<(Option<StatementConst>, FlatBlockEnd)> {
350 let (id, generic_args) = info.function.get_extern(self.db)?;
351 if self.nz_fns.contains(&id) {
352 let val = self.as_const(info.inputs[0].var_id)?;
353 let is_zero = match val {
354 ConstValue::Int(v, _) => v.is_zero(),
355 ConstValue::Struct(s, _) => s.iter().all(|v| {
356 v.clone().into_int().expect("Expected ConstValue::Int for size").is_zero()
357 }),
358 _ => unreachable!(),
359 };
360 Some(if is_zero {
361 (None, FlatBlockEnd::Goto(info.arms[0].block_id, Default::default()))
362 } else {
363 let arm = &info.arms[1];
364 let nz_var = arm.var_ids[0];
365 let nz_val = ConstValue::NonZero(Box::new(val.clone()));
366 self.var_info.insert(nz_var, VarInfo::Const(nz_val.clone()));
367 (
368 Some(StatementConst { value: nz_val, output: nz_var }),
369 FlatBlockEnd::Goto(arm.block_id, Default::default()),
370 )
371 })
372 } else if self.eq_fns.contains(&id) {
373 let lhs = self.as_int(info.inputs[0].var_id);
374 let rhs = self.as_int(info.inputs[1].var_id);
375 if (lhs.map(Zero::is_zero).unwrap_or_default() && rhs.is_none())
376 || (rhs.map(Zero::is_zero).unwrap_or_default() && lhs.is_none())
377 {
378 let db = self.db.upcast();
379 let nz_input = info.inputs[if lhs.is_some() { 1 } else { 0 }];
380 let var = &self.variables[nz_input.var_id].clone();
381 let function = self.type_value_ranges.get(&var.ty)?.is_zero;
382 let unused_nz_var = Variable::new(
383 self.db,
384 ImplLookupContext::default(),
385 corelib::core_nonzero_ty(db, var.ty),
386 var.location,
387 );
388 let unused_nz_var = self.variables.alloc(unused_nz_var);
389 return Some((
390 None,
391 FlatBlockEnd::Match {
392 info: MatchInfo::Extern(MatchExternInfo {
393 function,
394 inputs: vec![nz_input],
395 arms: vec![
396 MatchArm {
397 arm_selector: MatchArmSelector::VariantId(
398 corelib::jump_nz_zero_variant(db, var.ty),
399 ),
400 block_id: info.arms[1].block_id,
401 var_ids: vec![],
402 },
403 MatchArm {
404 arm_selector: MatchArmSelector::VariantId(
405 corelib::jump_nz_nonzero_variant(db, var.ty),
406 ),
407 block_id: info.arms[0].block_id,
408 var_ids: vec![unused_nz_var],
409 },
410 ],
411 location: info.location,
412 }),
413 },
414 ));
415 }
416 Some((
417 None,
418 FlatBlockEnd::Goto(
419 info.arms[if lhs? == rhs? { 1 } else { 0 }].block_id,
420 Default::default(),
421 ),
422 ))
423 } else if self.uadd_fns.contains(&id)
424 || self.usub_fns.contains(&id)
425 || self.diff_fns.contains(&id)
426 || self.iadd_fns.contains(&id)
427 || self.isub_fns.contains(&id)
428 {
429 let rhs = self.as_int(info.inputs[1].var_id);
430 if rhs.map(Zero::is_zero).unwrap_or_default() && !self.diff_fns.contains(&id) {
431 let arm = &info.arms[0];
432 self.var_info.insert(arm.var_ids[0], VarInfo::Var(info.inputs[0]));
433 return Some((None, FlatBlockEnd::Goto(arm.block_id, Default::default())));
434 }
435 let lhs = self.as_int(info.inputs[0].var_id);
436 let value = if self.uadd_fns.contains(&id) || self.iadd_fns.contains(&id) {
437 if lhs.map(Zero::is_zero).unwrap_or_default() {
438 let arm = &info.arms[0];
439 self.var_info.insert(arm.var_ids[0], VarInfo::Var(info.inputs[1]));
440 return Some((None, FlatBlockEnd::Goto(arm.block_id, Default::default())));
441 }
442 lhs? + rhs?
443 } else {
444 lhs? - rhs?
445 };
446 let ty = self.variables[info.arms[0].var_ids[0]].ty;
447 let range = self.type_value_ranges.get(&ty)?;
448 let (arm_index, value) = match range.normalized(value) {
449 NormalizedResult::InRange(value) => (0, value),
450 NormalizedResult::Under(value) => (1, value),
451 NormalizedResult::Over(value) => (
452 if self.iadd_fns.contains(&id) || self.isub_fns.contains(&id) { 2 } else { 1 },
453 value,
454 ),
455 };
456 let arm = &info.arms[arm_index];
457 let actual_output = arm.var_ids[0];
458 let value = ConstValue::Int(value, ty);
459 self.var_info.insert(actual_output, VarInfo::Const(value.clone()));
460 Some((
461 Some(StatementConst { value, output: actual_output }),
462 FlatBlockEnd::Goto(arm.block_id, Default::default()),
463 ))
464 } else if id == self.downcast {
465 let input_var = info.inputs[0].var_id;
466 let value = self.as_int(input_var)?;
467 let success_output = info.arms[0].var_ids[0];
468 let ty = self.variables[success_output].ty;
469 let range = self.type_value_ranges.get(&ty)?;
470 Some(if let NormalizedResult::InRange(value) = range.normalized(value.clone()) {
471 let value = ConstValue::Int(value, ty);
472 self.var_info.insert(success_output, VarInfo::Const(value.clone()));
473 (
474 Some(StatementConst { value, output: success_output }),
475 FlatBlockEnd::Goto(info.arms[0].block_id, Default::default()),
476 )
477 } else {
478 (None, FlatBlockEnd::Goto(info.arms[1].block_id, Default::default()))
479 })
480 } else if id == self.bounded_int_constrain {
481 let input_var = info.inputs[0].var_id;
482 let (value, nz_ty) = self.as_int_ex(input_var)?;
483 let generic_arg = generic_args[1];
484 let constrain_value = extract_matches!(generic_arg, GenericArgumentId::Constant)
485 .lookup_intern(self.db)
486 .into_int()
487 .unwrap();
488 let arm_idx = if value < &constrain_value { 0 } else { 1 };
489 let output = info.arms[arm_idx].var_ids[0];
490 Some((
491 Some(self.propagate_const_and_get_statement(value.clone(), output, nz_ty)),
492 FlatBlockEnd::Goto(info.arms[arm_idx].block_id, Default::default()),
493 ))
494 } else if id == self.array_get {
495 if self.as_int(info.inputs[1].var_id)?.is_zero() {
496 if let [success, failure] = info.arms.as_mut_slice() {
497 let arr = info.inputs[0].var_id;
498 let unused_arr_output0 = self.variables.alloc(self.variables[arr].clone());
499 let unused_arr_output1 = self.variables.alloc(self.variables[arr].clone());
500 info.inputs.truncate(1);
501 info.function = ModuleHelper { db: self.db.upcast(), id: self.array_module }
502 .function_id("array_snapshot_pop_front", generic_args)
503 .lowered(self.db);
504 success.var_ids.insert(0, unused_arr_output0);
505 failure.var_ids.insert(0, unused_arr_output1);
506 }
507 }
508 None
509 } else {
510 None
511 }
512 }
513
514 fn as_const(&self, var_id: VariableId) -> Option<&ConstValue> {
516 try_extract_matches!(self.var_info.get(&var_id)?, VarInfo::Const)
517 }
518
519 fn as_int_ex(&self, var_id: VariableId) -> Option<(&BigInt, bool)> {
522 match self.as_const(var_id)? {
523 ConstValue::Int(value, _) => Some((value, false)),
524 ConstValue::NonZero(const_value) => {
525 if let ConstValue::Int(value, _) = const_value.as_ref() {
526 Some((value, true))
527 } else {
528 None
529 }
530 }
531 _ => None,
532 }
533 }
534
535 fn as_int(&self, var_id: VariableId) -> Option<&BigInt> {
537 Some(self.as_int_ex(var_id)?.0)
538 }
539
540 fn maybe_replace_inputs(&mut self, inputs: &mut [VarUsage]) {
542 for input in inputs {
543 self.maybe_replace_input(input);
544 }
545 }
546
547 fn maybe_replace_input(&mut self, input: &mut VarUsage) {
549 if let Some(VarInfo::Var(new_var)) = self.var_info.get(&input.var_id) {
550 *input = *new_var;
551 }
552 }
553}
554
555pub fn priv_const_folding_info(
557 db: &dyn LoweringGroup,
558) -> Arc<crate::optimizations::const_folding::ConstFoldingLibfuncInfo> {
559 Arc::new(ConstFoldingLibfuncInfo::new(db))
560}
561
562#[derive(Debug, PartialEq, Eq)]
564pub struct ConstFoldingLibfuncInfo {
565 felt_sub: ExternFunctionId,
567 into_box: ExternFunctionId,
569 upcast: ExternFunctionId,
571 downcast: ExternFunctionId,
573 nz_fns: OrderedHashSet<ExternFunctionId>,
575 eq_fns: OrderedHashSet<ExternFunctionId>,
577 uadd_fns: OrderedHashSet<ExternFunctionId>,
579 usub_fns: OrderedHashSet<ExternFunctionId>,
581 diff_fns: OrderedHashSet<ExternFunctionId>,
583 iadd_fns: OrderedHashSet<ExternFunctionId>,
585 isub_fns: OrderedHashSet<ExternFunctionId>,
587 wide_mul_fns: OrderedHashSet<ExternFunctionId>,
589 div_rem_fns: OrderedHashSet<ExternFunctionId>,
591 bounded_int_add: ExternFunctionId,
593 bounded_int_sub: ExternFunctionId,
595 bounded_int_constrain: ExternFunctionId,
597 array_module: ModuleId,
599 array_get: ExternFunctionId,
601 storage_access_module: ModuleId,
603 storage_base_address_from_felt252: ExternFunctionId,
605 panic_with_felt252: FunctionId,
607 type_value_ranges: OrderedHashMap<TypeId, TypeInfo>,
609}
610impl ConstFoldingLibfuncInfo {
611 fn new(db: &dyn LoweringGroup) -> Self {
612 let core = ModuleHelper::core(db.upcast());
613 let felt_sub = core.extern_function_id("felt252_sub");
614 let box_module = core.submodule("box");
615 let into_box = box_module.extern_function_id("into_box");
616 let integer_module = core.submodule("integer");
617 let bounded_int_module = core.submodule("internal").submodule("bounded_int");
618 let upcast = integer_module.extern_function_id("upcast");
619 let downcast = integer_module.extern_function_id("downcast");
620 let array_module = core.submodule("array");
621 let array_get = array_module.extern_function_id("array_get");
622 let starknet_module = core.submodule("starknet");
623 let storage_access_module = starknet_module.submodule("storage_access");
624 let storage_base_address_from_felt252 =
625 storage_access_module.extern_function_id("storage_base_address_from_felt252");
626 let nz_fns = OrderedHashSet::<_>::from_iter(chain!(
627 [
628 core.extern_function_id("felt252_is_zero"),
629 bounded_int_module.extern_function_id("bounded_int_is_zero")
630 ],
631 ["u8", "u16", "u32", "u64", "u128", "u256", "i8", "i16", "i32", "i64", "i128"]
632 .map(|ty| integer_module.extern_function_id(format!("{ty}_is_zero")))
633 ));
634 let utypes = ["u8", "u16", "u32", "u64", "u128"];
635 let itypes = ["i8", "i16", "i32", "i64", "i128"];
636 let eq_fns = OrderedHashSet::<_>::from_iter(
637 chain!(utypes, itypes).map(|ty| integer_module.extern_function_id(format!("{ty}_eq"))),
638 );
639 let uadd_fns = OrderedHashSet::<_>::from_iter(
640 utypes.map(|ty| integer_module.extern_function_id(format!("{ty}_overflowing_add"))),
641 );
642 let usub_fns = OrderedHashSet::<_>::from_iter(
643 utypes.map(|ty| integer_module.extern_function_id(format!("{ty}_overflowing_sub"))),
644 );
645 let diff_fns = OrderedHashSet::<_>::from_iter(
646 itypes.map(|ty| integer_module.extern_function_id(format!("{ty}_diff"))),
647 );
648 let iadd_fns = OrderedHashSet::<_>::from_iter(
649 itypes
650 .map(|ty| integer_module.extern_function_id(format!("{ty}_overflowing_add_impl"))),
651 );
652 let isub_fns = OrderedHashSet::<_>::from_iter(
653 itypes
654 .map(|ty| integer_module.extern_function_id(format!("{ty}_overflowing_sub_impl"))),
655 );
656 let wide_mul_fns = OrderedHashSet::<_>::from_iter(chain!(
657 [bounded_int_module.extern_function_id("bounded_int_mul")],
658 ["u8", "u16", "u32", "u64", "i8", "i16", "i32", "i64"]
659 .map(|ty| integer_module.extern_function_id(format!("{ty}_wide_mul"))),
660 ));
661 let div_rem_fns = OrderedHashSet::<_>::from_iter(chain!(
662 [bounded_int_module.extern_function_id("bounded_int_div_rem")],
663 utypes.map(|ty| integer_module.extern_function_id(format!("{ty}_safe_divmod"))),
664 ));
665 let bounded_int_add = bounded_int_module.extern_function_id("bounded_int_add");
666 let bounded_int_sub = bounded_int_module.extern_function_id("bounded_int_sub");
667 let bounded_int_constrain = bounded_int_module.extern_function_id("bounded_int_constrain");
668 let type_value_ranges = OrderedHashMap::from_iter(
669 [
670 ("u8", BigInt::ZERO, u8::MAX.into()),
671 ("u16", BigInt::ZERO, u16::MAX.into()),
672 ("u32", BigInt::ZERO, u32::MAX.into()),
673 ("u64", BigInt::ZERO, u64::MAX.into()),
674 ("u128", BigInt::ZERO, u128::MAX.into()),
675 ("u256", BigInt::ZERO, BigInt::from(1) << 256),
676 ("i8", i8::MIN.into(), i8::MAX.into()),
677 ("i16", i16::MIN.into(), i16::MAX.into()),
678 ("i32", i32::MIN.into(), i32::MAX.into()),
679 ("i64", i64::MIN.into(), i64::MAX.into()),
680 ("i128", i128::MIN.into(), i128::MAX.into()),
681 ]
682 .map(|(ty, min, max): (&str, BigInt, BigInt)| {
683 let info = TypeInfo {
684 min,
685 max,
686 is_zero: integer_module
687 .function_id(format!("{ty}_is_zero"), vec![])
688 .lowered(db),
689 };
690 (corelib::get_core_ty_by_name(db.upcast(), ty.into(), vec![]), info)
691 }),
692 );
693 Self {
694 felt_sub,
695 into_box,
696 upcast,
697 downcast,
698 nz_fns,
699 eq_fns,
700 uadd_fns,
701 usub_fns,
702 diff_fns,
703 iadd_fns,
704 isub_fns,
705 wide_mul_fns,
706 div_rem_fns,
707 bounded_int_add,
708 bounded_int_sub,
709 bounded_int_constrain,
710 array_module: array_module.id,
711 array_get,
712 storage_access_module: storage_access_module.id,
713 storage_base_address_from_felt252,
714 panic_with_felt252: core.function_id("panic_with_felt252", vec![]).lowered(db),
715 type_value_ranges,
716 }
717 }
718}
719
720impl std::ops::Deref for ConstFoldingContext<'_> {
721 type Target = ConstFoldingLibfuncInfo;
722 fn deref(&self) -> &ConstFoldingLibfuncInfo {
723 self.libfunc_info
724 }
725}
726
727#[derive(Debug, PartialEq, Eq)]
729struct TypeInfo {
730 min: BigInt,
732 max: BigInt,
734 is_zero: FunctionId,
736}
737impl TypeInfo {
738 fn normalized(&self, value: BigInt) -> NormalizedResult {
741 if value < self.min {
742 NormalizedResult::Under(value - &self.min + &self.max + 1)
743 } else if value > self.max {
744 NormalizedResult::Over(value + &self.min - &self.max - 1)
745 } else {
746 NormalizedResult::InRange(value)
747 }
748 }
749}
750
751enum NormalizedResult {
753 InRange(BigInt),
755 Over(BigInt),
757 Under(BigInt),
759}