Prototype dead code elimination

This commit is contained in:
d0cd 2023-02-21 10:18:29 -08:00
parent 707cd3cfce
commit ec91d5655b
5 changed files with 340 additions and 5 deletions

View File

@ -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,
}
}
}

View File

@ -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())
}
}

View File

@ -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,
}
}
}

View 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())
}
}

View File

@ -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>;