Merge pull request #712 from schungx/master

Fix bug in optimize_ast.
This commit is contained in:
Stephen Chung 2023-04-10 13:52:26 +08:00 committed by GitHub
commit 1f8592242f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 80 additions and 29 deletions

View File

@ -98,7 +98,7 @@ jobs:
include:
- {os: ubuntu-latest, flags: "--profile unix", experimental: false}
- {os: windows-latest, flags: "--profile windows", experimental: true}
- {os: macos-latest, flags: "--profile macos", experimental: false}
#- {os: macos-latest, flags: "--profile macos", experimental: false}
steps:
- name: Checkout
uses: actions/checkout@v2

View File

@ -6,7 +6,8 @@ use crate::ast::{
SwitchCasesCollection,
};
use crate::engine::{
KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF, OP_NOT,
KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CURRY, KEYWORD_PRINT,
KEYWORD_TYPE_OF, OP_NOT,
};
use crate::eval::{Caches, GlobalRuntimeState};
use crate::func::builtin::get_builtin_binary_op_fn;
@ -816,21 +817,28 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
}
}
Stmt::Expr(expr) => {
optimize_expr(expr, state, false);
// expr(func())
Stmt::Expr(expr) if matches!(**expr, Expr::FnCall(..)) => {
state.set_dirty();
match mem::take(expr.as_mut()) {
Expr::FnCall(x, pos) => *stmt = Stmt::FnCall(x, pos),
_ => unreachable!(),
}
}
// Do not promote until the expression is fully optimized
if !state.is_dirty() && matches!(**expr, Expr::FnCall(..) | Expr::Stmt(..)) {
*stmt = match *mem::take(expr) {
// func(...);
Stmt::Expr(expr) => optimize_expr(expr, state, false),
// func(...)
Stmt::FnCall(..) => {
if let Stmt::FnCall(x, pos) = mem::take(stmt) {
let mut expr = Expr::FnCall(x, pos);
optimize_expr(&mut expr, state, false);
*stmt = match expr {
Expr::FnCall(x, pos) => Stmt::FnCall(x, pos),
// {};
Expr::Stmt(x) if x.is_empty() => Stmt::Noop(x.position()),
// {...};
Expr::Stmt(x) => (*x).into(),
_ => unreachable!(),
};
state.set_dirty();
_ => Stmt::Expr(expr.into()),
}
} else {
unreachable!();
}
}
@ -1104,7 +1112,6 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
// Fn
Expr::FnCall(x, pos)
if !x.is_qualified() // Non-qualified
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations
&& x.args.len() == 1
&& x.name == KEYWORD_FN_PTR
&& x.constant_args()
@ -1121,10 +1128,23 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
optimize_expr(&mut x.args[0], state, false);
}
}
// curry(FnPtr, constants...)
Expr::FnCall(x, pos)
if !x.is_qualified() // Non-qualified
&& x.args.len() >= 2
&& x.name == KEYWORD_FN_PTR_CURRY
&& matches!(x.args[0], Expr::DynamicConstant(ref v, ..) if v.is_fnptr())
&& x.constant_args()
=> {
let mut fn_ptr = x.args[0].get_literal_value().unwrap().cast::<FnPtr>();
fn_ptr.extend(x.args.iter().skip(1).map(|arg_expr| arg_expr.get_literal_value().unwrap()));
state.set_dirty();
*expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos);
}
// Do not call some special keywords
// Do not call some special keywords that may have side effects
Expr::FnCall(x, ..) if DONT_EVAL_KEYWORDS.contains(&x.name.as_str()) => {
x.args.iter_mut().for_each(|a| optimize_expr(a, state, false));
x.args.iter_mut().for_each(|arg_expr| optimize_expr(arg_expr, state, false));
}
// Call built-in operators
@ -1133,7 +1153,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations
&& x.constant_args() // all arguments are constants
=> {
let arg_values = &mut x.args.iter().map(|e| e.get_literal_value().unwrap()).collect::<StaticVec<_>>();
let arg_values = &mut x.args.iter().map(|arg_expr| arg_expr.get_literal_value().unwrap()).collect::<StaticVec<_>>();
let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect();
match x.name.as_str() {
@ -1165,10 +1185,10 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
_ => ()
}
x.args.iter_mut().for_each(|a| optimize_expr(a, state, false));
x.args.iter_mut().for_each(|arg_expr| optimize_expr(arg_expr, state, false));
// Move constant arguments
x.args.iter_mut().for_each(|arg| match arg {
x.args.iter_mut().for_each(|arg_expr| match arg_expr {
Expr::DynamicConstant(..) | Expr::Unit(..)
| Expr::StringConstant(..) | Expr::CharConstant(..)
| Expr::BoolConstant(..) | Expr::IntegerConstant(..) => (),
@ -1176,9 +1196,9 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
#[cfg(not(feature = "no_float"))]
Expr:: FloatConstant(..) => (),
_ => if let Some(value) = arg.get_literal_value() {
_ => if let Some(value) = arg_expr.get_literal_value() {
state.set_dirty();
*arg = Expr::DynamicConstant(value.into(), arg.start_position());
*arg_expr = Expr::DynamicConstant(value.into(), arg_expr.start_position());
},
});
}
@ -1216,11 +1236,11 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
}
// id(args ..) or xxx.id(args ..) -> optimize function call arguments
Expr::FnCall(x, ..) | Expr::MethodCall(x, ..) => x.args.iter_mut().for_each(|arg| {
optimize_expr(arg, state, false);
Expr::FnCall(x, ..) | Expr::MethodCall(x, ..) => x.args.iter_mut().for_each(|arg_expr| {
optimize_expr(arg_expr, state, false);
// Move constant arguments
match arg {
match arg_expr {
Expr::DynamicConstant(..) | Expr::Unit(..)
| Expr::StringConstant(..) | Expr::CharConstant(..)
| Expr::BoolConstant(..) | Expr::IntegerConstant(..) => (),
@ -1228,9 +1248,9 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
#[cfg(not(feature = "no_float"))]
Expr:: FloatConstant(..) => (),
_ => if let Some(value) = arg.get_literal_value() {
_ => if let Some(value) = arg_expr.get_literal_value() {
state.set_dirty();
*arg = Expr::DynamicConstant(value.into(), arg.start_position());
*arg_expr = Expr::DynamicConstant(value.into(), arg_expr.start_position());
},
}
}),
@ -1378,7 +1398,6 @@ impl Engine {
functions.into_iter().for_each(|fn_def| {
let mut fn_def = crate::func::shared_take_or_clone(fn_def);
// Optimize the function body
let body = mem::take(&mut *fn_def.body);

View File

@ -581,3 +581,10 @@ impl IndexMut<usize> for FnPtr {
self.curry.index_mut(index)
}
}
impl Extend<Dynamic> for FnPtr {
#[inline(always)]
fn extend<T: IntoIterator<Item = Dynamic>>(&mut self, iter: T) {
self.curry.extend(iter)
}
}

View File

@ -163,3 +163,28 @@ fn test_optimizer_scope() -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_closure"))]
#[test]
fn test_optimizer_reoptimize() -> Result<(), Box<EvalAltResult>> {
const SCRIPT: &str = "
const FOO = 42;
fn foo() {
let f = || FOO * 2;
call(f)
}
foo()
";
let engine = Engine::new();
let ast = engine.compile(SCRIPT)?;
let scope: Scope = ast.iter_literal_variables(true, false).collect();
let ast = engine.optimize_ast(&scope, ast, OptimizationLevel::Simple);
println!("{ast:#?}");
assert_eq!(engine.eval_ast::<INT>(&ast)?, 84);
Ok(())
}