Use StaticVec to avoid most allocations with function arguments.

This commit is contained in:
Stephen Chung 2020-05-10 21:25:47 +08:00
parent 974512d650
commit 8aa0e2ceb4
7 changed files with 161 additions and 80 deletions

View File

@ -332,6 +332,12 @@ fn cast_box<X: Variant, T: Variant>(item: Box<X>) -> Result<Box<T>, Box<X>> {
impl Dynamic { impl Dynamic {
/// Create a `Dynamic` from any type. A `Dynamic` value is simply returned as is. /// Create a `Dynamic` from any type. A `Dynamic` value is simply returned as is.
/// ///
/// # Safety
///
/// This type uses some unsafe code, mainly for type casting.
///
/// # Notes
///
/// Beware that you need to pass in an `Array` type for it to be recognized as an `Array`. /// Beware that you need to pass in an `Array` type for it to be recognized as an `Array`.
/// A `Vec<T>` does not get automatically converted to an `Array`, but will be a generic /// A `Vec<T>` does not get automatically converted to an `Array`, but will be a generic
/// restricted trait object instead, because `Vec<T>` is not a supported standard type. /// restricted trait object instead, because `Vec<T>` is not a supported standard type.

View File

@ -10,6 +10,7 @@ use crate::parser::{parse, parse_global_expr, AST};
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::Scope; use crate::scope::Scope;
use crate::token::{lex, Position}; use crate::token::{lex, Position};
use crate::utils::StaticVec;
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::engine::Map; use crate::engine::Map;
@ -20,7 +21,6 @@ use crate::stdlib::{
collections::HashMap, collections::HashMap,
mem, mem,
string::{String, ToString}, string::{String, ToString},
vec::Vec,
}; };
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf}; use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf};
@ -994,7 +994,7 @@ impl Engine {
args: A, args: A,
) -> Result<T, Box<EvalAltResult>> { ) -> Result<T, Box<EvalAltResult>> {
let mut arg_values = args.into_vec(); let mut arg_values = args.into_vec();
let mut args: Vec<_> = arg_values.iter_mut().collect(); let mut args: StaticVec<_> = arg_values.iter_mut().collect();
let fn_lib = ast.fn_lib(); let fn_lib = ast.fn_lib();
let pos = Position::none(); let pos = Position::none();
@ -1004,7 +1004,7 @@ impl Engine {
let state = State::new(fn_lib); let state = State::new(fn_lib);
let result = self.call_script_fn(Some(scope), &state, fn_def, &mut args, pos, 0)?; let result = self.call_script_fn(Some(scope), &state, fn_def, args.as_mut(), pos, 0)?;
let return_type = self.map_type_name(result.type_name()); let return_type = self.map_type_name(result.type_name());

View File

@ -109,12 +109,12 @@ impl Target<'_> {
.as_char() .as_char()
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
let mut chars: Vec<char> = s.chars().collect(); let mut chars: StaticVec<char> = s.chars().collect();
let ch = chars[x.1]; let ch = *chars.get_ref(x.1);
// See if changed - if so, update the String // See if changed - if so, update the String
if ch != new_ch { if ch != new_ch {
chars[x.1] = new_ch; *chars.get_mut(x.1) = new_ch;
s.clear(); s.clear();
chars.iter().for_each(|&ch| s.push(ch)); chars.iter().for_each(|&ch| s.push(ch));
} }
@ -194,7 +194,7 @@ pub struct FunctionsLib(HashMap<u64, ScriptedFunction>);
impl FunctionsLib { impl FunctionsLib {
/// Create a new `FunctionsLib` from a collection of `FnDef`. /// Create a new `FunctionsLib` from a collection of `FnDef`.
pub fn from_vec(vec: Vec<FnDef>) -> Self { pub fn from_iter(vec: impl IntoIterator<Item = FnDef>) -> Self {
FunctionsLib( FunctionsLib(
vec.into_iter() vec.into_iter()
.map(|fn_def| { .map(|fn_def| {
@ -446,7 +446,7 @@ fn search_scope<'a>(
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
{ {
if let Some((modules, hash)) = modules { if let Some((modules, hash)) = modules {
let (id, root_pos) = modules.get(0); let (id, root_pos) = modules.get_ref(0);
let module = if let Some(index) = modules.index() { let module = if let Some(index) = modules.index() {
scope scope
@ -666,7 +666,7 @@ impl Engine {
/// Function call arguments may be _consumed_ when the function requires them to be passed by value. /// Function call arguments may be _consumed_ when the function requires them to be passed by value.
/// All function arguments not in the first position are always passed by value and thus consumed. /// All function arguments not in the first position are always passed by value and thus consumed.
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
pub(crate) fn call_script_fn( pub(crate) fn call_script_fn<'a>(
&self, &self,
scope: Option<&mut Scope>, scope: Option<&mut Scope>,
state: &State, state: &State,
@ -892,7 +892,7 @@ impl Engine {
Expr::FnCall(x) if x.1.is_none() => { Expr::FnCall(x) if x.1.is_none() => {
let ((name, pos), _, hash, _, def_val) = x.as_ref(); let ((name, pos), _, hash, _, def_val) = x.as_ref();
let mut args: Vec<_> = once(obj) let mut args: StaticVec<_> = once(obj)
.chain( .chain(
idx_val idx_val
.downcast_mut::<StaticVec<Dynamic>>() .downcast_mut::<StaticVec<Dynamic>>()
@ -902,7 +902,7 @@ impl Engine {
.collect(); .collect();
// A function call is assumed to have side effects, so the value is changed // A function call is assumed to have side effects, so the value is changed
// TODO - Remove assumption of side effects by checking whether the first parameter is &mut // TODO - Remove assumption of side effects by checking whether the first parameter is &mut
self.exec_fn_call(state, name, *hash, &mut args, def_val.as_ref(), *pos, 0) self.exec_fn_call(state, name, *hash, args.as_mut(), def_val.as_ref(), *pos, 0)
.map(|v| (v, true)) .map(|v| (v, true))
} }
// xxx.module::fn_name(...) - syntax error // xxx.module::fn_name(...) - syntax error
@ -1396,7 +1396,7 @@ impl Engine {
.map(|expr| self.eval_expr(scope, state, expr, level)) .map(|expr| self.eval_expr(scope, state, expr, level))
.collect::<Result<StaticVec<_>, _>>()?; .collect::<Result<StaticVec<_>, _>>()?;
let mut args: Vec<_> = arg_values.iter_mut().collect(); let mut args: StaticVec<_> = arg_values.iter_mut().collect();
let hash_fn_spec = let hash_fn_spec =
calc_fn_hash(empty(), KEYWORD_EVAL, once(TypeId::of::<String>())); calc_fn_hash(empty(), KEYWORD_EVAL, once(TypeId::of::<String>()));
@ -1410,7 +1410,7 @@ impl Engine {
// Evaluate the text string as a script // Evaluate the text string as a script
let result = let result =
self.eval_script_expr(scope, state, args[0], args_expr[0].position()); self.eval_script_expr(scope, state, args.pop(), args_expr[0].position());
if scope.len() != prev_len { if scope.len() != prev_len {
// IMPORTANT! If the eval defines new variables in the current scope, // IMPORTANT! If the eval defines new variables in the current scope,
@ -1425,7 +1425,7 @@ impl Engine {
state, state,
name, name,
*hash_fn_def, *hash_fn_def,
&mut args, args.as_mut(),
def_val.as_ref(), def_val.as_ref(),
*pos, *pos,
level, level,
@ -1444,9 +1444,9 @@ impl Engine {
.map(|expr| self.eval_expr(scope, state, expr, level)) .map(|expr| self.eval_expr(scope, state, expr, level))
.collect::<Result<StaticVec<_>, _>>()?; .collect::<Result<StaticVec<_>, _>>()?;
let mut args: Vec<_> = arg_values.iter_mut().collect(); let mut args: StaticVec<_> = arg_values.iter_mut().collect();
let (id, root_pos) = modules.get(0); // First module let (id, root_pos) = modules.get_ref(0); // First module
let module = if let Some(index) = modules.index() { let module = if let Some(index) = modules.index() {
scope scope
@ -1462,7 +1462,7 @@ impl Engine {
// First search in script-defined functions (can override built-in) // First search in script-defined functions (can override built-in)
if let Some(fn_def) = module.get_qualified_scripted_fn(*hash_fn_def) { if let Some(fn_def) = module.get_qualified_scripted_fn(*hash_fn_def) {
self.call_script_fn(None, state, fn_def, &mut args, *pos, level) self.call_script_fn(None, state, fn_def, args.as_mut(), *pos, level)
} else { } else {
// Then search in Rust functions // Then search in Rust functions
@ -1476,7 +1476,7 @@ impl Engine {
let hash = *hash_fn_def ^ hash_fn_args; let hash = *hash_fn_def ^ hash_fn_args;
match module.get_qualified_fn(name, hash, *pos) { match module.get_qualified_fn(name, hash, *pos) {
Ok(func) => func(&mut args, *pos), Ok(func) => func(args.as_mut(), *pos),
Err(_) if def_val.is_some() => Ok(def_val.clone().unwrap()), Err(_) if def_val.is_some() => Ok(def_val.clone().unwrap()),
Err(err) => Err(err), Err(err) => Err(err),
} }

View File

@ -704,40 +704,29 @@ pub fn optimize_into_ast(
const fn_lib: &[(&str, usize)] = &[]; const fn_lib: &[(&str, usize)] = &[];
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
let lib = FunctionsLib::from_vec( let lib = FunctionsLib::from_iter(functions.iter().cloned().map(|mut fn_def| {
functions if !level.is_none() {
.iter() let pos = fn_def.body.position();
.cloned()
.map(|mut fn_def| {
if !level.is_none() {
let pos = fn_def.body.position();
// Optimize the function body // Optimize the function body
let mut body = let mut body = optimize(vec![fn_def.body], engine, &Scope::new(), &fn_lib, level);
optimize(vec![fn_def.body], engine, &Scope::new(), &fn_lib, level);
// {} -> Noop // {} -> Noop
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) { fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
// { return val; } -> val // { return val; } -> val
Stmt::ReturnWithVal(x) Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => {
if x.1.is_some() && (x.0).0 == ReturnType::Return => Stmt::Expr(Box::new(x.1.unwrap()))
{
Stmt::Expr(Box::new(x.1.unwrap()))
}
// { return; } -> ()
Stmt::ReturnWithVal(x)
if x.1.is_none() && (x.0).0 == ReturnType::Return =>
{
Stmt::Expr(Box::new(Expr::Unit((x.0).1)))
}
// All others
stmt => stmt,
};
} }
fn_def // { return; } -> ()
}) Stmt::ReturnWithVal(x) if x.1.is_none() && (x.0).0 == ReturnType::Return => {
.collect(), Stmt::Expr(Box::new(Expr::Unit((x.0).1)))
); }
// All others
stmt => stmt,
};
}
fn_def
}));
#[cfg(feature = "no_function")] #[cfg(feature = "no_function")]
let lib: FunctionsLib = Default::default(); let lib: FunctionsLib = Default::default();

View File

@ -724,7 +724,7 @@ fn parse_call_expr<'a>(
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
let hash_fn_def = { let hash_fn_def = {
if let Some(modules) = modules.as_mut() { if let Some(modules) = modules.as_mut() {
modules.set_index(stack.find_module(&modules.get(0).0)); modules.set_index(stack.find_module(&modules.get_ref(0).0));
// Rust functions are indexed in two steps: // Rust functions are indexed in two steps:
// 1) Calculate a hash in a similar manner to script-defined functions, // 1) Calculate a hash in a similar manner to script-defined functions,
@ -764,7 +764,7 @@ fn parse_call_expr<'a>(
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
let hash_fn_def = { let hash_fn_def = {
if let Some(modules) = modules.as_mut() { if let Some(modules) = modules.as_mut() {
modules.set_index(stack.find_module(&modules.get(0).0)); modules.set_index(stack.find_module(&modules.get_ref(0).0));
// Rust functions are indexed in two steps: // Rust functions are indexed in two steps:
// 1) Calculate a hash in a similar manner to script-defined functions, // 1) Calculate a hash in a similar manner to script-defined functions,
@ -1238,7 +1238,7 @@ fn parse_primary<'a>(
// Qualifiers + variable name // 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, empty());
modules.set_index(stack.find_module(&modules.get(0).0)); modules.set_index(stack.find_module(&modules.get_ref(0).0));
} }
_ => (), _ => (),
} }
@ -1446,7 +1446,7 @@ fn make_dot_expr(
#[cfg(feature = "no_module")] #[cfg(feature = "no_module")]
unreachable!(); unreachable!();
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
return Err(PERR::PropertyExpected.into_err(x.1.unwrap().get(0).1)); return Err(PERR::PropertyExpected.into_err(x.1.unwrap().get_ref(0).1));
} }
// lhs.dot_lhs.dot_rhs // lhs.dot_lhs.dot_rhs
(lhs, Expr::Dot(x)) => { (lhs, Expr::Dot(x)) => {

View File

@ -7,7 +7,7 @@ use crate::token::Position;
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
use crate::module::Module; use crate::module::Module;
use crate::stdlib::{borrow::Cow, boxed::Box, iter, vec::Vec}; use crate::stdlib::{borrow::Cow, boxed::Box, iter, string::String, vec::Vec};
/// Type of an entry in the Scope. /// Type of an entry in the Scope.
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]

View File

@ -1,15 +1,18 @@
//! Module containing various utility types and functions. //! Module containing various utility types and functions.
//
// TODO - remove unsafe code
use crate::stdlib::{ use crate::stdlib::{
any::TypeId, any::TypeId,
fmt, fmt,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
iter::FromIterator,
mem, mem,
vec::Vec, vec::Vec,
}; };
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
use crate::stdlib::{collections::hash_map::DefaultHasher, iter::FromIterator}; use crate::stdlib::collections::hash_map::DefaultHasher;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use ahash::AHasher; use ahash::AHasher;
@ -23,7 +26,7 @@ pub fn EMPTY_TYPE_ID() -> TypeId {
/// Module names are passed in via `&str` references from an iterator. /// Module names are passed in via `&str` references from an iterator.
/// Parameter types are passed in via `TypeId` values from an iterator. /// Parameter types are passed in via `TypeId` values from an iterator.
/// ///
/// ### Note /// # Note
/// ///
/// The first module name is skipped. Hashing starts from the _second_ module in the chain. /// The first module name is skipped. Hashing starts from the _second_ module in the chain.
pub fn calc_fn_spec<'a>( pub fn calc_fn_spec<'a>(
@ -47,8 +50,12 @@ pub fn calc_fn_spec<'a>(
/// ///
/// This is essentially a knock-off of the [`staticvec`](https://crates.io/crates/staticvec) crate. /// This is essentially a knock-off of the [`staticvec`](https://crates.io/crates/staticvec) crate.
/// This simplified implementation here is to avoid pulling in another crate. /// This simplified implementation here is to avoid pulling in another crate.
#[derive(Clone, Hash, Default)] ///
pub struct StaticVec<T: Default> { /// # Safety
///
/// This type uses some unsafe code (mainly to zero out unused array slots) for efficiency.
#[derive(Clone, Hash)]
pub struct StaticVec<T> {
/// Total number of values held. /// Total number of values held.
len: usize, len: usize,
/// Static storage. 4 slots should be enough for most cases - i.e. four levels of indirection. /// Static storage. 4 slots should be enough for most cases - i.e. four levels of indirection.
@ -57,7 +64,17 @@ pub struct StaticVec<T: Default> {
more: Vec<T>, more: Vec<T>,
} }
impl<T: Default> FromIterator<T> for StaticVec<T> { impl<T> Default for StaticVec<T> {
fn default() -> Self {
Self {
len: 0,
list: unsafe { mem::MaybeUninit::zeroed().assume_init() },
more: Vec::new(),
}
}
}
impl<T> FromIterator<T> for StaticVec<T> {
fn from_iter<X: IntoIterator<Item = T>>(iter: X) -> Self { fn from_iter<X: IntoIterator<Item = T>>(iter: X) -> Self {
let mut vec = StaticVec::new(); let mut vec = StaticVec::new();
@ -69,14 +86,22 @@ impl<T: Default> FromIterator<T> for StaticVec<T> {
} }
} }
impl<T: Default> StaticVec<T> { impl<T> StaticVec<T> {
/// Create a new `StaticVec`. /// Create a new `StaticVec`.
pub fn new() -> Self { pub fn new() -> Self {
Default::default() Default::default()
} }
/// Push a new value to the end of this `StaticVec`. /// Push a new value to the end of this `StaticVec`.
pub fn push<X: Into<T>>(&mut self, value: X) { pub fn push<X: Into<T>>(&mut self, value: X) {
if self.len >= self.list.len() { if self.len == self.list.len() {
// Move the fixed list to the Vec
for x in 0..self.list.len() {
let def_val: T = unsafe { mem::MaybeUninit::zeroed().assume_init() };
self.more
.push(mem::replace(self.list.get_mut(x).unwrap(), def_val));
}
self.more.push(value.into());
} else if self.len > self.list.len() {
self.more.push(value.into()); self.more.push(value.into());
} else { } else {
self.list[self.len] = value.into(); self.list[self.len] = value.into();
@ -92,9 +117,19 @@ impl<T: Default> StaticVec<T> {
let result = if self.len <= 0 { let result = if self.len <= 0 {
panic!("nothing to pop!") panic!("nothing to pop!")
} else if self.len <= self.list.len() { } else if self.len <= self.list.len() {
mem::take(self.list.get_mut(self.len - 1).unwrap()) let def_val: T = unsafe { mem::MaybeUninit::zeroed().assume_init() };
mem::replace(self.list.get_mut(self.len - 1).unwrap(), def_val)
} else { } else {
self.more.pop().unwrap() let r = self.more.pop().unwrap();
// Move back to the fixed list
if self.more.len() == self.list.len() {
for x in 0..self.list.len() {
self.list[self.list.len() - 1 - x] = self.more.pop().unwrap();
}
}
r
}; };
self.len -= 1; self.len -= 1;
@ -105,48 +140,99 @@ impl<T: Default> StaticVec<T> {
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.len self.len
} }
/// Get an item at a particular index. /// Get a reference to the item at a particular index.
/// ///
/// # Panics /// # Panics
/// ///
/// Panics if the index is out of bounds. /// Panics if the index is out of bounds.
pub fn get(&self, index: usize) -> &T { pub fn get_ref(&self, index: usize) -> &T {
if index >= self.len { if index >= self.len {
panic!("index OOB in StaticVec"); panic!("index OOB in StaticVec");
} }
if index < self.list.len() { if self.len < self.list.len() {
self.list.get(index).unwrap() self.list.get(index).unwrap()
} else { } else {
self.more.get(index - self.list.len()).unwrap() self.more.get(index).unwrap()
}
}
/// Get a mutable reference to the item at a particular index.
///
/// # Panics
///
/// Panics if the index is out of bounds.
pub fn get_mut(&mut self, index: usize) -> &mut T {
if index >= self.len {
panic!("index OOB in StaticVec");
}
if self.len < self.list.len() {
self.list.get_mut(index).unwrap()
} else {
self.more.get_mut(index).unwrap()
} }
} }
/// Get an iterator to entries in the `StaticVec`. /// Get an iterator to entries in the `StaticVec`.
pub fn iter(&self) -> impl Iterator<Item = &T> { pub fn iter(&self) -> impl Iterator<Item = &T> {
let num = if self.len >= self.list.len() { if self.len > self.list.len() {
self.list.len() self.more.iter()
} else { } else {
self.len self.list[..self.len].iter()
}; }
self.list[..num].iter().chain(self.more.iter())
} }
/// Get a mutable iterator to entries in the `StaticVec`. /// Get a mutable iterator to entries in the `StaticVec`.
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> { pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
let num = if self.len >= self.list.len() { if self.len > self.list.len() {
self.list.len() self.more.iter_mut()
} else { } else {
self.len self.list[..self.len].iter_mut()
}; }
self.list[..num].iter_mut().chain(self.more.iter_mut())
} }
} }
impl<T: Default + Clone + fmt::Debug> fmt::Debug for StaticVec<T> { impl<T: Copy> StaticVec<T> {
/// Get the item at a particular index.
///
/// # Panics
///
/// Panics if the index is out of bounds.
pub fn get(&self, index: usize) -> T {
if index >= self.len {
panic!("index OOB in StaticVec");
}
if self.len < self.list.len() {
*self.list.get(index).unwrap()
} else {
*self.more.get(index).unwrap()
}
}
}
impl<T: fmt::Debug> fmt::Debug for StaticVec<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[ ")?; write!(f, "[ ")?;
self.iter().try_for_each(|v| write!(f, "{:?}, ", v))?; self.iter().try_for_each(|v| write!(f, "{:?}, ", v))?;
write!(f, "]") write!(f, "]")
} }
} }
impl<T> AsRef<[T]> for StaticVec<T> {
fn as_ref(&self) -> &[T] {
if self.len > self.list.len() {
&self.more[..]
} else {
&self.list[..self.len]
}
}
}
impl<T> AsMut<[T]> for StaticVec<T> {
fn as_mut(&mut self) -> &mut [T] {
if self.len > self.list.len() {
&mut self.more[..]
} else {
&mut self.list[..self.len]
}
}
}