diff --git a/src/func.rs b/src/func.rs index 3f51ce9..5cc4a15 100644 --- a/src/func.rs +++ b/src/func.rs @@ -6,7 +6,7 @@ use host::Externals; use isa; use module::ModuleInstance; use parity_wasm::elements::Local; -use runner::{check_function_args, Interpreter, InterpreterState}; +use runner::{check_function_args, Interpreter, InterpreterState, StackRecycler}; use types::ValueType; use value::RuntimeValue; use {Signature, Trap}; @@ -140,7 +140,7 @@ impl FuncInstance { check_function_args(func.signature(), &args)?; match *func.as_internal() { FuncInstanceInternal::Internal { .. } => { - let mut interpreter = Interpreter::new(func, args)?; + let mut interpreter = Interpreter::new(func, args, None)?; interpreter.start_execution(externals) } FuncInstanceInternal::Host { @@ -150,6 +150,34 @@ impl FuncInstance { } } + /// Invoke this function using recycled stacks. + /// + /// # Errors + /// + /// Same as [`invoke`]. + /// + /// [`invoke`]: #method.invoke + pub fn invoke_with_stack( + func: &FuncRef, + args: &[RuntimeValue], + externals: &mut E, + stack_recycler: &mut StackRecycler, + ) -> Result, Trap> { + check_function_args(func.signature(), &args)?; + match *func.as_internal() { + FuncInstanceInternal::Internal { .. } => { + let mut interpreter = Interpreter::new(func, args, Some(stack_recycler))?; + let return_value = interpreter.start_execution(externals); + stack_recycler.recycle(interpreter); + return_value + } + FuncInstanceInternal::Host { + ref host_func_index, + .. + } => externals.invoke_index(*host_func_index, args.into()), + } + } + /// Invoke the function, get a resumable handle. This handle can then be used to [`start_execution`]. If a /// Host trap happens, caller can use [`resume_execution`] to feed the expected return value back in, and then /// continue the execution. @@ -171,7 +199,7 @@ impl FuncInstance { check_function_args(func.signature(), &args)?; match *func.as_internal() { FuncInstanceInternal::Internal { .. } => { - let interpreter = Interpreter::new(func, args)?; + let interpreter = Interpreter::new(func, args, None)?; Ok(FuncInvocation { kind: FuncInvocationKind::Internal(interpreter), }) diff --git a/src/lib.rs b/src/lib.rs index 70efe4c..c4bdc4a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -404,6 +404,7 @@ pub use self::host::{Externals, HostError, NopExternals, RuntimeArgs}; pub use self::imports::{ImportResolver, ImportsBuilder, ModuleImportResolver}; pub use self::memory::{MemoryInstance, MemoryRef, LINEAR_MEMORY_PAGE_SIZE}; pub use self::module::{ExternVal, ModuleInstance, ModuleRef, NotStartedModuleRef}; +pub use self::runner::{StackRecycler, DEFAULT_CALL_STACK_LIMIT, DEFAULT_VALUE_STACK_LIMIT}; pub use self::table::{TableInstance, TableRef}; pub use self::types::{GlobalDescriptor, MemoryDescriptor, Signature, TableDescriptor, ValueType}; pub use self::value::{Error as ValueError, FromRuntimeValue, LittleEndianConvert, RuntimeValue}; diff --git a/src/module.rs b/src/module.rs index c3b30e2..5c5d9a9 100644 --- a/src/module.rs +++ b/src/module.rs @@ -18,6 +18,7 @@ use imports::ImportResolver; use memory::MemoryRef; use memory_units::Pages; use parity_wasm::elements::{External, InitExpr, Instruction, Internal, ResizableLimits, Type}; +use runner::StackRecycler; use table::TableRef; use types::{GlobalDescriptor, MemoryDescriptor, TableDescriptor}; use validation::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX}; @@ -625,21 +626,43 @@ impl ModuleInstance { args: &[RuntimeValue], externals: &mut E, ) -> Result, Error> { + let func_instance = self.func_by_name(func_name)?; + + FuncInstance::invoke(&func_instance, args, externals).map_err(|t| Error::Trap(t)) + } + + /// Invoke exported function by a name using recycled stacks. + /// + /// # Errors + /// + /// Same as [`invoke_export`]. + /// + /// [`invoke_export`]: #method.invoke_export + pub fn invoke_export_with_stack( + &self, + func_name: &str, + args: &[RuntimeValue], + externals: &mut E, + stack_recycler: &mut StackRecycler, + ) -> Result, Error> { + let func_instance = self.func_by_name(func_name)?; + + FuncInstance::invoke_with_stack(&func_instance, args, externals, stack_recycler) + .map_err(|t| Error::Trap(t)) + } + + fn func_by_name(&self, func_name: &str) -> Result { let extern_val = self .export_by_name(func_name) .ok_or_else(|| Error::Function(format!("Module doesn't have export {}", func_name)))?; - let func_instance = match extern_val { - ExternVal::Func(func_instance) => func_instance, - unexpected => { - return Err(Error::Function(format!( - "Export {} is not a function, but {:?}", - func_name, unexpected - ))); - } - }; - - FuncInstance::invoke(&func_instance, args, externals).map_err(|t| Error::Trap(t)) + match extern_val { + ExternVal::Func(func_instance) => Ok(func_instance), + unexpected => Err(Error::Function(format!( + "Export {} is not a function, but {:?}", + func_name, unexpected + ))), + } } /// Find export by a name. diff --git a/src/runner.rs b/src/runner.rs index 9425844..a3e7277 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -18,10 +18,10 @@ use value::{ }; use {Signature, Trap, TrapKind, ValueType}; -/// Maximum number of entries in value stack. -pub const DEFAULT_VALUE_STACK_LIMIT: usize = (1024 * 1024) / ::core::mem::size_of::(); +/// Maximum number of bytes on the value stack. +pub const DEFAULT_VALUE_STACK_LIMIT: usize = 1024 * 1024; -// TODO: Make these parameters changeble. +/// Maximum number of levels on the call stack. pub const DEFAULT_CALL_STACK_LIMIT: usize = 64 * 1024; /// This is a wrapper around u64 to allow us to treat runtime values as a tag-free `u64` @@ -166,14 +166,18 @@ enum RunResult { /// Function interpreter. pub struct Interpreter { value_stack: ValueStack, - call_stack: Vec, + call_stack: CallStack, return_type: Option, state: InterpreterState, } impl Interpreter { - pub fn new(func: &FuncRef, args: &[RuntimeValue]) -> Result { - let mut value_stack = ValueStack::with_limit(DEFAULT_VALUE_STACK_LIMIT); + pub fn new( + func: &FuncRef, + args: &[RuntimeValue], + mut stack_recycler: Option<&mut StackRecycler>, + ) -> Result { + let mut value_stack = StackRecycler::recreate_value_stack(&mut stack_recycler); for &arg in args { let arg = arg.into(); value_stack.push(arg).map_err( @@ -183,7 +187,7 @@ impl Interpreter { )?; } - let mut call_stack = Vec::new(); + let mut call_stack = StackRecycler::recreate_call_stack(&mut stack_recycler); let initial_frame = FunctionContext::new(func.clone()); call_stack.push(initial_frame); @@ -278,14 +282,14 @@ impl Interpreter { match function_return { RunResult::Return => { - if self.call_stack.last().is_none() { + if self.call_stack.is_empty() { // This was the last frame in the call stack. This means we // are done executing. return Ok(()); } } RunResult::NestedCall(nested_func) => { - if self.call_stack.len() + 1 >= DEFAULT_CALL_STACK_LIMIT { + if self.call_stack.is_full() { return Err(TrapKind::StackOverflow.into()); } @@ -1363,16 +1367,6 @@ struct ValueStack { } impl ValueStack { - fn with_limit(limit: usize) -> ValueStack { - let mut buf = Vec::new(); - buf.resize(limit, RuntimeValueInternal(0)); - - ValueStack { - buf: buf.into_boxed_slice(), - sp: 0, - } - } - #[inline] fn drop_keep(&mut self, drop_keep: isa::DropKeep) { if drop_keep.keep == isa::Keep::Single { @@ -1454,3 +1448,97 @@ impl ValueStack { self.sp } } + +struct CallStack { + buf: Vec, + limit: usize, +} + +impl CallStack { + fn push(&mut self, ctx: FunctionContext) { + self.buf.push(ctx); + } + + fn pop(&mut self) -> Option { + self.buf.pop() + } + + fn is_empty(&self) -> bool { + self.buf.is_empty() + } + + fn is_full(&self) -> bool { + self.buf.len() + 1 >= self.limit + } +} + +/// Used to recycle stacks instead of allocating them repeatedly. +pub struct StackRecycler { + value_stack_buf: Option>, + value_stack_limit: usize, + call_stack_buf: Option>, + call_stack_limit: usize, +} + +impl StackRecycler { + /// Limit stacks created by this recycler to + /// - `value_stack_limit` bytes for values and + /// - `call_stack_limit` levels for calls. + pub fn with_limits(value_stack_limit: usize, call_stack_limit: usize) -> Self { + Self { + value_stack_buf: None, + value_stack_limit, + call_stack_buf: None, + call_stack_limit, + } + } + + fn recreate_value_stack(this: &mut Option<&mut Self>) -> ValueStack { + let limit = this + .as_ref() + .map_or(DEFAULT_VALUE_STACK_LIMIT, |this| this.value_stack_limit) + / ::core::mem::size_of::(); + + let buf = this + .as_mut() + .and_then(|this| this.value_stack_buf.take()) + .unwrap_or_else(|| { + let mut buf = Vec::new(); + buf.reserve_exact(limit); + buf.resize(limit, RuntimeValueInternal(0)); + buf.into_boxed_slice() + }); + + ValueStack { buf, sp: 0 } + } + + fn recreate_call_stack(this: &mut Option<&mut Self>) -> CallStack { + let limit = this + .as_ref() + .map_or(DEFAULT_CALL_STACK_LIMIT, |this| this.call_stack_limit); + + let buf = this + .as_mut() + .and_then(|this| this.call_stack_buf.take()) + .unwrap_or_default(); + + CallStack { buf, limit } + } + + pub(crate) fn recycle(&mut self, mut interpreter: Interpreter) { + for cell in interpreter.value_stack.buf.iter_mut() { + *cell = RuntimeValueInternal(0); + } + + interpreter.call_stack.buf.clear(); + + self.value_stack_buf = Some(interpreter.value_stack.buf); + self.call_stack_buf = Some(interpreter.call_stack.buf); + } +} + +impl Default for StackRecycler { + fn default() -> Self { + Self::with_limits(DEFAULT_VALUE_STACK_LIMIT, DEFAULT_CALL_STACK_LIMIT) + } +}