More optimizations.
This commit is contained in:
parent
952932f64c
commit
a02c0cfaa0
152
src/optimize.rs
152
src/optimize.rs
@ -1,27 +1,28 @@
|
|||||||
|
use crate::engine::KEYWORD_DUMP_AST;
|
||||||
use crate::parser::{Expr, Stmt};
|
use crate::parser::{Expr, Stmt};
|
||||||
|
|
||||||
fn optimize_stmt(stmt: Stmt, changed: &mut bool) -> Stmt {
|
fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt {
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::IfElse(expr, stmt1, None) => match *expr {
|
Stmt::IfElse(expr, stmt1, None) => match *expr {
|
||||||
Expr::False(pos) => {
|
Expr::False(pos) => {
|
||||||
*changed = true;
|
*changed = true;
|
||||||
Stmt::Noop(pos)
|
Stmt::Noop(pos)
|
||||||
}
|
}
|
||||||
Expr::True(_) => optimize_stmt(*stmt1, changed),
|
Expr::True(_) => optimize_stmt(*stmt1, changed, true),
|
||||||
expr => Stmt::IfElse(
|
expr => Stmt::IfElse(
|
||||||
Box::new(optimize_expr(expr, changed)),
|
Box::new(optimize_expr(expr, changed)),
|
||||||
Box::new(optimize_stmt(*stmt1, changed)),
|
Box::new(optimize_stmt(*stmt1, changed, true)),
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
Stmt::IfElse(expr, stmt1, Some(stmt2)) => match *expr {
|
Stmt::IfElse(expr, stmt1, Some(stmt2)) => match *expr {
|
||||||
Expr::False(_) => optimize_stmt(*stmt2, changed),
|
Expr::False(_) => optimize_stmt(*stmt2, changed, true),
|
||||||
Expr::True(_) => optimize_stmt(*stmt1, changed),
|
Expr::True(_) => optimize_stmt(*stmt1, changed, true),
|
||||||
expr => Stmt::IfElse(
|
expr => Stmt::IfElse(
|
||||||
Box::new(optimize_expr(expr, changed)),
|
Box::new(optimize_expr(expr, changed)),
|
||||||
Box::new(optimize_stmt(*stmt1, changed)),
|
Box::new(optimize_stmt(*stmt1, changed, true)),
|
||||||
Some(Box::new(optimize_stmt(*stmt2, changed))),
|
Some(Box::new(optimize_stmt(*stmt2, changed, true))),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -30,18 +31,18 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool) -> Stmt {
|
|||||||
*changed = true;
|
*changed = true;
|
||||||
Stmt::Noop(pos)
|
Stmt::Noop(pos)
|
||||||
}
|
}
|
||||||
Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*stmt, changed))),
|
Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*stmt, changed, false))),
|
||||||
expr => Stmt::While(
|
expr => Stmt::While(
|
||||||
Box::new(optimize_expr(expr, changed)),
|
Box::new(optimize_expr(expr, changed)),
|
||||||
Box::new(optimize_stmt(*stmt, changed)),
|
Box::new(optimize_stmt(*stmt, changed, false)),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
Stmt::Loop(stmt) => Stmt::Loop(Box::new(optimize_stmt(*stmt, changed))),
|
Stmt::Loop(stmt) => Stmt::Loop(Box::new(optimize_stmt(*stmt, changed, false))),
|
||||||
Stmt::For(id, expr, stmt) => Stmt::For(
|
Stmt::For(id, expr, stmt) => Stmt::For(
|
||||||
id,
|
id,
|
||||||
Box::new(optimize_expr(*expr, changed)),
|
Box::new(optimize_expr(*expr, changed)),
|
||||||
Box::new(optimize_stmt(*stmt, changed)),
|
Box::new(optimize_stmt(*stmt, changed, false)),
|
||||||
),
|
),
|
||||||
Stmt::Let(id, Some(expr), pos) => {
|
Stmt::Let(id, Some(expr), pos) => {
|
||||||
Stmt::Let(id, Some(Box::new(optimize_expr(*expr, changed))), pos)
|
Stmt::Let(id, Some(Box::new(optimize_expr(*expr, changed))), pos)
|
||||||
@ -53,42 +54,67 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool) -> Stmt {
|
|||||||
|
|
||||||
let mut result: Vec<_> = statements
|
let mut result: Vec<_> = statements
|
||||||
.into_iter() // For each statement
|
.into_iter() // For each statement
|
||||||
.map(|s| optimize_stmt(s, changed)) // Optimize the statement
|
.rev() // Scan in reverse
|
||||||
.filter(Stmt::is_op) // Remove no-op's
|
.map(|s| optimize_stmt(s, changed, preserve_result)) // Optimize the statement
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(i, s)| s.is_op() || (preserve_result && *i == 0)) // Remove no-op's but leave the last one if we need the result
|
||||||
|
.map(|(_, s)| s)
|
||||||
|
.rev()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if let Some(last_stmt) = result.pop() {
|
// Remove all raw expression statements that are pure except for the very last statement
|
||||||
// Remove all raw expression statements that evaluate to constants
|
let last_stmt = if preserve_result { result.pop() } else { None };
|
||||||
// except for the very last statement
|
|
||||||
result.retain(|stmt| match stmt {
|
|
||||||
Stmt::Expr(expr) if expr.is_constant() || expr.is_identifier() => false,
|
|
||||||
_ => true,
|
|
||||||
});
|
|
||||||
|
|
||||||
result.push(last_stmt);
|
result.retain(|stmt| match stmt {
|
||||||
|
Stmt::Expr(expr) if expr.is_pure() => false,
|
||||||
|
_ => true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(stmt) = last_stmt {
|
||||||
|
result.push(stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all let statements at the end of a block - the new variables will go away anyway.
|
||||||
|
// But be careful only remove ones that have no initial values or have values that are pure expressions,
|
||||||
|
// otherwise there may be side effects.
|
||||||
|
let mut removed = false;
|
||||||
|
|
||||||
|
while let Some(expr) = result.pop() {
|
||||||
|
match expr {
|
||||||
|
Stmt::Let(_, None, _) => removed = true,
|
||||||
|
Stmt::Let(_, Some(val_expr), _) if val_expr.is_pure() => removed = true,
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
result.push(expr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if preserve_result {
|
||||||
|
if removed {
|
||||||
|
result.push(Stmt::Noop(pos))
|
||||||
|
}
|
||||||
|
|
||||||
|
result = result
|
||||||
|
.into_iter()
|
||||||
|
.rev()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, s)| optimize_stmt(s, changed, i == 0)) // Optimize all other statements again
|
||||||
|
.rev()
|
||||||
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
*changed = *changed || original_len != result.len();
|
*changed = *changed || original_len != result.len();
|
||||||
|
|
||||||
match result[..] {
|
match result[..] {
|
||||||
|
// No statements in block - change to No-op
|
||||||
[] => {
|
[] => {
|
||||||
// No statements in block - change to No-op
|
|
||||||
*changed = true;
|
*changed = true;
|
||||||
Stmt::Noop(pos)
|
Stmt::Noop(pos)
|
||||||
}
|
}
|
||||||
[Stmt::Let(_, None, _)] => {
|
// Only one statement - promote
|
||||||
// Only one empty variable declaration - change to No-op
|
|
||||||
*changed = true;
|
|
||||||
Stmt::Noop(pos)
|
|
||||||
}
|
|
||||||
[Stmt::Let(_, Some(_), _)] => {
|
|
||||||
// Only one let statement, but cannot promote
|
|
||||||
// (otherwise the variable gets declared in the scope above)
|
|
||||||
// and still need to run just in case there are side effects
|
|
||||||
Stmt::Block(result, pos)
|
|
||||||
}
|
|
||||||
[_] => {
|
[_] => {
|
||||||
// Only one statement - promote
|
|
||||||
*changed = true;
|
*changed = true;
|
||||||
result.remove(0)
|
result.remove(0)
|
||||||
}
|
}
|
||||||
@ -103,26 +129,14 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool) -> Stmt {
|
|||||||
is_return,
|
is_return,
|
||||||
pos,
|
pos,
|
||||||
),
|
),
|
||||||
stmt @ Stmt::ReturnWithVal(None, _, _) => stmt,
|
|
||||||
|
|
||||||
stmt @ Stmt::Noop(_) | stmt @ Stmt::Break(_) => stmt,
|
stmt => stmt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
|
fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
|
||||||
match expr {
|
match expr {
|
||||||
Expr::IntegerConstant(_, _)
|
Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, changed, true) {
|
||||||
| Expr::Identifier(_, _)
|
|
||||||
| Expr::CharConstant(_, _)
|
|
||||||
| Expr::StringConstant(_, _)
|
|
||||||
| Expr::True(_)
|
|
||||||
| Expr::False(_)
|
|
||||||
| Expr::Unit(_) => expr,
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
|
||||||
Expr::FloatConstant(_, _) => expr,
|
|
||||||
|
|
||||||
Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, changed) {
|
|
||||||
Stmt::Noop(_) => {
|
Stmt::Noop(_) => {
|
||||||
*changed = true;
|
*changed = true;
|
||||||
Expr::Unit(pos)
|
Expr::Unit(pos)
|
||||||
@ -145,11 +159,10 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) {
|
Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) {
|
||||||
(Expr::Array(mut items, _), Expr::IntegerConstant(i, _))
|
(Expr::Array(mut items, _), Expr::IntegerConstant(i, _))
|
||||||
if i >= 0
|
if i >= 0 && (i as usize) < items.len() && items.iter().all(|x| x.is_pure()) =>
|
||||||
&& (i as usize) < items.len()
|
|
||||||
&& !items.iter().any(|x| x.is_constant() || x.is_identifier()) =>
|
|
||||||
{
|
{
|
||||||
// Array where everything is a constant or identifier - promote the item
|
// Array where everything is a pure - promote the indexed item.
|
||||||
|
// All other items can be thrown away.
|
||||||
*changed = true;
|
*changed = true;
|
||||||
items.remove(i as usize)
|
items.remove(i as usize)
|
||||||
}
|
}
|
||||||
@ -215,6 +228,10 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
|
|||||||
Box::new(optimize_expr(rhs, changed)),
|
Box::new(optimize_expr(rhs, changed)),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Expr::FunctionCall(id, args, def_value, pos) if id == KEYWORD_DUMP_AST => {
|
||||||
|
// Expr::FunctionCall(id, args, def_value, pos)
|
||||||
|
// }
|
||||||
Expr::FunctionCall(id, args, def_value, pos) => {
|
Expr::FunctionCall(id, args, def_value, pos) => {
|
||||||
let original_len = args.len();
|
let original_len = args.len();
|
||||||
|
|
||||||
@ -227,17 +244,29 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
|
|||||||
|
|
||||||
Expr::FunctionCall(id, args, def_value, pos)
|
Expr::FunctionCall(id, args, def_value, pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expr => expr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn optimize(mut statements: Vec<Stmt>) -> Vec<Stmt> {
|
pub(crate) fn optimize(statements: Vec<Stmt>) -> Vec<Stmt> {
|
||||||
|
let mut result = statements;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut changed = false;
|
let mut changed = false;
|
||||||
|
|
||||||
statements = statements
|
result = result
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|stmt| optimize_stmt(stmt, &mut changed))
|
.rev() // Scan in reverse
|
||||||
.filter(Stmt::is_op)
|
.enumerate()
|
||||||
|
.map(|(i, stmt)| {
|
||||||
|
// Keep all variable declarations at this level
|
||||||
|
let keep = stmt.is_var();
|
||||||
|
|
||||||
|
// Always keep the last return value
|
||||||
|
optimize_stmt(stmt, &mut changed, keep || i == 0)
|
||||||
|
})
|
||||||
|
.rev()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if !changed {
|
if !changed {
|
||||||
@ -245,5 +274,14 @@ pub(crate) fn optimize(mut statements: Vec<Stmt>) -> Vec<Stmt> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
statements
|
// Eliminate No-op's but always keep the last statement
|
||||||
|
let last_stmt = result.pop();
|
||||||
|
|
||||||
|
result.retain(Stmt::is_op); // Remove all No-op's
|
||||||
|
|
||||||
|
if let Some(stmt) = last_stmt {
|
||||||
|
result.push(stmt); // Add back the last statement
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user