Do not propagate constants if shadowed.
This commit is contained in:
parent
aff207d4f4
commit
8e8069f819
@ -1,6 +1,15 @@
|
|||||||
Rhai Release Notes
|
Rhai Release Notes
|
||||||
==================
|
==================
|
||||||
|
|
||||||
|
Version 0.19.8
|
||||||
|
==============
|
||||||
|
|
||||||
|
Bug fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
* Constants are no longer propagated by the optimizer if shadowed by a non-constant variable.
|
||||||
|
|
||||||
|
|
||||||
Version 0.19.7
|
Version 0.19.7
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
//! Module implementing the [`AST`] optimizer.
|
//! Module implementing the [`AST`] optimizer.
|
||||||
|
|
||||||
use crate::ast::{Expr, ScriptFnDef, Stmt};
|
use crate::ast::{Expr, ScriptFnDef, Stmt};
|
||||||
|
use crate::dynamic::AccessType;
|
||||||
use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF};
|
use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF};
|
||||||
use crate::fn_call::run_builtin_binary_op;
|
use crate::fn_call::run_builtin_binary_op;
|
||||||
use crate::parser::map_dynamic_to_expr;
|
use crate::parser::map_dynamic_to_expr;
|
||||||
@ -58,7 +59,7 @@ struct State<'a> {
|
|||||||
/// Has the [`AST`] been changed during this pass?
|
/// Has the [`AST`] been changed during this pass?
|
||||||
changed: bool,
|
changed: bool,
|
||||||
/// Collection of constants to use for eager function evaluations.
|
/// Collection of constants to use for eager function evaluations.
|
||||||
constants: Vec<(String, Expr)>,
|
variables: Vec<(String, AccessType, Expr)>,
|
||||||
/// An [`Engine`] instance for eager function evaluation.
|
/// An [`Engine`] instance for eager function evaluation.
|
||||||
engine: &'a Engine,
|
engine: &'a Engine,
|
||||||
/// [Module] containing script-defined functions.
|
/// [Module] containing script-defined functions.
|
||||||
@ -73,7 +74,7 @@ impl<'a> State<'a> {
|
|||||||
pub fn new(engine: &'a Engine, lib: &'a [&'a Module], level: OptimizationLevel) -> Self {
|
pub fn new(engine: &'a Engine, lib: &'a [&'a Module], level: OptimizationLevel) -> Self {
|
||||||
Self {
|
Self {
|
||||||
changed: false,
|
changed: false,
|
||||||
constants: vec![],
|
variables: vec![],
|
||||||
engine,
|
engine,
|
||||||
lib,
|
lib,
|
||||||
optimization_level: level,
|
optimization_level: level,
|
||||||
@ -94,27 +95,26 @@ impl<'a> State<'a> {
|
|||||||
pub fn is_dirty(&self) -> bool {
|
pub fn is_dirty(&self) -> bool {
|
||||||
self.changed
|
self.changed
|
||||||
}
|
}
|
||||||
/// Does a constant exist?
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn contains_constant(&self, name: &str) -> bool {
|
|
||||||
self.constants.iter().any(|(n, _)| n == name)
|
|
||||||
}
|
|
||||||
/// Prune the list of constants back to a specified size.
|
/// Prune the list of constants back to a specified size.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn restore_constants(&mut self, len: usize) {
|
pub fn restore_var(&mut self, len: usize) {
|
||||||
self.constants.truncate(len)
|
self.variables.truncate(len)
|
||||||
}
|
}
|
||||||
/// Add a new constant to the list.
|
/// Add a new constant to the list.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn push_constant(&mut self, name: &str, value: Expr) {
|
pub fn push_var(&mut self, name: &str, access: AccessType, value: Expr) {
|
||||||
self.constants.push((name.into(), value))
|
self.variables.push((name.into(), access, value))
|
||||||
}
|
}
|
||||||
/// Look up a constant from the list.
|
/// Look up a constant from the list.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn find_constant(&self, name: &str) -> Option<&Expr> {
|
pub fn find_constant(&self, name: &str) -> Option<&Expr> {
|
||||||
for (n, expr) in self.constants.iter().rev() {
|
for (n, access, expr) in self.variables.iter().rev() {
|
||||||
if n == name {
|
if n == name {
|
||||||
return Some(expr);
|
return if access.is_constant() {
|
||||||
|
Some(expr)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,20 +157,20 @@ fn optimize_stmt_block(
|
|||||||
count_promote_as_dirty: bool,
|
count_promote_as_dirty: bool,
|
||||||
) -> Stmt {
|
) -> Stmt {
|
||||||
let orig_len = statements.len(); // Original number of statements in the block, for change detection
|
let orig_len = statements.len(); // Original number of statements in the block, for change detection
|
||||||
let orig_constants_len = state.constants.len(); // Original number of constants in the state, for restore later
|
let orig_constants_len = state.variables.len(); // Original number of constants in the state, for restore later
|
||||||
|
|
||||||
// Optimize each statement in the block
|
// Optimize each statement in the block
|
||||||
statements.iter_mut().for_each(|stmt| match stmt {
|
statements.iter_mut().for_each(|stmt| match stmt {
|
||||||
// Add constant literals into the state
|
// Add constant literals into the state
|
||||||
Stmt::Const(var_def, Some(expr), _, pos) if expr.is_constant() => {
|
Stmt::Const(var_def, Some(expr), _, _) if expr.is_constant() => {
|
||||||
state.set_dirty();
|
state.push_var(&var_def.name, AccessType::Constant, mem::take(expr));
|
||||||
state.push_constant(&var_def.name, mem::take(expr));
|
|
||||||
*stmt = Stmt::Noop(*pos); // No need to keep constants
|
|
||||||
}
|
}
|
||||||
Stmt::Const(var_def, None, _, pos) => {
|
Stmt::Const(var_def, None, _, _) => {
|
||||||
state.set_dirty();
|
state.push_var(&var_def.name, AccessType::Constant, Expr::Unit(var_def.pos));
|
||||||
state.push_constant(&var_def.name, Expr::Unit(var_def.pos));
|
}
|
||||||
*stmt = Stmt::Noop(*pos); // No need to keep constants
|
// Add variables into the state
|
||||||
|
Stmt::Let(var_def, _, _, _) => {
|
||||||
|
state.push_var(&var_def.name, AccessType::Normal, Expr::Unit(var_def.pos));
|
||||||
}
|
}
|
||||||
// Optimize the statement
|
// Optimize the statement
|
||||||
_ => optimize_stmt(stmt, state, preserve_result),
|
_ => optimize_stmt(stmt, state, preserve_result),
|
||||||
@ -196,7 +196,9 @@ fn optimize_stmt_block(
|
|||||||
|
|
||||||
while let Some(expr) = statements.pop() {
|
while let Some(expr) = statements.pop() {
|
||||||
match expr {
|
match expr {
|
||||||
Stmt::Let(_, expr, _, _) => removed = expr.as_ref().map(Expr::is_pure).unwrap_or(true),
|
Stmt::Let(_, expr, _, _) | Stmt::Const(_, expr, _, _) => {
|
||||||
|
removed = expr.as_ref().map(Expr::is_pure).unwrap_or(true)
|
||||||
|
}
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Import(expr, _, _) => removed = expr.is_pure(),
|
Stmt::Import(expr, _, _) => removed = expr.is_pure(),
|
||||||
_ => {
|
_ => {
|
||||||
@ -241,7 +243,7 @@ fn optimize_stmt_block(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pop the stack and remove all the local constants
|
// Pop the stack and remove all the local constants
|
||||||
state.restore_constants(orig_constants_len);
|
state.restore_var(orig_constants_len);
|
||||||
|
|
||||||
match &statements[..] {
|
match &statements[..] {
|
||||||
// No statements in block - change to No-op
|
// No statements in block - change to No-op
|
||||||
@ -251,6 +253,8 @@ fn optimize_stmt_block(
|
|||||||
}
|
}
|
||||||
// Only one let statement - leave it alone
|
// Only one let statement - leave it alone
|
||||||
[x] if matches!(x, Stmt::Let(_, _, _, _)) => Stmt::Block(statements, pos),
|
[x] if matches!(x, Stmt::Let(_, _, _, _)) => Stmt::Block(statements, pos),
|
||||||
|
// Only one const statement - leave it alone
|
||||||
|
[x] if matches!(x, Stmt::Const(_, _, _, _)) => Stmt::Block(statements, pos),
|
||||||
// Only one import statement - leave it alone
|
// Only one import statement - leave it alone
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
[x] if matches!(x, Stmt::Import(_, _, _)) => Stmt::Block(statements, pos),
|
[x] if matches!(x, Stmt::Import(_, _, _)) => Stmt::Block(statements, pos),
|
||||||
@ -708,7 +712,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
|||||||
Expr::FnCall(x, _) => x.args.iter_mut().for_each(|a| optimize_expr(a, state)),
|
Expr::FnCall(x, _) => x.args.iter_mut().for_each(|a| optimize_expr(a, state)),
|
||||||
|
|
||||||
// constant-name
|
// constant-name
|
||||||
Expr::Variable(x) if x.1.is_none() && state.contains_constant(&x.3.name) => {
|
Expr::Variable(x) if x.1.is_none() && state.find_constant(&x.3.name).is_some() => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
|
|
||||||
// Replace constant with value
|
// Replace constant with value
|
||||||
@ -742,22 +746,23 @@ fn optimize_top_level(
|
|||||||
// Set up the state
|
// Set up the state
|
||||||
let mut state = State::new(engine, lib, level);
|
let mut state = State::new(engine, lib, level);
|
||||||
|
|
||||||
// Add constants from the scope that can be made into a literal into the state
|
// Add constants and variables from the scope
|
||||||
scope
|
scope.iter().for_each(|(name, constant, value)| {
|
||||||
.iter()
|
if !constant {
|
||||||
.filter(|(_, typ, _)| *typ)
|
state.push_var(name, AccessType::Normal, Expr::Unit(Position::NONE));
|
||||||
.for_each(|(name, _, value)| {
|
} else if let Some(val) = map_dynamic_to_expr(value, Position::NONE) {
|
||||||
if let Some(val) = map_dynamic_to_expr(value, Position::NONE) {
|
state.push_var(name, AccessType::Constant, val);
|
||||||
state.push_constant(name, val);
|
} else {
|
||||||
}
|
state.push_var(name, AccessType::Constant, Expr::Unit(Position::NONE));
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let orig_constants_len = state.constants.len();
|
let orig_constants_len = state.variables.len();
|
||||||
|
|
||||||
// Optimization loop
|
// Optimization loop
|
||||||
loop {
|
loop {
|
||||||
state.reset();
|
state.reset();
|
||||||
state.restore_constants(orig_constants_len);
|
state.restore_var(orig_constants_len);
|
||||||
|
|
||||||
let num_statements = statements.len();
|
let num_statements = statements.len();
|
||||||
|
|
||||||
@ -769,7 +774,7 @@ fn optimize_top_level(
|
|||||||
optimize_expr(value_expr, &mut state);
|
optimize_expr(value_expr, &mut state);
|
||||||
|
|
||||||
if value_expr.is_constant() {
|
if value_expr.is_constant() {
|
||||||
state.push_constant(&var_def.name, value_expr.clone());
|
state.push_var(&var_def.name, AccessType::Constant, value_expr.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep it in the global scope
|
// Keep it in the global scope
|
||||||
@ -779,13 +784,16 @@ fn optimize_top_level(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Stmt::Const(var_def, None, _, _) => {
|
Stmt::Const(var_def, None, _, _) => {
|
||||||
state.push_constant(&var_def.name, Expr::Unit(var_def.pos));
|
state.push_var(&var_def.name, AccessType::Constant, Expr::Unit(var_def.pos));
|
||||||
|
}
|
||||||
|
Stmt::Let(var_def, _, _, _) => {
|
||||||
|
state.push_var(&var_def.name, AccessType::Normal, Expr::Unit(var_def.pos));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Keep all variable declarations at this level
|
// Keep all variable declarations at this level
|
||||||
// and always keep the last return value
|
// and always keep the last return value
|
||||||
let keep = match stmt {
|
let keep = match stmt {
|
||||||
Stmt::Let(_, _, _, _) => true,
|
Stmt::Let(_, _, _, _) | Stmt::Const(_, _, _, _) => true,
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Import(_, _, _) => true,
|
Stmt::Import(_, _, _) => true,
|
||||||
_ => i == num_statements - 1,
|
_ => i == num_statements - 1,
|
||||||
|
@ -53,15 +53,19 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
|
|||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
engine.set_optimization_level(OptimizationLevel::Simple);
|
engine.set_optimization_level(OptimizationLevel::Simple);
|
||||||
|
|
||||||
let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } }")?;
|
let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?;
|
||||||
|
|
||||||
assert!(format!("{:?}", ast).starts_with("AST([], Module("));
|
assert!(format!("{:?}", ast).starts_with(r#"AST([Block([Const(Ident { name: "DECISION", pos: 1:9 }, Some(Unit(0:0)), false, 1:3), Expr(IntegerConstant(123, 1:53))], 1:1)]"#));
|
||||||
|
|
||||||
engine.set_optimization_level(OptimizationLevel::Full);
|
|
||||||
|
|
||||||
let ast = engine.compile("if 1 == 2 { 42 }")?;
|
let ast = engine.compile("if 1 == 2 { 42 }")?;
|
||||||
|
|
||||||
assert!(format!("{:?}", ast).starts_with("AST([], Module("));
|
assert!(format!("{:?}", ast).starts_with("AST([], Module("));
|
||||||
|
|
||||||
|
engine.set_optimization_level(OptimizationLevel::Full);
|
||||||
|
|
||||||
|
let ast = engine.compile("abs(-42)")?;
|
||||||
|
|
||||||
|
assert!(format!("{:?}", ast).starts_with(r"AST([Expr(IntegerConstant(42, 1:1))]"));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user