mirror of
https://github.com/AleoHQ/leo.git
synced 2024-11-11 04:49:15 +03:00
Prototype dead code elimination
This commit is contained in:
parent
707cd3cfce
commit
ec91d5655b
@ -18,16 +18,21 @@ use leo_span::Symbol;
|
||||
|
||||
use indexmap::IndexSet;
|
||||
|
||||
pub struct DeadCodeEliminator<'a> {
|
||||
#[derive(Default)]
|
||||
pub struct DeadCodeEliminator {
|
||||
/// The set of used variables in the current function body.
|
||||
pub(crate) used_variables: IndexSet<Symbol>,
|
||||
/// Whether or not the variables are necessary.
|
||||
pub(crate) is_necessary: bool,
|
||||
}
|
||||
|
||||
impl<'a> DeadCodeEliminator<'a> {
|
||||
impl DeadCodeEliminator {
|
||||
/// Initializes a new `DeadCodeEliminator`.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
used_variables: Default::default(),
|
||||
is_necessary: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,64 @@
|
||||
// Copyright (C) 2019-2023 Aleo Systems Inc.
|
||||
// This file is part of the Leo library.
|
||||
|
||||
// The Leo library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// The Leo library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{DeadCodeEliminator, FunctionInliner, Replacer};
|
||||
|
||||
use leo_ast::{
|
||||
AccessExpression, CallExpression, Expression, ExpressionReconstructor, Identifier, ReturnStatement, Statement,
|
||||
StatementReconstructor, StructExpression, StructVariableInitializer, UnitExpression, Variant,
|
||||
};
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use itertools::Itertools;
|
||||
|
||||
impl ExpressionReconstructor for DeadCodeEliminator {
|
||||
type AdditionalOutput = ();
|
||||
|
||||
/// Reconstruct the components of the struct init expression.
|
||||
/// This is necessary since the reconstructor does not explicitly visit each component of the expression.
|
||||
fn reconstruct_struct_init(&mut self, input: StructExpression) -> (Expression, Self::AdditionalOutput) {
|
||||
(
|
||||
Expression::Struct(StructExpression {
|
||||
name: input.name,
|
||||
// Reconstruct each of the struct members.
|
||||
members: input
|
||||
.members
|
||||
.into_iter()
|
||||
.map(|member| StructVariableInitializer {
|
||||
identifier: member.identifier,
|
||||
expression: match member.expression {
|
||||
Some(expression) => Some(self.reconstruct_expression(expression).0),
|
||||
None => unreachable!("Static single assignment ensures that the expression always exists."),
|
||||
},
|
||||
})
|
||||
.collect(),
|
||||
span: input.span,
|
||||
}),
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Marks identifiers as used.
|
||||
/// This is necessary to determine which statements can be eliminated from the program.
|
||||
fn reconstruct_identifier(&mut self, input: Identifier) -> (Expression, Self::AdditionalOutput) {
|
||||
// Add the identifier to `self.used_variables`.
|
||||
if self.is_necessary {
|
||||
self.used_variables.insert(input.name);
|
||||
}
|
||||
// Return the identifier as is.
|
||||
(Expression::Identifier(input), Default::default())
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
// Copyright (C) 2019-2023 Aleo Systems Inc.
|
||||
// This file is part of the Leo library.
|
||||
|
||||
// The Leo library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// The Leo library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::DeadCodeEliminator;
|
||||
|
||||
use leo_ast::{Finalize, Function, ProgramReconstructor, ProgramScope, StatementReconstructor};
|
||||
|
||||
impl ProgramReconstructor for DeadCodeEliminator {
|
||||
fn reconstruct_function(&mut self, input: Function) -> Function {
|
||||
// Reset the state of the dead code eliminator.
|
||||
self.used_variables.clear();
|
||||
self.is_necessary = false;
|
||||
|
||||
// Traverse the function body.
|
||||
let block = self.reconstruct_block(input.block).0;
|
||||
|
||||
// Reconstruct the finalize block, if it exists.
|
||||
let finalize = input.finalize.map(|finalize| {
|
||||
// Reset the state of the dead code eliminator.
|
||||
self.used_variables.clear();
|
||||
self.is_necessary = false;
|
||||
|
||||
// Traverse the finalize block.
|
||||
let block = self.reconstruct_block(finalize.block).0;
|
||||
|
||||
Finalize {
|
||||
identifier: finalize.identifier,
|
||||
input: finalize.input,
|
||||
output: finalize.output,
|
||||
output_type: finalize.output_type,
|
||||
block,
|
||||
span: finalize.span,
|
||||
}
|
||||
});
|
||||
|
||||
Function {
|
||||
annotations: input.annotations,
|
||||
variant: input.variant,
|
||||
identifier: input.identifier,
|
||||
input: input.input,
|
||||
output: input.output,
|
||||
output_type: input.output_type,
|
||||
block,
|
||||
finalize,
|
||||
span: input.span,
|
||||
}
|
||||
}
|
||||
}
|
199
compiler/passes/src/dead_code_elimination/eliminate_statement.rs
Normal file
199
compiler/passes/src/dead_code_elimination/eliminate_statement.rs
Normal file
@ -0,0 +1,199 @@
|
||||
// Copyright (C) 2019-2023 Aleo Systems Inc.
|
||||
// This file is part of the Leo library.
|
||||
|
||||
// The Leo library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// The Leo library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{DeadCodeEliminator, FunctionInliner};
|
||||
|
||||
use leo_ast::{
|
||||
AssertStatement, AssertVariant, AssignStatement, Block, ConditionalStatement, ConsoleStatement, DecrementStatement,
|
||||
DefinitionStatement, Expression, ExpressionReconstructor, ExpressionStatement, IncrementStatement,
|
||||
IterationStatement, ReturnStatement, Statement, StatementReconstructor,
|
||||
};
|
||||
|
||||
impl StatementReconstructor for DeadCodeEliminator {
|
||||
fn reconstruct_assert(&mut self, input: AssertStatement) -> (Statement, Self::AdditionalOutput) {
|
||||
// Set the `is_necessary` flag.
|
||||
self.is_necessary = true;
|
||||
|
||||
// Visit the statement.
|
||||
let statement = Statement::Assert(AssertStatement {
|
||||
variant: match input.variant {
|
||||
AssertVariant::Assert(expr) => AssertVariant::Assert(self.reconstruct_expression(expr).0),
|
||||
AssertVariant::AssertEq(left, right) => AssertVariant::AssertEq(
|
||||
self.reconstruct_expression(left).0,
|
||||
self.reconstruct_expression(right).0,
|
||||
),
|
||||
AssertVariant::AssertNeq(left, right) => AssertVariant::AssertNeq(
|
||||
self.reconstruct_expression(left).0,
|
||||
self.reconstruct_expression(right).0,
|
||||
),
|
||||
},
|
||||
span: input.span,
|
||||
});
|
||||
|
||||
// Unset the `is_necessary` flag.
|
||||
self.is_necessary = false;
|
||||
|
||||
(statement, Default::default())
|
||||
}
|
||||
|
||||
/// Reconstruct an assignment statement by eliminating any dead code.
|
||||
fn reconstruct_assign(&mut self, input: AssignStatement) -> (Statement, Self::AdditionalOutput) {
|
||||
// Check the lhs of the assignment to see any of variables are used.
|
||||
let lhs_is_used = match &input.place {
|
||||
Expression::Identifier(identifier) => self.used_variables.contains(&identifier.name),
|
||||
Expression::Tuple(tuple_expression) => tuple_expression
|
||||
.elements
|
||||
.iter()
|
||||
.map(|element| match element {
|
||||
Expression::Identifier(identifier) => identifier.name,
|
||||
_ => unreachable!(
|
||||
"The previous compiler passes guarantee the tuple elements on the lhs are identifiers."
|
||||
),
|
||||
})
|
||||
.any(|symbol| self.used_variables.contains(&symbol)),
|
||||
_ => unreachable!(
|
||||
"The previous compiler passes guarantee that `place` is either an identifier or tuple of identifiers."
|
||||
),
|
||||
};
|
||||
|
||||
match lhs_is_used {
|
||||
// If the lhs is used, then we return the original statement.
|
||||
true => (Statement::Assign(Box::new(input)), Default::default()),
|
||||
// Otherwise, we can eliminate it.
|
||||
false => (Statement::dummy(Default::default()), Default::default()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reconstructs the statements inside a basic block, eliminating any dead code.
|
||||
fn reconstruct_block(&mut self, block: Block) -> (Block, Self::AdditionalOutput) {
|
||||
// Reconstruct each of the statements in reverse.
|
||||
let statements = block
|
||||
.statements
|
||||
.into_iter()
|
||||
.rev()
|
||||
.map(|statement| self.reconstruct_statement(statement).0)
|
||||
.rev()
|
||||
.collect();
|
||||
|
||||
(
|
||||
Block {
|
||||
statements,
|
||||
span: block.span,
|
||||
},
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Flattening removes conditional statements from the program.
|
||||
fn reconstruct_conditional(&mut self, _: ConditionalStatement) -> (Statement, Self::AdditionalOutput) {
|
||||
unreachable!("`ConditionalStatement`s should not be in the AST at this phase of compilation.")
|
||||
}
|
||||
|
||||
/// Parsing guarantees that console statements are not present in the program.
|
||||
fn reconstruct_console(&mut self, _: ConsoleStatement) -> (Statement, Self::AdditionalOutput) {
|
||||
unreachable!("`ConsoleStatement`s should not be in the AST at this phase of compilation.")
|
||||
}
|
||||
|
||||
fn reconstruct_decrement(&mut self, input: DecrementStatement) -> (Statement, Self::AdditionalOutput) {
|
||||
// Set the `is_necessary` flag.
|
||||
self.is_necessary = true;
|
||||
|
||||
// Visit the statement.
|
||||
let statement = Statement::Decrement(DecrementStatement {
|
||||
mapping: input.mapping,
|
||||
index: self.reconstruct_expression(input.index).0,
|
||||
amount: self.reconstruct_expression(input.amount).0,
|
||||
span: input.span,
|
||||
});
|
||||
|
||||
// Unset the `is_necessary` flag.
|
||||
self.is_necessary = false;
|
||||
|
||||
(statement, Default::default())
|
||||
}
|
||||
|
||||
/// Static single assignment replaces definition statements with assignment statements.
|
||||
fn reconstruct_definition(&mut self, _: DefinitionStatement) -> (Statement, Self::AdditionalOutput) {
|
||||
unreachable!("`DefinitionStatement`s should not exist in the AST at this phase of compilation.")
|
||||
}
|
||||
|
||||
/// Reconstructs expression statements by eliminating any dead code.
|
||||
fn reconstruct_expression_statement(&mut self, input: ExpressionStatement) -> (Statement, Self::AdditionalOutput) {
|
||||
match input.expression {
|
||||
Expression::Call(expression) => {
|
||||
// Set the `is_necessary` flag.
|
||||
self.is_necessary = true;
|
||||
|
||||
// Visit the expression.
|
||||
let statement = Statement::Expression(ExpressionStatement {
|
||||
expression: self.reconstruct_call(expression).0,
|
||||
span: input.span,
|
||||
});
|
||||
|
||||
// Unset the `is_necessary` flag.
|
||||
self.is_necessary = false;
|
||||
|
||||
(statement, Default::default())
|
||||
}
|
||||
_ => unreachable!("Type checking guarantees that expression statements are always function calls."),
|
||||
}
|
||||
}
|
||||
|
||||
fn reconstruct_increment(&mut self, input: IncrementStatement) -> (Statement, Self::AdditionalOutput) {
|
||||
// Set the `is_necessary` flag.
|
||||
self.is_necessary = true;
|
||||
|
||||
// Visit the statement.
|
||||
let statement = Statement::Increment(IncrementStatement {
|
||||
mapping: input.mapping,
|
||||
index: self.reconstruct_expression(input.index).0,
|
||||
amount: self.reconstruct_expression(input.amount).0,
|
||||
span: input.span,
|
||||
});
|
||||
|
||||
// Unset the `is_necessary` flag.
|
||||
self.is_necessary = false;
|
||||
|
||||
(statement, Default::default())
|
||||
}
|
||||
|
||||
/// Loop unrolling unrolls and removes iteration statements from the program.
|
||||
fn reconstruct_iteration(&mut self, _: IterationStatement) -> (Statement, Self::AdditionalOutput) {
|
||||
unreachable!("`IterationStatement`s should not be in the AST at this phase of compilation.");
|
||||
}
|
||||
|
||||
fn reconstruct_return(&mut self, input: ReturnStatement) -> (Statement, Self::AdditionalOutput) {
|
||||
// Set the `is_necessary` flag.
|
||||
self.is_necessary = true;
|
||||
|
||||
// Visit the statement.
|
||||
let statement = Statement::Return(ReturnStatement {
|
||||
expression: self.reconstruct_expression(input.expression).0,
|
||||
finalize_arguments: input.finalize_arguments.map(|arguments| {
|
||||
arguments
|
||||
.into_iter()
|
||||
.map(|argument| self.reconstruct_expression(argument).0)
|
||||
.collect()
|
||||
}),
|
||||
span: input.span,
|
||||
});
|
||||
|
||||
// Unset the `is_necessary` flag.
|
||||
self.is_necessary = false;
|
||||
|
||||
(statement, Default::default())
|
||||
}
|
||||
}
|
@ -14,7 +14,10 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! The Dead Code Elimination pass traverses the AST and eliminates unused code.
|
||||
//! The Dead Code Elimination pass traverses the AST and eliminates unused code,
|
||||
//! specifically assignment statements, within the boundary of `transition`s and `function`s.
|
||||
//! The pass is run after the Function Inlining pass.
|
||||
//!
|
||||
//! See https://en.wikipedia.org/wiki/Dead-code_elimination for more information.
|
||||
//!
|
||||
//! Consider the following flattened Leo code.
|
||||
@ -41,7 +44,10 @@
|
||||
//! return value$3;
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Note this pass relies on the following invariants:
|
||||
//! - No shadowing for all variables, struct names, function names, etc.
|
||||
//! - Unique variable names (provided by SSA)
|
||||
//! - Flattened code (provided by the flattening pass)
|
||||
|
||||
mod eliminate_expression;
|
||||
|
||||
@ -57,7 +63,7 @@ use crate::Pass;
|
||||
use leo_ast::{Ast, ProgramReconstructor};
|
||||
use leo_errors::Result;
|
||||
|
||||
impl<'a> Pass for FunctionInliner<'a> {
|
||||
impl Pass for DeadCodeEliminator {
|
||||
type Input = Ast;
|
||||
type Output = Result<Ast>;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user