Skip to main content

cairo_lang_lowering/optimizations/
const_folding.rs

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
52/// Converts a const value to a specialization arg.
53/// For struct, tuple, fixed-size array and enum const values, recursively converts to the
54/// corresponding SpecializationArg variant.
55fn 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            // Only convert to SpecializationArg::Struct for struct, tuple, or fixed-size array.
63            // For closures and other types, fall back to Const.
64            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/// Keeps track of equivalent values that variables might be replaced with.
88/// Note: We don't keep track of types as we assume the usage is always correct.
89#[derive(Debug, Clone)]
90enum VarInfo<'db> {
91    /// The variable is a const value.
92    Const(ConstValueId<'db>),
93    /// The variable can be replaced by another variable.
94    Var(VarUsage<'db>),
95    /// The variable is a snapshot of another variable.
96    Snapshot(Rc<VarInfo<'db>>),
97    /// The variable is a struct of other variables.
98    /// `None` values represent variables that are not tracked.
99    Struct(Vec<Option<Rc<VarInfo<'db>>>>),
100    /// The variable is an enum of a known variant of other variables.
101    Enum { variant: ConcreteVariant<'db>, payload: Rc<VarInfo<'db>> },
102    /// The variable is a box of another variable.
103    Box(Rc<VarInfo<'db>>),
104    /// The variable is an array of known size of other variables.
105    /// `None` values represent variables that are not tracked.
106    Array(Vec<Option<Rc<VarInfo<'db>>>>),
107}
108impl<'db> VarInfo<'db> {
109    /// Peels the snapshots from the variable info and returns the number of snapshots performed.
110    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    /// Wraps the variable info with the given number of snapshots.
119    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    /// The block is reachable from the function start only through the goto at the end of the given
130    /// block.
131    FromSingleGoto(BlockId),
132    /// The block is reachable from the function start after const-folding - just does not fit
133    /// `FromSingleGoto`.
134    Any,
135}
136
137/// Performs constant folding on the lowered program.
138/// The optimization only works when the blocks are topologically sorted.
139pub 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    // Note that we can keep the var_info across blocks because the lowering
149    // is in static single assignment form.
150    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    /// The used database.
171    db: &'db dyn Database,
172    /// The variables arena, mostly used to get the type of variables.
173    pub variables: &'mt mut VariableArena<'db>,
174    /// The accumulated information about the const values of variables.
175    var_info: UnorderedHashMap<VariableId, Rc<VarInfo<'db>>>,
176    /// The libfunc information.
177    libfunc_info: &'db ConstFoldingLibfuncInfo<'db>,
178    /// The specialization base of the caller function (or the caller if the function is not
179    /// specialized).
180    caller_function: ConcreteFunctionWithBodyId<'db>,
181    /// Reachability of blocks from the function start.
182    /// If the block is not in this map, it means that it is unreachable (or that it was already
183    /// visited and its reachability won't be checked again).
184    reachability: UnorderedHashMap<BlockId, Reachability>,
185    /// Additional statements to add to the block.
186    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    /// Determines if a block is reachable from the function start and propagates constant values
207    /// when the block is reachable via a single goto statement.
208    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    /// Processes a statement and applies the constant folding optimizations.
236    ///
237    /// This method performs the following operations:
238    /// - Updates the `var_info` map with constant values of variables
239    /// - Replace the input statement with optimized versions when possible
240    /// - Updates `self.additional_stmts` with statements that need to be added to the block.
241    ///
242    /// Note: `self.visit_block_end` must be called after processing all statements
243    /// in a block to actually add the additional statements.
244    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    /// Processes the block's end and incorporates additional statements into the block.
381    ///
382    /// This method handles the following tasks:
383    /// - Inserts the accumulated additional statements into the block.
384    /// - Converts match endings to goto when applicable.
385    /// - Updates self.reachability based on the block's ending.
386    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                            // Create the variable that was previously introduced in match arm.
420                            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    /// Handles a statement call.
453    ///
454    /// Returns None if no additional changes are required.
455    /// If changes are required, returns an updated statement (to override the current
456    /// statement).
457    /// May add additional statements to `self.additional_stmts` if just replacing the current
458    /// statement is not enough.
459    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            // Note that divisor is never 0, due to NonZero type always being the divisor.
598            if let Some(rhs) = self.as_int(stmt.inputs[1].var_id)
599                // Returns the original value when dividing by 1.
600                && 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                // If the value is 0, result is 0 regardless of the divisor.
606                && 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                // Constant fold when both operands are constants
614
615                // Use field_div for Felt252 division
616                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    /// Tries to specialize the call.
710    /// Returns The specialized call statement if it was specialized, or None otherwise.
711    ///
712    /// Specialization occurs only if `priv_should_specialize` returns true.
713    /// Additionally specialization of a callee the with the same base as the caller is currently
714    /// not supported.
715    fn try_specialize_call(&self, call_stmt: &mut StatementCall<'db>) -> Option<Statement<'db>> {
716        if call_stmt.with_coupon {
717            return None;
718        }
719        // No specialization when avoiding inlining.
720        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        // Do not specialize the call that should be inlined.
743        if call_stmt.is_specialization_base_call {
744            return None;
745        }
746
747        // Do not specialize a recursive call that was already specialized.
748        if called_base == caller_base && called_function != called_base {
749            return None;
750        }
751
752        // Avoid specializing with a function that is in the same SCC as the caller (and is not the
753        // same function).
754        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            // No const inputs
762            return None;
763        }
764
765        // If we are specializing a recursive call, use only subset of the caller.
766        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            // No argument was assigned -> no specialization.
797            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            // Canonicalize the specialization rather than adding a specialization of a specialized
804            // function.
805            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    /// Adds `value` as a const to `var_info` and return a const statement for it.
855    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    /// Adds 0 const to `var_info` and return a const statement for it.
867    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    /// Returns a statement that introduces the requested value into `output`, or None if fails.
872    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            // Handling const empty structs - which are not supported in sierra-gen.
882            Some(Statement::StructConstruct(StatementStructConstruct { inputs: vec![], output }))
883        } else {
884            None
885        }
886    }
887
888    /// Handles the end of block matching on an enum.
889    /// Possibly extends the blocks statements as well.
890    /// Returns None if no additional changes are required.
891    /// If changes are required, returns the updated block end.
892    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        // Checking whether we have actual const info on the enum.
907        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            // Propagating the const value information.
913            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                // First var is the input, and needs to equal to the output of the const statement.
929                stmt.outputs_mut()[0] = snapshot_vars[0];
930                statements.push(stmt);
931                // Adding snapshot taking statements for snapshots.
932                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            // Propagating the const value information.
961            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                    // Adding snapshot taking statements for snapshots.
966                    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    /// Handles the end of a block based on an extern function call.
982    /// Possibly extends the blocks statements as well.
983    /// Returns None if no additional changes are required.
984    /// If changes are required, returns the updated block end.
985    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            // TODO(orizi): Propagate success values as well.
1333            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    /// Returns the const value of a variable if it exists.
1351    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    /// Returns the const value as an int if it exists and is an integer.
1356    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    /// Replaces the inputs in place if they are in the var_info map.
1371    fn maybe_replace_inputs(&self, inputs: &mut [VarUsage<'db>]) {
1372        for input in inputs {
1373            self.maybe_replace_input(input);
1374        }
1375    }
1376
1377    /// Replaces the input in place if it is in the var_info map.
1378    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    /// Given a var_info and its type, return the corresponding specialization argument, if it
1387    /// exists.
1388    ///
1389    /// The `coerce` argument is used to constrain the specialization argument of recursive calls to
1390    /// the value that is used by the caller.
1391    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        // Skip zero-sized constants as they are not supported in sierra-gen.
1399        require(self.db.type_size_info(ty).ok()? != TypeSizeInformation::ZeroSized)?;
1400        // Skip specialization arguments if they are coerced to not be specialized.
1401        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                // Use a local accumulator to avoid mutating unknown_vars if we return None.
1422                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                // Accumulate into locals first; only extend unknown_vars if we end up specializing.
1457                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                // Get element types based on the type.
1475                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                    // For closures and other types, don't specialize.
1483                    _ => 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                // Accumulate into locals first; only extend unknown_vars if we end up specializing.
1497                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    /// Returns true if const-folding should be skipped for the current function.
1549    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        // Skipping const-folding for `panic_with_const_felt252` - to avoid replacing a call to
1555        // `panic_with_felt252` with `panic_with_const_felt252` and causing accidental recursion.
1556        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
1565/// Returns a `VarInfo` of a variable only if it is copyable.
1566fn 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/// Internal query for the libfuncs information required for const folding.
1574#[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/// Holds static information about libfuncs required for the optimization.
1582#[derive(Debug, PartialEq, Eq, salsa::Update)]
1583pub struct ConstFoldingLibfuncInfo<'db> {
1584    /// The `felt252_sub` libfunc.
1585    felt_sub: ExternFunctionId<'db>,
1586    /// The `felt252_add` libfunc.
1587    felt_add: ExternFunctionId<'db>,
1588    /// The `felt252_mul` libfunc.
1589    felt_mul: ExternFunctionId<'db>,
1590    /// The `felt252_div` libfunc.
1591    felt_div: ExternFunctionId<'db>,
1592    /// The `box_forward_snapshot` libfunc.
1593    box_forward_snapshot: GenericFunctionId<'db>,
1594    /// The set of functions that check if numbers are equal.
1595    eq_fns: OrderedHashSet<ExternFunctionId<'db>>,
1596    /// The set of functions to add unsigned ints.
1597    uadd_fns: OrderedHashSet<ExternFunctionId<'db>>,
1598    /// The set of functions to subtract unsigned ints.
1599    usub_fns: OrderedHashSet<ExternFunctionId<'db>>,
1600    /// The set of functions to get the difference of signed ints.
1601    diff_fns: OrderedHashSet<ExternFunctionId<'db>>,
1602    /// The set of functions to add signed ints.
1603    iadd_fns: OrderedHashSet<ExternFunctionId<'db>>,
1604    /// The set of functions to subtract signed ints.
1605    isub_fns: OrderedHashSet<ExternFunctionId<'db>>,
1606    /// The set of functions to multiply integers.
1607    wide_mul_fns: OrderedHashSet<ExternFunctionId<'db>>,
1608    /// The set of functions to divide and get the remainder of integers.
1609    div_rem_fns: OrderedHashSet<ExternFunctionId<'db>>,
1610    /// The `bounded_int_add` libfunc.
1611    bounded_int_add: ExternFunctionId<'db>,
1612    /// The `bounded_int_sub` libfunc.
1613    bounded_int_sub: ExternFunctionId<'db>,
1614    /// The `bounded_int_constrain` libfunc.
1615    bounded_int_constrain: ExternFunctionId<'db>,
1616    /// The `bounded_int_trim_min` libfunc.
1617    bounded_int_trim_min: ExternFunctionId<'db>,
1618    /// The `bounded_int_trim_max` libfunc.
1619    bounded_int_trim_max: ExternFunctionId<'db>,
1620    /// The `array_get` libfunc.
1621    array_get: ExternFunctionId<'db>,
1622    /// The `array_snapshot_pop_front` libfunc.
1623    array_snapshot_pop_front: ExternFunctionId<'db>,
1624    /// The `array_snapshot_pop_back` libfunc.
1625    array_snapshot_pop_back: ExternFunctionId<'db>,
1626    /// The `array_len` libfunc.
1627    array_len: ExternFunctionId<'db>,
1628    /// The `array_new` libfunc.
1629    array_new: ExternFunctionId<'db>,
1630    /// The `array_append` libfunc.
1631    array_append: ExternFunctionId<'db>,
1632    /// The `array_pop_front` libfunc.
1633    array_pop_front: ExternFunctionId<'db>,
1634    /// The `storage_base_address_from_felt252` libfunc.
1635    storage_base_address_from_felt252: ExternFunctionId<'db>,
1636    /// The `storage_base_address_const` libfunc.
1637    storage_base_address_const: GenericFunctionId<'db>,
1638    /// The `core::panic_with_felt252` function.
1639    panic_with_felt252: FunctionId<'db>,
1640    /// The `core::panic_with_const_felt252` function.
1641    pub panic_with_const_felt252: FreeFunctionId<'db>,
1642    /// The `core::panics::panic_with_byte_array` function.
1643    panic_with_byte_array: FunctionId<'db>,
1644    /// Information per type.
1645    type_info: OrderedHashMap<TypeId<'db>, TypeInfo<'db>>,
1646    /// The info used for semantic const calculation.
1647    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/// The information of a type required for const foldings.
1800#[derive(Debug, PartialEq, Eq, salsa::Update)]
1801struct TypeInfo<'db> {
1802    /// The function to check if the value is zero for the type.
1803    is_zero: FunctionId<'db>,
1804    /// Inc function to increase the value by one.
1805    inc: Option<FunctionId<'db>>,
1806    /// Dec function to decrease the value by one.
1807    dec: Option<FunctionId<'db>>,
1808}
1809
1810trait TypeRangeNormalizer {
1811    /// Normalizes the value to the range.
1812    /// Assumes the value is within size of range of the range.
1813    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
1827/// The result of normalizing a value to a range.
1828enum NormalizedResult {
1829    /// The original value is in the range, carries the value, or an equivalent value.
1830    InRange(BigInt),
1831    /// The original value is larger than range max, carries the normalized value.
1832    Over(BigInt),
1833    /// The original value is smaller than range min, carries the normalized value.
1834    Under(BigInt),
1835}