diff --git a/benches/.gitignore b/benches/.gitignore index ea8c4bf..ffc9328 100644 --- a/benches/.gitignore +++ b/benches/.gitignore @@ -1 +1,3 @@ /target +*.trace + diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 3626446..f3d3fe8 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -7,3 +7,6 @@ authors = ["Sergey Pepyakin "] wasmi = { path = ".." } assert_matches = "1.2" wabt = "0.3" + +[profile.bench] +debug = true diff --git a/src/common/mod.rs b/src/common/mod.rs index 0801984..49ff10c 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,4 +1,3 @@ -use parity_wasm::elements::BlockType; pub mod stack; @@ -7,38 +6,4 @@ pub const DEFAULT_MEMORY_INDEX: u32 = 0; /// Index of default table. pub const DEFAULT_TABLE_INDEX: u32 = 0; -/// Control stack frame. -#[derive(Debug, Clone)] -pub struct BlockFrame { - /// Frame type. - pub frame_type: BlockFrameType, - /// A signature, which is a block signature type indicating the number and types of result values of the region. - pub block_type: BlockType, - /// A label for reference to block instruction. - pub begin_position: usize, - /// A label for reference from branch instructions. - pub branch_position: usize, - /// A label for reference from end instructions. - pub end_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. - pub 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. - pub polymorphic_stack: bool, -} - -/// Type of block frame. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum BlockFrameType { - /// Function frame. - Function, - /// Usual block frame. - Block, - /// Loop frame (branching to the beginning of block). - Loop, - /// True-subblock of if expression. - IfTrue, - /// False-subblock of if expression. - IfFalse, -} +// TODO: Move BlockFrame under validation. diff --git a/src/common/stack.rs b/src/common/stack.rs index fc1e289..2366adb 100644 --- a/src/common/stack.rs +++ b/src/common/stack.rs @@ -1,5 +1,4 @@ -use std::collections::VecDeque; use std::error; use std::fmt; @@ -22,7 +21,7 @@ impl error::Error for Error { #[derive(Debug)] pub struct StackWithLimit where T: Clone { /// Stack values. - values: VecDeque, + values: Vec, /// Stack limit (maximal stack len). limit: usize, } @@ -30,7 +29,7 @@ pub struct StackWithLimit where T: Clone { impl StackWithLimit where T: Clone { pub fn with_limit(limit: usize) -> Self { StackWithLimit { - values: VecDeque::new(), + values: Vec::new(), limit: limit } } @@ -43,19 +42,15 @@ impl StackWithLimit where T: Clone { self.values.len() } - pub fn limit(&self) -> usize { - self.limit - } - pub fn top(&self) -> Result<&T, Error> { self.values - .back() + .last() .ok_or_else(|| Error("non-empty stack expected".into())) } pub fn top_mut(&mut self) -> Result<&mut T, Error> { self.values - .back_mut() + .last_mut() .ok_or_else(|| Error("non-empty stack expected".into())) } @@ -72,13 +67,13 @@ impl StackWithLimit where T: Clone { return Err(Error(format!("exceeded stack limit {}", self.limit))); } - self.values.push_back(value); + self.values.push(value); Ok(()) } pub fn pop(&mut self) -> Result { self.values - .pop_back() + .pop() .ok_or_else(|| Error("non-empty stack expected".into())) } diff --git a/src/func.rs b/src/func.rs index 6a84ac1..beeacea 100644 --- a/src/func.rs +++ b/src/func.rs @@ -1,12 +1,12 @@ use std::rc::{Rc, Weak}; use std::fmt; -use std::collections::HashMap; -use parity_wasm::elements::{Local, Instructions}; +use parity_wasm::elements::Local; use {Trap, TrapKind, Signature}; use host::Externals; use runner::{check_function_args, Interpreter}; use value::RuntimeValue; use module::ModuleInstance; +use isa; /// Reference to a function (See [`FuncInstance`] for details). /// @@ -158,6 +158,5 @@ impl FuncInstance { #[derive(Clone, Debug)] pub struct FuncBody { pub locals: Vec, - pub instructions: Instructions, - pub labels: HashMap, + pub code: isa::Instructions, } diff --git a/src/isa.rs b/src/isa.rs new file mode 100644 index 0000000..5640c7a --- /dev/null +++ b/src/isa.rs @@ -0,0 +1,304 @@ +//! An instruction set used by wasmi. +//! +//! The instruction set is mostly derived from Wasm. However, +//! there is a substantial difference. +//! +//! # Structured Stack Machine vs Plain One +//! +//! Wasm is a structured stack machine. Wasm encodes control flow in structures +//! similar to that commonly found in a programming languages +//! such as if, while. That contrasts to a plain stack machine which +//! encodes all control flow with goto-like instructions. +//! +//! Structured stack machine code aligns well with goals of Wasm, +//! namely providing fast validation of Wasm code and compilation to native code. +//! +//! Unfortunately, the downside of structured stack machine code is +//! that it is less convenient to interpret. For example, let's look at +//! the following example in hypothetical structured stack machine: +//! +//! ```plain +//! loop +//! ... +//! if_true_jump_to_end +//! ... +//! end +//! ``` +//! +//! To execute `if_true_jump_to_end` , the interpreter needs to skip all instructions +//! until it reaches the *matching* `end`. That's quite inefficient compared +//! to a plain goto to the specific position. +//! +//! Because of this, the translation from the Wasm structured stack machine into a +//! plain one is taking place. +//! +//! # Locals +//! +//! In a plain stack machine local variables and arguments live on the stack. Instead of +//! accessing predifined locals slots in a plain stack machine locals are addressed relative +//! to the current stack pointer. Because of this instead of taking an index of a local +//! in {get,set,tee}_local operations, they take a relative depth as immediate. This works +//! because at each instruction we always know the current stack height. +//! +//! Roughly, the stack layout looks like this +//! +//! | caller arguments | +//! | - arg 1 | +//! | - arg 2 | +//! +------------------+ +//! | callee locals | +//! | - var 1 | +//! | - var 2 | +//! +------------------+ +//! | operands | +//! | - op 1 | +//! | - op 2 | +//! | | <-- current stack pointer +//! +------------------+ +//! +//! # Differences from Wasm +//! +//! - There is no `nop` instruction. +//! - All control flow strucutres are flattened to plain gotos. +//! - Implicit returns via reaching function scope `End` are replaced with an explicit `return` instruction. +//! - Locals live on the value stack now. +//! - Load/store instructions doesn't take `align` parameter. +//! - *.const store value in straight encoding. +//! - Reserved immediates are ignored for `call_indirect`, `current_memory`, `grow_memory`. +//! + +/// Should we keep a value before "discarding" a stack frame? +/// +/// Note that this is a `enum` since Wasm doesn't support multiple return +/// values at the moment. +#[repr(u8)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Keep { + None, + /// Pop one value from the yet-to-be-discarded stack frame to the + /// current stack frame. + Single, +} + +/// Specifies how many values we should keep and how many we should drop. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct DropKeep { + pub drop: u32, + pub keep: Keep, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Target { + pub dst_pc: u32, + pub drop_keep: DropKeep, +} + +#[allow(unused)] // TODO: Remove +#[derive(Debug, Clone, PartialEq)] +pub enum Instruction { + /// Push a local variable or an argument from the specified depth. + GetLocal(u32), + + /// Pop a value and put it in at the specified depth. + SetLocal(u32), + + /// Copy a value to the specified depth. + TeeLocal(u32), + + /// Similar to the Wasm ones, but instead of a label depth + /// they specify direct PC. + Br(Target), + BrIfEqz(Target), + BrIfNez(Target), + + /// br_table [t1 t2 t3 .. tn] tdefault + /// + /// Pops the value from the stack. Then this value is used as an index + /// to the branch table. + /// + /// However, the last target represents the default target. So if the index + /// is greater than length of the branch table, then the last index will be used. + /// + /// Validation ensures that there should be at least one target. + BrTable(Box<[Target]>), + + Unreachable, + Return(DropKeep), + + Call(u32), + CallIndirect(u32), + + Drop, + Select, + + GetGlobal(u32), + SetGlobal(u32), + + I32Load(u32), + I64Load(u32), + F32Load(u32), + F64Load(u32), + I32Load8S(u32), + I32Load8U(u32), + I32Load16S(u32), + I32Load16U(u32), + I64Load8S(u32), + I64Load8U(u32), + I64Load16S(u32), + I64Load16U(u32), + I64Load32S(u32), + I64Load32U(u32), + I32Store(u32), + I64Store(u32), + F32Store(u32), + F64Store(u32), + I32Store8(u32), + I32Store16(u32), + I64Store8(u32), + I64Store16(u32), + I64Store32(u32), + + CurrentMemory, + GrowMemory, + + I32Const(i32), + I64Const(i64), + F32Const(u32), + F64Const(u64), + + I32Eqz, + I32Eq, + I32Ne, + I32LtS, + I32LtU, + I32GtS, + I32GtU, + I32LeS, + I32LeU, + I32GeS, + I32GeU, + + I64Eqz, + I64Eq, + I64Ne, + I64LtS, + I64LtU, + I64GtS, + I64GtU, + I64LeS, + I64LeU, + I64GeS, + I64GeU, + + F32Eq, + F32Ne, + F32Lt, + F32Gt, + F32Le, + F32Ge, + + F64Eq, + F64Ne, + F64Lt, + F64Gt, + F64Le, + F64Ge, + + I32Clz, + I32Ctz, + I32Popcnt, + I32Add, + I32Sub, + I32Mul, + I32DivS, + I32DivU, + I32RemS, + I32RemU, + I32And, + I32Or, + I32Xor, + I32Shl, + I32ShrS, + I32ShrU, + I32Rotl, + I32Rotr, + + I64Clz, + I64Ctz, + I64Popcnt, + I64Add, + I64Sub, + I64Mul, + I64DivS, + I64DivU, + I64RemS, + I64RemU, + I64And, + I64Or, + I64Xor, + I64Shl, + I64ShrS, + I64ShrU, + I64Rotl, + I64Rotr, + F32Abs, + F32Neg, + F32Ceil, + F32Floor, + F32Trunc, + F32Nearest, + F32Sqrt, + F32Add, + F32Sub, + F32Mul, + F32Div, + F32Min, + F32Max, + F32Copysign, + F64Abs, + F64Neg, + F64Ceil, + F64Floor, + F64Trunc, + F64Nearest, + F64Sqrt, + F64Add, + F64Sub, + F64Mul, + F64Div, + F64Min, + F64Max, + F64Copysign, + + I32WrapI64, + I32TruncSF32, + I32TruncUF32, + I32TruncSF64, + I32TruncUF64, + I64ExtendSI32, + I64ExtendUI32, + I64TruncSF32, + I64TruncUF32, + I64TruncSF64, + I64TruncUF64, + F32ConvertSI32, + F32ConvertUI32, + F32ConvertSI64, + F32ConvertUI64, + F32DemoteF64, + F64ConvertSI32, + F64ConvertUI32, + F64ConvertSI64, + F64ConvertUI64, + F64PromoteF32, + + I32ReinterpretF32, + I64ReinterpretF64, + F32ReinterpretI32, + F64ReinterpretI64, +} + +#[derive(Debug, Clone)] +pub struct Instructions { + pub code: Vec, +} diff --git a/src/lib.rs b/src/lib.rs index b9a72b9..c563c75 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,7 +109,6 @@ extern crate nan_preserving_float; use std::fmt; use std::error; -use std::collections::HashMap; /// Error type which can thrown by wasm code or by host environment. /// @@ -356,6 +355,7 @@ mod imports; mod global; mod func; mod types; +mod isa; #[cfg(test)] mod tests; @@ -378,7 +378,7 @@ pub mod memory_units { /// Deserialized module prepared for instantiation. pub struct Module { - labels: HashMap>, + code_map: Vec, module: parity_wasm::elements::Module, } @@ -418,12 +418,12 @@ impl Module { pub fn from_parity_wasm_module(module: parity_wasm::elements::Module) -> Result { use validation::{validate_module, ValidatedModule}; let ValidatedModule { - labels, + code_map, module, } = validate_module(module)?; Ok(Module { - labels, + code_map, module, }) } @@ -524,7 +524,7 @@ impl Module { &self.module } - pub(crate) fn labels(&self) -> &HashMap> { - &self.labels + pub(crate) fn code(&self) -> &Vec { + &self.code_map } } diff --git a/src/module.rs b/src/module.rs index faaf95e..47deb32 100644 --- a/src/module.rs +++ b/src/module.rs @@ -291,7 +291,7 @@ impl ModuleInstance { } } - let labels = loaded_module.labels(); + let code = loaded_module.code(); { let funcs = module.function_section().map(|fs| fs.entries()).unwrap_or( &[], @@ -308,13 +308,12 @@ impl ModuleInstance { let signature = instance.signature_by_index(ty.type_ref()).expect( "Due to validation type should exists", ); - let labels = labels.get(&index).expect( + let code = code.get(index).expect( "At func validation time labels are collected; Collected labels are added by index; qed", ).clone(); let func_body = FuncBody { locals: body.locals().to_vec(), - instructions: body.code().clone(), - labels: labels, + code: code, }; let func_instance = FuncInstance::alloc_internal(Rc::downgrade(&instance.0), signature, func_body); diff --git a/src/runner.rs b/src/runner.rs index ea94d11..225ae29 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -3,123 +3,147 @@ use std::ops; use std::{u32, usize}; use std::fmt; use std::iter::repeat; -use std::collections::{HashMap, VecDeque}; -use parity_wasm::elements::{Instruction, BlockType, Local}; +use parity_wasm::elements::Local; use {Error, Trap, TrapKind, Signature}; use module::ModuleRef; +use memory::MemoryRef; use func::{FuncRef, FuncInstance, FuncInstanceInternal}; use value::{ RuntimeValue, FromRuntimeValue, WrapInto, TryTruncateInto, ExtendInto, ArithmeticOps, Integer, Float, LittleEndianConvert, TransmuteInto, }; use host::Externals; -use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX, BlockFrame, BlockFrameType}; -use common::stack::StackWithLimit; +use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX}; use memory_units::Pages; use nan_preserving_float::{F32, F64}; +use isa; /// Maximum number of entries in value stack. -pub const DEFAULT_VALUE_STACK_LIMIT: usize = 16384; -/// Maximum number of entries in frame stack. -pub const DEFAULT_FRAME_STACK_LIMIT: usize = 16384; +pub const DEFAULT_VALUE_STACK_LIMIT: usize = (512 * 1024) / ::std::mem::size_of::(); -/// Function interpreter. -pub struct Interpreter<'a, E: Externals + 'a> { - externals: &'a mut E, -} +// TODO: Make these parameters changeble. +pub const DEFAULT_CALL_STACK_LIMIT: usize = 16 * 1024; /// Interpreter action to execute after executing instruction. pub enum InstructionOutcome { /// Continue with next instruction. RunNextInstruction, - /// Branch to given frame. - Branch(usize), + /// Branch to an instruction at the given position. + Branch(isa::Target), /// Execute function call. ExecuteCall(FuncRef), - /// End current frame. - End, /// Return from current function block. - Return, + Return(isa::DropKeep), } /// Function run result. enum RunResult { - /// Function has returned (optional) value. - Return(Option), + /// Function has returned. + Return, /// Function is calling other function. NestedCall(FuncRef), } +/// Function interpreter. +pub struct Interpreter<'a, E: Externals + 'a> { + externals: &'a mut E, + value_stack: ValueStack, +} + impl<'a, E: Externals> Interpreter<'a, E> { pub fn new(externals: &'a mut E) -> Interpreter<'a, E> { + let value_stack = ValueStack::with_limit(DEFAULT_VALUE_STACK_LIMIT); Interpreter { externals, + value_stack, } } pub fn start_execution(&mut self, func: &FuncRef, args: &[RuntimeValue]) -> Result, Trap> { - let context = FunctionContext::new( - func.clone(), - DEFAULT_VALUE_STACK_LIMIT, - DEFAULT_FRAME_STACK_LIMIT, - func.signature(), - args.into_iter().cloned().collect(), - ); + for arg in args { + self.value_stack + .push(*arg) + .map_err( + // There is not enough space for pushing initial arguments. + // Weird, but bail out anyway. + |_| Trap::from(TrapKind::StackOverflow) + )?; + } - let mut function_stack = VecDeque::new(); - function_stack.push_back(context); + let initial_frame = FunctionContext::new(func.clone()); - self.run_interpreter_loop(&mut function_stack) + let mut call_stack = Vec::new(); + call_stack.push(initial_frame); + + self.run_interpreter_loop(&mut call_stack)?; + + let opt_return_value = func.signature().return_type().map(|_vt| { + self.value_stack.pop() + }); + + // Ensure that stack is empty after the execution. + assert!(self.value_stack.len() == 0); + + Ok(opt_return_value) } - fn run_interpreter_loop(&mut self, function_stack: &mut VecDeque) -> Result, Trap> { + fn run_interpreter_loop(&mut self, call_stack: &mut Vec) -> Result<(), Trap> { loop { - let mut function_context = function_stack.pop_back().expect("on loop entry - not empty; on loop continue - checking for emptiness; qed"); + let mut function_context = call_stack + .pop() + .expect("on loop entry - not empty; on loop continue - checking for emptiness; qed"); let function_ref = function_context.function.clone(); let function_body = function_ref .body() .expect( "Host functions checked in function_return below; Internal functions always have a body; qed" ); + if !function_context.is_initialized() { - let return_type = function_context.return_type; - function_context.initialize(&function_body.locals); - function_context.push_frame(&function_body.labels, BlockFrameType::Function, return_type).map_err(Trap::new)?; + // Initialize stack frame for the function call. + function_context.initialize(&function_body.locals, &mut self.value_stack)?; } - let function_return = self.do_run_function(&mut function_context, function_body.instructions.elements(), &function_body.labels).map_err(Trap::new)?; + let function_return = + self.do_run_function( + &mut function_context, + &function_body.code.code, + ).map_err(Trap::new)?; match function_return { - RunResult::Return(return_value) => { - match function_stack.back_mut() { - Some(caller_context) => if let Some(return_value) = return_value { - caller_context.value_stack_mut().push(return_value).map_err(Trap::new)?; - }, - None => return Ok(return_value), + RunResult::Return => { + if call_stack.last().is_none() { + // This was the last frame in the call stack. This means we + // are done executing. + return Ok(()); } }, RunResult::NestedCall(nested_func) => { + if call_stack.len() + 1 >= DEFAULT_CALL_STACK_LIMIT { + return Err(TrapKind::StackOverflow.into()); + } + match *nested_func.as_internal() { FuncInstanceInternal::Internal { .. } => { - let nested_context = function_context.nested(nested_func.clone()).map_err(Trap::new)?; - function_stack.push_back(function_context); - function_stack.push_back(nested_context); + let nested_context = FunctionContext::new(nested_func.clone()); + call_stack.push(function_context); + call_stack.push(nested_context); }, FuncInstanceInternal::Host { ref signature, .. } => { - let args = prepare_function_args(signature, &mut function_context.value_stack); + let args = prepare_function_args(signature, &mut self.value_stack); let return_val = FuncInstance::invoke(&nested_func, &args, self.externals)?; // Check if `return_val` matches the signature. - let value_ty = return_val.clone().map(|val| val.value_type()); + let value_ty = return_val.as_ref().map(|val| val.value_type()); let expected_ty = nested_func.signature().return_type(); if value_ty != expected_ty { return Err(TrapKind::UnexpectedSignature.into()); } if let Some(return_val) = return_val { - function_context.value_stack_mut().push(return_val).map_err(Trap::new)?; + self.value_stack.push(return_val).map_err(Trap::new)?; } - function_stack.push_back(function_context); + call_stack.push(function_context); } } }, @@ -127,239 +151,218 @@ impl<'a, E: Externals> Interpreter<'a, E> { } } - fn do_run_function(&mut self, function_context: &mut FunctionContext, function_body: &[Instruction], function_labels: &HashMap) -> Result { + fn do_run_function(&mut self, function_context: &mut FunctionContext, instructions: &[isa::Instruction]) -> Result { loop { - let instruction = &function_body[function_context.position]; + let instruction = &instructions[function_context.position]; - match self.run_instruction(function_context, function_labels, instruction)? { + match self.run_instruction(function_context, instruction)? { InstructionOutcome::RunNextInstruction => function_context.position += 1, - InstructionOutcome::Branch(mut index) => { - // discard index - 1 blocks - while index >= 1 { - function_context.discard_frame(); - index -= 1; - } - - function_context.pop_frame(true)?; - if function_context.frame_stack().is_empty() { - break; - } + InstructionOutcome::Branch(target) => { + function_context.position = target.dst_pc as usize; + self.value_stack.drop_keep(target.drop_keep); }, InstructionOutcome::ExecuteCall(func_ref) => { function_context.position += 1; return Ok(RunResult::NestedCall(func_ref)); }, - InstructionOutcome::End => { - if function_context.frame_stack().is_empty() { - break; - } + InstructionOutcome::Return(drop_keep) => { + self.value_stack.drop_keep(drop_keep); + break; }, - InstructionOutcome::Return => break, } } - Ok(RunResult::Return(match function_context.return_type { - BlockType::Value(_) => { - let result = function_context - .value_stack_mut() - .pop(); - Some(result) - }, - BlockType::NoResult => None, - })) + Ok(RunResult::Return) } - fn run_instruction(&mut self, context: &mut FunctionContext, labels: &HashMap, instruction: &Instruction) -> Result { + #[inline(always)] + fn run_instruction(&mut self, context: &mut FunctionContext, instruction: &isa::Instruction) -> Result { match instruction { - &Instruction::Unreachable => self.run_unreachable(context), - &Instruction::Nop => self.run_nop(context), - &Instruction::Block(block_type) => self.run_block(context, labels, block_type), - &Instruction::Loop(block_type) => self.run_loop(context, labels, block_type), - &Instruction::If(block_type) => self.run_if(context, labels, block_type), - &Instruction::Else => self.run_else(context, labels), - &Instruction::End => self.run_end(context), - &Instruction::Br(idx) => self.run_br(context, idx), - &Instruction::BrIf(idx) => self.run_br_if(context, idx), - &Instruction::BrTable(ref table, default) => self.run_br_table(context, table, default), - &Instruction::Return => self.run_return(context), + &isa::Instruction::Unreachable => self.run_unreachable(context), - &Instruction::Call(index) => self.run_call(context, index), - &Instruction::CallIndirect(index, _reserved) => self.run_call_indirect(context, index), + &isa::Instruction::Br(ref target) => self.run_br(context, target.clone()), + &isa::Instruction::BrIfEqz(ref target) => self.run_br_eqz(target.clone()), + &isa::Instruction::BrIfNez(ref target) => self.run_br_nez(target.clone()), + &isa::Instruction::BrTable(ref targets) => self.run_br_table(targets), + &isa::Instruction::Return(drop_keep) => self.run_return(drop_keep), - &Instruction::Drop => self.run_drop(context), - &Instruction::Select => self.run_select(context), + &isa::Instruction::Call(index) => self.run_call(context, index), + &isa::Instruction::CallIndirect(index) => self.run_call_indirect(context, index), - &Instruction::GetLocal(index) => self.run_get_local(context, index), - &Instruction::SetLocal(index) => self.run_set_local(context, index), - &Instruction::TeeLocal(index) => self.run_tee_local(context, index), - &Instruction::GetGlobal(index) => self.run_get_global(context, index), - &Instruction::SetGlobal(index) => self.run_set_global(context, index), + &isa::Instruction::Drop => self.run_drop(), + &isa::Instruction::Select => self.run_select(), - &Instruction::I32Load(align, offset) => self.run_load::(context, align, offset), - &Instruction::I64Load(align, offset) => self.run_load::(context, align, offset), - &Instruction::F32Load(align, offset) => self.run_load::(context, align, offset), - &Instruction::F64Load(align, offset) => self.run_load::(context, align, offset), - &Instruction::I32Load8S(align, offset) => self.run_load_extend::(context, align, offset), - &Instruction::I32Load8U(align, offset) => self.run_load_extend::(context, align, offset), - &Instruction::I32Load16S(align, offset) => self.run_load_extend::(context, align, offset), - &Instruction::I32Load16U(align, offset) => self.run_load_extend::(context, align, offset), - &Instruction::I64Load8S(align, offset) => self.run_load_extend::(context, align, offset), - &Instruction::I64Load8U(align, offset) => self.run_load_extend::(context, align, offset), - &Instruction::I64Load16S(align, offset) => self.run_load_extend::(context, align, offset), - &Instruction::I64Load16U(align, offset) => self.run_load_extend::(context, align, offset), - &Instruction::I64Load32S(align, offset) => self.run_load_extend::(context, align, offset), - &Instruction::I64Load32U(align, offset) => self.run_load_extend::(context, align, offset), + &isa::Instruction::GetLocal(depth) => self.run_get_local(depth), + &isa::Instruction::SetLocal(depth) => self.run_set_local(depth), + &isa::Instruction::TeeLocal(depth) => self.run_tee_local(depth), + &isa::Instruction::GetGlobal(index) => self.run_get_global(context, index), + &isa::Instruction::SetGlobal(index) => self.run_set_global(context, index), - &Instruction::I32Store(align, offset) => self.run_store::(context, align, offset), - &Instruction::I64Store(align, offset) => self.run_store::(context, align, offset), - &Instruction::F32Store(align, offset) => self.run_store::(context, align, offset), - &Instruction::F64Store(align, offset) => self.run_store::(context, align, offset), - &Instruction::I32Store8(align, offset) => self.run_store_wrap::(context, align, offset), - &Instruction::I32Store16(align, offset) => self.run_store_wrap::(context, align, offset), - &Instruction::I64Store8(align, offset) => self.run_store_wrap::(context, align, offset), - &Instruction::I64Store16(align, offset) => self.run_store_wrap::(context, align, offset), - &Instruction::I64Store32(align, offset) => self.run_store_wrap::(context, align, offset), + &isa::Instruction::I32Load(offset) => self.run_load::(context, offset), + &isa::Instruction::I64Load(offset) => self.run_load::(context, offset), + &isa::Instruction::F32Load(offset) => self.run_load::(context, offset), + &isa::Instruction::F64Load(offset) => self.run_load::(context, offset), + &isa::Instruction::I32Load8S(offset) => self.run_load_extend::(context, offset), + &isa::Instruction::I32Load8U(offset) => self.run_load_extend::(context, offset), + &isa::Instruction::I32Load16S(offset) => self.run_load_extend::(context, offset), + &isa::Instruction::I32Load16U(offset) => self.run_load_extend::(context, offset), + &isa::Instruction::I64Load8S(offset) => self.run_load_extend::(context, offset), + &isa::Instruction::I64Load8U(offset) => self.run_load_extend::(context, offset), + &isa::Instruction::I64Load16S(offset) => self.run_load_extend::(context, offset), + &isa::Instruction::I64Load16U(offset) => self.run_load_extend::(context, offset), + &isa::Instruction::I64Load32S(offset) => self.run_load_extend::(context, offset), + &isa::Instruction::I64Load32U(offset) => self.run_load_extend::(context, offset), - &Instruction::CurrentMemory(_) => self.run_current_memory(context), - &Instruction::GrowMemory(_) => self.run_grow_memory(context), + &isa::Instruction::I32Store(offset) => self.run_store::(context, offset), + &isa::Instruction::I64Store(offset) => self.run_store::(context, offset), + &isa::Instruction::F32Store(offset) => self.run_store::(context, offset), + &isa::Instruction::F64Store(offset) => self.run_store::(context, offset), + &isa::Instruction::I32Store8(offset) => self.run_store_wrap::(context, offset), + &isa::Instruction::I32Store16(offset) => self.run_store_wrap::(context, offset), + &isa::Instruction::I64Store8(offset) => self.run_store_wrap::(context, offset), + &isa::Instruction::I64Store16(offset) => self.run_store_wrap::(context, offset), + &isa::Instruction::I64Store32(offset) => self.run_store_wrap::(context, offset), - &Instruction::I32Const(val) => self.run_const(context, val.into()), - &Instruction::I64Const(val) => self.run_const(context, val.into()), - &Instruction::F32Const(val) => self.run_const(context, RuntimeValue::decode_f32(val)), - &Instruction::F64Const(val) => self.run_const(context, RuntimeValue::decode_f64(val)), + &isa::Instruction::CurrentMemory => self.run_current_memory(context), + &isa::Instruction::GrowMemory => self.run_grow_memory(context), - &Instruction::I32Eqz => self.run_eqz::(context), - &Instruction::I32Eq => self.run_eq::(context), - &Instruction::I32Ne => self.run_ne::(context), - &Instruction::I32LtS => self.run_lt::(context), - &Instruction::I32LtU => self.run_lt::(context), - &Instruction::I32GtS => self.run_gt::(context), - &Instruction::I32GtU => self.run_gt::(context), - &Instruction::I32LeS => self.run_lte::(context), - &Instruction::I32LeU => self.run_lte::(context), - &Instruction::I32GeS => self.run_gte::(context), - &Instruction::I32GeU => self.run_gte::(context), + &isa::Instruction::I32Const(val) => self.run_const(val.into()), + &isa::Instruction::I64Const(val) => self.run_const(val.into()), + &isa::Instruction::F32Const(val) => self.run_const(RuntimeValue::decode_f32(val)), + &isa::Instruction::F64Const(val) => self.run_const(RuntimeValue::decode_f64(val)), - &Instruction::I64Eqz => self.run_eqz::(context), - &Instruction::I64Eq => self.run_eq::(context), - &Instruction::I64Ne => self.run_ne::(context), - &Instruction::I64LtS => self.run_lt::(context), - &Instruction::I64LtU => self.run_lt::(context), - &Instruction::I64GtS => self.run_gt::(context), - &Instruction::I64GtU => self.run_gt::(context), - &Instruction::I64LeS => self.run_lte::(context), - &Instruction::I64LeU => self.run_lte::(context), - &Instruction::I64GeS => self.run_gte::(context), - &Instruction::I64GeU => self.run_gte::(context), + &isa::Instruction::I32Eqz => self.run_eqz::(), + &isa::Instruction::I32Eq => self.run_eq::(), + &isa::Instruction::I32Ne => self.run_ne::(), + &isa::Instruction::I32LtS => self.run_lt::(), + &isa::Instruction::I32LtU => self.run_lt::(), + &isa::Instruction::I32GtS => self.run_gt::(), + &isa::Instruction::I32GtU => self.run_gt::(), + &isa::Instruction::I32LeS => self.run_lte::(), + &isa::Instruction::I32LeU => self.run_lte::(), + &isa::Instruction::I32GeS => self.run_gte::(), + &isa::Instruction::I32GeU => self.run_gte::(), - &Instruction::F32Eq => self.run_eq::(context), - &Instruction::F32Ne => self.run_ne::(context), - &Instruction::F32Lt => self.run_lt::(context), - &Instruction::F32Gt => self.run_gt::(context), - &Instruction::F32Le => self.run_lte::(context), - &Instruction::F32Ge => self.run_gte::(context), + &isa::Instruction::I64Eqz => self.run_eqz::(), + &isa::Instruction::I64Eq => self.run_eq::(), + &isa::Instruction::I64Ne => self.run_ne::(), + &isa::Instruction::I64LtS => self.run_lt::(), + &isa::Instruction::I64LtU => self.run_lt::(), + &isa::Instruction::I64GtS => self.run_gt::(), + &isa::Instruction::I64GtU => self.run_gt::(), + &isa::Instruction::I64LeS => self.run_lte::(), + &isa::Instruction::I64LeU => self.run_lte::(), + &isa::Instruction::I64GeS => self.run_gte::(), + &isa::Instruction::I64GeU => self.run_gte::(), - &Instruction::F64Eq => self.run_eq::(context), - &Instruction::F64Ne => self.run_ne::(context), - &Instruction::F64Lt => self.run_lt::(context), - &Instruction::F64Gt => self.run_gt::(context), - &Instruction::F64Le => self.run_lte::(context), - &Instruction::F64Ge => self.run_gte::(context), + &isa::Instruction::F32Eq => self.run_eq::(), + &isa::Instruction::F32Ne => self.run_ne::(), + &isa::Instruction::F32Lt => self.run_lt::(), + &isa::Instruction::F32Gt => self.run_gt::(), + &isa::Instruction::F32Le => self.run_lte::(), + &isa::Instruction::F32Ge => self.run_gte::(), - &Instruction::I32Clz => self.run_clz::(context), - &Instruction::I32Ctz => self.run_ctz::(context), - &Instruction::I32Popcnt => self.run_popcnt::(context), - &Instruction::I32Add => self.run_add::(context), - &Instruction::I32Sub => self.run_sub::(context), - &Instruction::I32Mul => self.run_mul::(context), - &Instruction::I32DivS => self.run_div::(context), - &Instruction::I32DivU => self.run_div::(context), - &Instruction::I32RemS => self.run_rem::(context), - &Instruction::I32RemU => self.run_rem::(context), - &Instruction::I32And => self.run_and::(context), - &Instruction::I32Or => self.run_or::(context), - &Instruction::I32Xor => self.run_xor::(context), - &Instruction::I32Shl => self.run_shl::(context, 0x1F), - &Instruction::I32ShrS => self.run_shr::(context, 0x1F), - &Instruction::I32ShrU => self.run_shr::(context, 0x1F), - &Instruction::I32Rotl => self.run_rotl::(context), - &Instruction::I32Rotr => self.run_rotr::(context), + &isa::Instruction::F64Eq => self.run_eq::(), + &isa::Instruction::F64Ne => self.run_ne::(), + &isa::Instruction::F64Lt => self.run_lt::(), + &isa::Instruction::F64Gt => self.run_gt::(), + &isa::Instruction::F64Le => self.run_lte::(), + &isa::Instruction::F64Ge => self.run_gte::(), - &Instruction::I64Clz => self.run_clz::(context), - &Instruction::I64Ctz => self.run_ctz::(context), - &Instruction::I64Popcnt => self.run_popcnt::(context), - &Instruction::I64Add => self.run_add::(context), - &Instruction::I64Sub => self.run_sub::(context), - &Instruction::I64Mul => self.run_mul::(context), - &Instruction::I64DivS => self.run_div::(context), - &Instruction::I64DivU => self.run_div::(context), - &Instruction::I64RemS => self.run_rem::(context), - &Instruction::I64RemU => self.run_rem::(context), - &Instruction::I64And => self.run_and::(context), - &Instruction::I64Or => self.run_or::(context), - &Instruction::I64Xor => self.run_xor::(context), - &Instruction::I64Shl => self.run_shl::(context, 0x3F), - &Instruction::I64ShrS => self.run_shr::(context, 0x3F), - &Instruction::I64ShrU => self.run_shr::(context, 0x3F), - &Instruction::I64Rotl => self.run_rotl::(context), - &Instruction::I64Rotr => self.run_rotr::(context), + &isa::Instruction::I32Clz => self.run_clz::(), + &isa::Instruction::I32Ctz => self.run_ctz::(), + &isa::Instruction::I32Popcnt => self.run_popcnt::(), + &isa::Instruction::I32Add => self.run_add::(), + &isa::Instruction::I32Sub => self.run_sub::(), + &isa::Instruction::I32Mul => self.run_mul::(), + &isa::Instruction::I32DivS => self.run_div::(), + &isa::Instruction::I32DivU => self.run_div::(), + &isa::Instruction::I32RemS => self.run_rem::(), + &isa::Instruction::I32RemU => self.run_rem::(), + &isa::Instruction::I32And => self.run_and::(), + &isa::Instruction::I32Or => self.run_or::(), + &isa::Instruction::I32Xor => self.run_xor::(), + &isa::Instruction::I32Shl => self.run_shl::(0x1F), + &isa::Instruction::I32ShrS => self.run_shr::(0x1F), + &isa::Instruction::I32ShrU => self.run_shr::(0x1F), + &isa::Instruction::I32Rotl => self.run_rotl::(), + &isa::Instruction::I32Rotr => self.run_rotr::(), - &Instruction::F32Abs => self.run_abs::(context), - &Instruction::F32Neg => self.run_neg::(context), - &Instruction::F32Ceil => self.run_ceil::(context), - &Instruction::F32Floor => self.run_floor::(context), - &Instruction::F32Trunc => self.run_trunc::(context), - &Instruction::F32Nearest => self.run_nearest::(context), - &Instruction::F32Sqrt => self.run_sqrt::(context), - &Instruction::F32Add => self.run_add::(context), - &Instruction::F32Sub => self.run_sub::(context), - &Instruction::F32Mul => self.run_mul::(context), - &Instruction::F32Div => self.run_div::(context), - &Instruction::F32Min => self.run_min::(context), - &Instruction::F32Max => self.run_max::(context), - &Instruction::F32Copysign => self.run_copysign::(context), + &isa::Instruction::I64Clz => self.run_clz::(), + &isa::Instruction::I64Ctz => self.run_ctz::(), + &isa::Instruction::I64Popcnt => self.run_popcnt::(), + &isa::Instruction::I64Add => self.run_add::(), + &isa::Instruction::I64Sub => self.run_sub::(), + &isa::Instruction::I64Mul => self.run_mul::(), + &isa::Instruction::I64DivS => self.run_div::(), + &isa::Instruction::I64DivU => self.run_div::(), + &isa::Instruction::I64RemS => self.run_rem::(), + &isa::Instruction::I64RemU => self.run_rem::(), + &isa::Instruction::I64And => self.run_and::(), + &isa::Instruction::I64Or => self.run_or::(), + &isa::Instruction::I64Xor => self.run_xor::(), + &isa::Instruction::I64Shl => self.run_shl::(0x3F), + &isa::Instruction::I64ShrS => self.run_shr::(0x3F), + &isa::Instruction::I64ShrU => self.run_shr::(0x3F), + &isa::Instruction::I64Rotl => self.run_rotl::(), + &isa::Instruction::I64Rotr => self.run_rotr::(), - &Instruction::F64Abs => self.run_abs::(context), - &Instruction::F64Neg => self.run_neg::(context), - &Instruction::F64Ceil => self.run_ceil::(context), - &Instruction::F64Floor => self.run_floor::(context), - &Instruction::F64Trunc => self.run_trunc::(context), - &Instruction::F64Nearest => self.run_nearest::(context), - &Instruction::F64Sqrt => self.run_sqrt::(context), - &Instruction::F64Add => self.run_add::(context), - &Instruction::F64Sub => self.run_sub::(context), - &Instruction::F64Mul => self.run_mul::(context), - &Instruction::F64Div => self.run_div::(context), - &Instruction::F64Min => self.run_min::(context), - &Instruction::F64Max => self.run_max::(context), - &Instruction::F64Copysign => self.run_copysign::(context), + &isa::Instruction::F32Abs => self.run_abs::(), + &isa::Instruction::F32Neg => self.run_neg::(), + &isa::Instruction::F32Ceil => self.run_ceil::(), + &isa::Instruction::F32Floor => self.run_floor::(), + &isa::Instruction::F32Trunc => self.run_trunc::(), + &isa::Instruction::F32Nearest => self.run_nearest::(), + &isa::Instruction::F32Sqrt => self.run_sqrt::(), + &isa::Instruction::F32Add => self.run_add::(), + &isa::Instruction::F32Sub => self.run_sub::(), + &isa::Instruction::F32Mul => self.run_mul::(), + &isa::Instruction::F32Div => self.run_div::(), + &isa::Instruction::F32Min => self.run_min::(), + &isa::Instruction::F32Max => self.run_max::(), + &isa::Instruction::F32Copysign => self.run_copysign::(), - &Instruction::I32WrapI64 => self.run_wrap::(context), - &Instruction::I32TruncSF32 => self.run_trunc_to_int::(context), - &Instruction::I32TruncUF32 => self.run_trunc_to_int::(context), - &Instruction::I32TruncSF64 => self.run_trunc_to_int::(context), - &Instruction::I32TruncUF64 => self.run_trunc_to_int::(context), - &Instruction::I64ExtendSI32 => self.run_extend::(context), - &Instruction::I64ExtendUI32 => self.run_extend::(context), - &Instruction::I64TruncSF32 => self.run_trunc_to_int::(context), - &Instruction::I64TruncUF32 => self.run_trunc_to_int::(context), - &Instruction::I64TruncSF64 => self.run_trunc_to_int::(context), - &Instruction::I64TruncUF64 => self.run_trunc_to_int::(context), - &Instruction::F32ConvertSI32 => self.run_extend::(context), - &Instruction::F32ConvertUI32 => self.run_extend::(context), - &Instruction::F32ConvertSI64 => self.run_wrap::(context), - &Instruction::F32ConvertUI64 => self.run_wrap::(context), - &Instruction::F32DemoteF64 => self.run_wrap::(context), - &Instruction::F64ConvertSI32 => self.run_extend::(context), - &Instruction::F64ConvertUI32 => self.run_extend::(context), - &Instruction::F64ConvertSI64 => self.run_extend::(context), - &Instruction::F64ConvertUI64 => self.run_extend::(context), - &Instruction::F64PromoteF32 => self.run_extend::(context), + &isa::Instruction::F64Abs => self.run_abs::(), + &isa::Instruction::F64Neg => self.run_neg::(), + &isa::Instruction::F64Ceil => self.run_ceil::(), + &isa::Instruction::F64Floor => self.run_floor::(), + &isa::Instruction::F64Trunc => self.run_trunc::(), + &isa::Instruction::F64Nearest => self.run_nearest::(), + &isa::Instruction::F64Sqrt => self.run_sqrt::(), + &isa::Instruction::F64Add => self.run_add::(), + &isa::Instruction::F64Sub => self.run_sub::(), + &isa::Instruction::F64Mul => self.run_mul::(), + &isa::Instruction::F64Div => self.run_div::(), + &isa::Instruction::F64Min => self.run_min::(), + &isa::Instruction::F64Max => self.run_max::(), + &isa::Instruction::F64Copysign => self.run_copysign::(), - &Instruction::I32ReinterpretF32 => self.run_reinterpret::(context), - &Instruction::I64ReinterpretF64 => self.run_reinterpret::(context), - &Instruction::F32ReinterpretI32 => self.run_reinterpret::(context), - &Instruction::F64ReinterpretI64 => self.run_reinterpret::(context), + &isa::Instruction::I32WrapI64 => self.run_wrap::(), + &isa::Instruction::I32TruncSF32 => self.run_trunc_to_int::(), + &isa::Instruction::I32TruncUF32 => self.run_trunc_to_int::(), + &isa::Instruction::I32TruncSF64 => self.run_trunc_to_int::(), + &isa::Instruction::I32TruncUF64 => self.run_trunc_to_int::(), + &isa::Instruction::I64ExtendSI32 => self.run_extend::(), + &isa::Instruction::I64ExtendUI32 => self.run_extend::(), + &isa::Instruction::I64TruncSF32 => self.run_trunc_to_int::(), + &isa::Instruction::I64TruncUF32 => self.run_trunc_to_int::(), + &isa::Instruction::I64TruncSF64 => self.run_trunc_to_int::(), + &isa::Instruction::I64TruncUF64 => self.run_trunc_to_int::(), + &isa::Instruction::F32ConvertSI32 => self.run_extend::(), + &isa::Instruction::F32ConvertUI32 => self.run_extend::(), + &isa::Instruction::F32ConvertSI64 => self.run_wrap::(), + &isa::Instruction::F32ConvertUI64 => self.run_wrap::(), + &isa::Instruction::F32DemoteF64 => self.run_wrap::(), + &isa::Instruction::F64ConvertSI32 => self.run_extend::(), + &isa::Instruction::F64ConvertUI32 => self.run_extend::(), + &isa::Instruction::F64ConvertSI64 => self.run_extend::(), + &isa::Instruction::F64ConvertUI64 => self.run_extend::(), + &isa::Instruction::F64PromoteF32 => self.run_extend::(), + + &isa::Instruction::I32ReinterpretF32 => self.run_reinterpret::(), + &isa::Instruction::I64ReinterpretF64 => self.run_reinterpret::(), + &isa::Instruction::F32ReinterpretI32 => self.run_reinterpret::(), + &isa::Instruction::F64ReinterpretI64 => self.run_reinterpret::(), } } @@ -367,72 +370,45 @@ impl<'a, E: Externals> Interpreter<'a, E> { Err(TrapKind::Unreachable) } - fn run_nop(&mut self, _context: &mut FunctionContext) -> Result { - Ok(InstructionOutcome::RunNextInstruction) + fn run_br(&mut self, _context: &mut FunctionContext, target: isa::Target) -> Result { + Ok(InstructionOutcome::Branch(target)) } - fn run_block(&mut self, context: &mut FunctionContext, labels: &HashMap, block_type: BlockType) -> Result { - context.push_frame(labels, BlockFrameType::Block, block_type)?; - Ok(InstructionOutcome::RunNextInstruction) - } - - fn run_loop(&mut self, context: &mut FunctionContext, labels: &HashMap, block_type: BlockType) -> Result { - context.push_frame(labels, BlockFrameType::Loop, block_type)?; - Ok(InstructionOutcome::RunNextInstruction) - } - - fn run_if(&mut self, context: &mut FunctionContext, labels: &HashMap, block_type: BlockType) -> Result { - let condition: bool = context - .value_stack_mut() - .pop_as(); - let block_frame_type = if condition { BlockFrameType::IfTrue } else { - let else_pos = labels[&context.position]; - if !labels.contains_key(&else_pos) { - context.position = else_pos; - return Ok(InstructionOutcome::RunNextInstruction); - } - - context.position = else_pos; - BlockFrameType::IfFalse - }; - context.push_frame(labels, block_frame_type, block_type)?; - - Ok(InstructionOutcome::RunNextInstruction) - } - - fn run_else(&mut self, context: &mut FunctionContext, labels: &HashMap) -> Result { - let end_pos = labels[&context.position]; - context.pop_frame(false)?; - context.position = end_pos; - Ok(InstructionOutcome::RunNextInstruction) - } - - fn run_end(&mut self, context: &mut FunctionContext) -> Result { - context.pop_frame(false)?; - Ok(InstructionOutcome::End) - } - - fn run_br(&mut self, _context: &mut FunctionContext, label_idx: u32) -> Result { - Ok(InstructionOutcome::Branch(label_idx as usize)) - } - - fn run_br_if(&mut self, context: &mut FunctionContext, label_idx: u32) -> Result { - let condition = context.value_stack_mut().pop_as(); + fn run_br_nez(&mut self, target: isa::Target) -> Result { + let condition = self.value_stack.pop_as(); if condition { - Ok(InstructionOutcome::Branch(label_idx as usize)) + Ok(InstructionOutcome::Branch(target)) } else { Ok(InstructionOutcome::RunNextInstruction) } } - fn run_br_table(&mut self, context: &mut FunctionContext, table: &[u32], default: u32) -> Result { - let index: u32 = context.value_stack_mut() - .pop_as(); - Ok(InstructionOutcome::Branch(table.get(index as usize).cloned().unwrap_or(default) as usize)) + fn run_br_eqz(&mut self, target: isa::Target) -> Result { + let condition = self.value_stack.pop_as(); + if condition { + Ok(InstructionOutcome::RunNextInstruction) + } else { + + Ok(InstructionOutcome::Branch(target)) + } } - fn run_return(&mut self, _context: &mut FunctionContext) -> Result { - Ok(InstructionOutcome::Return) + fn run_br_table(&mut self, table: &[isa::Target]) -> Result { + let index: u32 = self.value_stack.pop_as(); + + let dst = if (index as usize) < table.len() - 1 { + table[index as usize].clone() + } else { + table + .last() + .expect("Due to validation there should be at least one label") + .clone() + }; + Ok(InstructionOutcome::Branch(dst)) + } + + fn run_return(&mut self, drop_keep: isa::DropKeep) -> Result { + Ok(InstructionOutcome::Return(drop_keep)) } fn run_call( @@ -452,9 +428,7 @@ impl<'a, E: Externals> Interpreter<'a, E> { context: &mut FunctionContext, signature_idx: u32, ) -> Result { - let table_func_idx: u32 = context - .value_stack_mut() - .pop_as(); + let table_func_idx: u32 = self.value_stack.pop_as(); let table = context .module() .table_by_index(DEFAULT_TABLE_INDEX) @@ -478,46 +452,44 @@ impl<'a, E: Externals> Interpreter<'a, E> { Ok(InstructionOutcome::ExecuteCall(func_ref)) } - fn run_drop(&mut self, context: &mut FunctionContext) -> Result { - let _ = context - .value_stack_mut() - .pop(); + fn run_drop(&mut self) -> Result { + let _ = self.value_stack.pop(); Ok(InstructionOutcome::RunNextInstruction) } - fn run_select(&mut self, context: &mut FunctionContext) -> Result { - let (left, mid, right) = context - .value_stack_mut() + fn run_select(&mut self) -> Result { + let (left, mid, right) = self + .value_stack .pop_triple(); let condition = right .try_into() .expect("Due to validation stack top should be I32"); let val = if condition { left } else { mid }; - context.value_stack_mut().push(val)?; + self.value_stack.push(val)?; Ok(InstructionOutcome::RunNextInstruction) } - fn run_get_local(&mut self, context: &mut FunctionContext, index: u32) -> Result { - let val = context.get_local(index as usize); - context.value_stack_mut().push(val)?; + fn run_get_local(&mut self, index: u32) -> Result { + let val = *self.value_stack.pick_mut(index as usize); + self.value_stack.push(val)?; Ok(InstructionOutcome::RunNextInstruction) } - fn run_set_local(&mut self, context: &mut FunctionContext, index: u32) -> Result { - let arg = context - .value_stack_mut() + fn run_set_local(&mut self, index: u32) -> Result { + let val = self + .value_stack .pop(); - context.set_local(index as usize, arg); + *self.value_stack.pick_mut(index as usize) = val; Ok(InstructionOutcome::RunNextInstruction) } - fn run_tee_local(&mut self, context: &mut FunctionContext, index: u32) -> Result { - let arg = context - .value_stack() + fn run_tee_local(&mut self, index: u32) -> Result { + let val = self + .value_stack .top() .clone(); - context.set_local(index as usize, arg); + *self.value_stack.pick_mut(index as usize) = val; Ok(InstructionOutcome::RunNextInstruction) } @@ -531,7 +503,7 @@ impl<'a, E: Externals> Interpreter<'a, E> { .global_by_index(index) .expect("Due to validation global should exists"); let val = global.get(); - context.value_stack_mut().push(val)?; + self.value_stack.push(val)?; Ok(InstructionOutcome::RunNextInstruction) } @@ -540,8 +512,8 @@ impl<'a, E: Externals> Interpreter<'a, E> { context: &mut FunctionContext, index: u32, ) -> Result { - let val = context - .value_stack_mut() + let val = self + .value_stack .pop(); let global = context .module() @@ -551,60 +523,60 @@ impl<'a, E: Externals> Interpreter<'a, E> { Ok(InstructionOutcome::RunNextInstruction) } - fn run_load(&mut self, context: &mut FunctionContext, _align: u32, offset: u32) -> Result + fn run_load(&mut self, context: &mut FunctionContext, offset: u32) -> Result where RuntimeValue: From, T: LittleEndianConvert { - let raw_address = context - .value_stack_mut() + let raw_address = self + .value_stack .pop_as(); let address = effective_address( offset, raw_address, )?; - let m = context.module() - .memory_by_index(DEFAULT_MEMORY_INDEX) + let m = context + .memory() .expect("Due to validation memory should exists"); let b = m.get(address, mem::size_of::()) .map_err(|_| TrapKind::MemoryAccessOutOfBounds)?; let n = T::from_little_endian(&b) .expect("Can't fail since buffer length should be size_of::"); - context.value_stack_mut().push(n.into())?; + self.value_stack.push(n.into())?; Ok(InstructionOutcome::RunNextInstruction) } - fn run_load_extend(&mut self, context: &mut FunctionContext, _align: u32, offset: u32) -> Result + fn run_load_extend(&mut self, context: &mut FunctionContext, offset: u32) -> Result where T: ExtendInto, RuntimeValue: From, T: LittleEndianConvert { - let raw_address = context - .value_stack_mut() + let raw_address = self + .value_stack .pop_as(); let address = effective_address( offset, raw_address, )?; - let m = context.module() - .memory_by_index(DEFAULT_MEMORY_INDEX) + let m = context + .memory() .expect("Due to validation memory should exists"); let b = m.get(address, mem::size_of::()) .map_err(|_| TrapKind::MemoryAccessOutOfBounds)?; let v = T::from_little_endian(&b) .expect("Can't fail since buffer length should be size_of::"); let stack_value: U = v.extend_into(); - context - .value_stack_mut() + self + .value_stack .push(stack_value.into()) .map_err(Into::into) .map(|_| InstructionOutcome::RunNextInstruction) } - fn run_store(&mut self, context: &mut FunctionContext, _align: u32, offset: u32) -> Result + fn run_store(&mut self, context: &mut FunctionContext, offset: u32) -> Result where T: FromRuntimeValue, T: LittleEndianConvert { - let stack_value = context - .value_stack_mut() + let stack_value = self + .value_stack .pop_as::() .into_little_endian(); - let raw_address = context - .value_stack_mut() + let raw_address = self + .value_stack .pop_as::(); let address = effective_address( @@ -612,8 +584,8 @@ impl<'a, E: Externals> Interpreter<'a, E> { raw_address, )?; - let m = context.module() - .memory_by_index(DEFAULT_MEMORY_INDEX) + let m = context + .memory() .expect("Due to validation memory should exists"); m.set(address, &stack_value) .map_err(|_| TrapKind::MemoryAccessOutOfBounds)?; @@ -623,7 +595,6 @@ impl<'a, E: Externals> Interpreter<'a, E> { fn run_store_wrap( &mut self, context: &mut FunctionContext, - _align: u32, offset: u32, ) -> Result where @@ -631,22 +602,22 @@ impl<'a, E: Externals> Interpreter<'a, E> { T: WrapInto, U: LittleEndianConvert, { - let stack_value: T = context - .value_stack_mut() + let stack_value: T = self + .value_stack .pop() .try_into() .expect("Due to validation value should be of proper type"); let stack_value = stack_value.wrap_into().into_little_endian(); - let raw_address = context - .value_stack_mut() + let raw_address = self + .value_stack .pop_as::(); let address = effective_address( offset, raw_address, )?; - let m = context.module() - .memory_by_index(DEFAULT_MEMORY_INDEX) + let m = context + .memory() .expect("Due to validation memory should exists"); m.set(address, &stack_value) .map_err(|_| TrapKind::MemoryAccessOutOfBounds)?; @@ -654,387 +625,371 @@ impl<'a, E: Externals> Interpreter<'a, E> { } fn run_current_memory(&mut self, context: &mut FunctionContext) -> Result { - let m = context.module() - .memory_by_index(DEFAULT_MEMORY_INDEX) + let m = context + .memory() .expect("Due to validation memory should exists"); let s = m.current_size().0; - context - .value_stack_mut() + self + .value_stack .push(RuntimeValue::I32(s as i32))?; Ok(InstructionOutcome::RunNextInstruction) } fn run_grow_memory(&mut self, context: &mut FunctionContext) -> Result { - let pages: u32 = context - .value_stack_mut() + let pages: u32 = self + .value_stack .pop_as(); - let m = context.module() - .memory_by_index(DEFAULT_MEMORY_INDEX) + let m = context + .memory() .expect("Due to validation memory should exists"); let m = match m.grow(Pages(pages as usize)) { Ok(Pages(new_size)) => new_size as u32, Err(_) => u32::MAX, // Returns -1 (or 0xFFFFFFFF) in case of error. }; - context - .value_stack_mut() + self + .value_stack .push(RuntimeValue::I32(m as i32))?; Ok(InstructionOutcome::RunNextInstruction) } - fn run_const(&mut self, context: &mut FunctionContext, val: RuntimeValue) -> Result { - context - .value_stack_mut() + fn run_const(&mut self, val: RuntimeValue) -> Result { + self + .value_stack .push(val) .map_err(Into::into) .map(|_| InstructionOutcome::RunNextInstruction) } - fn run_relop(&mut self, context: &mut FunctionContext, f: F) -> Result + fn run_relop(&mut self, f: F) -> Result where T: FromRuntimeValue, F: FnOnce(T, T) -> bool, { - let (left, right) = context - .value_stack_mut() - .pop_pair_as::() - .expect("Due to validation stack should contain pair of values"); + let (left, right) = self + .value_stack + .pop_pair_as::(); let v = if f(left, right) { RuntimeValue::I32(1) } else { RuntimeValue::I32(0) }; - context.value_stack_mut().push(v)?; + self.value_stack.push(v)?; Ok(InstructionOutcome::RunNextInstruction) } - fn run_eqz(&mut self, context: &mut FunctionContext) -> Result + fn run_eqz(&mut self) -> Result where T: FromRuntimeValue, T: PartialEq + Default { - let v = context - .value_stack_mut() + let v = self + .value_stack .pop_as::(); let v = RuntimeValue::I32(if v == Default::default() { 1 } else { 0 }); - context.value_stack_mut().push(v)?; + self.value_stack.push(v)?; Ok(InstructionOutcome::RunNextInstruction) } - fn run_eq(&mut self, context: &mut FunctionContext) -> Result + fn run_eq(&mut self) -> Result where T: FromRuntimeValue + PartialEq { - self.run_relop(context, |left: T, right: T| left == right) + self.run_relop(|left: T, right: T| left == right) } - fn run_ne(&mut self, context: &mut FunctionContext) -> Result + fn run_ne(&mut self) -> Result where T: FromRuntimeValue + PartialEq { - self.run_relop(context, |left: T, right: T| left != right) + self.run_relop(|left: T, right: T| left != right) } - fn run_lt(&mut self, context: &mut FunctionContext) -> Result + fn run_lt(&mut self) -> Result where T: FromRuntimeValue + PartialOrd { - self.run_relop(context, |left: T, right: T| left < right) + self.run_relop(|left: T, right: T| left < right) } - fn run_gt(&mut self, context: &mut FunctionContext) -> Result + fn run_gt(&mut self) -> Result where T: FromRuntimeValue + PartialOrd { - self.run_relop(context, |left: T, right: T| left > right) + self.run_relop(|left: T, right: T| left > right) } - fn run_lte(&mut self, context: &mut FunctionContext) -> Result + fn run_lte(&mut self) -> Result where T: FromRuntimeValue + PartialOrd { - self.run_relop(context, |left: T, right: T| left <= right) + self.run_relop(|left: T, right: T| left <= right) } - fn run_gte(&mut self, context: &mut FunctionContext) -> Result + fn run_gte(&mut self) -> Result where T: FromRuntimeValue + PartialOrd { - self.run_relop(context, |left: T, right: T| left >= right) + self.run_relop(|left: T, right: T| left >= right) } - fn run_unop(&mut self, context: &mut FunctionContext, f: F) -> Result + fn run_unop(&mut self, f: F) -> Result where F: FnOnce(T) -> U, T: FromRuntimeValue, RuntimeValue: From { - let v = context - .value_stack_mut() + let v = self + .value_stack .pop_as::(); let v = f(v); - context.value_stack_mut().push(v.into())?; + self.value_stack.push(v.into())?; Ok(InstructionOutcome::RunNextInstruction) } - fn run_clz(&mut self, context: &mut FunctionContext) -> Result + fn run_clz(&mut self) -> Result where RuntimeValue: From, T: Integer + FromRuntimeValue { - self.run_unop(context, |v: T| v.leading_zeros()) + self.run_unop(|v: T| v.leading_zeros()) } - fn run_ctz(&mut self, context: &mut FunctionContext) -> Result + fn run_ctz(&mut self) -> Result where RuntimeValue: From, T: Integer + FromRuntimeValue { - self.run_unop(context, |v: T| v.trailing_zeros()) + self.run_unop(|v: T| v.trailing_zeros()) } - fn run_popcnt(&mut self, context: &mut FunctionContext) -> Result + fn run_popcnt(&mut self) -> Result where RuntimeValue: From, T: Integer + FromRuntimeValue { - self.run_unop(context, |v: T| v.count_ones()) + self.run_unop(|v: T| v.count_ones()) } - fn run_add(&mut self, context: &mut FunctionContext) -> Result + fn run_add(&mut self) -> Result where RuntimeValue: From, T: ArithmeticOps + FromRuntimeValue { - let (left, right) = context - .value_stack_mut() - .pop_pair_as::() - .expect("Due to validation stack should contain pair of values"); + let (left, right) = self + .value_stack + .pop_pair_as::(); let v = left.add(right); - context.value_stack_mut().push(v.into())?; + self.value_stack.push(v.into())?; Ok(InstructionOutcome::RunNextInstruction) } - fn run_sub(&mut self, context: &mut FunctionContext) -> Result + fn run_sub(&mut self) -> Result where RuntimeValue: From, T: ArithmeticOps + FromRuntimeValue { - let (left, right) = context - .value_stack_mut() - .pop_pair_as::() - .expect("Due to validation stack should contain pair of values"); + let (left, right) = self + .value_stack + .pop_pair_as::(); let v = left.sub(right); - context.value_stack_mut().push(v.into())?; + self.value_stack.push(v.into())?; Ok(InstructionOutcome::RunNextInstruction) } - fn run_mul(&mut self, context: &mut FunctionContext) -> Result + fn run_mul(&mut self) -> Result where RuntimeValue: From, T: ArithmeticOps + FromRuntimeValue { - let (left, right) = context - .value_stack_mut() - .pop_pair_as::() - .expect("Due to validation stack should contain pair of values"); + let (left, right) = self + .value_stack + .pop_pair_as::(); let v = left.mul(right); - context.value_stack_mut().push(v.into())?; + self.value_stack.push(v.into())?; Ok(InstructionOutcome::RunNextInstruction) } - fn run_div(&mut self, context: &mut FunctionContext) -> Result + fn run_div(&mut self) -> Result where RuntimeValue: From, T: TransmuteInto + FromRuntimeValue, U: ArithmeticOps + TransmuteInto { - let (left, right) = context - .value_stack_mut() - .pop_pair_as::() - .expect("Due to validation stack should contain pair of values"); + let (left, right) = self + .value_stack + .pop_pair_as::(); let (left, right) = (left.transmute_into(), right.transmute_into()); let v = left.div(right)?; let v = v.transmute_into(); - context.value_stack_mut().push(v.into())?; + self.value_stack.push(v.into())?; Ok(InstructionOutcome::RunNextInstruction) } - fn run_rem(&mut self, context: &mut FunctionContext) -> Result + fn run_rem(&mut self) -> Result where RuntimeValue: From, T: TransmuteInto + FromRuntimeValue, U: Integer + TransmuteInto { - let (left, right) = context - .value_stack_mut() - .pop_pair_as::() - .expect("Due to validation stack should contain pair of values"); + let (left, right) = self + .value_stack + .pop_pair_as::(); let (left, right) = (left.transmute_into(), right.transmute_into()); let v = left.rem(right)?; let v = v.transmute_into(); - context.value_stack_mut().push(v.into())?; + self.value_stack.push(v.into())?; Ok(InstructionOutcome::RunNextInstruction) } - fn run_and(&mut self, context: &mut FunctionContext) -> Result + fn run_and(&mut self) -> Result where RuntimeValue: From<::Output>, T: ops::BitAnd + FromRuntimeValue { - let (left, right) = context - .value_stack_mut() - .pop_pair_as::() - .expect("Due to validation stack should contain pair of values"); + let (left, right) = self + .value_stack + .pop_pair_as::(); let v = left.bitand(right); - context.value_stack_mut().push(v.into())?; + self.value_stack.push(v.into())?; Ok(InstructionOutcome::RunNextInstruction) } - fn run_or(&mut self, context: &mut FunctionContext) -> Result + fn run_or(&mut self) -> Result where RuntimeValue: From<::Output>, T: ops::BitOr + FromRuntimeValue { - let (left, right) = context - .value_stack_mut() - .pop_pair_as::() - .expect("Due to validation stack should contain pair of values"); + let (left, right) = self + .value_stack + .pop_pair_as::(); let v = left.bitor(right); - context.value_stack_mut().push(v.into())?; + self.value_stack.push(v.into())?; Ok(InstructionOutcome::RunNextInstruction) } - fn run_xor(&mut self, context: &mut FunctionContext) -> Result + fn run_xor(&mut self) -> Result where RuntimeValue: From<::Output>, T: ops::BitXor + FromRuntimeValue { - let (left, right) = context - .value_stack_mut() - .pop_pair_as::() - .expect("Due to validation stack should contain pair of values"); + let (left, right) = self + .value_stack + .pop_pair_as::(); let v = left.bitxor(right); - context.value_stack_mut().push(v.into())?; + self.value_stack.push(v.into())?; Ok(InstructionOutcome::RunNextInstruction) } - fn run_shl(&mut self, context: &mut FunctionContext, mask: T) -> Result + fn run_shl(&mut self, mask: T) -> Result where RuntimeValue: From<>::Output>, T: ops::Shl + ops::BitAnd + FromRuntimeValue { - let (left, right) = context - .value_stack_mut() - .pop_pair_as::() - .expect("Due to validation stack should contain pair of values"); + let (left, right) = self + .value_stack + .pop_pair_as::(); let v = left.shl(right & mask); - context.value_stack_mut().push(v.into())?; + self.value_stack.push(v.into())?; Ok(InstructionOutcome::RunNextInstruction) } - fn run_shr(&mut self, context: &mut FunctionContext, mask: U) -> Result + fn run_shr(&mut self, mask: U) -> Result where RuntimeValue: From, T: TransmuteInto + FromRuntimeValue, U: ops::Shr + ops::BitAnd, >::Output: TransmuteInto { - let (left, right) = context - .value_stack_mut() - .pop_pair_as::() - .expect("Due to validation stack should contain pair of values"); + let (left, right) = self + .value_stack + .pop_pair_as::(); let (left, right) = (left.transmute_into(), right.transmute_into()); let v = left.shr(right & mask); let v = v.transmute_into(); - context.value_stack_mut().push(v.into())?; + self.value_stack.push(v.into())?; Ok(InstructionOutcome::RunNextInstruction) } - fn run_rotl(&mut self, context: &mut FunctionContext) -> Result + fn run_rotl(&mut self) -> Result where RuntimeValue: From, T: Integer + FromRuntimeValue { - let (left, right) = context - .value_stack_mut() - .pop_pair_as::() - .expect("Due to validation stack should contain pair of values"); + let (left, right) = self + .value_stack + .pop_pair_as::(); let v = left.rotl(right); - context.value_stack_mut().push(v.into())?; + self.value_stack.push(v.into())?; Ok(InstructionOutcome::RunNextInstruction) } - fn run_rotr(&mut self, context: &mut FunctionContext) -> Result + fn run_rotr(&mut self) -> Result where RuntimeValue: From, T: Integer + FromRuntimeValue { - let (left, right) = context - .value_stack_mut() - .pop_pair_as::() - .expect("Due to validation stack should contain pair of values"); + let (left, right) = self + .value_stack + .pop_pair_as::(); let v = left.rotr(right); - context.value_stack_mut().push(v.into())?; + self.value_stack.push(v.into())?; Ok(InstructionOutcome::RunNextInstruction) } - fn run_abs(&mut self, context: &mut FunctionContext) -> Result + fn run_abs(&mut self) -> Result where RuntimeValue: From, T: Float + FromRuntimeValue { - self.run_unop(context, |v: T| v.abs()) + self.run_unop(|v: T| v.abs()) } - fn run_neg(&mut self, context: &mut FunctionContext) -> Result + fn run_neg(&mut self) -> Result where RuntimeValue: From<::Output>, T: ops::Neg + FromRuntimeValue { - self.run_unop(context, |v: T| v.neg()) + self.run_unop(|v: T| v.neg()) } - fn run_ceil(&mut self, context: &mut FunctionContext) -> Result + fn run_ceil(&mut self) -> Result where RuntimeValue: From, T: Float + FromRuntimeValue { - self.run_unop(context, |v: T| v.ceil()) + self.run_unop(|v: T| v.ceil()) } - fn run_floor(&mut self, context: &mut FunctionContext) -> Result + fn run_floor(&mut self) -> Result where RuntimeValue: From, T: Float + FromRuntimeValue { - self.run_unop(context, |v: T| v.floor()) + self.run_unop(|v: T| v.floor()) } - fn run_trunc(&mut self, context: &mut FunctionContext) -> Result + fn run_trunc(&mut self) -> Result where RuntimeValue: From, T: Float + FromRuntimeValue { - self.run_unop(context, |v: T| v.trunc()) + self.run_unop(|v: T| v.trunc()) } - fn run_nearest(&mut self, context: &mut FunctionContext) -> Result + fn run_nearest(&mut self) -> Result where RuntimeValue: From, T: Float + FromRuntimeValue { - self.run_unop(context, |v: T| v.nearest()) + self.run_unop(|v: T| v.nearest()) } - fn run_sqrt(&mut self, context: &mut FunctionContext) -> Result + fn run_sqrt(&mut self) -> Result where RuntimeValue: From, T: Float + FromRuntimeValue { - self.run_unop(context, |v: T| v.sqrt()) + self.run_unop(|v: T| v.sqrt()) } - fn run_min(&mut self, context: &mut FunctionContext) -> Result + fn run_min(&mut self) -> Result where RuntimeValue: From, T: Float + FromRuntimeValue { - let (left, right) = context - .value_stack_mut() - .pop_pair_as::() - .expect("Due to validation stack should contain pair of values"); + let (left, right) = self + .value_stack + .pop_pair_as::(); let v = left.min(right); - context.value_stack_mut().push(v.into())?; + self.value_stack.push(v.into())?; Ok(InstructionOutcome::RunNextInstruction) } - fn run_max(&mut self, context: &mut FunctionContext) -> Result + fn run_max(&mut self) -> Result where RuntimeValue: From, T: Float + FromRuntimeValue { - let (left, right) = context - .value_stack_mut() - .pop_pair_as::() - .expect("Due to validation stack should contain pair of values"); + let (left, right) = self + .value_stack + .pop_pair_as::(); let v = left.max(right); - context.value_stack_mut().push(v.into())?; + self.value_stack.push(v.into())?; Ok(InstructionOutcome::RunNextInstruction) } - fn run_copysign(&mut self, context: &mut FunctionContext) -> Result + fn run_copysign(&mut self) -> Result where RuntimeValue: From, T: Float + FromRuntimeValue { - let (left, right) = context - .value_stack_mut() - .pop_pair_as::() - .expect("Due to validation stack should contain pair of values"); + let (left, right) = self + .value_stack + .pop_pair_as::(); let v = left.copysign(right); - context.value_stack_mut().push(v.into())?; + self.value_stack.push(v.into())?; Ok(InstructionOutcome::RunNextInstruction) } - fn run_wrap(&mut self, context: &mut FunctionContext) -> Result + fn run_wrap(&mut self) -> Result where RuntimeValue: From, T: WrapInto + FromRuntimeValue { - self.run_unop(context, |v: T| v.wrap_into()) + self.run_unop(|v: T| v.wrap_into()) } - fn run_trunc_to_int(&mut self, context: &mut FunctionContext) -> Result + fn run_trunc_to_int(&mut self) -> Result where RuntimeValue: From, T: TryTruncateInto + FromRuntimeValue, U: TransmuteInto, { - let v = context - .value_stack_mut() + let v = self + .value_stack .pop_as::(); v.try_truncate_into() .map(|v| v.transmute_into()) - .map(|v| context.value_stack_mut().push(v.into())) + .map(|v| self.value_stack.push(v.into())) .map(|_| InstructionOutcome::RunNextInstruction) } - fn run_extend(&mut self, context: &mut FunctionContext) -> Result + fn run_extend(&mut self) -> Result where RuntimeValue: From, T: ExtendInto + FromRuntimeValue, U: TransmuteInto { - let v = context - .value_stack_mut() + let v = self + .value_stack .pop_as::(); let v = v.extend_into().transmute_into(); - context.value_stack_mut().push(v.into())?; + self.value_stack.push(v.into())?; Ok(InstructionOutcome::RunNextInstruction) } - fn run_reinterpret(&mut self, context: &mut FunctionContext) -> Result + fn run_reinterpret(&mut self) -> Result where RuntimeValue: From, T: FromRuntimeValue, T: TransmuteInto { - let v = context - .value_stack_mut() + let v = self + .value_stack .pop_as::(); let v = v.transmute_into(); - context.value_stack_mut().push(v.into())?; + self.value_stack.push(v.into())?; Ok(InstructionOutcome::RunNextInstruction) } @@ -1047,157 +1002,56 @@ struct FunctionContext { /// Internal function reference. pub function: FuncRef, pub module: ModuleRef, - /// Function return type. - pub return_type: BlockType, - /// Local variables. - pub locals: Vec, - /// Values stack. - pub value_stack: ValueStack, - /// Blocks frames stack. - pub frame_stack: StackWithLimit, + pub memory: Option, /// Current instruction position. pub position: usize, } impl FunctionContext { - pub fn new(function: FuncRef, value_stack_limit: usize, frame_stack_limit: usize, signature: &Signature, args: Vec) -> Self { + pub fn new(function: FuncRef) -> Self { let module = match *function.as_internal() { FuncInstanceInternal::Internal { ref module, .. } => module.upgrade().expect("module deallocated"), FuncInstanceInternal::Host { .. } => panic!("Host functions can't be called as internally defined functions; Thus FunctionContext can be created only with internally defined functions; qed"), }; + let memory = module.memory_by_index(DEFAULT_MEMORY_INDEX); FunctionContext { is_initialized: false, function: function, module: ModuleRef(module), - return_type: signature.return_type().map(|vt| BlockType::Value(vt.into_elements())).unwrap_or(BlockType::NoResult), - value_stack: ValueStack::with_limit(value_stack_limit), - frame_stack: StackWithLimit::with_limit(frame_stack_limit), - locals: args, + memory: memory, position: 0, } } - pub fn nested(&mut self, function: FuncRef) -> Result { - let (function_locals, module, function_return_type) = { - let module = match *function.as_internal() { - FuncInstanceInternal::Internal { ref module, .. } => module.upgrade().expect("module deallocated"), - FuncInstanceInternal::Host { .. } => panic!("Host functions can't be called as internally defined functions; Thus FunctionContext can be created only with internally defined functions; qed"), - }; - let function_type = function.signature(); - let function_return_type = function_type.return_type().map(|vt| BlockType::Value(vt.into_elements())).unwrap_or(BlockType::NoResult); - let function_locals = prepare_function_args(function_type, &mut self.value_stack); - (function_locals, module, function_return_type) - }; - - Ok(FunctionContext { - is_initialized: false, - function: function, - module: ModuleRef(module), - return_type: function_return_type, - value_stack: ValueStack::with_limit(self.value_stack.limit() - self.value_stack.len()), - frame_stack: StackWithLimit::with_limit(self.frame_stack.limit() - self.frame_stack.len()), - locals: function_locals, - position: 0, - }) - } - pub fn is_initialized(&self) -> bool { self.is_initialized } - pub fn initialize(&mut self, locals: &[Local]) { + pub fn initialize(&mut self, locals: &[Local], value_stack: &mut ValueStack) -> Result<(), TrapKind> { debug_assert!(!self.is_initialized); - self.is_initialized = true; let locals = locals.iter() .flat_map(|l| repeat(l.value_type()).take(l.count() as usize)) .map(::types::ValueType::from_elements) .map(RuntimeValue::default) .collect::>(); - self.locals.extend(locals); + + // TODO: Replace with extend. + for local in locals { + value_stack.push(local) + .map_err(|_| TrapKind::StackOverflow)?; + } + + self.is_initialized = true; + Ok(()) } pub fn module(&self) -> ModuleRef { self.module.clone() } - pub fn set_local(&mut self, index: usize, value: RuntimeValue) { - let l = self.locals.get_mut(index).expect("Due to validation local should exists"); - *l = value; - } - - pub fn get_local(&mut self, index: usize) -> RuntimeValue { - self.locals.get(index) - .cloned() - .expect("Due to validation local should exists") - } - - pub fn value_stack(&self) -> &ValueStack { - &self.value_stack - } - - pub fn value_stack_mut(&mut self) -> &mut ValueStack { - &mut self.value_stack - } - - pub fn frame_stack(&self) -> &StackWithLimit { - &self.frame_stack - } - - pub fn push_frame(&mut self, labels: &HashMap, frame_type: BlockFrameType, block_type: BlockType) -> Result<(), TrapKind> { - let begin_position = self.position; - let branch_position = match frame_type { - BlockFrameType::Function => usize::MAX, - BlockFrameType::Loop => begin_position, - BlockFrameType::IfTrue => { - let else_pos = labels[&begin_position]; - 1usize + match labels.get(&else_pos) { - Some(end_pos) => *end_pos, - None => else_pos, - } - }, - _ => labels[&begin_position] + 1, - }; - let end_position = match frame_type { - BlockFrameType::Function => usize::MAX, - _ => labels[&begin_position] + 1, - }; - - self.frame_stack.push(BlockFrame { - frame_type: frame_type, - block_type: block_type, - begin_position: begin_position, - branch_position: branch_position, - end_position: end_position, - value_stack_len: self.value_stack.len(), - polymorphic_stack: false, - }).map_err(|_| TrapKind::StackOverflow)?; - - Ok(()) - } - - pub fn discard_frame(&mut self) { - let _ = self.frame_stack.pop().expect("Due to validation frame stack shouldn't be empty"); - } - - pub fn pop_frame(&mut self, is_branch: bool) -> Result<(), TrapKind> { - let frame = self.frame_stack - .pop() - .expect("Due to validation frame stack shouldn't be empty"); - assert!(frame.value_stack_len <= self.value_stack.len(), "invalid stack len"); - - let frame_value = match frame.block_type { - BlockType::Value(_) if frame.frame_type != BlockFrameType::Loop || !is_branch => - Some(self.value_stack.pop()), - _ => None, - }; - self.value_stack.resize(frame.value_stack_len); - self.position = if is_branch { frame.branch_position } else { frame.end_position }; - if let Some(frame_value) = frame_value { - self.value_stack.push(frame_value)?; - } - - Ok(()) + pub fn memory(&self) -> Option<&MemoryRef> { + self.memory.as_ref() } } @@ -1252,65 +1106,92 @@ pub fn check_function_args(signature: &Signature, args: &[RuntimeValue]) -> Resu Ok(()) } +#[derive(Debug)] struct ValueStack { - stack_with_limit: StackWithLimit, + buf: Box<[RuntimeValue]>, + /// Index of the first free place in the stack. + sp: usize, } impl ValueStack { fn with_limit(limit: usize) -> ValueStack { + let mut buf = Vec::new(); + buf.resize(limit, RuntimeValue::I32(0)); + ValueStack { - stack_with_limit: StackWithLimit::with_limit(limit), + buf: buf.into_boxed_slice(), + sp: 0, } } + #[inline] + fn drop_keep(&mut self, drop_keep: isa::DropKeep) { + if drop_keep.keep == isa::Keep::Single { + let top = *self.top(); + *self.pick_mut(drop_keep.drop as usize + 1) = top; + } + + let cur_stack_len = self.len(); + self.sp = cur_stack_len - drop_keep.drop as usize; + } + + #[inline] fn pop_as(&mut self) -> T where T: FromRuntimeValue, { - let value = self.stack_with_limit - .pop() - .expect("Due to validation stack shouldn't be empty"); + let value = self.pop(); value.try_into().expect("Due to validation stack top's type should match") } - fn pop_pair_as(&mut self) -> Result<(T, T), Error> + #[inline] + fn pop_pair_as(&mut self) -> (T, T) where T: FromRuntimeValue, { let right = self.pop_as(); let left = self.pop_as(); - Ok((left, right)) + (left, right) } + #[inline] fn pop_triple(&mut self) -> (RuntimeValue, RuntimeValue, RuntimeValue) { - let right = self.stack_with_limit.pop().expect("Due to validation stack shouldn't be empty"); - let mid = self.stack_with_limit.pop().expect("Due to validation stack shouldn't be empty"); - let left = self.stack_with_limit.pop().expect("Due to validation stack shouldn't be empty"); + let right = self.pop(); + let mid = self.pop(); + let left = self.pop(); (left, mid, right) } - fn pop(&mut self) -> RuntimeValue { - self.stack_with_limit.pop().expect("Due to validation stack shouldn't be empty") - } - - fn push(&mut self, value: RuntimeValue) -> Result<(), TrapKind> { - self.stack_with_limit.push(value) - .map_err(|_| TrapKind::StackOverflow) - } - - fn resize(&mut self, new_len: usize) { - self.stack_with_limit.resize(new_len, RuntimeValue::I32(0)); - } - - fn len(&self) -> usize { - self.stack_with_limit.len() - } - - fn limit(&self) -> usize { - self.stack_with_limit.limit() - } - + #[inline] fn top(&self) -> &RuntimeValue { - self.stack_with_limit.top().expect("Due to validation stack shouldn't be empty") + self.pick(1) + } + + fn pick(&self, depth: usize) -> &RuntimeValue { + &self.buf[self.sp - depth] + } + + #[inline] + fn pick_mut(&mut self, depth: usize) -> &mut RuntimeValue { + &mut self.buf[self.sp - depth] + } + + #[inline] + fn pop(&mut self) -> RuntimeValue { + self.sp -= 1; + self.buf[self.sp] + } + + #[inline] + fn push(&mut self, value: RuntimeValue) -> Result<(), TrapKind> { + let cell = self.buf.get_mut(self.sp).ok_or_else(|| TrapKind::StackOverflow)?; + *cell = value; + self.sp += 1; + Ok(()) + } + + #[inline] + fn len(&self) -> usize { + self.sp } } diff --git a/src/validation/func.rs b/src/validation/func.rs index e004bb0..7107e05 100644 --- a/src/validation/func.rs +++ b/src/validation/func.rs @@ -1,5 +1,4 @@ use std::u32; -use std::collections::HashMap; use parity_wasm::elements::{Instruction, BlockType, ValueType, TableElementType, Func, FuncBody}; use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX}; use validation::context::ModuleContext; @@ -8,29 +7,90 @@ use validation::Error; use validation::util::Locals; use common::stack::StackWithLimit; -use common::{BlockFrame, BlockFrameType}; +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; -/// 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. None if validating expression. - return_type: Option, - /// Labels positions. - labels: HashMap, +/// 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. @@ -42,669 +102,6 @@ enum StackValueType { Specific(ValueType), } -/// Function validator. -pub struct Validator; - -/// Instruction outcome. -#[derive(Debug, Clone)] -enum InstructionOutcome { - /// Continue with next instruction. - ValidateNextInstruction, - /// Unreachable instruction reached. - Unreachable, -} - -impl Validator { - pub fn validate_function( - module: &ModuleContext, - func: &Func, - body: &FuncBody, - ) -> Result, Error> { - let (params, result_ty) = module.require_function_type(func.type_ref())?; - - let mut context = FunctionValidationContext::new( - &module, - Locals::new(params, body.locals()), - DEFAULT_VALUE_STACK_LIMIT, - DEFAULT_FRAME_STACK_LIMIT, - result_ty, - ); - - context.push_label(BlockFrameType::Function, result_ty)?; - Validator::validate_function_block(&mut context, body.code().elements())?; - while !context.frame_stack.is_empty() { - context.pop_label()?; - } - - Ok(context.into_labels()) - } - - fn validate_function_block(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 opcode = &body[context.position]; - match Validator::validate_instruction(context, opcode)? { - InstructionOutcome::ValidateNextInstruction => (), - InstructionOutcome::Unreachable => context.unreachable()?, - } - - context.position += 1; - if context.position == body_len { - return Ok(()); - } - } - } - - fn validate_instruction(context: &mut FunctionValidationContext, instruction: &Instruction) -> Result { - use self::Instruction::*; - match *instruction { - Unreachable => Ok(InstructionOutcome::Unreachable), - Nop => Ok(InstructionOutcome::ValidateNextInstruction), - Block(block_type) => Validator::validate_block(context, block_type), - Loop(block_type) => Validator::validate_loop(context, block_type), - If(block_type) => Validator::validate_if(context, block_type), - Else => Validator::validate_else(context), - End => Validator::validate_end(context), - Br(idx) => Validator::validate_br(context, idx), - BrIf(idx) => Validator::validate_br_if(context, idx), - BrTable(ref table, default) => Validator::validate_br_table(context, table, default), - Return => Validator::validate_return(context), - - Call(index) => Validator::validate_call(context, index), - CallIndirect(index, _reserved) => Validator::validate_call_indirect(context, index), - - Drop => Validator::validate_drop(context), - Select => Validator::validate_select(context), - - GetLocal(index) => Validator::validate_get_local(context, index), - SetLocal(index) => Validator::validate_set_local(context, index), - TeeLocal(index) => Validator::validate_tee_local(context, index), - GetGlobal(index) => Validator::validate_get_global(context, index), - SetGlobal(index) => Validator::validate_set_global(context, index), - - I32Load(align, _) => Validator::validate_load(context, align, 4, ValueType::I32), - I64Load(align, _) => Validator::validate_load(context, align, 8, ValueType::I64), - F32Load(align, _) => Validator::validate_load(context, align, 4, ValueType::F32), - F64Load(align, _) => Validator::validate_load(context, align, 8, ValueType::F64), - I32Load8S(align, _) => Validator::validate_load(context, align, 1, ValueType::I32), - I32Load8U(align, _) => Validator::validate_load(context, align, 1, ValueType::I32), - I32Load16S(align, _) => Validator::validate_load(context, align, 2, ValueType::I32), - I32Load16U(align, _) => Validator::validate_load(context, align, 2, ValueType::I32), - I64Load8S(align, _) => Validator::validate_load(context, align, 1, ValueType::I64), - I64Load8U(align, _) => Validator::validate_load(context, align, 1, ValueType::I64), - I64Load16S(align, _) => Validator::validate_load(context, align, 2, ValueType::I64), - I64Load16U(align, _) => Validator::validate_load(context, align, 2, ValueType::I64), - I64Load32S(align, _) => Validator::validate_load(context, align, 4, ValueType::I64), - I64Load32U(align, _) => Validator::validate_load(context, align, 4, ValueType::I64), - - I32Store(align, _) => Validator::validate_store(context, align, 4, ValueType::I32), - I64Store(align, _) => Validator::validate_store(context, align, 8, ValueType::I64), - F32Store(align, _) => Validator::validate_store(context, align, 4, ValueType::F32), - F64Store(align, _) => Validator::validate_store(context, align, 8, ValueType::F64), - I32Store8(align, _) => Validator::validate_store(context, align, 1, ValueType::I32), - I32Store16(align, _) => Validator::validate_store(context, align, 2, ValueType::I32), - I64Store8(align, _) => Validator::validate_store(context, align, 1, ValueType::I64), - I64Store16(align, _) => Validator::validate_store(context, align, 2, ValueType::I64), - I64Store32(align, _) => Validator::validate_store(context, align, 4, ValueType::I64), - - CurrentMemory(_) => Validator::validate_current_memory(context), - GrowMemory(_) => Validator::validate_grow_memory(context), - - I32Const(_) => Validator::validate_const(context, ValueType::I32), - I64Const(_) => Validator::validate_const(context, ValueType::I64), - F32Const(_) => Validator::validate_const(context, ValueType::F32), - F64Const(_) => Validator::validate_const(context, ValueType::F64), - - I32Eqz => Validator::validate_testop(context, ValueType::I32), - I32Eq => Validator::validate_relop(context, ValueType::I32), - I32Ne => Validator::validate_relop(context, ValueType::I32), - I32LtS => Validator::validate_relop(context, ValueType::I32), - I32LtU => Validator::validate_relop(context, ValueType::I32), - I32GtS => Validator::validate_relop(context, ValueType::I32), - I32GtU => Validator::validate_relop(context, ValueType::I32), - I32LeS => Validator::validate_relop(context, ValueType::I32), - I32LeU => Validator::validate_relop(context, ValueType::I32), - I32GeS => Validator::validate_relop(context, ValueType::I32), - I32GeU => Validator::validate_relop(context, ValueType::I32), - - I64Eqz => Validator::validate_testop(context, ValueType::I64), - I64Eq => Validator::validate_relop(context, ValueType::I64), - I64Ne => Validator::validate_relop(context, ValueType::I64), - I64LtS => Validator::validate_relop(context, ValueType::I64), - I64LtU => Validator::validate_relop(context, ValueType::I64), - I64GtS => Validator::validate_relop(context, ValueType::I64), - I64GtU => Validator::validate_relop(context, ValueType::I64), - I64LeS => Validator::validate_relop(context, ValueType::I64), - I64LeU => Validator::validate_relop(context, ValueType::I64), - I64GeS => Validator::validate_relop(context, ValueType::I64), - I64GeU => Validator::validate_relop(context, ValueType::I64), - - F32Eq => Validator::validate_relop(context, ValueType::F32), - F32Ne => Validator::validate_relop(context, ValueType::F32), - F32Lt => Validator::validate_relop(context, ValueType::F32), - F32Gt => Validator::validate_relop(context, ValueType::F32), - F32Le => Validator::validate_relop(context, ValueType::F32), - F32Ge => Validator::validate_relop(context, ValueType::F32), - - F64Eq => Validator::validate_relop(context, ValueType::F64), - F64Ne => Validator::validate_relop(context, ValueType::F64), - F64Lt => Validator::validate_relop(context, ValueType::F64), - F64Gt => Validator::validate_relop(context, ValueType::F64), - F64Le => Validator::validate_relop(context, ValueType::F64), - F64Ge => Validator::validate_relop(context, ValueType::F64), - - I32Clz => Validator::validate_unop(context, ValueType::I32), - I32Ctz => Validator::validate_unop(context, ValueType::I32), - I32Popcnt => Validator::validate_unop(context, ValueType::I32), - I32Add => Validator::validate_binop(context, ValueType::I32), - I32Sub => Validator::validate_binop(context, ValueType::I32), - I32Mul => Validator::validate_binop(context, ValueType::I32), - I32DivS => Validator::validate_binop(context, ValueType::I32), - I32DivU => Validator::validate_binop(context, ValueType::I32), - I32RemS => Validator::validate_binop(context, ValueType::I32), - I32RemU => Validator::validate_binop(context, ValueType::I32), - I32And => Validator::validate_binop(context, ValueType::I32), - I32Or => Validator::validate_binop(context, ValueType::I32), - I32Xor => Validator::validate_binop(context, ValueType::I32), - I32Shl => Validator::validate_binop(context, ValueType::I32), - I32ShrS => Validator::validate_binop(context, ValueType::I32), - I32ShrU => Validator::validate_binop(context, ValueType::I32), - I32Rotl => Validator::validate_binop(context, ValueType::I32), - I32Rotr => Validator::validate_binop(context, ValueType::I32), - - I64Clz => Validator::validate_unop(context, ValueType::I64), - I64Ctz => Validator::validate_unop(context, ValueType::I64), - I64Popcnt => Validator::validate_unop(context, ValueType::I64), - I64Add => Validator::validate_binop(context, ValueType::I64), - I64Sub => Validator::validate_binop(context, ValueType::I64), - I64Mul => Validator::validate_binop(context, ValueType::I64), - I64DivS => Validator::validate_binop(context, ValueType::I64), - I64DivU => Validator::validate_binop(context, ValueType::I64), - I64RemS => Validator::validate_binop(context, ValueType::I64), - I64RemU => Validator::validate_binop(context, ValueType::I64), - I64And => Validator::validate_binop(context, ValueType::I64), - I64Or => Validator::validate_binop(context, ValueType::I64), - I64Xor => Validator::validate_binop(context, ValueType::I64), - I64Shl => Validator::validate_binop(context, ValueType::I64), - I64ShrS => Validator::validate_binop(context, ValueType::I64), - I64ShrU => Validator::validate_binop(context, ValueType::I64), - I64Rotl => Validator::validate_binop(context, ValueType::I64), - I64Rotr => Validator::validate_binop(context, ValueType::I64), - - F32Abs => Validator::validate_unop(context, ValueType::F32), - F32Neg => Validator::validate_unop(context, ValueType::F32), - F32Ceil => Validator::validate_unop(context, ValueType::F32), - F32Floor => Validator::validate_unop(context, ValueType::F32), - F32Trunc => Validator::validate_unop(context, ValueType::F32), - F32Nearest => Validator::validate_unop(context, ValueType::F32), - F32Sqrt => Validator::validate_unop(context, ValueType::F32), - F32Add => Validator::validate_binop(context, ValueType::F32), - F32Sub => Validator::validate_binop(context, ValueType::F32), - F32Mul => Validator::validate_binop(context, ValueType::F32), - F32Div => Validator::validate_binop(context, ValueType::F32), - F32Min => Validator::validate_binop(context, ValueType::F32), - F32Max => Validator::validate_binop(context, ValueType::F32), - F32Copysign => Validator::validate_binop(context, ValueType::F32), - - F64Abs => Validator::validate_unop(context, ValueType::F64), - F64Neg => Validator::validate_unop(context, ValueType::F64), - F64Ceil => Validator::validate_unop(context, ValueType::F64), - F64Floor => Validator::validate_unop(context, ValueType::F64), - F64Trunc => Validator::validate_unop(context, ValueType::F64), - F64Nearest => Validator::validate_unop(context, ValueType::F64), - F64Sqrt => Validator::validate_unop(context, ValueType::F64), - F64Add => Validator::validate_binop(context, ValueType::F64), - F64Sub => Validator::validate_binop(context, ValueType::F64), - F64Mul => Validator::validate_binop(context, ValueType::F64), - F64Div => Validator::validate_binop(context, ValueType::F64), - F64Min => Validator::validate_binop(context, ValueType::F64), - F64Max => Validator::validate_binop(context, ValueType::F64), - F64Copysign => Validator::validate_binop(context, ValueType::F64), - - I32WrapI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::I32), - I32TruncSF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I32), - I32TruncUF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I32), - I32TruncSF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I32), - I32TruncUF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I32), - I64ExtendSI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::I64), - I64ExtendUI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::I64), - I64TruncSF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I64), - I64TruncUF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I64), - I64TruncSF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I64), - I64TruncUF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I64), - F32ConvertSI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F32), - F32ConvertUI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F32), - F32ConvertSI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F32), - F32ConvertUI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F32), - F32DemoteF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::F32), - F64ConvertSI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F64), - F64ConvertUI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F64), - F64ConvertSI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F64), - F64ConvertUI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F64), - F64PromoteF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::F64), - - I32ReinterpretF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I32), - I64ReinterpretF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I64), - F32ReinterpretI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F32), - F64ReinterpretI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F64), - } - } - - fn validate_const(context: &mut FunctionValidationContext, value_type: ValueType) -> Result { - context.push_value(value_type.into())?; - Ok(InstructionOutcome::ValidateNextInstruction) - } - - fn validate_unop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result { - context.pop_value(value_type.into())?; - context.push_value(value_type.into())?; - Ok(InstructionOutcome::ValidateNextInstruction) - } - - fn validate_binop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result { - context.pop_value(value_type.into())?; - context.pop_value(value_type.into())?; - context.push_value(value_type.into())?; - Ok(InstructionOutcome::ValidateNextInstruction) - } - - fn validate_testop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result { - context.pop_value(value_type.into())?; - context.push_value(ValueType::I32.into())?; - Ok(InstructionOutcome::ValidateNextInstruction) - } - - fn validate_relop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result { - context.pop_value(value_type.into())?; - context.pop_value(value_type.into())?; - context.push_value(ValueType::I32.into())?; - Ok(InstructionOutcome::ValidateNextInstruction) - } - - fn validate_cvtop(context: &mut FunctionValidationContext, value_type1: ValueType, value_type2: ValueType) -> Result { - context.pop_value(value_type1.into())?; - context.push_value(value_type2.into())?; - Ok(InstructionOutcome::ValidateNextInstruction) - } - - fn validate_drop(context: &mut FunctionValidationContext) -> Result { - context.pop_value(StackValueType::Any).map(|_| ())?; - Ok(InstructionOutcome::ValidateNextInstruction) - } - - fn validate_select(context: &mut FunctionValidationContext) -> Result { - context.pop_value(ValueType::I32.into())?; - let select_type = context.pop_value(StackValueType::Any)?; - context.pop_value(select_type)?; - context.push_value(select_type)?; - Ok(InstructionOutcome::ValidateNextInstruction) - } - - fn validate_get_local(context: &mut FunctionValidationContext, index: u32) -> Result { - let local_type = context.require_local(index)?; - context.push_value(local_type)?; - Ok(InstructionOutcome::ValidateNextInstruction) - } - - fn validate_set_local(context: &mut FunctionValidationContext, index: u32) -> Result { - let local_type = context.require_local(index)?; - let value_type = context.pop_value(StackValueType::Any)?; - if local_type != value_type { - return Err(Error(format!("Trying to update local {} of type {:?} with value of type {:?}", index, local_type, value_type))); - } - Ok(InstructionOutcome::ValidateNextInstruction) - } - - fn validate_tee_local(context: &mut FunctionValidationContext, index: u32) -> Result { - let local_type = context.require_local(index)?; - context.tee_value(local_type)?; - Ok(InstructionOutcome::ValidateNextInstruction) - } - - fn validate_get_global(context: &mut FunctionValidationContext, index: u32) -> Result { - let global_type: StackValueType = { - let global = context.module.require_global(index, None)?; - global.content_type().into() - }; - context.push_value(global_type)?; - Ok(InstructionOutcome::ValidateNextInstruction) - } - - fn validate_set_global(context: &mut FunctionValidationContext, index: u32) -> Result { - let global_type: StackValueType = { - let global = context.module.require_global(index, Some(true))?; - global.content_type().into() - }; - let value_type = context.pop_value(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(InstructionOutcome::ValidateNextInstruction) - } - - fn validate_load(context: &mut FunctionValidationContext, align: u32, max_align: u32, value_type: ValueType) -> Result { - 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.pop_value(ValueType::I32.into())?; - context.module.require_memory(DEFAULT_MEMORY_INDEX)?; - context.push_value(value_type.into())?; - Ok(InstructionOutcome::ValidateNextInstruction) - } - - fn validate_store(context: &mut FunctionValidationContext, align: u32, max_align: u32, value_type: ValueType) -> Result { - 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)?; - context.pop_value(value_type.into())?; - context.pop_value(ValueType::I32.into())?; - Ok(InstructionOutcome::ValidateNextInstruction) - } - - fn validate_block(context: &mut FunctionValidationContext, block_type: BlockType) -> Result { - context.push_label(BlockFrameType::Block, block_type).map(|_| InstructionOutcome::ValidateNextInstruction) - } - - fn validate_loop(context: &mut FunctionValidationContext, block_type: BlockType) -> Result { - context.push_label(BlockFrameType::Loop, block_type).map(|_| InstructionOutcome::ValidateNextInstruction) - } - - fn validate_if(context: &mut FunctionValidationContext, block_type: BlockType) -> Result { - context.pop_value(ValueType::I32.into())?; - context.push_label(BlockFrameType::IfTrue, block_type).map(|_| InstructionOutcome::ValidateNextInstruction) - } - - fn validate_else(context: &mut FunctionValidationContext) -> Result { - let block_type = { - let top_frame = context.top_label()?; - if top_frame.frame_type != BlockFrameType::IfTrue { - return Err(Error("Misplaced else instruction".into())); - } - top_frame.block_type - }; - context.pop_label()?; - - if let BlockType::Value(value_type) = block_type { - context.pop_value(value_type.into())?; - } - context.push_label(BlockFrameType::IfFalse, block_type).map(|_| InstructionOutcome::ValidateNextInstruction) - } - - fn validate_end(context: &mut FunctionValidationContext) -> Result { - { - let top_frame = context.top_label()?; - if top_frame.frame_type == BlockFrameType::IfTrue { - if top_frame.block_type != BlockType::NoResult { - return Err(Error(format!("If block without else required to have NoResult block type. But it have {:?} type", top_frame.block_type))); - } - } - } - - context.pop_label().map(|_| InstructionOutcome::ValidateNextInstruction) - } - - fn validate_br(context: &mut FunctionValidationContext, idx: u32) -> Result { - let (frame_type, frame_block_type) = { - let frame = context.require_label(idx)?; - (frame.frame_type, frame.block_type) - }; - if frame_type != BlockFrameType::Loop { - if let BlockType::Value(value_type) = frame_block_type { - context.tee_value(value_type.into())?; - } - } - Ok(InstructionOutcome::Unreachable) - } - - fn validate_br_if(context: &mut FunctionValidationContext, idx: u32) -> Result { - context.pop_value(ValueType::I32.into())?; - - let (frame_type, frame_block_type) = { - let frame = context.require_label(idx)?; - (frame.frame_type, frame.block_type) - }; - if frame_type != BlockFrameType::Loop { - if let BlockType::Value(value_type) = frame_block_type { - context.tee_value(value_type.into())?; - } - } - Ok(InstructionOutcome::ValidateNextInstruction) - } - - fn validate_br_table(context: &mut FunctionValidationContext, table: &[u32], default: u32) -> Result { - let required_block_type: BlockType = { - let default_block = context.require_label(default)?; - let required_block_type = if default_block.frame_type != BlockFrameType::Loop { - default_block.block_type - } else { - BlockType::NoResult - }; - - for label in table { - let label_block = context.require_label(*label)?; - let label_block_type = if label_block.frame_type != BlockFrameType::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 - }; - - context.pop_value(ValueType::I32.into())?; - if let BlockType::Value(value_type) = required_block_type { - context.tee_value(value_type.into())?; - } - - Ok(InstructionOutcome::Unreachable) - } - - fn validate_return(context: &mut FunctionValidationContext) -> Result { - if let BlockType::Value(value_type) = context.return_type()? { - context.tee_value(value_type.into())?; - } - Ok(InstructionOutcome::Unreachable) - } - - fn validate_call(context: &mut FunctionValidationContext, idx: u32) -> Result { - let (argument_types, return_type) = context.module.require_function(idx)?; - for argument_type in argument_types.iter().rev() { - context.pop_value((*argument_type).into())?; - } - if let BlockType::Value(value_type) = return_type { - context.push_value(value_type.into())?; - } - Ok(InstructionOutcome::ValidateNextInstruction) - } - - fn validate_call_indirect(context: &mut FunctionValidationContext, idx: u32) -> Result { - { - 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() - ))); - } - } - - context.pop_value(ValueType::I32.into())?; - let (argument_types, return_type) = context.module.require_function_type(idx)?; - for argument_type in argument_types.iter().rev() { - context.pop_value((*argument_type).into())?; - } - if let BlockType::Value(value_type) = return_type { - context.push_value(value_type.into())?; - } - Ok(InstructionOutcome::ValidateNextInstruction) - } - - fn validate_current_memory(context: &mut FunctionValidationContext) -> Result { - context.module.require_memory(DEFAULT_MEMORY_INDEX)?; - context.push_value(ValueType::I32.into())?; - Ok(InstructionOutcome::ValidateNextInstruction) - } - - fn validate_grow_memory(context: &mut FunctionValidationContext) -> Result { - context.module.require_memory(DEFAULT_MEMORY_INDEX)?; - context.pop_value(ValueType::I32.into())?; - context.push_value(ValueType::I32.into())?; - Ok(InstructionOutcome::ValidateNextInstruction) - } -} - -impl<'a> FunctionValidationContext<'a> { - fn new( - module: &'a ModuleContext, - locals: Locals<'a>, - value_stack_limit: usize, - frame_stack_limit: usize, - return_type: BlockType, - ) -> 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: Some(return_type), - labels: HashMap::new(), - } - } - - fn push_value(&mut self, value_type: StackValueType) -> Result<(), Error> { - Ok(self.value_stack.push(value_type.into())?) - } - - fn pop_value(&mut self, value_type: StackValueType) -> Result { - let (is_stack_polymorphic, label_value_stack_len) = { - let frame = self.top_label()?; - (frame.polymorphic_stack, frame.value_stack_len) - }; - let stack_is_empty = self.value_stack.len() == label_value_stack_len; - let actual_value = if stack_is_empty && is_stack_polymorphic { - StackValueType::Any - } else { - self.check_stack_access()?; - self.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 check_stack_access(&self) -> Result<(), Error> { - let value_stack_min = self.frame_stack.top().expect("at least 1 topmost block").value_stack_len; - if self.value_stack.len() > value_stack_min { - Ok(()) - } else { - Err(Error("Trying to access parent frame stack values.".into())) - } - } - - fn tee_value(&mut self, value_type: StackValueType) -> Result<(), Error> { - let _ = self.pop_value(value_type)?; - self.push_value(value_type)?; - Ok(()) - } - - fn unreachable(&mut self) -> Result<(), Error> { - let frame = self.frame_stack.top_mut()?; - self.value_stack.resize(frame.value_stack_len, StackValueType::Any); - frame.polymorphic_stack = true; - Ok(()) - } - - fn top_label(&self) -> Result<&BlockFrame, Error> { - Ok(self.frame_stack.top()?) - } - - fn push_label(&mut self, frame_type: BlockFrameType, block_type: BlockType) -> Result<(), Error> { - Ok(self.frame_stack.push(BlockFrame { - frame_type: frame_type, - block_type: block_type, - begin_position: self.position, - branch_position: self.position, - end_position: self.position, - value_stack_len: self.value_stack.len(), - polymorphic_stack: false, - })?) - } - - fn pop_label(&mut self) -> Result { - // 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 = self.frame_stack.top()?.block_type; - match block_type { - BlockType::NoResult => (), - BlockType::Value(required_value_type) => { - self.pop_value(StackValueType::Specific(required_value_type))?; - } - } - - let frame = self.frame_stack.pop()?; - if self.value_stack.len() != frame.value_stack_len { - return Err(Error(format!( - "Unexpected stack height {}, expected {}", - self.value_stack.len(), - frame.value_stack_len - ))); - } - - if !self.frame_stack.is_empty() { - self.labels.insert(frame.begin_position, self.position); - } - if let BlockType::Value(value_type) = frame.block_type { - self.push_value(value_type.into())?; - } - - Ok(InstructionOutcome::ValidateNextInstruction) - } - - fn require_label(&self, idx: u32) -> Result<&BlockFrame, Error> { - Ok(self.frame_stack.get(idx as usize)?) - } - - fn return_type(&self) -> Result { - self.return_type.ok_or(Error("Trying to return from expression".into())) - } - - fn require_local(&self, idx: u32) -> Result { - Ok(self.locals.type_of_local(idx).map(StackValueType::from)?) - } - - fn into_labels(self) -> HashMap { - self.labels - } -} - impl StackValueType { fn is_any(&self) -> bool { match self { @@ -752,3 +149,1654 @@ impl PartialEq for ValueType { 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::Instruction::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::Instruction::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::Instruction::Return(drop_keep)); + + return Ok(Outcome::Unreachable); + } + + Call(index) => { + Validator::validate_call(context, index)?; + context.sink.emit(isa::Instruction::Call(index)); + } + CallIndirect(index, _reserved) => { + Validator::validate_call_indirect(context, index)?; + context.sink.emit(isa::Instruction::CallIndirect(index)); + } + + Drop => { + Validator::validate_drop(context)?; + context.sink.emit(isa::Instruction::Drop); + } + Select => { + Validator::validate_select(context)?; + context.sink.emit(isa::Instruction::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::Instruction::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::Instruction::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::Instruction::TeeLocal(depth), + ); + } + GetGlobal(index) => { + Validator::validate_get_global(context, index)?; + context.sink.emit(isa::Instruction::GetGlobal(index)); + } + SetGlobal(index) => { + Validator::validate_set_global(context, index)?; + context.sink.emit(isa::Instruction::SetGlobal(index)); + } + + I32Load(align, offset) => { + Validator::validate_load(context, align, 4, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32Load(offset)); + } + I64Load(align, offset) => { + Validator::validate_load(context, align, 8, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Load(offset)); + } + F32Load(align, offset) => { + Validator::validate_load(context, align, 4, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32Load(offset)); + } + F64Load(align, offset) => { + Validator::validate_load(context, align, 8, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64Load(offset)); + } + I32Load8S(align, offset) => { + Validator::validate_load(context, align, 1, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32Load8S(offset)); + } + I32Load8U(align, offset) => { + Validator::validate_load(context, align, 1, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32Load8U(offset)); + } + I32Load16S(align, offset) => { + Validator::validate_load(context, align, 2, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32Load16S(offset)); + } + I32Load16U(align, offset) => { + Validator::validate_load(context, align, 2, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32Load16U(offset)); + } + I64Load8S(align, offset) => { + Validator::validate_load(context, align, 1, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Load8S(offset)); + } + I64Load8U(align, offset) => { + Validator::validate_load(context, align, 1, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Load8U(offset)); + } + I64Load16S(align, offset) => { + Validator::validate_load(context, align, 2, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Load16S(offset)); + } + I64Load16U(align, offset) => { + Validator::validate_load(context, align, 2, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Load16U(offset)); + } + I64Load32S(align, offset) => { + Validator::validate_load(context, align, 4, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Load32S(offset)); + } + I64Load32U(align, offset) => { + Validator::validate_load(context, align, 4, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Load32U(offset)); + } + + I32Store(align, offset) => { + Validator::validate_store(context, align, 4, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32Store(offset)); + } + I64Store(align, offset) => { + Validator::validate_store(context, align, 8, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Store(offset)); + } + F32Store(align, offset) => { + Validator::validate_store(context, align, 4, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32Store(offset)); + } + F64Store(align, offset) => { + Validator::validate_store(context, align, 8, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64Store(offset)); + } + I32Store8(align, offset) => { + Validator::validate_store(context, align, 1, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32Store8(offset)); + } + I32Store16(align, offset) => { + Validator::validate_store(context, align, 2, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32Store16(offset)); + } + I64Store8(align, offset) => { + Validator::validate_store(context, align, 1, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Store8(offset)); + } + I64Store16(align, offset) => { + Validator::validate_store(context, align, 2, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Store16(offset)); + } + I64Store32(align, offset) => { + Validator::validate_store(context, align, 4, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Store32(offset)); + } + + CurrentMemory(_) => { + Validator::validate_current_memory(context)?; + context.sink.emit(isa::Instruction::CurrentMemory); + } + GrowMemory(_) => { + Validator::validate_grow_memory(context)?; + context.sink.emit(isa::Instruction::GrowMemory); + } + + I32Const(v) => { + Validator::validate_const(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32Const(v)); + } + I64Const(v) => { + Validator::validate_const(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Const(v)); + } + F32Const(v) => { + Validator::validate_const(context, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32Const(v)); + } + F64Const(v) => { + Validator::validate_const(context, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64Const(v)); + } + + I32Eqz => { + Validator::validate_testop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32Eqz); + } + I32Eq => { + Validator::validate_relop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32Eq); + } + I32Ne => { + Validator::validate_relop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32Ne); + } + I32LtS => { + Validator::validate_relop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32LtS); + } + I32LtU => { + Validator::validate_relop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32LtU); + } + I32GtS => { + Validator::validate_relop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32GtS); + } + I32GtU => { + Validator::validate_relop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32GtU); + } + I32LeS => { + Validator::validate_relop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32LeS); + } + I32LeU => { + Validator::validate_relop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32LeU); + } + I32GeS => { + Validator::validate_relop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32GeS); + } + I32GeU => { + Validator::validate_relop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32GeU); + } + + I64Eqz => { + Validator::validate_testop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Eqz); + } + I64Eq => { + Validator::validate_relop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Eq); + } + I64Ne => { + Validator::validate_relop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Ne); + } + I64LtS => { + Validator::validate_relop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64LtS); + } + I64LtU => { + Validator::validate_relop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64LtU); + } + I64GtS => { + Validator::validate_relop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64GtS); + } + I64GtU => { + Validator::validate_relop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64GtU); + } + I64LeS => { + Validator::validate_relop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64LeS); + } + I64LeU => { + Validator::validate_relop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64LeU); + } + I64GeS => { + Validator::validate_relop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64GeS); + } + I64GeU => { + Validator::validate_relop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64GeU); + } + + F32Eq => { + Validator::validate_relop(context, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32Eq); + } + F32Ne => { + Validator::validate_relop(context, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32Ne); + } + F32Lt => { + Validator::validate_relop(context, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32Lt); + } + F32Gt => { + Validator::validate_relop(context, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32Gt); + } + F32Le => { + Validator::validate_relop(context, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32Le); + } + F32Ge => { + Validator::validate_relop(context, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32Ge); + } + + F64Eq => { + Validator::validate_relop(context, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64Eq); + } + F64Ne => { + Validator::validate_relop(context, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64Ne); + } + F64Lt => { + Validator::validate_relop(context, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64Lt); + } + F64Gt => { + Validator::validate_relop(context, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64Gt); + } + F64Le => { + Validator::validate_relop(context, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64Le); + } + F64Ge => { + Validator::validate_relop(context, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64Ge); + } + + I32Clz => { + Validator::validate_unop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32Clz); + } + I32Ctz => { + Validator::validate_unop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32Ctz); + } + I32Popcnt => { + Validator::validate_unop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32Popcnt); + } + I32Add => { + Validator::validate_binop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32Add); + } + I32Sub => { + Validator::validate_binop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32Sub); + } + I32Mul => { + Validator::validate_binop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32Mul); + } + I32DivS => { + Validator::validate_binop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32DivS); + } + I32DivU => { + Validator::validate_binop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32DivU); + } + I32RemS => { + Validator::validate_binop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32RemS); + } + I32RemU => { + Validator::validate_binop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32RemU); + } + I32And => { + Validator::validate_binop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32And); + } + I32Or => { + Validator::validate_binop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32Or); + } + I32Xor => { + Validator::validate_binop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32Xor); + } + I32Shl => { + Validator::validate_binop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32Shl); + } + I32ShrS => { + Validator::validate_binop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32ShrS); + } + I32ShrU => { + Validator::validate_binop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32ShrU); + } + I32Rotl => { + Validator::validate_binop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32Rotl); + } + I32Rotr => { + Validator::validate_binop(context, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32Rotr); + } + + I64Clz => { + Validator::validate_unop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Clz); + } + I64Ctz => { + Validator::validate_unop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Ctz); + } + I64Popcnt => { + Validator::validate_unop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Popcnt); + } + I64Add => { + Validator::validate_binop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Add); + } + I64Sub => { + Validator::validate_binop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Sub); + } + I64Mul => { + Validator::validate_binop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Mul); + } + I64DivS => { + Validator::validate_binop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64DivS); + } + I64DivU => { + Validator::validate_binop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64DivU); + } + I64RemS => { + Validator::validate_binop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64RemS); + } + I64RemU => { + Validator::validate_binop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64RemU); + } + I64And => { + Validator::validate_binop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64And); + } + I64Or => { + Validator::validate_binop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Or); + } + I64Xor => { + Validator::validate_binop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Xor); + } + I64Shl => { + Validator::validate_binop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Shl); + } + I64ShrS => { + Validator::validate_binop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64ShrS); + } + I64ShrU => { + Validator::validate_binop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64ShrU); + } + I64Rotl => { + Validator::validate_binop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Rotl); + } + I64Rotr => { + Validator::validate_binop(context, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Rotr); + } + + F32Abs => { + Validator::validate_unop(context, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32Abs); + } + F32Neg => { + Validator::validate_unop(context, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32Neg); + } + F32Ceil => { + Validator::validate_unop(context, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32Ceil); + } + F32Floor => { + Validator::validate_unop(context, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32Floor); + } + F32Trunc => { + Validator::validate_unop(context, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32Trunc); + } + F32Nearest => { + Validator::validate_unop(context, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32Nearest); + } + F32Sqrt => { + Validator::validate_unop(context, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32Sqrt); + } + F32Add => { + Validator::validate_binop(context, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32Add); + } + F32Sub => { + Validator::validate_binop(context, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32Sub); + } + F32Mul => { + Validator::validate_binop(context, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32Mul); + } + F32Div => { + Validator::validate_binop(context, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32Div); + } + F32Min => { + Validator::validate_binop(context, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32Min); + } + F32Max => { + Validator::validate_binop(context, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32Max); + } + F32Copysign => { + Validator::validate_binop(context, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32Copysign); + } + + F64Abs => { + Validator::validate_unop(context, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64Abs); + } + F64Neg => { + Validator::validate_unop(context, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64Neg); + } + F64Ceil => { + Validator::validate_unop(context, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64Ceil); + } + F64Floor => { + Validator::validate_unop(context, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64Floor); + } + F64Trunc => { + Validator::validate_unop(context, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64Trunc); + } + F64Nearest => { + Validator::validate_unop(context, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64Nearest); + } + F64Sqrt => { + Validator::validate_unop(context, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64Sqrt); + } + F64Add => { + Validator::validate_binop(context, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64Add); + } + F64Sub => { + Validator::validate_binop(context, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64Sub); + } + F64Mul => { + Validator::validate_binop(context, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64Mul); + } + F64Div => { + Validator::validate_binop(context, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64Div); + } + F64Min => { + Validator::validate_binop(context, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64Min); + } + F64Max => { + Validator::validate_binop(context, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64Max); + } + F64Copysign => { + Validator::validate_binop(context, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64Copysign); + } + + I32WrapI64 => { + Validator::validate_cvtop(context, ValueType::I64, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32WrapI64); + } + I32TruncSF32 => { + Validator::validate_cvtop(context, ValueType::F32, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32TruncSF32); + } + I32TruncUF32 => { + Validator::validate_cvtop(context, ValueType::F32, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32TruncUF32); + } + I32TruncSF64 => { + Validator::validate_cvtop(context, ValueType::F64, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32TruncSF64); + } + I32TruncUF64 => { + Validator::validate_cvtop(context, ValueType::F64, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32TruncUF64); + } + I64ExtendSI32 => { + Validator::validate_cvtop(context, ValueType::I32, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64ExtendSI32); + } + I64ExtendUI32 => { + Validator::validate_cvtop(context, ValueType::I32, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64ExtendUI32); + } + I64TruncSF32 => { + Validator::validate_cvtop(context, ValueType::F32, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64TruncSF32); + } + I64TruncUF32 => { + Validator::validate_cvtop(context, ValueType::F32, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64TruncUF32); + } + I64TruncSF64 => { + Validator::validate_cvtop(context, ValueType::F64, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64TruncSF64); + } + I64TruncUF64 => { + Validator::validate_cvtop(context, ValueType::F64, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64TruncUF64); + } + F32ConvertSI32 => { + Validator::validate_cvtop(context, ValueType::I32, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32ConvertSI32); + } + F32ConvertUI32 => { + Validator::validate_cvtop(context, ValueType::I32, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32ConvertUI32); + } + F32ConvertSI64 => { + Validator::validate_cvtop(context, ValueType::I64, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32ConvertSI64); + } + F32ConvertUI64 => { + Validator::validate_cvtop(context, ValueType::I64, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32ConvertUI64); + } + F32DemoteF64 => { + Validator::validate_cvtop(context, ValueType::F64, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32DemoteF64); + } + F64ConvertSI32 => { + Validator::validate_cvtop(context, ValueType::I32, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64ConvertSI32); + } + F64ConvertUI32 => { + Validator::validate_cvtop(context, ValueType::I32, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64ConvertUI32); + } + F64ConvertSI64 => { + Validator::validate_cvtop(context, ValueType::I64, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64ConvertSI64); + } + F64ConvertUI64 => { + Validator::validate_cvtop(context, ValueType::I64, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64ConvertUI64); + } + F64PromoteF32 => { + Validator::validate_cvtop(context, ValueType::F32, ValueType::F64)?; + context.sink.emit(isa::Instruction::F64PromoteF32); + } + + I32ReinterpretF32 => { + Validator::validate_cvtop(context, ValueType::F32, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32ReinterpretF32); + } + I64ReinterpretF64 => { + Validator::validate_cvtop(context, ValueType::F64, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64ReinterpretF64); + } + F32ReinterpretI32 => { + Validator::validate_cvtop(context, ValueType::I32, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32ReinterpretI32); + } + F64ReinterpretI64 => { + Validator::validate_cvtop(context, ValueType::I64, ValueType::F64)?; + context.sink.emit(isa::Instruction::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 { + isa::Instructions { + code: 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, +} + +/// A relocation entry that specifies. +#[derive(Debug)] +enum Reloc { + /// Patch the destination of the branch instruction (br, br_eqz, br_nez) + /// at the specified pc. + Br { + pc: u32, + }, + /// Patch the specified destination index inside of br_table instruction at + /// the specified pc. + BrTable { + pc: u32, + idx: usize, + }, +} + +/// 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: Vec, + labels: Vec<(Label, Vec)>, +} + +impl Sink { + fn with_instruction_capacity(capacity: usize) -> Sink { + Sink { + ins: Vec::with_capacity(capacity), + labels: Vec::new(), + } + } + + fn cur_pc(&self) -> u32 { + self.ins.len() as u32 + } + + fn pc_or_placeholder 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::Instruction) { + 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, || Reloc::Br { pc }); + self.ins.push(isa::Instruction::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, || Reloc::Br { pc }); + self.ins.push(isa::Instruction::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, || Reloc::Br { pc }); + self.ins.push(isa::Instruction::BrIfNez(isa::Target { + dst_pc, + drop_keep: drop_keep.into(), + })); + } + + fn emit_br_table(&mut self, targets: &[Target], default: Target) { + use std::iter; + + let pc = self.cur_pc(); + let mut isa_targets = Vec::new(); + for (idx, &Target { label, drop_keep }) in targets.iter().chain(iter::once(&default)).enumerate() { + let dst_pc = self.pc_or_placeholder(label, || Reloc::BrTable { pc, idx }); + isa_targets.push( + isa::Target { + dst_pc, + drop_keep: drop_keep.into(), + }, + ); + } + self.ins.push(isa::Instruction::BrTable( + isa_targets.into_boxed_slice(), + )); + } + + /// 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 std::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 { + match reloc { + Reloc::Br { pc } => match self.ins[pc as usize] { + isa::Instruction::Br(ref mut target) + | isa::Instruction::BrIfEqz(ref mut target) + | isa::Instruction::BrIfNez(ref mut target) => target.dst_pc = dst_pc, + _ => panic!("branch relocation points to a non-branch instruction"), + }, + Reloc::BrTable { pc, idx } => match self.ins[pc as usize] { + isa::Instruction::BrTable(ref mut targets) => targets[idx].dst_pc = dst_pc, + _ => panic!("brtable relocation points to not brtable instruction"), + } + } + } + + // Mark this label as resolved. + self.labels[label.0] = (Label::Resolved(dst_pc), Vec::new()); + } + + /// Consume this Sink and returns isa::Instruction. + fn into_inner(self) -> Vec { + // 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/validation/mod.rs b/src/validation/mod.rs index 78f8355..d6e452f 100644 --- a/src/validation/mod.rs +++ b/src/validation/mod.rs @@ -1,14 +1,15 @@ use std::error; use std::fmt; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; use parity_wasm::elements::{ BlockType, External, GlobalEntry, GlobalType, Internal, MemoryType, Module, Instruction, ResizableLimits, TableType, ValueType, InitExpr, Type, }; use common::stack; use self::context::ModuleContextBuilder; -use self::func::Validator; +use self::func::FunctionReader; use memory_units::Pages; +use isa; mod context; mod func; @@ -40,7 +41,7 @@ impl From for Error { #[derive(Clone)] pub struct ValidatedModule { - pub labels: HashMap>, + pub code_map: Vec, pub module: Module, } @@ -167,7 +168,7 @@ pub fn deny_floating_point(module: &Module) -> Result<(), Error> { pub fn validate_module(module: Module) -> Result { let mut context_builder = ModuleContextBuilder::new(); let mut imported_globals = Vec::new(); - let mut labels = HashMap::new(); + let mut code_map = Vec::new(); // Copy types from module as is. context_builder.set_types( @@ -257,12 +258,12 @@ pub fn validate_module(module: Module) -> Result { index )), )?; - let func_labels = Validator::validate_function(&context, function, function_body) + let code = FunctionReader::read_function(&context, function, function_body) .map_err(|e| { let Error(ref msg) = e; - Error(format!("Function #{} validation error: {}", index, msg)) + Error(format!("Function #{} reading/validation error: {}", index, msg)) })?; - labels.insert(index, func_labels); + code_map.push(code); } } @@ -374,7 +375,7 @@ pub fn validate_module(module: Module) -> Result { Ok(ValidatedModule { module, - labels + code_map, }) } diff --git a/src/validation/tests.rs b/src/validation/tests.rs index 591c30a..7feb726 100644 --- a/src/validation/tests.rs +++ b/src/validation/tests.rs @@ -1,9 +1,12 @@ -use super::validate_module; +use super::{validate_module, ValidatedModule}; use parity_wasm::builder::module; use parity_wasm::elements::{ - External, GlobalEntry, GlobalType, ImportEntry, InitExpr, MemoryType, - Instruction, Instructions, TableType, ValueType, BlockType + External, GlobalEntry, GlobalType, ImportEntry, InitExpr, MemoryType, + Instruction, Instructions, TableType, ValueType, BlockType, deserialize_buffer, + Module, }; +use isa; +use wabt; #[test] fn empty_is_valid() { @@ -299,3 +302,617 @@ fn if_else_with_return_type_validation() { .build(); validate_module(m).unwrap(); } + +fn validate(wat: &str) -> ValidatedModule { + let wasm = wabt::wat2wasm(wat).unwrap(); + let module = deserialize_buffer::(&wasm).unwrap(); + let validated_module = validate_module(module).unwrap(); + validated_module +} + +fn compile(wat: &str) -> Vec { + let validated_module = validate(wat); + let code = &validated_module.code_map[0]; + code.code.clone() +} + +#[test] +fn implicit_return_no_value() { + let code = compile(r#" + (module + (func (export "call") + ) + ) + "#); + assert_eq!( + code, + vec![ + isa::Instruction::Return(isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }) + ] + ) +} + +#[test] +fn implicit_return_with_value() { + let code = compile(r#" + (module + (func (export "call") (result i32) + i32.const 0 + ) + ) + "#); + assert_eq!( + code, + vec![ + isa::Instruction::I32Const(0), + isa::Instruction::Return(isa::DropKeep { + drop: 0, + keep: isa::Keep::Single, + }), + ] + ) +} + +#[test] +fn implicit_return_param() { + let code = compile(r#" + (module + (func (export "call") (param i32) + ) + ) + "#); + assert_eq!( + code, + vec![ + isa::Instruction::Return(isa::DropKeep { + drop: 1, + keep: isa::Keep::None, + }), + ] + ) +} + +#[test] +fn get_local() { + let code = compile(r#" + (module + (func (export "call") (param i32) (result i32) + get_local 0 + ) + ) + "#); + assert_eq!( + code, + vec![ + isa::Instruction::GetLocal(1), + isa::Instruction::Return(isa::DropKeep { + drop: 1, + keep: isa::Keep::Single, + }), + ] + ) +} + +#[test] +fn explicit_return() { + let code = compile(r#" + (module + (func (export "call") (param i32) (result i32) + get_local 0 + return + ) + ) + "#); + assert_eq!( + code, + vec![ + isa::Instruction::GetLocal(1), + isa::Instruction::Return(isa::DropKeep { + drop: 1, + keep: isa::Keep::Single, + }), + isa::Instruction::Return(isa::DropKeep { + drop: 1, + keep: isa::Keep::Single, + }), + ] + ) +} + +#[test] +fn add_params() { + let code = compile(r#" + (module + (func (export "call") (param i32) (param i32) (result i32) + get_local 0 + get_local 1 + i32.add + ) + ) + "#); + assert_eq!( + code, + vec![ + // This is tricky. Locals are now loaded from the stack. The load + // happens from address relative of the current stack pointer. The first load + // takes the value below the previous one (i.e the second argument) and then, it increments + // the stack pointer. And then the same thing hapens with the value below the previous one + // (which happens to be the value loaded by the first get_local). + isa::Instruction::GetLocal(2), + isa::Instruction::GetLocal(2), + isa::Instruction::I32Add, + isa::Instruction::Return(isa::DropKeep { + drop: 2, + keep: isa::Keep::Single, + }), + ] + ) +} + +#[test] +fn drop_locals() { + let code = compile(r#" + (module + (func (export "call") (param i32) + (local i32) + get_local 0 + set_local 1 + ) + ) + "#); + assert_eq!( + code, + vec![ + isa::Instruction::GetLocal(2), + isa::Instruction::SetLocal(1), + isa::Instruction::Return(isa::DropKeep { + drop: 2, + keep: isa::Keep::None, + }), + ] + ) +} + +#[test] +fn if_without_else() { + let code = compile(r#" + (module + (func (export "call") (param i32) (result i32) + i32.const 1 + if + i32.const 2 + return + end + i32.const 3 + ) + ) + "#); + assert_eq!( + code, + vec![ + isa::Instruction::I32Const(1), + isa::Instruction::BrIfEqz(isa::Target { + dst_pc: 4, + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }), + isa::Instruction::I32Const(2), + isa::Instruction::Return(isa::DropKeep { + drop: 1, // 1 param + keep: isa::Keep::Single, // 1 result + }), + isa::Instruction::I32Const(3), + isa::Instruction::Return(isa::DropKeep { + drop: 1, + keep: isa::Keep::Single, + }), + ] + ) +} + +#[test] +fn if_else() { + let code = compile(r#" + (module + (func (export "call") + (local i32) + i32.const 1 + if + i32.const 2 + set_local 0 + else + i32.const 3 + set_local 0 + end + ) + ) + "#); + assert_eq!( + code, + vec![ + isa::Instruction::I32Const(1), + isa::Instruction::BrIfEqz(isa::Target { + dst_pc: 5, + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }), + isa::Instruction::I32Const(2), + isa::Instruction::SetLocal(1), + isa::Instruction::Br(isa::Target { + dst_pc: 7, + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }), + isa::Instruction::I32Const(3), + isa::Instruction::SetLocal(1), + isa::Instruction::Return(isa::DropKeep { + drop: 1, + keep: isa::Keep::None, + }), + ] + ) +} + +#[test] +fn if_else_returns_result() { + let code = compile(r#" + (module + (func (export "call") + i32.const 1 + if (result i32) + i32.const 2 + else + i32.const 3 + end + drop + ) + ) + "#); + assert_eq!( + code, + vec![ + isa::Instruction::I32Const(1), + isa::Instruction::BrIfEqz(isa::Target { + dst_pc: 4, + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }), + isa::Instruction::I32Const(2), + isa::Instruction::Br(isa::Target { + dst_pc: 5, + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }), + isa::Instruction::I32Const(3), + isa::Instruction::Drop, + isa::Instruction::Return(isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }), + ] + ) +} + +#[test] +fn if_else_branch_from_true_branch() { + let code = compile(r#" + (module + (func (export "call") + i32.const 1 + if (result i32) + i32.const 1 + i32.const 1 + br_if 0 + drop + i32.const 2 + else + i32.const 3 + end + drop + ) + ) + "#); + assert_eq!( + code, + vec![ + isa::Instruction::I32Const(1), + isa::Instruction::BrIfEqz(isa::Target { + dst_pc: 8, + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }), + isa::Instruction::I32Const(1), + isa::Instruction::I32Const(1), + isa::Instruction::BrIfNez(isa::Target { + dst_pc: 9, + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::Single, + }, + }), + isa::Instruction::Drop, + isa::Instruction::I32Const(2), + isa::Instruction::Br(isa::Target { + dst_pc: 9, + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }), + isa::Instruction::I32Const(3), + isa::Instruction::Drop, + isa::Instruction::Return(isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }), + ] + ) +} + +#[test] +fn if_else_branch_from_false_branch() { + let code = compile(r#" + (module + (func (export "call") + i32.const 1 + if (result i32) + i32.const 1 + else + i32.const 2 + i32.const 1 + br_if 0 + drop + i32.const 3 + end + drop + ) + ) + "#); + assert_eq!( + code, + vec![ + isa::Instruction::I32Const(1), + isa::Instruction::BrIfEqz(isa::Target { + dst_pc: 4, + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }), + isa::Instruction::I32Const(1), + isa::Instruction::Br(isa::Target { + dst_pc: 9, + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }), + isa::Instruction::I32Const(2), + isa::Instruction::I32Const(1), + isa::Instruction::BrIfNez(isa::Target { + dst_pc: 9, + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::Single, + }, + }), + isa::Instruction::Drop, + isa::Instruction::I32Const(3), + isa::Instruction::Drop, + isa::Instruction::Return(isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }), + ] + ) +} + +#[test] +fn loop_() { + let code = compile(r#" + (module + (func (export "call") + loop (result i32) + i32.const 1 + br_if 0 + i32.const 2 + end + drop + ) + ) + "#); + assert_eq!( + code, + vec![ + isa::Instruction::I32Const(1), + isa::Instruction::BrIfNez(isa::Target { + dst_pc: 0, + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }), + isa::Instruction::I32Const(2), + isa::Instruction::Drop, + isa::Instruction::Return(isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }), + ] + ) +} + +#[test] +fn loop_empty() { + let code = compile(r#" + (module + (func (export "call") + loop + end + ) + ) + "#); + assert_eq!( + code, + vec![ + isa::Instruction::Return(isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }), + ] + ) +} + +#[test] +fn brtable() { + let code = compile(r#" + (module + (func (export "call") + block $1 + loop $2 + i32.const 0 + br_table $2 $1 + end + end + ) + ) + "#); + assert_eq!( + code, + vec![ + isa::Instruction::I32Const(0), + isa::Instruction::BrTable( + vec![ + isa::Target { + dst_pc: 0, + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }, + isa::Target { + dst_pc: 2, + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }, + ].into_boxed_slice() + ), + isa::Instruction::Return(isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }), + ] + ) +} + +#[test] +fn brtable_returns_result() { + let code = compile(r#" + (module + (func (export "call") + block $1 (result i32) + block $2 (result i32) + i32.const 0 + i32.const 1 + br_table $2 $1 + end + unreachable + end + drop + ) + ) + "#); + assert_eq!( + code, + vec![ + isa::Instruction::I32Const(0), + isa::Instruction::I32Const(1), + isa::Instruction::BrTable( + vec![ + isa::Target { + dst_pc: 3, + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::Single, + }, + }, + isa::Target { + dst_pc: 4, + drop_keep: isa::DropKeep { + keep: isa::Keep::Single, + drop: 0, + }, + }, + ].into_boxed_slice() + ), + isa::Instruction::Unreachable, + isa::Instruction::Drop, + isa::Instruction::Return(isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }), + ] + ) +} + +#[test] +fn wabt_example() { + let code = compile(r#" + (module + (func (export "call") (param i32) (result i32) + block $exit + get_local 0 + br_if $exit + i32.const 1 + return + end + i32.const 2 + return + ) + ) + "#); + assert_eq!( + code, + vec![ + isa::Instruction::GetLocal(1), + isa::Instruction::BrIfNez(isa::Target { + dst_pc: 4, + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }), + isa::Instruction::I32Const(1), + isa::Instruction::Return(isa::DropKeep { + drop: 1, // 1 parameter + keep: isa::Keep::Single, + }), + isa::Instruction::I32Const(2), + isa::Instruction::Return(isa::DropKeep { + drop: 1, + keep: isa::Keep::Single, + }), + isa::Instruction::Return(isa::DropKeep { + drop: 1, + keep: isa::Keep::Single, + }), + ] + ) +} diff --git a/src/validation/util.rs b/src/validation/util.rs index a219e4c..acdec1b 100644 --- a/src/validation/util.rs +++ b/src/validation/util.rs @@ -10,14 +10,36 @@ use validation::Error; pub struct Locals<'a> { params: &'a [ValueType], local_groups: &'a [Local], + count: u32, } impl<'a> Locals<'a> { - pub fn new(params: &'a [ValueType], local_groups: &'a [Local]) -> Locals<'a> { - Locals { + /// Create a new wrapper around declared variables and parameters. + pub fn new(params: &'a [ValueType], local_groups: &'a [Local]) -> Result, Error> { + let mut acc = params.len() as u32; + for locals_group in local_groups { + acc = acc + .checked_add(locals_group.count()) + .ok_or_else(|| + Error(String::from("Locals range not in 32-bit range")) + )?; + } + + Ok(Locals { params, local_groups, - } + count: acc, + }) + } + + /// Returns parameter count. + pub fn param_count(&self) -> u32 { + self.params.len() as u32 + } + + /// Returns total count of all declared locals and paramaterers. + pub fn count(&self) -> u32 { + self.count } /// Returns the type of a local variable (either a declared local or a param). @@ -29,7 +51,7 @@ impl<'a> Locals<'a> { } // If an index doesn't point to a param, then we have to look into local declarations. - let mut start_idx = self.params.len() as u32; + let mut start_idx = self.param_count(); for locals_group in self.local_groups { let end_idx = start_idx .checked_add(locals_group.count()) @@ -62,7 +84,7 @@ mod tests { fn locals_it_works() { let params = vec![ValueType::I32, ValueType::I64]; let local_groups = vec![Local::new(2, ValueType::F32), Local::new(2, ValueType::F64)]; - let locals = Locals::new(¶ms, &local_groups); + let locals = Locals::new(¶ms, &local_groups).unwrap(); assert_matches!(locals.type_of_local(0), Ok(ValueType::I32)); assert_matches!(locals.type_of_local(1), Ok(ValueType::I64)); @@ -76,7 +98,7 @@ mod tests { #[test] fn locals_no_declared_locals() { let params = vec![ValueType::I32]; - let locals = Locals::new(¶ms, &[]); + let locals = Locals::new(¶ms, &[]).unwrap(); assert_matches!(locals.type_of_local(0), Ok(ValueType::I32)); assert_matches!(locals.type_of_local(1), Err(_)); @@ -85,7 +107,7 @@ mod tests { #[test] fn locals_no_params() { let local_groups = vec![Local::new(2, ValueType::I32), Local::new(3, ValueType::I64)]; - let locals = Locals::new(&[], &local_groups); + let locals = Locals::new(&[], &local_groups).unwrap(); assert_matches!(locals.type_of_local(0), Ok(ValueType::I32)); assert_matches!(locals.type_of_local(1), Ok(ValueType::I32)); @@ -101,12 +123,9 @@ mod tests { Local::new(u32::max_value(), ValueType::I32), Local::new(1, ValueType::I64), ]; - let locals = Locals::new(&[], &local_groups); - assert_matches!( - locals.type_of_local(u32::max_value() - 1), - Ok(ValueType::I32) + Locals::new(&[], &local_groups), + Err(_) ); - assert_matches!(locals.type_of_local(u32::max_value()), Err(_)); } } diff --git a/tests/spec/run.rs b/tests/spec/run.rs index 1fe12c6..80d312f 100644 --- a/tests/spec/run.rs +++ b/tests/spec/run.rs @@ -360,6 +360,8 @@ fn try_spec(name: &str) -> Result<(), Error> { }}; } + println!("Running spec cmd {}: {:?}", line, kind); + match kind { CommandKind::Module { name, module, .. } => { load_module(&module.into_vec()?, &name, &mut spec_driver)