diff --git a/Cargo.toml b/Cargo.toml index 085b02364e..ab5bdd2ba7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,8 @@ exclude = [ "/res/*", "/tests/*", "/fuzz/*", "/benches/*" ] [dependencies] wasmi_core = { version = "0.1", path = "core", default-features = false } validation = { package = "wasmi-validation", version = "0.4", path = "validation", default-features = false } -parity-wasm = { version = "0.42.0", default-features = false } +parity-wasm = { version = "0.42.0", features = ["sign_ext"] } +specs = { path = "../../crates/specs" } [dev-dependencies] assert_matches = "1.5" diff --git a/core/Cargo.toml b/core/Cargo.toml index af263bfe1e..5d23498c6f 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -11,7 +11,7 @@ description = "WebAssembly interpreter" keywords = ["wasm", "webassembly", "bytecode", "interpreter"] [dependencies] -parity-wasm = { version = "0.42.0", default-features = false } +parity-wasm = { version = "0.42.0", features = ["sign_ext"] } memory_units = "0.4.0" libm = "0.2.1" num-rational = { version = "0.4", default-features = false, features = ["num-bigint"] } diff --git a/core/src/value.rs b/core/src/value.rs index 65f27734ce..abe860ba68 100644 --- a/core/src/value.rs +++ b/core/src/value.rs @@ -76,6 +76,16 @@ pub enum Value { F64(F64), } +impl Into for Value { + fn into(self) -> u64 { + match self { + Value::I32(val) => val as u32 as u64, + Value::I64(val) => val as u64, + _ => unreachable!(), + } + } +} + /// Trait for creating value from a [`Value`]. /// /// Typically each implementation can create a value from the specific type. diff --git a/src/func.rs b/src/func.rs index 09026c11c1..5a686d21ba 100644 --- a/src/func.rs +++ b/src/func.rs @@ -2,6 +2,7 @@ use crate::{ host::Externals, isa, module::ModuleInstance, + monitor::Monitor, runner::{check_function_args, Interpreter, InterpreterState, StackRecycler}, RuntimeValue, Signature, @@ -13,7 +14,7 @@ use alloc::{ rc::{Rc, Weak}, vec::Vec, }; -use core::fmt; +use core::{fmt, hash::Hash}; use parity_wasm::elements::Local; /// Reference to a function (See [`FuncInstance`] for details). @@ -21,7 +22,7 @@ use parity_wasm::elements::Local; /// This reference has a reference-counting semantics. /// /// [`FuncInstance`]: struct.FuncInstance.html -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct FuncRef(Rc); impl ::core::ops::Deref for FuncRef { @@ -45,14 +46,16 @@ impl ::core::ops::Deref for FuncRef { /// See more in [`Externals`]. /// /// [`Externals`]: trait.Externals.html +#[derive(PartialEq, Eq, Hash)] pub struct FuncInstance(FuncInstanceInternal); #[derive(Clone)] -pub(crate) enum FuncInstanceInternal { +pub enum FuncInstanceInternal { Internal { signature: Rc, module: Weak, body: Rc, + index: usize, }, Host { signature: Signature, @@ -60,13 +63,61 @@ pub(crate) enum FuncInstanceInternal { }, } +impl Hash for FuncInstanceInternal { + fn hash(&self, state: &mut H) { + match self { + FuncInstanceInternal::Internal { index, .. } => { + 0.hash(state); + index.hash(state); + } + FuncInstanceInternal::Host { + host_func_index, .. + } => { + 1.hash(state); + host_func_index.hash(state); + } + } + } +} + +impl PartialEq for FuncInstanceInternal { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Internal { index: l_index, .. }, Self::Internal { index: r_index, .. }) => { + l_index == r_index + } + ( + Self::Host { + signature: l_signature, + host_func_index: l_host_func_index, + }, + Self::Host { + signature: r_signature, + host_func_index: r_host_func_index, + }, + ) => l_signature == r_signature && l_host_func_index == r_host_func_index, + _ => false, + } + } +} + +impl Eq for FuncInstanceInternal {} + impl fmt::Debug for FuncInstance { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.as_internal() { - FuncInstanceInternal::Internal { ref signature, .. } => { + FuncInstanceInternal::Internal { + ref signature, + index, + .. + } => { // We can't write description of self.module here, because it generate // debug string for function instances and this will lead to infinite loop. - write!(f, "Internal {{ signature={:?} }}", signature,) + write!( + f, + "Internal {{ signature={:?}, index={} }}", + signature, index + ) } FuncInstanceInternal::Host { ref signature, .. } => { write!(f, "Host {{ signature={:?} }}", signature) @@ -104,7 +155,7 @@ impl FuncInstance { } } - pub(crate) fn as_internal(&self) -> &FuncInstanceInternal { + pub fn as_internal(&self) -> &FuncInstanceInternal { &self.0 } @@ -112,16 +163,18 @@ impl FuncInstance { module: Weak, signature: Rc, body: FuncBody, + index: usize, ) -> FuncRef { let func = FuncInstanceInternal::Internal { signature, module, body: Rc::new(body), + index, }; FuncRef(Rc::new(FuncInstance(func))) } - pub(crate) fn body(&self) -> Option> { + pub fn body(&self) -> Option> { match *self.as_internal() { FuncInstanceInternal::Internal { ref body, .. } => Some(Rc::clone(body)), FuncInstanceInternal::Host { .. } => None, @@ -155,6 +208,23 @@ impl FuncInstance { } } + pub fn invoke_trace( + func: &FuncRef, + args: &[RuntimeValue], + externals: &mut E, + monitor: &mut dyn Monitor, + ) -> Result, Trap> { + check_function_args(func.signature(), args)?; + match *func.as_internal() { + FuncInstanceInternal::Internal { .. } => { + let mut interpreter = Interpreter::new(func, args, None)?; + interpreter.monitor = Some(monitor); + interpreter.start_execution(externals) + } + FuncInstanceInternal::Host { .. } => unreachable!(), + } + } + /// Invoke this function using recycled stacks. /// /// # Errors @@ -197,10 +267,10 @@ impl FuncInstance { /// [`Trap`]: #enum.Trap.html /// [`start_execution`]: struct.FuncInvocation.html#method.start_execution /// [`resume_execution`]: struct.FuncInvocation.html#method.resume_execution - pub fn invoke_resumable<'args>( + pub fn invoke_resumable<'a, 'args>( func: &FuncRef, args: impl Into>, - ) -> Result, Trap> { + ) -> Result, Trap> { let args = args.into(); check_function_args(func.signature(), &args)?; match *func.as_internal() { @@ -255,12 +325,12 @@ impl From for ResumableError { } /// A resumable invocation handle. This struct is returned by `FuncInstance::invoke_resumable`. -pub struct FuncInvocation<'args> { - kind: FuncInvocationKind<'args>, +pub struct FuncInvocation<'a, 'args> { + kind: FuncInvocationKind<'a, 'args>, } -enum FuncInvocationKind<'args> { - Internal(Interpreter), +enum FuncInvocationKind<'a, 'args> { + Internal(Interpreter<'a>), Host { args: Cow<'args, [RuntimeValue]>, host_func_index: usize, @@ -268,7 +338,7 @@ enum FuncInvocationKind<'args> { }, } -impl<'args> FuncInvocation<'args> { +impl<'a, 'args> FuncInvocation<'a, 'args> { /// Whether this invocation is currently resumable. pub fn is_resumable(&self) -> bool { match &self.kind { diff --git a/src/isa.rs b/src/isa.rs index e2b63cfd42..8882d6cce9 100644 --- a/src/isa.rs +++ b/src/isa.rs @@ -68,6 +68,8 @@ //! use alloc::vec::Vec; +use parity_wasm::elements::ValueType; +use specs::itable::UnaryOp; /// Should we keep a value before "discarding" a stack frame? /// @@ -78,7 +80,7 @@ pub enum Keep { None, /// Pop one value from the yet-to-be-discarded stack frame to the /// current stack frame. - Single, + Single(ValueType), } impl Keep { @@ -86,7 +88,7 @@ impl Keep { pub fn count(&self) -> u32 { match *self { Keep::None => 0, - Keep::Single => 1, + Keep::Single(..) => 1, } } } @@ -117,7 +119,7 @@ pub enum Reloc { #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct BrTargets<'a> { - stream: &'a [InstructionInternal], + pub stream: &'a [InstructionInternal], } impl<'a> BrTargets<'a> { @@ -140,13 +142,13 @@ impl<'a> BrTargets<'a> { #[allow(clippy::upper_case_acronyms)] pub enum Instruction<'a> { /// Push a local variable or an argument from the specified depth. - GetLocal(u32), + GetLocal(u32, ValueType), /// Pop a value and put it in at the specified depth. - SetLocal(u32), + SetLocal(u32, ValueType), /// Copy a value to the specified depth. - TeeLocal(u32), + TeeLocal(u32, ValueType), /// Similar to the Wasm ones, but instead of a label depth /// they specify direct PC. @@ -172,7 +174,7 @@ pub enum Instruction<'a> { CallIndirect(u32), Drop, - Select, + Select(ValueType), GetGlobal(u32), SetGlobal(u32), @@ -339,6 +341,23 @@ pub enum Instruction<'a> { I64ReinterpretF64, F32ReinterpretI32, F64ReinterpretI64, + + I32Extend8S, + I32Extend16S, + I64Extend8S, + I64Extend16S, + I64Extend32S, +} + +impl<'a> From> for UnaryOp { + fn from(value: Instruction<'a>) -> Self { + match value { + Instruction::I32Clz | Instruction::I64Clz => UnaryOp::Clz, + Instruction::I32Ctz | Instruction::I64Ctz => UnaryOp::Ctz, + Instruction::I32Popcnt | Instruction::I64Popcnt => UnaryOp::Popcnt, + _ => unreachable!(), + } + } } /// The internally-stored instruction type. This differs from `Instruction` in that the `BrTable` @@ -351,10 +370,10 @@ pub enum Instruction<'a> { /// borrows the list of instructions and returns targets by reading it. #[derive(Copy, Debug, Clone, PartialEq, Eq)] #[allow(clippy::upper_case_acronyms)] -pub(crate) enum InstructionInternal { - GetLocal(u32), - SetLocal(u32), - TeeLocal(u32), +pub enum InstructionInternal { + GetLocal(u32, ValueType), + SetLocal(u32, ValueType), + TeeLocal(u32, ValueType), Br(Target), BrIfEqz(Target), BrIfNez(Target), @@ -368,7 +387,7 @@ pub(crate) enum InstructionInternal { CallIndirect(u32), Drop, - Select, + Select(ValueType), GetGlobal(u32), SetGlobal(u32), @@ -535,11 +554,17 @@ pub(crate) enum InstructionInternal { I64ReinterpretF64, F32ReinterpretI32, F64ReinterpretI64, + + I32Extend8S, + I32Extend16S, + I64Extend8S, + I64Extend16S, + I64Extend32S, } #[derive(Debug, Clone)] pub struct Instructions { - vec: Vec, + pub(crate) vec: Vec, } impl Instructions { @@ -600,9 +625,9 @@ impl<'a> Iterator for InstructionIter<'a> { let internal = self.instructions.get(self.position as usize)?; let out = match *internal { - InstructionInternal::GetLocal(x) => Instruction::GetLocal(x), - InstructionInternal::SetLocal(x) => Instruction::SetLocal(x), - InstructionInternal::TeeLocal(x) => Instruction::TeeLocal(x), + InstructionInternal::GetLocal(x, typ) => Instruction::GetLocal(x, typ), + InstructionInternal::SetLocal(x, typ) => Instruction::SetLocal(x, typ), + InstructionInternal::TeeLocal(x, typ) => Instruction::TeeLocal(x, typ), InstructionInternal::Br(x) => Instruction::Br(x), InstructionInternal::BrIfEqz(x) => Instruction::BrIfEqz(x), InstructionInternal::BrIfNez(x) => Instruction::BrIfNez(x), @@ -624,7 +649,7 @@ impl<'a> Iterator for InstructionIter<'a> { InstructionInternal::CallIndirect(x) => Instruction::CallIndirect(x), InstructionInternal::Drop => Instruction::Drop, - InstructionInternal::Select => Instruction::Select, + InstructionInternal::Select(vtype) => Instruction::Select(vtype), InstructionInternal::GetGlobal(x) => Instruction::GetGlobal(x), InstructionInternal::SetGlobal(x) => Instruction::SetGlobal(x), @@ -791,6 +816,12 @@ impl<'a> Iterator for InstructionIter<'a> { InstructionInternal::I64ReinterpretF64 => Instruction::I64ReinterpretF64, InstructionInternal::F32ReinterpretI32 => Instruction::F32ReinterpretI32, InstructionInternal::F64ReinterpretI64 => Instruction::F64ReinterpretI64, + + InstructionInternal::I32Extend8S => Instruction::I32Extend8S, + InstructionInternal::I32Extend16S => Instruction::I32Extend16S, + InstructionInternal::I64Extend8S => Instruction::I64Extend8S, + InstructionInternal::I64Extend16S => Instruction::I64Extend16S, + InstructionInternal::I64Extend32S => Instruction::I64Extend32S, }; self.position += 1; diff --git a/src/lib.rs b/src/lib.rs index b8901624cf..22527e1fdf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,7 +94,7 @@ //! } //! ``` -#![warn(missing_docs)] +// #![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] #![allow(clippy::len_without_is_empty)] #![allow(clippy::new_ret_no_self)] @@ -258,18 +258,20 @@ impl From for Error { } } -mod func; +pub mod func; mod global; mod host; mod imports; -mod isa; mod memory; -mod module; +pub mod module; mod prepare; -mod runner; +pub mod runner; mod table; mod types; +pub mod isa; +pub mod monitor; + pub use self::{ func::{FuncInstance, FuncInvocation, FuncRef, ResumableError}, global::{GlobalInstance, GlobalRef}, @@ -318,7 +320,7 @@ pub mod nan_preserving_float { /// Deserialized module prepared for instantiation. pub struct Module { code_map: Vec, - module: parity_wasm::elements::Module, + pub module: parity_wasm::elements::Module, } impl Module { @@ -452,7 +454,7 @@ impl Module { Module::from_parity_wasm_module(module) } - pub(crate) fn module(&self) -> &parity_wasm::elements::Module { + pub fn module(&self) -> &parity_wasm::elements::Module { &self.module } diff --git a/src/memory/mod.rs b/src/memory/mod.rs index 820eefeb03..bcc1de9438 100644 --- a/src/memory/mod.rs +++ b/src/memory/mod.rs @@ -12,6 +12,7 @@ use core::{ u32, }; use parity_wasm::elements::ResizableLimits; +use std::collections::HashMap; #[cfg(all(feature = "virtual_memory", target_pointer_width = "64"))] #[path = "mmap_bytebuf.rs"] @@ -64,6 +65,8 @@ pub struct MemoryInstance { limits: ResizableLimits, /// Linear memory buffer with lazy allocation. buffer: RefCell, + /// Origin memory size at phantom entry, and a hashmap to track the modified memory. + pub buffer_cache: RefCell)>>, initial: Pages, current_size: Cell, maximum: Option, @@ -148,6 +151,7 @@ impl MemoryInstance { Ok(MemoryInstance { limits, buffer: RefCell::new(ByteBuf::new(initial_size.0).map_err(Error::Memory)?), + buffer_cache: RefCell::new(None), initial, current_size: Cell::new(initial_size.0), maximum, @@ -214,7 +218,21 @@ impl MemoryInstance { let mut buffer = self.buffer.borrow_mut(); let region = self.checked_region(&mut buffer, offset as usize, size)?; - Ok(buffer.as_slice_mut()[region.range()].to_vec()) + let mut buffer = buffer.as_slice_mut()[region.range()].to_vec(); + + let buf_cache = self.buffer_cache.borrow(); + if buf_cache.is_some() { + let mut index = 0; + + for offset in region.range() { + if let Some(value) = buf_cache.as_ref().unwrap().1.get(&offset) { + buffer[index] = *value; + } + index += 1; + } + } + + Ok(buffer) } /// Copy data from given offset in the memory into `target` slice. @@ -228,6 +246,18 @@ impl MemoryInstance { target.copy_from_slice(&buffer.as_slice_mut()[region.range()]); + let buf_cache = self.buffer_cache.borrow(); + if buf_cache.is_some() { + let mut index = 0; + + for offset in region.range() { + if let Some(value) = buf_cache.as_ref().unwrap().1.get(&offset) { + target[index] = *value; + } + index += 1; + } + } + Ok(()) } @@ -238,7 +268,18 @@ impl MemoryInstance { .checked_region(&mut buffer, offset as usize, value.len())? .range(); - buffer.as_slice_mut()[range].copy_from_slice(value); + let mut buf_cache = self.buffer_cache.borrow_mut(); + if buf_cache.is_some() { + let mut index = 0; + + for offset in range { + buf_cache.as_mut().unwrap().1.insert(offset, value[index]); + + index += 1; + } + } else { + buffer.as_slice_mut()[range].copy_from_slice(value); + } Ok(()) } @@ -290,6 +331,20 @@ impl MemoryInstance { Ok(size_before_grow) } + pub fn shrink(&self, origin: Pages) -> Result<(), Error> { + let new_size: Pages = origin; + + let new_buffer_length: Bytes = new_size.into(); + self.buffer + .borrow_mut() + .realloc(new_buffer_length.0) + .map_err(Error::Memory)?; + + self.current_size.set(new_buffer_length.0); + + Ok(()) + } + fn checked_region( &self, buffer: &mut ByteBuf, diff --git a/src/module.rs b/src/module.rs index 282f75a59a..d78baff2e7 100644 --- a/src/module.rs +++ b/src/module.rs @@ -5,6 +5,7 @@ use crate::{ imports::ImportResolver, memory::MemoryRef, memory_units::Pages, + monitor::Monitor, nan_preserving_float::{F32, F64}, runner::StackRecycler, table::TableRef, @@ -29,6 +30,7 @@ use core::{ fmt, }; use parity_wasm::elements::{External, InitExpr, Instruction, Internal, ResizableLimits, Type}; +use std::collections::HashMap; use validation::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX}; /// Reference to a [`ModuleInstance`]. @@ -164,12 +166,14 @@ impl ExternVal { /// [`invoke_export`]: #method.invoke_export #[derive(Debug)] pub struct ModuleInstance { - signatures: RefCell>>, + pub(crate) signatures: RefCell>>, tables: RefCell>, funcs: RefCell>, memories: RefCell>, globals: RefCell>, - exports: RefCell>, + pub exports: RefCell>, + + funcs_index: RefCell>, } impl ModuleInstance { @@ -181,30 +185,43 @@ impl ModuleInstance { memories: RefCell::new(Vec::new()), globals: RefCell::new(Vec::new()), exports: RefCell::new(BTreeMap::new()), + + funcs_index: RefCell::new(HashMap::new()), } } - pub(crate) fn memory_by_index(&self, idx: u32) -> Option { + pub fn memory_by_index(&self, idx: u32) -> Option { self.memories.borrow_mut().get(idx as usize).cloned() } - pub(crate) fn table_by_index(&self, idx: u32) -> Option { + pub fn table_by_index(&self, idx: u32) -> Option { self.tables.borrow_mut().get(idx as usize).cloned() } - pub(crate) fn global_by_index(&self, idx: u32) -> Option { + pub fn global_by_index(&self, idx: u32) -> Option { self.globals.borrow_mut().get(idx as usize).cloned() } - pub(crate) fn func_by_index(&self, idx: u32) -> Option { + pub fn func_by_index(&self, idx: u32) -> Option { self.funcs.borrow().get(idx as usize).cloned() } + pub fn func_index_by_func_ref(&self, func: &FuncRef) -> u32 { + *self.funcs_index.borrow().get(func).unwrap() + } + pub(crate) fn signature_by_index(&self, idx: u32) -> Option> { self.signatures.borrow().get(idx as usize).cloned() } + fn funcs(&self) -> RefCell> { + self.funcs.clone() + } + fn push_func(&self, func: FuncRef) { + let index = self.funcs().borrow().len() as u32; + + self.funcs_index.borrow_mut().insert(func.clone(), index); self.funcs.borrow_mut().push(func); } @@ -281,6 +298,7 @@ impl ModuleInstance { import.field(), ))); } + instance.push_func(func.clone()) } (&External::Table(ref tt), &ExternVal::Table(ref table)) => { @@ -334,8 +352,13 @@ impl ModuleInstance { locals: body.locals().to_vec(), code, }; - let func_instance = - FuncInstance::alloc_internal(Rc::downgrade(&instance.0), signature, func_body); + let func_instance = FuncInstance::alloc_internal( + Rc::downgrade(&instance.0), + signature, + func_body, + instance.funcs().borrow().len(), + ); + instance.push_func(func_instance); } } @@ -640,10 +663,25 @@ impl ModuleInstance { externals: &mut E, ) -> Result, Error> { let func_instance = self.func_by_name(func_name)?; - FuncInstance::invoke(&func_instance, args, externals).map_err(Error::Trap) } + pub fn invoke_export_trace( + &self, + func_name: &str, + args: &[RuntimeValue], + externals: &mut E, + monitor: &mut dyn Monitor, + ) -> Result, Error> { + { + monitor.invoke_exported_function_pre_hook(); + } + + let func_instance = self.func_by_name(func_name)?; + + FuncInstance::invoke_trace(&func_instance, args, externals, monitor).map_err(Error::Trap) + } + /// Invoke exported function by a name using recycled stacks. /// /// # Errors @@ -664,7 +702,7 @@ impl ModuleInstance { .map_err(Error::Trap) } - fn func_by_name(&self, func_name: &str) -> Result { + pub 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)))?; @@ -705,8 +743,8 @@ impl ModuleInstance { /// [`assert_no_start`]: #method.assert_no_start /// [`not_started_instance`]: #method.not_started_instance pub struct NotStartedModuleRef<'a> { - loaded_module: &'a Module, - instance: ModuleRef, + pub loaded_module: &'a Module, + pub instance: ModuleRef, } impl<'a> NotStartedModuleRef<'a> { @@ -739,6 +777,30 @@ impl<'a> NotStartedModuleRef<'a> { Ok(self.instance) } + /// Executes `start` function (if any) and returns fully instantiated module. + /// + /// # Errors + /// + /// Returns `Err` if start function traps. + pub fn run_start_tracer( + self, + state: &mut E, + monitor: &mut dyn Monitor, + ) -> Result { + { + monitor.invoke_exported_function_pre_hook(); + } + + if let Some(start_fn_idx) = self.loaded_module.module().start_section() { + let start_func = self + .instance + .func_by_index(start_fn_idx) + .expect("Due to validation start function should exists"); + FuncInstance::invoke_trace(&start_func, &[], state, monitor)?; + } + Ok(self.instance) + } + /// Executes `start` function (if any) and returns fully instantiated module. /// /// # Errors @@ -780,7 +842,7 @@ impl<'a> NotStartedModuleRef<'a> { } } -fn eval_init_expr(init_expr: &InitExpr, module: &ModuleInstance) -> RuntimeValue { +pub fn eval_init_expr(init_expr: &InitExpr, module: &ModuleInstance) -> RuntimeValue { let code = init_expr.code(); debug_assert!( code.len() == 2, @@ -891,7 +953,7 @@ mod tests { ExternVal::Func(FuncInstance::alloc_host(Signature::new(&[][..], None), 0)), ExternVal::Func(FuncInstance::alloc_host(Signature::new(&[][..], None), 1)), ] - .iter(), + .iter() ) .is_err()); diff --git a/src/monitor/mod.rs b/src/monitor/mod.rs new file mode 100644 index 0000000000..56b45cc44c --- /dev/null +++ b/src/monitor/mod.rs @@ -0,0 +1,48 @@ +use parity_wasm::elements::Module; +use wasmi_core::Value; + +use crate::{ + isa::Instruction, + runner::{FunctionContext, InstructionOutcome, ValueStack}, + Error, + ModuleRef, +}; + +pub trait Monitor { + fn register_module( + &mut self, + _module: &Module, + _module_ref: &ModuleRef, + _entry: &str, + ) -> Result<(), Error> { + Ok(()) + } + + /// Called before each exported function(zkmain or start function) is executed. + fn invoke_exported_function_pre_hook(&mut self) {} + + /// Called before each instruction is executed. + fn invoke_instruction_pre_hook( + &mut self, + _value_stack: &ValueStack, + _function_context: &FunctionContext, + _instruction: &Instruction, + ) { + } + /// Called after each instruction is executed. + fn invoke_instruction_post_hook( + &mut self, + _fid: u32, + _iid: u32, + _sp: u32, + _allocated_memory_pages: u32, + _value_stack: &ValueStack, + _function_context: &FunctionContext, + _instruction: &Instruction, + _outcome: &InstructionOutcome, + ) { + } + + /// Called after 'call_host' instruction is executed. + fn invoke_call_host_post_hook(&mut self, _return_value: Option) {} +} diff --git a/src/prepare/compile.rs b/src/prepare/compile.rs index d5a9f62a01..ee34e51e60 100644 --- a/src/prepare/compile.rs +++ b/src/prepare/compile.rs @@ -1,8 +1,8 @@ use alloc::{string::String, vec::Vec}; -use parity_wasm::elements::{BlockType, FuncBody, Instruction}; +use parity_wasm::elements::{BlockType, FuncBody, Instruction, SignExtInstruction, ValueType}; -use crate::isa; +use crate::isa::{self, InstructionInternal}; use validation::{ func::{ require_label, @@ -89,6 +89,17 @@ impl FuncValidator for Compiler { .label_stack .push(BlockFrameType::Block { end_label }); + for local_group in body.locals() { + for _ in 0..local_group.count() { + match local_group.value_type() { + ValueType::I32 => compiler.sink.emit(InstructionInternal::I32Const(0)), + ValueType::I64 => compiler.sink.emit(InstructionInternal::I64Const(0)), + ValueType::F32 => compiler.sink.emit(InstructionInternal::F32Const(0)), + ValueType::F64 => compiler.sink.emit(InstructionInternal::F64Const(0)), + } + } + } + compiler } fn next_instruction( @@ -289,8 +300,8 @@ impl Compiler { // These two unwraps are guaranteed to succeed by validation. const REQUIRE_TARGET_PROOF: &str = "validation step ensures that the value stack underflows; - validation also ensures that the depth is correct; - qed"; + validation also ensures that the depth is correct; + qed"; let targets = targets.expect(REQUIRE_TARGET_PROOF); let default_target = default_target.expect(REQUIRE_TARGET_PROOF); @@ -326,25 +337,35 @@ impl Compiler { } Select => { context.step(instruction)?; - self.sink.emit(isa::InstructionInternal::Select); + if let StackValueType::Specific(t) = context.value_stack.top()? { + self.sink.emit(isa::InstructionInternal::Select(*t)); + } else { + unreachable!() + } } GetLocal(index) => { // We need to calculate relative depth before validation since // it will change the value stack size. - let depth = relative_local_depth(index, &context.locals, &context.value_stack)?; + let (depth, typ) = + relative_local_depth_type(index, &context.locals, &context.value_stack)?; context.step(instruction)?; - self.sink.emit(isa::InstructionInternal::GetLocal(depth)); + self.sink + .emit(isa::InstructionInternal::GetLocal(depth, typ)); } SetLocal(index) => { context.step(instruction)?; - let depth = relative_local_depth(index, &context.locals, &context.value_stack)?; - self.sink.emit(isa::InstructionInternal::SetLocal(depth)); + let (depth, typ) = + relative_local_depth_type(index, &context.locals, &context.value_stack)?; + self.sink + .emit(isa::InstructionInternal::SetLocal(depth, typ)); } TeeLocal(index) => { context.step(instruction)?; - let depth = relative_local_depth(index, &context.locals, &context.value_stack)?; - self.sink.emit(isa::InstructionInternal::TeeLocal(depth)); + let (depth, typ) = + relative_local_depth_type(index, &context.locals, &context.value_stack)?; + self.sink + .emit(isa::InstructionInternal::TeeLocal(depth, typ)); } GetGlobal(index) => { context.step(instruction)?; @@ -976,6 +997,23 @@ impl Compiler { context.step(instruction)?; self.sink.emit(isa::InstructionInternal::F64ReinterpretI64); } + SignExt(ref ext) => match ext { + SignExtInstruction::I32Extend8S => { + self.sink.emit(isa::InstructionInternal::I32Extend8S); + } + SignExtInstruction::I32Extend16S => { + self.sink.emit(isa::InstructionInternal::I32Extend16S); + } + SignExtInstruction::I64Extend8S => { + self.sink.emit(isa::InstructionInternal::I64Extend8S); + } + SignExtInstruction::I64Extend16S => { + self.sink.emit(isa::InstructionInternal::I64Extend16S); + } + SignExtInstruction::I64Extend32S => { + self.sink.emit(isa::InstructionInternal::I64Extend32S); + } + }, _ => { context.step(instruction)?; } @@ -1003,7 +1041,7 @@ fn compute_drop_keep( // only via reaching it's closing `End` operator. (StartedWith::Loop, _) => isa::Keep::None, - (_, BlockType::Value(_)) => isa::Keep::Single, + (_, BlockType::Value(v)) => isa::Keep::Single(v), (_, BlockType::NoResult) => isa::Keep::None, }; @@ -1112,7 +1150,7 @@ fn drop_keep_return( /// by `idx`. /// /// See stack layout definition in mod isa. -fn relative_local_depth( +fn _relative_local_depth( idx: u32, locals: &Locals, value_stack: &StackWithLimit, @@ -1127,6 +1165,26 @@ fn relative_local_depth( Ok(depth) } +/// Returns a relative depth on the stack of a local variable specified +/// by `idx`. +/// +/// See stack layout definition in mod isa. +fn relative_local_depth_type( + idx: u32, + locals: &Locals, + value_stack: &StackWithLimit, +) -> Result<(u32, ValueType), Error> { + let value_stack_height = value_stack.len() as u32; + let locals_and_params_count = locals.count(); + + let depth = value_stack_height + .checked_add(locals_and_params_count) + .and_then(|x| x.checked_sub(idx)) + .ok_or_else(|| Error(String::from("Locals range not in 32-bit range")))?; + let typ = locals.type_of_local(idx)?; + Ok((depth, typ)) +} + /// The target of a branch instruction. /// /// It references a `LabelId` instead of exact instruction address. This is handy diff --git a/src/prepare/tests.rs b/src/prepare/tests.rs index 40005a917a..7e3c5b0c50 100644 --- a/src/prepare/tests.rs +++ b/src/prepare/tests.rs @@ -6,7 +6,10 @@ use std::println; use super::{compile_module, CompiledModule}; use crate::isa; -use parity_wasm::{deserialize_buffer, elements::Module}; +use parity_wasm::{ + deserialize_buffer, + elements::{Module, ValueType}, +}; fn validate(wat: &str) -> CompiledModule { let wasm = wat::parse_str(wat).unwrap(); @@ -81,7 +84,7 @@ fn implicit_return_with_value() { isa::Instruction::I32Const(0), isa::Instruction::Return(isa::DropKeep { drop: 0, - keep: isa::Keep::Single, + keep: isa::Keep::Single(ValueType::I32), }), ] ) @@ -122,10 +125,10 @@ fn get_local() { assert_eq!( code, vec![ - isa::Instruction::GetLocal(1), + isa::Instruction::GetLocal(1, ValueType::I32), isa::Instruction::Return(isa::DropKeep { drop: 1, - keep: isa::Keep::Single, + keep: isa::Keep::Single(ValueType::I32), }), ] ) @@ -148,12 +151,12 @@ fn get_local_2() { assert_eq!( code, vec![ - isa::Instruction::GetLocal(2), - isa::Instruction::GetLocal(2), + isa::Instruction::GetLocal(2, ValueType::I32), + isa::Instruction::GetLocal(2, ValueType::I32), isa::Instruction::Drop, isa::Instruction::Return(isa::DropKeep { drop: 2, - keep: isa::Keep::Single, + keep: isa::Keep::Single(ValueType::I32), }), ] ) @@ -175,14 +178,14 @@ fn explicit_return() { assert_eq!( code, vec![ - isa::Instruction::GetLocal(1), + isa::Instruction::GetLocal(1, ValueType::I32), isa::Instruction::Return(isa::DropKeep { drop: 1, - keep: isa::Keep::Single, + keep: isa::Keep::Single(ValueType::I32), }), isa::Instruction::Return(isa::DropKeep { drop: 1, - keep: isa::Keep::Single, + keep: isa::Keep::Single(ValueType::I32), }), ] ) @@ -210,12 +213,12 @@ fn add_params() { // takes the value below the previous one (i.e the second argument) and then, it increments // the stack pointer. And then the same thing hapens with the value below the previous one // (which happens to be the value loaded by the first get_local). - isa::Instruction::GetLocal(2), - isa::Instruction::GetLocal(2), + isa::Instruction::GetLocal(2, ValueType::I32), + isa::Instruction::GetLocal(2, ValueType::I32), isa::Instruction::I32Add, isa::Instruction::Return(isa::DropKeep { drop: 2, - keep: isa::Keep::Single, + keep: isa::Keep::Single(ValueType::I32), }), ] ) @@ -238,8 +241,9 @@ fn drop_locals() { assert_eq!( code, vec![ - isa::Instruction::GetLocal(2), - isa::Instruction::SetLocal(1), + isa::Instruction::I32Const(0), + isa::Instruction::GetLocal(2, ValueType::I32), + isa::Instruction::SetLocal(1, ValueType::I32), isa::Instruction::Return(isa::DropKeep { drop: 2, keep: isa::Keep::None, @@ -278,13 +282,13 @@ fn if_without_else() { }), isa::Instruction::I32Const(2), isa::Instruction::Return(isa::DropKeep { - drop: 1, // 1 param - keep: isa::Keep::Single, // 1 result + drop: 1, // 1 param + keep: isa::Keep::Single(ValueType::I32), // 1 result }), isa::Instruction::I32Const(3), isa::Instruction::Return(isa::DropKeep { drop: 1, - keep: isa::Keep::Single, + keep: isa::Keep::Single(ValueType::I32), }), ] ) @@ -313,25 +317,26 @@ fn if_else() { assert_eq!( code, vec![ + isa::Instruction::I32Const(0), isa::Instruction::I32Const(1), isa::Instruction::BrIfEqz(isa::Target { - dst_pc: pcs[5], + dst_pc: pcs[6], drop_keep: isa::DropKeep { drop: 0, keep: isa::Keep::None, }, }), isa::Instruction::I32Const(2), - isa::Instruction::SetLocal(1), + isa::Instruction::SetLocal(1, ValueType::I32), isa::Instruction::Br(isa::Target { - dst_pc: pcs[7], + dst_pc: pcs[8], drop_keep: isa::DropKeep { drop: 0, keep: isa::Keep::None, }, }), isa::Instruction::I32Const(3), - isa::Instruction::SetLocal(1), + isa::Instruction::SetLocal(1, ValueType::I32), isa::Instruction::Return(isa::DropKeep { drop: 1, keep: isa::Keep::None, @@ -426,7 +431,7 @@ fn if_else_branch_from_true_branch() { dst_pc: pcs[9], drop_keep: isa::DropKeep { drop: 0, - keep: isa::Keep::Single, + keep: isa::Keep::Single(ValueType::I32), }, }), isa::Instruction::Drop, @@ -495,7 +500,7 @@ fn if_else_branch_from_false_branch() { dst_pc: pcs[9], drop_keep: isa::DropKeep { drop: 0, - keep: isa::Keep::Single, + keep: isa::Keep::Single(ValueType::I32), }, }), isa::Instruction::Drop, @@ -603,14 +608,14 @@ fn spec_as_br_if_value_cond() { dst_pc: 9, drop_keep: isa::DropKeep { drop: 1, - keep: isa::Keep::Single + keep: isa::Keep::Single(ValueType::I32) } }, isa::Target { dst_pc: 9, drop_keep: isa::DropKeep { drop: 1, - keep: isa::Keep::Single + keep: isa::Keep::Single(ValueType::I32) } } ]), @@ -618,14 +623,14 @@ fn spec_as_br_if_value_cond() { dst_pc: 9, drop_keep: isa::DropKeep { drop: 0, - keep: isa::Keep::Single + keep: isa::Keep::Single(ValueType::I32) } }), Drop, I32Const(7), Return(isa::DropKeep { drop: 0, - keep: isa::Keep::Single + keep: isa::Keep::Single(ValueType::I32) }) ] ); @@ -707,13 +712,13 @@ fn brtable_returns_result() { dst_pc: pcs[3], drop_keep: isa::DropKeep { drop: 0, - keep: isa::Keep::Single, + keep: isa::Keep::Single(ValueType::I32), }, }, isa::Target { dst_pc: pcs[4], drop_keep: isa::DropKeep { - keep: isa::Keep::Single, + keep: isa::Keep::Single(ValueType::I32), drop: 0, }, } @@ -750,7 +755,7 @@ fn wabt_example() { assert_eq!( code, vec![ - isa::Instruction::GetLocal(1), + isa::Instruction::GetLocal(1, ValueType::I32), isa::Instruction::BrIfNez(isa::Target { dst_pc: pcs[4], drop_keep: isa::DropKeep { @@ -761,16 +766,16 @@ fn wabt_example() { isa::Instruction::I32Const(1), isa::Instruction::Return(isa::DropKeep { drop: 1, // 1 parameter - keep: isa::Keep::Single, + keep: isa::Keep::Single(ValueType::I32), }), isa::Instruction::I32Const(2), isa::Instruction::Return(isa::DropKeep { drop: 1, - keep: isa::Keep::Single, + keep: isa::Keep::Single(ValueType::I32), }), isa::Instruction::Return(isa::DropKeep { drop: 1, - keep: isa::Keep::Single, + keep: isa::Keep::Single(ValueType::I32), }), ] ) diff --git a/src/runner.rs b/src/runner.rs index 70db7bd3d4..6ec8f93c7c 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -3,10 +3,11 @@ use crate::{ func::{FuncInstance, FuncInstanceInternal, FuncRef}, host::Externals, - isa, + isa::{self}, memory::MemoryRef, memory_units::Pages, module::ModuleRef, + monitor::Monitor, nan_preserving_float::{F32, F64}, value::{ ArithmeticOps, @@ -27,13 +28,17 @@ use crate::{ use alloc::{boxed::Box, vec::Vec}; use core::{fmt, ops, u32, usize}; use parity_wasm::elements::Local; +use specs::mtable::VarType; use validation::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX}; /// Maximum number of bytes on the value stack. -pub const DEFAULT_VALUE_STACK_LIMIT: usize = 1024 * 1024; +/// wasmi's default value is 1024 * 1024, +/// ZKWASM: Maximum number of entries on the value stack. +/// we set 4096 to adapt zkWasm +pub const DEFAULT_VALUE_STACK_LIMIT: usize = 4096; /// Maximum number of levels on the call stack. -pub const DEFAULT_CALL_STACK_LIMIT: usize = 64 * 1024; +pub const DEFAULT_CALL_STACK_LIMIT: usize = 128 * 1024; /// This is a wrapper around u64 to allow us to treat runtime values as a tag-free `u64` /// (where if the runtime value is <64 bits the upper bits are 0). This is safe, since @@ -47,7 +52,7 @@ pub const DEFAULT_CALL_STACK_LIMIT: usize = 64 * 1024; /// at these boundaries. #[derive(Copy, Clone, Debug, PartialEq, Default)] #[repr(transparent)] -struct ValueInternal(pub u64); +pub struct ValueInternal(pub u64); impl ValueInternal { pub fn with_type(self, ty: ValueType) -> RuntimeValue { @@ -60,7 +65,7 @@ impl ValueInternal { } } -trait FromValueInternal +pub trait FromValueInternal where Self: Sized, { @@ -110,6 +115,13 @@ macro_rules! impl_from_value_internal_float { impl_from_value_internal!(i8, u8, i16, u16, i32, u32, i64, u64); impl_from_value_internal_float!(f32, f64, F32, F64); +pub fn from_value_internal_to_u64_with_typ(vtype: VarType, val: ValueInternal) -> u64 { + match vtype { + VarType::I32 => val.0 as u32 as u64, + VarType::I64 => val.0 as u64, + } +} + impl From for ValueInternal { fn from(other: bool) -> Self { (if other { 1 } else { 0 }).into() @@ -172,20 +184,21 @@ enum RunResult { } /// Function interpreter. -pub struct Interpreter { +pub struct Interpreter<'a> { value_stack: ValueStack, call_stack: CallStack, return_type: Option, state: InterpreterState, scratch: Vec, + pub(crate) monitor: Option<&'a mut dyn Monitor>, } -impl Interpreter { +impl<'m> Interpreter<'m> { pub fn new( func: &FuncRef, args: &[RuntimeValue], mut stack_recycler: Option<&mut StackRecycler>, - ) -> Result { + ) -> Result, Trap> { let mut value_stack = StackRecycler::recreate_value_stack(&mut stack_recycler); for &arg in args { let arg = arg.into(); @@ -208,6 +221,7 @@ impl Interpreter { return_type, state: InterpreterState::Initialized, scratch: Vec::new(), + monitor: None, }) } @@ -346,6 +360,10 @@ impl Interpreter { .push(return_val.into()) .map_err(Trap::from)?; } + + self.monitor + .as_mut() + .map(|monitor| monitor.invoke_call_host_post_hook(return_val)); } } } @@ -359,15 +377,48 @@ impl Interpreter { instructions: &isa::Instructions, ) -> Result { let mut iter = instructions.iterate_from(function_context.position); - loop { + let pc = iter.position(); + let sp = self.value_stack.sp; + let instruction = iter.next().expect( "Ran out of instructions, this should be impossible \ since validation ensures that we either have an explicit \ return or an implicit block `end`.", ); - match self.run_instruction(function_context, &instruction)? { + self.monitor.as_mut().map(|monitor| { + monitor.invoke_instruction_pre_hook( + &self.value_stack, + &function_context, + &instruction, + ) + }); + + let current_memory = { + function_context + .memory() + .map_or(0usize, |m| m.current_size().0) + }; + + let outcome = self.run_instruction(function_context, &instruction)?; + + self.monitor.as_mut().map(|monitor| { + monitor.invoke_instruction_post_hook( + function_context + .module + .func_index_by_func_ref(&function_context.function), + pc, + sp.try_into().unwrap(), + current_memory.try_into().unwrap(), + &self.value_stack, + &function_context, + &instruction, + &outcome, + ) + }); + + match outcome { InstructionOutcome::RunNextInstruction => {} InstructionOutcome::Branch(target) => { iter = instructions.iterate_from(target.dst_pc); @@ -400,17 +451,17 @@ impl Interpreter { isa::Instruction::BrIfEqz(target) => self.run_br_eqz(*target), isa::Instruction::BrIfNez(target) => self.run_br_nez(*target), isa::Instruction::BrTable(targets) => self.run_br_table(*targets), - isa::Instruction::Return(drop_keep) => self.run_return(*drop_keep), + isa::Instruction::Return(drop_keep, ..) => self.run_return(*drop_keep), isa::Instruction::Call(index) => self.run_call(context, *index), isa::Instruction::CallIndirect(index) => self.run_call_indirect(context, *index), isa::Instruction::Drop => self.run_drop(), - isa::Instruction::Select => self.run_select(), + isa::Instruction::Select(_) => self.run_select(), - isa::Instruction::GetLocal(depth) => self.run_get_local(*depth), - isa::Instruction::SetLocal(depth) => self.run_set_local(*depth), - isa::Instruction::TeeLocal(depth) => self.run_tee_local(*depth), + isa::Instruction::GetLocal(depth, ..) => self.run_get_local(*depth), + isa::Instruction::SetLocal(depth, ..) => self.run_set_local(*depth), + isa::Instruction::TeeLocal(depth, ..) => self.run_tee_local(*depth), isa::Instruction::GetGlobal(index) => self.run_get_global(context, *index), isa::Instruction::SetGlobal(index) => self.run_set_global(context, *index), @@ -605,6 +656,12 @@ impl Interpreter { isa::Instruction::I64ReinterpretF64 => self.run_reinterpret::(), isa::Instruction::F32ReinterpretI32 => self.run_reinterpret::(), isa::Instruction::F64ReinterpretI64 => self.run_reinterpret::(), + + isa::Instruction::I32Extend8S => self.run_extend::(), + isa::Instruction::I32Extend16S => self.run_extend::(), + isa::Instruction::I64Extend8S => self.run_extend::(), + isa::Instruction::I64Extend16S => self.run_extend::(), + isa::Instruction::I64Extend32S => self.run_extend::(), } } @@ -1268,7 +1325,7 @@ impl Interpreter { } /// Function execution context. -struct FunctionContext { +pub struct FunctionContext { /// Is context initialized. pub is_initialized: bool, /// Internal function reference. @@ -1301,14 +1358,19 @@ impl FunctionContext { pub fn initialize( &mut self, - locals: &[Local], - value_stack: &mut ValueStack, + _locals: &[Local], + _value_stack: &mut ValueStack, ) -> Result<(), TrapCode> { debug_assert!(!self.is_initialized); - let num_locals = locals.iter().map(|l| l.count() as usize).sum(); - - value_stack.extend(num_locals)?; + { + /* + * Since we have explicitly pushed local variables via T.const instruction, + * we bypass extendind value_stack here. + */ + // let num_locals = locals.iter().map(|l| l.count() as usize).sum(); + // value_stack.extend(num_locals)?; + } self.is_initialized = true; Ok(()) @@ -1329,7 +1391,7 @@ impl fmt::Debug for FunctionContext { } } -fn effective_address(address: u32, offset: u32) -> Result { +pub fn effective_address(address: u32, offset: u32) -> Result { match offset.checked_add(address) { None => Err(TrapCode::MemoryAccessOutOfBounds), Some(address) => Ok(address), @@ -1370,7 +1432,7 @@ pub fn check_function_args(signature: &Signature, args: &[RuntimeValue]) -> Resu Ok(()) } -struct ValueStack { +pub struct ValueStack { buf: Box<[ValueInternal]>, /// Index of the first free place in the stack. sp: usize, @@ -1388,7 +1450,7 @@ impl core::fmt::Debug for ValueStack { impl ValueStack { #[inline] fn drop_keep(&mut self, drop_keep: isa::DropKeep) { - if drop_keep.keep == isa::Keep::Single { + if let isa::Keep::Single(_) = drop_keep.keep { let top = *self.top(); *self.pick_mut(drop_keep.drop as usize + 1) = top; } @@ -1426,11 +1488,11 @@ impl ValueStack { } #[inline] - fn top(&self) -> &ValueInternal { + pub fn top(&self) -> &ValueInternal { self.pick(1) } - fn pick(&self, depth: usize) -> &ValueInternal { + pub fn pick(&self, depth: usize) -> &ValueInternal { &self.buf[self.sp - depth] } @@ -1453,6 +1515,7 @@ impl ValueStack { Ok(()) } + #[allow(dead_code)] fn extend(&mut self, len: usize) -> Result<(), TrapCode> { let cells = self .buf @@ -1466,7 +1529,7 @@ impl ValueStack { } #[inline] - fn len(&self) -> usize { + pub fn len(&self) -> usize { self.sp } @@ -1519,7 +1582,7 @@ pub struct StackRecycler { impl StackRecycler { /// Limit stacks created by this recycler to - /// - `value_stack_limit` bytes for values and + /// - `value_stack_limit` entries for values and /// - `call_stack_limit` levels for calls. pub fn with_limits(value_stack_limit: usize, call_stack_limit: usize) -> Self { Self { @@ -1549,8 +1612,7 @@ impl StackRecycler { 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::(); + .map_or(DEFAULT_VALUE_STACK_LIMIT, |this| this.value_stack_limit); let buf = this .as_mut() diff --git a/src/types.rs b/src/types.rs index e38889ee2d..cc9a2bb3d3 100644 --- a/src/types.rs +++ b/src/types.rs @@ -71,6 +71,19 @@ impl Signature { } } +impl Into for Signature { + fn into(self) -> specs::host_function::Signature { + specs::host_function::Signature { + params: self + .params() + .iter() + .map(|param| param.into_elements().into()) + .collect(), + return_type: self.return_type().map(|ret| ret.into_elements().into()), + } + } +} + /// Description of a global variable. /// /// Primarly used to describe imports of global variables. diff --git a/validation/Cargo.toml b/validation/Cargo.toml index 1ed24b217b..fcb039799b 100644 --- a/validation/Cargo.toml +++ b/validation/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/paritytech/wasmi" description = "Wasm code validator" [dependencies] -parity-wasm = { version = "0.42.0", default-features = false } +parity-wasm = { version = "0.42.0", features = ["sign_ext"] } [dev-dependencies] assert_matches = "1.1" diff --git a/validation/src/func.rs b/validation/src/func.rs index 7553401685..d193422ae6 100644 --- a/validation/src/func.rs +++ b/validation/src/func.rs @@ -9,7 +9,15 @@ use crate::{ }; use core::u32; -use parity_wasm::elements::{BlockType, Func, FuncBody, Instruction, TableElementType, ValueType}; +use parity_wasm::elements::{ + BlockType, + Func, + FuncBody, + Instruction, + SignExtInstruction, + TableElementType, + ValueType, +}; /// Maximum number of entries in value stack per function. const DEFAULT_VALUE_STACK_LIMIT: usize = 16384; @@ -784,6 +792,23 @@ impl<'a> FunctionValidationContext<'a> { F64ReinterpretI64 => { self.validate_cvtop(ValueType::I64, ValueType::F64)?; } + SignExt(ref ext) => match ext { + SignExtInstruction::I32Extend8S => { + self.validate_cvtop(ValueType::I32, ValueType::I32)? + } + SignExtInstruction::I32Extend16S => { + self.validate_cvtop(ValueType::I32, ValueType::I32)? + } + SignExtInstruction::I64Extend8S => { + self.validate_cvtop(ValueType::I64, ValueType::I64)? + } + SignExtInstruction::I64Extend16S => { + self.validate_cvtop(ValueType::I64, ValueType::I64)? + } + SignExtInstruction::I64Extend32S => { + self.validate_cvtop(ValueType::I64, ValueType::I64)? + } + }, } Ok(())