cairo_lang_lowering/add_withdraw_gas/
mod.rs

1use cairo_lang_diagnostics::Maybe;
2use cairo_lang_semantic::corelib::{
3    core_array_felt252_ty, core_module, core_submodule, get_function_id, get_ty_by_name,
4    option_none_variant, option_some_variant, unit_ty,
5};
6use cairo_lang_semantic::items::constant::ConstValue;
7use cairo_lang_semantic::{GenericArgumentId, MatchArmSelector, TypeLongId};
8use cairo_lang_utils::Intern;
9use num_bigint::{BigInt, Sign};
10
11use crate::db::LoweringGroup;
12use crate::ids::{ConcreteFunctionWithBodyId, LocationId, SemanticFunctionIdEx};
13use crate::lower::context::{VarRequest, VariableAllocator};
14use crate::{
15    BlockId, FlatBlock, FlatBlockEnd, FlatLowered, MatchArm, MatchExternInfo, MatchInfo, Statement,
16    StatementCall, StatementConst, StatementStructConstruct, VarUsage,
17};
18
19/// Main function for the add_withdraw_gas lowering phase. Adds a `withdraw_gas` statement to the
20/// given function, if needed.
21pub fn add_withdraw_gas(
22    db: &dyn LoweringGroup,
23    function: ConcreteFunctionWithBodyId,
24    lowered: &mut FlatLowered,
25) -> Maybe<()> {
26    if db.needs_withdraw_gas(function)? {
27        add_withdraw_gas_to_function(db, function, lowered)?;
28    }
29
30    Ok(())
31}
32
33/// Adds a `withdraw_gas` call statement to the given function.
34/// Creates a new root block that matches on `withdraw_gas`, moves the old root block to the success
35/// arm of it, and creates a new panic block for the failure arm.
36fn add_withdraw_gas_to_function(
37    db: &dyn LoweringGroup,
38    function: ConcreteFunctionWithBodyId,
39    lowered: &mut FlatLowered,
40) -> Maybe<()> {
41    let location = LocationId::from_stable_location(db, function.stable_location(db)?)
42        .with_auto_generation_note(db, "withdraw_gas");
43    let panic_block = create_panic_block(db, function, lowered, location)?;
44
45    let old_root_block = lowered.blocks.root_block()?.clone();
46    let old_root_new_id = lowered.blocks.push(old_root_block);
47    let panic_block_id = lowered.blocks.push(panic_block);
48    let new_root_block = FlatBlock {
49        statements: vec![],
50        end: FlatBlockEnd::Match {
51            info: MatchInfo::Extern(MatchExternInfo {
52                function: get_function_id(
53                    db.upcast(),
54                    core_submodule(db.upcast(), "gas"),
55                    "withdraw_gas".into(),
56                    vec![],
57                )
58                .lowered(db),
59                inputs: vec![],
60                arms: vec![
61                    MatchArm {
62                        arm_selector: MatchArmSelector::VariantId(option_some_variant(
63                            db.upcast(),
64                            unit_ty(db.upcast()),
65                        )),
66                        block_id: old_root_new_id,
67                        var_ids: vec![],
68                    },
69                    MatchArm {
70                        arm_selector: MatchArmSelector::VariantId(option_none_variant(
71                            db.upcast(),
72                            unit_ty(db.upcast()),
73                        )),
74                        block_id: panic_block_id,
75                        var_ids: vec![],
76                    },
77                ],
78                location,
79            }),
80        },
81    };
82
83    lowered.blocks.reset_block(BlockId(0), new_root_block);
84
85    Ok(())
86}
87
88/// Creates the panic block for the case `withdraw_gas` failure.
89fn create_panic_block(
90    db: &dyn LoweringGroup,
91    function: ConcreteFunctionWithBodyId,
92    lowered: &mut FlatLowered,
93    location: LocationId,
94) -> Maybe<FlatBlock> {
95    let mut variables = VariableAllocator::new(
96        db,
97        function.function_with_body_id(db).base_semantic_function(db),
98        lowered.variables.clone(),
99    )?;
100    let new_array_var =
101        variables.new_var(VarRequest { ty: core_array_felt252_ty(db.upcast()), location });
102    let out_of_gas_err_var = variables.new_var(VarRequest { ty: db.core_felt252_ty(), location });
103    let panic_instance_var = variables.new_var(VarRequest {
104        ty: get_ty_by_name(db.upcast(), core_module(db.upcast()), "Panic".into(), vec![]),
105        location,
106    });
107    let panic_data_var =
108        variables.new_var(VarRequest { ty: core_array_felt252_ty(db.upcast()), location });
109    let err_data_var = variables.new_var(VarRequest {
110        ty: TypeLongId::Tuple(vec![variables[panic_instance_var].ty, variables[panic_data_var].ty])
111            .intern(db),
112        location,
113    });
114    lowered.variables = variables.variables;
115
116    let array_module = core_submodule(db.upcast(), "array");
117
118    let add_location = |var_id| VarUsage { var_id, location };
119
120    // The block consists of creating a new array, appending 'Out of gas' to it and panic with this
121    // array as panic data.
122    Ok(FlatBlock {
123        statements: vec![
124            Statement::Call(StatementCall {
125                function: get_function_id(db.upcast(), array_module, "array_new".into(), vec![
126                    GenericArgumentId::Type(db.core_felt252_ty()),
127                ])
128                .lowered(db),
129                inputs: vec![],
130                with_coupon: false,
131                outputs: vec![new_array_var],
132                location,
133            }),
134            Statement::Const(StatementConst {
135                value: ConstValue::Int(
136                    BigInt::from_bytes_be(Sign::Plus, "Out of gas".as_bytes()),
137                    db.core_felt252_ty(),
138                ),
139                output: out_of_gas_err_var,
140            }),
141            Statement::Call(StatementCall {
142                function: get_function_id(db.upcast(), array_module, "array_append".into(), vec![
143                    GenericArgumentId::Type(db.core_felt252_ty()),
144                ])
145                .lowered(db),
146                inputs: vec![new_array_var, out_of_gas_err_var]
147                    .into_iter()
148                    .map(add_location)
149                    .collect(),
150                with_coupon: false,
151                outputs: vec![panic_data_var],
152                location,
153            }),
154            Statement::StructConstruct(StatementStructConstruct {
155                inputs: vec![],
156                output: panic_instance_var,
157            }),
158            Statement::StructConstruct(StatementStructConstruct {
159                inputs: vec![panic_instance_var, panic_data_var]
160                    .into_iter()
161                    .map(add_location)
162                    .collect(),
163                output: err_data_var,
164            }),
165        ],
166        end: FlatBlockEnd::Panic(VarUsage { var_id: err_data_var, location }),
167    })
168}