Change optimize_ast to take optimization level as parameter.

This commit is contained in:
Stephen Chung 2020-04-08 09:30:50 +08:00
parent b74c85f04c
commit e0bb2e5c97
5 changed files with 54 additions and 16 deletions

View File

@ -1753,6 +1753,19 @@ An [`Engine`]'s optimization level is set via a call to `set_optimization_level`
engine.set_optimization_level(rhai::OptimizationLevel::Full);
```
If it is ever needed to _re_-optimize an `AST`, use the `optimize_ast` method.
```rust
// Compile script to AST
let ast = engine.compile("40 + 2")?;
// Create a new 'Scope' - put constants in it to aid optimization if using 'OptimizationLevel::Full'
let scope = Scope::new();
// Re-optimize the AST
let ast = engine.optimize_ast(&scope, &ast, OptimizationLevel::Full);
```
When the optimization level is [`OptimizationLevel::Full`], the [`Engine`] assumes all functions to be _pure_ and will _eagerly_
evaluated all function calls with constant arguments, using the result to replace the call. This also applies to all operators
(which are implemented as functions). For instance, the same example above:

View File

@ -145,9 +145,7 @@ fn main() {
#[cfg(not(feature = "no_optimize"))]
{
engine.set_optimization_level(OptimizationLevel::Full);
ast = engine.optimize_ast(&scope, r);
engine.set_optimization_level(OptimizationLevel::None);
ast = engine.optimize_ast(&scope, r, OptimizationLevel::Full);
}
#[cfg(feature = "no_optimize")]

View File

@ -10,7 +10,7 @@ use crate::result::EvalAltResult;
use crate::scope::Scope;
#[cfg(not(feature = "no_optimize"))]
use crate::optimize::optimize_into_ast;
use crate::optimize::{optimize_into_ast, OptimizationLevel};
use crate::stdlib::{
any::{type_name, TypeId},
@ -902,9 +902,14 @@ impl<'e> Engine<'e> {
/// compiled just once. Before evaluation, constants are passed into the `Engine` via an external scope
/// (i.e. with `scope.push_constant(...)`). Then, the `AST is cloned and the copy re-optimized before running.
#[cfg(not(feature = "no_optimize"))]
pub fn optimize_ast(&self, scope: &Scope, ast: AST) -> AST {
pub fn optimize_ast(
&self,
scope: &Scope,
ast: AST,
optimization_level: OptimizationLevel,
) -> AST {
let fn_lib = ast.1.iter().map(|fn_def| fn_def.as_ref().clone()).collect();
optimize_into_ast(self, scope, ast.0, fn_lib)
optimize_into_ast(self, scope, ast.0, fn_lib, optimization_level)
}
/// Override default action of `print` (print to stdout using `println!`)

View File

@ -41,16 +41,23 @@ struct State<'a> {
engine: &'a Engine<'a>,
/// Library of script-defined functions.
fn_lib: &'a [(&'a str, usize)],
/// Optimization level.
optimization_level: OptimizationLevel,
}
impl<'a> State<'a> {
/// Create a new State.
pub fn new(engine: &'a Engine<'a>, fn_lib: &'a [(&'a str, usize)]) -> Self {
pub fn new(
engine: &'a Engine<'a>,
fn_lib: &'a [(&'a str, usize)],
level: OptimizationLevel,
) -> Self {
Self {
changed: false,
constants: vec![],
engine,
fn_lib,
optimization_level: level,
}
}
/// Reset the state from dirty to clean.
@ -501,7 +508,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
// Eagerly call functions
Expr::FunctionCall(id, args, def_value, pos)
if state.engine.optimization_level == OptimizationLevel::Full // full optimizations
if state.optimization_level == OptimizationLevel::Full // full optimizations
&& args.iter().all(|expr| expr.is_constant()) // all arguments are constants
=> {
// First search in script-defined functions (can override built-in)
@ -560,14 +567,15 @@ pub(crate) fn optimize<'a>(
engine: &Engine<'a>,
scope: &Scope,
fn_lib: &'a [(&'a str, usize)],
level: OptimizationLevel,
) -> Vec<Stmt> {
// If optimization level is None then skip optimizing
if engine.optimization_level == OptimizationLevel::None {
if level == OptimizationLevel::None {
return statements;
}
// Set up the state
let mut state = State::new(engine, fn_lib);
let mut state = State::new(engine, fn_lib, level);
// Add constants from the scope into the state
scope
@ -640,6 +648,7 @@ pub fn optimize_into_ast(
scope: &Scope,
statements: Vec<Stmt>,
functions: Vec<FnDef>,
level: OptimizationLevel,
) -> AST {
let fn_lib: Vec<_> = functions
.iter()
@ -651,11 +660,12 @@ pub fn optimize_into_ast(
.iter()
.cloned()
.map(|mut fn_def| {
if engine.optimization_level != OptimizationLevel::None {
if level != OptimizationLevel::None {
let pos = fn_def.body.position();
// Optimize the function body
let mut body = optimize(vec![fn_def.body], engine, &Scope::new(), &fn_lib);
let mut body =
optimize(vec![fn_def.body], engine, &Scope::new(), &fn_lib, level);
// {} -> Noop
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
@ -675,10 +685,10 @@ pub fn optimize_into_ast(
);
AST(
match engine.optimization_level {
match level {
OptimizationLevel::None => statements,
OptimizationLevel::Simple | OptimizationLevel::Full => {
optimize(statements, engine, &scope, &fn_lib)
optimize(statements, engine, &scope, &fn_lib, level)
}
},
#[cfg(feature = "sync")]

View File

@ -2781,7 +2781,13 @@ pub fn parse_global_expr<'a, 'e>(
Ok(
// Optimize AST
#[cfg(not(feature = "no_optimize"))]
optimize_into_ast(engine, scope, vec![Stmt::Expr(Box::new(expr))], vec![]),
optimize_into_ast(
engine,
scope,
vec![Stmt::Expr(Box::new(expr))],
vec![],
engine.optimization_level,
),
//
// Do not optimize AST if `no_optimize`
#[cfg(feature = "no_optimize")]
@ -2866,7 +2872,13 @@ pub fn parse<'a, 'e>(
Ok(
// Optimize AST
#[cfg(not(feature = "no_optimize"))]
optimize_into_ast(engine, scope, statements, functions),
optimize_into_ast(
engine,
scope,
statements,
functions,
engine.optimization_level,
),
//
// Do not optimize AST if `no_optimize`
#[cfg(feature = "no_optimize")]