Skip to main content

cairo_lang_lowering/optimizations/
early_unsafe_panic.rs

1#[cfg(test)]
2#[path = "early_unsafe_panic_test.rs"]
3mod test;
4
5use std::collections::HashSet;
6
7use cairo_lang_defs::ids::ExternFunctionId;
8use cairo_lang_filesystem::flag::FlagsGroup;
9use cairo_lang_semantic::helper::ModuleHelper;
10use salsa::Database;
11
12use crate::analysis::core::StatementLocation;
13use crate::analysis::{DataflowAnalyzer, DataflowBackAnalysis, Direction, Edge};
14use crate::ids::{LocationId, SemanticFunctionIdEx};
15use crate::{
16    Block, BlockEnd, BlockId, Lowered, MatchExternInfo, MatchInfo, Statement, StatementCall,
17};
18
19/// Adds an early unsafe_panic when we detect that `return` is unreachable from a certain point in
20/// the code. This step is needed to avoid issues with undroppable references in Sierra to CASM.
21///
22/// This step might replace a match on an empty enum with a call to unsafe_panic and we rely on the
23/// 'trim_unreachable' optimization to clean that up.
24pub fn early_unsafe_panic<'db>(db: &'db dyn Database, lowered: &mut Lowered<'db>) {
25    if !db.flag_unsafe_panic() || lowered.blocks.is_empty() {
26        return;
27    }
28
29    let core = ModuleHelper::core(db);
30    let libfuncs_with_sideffect = HashSet::from_iter([
31        core.submodule("debug").extern_function_id("print"),
32        core.submodule("internal").extern_function_id("trace"),
33    ]);
34
35    let mut ctx = UnsafePanicContext { db, libfuncs_with_sideffect, fixes: Vec::new() };
36    let root_info = DataflowBackAnalysis::new(lowered, &mut ctx).run();
37
38    // If the root block is completely unreachable (no path to return), replace entire function
39    // with unsafe_panic from the start.
40    let fixes = if let ReachableSideEffects::Unreachable(location) = root_info {
41        vec![((BlockId::root(), 0), location)]
42    } else {
43        ctx.fixes
44    };
45
46    let panic_func_id = core.submodule("panics").function_id("unsafe_panic", vec![]).lowered(db);
47    for ((block_id, statement_idx), location) in fixes {
48        let block = &mut lowered.blocks[block_id];
49        block.statements.truncate(statement_idx);
50
51        block.end = BlockEnd::Match {
52            info: MatchInfo::Extern(MatchExternInfo {
53                arms: vec![],
54                location,
55                function: panic_func_id,
56                inputs: vec![],
57            }),
58        }
59    }
60}
61
62pub struct UnsafePanicContext<'db> {
63    db: &'db dyn Database,
64
65    /// The list of blocks where we can insert unsafe_panic.
66    fixes: Vec<(StatementLocation, LocationId<'db>)>,
67
68    /// libfuncs with side effects that we need to ignore.
69    libfuncs_with_sideffect: HashSet<ExternFunctionId<'db>>,
70}
71
72impl<'db> UnsafePanicContext<'db> {
73    /// Returns true if the statement has side effects.
74    pub fn has_side_effects(&self, stmt: &Statement<'db>) -> bool {
75        if let Statement::Call(StatementCall { function, .. }) = stmt {
76            let Some((extern_fn, _gargs)) = function.get_extern(self.db) else {
77                return false;
78            };
79
80            if self.libfuncs_with_sideffect.contains(&extern_fn) {
81                return true;
82            }
83        }
84
85        false
86    }
87}
88
89/// Can this state lead to a return or a statement with side effect.
90#[derive(Clone, Default, PartialEq, Debug)]
91pub enum ReachableSideEffects<'db> {
92    /// Some return statement or statement with side effect is reachable.
93    #[default]
94    Reachable,
95    /// No return statement or statement with side effect is reachable.
96    /// holds the location of the closest match with no returning arms.
97    Unreachable(LocationId<'db>),
98}
99
100impl<'db, 'a> DataflowAnalyzer<'db, 'a> for UnsafePanicContext<'db> {
101    type Info = ReachableSideEffects<'db>;
102    const DIRECTION: Direction = Direction::Backward;
103
104    fn transfer_block(&mut self, info: &mut Self::Info, block_id: BlockId, block: &'a Block<'db>) {
105        if let BlockEnd::Match { info: match_info } = &block.end
106            && let ReachableSideEffects::Unreachable(_) = info
107        {
108            self.fixes.push(((block_id, block.statements.len()), *match_info.location()));
109        }
110        if ReachableSideEffects::Reachable == *info {
111            return;
112        }
113        for (i, stmt) in block.statements.iter().enumerate() {
114            if self.has_side_effects(stmt)
115                && let ReachableSideEffects::Unreachable(locations) = *info
116            {
117                self.fixes.push(((block_id, i), locations));
118                *info = ReachableSideEffects::Reachable;
119                break;
120            }
121        }
122    }
123
124    fn merge(
125        &mut self,
126        lowered: &Lowered<'db>,
127        statement_location: StatementLocation,
128        info1: Self::Info,
129        info2: Self::Info,
130    ) -> Self::Info {
131        match (info1, info2) {
132            (ReachableSideEffects::Reachable, _) | (_, ReachableSideEffects::Reachable) => {
133                ReachableSideEffects::Reachable
134            }
135            // Both are unreachable.
136            (ReachableSideEffects::Unreachable(_), ReachableSideEffects::Unreachable(_)) => {
137                ReachableSideEffects::Unreachable(
138                    lowered.blocks[statement_location.0].end.location().unwrap(),
139                )
140            }
141        }
142    }
143
144    fn transfer_edge(&mut self, info: &Self::Info, edge: &Edge<'db, 'a>) -> Self::Info {
145        if let Edge::MatchArm { arm, .. } = edge
146            && let ReachableSideEffects::Unreachable(l) = info
147        {
148            self.fixes.push(((arm.block_id, 0), *l));
149        }
150        info.clone()
151    }
152
153    fn initial_info(&mut self, _block_id: BlockId, block_end: &'a BlockEnd<'db>) -> Self::Info {
154        match block_end {
155            BlockEnd::Match { info } => ReachableSideEffects::Unreachable(*info.location()),
156            _ => ReachableSideEffects::Reachable,
157        }
158    }
159}