Avoid repeating empty TypeId's when calculating hash.

This commit is contained in:
Stephen Chung 2020-05-19 20:07:51 +08:00
parent 3295060dba
commit ab76a69b12
5 changed files with 81 additions and 80 deletions

View File

@ -12,7 +12,7 @@ use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
use crate::result::EvalAltResult;
use crate::scope::{EntryType as ScopeEntryType, Scope};
use crate::token::Position;
use crate::utils::{StaticVec, EMPTY_TYPE_ID};
use crate::utils::StaticVec;
#[cfg(not(feature = "no_module"))]
use crate::module::{resolvers, ModuleRef, ModuleResolver};
@ -25,7 +25,7 @@ use crate::stdlib::{
boxed::Box,
collections::HashMap,
format,
iter::{empty, once, repeat},
iter::{empty, once},
mem,
num::NonZeroUsize,
ops::{Deref, DerefMut},
@ -215,8 +215,7 @@ impl<'a> State<'a> {
/// Since script-defined functions have `Dynamic` parameters, functions with the same name
/// and number of parameters are considered equivalent.
///
/// The key of the `HashMap` is a `u64` hash calculated by the function `calc_fn_hash`
/// with dummy parameter types `EMPTY_TYPE_ID()` repeated the correct number of times.
/// The key of the `HashMap` is a `u64` hash calculated by the function `calc_fn_hash`.
#[derive(Debug, Clone, Default)]
pub struct FunctionsLib(HashMap<u64, SharedFnDef>);
@ -226,9 +225,8 @@ impl FunctionsLib {
FunctionsLib(
vec.into_iter()
.map(|fn_def| {
// Qualifiers (none) + function name + placeholders (one for each parameter).
let args_iter = repeat(EMPTY_TYPE_ID()).take(fn_def.params.len());
let hash = calc_fn_hash(empty(), &fn_def.name, args_iter);
// Qualifiers (none) + function name + number of arguments.
let hash = calc_fn_hash(empty(), &fn_def.name, fn_def.params.len(), empty());
(hash, fn_def.into())
})
.collect(),
@ -253,8 +251,8 @@ impl FunctionsLib {
params: usize,
public_only: bool,
) -> Option<&FnDef> {
// Qualifiers (none) + function name + placeholders (one for each parameter).
let hash_fn_def = calc_fn_hash(empty(), name, repeat(EMPTY_TYPE_ID()).take(params));
// Qualifiers (none) + function name + number of arguments.
let hash_fn_def = calc_fn_hash(empty(), name, params, empty());
let fn_def = self.get_function(hash_fn_def);
match fn_def.as_ref().map(|f| f.access) {
@ -873,8 +871,13 @@ impl Engine {
pos: Position,
level: usize,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
// Qualifiers (none) + function name + argument `TypeId`'s.
let hash_fn = calc_fn_hash(empty(), fn_name, args.iter().map(|a| a.type_id()));
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
let hash_fn = calc_fn_hash(
empty(),
fn_name,
args.len(),
args.iter().map(|a| a.type_id()),
);
let hashes = (hash_fn, hash_fn_def);
match fn_name {
@ -1322,7 +1325,7 @@ impl Engine {
Dynamic(Union::Array(mut rhs_value)) => {
let op = "==";
let def_value = false.into();
let hash_fn_def = calc_fn_hash(empty(), op, repeat(EMPTY_TYPE_ID()).take(2));
let hash_fn_def = calc_fn_hash(empty(), op, 2, empty());
// Call the `==` operator to compare each value
for value in rhs_value.iter_mut() {
@ -1331,7 +1334,8 @@ impl Engine {
let pos = rhs.position();
// Qualifiers (none) + function name + argument `TypeId`'s.
let hash_fn = calc_fn_hash(empty(), op, args.iter().map(|a| a.type_id()));
let hash_fn =
calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id()));
let hashes = (hash_fn, hash_fn_def);
let (r, _) = self
@ -1486,7 +1490,7 @@ impl Engine {
let mut args: StaticVec<_> = arg_values.iter_mut().collect();
if name == KEYWORD_EVAL && args.len() == 1 && args.get(0).is::<String>() {
let hash_fn = calc_fn_hash(empty(), name, once(TypeId::of::<String>()));
let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::<String>()));
if !self.has_override(state, (hash_fn, *hash_fn_def)) {
// eval - only in function call style
@ -1547,11 +1551,11 @@ impl Engine {
// Rust functions are indexed in two steps:
// 1) Calculate a hash in a similar manner to script-defined functions,
// i.e. qualifiers + function name + dummy parameter types (one for each parameter).
// 2) Calculate a second hash with no qualifiers, empty function name, and
// the actual list of parameter `TypeId`'.s
// i.e. qualifiers + function name + number of arguments.
// 2) Calculate a second hash with no qualifiers, empty function name,
// zero number of arguments, and the actual list of argument `TypeId`'.s
let hash_fn_args =
calc_fn_hash(empty(), "", args.iter().map(|a| a.type_id()));
calc_fn_hash(empty(), "", 0, args.iter().map(|a| a.type_id()));
// 3) The final hash is the XOR of the two hashes.
let hash_fn_native = *hash_fn_def ^ hash_fn_args;

View File

@ -8,18 +8,22 @@ use crate::fn_native::{
CallableFunction::{Method, Pure},
FnCallArgs, IteratorFn, SharedFunction,
};
use crate::parser::{FnAccess, AST};
use crate::parser::{
FnAccess,
FnAccess::{Private, Public},
AST,
};
use crate::result::EvalAltResult;
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
use crate::token::{Position, Token};
use crate::utils::{StaticVec, EMPTY_TYPE_ID};
use crate::utils::StaticVec;
use crate::stdlib::{
any::TypeId,
boxed::Box,
collections::HashMap,
fmt,
iter::{empty, repeat},
iter::empty,
mem,
num::NonZeroUsize,
ops::{Deref, DerefMut},
@ -30,9 +34,6 @@ use crate::stdlib::{
vec::Vec,
};
/// Default function access mode.
const DEF_ACCESS: FnAccess = FnAccess::Public;
/// Return type of module-level Rust function.
pub type FuncReturn<T> = Result<T, Box<EvalAltResult>>;
@ -281,7 +282,7 @@ impl Module {
params: &[TypeId],
func: CallableFunction,
) -> u64 {
let hash_fn = calc_fn_hash(empty(), &name, params.iter().cloned());
let hash_fn = calc_fn_hash(empty(), &name, params.len(), params.iter().cloned());
let params = params.into_iter().cloned().collect();
@ -312,7 +313,7 @@ impl Module {
) -> u64 {
let f = move |_: &mut FnCallArgs| func().map(Dynamic::from);
let arg_types = [];
self.set_fn(name.into(), DEF_ACCESS, &arg_types, Pure(Box::new(f)))
self.set_fn(name.into(), Public, &arg_types, Pure(Box::new(f)))
}
/// Set a Rust function taking one parameter into the module, returning a hash key.
@ -337,7 +338,7 @@ impl Module {
let f =
move |args: &mut FnCallArgs| func(mem::take(args[0]).cast::<A>()).map(Dynamic::from);
let arg_types = [TypeId::of::<A>()];
self.set_fn(name.into(), DEF_ACCESS, &arg_types, Pure(Box::new(f)))
self.set_fn(name.into(), Public, &arg_types, Pure(Box::new(f)))
}
/// Set a Rust function taking one mutable parameter into the module, returning a hash key.
@ -363,7 +364,7 @@ impl Module {
func(args[0].downcast_mut::<A>().unwrap()).map(Dynamic::from)
};
let arg_types = [TypeId::of::<A>()];
self.set_fn(name.into(), DEF_ACCESS, &arg_types, Method(Box::new(f)))
self.set_fn(name.into(), Public, &arg_types, Method(Box::new(f)))
}
/// Set a Rust function taking two parameters into the module, returning a hash key.
@ -394,7 +395,7 @@ impl Module {
func(a, b).map(Dynamic::from)
};
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn(name.into(), DEF_ACCESS, &arg_types, Pure(Box::new(f)))
self.set_fn(name.into(), Public, &arg_types, Pure(Box::new(f)))
}
/// Set a Rust function taking two parameters (the first one mutable) into the module,
@ -429,7 +430,7 @@ impl Module {
func(a, b).map(Dynamic::from)
};
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn(name.into(), DEF_ACCESS, &arg_types, Method(Box::new(f)))
self.set_fn(name.into(), Public, &arg_types, Method(Box::new(f)))
}
/// Set a Rust function taking three parameters into the module, returning a hash key.
@ -467,7 +468,7 @@ impl Module {
func(a, b, c).map(Dynamic::from)
};
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(name.into(), DEF_ACCESS, &arg_types, Pure(Box::new(f)))
self.set_fn(name.into(), Public, &arg_types, Pure(Box::new(f)))
}
/// Set a Rust function taking three parameters (the first one mutable) into the module,
@ -506,7 +507,7 @@ impl Module {
func(a, b, c).map(Dynamic::from)
};
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(name.into(), DEF_ACCESS, &arg_types, Method(Box::new(f)))
self.set_fn(name.into(), Public, &arg_types, Method(Box::new(f)))
}
/// Get a Rust function.
@ -617,27 +618,24 @@ impl Module {
// Index all variables
for (var_name, value) in &module.variables {
// Qualifiers + variable name
let hash_var = calc_fn_hash(qualifiers.iter().map(|&v| v), var_name, empty());
let hash_var = calc_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0, empty());
variables.push((hash_var, value.clone()));
}
// Index all Rust functions
for (name, access, params, func) in module.functions.values() {
match access {
// Private functions are not exported
FnAccess::Private => continue,
FnAccess::Public => (),
Private => continue,
Public => (),
}
// Rust functions are indexed in two steps:
// 1) Calculate a hash in a similar manner to script-defined functions,
// i.e. qualifiers + function name + dummy parameter types (one for each parameter).
let hash_fn_def = calc_fn_hash(
qualifiers.iter().map(|&v| v),
name,
repeat(EMPTY_TYPE_ID()).take(params.len()),
);
// 2) Calculate a second hash with no qualifiers, empty function name, and
// the actual list of parameter `TypeId`'.s
let hash_fn_args = calc_fn_hash(empty(), "", params.iter().cloned());
// i.e. qualifiers + function name + number of arguments.
let hash_fn_def =
calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty());
// 2) Calculate a second hash with no qualifiers, empty function name,
// zero number of arguments, and the actual list of argument `TypeId`'.s
let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned());
// 3) The final hash is the XOR of the two hashes.
let hash_fn_native = hash_fn_def ^ hash_fn_args;
@ -647,14 +645,15 @@ impl Module {
for fn_def in module.fn_lib.values() {
match fn_def.access {
// Private functions are not exported
FnAccess::Private => continue,
DEF_ACCESS => (),
Private => continue,
Public => (),
}
// Qualifiers + function name + placeholders (one for each parameter)
// Qualifiers + function name + number of arguments.
let hash_fn_def = calc_fn_hash(
qualifiers.iter().map(|&v| v),
&fn_def.name,
repeat(EMPTY_TYPE_ID()).take(fn_def.params.len()),
fn_def.params.len(),
empty(),
);
functions.push((hash_fn_def, CallableFunction::Script(fn_def.clone()).into()));
}

View File

@ -119,7 +119,12 @@ fn call_fn(
pos: Position,
) -> Result<Option<Dynamic>, Box<EvalAltResult>> {
// Search built-in's and external functions
let hash_fn = calc_fn_hash(empty(), fn_name, args.iter().map(|a| a.type_id()));
let hash_fn = calc_fn_hash(
empty(),
fn_name,
args.len(),
args.iter().map(|a| a.type_id()),
);
global_module
.get_fn(hash_fn)

View File

@ -7,7 +7,7 @@ use crate::error::{LexError, ParseError, ParseErrorType};
use crate::optimize::{optimize_into_ast, OptimizationLevel};
use crate::scope::{EntryType as ScopeEntryType, Scope};
use crate::token::{Position, Token, TokenIterator};
use crate::utils::{StaticVec, EMPTY_TYPE_ID};
use crate::utils::StaticVec;
#[cfg(not(feature = "no_module"))]
use crate::module::ModuleRef;
@ -22,7 +22,7 @@ use crate::stdlib::{
char,
collections::HashMap,
format,
iter::{empty, repeat, Peekable},
iter::{empty, Peekable},
num::NonZeroUsize,
ops::{Add, Deref, DerefMut},
rc::Rc,
@ -767,13 +767,14 @@ fn parse_call_expr<'a>(
// Rust functions are indexed in two steps:
// 1) Calculate a hash in a similar manner to script-defined functions,
// i.e. qualifiers + function name + no parameters.
// 2) Calculate a second hash with no qualifiers, empty function name, and
// the actual list of parameter `TypeId`'.s
// i.e. qualifiers + function name + number of arguments.
// 2) Calculate a second hash with no qualifiers, empty function name,
// zero number of arguments, and the actual list of argument `TypeId`'s.
// 3) The final hash is the XOR of the two hashes.
calc_fn_hash(modules.iter().map(|(m, _)| m.as_str()), &id, empty())
let qualifiers = modules.iter().map(|(m, _)| m.as_str());
calc_fn_hash(qualifiers, &id, 0, empty())
} else {
calc_fn_hash(empty(), &id, empty())
calc_fn_hash(empty(), &id, 0, empty())
}
};
// Qualifiers (none) + function name + no parameters.
@ -799,7 +800,6 @@ fn parse_call_expr<'a>(
// id(...args)
(Token::RightParen, _) => {
eat_token(input, Token::RightParen);
let args_iter = repeat(EMPTY_TYPE_ID()).take(args.len());
#[cfg(not(feature = "no_module"))]
let hash_fn_def = {
@ -808,13 +808,14 @@ fn parse_call_expr<'a>(
// Rust functions are indexed in two steps:
// 1) Calculate a hash in a similar manner to script-defined functions,
// i.e. qualifiers + function name + dummy parameter types (one for each parameter).
// 2) Calculate a second hash with no qualifiers, empty function name, and
// the actual list of parameter `TypeId`'.s
// i.e. qualifiers + function name + number of arguments.
// 2) Calculate a second hash with no qualifiers, empty function name,
// zero number of arguments, and the actual list of argument `TypeId`'s.
// 3) The final hash is the XOR of the two hashes.
calc_fn_hash(modules.iter().map(|(m, _)| m.as_str()), &id, args_iter)
let qualifiers = modules.iter().map(|(m, _)| m.as_str());
calc_fn_hash(qualifiers, &id, args.len(), empty())
} else {
calc_fn_hash(empty(), &id, args_iter)
calc_fn_hash(empty(), &id, args.len(), empty())
}
};
// Qualifiers (none) + function name + dummy parameter types (one for each parameter).
@ -1297,7 +1298,7 @@ fn parse_primary<'a>(
let modules = modules.as_mut().unwrap();
// Qualifiers + variable name
*hash = calc_fn_hash(modules.iter().map(|(v, _)| v.as_str()), name, empty());
*hash = calc_fn_hash(modules.iter().map(|(v, _)| v.as_str()), name, 0, empty());
modules.set_index(state.find_module(&modules.get(0).0));
}
_ => (),
@ -1362,7 +1363,7 @@ fn parse_unary<'a>(
// Call negative function
expr => {
let op = "-";
let hash = calc_fn_hash(empty(), op, repeat(EMPTY_TYPE_ID()).take(2));
let hash = calc_fn_hash(empty(), op, 2, empty());
let mut args = StaticVec::new();
args.push(expr);
@ -1388,7 +1389,7 @@ fn parse_unary<'a>(
args.push(parse_primary(input, state, level + 1, allow_stmt_expr)?);
let op = "!";
let hash = calc_fn_hash(empty(), op, repeat(EMPTY_TYPE_ID()).take(2));
let hash = calc_fn_hash(empty(), op, 2, empty());
Ok(Expr::FnCall(Box::new((
(op.into(), pos),
@ -1492,7 +1493,7 @@ fn parse_op_assignment_stmt<'a>(
args.push(lhs_copy);
args.push(rhs);
let hash = calc_fn_hash(empty(), &op, repeat(EMPTY_TYPE_ID()).take(args.len()));
let hash = calc_fn_hash(empty(), &op, args.len(), empty());
let rhs_expr = Expr::FnCall(Box::new(((op, pos), None, hash, args, None)));
make_assignment_stmt(state, lhs, rhs_expr, pos)
@ -1780,7 +1781,7 @@ fn parse_binary_op<'a>(
let cmp_def = Some(false.into());
let op = op_token.syntax();
let hash = calc_fn_hash(empty(), &op, repeat(EMPTY_TYPE_ID()).take(2));
let hash = calc_fn_hash(empty(), &op, 2, empty());
let mut args = StaticVec::new();
args.push(root);
@ -1839,8 +1840,7 @@ fn parse_binary_op<'a>(
Expr::FnCall(x) => {
let ((id, _), _, hash, args, _) = x.as_mut();
// Recalculate function call hash because there is an additional argument
let args_iter = repeat(EMPTY_TYPE_ID()).take(args.len() + 1);
*hash = calc_fn_hash(empty(), id, args_iter);
*hash = calc_fn_hash(empty(), id, args.len() + 1, empty());
}
_ => (),
}
@ -2540,12 +2540,8 @@ fn parse_global_level<'a>(
let mut state = ParseState::new(max_expr_depth.1);
let func = parse_fn(input, &mut state, access, 0, true)?;
// Qualifiers (none) + function name + argument `TypeId`'s
let hash = calc_fn_hash(
empty(),
&func.name,
repeat(EMPTY_TYPE_ID()).take(func.params.len()),
);
// Qualifiers (none) + function name + number of arguments.
let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty());
functions.insert(hash, func);
continue;

View File

@ -21,11 +21,6 @@ use crate::stdlib::collections::hash_map::DefaultHasher;
#[cfg(feature = "no_std")]
use ahash::AHasher;
#[inline(always)]
pub fn EMPTY_TYPE_ID() -> TypeId {
TypeId::of::<()>()
}
/// Calculate a `u64` hash key from a module-qualified function name and parameter types.
///
/// Module names are passed in via `&str` references from an iterator.
@ -37,6 +32,7 @@ pub fn EMPTY_TYPE_ID() -> TypeId {
pub fn calc_fn_spec<'a>(
modules: impl Iterator<Item = &'a str>,
fn_name: &str,
num: usize,
params: impl Iterator<Item = TypeId>,
) -> u64 {
#[cfg(feature = "no_std")]
@ -47,6 +43,7 @@ pub fn calc_fn_spec<'a>(
// We always skip the first module
modules.skip(1).for_each(|m| m.hash(&mut s));
s.write(fn_name.as_bytes());
s.write_usize(num);
params.for_each(|t| t.hash(&mut s));
s.finish()
}