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