diff --git a/Cargo.toml b/Cargo.toml index bb06203..a1c42ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,15 +10,8 @@ description = "WebAssembly interpreter" keywords = ["wasm", "webassembly", "bytecode", "interpreter"] exclude = [ "/res/*", "/tests/*", "/fuzz/*", "/benches/*" ] -[features] -default = ["std"] -# Disable for no_std support -std = ["parity-wasm/std"] -# Enable for no_std support -# hashbrown only works on no_std -core = ["hashbrown/nightly", "libm"] - [dependencies] +wasmi-validation = { path = "validation", default-features = false } parity-wasm = { version = "0.31", default-features = false } hashbrown = { version = "0.1.8", optional = true } memory_units = "0.3.0" @@ -28,3 +21,21 @@ libm = { version = "0.1.2", optional = true } assert_matches = "1.1" rand = "0.4.2" wabt = "0.6" + +[features] +default = ["std"] +# Disable for no_std support +std = [ + "parity-wasm/std", + "wasmi-validation/std", +] +# Enable for no_std support +# hashbrown only works on no_std +core = [ + "wasmi-validation/core", + "hashbrown/nightly", + "libm" +] + +[workspace] +members = ["validation"] diff --git a/src/common/mod.rs b/src/common/mod.rs deleted file mode 100644 index 6fdb382..0000000 --- a/src/common/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod stack; - -/// Index of default linear memory. -pub const DEFAULT_MEMORY_INDEX: u32 = 0; -/// Index of default table. -pub const DEFAULT_TABLE_INDEX: u32 = 0; - -// TODO: Move BlockFrame under validation. diff --git a/src/func.rs b/src/func.rs index bc650dd..3f51ce9 100644 --- a/src/func.rs +++ b/src/func.rs @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use alloc::prelude::*; +use alloc::prelude::v1::*; use alloc::rc::{Rc, Weak}; use core::fmt; use host::Externals; diff --git a/src/imports.rs b/src/imports.rs index 44866a7..5cd72eb 100644 --- a/src/imports.rs +++ b/src/imports.rs @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use alloc::prelude::*; +use alloc::prelude::v1::*; #[cfg(not(feature = "std"))] use hashbrown::HashMap; diff --git a/src/isa.rs b/src/isa.rs index 0325f4e..fcb3905 100644 --- a/src/isa.rs +++ b/src/isa.rs @@ -68,7 +68,7 @@ //! #[allow(unused_imports)] -use alloc::prelude::*; +use alloc::prelude::v1::*; /// Should we keep a value before "discarding" a stack frame? /// @@ -82,6 +82,16 @@ pub enum Keep { Single, } +impl Keep { + /// Reutrns a number of items that should be kept on the stack. + pub fn count(&self) -> u32 { + match *self { + Keep::None => 0, + Keep::Single => 1, + } + } +} + /// Specifies how many values we should keep and how many we should drop. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct DropKeep { diff --git a/src/lib.rs b/src/lib.rs index e2fb2f7..70efe4c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,7 +97,7 @@ #![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] //// alloc is required in no_std -#![cfg_attr(not(feature = "std"), feature(alloc))] +#![cfg_attr(not(feature = "std"), feature(alloc, alloc_prelude))] #[cfg(not(feature = "std"))] #[macro_use] @@ -110,18 +110,19 @@ extern crate std as alloc; extern crate core; #[cfg(test)] -extern crate wabt; -#[cfg(test)] -#[macro_use] extern crate assert_matches; +#[cfg(test)] +extern crate wabt; #[cfg(not(feature = "std"))] extern crate hashbrown; extern crate memory_units as memory_units_crate; extern crate parity_wasm; +extern crate wasmi_validation as validation; + #[allow(unused_imports)] -use alloc::prelude::*; +use alloc::prelude::v1::*; use core::fmt; #[cfg(feature = "std")] use std::error; @@ -380,7 +381,6 @@ impl From for Error { } } -mod common; mod func; mod global; mod host; @@ -389,10 +389,10 @@ mod isa; mod memory; mod module; pub mod nan_preserving_float; +mod prepare; mod runner; mod table; mod types; -mod validation; mod value; #[cfg(test)] @@ -454,8 +454,7 @@ impl Module { /// } /// ``` pub fn from_parity_wasm_module(module: parity_wasm::elements::Module) -> Result { - use validation::{validate_module, ValidatedModule}; - let ValidatedModule { code_map, module } = validate_module(module)?; + let prepare::CompiledModule { code_map, module } = prepare::compile_module(module)?; Ok(Module { code_map, module }) } @@ -517,7 +516,7 @@ impl Module { /// assert!(module.deny_floating_point().is_err()); /// ``` pub fn deny_floating_point(&self) -> Result<(), Error> { - validation::deny_floating_point(&self.module).map_err(Into::into) + prepare::deny_floating_point(&self.module).map_err(Into::into) } /// Create `Module` from a given buffer. diff --git a/src/memory.rs b/src/memory.rs index 62e51b6..1f29f03 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -1,11 +1,12 @@ #[allow(unused_imports)] -use alloc::prelude::*; +use alloc::prelude::v1::*; use alloc::rc::Rc; -use core::cell::{Cell, RefCell}; -use core::cmp; -use core::fmt; -use core::ops::Range; -use core::u32; +use core::{ + cell::{Cell, RefCell}, + cmp, fmt, + ops::Range, + u32, +}; use memory_units::{Bytes, Pages, RoundUpTo}; use parity_wasm::elements::ResizableLimits; use value::LittleEndianConvert; @@ -18,9 +19,6 @@ use Error; /// [`MemoryInstance`]: struct.MemoryInstance.html pub const LINEAR_MEMORY_PAGE_SIZE: Bytes = Bytes(65536); -/// Maximal number of pages. -const LINEAR_MEMORY_MAX_PAGES: Pages = Pages(65536); - /// Reference to a memory (See [`MemoryInstance`] for details). /// /// This reference has a reference-counting semantics. @@ -111,7 +109,22 @@ impl MemoryInstance { /// /// [`LINEAR_MEMORY_PAGE_SIZE`]: constant.LINEAR_MEMORY_PAGE_SIZE.html pub fn alloc(initial: Pages, maximum: Option) -> Result { - validate_memory(initial, maximum).map_err(Error::Memory)?; + { + use std::convert::TryInto; + let initial_u32: u32 = initial.0.try_into().map_err(|_| { + Error::Memory(format!("initial ({}) can't be coerced to u32", initial.0)) + })?; + let maximum_u32: Option = match maximum { + Some(maximum_pages) => Some(maximum_pages.0.try_into().map_err(|_| { + Error::Memory(format!( + "maximum ({}) can't be coerced to u32", + maximum_pages.0 + )) + })?), + None => None, + }; + validation::validate_memory(initial_u32, maximum_u32).map_err(Error::Memory)?; + } let memory = MemoryInstance::new(initial, maximum); Ok(MemoryRef(Rc::new(memory))) @@ -271,7 +284,9 @@ impl MemoryInstance { } let new_size: Pages = size_before_grow + additional; - let maximum = self.maximum.unwrap_or(LINEAR_MEMORY_MAX_PAGES); + let maximum = self + .maximum + .unwrap_or(Pages(validation::LINEAR_MEMORY_MAX_PAGES as usize)); if new_size > maximum { return Err(Error::Memory(format!( "Trying to grow memory by {} pages when already have {}", @@ -549,31 +564,6 @@ impl MemoryInstance { } } -pub fn validate_memory(initial: Pages, maximum: Option) -> Result<(), String> { - if initial > LINEAR_MEMORY_MAX_PAGES { - return Err(format!( - "initial memory size must be at most {} pages", - LINEAR_MEMORY_MAX_PAGES.0 - )); - } - if let Some(maximum) = maximum { - if initial > maximum { - return Err(format!( - "maximum limit {} is less than minimum {}", - maximum.0, initial.0, - )); - } - - if maximum > LINEAR_MEMORY_MAX_PAGES { - return Err(format!( - "maximum memory size must be at most {} pages", - LINEAR_MEMORY_MAX_PAGES.0 - )); - } - } - Ok(()) -} - #[cfg(test)] mod tests { diff --git a/src/module.rs b/src/module.rs index 8222e42..c3b30e2 100644 --- a/src/module.rs +++ b/src/module.rs @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use alloc::prelude::*; +use alloc::prelude::v1::*; use alloc::rc::Rc; use core::cell::RefCell; use core::fmt; @@ -10,7 +10,6 @@ use hashbrown::HashMap; #[cfg(feature = "std")] use std::collections::HashMap; -use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX}; use core::cell::Ref; use func::{FuncBody, FuncInstance, FuncRef}; use global::{GlobalInstance, GlobalRef}; @@ -21,6 +20,7 @@ use memory_units::Pages; use parity_wasm::elements::{External, InitExpr, Instruction, Internal, ResizableLimits, Type}; use table::TableRef; use types::{GlobalDescriptor, MemoryDescriptor, TableDescriptor}; +use validation::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX}; use {Error, MemoryInstance, Module, RuntimeValue, Signature, TableInstance}; /// Reference to a [`ModuleInstance`]. diff --git a/src/nan_preserving_float.rs b/src/nan_preserving_float.rs index 1f12089..e63d476 100644 --- a/src/nan_preserving_float.rs +++ b/src/nan_preserving_float.rs @@ -153,9 +153,11 @@ mod tests { use super::{F32, F64}; - use core::fmt::Debug; - use core::iter; - use core::ops::{Add, Div, Mul, Neg, Sub}; + use core::{ + fmt::Debug, + iter, + ops::{Add, Div, Mul, Neg, Sub}, + }; fn test_ops(iter: I) where diff --git a/src/prepare/compile.rs b/src/prepare/compile.rs new file mode 100644 index 0000000..83b3f6e --- /dev/null +++ b/src/prepare/compile.rs @@ -0,0 +1,1276 @@ +#[allow(unused_imports)] +use alloc::prelude::v1::*; + +use parity_wasm::elements::{BlockType, FuncBody, Instruction}; + +use validation::func::{ + require_label, top_label, BlockFrame, FunctionValidationContext, StackValueType, StartedWith, +}; +use validation::stack::StackWithLimit; +use validation::util::Locals; +use validation::{Error, FuncValidator}; + +use isa; + +/// Type of block frame. +#[derive(Debug, Clone, Copy)] +enum BlockFrameType { + /// Usual block frame. + /// + /// Can be used for an implicit function block. + Block { end_label: LabelId }, + /// Loop frame (branching to the beginning of block). + Loop { header: LabelId }, + /// True-subblock of if expression. + IfTrue { + /// If jump happens inside the if-true block then control will + /// land on this label. + end_label: LabelId, + + /// If the condition of the `if` statement is unsatisfied, control + /// will land on this label. This label might point to `else` block if it + /// exists. Otherwise it equal to `end_label`. + if_not: LabelId, + }, + /// False-subblock of if expression. + IfFalse { end_label: LabelId }, +} + +impl BlockFrameType { + /// Returns a label which should be used as a branch destination. + fn br_destination(&self) -> LabelId { + match *self { + BlockFrameType::Block { end_label } => end_label, + BlockFrameType::Loop { header } => header, + BlockFrameType::IfTrue { end_label, .. } => end_label, + BlockFrameType::IfFalse { end_label } => end_label, + } + } + + /// Returns a label which should be resolved at the `End` opcode. + /// + /// All block types have it except loops. Loops doesn't use end as a branch + /// destination. + fn end_label(&self) -> LabelId { + match *self { + BlockFrameType::Block { end_label } => end_label, + BlockFrameType::IfTrue { end_label, .. } => end_label, + BlockFrameType::IfFalse { end_label } => end_label, + BlockFrameType::Loop { .. } => panic!("loop doesn't use end label"), + } + } +} + +pub struct Compiler { + /// A sink used to emit optimized code. + sink: Sink, + label_stack: Vec, +} + +impl FuncValidator for Compiler { + type Output = isa::Instructions; + fn new(_ctx: &FunctionValidationContext, body: &FuncBody) -> Self { + let code_len = body.code().elements().len(); + let mut compiler = Compiler { + sink: Sink::with_capacity(code_len), + label_stack: Vec::new(), + }; + + // Push implicit frame for the outer function block. + let end_label = compiler.sink.new_label(); + compiler + .label_stack + .push(BlockFrameType::Block { end_label }); + + compiler + } + fn next_instruction( + &mut self, + ctx: &mut FunctionValidationContext, + instruction: &Instruction, + ) -> Result<(), Error> { + self.compile_instruction(ctx, instruction) + } + fn finish(self) -> Self::Output { + self.sink.into_inner() + } +} + +impl Compiler { + fn compile_instruction( + &mut self, + context: &mut FunctionValidationContext, + instruction: &Instruction, + ) -> Result<(), Error> { + use self::Instruction::*; + + match *instruction { + Unreachable => { + self.sink.emit(isa::InstructionInternal::Unreachable); + context.step(instruction)?; + } + Block(_) => { + context.step(instruction)?; + + let end_label = self.sink.new_label(); + self.label_stack.push(BlockFrameType::Block { end_label }); + } + Loop(_) => { + context.step(instruction)?; + + // Resolve loop header right away. + let header = self.sink.new_label(); + self.sink.resolve_label(header); + self.label_stack.push(BlockFrameType::Loop { header }); + } + If(_) => { + context.step(instruction)?; + + // `if_not` will be resolved whenever `End` or `Else` operator will be met. + // `end_label` will always be resolved at `End`. + let if_not = self.sink.new_label(); + let end_label = self.sink.new_label(); + self.label_stack + .push(BlockFrameType::IfTrue { if_not, end_label }); + + self.sink.emit_br_eqz(Target { + label: if_not, + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }); + } + Else => { + context.step(instruction)?; + + let top_label = self.label_stack.pop().expect( + "label_stack should reflect the frame stack; + frame stack is never empty while being processed; qed", + ); + let (if_not, end_label) = match top_label { + BlockFrameType::IfTrue { if_not, end_label } => (if_not, end_label), + _ => unreachable!( + "validation ensures that the top frame was opened by If block; + `top_label` should be `IfTrue` at this point; + this statement is unreachable; + qed" + ), + }; + + // First, we need to finish if-true block: add a jump from the end of the if-true block + // to the "end_label" (it will be resolved at End). + self.sink.emit_br(Target { + label: end_label, + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }); + + // Resolve `if_not` to here so when if condition is unsatisfied control flow + // will jump to this label. + self.sink.resolve_label(if_not); + + self.label_stack.push(BlockFrameType::IfFalse { end_label }); + } + End => { + let started_with = top_label(&context.frame_stack).started_with; + let return_drop_keep = if context.frame_stack.len() == 1 { + // We are about to close the last frame. + Some(drop_keep_return( + &context.locals, + &context.value_stack, + &context.frame_stack, + )) + } else { + None + }; + + context.step(instruction)?; + + let top_frame_type = self.label_stack.pop().expect( + "label_stack should reflect the frame stack; + frame stack is never empty while being processed; qed", + ); + + if let BlockFrameType::IfTrue { if_not, .. } = top_frame_type { + // Resolve `if_not` label. If the `if's` condition doesn't hold the control will jump + // to here. + self.sink.resolve_label(if_not); + } + + // Unless it's a loop, resolve the `end_label` position here. + if started_with != StartedWith::Loop { + let end_label = top_frame_type.end_label(); + self.sink.resolve_label(end_label); + } + + if let Some(drop_keep) = return_drop_keep { + // It was the last instruction. Emit the explicit return instruction. + let drop_keep = drop_keep.expect( + "validation step ensures that the value stack underflows; + validation also ensures that the frame stack is not empty; + `drop_keep_return` can't fail; + qed", + ); + self.sink.emit(isa::InstructionInternal::Return(drop_keep)); + } + } + Br(depth) => { + let target = require_target( + depth, + context.value_stack.len(), + &context.frame_stack, + &self.label_stack, + ); + + context.step(instruction)?; + + let target = target.expect( + "validation step ensures that the value stack underflows; + validation also ensures that the depth is correct; + require_target doesn't fail; + qed", + ); + self.sink.emit_br(target); + } + BrIf(depth) => { + context.step(instruction)?; + + let target = require_target( + depth, + context.value_stack.len(), + &context.frame_stack, + &self.label_stack, + ) + .expect( + "validation step ensures that the value stack underflows; + validation also ensures that the depth is correct; + require_target doesn't fail; + qed", + ); + self.sink.emit_br_nez(target); + } + BrTable(ref table, default) => { + // At this point, the condition value is at the top of the stack. + // But at the point of actual jump the condition will already be + // popped off. + let value_stack_height = context.value_stack.len().saturating_sub(1); + + let mut targets = table + .iter() + .map(|depth| { + require_target( + *depth, + value_stack_height, + &context.frame_stack, + &self.label_stack, + ) + }) + .collect::, _>>(); + let default_target = require_target( + default, + value_stack_height, + &context.frame_stack, + &self.label_stack, + ); + + context.step(instruction)?; + + // These two unwraps are guaranteed to succeed by validation. + const REQUIRE_TARGET_PROOF: &'static str = + "validation step ensures that the value stack underflows; + 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); + + self.sink.emit_br_table(&targets, default_target); + } + Return => { + let drop_keep = + drop_keep_return(&context.locals, &context.value_stack, &context.frame_stack); + + context.step(instruction)?; + + let drop_keep = drop_keep.expect( + "validation step ensures that the value stack underflows; + validation also ensures that the frame stack is not empty; + `drop_keep_return` can't fail; + qed", + ); + self.sink.emit(isa::InstructionInternal::Return(drop_keep)); + } + Call(index) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::Call(index)); + } + CallIndirect(index, _reserved) => { + context.step(instruction)?; + self.sink + .emit(isa::InstructionInternal::CallIndirect(index)); + } + + Drop => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::Drop); + } + Select => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::Select); + } + + 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)?; + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::GetLocal(depth)); + } + SetLocal(index) => { + context.step(instruction)?; + let depth = relative_local_depth(index, &context.locals, &context.value_stack)?; + self.sink.emit(isa::InstructionInternal::SetLocal(depth)); + } + TeeLocal(index) => { + context.step(instruction)?; + let depth = relative_local_depth(index, &context.locals, &context.value_stack)?; + self.sink.emit(isa::InstructionInternal::TeeLocal(depth)); + } + GetGlobal(index) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::GetGlobal(index)); + } + SetGlobal(index) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::SetGlobal(index)); + } + + I32Load(_, offset) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32Load(offset)); + } + I64Load(_, offset) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Load(offset)); + } + F32Load(_, offset) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32Load(offset)); + } + F64Load(_, offset) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64Load(offset)); + } + I32Load8S(_, offset) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32Load8S(offset)); + } + I32Load8U(_, offset) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32Load8U(offset)); + } + I32Load16S(_, offset) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32Load16S(offset)); + } + I32Load16U(_, offset) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32Load16U(offset)); + } + I64Load8S(_, offset) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Load8S(offset)); + } + I64Load8U(_, offset) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Load8U(offset)); + } + I64Load16S(_, offset) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Load16S(offset)); + } + I64Load16U(_, offset) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Load16U(offset)); + } + I64Load32S(_, offset) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Load32S(offset)); + } + I64Load32U(_, offset) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Load32U(offset)); + } + + I32Store(_, offset) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32Store(offset)); + } + I64Store(_, offset) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Store(offset)); + } + F32Store(_, offset) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32Store(offset)); + } + F64Store(_, offset) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64Store(offset)); + } + I32Store8(_, offset) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32Store8(offset)); + } + I32Store16(_, offset) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32Store16(offset)); + } + I64Store8(_, offset) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Store8(offset)); + } + I64Store16(_, offset) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Store16(offset)); + } + I64Store32(_, offset) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Store32(offset)); + } + + CurrentMemory(_) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::CurrentMemory); + } + GrowMemory(_) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::GrowMemory); + } + + I32Const(v) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32Const(v)); + } + I64Const(v) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Const(v)); + } + F32Const(v) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32Const(v)); + } + F64Const(v) => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64Const(v)); + } + + I32Eqz => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32Eqz); + } + I32Eq => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32Eq); + } + I32Ne => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32Ne); + } + I32LtS => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32LtS); + } + I32LtU => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32LtU); + } + I32GtS => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32GtS); + } + I32GtU => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32GtU); + } + I32LeS => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32LeS); + } + I32LeU => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32LeU); + } + I32GeS => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32GeS); + } + I32GeU => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32GeU); + } + + I64Eqz => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Eqz); + } + I64Eq => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Eq); + } + I64Ne => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Ne); + } + I64LtS => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64LtS); + } + I64LtU => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64LtU); + } + I64GtS => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64GtS); + } + I64GtU => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64GtU); + } + I64LeS => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64LeS); + } + I64LeU => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64LeU); + } + I64GeS => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64GeS); + } + I64GeU => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64GeU); + } + + F32Eq => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32Eq); + } + F32Ne => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32Ne); + } + F32Lt => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32Lt); + } + F32Gt => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32Gt); + } + F32Le => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32Le); + } + F32Ge => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32Ge); + } + + F64Eq => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64Eq); + } + F64Ne => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64Ne); + } + F64Lt => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64Lt); + } + F64Gt => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64Gt); + } + F64Le => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64Le); + } + F64Ge => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64Ge); + } + + I32Clz => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32Clz); + } + I32Ctz => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32Ctz); + } + I32Popcnt => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32Popcnt); + } + I32Add => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32Add); + } + I32Sub => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32Sub); + } + I32Mul => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32Mul); + } + I32DivS => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32DivS); + } + I32DivU => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32DivU); + } + I32RemS => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32RemS); + } + I32RemU => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32RemU); + } + I32And => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32And); + } + I32Or => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32Or); + } + I32Xor => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32Xor); + } + I32Shl => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32Shl); + } + I32ShrS => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32ShrS); + } + I32ShrU => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32ShrU); + } + I32Rotl => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32Rotl); + } + I32Rotr => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32Rotr); + } + + I64Clz => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Clz); + } + I64Ctz => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Ctz); + } + I64Popcnt => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Popcnt); + } + I64Add => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Add); + } + I64Sub => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Sub); + } + I64Mul => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Mul); + } + I64DivS => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64DivS); + } + I64DivU => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64DivU); + } + I64RemS => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64RemS); + } + I64RemU => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64RemU); + } + I64And => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64And); + } + I64Or => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Or); + } + I64Xor => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Xor); + } + I64Shl => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Shl); + } + I64ShrS => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64ShrS); + } + I64ShrU => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64ShrU); + } + I64Rotl => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Rotl); + } + I64Rotr => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64Rotr); + } + + F32Abs => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32Abs); + } + F32Neg => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32Neg); + } + F32Ceil => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32Ceil); + } + F32Floor => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32Floor); + } + F32Trunc => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32Trunc); + } + F32Nearest => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32Nearest); + } + F32Sqrt => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32Sqrt); + } + F32Add => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32Add); + } + F32Sub => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32Sub); + } + F32Mul => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32Mul); + } + F32Div => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32Div); + } + F32Min => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32Min); + } + F32Max => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32Max); + } + F32Copysign => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32Copysign); + } + + F64Abs => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64Abs); + } + F64Neg => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64Neg); + } + F64Ceil => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64Ceil); + } + F64Floor => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64Floor); + } + F64Trunc => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64Trunc); + } + F64Nearest => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64Nearest); + } + F64Sqrt => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64Sqrt); + } + F64Add => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64Add); + } + F64Sub => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64Sub); + } + F64Mul => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64Mul); + } + F64Div => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64Div); + } + F64Min => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64Min); + } + F64Max => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64Max); + } + F64Copysign => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64Copysign); + } + + I32WrapI64 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32WrapI64); + } + I32TruncSF32 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32TruncSF32); + } + I32TruncUF32 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32TruncUF32); + } + I32TruncSF64 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32TruncSF64); + } + I32TruncUF64 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32TruncUF64); + } + I64ExtendSI32 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64ExtendSI32); + } + I64ExtendUI32 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64ExtendUI32); + } + I64TruncSF32 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64TruncSF32); + } + I64TruncUF32 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64TruncUF32); + } + I64TruncSF64 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64TruncSF64); + } + I64TruncUF64 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64TruncUF64); + } + F32ConvertSI32 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32ConvertSI32); + } + F32ConvertUI32 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32ConvertUI32); + } + F32ConvertSI64 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32ConvertSI64); + } + F32ConvertUI64 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32ConvertUI64); + } + F32DemoteF64 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32DemoteF64); + } + F64ConvertSI32 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64ConvertSI32); + } + F64ConvertUI32 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64ConvertUI32); + } + F64ConvertSI64 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64ConvertSI64); + } + F64ConvertUI64 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64ConvertUI64); + } + F64PromoteF32 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64PromoteF32); + } + + I32ReinterpretF32 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I32ReinterpretF32); + } + I64ReinterpretF64 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::I64ReinterpretF64); + } + F32ReinterpretI32 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F32ReinterpretI32); + } + F64ReinterpretI64 => { + context.step(instruction)?; + self.sink.emit(isa::InstructionInternal::F64ReinterpretI64); + } + _ => { + context.step(instruction)?; + } + }; + + assert_eq!(self.label_stack.len(), context.frame_stack.len(),); + + Ok(()) + } +} + +/// Computes how many values should be dropped and kept for the specific branch. +/// +/// Returns `Err` if underflow of the value stack detected. +fn compute_drop_keep( + in_stack_polymorphic_state: bool, + started_with: StartedWith, + block_type: BlockType, + actual_value_stack_height: usize, + start_value_stack_height: usize, +) -> Result { + // Find out how many values we need to keep (copy to the new stack location after the drop). + let keep: isa::Keep = match (started_with, block_type) { + // A loop doesn't take a value upon a branch. It can return value + // only via reaching it's closing `End` operator. + (StartedWith::Loop, _) => isa::Keep::None, + + (_, BlockType::Value(_)) => isa::Keep::Single, + (_, BlockType::NoResult) => isa::Keep::None, + }; + + // Find out how many values we need to discard. + let drop = if in_stack_polymorphic_state { + // Polymorphic stack is a weird state. Fortunately, it is always about the code that + // will not be executed, so we don't bother and return 0 here. + 0 + } else { + if actual_value_stack_height < start_value_stack_height { + return Err(Error(format!( + "Stack underflow detected: value stack height ({}) is lower than minimum stack len ({})", + actual_value_stack_height, + start_value_stack_height, + ))); + } + if (actual_value_stack_height as u32 - start_value_stack_height as u32) < keep.count() { + return Err(Error(format!( + "Stack underflow detected: asked to keep {:?} values, but there are only {}", + keep, + actual_value_stack_height as u32 - start_value_stack_height as u32, + ))); + } + (actual_value_stack_height as u32 - start_value_stack_height as u32) - keep.count() + }; + + Ok(isa::DropKeep { drop, keep }) +} + +/// Returns the requested target for branch referred by `depth`. +/// +/// Returns `Err` if +/// - if the `depth` is greater than the current height of the frame stack +/// - if underflow of the value stack detected. +fn require_target( + depth: u32, + value_stack_height: usize, + frame_stack: &StackWithLimit, + label_stack: &[BlockFrameType], +) -> Result { + let is_stack_polymorphic = top_label(frame_stack).polymorphic_stack; + let frame = require_label(depth, frame_stack)?; + + // Get the label by the given `depth`. + let idx = label_stack + .len() + .checked_sub(1) + .expect("this is ensured by `require_label` above") + .checked_sub(depth as usize) + .expect("this is ensured by `require_label` above"); + let label = label_stack + .get(idx) + .expect("this is ensured by `require_label` above"); + + let drop_keep = compute_drop_keep( + is_stack_polymorphic, + frame.started_with, + frame.block_type, + value_stack_height, + frame.value_stack_len, + )?; + + Ok(Target { + label: label.br_destination(), + drop_keep, + }) +} + +/// Compute drop/keep for the return statement. +/// +/// Returns `Err` if: +/// - frame stack is empty. +/// - underflow of the value stack detected. +fn drop_keep_return( + locals: &Locals, + value_stack: &StackWithLimit, + frame_stack: &StackWithLimit, +) -> Result { + if frame_stack.is_empty() { + return Err(Error( + "drop_keep_return can't be called with the frame stack empty".into(), + )); + } + + let is_stack_polymorphic = top_label(frame_stack).polymorphic_stack; + let deepest = frame_stack + .len() + .checked_sub(1) + .expect("frame_stack is not empty") as u32; + let frame = require_label(deepest, frame_stack).expect("frame_stack is not empty"); + let mut drop_keep = compute_drop_keep( + is_stack_polymorphic, + frame.started_with, + frame.block_type, + value_stack.len(), + frame.value_stack_len, + )?; + + // Drop all local variables and parameters upon exit. + drop_keep.drop += locals.count(); + + Ok(drop_keep) +} + +/// 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( + idx: u32, + locals: &Locals, + value_stack: &StackWithLimit, +) -> Result { + 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")))?; + Ok(depth) +} + +/// The target of a branch instruction. +/// +/// It references a `LabelId` instead of exact instruction address. This is handy +/// for emitting code right away with labels resolved later. +#[derive(Clone)] +struct Target { + label: LabelId, + drop_keep: isa::DropKeep, +} + +/// Identifier of a label. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +struct LabelId(usize); + +#[derive(Debug, PartialEq, Eq)] +enum Label { + Resolved(u32), + NotResolved, +} + +struct Sink { + ins: isa::Instructions, + labels: Vec<(Label, Vec)>, +} + +impl Sink { + fn with_capacity(capacity: usize) -> Sink { + Sink { + ins: isa::Instructions::with_capacity(capacity), + labels: Vec::new(), + } + } + + fn cur_pc(&self) -> u32 { + self.ins.current_pc() + } + + fn pc_or_placeholder isa::Reloc>( + &mut self, + label: LabelId, + reloc_creator: F, + ) -> u32 { + match self.labels[label.0] { + (Label::Resolved(dst_pc), _) => dst_pc, + (Label::NotResolved, ref mut unresolved) => { + unresolved.push(reloc_creator()); + u32::max_value() + } + } + } + + fn emit(&mut self, instruction: isa::InstructionInternal) { + self.ins.push(instruction); + } + + fn emit_br(&mut self, target: Target) { + let Target { label, drop_keep } = target; + let pc = self.cur_pc(); + let dst_pc = self.pc_or_placeholder(label, || isa::Reloc::Br { pc }); + self.ins.push(isa::InstructionInternal::Br(isa::Target { + dst_pc, + drop_keep: drop_keep.into(), + })); + } + + fn emit_br_eqz(&mut self, target: Target) { + let Target { label, drop_keep } = target; + let pc = self.cur_pc(); + let dst_pc = self.pc_or_placeholder(label, || isa::Reloc::Br { pc }); + self.ins + .push(isa::InstructionInternal::BrIfEqz(isa::Target { + dst_pc, + drop_keep: drop_keep.into(), + })); + } + + fn emit_br_nez(&mut self, target: Target) { + let Target { label, drop_keep } = target; + let pc = self.cur_pc(); + let dst_pc = self.pc_or_placeholder(label, || isa::Reloc::Br { pc }); + self.ins + .push(isa::InstructionInternal::BrIfNez(isa::Target { + dst_pc, + drop_keep: drop_keep.into(), + })); + } + + fn emit_br_table(&mut self, targets: &[Target], default: Target) { + use core::iter; + + let pc = self.cur_pc(); + + self.ins.push(isa::InstructionInternal::BrTable { + count: targets.len() as u32 + 1, + }); + + for (idx, &Target { label, drop_keep }) in + targets.iter().chain(iter::once(&default)).enumerate() + { + let dst_pc = self.pc_or_placeholder(label, || isa::Reloc::BrTable { pc, idx }); + self.ins + .push(isa::InstructionInternal::BrTableTarget(isa::Target { + dst_pc, + drop_keep: drop_keep.into(), + })); + } + } + + /// Create a new unresolved label. + fn new_label(&mut self) -> LabelId { + let label_idx = self.labels.len(); + self.labels.push((Label::NotResolved, Vec::new())); + LabelId(label_idx) + } + + /// Resolve the label at the current position. + /// + /// Panics if the label is already resolved. + fn resolve_label(&mut self, label: LabelId) { + use core::mem; + + if let (Label::Resolved(_), _) = self.labels[label.0] { + panic!("Trying to resolve already resolved label"); + } + let dst_pc = self.cur_pc(); + + // Patch all relocations that was previously recorded for this + // particular label. + let unresolved_rels = mem::replace(&mut self.labels[label.0].1, Vec::new()); + for reloc in unresolved_rels { + self.ins.patch_relocation(reloc, dst_pc); + } + + // Mark this label as resolved. + self.labels[label.0] = (Label::Resolved(dst_pc), Vec::new()); + } + + /// Consume this Sink and returns isa::Instructions. + fn into_inner(self) -> isa::Instructions { + // At this moment all labels should be resolved. + assert!( + { + self.labels + .iter() + .all(|(state, unresolved)| match (state, unresolved) { + (Label::Resolved(_), unresolved) if unresolved.is_empty() => true, + _ => false, + }) + }, + "there are unresolved labels left: {:?}", + self.labels + ); + self.ins + } +} diff --git a/src/prepare/mod.rs b/src/prepare/mod.rs new file mode 100644 index 0000000..874eeb8 --- /dev/null +++ b/src/prepare/mod.rs @@ -0,0 +1,170 @@ +#[allow(unused_imports)] +use alloc::prelude::v1::*; + +use crate::{ + isa, + validation::{validate_module, Error, Validator}, +}; +use parity_wasm::elements::Module; + +mod compile; + +#[cfg(test)] +mod tests; + +#[derive(Clone)] +pub struct CompiledModule { + pub code_map: Vec, + pub module: Module, +} + +pub struct WasmiValidation { + code_map: Vec, +} + +// This implementation of `Validation` is compiling wasm code at the +// validation time. +impl Validator for WasmiValidation { + type Output = Vec; + type FuncValidator = compile::Compiler; + fn new(_module: &Module) -> Self { + WasmiValidation { + // TODO: with capacity? + code_map: Vec::new(), + } + } + fn on_function_validated(&mut self, _index: u32, output: isa::Instructions) { + self.code_map.push(output); + } + fn finish(self) -> Vec { + self.code_map + } +} + +/// Validate a module and compile it to the internal representation. +pub fn compile_module(module: Module) -> Result { + let code_map = validate_module::(&module)?; + Ok(CompiledModule { module, code_map }) +} + +/// Verify that the module doesn't use floating point instructions or types. +/// +/// Returns `Err` if +/// +/// - Any of function bodies uses a floating pointer instruction (an instruction that +/// consumes or produces a value of a floating point type) +/// - If a floating point type used in a definition of a function. +pub fn deny_floating_point(module: &Module) -> Result<(), Error> { + use parity_wasm::elements::{ + Instruction::{self, *}, + Type, ValueType, + }; + + if let Some(code) = module.code_section() { + for op in code.bodies().iter().flat_map(|body| body.code().elements()) { + macro_rules! match_eq { + ($pattern:pat) => { + |val| if let $pattern = *val { true } else { false } + }; + } + + const DENIED: &[fn(&Instruction) -> bool] = &[ + match_eq!(F32Load(_, _)), + match_eq!(F64Load(_, _)), + match_eq!(F32Store(_, _)), + match_eq!(F64Store(_, _)), + match_eq!(F32Const(_)), + match_eq!(F64Const(_)), + match_eq!(F32Eq), + match_eq!(F32Ne), + match_eq!(F32Lt), + match_eq!(F32Gt), + match_eq!(F32Le), + match_eq!(F32Ge), + match_eq!(F64Eq), + match_eq!(F64Ne), + match_eq!(F64Lt), + match_eq!(F64Gt), + match_eq!(F64Le), + match_eq!(F64Ge), + match_eq!(F32Abs), + match_eq!(F32Neg), + match_eq!(F32Ceil), + match_eq!(F32Floor), + match_eq!(F32Trunc), + match_eq!(F32Nearest), + match_eq!(F32Sqrt), + match_eq!(F32Add), + match_eq!(F32Sub), + match_eq!(F32Mul), + match_eq!(F32Div), + match_eq!(F32Min), + match_eq!(F32Max), + match_eq!(F32Copysign), + match_eq!(F64Abs), + match_eq!(F64Neg), + match_eq!(F64Ceil), + match_eq!(F64Floor), + match_eq!(F64Trunc), + match_eq!(F64Nearest), + match_eq!(F64Sqrt), + match_eq!(F64Add), + match_eq!(F64Sub), + match_eq!(F64Mul), + match_eq!(F64Div), + match_eq!(F64Min), + match_eq!(F64Max), + match_eq!(F64Copysign), + match_eq!(F32ConvertSI32), + match_eq!(F32ConvertUI32), + match_eq!(F32ConvertSI64), + match_eq!(F32ConvertUI64), + match_eq!(F32DemoteF64), + match_eq!(F64ConvertSI32), + match_eq!(F64ConvertUI32), + match_eq!(F64ConvertSI64), + match_eq!(F64ConvertUI64), + match_eq!(F64PromoteF32), + match_eq!(F32ReinterpretI32), + match_eq!(F64ReinterpretI64), + match_eq!(I32TruncSF32), + match_eq!(I32TruncUF32), + match_eq!(I32TruncSF64), + match_eq!(I32TruncUF64), + match_eq!(I64TruncSF32), + match_eq!(I64TruncUF32), + match_eq!(I64TruncSF64), + match_eq!(I64TruncUF64), + match_eq!(I32ReinterpretF32), + match_eq!(I64ReinterpretF64), + ]; + + if DENIED.iter().any(|is_denied| is_denied(op)) { + return Err(Error(format!("Floating point operation denied: {:?}", op))); + } + } + } + + if let (Some(sec), Some(types)) = (module.function_section(), module.type_section()) { + let types = types.types(); + + for sig in sec.entries() { + if let Some(typ) = types.get(sig.type_ref() as usize) { + match *typ { + Type::Function(ref func) => { + if func + .params() + .iter() + .chain(func.return_type().as_ref()) + .any(|&typ| typ == ValueType::F32 || typ == ValueType::F64) + { + return Err(Error(format!("Use of floating point types denied"))); + } + } + } + } + } + } + + Ok(()) +} diff --git a/src/validation/tests.rs b/src/prepare/tests.rs similarity index 65% rename from src/validation/tests.rs rename to src/prepare/tests.rs index 49cee36..9f9faeb 100644 --- a/src/validation/tests.rs +++ b/src/prepare/tests.rs @@ -1,285 +1,17 @@ -use super::{validate_module, ValidatedModule}; +use super::{compile_module, CompiledModule}; +use parity_wasm::{deserialize_buffer, elements::Module}; + use isa; -use parity_wasm::builder::module; -use parity_wasm::elements::{ - deserialize_buffer, BlockType, External, GlobalEntry, GlobalType, ImportEntry, InitExpr, - Instruction, Instructions, MemoryType, Module, TableType, ValueType, -}; use wabt; -#[test] -fn empty_is_valid() { - let module = module().build(); - assert!(validate_module(module).is_ok()); -} - -#[test] -fn limits() { - let test_cases = vec![ - // min > max - (10, Some(9), false), - // min = max - (10, Some(10), true), - // table/memory is always valid without max - (10, None, true), - ]; - - for (min, max, is_valid) in test_cases { - // defined table - let m = module().table().with_min(min).with_max(max).build().build(); - assert_eq!(validate_module(m).is_ok(), is_valid); - - // imported table - let m = module() - .with_import(ImportEntry::new( - "core".into(), - "table".into(), - External::Table(TableType::new(min, max)), - )) - .build(); - assert_eq!(validate_module(m).is_ok(), is_valid); - - // defined memory - let m = module() - .memory() - .with_min(min) - .with_max(max) - .build() - .build(); - assert_eq!(validate_module(m).is_ok(), is_valid); - - // imported table - let m = module() - .with_import(ImportEntry::new( - "core".into(), - "memory".into(), - External::Memory(MemoryType::new(min, max)), - )) - .build(); - assert_eq!(validate_module(m).is_ok(), is_valid); - } -} - -#[test] -fn global_init_const() { - let m = module() - .with_global(GlobalEntry::new( - GlobalType::new(ValueType::I32, true), - InitExpr::new(vec![Instruction::I32Const(42), Instruction::End]), - )) - .build(); - assert!(validate_module(m).is_ok()); - - // init expr type differs from declared global type - let m = module() - .with_global(GlobalEntry::new( - GlobalType::new(ValueType::I64, true), - InitExpr::new(vec![Instruction::I32Const(42), Instruction::End]), - )) - .build(); - assert!(validate_module(m).is_err()); -} - -#[test] -fn global_init_global() { - let m = module() - .with_import(ImportEntry::new( - "env".into(), - "ext_global".into(), - External::Global(GlobalType::new(ValueType::I32, false)), - )) - .with_global(GlobalEntry::new( - GlobalType::new(ValueType::I32, true), - InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]), - )) - .build(); - assert!(validate_module(m).is_ok()); - - // get_global can reference only previously defined globals - let m = module() - .with_global(GlobalEntry::new( - GlobalType::new(ValueType::I32, true), - InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]), - )) - .build(); - assert!(validate_module(m).is_err()); - - // get_global can reference only const globals - let m = module() - .with_import(ImportEntry::new( - "env".into(), - "ext_global".into(), - External::Global(GlobalType::new(ValueType::I32, true)), - )) - .with_global(GlobalEntry::new( - GlobalType::new(ValueType::I32, true), - InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]), - )) - .build(); - assert!(validate_module(m).is_err()); - - // get_global in init_expr can only refer to imported globals. - let m = module() - .with_global(GlobalEntry::new( - GlobalType::new(ValueType::I32, false), - InitExpr::new(vec![Instruction::I32Const(0), Instruction::End]), - )) - .with_global(GlobalEntry::new( - GlobalType::new(ValueType::I32, true), - InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]), - )) - .build(); - assert!(validate_module(m).is_err()); -} - -#[test] -fn global_init_misc() { - // without delimiting End opcode - let m = module() - .with_global(GlobalEntry::new( - GlobalType::new(ValueType::I32, true), - InitExpr::new(vec![Instruction::I32Const(42)]), - )) - .build(); - assert!(validate_module(m).is_err()); - - // empty init expr - let m = module() - .with_global(GlobalEntry::new( - GlobalType::new(ValueType::I32, true), - InitExpr::new(vec![Instruction::End]), - )) - .build(); - assert!(validate_module(m).is_err()); - - // not an constant opcode used - let m = module() - .with_global(GlobalEntry::new( - GlobalType::new(ValueType::I32, true), - InitExpr::new(vec![Instruction::Unreachable, Instruction::End]), - )) - .build(); - assert!(validate_module(m).is_err()); -} - -#[test] -fn module_limits_validity() { - // module cannot contain more than 1 memory atm. - let m = module() - .with_import(ImportEntry::new( - "core".into(), - "memory".into(), - External::Memory(MemoryType::new(10, None)), - )) - .memory() - .with_min(10) - .build() - .build(); - assert!(validate_module(m).is_err()); - - // module cannot contain more than 1 table atm. - let m = module() - .with_import(ImportEntry::new( - "core".into(), - "table".into(), - External::Table(TableType::new(10, None)), - )) - .table() - .with_min(10) - .build() - .build(); - assert!(validate_module(m).is_err()); -} - -#[test] -fn funcs() { - // recursive function calls is legal. - let m = module() - .function() - .signature() - .return_type() - .i32() - .build() - .body() - .with_instructions(Instructions::new(vec![ - Instruction::Call(1), - Instruction::End, - ])) - .build() - .build() - .function() - .signature() - .return_type() - .i32() - .build() - .body() - .with_instructions(Instructions::new(vec![ - Instruction::Call(0), - Instruction::End, - ])) - .build() - .build() - .build(); - assert!(validate_module(m).is_ok()); -} - -#[test] -fn globals() { - // import immutable global is legal. - let m = module() - .with_import(ImportEntry::new( - "env".into(), - "ext_global".into(), - External::Global(GlobalType::new(ValueType::I32, false)), - )) - .build(); - assert!(validate_module(m).is_ok()); - - // import mutable global is invalid. - let m = module() - .with_import(ImportEntry::new( - "env".into(), - "ext_global".into(), - External::Global(GlobalType::new(ValueType::I32, true)), - )) - .build(); - assert!(validate_module(m).is_err()); -} - -#[test] -fn if_else_with_return_type_validation() { - let m = module() - .function() - .signature() - .build() - .body() - .with_instructions(Instructions::new(vec![ - Instruction::I32Const(1), - Instruction::If(BlockType::NoResult), - Instruction::I32Const(1), - Instruction::If(BlockType::Value(ValueType::I32)), - Instruction::I32Const(1), - Instruction::Else, - Instruction::I32Const(2), - Instruction::End, - Instruction::Drop, - Instruction::End, - Instruction::End, - ])) - .build() - .build() - .build(); - validate_module(m).unwrap(); -} - -fn validate(wat: &str) -> ValidatedModule { +fn validate(wat: &str) -> CompiledModule { let wasm = wabt::wat2wasm(wat).unwrap(); let module = deserialize_buffer::(&wasm).unwrap(); - let validated_module = validate_module(module).unwrap(); - validated_module + let compiled_module = compile_module(module).unwrap(); + compiled_module } -fn compile(module: &ValidatedModule) -> (Vec, Vec) { +fn compile(module: &CompiledModule) -> (Vec, Vec) { let code = &module.code_map[0]; let mut instructions = Vec::new(); let mut pcs = Vec::new(); @@ -806,6 +538,68 @@ fn loop_empty() { ) } +#[test] +fn spec_as_br_if_value_cond() { + use self::isa::Instruction::*; + + let module = validate( + r#" + (func (export "as-br_if-value-cond") (result i32) + (block (result i32) + (drop + (br_if 0 + (i32.const 6) + (br_table 0 0 + (i32.const 9) + (i32.const 0) + ) + ) + ) + (i32.const 7) + ) + ) + "#, + ); + let (code, _) = compile(&module); + assert_eq!( + code, + vec![ + I32Const(6), + I32Const(9), + I32Const(0), + isa::Instruction::BrTable(targets![ + isa::Target { + dst_pc: 9, + drop_keep: isa::DropKeep { + drop: 1, + keep: isa::Keep::Single + } + }, + isa::Target { + dst_pc: 9, + drop_keep: isa::DropKeep { + drop: 1, + keep: isa::Keep::Single + } + } + ]), + BrIfNez(isa::Target { + dst_pc: 9, + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::Single + } + }), + Drop, + I32Const(7), + Return(isa::DropKeep { + drop: 0, + keep: isa::Keep::Single + }) + ] + ); +} + #[test] fn brtable() { let module = validate( diff --git a/src/runner.rs b/src/runner.rs index ed7bfc7..9425844 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -1,6 +1,5 @@ #[allow(unused_imports)] -use alloc::prelude::*; -use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX}; +use alloc::prelude::v1::*; use core::fmt; use core::ops; use core::{u32, usize}; @@ -12,6 +11,7 @@ use memory_units::Pages; use module::ModuleRef; use nan_preserving_float::{F32, F64}; use parity_wasm::elements::Local; +use validation::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX}; use value::{ ArithmeticOps, ExtendInto, Float, Integer, LittleEndianConvert, RuntimeValue, TransmuteInto, TryTruncateInto, WrapInto, diff --git a/src/table.rs b/src/table.rs index 7d8d744..63b633f 100644 --- a/src/table.rs +++ b/src/table.rs @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use alloc::prelude::*; +use alloc::prelude::v1::*; use alloc::rc::Rc; use core::cell::RefCell; use core::fmt; diff --git a/src/validation/func.rs b/src/validation/func.rs deleted file mode 100644 index 78dfd5e..0000000 --- a/src/validation/func.rs +++ /dev/null @@ -1,1956 +0,0 @@ -#[allow(unused_imports)] -use alloc::prelude::*; -use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX}; -use core::u32; -use parity_wasm::elements::{BlockType, Func, FuncBody, Instruction, TableElementType, ValueType}; -use validation::context::ModuleContext; - -use validation::util::Locals; -use validation::Error; - -use common::stack::StackWithLimit; -use isa; - -/// Maximum number of entries in value stack per function. -const DEFAULT_VALUE_STACK_LIMIT: usize = 16384; -/// Maximum number of entries in frame stack per function. -const DEFAULT_FRAME_STACK_LIMIT: usize = 16384; - -/// Control stack frame. -#[derive(Debug, Clone)] -struct BlockFrame { - /// Frame type. - frame_type: BlockFrameType, - /// A signature, which is a block signature type indicating the number and types of result values of the region. - block_type: BlockType, - /// A label for reference to block instruction. - begin_position: usize, - /// A limit integer value, which is an index into the value stack indicating where to reset it to on a branch to that label. - value_stack_len: usize, - /// Boolean which signals whether value stack became polymorphic. Value stack starts in non-polymorphic state and - /// becomes polymorphic only after an instruction that never passes control further is executed, - /// i.e. `unreachable`, `br` (but not `br_if`!), etc. - polymorphic_stack: bool, -} - -/// Type of block frame. -#[derive(Debug, Clone, Copy, PartialEq)] -enum BlockFrameType { - /// Usual block frame. - /// - /// Can be used for an implicit function block. - Block { end_label: LabelId }, - /// Loop frame (branching to the beginning of block). - Loop { header: LabelId }, - /// True-subblock of if expression. - IfTrue { - /// If jump happens inside the if-true block then control will - /// land on this label. - end_label: LabelId, - - /// If the condition of the `if` statement is unsatisfied, control - /// will land on this label. This label might point to `else` block if it - /// exists. Otherwise it equal to `end_label`. - if_not: LabelId, - }, - /// False-subblock of if expression. - IfFalse { end_label: LabelId }, -} - -impl BlockFrameType { - /// Returns a label which should be used as a branch destination. - fn br_destination(&self) -> LabelId { - match *self { - BlockFrameType::Block { end_label } => end_label, - BlockFrameType::Loop { header } => header, - BlockFrameType::IfTrue { end_label, .. } => end_label, - BlockFrameType::IfFalse { end_label } => end_label, - } - } - - /// Returns a label which should be resolved at the `End` opcode. - /// - /// All block types have it except loops. Loops doesn't use end as a branch - /// destination. - fn end_label(&self) -> LabelId { - match *self { - BlockFrameType::Block { end_label } => end_label, - BlockFrameType::IfTrue { end_label, .. } => end_label, - BlockFrameType::IfFalse { end_label } => end_label, - BlockFrameType::Loop { .. } => panic!("loop doesn't use end label"), - } - } - - fn is_loop(&self) -> bool { - match *self { - BlockFrameType::Loop { .. } => true, - _ => false, - } - } -} - -/// Value type on the stack. -#[derive(Debug, Clone, Copy)] -enum StackValueType { - /// Any value type. - Any, - /// Concrete value type. - Specific(ValueType), -} - -impl StackValueType { - fn is_any(&self) -> bool { - match self { - &StackValueType::Any => true, - _ => false, - } - } - - fn value_type(&self) -> ValueType { - match self { - &StackValueType::Any => unreachable!("must be checked by caller"), - &StackValueType::Specific(value_type) => value_type, - } - } -} - -impl From for StackValueType { - fn from(value_type: ValueType) -> Self { - StackValueType::Specific(value_type) - } -} - -impl PartialEq for StackValueType { - fn eq(&self, other: &StackValueType) -> bool { - if self.is_any() || other.is_any() { - true - } else { - self.value_type() == other.value_type() - } - } -} - -impl PartialEq for StackValueType { - fn eq(&self, other: &ValueType) -> bool { - if self.is_any() { - true - } else { - self.value_type() == *other - } - } -} - -impl PartialEq for ValueType { - fn eq(&self, other: &StackValueType) -> bool { - other == self - } -} - -/// Instruction outcome. -#[derive(Debug, Clone)] -enum Outcome { - /// Continue with next instruction. - NextInstruction, - /// Unreachable instruction reached. - Unreachable, -} - -pub struct FunctionReader; - -impl FunctionReader { - pub fn read_function( - module: &ModuleContext, - func: &Func, - body: &FuncBody, - ) -> Result { - let (params, result_ty) = module.require_function_type(func.type_ref())?; - - let ins_size_estimate = body.code().elements().len(); - let mut context = FunctionValidationContext::new( - &module, - Locals::new(params, body.locals())?, - DEFAULT_VALUE_STACK_LIMIT, - DEFAULT_FRAME_STACK_LIMIT, - result_ty, - ins_size_estimate, - ); - - let end_label = context.sink.new_label(); - push_label( - BlockFrameType::Block { end_label }, - result_ty, - context.position, - &context.value_stack, - &mut context.frame_stack, - )?; - FunctionReader::read_function_body(&mut context, body.code().elements())?; - - assert!(context.frame_stack.is_empty()); - - Ok(context.into_code()) - } - - fn read_function_body( - context: &mut FunctionValidationContext, - body: &[Instruction], - ) -> Result<(), Error> { - let body_len = body.len(); - if body_len == 0 { - return Err(Error("Non-empty function body expected".into())); - } - - loop { - let instruction = &body[context.position]; - - let outcome = - FunctionReader::read_instruction(context, instruction).map_err(|err| { - Error(format!( - "At instruction {:?}(@{}): {}", - instruction, context.position, err - )) - })?; - - match outcome { - Outcome::NextInstruction => (), - Outcome::Unreachable => { - make_top_frame_polymorphic(&mut context.value_stack, &mut context.frame_stack) - } - } - - context.position += 1; - if context.position == body_len { - return Ok(()); - } - } - } - - fn read_instruction( - context: &mut FunctionValidationContext, - instruction: &Instruction, - ) -> Result { - use self::Instruction::*; - - match *instruction { - // Nop instruction doesn't do anything. It is safe to just skip it. - Nop => {} - - Unreachable => { - context.sink.emit(isa::InstructionInternal::Unreachable); - return Ok(Outcome::Unreachable); - } - - Block(block_type) => { - let end_label = context.sink.new_label(); - push_label( - BlockFrameType::Block { end_label }, - block_type, - context.position, - &context.value_stack, - &mut context.frame_stack, - )?; - } - Loop(block_type) => { - // Resolve loop header right away. - let header = context.sink.new_label(); - context.sink.resolve_label(header); - - push_label( - BlockFrameType::Loop { header }, - block_type, - context.position, - &context.value_stack, - &mut context.frame_stack, - )?; - } - If(block_type) => { - // `if_not` will be resolved whenever `End` or `Else` operator will be met. - // `end_label` will always be resolved at `End`. - let if_not = context.sink.new_label(); - let end_label = context.sink.new_label(); - - pop_value( - &mut context.value_stack, - &context.frame_stack, - ValueType::I32.into(), - )?; - push_label( - BlockFrameType::IfTrue { if_not, end_label }, - block_type, - context.position, - &context.value_stack, - &mut context.frame_stack, - )?; - - context.sink.emit_br_eqz(Target { - label: if_not, - drop_keep: isa::DropKeep { - drop: 0, - keep: isa::Keep::None, - }, - }); - } - Else => { - let (block_type, if_not, end_label) = { - let top_frame = top_label(&context.frame_stack); - - let (if_not, end_label) = match top_frame.frame_type { - BlockFrameType::IfTrue { if_not, end_label } => (if_not, end_label), - _ => return Err(Error("Misplaced else instruction".into())), - }; - (top_frame.block_type, if_not, end_label) - }; - - // First, we need to finish if-true block: add a jump from the end of the if-true block - // to the "end_label" (it will be resolved at End). - context.sink.emit_br(Target { - label: end_label, - drop_keep: isa::DropKeep { - drop: 0, - keep: isa::Keep::None, - }, - }); - - // Resolve `if_not` to here so when if condition is unsatisfied control flow - // will jump to this label. - context.sink.resolve_label(if_not); - - // Then, we pop the current label. It discards all values that pushed in the current - // frame. - pop_label(&mut context.value_stack, &mut context.frame_stack)?; - push_label( - BlockFrameType::IfFalse { end_label }, - block_type, - context.position, - &context.value_stack, - &mut context.frame_stack, - )?; - } - End => { - let (frame_type, block_type) = { - let top = top_label(&context.frame_stack); - (top.frame_type, top.block_type) - }; - - if let BlockFrameType::IfTrue { if_not, .. } = frame_type { - // A `if` without an `else` can't return a result. - if block_type != BlockType::NoResult { - return Err(Error(format!( - "If block without else required to have NoResult block type. But it has {:?} type", - block_type - ))); - } - - // Resolve `if_not` label. If the `if's` condition doesn't hold the control will jump - // to here. - context.sink.resolve_label(if_not); - } - - // Unless it's a loop, resolve the `end_label` position here. - if !frame_type.is_loop() { - let end_label = frame_type.end_label(); - context.sink.resolve_label(end_label); - } - - if context.frame_stack.len() == 1 { - // We are about to close the last frame. Insert - // an explicit return. - - // Check the return type. - if let BlockType::Value(value_type) = context.return_type()? { - tee_value( - &mut context.value_stack, - &context.frame_stack, - value_type.into(), - )?; - } - - // Emit the return instruction. - let drop_keep = drop_keep_return( - &context.locals, - &context.value_stack, - &context.frame_stack, - ); - context - .sink - .emit(isa::InstructionInternal::Return(drop_keep)); - } - - pop_label(&mut context.value_stack, &mut context.frame_stack)?; - - // Push the result value. - if let BlockType::Value(value_type) = block_type { - push_value(&mut context.value_stack, value_type.into())?; - } - } - Br(depth) => { - Validator::validate_br(context, depth)?; - - let target = require_target(depth, &context.value_stack, &context.frame_stack); - context.sink.emit_br(target); - - return Ok(Outcome::Unreachable); - } - BrIf(depth) => { - Validator::validate_br_if(context, depth)?; - - let target = require_target(depth, &context.value_stack, &context.frame_stack); - context.sink.emit_br_nez(target); - } - BrTable(ref table, default) => { - Validator::validate_br_table(context, table, default)?; - - let mut targets = Vec::new(); - for depth in table.iter() { - let target = require_target(*depth, &context.value_stack, &context.frame_stack); - targets.push(target); - } - let default_target = - require_target(default, &context.value_stack, &context.frame_stack); - context.sink.emit_br_table(&targets, default_target); - - return Ok(Outcome::Unreachable); - } - Return => { - if let BlockType::Value(value_type) = context.return_type()? { - tee_value( - &mut context.value_stack, - &context.frame_stack, - value_type.into(), - )?; - } - - let drop_keep = - drop_keep_return(&context.locals, &context.value_stack, &context.frame_stack); - context - .sink - .emit(isa::InstructionInternal::Return(drop_keep)); - - return Ok(Outcome::Unreachable); - } - - Call(index) => { - Validator::validate_call(context, index)?; - context.sink.emit(isa::InstructionInternal::Call(index)); - } - CallIndirect(index, _reserved) => { - Validator::validate_call_indirect(context, index)?; - context - .sink - .emit(isa::InstructionInternal::CallIndirect(index)); - } - - Drop => { - Validator::validate_drop(context)?; - context.sink.emit(isa::InstructionInternal::Drop); - } - Select => { - Validator::validate_select(context)?; - context.sink.emit(isa::InstructionInternal::Select); - } - - 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)?; - Validator::validate_get_local(context, index)?; - context.sink.emit(isa::InstructionInternal::GetLocal(depth)); - } - SetLocal(index) => { - Validator::validate_set_local(context, index)?; - let depth = relative_local_depth(index, &context.locals, &context.value_stack)?; - context.sink.emit(isa::InstructionInternal::SetLocal(depth)); - } - TeeLocal(index) => { - Validator::validate_tee_local(context, index)?; - let depth = relative_local_depth(index, &context.locals, &context.value_stack)?; - context.sink.emit(isa::InstructionInternal::TeeLocal(depth)); - } - GetGlobal(index) => { - Validator::validate_get_global(context, index)?; - context - .sink - .emit(isa::InstructionInternal::GetGlobal(index)); - } - SetGlobal(index) => { - Validator::validate_set_global(context, index)?; - context - .sink - .emit(isa::InstructionInternal::SetGlobal(index)); - } - - I32Load(align, offset) => { - Validator::validate_load(context, align, 4, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32Load(offset)); - } - I64Load(align, offset) => { - Validator::validate_load(context, align, 8, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64Load(offset)); - } - F32Load(align, offset) => { - Validator::validate_load(context, align, 4, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32Load(offset)); - } - F64Load(align, offset) => { - Validator::validate_load(context, align, 8, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64Load(offset)); - } - I32Load8S(align, offset) => { - Validator::validate_load(context, align, 1, ValueType::I32)?; - context - .sink - .emit(isa::InstructionInternal::I32Load8S(offset)); - } - I32Load8U(align, offset) => { - Validator::validate_load(context, align, 1, ValueType::I32)?; - context - .sink - .emit(isa::InstructionInternal::I32Load8U(offset)); - } - I32Load16S(align, offset) => { - Validator::validate_load(context, align, 2, ValueType::I32)?; - context - .sink - .emit(isa::InstructionInternal::I32Load16S(offset)); - } - I32Load16U(align, offset) => { - Validator::validate_load(context, align, 2, ValueType::I32)?; - context - .sink - .emit(isa::InstructionInternal::I32Load16U(offset)); - } - I64Load8S(align, offset) => { - Validator::validate_load(context, align, 1, ValueType::I64)?; - context - .sink - .emit(isa::InstructionInternal::I64Load8S(offset)); - } - I64Load8U(align, offset) => { - Validator::validate_load(context, align, 1, ValueType::I64)?; - context - .sink - .emit(isa::InstructionInternal::I64Load8U(offset)); - } - I64Load16S(align, offset) => { - Validator::validate_load(context, align, 2, ValueType::I64)?; - context - .sink - .emit(isa::InstructionInternal::I64Load16S(offset)); - } - I64Load16U(align, offset) => { - Validator::validate_load(context, align, 2, ValueType::I64)?; - context - .sink - .emit(isa::InstructionInternal::I64Load16U(offset)); - } - I64Load32S(align, offset) => { - Validator::validate_load(context, align, 4, ValueType::I64)?; - context - .sink - .emit(isa::InstructionInternal::I64Load32S(offset)); - } - I64Load32U(align, offset) => { - Validator::validate_load(context, align, 4, ValueType::I64)?; - context - .sink - .emit(isa::InstructionInternal::I64Load32U(offset)); - } - - I32Store(align, offset) => { - Validator::validate_store(context, align, 4, ValueType::I32)?; - context - .sink - .emit(isa::InstructionInternal::I32Store(offset)); - } - I64Store(align, offset) => { - Validator::validate_store(context, align, 8, ValueType::I64)?; - context - .sink - .emit(isa::InstructionInternal::I64Store(offset)); - } - F32Store(align, offset) => { - Validator::validate_store(context, align, 4, ValueType::F32)?; - context - .sink - .emit(isa::InstructionInternal::F32Store(offset)); - } - F64Store(align, offset) => { - Validator::validate_store(context, align, 8, ValueType::F64)?; - context - .sink - .emit(isa::InstructionInternal::F64Store(offset)); - } - I32Store8(align, offset) => { - Validator::validate_store(context, align, 1, ValueType::I32)?; - context - .sink - .emit(isa::InstructionInternal::I32Store8(offset)); - } - I32Store16(align, offset) => { - Validator::validate_store(context, align, 2, ValueType::I32)?; - context - .sink - .emit(isa::InstructionInternal::I32Store16(offset)); - } - I64Store8(align, offset) => { - Validator::validate_store(context, align, 1, ValueType::I64)?; - context - .sink - .emit(isa::InstructionInternal::I64Store8(offset)); - } - I64Store16(align, offset) => { - Validator::validate_store(context, align, 2, ValueType::I64)?; - context - .sink - .emit(isa::InstructionInternal::I64Store16(offset)); - } - I64Store32(align, offset) => { - Validator::validate_store(context, align, 4, ValueType::I64)?; - context - .sink - .emit(isa::InstructionInternal::I64Store32(offset)); - } - - CurrentMemory(_) => { - Validator::validate_current_memory(context)?; - context.sink.emit(isa::InstructionInternal::CurrentMemory); - } - GrowMemory(_) => { - Validator::validate_grow_memory(context)?; - context.sink.emit(isa::InstructionInternal::GrowMemory); - } - - I32Const(v) => { - Validator::validate_const(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32Const(v)); - } - I64Const(v) => { - Validator::validate_const(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64Const(v)); - } - F32Const(v) => { - Validator::validate_const(context, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32Const(v)); - } - F64Const(v) => { - Validator::validate_const(context, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64Const(v)); - } - - I32Eqz => { - Validator::validate_testop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32Eqz); - } - I32Eq => { - Validator::validate_relop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32Eq); - } - I32Ne => { - Validator::validate_relop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32Ne); - } - I32LtS => { - Validator::validate_relop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32LtS); - } - I32LtU => { - Validator::validate_relop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32LtU); - } - I32GtS => { - Validator::validate_relop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32GtS); - } - I32GtU => { - Validator::validate_relop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32GtU); - } - I32LeS => { - Validator::validate_relop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32LeS); - } - I32LeU => { - Validator::validate_relop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32LeU); - } - I32GeS => { - Validator::validate_relop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32GeS); - } - I32GeU => { - Validator::validate_relop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32GeU); - } - - I64Eqz => { - Validator::validate_testop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64Eqz); - } - I64Eq => { - Validator::validate_relop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64Eq); - } - I64Ne => { - Validator::validate_relop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64Ne); - } - I64LtS => { - Validator::validate_relop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64LtS); - } - I64LtU => { - Validator::validate_relop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64LtU); - } - I64GtS => { - Validator::validate_relop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64GtS); - } - I64GtU => { - Validator::validate_relop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64GtU); - } - I64LeS => { - Validator::validate_relop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64LeS); - } - I64LeU => { - Validator::validate_relop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64LeU); - } - I64GeS => { - Validator::validate_relop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64GeS); - } - I64GeU => { - Validator::validate_relop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64GeU); - } - - F32Eq => { - Validator::validate_relop(context, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32Eq); - } - F32Ne => { - Validator::validate_relop(context, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32Ne); - } - F32Lt => { - Validator::validate_relop(context, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32Lt); - } - F32Gt => { - Validator::validate_relop(context, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32Gt); - } - F32Le => { - Validator::validate_relop(context, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32Le); - } - F32Ge => { - Validator::validate_relop(context, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32Ge); - } - - F64Eq => { - Validator::validate_relop(context, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64Eq); - } - F64Ne => { - Validator::validate_relop(context, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64Ne); - } - F64Lt => { - Validator::validate_relop(context, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64Lt); - } - F64Gt => { - Validator::validate_relop(context, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64Gt); - } - F64Le => { - Validator::validate_relop(context, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64Le); - } - F64Ge => { - Validator::validate_relop(context, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64Ge); - } - - I32Clz => { - Validator::validate_unop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32Clz); - } - I32Ctz => { - Validator::validate_unop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32Ctz); - } - I32Popcnt => { - Validator::validate_unop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32Popcnt); - } - I32Add => { - Validator::validate_binop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32Add); - } - I32Sub => { - Validator::validate_binop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32Sub); - } - I32Mul => { - Validator::validate_binop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32Mul); - } - I32DivS => { - Validator::validate_binop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32DivS); - } - I32DivU => { - Validator::validate_binop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32DivU); - } - I32RemS => { - Validator::validate_binop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32RemS); - } - I32RemU => { - Validator::validate_binop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32RemU); - } - I32And => { - Validator::validate_binop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32And); - } - I32Or => { - Validator::validate_binop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32Or); - } - I32Xor => { - Validator::validate_binop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32Xor); - } - I32Shl => { - Validator::validate_binop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32Shl); - } - I32ShrS => { - Validator::validate_binop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32ShrS); - } - I32ShrU => { - Validator::validate_binop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32ShrU); - } - I32Rotl => { - Validator::validate_binop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32Rotl); - } - I32Rotr => { - Validator::validate_binop(context, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32Rotr); - } - - I64Clz => { - Validator::validate_unop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64Clz); - } - I64Ctz => { - Validator::validate_unop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64Ctz); - } - I64Popcnt => { - Validator::validate_unop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64Popcnt); - } - I64Add => { - Validator::validate_binop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64Add); - } - I64Sub => { - Validator::validate_binop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64Sub); - } - I64Mul => { - Validator::validate_binop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64Mul); - } - I64DivS => { - Validator::validate_binop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64DivS); - } - I64DivU => { - Validator::validate_binop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64DivU); - } - I64RemS => { - Validator::validate_binop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64RemS); - } - I64RemU => { - Validator::validate_binop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64RemU); - } - I64And => { - Validator::validate_binop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64And); - } - I64Or => { - Validator::validate_binop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64Or); - } - I64Xor => { - Validator::validate_binop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64Xor); - } - I64Shl => { - Validator::validate_binop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64Shl); - } - I64ShrS => { - Validator::validate_binop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64ShrS); - } - I64ShrU => { - Validator::validate_binop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64ShrU); - } - I64Rotl => { - Validator::validate_binop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64Rotl); - } - I64Rotr => { - Validator::validate_binop(context, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64Rotr); - } - - F32Abs => { - Validator::validate_unop(context, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32Abs); - } - F32Neg => { - Validator::validate_unop(context, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32Neg); - } - F32Ceil => { - Validator::validate_unop(context, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32Ceil); - } - F32Floor => { - Validator::validate_unop(context, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32Floor); - } - F32Trunc => { - Validator::validate_unop(context, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32Trunc); - } - F32Nearest => { - Validator::validate_unop(context, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32Nearest); - } - F32Sqrt => { - Validator::validate_unop(context, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32Sqrt); - } - F32Add => { - Validator::validate_binop(context, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32Add); - } - F32Sub => { - Validator::validate_binop(context, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32Sub); - } - F32Mul => { - Validator::validate_binop(context, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32Mul); - } - F32Div => { - Validator::validate_binop(context, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32Div); - } - F32Min => { - Validator::validate_binop(context, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32Min); - } - F32Max => { - Validator::validate_binop(context, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32Max); - } - F32Copysign => { - Validator::validate_binop(context, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32Copysign); - } - - F64Abs => { - Validator::validate_unop(context, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64Abs); - } - F64Neg => { - Validator::validate_unop(context, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64Neg); - } - F64Ceil => { - Validator::validate_unop(context, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64Ceil); - } - F64Floor => { - Validator::validate_unop(context, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64Floor); - } - F64Trunc => { - Validator::validate_unop(context, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64Trunc); - } - F64Nearest => { - Validator::validate_unop(context, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64Nearest); - } - F64Sqrt => { - Validator::validate_unop(context, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64Sqrt); - } - F64Add => { - Validator::validate_binop(context, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64Add); - } - F64Sub => { - Validator::validate_binop(context, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64Sub); - } - F64Mul => { - Validator::validate_binop(context, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64Mul); - } - F64Div => { - Validator::validate_binop(context, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64Div); - } - F64Min => { - Validator::validate_binop(context, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64Min); - } - F64Max => { - Validator::validate_binop(context, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64Max); - } - F64Copysign => { - Validator::validate_binop(context, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64Copysign); - } - - I32WrapI64 => { - Validator::validate_cvtop(context, ValueType::I64, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32WrapI64); - } - I32TruncSF32 => { - Validator::validate_cvtop(context, ValueType::F32, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32TruncSF32); - } - I32TruncUF32 => { - Validator::validate_cvtop(context, ValueType::F32, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32TruncUF32); - } - I32TruncSF64 => { - Validator::validate_cvtop(context, ValueType::F64, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32TruncSF64); - } - I32TruncUF64 => { - Validator::validate_cvtop(context, ValueType::F64, ValueType::I32)?; - context.sink.emit(isa::InstructionInternal::I32TruncUF64); - } - I64ExtendSI32 => { - Validator::validate_cvtop(context, ValueType::I32, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64ExtendSI32); - } - I64ExtendUI32 => { - Validator::validate_cvtop(context, ValueType::I32, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64ExtendUI32); - } - I64TruncSF32 => { - Validator::validate_cvtop(context, ValueType::F32, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64TruncSF32); - } - I64TruncUF32 => { - Validator::validate_cvtop(context, ValueType::F32, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64TruncUF32); - } - I64TruncSF64 => { - Validator::validate_cvtop(context, ValueType::F64, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64TruncSF64); - } - I64TruncUF64 => { - Validator::validate_cvtop(context, ValueType::F64, ValueType::I64)?; - context.sink.emit(isa::InstructionInternal::I64TruncUF64); - } - F32ConvertSI32 => { - Validator::validate_cvtop(context, ValueType::I32, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32ConvertSI32); - } - F32ConvertUI32 => { - Validator::validate_cvtop(context, ValueType::I32, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32ConvertUI32); - } - F32ConvertSI64 => { - Validator::validate_cvtop(context, ValueType::I64, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32ConvertSI64); - } - F32ConvertUI64 => { - Validator::validate_cvtop(context, ValueType::I64, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32ConvertUI64); - } - F32DemoteF64 => { - Validator::validate_cvtop(context, ValueType::F64, ValueType::F32)?; - context.sink.emit(isa::InstructionInternal::F32DemoteF64); - } - F64ConvertSI32 => { - Validator::validate_cvtop(context, ValueType::I32, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64ConvertSI32); - } - F64ConvertUI32 => { - Validator::validate_cvtop(context, ValueType::I32, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64ConvertUI32); - } - F64ConvertSI64 => { - Validator::validate_cvtop(context, ValueType::I64, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64ConvertSI64); - } - F64ConvertUI64 => { - Validator::validate_cvtop(context, ValueType::I64, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64ConvertUI64); - } - F64PromoteF32 => { - Validator::validate_cvtop(context, ValueType::F32, ValueType::F64)?; - context.sink.emit(isa::InstructionInternal::F64PromoteF32); - } - - I32ReinterpretF32 => { - Validator::validate_cvtop(context, ValueType::F32, ValueType::I32)?; - context - .sink - .emit(isa::InstructionInternal::I32ReinterpretF32); - } - I64ReinterpretF64 => { - Validator::validate_cvtop(context, ValueType::F64, ValueType::I64)?; - context - .sink - .emit(isa::InstructionInternal::I64ReinterpretF64); - } - F32ReinterpretI32 => { - Validator::validate_cvtop(context, ValueType::I32, ValueType::F32)?; - context - .sink - .emit(isa::InstructionInternal::F32ReinterpretI32); - } - F64ReinterpretI64 => { - Validator::validate_cvtop(context, ValueType::I64, ValueType::F64)?; - context - .sink - .emit(isa::InstructionInternal::F64ReinterpretI64); - } - } - - Ok(Outcome::NextInstruction) - } -} - -/// Function validator. -struct Validator; - -impl Validator { - fn validate_const( - context: &mut FunctionValidationContext, - value_type: ValueType, - ) -> Result<(), Error> { - push_value(&mut context.value_stack, value_type.into())?; - Ok(()) - } - - fn validate_unop( - context: &mut FunctionValidationContext, - value_type: ValueType, - ) -> Result<(), Error> { - pop_value( - &mut context.value_stack, - &context.frame_stack, - value_type.into(), - )?; - push_value(&mut context.value_stack, value_type.into())?; - Ok(()) - } - - fn validate_binop( - context: &mut FunctionValidationContext, - value_type: ValueType, - ) -> Result<(), Error> { - pop_value( - &mut context.value_stack, - &context.frame_stack, - value_type.into(), - )?; - pop_value( - &mut context.value_stack, - &context.frame_stack, - value_type.into(), - )?; - push_value(&mut context.value_stack, value_type.into())?; - Ok(()) - } - - fn validate_testop( - context: &mut FunctionValidationContext, - value_type: ValueType, - ) -> Result<(), Error> { - pop_value( - &mut context.value_stack, - &context.frame_stack, - value_type.into(), - )?; - push_value(&mut context.value_stack, ValueType::I32.into())?; - Ok(()) - } - - fn validate_relop( - context: &mut FunctionValidationContext, - value_type: ValueType, - ) -> Result<(), Error> { - pop_value( - &mut context.value_stack, - &context.frame_stack, - value_type.into(), - )?; - pop_value( - &mut context.value_stack, - &context.frame_stack, - value_type.into(), - )?; - push_value(&mut context.value_stack, ValueType::I32.into())?; - Ok(()) - } - - fn validate_cvtop( - context: &mut FunctionValidationContext, - value_type1: ValueType, - value_type2: ValueType, - ) -> Result<(), Error> { - pop_value( - &mut context.value_stack, - &context.frame_stack, - value_type1.into(), - )?; - push_value(&mut context.value_stack, value_type2.into())?; - Ok(()) - } - - fn validate_drop(context: &mut FunctionValidationContext) -> Result<(), Error> { - pop_value( - &mut context.value_stack, - &context.frame_stack, - StackValueType::Any, - )?; - Ok(()) - } - - fn validate_select(context: &mut FunctionValidationContext) -> Result<(), Error> { - pop_value( - &mut context.value_stack, - &context.frame_stack, - ValueType::I32.into(), - )?; - let select_type = pop_value( - &mut context.value_stack, - &context.frame_stack, - StackValueType::Any, - )?; - pop_value(&mut context.value_stack, &context.frame_stack, select_type)?; - push_value(&mut context.value_stack, select_type)?; - Ok(()) - } - - fn validate_get_local( - context: &mut FunctionValidationContext, - index: u32, - ) -> Result<(), Error> { - let local_type = require_local(&context.locals, index)?; - push_value(&mut context.value_stack, local_type.into())?; - Ok(()) - } - - fn validate_set_local( - context: &mut FunctionValidationContext, - index: u32, - ) -> Result<(), Error> { - let local_type = require_local(&context.locals, index)?; - let value_type = pop_value( - &mut context.value_stack, - &context.frame_stack, - StackValueType::Any, - )?; - if StackValueType::from(local_type) != value_type { - return Err(Error(format!( - "Trying to update local {} of type {:?} with value of type {:?}", - index, local_type, value_type - ))); - } - Ok(()) - } - - fn validate_tee_local( - context: &mut FunctionValidationContext, - index: u32, - ) -> Result<(), Error> { - let local_type = require_local(&context.locals, index)?; - tee_value( - &mut context.value_stack, - &context.frame_stack, - local_type.into(), - )?; - Ok(()) - } - - fn validate_get_global( - context: &mut FunctionValidationContext, - index: u32, - ) -> Result<(), Error> { - let global_type: StackValueType = { - let global = context.module.require_global(index, None)?; - global.content_type().into() - }; - push_value(&mut context.value_stack, global_type)?; - Ok(()) - } - - fn validate_set_global( - context: &mut FunctionValidationContext, - index: u32, - ) -> Result<(), Error> { - let global_type: StackValueType = { - let global = context.module.require_global(index, Some(true))?; - global.content_type().into() - }; - let value_type = pop_value( - &mut context.value_stack, - &context.frame_stack, - StackValueType::Any, - )?; - if global_type != value_type { - return Err(Error(format!( - "Trying to update global {} of type {:?} with value of type {:?}", - index, global_type, value_type - ))); - } - Ok(()) - } - - fn validate_load( - context: &mut FunctionValidationContext, - align: u32, - max_align: u32, - value_type: ValueType, - ) -> Result<(), Error> { - if 1u32.checked_shl(align).unwrap_or(u32::MAX) > max_align { - return Err(Error(format!( - "Too large memory alignment 2^{} (expected at most {})", - align, max_align - ))); - } - - pop_value( - &mut context.value_stack, - &context.frame_stack, - ValueType::I32.into(), - )?; - context.module.require_memory(DEFAULT_MEMORY_INDEX)?; - push_value(&mut context.value_stack, value_type.into())?; - Ok(()) - } - - fn validate_store( - context: &mut FunctionValidationContext, - align: u32, - max_align: u32, - value_type: ValueType, - ) -> Result<(), Error> { - if 1u32.checked_shl(align).unwrap_or(u32::MAX) > max_align { - return Err(Error(format!( - "Too large memory alignment 2^{} (expected at most {})", - align, max_align - ))); - } - - context.module.require_memory(DEFAULT_MEMORY_INDEX)?; - pop_value( - &mut context.value_stack, - &context.frame_stack, - value_type.into(), - )?; - pop_value( - &mut context.value_stack, - &context.frame_stack, - ValueType::I32.into(), - )?; - Ok(()) - } - - fn validate_br(context: &mut FunctionValidationContext, depth: u32) -> Result<(), Error> { - let (frame_type, frame_block_type) = { - let frame = require_label(depth, &context.frame_stack)?; - (frame.frame_type, frame.block_type) - }; - if !frame_type.is_loop() { - if let BlockType::Value(value_type) = frame_block_type { - tee_value( - &mut context.value_stack, - &context.frame_stack, - value_type.into(), - )?; - } - } - Ok(()) - } - - fn validate_br_if(context: &mut FunctionValidationContext, depth: u32) -> Result<(), Error> { - pop_value( - &mut context.value_stack, - &context.frame_stack, - ValueType::I32.into(), - )?; - - let (frame_type, frame_block_type) = { - let frame = require_label(depth, &context.frame_stack)?; - (frame.frame_type, frame.block_type) - }; - if !frame_type.is_loop() { - if let BlockType::Value(value_type) = frame_block_type { - tee_value( - &mut context.value_stack, - &context.frame_stack, - value_type.into(), - )?; - } - } - Ok(()) - } - - fn validate_br_table( - context: &mut FunctionValidationContext, - table: &[u32], - default: u32, - ) -> Result<(), Error> { - let required_block_type: BlockType = { - let default_block = require_label(default, &context.frame_stack)?; - let required_block_type = if !default_block.frame_type.is_loop() { - default_block.block_type - } else { - BlockType::NoResult - }; - - for label in table { - let label_block = require_label(*label, &context.frame_stack)?; - let label_block_type = if !label_block.frame_type.is_loop() { - label_block.block_type - } else { - BlockType::NoResult - }; - if required_block_type != label_block_type { - return Err(Error(format!( - "Labels in br_table points to block of different types: {:?} and {:?}", - required_block_type, label_block.block_type - ))); - } - } - required_block_type - }; - - pop_value( - &mut context.value_stack, - &context.frame_stack, - ValueType::I32.into(), - )?; - if let BlockType::Value(value_type) = required_block_type { - tee_value( - &mut context.value_stack, - &context.frame_stack, - value_type.into(), - )?; - } - - Ok(()) - } - - fn validate_call(context: &mut FunctionValidationContext, idx: u32) -> Result<(), Error> { - let (argument_types, return_type) = context.module.require_function(idx)?; - for argument_type in argument_types.iter().rev() { - pop_value( - &mut context.value_stack, - &context.frame_stack, - (*argument_type).into(), - )?; - } - if let BlockType::Value(value_type) = return_type { - push_value(&mut context.value_stack, value_type.into())?; - } - Ok(()) - } - - fn validate_call_indirect( - context: &mut FunctionValidationContext, - idx: u32, - ) -> Result<(), Error> { - { - let table = context.module.require_table(DEFAULT_TABLE_INDEX)?; - if table.elem_type() != TableElementType::AnyFunc { - return Err(Error(format!( - "Table {} has element type {:?} while `anyfunc` expected", - idx, - table.elem_type() - ))); - } - } - - pop_value( - &mut context.value_stack, - &context.frame_stack, - ValueType::I32.into(), - )?; - let (argument_types, return_type) = context.module.require_function_type(idx)?; - for argument_type in argument_types.iter().rev() { - pop_value( - &mut context.value_stack, - &context.frame_stack, - (*argument_type).into(), - )?; - } - if let BlockType::Value(value_type) = return_type { - push_value(&mut context.value_stack, value_type.into())?; - } - Ok(()) - } - - fn validate_current_memory(context: &mut FunctionValidationContext) -> Result<(), Error> { - context.module.require_memory(DEFAULT_MEMORY_INDEX)?; - push_value(&mut context.value_stack, ValueType::I32.into())?; - Ok(()) - } - - fn validate_grow_memory(context: &mut FunctionValidationContext) -> Result<(), Error> { - context.module.require_memory(DEFAULT_MEMORY_INDEX)?; - pop_value( - &mut context.value_stack, - &context.frame_stack, - ValueType::I32.into(), - )?; - push_value(&mut context.value_stack, ValueType::I32.into())?; - Ok(()) - } -} - -/// Function validation context. -struct FunctionValidationContext<'a> { - /// Wasm module - module: &'a ModuleContext, - /// Current instruction position. - position: usize, - /// Local variables. - locals: Locals<'a>, - /// Value stack. - value_stack: StackWithLimit, - /// Frame stack. - frame_stack: StackWithLimit, - /// Function return type. - return_type: BlockType, - /// A sink used to emit optimized code. - sink: Sink, -} - -impl<'a> FunctionValidationContext<'a> { - fn new( - module: &'a ModuleContext, - locals: Locals<'a>, - value_stack_limit: usize, - frame_stack_limit: usize, - return_type: BlockType, - size_estimate: usize, - ) -> Self { - FunctionValidationContext { - module: module, - position: 0, - locals: locals, - value_stack: StackWithLimit::with_limit(value_stack_limit), - frame_stack: StackWithLimit::with_limit(frame_stack_limit), - return_type: return_type, - sink: Sink::with_instruction_capacity(size_estimate), - } - } - - fn return_type(&self) -> Result { - Ok(self.return_type) - } - - fn into_code(self) -> isa::Instructions { - self.sink.into_inner() - } -} - -fn make_top_frame_polymorphic( - value_stack: &mut StackWithLimit, - frame_stack: &mut StackWithLimit, -) { - let frame = frame_stack - .top_mut() - .expect("make_top_frame_polymorphic is called with empty frame stack"); - value_stack.resize(frame.value_stack_len, StackValueType::Any); - frame.polymorphic_stack = true; -} - -fn push_value( - value_stack: &mut StackWithLimit, - value_type: StackValueType, -) -> Result<(), Error> { - Ok(value_stack.push(value_type.into())?) -} - -// TODO: Rename value_type -> expected_value_ty -fn pop_value( - value_stack: &mut StackWithLimit, - frame_stack: &StackWithLimit, - value_type: StackValueType, -) -> Result { - let (is_stack_polymorphic, label_value_stack_len) = { - let frame = top_label(frame_stack); - (frame.polymorphic_stack, frame.value_stack_len) - }; - let stack_is_empty = value_stack.len() == label_value_stack_len; - let actual_value = if stack_is_empty && is_stack_polymorphic { - StackValueType::Any - } else { - let value_stack_min = frame_stack - .top() - .expect("at least 1 topmost block") - .value_stack_len; - if value_stack.len() <= value_stack_min { - return Err(Error("Trying to access parent frame stack values.".into())); - } - value_stack.pop()? - }; - match actual_value { - StackValueType::Specific(stack_value_type) if stack_value_type == value_type => { - Ok(actual_value) - } - StackValueType::Any => Ok(actual_value), - stack_value_type @ _ => Err(Error(format!( - "Expected value of type {:?} on top of stack. Got {:?}", - value_type, stack_value_type - ))), - } -} - -fn tee_value( - value_stack: &mut StackWithLimit, - frame_stack: &StackWithLimit, - value_type: StackValueType, -) -> Result<(), Error> { - let _ = pop_value(value_stack, frame_stack, value_type)?; - push_value(value_stack, value_type)?; - Ok(()) -} - -fn push_label( - frame_type: BlockFrameType, - block_type: BlockType, - position: usize, - value_stack: &StackWithLimit, - frame_stack: &mut StackWithLimit, -) -> Result<(), Error> { - Ok(frame_stack.push(BlockFrame { - frame_type: frame_type, - block_type: block_type, - begin_position: position, - value_stack_len: value_stack.len(), - polymorphic_stack: false, - })?) -} - -// TODO: Refactor -fn pop_label( - value_stack: &mut StackWithLimit, - frame_stack: &mut StackWithLimit, -) -> Result<(), Error> { - // Don't pop frame yet. This is essential since we still might pop values from the value stack - // and this in turn requires current frame to check whether or not we've reached - // unreachable. - let block_type = frame_stack.top()?.block_type; - match block_type { - BlockType::NoResult => (), - BlockType::Value(required_value_type) => { - let _ = pop_value( - value_stack, - frame_stack, - StackValueType::Specific(required_value_type), - )?; - } - } - - let frame = frame_stack.pop()?; - if value_stack.len() != frame.value_stack_len { - return Err(Error(format!( - "Unexpected stack height {}, expected {}", - value_stack.len(), - frame.value_stack_len - ))); - } - - Ok(()) -} - -fn top_label(frame_stack: &StackWithLimit) -> &BlockFrame { - frame_stack - .top() - .expect("this function can't be called with empty frame stack") -} - -fn require_label( - depth: u32, - frame_stack: &StackWithLimit, -) -> Result<&BlockFrame, Error> { - Ok(frame_stack.get(depth as usize)?) -} - -fn require_target( - depth: u32, - value_stack: &StackWithLimit, - frame_stack: &StackWithLimit, -) -> Target { - let is_stack_polymorphic = top_label(frame_stack).polymorphic_stack; - let frame = - require_label(depth, frame_stack).expect("require_target called with a bogus depth"); - - // Find out how many values we need to keep (copy to the new stack location after the drop). - let keep: isa::Keep = match (frame.frame_type, frame.block_type) { - // A loop doesn't take a value upon a branch. It can return value - // only via reaching it's closing `End` operator. - (BlockFrameType::Loop { .. }, _) => isa::Keep::None, - - (_, BlockType::Value(_)) => isa::Keep::Single, - (_, BlockType::NoResult) => isa::Keep::None, - }; - - // Find out how many values we need to discard. - let drop = if is_stack_polymorphic { - // Polymorphic stack is a weird state. Fortunately, it always about the code that - // will not be executed, so we don't bother and return 0 here. - 0 - } else { - let value_stack_height = value_stack.len(); - assert!( - value_stack_height >= frame.value_stack_len, - "Stack underflow detected: value stack height ({}) is lower than minimum stack len ({})", - value_stack_height, - frame.value_stack_len, - ); - assert!( - (value_stack_height as u32 - frame.value_stack_len as u32) >= keep as u32, - "Stack underflow detected: asked to keep {:?} values, but there are only {}", - keep, - value_stack_height as u32 - frame.value_stack_len as u32, - ); - (value_stack_height as u32 - frame.value_stack_len as u32) - keep as u32 - }; - - Target { - label: frame.frame_type.br_destination(), - drop_keep: isa::DropKeep { drop, keep }, - } -} - -fn drop_keep_return( - locals: &Locals, - value_stack: &StackWithLimit, - frame_stack: &StackWithLimit, -) -> isa::DropKeep { - assert!( - !frame_stack.is_empty(), - "drop_keep_return can't be called with the frame stack empty" - ); - - let deepest = (frame_stack.len() - 1) as u32; - let mut drop_keep = require_target(deepest, value_stack, frame_stack).drop_keep; - - // Drop all local variables and parameters upon exit. - drop_keep.drop += locals.count(); - - drop_keep -} - -fn require_local(locals: &Locals, idx: u32) -> Result { - Ok(locals.type_of_local(idx)?) -} - -/// See stack layout definition in mod isa. -fn relative_local_depth( - idx: u32, - locals: &Locals, - value_stack: &StackWithLimit, -) -> Result { - 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")))?; - Ok(depth) -} - -/// The target of a branch instruction. -/// -/// It references a `LabelId` instead of exact instruction address. This is handy -/// for emitting code right away with labels resolved later. -#[derive(Clone)] -struct Target { - label: LabelId, - drop_keep: isa::DropKeep, -} - -/// Identifier of a label. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -struct LabelId(usize); - -#[derive(Debug, PartialEq, Eq)] -enum Label { - Resolved(u32), - NotResolved, -} - -struct Sink { - ins: isa::Instructions, - labels: Vec<(Label, Vec)>, -} - -impl Sink { - fn with_instruction_capacity(capacity: usize) -> Sink { - Sink { - ins: isa::Instructions::with_capacity(capacity), - labels: Vec::new(), - } - } - - fn cur_pc(&self) -> u32 { - self.ins.current_pc() - } - - fn pc_or_placeholder isa::Reloc>( - &mut self, - label: LabelId, - reloc_creator: F, - ) -> u32 { - match self.labels[label.0] { - (Label::Resolved(dst_pc), _) => dst_pc, - (Label::NotResolved, ref mut unresolved) => { - unresolved.push(reloc_creator()); - u32::max_value() - } - } - } - - fn emit(&mut self, instruction: isa::InstructionInternal) { - self.ins.push(instruction); - } - - fn emit_br(&mut self, target: Target) { - let Target { label, drop_keep } = target; - let pc = self.cur_pc(); - let dst_pc = self.pc_or_placeholder(label, || isa::Reloc::Br { pc }); - self.ins.push(isa::InstructionInternal::Br(isa::Target { - dst_pc, - drop_keep: drop_keep.into(), - })); - } - - fn emit_br_eqz(&mut self, target: Target) { - let Target { label, drop_keep } = target; - let pc = self.cur_pc(); - let dst_pc = self.pc_or_placeholder(label, || isa::Reloc::Br { pc }); - self.ins - .push(isa::InstructionInternal::BrIfEqz(isa::Target { - dst_pc, - drop_keep: drop_keep.into(), - })); - } - - fn emit_br_nez(&mut self, target: Target) { - let Target { label, drop_keep } = target; - let pc = self.cur_pc(); - let dst_pc = self.pc_or_placeholder(label, || isa::Reloc::Br { pc }); - self.ins - .push(isa::InstructionInternal::BrIfNez(isa::Target { - dst_pc, - drop_keep: drop_keep.into(), - })); - } - - fn emit_br_table(&mut self, targets: &[Target], default: Target) { - use core::iter; - - let pc = self.cur_pc(); - - self.ins.push(isa::InstructionInternal::BrTable { - count: targets.len() as u32 + 1, - }); - - for (idx, &Target { label, drop_keep }) in - targets.iter().chain(iter::once(&default)).enumerate() - { - let dst_pc = self.pc_or_placeholder(label, || isa::Reloc::BrTable { pc, idx }); - self.ins - .push(isa::InstructionInternal::BrTableTarget(isa::Target { - dst_pc, - drop_keep: drop_keep.into(), - })); - } - } - - /// Create a new unresolved label. - fn new_label(&mut self) -> LabelId { - let label_idx = self.labels.len(); - self.labels.push((Label::NotResolved, Vec::new())); - LabelId(label_idx) - } - - /// Resolve the label at the current position. - /// - /// Panics if the label is already resolved. - fn resolve_label(&mut self, label: LabelId) { - use core::mem; - - if let (Label::Resolved(_), _) = self.labels[label.0] { - panic!("Trying to resolve already resolved label"); - } - let dst_pc = self.cur_pc(); - - // Patch all relocations that was previously recorded for this - // particular label. - let unresolved_rels = mem::replace(&mut self.labels[label.0].1, Vec::new()); - for reloc in unresolved_rels { - self.ins.patch_relocation(reloc, dst_pc); - } - - // Mark this label as resolved. - self.labels[label.0] = (Label::Resolved(dst_pc), Vec::new()); - } - - /// Consume this Sink and returns isa::Instructions. - fn into_inner(self) -> isa::Instructions { - // At this moment all labels should be resolved. - assert!( - { - self.labels - .iter() - .all(|(state, unresolved)| match (state, unresolved) { - (Label::Resolved(_), unresolved) if unresolved.is_empty() => true, - _ => false, - }) - }, - "there are unresolved labels left: {:?}", - self.labels - ); - self.ins - } -} diff --git a/test.sh b/test.sh index 3cef9ad..4ee4c32 100755 --- a/test.sh +++ b/test.sh @@ -4,6 +4,6 @@ set -eux cd $(dirname $0) -time cargo test +time cargo test --all cd - diff --git a/validation/Cargo.toml b/validation/Cargo.toml new file mode 100644 index 0000000..81f38c6 --- /dev/null +++ b/validation/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "wasmi-validation" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2018" + +[dependencies] +parity-wasm = { version = "0.31", default-features = false } +hashbrown = { version = "0.1.8", optional = true } + +[dev-dependencies] +assert_matches = "1.1" + +[features] +default = ["std"] +std = ["parity-wasm/std"] +core = [ + "hashbrown/nightly" +] diff --git a/src/validation/context.rs b/validation/src/context.rs similarity index 98% rename from src/validation/context.rs rename to validation/src/context.rs index 62ccf6f..e7de32b 100644 --- a/src/validation/context.rs +++ b/validation/src/context.rs @@ -1,9 +1,9 @@ +use crate::Error; #[allow(unused_imports)] -use alloc::prelude::*; +use alloc::prelude::v1::*; use parity_wasm::elements::{ BlockType, FunctionType, GlobalType, MemoryType, TableType, ValueType, }; -use validation::Error; #[derive(Default, Debug)] pub struct ModuleContext { diff --git a/validation/src/func.rs b/validation/src/func.rs new file mode 100644 index 0000000..23e72f4 --- /dev/null +++ b/validation/src/func.rs @@ -0,0 +1,1219 @@ +#[allow(unused_imports)] +use alloc::prelude::v1::*; + +use crate::{ + context::ModuleContext, stack::StackWithLimit, util::Locals, Error, FuncValidator, + DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX, +}; + +use core::u32; +use parity_wasm::elements::{BlockType, Func, FuncBody, Instruction, TableElementType, ValueType}; + +/// Maximum number of entries in value stack per function. +const DEFAULT_VALUE_STACK_LIMIT: usize = 16384; +/// Maximum number of entries in frame stack per function. +const DEFAULT_FRAME_STACK_LIMIT: usize = 16384; + +/// Control stack frame. +#[derive(Debug, Clone)] +pub struct BlockFrame { + /// The opcode that started this block frame. + pub started_with: StartedWith, + /// A signature, which is a block signature type indicating the number and types of result + /// values of the region. + pub block_type: BlockType, + /// A limit integer value, which is an index into the value stack indicating where to reset it + /// to on a branch to that label. + pub value_stack_len: usize, + /// Boolean which signals whether value stack became polymorphic. Value stack starts in + /// a non-polymorphic state and becomes polymorphic only after an instruction that never passes + /// control further is executed, i.e. `unreachable`, `br` (but not `br_if`!), etc. + pub polymorphic_stack: bool, +} + +/// An opcode that opened the particular frame. +/// +/// We need that to ensure proper combinations with `End` instruction. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum StartedWith { + Block, + If, + Else, + Loop, +} + +/// Value type on the stack. +#[derive(Debug, Clone, Copy)] +pub enum StackValueType { + /// Any value type. + Any, + /// Concrete value type. + Specific(ValueType), +} + +impl From for StackValueType { + fn from(value_type: ValueType) -> Self { + StackValueType::Specific(value_type) + } +} + +impl PartialEq for StackValueType { + fn eq(&self, other: &StackValueType) -> bool { + match (*self, *other) { + // Any type is equal with any other type. + (StackValueType::Any, _) => true, + (_, StackValueType::Any) => true, + (StackValueType::Specific(self_ty), StackValueType::Specific(other_ty)) => { + self_ty == other_ty + } + } + } +} + +impl PartialEq for StackValueType { + fn eq(&self, other: &ValueType) -> bool { + match *self { + StackValueType::Any => true, + StackValueType::Specific(value_ty) => value_ty == *other, + } + } +} + +impl PartialEq for ValueType { + fn eq(&self, other: &StackValueType) -> bool { + other == self + } +} + +pub fn drive( + module: &ModuleContext, + func: &Func, + body: &FuncBody, +) -> Result { + let (params, result_ty) = module.require_function_type(func.type_ref())?; + + let code = body.code().elements(); + let code_len = code.len(); + if code_len == 0 { + return Err(Error("Non-empty function body expected".into())); + } + + let mut context = FunctionValidationContext::new( + &module, + Locals::new(params, body.locals())?, + DEFAULT_VALUE_STACK_LIMIT, + DEFAULT_FRAME_STACK_LIMIT, + result_ty, + )?; + + let mut validator = T::new(&context, body); + + for (position, instruction) in code.iter().enumerate() { + validator + .next_instruction(&mut context, instruction) + .map_err(|err| { + Error(format!( + "At instruction {:?}(@{}): {}", + instruction, position, err + )) + })?; + } + + // The last `end` opcode should pop last instruction. + // parity-wasm ensures that there is always `End` opcode at + // the end of the function body. + assert!(context.frame_stack.is_empty()); + + Ok(validator.finish()) +} + +/// Function validation context. +pub struct FunctionValidationContext<'a> { + /// Wasm module + pub module: &'a ModuleContext, + /// Local variables. + pub locals: Locals<'a>, + /// Value stack. + pub value_stack: StackWithLimit, + /// Frame stack. + pub frame_stack: StackWithLimit, + /// Function return type. + pub return_type: BlockType, +} + +impl<'a> FunctionValidationContext<'a> { + fn new( + module: &'a ModuleContext, + locals: Locals<'a>, + value_stack_limit: usize, + frame_stack_limit: usize, + return_type: BlockType, + ) -> Result { + let mut ctx = FunctionValidationContext { + module: module, + locals: locals, + value_stack: StackWithLimit::with_limit(value_stack_limit), + frame_stack: StackWithLimit::with_limit(frame_stack_limit), + return_type: return_type, + }; + push_label( + StartedWith::Block, + return_type, + &ctx.value_stack, + &mut ctx.frame_stack, + )?; + Ok(ctx) + } + + pub fn step(&mut self, instruction: &Instruction) -> Result<(), Error> { + use self::Instruction::*; + + match *instruction { + // Nop instruction doesn't do anything. It is safe to just skip it. + Nop => {} + + Unreachable => { + make_top_frame_polymorphic(&mut self.value_stack, &mut self.frame_stack); + } + + Block(block_type) => { + push_label( + StartedWith::Block, + block_type, + &self.value_stack, + &mut self.frame_stack, + )?; + } + Loop(block_type) => { + push_label( + StartedWith::Loop, + block_type, + &self.value_stack, + &mut self.frame_stack, + )?; + } + If(block_type) => { + pop_value( + &mut self.value_stack, + &self.frame_stack, + ValueType::I32.into(), + )?; + push_label( + StartedWith::If, + block_type, + &self.value_stack, + &mut self.frame_stack, + )?; + } + Else => { + let block_type = { + let top = top_label(&self.frame_stack); + if top.started_with != StartedWith::If { + return Err(Error("Misplaced else instruction".into())); + } + top.block_type + }; + + // Then, we pop the current label. It discards all values that pushed in the current + // frame. + pop_label(&mut self.value_stack, &mut self.frame_stack)?; + push_label( + StartedWith::Else, + block_type, + &self.value_stack, + &mut self.frame_stack, + )?; + } + End => { + let block_type = { + let top = top_label(&self.frame_stack); + + if top.started_with == StartedWith::If && top.block_type != BlockType::NoResult + { + // A `if` without an `else` can't return a result. + return Err(Error(format!( + "If block without else required to have NoResult block type. But it has {:?} type", + top.block_type + ))); + } + + top.block_type + }; + + if self.frame_stack.len() == 1 { + // We are about to close the last frame. + + // Check the return type. + if let BlockType::Value(value_type) = self.return_type { + tee_value(&mut self.value_stack, &self.frame_stack, value_type.into())?; + } + + pop_label(&mut self.value_stack, &mut self.frame_stack)?; + + // We just poped the last frame. To avoid some difficulties + // we prefer to keep this branch explicit and bail out here. + () + } else { + pop_label(&mut self.value_stack, &mut self.frame_stack)?; + + // Push the result value. + if let BlockType::Value(value_type) = block_type { + push_value(&mut self.value_stack, value_type.into())?; + } + } + } + Br(depth) => { + self.validate_br(depth)?; + make_top_frame_polymorphic(&mut self.value_stack, &mut self.frame_stack); + } + BrIf(depth) => { + self.validate_br_if(depth)?; + } + BrTable(ref table, default) => { + self.validate_br_table(table, default)?; + make_top_frame_polymorphic(&mut self.value_stack, &mut self.frame_stack); + } + Return => { + if let BlockType::Value(value_type) = self.return_type { + tee_value(&mut self.value_stack, &self.frame_stack, value_type.into())?; + } + make_top_frame_polymorphic(&mut self.value_stack, &mut self.frame_stack); + } + + Call(index) => { + self.validate_call(index)?; + } + CallIndirect(index, _reserved) => { + self.validate_call_indirect(index)?; + } + + Drop => { + self.validate_drop()?; + } + Select => { + self.validate_select()?; + } + + GetLocal(index) => { + self.validate_get_local(index)?; + } + SetLocal(index) => { + self.validate_set_local(index)?; + } + TeeLocal(index) => { + self.validate_tee_local(index)?; + } + GetGlobal(index) => { + self.validate_get_global(index)?; + } + SetGlobal(index) => { + self.validate_set_global(index)?; + } + + I32Load(align, _) => { + self.validate_load(align, 4, ValueType::I32)?; + } + I64Load(align, _) => { + self.validate_load(align, 8, ValueType::I64)?; + } + F32Load(align, _) => { + self.validate_load(align, 4, ValueType::F32)?; + } + F64Load(align, _) => { + self.validate_load(align, 8, ValueType::F64)?; + } + I32Load8S(align, _) => { + self.validate_load(align, 1, ValueType::I32)?; + } + I32Load8U(align, _) => { + self.validate_load(align, 1, ValueType::I32)?; + } + I32Load16S(align, _) => { + self.validate_load(align, 2, ValueType::I32)?; + } + I32Load16U(align, _) => { + self.validate_load(align, 2, ValueType::I32)?; + } + I64Load8S(align, _) => { + self.validate_load(align, 1, ValueType::I64)?; + } + I64Load8U(align, _) => { + self.validate_load(align, 1, ValueType::I64)?; + } + I64Load16S(align, _) => { + self.validate_load(align, 2, ValueType::I64)?; + } + I64Load16U(align, _) => { + self.validate_load(align, 2, ValueType::I64)?; + } + I64Load32S(align, _) => { + self.validate_load(align, 4, ValueType::I64)?; + } + I64Load32U(align, _) => { + self.validate_load(align, 4, ValueType::I64)?; + } + + I32Store(align, _) => { + self.validate_store(align, 4, ValueType::I32)?; + } + I64Store(align, _) => { + self.validate_store(align, 8, ValueType::I64)?; + } + F32Store(align, _) => { + self.validate_store(align, 4, ValueType::F32)?; + } + F64Store(align, _) => { + self.validate_store(align, 8, ValueType::F64)?; + } + I32Store8(align, _) => { + self.validate_store(align, 1, ValueType::I32)?; + } + I32Store16(align, _) => { + self.validate_store(align, 2, ValueType::I32)?; + } + I64Store8(align, _) => { + self.validate_store(align, 1, ValueType::I64)?; + } + I64Store16(align, _) => { + self.validate_store(align, 2, ValueType::I64)?; + } + I64Store32(align, _) => { + self.validate_store(align, 4, ValueType::I64)?; + } + + CurrentMemory(_) => { + self.validate_current_memory()?; + } + GrowMemory(_) => { + self.validate_grow_memory()?; + } + + I32Const(_) => { + self.validate_const(ValueType::I32)?; + } + I64Const(_) => { + self.validate_const(ValueType::I64)?; + } + F32Const(_) => { + self.validate_const(ValueType::F32)?; + } + F64Const(_) => { + self.validate_const(ValueType::F64)?; + } + + I32Eqz => { + self.validate_testop(ValueType::I32)?; + } + I32Eq => { + self.validate_relop(ValueType::I32)?; + } + I32Ne => { + self.validate_relop(ValueType::I32)?; + } + I32LtS => { + self.validate_relop(ValueType::I32)?; + } + I32LtU => { + self.validate_relop(ValueType::I32)?; + } + I32GtS => { + self.validate_relop(ValueType::I32)?; + } + I32GtU => { + self.validate_relop(ValueType::I32)?; + } + I32LeS => { + self.validate_relop(ValueType::I32)?; + } + I32LeU => { + self.validate_relop(ValueType::I32)?; + } + I32GeS => { + self.validate_relop(ValueType::I32)?; + } + I32GeU => { + self.validate_relop(ValueType::I32)?; + } + + I64Eqz => { + self.validate_testop(ValueType::I64)?; + } + I64Eq => { + self.validate_relop(ValueType::I64)?; + } + I64Ne => { + self.validate_relop(ValueType::I64)?; + } + I64LtS => { + self.validate_relop(ValueType::I64)?; + } + I64LtU => { + self.validate_relop(ValueType::I64)?; + } + I64GtS => { + self.validate_relop(ValueType::I64)?; + } + I64GtU => { + self.validate_relop(ValueType::I64)?; + } + I64LeS => { + self.validate_relop(ValueType::I64)?; + } + I64LeU => { + self.validate_relop(ValueType::I64)?; + } + I64GeS => { + self.validate_relop(ValueType::I64)?; + } + I64GeU => { + self.validate_relop(ValueType::I64)?; + } + + F32Eq => { + self.validate_relop(ValueType::F32)?; + } + F32Ne => { + self.validate_relop(ValueType::F32)?; + } + F32Lt => { + self.validate_relop(ValueType::F32)?; + } + F32Gt => { + self.validate_relop(ValueType::F32)?; + } + F32Le => { + self.validate_relop(ValueType::F32)?; + } + F32Ge => { + self.validate_relop(ValueType::F32)?; + } + + F64Eq => { + self.validate_relop(ValueType::F64)?; + } + F64Ne => { + self.validate_relop(ValueType::F64)?; + } + F64Lt => { + self.validate_relop(ValueType::F64)?; + } + F64Gt => { + self.validate_relop(ValueType::F64)?; + } + F64Le => { + self.validate_relop(ValueType::F64)?; + } + F64Ge => { + self.validate_relop(ValueType::F64)?; + } + + I32Clz => { + self.validate_unop(ValueType::I32)?; + } + I32Ctz => { + self.validate_unop(ValueType::I32)?; + } + I32Popcnt => { + self.validate_unop(ValueType::I32)?; + } + I32Add => { + self.validate_binop(ValueType::I32)?; + } + I32Sub => { + self.validate_binop(ValueType::I32)?; + } + I32Mul => { + self.validate_binop(ValueType::I32)?; + } + I32DivS => { + self.validate_binop(ValueType::I32)?; + } + I32DivU => { + self.validate_binop(ValueType::I32)?; + } + I32RemS => { + self.validate_binop(ValueType::I32)?; + } + I32RemU => { + self.validate_binop(ValueType::I32)?; + } + I32And => { + self.validate_binop(ValueType::I32)?; + } + I32Or => { + self.validate_binop(ValueType::I32)?; + } + I32Xor => { + self.validate_binop(ValueType::I32)?; + } + I32Shl => { + self.validate_binop(ValueType::I32)?; + } + I32ShrS => { + self.validate_binop(ValueType::I32)?; + } + I32ShrU => { + self.validate_binop(ValueType::I32)?; + } + I32Rotl => { + self.validate_binop(ValueType::I32)?; + } + I32Rotr => { + self.validate_binop(ValueType::I32)?; + } + + I64Clz => { + self.validate_unop(ValueType::I64)?; + } + I64Ctz => { + self.validate_unop(ValueType::I64)?; + } + I64Popcnt => { + self.validate_unop(ValueType::I64)?; + } + I64Add => { + self.validate_binop(ValueType::I64)?; + } + I64Sub => { + self.validate_binop(ValueType::I64)?; + } + I64Mul => { + self.validate_binop(ValueType::I64)?; + } + I64DivS => { + self.validate_binop(ValueType::I64)?; + } + I64DivU => { + self.validate_binop(ValueType::I64)?; + } + I64RemS => { + self.validate_binop(ValueType::I64)?; + } + I64RemU => { + self.validate_binop(ValueType::I64)?; + } + I64And => { + self.validate_binop(ValueType::I64)?; + } + I64Or => { + self.validate_binop(ValueType::I64)?; + } + I64Xor => { + self.validate_binop(ValueType::I64)?; + } + I64Shl => { + self.validate_binop(ValueType::I64)?; + } + I64ShrS => { + self.validate_binop(ValueType::I64)?; + } + I64ShrU => { + self.validate_binop(ValueType::I64)?; + } + I64Rotl => { + self.validate_binop(ValueType::I64)?; + } + I64Rotr => { + self.validate_binop(ValueType::I64)?; + } + + F32Abs => { + self.validate_unop(ValueType::F32)?; + } + F32Neg => { + self.validate_unop(ValueType::F32)?; + } + F32Ceil => { + self.validate_unop(ValueType::F32)?; + } + F32Floor => { + self.validate_unop(ValueType::F32)?; + } + F32Trunc => { + self.validate_unop(ValueType::F32)?; + } + F32Nearest => { + self.validate_unop(ValueType::F32)?; + } + F32Sqrt => { + self.validate_unop(ValueType::F32)?; + } + F32Add => { + self.validate_binop(ValueType::F32)?; + } + F32Sub => { + self.validate_binop(ValueType::F32)?; + } + F32Mul => { + self.validate_binop(ValueType::F32)?; + } + F32Div => { + self.validate_binop(ValueType::F32)?; + } + F32Min => { + self.validate_binop(ValueType::F32)?; + } + F32Max => { + self.validate_binop(ValueType::F32)?; + } + F32Copysign => { + self.validate_binop(ValueType::F32)?; + } + + F64Abs => { + self.validate_unop(ValueType::F64)?; + } + F64Neg => { + self.validate_unop(ValueType::F64)?; + } + F64Ceil => { + self.validate_unop(ValueType::F64)?; + } + F64Floor => { + self.validate_unop(ValueType::F64)?; + } + F64Trunc => { + self.validate_unop(ValueType::F64)?; + } + F64Nearest => { + self.validate_unop(ValueType::F64)?; + } + F64Sqrt => { + self.validate_unop(ValueType::F64)?; + } + F64Add => { + self.validate_binop(ValueType::F64)?; + } + F64Sub => { + self.validate_binop(ValueType::F64)?; + } + F64Mul => { + self.validate_binop(ValueType::F64)?; + } + F64Div => { + self.validate_binop(ValueType::F64)?; + } + F64Min => { + self.validate_binop(ValueType::F64)?; + } + F64Max => { + self.validate_binop(ValueType::F64)?; + } + F64Copysign => { + self.validate_binop(ValueType::F64)?; + } + + I32WrapI64 => { + self.validate_cvtop(ValueType::I64, ValueType::I32)?; + } + I32TruncSF32 => { + self.validate_cvtop(ValueType::F32, ValueType::I32)?; + } + I32TruncUF32 => { + self.validate_cvtop(ValueType::F32, ValueType::I32)?; + } + I32TruncSF64 => { + self.validate_cvtop(ValueType::F64, ValueType::I32)?; + } + I32TruncUF64 => { + self.validate_cvtop(ValueType::F64, ValueType::I32)?; + } + I64ExtendSI32 => { + self.validate_cvtop(ValueType::I32, ValueType::I64)?; + } + I64ExtendUI32 => { + self.validate_cvtop(ValueType::I32, ValueType::I64)?; + } + I64TruncSF32 => { + self.validate_cvtop(ValueType::F32, ValueType::I64)?; + } + I64TruncUF32 => { + self.validate_cvtop(ValueType::F32, ValueType::I64)?; + } + I64TruncSF64 => { + self.validate_cvtop(ValueType::F64, ValueType::I64)?; + } + I64TruncUF64 => { + self.validate_cvtop(ValueType::F64, ValueType::I64)?; + } + F32ConvertSI32 => { + self.validate_cvtop(ValueType::I32, ValueType::F32)?; + } + F32ConvertUI32 => { + self.validate_cvtop(ValueType::I32, ValueType::F32)?; + } + F32ConvertSI64 => { + self.validate_cvtop(ValueType::I64, ValueType::F32)?; + } + F32ConvertUI64 => { + self.validate_cvtop(ValueType::I64, ValueType::F32)?; + } + F32DemoteF64 => { + self.validate_cvtop(ValueType::F64, ValueType::F32)?; + } + F64ConvertSI32 => { + self.validate_cvtop(ValueType::I32, ValueType::F64)?; + } + F64ConvertUI32 => { + self.validate_cvtop(ValueType::I32, ValueType::F64)?; + } + F64ConvertSI64 => { + self.validate_cvtop(ValueType::I64, ValueType::F64)?; + } + F64ConvertUI64 => { + self.validate_cvtop(ValueType::I64, ValueType::F64)?; + } + F64PromoteF32 => { + self.validate_cvtop(ValueType::F32, ValueType::F64)?; + } + + I32ReinterpretF32 => { + self.validate_cvtop(ValueType::F32, ValueType::I32)?; + } + I64ReinterpretF64 => { + self.validate_cvtop(ValueType::F64, ValueType::I64)?; + } + F32ReinterpretI32 => { + self.validate_cvtop(ValueType::I32, ValueType::F32)?; + } + F64ReinterpretI64 => { + self.validate_cvtop(ValueType::I64, ValueType::F64)?; + } + } + + Ok(()) + } + + fn validate_const(&mut self, value_type: ValueType) -> Result<(), Error> { + push_value(&mut self.value_stack, value_type.into())?; + Ok(()) + } + + fn validate_unop(&mut self, value_type: ValueType) -> Result<(), Error> { + pop_value(&mut self.value_stack, &self.frame_stack, value_type.into())?; + push_value(&mut self.value_stack, value_type.into())?; + Ok(()) + } + + fn validate_binop(&mut self, value_type: ValueType) -> Result<(), Error> { + pop_value(&mut self.value_stack, &self.frame_stack, value_type.into())?; + pop_value(&mut self.value_stack, &self.frame_stack, value_type.into())?; + push_value(&mut self.value_stack, value_type.into())?; + Ok(()) + } + + fn validate_testop(&mut self, value_type: ValueType) -> Result<(), Error> { + pop_value(&mut self.value_stack, &self.frame_stack, value_type.into())?; + push_value(&mut self.value_stack, ValueType::I32.into())?; + Ok(()) + } + + fn validate_relop(&mut self, value_type: ValueType) -> Result<(), Error> { + pop_value(&mut self.value_stack, &self.frame_stack, value_type.into())?; + pop_value(&mut self.value_stack, &self.frame_stack, value_type.into())?; + push_value(&mut self.value_stack, ValueType::I32.into())?; + Ok(()) + } + + fn validate_cvtop( + &mut self, + value_type1: ValueType, + value_type2: ValueType, + ) -> Result<(), Error> { + pop_value(&mut self.value_stack, &self.frame_stack, value_type1.into())?; + push_value(&mut self.value_stack, value_type2.into())?; + Ok(()) + } + + fn validate_drop(&mut self) -> Result<(), Error> { + pop_value( + &mut self.value_stack, + &self.frame_stack, + StackValueType::Any, + )?; + Ok(()) + } + + fn validate_select(&mut self) -> Result<(), Error> { + pop_value( + &mut self.value_stack, + &self.frame_stack, + ValueType::I32.into(), + )?; + let select_type = pop_value( + &mut self.value_stack, + &self.frame_stack, + StackValueType::Any, + )?; + pop_value(&mut self.value_stack, &self.frame_stack, select_type)?; + push_value(&mut self.value_stack, select_type)?; + Ok(()) + } + + fn validate_get_local(&mut self, index: u32) -> Result<(), Error> { + let local_type = require_local(&self.locals, index)?; + push_value(&mut self.value_stack, local_type.into())?; + Ok(()) + } + + fn validate_set_local(&mut self, index: u32) -> Result<(), Error> { + let local_type = require_local(&self.locals, index)?; + let value_type = pop_value( + &mut self.value_stack, + &self.frame_stack, + StackValueType::Any, + )?; + if StackValueType::from(local_type) != value_type { + return Err(Error(format!( + "Trying to update local {} of type {:?} with value of type {:?}", + index, local_type, value_type + ))); + } + Ok(()) + } + + fn validate_tee_local(&mut self, index: u32) -> Result<(), Error> { + let local_type = require_local(&self.locals, index)?; + tee_value(&mut self.value_stack, &self.frame_stack, local_type.into())?; + Ok(()) + } + + fn validate_get_global(&mut self, index: u32) -> Result<(), Error> { + let global_type: StackValueType = { + let global = self.module.require_global(index, None)?; + global.content_type().into() + }; + push_value(&mut self.value_stack, global_type)?; + Ok(()) + } + + fn validate_set_global(&mut self, index: u32) -> Result<(), Error> { + let global_type: StackValueType = { + let global = self.module.require_global(index, Some(true))?; + global.content_type().into() + }; + let value_type = pop_value( + &mut self.value_stack, + &self.frame_stack, + StackValueType::Any, + )?; + if global_type != value_type { + return Err(Error(format!( + "Trying to update global {} of type {:?} with value of type {:?}", + index, global_type, value_type + ))); + } + Ok(()) + } + + fn validate_load( + &mut self, + align: u32, + max_align: u32, + value_type: ValueType, + ) -> Result<(), Error> { + if 1u32.checked_shl(align).unwrap_or(u32::MAX) > max_align { + return Err(Error(format!( + "Too large memory alignment 2^{} (expected at most {})", + align, max_align + ))); + } + + pop_value( + &mut self.value_stack, + &self.frame_stack, + ValueType::I32.into(), + )?; + self.module.require_memory(DEFAULT_MEMORY_INDEX)?; + push_value(&mut self.value_stack, value_type.into())?; + Ok(()) + } + + fn validate_store( + &mut self, + align: u32, + max_align: u32, + value_type: ValueType, + ) -> Result<(), Error> { + if 1u32.checked_shl(align).unwrap_or(u32::MAX) > max_align { + return Err(Error(format!( + "Too large memory alignment 2^{} (expected at most {})", + align, max_align + ))); + } + + self.module.require_memory(DEFAULT_MEMORY_INDEX)?; + pop_value(&mut self.value_stack, &self.frame_stack, value_type.into())?; + pop_value( + &mut self.value_stack, + &self.frame_stack, + ValueType::I32.into(), + )?; + Ok(()) + } + + fn validate_br(&mut self, depth: u32) -> Result<(), Error> { + let (started_with, frame_block_type) = { + let frame = require_label(depth, &self.frame_stack)?; + (frame.started_with, frame.block_type) + }; + if started_with != StartedWith::Loop { + if let BlockType::Value(value_type) = frame_block_type { + tee_value(&mut self.value_stack, &self.frame_stack, value_type.into())?; + } + } + Ok(()) + } + + fn validate_br_if(&mut self, depth: u32) -> Result<(), Error> { + pop_value( + &mut self.value_stack, + &self.frame_stack, + ValueType::I32.into(), + )?; + + let (started_with, frame_block_type) = { + let frame = require_label(depth, &self.frame_stack)?; + (frame.started_with, frame.block_type) + }; + if started_with != StartedWith::Loop { + if let BlockType::Value(value_type) = frame_block_type { + tee_value(&mut self.value_stack, &self.frame_stack, value_type.into())?; + } + } + Ok(()) + } + + fn validate_br_table(&mut self, table: &[u32], default: u32) -> Result<(), Error> { + let required_block_type: BlockType = { + let default_block = require_label(default, &self.frame_stack)?; + let required_block_type = if default_block.started_with == StartedWith::Loop { + BlockType::NoResult + } else { + default_block.block_type + }; + + for label in table { + let label_block = require_label(*label, &self.frame_stack)?; + let label_block_type = if label_block.started_with == StartedWith::Loop { + BlockType::NoResult + } else { + label_block.block_type + }; + if required_block_type != label_block_type { + return Err(Error(format!( + "Labels in br_table points to block of different types: {:?} and {:?}", + required_block_type, label_block.block_type + ))); + } + } + required_block_type + }; + + pop_value( + &mut self.value_stack, + &self.frame_stack, + ValueType::I32.into(), + )?; + if let BlockType::Value(value_type) = required_block_type { + tee_value(&mut self.value_stack, &self.frame_stack, value_type.into())?; + } + + Ok(()) + } + + fn validate_call(&mut self, idx: u32) -> Result<(), Error> { + let (argument_types, return_type) = self.module.require_function(idx)?; + for argument_type in argument_types.iter().rev() { + pop_value( + &mut self.value_stack, + &self.frame_stack, + (*argument_type).into(), + )?; + } + if let BlockType::Value(value_type) = return_type { + push_value(&mut self.value_stack, value_type.into())?; + } + Ok(()) + } + + fn validate_call_indirect(&mut self, idx: u32) -> Result<(), Error> { + { + let table = self.module.require_table(DEFAULT_TABLE_INDEX)?; + if table.elem_type() != TableElementType::AnyFunc { + return Err(Error(format!( + "Table {} has element type {:?} while `anyfunc` expected", + idx, + table.elem_type() + ))); + } + } + + pop_value( + &mut self.value_stack, + &self.frame_stack, + ValueType::I32.into(), + )?; + let (argument_types, return_type) = self.module.require_function_type(idx)?; + for argument_type in argument_types.iter().rev() { + pop_value( + &mut self.value_stack, + &self.frame_stack, + (*argument_type).into(), + )?; + } + if let BlockType::Value(value_type) = return_type { + push_value(&mut self.value_stack, value_type.into())?; + } + Ok(()) + } + + fn validate_current_memory(&mut self) -> Result<(), Error> { + self.module.require_memory(DEFAULT_MEMORY_INDEX)?; + push_value(&mut self.value_stack, ValueType::I32.into())?; + Ok(()) + } + + fn validate_grow_memory(&mut self) -> Result<(), Error> { + self.module.require_memory(DEFAULT_MEMORY_INDEX)?; + pop_value( + &mut self.value_stack, + &self.frame_stack, + ValueType::I32.into(), + )?; + push_value(&mut self.value_stack, ValueType::I32.into())?; + Ok(()) + } +} + +fn make_top_frame_polymorphic( + value_stack: &mut StackWithLimit, + frame_stack: &mut StackWithLimit, +) { + let frame = frame_stack + .top_mut() + .expect("make_top_frame_polymorphic is called with empty frame stack"); + value_stack.resize(frame.value_stack_len, StackValueType::Any); + frame.polymorphic_stack = true; +} + +fn push_value( + value_stack: &mut StackWithLimit, + value_type: StackValueType, +) -> Result<(), Error> { + Ok(value_stack.push(value_type.into())?) +} + +fn pop_value( + value_stack: &mut StackWithLimit, + frame_stack: &StackWithLimit, + expected_value_ty: StackValueType, +) -> Result { + let (is_stack_polymorphic, label_value_stack_len) = { + let frame = top_label(frame_stack); + (frame.polymorphic_stack, frame.value_stack_len) + }; + let stack_is_empty = value_stack.len() == label_value_stack_len; + let actual_value = if stack_is_empty && is_stack_polymorphic { + StackValueType::Any + } else { + let value_stack_min = frame_stack + .top() + .expect("at least 1 topmost block") + .value_stack_len; + if value_stack.len() <= value_stack_min { + return Err(Error("Trying to access parent frame stack values.".into())); + } + value_stack.pop()? + }; + match actual_value { + StackValueType::Specific(stack_value_type) if stack_value_type == expected_value_ty => { + Ok(actual_value) + } + StackValueType::Any => Ok(actual_value), + stack_value_type @ _ => Err(Error(format!( + "Expected value of type {:?} on top of stack. Got {:?}", + expected_value_ty, stack_value_type + ))), + } +} + +fn tee_value( + value_stack: &mut StackWithLimit, + frame_stack: &StackWithLimit, + value_type: StackValueType, +) -> Result<(), Error> { + let _ = pop_value(value_stack, frame_stack, value_type)?; + push_value(value_stack, value_type)?; + Ok(()) +} + +fn push_label( + started_with: StartedWith, + block_type: BlockType, + value_stack: &StackWithLimit, + frame_stack: &mut StackWithLimit, +) -> Result<(), Error> { + Ok(frame_stack.push(BlockFrame { + started_with, + block_type: block_type, + value_stack_len: value_stack.len(), + polymorphic_stack: false, + })?) +} + +// TODO: Refactor +fn pop_label( + value_stack: &mut StackWithLimit, + frame_stack: &mut StackWithLimit, +) -> Result<(), Error> { + // Don't pop frame yet. This is essential since we still might pop values from the value stack + // and this in turn requires current frame to check whether or not we've reached + // unreachable. + let block_type = frame_stack.top()?.block_type; + match block_type { + BlockType::NoResult => (), + BlockType::Value(required_value_type) => { + let _ = pop_value( + value_stack, + frame_stack, + StackValueType::Specific(required_value_type), + )?; + } + } + + let frame = frame_stack.pop()?; + if value_stack.len() != frame.value_stack_len { + return Err(Error(format!( + "Unexpected stack height {}, expected {}", + value_stack.len(), + frame.value_stack_len + ))); + } + + Ok(()) +} + +/// Returns the top most frame from the frame stack. +/// +/// # Panics +/// +/// Can be called only when the frame stack is not empty: that is, it is ok to call this function +/// after initialization of the validation and until the validation reached the latest `End` +/// operator. +pub fn top_label(frame_stack: &StackWithLimit) -> &BlockFrame { + frame_stack + .top() + .expect("this function can't be called with empty frame stack") +} + +pub fn require_label( + depth: u32, + frame_stack: &StackWithLimit, +) -> Result<&BlockFrame, Error> { + Ok(frame_stack.get(depth as usize)?) +} + +fn require_local(locals: &Locals, idx: u32) -> Result { + Ok(locals.type_of_local(idx)?) +} diff --git a/src/validation/mod.rs b/validation/src/lib.rs similarity index 66% rename from src/validation/mod.rs rename to validation/src/lib.rs index 01b583a..8a562a6 100644 --- a/src/validation/mod.rs +++ b/validation/src/lib.rs @@ -1,5 +1,28 @@ +// TODO: Uncomment +// #![warn(missing_docs)] + +#![cfg_attr(not(feature = "std"), no_std)] +//// alloc is required in no_std +#![cfg_attr(not(feature = "std"), feature(alloc, alloc_prelude))] + +#[cfg(not(feature = "std"))] +#[macro_use] +extern crate alloc; +#[cfg(feature = "std")] +extern crate std as alloc; + +pub mod stack; + +/// Index of default linear memory. +pub const DEFAULT_MEMORY_INDEX: u32 = 0; +/// Index of default table. +pub const DEFAULT_TABLE_INDEX: u32 = 0; + +/// Maximal number of pages that a wasm instance supports. +pub const LINEAR_MEMORY_MAX_PAGES: u32 = 65536; + #[allow(unused_imports)] -use alloc::prelude::*; +use alloc::prelude::v1::*; use core::fmt; #[cfg(feature = "std")] use std::error; @@ -10,24 +33,22 @@ use hashbrown::HashSet; use std::collections::HashSet; use self::context::ModuleContextBuilder; -use self::func::FunctionReader; -use common::stack; -use isa; -use memory_units::Pages; use parity_wasm::elements::{ - BlockType, External, GlobalEntry, GlobalType, InitExpr, Instruction, Internal, MemoryType, - Module, ResizableLimits, TableType, Type, ValueType, + BlockType, External, FuncBody, GlobalEntry, GlobalType, InitExpr, Instruction, Internal, + MemoryType, Module, ResizableLimits, TableType, Type, ValueType, }; -mod context; -mod func; -mod util; +pub mod context; +pub mod func; +pub mod util; #[cfg(test)] mod tests; +// TODO: Consider using a type other than String, because +// of formatting machinary is not welcomed in substrate runtimes. #[derive(Debug)] -pub struct Error(String); +pub struct Error(pub String); impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -48,137 +69,77 @@ impl From for Error { } } -#[derive(Clone)] -pub struct ValidatedModule { - pub code_map: Vec, - pub module: Module, +pub trait Validator { + type Output; + type FuncValidator: FuncValidator; + fn new(module: &Module) -> Self; + fn on_function_validated( + &mut self, + index: u32, + output: <::FuncValidator as FuncValidator>::Output, + ); + fn finish(self) -> Self::Output; } -impl ::core::ops::Deref for ValidatedModule { - type Target = Module; - fn deref(&self) -> &Module { - &self.module +pub trait FuncValidator { + type Output; + fn new(ctx: &func::FunctionValidationContext, body: &FuncBody) -> Self; + fn next_instruction( + &mut self, + ctx: &mut func::FunctionValidationContext, + instruction: &Instruction, + ) -> Result<(), Error>; + fn finish(self) -> Self::Output; +} + +/// A module validator that just validates modules and produces no result. +pub struct PlainValidator; + +impl Validator for PlainValidator { + type Output = (); + type FuncValidator = PlainFuncValidator; + fn new(_module: &Module) -> PlainValidator { + PlainValidator + } + fn on_function_validated( + &mut self, + _index: u32, + _output: <::FuncValidator as FuncValidator>::Output, + ) -> () { + () + } + fn finish(self) -> () { + () } } -pub fn deny_floating_point(module: &Module) -> Result<(), Error> { - if let Some(code) = module.code_section() { - for op in code.bodies().iter().flat_map(|body| body.code().elements()) { - use parity_wasm::elements::Instruction::*; +/// A function validator that just validates modules and produces no result. +pub struct PlainFuncValidator; - macro_rules! match_eq { - ($pattern:pat) => { - |val| if let $pattern = *val { true } else { false } - }; - } +impl FuncValidator for PlainFuncValidator { + type Output = (); - const DENIED: &[fn(&Instruction) -> bool] = &[ - match_eq!(F32Load(_, _)), - match_eq!(F64Load(_, _)), - match_eq!(F32Store(_, _)), - match_eq!(F64Store(_, _)), - match_eq!(F32Const(_)), - match_eq!(F64Const(_)), - match_eq!(F32Eq), - match_eq!(F32Ne), - match_eq!(F32Lt), - match_eq!(F32Gt), - match_eq!(F32Le), - match_eq!(F32Ge), - match_eq!(F64Eq), - match_eq!(F64Ne), - match_eq!(F64Lt), - match_eq!(F64Gt), - match_eq!(F64Le), - match_eq!(F64Ge), - match_eq!(F32Abs), - match_eq!(F32Neg), - match_eq!(F32Ceil), - match_eq!(F32Floor), - match_eq!(F32Trunc), - match_eq!(F32Nearest), - match_eq!(F32Sqrt), - match_eq!(F32Add), - match_eq!(F32Sub), - match_eq!(F32Mul), - match_eq!(F32Div), - match_eq!(F32Min), - match_eq!(F32Max), - match_eq!(F32Copysign), - match_eq!(F64Abs), - match_eq!(F64Neg), - match_eq!(F64Ceil), - match_eq!(F64Floor), - match_eq!(F64Trunc), - match_eq!(F64Nearest), - match_eq!(F64Sqrt), - match_eq!(F64Add), - match_eq!(F64Sub), - match_eq!(F64Mul), - match_eq!(F64Div), - match_eq!(F64Min), - match_eq!(F64Max), - match_eq!(F64Copysign), - match_eq!(F32ConvertSI32), - match_eq!(F32ConvertUI32), - match_eq!(F32ConvertSI64), - match_eq!(F32ConvertUI64), - match_eq!(F32DemoteF64), - match_eq!(F64ConvertSI32), - match_eq!(F64ConvertUI32), - match_eq!(F64ConvertSI64), - match_eq!(F64ConvertUI64), - match_eq!(F64PromoteF32), - match_eq!(F32ReinterpretI32), - match_eq!(F64ReinterpretI64), - match_eq!(I32TruncSF32), - match_eq!(I32TruncUF32), - match_eq!(I32TruncSF64), - match_eq!(I32TruncUF64), - match_eq!(I64TruncSF32), - match_eq!(I64TruncUF32), - match_eq!(I64TruncSF64), - match_eq!(I64TruncUF64), - match_eq!(I32ReinterpretF32), - match_eq!(I64ReinterpretF64), - ]; - - if DENIED.iter().any(|is_denied| is_denied(op)) { - return Err(Error(format!("Floating point operation denied: {:?}", op))); - } - } + fn new(_ctx: &func::FunctionValidationContext, _body: &FuncBody) -> PlainFuncValidator { + PlainFuncValidator } - if let (Some(sec), Some(types)) = (module.function_section(), module.type_section()) { - use parity_wasm::elements::{Type, ValueType}; - - let types = types.types(); - - for sig in sec.entries() { - if let Some(typ) = types.get(sig.type_ref() as usize) { - match *typ { - Type::Function(ref func) => { - if func - .params() - .iter() - .chain(func.return_type().as_ref()) - .any(|&typ| typ == ValueType::F32 || typ == ValueType::F64) - { - return Err(Error(format!("Use of floating point types denied"))); - } - } - } - } - } + fn next_instruction( + &mut self, + ctx: &mut func::FunctionValidationContext, + instruction: &Instruction, + ) -> Result<(), Error> { + ctx.step(instruction) } - Ok(()) + fn finish(self) -> () { + () + } } -pub fn validate_module(module: Module) -> Result { +pub fn validate_module(module: &Module) -> Result { let mut context_builder = ModuleContextBuilder::new(); let mut imported_globals = Vec::new(); - let mut code_map = Vec::new(); + let mut validation = V::new(&module); // Copy types from module as is. context_builder.set_types( @@ -265,15 +226,15 @@ pub fn validate_module(module: Module) -> Result { .bodies() .get(index as usize) .ok_or(Error(format!("Missing body for function {}", index)))?; - let code = - FunctionReader::read_function(&context, function, function_body).map_err(|e| { - let Error(ref msg) = e; + + let output = func::drive::(&context, function, function_body) + .map_err(|Error(ref msg)| { Error(format!( "Function #{} reading/validation error: {}", index, msg )) })?; - code_map.push(code); + validation.on_function_validated(index as u32, output); } } @@ -381,7 +342,7 @@ pub fn validate_module(module: Module) -> Result { } } - Ok(ValidatedModule { module, code_map }) + Ok(validation.finish()) } fn validate_limits(limits: &ResizableLimits) -> Result<(), Error> { @@ -398,9 +359,34 @@ fn validate_limits(limits: &ResizableLimits) -> Result<(), Error> { } fn validate_memory_type(memory_type: &MemoryType) -> Result<(), Error> { - let initial: Pages = Pages(memory_type.limits().initial() as usize); - let maximum: Option = memory_type.limits().maximum().map(|m| Pages(m as usize)); - ::memory::validate_memory(initial, maximum).map_err(Error) + let initial = memory_type.limits().initial(); + let maximum: Option = memory_type.limits().maximum(); + validate_memory(initial, maximum).map_err(Error) +} + +pub fn validate_memory(initial: u32, maximum: Option) -> Result<(), String> { + if initial > LINEAR_MEMORY_MAX_PAGES { + return Err(format!( + "initial memory size must be at most {} pages", + LINEAR_MEMORY_MAX_PAGES + )); + } + if let Some(maximum) = maximum { + if initial > maximum { + return Err(format!( + "maximum limit {} is less than minimum {}", + maximum, initial, + )); + } + + if maximum > LINEAR_MEMORY_MAX_PAGES { + return Err(format!( + "maximum memory size must be at most {} pages", + LINEAR_MEMORY_MAX_PAGES + )); + } + } + Ok(()) } fn validate_table_type(table_type: &TableType) -> Result<(), Error> { diff --git a/src/common/stack.rs b/validation/src/stack.rs similarity index 98% rename from src/common/stack.rs rename to validation/src/stack.rs index 9e96a63..6f1166a 100644 --- a/src/common/stack.rs +++ b/validation/src/stack.rs @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use alloc::prelude::*; +use alloc::prelude::v1::*; use core::fmt; #[cfg(feature = "std")] diff --git a/validation/src/tests.rs b/validation/src/tests.rs new file mode 100644 index 0000000..c6794cf --- /dev/null +++ b/validation/src/tests.rs @@ -0,0 +1,277 @@ +use crate::{Error, PlainValidator}; +use parity_wasm::{ + builder::module, + elements::{ + BlockType, External, GlobalEntry, GlobalType, ImportEntry, InitExpr, Instruction, + Instructions, MemoryType, Module, TableType, ValueType, + }, +}; + +fn validate_module(module: &Module) -> Result<(), Error> { + super::validate_module::(module) +} + +#[test] +fn empty_is_valid() { + let module = module().build(); + assert!(validate_module(&module).is_ok()); +} + +#[test] +fn limits() { + let test_cases = vec![ + // min > max + (10, Some(9), false), + // min = max + (10, Some(10), true), + // table/memory is always valid without max + (10, None, true), + ]; + + for (min, max, is_valid) in test_cases { + // defined table + let m = module().table().with_min(min).with_max(max).build().build(); + assert_eq!(validate_module(&m).is_ok(), is_valid); + + // imported table + let m = module() + .with_import(ImportEntry::new( + "core".into(), + "table".into(), + External::Table(TableType::new(min, max)), + )) + .build(); + assert_eq!(validate_module(&m).is_ok(), is_valid); + + // defined memory + let m = module() + .memory() + .with_min(min) + .with_max(max) + .build() + .build(); + assert_eq!(validate_module(&m).is_ok(), is_valid); + + // imported table + let m = module() + .with_import(ImportEntry::new( + "core".into(), + "memory".into(), + External::Memory(MemoryType::new(min, max)), + )) + .build(); + assert_eq!(validate_module(&m).is_ok(), is_valid); + } +} + +#[test] +fn global_init_const() { + let m = module() + .with_global(GlobalEntry::new( + GlobalType::new(ValueType::I32, true), + InitExpr::new(vec![Instruction::I32Const(42), Instruction::End]), + )) + .build(); + assert!(validate_module(&m).is_ok()); + + // init expr type differs from declared global type + let m = module() + .with_global(GlobalEntry::new( + GlobalType::new(ValueType::I64, true), + InitExpr::new(vec![Instruction::I32Const(42), Instruction::End]), + )) + .build(); + assert!(validate_module(&m).is_err()); +} + +#[test] +fn global_init_global() { + let m = module() + .with_import(ImportEntry::new( + "env".into(), + "ext_global".into(), + External::Global(GlobalType::new(ValueType::I32, false)), + )) + .with_global(GlobalEntry::new( + GlobalType::new(ValueType::I32, true), + InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]), + )) + .build(); + assert!(validate_module(&m).is_ok()); + + // get_global can reference only previously defined globals + let m = module() + .with_global(GlobalEntry::new( + GlobalType::new(ValueType::I32, true), + InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]), + )) + .build(); + assert!(validate_module(&m).is_err()); + + // get_global can reference only const globals + let m = module() + .with_import(ImportEntry::new( + "env".into(), + "ext_global".into(), + External::Global(GlobalType::new(ValueType::I32, true)), + )) + .with_global(GlobalEntry::new( + GlobalType::new(ValueType::I32, true), + InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]), + )) + .build(); + assert!(validate_module(&m).is_err()); + + // get_global in init_expr can only refer to imported globals. + let m = module() + .with_global(GlobalEntry::new( + GlobalType::new(ValueType::I32, false), + InitExpr::new(vec![Instruction::I32Const(0), Instruction::End]), + )) + .with_global(GlobalEntry::new( + GlobalType::new(ValueType::I32, true), + InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]), + )) + .build(); + assert!(validate_module(&m).is_err()); +} + +#[test] +fn global_init_misc() { + // without delimiting End opcode + let m = module() + .with_global(GlobalEntry::new( + GlobalType::new(ValueType::I32, true), + InitExpr::new(vec![Instruction::I32Const(42)]), + )) + .build(); + assert!(validate_module(&m).is_err()); + + // empty init expr + let m = module() + .with_global(GlobalEntry::new( + GlobalType::new(ValueType::I32, true), + InitExpr::new(vec![Instruction::End]), + )) + .build(); + assert!(validate_module(&m).is_err()); + + // not an constant opcode used + let m = module() + .with_global(GlobalEntry::new( + GlobalType::new(ValueType::I32, true), + InitExpr::new(vec![Instruction::Unreachable, Instruction::End]), + )) + .build(); + assert!(validate_module(&m).is_err()); +} + +#[test] +fn module_limits_validity() { + // module cannot contain more than 1 memory atm. + let m = module() + .with_import(ImportEntry::new( + "core".into(), + "memory".into(), + External::Memory(MemoryType::new(10, None)), + )) + .memory() + .with_min(10) + .build() + .build(); + assert!(validate_module(&m).is_err()); + + // module cannot contain more than 1 table atm. + let m = module() + .with_import(ImportEntry::new( + "core".into(), + "table".into(), + External::Table(TableType::new(10, None)), + )) + .table() + .with_min(10) + .build() + .build(); + assert!(validate_module(&m).is_err()); +} + +#[test] +fn funcs() { + // recursive function calls is legal. + let m = module() + .function() + .signature() + .return_type() + .i32() + .build() + .body() + .with_instructions(Instructions::new(vec![ + Instruction::Call(1), + Instruction::End, + ])) + .build() + .build() + .function() + .signature() + .return_type() + .i32() + .build() + .body() + .with_instructions(Instructions::new(vec![ + Instruction::Call(0), + Instruction::End, + ])) + .build() + .build() + .build(); + assert!(validate_module(&m).is_ok()); +} + +#[test] +fn globals() { + // import immutable global is legal. + let m = module() + .with_import(ImportEntry::new( + "env".into(), + "ext_global".into(), + External::Global(GlobalType::new(ValueType::I32, false)), + )) + .build(); + assert!(validate_module(&m).is_ok()); + + // import mutable global is invalid. + let m = module() + .with_import(ImportEntry::new( + "env".into(), + "ext_global".into(), + External::Global(GlobalType::new(ValueType::I32, true)), + )) + .build(); + assert!(validate_module(&m).is_err()); +} + +#[test] +fn if_else_with_return_type_validation() { + let m = module() + .function() + .signature() + .build() + .body() + .with_instructions(Instructions::new(vec![ + Instruction::I32Const(1), + Instruction::If(BlockType::NoResult), + Instruction::I32Const(1), + Instruction::If(BlockType::Value(ValueType::I32)), + Instruction::I32Const(1), + Instruction::Else, + Instruction::I32Const(2), + Instruction::End, + Instruction::Drop, + Instruction::End, + Instruction::End, + ])) + .build() + .build() + .build(); + validate_module(&m).unwrap(); +} diff --git a/src/validation/util.rs b/validation/src/util.rs similarity index 97% rename from src/validation/util.rs rename to validation/src/util.rs index 6a42de4..5f320e9 100644 --- a/src/validation/util.rs +++ b/validation/src/util.rs @@ -1,7 +1,10 @@ +use crate::Error; #[allow(unused_imports)] -use alloc::prelude::*; +use alloc::prelude::v1::*; use parity_wasm::elements::{Local, ValueType}; -use validation::Error; + +#[cfg(test)] +use assert_matches::assert_matches; /// Locals are the concatenation of a slice of function parameters /// with function declared local variables.