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, 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 Async(_) => false,
63 Binary(bin) => {
64 use BinaryOperation::*;
65 let halting_op = match bin.op {
66 // These can halt for any of their operand types.
67 Div | Mod | Rem | Shl | Shr => true,
68 // These can only halt for integers.
69 Add | Mul | Pow => {
70 matches!(
71 self.state.type_table.get(&expr.id()).expect("Types should be assigned."),
72 Type::Integer(..)
73 )
74 }
75 _ => false,
76 };
77 !halting_op && sef(&bin.left) && sef(&bin.right)
78 }
79 Call(..) => {
80 // Since calls may halt, be conservative and don't consider any call side effect free.
81 false
82 }
83 Cast(..) => {
84 // At least for now, be conservative and don't consider any cast side effect free.
85 // Of course for some combinations of types, casts will never halt.
86 false
87 }
88 Struct(struct_) => struct_.members.iter().all(|mem| mem.expression.as_ref().is_none_or(sef)),
89 Ternary(tern) => [&tern.condition, &tern.if_true, &tern.if_false].into_iter().all(sef),
90 Tuple(tuple) => tuple.elements.iter().all(sef),
91 Unary(un) => {
92 use UnaryOperation::*;
93 let halting_op = match un.op {
94 // These can halt for any of their operand types.
95 Abs | Inverse | SquareRoot => true,
96 // Negate can only halt for integers.
97 Negate => {
98 matches!(
99 self.state.type_table.get(&expr.id()).expect("Type should be assigned."),
100 Type::Integer(..)
101 )
102 }
103 _ => false,
104 };
105 !halting_op && sef(&un.receiver)
106 }
107 Err(_) => false,
108 Path(_) | Literal(_) | Locator(_) | Unit(_) => true,
109 }
110 }
111}