Eagerly evaluate built-in operators for OptimizationLevel::Simple.

This commit is contained in:
Stephen Chung
2020-10-05 10:27:31 +08:00
parent b91a073596
commit 0d0affd5e9
11 changed files with 182 additions and 104 deletions

View File

@@ -439,7 +439,33 @@ impl Engine {
}
// Has a system function an override?
fn has_override(&self, lib: &Module, hash_fn: u64, hash_script: u64, pub_only: bool) -> bool {
pub(crate) fn has_override_by_name_and_arguments(
&self,
lib: &Module,
name: &str,
arg_types: &[TypeId],
pub_only: bool,
) -> bool {
let arg_len = if arg_types.is_empty() {
usize::MAX
} else {
arg_types.len()
};
let hash_fn = calc_fn_hash(empty(), name, arg_len, arg_types.iter().cloned());
let hash_script = calc_fn_hash(empty(), name, arg_types.len(), empty());
self.has_override(lib, hash_fn, hash_script, pub_only)
}
// Has a system function an override?
pub(crate) fn has_override(
&self,
lib: &Module,
hash_fn: u64,
hash_script: u64,
pub_only: bool,
) -> bool {
// NOTE: We skip script functions for global_module and packages, and native functions for lib
// First check script-defined functions

View File

@@ -294,6 +294,23 @@ impl Module {
hash_script
}
/// Get a script-defined function in the module based on name and number of parameters.
pub fn get_script_fn(
&self,
name: &str,
num_params: usize,
public_only: bool,
) -> Option<&Shared<ScriptFnDef>> {
self.functions
.values()
.find(|(fn_name, access, num, _, _)| {
(!public_only || *access == FnAccess::Public)
&& *num == num_params
&& fn_name == name
})
.map(|(_, _, _, _, f)| f.get_shared_fn_def())
}
/// Does a sub-module exist in the module?
///
/// # Examples

View File

@@ -5,6 +5,7 @@ use crate::calc_fn_hash;
use crate::engine::{
Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
};
use crate::fn_call::run_builtin_binary_op;
use crate::fn_native::FnPtr;
use crate::module::Module;
use crate::parser::{map_dynamic_to_expr, Expr, ScriptFnDef, Stmt, AST};
@@ -568,58 +569,75 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
}
}
// Call built-in functions
Expr::FnCall(mut x)
if x.1.is_none() // Non-qualified
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations
&& x.3.len() == 2 // binary call
&& x.3.iter().all(Expr::is_constant) // all arguments are constants
=> {
let ((name, _, _, pos), _, _, args, _) = x.as_mut();
let arg_values: StaticVec<_> = args.iter().map(Expr::get_constant_value).collect();
let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect();
// Search for overloaded operators (can override built-in).
if !state.engine.has_override_by_name_and_arguments(state.lib, name, arg_types.as_ref(), false) {
if let Some(expr) = run_builtin_binary_op(name, &arg_values[0], &arg_values[1])
.ok().flatten()
.and_then(|result| map_dynamic_to_expr(result, *pos))
{
state.set_dirty();
return expr;
}
}
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
Expr::FnCall(x)
}
// Eagerly call functions
Expr::FnCall(mut x)
if x.1.is_none() // Non-qualified
&& state.optimization_level == OptimizationLevel::Full // full optimizations
&& x.3.iter().all(|expr| expr.is_constant()) // all arguments are constants
&& x.3.iter().all(Expr::is_constant) // all arguments are constants
=> {
let ((name, _, _, pos), _, _, args, def_value) = x.as_mut();
// First search in functions lib (can override built-in)
// Cater for both normal function call style and method call style (one additional arguments)
let has_script_fn = cfg!(not(feature = "no_function")) && state.lib.iter_fn().find(|(_, _, _, _,f)| {
if !f.is_script() { return false; }
let fn_def = f.get_fn_def();
fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len())
}).is_some();
// First search for script-defined functions (can override built-in)
let has_script_fn = cfg!(not(feature = "no_function"))
&& state.lib.get_script_fn(name, args.len(), false).is_some();
if has_script_fn {
// A script-defined function overrides the built-in function - do not make the call
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
return Expr::FnCall(x);
if !has_script_fn {
let mut arg_values: StaticVec<_> = args.iter().map(Expr::get_constant_value).collect();
// Save the typename of the first argument if it is `type_of()`
// This is to avoid `call_args` being passed into the closure
let arg_for_type_of = if name == KEYWORD_TYPE_OF && arg_values.len() == 1 {
state.engine.map_type_name(arg_values[0].type_name())
} else {
""
};
if let Some(expr) = call_fn_with_constant_arguments(&state, name, arg_values.as_mut())
.or_else(|| {
if !arg_for_type_of.is_empty() {
// Handle `type_of()`
Some(arg_for_type_of.to_string().into())
} else {
// Otherwise use the default value, if any
def_value.map(|v| v.into())
}
})
.and_then(|result| map_dynamic_to_expr(result, *pos))
{
state.set_dirty();
return expr;
}
}
let mut arg_values: StaticVec<_> = args.iter().map(Expr::get_constant_value).collect();
// Save the typename of the first argument if it is `type_of()`
// This is to avoid `call_args` being passed into the closure
let arg_for_type_of = if name == KEYWORD_TYPE_OF && arg_values.len() == 1 {
state.engine.map_type_name(arg_values[0].type_name())
} else {
""
};
call_fn_with_constant_arguments(&state, name, arg_values.as_mut())
.or_else(|| {
if !arg_for_type_of.is_empty() {
// Handle `type_of()`
Some(arg_for_type_of.to_string().into())
} else {
// Otherwise use the default value, if any
def_value.map(|v| v.into())
}
})
.and_then(|result| map_dynamic_to_expr(result, *pos))
.map(|expr| {
state.set_dirty();
expr
})
.unwrap_or_else(|| {
// Optimize function call arguments
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
Expr::FnCall(x)
})
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
Expr::FnCall(x)
}
// id(args ..) -> optimize function call arguments