Hash functions only once via custom hasher.

This commit is contained in:
Stephen Chung 2020-05-31 00:02:23 +08:00
parent 439053b153
commit c9de37e8d1
4 changed files with 130 additions and 87 deletions

View File

@ -12,7 +12,7 @@ use crate::r#unsafe::{unsafe_cast_var_name_to_lifetime, unsafe_mut_cast_to_lifet
use crate::result::EvalAltResult;
use crate::scope::{EntryType as ScopeEntryType, Scope};
use crate::token::Position;
use crate::utils::StaticVec;
use crate::utils::{StaticVec, StraightHasherBuilder};
#[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT;
@ -196,7 +196,7 @@ impl State {
///
/// 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, Shared<FnDef>>);
pub struct FunctionsLib(HashMap<u64, Shared<FnDef>, StraightHasherBuilder>);
impl FunctionsLib {
/// Create a new `FunctionsLib` from a collection of `FnDef`.
@ -261,7 +261,7 @@ impl From<Vec<(u64, Shared<FnDef>)>> for FunctionsLib {
}
impl Deref for FunctionsLib {
type Target = HashMap<u64, Shared<FnDef>>;
type Target = HashMap<u64, Shared<FnDef>, StraightHasherBuilder>;
fn deref(&self) -> &Self::Target {
&self.0
@ -269,7 +269,7 @@ impl Deref for FunctionsLib {
}
impl DerefMut for FunctionsLib {
fn deref_mut(&mut self) -> &mut HashMap<u64, Shared<FnDef>> {
fn deref_mut(&mut self) -> &mut HashMap<u64, Shared<FnDef>, StraightHasherBuilder> {
&mut self.0
}
}
@ -952,21 +952,24 @@ impl Engine {
let mut idx_val = idx_values.pop();
if is_index {
let pos = rhs.position();
match rhs {
// xxx[idx].dot_rhs... | xxx[idx][dot_rhs]...
// xxx[idx].expr... | xxx[idx][expr]...
Expr::Dot(x) | Expr::Index(x) => {
let (idx, expr, pos) = x.as_ref();
let is_idx = matches!(rhs, Expr::Index(_));
let pos = x.0.position();
let this_ptr = &mut self
.get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, op_pos, false)?;
let idx_pos = idx.position();
let this_ptr = &mut self.get_indexed_mut(
state, lib, obj, is_ref, idx_val, idx_pos, op_pos, false,
)?;
self.eval_dot_index_chain_helper(
state, lib, this_ptr, &x.1, idx_values, is_idx, x.2, level, new_val,
state, lib, this_ptr, expr, idx_values, is_idx, *pos, level, new_val,
)
}
// xxx[rhs] = new_val
_ if new_val.is_some() => {
let pos = rhs.position();
let this_ptr = &mut self
.get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, op_pos, true)?;
@ -975,16 +978,7 @@ impl Engine {
}
// xxx[rhs]
_ => self
.get_indexed_mut(
state,
lib,
obj,
is_ref,
idx_val,
rhs.position(),
op_pos,
false,
)
.get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, op_pos, false)
.map(|v| (v.clone_into_dynamic(), false)),
}
} else {
@ -1050,57 +1044,51 @@ impl Engine {
.map(|(v, _)| (v, false))
}
#[cfg(not(feature = "no_object"))]
// {xxx:map}.idx_lhs[idx_expr] | {xxx:map}.dot_lhs.rhs
// {xxx:map}.prop[expr] | {xxx:map}.prop.expr
Expr::Index(x) | Expr::Dot(x) if obj.is::<Map>() => {
let (prop, expr, pos) = x.as_ref();
let is_idx = matches!(rhs, Expr::Index(_));
let mut val = if let Expr::Property(p) = &x.0 {
let mut val = if let Expr::Property(p) = prop {
let ((prop, _, _), _) = p.as_ref();
let index = prop.clone().into();
self.get_indexed_mut(state, lib, obj, is_ref, index, x.2, op_pos, false)?
self.get_indexed_mut(state, lib, obj, is_ref, index, *pos, op_pos, false)?
} else {
// Syntax error
return Err(Box::new(EvalAltResult::ErrorDotExpr(
"".into(),
rhs.position(),
)));
unreachable!();
};
self.eval_dot_index_chain_helper(
state, lib, &mut val, &x.1, idx_values, is_idx, x.2, level, new_val,
state, lib, &mut val, expr, idx_values, is_idx, *pos, level, new_val,
)
}
// xxx.idx_lhs[idx_expr] | xxx.dot_lhs.rhs
// xxx.prop[expr] | xxx.prop.expr
Expr::Index(x) | Expr::Dot(x) => {
let is_idx = matches!(rhs, Expr::Index(_));
let (prop, expr, pos) = x.as_ref();
let is_idx = matches!(expr, Expr::Index(_));
let args = &mut [obj, &mut Default::default()];
let (mut val, updated) = if let Expr::Property(p) = &x.0 {
let (mut val, updated) = if let Expr::Property(p) = prop {
let ((_, getter, _), _) = p.as_ref();
let args = &mut args[..1];
self.exec_fn_call(state, lib, getter, true, 0, args, is_ref, None, x.2, 0)?
self.exec_fn_call(state, lib, getter, true, 0, args, is_ref, None, *pos, 0)?
} else {
// Syntax error
return Err(Box::new(EvalAltResult::ErrorDotExpr(
"".into(),
rhs.position(),
)));
unreachable!();
};
let val = &mut val;
let target = &mut val.into();
let (result, may_be_changed) = self.eval_dot_index_chain_helper(
state, lib, target, &x.1, idx_values, is_idx, x.2, level, new_val,
state, lib, target, expr, idx_values, is_idx, *pos, level, new_val,
)?;
// Feed the value back via a setter just in case it has been updated
if updated || may_be_changed {
if let Expr::Property(p) = &x.0 {
if let Expr::Property(p) = prop {
let ((_, _, setter), _) = p.as_ref();
// Re-use args because the first &mut parameter will not be consumed
args[1] = val;
self.exec_fn_call(
state, lib, setter, true, 0, args, is_ref, None, x.2, 0,
state, lib, setter, true, 0, args, is_ref, None, *pos, 0,
)
.or_else(|err| match *err {
// If there is no setter, no need to feed it back because the property is read-only

View File

@ -12,7 +12,7 @@ use crate::parser::{
use crate::result::EvalAltResult;
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
use crate::token::{Position, Token};
use crate::utils::StaticVec;
use crate::utils::{StaticVec, StraightHasherBuilder};
use crate::stdlib::{
any::TypeId,
@ -44,10 +44,14 @@ pub struct Module {
variables: HashMap<String, Dynamic>,
/// Flattened collection of all module variables, including those in sub-modules.
all_variables: HashMap<u64, Dynamic>,
all_variables: HashMap<u64, Dynamic, StraightHasherBuilder>,
/// External Rust functions.
functions: HashMap<u64, (String, FnAccess, StaticVec<TypeId>, CallableFunction)>,
functions: HashMap<
u64,
(String, FnAccess, StaticVec<TypeId>, CallableFunction),
StraightHasherBuilder,
>,
/// Script-defined functions.
lib: FunctionsLib,
@ -57,7 +61,7 @@ pub struct Module {
/// Flattened collection of all external Rust functions, native or scripted,
/// including those in sub-modules.
all_functions: HashMap<u64, CallableFunction>,
all_functions: HashMap<u64, CallableFunction, StraightHasherBuilder>,
}
impl fmt::Debug for Module {
@ -101,7 +105,7 @@ impl Module {
/// ```
pub fn new_with_capacity(capacity: usize) -> Self {
Self {
functions: HashMap::with_capacity(capacity),
functions: HashMap::with_capacity_and_hasher(capacity, StraightHasherBuilder),
..Default::default()
}
}

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;
use crate::utils::{StaticVec, StraightHasherBuilder};
#[cfg(not(feature = "no_module"))]
use crate::module::ModuleRef;
@ -1496,22 +1496,21 @@ fn parse_op_assignment_stmt<'a>(
}
/// Make a dot expression.
fn make_dot_expr(
lhs: Expr,
rhs: Expr,
op_pos: Position,
is_index: bool,
) -> Result<Expr, ParseError> {
fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, ParseError> {
Ok(match (lhs, rhs) {
// idx_lhs[idx_rhs].rhs
// idx_lhs[idx_expr].rhs
// Attach dot chain to the bottom level of indexing chain
(Expr::Index(x), rhs) => {
Expr::Index(Box::new((x.0, make_dot_expr(x.1, rhs, op_pos, true)?, x.2)))
let (idx_lhs, idx_expr, pos) = *x;
Expr::Index(Box::new((
idx_lhs,
make_dot_expr(idx_expr, rhs, op_pos)?,
pos,
)))
}
// lhs.id
(lhs, Expr::Variable(x)) if x.1.is_none() => {
let (name, pos) = x.0;
let lhs = if is_index { lhs.into_property() } else { lhs };
let getter = make_getter(&name);
let setter = make_setter(&name);
@ -1519,11 +1518,6 @@ fn make_dot_expr(
Expr::Dot(Box::new((lhs, rhs, op_pos)))
}
(lhs, Expr::Property(x)) => {
let lhs = if is_index { lhs.into_property() } else { lhs };
let rhs = Expr::Property(x);
Expr::Dot(Box::new((lhs, rhs, op_pos)))
}
// lhs.module::id - syntax error
(_, Expr::Variable(x)) if x.1.is_some() => {
#[cfg(feature = "no_module")]
@ -1531,34 +1525,30 @@ fn make_dot_expr(
#[cfg(not(feature = "no_module"))]
return Err(PERR::PropertyExpected.into_err(x.1.unwrap().get(0).1));
}
// lhs.prop
(lhs, prop @ Expr::Property(_)) => Expr::Dot(Box::new((lhs, prop, op_pos))),
// lhs.dot_lhs.dot_rhs
(lhs, Expr::Dot(x)) => {
let (dot_lhs, dot_rhs, pos) = *x;
Expr::Dot(Box::new((
lhs,
Expr::Dot(Box::new((
dot_lhs.into_property(),
dot_rhs.into_property(),
pos,
))),
Expr::Dot(Box::new((dot_lhs.into_property(), dot_rhs, pos))),
op_pos,
)))
}
// lhs.idx_lhs[idx_rhs]
(lhs, Expr::Index(x)) => {
let (idx_lhs, idx_rhs, pos) = *x;
let (dot_lhs, dot_rhs, pos) = *x;
Expr::Dot(Box::new((
lhs,
Expr::Index(Box::new((
idx_lhs.into_property(),
idx_rhs.into_property(),
pos,
))),
Expr::Index(Box::new((dot_lhs.into_property(), dot_rhs, pos))),
op_pos,
)))
}
// lhs.func()
(lhs, func @ Expr::FnCall(_)) => Expr::Dot(Box::new((lhs, func, op_pos))),
// lhs.rhs
(lhs, rhs) => Expr::Dot(Box::new((lhs, rhs.into_property(), op_pos))),
_ => unreachable!(),
})
}
@ -1822,7 +1812,7 @@ fn parse_binary_op<'a>(
_ => (),
}
make_dot_expr(current_lhs, rhs, pos, false)?
make_dot_expr(current_lhs, rhs, pos)?
}
token => return Err(PERR::UnknownOperator(token.into()).into_err(pos)),
@ -2493,22 +2483,20 @@ pub fn parse_global_expr<'a>(
fn parse_global_level<'a>(
input: &mut Peekable<TokenIterator<'a>>,
max_expr_depth: (usize, usize),
) -> Result<(Vec<Stmt>, HashMap<u64, FnDef>), ParseError> {
) -> Result<(Vec<Stmt>, Vec<FnDef>), ParseError> {
let mut statements = Vec::<Stmt>::new();
let mut functions = HashMap::<u64, FnDef>::new();
let mut functions = HashMap::with_hasher(StraightHasherBuilder);
let mut state = ParseState::new(max_expr_depth.0);
while !input.peek().unwrap().0.is_eof() {
// Collect all the function definitions
#[cfg(not(feature = "no_function"))]
{
let mut access = FnAccess::Public;
let mut must_be_fn = false;
if match_token(input, Token::Private)? {
access = FnAccess::Private;
must_be_fn = true;
}
let (access, must_be_fn) = if match_token(input, Token::Private)? {
(FnAccess::Private, true)
} else {
(FnAccess::Public, false)
};
match input.peek().unwrap() {
(Token::Fn, _) => {
@ -2565,7 +2553,7 @@ fn parse_global_level<'a>(
}
}
Ok((statements, functions))
Ok((statements, functions.into_iter().map(|(_, v)| v).collect()))
}
/// Run the parser on an input stream, returning an AST.
@ -2576,9 +2564,8 @@ pub fn parse<'a>(
optimization_level: OptimizationLevel,
max_expr_depth: (usize, usize),
) -> Result<AST, ParseError> {
let (statements, functions) = parse_global_level(input, max_expr_depth)?;
let (statements, lib) = parse_global_level(input, max_expr_depth)?;
let lib = functions.into_iter().map(|(_, v)| v).collect();
Ok(
// Optimize AST
optimize_into_ast(engine, scope, statements, lib, optimization_level),

View File

@ -11,7 +11,7 @@ use crate::stdlib::{
borrow::Borrow,
boxed::Box,
fmt,
hash::{Hash, Hasher},
hash::{BuildHasher, Hash, Hasher},
iter::FromIterator,
mem,
mem::MaybeUninit,
@ -27,6 +27,48 @@ use crate::stdlib::collections::hash_map::DefaultHasher;
#[cfg(feature = "no_std")]
use ahash::AHasher;
/// A hasher that only takes one single `u64` and returns it as a hash key.
///
/// # Panics
///
/// Panics when hashing any data type other than a `u64`.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
pub struct StraightHasher(u64);
impl Hasher for StraightHasher {
#[inline(always)]
fn finish(&self) -> u64 {
self.0
}
#[inline]
fn write(&mut self, bytes: &[u8]) {
let mut key = [0_u8; 8];
key.copy_from_slice(&bytes[..8]); // Panics if fewer than 8 bytes
self.0 = u64::from_le_bytes(key);
}
}
impl StraightHasher {
/// Create a `StraightHasher`.
#[inline(always)]
pub fn new() -> Self {
Self(0)
}
}
/// A hash builder for `StraightHasher`.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
pub struct StraightHasherBuilder;
impl BuildHasher for StraightHasherBuilder {
type Hasher = StraightHasher;
#[inline(always)]
fn build_hasher(&self) -> Self::Hasher {
StraightHasher::new()
}
}
/// 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.
@ -108,6 +150,7 @@ pub struct StaticVec<T> {
const MAX_STATIC_VEC: usize = 4;
impl<T> Drop for StaticVec<T> {
#[inline(always)]
fn drop(&mut self) {
self.clear();
}
@ -174,6 +217,7 @@ impl<T> FromIterator<T> for StaticVec<T> {
impl<T> StaticVec<T> {
/// Create a new `StaticVec`.
#[inline(always)]
pub fn new() -> Self {
Default::default()
}
@ -189,6 +233,7 @@ impl<T> StaticVec<T> {
self.len = 0;
}
/// Extract a `MaybeUninit` into a concrete initialized type.
#[inline(always)]
fn extract(value: MaybeUninit<T>) -> T {
unsafe { value.assume_init() }
}
@ -250,6 +295,7 @@ impl<T> StaticVec<T> {
);
}
/// Is data stored in fixed-size storage?
#[inline(always)]
fn is_fixed_storage(&self) -> bool {
self.len <= MAX_STATIC_VEC
}
@ -359,10 +405,12 @@ impl<T> StaticVec<T> {
result
}
/// Get the number of items in this `StaticVec`.
#[inline(always)]
pub fn len(&self) -> usize {
self.len
}
/// Is this `StaticVec` empty?
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.len == 0
}
@ -605,41 +653,48 @@ pub struct ImmutableString(Shared<String>);
impl Deref for ImmutableString {
type Target = String;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<String> for ImmutableString {
#[inline(always)]
fn as_ref(&self) -> &String {
&self.0
}
}
impl Borrow<str> for ImmutableString {
#[inline(always)]
fn borrow(&self) -> &str {
self.0.as_str()
}
}
impl From<&str> for ImmutableString {
#[inline(always)]
fn from(value: &str) -> Self {
Self(value.to_string().into())
}
}
impl From<String> for ImmutableString {
#[inline(always)]
fn from(value: String) -> Self {
Self(value.into())
}
}
impl From<Box<String>> for ImmutableString {
#[inline(always)]
fn from(value: Box<String>) -> Self {
Self(value.into())
}
}
impl From<ImmutableString> for String {
#[inline(always)]
fn from(value: ImmutableString) -> Self {
value.into_owned()
}
@ -648,42 +703,49 @@ impl From<ImmutableString> for String {
impl FromStr for ImmutableString {
type Err = ();
#[inline(always)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.to_string().into()))
}
}
impl FromIterator<char> for ImmutableString {
#[inline(always)]
fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
Self(iter.into_iter().collect::<String>().into())
}
}
impl<'a> FromIterator<&'a char> for ImmutableString {
#[inline(always)]
fn from_iter<T: IntoIterator<Item = &'a char>>(iter: T) -> Self {
Self(iter.into_iter().cloned().collect::<String>().into())
}
}
impl<'a> FromIterator<&'a str> for ImmutableString {
#[inline(always)]
fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
Self(iter.into_iter().collect::<String>().into())
}
}
impl<'a> FromIterator<String> for ImmutableString {
#[inline(always)]
fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
Self(iter.into_iter().collect::<String>().into())
}
}
impl fmt::Display for ImmutableString {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.0.as_str(), f)
}
}
impl fmt::Debug for ImmutableString {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.0.as_str(), f)
}
@ -818,6 +880,7 @@ impl Add<char> for &ImmutableString {
}
impl AddAssign<char> for ImmutableString {
#[inline(always)]
fn add_assign(&mut self, rhs: char) {
self.make_mut().push(rhs);
}
@ -832,6 +895,7 @@ impl ImmutableString {
}
/// Make sure that the `ImmutableString` is unique (i.e. no other outstanding references).
/// Then return a mutable reference to the `String`.
#[inline(always)]
pub fn make_mut(&mut self) -> &mut String {
shared_make_mut(&mut self.0)
}