Reduce size of Stmt.

This commit is contained in:
Stephen Chung
2022-02-16 17:51:14 +08:00
parent cf0660e36b
commit 0d2e3d82f3
9 changed files with 457 additions and 342 deletions

View File

@@ -1,7 +1,9 @@
//! Module implementing the [`AST`] optimizer.
#![cfg(not(feature = "no_optimize"))]
use crate::ast::{Expr, OpAssignment, Stmt, StmtBlockContainer, AST_OPTION_FLAGS::*};
use crate::ast::{
Expr, OpAssignment, Stmt, StmtBlock, StmtBlockContainer, SwitchCases, AST_OPTION_FLAGS::*,
};
use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF};
use crate::eval::{EvalState, GlobalRuntimeState};
use crate::func::builtin::get_builtin_binary_op_fn;
@@ -252,22 +254,22 @@ fn optimize_stmt_block(
// Optimize each statement in the block
for stmt in statements.iter_mut() {
match stmt {
Stmt::Var(value_expr, x, options, ..) => {
Stmt::Var(x, options, ..) => {
if options.contains(AST_OPTION_CONSTANT) {
// Add constant literals into the state
optimize_expr(value_expr, state, false);
optimize_expr(&mut x.1, state, false);
if value_expr.is_constant() {
if x.1.is_constant() {
state.push_var(
x.name.as_str(),
x.0.name.as_str(),
AccessMode::ReadOnly,
value_expr.get_literal_value(),
x.1.get_literal_value(),
);
}
} else {
// Add variables into the state
optimize_expr(value_expr, state, false);
state.push_var(x.name.as_str(), AccessMode::ReadWrite, None);
optimize_expr(&mut x.1, state, false);
state.push_var(x.0.name.as_str(), AccessMode::ReadWrite, None);
}
}
// Optimize the statement
@@ -284,10 +286,11 @@ fn optimize_stmt_block(
.find_map(|(i, stmt)| match stmt {
stmt if !is_pure(stmt) => Some(i),
Stmt::Var(e, _, ..) | Stmt::Expr(e) if !e.is_constant() => Some(i),
Stmt::Var(x, ..) if x.1.is_constant() => Some(i),
Stmt::Expr(e) if !e.is_constant() => Some(i),
#[cfg(not(feature = "no_module"))]
Stmt::Import(e, ..) if !e.is_constant() => Some(i),
Stmt::Import(x, ..) if !x.0.is_constant() => Some(i),
_ => None,
})
@@ -320,7 +323,7 @@ fn optimize_stmt_block(
loop {
match statements[..] {
// { return; } -> {}
[Stmt::Return(options, None, ..)]
[Stmt::Return(None, options, ..)]
if reduce_return && !options.contains(AST_OPTION_BREAK) =>
{
state.set_dirty();
@@ -331,7 +334,7 @@ fn optimize_stmt_block(
statements.clear();
}
// { ...; return; } -> { ... }
[.., ref last_stmt, Stmt::Return(options, None, ..)]
[.., ref last_stmt, Stmt::Return(None, options, ..)]
if reduce_return
&& !options.contains(AST_OPTION_BREAK)
&& !last_stmt.returns_value() =>
@@ -340,7 +343,7 @@ fn optimize_stmt_block(
statements.pop().unwrap();
}
// { ...; return val; } -> { ...; val }
[.., Stmt::Return(options, ref mut expr, pos)]
[.., Stmt::Return(ref mut expr, options, pos)]
if reduce_return && !options.contains(AST_OPTION_BREAK) =>
{
state.set_dirty();
@@ -377,14 +380,14 @@ fn optimize_stmt_block(
statements.clear();
}
// { ...; return; } -> { ... }
[.., Stmt::Return(options, None, ..)]
[.., Stmt::Return(None, options, ..)]
if reduce_return && !options.contains(AST_OPTION_BREAK) =>
{
state.set_dirty();
statements.pop().unwrap();
}
// { ...; return pure_val; } -> { ... }
[.., Stmt::Return(options, Some(ref expr), ..)]
[.., Stmt::Return(Some(ref expr), options, ..)]
if reduce_return
&& !options.contains(AST_OPTION_BREAK)
&& expr.is_pure() =>
@@ -460,7 +463,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
}
// if expr {}
Stmt::If(condition, x, ..) if x.0.is_empty() && x.1.is_empty() => {
Stmt::If(x, ..) if x.1.is_empty() && x.2.is_empty() => {
let condition = &mut x.0;
state.set_dirty();
let pos = condition.start_position();
@@ -469,56 +473,72 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
*stmt = if preserve_result {
// -> { expr, Noop }
Stmt::Block(
[Stmt::Expr(expr), Stmt::Noop(pos)].into(),
Span::new(pos, Position::NONE),
(
[Stmt::Expr(expr.into()), Stmt::Noop(pos)],
pos,
Position::NONE,
)
.into()
} else {
// -> expr
Stmt::Expr(expr)
Stmt::Expr(expr.into())
};
}
// if false { if_block } -> Noop
Stmt::If(Expr::BoolConstant(false, pos), x, ..) if x.1.is_empty() => {
state.set_dirty();
*stmt = Stmt::Noop(*pos);
Stmt::If(x, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) && x.2.is_empty() => {
if let Expr::BoolConstant(false, pos) = x.0 {
state.set_dirty();
*stmt = Stmt::Noop(pos);
} else {
unreachable!("`Expr::BoolConstant`");
}
}
// if false { if_block } else { else_block } -> else_block
Stmt::If(Expr::BoolConstant(false, ..), x, ..) => {
Stmt::If(x, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) => {
state.set_dirty();
*stmt =
match optimize_stmt_block(mem::take(&mut *x.2), state, preserve_result, true, false)
{
statements if statements.is_empty() => Stmt::Noop(x.2.position()),
statements => (statements, x.2.span()).into(),
}
}
// if true { if_block } else { else_block } -> if_block
Stmt::If(x, ..) if matches!(x.0, Expr::BoolConstant(true, ..)) => {
state.set_dirty();
*stmt =
match optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false)
{
statements if statements.is_empty() => Stmt::Noop(x.1.position()),
statements => Stmt::Block(statements.into_boxed_slice(), x.1.span()),
}
}
// if true { if_block } else { else_block } -> if_block
Stmt::If(Expr::BoolConstant(true, ..), x, ..) => {
state.set_dirty();
*stmt =
match optimize_stmt_block(mem::take(&mut *x.0), state, preserve_result, true, false)
{
statements if statements.is_empty() => Stmt::Noop(x.0.position()),
statements => Stmt::Block(statements.into_boxed_slice(), x.0.span()),
statements => (statements, x.1.span()).into(),
}
}
// if expr { if_block } else { else_block }
Stmt::If(condition, x, ..) => {
Stmt::If(x, ..) => {
let (condition, body, other) = x.as_mut();
optimize_expr(condition, state, false);
*x.0 = optimize_stmt_block(mem::take(&mut *x.0), state, preserve_result, true, false);
*x.1 = optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false);
**body =
optimize_stmt_block(mem::take(&mut **body), state, preserve_result, true, false);
**other =
optimize_stmt_block(mem::take(&mut **other), state, preserve_result, true, false);
}
// switch const { ... }
Stmt::Switch(match_expr, x, pos) if match_expr.is_constant() => {
Stmt::Switch(x, pos) if x.0.is_constant() => {
let (
match_expr,
SwitchCases {
cases,
ranges,
def_case,
},
) = x.as_mut();
let value = match_expr.get_literal_value().unwrap();
let hasher = &mut get_hasher();
value.hash(hasher);
let hash = hasher.finish();
let cases = &mut x.cases;
// First check hashes
if let Some(block) = cases.get_mut(&hash) {
if let Some(mut condition) = mem::take(&mut block.condition) {
@@ -526,18 +546,18 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
optimize_expr(&mut condition, state, false);
let def_stmt =
optimize_stmt_block(mem::take(&mut x.def_case), state, true, true, false);
optimize_stmt_block(mem::take(def_case), state, true, true, false);
*stmt = Stmt::If(
condition,
Box::new((
(
condition,
mem::take(&mut block.statements),
Stmt::Block(
def_stmt.into_boxed_slice(),
x.def_case.span_or_else(*pos, Position::NONE),
)
StmtBlock::new_with_span(
def_stmt,
def_case.span_or_else(*pos, Position::NONE),
),
)
.into(),
)),
match_expr.start_position(),
);
} else {
@@ -549,7 +569,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
true,
false,
);
*stmt = Stmt::Block(statements.into_boxed_slice(), block.statements.span());
*stmt = (statements, block.statements.span()).into();
}
state.set_dirty();
@@ -557,8 +577,6 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
}
// Then check ranges
let ranges = &mut x.ranges;
if value.is::<INT>() && !ranges.is_empty() {
let value = value.as_int().expect("`INT`");
@@ -576,23 +594,18 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def }
optimize_expr(&mut condition, state, false);
let def_stmt = optimize_stmt_block(
mem::take(&mut x.def_case),
state,
true,
true,
false,
);
let def_stmt =
optimize_stmt_block(mem::take(def_case), state, true, true, false);
*stmt = Stmt::If(
condition,
Box::new((
(
condition,
mem::take(&mut block.statements),
Stmt::Block(
def_stmt.into_boxed_slice(),
x.def_case.span_or_else(*pos, Position::NONE),
)
StmtBlock::new_with_span(
def_stmt,
def_case.span_or_else(*pos, Position::NONE),
),
)
.into(),
)),
match_expr.start_position(),
);
} else {
@@ -600,8 +613,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
let statements = mem::take(&mut *block.statements);
let statements =
optimize_stmt_block(statements, state, true, true, false);
*stmt =
Stmt::Block(statements.into_boxed_slice(), block.statements.span());
*stmt = (statements, block.statements.span()).into();
}
state.set_dirty();
@@ -644,17 +656,25 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// Promote the default case
state.set_dirty();
let def_stmt =
optimize_stmt_block(mem::take(&mut x.def_case), state, true, true, false);
*stmt = Stmt::Block(
def_stmt.into_boxed_slice(),
x.def_case.span_or_else(*pos, Position::NONE),
);
let def_stmt = optimize_stmt_block(mem::take(def_case), state, true, true, false);
*stmt = (def_stmt, def_case.span_or_else(*pos, Position::NONE)).into();
}
// switch
Stmt::Switch(match_expr, x, ..) => {
Stmt::Switch(x, ..) => {
let (
match_expr,
SwitchCases {
cases,
ranges,
def_case,
..
},
) = x.as_mut();
optimize_expr(match_expr, state, false);
for block in x.cases.values_mut() {
// Optimize cases
for block in cases.values_mut() {
let statements = mem::take(&mut *block.statements);
*block.statements =
optimize_stmt_block(statements, state, preserve_result, true, false);
@@ -669,30 +689,58 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
}
// Remove false cases
while let Some((&key, ..)) = x.cases.iter().find(|(.., block)| match block.condition {
Some(Expr::BoolConstant(false, ..)) => true,
_ => false,
}) {
state.set_dirty();
x.cases.remove(&key);
cases.retain(|_, block| match block.condition {
Some(Expr::BoolConstant(false, ..)) => {
state.set_dirty();
false
}
_ => true,
});
// Optimize ranges
for (.., block) in ranges.iter_mut() {
let statements = mem::take(&mut *block.statements);
*block.statements =
optimize_stmt_block(statements, state, preserve_result, true, false);
if let Some(mut condition) = mem::take(&mut block.condition) {
optimize_expr(&mut condition, state, false);
match condition {
Expr::Unit(..) | Expr::BoolConstant(true, ..) => state.set_dirty(),
_ => block.condition = Some(condition),
}
}
}
let def_block = mem::take(&mut *x.def_case);
*x.def_case = optimize_stmt_block(def_block, state, preserve_result, true, false);
// Remove false ranges
ranges.retain(|(.., block)| match block.condition {
Some(Expr::BoolConstant(false, ..)) => {
state.set_dirty();
false
}
_ => true,
});
let def_block = mem::take(&mut ***def_case);
***def_case = optimize_stmt_block(def_block, state, preserve_result, true, false);
}
// while false { block } -> Noop
Stmt::While(Expr::BoolConstant(false, pos), ..) => {
state.set_dirty();
*stmt = Stmt::Noop(*pos)
}
Stmt::While(x, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) => match x.0 {
Expr::BoolConstant(false, pos) => {
state.set_dirty();
*stmt = Stmt::Noop(pos)
}
_ => unreachable!("`Expr::BoolConstant"),
},
// while expr { block }
Stmt::While(condition, body, ..) => {
Stmt::While(x, ..) => {
let (condition, body) = x.as_mut();
optimize_expr(condition, state, false);
if let Expr::BoolConstant(true, pos) = condition {
*condition = Expr::Unit(*pos);
}
***body = optimize_stmt_block(mem::take(&mut **body), state, false, true, false);
**body = optimize_stmt_block(mem::take(&mut **body), state, false, true, false);
if body.len() == 1 {
match body[0] {
@@ -701,14 +749,11 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// Only a single break statement - turn into running the guard expression once
state.set_dirty();
if !condition.is_unit() {
let mut statements = vec![Stmt::Expr(mem::take(condition))];
let mut statements = vec![Stmt::Expr(mem::take(condition).into())];
if preserve_result {
statements.push(Stmt::Noop(pos))
}
*stmt = Stmt::Block(
statements.into_boxed_slice(),
Span::new(pos, Position::NONE),
);
*stmt = (statements, Span::new(pos, Position::NONE)).into();
} else {
*stmt = Stmt::Noop(pos);
};
@@ -717,37 +762,51 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
}
}
}
// do { block } while false | do { block } until true -> { block }
Stmt::Do(body, Expr::BoolConstant(x, ..), options, ..)
if *x == options.contains(AST_OPTION_NEGATED) =>
// do { block } until true -> { block }
Stmt::Do(x, options, ..)
if matches!(x.0, Expr::BoolConstant(true, ..))
&& options.contains(AST_OPTION_NEGATED) =>
{
state.set_dirty();
*stmt = Stmt::Block(
optimize_stmt_block(mem::take(&mut **body), state, false, true, false)
.into_boxed_slice(),
body.span(),
);
*stmt = (
optimize_stmt_block(mem::take(&mut *x.1), state, false, true, false),
x.1.span(),
)
.into();
}
// do { block } while false -> { block }
Stmt::Do(x, options, ..)
if matches!(x.0, Expr::BoolConstant(false, ..))
&& !options.contains(AST_OPTION_NEGATED) =>
{
state.set_dirty();
*stmt = (
optimize_stmt_block(mem::take(&mut *x.1), state, false, true, false),
x.1.span(),
)
.into();
}
// do { block } while|until expr
Stmt::Do(body, condition, ..) => {
optimize_expr(condition, state, false);
***body = optimize_stmt_block(mem::take(&mut **body), state, false, true, false);
Stmt::Do(x, ..) => {
optimize_expr(&mut x.0, state, false);
*x.1 = optimize_stmt_block(mem::take(&mut *x.1), state, false, true, false);
}
// for id in expr { block }
Stmt::For(iterable, x, ..) => {
optimize_expr(iterable, state, false);
*x.2 = optimize_stmt_block(mem::take(&mut *x.2), state, false, true, false);
Stmt::For(x, ..) => {
optimize_expr(&mut x.1, state, false);
*x.3 = optimize_stmt_block(mem::take(&mut *x.3), state, false, true, false);
}
// let id = expr;
Stmt::Var(expr, _, options, ..) if !options.contains(AST_OPTION_CONSTANT) => {
optimize_expr(expr, state, false)
Stmt::Var(x, options, ..) if !options.contains(AST_OPTION_CONSTANT) => {
optimize_expr(&mut x.1, state, false)
}
// import expr as var;
#[cfg(not(feature = "no_module"))]
Stmt::Import(expr, ..) => optimize_expr(expr, state, false),
Stmt::Import(x, ..) => optimize_expr(&mut x.0, state, false),
// { block }
Stmt::Block(statements, span) => {
let statements = mem::take(statements).into_vec().into();
Stmt::Block(block) => {
let span = block.span();
let statements = block.take_statements().into_vec().into();
let mut block = optimize_stmt_block(statements, state, preserve_result, true, false);
match block.as_mut_slice() {
@@ -760,18 +819,18 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
state.set_dirty();
*stmt = mem::take(s);
}
_ => *stmt = Stmt::Block(block.into_boxed_slice(), *span),
_ => *stmt = (block, span).into(),
}
}
// try { pure try_block } catch ( var ) { catch_block } -> try_block
Stmt::TryCatch(x, ..) if x.try_block.iter().all(Stmt::is_pure) => {
// If try block is pure, there will never be any exceptions
state.set_dirty();
*stmt = Stmt::Block(
optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false)
.into_boxed_slice(),
*stmt = (
optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false),
x.try_block.span(),
);
)
.into();
}
// try { try_block } catch ( var ) { catch_block }
Stmt::TryCatch(x, ..) => {
@@ -780,31 +839,33 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
*x.catch_block =
optimize_stmt_block(mem::take(&mut *x.catch_block), state, false, true, false);
}
// func(...)
Stmt::Expr(expr @ Expr::FnCall(..)) => {
Stmt::Expr(expr) => {
optimize_expr(expr, state, false);
match expr {
match expr.as_mut() {
// func(...)
Expr::FnCall(x, pos) => {
state.set_dirty();
*stmt = Stmt::FnCall(mem::take(x), *pos);
}
// {...};
Expr::Stmt(x) => {
if x.is_empty() {
state.set_dirty();
*stmt = Stmt::Noop(x.position());
} else {
state.set_dirty();
*stmt = mem::take(&mut **x).into();
}
}
// expr;
_ => (),
}
}
// {}
Stmt::Expr(Expr::Stmt(x)) if x.is_empty() => {
state.set_dirty();
*stmt = Stmt::Noop(x.position());
}
// {...};
Stmt::Expr(Expr::Stmt(x)) => {
state.set_dirty();
*stmt = mem::take(&mut **x).into();
}
// expr;
Stmt::Expr(expr) => optimize_expr(expr, state, false),
// return expr;
Stmt::Return(_, Some(ref mut expr), _) => optimize_expr(expr, state, false),
Stmt::Return(Some(ref mut expr), ..) => optimize_expr(expr, state, false),
// All other statements - skip
_ => (),