cairo_lang_lowering/panic/
mod.rs

1use std::collections::VecDeque;
2
3use cairo_lang_diagnostics::Maybe;
4use cairo_lang_filesystem::flag::Flag;
5use cairo_lang_filesystem::ids::FlagId;
6use cairo_lang_semantic::corelib::{get_core_enum_concrete_variant, get_panic_ty, never_ty};
7use cairo_lang_semantic::helper::ModuleHelper;
8use cairo_lang_semantic::items::constant::ConstValue;
9use cairo_lang_semantic::{self as semantic, GenericArgumentId};
10use cairo_lang_utils::{Intern, Upcast};
11use itertools::{Itertools, chain, zip_eq};
12use semantic::{ConcreteVariant, MatchArmSelector, TypeId};
13
14use crate::blocks::FlatBlocksBuilder;
15use crate::db::{ConcreteSCCRepresentative, LoweringGroup};
16use crate::graph_algorithms::strongly_connected_components::concrete_function_with_body_scc;
17use crate::ids::{ConcreteFunctionWithBodyId, FunctionId, SemanticFunctionIdEx, Signature};
18use crate::lower::context::{VarRequest, VariableAllocator};
19use crate::{
20    BlockId, DependencyType, FlatBlock, FlatBlockEnd, FlatLowered, MatchArm, MatchEnumInfo,
21    MatchInfo, Statement, StatementCall, StatementEnumConstruct, StatementStructConstruct,
22    StatementStructDestructure, VarRemapping, VarUsage, VariableId,
23};
24
25// TODO(spapini): Remove tuple in the Ok() variant of the panic, by supporting multiple values in
26// the Sierra type.
27
28/// Lowering phase that converts BlockEnd::Panic into BlockEnd::Return, and wraps necessary types
29/// with PanicResult<>.
30pub fn lower_panics(
31    db: &dyn LoweringGroup,
32    function_id: ConcreteFunctionWithBodyId,
33    lowered: &mut FlatLowered,
34) -> Maybe<()> {
35    // Skip this phase for non panicable functions.
36    if !db.function_with_body_may_panic(function_id)? {
37        return Ok(());
38    }
39
40    let variables = VariableAllocator::new(
41        db,
42        function_id.function_with_body_id(db).base_semantic_function(db),
43        lowered.variables.clone(),
44    )?;
45
46    let signature = function_id.signature(db)?;
47    // All types should be fully concrete at this point.
48    assert!(signature.is_fully_concrete(db));
49    let panic_info = PanicSignatureInfo::new(db, &signature);
50    let mut ctx = PanicLoweringContext {
51        variables,
52        block_queue: VecDeque::from(lowered.blocks.get().clone()),
53        flat_blocks: FlatBlocksBuilder::new(),
54        panic_info,
55    };
56
57    if matches!(
58        db.get_flag(FlagId::new(db.upcast(), "panic_backtrace")),
59        Some(flag) if matches!(*flag, Flag::PanicBacktrace(true)),
60    ) {
61        let trace_fn = ModuleHelper::core(db.upcast())
62            .submodule("internal")
63            .function_id(
64                "trace",
65                vec![GenericArgumentId::Constant(
66                    ConstValue::Int(
67                        0x70616e6963u64.into(), // 'panic' as numeric.
68                        db.core_info().felt252,
69                    )
70                    .intern(db),
71                )],
72            )
73            .lowered(db);
74        for block in ctx.block_queue.iter_mut() {
75            if let FlatBlockEnd::Panic(end) = &block.end {
76                block.statements.push(Statement::Call(StatementCall {
77                    function: trace_fn,
78                    inputs: vec![],
79                    with_coupon: false,
80                    outputs: vec![],
81                    location: end.location,
82                }));
83            }
84        }
85    }
86
87    // Iterate block queue (old and new blocks).
88    while let Some(block) = ctx.block_queue.pop_front() {
89        ctx = handle_block(ctx, block)?;
90    }
91
92    lowered.variables = ctx.variables.variables;
93    lowered.blocks = ctx.flat_blocks.build().unwrap();
94
95    Ok(())
96}
97
98/// Handles the lowering of panics in a single block.
99fn handle_block(
100    mut ctx: PanicLoweringContext<'_>,
101    mut block: FlatBlock,
102) -> Maybe<PanicLoweringContext<'_>> {
103    let mut block_ctx = PanicBlockLoweringContext { ctx, statements: Vec::new() };
104    for (i, stmt) in block.statements.iter().cloned().enumerate() {
105        if let Some((cur_block_end, continuation_block)) = block_ctx.handle_statement(&stmt)? {
106            // This case means that the lowering should split the block here.
107
108            // Block ended with a match.
109            ctx = block_ctx.handle_end(cur_block_end);
110            if let Some(continuation_block) = continuation_block {
111                // The rest of the statements in this block have not been handled yet, and should be
112                // handled as a part of the continuation block - the second block in the "split".
113                let block_to_edit =
114                    &mut ctx.block_queue[continuation_block.0 - ctx.flat_blocks.len()];
115                block_to_edit.statements.extend(block.statements.drain(i + 1..));
116                block_to_edit.end = block.end;
117            }
118            return Ok(ctx);
119        }
120    }
121    ctx = block_ctx.handle_end(block.end);
122    Ok(ctx)
123}
124
125pub struct PanicSignatureInfo {
126    /// The types of all the variables returned on OK: Reference variables and the original result.
127    ok_ret_tys: Vec<TypeId>,
128    /// The type of the Ok() variant.
129    ok_ty: TypeId,
130    /// The Ok() variant.
131    ok_variant: ConcreteVariant,
132    /// The Err() variant.
133    err_variant: ConcreteVariant,
134    /// The PanicResult concrete type - the new return type of the function.
135    pub actual_return_ty: TypeId,
136    /// Does the function always panic.
137    /// Note that if it does - the function returned type is always `(Panic, Array<felt252>)`.
138    always_panic: bool,
139}
140impl PanicSignatureInfo {
141    pub fn new(db: &dyn LoweringGroup, signature: &Signature) -> Self {
142        let extra_rets = signature.extra_rets.iter().map(|param| param.ty());
143        let original_return_ty = signature.return_type;
144
145        let ok_ret_tys = chain!(extra_rets, [original_return_ty]).collect_vec();
146        let ok_ty = semantic::TypeLongId::Tuple(ok_ret_tys.clone()).intern(db);
147        let ok_variant = get_core_enum_concrete_variant(
148            db.upcast(),
149            "PanicResult",
150            vec![GenericArgumentId::Type(ok_ty)],
151            "Ok",
152        );
153        let err_variant = get_core_enum_concrete_variant(
154            db.upcast(),
155            "PanicResult",
156            vec![GenericArgumentId::Type(ok_ty)],
157            "Err",
158        );
159        let always_panic = original_return_ty == never_ty(db.upcast());
160        let panic_ty = if always_panic { err_variant.ty } else { get_panic_ty(db.upcast(), ok_ty) };
161
162        Self {
163            ok_ret_tys,
164            ok_ty,
165            ok_variant,
166            err_variant,
167            actual_return_ty: panic_ty,
168            always_panic,
169        }
170    }
171}
172
173struct PanicLoweringContext<'a> {
174    variables: VariableAllocator<'a>,
175    block_queue: VecDeque<FlatBlock>,
176    flat_blocks: FlatBlocksBuilder,
177    panic_info: PanicSignatureInfo,
178}
179impl PanicLoweringContext<'_> {
180    pub fn db(&self) -> &dyn LoweringGroup {
181        self.variables.db
182    }
183
184    fn enqueue_block(&mut self, block: FlatBlock) -> BlockId {
185        self.block_queue.push_back(block);
186        BlockId(self.flat_blocks.len() + self.block_queue.len())
187    }
188}
189
190struct PanicBlockLoweringContext<'a> {
191    ctx: PanicLoweringContext<'a>,
192    statements: Vec<Statement>,
193}
194impl<'a> PanicBlockLoweringContext<'a> {
195    pub fn db(&self) -> &dyn LoweringGroup {
196        self.ctx.db()
197    }
198
199    fn new_var(&mut self, req: VarRequest) -> VariableId {
200        self.ctx.variables.new_var(req)
201    }
202
203    /// Handles a statement. If needed, returns the continuation block and the block end for this
204    /// block.
205    /// The continuation block happens when a panic match is added, and the block needs to be split.
206    /// The continuation block is the second block in the "split". This function already partially
207    /// creates this second block, and returns it.
208    /// In case there is no panic match - but just a panic, there is no continuation block.
209    fn handle_statement(
210        &mut self,
211        stmt: &Statement,
212    ) -> Maybe<Option<(FlatBlockEnd, Option<BlockId>)>> {
213        if let Statement::Call(call) = &stmt {
214            if let Some(with_body) = call.function.body(self.db())? {
215                if self.db().function_with_body_may_panic(with_body)? {
216                    return Ok(Some(self.handle_call_panic(call)?));
217                }
218            }
219        }
220        self.statements.push(stmt.clone());
221        Ok(None)
222    }
223
224    /// Handles a call statement to a panicking function.
225    /// Returns the continuation block ID for the caller to complete it, and the block end to set
226    /// for the current block.
227    fn handle_call_panic(
228        &mut self,
229        call: &StatementCall,
230    ) -> Maybe<(FlatBlockEnd, Option<BlockId>)> {
231        // Extract return variable.
232        let mut original_outputs = call.outputs.clone();
233        let location = call.location.with_auto_generation_note(self.db(), "Panic handling");
234
235        // Get callee info.
236        let callee_signature = call.function.signature(self.ctx.variables.db)?;
237        let callee_info = PanicSignatureInfo::new(self.ctx.variables.db, &callee_signature);
238        if callee_info.always_panic {
239            // The panic value, which is actually of type (Panics, Array<felt252>).
240            let panic_result_var =
241                self.new_var(VarRequest { ty: callee_info.actual_return_ty, location });
242            // Emit the new statement.
243            self.statements.push(Statement::Call(StatementCall {
244                function: call.function,
245                inputs: call.inputs.clone(),
246                with_coupon: call.with_coupon,
247                outputs: vec![panic_result_var],
248                location,
249            }));
250            return Ok((
251                FlatBlockEnd::Panic(VarUsage { var_id: panic_result_var, location }),
252                None,
253            ));
254        }
255
256        // Allocate 2 new variables.
257        // panic_result_var - for the new return variable, with is actually of type PanicResult<ty>.
258        let panic_result_var =
259            self.new_var(VarRequest { ty: callee_info.actual_return_ty, location });
260        let n_callee_implicits = original_outputs.len() - callee_info.ok_ret_tys.len();
261        let mut call_outputs = original_outputs.drain(..n_callee_implicits).collect_vec();
262        call_outputs.push(panic_result_var);
263        // inner_ok_value - for the Ok() match arm input.
264        let inner_ok_value = self.new_var(VarRequest { ty: callee_info.ok_ty, location });
265        // inner_ok_values - for the destructure.
266        let inner_ok_values = callee_info
267            .ok_ret_tys
268            .iter()
269            .copied()
270            .map(|ty| self.new_var(VarRequest { ty, location }))
271            .collect_vec();
272
273        // Emit the new statement.
274        self.statements.push(Statement::Call(StatementCall {
275            function: call.function,
276            inputs: call.inputs.clone(),
277            with_coupon: call.with_coupon,
278            outputs: call_outputs,
279            location,
280        }));
281
282        // Start constructing a match on the result.
283        let block_continuation =
284            self.ctx.enqueue_block(FlatBlock { statements: vec![], end: FlatBlockEnd::NotSet });
285
286        // Prepare Ok() match arm block. This block will be the continuation block.
287        // This block is only partially created. It is returned at this function to let the caller
288        // complete it.
289        let block_ok = self.ctx.enqueue_block(FlatBlock {
290            statements: vec![Statement::StructDestructure(StatementStructDestructure {
291                input: VarUsage { var_id: inner_ok_value, location },
292                outputs: inner_ok_values.clone(),
293            })],
294            end: FlatBlockEnd::Goto(
295                block_continuation,
296                VarRemapping {
297                    remapping: zip_eq(
298                        original_outputs,
299                        inner_ok_values.into_iter().map(|var_id| VarUsage { var_id, location }),
300                    )
301                    .collect(),
302                },
303            ),
304        });
305
306        // Prepare Err() match arm block.
307        let err_var = self.new_var(VarRequest { ty: self.ctx.panic_info.err_variant.ty, location });
308        let block_err = self.ctx.enqueue_block(FlatBlock {
309            statements: vec![],
310            end: FlatBlockEnd::Panic(VarUsage { var_id: err_var, location }),
311        });
312
313        let cur_block_end = FlatBlockEnd::Match {
314            info: MatchInfo::Enum(MatchEnumInfo {
315                concrete_enum_id: callee_info.ok_variant.concrete_enum_id,
316                input: VarUsage { var_id: panic_result_var, location },
317                arms: vec![
318                    MatchArm {
319                        arm_selector: MatchArmSelector::VariantId(callee_info.ok_variant),
320                        block_id: block_ok,
321                        var_ids: vec![inner_ok_value],
322                    },
323                    MatchArm {
324                        arm_selector: MatchArmSelector::VariantId(callee_info.err_variant),
325                        block_id: block_err,
326                        var_ids: vec![err_var],
327                    },
328                ],
329                location,
330            }),
331        };
332
333        Ok((cur_block_end, Some(block_continuation)))
334    }
335
336    fn handle_end(mut self, end: FlatBlockEnd) -> PanicLoweringContext<'a> {
337        let end = match end {
338            FlatBlockEnd::Goto(target, remapping) => FlatBlockEnd::Goto(target, remapping),
339            FlatBlockEnd::Panic(err_data) => {
340                // Wrap with PanicResult::Err.
341                let ty = self.ctx.panic_info.actual_return_ty;
342                let location = err_data.location;
343                let output = if self.ctx.panic_info.always_panic {
344                    err_data.var_id
345                } else {
346                    let output = self.new_var(VarRequest { ty, location });
347                    self.statements.push(Statement::EnumConstruct(StatementEnumConstruct {
348                        variant: self.ctx.panic_info.err_variant.clone(),
349                        input: err_data,
350                        output,
351                    }));
352                    output
353                };
354                FlatBlockEnd::Return(vec![VarUsage { var_id: output, location }], location)
355            }
356            FlatBlockEnd::Return(returns, location) => {
357                // Tuple construction.
358                let tupled_res =
359                    self.new_var(VarRequest { ty: self.ctx.panic_info.ok_ty, location });
360                self.statements.push(Statement::StructConstruct(StatementStructConstruct {
361                    inputs: returns,
362                    output: tupled_res,
363                }));
364
365                // Wrap with PanicResult::Ok.
366                let ty = self.ctx.panic_info.actual_return_ty;
367                let output = self.new_var(VarRequest { ty, location });
368                self.statements.push(Statement::EnumConstruct(StatementEnumConstruct {
369                    variant: self.ctx.panic_info.ok_variant.clone(),
370                    input: VarUsage { var_id: tupled_res, location },
371                    output,
372                }));
373                FlatBlockEnd::Return(vec![VarUsage { var_id: output, location }], location)
374            }
375            FlatBlockEnd::NotSet => unreachable!(),
376            FlatBlockEnd::Match { info } => FlatBlockEnd::Match { info },
377        };
378        self.ctx.flat_blocks.alloc(FlatBlock { statements: self.statements, end });
379        self.ctx
380    }
381}
382
383// ============= Query implementations =============
384
385/// Query implementation of [crate::db::LoweringGroup::function_may_panic].
386pub fn function_may_panic(db: &dyn LoweringGroup, function: FunctionId) -> Maybe<bool> {
387    if let Some(body) = function.body(db.upcast())? {
388        return db.function_with_body_may_panic(body);
389    }
390    Ok(function.signature(db)?.panicable)
391}
392
393/// A trait to add helper methods in [LoweringGroup].
394pub trait MayPanicTrait<'a>: Upcast<dyn LoweringGroup + 'a> {
395    /// Returns whether a [ConcreteFunctionWithBodyId] may panic.
396    fn function_with_body_may_panic(&self, function: ConcreteFunctionWithBodyId) -> Maybe<bool> {
397        let scc_representative = self
398            .upcast()
399            .concrete_function_with_body_scc_representative(function, DependencyType::Call);
400        self.upcast().scc_may_panic(scc_representative)
401    }
402}
403impl<'a, T: Upcast<dyn LoweringGroup + 'a> + ?Sized> MayPanicTrait<'a> for T {}
404
405/// Query implementation of [crate::db::LoweringGroup::scc_may_panic].
406pub fn scc_may_panic(db: &dyn LoweringGroup, scc: ConcreteSCCRepresentative) -> Maybe<bool> {
407    // Find the SCC representative.
408    let scc_functions = concrete_function_with_body_scc(db, scc.0, DependencyType::Call);
409    for function in scc_functions {
410        if db.needs_withdraw_gas(function)? {
411            return Ok(true);
412        }
413        if db.has_direct_panic(function)? {
414            return Ok(true);
415        }
416        // For each direct callee, find if it may panic.
417        let direct_callees =
418            db.concrete_function_with_body_direct_callees(function, DependencyType::Call)?;
419        for direct_callee in direct_callees {
420            if let Some(callee_body) = direct_callee.body(db.upcast())? {
421                let callee_scc = db.concrete_function_with_body_scc_representative(
422                    callee_body,
423                    DependencyType::Call,
424                );
425                if callee_scc != scc && db.scc_may_panic(callee_scc)? {
426                    return Ok(true);
427                }
428            } else if direct_callee.signature(db)?.panicable {
429                return Ok(true);
430            }
431        }
432    }
433    Ok(false)
434}
435
436/// Query implementation of [crate::db::LoweringGroup::has_direct_panic].
437pub fn has_direct_panic(
438    db: &dyn LoweringGroup,
439    function_id: ConcreteFunctionWithBodyId,
440) -> Maybe<bool> {
441    let lowered_function = db.priv_concrete_function_with_body_lowered_flat(function_id)?;
442    Ok(itertools::any(&lowered_function.blocks, |(_, block)| {
443        matches!(&block.end, FlatBlockEnd::Panic(..))
444    }))
445}