Evaluate function call args more efficiently.

This commit is contained in:
Stephen Chung 2022-01-08 18:40:19 +08:00
parent afb651d0aa
commit f399e8a905
2 changed files with 92 additions and 69 deletions

View File

@ -229,9 +229,14 @@ impl Engine {
) )
} else { } else {
// Normal function call // Normal function call
let (first_arg, args) = args.split_first().map_or_else(
|| (None, args.as_ref()),
|(first, rest)| (Some(first), rest),
);
self.make_function_call( self.make_function_call(
scope, global, state, lib, this_ptr, name, args, constants, *hashes, pos, *capture, scope, global, state, lib, this_ptr, name, first_arg, args, constants, *hashes,
level, pos, *capture, level,
) )
} }
} }

View File

@ -887,12 +887,16 @@ impl Engine {
arg_expr: &Expr, arg_expr: &Expr,
constants: &[Dynamic], constants: &[Dynamic],
) -> RhaiResultOf<(Dynamic, Position)> { ) -> RhaiResultOf<(Dynamic, Position)> {
match arg_expr { Ok((
Expr::Stack(slot, pos) => Ok((constants[*slot].clone(), *pos)), if let Expr::Stack(slot, _) = arg_expr {
ref arg => self constants[*slot].clone()
.eval_expr(scope, global, state, lib, this_ptr, arg, level) } else if let Some(value) = arg_expr.get_literal_value() {
.map(|v| (v, arg.position())), value
} } else {
self.eval_expr(scope, global, state, lib, this_ptr, arg_expr, level)?
},
arg_expr.position(),
))
} }
/// Call a function in normal function-call style. /// Call a function in normal function-call style.
@ -904,6 +908,7 @@ impl Engine {
lib: &[&Module], lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
fn_name: &str, fn_name: &str,
first_arg: Option<&Expr>,
args_expr: &[Expr], args_expr: &[Expr],
constants: &[Dynamic], constants: &[Dynamic],
hashes: FnCallHashes, hashes: FnCallHashes,
@ -911,8 +916,9 @@ impl Engine {
capture_scope: bool, capture_scope: bool,
level: usize, level: usize,
) -> RhaiResult { ) -> RhaiResult {
let mut first_arg = first_arg;
let mut a_expr = args_expr; let mut a_expr = args_expr;
let mut total_args = a_expr.len(); let mut total_args = if first_arg.is_some() { 1 } else { 0 } + a_expr.len();
let mut curry = FnArgsVec::new_const(); let mut curry = FnArgsVec::new_const();
let mut name = fn_name; let mut name = fn_name;
let mut hashes = hashes; let mut hashes = hashes;
@ -921,26 +927,29 @@ impl Engine {
match name { match name {
// Handle call() // Handle call()
KEYWORD_FN_PTR_CALL if total_args >= 1 => { KEYWORD_FN_PTR_CALL if total_args >= 1 => {
let (arg, arg_pos) = self.get_arg_value( let arg = first_arg.unwrap();
scope, global, state, lib, this_ptr, level, &a_expr[0], constants, let (arg_value, arg_pos) =
)?; self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?;
if !arg.is::<FnPtr>() { if !arg_value.is::<FnPtr>() {
return Err(self.make_type_mismatch_err::<FnPtr>( return Err(self.make_type_mismatch_err::<FnPtr>(
self.map_type_name(arg.type_name()), self.map_type_name(arg_value.type_name()),
arg_pos, arg_pos,
)); ));
} }
let fn_ptr = arg.cast::<FnPtr>(); let fn_ptr = arg_value.cast::<FnPtr>();
curry.extend(fn_ptr.curry().iter().cloned()); curry.extend(fn_ptr.curry().iter().cloned());
// Redirect function name // Redirect function name
redirected = fn_ptr.take_data().0; redirected = fn_ptr.take_data().0;
name = &redirected; name = &redirected;
// Skip the first argument // Shift the arguments
a_expr = &a_expr[1..]; first_arg = a_expr.get(0);
if !a_expr.is_empty() {
a_expr = &a_expr[1..];
}
total_args -= 1; total_args -= 1;
// Recalculate hash // Recalculate hash
@ -953,12 +962,12 @@ impl Engine {
} }
// Handle Fn() // Handle Fn()
KEYWORD_FN_PTR if total_args == 1 => { KEYWORD_FN_PTR if total_args == 1 => {
let (arg, arg_pos) = self.get_arg_value( let arg = first_arg.unwrap();
scope, global, state, lib, this_ptr, level, &a_expr[0], constants, let (arg_value, arg_pos) =
)?; self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?;
// Fn - only in function call style // Fn - only in function call style
return arg return arg_value
.into_immutable_string() .into_immutable_string()
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos)) .map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))
.and_then(FnPtr::try_from) .and_then(FnPtr::try_from)
@ -968,30 +977,30 @@ impl Engine {
// Handle curry() // Handle curry()
KEYWORD_FN_PTR_CURRY if total_args > 1 => { KEYWORD_FN_PTR_CURRY if total_args > 1 => {
let (arg, arg_pos) = self.get_arg_value( let first = first_arg.unwrap();
scope, global, state, lib, this_ptr, level, &a_expr[0], constants, let (arg_value, arg_pos) = self
)?; .get_arg_value(scope, global, state, lib, this_ptr, level, first, constants)?;
if !arg.is::<FnPtr>() { if !arg_value.is::<FnPtr>() {
return Err(self.make_type_mismatch_err::<FnPtr>( return Err(self.make_type_mismatch_err::<FnPtr>(
self.map_type_name(arg.type_name()), self.map_type_name(arg_value.type_name()),
arg_pos, arg_pos,
)); ));
} }
let (name, fn_curry) = arg.cast::<FnPtr>().take_data(); let (name, fn_curry) = arg_value.cast::<FnPtr>().take_data();
// Append the new curried arguments to the existing list. // Append the new curried arguments to the existing list.
let fn_curry = a_expr.iter().skip(1).try_fold( let fn_curry =
fn_curry, a_expr
|mut curried, expr| -> RhaiResultOf<_> { .iter()
let (value, _) = self.get_arg_value( .try_fold(fn_curry, |mut curried, expr| -> RhaiResultOf<_> {
scope, global, state, lib, this_ptr, level, expr, constants, let (value, _) = self.get_arg_value(
)?; scope, global, state, lib, this_ptr, level, expr, constants,
curried.push(value); )?;
Ok(curried) curried.push(value);
}, Ok(curried)
)?; })?;
return Ok(FnPtr::new_unchecked(name, fn_curry).into()); return Ok(FnPtr::new_unchecked(name, fn_curry).into());
} }
@ -999,28 +1008,28 @@ impl Engine {
// Handle is_shared() // Handle is_shared()
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
crate::engine::KEYWORD_IS_SHARED if total_args == 1 => { crate::engine::KEYWORD_IS_SHARED if total_args == 1 => {
let (arg, _) = self.get_arg_value( let arg = first_arg.unwrap();
scope, global, state, lib, this_ptr, level, &a_expr[0], constants, let (arg_value, _) =
)?; self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?;
return Ok(arg.is_shared().into()); return Ok(arg_value.is_shared().into());
} }
// Handle is_def_fn() // Handle is_def_fn()
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => { crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => {
let (arg, arg_pos) = self.get_arg_value( let first = first_arg.unwrap();
scope, global, state, lib, this_ptr, level, &a_expr[0], constants, let (arg_value, arg_pos) = self
)?; .get_arg_value(scope, global, state, lib, this_ptr, level, first, constants)?;
let fn_name = arg let fn_name = arg_value
.into_immutable_string() .into_immutable_string()
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?; .map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?;
let (arg, arg_pos) = self.get_arg_value( let (arg_value, arg_pos) = self.get_arg_value(
scope, global, state, lib, this_ptr, level, &a_expr[1], constants, scope, global, state, lib, this_ptr, level, &a_expr[0], constants,
)?; )?;
let num_params = arg let num_params = arg_value
.as_int() .as_int()
.map_err(|typ| self.make_type_mismatch_err::<crate::INT>(typ, arg_pos))?; .map_err(|typ| self.make_type_mismatch_err::<crate::INT>(typ, arg_pos))?;
@ -1035,10 +1044,10 @@ impl Engine {
// Handle is_def_var() // Handle is_def_var()
KEYWORD_IS_DEF_VAR if total_args == 1 => { KEYWORD_IS_DEF_VAR if total_args == 1 => {
let (arg, arg_pos) = self.get_arg_value( let arg = first_arg.unwrap();
scope, global, state, lib, this_ptr, level, &a_expr[0], constants, let (arg_value, arg_pos) =
)?; self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?;
let var_name = arg let var_name = arg_value
.into_immutable_string() .into_immutable_string()
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?; .map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?;
return Ok(scope.contains(&var_name).into()); return Ok(scope.contains(&var_name).into());
@ -1048,10 +1057,10 @@ impl Engine {
KEYWORD_EVAL if total_args == 1 => { KEYWORD_EVAL if total_args == 1 => {
// eval - only in function call style // eval - only in function call style
let orig_scope_len = scope.len(); let orig_scope_len = scope.len();
let (value, pos) = self.get_arg_value( let arg = first_arg.unwrap();
scope, global, state, lib, this_ptr, level, &a_expr[0], constants, let (arg_value, pos) =
)?; self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?;
let script = &value let script = &arg_value
.into_immutable_string() .into_immutable_string()
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, pos))?; .map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, pos))?;
let result = self.eval_script_expr_in_place( let result = self.eval_script_expr_in_place(
@ -1085,8 +1094,8 @@ impl Engine {
} }
// Normal function call - except for Fn, curry, call and eval (handled above) // Normal function call - except for Fn, curry, call and eval (handled above)
let mut arg_values = FnArgsVec::with_capacity(a_expr.len()); let mut arg_values = FnArgsVec::with_capacity(total_args);
let mut args = FnArgsVec::with_capacity(a_expr.len() + curry.len()); let mut args = FnArgsVec::with_capacity(total_args + curry.len());
let mut is_ref_mut = false; let mut is_ref_mut = false;
// Capture parent scope? // Capture parent scope?
@ -1094,10 +1103,14 @@ impl Engine {
// If so, do it separately because we cannot convert the first argument (if it is a simple // If so, do it separately because we cannot convert the first argument (if it is a simple
// variable access) to &mut because `scope` is needed. // variable access) to &mut because `scope` is needed.
if capture_scope && !scope.is_empty() { if capture_scope && !scope.is_empty() {
a_expr.iter().try_for_each(|expr| { first_arg
self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants) .iter()
.map(|(value, _)| arg_values.push(value.flatten())) .map(|&v| v)
})?; .chain(a_expr.iter())
.try_for_each(|expr| {
self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants)
.map(|(value, _)| arg_values.push(value.flatten()))
})?;
args.extend(curry.iter_mut()); args.extend(curry.iter_mut());
args.extend(arg_values.iter_mut()); args.extend(arg_values.iter_mut());
@ -1113,17 +1126,17 @@ impl Engine {
} }
// Call with blank scope // Call with blank scope
if a_expr.is_empty() && curry.is_empty() { if total_args == 0 && curry.is_empty() {
// No arguments // No arguments
} else { } else {
// If the first argument is a variable, and there is no curried arguments, // If the first argument is a variable, and there is no curried arguments,
// convert to method-call style in order to leverage potential &mut first argument and // convert to method-call style in order to leverage potential &mut first argument and
// avoid cloning the value // avoid cloning the value
if curry.is_empty() && !a_expr.is_empty() && a_expr[0].is_variable_access(false) { if curry.is_empty() && first_arg.map_or(false, |expr| expr.is_variable_access(false)) {
// func(x, ...) -> x.func(...) // func(x, ...) -> x.func(...)
let (first_expr, rest_expr) = a_expr.split_first().unwrap(); let first_expr = first_arg.unwrap();
rest_expr.iter().try_for_each(|expr| { a_expr.iter().try_for_each(|expr| {
self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants) self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants)
.map(|(value, _)| arg_values.push(value.flatten())) .map(|(value, _)| arg_values.push(value.flatten()))
})?; })?;
@ -1155,10 +1168,15 @@ impl Engine {
} }
} else { } else {
// func(..., ...) // func(..., ...)
a_expr.iter().try_for_each(|expr| { first_arg
self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants) .into_iter()
.chain(a_expr.iter())
.try_for_each(|expr| {
self.get_arg_value(
scope, global, state, lib, this_ptr, level, expr, constants,
)
.map(|(value, _)| arg_values.push(value.flatten())) .map(|(value, _)| arg_values.push(value.flatten()))
})?; })?;
args.extend(curry.iter_mut()); args.extend(curry.iter_mut());
args.extend(arg_values.iter_mut()); args.extend(arg_values.iter_mut());
} }