cairo_lang_lowering/optimizations/
early_unsafe_panic.rs1#[cfg(test)]
2#[path = "early_unsafe_panic_test.rs"]
3mod test;
4
5use cairo_lang_defs::ids::ExternFunctionId;
6use cairo_lang_filesystem::flag::FlagsGroup;
7use cairo_lang_semantic::helper::ModuleHelper;
8use cairo_lang_utils::unordered_hash_set::UnorderedHashSet;
9use salsa::Database;
10
11use crate::analysis::core::StatementLocation;
12use crate::analysis::{DataflowAnalyzer, DataflowBackAnalysis, Direction, Edge};
13use crate::ids::{LocationId, SemanticFunctionIdEx};
14use crate::{
15 Block, BlockEnd, BlockId, Lowered, MatchExternInfo, MatchInfo, Statement, StatementCall,
16};
17
18pub fn early_unsafe_panic<'db>(db: &'db dyn Database, lowered: &mut Lowered<'db>) {
24 if !db.flag_unsafe_panic() || lowered.blocks.is_empty() {
25 return;
26 }
27
28 let core = ModuleHelper::core(db);
29 let libfuncs_with_sideffect = UnorderedHashSet::from_iter([
30 core.submodule("debug").extern_function_id("print"),
31 core.submodule("internal").extern_function_id("trace"),
32 ]);
33
34 let mut ctx = UnsafePanicContext { db, libfuncs_with_sideffect, fixes: Vec::new() };
35 let root_info = DataflowBackAnalysis::new(lowered, &mut ctx).run();
36
37 let fixes = if let ReachableSideEffects::Unreachable(location) = root_info {
40 vec![((BlockId::root(), 0), location)]
41 } else {
42 ctx.fixes
43 };
44
45 let panic_func_id = core.submodule("panics").function_id("unsafe_panic", vec![]).lowered(db);
46 for ((block_id, statement_idx), location) in fixes {
47 let block = &mut lowered.blocks[block_id];
48 block.statements.truncate(statement_idx);
49
50 block.end = BlockEnd::Match {
51 info: MatchInfo::Extern(MatchExternInfo {
52 arms: vec![],
53 location,
54 function: panic_func_id,
55 inputs: vec![],
56 }),
57 }
58 }
59}
60
61pub struct UnsafePanicContext<'db> {
62 db: &'db dyn Database,
63
64 fixes: Vec<(StatementLocation, LocationId<'db>)>,
66
67 libfuncs_with_sideffect: UnorderedHashSet<ExternFunctionId<'db>>,
69}
70
71impl<'db> UnsafePanicContext<'db> {
72 pub fn has_side_effects(&self, stmt: &Statement<'db>) -> bool {
74 if let Statement::Call(StatementCall { function, .. }) = stmt {
75 let Some((extern_fn, _gargs)) = function.get_extern(self.db) else {
76 return false;
77 };
78
79 if self.libfuncs_with_sideffect.contains(&extern_fn) {
80 return true;
81 }
82 }
83
84 false
85 }
86}
87
88#[derive(Clone, Default, PartialEq, Debug)]
90pub enum ReachableSideEffects<'db> {
91 #[default]
93 Reachable,
94 Unreachable(LocationId<'db>),
97}
98
99impl<'db, 'a> DataflowAnalyzer<'db, 'a> for UnsafePanicContext<'db> {
100 type Info = ReachableSideEffects<'db>;
101 const DIRECTION: Direction = Direction::Backward;
102
103 fn transfer_block(&mut self, info: &mut Self::Info, block_id: BlockId, block: &'a Block<'db>) {
104 if let BlockEnd::Match { info: match_info } = &block.end
105 && let ReachableSideEffects::Unreachable(_) = info
106 {
107 self.fixes.push(((block_id, block.statements.len()), *match_info.location()));
108 }
109 let ReachableSideEffects::Unreachable(location) = *info else {
110 return;
111 };
112 for (i, stmt) in block.statements.iter().enumerate().rev() {
115 if self.has_side_effects(stmt) {
116 self.fixes.push(((block_id, i + 1), location));
117 *info = ReachableSideEffects::Reachable;
118 break;
119 }
120 }
121 }
122
123 fn merge(
124 &mut self,
125 lowered: &Lowered<'db>,
126 statement_location: StatementLocation,
127 info1: Self::Info,
128 info2: Self::Info,
129 ) -> Self::Info {
130 match (info1, info2) {
131 (ReachableSideEffects::Reachable, _) | (_, ReachableSideEffects::Reachable) => {
132 ReachableSideEffects::Reachable
133 }
134 (ReachableSideEffects::Unreachable(_), ReachableSideEffects::Unreachable(_)) => {
136 ReachableSideEffects::Unreachable(
137 lowered.blocks[statement_location.0].end.location().unwrap(),
138 )
139 }
140 }
141 }
142
143 fn transfer_edge(&mut self, info: &Self::Info, edge: &Edge<'db, 'a>) -> Self::Info {
144 if let Edge::MatchArm { arm, .. } = edge
145 && let ReachableSideEffects::Unreachable(l) = info
146 {
147 self.fixes.push(((arm.block_id, 0), *l));
148 }
149 info.clone()
150 }
151
152 fn initial_info(&mut self, _block_id: BlockId, block_end: &'a BlockEnd<'db>) -> Self::Info {
153 match block_end {
154 BlockEnd::Match { info } => ReachableSideEffects::Unreachable(*info.location()),
155 _ => ReachableSideEffects::Reachable,
156 }
157 }
158}