...
This commit is contained in:
		
							
								
								
									
										212
									
								
								rhai_engine/rhaibook/engine/optimize/constants.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										212
									
								
								rhai_engine/rhaibook/engine/optimize/constants.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,212 @@
 | 
			
		||||
Constants Propagation
 | 
			
		||||
=====================
 | 
			
		||||
 | 
			
		||||
{{#include ../../links.md}}
 | 
			
		||||
 | 
			
		||||
```admonish tip.side
 | 
			
		||||
 | 
			
		||||
Effective in template-based machine-generated scripts to turn on/off certain sections.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
[Constants] propagation is commonly used to:
 | 
			
		||||
 | 
			
		||||
* remove dead code,
 | 
			
		||||
 | 
			
		||||
* avoid [variable] lookups,
 | 
			
		||||
 | 
			
		||||
* [pre-calculate](op-eval.md) [constant] expressions.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
const ABC = true;
 | 
			
		||||
const X = 41;
 | 
			
		||||
 | 
			
		||||
if ABC || calc(X+1) { print("done!"); }     // 'ABC' is constant so replaced by 'true'...
 | 
			
		||||
                                            // 'X' is constant so replaced by 41... 
 | 
			
		||||
 | 
			
		||||
if true || calc(42) { print("done!"); }     // '41+1' is replaced by 42
 | 
			
		||||
                                            // since '||' short-circuits, 'calc' is never called
 | 
			
		||||
 | 
			
		||||
if true { print("done!"); }                 // <- the line above is equivalent to this
 | 
			
		||||
 | 
			
		||||
print("done!");                             // <- the line above is further simplified to this
 | 
			
		||||
                                            //    because the condition is always true
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
~~~admonish tip "Tip: Custom `Scope` constants"
 | 
			
		||||
 | 
			
		||||
[Constant] values can be provided in a custom [`Scope`] object to the [`Engine`]
 | 
			
		||||
for optimization purposes.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
use rhai::{Engine, Scope};
 | 
			
		||||
 | 
			
		||||
let engine = Engine::new();
 | 
			
		||||
 | 
			
		||||
let mut scope = Scope::new();
 | 
			
		||||
 | 
			
		||||
// Add constant to custom scope
 | 
			
		||||
scope.push_constant("ABC", true);
 | 
			
		||||
 | 
			
		||||
// Evaluate script with custom scope
 | 
			
		||||
engine.run_with_scope(&mut scope,
 | 
			
		||||
r#"
 | 
			
		||||
    if ABC {    // 'ABC' is replaced by 'true'
 | 
			
		||||
        print("done!");
 | 
			
		||||
    }
 | 
			
		||||
"#)?;
 | 
			
		||||
```
 | 
			
		||||
~~~
 | 
			
		||||
 | 
			
		||||
~~~admonish tip "Tip: Customer module constants"
 | 
			
		||||
 | 
			
		||||
[Constants] defined in [modules] that are registered into an [`Engine`] via
 | 
			
		||||
`Engine::register_global_module` are used in optimization.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
use rhai::{Engine, Module};
 | 
			
		||||
 | 
			
		||||
let mut engine = Engine::new();
 | 
			
		||||
 | 
			
		||||
let mut module = Module::new();
 | 
			
		||||
 | 
			
		||||
// Add constant to module
 | 
			
		||||
module.set_var("ABC", true);
 | 
			
		||||
 | 
			
		||||
// Register global module
 | 
			
		||||
engine.register_global_module(module.into());
 | 
			
		||||
 | 
			
		||||
// Evaluate script
 | 
			
		||||
engine.run(
 | 
			
		||||
r#"
 | 
			
		||||
    if ABC {    // 'ABC' is replaced by 'true'
 | 
			
		||||
        print("done!");
 | 
			
		||||
    }
 | 
			
		||||
"#)?;
 | 
			
		||||
```
 | 
			
		||||
~~~
 | 
			
		||||
 | 
			
		||||
~~~admonish danger "Caveat: Constants in custom scope and modules are also propagated into functions"
 | 
			
		||||
 | 
			
		||||
[Constants] defined at _global_ level typically cannot be seen by script [functions] because they are _pure_.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
const MY_CONSTANT = 42;     // <- constant defined at global level
 | 
			
		||||
 | 
			
		||||
print(MY_CONSTANT);         // <- optimized to: print(42)
 | 
			
		||||
 | 
			
		||||
fn foo() {
 | 
			
		||||
    MY_CONSTANT             // <- not optimized: 'foo' cannot see 'MY_CONSTANT'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
print(foo());               // error: 'MY_CONSTANT' not found
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
When [constants] are provided in a custom [`Scope`] (e.g. via `Engine::compile_with_scope`,
 | 
			
		||||
`Engine::eval_with_scope` or `Engine::run_with_scope`), or in a [module] registered via
 | 
			
		||||
`Engine::register_global_module`, instead of defined within the same script, they are also
 | 
			
		||||
propagated to [functions].
 | 
			
		||||
 | 
			
		||||
This is usually the intuitive usage and behavior expected by regular users, even though it means
 | 
			
		||||
that a script will behave differently (essentially a runtime error) when [script optimization] is disabled.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
use rhai::{Engine, Scope};
 | 
			
		||||
 | 
			
		||||
let engine = Engine::new();
 | 
			
		||||
 | 
			
		||||
let mut scope = Scope::new();
 | 
			
		||||
 | 
			
		||||
// Add constant to custom scope
 | 
			
		||||
scope.push_constant("MY_CONSTANT", 42_i64);
 | 
			
		||||
 | 
			
		||||
engine.run_with_scope(&mut scope,
 | 
			
		||||
"
 | 
			
		||||
    print(MY_CONSTANT);     // optimized to: print(42)
 | 
			
		||||
 | 
			
		||||
    fn foo() {
 | 
			
		||||
        MY_CONSTANT         // optimized to: fn foo() { 42 }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    print(foo());           // prints 42
 | 
			
		||||
")?;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The script will act differently when [script optimization] is disabled because script [functions]
 | 
			
		||||
are _pure_ and typically cannot see [constants] within the custom [`Scope`].
 | 
			
		||||
 | 
			
		||||
Therefore, constants in [functions] now throw a runtime error.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
use rhai::{Engine, Scope, OptimizationLevel};
 | 
			
		||||
 | 
			
		||||
let mut engine = Engine::new();
 | 
			
		||||
 | 
			
		||||
// Turn off script optimization, no constants propagation is performed
 | 
			
		||||
engine.set_optimization_level(OptimizationLevel::None);
 | 
			
		||||
 | 
			
		||||
let mut scope = Scope::new();
 | 
			
		||||
 | 
			
		||||
// Add constant to custom scope
 | 
			
		||||
scope.push_constant("MY_CONSTANT", 42_i64);
 | 
			
		||||
 | 
			
		||||
engine.run_with_scope(&mut scope,
 | 
			
		||||
"
 | 
			
		||||
    print(MY_CONSTANT);     // prints 42
 | 
			
		||||
 | 
			
		||||
    fn foo() {
 | 
			
		||||
        MY_CONSTANT         // <- 'foo' cannot see 'MY_CONSTANT'
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    print(foo());           // error: 'MY_CONSTANT' not found
 | 
			
		||||
")?;
 | 
			
		||||
```
 | 
			
		||||
~~~
 | 
			
		||||
 | 
			
		||||
~~~admonish danger "Caveat: Beware of large constants"
 | 
			
		||||
 | 
			
		||||
[Constants] propagation replaces each usage of the [constant] with a clone of its value.
 | 
			
		||||
 | 
			
		||||
This may have negative implications to performance if the [constant] value is expensive to clone
 | 
			
		||||
(e.g. if the type is very large).
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
let mut scope = Scope::new();
 | 
			
		||||
 | 
			
		||||
// Push a large constant into the scope...
 | 
			
		||||
let big_type = AVeryLargeType::take_long_time_to_create();
 | 
			
		||||
scope.push_constant("MY_BIG_TYPE", big_type);
 | 
			
		||||
 | 
			
		||||
// Causes each usage of 'MY_BIG_TYPE' in the script below to be replaced
 | 
			
		||||
// by cloned copies of 'AVeryLargeType'.
 | 
			
		||||
let result = engine.run_with_scope(&mut scope,
 | 
			
		||||
"
 | 
			
		||||
    let value = MY_BIG_TYPE.value;
 | 
			
		||||
    let data = MY_BIG_TYPE.data;
 | 
			
		||||
    let len = MY_BIG_TYPE.len();
 | 
			
		||||
    let has_options = MY_BIG_TYPE.has_options();
 | 
			
		||||
    let num_options = MY_BIG_TYPE.options_len();
 | 
			
		||||
")?;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
To avoid this, compile the script first to an [`AST`] _without_ the [constants], then evaluate the
 | 
			
		||||
[`AST`] (e.g. with `Engine::eval_ast_with_scope` or `Engine::run_ast_with_scope`) together with
 | 
			
		||||
the [constants].
 | 
			
		||||
~~~
 | 
			
		||||
 | 
			
		||||
~~~admonish danger "Caveat: Constants may be modified by Rust methods"
 | 
			
		||||
 | 
			
		||||
If the [constants] are modified later on (yes, it is possible, via Rust _methods_),
 | 
			
		||||
the modified values will not show up in the optimized script.
 | 
			
		||||
Only the initialization values of [constants] are ever retained.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
const MY_SECRET_ANSWER = 42;
 | 
			
		||||
 | 
			
		||||
MY_SECRET_ANSWER.update_to(666);    // assume 'update_to(&mut i64)' is a Rust function
 | 
			
		||||
 | 
			
		||||
print(MY_SECRET_ANSWER);            // prints 42 because the constant is propagated
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
This is almost never a problem because real-world scripts seldom modify a [constant],
 | 
			
		||||
but the possibility is always there.
 | 
			
		||||
~~~
 | 
			
		||||
							
								
								
									
										51
									
								
								rhai_engine/rhaibook/engine/optimize/dead-code.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								rhai_engine/rhaibook/engine/optimize/dead-code.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
Dead Code Elimination
 | 
			
		||||
=====================
 | 
			
		||||
 | 
			
		||||
{{#include ../../links.md}}
 | 
			
		||||
 | 
			
		||||
```admonish question.side.wide "But who writes dead code?"
 | 
			
		||||
 | 
			
		||||
Nobody deliberately writes scripts with dead code (we hope).
 | 
			
		||||
 | 
			
		||||
They are, however, extremely common in template-based machine-generated scripts.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Rhai attempts to eliminate _dead code_.
 | 
			
		||||
 | 
			
		||||
"Dead code" is code that does nothing and has no side effects.
 | 
			
		||||
 | 
			
		||||
Example is an pure expression by itself as a statement (allowed in Rhai).
 | 
			
		||||
The result of the expression is calculated then immediately discarded and not used.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
{
 | 
			
		||||
    let x = 999;            // NOT eliminated: variable may be used later on (perhaps even an 'eval')
 | 
			
		||||
    
 | 
			
		||||
    123;                    // eliminated: no effect
 | 
			
		||||
    
 | 
			
		||||
    "hello";                // eliminated: no effect
 | 
			
		||||
    
 | 
			
		||||
    [1, 2, x, 4];           // eliminated: no effect
 | 
			
		||||
    
 | 
			
		||||
    if 42 > 0 {             // '42 > 0' is replaced by 'true' and the first branch promoted
 | 
			
		||||
        foo(42);            // promoted, NOT eliminated: the function 'foo' may have side-effects
 | 
			
		||||
    } else {
 | 
			
		||||
        bar(x);             // eliminated: branch is never reached
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    let z = x;              // eliminated: local variable, no side-effects, and only pure afterwards
 | 
			
		||||
    
 | 
			
		||||
    666                     // NOT eliminated: this is the return value of the block,
 | 
			
		||||
                            // and the block is the last one so this is the return value of the whole script
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The above script optimizes to:
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
{
 | 
			
		||||
    let x = 999;
 | 
			
		||||
    foo(42);
 | 
			
		||||
    666
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										25
									
								
								rhai_engine/rhaibook/engine/optimize/disable.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								rhai_engine/rhaibook/engine/optimize/disable.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
Turn Off Script Optimizations
 | 
			
		||||
=============================
 | 
			
		||||
 | 
			
		||||
{{#include ../../links.md}}
 | 
			
		||||
 | 
			
		||||
When scripts:
 | 
			
		||||
 | 
			
		||||
* are known to be run only _once_ and then thrown away,
 | 
			
		||||
 | 
			
		||||
* are known to contain no dead code,
 | 
			
		||||
 | 
			
		||||
* do not use constants in calculations
 | 
			
		||||
 | 
			
		||||
the optimization pass may be a waste of time and resources.
 | 
			
		||||
 | 
			
		||||
In that case, turn optimization off by setting the optimization level to [`OptimizationLevel::None`].
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
let engine = rhai::Engine::new();
 | 
			
		||||
 | 
			
		||||
// Turn off the optimizer
 | 
			
		||||
engine.set_optimization_level(rhai::OptimizationLevel::None);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Alternatively, disable optimizations via the [`no_optimize`] feature.
 | 
			
		||||
							
								
								
									
										75
									
								
								rhai_engine/rhaibook/engine/optimize/eager.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								rhai_engine/rhaibook/engine/optimize/eager.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
			
		||||
Eager Function Evaluation When Using Full Optimization Level
 | 
			
		||||
============================================================
 | 
			
		||||
 | 
			
		||||
{{#include ../../links.md}}
 | 
			
		||||
 | 
			
		||||
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).
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// When compiling the following with OptimizationLevel::Full...
 | 
			
		||||
 | 
			
		||||
const DECISION = 1;
 | 
			
		||||
                            //    this condition is now eliminated because 'sign(DECISION) > 0'
 | 
			
		||||
if sign(DECISION) > 0       // <- is a call to the 'sign' and '>' functions, and they return 'true'
 | 
			
		||||
{
 | 
			
		||||
    print("hello!");        // <- this block is promoted to the parent level
 | 
			
		||||
} else {
 | 
			
		||||
    print("boo!");          // <- this block is eliminated because it is never reached
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
print("hello!");            // <- the above is equivalent to this
 | 
			
		||||
                            //    ('print' and 'debug' are handled specially)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
~~~admonish danger "Won't this be dangerous?"
 | 
			
		||||
 | 
			
		||||
Yes! _Very!_
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// Nuclear silo control
 | 
			
		||||
if launch_nukes && president_okeyed {
 | 
			
		||||
    print("This is NOT a drill!");
 | 
			
		||||
    update_defcon(1);
 | 
			
		||||
    start_world_war(3);
 | 
			
		||||
    launch_all_nukes();
 | 
			
		||||
} else {
 | 
			
		||||
    print("This is a drill.  Thank you for your cooperation.");
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
In the script above (well... as if nuclear silos will one day be controlled by Rhai scripts),
 | 
			
		||||
the functions `update_defcon`, `start_world_war` and `launch_all_nukes` will be evaluated
 | 
			
		||||
during _compilation_ because they have constant arguments.
 | 
			
		||||
 | 
			
		||||
The [variables] `launch_nukes` and `president_okeyed` are never checked, because the script
 | 
			
		||||
actually has not yet been run!  The functions are called during compilation.
 | 
			
		||||
This is, _obviously_, not what you want.
 | 
			
		||||
 | 
			
		||||
**Moral of the story: compile with an [`Engine`] that does not have any functions registered.
 | 
			
		||||
Register functions _AFTER_ compilation.**
 | 
			
		||||
~~~
 | 
			
		||||
 | 
			
		||||
~~~admonish question "Why would I ever want to do this then?"
 | 
			
		||||
 | 
			
		||||
Good question! There are two reasons:
 | 
			
		||||
 | 
			
		||||
* A function call may result in cleaner code than the resultant value.
 | 
			
		||||
  In Rust, this would have been handled via a `const` function.
 | 
			
		||||
 | 
			
		||||
* Evaluating a value to a [custom type] that has no representation in script.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// A complex function that returns a unique ID based on the arguments
 | 
			
		||||
let id = make_unique_id(123, "hello", true);
 | 
			
		||||
 | 
			
		||||
// The above is arguably clearer than:
 | 
			
		||||
//   let id = 835781293546; // generated from 123, "hello" and true
 | 
			
		||||
 | 
			
		||||
// A custom type that cannot be represented in script
 | 
			
		||||
let complex_obj = make_complex_obj(42);
 | 
			
		||||
```
 | 
			
		||||
~~~
 | 
			
		||||
							
								
								
									
										62
									
								
								rhai_engine/rhaibook/engine/optimize/index.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								rhai_engine/rhaibook/engine/optimize/index.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
Script Optimization
 | 
			
		||||
===================
 | 
			
		||||
 | 
			
		||||
{{#title Script Optimization}}
 | 
			
		||||
 | 
			
		||||
{{#include ../../links.md}}
 | 
			
		||||
 | 
			
		||||
Rhai includes an _optimizer_ that tries to optimize a script after parsing.
 | 
			
		||||
This can reduce resource utilization and increase execution speed.
 | 
			
		||||
 | 
			
		||||
Script optimization can be turned off via the [`no_optimize`] feature.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Optimization Levels
 | 
			
		||||
===================
 | 
			
		||||
 | 
			
		||||
{{#include ../../links.md}}
 | 
			
		||||
 | 
			
		||||
There are three levels of optimization: `None`, `Simple` and `Full`.
 | 
			
		||||
The default is `Simple`.
 | 
			
		||||
 | 
			
		||||
An [`Engine`]'s optimization level is set via [`Engine::set_optimization_level`][options].
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// Turn on aggressive optimizations
 | 
			
		||||
engine.set_optimization_level(rhai::OptimizationLevel::Full);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
`None`
 | 
			
		||||
------
 | 
			
		||||
 | 
			
		||||
`None` is obvious – no optimization on the AST is performed.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
`Simple` (Default)
 | 
			
		||||
------------------
 | 
			
		||||
 | 
			
		||||
`Simple` performs only relatively _safe_ optimizations without causing side-effects (i.e. it only
 | 
			
		||||
relies on static analysis and [built-in operators] for [constant] [standard types], and will not
 | 
			
		||||
perform any external function calls).
 | 
			
		||||
 | 
			
		||||
```admonish warning.small
 | 
			
		||||
After _constants propagation_ is performed, if the [constants] are then modified (yes, it is possible, via Rust functions),
 | 
			
		||||
the modified values will _not_ show up in the optimized script.
 | 
			
		||||
 | 
			
		||||
Only the initialization values of [constants] are ever retained.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```admonish warning.small
 | 
			
		||||
 | 
			
		||||
Overriding a [built-in operator] in the [`Engine`] afterwards has no effect after the
 | 
			
		||||
optimizer replaces an expression with its calculated value.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
`Full`
 | 
			
		||||
------
 | 
			
		||||
 | 
			
		||||
`Full` is _much_ more aggressive, _including_ calling external functions on [constant] arguments to
 | 
			
		||||
determine their results.
 | 
			
		||||
 | 
			
		||||
One benefit to this is that many more optimization opportunities arise, especially with regards to
 | 
			
		||||
comparison operators.
 | 
			
		||||
							
								
								
									
										85
									
								
								rhai_engine/rhaibook/engine/optimize/op-eval.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								rhai_engine/rhaibook/engine/optimize/op-eval.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,85 @@
 | 
			
		||||
Eager Operator Evaluation
 | 
			
		||||
=========================
 | 
			
		||||
 | 
			
		||||
{{#include ../../links.md}}
 | 
			
		||||
 | 
			
		||||
Most operators are actually function calls, and those functions can be overridden, so whether they
 | 
			
		||||
are optimized away depends on the situation:
 | 
			
		||||
 | 
			
		||||
```admonish info.side.wide "No external functions"
 | 
			
		||||
 | 
			
		||||
Rhai guarantees that no external function will be run, which may trigger side-effects
 | 
			
		||||
(unless the optimization level is [`OptimizationLevel::Full`]).
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
* if the operands are not [constant] values, it is **not** optimized;
 | 
			
		||||
 | 
			
		||||
* if the [operator] is [overloaded][operator overloading], it is **not** optimized because the
 | 
			
		||||
  overloading function may not be _pure_ (i.e. may cause side-effects when called);
 | 
			
		||||
 | 
			
		||||
* if the [operator] is not _built-in_ (see list of [built-in operators]), it is **not** optimized;
 | 
			
		||||
 | 
			
		||||
* if the [operator] is a [built-in operator] for a [standard type][standard types], it is called and
 | 
			
		||||
  replaced by a [constant] result.
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
// The following is most likely generated by machine.
 | 
			
		||||
 | 
			
		||||
const DECISION = 1;             // this is an integer, one of the standard types
 | 
			
		||||
 | 
			
		||||
if DECISION == 1 {              // this is optimized into 'true'
 | 
			
		||||
    :
 | 
			
		||||
} else if DECISION == 2 {       // this is optimized into 'false'
 | 
			
		||||
    :
 | 
			
		||||
} else if DECISION == 3 {       // this is optimized into 'false'
 | 
			
		||||
    :
 | 
			
		||||
} else {
 | 
			
		||||
    :
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Or an equivalent using 'switch':
 | 
			
		||||
 | 
			
		||||
switch DECISION {
 | 
			
		||||
    1 => ...,                   // this statement is promoted
 | 
			
		||||
    2 => ...,                   // this statement is eliminated
 | 
			
		||||
    3 => ...,                   // this statement is eliminated
 | 
			
		||||
    _ => ...                    // this statement is eliminated
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Pre-Evaluation of Constant Expressions
 | 
			
		||||
--------------------------------------
 | 
			
		||||
 | 
			
		||||
Because of the eager evaluation of [operators][built-in operators] for [standard types], many
 | 
			
		||||
[constant] expressions will be evaluated and replaced by the result.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
let x = (1+2) * 3 - 4/5 % 6;    // will be replaced by 'let x = 9'
 | 
			
		||||
 | 
			
		||||
let y = (1 > 2) || (3 <= 4);    // will be replaced by 'let y = true'
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
For operators that are not optimized away due to one of the above reasons, the function calls are
 | 
			
		||||
simply left behind.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// Assume 'new_state' returns some custom type that is NOT one of the standard types.
 | 
			
		||||
// Also assume that the '==' operator is defined for that custom type.
 | 
			
		||||
const DECISION_1 = new_state(1);
 | 
			
		||||
const DECISION_2 = new_state(2);
 | 
			
		||||
const DECISION_3 = new_state(3);
 | 
			
		||||
 | 
			
		||||
if DECISION == 1 {              // NOT optimized away because the operator is not built-in
 | 
			
		||||
    :                           // and may cause side-effects if called!
 | 
			
		||||
    :
 | 
			
		||||
} else if DECISION == 2 {       // same here, NOT optimized away
 | 
			
		||||
    :
 | 
			
		||||
} else if DECISION == 3 {       // same here, NOT optimized away
 | 
			
		||||
    :
 | 
			
		||||
} else {
 | 
			
		||||
    :
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Alternatively, turn the optimizer to [`OptimizationLevel::Full`].
 | 
			
		||||
							
								
								
									
										21
									
								
								rhai_engine/rhaibook/engine/optimize/passes.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								rhai_engine/rhaibook/engine/optimize/passes.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
Optimization Passes
 | 
			
		||||
===================
 | 
			
		||||
 | 
			
		||||
{{#include ../../links.md}}
 | 
			
		||||
 | 
			
		||||
[Script optimization] is performed via multiple _passes_.
 | 
			
		||||
Each pass does a specific optimization.
 | 
			
		||||
 | 
			
		||||
The optimization is completed when no passes can simplify the [`AST`] any further.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Built-in Optimization Passes
 | 
			
		||||
----------------------------
 | 
			
		||||
 | 
			
		||||
| Pass                                       | Description                                       |
 | 
			
		||||
| ------------------------------------------ | ------------------------------------------------- |
 | 
			
		||||
| [Dead code elimination](dead-code.md)      | Eliminates code that cannot be reached            |
 | 
			
		||||
| [Constants propagation](constants.md)      | Replaces [constants] with values                  |
 | 
			
		||||
| [Compound assignments rewrite](rewrite.md) | Rewrites assignments into compound assignments    |
 | 
			
		||||
| [Eager operator evaluation](op-eval.md)    | Eagerly calls operators with [constant] arguments |
 | 
			
		||||
| [Eager function evaluation](eager.md)      | Eagerly calls functions with [constant] arguments |
 | 
			
		||||
							
								
								
									
										50
									
								
								rhai_engine/rhaibook/engine/optimize/reoptimize.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								rhai_engine/rhaibook/engine/optimize/reoptimize.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
			
		||||
Re-Optimize an AST
 | 
			
		||||
==================
 | 
			
		||||
 | 
			
		||||
{{#include ../../links.md}}
 | 
			
		||||
 | 
			
		||||
Sometimes it is more efficient to store one single, large script with delimited code blocks guarded by
 | 
			
		||||
constant variables.  This script is compiled once to an [`AST`].
 | 
			
		||||
 | 
			
		||||
Then, depending on the execution environment, constants are passed into the [`Engine`] and the
 | 
			
		||||
[`AST`] is _re_-optimized based on those constants via `Engine::optimize_ast`, effectively pruning
 | 
			
		||||
out unused code sections.
 | 
			
		||||
 | 
			
		||||
The final, optimized [`AST`] is then used for evaluations.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// Compile master script to AST
 | 
			
		||||
let master_ast = engine.compile(
 | 
			
		||||
"
 | 
			
		||||
    fn do_work() {
 | 
			
		||||
        // Constants in scope are also propagated into functions
 | 
			
		||||
        print(SCENARIO);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    switch SCENARIO {
 | 
			
		||||
        1 => do_work(),
 | 
			
		||||
        2 => do_something(),
 | 
			
		||||
        3 => do_something_else(),
 | 
			
		||||
        _ => do_nothing()
 | 
			
		||||
    }
 | 
			
		||||
")?;
 | 
			
		||||
 | 
			
		||||
for n in 0..5_i64 {
 | 
			
		||||
    // Create a new scope - put constants in it to aid optimization
 | 
			
		||||
    let mut scope = Scope::new();
 | 
			
		||||
    scope.push_constant("SCENARIO", n);
 | 
			
		||||
 | 
			
		||||
    // Re-optimize the AST
 | 
			
		||||
    let new_ast = engine.optimize_ast(&scope, master_ast.clone(), OptimizationLevel::Simple);
 | 
			
		||||
 | 
			
		||||
    // Run it
 | 
			
		||||
    engine.run_ast(&new_ast)?;
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```admonish note.small "Constants propagation"
 | 
			
		||||
 | 
			
		||||
Beware that [constants] inside the custom [`Scope`] will also be propagated to [functions] defined
 | 
			
		||||
within the script while normally such [functions] are _pure_ and cannot see [variables]/[constants]
 | 
			
		||||
within the global [`Scope`].
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										47
									
								
								rhai_engine/rhaibook/engine/optimize/rewrite.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								rhai_engine/rhaibook/engine/optimize/rewrite.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
Compound Assignment Rewrite
 | 
			
		||||
===========================
 | 
			
		||||
 | 
			
		||||
{{#include ../../links.md}}
 | 
			
		||||
 | 
			
		||||
```admonish info.side "Avoid cloning"
 | 
			
		||||
 | 
			
		||||
Arguments passed as value are always cloned.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Usually, a _compound assignment_ (e.g. `+=` for append) takes a mutable first parameter
 | 
			
		||||
(i.e. `&mut`) while the corresponding simple [operator] (i.e. `+`) does not.
 | 
			
		||||
 | 
			
		||||
The script optimizer rewrites normal assignments into _compound assignments_ wherever possible in
 | 
			
		||||
order to avoid unnecessary cloning.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
let big = create_some_very_big_type();
 | 
			
		||||
 | 
			
		||||
big = big + 1;
 | 
			
		||||
//    ^ 'big' is cloned here
 | 
			
		||||
 | 
			
		||||
// The above is equivalent to:
 | 
			
		||||
let temp_value = big + 1;
 | 
			
		||||
big = temp_value;
 | 
			
		||||
 | 
			
		||||
big += 1;           // <- 'big' is NOT cloned
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
~~~admonish warning.small "Warning: Simple references only"
 | 
			
		||||
 | 
			
		||||
Only _simple variable references_ are optimized.
 | 
			
		||||
 | 
			
		||||
No [_common sub-expression elimination_](https://en.wikipedia.org/wiki/Common_subexpression_elimination)
 | 
			
		||||
is performed by Rhai.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
x = x + 1;          // <- this statement...
 | 
			
		||||
 | 
			
		||||
x += 1;             // <- ... is rewritten to this
 | 
			
		||||
 | 
			
		||||
x[y] = x[y] + 1;    // <- but this is not,
 | 
			
		||||
                    //    so MUCH slower...
 | 
			
		||||
 | 
			
		||||
x[y] += 1;          // <- ... than this
 | 
			
		||||
```
 | 
			
		||||
~~~
 | 
			
		||||
							
								
								
									
										85
									
								
								rhai_engine/rhaibook/engine/optimize/semantics.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								rhai_engine/rhaibook/engine/optimize/semantics.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,85 @@
 | 
			
		||||
Subtle Semantic Changes After Optimization
 | 
			
		||||
==========================================
 | 
			
		||||
 | 
			
		||||
{{#include ../../links.md}}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Some optimizations can alter subtle semantics of the script, causing the script to behave
 | 
			
		||||
differently when run with or without optimization.
 | 
			
		||||
 | 
			
		||||
Typically, this involves some form of error that may arise in the original, unoptimized script but
 | 
			
		||||
is optimized away by the [script optimizer][script optimization].
 | 
			
		||||
 | 
			
		||||
```admonish danger.small "DO NOT depend on runtime errors"
 | 
			
		||||
 | 
			
		||||
Needless to say, it is usually a _Very Bad Idea™_ to depend on a script failing with a runtime error
 | 
			
		||||
or such kind of subtleties.
 | 
			
		||||
 | 
			
		||||
If it turns out to be necessary (why? I would never guess), turn script optimization off by setting
 | 
			
		||||
the optimization level to [`OptimizationLevel::None`].
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Disappearing Runtime Errors
 | 
			
		||||
---------------------------
 | 
			
		||||
 | 
			
		||||
For example:
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
if true {           // condition always true
 | 
			
		||||
    123.456;        // eliminated
 | 
			
		||||
    hello;          // eliminated, EVEN THOUGH the variable doesn't exist!
 | 
			
		||||
    foo(42)         // promoted up-level
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
foo(42)             // <- the above optimizes to this
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
If the original script were evaluated instead, it would have been an error –
 | 
			
		||||
the variable `hello` does not exist, so the script would have been terminated at that point
 | 
			
		||||
with a runtime error.
 | 
			
		||||
 | 
			
		||||
In fact, any errors inside a statement that has been eliminated will silently _disappear_.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
print("start!");
 | 
			
		||||
if my_decision { /* do nothing... */ }  // eliminated due to no effect
 | 
			
		||||
print("end!");
 | 
			
		||||
 | 
			
		||||
// The above optimizes to:
 | 
			
		||||
 | 
			
		||||
print("start!");
 | 
			
		||||
print("end!");
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
In the script above, if `my_decision` holds anything other than a boolean value,
 | 
			
		||||
the script should have been terminated due to a type error.
 | 
			
		||||
 | 
			
		||||
However, after optimization, the entire [`if`] statement is removed (because an access to
 | 
			
		||||
`my_decision` produces no side-effects), thus the script silently runs to completion without errors.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Eliminated Useless Work
 | 
			
		||||
-----------------------
 | 
			
		||||
 | 
			
		||||
Another example is more subtle – that of an empty loop body.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// ... say, the 'Engine' is limited to no more than 10,000 operations...
 | 
			
		||||
 | 
			
		||||
// The following should fail because it exceeds the operations limit:
 | 
			
		||||
for n in 0..42000 {
 | 
			
		||||
    // empty loop
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// The above is optimized away because the loop body is empty
 | 
			
		||||
// and the iterations simply do nothing.
 | 
			
		||||
()
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Normally, and empty loop body inside a [`for`] statement with a pure iterator does nothing and can
 | 
			
		||||
be safely eliminated.
 | 
			
		||||
 | 
			
		||||
Thus the script now runs silently to completion without errors.
 | 
			
		||||
 | 
			
		||||
Without optimization, the script may fail by exceeding the [maximum number of operations] allowed.
 | 
			
		||||
							
								
								
									
										23
									
								
								rhai_engine/rhaibook/engine/optimize/side-effects.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								rhai_engine/rhaibook/engine/optimize/side-effects.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
Side-Effect Considerations for Full Optimization Level
 | 
			
		||||
======================================================
 | 
			
		||||
 | 
			
		||||
{{#include ../../links.md}}
 | 
			
		||||
 | 
			
		||||
All of Rhai's built-in functions (and operators which are implemented as functions) are _pure_
 | 
			
		||||
(i.e. they do not mutate state nor cause any side-effects, with the exception of `print` and `debug`
 | 
			
		||||
which are handled specially) so using [`OptimizationLevel::Full`] is usually quite safe _unless_
 | 
			
		||||
custom types and functions are registered.
 | 
			
		||||
 | 
			
		||||
If custom functions are registered, they _may_ be called (or maybe not, if the calls happen to lie
 | 
			
		||||
within a pruned code block).
 | 
			
		||||
 | 
			
		||||
If custom functions are registered to overload [built-in operators], they will also be called when
 | 
			
		||||
the operators are used (in an [`if`] statement, for example), potentially causing side-effects.
 | 
			
		||||
 | 
			
		||||
```admonish tip.small "Rule of thumb"
 | 
			
		||||
 | 
			
		||||
* _Always_ register custom types and functions _after_ compiling scripts if [`OptimizationLevel::Full`] is used.
 | 
			
		||||
 | 
			
		||||
* _DO NOT_ depend on knowledge that the functions have no side-effects, because those functions can change later on and,
 | 
			
		||||
  when that happens, existing scripts may break in subtle ways.
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										53
									
								
								rhai_engine/rhaibook/engine/optimize/volatility.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								rhai_engine/rhaibook/engine/optimize/volatility.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
Volatility Considerations for Full Optimization Level
 | 
			
		||||
=====================================================
 | 
			
		||||
 | 
			
		||||
{{#include ../../links.md}}
 | 
			
		||||
 | 
			
		||||
Even if a custom function does not mutate state nor cause side-effects, it may still be _volatile_,
 | 
			
		||||
i.e. it _depends_ on external environment and does not guarantee the same result for the same inputs.
 | 
			
		||||
 | 
			
		||||
A perfect example is a function that gets the current time – obviously each run will return a
 | 
			
		||||
different value!
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
print(get_current_time(true));      // prints the current time
 | 
			
		||||
                                    // notice the call to 'get_current_time'
 | 
			
		||||
                                    // has constant arguments
 | 
			
		||||
 | 
			
		||||
// The above, under full optimization level, is rewritten to:
 | 
			
		||||
 | 
			
		||||
print("10:25AM");                   // the function call is replaced by
 | 
			
		||||
                                    // its result at the time of optimization!
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```admonish danger.small "Warning"
 | 
			
		||||
 | 
			
		||||
**Avoid using [`OptimizationLevel::Full`]** if volatile custom functions are involved.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The optimizer, when using [`OptimizationLevel::Full`], _merrily assumes_ that all functions are
 | 
			
		||||
_non-volatile_, so when it finds [constant] arguments (or none) it eagerly executes the function
 | 
			
		||||
call and replaces it with the result.
 | 
			
		||||
 | 
			
		||||
This causes the script to behave differently from the intended semantics.
 | 
			
		||||
 | 
			
		||||
~~~admonish tip "Tip: Mark a function as volatile"
 | 
			
		||||
 | 
			
		||||
All native functions are assumed to be **non-volatile**, meaning that they are eagerly called under
 | 
			
		||||
[`OptimizationLevel::Full`] when all arguments are [constant] (or none).
 | 
			
		||||
 | 
			
		||||
It is possible to [mark a function defined within a plugin module as volatile]({{rootUrl}}/plugins/module.md#volatile-functions)
 | 
			
		||||
to prevent this behavior.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
#[export_module]
 | 
			
		||||
mod my_module {
 | 
			
		||||
    // This function is marked 'volatile' and will not be
 | 
			
		||||
    // eagerly executed even under OptimizationLevel::Full.
 | 
			
		||||
    #[rhai_fn(volatile)]
 | 
			
		||||
    pub get_current_time(am_pm: bool) -> String {
 | 
			
		||||
        // ...
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
~~~
 | 
			
		||||
		Reference in New Issue
	
	Block a user