leo_passes/dead_code_elimination/
ast.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 super::DeadCodeEliminatingVisitor;
18
19use leo_ast::*;
20
21impl AstReconstructor for DeadCodeEliminatingVisitor<'_> {
22    type AdditionalInput = ();
23    type AdditionalOutput = ();
24
25    /* Expressions */
26    // Use and reconstruct a path.
27    fn reconstruct_path(&mut self, input: Path, _additional: &()) -> (Expression, Self::AdditionalOutput) {
28        // At this stage, all `Path`s should refer to local variables or mappings, so it's safe to
29        // refer to them using the last symbol in the path.
30        self.used_variables.insert(input.identifier().name);
31        (input.into(), Default::default())
32    }
33
34    // We need to make sure we hit identifiers, so do our own traversal
35    // rather than relying on the default.
36    fn reconstruct_struct_init(
37        &mut self,
38        mut input: leo_ast::StructExpression,
39        _additional: &(),
40    ) -> (Expression, Self::AdditionalOutput) {
41        for member in input.members.iter_mut() {
42            if let Some(expr) = std::mem::take(&mut member.expression) {
43                member.expression = Some(self.reconstruct_expression(expr, &()).0);
44            } else {
45                // We're not actually going to modify it.
46                self.reconstruct_path(Path::from(member.identifier).into_absolute(), &());
47            }
48        }
49
50        (input.into(), Default::default())
51    }
52
53    /* Statements */
54    /// Reconstruct an assignment statement by eliminating any dead code.
55    fn reconstruct_assign(&mut self, _input: AssignStatement) -> (Statement, Self::AdditionalOutput) {
56        panic!("`AssignStatement`s should not exist in the AST at this phase of compilation.")
57    }
58
59    /// Reconstructs the statements inside a basic block, eliminating any dead code.
60    fn reconstruct_block(&mut self, block: Block) -> (Block, Self::AdditionalOutput) {
61        // Don't count empty blocks as statements, as that would be a bit misleading to the user as
62        // to how much the code is being transformed.
63        self.statements_before += block.statements.iter().filter(|stmt| !stmt.is_empty()).count() as u32;
64
65        // Reconstruct each of the statements in reverse.
66        let mut statements: Vec<Statement> =
67            block.statements.into_iter().rev().map(|statement| self.reconstruct_statement(statement).0).collect();
68
69        statements.retain(|stmt| !stmt.is_empty());
70
71        // Reverse the direction of `statements`.
72        statements.reverse();
73
74        self.statements_after += statements.len() as u32;
75
76        (Block { statements, span: block.span, id: block.id }, Default::default())
77    }
78
79    /// Static single assignment replaces definition statements with assignment statements.
80    fn reconstruct_definition(&mut self, mut input: DefinitionStatement) -> (Statement, Self::AdditionalOutput) {
81        // Check the lhs of the definition to see any of variables are used.
82        let lhs_is_used = match &input.place {
83            DefinitionPlace::Single(identifier) => self.used_variables.contains(&identifier.name),
84            DefinitionPlace::Multiple(identifiers) => {
85                identifiers.iter().any(|identifier| self.used_variables.contains(&identifier.name))
86            }
87        };
88
89        if !lhs_is_used && self.side_effect_free(&input.value) {
90            // We can eliminate this statement.
91            (Statement::dummy(), Default::default())
92        } else {
93            // We still need it.
94            input.value = self.reconstruct_expression(input.value, &()).0;
95            (input.into(), Default::default())
96        }
97    }
98
99    /// Loop unrolling unrolls and removes iteration statements from the program.
100    fn reconstruct_iteration(&mut self, _: IterationStatement) -> (Statement, Self::AdditionalOutput) {
101        panic!("`IterationStatement`s should not be in the AST at this phase of compilation.");
102    }
103
104    fn reconstruct_expression_statement(&mut self, input: ExpressionStatement) -> (Statement, Self::AdditionalOutput) {
105        if self.side_effect_free(&input.expression) {
106            (Statement::dummy(), Default::default())
107        } else {
108            (
109                ExpressionStatement { expression: self.reconstruct_expression(input.expression, &()).0, ..input }
110                    .into(),
111                Default::default(),
112            )
113        }
114    }
115}