leo_passes/dead_code_elimination/visitor.rs
1// Copyright (C) 2019-2025 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Leo library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17use crate::CompilerState;
18
19use leo_ast::{BinaryOperation, Expression, Node as _, Type, TypeReconstructor, UnaryOperation};
20use leo_span::{Symbol, sym};
21
22use indexmap::IndexSet;
23
24pub struct DeadCodeEliminatingVisitor<'a> {
25 pub state: &'a mut CompilerState,
26
27 /// The set of used variables in the current function body.
28 pub used_variables: IndexSet<Symbol>,
29
30 /// The name of the program currently being processed.
31 pub program_name: Symbol,
32
33 /// How many statements were in the AST before DCE?
34 pub statements_before: u32,
35
36 /// How many statements were in the AST after DCE?
37 pub statements_after: u32,
38}
39
40impl DeadCodeEliminatingVisitor<'_> {
41 pub fn side_effect_free(&self, expr: &Expression) -> bool {
42 use Expression::*;
43
44 let sef = |expr| self.side_effect_free(expr);
45
46 match expr {
47 ArrayAccess(array) => sef(&array.array) && sef(&array.index),
48 MemberAccess(mem) => sef(&mem.inner),
49 Repeat(repeat) => sef(&repeat.expr) && sef(&repeat.count),
50 TupleAccess(tuple) => sef(&tuple.tuple),
51 Array(array) => array.elements.iter().all(sef),
52 AssociatedConstant(_) => true,
53 AssociatedFunction(func) => {
54 // CheatCode, Mapping, and Future operations obviously have side effects.
55 // Pedersen64 and Pedersen128 operations can fail for large inputs.
56 func.arguments.iter().all(sef)
57 && !matches!(
58 func.variant.name,
59 sym::CheatCode | sym::Mapping | sym::Future | sym::Pedersen64 | sym::Pedersen128
60 )
61 }
62 Binary(bin) => {
63 use BinaryOperation::*;
64 let halting_op = match bin.op {
65 // These can halt for any of their operand types.
66 Div | Mod | Rem | Shl | Shr => true,
67 // These can only halt for integers.
68 Add | Mul | Pow => {
69 matches!(
70 self.state.type_table.get(&expr.id()).expect("Types should be assigned."),
71 Type::Integer(..)
72 )
73 }
74 _ => false,
75 };
76 !halting_op && sef(&bin.left) && sef(&bin.right)
77 }
78 Call(..) => {
79 // Since calls may halt, be conservative and don't consider any call side effect free.
80 false
81 }
82 Cast(..) => {
83 // At least for now, be conservative and don't consider any cast side effect free.
84 // Of course for some combinations of types, casts will never halt.
85 false
86 }
87 Struct(struct_) => struct_.members.iter().all(|mem| mem.expression.as_ref().is_none_or(sef)),
88 Ternary(tern) => [&tern.condition, &tern.if_true, &tern.if_false].into_iter().all(sef),
89 Tuple(tuple) => tuple.elements.iter().all(sef),
90 Unary(un) => {
91 use UnaryOperation::*;
92 let halting_op = match un.op {
93 // These can halt for any of their operand types.
94 Abs | Inverse | SquareRoot => true,
95 // Negate can only halt for integers.
96 Negate => {
97 matches!(
98 self.state.type_table.get(&expr.id()).expect("Type should be assigned."),
99 Type::Integer(..)
100 )
101 }
102 _ => false,
103 };
104 !halting_op && sef(&un.receiver)
105 }
106 Err(_) => false,
107 Identifier(_) | Literal(_) | Locator(_) | Unit(_) => true,
108 }
109 }
110}
111
112impl TypeReconstructor for DeadCodeEliminatingVisitor<'_> {}