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: &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 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 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(), 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 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
107fn 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 ctx = block_ctx.handle_end(cur_block_end);
119 if let Some(continuation_block) = continuation_block {
120 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 ok_ret_tys: Vec<TypeId>,
137 ok_ty: TypeId,
139 ok_variant: ConcreteVariant,
141 err_variant: ConcreteVariant,
143 pub actual_return_ty: TypeId,
145 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 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 fn handle_call_panic(
237 &mut self,
238 call: &StatementCall,
239 ) -> Maybe<(FlatBlockEnd, Option<BlockId>)> {
240 let mut original_outputs = call.outputs.clone();
242 let location = call.location.with_auto_generation_note(self.db(), "Panic handling");
243
244 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 let panic_result_var =
250 self.new_var(VarRequest { ty: callee_info.actual_return_ty, location });
251 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 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 let inner_ok_value = self.new_var(VarRequest { ty: callee_info.ok_ty, location });
274 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 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 let block_continuation =
293 self.ctx.enqueue_block(FlatBlock { statements: vec![], end: FlatBlockEnd::NotSet });
294
295 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 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 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 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 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
392pub 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
402pub trait MayPanicTrait<'a>: Upcast<dyn LoweringGroup + 'a> {
404 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
414pub fn scc_may_panic(db: &dyn LoweringGroup, scc: ConcreteSCCRepresentative) -> Maybe<bool> {
416 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 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
445pub 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}