Skip to main content

cairo_lang_semantic/usage/
mod.rs

1//! Introduces [Usages], which is responsible for computing variables usage in semantic blocks\
2//! of a function.
3
4use cairo_lang_proc_macros::DebugWithDb;
5use cairo_lang_utils::extract_matches;
6use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
7use cairo_lang_utils::ordered_hash_set::OrderedHashSet;
8
9use crate::expr::fmt::ExprFormatter;
10use crate::expr::objects::Arenas;
11use crate::{
12    Condition, Expr, ExprClosure, ExprFor, ExprFunctionCall, ExprFunctionCallArg, ExprId, ExprLoop,
13    ExprVarMemberPath, ExprWhile, FixedSizeArrayItems, FunctionBody, MemberAccessKind, Parameter,
14    Pattern, PatternArena, PatternId, Statement, StatementBreak, StatementExpr, StatementLet,
15    StatementReturn, VarId,
16};
17
18#[cfg(test)]
19mod test;
20
21/// Member path (e.g. a.b.c). Unlike [ExprVarMemberPath], this is not an expression, and has no
22/// syntax pointers.
23#[derive(Clone, Debug, Hash, PartialEq, Eq, DebugWithDb)]
24#[debug_db(ExprFormatter<'db>)]
25pub enum MemberPath<'db> {
26    Var(VarId<'db>),
27    Member { parent: Box<MemberPath<'db>>, kind: MemberAccessKind<'db> },
28}
29impl<'db> MemberPath<'db> {
30    pub fn base_var(&self) -> VarId<'db> {
31        match self {
32            MemberPath::Var(var) => *var,
33            MemberPath::Member { parent, .. } => parent.base_var(),
34        }
35    }
36}
37impl<'db> From<&ExprVarMemberPath<'db>> for MemberPath<'db> {
38    fn from(value: &ExprVarMemberPath<'db>) -> Self {
39        match value {
40            ExprVarMemberPath::Var(expr) => MemberPath::Var(expr.var),
41            ExprVarMemberPath::Member { parent, kind, .. } => {
42                MemberPath::Member { parent: Box::new(parent.as_ref().into()), kind: kind.clone() }
43            }
44        }
45    }
46}
47
48/// Usages of variables and member paths in semantic code.
49#[derive(Clone, Debug, Default, DebugWithDb)]
50#[debug_db(ExprFormatter<'db>)]
51pub struct Usage<'db> {
52    /// Member paths that are read.
53    pub usage: OrderedHashMap<MemberPath<'db>, ExprVarMemberPath<'db>>,
54    /// Member paths that are assigned to.
55    pub changes: OrderedHashMap<MemberPath<'db>, ExprVarMemberPath<'db>>,
56    /// Member paths that are read as snapshots.
57    pub snap_usage: OrderedHashMap<MemberPath<'db>, ExprVarMemberPath<'db>>,
58    /// Variables that are defined.
59    pub introductions: OrderedHashSet<VarId<'db>>,
60    /// indicates that the expression has an early return.
61    pub has_early_return: bool,
62}
63
64impl<'db> Usage<'db> {
65    /// Adds the usage and changes from 'usage' to self, Ignoring `introductions`.
66    pub fn add_usage_and_changes(&mut self, usage: &Usage<'db>) {
67        for (path, expr) in usage.usage.iter() {
68            self.usage.insert(path.clone(), expr.clone());
69        }
70        for (path, expr) in usage.changes.iter() {
71            self.changes.insert(path.clone(), expr.clone());
72        }
73        for (path, expr) in usage.snap_usage.iter() {
74            self.snap_usage.insert(path.clone(), expr.clone());
75        }
76        self.has_early_return |= usage.has_early_return;
77    }
78
79    /// Removes usage that was introduced current block and usage that is already covered
80    /// by containing variables.
81    pub fn finalize_as_scope(&mut self) {
82        // Prune usages and introdictions from snap_usage. Runs before the `changes` pass so that
83        // the promotion below always promotes the top-most snapshotted ancestor.
84        for member_path in prune_and_get_candidates(&mut self.snap_usage, |k| {
85            self.usage.contains_key(k) || self.introductions.contains(&k.base_var())
86        }) {
87            // Prune snap_usage that are members of other snap_usage or usages.
88            let mut current_path = &member_path;
89            while let MemberPath::Member { parent, .. } = current_path {
90                current_path = parent.as_ref();
91                if self.snap_usage.contains_key(current_path)
92                    | self.usage.contains_key(current_path)
93                {
94                    self.snap_usage.swap_remove(&member_path);
95                    break;
96                }
97            }
98        }
99        // Prune introductions from changes.
100        for member_path in prune_and_get_candidates(&mut self.changes, |k| {
101            self.introductions.contains(&k.base_var())
102        }) {
103            // Prune changes that are members of other changes.
104            // Also if a child is changed and an ancestor is used as a snapshot, promote the
105            // ancestor into `usage`/`changes` so the whole ancestor is read and written back,
106            // instead of returning the child of a stale snapshot.
107            // TODO(TomerStarkware): Deconstruct the parent, and snap_use other members.
108            let mut current_path = &member_path;
109            while let MemberPath::Member { parent, .. } = current_path {
110                current_path = parent.as_ref();
111                if self.snap_usage.contains_key(current_path) {
112                    // Note that current_path must be top most usage as we prune snap_usage and
113                    // usage.
114                    if let Some(value) = self.snap_usage.swap_remove(current_path) {
115                        self.usage.insert(current_path.clone(), value.clone());
116                        self.changes.insert(current_path.clone(), value);
117                    };
118                }
119                if self.changes.contains_key(current_path) {
120                    self.changes.swap_remove(&member_path);
121                    break;
122                }
123            }
124        }
125        // Prune introductions from usages. Runs after the `changes` pass so that members of an
126        // ancestor promoted above are pruned as well.
127        for member_path in prune_and_get_candidates(&mut self.usage, |k| {
128            self.introductions.contains(&k.base_var())
129        }) {
130            // Prune usages that are members of other usages.
131            let mut current_path = &member_path;
132            while let MemberPath::Member { parent, .. } = current_path {
133                current_path = parent.as_ref();
134                if self.usage.contains_key(current_path) {
135                    self.usage.swap_remove(&member_path);
136                    break;
137                }
138            }
139        }
140    }
141}
142
143/// Prunes the map by removing entries for which `filter` returns true.
144/// From the remaining entries, collects the `MemberPath::Member` variants and returns them.
145fn prune_and_get_candidates<'db>(
146    map: &mut OrderedHashMap<MemberPath<'db>, ExprVarMemberPath<'db>>,
147    filter: impl Fn(&MemberPath<'db>) -> bool,
148) -> Vec<MemberPath<'db>> {
149    let mut candidates = Vec::new();
150    map.retain(|k, _| {
151        if filter(k) {
152            return false;
153        }
154        if matches!(k, MemberPath::Member { .. }) {
155            candidates.push(k.clone());
156        }
157        true
158    });
159    candidates
160}
161
162/// Usages of member paths in expressions of interest, currently loops and closures.
163#[derive(Debug, DebugWithDb)]
164#[debug_db(ExprFormatter<'db>)]
165pub struct Usages<'db> {
166    /// Mapping from an [ExprId] to its [Usage].
167    pub usages: OrderedHashMap<ExprId, Usage<'db>>,
168}
169impl<'db> Usages<'db> {
170    pub fn from_function_body(function_body: &FunctionBody<'db>) -> Self {
171        let mut current = Usage::default();
172        let mut usages = Self { usages: Default::default() };
173        usages.handle_expr(&function_body.arenas, function_body.body_expr, &mut current);
174        usages
175    }
176
177    pub fn handle_closure(
178        &mut self,
179        arenas: &Arenas<'db>,
180        param_ids: &[Parameter<'db>],
181        body: ExprId,
182    ) -> Usage<'db> {
183        let mut usage: Usage<'_> = Default::default();
184
185        usage.introductions.extend(param_ids.iter().map(|param| VarId::Param(param.id)));
186        self.handle_expr(arenas, body, &mut usage);
187        usage.finalize_as_scope();
188        usage
189    }
190
191    fn handle_expr(
192        &mut self,
193        arenas: &Arenas<'db>,
194        curr_expr_id: ExprId,
195        current: &mut Usage<'db>,
196    ) {
197        match &arenas.exprs[curr_expr_id] {
198            Expr::Tuple(expr) => {
199                for expr_id in &expr.items {
200                    self.handle_expr(arenas, *expr_id, current);
201                }
202            }
203            Expr::FixedSizeArray(expr) => match &expr.items {
204                FixedSizeArrayItems::Items(items) => {
205                    for expr_id in items {
206                        self.handle_expr(arenas, *expr_id, current);
207                    }
208                }
209                FixedSizeArrayItems::ValueAndSize(value, _) => {
210                    self.handle_expr(arenas, *value, current);
211                }
212            },
213            Expr::Snapshot(expr) => {
214                let expr_id = expr.inner;
215
216                match &arenas.exprs[expr_id] {
217                    Expr::Var(expr_var) => {
218                        current.snap_usage.insert(
219                            MemberPath::Var(expr_var.var),
220                            ExprVarMemberPath::Var(expr_var.clone()),
221                        );
222                    }
223                    Expr::MemberAccess(expr) => {
224                        if let Some(member_path) = &expr.member_path {
225                            current.snap_usage.insert(member_path.into(), member_path.clone());
226                        } else {
227                            self.handle_expr(arenas, expr.expr, current);
228                        }
229                    }
230                    _ => self.handle_expr(arenas, expr_id, current),
231                }
232            }
233            Expr::Desnap(expr) => self.handle_expr(arenas, expr.inner, current),
234            Expr::Assignment(expr) => {
235                self.handle_expr(arenas, expr.rhs, current);
236                current.usage.insert((&expr.ref_arg).into(), expr.ref_arg.clone());
237                current.changes.insert((&expr.ref_arg).into(), expr.ref_arg.clone());
238            }
239            Expr::LogicalOperator(expr) => {
240                self.handle_expr(arenas, expr.lhs, current);
241                self.handle_expr(arenas, expr.rhs, current);
242            }
243            Expr::Block(expr) => {
244                let mut usage = Default::default();
245                for stmt in &expr.statements {
246                    match &arenas.statements[*stmt] {
247                        Statement::Let(StatementLet {
248                            pattern,
249                            expr,
250                            else_clause,
251                            stable_ptr: _,
252                        }) => {
253                            self.handle_expr(arenas, *expr, &mut usage);
254                            Self::handle_pattern(&arenas.patterns, *pattern, &mut usage);
255
256                            if let Some(else_clause) = else_clause {
257                                self.handle_expr(arenas, *else_clause, &mut usage);
258                            }
259                        }
260                        Statement::Expr(StatementExpr { expr, stable_ptr: _ }) => {
261                            self.handle_expr(arenas, *expr, &mut usage)
262                        }
263                        Statement::Continue(_) => (),
264                        Statement::Return(StatementReturn { expr_option, stable_ptr: _ }) => {
265                            usage.has_early_return = true;
266                            if let Some(expr) = expr_option {
267                                self.handle_expr(arenas, *expr, &mut usage)
268                            };
269                        }
270                        Statement::Break(StatementBreak { expr_option, stable_ptr: _ }) => {
271                            if let Some(expr) = expr_option {
272                                self.handle_expr(arenas, *expr, &mut usage)
273                            };
274                        }
275                        Statement::Item(_) => {}
276                    };
277                }
278                if let Some(expr_id) = expr.tail {
279                    self.handle_expr(arenas, expr_id, &mut usage)
280                }
281                usage.finalize_as_scope();
282                current.add_usage_and_changes(&usage);
283            }
284            Expr::Loop(ExprLoop { body, ty: _, stable_ptr: _ }) => {
285                let mut usage = Default::default();
286                self.handle_expr(arenas, *body, &mut usage);
287                current.add_usage_and_changes(&usage);
288                self.usages.insert(curr_expr_id, usage);
289            }
290            Expr::While(ExprWhile { condition, body, stable_ptr: _, ty: _ }) => {
291                let mut usage = Default::default();
292                match condition {
293                    Condition::BoolExpr(expr) => {
294                        self.handle_expr(arenas, *expr, &mut usage);
295                    }
296                    Condition::Let(expr, patterns) => {
297                        self.handle_expr(arenas, *expr, &mut usage);
298                        for pattern in patterns {
299                            Self::handle_pattern(&arenas.patterns, *pattern, &mut usage);
300                        }
301                    }
302                }
303                self.handle_expr(arenas, *body, &mut usage);
304                usage.finalize_as_scope();
305                current.add_usage_and_changes(&usage);
306
307                self.usages.insert(curr_expr_id, usage);
308            }
309            Expr::For(ExprFor {
310                expr_id,
311                into_iter_member_path,
312                pattern,
313                body,
314                stable_ptr: _,
315                into_iter: _,
316                next_function_id: _,
317                ty: _,
318            }) => {
319                self.handle_expr(arenas, *expr_id, current);
320                current
321                    .introductions
322                    .insert(extract_matches!(into_iter_member_path, ExprVarMemberPath::Var).var);
323                let mut usage: Usage<'_> = Default::default();
324                usage.usage.insert(into_iter_member_path.into(), into_iter_member_path.clone());
325                usage.changes.insert(into_iter_member_path.into(), into_iter_member_path.clone());
326                Self::handle_pattern(&arenas.patterns, *pattern, &mut usage);
327                self.handle_expr(arenas, *body, &mut usage);
328                usage.finalize_as_scope();
329                current.add_usage_and_changes(&usage);
330                self.usages.insert(curr_expr_id, usage);
331            }
332            Expr::ExprClosure(ExprClosure { body, params, stable_ptr: _, ty: _ }) => {
333                let usage = self.handle_closure(arenas, params, *body);
334
335                current.add_usage_and_changes(&usage);
336                self.usages.insert(curr_expr_id, usage);
337            }
338            Expr::FunctionCall(ExprFunctionCall {
339                args,
340                function: _,
341                coupon_arg: _,
342                stable_ptr: _,
343                ty: _,
344            }) => {
345                for arg in args {
346                    match arg {
347                        ExprFunctionCallArg::Reference(member_path) => {
348                            current.usage.insert(member_path.into(), member_path.clone());
349                            current.changes.insert(member_path.into(), member_path.clone());
350                        }
351                        ExprFunctionCallArg::Value(expr)
352                        | ExprFunctionCallArg::TempReference(expr) => {
353                            self.handle_expr(arenas, *expr, current)
354                        }
355                    }
356                }
357            }
358            Expr::Match(expr) => {
359                self.handle_expr(arenas, expr.matched_expr, current);
360                for arm in &expr.arms {
361                    for pattern in &arm.patterns {
362                        Self::handle_pattern(&arenas.patterns, *pattern, current);
363                    }
364                    self.handle_expr(arenas, arm.expression, current);
365                }
366            }
367            Expr::If(expr) => {
368                for condition in &expr.conditions {
369                    match condition {
370                        Condition::BoolExpr(expr) => {
371                            self.handle_expr(arenas, *expr, current);
372                        }
373                        Condition::Let(expr, patterns) => {
374                            self.handle_expr(arenas, *expr, current);
375                            for pattern in patterns {
376                                Self::handle_pattern(&arenas.patterns, *pattern, current);
377                            }
378                        }
379                    }
380                }
381
382                self.handle_expr(arenas, expr.if_block, current);
383                if let Some(expr) = expr.else_block {
384                    self.handle_expr(arenas, expr, current);
385                }
386            }
387            Expr::Var(expr) => {
388                current
389                    .usage
390                    .insert(MemberPath::Var(expr.var), ExprVarMemberPath::Var(expr.clone()));
391            }
392            Expr::Literal(_) | Expr::StringLiteral(_) => {}
393            Expr::MemberAccess(expr) => {
394                if let Some(member_path) = &expr.member_path {
395                    current.usage.insert(member_path.into(), member_path.clone());
396                } else {
397                    self.handle_expr(arenas, expr.expr, current);
398                }
399            }
400            Expr::StructCtor(expr) => {
401                for (expr_id, _) in &expr.members {
402                    self.handle_expr(arenas, *expr_id, current);
403                }
404                if let Some(base) = &expr.base_struct {
405                    self.handle_expr(arenas, *base, current);
406                }
407            }
408            Expr::EnumVariantCtor(expr) => self.handle_expr(arenas, expr.value_expr, current),
409            Expr::PropagateError(expr) => {
410                current.has_early_return = true;
411                self.handle_expr(arenas, expr.inner, current)
412            }
413            Expr::Constant(_) => {}
414            Expr::Missing(_) => {}
415        }
416    }
417
418    fn handle_pattern(arena: &PatternArena<'db>, pattern: PatternId, current: &mut Usage<'db>) {
419        let pattern = &arena[pattern];
420        match pattern {
421            Pattern::Literal(_) | Pattern::StringLiteral(_) => {}
422            Pattern::Variable(pattern) => {
423                current.introductions.insert(VarId::Local(pattern.var.id));
424            }
425            Pattern::Struct(pattern) => {
426                for (pattern, _) in &pattern.field_patterns {
427                    Self::handle_pattern(arena, *pattern, current);
428                }
429            }
430            Pattern::Tuple(pattern) => {
431                for pattern in &pattern.field_patterns {
432                    Self::handle_pattern(arena, *pattern, current);
433                }
434            }
435            Pattern::FixedSizeArray(pattern) => {
436                for pattern in &pattern.elements_patterns {
437                    Self::handle_pattern(arena, *pattern, current);
438                }
439            }
440            Pattern::EnumVariant(pattern) => {
441                if let Some(inner_pattern) = &pattern.inner_pattern {
442                    Self::handle_pattern(arena, *inner_pattern, current);
443                }
444            }
445            Pattern::Otherwise(_) => {}
446            Pattern::Missing(_) => {}
447        }
448    }
449}