diff --git a/README.md b/README.md index 8cfe5444..c6ec7f48 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/examples/repl.rs b/examples/repl.rs index d4679842..56f0ac69 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -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")] diff --git a/src/api.rs b/src/api.rs index 6df0cc8d..55943f24 100644 --- a/src/api.rs +++ b/src/api.rs @@ -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!`) diff --git a/src/optimize.rs b/src/optimize.rs index 85c61d1e..74b77395 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -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 { // 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, functions: Vec, + 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")] diff --git a/src/parser.rs b/src/parser.rs index 98a3aff3..c5a1041e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -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")]