Skip to main content

cairo_lang_sierra_generator/
block_generator.rs

1#[cfg(test)]
2#[path = "block_generator_test.rs"]
3mod test;
4
5use cairo_lang_diagnostics::Maybe;
6use cairo_lang_filesystem::flag::FlagsGroup;
7use cairo_lang_lowering::BlockId;
8use cairo_lang_lowering::db::LoweringGroup;
9use cairo_lang_lowering::ids::LocationId;
10use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
11use itertools::{chain, enumerate, zip_eq};
12use lowering::analysis::StatementLocation;
13use lowering::{MatchArm, VarUsage};
14use sierra::extensions::lib_func::SierraApChange;
15use sierra::program;
16use {cairo_lang_lowering as lowering, cairo_lang_sierra as sierra};
17
18use crate::block_generator::sierra::ids::ConcreteLibfuncId;
19use crate::db::SierraGenGroup;
20use crate::expr_generator_context::{ExprGenerationResult, ExprGeneratorContext};
21use crate::lifetime::{DropLocation, SierraGenVar, UseLocation};
22use crate::local_variables::MIN_SIZE_FOR_LOCAL_INTO_BOX;
23use crate::pre_sierra::{self, StatementWithLocation};
24use crate::replace_ids::{DebugReplacer, SierraIdReplacer};
25use crate::utils::{
26    branch_align_libfunc_id, const_libfunc_id_by_type, disable_ap_tracking_libfunc_id,
27    drop_libfunc_id, dup_libfunc_id, enable_ap_tracking_libfunc_id,
28    enum_from_bounded_int_libfunc_id, enum_init_libfunc_id, get_concrete_libfunc_id,
29    get_libfunc_signature, into_box_libfunc_id, jump_libfunc_id, jump_statement,
30    local_into_box_libfunc_id, match_enum_libfunc_id, rename_libfunc_id, return_statement,
31    simple_basic_statement, snapshot_take_libfunc_id, struct_construct_libfunc_id,
32    struct_deconstruct_libfunc_id, unbox_libfunc_id,
33};
34
35/// Generates Sierra code for the body of the given [lowering::Block].
36/// Returns a list of Sierra statements.
37pub fn generate_block_body_code<'db>(
38    context: &mut ExprGeneratorContext<'db, '_>,
39    block_id: lowering::BlockId,
40    block: &lowering::Block<'db>,
41) -> Maybe<()> {
42    if context.should_disable_ap_tracking(&block_id) {
43        context.set_ap_tracking(false);
44        context.push_statement(simple_basic_statement(
45            disable_ap_tracking_libfunc_id(context.get_db()),
46            &[],
47            &[],
48        ));
49    }
50
51    let drops = context.get_drops();
52
53    add_drop_statements(context, drops, &DropLocation::BeginningOfBlock(block_id))?;
54
55    // Process the statements.
56    for (i, statement) in block.statements.iter().enumerate() {
57        let statement_lowering_location = (block_id, i);
58        let statement_cairo_location = statement.location();
59        context.maybe_set_cairo_location(statement_cairo_location);
60        generate_statement_code(context, statement, &statement_lowering_location)?;
61        let drop_location = &DropLocation::PostStatement(statement_lowering_location);
62        add_drop_statements(context, drops, drop_location)?;
63    }
64
65    add_drop_statements(
66        context,
67        drops,
68        &DropLocation::PostStatement((block_id, block.statements.len())),
69    )?;
70
71    Ok(())
72}
73
74/// Adds calls to the `drop` libfunc for the given [DropLocation], according to the `drops`
75/// argument (computed by [find_variable_lifetime](crate::lifetime::find_variable_lifetime)).
76fn add_drop_statements<'db>(
77    context: &mut ExprGeneratorContext<'db, '_>,
78    drops: &OrderedHashMap<DropLocation, Vec<SierraGenVar>>,
79    drop_location: &DropLocation,
80) -> Maybe<()> {
81    let Some(vars) = drops.get(drop_location) else { return Ok(()) };
82
83    for sierra_gen_var in vars {
84        let sierra_var = context.get_sierra_variable(*sierra_gen_var);
85        let ty = context.get_variable_sierra_type(*sierra_gen_var)?;
86        context.push_statement(simple_basic_statement(
87            drop_libfunc_id(context.get_db(), ty),
88            &[sierra_var],
89            &[],
90        ));
91    }
92
93    Ok(())
94}
95
96/// Elements to be processed for block code generation.
97enum BlockGenStackElement<'db> {
98    /// Generated code for the given block.
99    Block(BlockId),
100    /// Output the given Sierra statement.
101    Statement(pre_sierra::Statement<'db>),
102    /// Configuration for the following blocks.
103    Config { starting_cairo_location: Option<LocationId<'db>>, ap_tracking_state: bool },
104}
105
106/// Generates Sierra statements for a function from the given [ExprGeneratorContext].
107///
108/// Returns a vector of Sierra statements.
109pub fn generate_function_result<'db>(
110    mut context: ExprGeneratorContext<'db, '_>,
111) -> Maybe<ExprGenerationResult<'db>> {
112    let mut block_gen_stack = vec![BlockGenStackElement::Block(BlockId::root())];
113    while let Some(element) = block_gen_stack.pop() {
114        match element {
115            BlockGenStackElement::Block(block_id) => {
116                generate_block_code(&mut context, &mut block_gen_stack, block_id)?
117            }
118            BlockGenStackElement::Statement(statement) => context.push_statement(statement),
119            BlockGenStackElement::Config { starting_cairo_location, ap_tracking_state } => {
120                context.curr_cairo_location = starting_cairo_location;
121                context.set_ap_tracking(ap_tracking_state);
122            }
123        }
124    }
125    Ok(context.result())
126}
127
128/// Generates Sierra for a given [lowering::Block].
129///
130/// Returns a list of Sierra statements.
131/// Assumes `block_id` exists in `self.lowered.blocks`.
132fn generate_block_code<'db>(
133    context: &mut ExprGeneratorContext<'db, '_>,
134    block_gen_stack: &mut Vec<BlockGenStackElement<'db>>,
135    block_id: BlockId,
136) -> Maybe<()> {
137    let block = context.get_lowered_block(block_id);
138    let statement_location: StatementLocation = (block_id, block.statements.len());
139
140    generate_block_body_code(context, block_id, block)?;
141
142    match &block.end {
143        lowering::BlockEnd::Return(returned_variables, _location) => {
144            generate_return_code(context, returned_variables, &statement_location)?;
145        }
146        lowering::BlockEnd::Panic(_) => {
147            unreachable!("Panics should have been stripped in a previous phase.")
148        }
149        lowering::BlockEnd::Goto(target_block_id, remapping) => {
150            let StatementWithLocation { statement: push_values_statement, location } =
151                generate_push_values_statement_for_remapping(
152                    context,
153                    statement_location,
154                    remapping,
155                )?;
156            context.maybe_set_cairo_location(location);
157            context.push_statement(push_values_statement);
158
159            if *target_block_id == block_id.next_block_id() {
160                let label = pre_sierra::Statement::Label(pre_sierra::Label {
161                    id: *context.block_label(*target_block_id),
162                });
163                context.push_statement(label);
164                block_gen_stack.push(BlockGenStackElement::Block(*target_block_id));
165            } else {
166                let jump = jump_statement(
167                    jump_libfunc_id(context.get_db()),
168                    *context.block_label(*target_block_id),
169                );
170                context.push_statement(jump);
171            }
172        }
173        lowering::BlockEnd::NotSet => unreachable!(),
174        // Process the block end if it's a match.
175        lowering::BlockEnd::Match { info } => {
176            let statement_location = (block_id, block.statements.len());
177            let statement_cairo_location = info.location();
178            if context.should_enable_ap_tracking(&block_id) {
179                context.set_ap_tracking(true);
180                context.push_statement(simple_basic_statement(
181                    enable_ap_tracking_libfunc_id(context.get_db()),
182                    &[],
183                    &[],
184                ));
185            }
186
187            context.maybe_set_cairo_location(Some(*statement_cairo_location));
188
189            match info {
190                lowering::MatchInfo::Extern(s) => {
191                    generate_match_extern_code(context, block_gen_stack, s, &statement_location)
192                }
193                lowering::MatchInfo::Enum(s) => {
194                    generate_match_enum_code(context, block_gen_stack, s, &statement_location)
195                }
196                lowering::MatchInfo::Value(s) => {
197                    generate_match_value_code(context, block_gen_stack, s, &statement_location)
198                }
199            }?;
200        }
201    }
202    Ok(())
203}
204
205/// Generates a push_values statement that corresponds to `remapping`.
206fn generate_push_values_statement_for_remapping<'db, 'mt>(
207    context: &mut ExprGeneratorContext<'db, 'mt>,
208    statement_location: (lowering::BlockId, usize),
209    remapping: &lowering::VarRemapping<'db>,
210) -> Maybe<pre_sierra::StatementWithLocation<'db>> {
211    let mut push_values = Vec::<pre_sierra::PushValue>::new();
212    for (idx, (output, inner_output)) in remapping.iter().enumerate() {
213        let ty = context.get_variable_sierra_type(*inner_output)?;
214        let var_on_stack_ty = context.get_variable_sierra_type(*output)?;
215
216        if ty != var_on_stack_ty {
217            let debug_replacer = DebugReplacer { db: context.get_db() };
218            panic!(
219                "Internal compiler error: Inconsistent types in \
220                 generate_push_values_statement_for_remapping(): ty: `{}`, var_on_stack_ty: `{}`",
221                debug_replacer.replace_type_id(&ty),
222                debug_replacer.replace_type_id(&var_on_stack_ty),
223            );
224        }
225        let dup = !context.is_last_use(&UseLocation { statement_location, idx });
226        push_values.push(pre_sierra::PushValue {
227            var: context.get_sierra_variable(*inner_output),
228            var_on_stack: context.get_sierra_variable(*output),
229            ty,
230            dup,
231        })
232    }
233    let location = remapping.iter().next_back().map(|(_, inner_output)| inner_output.location);
234    Ok(StatementWithLocation {
235        statement: pre_sierra::Statement::PushValues(push_values),
236        location,
237    })
238}
239
240/// Generates Sierra code for a `return` statement.
241/// Pushes the given returned values on the top of the stack, and returns from the function.
242///
243/// Returns a list of Sierra statements.
244pub fn generate_return_code<'db>(
245    context: &mut ExprGeneratorContext<'db, '_>,
246    returned_variables: &[lowering::VarUsage<'db>],
247    statement_location: &StatementLocation,
248) -> Maybe<()> {
249    // Copy the result to the top of the stack before returning.
250    let mut return_variables_on_stack = vec![];
251    let mut push_values = vec![];
252
253    for (idx, returned_variable) in returned_variables.iter().enumerate() {
254        let use_location = UseLocation { statement_location: *statement_location, idx };
255        let should_dup = should_dup(context, &use_location);
256        let var = context.get_sierra_variable(*returned_variable);
257        let return_variable_on_stack = if should_dup {
258            context.allocate_sierra_variable(returned_variable.var_id)
259        } else {
260            var.clone()
261        };
262        return_variables_on_stack.push(return_variable_on_stack.clone());
263        push_values.push(pre_sierra::PushValue {
264            var,
265            var_on_stack: return_variable_on_stack,
266            ty: context.get_variable_sierra_type(*returned_variable)?,
267            dup: should_dup,
268        });
269    }
270    let location = returned_variables.last().map(|var| var.location);
271    context.maybe_set_cairo_location(location);
272    context.push_statement(pre_sierra::Statement::PushValues(push_values));
273    context.push_statement(return_statement(return_variables_on_stack));
274    Ok(())
275}
276
277/// Generates Sierra code for [lowering::Statement].
278pub fn generate_statement_code<'db>(
279    context: &mut ExprGeneratorContext<'db, '_>,
280    statement: &lowering::Statement<'db>,
281    statement_location: &StatementLocation,
282) -> Maybe<()> {
283    match statement {
284        lowering::Statement::Const(statement_literal) => {
285            generate_statement_const_code(context, statement_literal)
286        }
287        lowering::Statement::Call(statement_call) => {
288            generate_statement_call_code(context, statement_call, statement_location)
289        }
290        lowering::Statement::EnumConstruct(statement_enum_construct) => {
291            generate_statement_enum_construct(context, statement_enum_construct, statement_location)
292        }
293        lowering::Statement::StructConstruct(statement) => {
294            generate_statement_struct_construct_code(context, statement, statement_location)
295        }
296        lowering::Statement::StructDestructure(statement) => {
297            generate_statement_struct_destructure_code(context, statement, statement_location)
298        }
299        lowering::Statement::Snapshot(statement) => {
300            generate_statement_snapshot(context, statement, statement_location)
301        }
302        lowering::Statement::Desnap(statement) => {
303            generate_statement_desnap(context, statement, statement_location)
304        }
305        lowering::Statement::IntoBox(statement) => {
306            generate_statement_into_box(context, statement, statement_location)
307        }
308        lowering::Statement::Unbox(statement) => {
309            generate_statement_unbox(context, statement, statement_location)
310        }
311    }
312}
313
314/// Generates Sierra code for [lowering::StatementConst].
315fn generate_statement_const_code<'db>(
316    context: &mut ExprGeneratorContext<'db, '_>,
317    statement: &lowering::StatementConst<'db>,
318) -> Maybe<()> {
319    let output_var = context.get_sierra_variable(statement.output);
320    context.push_statement(simple_basic_statement(
321        const_libfunc_id_by_type(context.get_db(), statement.value, statement.boxed),
322        &[],
323        &[output_var],
324    ));
325    Ok(())
326}
327
328/// Generates Sierra code for [lowering::StatementCall].
329fn generate_statement_call_code<'db>(
330    context: &mut ExprGeneratorContext<'db, '_>,
331    statement: &lowering::StatementCall<'db>,
332    statement_location: &StatementLocation,
333) -> Maybe<()> {
334    // Check if this is a user defined function or a libfunc.
335    let (body, libfunc_id) =
336        get_concrete_libfunc_id(context.get_db(), statement.function, statement.with_coupon);
337    // Checks if the call invalidates ap tracking.
338    let libfunc_signature = get_libfunc_signature(context.get_db(), &libfunc_id);
339    let [branch_signature] = &libfunc_signature.branch_signatures[..] else {
340        panic!(
341            "Unexpected branches in '{}'.",
342            DebugReplacer { db: context.get_db() }.replace_libfunc_id(&libfunc_id)
343        );
344    };
345    if matches!(branch_signature.ap_change, SierraApChange::Unknown) {
346        context.set_ap_tracking(false)
347    }
348
349    if body.is_some() {
350        // Create [pre_sierra::PushValue] instances for the arguments.
351        let mut args_on_stack: Vec<sierra::ids::VarId> = vec![];
352        let mut push_values_vec: Vec<pre_sierra::PushValue> = vec![];
353
354        for (idx, var_usage) in statement.inputs.iter().enumerate() {
355            let use_location = UseLocation { statement_location: *statement_location, idx };
356            let should_dup = should_dup(context, &use_location);
357            let var = context.get_sierra_variable(var_usage.var_id);
358            let arg_on_stack = if should_dup {
359                context.allocate_sierra_variable(var_usage.var_id)
360            } else {
361                var.clone()
362            };
363            push_values_vec.push(pre_sierra::PushValue {
364                var,
365                var_on_stack: arg_on_stack.clone(),
366                ty: context.get_variable_sierra_type(var_usage.var_id)?,
367                dup: should_dup,
368            });
369            args_on_stack.push(arg_on_stack);
370        }
371
372        // Push the arguments.
373        context.push_statement(pre_sierra::Statement::PushValues(push_values_vec));
374        // Call the function.
375        let call_stmt = simple_basic_statement(
376            libfunc_id,
377            &args_on_stack,
378            &context.get_sierra_variables(&statement.outputs),
379        );
380        context.push_statement(call_stmt);
381    } else {
382        assert!(!statement.with_coupon, "Extern functions cannot have a __coupon__ argument.");
383        // Dup variables as needed.
384        let inputs_after_dup =
385            maybe_add_dup_statements(context, statement_location, &statement.inputs)?;
386        let stmt = simple_basic_statement(
387            libfunc_id,
388            &inputs_after_dup,
389            &context.get_sierra_variables(&statement.outputs),
390        );
391        context.push_statement(stmt);
392    }
393    Ok(())
394}
395
396/// Returns if the variable at the given location should be duplicated.
397fn should_dup<'db>(
398    context: &mut ExprGeneratorContext<'db, '_>,
399    use_location: &UseLocation,
400) -> bool {
401    !context.is_last_use(use_location)
402}
403
404/// Adds calls to the `dup` libfunc for the given [StatementLocation] and the given statement's
405/// inputs.
406fn maybe_add_dup_statements<'db>(
407    context: &mut ExprGeneratorContext<'db, '_>,
408    statement_location: &StatementLocation,
409    lowering_vars: &[VarUsage<'db>],
410) -> Maybe<Vec<sierra::ids::VarId>> {
411    lowering_vars
412        .iter()
413        .enumerate()
414        .map(|(idx, lowering_var)| {
415            maybe_add_dup_statement(context, statement_location, idx, lowering_var)
416        })
417        .collect()
418}
419
420/// If necessary, adds a call to the `dup` libfunc for the given [StatementLocation] and the given
421/// statement's input, and returns the duplicated copy. Otherwise, returns the original variable.
422fn maybe_add_dup_statement<'db>(
423    context: &mut ExprGeneratorContext<'db, '_>,
424    statement_location: &StatementLocation,
425    idx: usize,
426    lowering_var: &VarUsage<'db>,
427) -> Maybe<sierra::ids::VarId> {
428    let sierra_var = context.get_sierra_variable(*lowering_var);
429
430    // Check whether the variable should be dupped.
431    if context.is_last_use(&UseLocation { statement_location: *statement_location, idx }) {
432        // Dup is not required.
433        Ok(sierra_var)
434    } else {
435        let ty = context.get_variable_sierra_type(*lowering_var)?;
436        let dup_var = context.allocate_sierra_variable(lowering_var.var_id);
437        context.push_statement(simple_basic_statement(
438            dup_libfunc_id(context.get_db(), ty),
439            std::slice::from_ref(&sierra_var),
440            &[sierra_var.clone(), dup_var.clone()],
441        ));
442        Ok(dup_var)
443    }
444}
445
446/// Generates Sierra code for [lowering::MatchExternInfo].
447fn generate_match_extern_code<'db>(
448    context: &mut ExprGeneratorContext<'db, '_>,
449    block_gen_stack: &mut Vec<BlockGenStackElement<'db>>,
450    match_info: &lowering::MatchExternInfo<'db>,
451    statement_location: &StatementLocation,
452) -> Maybe<()> {
453    // Prepare the Sierra input variables.
454    let args = maybe_add_dup_statements(context, statement_location, &match_info.inputs)?;
455    // Get the [ConcreteLibfuncId].
456    let (_function_long_id, libfunc_id) =
457        get_concrete_libfunc_id(context.get_db(), match_info.function, false);
458
459    generate_match_code(context, block_gen_stack, libfunc_id, args, &match_info.arms)
460}
461/// Generates Sierra code for the match a [lowering::MatchExternInfo] or [lowering::MatchEnumInfo]
462/// statement.
463fn generate_match_code<'db>(
464    context: &mut ExprGeneratorContext<'db, '_>,
465    block_gen_stack: &mut Vec<BlockGenStackElement<'db>>,
466    libfunc_id: ConcreteLibfuncId,
467    args: Vec<sierra::ids::VarId>,
468    arms: &[lowering::MatchArm<'db>],
469) -> Maybe<()> {
470    // Generate labels for all the arms, except for the first (which will be Fallthrough).
471    let arm_labels: Vec<(pre_sierra::Statement<'_>, pre_sierra::LabelId<'_>)> =
472        (1..arms.len()).map(|_i| context.new_label()).collect();
473    // Generate a label for the end of the match.
474    let (end_label, _) = context.new_label();
475
476    // Create the arm branches.
477    let arm_targets: Vec<program::GenBranchTarget<pre_sierra::LabelId<'_>>> = if arms.is_empty() {
478        vec![]
479    } else {
480        chain!(
481            [program::GenBranchTarget::Fallthrough],
482            arm_labels
483                .iter()
484                .map(|(_statement, label_id)| program::GenBranchTarget::Statement(*label_id)),
485        )
486        .collect()
487    };
488
489    let branches: Vec<_> = zip_eq(arms, arm_targets)
490        .map(|(arm, target)| program::GenBranchInfo {
491            target,
492            results: context.get_sierra_variables(&arm.var_ids),
493        })
494        .collect();
495
496    // Call the match libfunc.
497    context.push_statement(pre_sierra::Statement::Sierra(program::GenStatement::Invocation(
498        program::GenInvocation { libfunc_id, args, branches },
499    )));
500
501    let starting_cairo_location = context.curr_cairo_location.take();
502    let ap_tracking_state = context.get_ap_tracking();
503    // No need for `branch_align` if there's only one arm - since there's only one path.
504    let require_branch_aligns = arms.len() > 1;
505
506    block_gen_stack.push(BlockGenStackElement::Statement(end_label));
507    // Generate the blocks.
508    for (i, MatchArm { arm_selector: _, block_id, var_ids: _ }) in enumerate(arms).rev() {
509        block_gen_stack.push(BlockGenStackElement::Block(*block_id));
510        if require_branch_aligns {
511            block_gen_stack.push(BlockGenStackElement::Statement(simple_basic_statement(
512                branch_align_libfunc_id(context.get_db()),
513                &[],
514                &[],
515            )));
516        }
517        if i > 0 {
518            block_gen_stack.push(BlockGenStackElement::Statement(arm_labels[i - 1].0.clone()));
519        }
520        block_gen_stack
521            .push(BlockGenStackElement::Config { starting_cairo_location, ap_tracking_state });
522    }
523    Ok(())
524}
525
526/// Generates Sierra code for [lowering::StatementEnumConstruct].
527fn generate_statement_enum_construct<'db>(
528    context: &mut ExprGeneratorContext<'db, '_>,
529    statement: &lowering::StatementEnumConstruct<'db>,
530    statement_location: &StatementLocation,
531) -> Maybe<()> {
532    let input = maybe_add_dup_statement(context, statement_location, 0, &statement.input)?;
533    let stmt = simple_basic_statement(
534        enum_init_libfunc_id(
535            context.get_db(),
536            context.get_variable_sierra_type(statement.output)?,
537            statement.variant.idx,
538        ),
539        &[input],
540        &[context.get_sierra_variable(statement.output)],
541    );
542    context.push_statement(stmt);
543    Ok(())
544}
545
546/// Generates Sierra code for [lowering::StatementStructConstruct].
547fn generate_statement_struct_construct_code<'db>(
548    context: &mut ExprGeneratorContext<'db, '_>,
549    statement: &lowering::StatementStructConstruct<'db>,
550    statement_location: &StatementLocation,
551) -> Maybe<()> {
552    let inputs = maybe_add_dup_statements(context, statement_location, &statement.inputs)?;
553    let stmt = simple_basic_statement(
554        struct_construct_libfunc_id(
555            context.get_db(),
556            context.get_variable_sierra_type(statement.output)?,
557        ),
558        &inputs,
559        &[context.get_sierra_variable(statement.output)],
560    );
561    context.push_statement(stmt);
562    Ok(())
563}
564
565/// Generates Sierra code for [lowering::StatementIntoBox].
566fn generate_statement_into_box<'db>(
567    context: &mut ExprGeneratorContext<'db, '_>,
568    statement: &lowering::StatementIntoBox<'db>,
569    statement_location: &StatementLocation,
570) -> Maybe<()> {
571    let input = maybe_add_dup_statement(context, statement_location, 0, &statement.input)?;
572    let var_id = statement.input.var_id;
573    let ty = context.get_variable_sierra_type(var_id)?;
574    let db = context.get_db();
575
576    let use_local_into_box = db.flag_future_sierra()
577        && context.is_fp_relative(var_id)
578        && db.type_size(context.get_lowered_variable(var_id).ty) >= MIN_SIZE_FOR_LOCAL_INTO_BOX;
579    let libfunc_id = if use_local_into_box {
580        local_into_box_libfunc_id(db, ty)
581    } else {
582        into_box_libfunc_id(db, ty)
583    };
584    let stmt = simple_basic_statement(
585        libfunc_id,
586        &[input],
587        &[context.get_sierra_variable(statement.output)],
588    );
589    context.push_statement(stmt);
590    Ok(())
591}
592
593/// Generates Sierra code for [lowering::StatementUnbox].
594fn generate_statement_unbox<'db>(
595    context: &mut ExprGeneratorContext<'db, '_>,
596    statement: &lowering::StatementUnbox<'db>,
597    statement_location: &StatementLocation,
598) -> Maybe<()> {
599    let input = maybe_add_dup_statement(context, statement_location, 0, &statement.input)?;
600    let stmt = simple_basic_statement(
601        unbox_libfunc_id(context.get_db(), context.get_variable_sierra_type(statement.output)?),
602        &[input],
603        &[context.get_sierra_variable(statement.output)],
604    );
605    context.push_statement(stmt);
606    Ok(())
607}
608
609/// Generates Sierra code for [lowering::StatementStructDestructure].
610fn generate_statement_struct_destructure_code<'db>(
611    context: &mut ExprGeneratorContext<'db, '_>,
612    statement: &lowering::StatementStructDestructure<'db>,
613    statement_location: &StatementLocation,
614) -> Maybe<()> {
615    let input = maybe_add_dup_statement(context, statement_location, 0, &statement.input)?;
616    let stmt = simple_basic_statement(
617        struct_deconstruct_libfunc_id(
618            context.get_db(),
619            context.get_variable_sierra_type(statement.input.var_id)?,
620        )?,
621        &[input],
622        &context.get_sierra_variables(&statement.outputs),
623    );
624    context.push_statement(stmt);
625    Ok(())
626}
627
628/// Generates Sierra code for [lowering::MatchEnumInfo]. where the matched value is a felt252.
629/// Note that the input is a bounded_int and we convert it to an enum, using the
630/// enum_from_bounded_int libfunc.
631fn generate_match_value_code<'db>(
632    context: &mut ExprGeneratorContext<'db, '_>,
633    block_gen_stack: &mut Vec<BlockGenStackElement<'db>>,
634    match_info: &lowering::MatchEnumValue<'db>,
635    statement_location: &StatementLocation,
636) -> Maybe<()> {
637    // Prepare the Sierra input variables.
638    let bounded_int = maybe_add_dup_statement(context, statement_location, 0, &match_info.input)?;
639
640    // Get the [ConcreteLibfuncId].
641    let concrete_enum_type = context.get_db().get_index_enum_type_id(match_info.num_of_arms)?;
642    let enum_var = context.allocate_sierra_variable(match_info.input.var_id);
643    context.push_statement(simple_basic_statement(
644        enum_from_bounded_int_libfunc_id(context.get_db(), concrete_enum_type.clone()),
645        &[bounded_int],
646        std::slice::from_ref(&enum_var),
647    ));
648
649    let libfunc_id = match_enum_libfunc_id(context.get_db(), concrete_enum_type.clone())?;
650
651    let args = vec![enum_var];
652    generate_match_code(context, block_gen_stack, libfunc_id, args, &match_info.arms)
653}
654
655/// Generates Sierra code for [lowering::MatchEnumInfo].
656fn generate_match_enum_code<'db>(
657    context: &mut ExprGeneratorContext<'db, '_>,
658    block_gen_stack: &mut Vec<BlockGenStackElement<'db>>,
659    match_info: &lowering::MatchEnumInfo<'db>,
660    statement_location: &StatementLocation,
661) -> Maybe<()> {
662    // Prepare the Sierra input variables.
663    let matched_enum = maybe_add_dup_statement(context, statement_location, 0, &match_info.input)?;
664    // Get the [ConcreteLibfuncId].
665    let concrete_enum_type = context.get_variable_sierra_type(match_info.input)?;
666    let libfunc_id = match_enum_libfunc_id(context.get_db(), concrete_enum_type)?;
667
668    let args = vec![matched_enum];
669    generate_match_code(context, block_gen_stack, libfunc_id, args, &match_info.arms)
670}
671
672/// Generates Sierra code for [lowering::StatementSnapshot].
673fn generate_statement_snapshot<'db>(
674    context: &mut ExprGeneratorContext<'db, '_>,
675    statement: &lowering::StatementSnapshot<'db>,
676    statement_location: &StatementLocation,
677) -> Maybe<()> {
678    // Prepare the Sierra input variables.
679    let input = maybe_add_dup_statement(context, statement_location, 0, &statement.input)?;
680
681    let ty = context.get_variable_sierra_type(statement.input.var_id)?;
682    let func = snapshot_take_libfunc_id(context.get_db(), ty);
683    let stmt = simple_basic_statement(
684        func,
685        &[input],
686        &[
687            context.get_sierra_variable(statement.original()),
688            context.get_sierra_variable(statement.snapshot()),
689        ],
690    );
691    context.push_statement(stmt);
692    Ok(())
693}
694
695/// Generates Sierra code for [lowering::StatementDesnap].
696fn generate_statement_desnap<'db>(
697    context: &mut ExprGeneratorContext<'db, '_>,
698    statement: &lowering::StatementDesnap<'db>,
699    statement_location: &StatementLocation,
700) -> Maybe<()> {
701    // Dup variables as needed.
702    let inputs_after_dup =
703        maybe_add_dup_statements(context, statement_location, &[statement.input])?;
704    let rename_stmt = simple_basic_statement(
705        rename_libfunc_id(
706            context.get_db(),
707            context.get_variable_sierra_type(statement.input.var_id)?,
708        ),
709        &inputs_after_dup,
710        &[context.get_sierra_variable(statement.output)],
711    );
712    context.push_statement(rename_stmt);
713    Ok(())
714}