Reduce indirections.

This commit is contained in:
Stephen Chung 2021-03-23 20:04:54 +08:00
parent f9429c06f9
commit 0d7f2c16cc
6 changed files with 137 additions and 127 deletions

View File

@ -4,21 +4,23 @@ Rhai Release Notes
Version 0.19.15 Version 0.19.15
=============== ===============
This version replaces internal usage of `HashMap` with `BTreeMap` in many cases, which should result This version replaces all internal usage of `HashMap` with `BTreeMap`, which should result
in speed improvements because a `BTreeMap` is faster when the number of items held is small. in some speed improvement because a `BTreeMap` is leaner when the number of items held is small.
Most, if not all, collections in Rhai hold very few data items, so this is a typical scenario of
many tiny-sized collections.
This also translates to the Rhai object map type, `Map`, which used to be an alias to `HashMap` and The Rhai object map type, `Map`, used to be an alias to `HashMap` and is now aliased to `BTreeMap`
is now aliased to `BTreeMap` instead. This change is due to the fact that, in the vast majority of instead. This is also because, in the vast majority of usage cases, the number of properties held by
object map usages, the number of properties held is small. an object map is small.
`HashMap` and `BTreeMap` have almost identical public API's so this change is unlikely to break a `HashMap` and `BTreeMap` have almost identical public API's so this change is unlikely to break
lot of existing code. existing code.
Breaking changes Breaking changes
---------------- ----------------
* `Map` is now an alias to `BTreeMap` instead of `HashMap`. This is because most object maps used have few properties. * `Map` is now an alias to `BTreeMap` instead of `HashMap` because most object maps hold few properties.
* The traits `RegisterFn` and `RegisterResultFn` are removed. `Engine::register_fn` and `Engine::register_result_fn` are now implemented directly on `Engine`. * The traits `RegisterFn` and `RegisterResultFn` are removed. `Engine::register_fn` and `Engine::register_result_fn` are now implemented directly on `Engine`.
* `FnPtr::call_dynamic` now takes `&NativeCallContext` instead of consuming it. * `FnPtr::call_dynamic` now takes `&NativeCallContext` instead of consuming it.
* All `Module::set_fn_XXX` methods are removed, in favor of `Module::set_native_fn`. * All `Module::set_fn_XXX` methods are removed, in favor of `Module::set_native_fn`.
@ -28,7 +30,7 @@ Breaking changes
Enhancements Enhancements
------------ ------------
* Replaced most `HashMap` usage with `BTreeMap` for better performance when the number of items is small. * Replaced all `HashMap` usage with `BTreeMap` for better performance because collections in Rhai are tiny.
* `Engine::register_result_fn` no longer requires the successful return type to be `Dynamic`. It can now be any clonable type. * `Engine::register_result_fn` no longer requires the successful return type to be `Dynamic`. It can now be any clonable type.
* `#[rhai_fn(return_raw)]` can now return `Result<T, Box<EvalAltResult>>` where `T` is any clonable type instead of `Result<Dynamic, Box<EvalAltResult>>`. * `#[rhai_fn(return_raw)]` can now return `Result<T, Box<EvalAltResult>>` where `T` is any clonable type instead of `Result<Dynamic, Box<EvalAltResult>>`.

View File

@ -1285,7 +1285,7 @@ pub struct BinaryExpr {
pub struct OpAssignment { pub struct OpAssignment {
pub hash_op_assign: u64, pub hash_op_assign: u64,
pub hash_op: u64, pub hash_op: u64,
pub op: Cow<'static, str>, pub op: &'static str,
} }
/// _(INTERNALS)_ An set of function call hashes. /// _(INTERNALS)_ An set of function call hashes.

View File

@ -342,13 +342,13 @@ impl<'a> Target<'a> {
#[inline(always)] #[inline(always)]
pub fn is<T: Variant + Clone>(&self) -> bool { pub fn is<T: Variant + Clone>(&self) -> bool {
match self { match self {
Target::Ref(r) => r.is::<T>(), Self::Ref(r) => r.is::<T>(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Target::LockGuard((r, _)) => r.is::<T>(), Self::LockGuard((r, _)) => r.is::<T>(),
Target::Value(r) => r.is::<T>(), Self::Value(r) => r.is::<T>(),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Target::StringChar(_, _, _) => TypeId::of::<T>() == TypeId::of::<char>(), Self::StringChar(_, _, _) => TypeId::of::<T>() == TypeId::of::<char>(),
} }
} }
/// Get the value of the `Target` as a `Dynamic`, cloning a referenced value if necessary. /// Get the value of the `Target` as a `Dynamic`, cloning a referenced value if necessary.
@ -1056,7 +1056,7 @@ impl Engine {
idx_values: &mut StaticVec<ChainArgument>, idx_values: &mut StaticVec<ChainArgument>,
chain_type: ChainType, chain_type: ChainType,
level: usize, level: usize,
new_val: Option<((Dynamic, Position), (&Option<OpAssignment>, Position))>, new_val: Option<((Dynamic, Position), (Option<OpAssignment>, Position))>,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
assert!(chain_type != ChainType::NonChaining); assert!(chain_type != ChainType::NonChaining);
@ -1097,6 +1097,8 @@ impl Engine {
// xxx[rhs] op= new_val // xxx[rhs] op= new_val
_ if new_val.is_some() => { _ if new_val.is_some() => {
let idx_val = idx_val.as_index_value(); let idx_val = idx_val.as_index_value();
#[cfg(not(feature = "no_index"))]
let mut idx_val2 = idx_val.clone(); let mut idx_val2 = idx_val.clone();
// `call_setter` is introduced to bypass double mutable borrowing of target // `call_setter` is introduced to bypass double mutable borrowing of target
@ -1357,7 +1359,7 @@ impl Engine {
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
expr: &Expr, expr: &Expr,
level: usize, level: usize,
new_val: Option<((Dynamic, Position), (&Option<OpAssignment>, Position))>, new_val: Option<((Dynamic, Position), (Option<OpAssignment>, Position))>,
) -> RhaiResult { ) -> RhaiResult {
let (crate::ast::BinaryExpr { lhs, rhs }, chain_type, op_pos) = match expr { let (crate::ast::BinaryExpr { lhs, rhs }, chain_type, op_pos) = match expr {
Expr::Index(x, pos) => (x.as_ref(), ChainType::Index, *pos), Expr::Index(x, pos) => (x.as_ref(), ChainType::Index, *pos),
@ -1524,7 +1526,7 @@ impl Engine {
state: &mut State, state: &mut State,
_lib: &[&Module], _lib: &[&Module],
target: &'t mut Dynamic, target: &'t mut Dynamic,
idx: Dynamic, mut idx: Dynamic,
idx_pos: Position, idx_pos: Position,
_create: bool, _create: bool,
_is_ref: bool, _is_ref: bool,
@ -1557,21 +1559,18 @@ impl Engine {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Dynamic(Union::Map(map, _)) => { Dynamic(Union::Map(map, _)) => {
// val_map[idx] // val_map[idx]
Ok(if _create { let index = &*idx.read_lock::<ImmutableString>().ok_or_else(|| {
let index = idx.take_immutable_string().map_err(|err| { self.make_type_mismatch_err::<ImmutableString>(idx.type_name(), idx_pos)
self.make_type_mismatch_err::<ImmutableString>(err, idx_pos) })?;
})?;
map.entry(index).or_insert_with(Default::default).into() if _create && !map.contains_key(index) {
} else { map.insert(index.clone(), Default::default());
let index = idx.read_lock::<ImmutableString>().ok_or_else(|| { }
self.make_type_mismatch_err::<ImmutableString>("", idx_pos)
})?;
map.get_mut(&*index) Ok(map
.map(Target::from) .get_mut(index)
.unwrap_or_else(|| Target::from(())) .map(Target::from)
}) .unwrap_or_else(|| Target::from(())))
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -1596,7 +1595,6 @@ impl Engine {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
_ if _indexers => { _ if _indexers => {
let type_name = target.type_name(); let type_name = target.type_name();
let mut idx = idx;
let args = &mut [target, &mut idx]; let args = &mut [target, &mut idx];
let hash_get = FnCallHash::from_native(calc_fn_hash(empty(), FN_IDX_GET, 2)); let hash_get = FnCallHash::from_native(calc_fn_hash(empty(), FN_IDX_GET, 2));
self.exec_fn_call( self.exec_fn_call(
@ -1863,7 +1861,7 @@ impl Engine {
mods: &mut Imports, mods: &mut Imports,
state: &mut State, state: &mut State,
lib: &[&Module], lib: &[&Module],
op_info: &Option<OpAssignment>, op_info: Option<OpAssignment>,
op_pos: Position, op_pos: Position,
mut target: Target, mut target: Target,
mut new_value: Dynamic, mut new_value: Dynamic,
@ -1889,20 +1887,19 @@ impl Engine {
lhs_ptr_inner = target.as_mut(); lhs_ptr_inner = target.as_mut();
} }
let hash = *hash_op_assign; let hash = hash_op_assign;
let args = &mut [lhs_ptr_inner, &mut new_value]; let args = &mut [lhs_ptr_inner, &mut new_value];
match self.call_native_fn(mods, state, lib, op, hash, args, true, true, op_pos) { match self.call_native_fn(mods, state, lib, op, hash, args, true, true, op_pos) {
Ok(_) => (), Ok(_) => (),
Err(err) if matches!(err.as_ref(), EvalAltResult::ErrorFunctionNotFound(f, _) if f.starts_with(op.as_ref())) => Err(err) if matches!(err.as_ref(), EvalAltResult::ErrorFunctionNotFound(f, _) if f.starts_with(op)) =>
{ {
// Expand to `var = var op rhs` // Expand to `var = var op rhs`
let op = &op[..op.len() - 1]; // extract operator without = let op = &op[..op.len() - 1]; // extract operator without =
// Run function // Run function
let (value, _) = self.call_native_fn( let (value, _) = self
mods, state, lib, op, *hash_op, args, true, false, op_pos, .call_native_fn(mods, state, lib, op, hash_op, args, true, false, op_pos)?;
)?;
*args[0] = value.flatten(); *args[0] = value.flatten();
} }
@ -1975,7 +1972,7 @@ impl Engine {
mods, mods,
state, state,
lib, lib,
op_info, op_info.clone(),
*op_pos, *op_pos,
lhs_ptr, lhs_ptr,
rhs_val, rhs_val,
@ -1991,7 +1988,7 @@ impl Engine {
let rhs_val = self let rhs_val = self
.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)? .eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?
.flatten(); .flatten();
let _new_val = Some(((rhs_val, rhs_expr.position()), (op_info, *op_pos))); let _new_val = Some(((rhs_val, rhs_expr.position()), (op_info.clone(), *op_pos)));
// Must be either `var[index] op= val` or `var.prop op= val` // Must be either `var[index] op= val` or `var.prop op= val`
match lhs_expr { match lhs_expr {

View File

@ -1278,9 +1278,9 @@ impl Engine {
// Trims the JSON string and add a '#' in front // Trims the JSON string and add a '#' in front
let json_text = json.trim_start(); let json_text = json.trim_start();
let scripts = if json_text.starts_with(Token::MapStart.syntax().as_ref()) { let scripts = if json_text.starts_with(Token::MapStart.keyword_syntax()) {
[json_text, ""] [json_text, ""]
} else if json_text.starts_with(Token::LeftBrace.syntax().as_ref()) { } else if json_text.starts_with(Token::LeftBrace.keyword_syntax()) {
["#", json_text] ["#", json_text]
} else { } else {
return Err(crate::ParseErrorType::MissingToken( return Err(crate::ParseErrorType::MissingToken(

View File

@ -10,7 +10,6 @@ use crate::module::NamespaceRef;
use crate::optimize::optimize_into_ast; use crate::optimize::optimize_into_ast;
use crate::optimize::OptimizationLevel; use crate::optimize::OptimizationLevel;
use crate::stdlib::{ use crate::stdlib::{
borrow::Cow,
boxed::Box, boxed::Box,
collections::BTreeMap, collections::BTreeMap,
format, format,
@ -1355,7 +1354,7 @@ fn parse_unary(
/// Make an assignment statement. /// Make an assignment statement.
fn make_assignment_stmt<'a>( fn make_assignment_stmt<'a>(
op: Cow<'static, str>, op: &'static str,
state: &mut ParseState, state: &mut ParseState,
lhs: Expr, lhs: Expr,
rhs: Expr, rhs: Expr,
@ -1479,7 +1478,7 @@ fn parse_op_assignment_stmt(
| Token::PowerOfAssign | Token::PowerOfAssign
| Token::AndAssign | Token::AndAssign
| Token::OrAssign | Token::OrAssign
| Token::XOrAssign => token.syntax(), | Token::XOrAssign => token.keyword_syntax(),
_ => return Ok(Stmt::Expr(lhs)), _ => return Ok(Stmt::Expr(lhs)),
}; };

View File

@ -381,6 +381,99 @@ pub enum Token {
} }
impl Token { impl Token {
/// Get the syntax of the token if it is a keyword.
///
/// # Panics
///
/// Panics if the token is not a keyword.
pub fn keyword_syntax(&self) -> &'static str {
use Token::*;
match self {
LeftBrace => "{",
RightBrace => "}",
LeftParen => "(",
RightParen => ")",
LeftBracket => "[",
RightBracket => "]",
Plus => "+",
UnaryPlus => "+",
Minus => "-",
UnaryMinus => "-",
Multiply => "*",
Divide => "/",
SemiColon => ";",
Colon => ":",
DoubleColon => "::",
DoubleArrow => "=>",
Underscore => "_",
Comma => ",",
Period => ".",
MapStart => "#{",
Equals => "=",
True => "true",
False => "false",
Let => "let",
Const => "const",
If => "if",
Else => "else",
Switch => "switch",
Do => "do",
While => "while",
Until => "until",
Loop => "loop",
For => "for",
In => "in",
LessThan => "<",
GreaterThan => ">",
Bang => "!",
LessThanEqualsTo => "<=",
GreaterThanEqualsTo => ">=",
EqualsTo => "==",
NotEqualsTo => "!=",
Pipe => "|",
Or => "||",
Ampersand => "&",
And => "&&",
Continue => "continue",
Break => "break",
Return => "return",
Throw => "throw",
Try => "try",
Catch => "catch",
PlusAssign => "+=",
MinusAssign => "-=",
MultiplyAssign => "*=",
DivideAssign => "/=",
LeftShiftAssign => "<<=",
RightShiftAssign => ">>=",
AndAssign => "&=",
OrAssign => "|=",
XOrAssign => "^=",
LeftShift => "<<",
RightShift => ">>",
XOr => "^",
Modulo => "%",
ModuloAssign => "%=",
PowerOf => "**",
PowerOfAssign => "**=",
#[cfg(not(feature = "no_function"))]
Fn => "fn",
#[cfg(not(feature = "no_function"))]
Private => "private",
#[cfg(not(feature = "no_module"))]
Import => "import",
#[cfg(not(feature = "no_module"))]
Export => "export",
#[cfg(not(feature = "no_module"))]
As => "as",
t => unreachable!("{:?} is not a keyword", t),
}
}
/// Get the syntax of the token. /// Get the syntax of the token.
pub fn syntax(&self) -> Cow<'static, str> { pub fn syntax(&self) -> Cow<'static, str> {
use Token::*; use Token::*;
@ -399,90 +492,9 @@ impl Token {
LexError(err) => err.to_string().into(), LexError(err) => err.to_string().into(),
Comment(s) => s.clone().into(), Comment(s) => s.clone().into(),
token => match token { EOF => "{EOF}".into(),
LeftBrace => "{",
RightBrace => "}",
LeftParen => "(",
RightParen => ")",
LeftBracket => "[",
RightBracket => "]",
Plus => "+",
UnaryPlus => "+",
Minus => "-",
UnaryMinus => "-",
Multiply => "*",
Divide => "/",
SemiColon => ";",
Colon => ":",
DoubleColon => "::",
DoubleArrow => "=>",
Underscore => "_",
Comma => ",",
Period => ".",
MapStart => "#{",
Equals => "=",
True => "true",
False => "false",
Let => "let",
Const => "const",
If => "if",
Else => "else",
Switch => "switch",
Do => "do",
While => "while",
Until => "until",
Loop => "loop",
For => "for",
In => "in",
LessThan => "<",
GreaterThan => ">",
Bang => "!",
LessThanEqualsTo => "<=",
GreaterThanEqualsTo => ">=",
EqualsTo => "==",
NotEqualsTo => "!=",
Pipe => "|",
Or => "||",
Ampersand => "&",
And => "&&",
Continue => "continue",
Break => "break",
Return => "return",
Throw => "throw",
Try => "try",
Catch => "catch",
PlusAssign => "+=",
MinusAssign => "-=",
MultiplyAssign => "*=",
DivideAssign => "/=",
LeftShiftAssign => "<<=",
RightShiftAssign => ">>=",
AndAssign => "&=",
OrAssign => "|=",
XOrAssign => "^=",
LeftShift => "<<",
RightShift => ">>",
XOr => "^",
Modulo => "%",
ModuloAssign => "%=",
PowerOf => "**",
PowerOfAssign => "**=",
#[cfg(not(feature = "no_function"))] token => token.keyword_syntax().into(),
Fn => "fn",
#[cfg(not(feature = "no_function"))]
Private => "private",
#[cfg(not(feature = "no_module"))]
Import => "import",
#[cfg(not(feature = "no_module"))]
Export => "export",
#[cfg(not(feature = "no_module"))]
As => "as",
EOF => "{EOF}",
t => unreachable!("operator should be matched in outer scope: {:?}", t),
}
.into(),
} }
} }