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
25pub fn lower_panics(
31 db: &dyn LoweringGroup,
32 function_id: ConcreteFunctionWithBodyId,
33 lowered: &mut FlatLowered,
34) -> Maybe<()> {
35 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 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(), 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 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
98fn 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 ctx = block_ctx.handle_end(cur_block_end);
110 if let Some(continuation_block) = continuation_block {
111 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 ok_ret_tys: Vec<TypeId>,
128 ok_ty: TypeId,
130 ok_variant: ConcreteVariant,
132 err_variant: ConcreteVariant,
134 pub actual_return_ty: TypeId,
136 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 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 fn handle_call_panic(
228 &mut self,
229 call: &StatementCall,
230 ) -> Maybe<(FlatBlockEnd, Option<BlockId>)> {
231 let mut original_outputs = call.outputs.clone();
233 let location = call.location.with_auto_generation_note(self.db(), "Panic handling");
234
235 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 let panic_result_var =
241 self.new_var(VarRequest { ty: callee_info.actual_return_ty, location });
242 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 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 let inner_ok_value = self.new_var(VarRequest { ty: callee_info.ok_ty, location });
265 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 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 let block_continuation =
284 self.ctx.enqueue_block(FlatBlock { statements: vec![], end: FlatBlockEnd::NotSet });
285
286 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 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 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 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 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
383pub 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
393pub trait MayPanicTrait<'a>: Upcast<dyn LoweringGroup + 'a> {
395 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
405pub fn scc_may_panic(db: &dyn LoweringGroup, scc: ConcreteSCCRepresentative) -> Maybe<bool> {
407 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 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
436pub 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}