Skip to content

Commit 2867c59

Browse files
committed
Very simple revamp of dead code elimination.
Enable DCE by default. Eliminates useless associated function calls. Also threw in just a few changes to Display implementations for some nodes. Fixes #28504
1 parent d2677de commit 2867c59

File tree

681 files changed

+851
-995
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

681 files changed

+851
-995
lines changed

compiler/ast/src/common/identifier.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,12 @@ impl Identifier {
6767

6868
impl fmt::Display for Identifier {
6969
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
70-
write!(f, "{}", self.name)
70+
self.name.fmt(f)
7171
}
7272
}
7373
impl fmt::Debug for Identifier {
7474
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
75-
write!(f, "{}", self.name)
75+
self.name.fmt(f)
7676
}
7777
}
7878

compiler/ast/src/expressions/call.rs

+3-9
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
use super::*;
1818
use leo_span::Symbol;
1919

20+
use itertools::Itertools as _;
21+
2022
/// A function call expression, e.g.`foo(args)` or `Foo::bar(args)`.
2123
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2224
pub struct CallExpression {
@@ -35,15 +37,7 @@ pub struct CallExpression {
3537

3638
impl fmt::Display for CallExpression {
3739
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
38-
write!(f, "{}(", self.function)?;
39-
40-
for (i, param) in self.arguments.iter().enumerate() {
41-
write!(f, "{param}")?;
42-
if i < self.arguments.len() - 1 {
43-
write!(f, ", ")?;
44-
}
45-
}
46-
write!(f, ")")
40+
write!(f, "{}({})", self.function, self.arguments.iter().format(", "))
4741
}
4842
}
4943

compiler/ast/src/expressions/mod.rs

+28-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
1616

1717
use crate::{Identifier, Node, NodeID};
18-
use leo_span::Span;
18+
use leo_span::{Span, sym};
1919

2020
use serde::{Deserialize, Serialize};
2121
use std::fmt;
@@ -211,6 +211,33 @@ pub(crate) enum Associativity {
211211
}
212212

213213
impl Expression {
214+
pub fn side_effect_free(&self) -> bool {
215+
use Expression::*;
216+
match self {
217+
Access(AccessExpression::Array(array)) => array.array.side_effect_free() && array.index.side_effect_free(),
218+
Access(AccessExpression::AssociatedConstant(_)) => true,
219+
Access(AccessExpression::AssociatedFunction(func)) => {
220+
func.arguments.iter().all(|expr| expr.side_effect_free())
221+
&& !matches!(func.variant.name, sym::CheatCode | sym::Mapping | sym::Future)
222+
}
223+
Access(AccessExpression::Member(mem)) => mem.inner.side_effect_free(),
224+
Access(AccessExpression::Tuple(tuple)) => tuple.tuple.side_effect_free(),
225+
Array(array) => array.elements.iter().all(|expr| expr.side_effect_free()),
226+
Binary(bin) => bin.left.side_effect_free() && bin.right.side_effect_free(),
227+
Cast(cast) => cast.expression.side_effect_free(),
228+
Struct(struct_) => {
229+
struct_.members.iter().all(|mem| mem.expression.as_ref().map_or(true, |expr| expr.side_effect_free()))
230+
}
231+
Ternary(tern) => {
232+
[&tern.condition, &tern.if_true, &tern.if_false].into_iter().all(|expr| expr.side_effect_free())
233+
}
234+
Tuple(tuple) => tuple.elements.iter().all(|expr| expr.side_effect_free()),
235+
Unary(un) => un.receiver.side_effect_free(),
236+
Call(_) | Err(_) => false,
237+
Identifier(_) | Literal(_) | Locator(_) | Unit(_) => true,
238+
}
239+
}
240+
214241
pub(crate) fn precedence(&self) -> u32 {
215242
use Expression::*;
216243
match self {

compiler/ast/src/expressions/tuple.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
use super::*;
1818

19+
use itertools::Itertools as _;
20+
1921
// TODO: Consider a restricted interface for constructing a tuple expression.
2022

2123
/// A tuple expression, e.g., `(foo, false, 42)`.
@@ -32,7 +34,11 @@ pub struct TupleExpression {
3234

3335
impl fmt::Display for TupleExpression {
3436
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
35-
write!(f, "({})", self.elements.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(","))
37+
if self.elements.len() == 1 {
38+
write!(f, "({},)", self.elements[0])
39+
} else {
40+
write!(f, "({})", self.elements.iter().join(","))
41+
}
3642
}
3743
}
3844

compiler/ast/src/passes/reconstructor.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,7 @@ pub trait ExpressionReconstructor {
172172
.into_iter()
173173
.map(|member| StructVariableInitializer {
174174
identifier: member.identifier,
175-
expression: match member.expression {
176-
Some(expression) => Some(self.reconstruct_expression(expression).0),
177-
None => Some(self.reconstruct_expression(Expression::Identifier(member.identifier)).0),
178-
},
175+
expression: member.expression.map(|expr| self.reconstruct_expression(expr).0),
179176
span: member.span,
180177
id: member.id,
181178
})

compiler/ast/src/statement/console/console_statement.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ pub struct ConsoleStatement {
3333

3434
impl fmt::Display for ConsoleStatement {
3535
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
36-
write!(f, "console.{};", self.function)
36+
write!(f, "console.{}", self.function)
3737
}
3838
}
3939

4040
impl fmt::Debug for ConsoleStatement {
4141
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
42-
write!(f, "console.{};", self.function)
42+
write!(f, "console.{}", self.function)
4343
}
4444
}
4545

compiler/ast/src/statement/expression.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub struct ExpressionStatement {
3333

3434
impl fmt::Display for ExpressionStatement {
3535
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
36-
write!(f, "{};", self.expression)
36+
self.expression.fmt(f)
3737
}
3838
}
3939

compiler/ast/src/statement/mod.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,19 @@ pub enum Statement {
7878

7979
impl Statement {
8080
/// Returns a dummy statement made from an empty block `{}`.
81-
pub fn dummy(span: Span, id: NodeID) -> Self {
82-
Self::Block(Block { statements: Vec::new(), span, id })
81+
pub fn dummy() -> Self {
82+
Self::Block(Block { statements: Vec::new(), span: Default::default(), id: Default::default() })
8383
}
8484

8585
pub(crate) fn semicolon(&self) -> &'static str {
8686
use Statement::*;
8787

8888
if matches!(self, Block(..) | Conditional(..) | Iteration(..)) { "" } else { ";" }
8989
}
90+
91+
pub fn is_empty(self: &Statement) -> bool {
92+
matches!(self, Statement::Block(block) if block.statements.is_empty())
93+
}
9094
}
9195

9296
impl fmt::Display for Statement {

compiler/compiler/src/compiler.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ impl<'a, N: Network> Compiler<'a, N> {
309309
/// Runs the dead code elimination pass.
310310
pub fn dead_code_elimination_pass(&mut self) -> Result<()> {
311311
if self.compiler_options.build.dce_enabled {
312-
self.ast = DeadCodeEliminator::do_pass((std::mem::take(&mut self.ast), &self.node_builder))?;
312+
self.ast = DeadCodeEliminator::do_pass((std::mem::take(&mut self.ast),))?;
313313
}
314314

315315
if self.compiler_options.output.dce_ast {

compiler/parser/src/test.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,9 @@
1616

1717
use crate::{ParserContext, SpannedToken, tokenizer};
1818

19-
use leo_ast::{NodeBuilder, NodeID, Statement};
19+
use leo_ast::{NodeBuilder, Statement};
2020
use leo_errors::{LeoError, emitter::Handler};
2121
use leo_span::{
22-
Span,
2322
source_map::FileName,
2423
symbol::{SessionGlobals, create_session_if_not_set_then},
2524
};
@@ -120,7 +119,7 @@ impl Namespace for ParseStatementNamespace {
120119
create_session_if_not_set_then(|s| {
121120
let tokenizer = tokenize(test, s)?;
122121
if all_are_comments(&tokenizer) {
123-
return Ok(toml_or_fail(Statement::dummy(Span::default(), NodeID::default())));
122+
return Ok(toml_or_fail(Statement::dummy()));
124123
}
125124
with_handler(tokenizer, |p| p.parse_statement()).map(toml_or_fail)
126125
})

compiler/passes/src/dead_code_elimination/dead_code_eliminator.rs

+5-11
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,19 @@
1414
// You should have received a copy of the GNU General Public License
1515
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
1616

17-
use leo_ast::NodeBuilder;
1817
use leo_span::Symbol;
1918

2019
use indexmap::IndexSet;
2120

22-
pub struct DeadCodeEliminator<'a> {
23-
/// A counter to generate unique node IDs.
24-
pub(crate) node_builder: &'a NodeBuilder,
21+
#[derive(Debug, Default)]
22+
pub struct DeadCodeEliminator {
2523
/// The set of used variables in the current function body.
2624
pub(crate) used_variables: IndexSet<Symbol>,
27-
/// Whether or not the variables are necessary.
28-
pub(crate) is_necessary: bool,
29-
/// Whether or not we are currently traversing an async function.
30-
pub(crate) is_async: bool,
3125
}
3226

33-
impl<'a> DeadCodeEliminator<'a> {
27+
impl DeadCodeEliminator {
3428
/// Initializes a new `DeadCodeEliminator`.
35-
pub fn new(node_builder: &'a NodeBuilder) -> Self {
36-
Self { node_builder, used_variables: Default::default(), is_necessary: false, is_async: false }
29+
pub(crate) fn new() -> Self {
30+
Default::default()
3731
}
3832
}

compiler/passes/src/dead_code_elimination/eliminate_expression.rs

+4-73
Original file line numberDiff line numberDiff line change
@@ -16,83 +16,14 @@
1616

1717
use crate::DeadCodeEliminator;
1818

19-
use leo_ast::{
20-
AccessExpression,
21-
AssociatedFunction,
22-
Expression,
23-
ExpressionReconstructor,
24-
Identifier,
25-
StructExpression,
26-
StructVariableInitializer,
27-
};
28-
use leo_span::sym;
19+
use leo_ast::{Expression, ExpressionReconstructor, Identifier};
2920

30-
impl ExpressionReconstructor for DeadCodeEliminator<'_> {
21+
impl ExpressionReconstructor for DeadCodeEliminator {
3122
type AdditionalOutput = ();
3223

33-
/// Reconstructs the associated function access expression.
34-
fn reconstruct_associated_function(&mut self, input: AssociatedFunction) -> (Expression, Self::AdditionalOutput) {
35-
// If the associated function manipulates a mapping, or a cheat code, mark the statement as necessary.
36-
match (&input.variant.name, input.name.name) {
37-
(&sym::Mapping, sym::remove)
38-
| (&sym::Mapping, sym::set)
39-
| (&sym::Future, sym::Await)
40-
| (&sym::CheatCode, _) => {
41-
self.is_necessary = true;
42-
}
43-
_ => {}
44-
};
45-
// Reconstruct the access expression.
46-
let result = (
47-
Expression::Access(AccessExpression::AssociatedFunction(AssociatedFunction {
48-
variant: input.variant,
49-
name: input.name,
50-
arguments: input.arguments.into_iter().map(|arg| self.reconstruct_expression(arg).0).collect(),
51-
span: input.span,
52-
id: input.id,
53-
})),
54-
Default::default(),
55-
);
56-
// Unset `self.is_necessary`.
57-
self.is_necessary = false;
58-
result
59-
}
60-
61-
/// Reconstruct the components of the struct init expression.
62-
/// This is necessary since the reconstructor does not explicitly visit each component of the expression.
63-
fn reconstruct_struct_init(&mut self, input: StructExpression) -> (Expression, Self::AdditionalOutput) {
64-
(
65-
Expression::Struct(StructExpression {
66-
name: input.name,
67-
// Reconstruct each of the struct members.
68-
members: input
69-
.members
70-
.into_iter()
71-
.map(|member| StructVariableInitializer {
72-
identifier: member.identifier,
73-
expression: match member.expression {
74-
Some(expression) => Some(self.reconstruct_expression(expression).0),
75-
None => unreachable!("Static single assignment ensures that the expression always exists."),
76-
},
77-
span: member.span,
78-
id: member.id,
79-
})
80-
.collect(),
81-
span: input.span,
82-
id: input.id,
83-
}),
84-
Default::default(),
85-
)
86-
}
87-
88-
/// Marks identifiers as used.
89-
/// This is necessary to determine which statements can be eliminated from the program.
24+
// Use and reconstruct an identifier that may not necessarily be used.
9025
fn reconstruct_identifier(&mut self, input: Identifier) -> (Expression, Self::AdditionalOutput) {
91-
// Add the identifier to `self.used_variables`.
92-
if self.is_necessary {
93-
self.used_variables.insert(input.name);
94-
}
95-
// Return the identifier as is.
26+
self.used_variables.insert(input.name);
9627
(Expression::Identifier(input), Default::default())
9728
}
9829
}

compiler/passes/src/dead_code_elimination/eliminate_program.rs

+5-17
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,14 @@ use crate::DeadCodeEliminator;
1818

1919
use leo_ast::{Function, ProgramReconstructor, StatementReconstructor};
2020

21-
impl ProgramReconstructor for DeadCodeEliminator<'_> {
22-
fn reconstruct_function(&mut self, input: Function) -> Function {
21+
impl ProgramReconstructor for DeadCodeEliminator {
22+
fn reconstruct_function(&mut self, mut input: Function) -> Function {
2323
// Reset the state of the dead code eliminator.
2424
self.used_variables.clear();
25-
self.is_necessary = false;
26-
self.is_async = input.variant.is_async_function();
2725

2826
// Traverse the function body.
29-
let block = self.reconstruct_block(input.block).0;
30-
31-
Function {
32-
annotations: input.annotations,
33-
variant: input.variant,
34-
identifier: input.identifier,
35-
input: input.input,
36-
output: input.output,
37-
output_type: input.output_type,
38-
block,
39-
span: input.span,
40-
id: input.id,
41-
}
27+
input.block = self.reconstruct_block(input.block).0;
28+
29+
input
4230
}
4331
}

0 commit comments

Comments
 (0)