From 22b260a3b9defe3de1c6c25be75159ae69db2bb6 Mon Sep 17 00:00:00 2001 From: Jef Date: Wed, 18 Apr 2018 14:09:09 +0200 Subject: [PATCH 01/28] Optionally deny floating point operations (#83) * Optionally deny floating point operations * Deny floating-point parameters and fix docs/indentation * Test denial of floating-point parameters --- src/lib.rs | 61 ++++++++++++++++++++++- src/validation/mod.rs | 113 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 618ae75..b36532c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -385,7 +385,6 @@ pub struct Module { } impl Module { - /// Create `Module` from `parity_wasm::elements::Module`. /// /// This function will load, validate and prepare a `parity_wasm`'s `Module`. @@ -431,6 +430,66 @@ impl Module { }) } + /// Fail if the module contains any floating-point operations + /// + /// # Errors + /// + /// Returns `Err` if provided `Module` is not valid. + /// + /// # Examples + /// + /// ```rust + /// # extern crate wasmi; + /// # extern crate wabt; + /// + /// let wasm_binary: Vec = + /// wabt::wat2wasm( + /// r#" + /// (module + /// (func $add (param $lhs i32) (param $rhs i32) (result i32) + /// get_local $lhs + /// get_local $rhs + /// i32.add)) + /// "#, + /// ) + /// .expect("failed to parse wat"); + /// + /// // Load wasm binary and prepare it for instantiation. + /// let module = wasmi::Module::from_buffer(&wasm_binary).expect("Parsing failed"); + /// assert!(module.deny_floating_point().is_ok()); + /// + /// let wasm_binary: Vec = + /// wabt::wat2wasm( + /// r#" + /// (module + /// (func $add (param $lhs f32) (param $rhs f32) (result f32) + /// get_local $lhs + /// get_local $rhs + /// f32.add)) + /// "#, + /// ) + /// .expect("failed to parse wat"); + /// + /// let module = wasmi::Module::from_buffer(&wasm_binary).expect("Parsing failed"); + /// assert!(module.deny_floating_point().is_err()); + /// + /// let wasm_binary: Vec = + /// wabt::wat2wasm( + /// r#" + /// (module + /// (func $add (param $lhs f32) (param $rhs f32) (result f32) + /// get_local $lhs)) + /// "#, + /// ) + /// .expect("failed to parse wat"); + /// + /// let module = wasmi::Module::from_buffer(&wasm_binary).expect("Parsing failed"); + /// assert!(module.deny_floating_point().is_err()); + /// ``` + pub fn deny_floating_point(&self) -> Result<(), Error> { + validation::deny_floating_point(&self.module).map_err(Into::into) + } + /// Create `Module` from a given buffer. /// /// This function will deserialize wasm module from a given module, diff --git a/src/validation/mod.rs b/src/validation/mod.rs index 5e4bc1e..268f420 100644 --- a/src/validation/mod.rs +++ b/src/validation/mod.rs @@ -50,6 +50,119 @@ impl ::std::ops::Deref for ValidatedModule { } } +pub fn deny_floating_point(module: &Module) -> Result<(), Error> { + if let Some(code) = module.code_section() { + for op in code.bodies().iter().flat_map(|body| body.code().elements()) { + use parity_wasm::elements::Opcode::*; + + macro_rules! match_eq { + ($pattern:pat) => { + |val| if let $pattern = *val { true } else { false } + }; + } + + const DENIED: &[fn(&Opcode) -> bool] = &[ + match_eq!(F32Load(_, _)), + match_eq!(F64Load(_, _)), + match_eq!(F32Store(_, _)), + match_eq!(F64Store(_, _)), + match_eq!(F32Const(_)), + match_eq!(F64Const(_)), + match_eq!(F32Eq), + match_eq!(F32Ne), + match_eq!(F32Lt), + match_eq!(F32Gt), + match_eq!(F32Le), + match_eq!(F32Ge), + match_eq!(F64Eq), + match_eq!(F64Ne), + match_eq!(F64Lt), + match_eq!(F64Gt), + match_eq!(F64Le), + match_eq!(F64Ge), + match_eq!(F32Abs), + match_eq!(F32Neg), + match_eq!(F32Ceil), + match_eq!(F32Floor), + match_eq!(F32Trunc), + match_eq!(F32Nearest), + match_eq!(F32Sqrt), + match_eq!(F32Add), + match_eq!(F32Sub), + match_eq!(F32Mul), + match_eq!(F32Div), + match_eq!(F32Min), + match_eq!(F32Max), + match_eq!(F32Copysign), + match_eq!(F64Abs), + match_eq!(F64Neg), + match_eq!(F64Ceil), + match_eq!(F64Floor), + match_eq!(F64Trunc), + match_eq!(F64Nearest), + match_eq!(F64Sqrt), + match_eq!(F64Add), + match_eq!(F64Sub), + match_eq!(F64Mul), + match_eq!(F64Div), + match_eq!(F64Min), + match_eq!(F64Max), + match_eq!(F64Copysign), + match_eq!(F32ConvertSI32), + match_eq!(F32ConvertUI32), + match_eq!(F32ConvertSI64), + match_eq!(F32ConvertUI64), + match_eq!(F32DemoteF64), + match_eq!(F64ConvertSI32), + match_eq!(F64ConvertUI32), + match_eq!(F64ConvertSI64), + match_eq!(F64ConvertUI64), + match_eq!(F64PromoteF32), + match_eq!(F32ReinterpretI32), + match_eq!(F64ReinterpretI64), + match_eq!(I32TruncSF32), + match_eq!(I32TruncUF32), + match_eq!(I32TruncSF64), + match_eq!(I32TruncUF64), + match_eq!(I64TruncSF32), + match_eq!(I64TruncUF32), + match_eq!(I64TruncSF64), + match_eq!(I64TruncUF64), + match_eq!(I32ReinterpretF32), + match_eq!(I64ReinterpretF64), + ]; + + if DENIED.iter().any(|is_denied| is_denied(op)) { + return Err(Error(format!("Floating point operation denied: {:?}", op))); + } + } + } + + if let (Some(sec), Some(types)) = (module.function_section(), module.type_section()) { + use parity_wasm::elements::{Type, ValueType}; + + let types = types.types(); + + for sig in sec.entries() { + if let Some(typ) = types.get(sig.type_ref() as usize) { + match *typ { + Type::Function(ref func) => { + if func.params() + .iter() + .chain(func.return_type().as_ref()) + .any(|&typ| typ == ValueType::F32 || typ == ValueType::F64) + { + return Err(Error(format!("Use of floating point types denied"))); + } + } + } + } + } + } + + Ok(()) +} + pub fn validate_module(module: Module) -> Result { let mut context_builder = ModuleContextBuilder::new(); let mut imported_globals = Vec::new(); From 5cda9a05dad8bdf18d0ad0c83159b576f6e20c7d Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Wed, 18 Apr 2018 17:44:10 +0300 Subject: [PATCH 02/28] Check the signature of host function. (#84) --- src/runner.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/runner.rs b/src/runner.rs index 15ec910..f075d21 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -107,6 +107,14 @@ impl<'a, E: Externals> Interpreter<'a, E> { FuncInstanceInternal::Host { ref signature, .. } => { let args = prepare_function_args(signature, &mut function_context.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 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)?; } From 730c918a80675dd0578a9784837c33521fe40012 Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Fri, 20 Apr 2018 17:55:07 +0300 Subject: [PATCH 03/28] Don't expand locals. (#86) --- Cargo.toml | 1 + src/lib.rs | 4 ++ src/validation/func.rs | 23 ++------- src/validation/mod.rs | 1 + src/validation/util.rs | 112 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 123 insertions(+), 18 deletions(-) create mode 100644 src/validation/util.rs diff --git a/Cargo.toml b/Cargo.toml index c85cb43..a383f85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ memory_units = "0.3.0" [dev-dependencies] wabt = "~0.2.2" +assert_matches = "1.1" [features] # 32-bit platforms are not supported and not tested. Use this flag if you really want to use diff --git a/src/lib.rs b/src/lib.rs index b36532c..34ca058 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,6 +98,10 @@ #[cfg(test)] extern crate wabt; +#[cfg(test)] +#[macro_use] +extern crate assert_matches; + extern crate parity_wasm; extern crate byteorder; extern crate memory_units as memory_units_crate; diff --git a/src/validation/func.rs b/src/validation/func.rs index 1145644..b15acc1 100644 --- a/src/validation/func.rs +++ b/src/validation/func.rs @@ -1,11 +1,11 @@ use std::u32; -use std::iter::repeat; use std::collections::HashMap; use parity_wasm::elements::{Opcode, BlockType, ValueType, TableElementType, Func, FuncBody}; use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX}; use validation::context::ModuleContext; use validation::Error; +use validation::util::Locals; use common::stack::StackWithLimit; use common::{BlockFrame, BlockFrameType}; @@ -22,7 +22,7 @@ struct FunctionValidationContext<'a> { /// Current instruction position. position: usize, /// Local variables. - locals: &'a [ValueType], + locals: Locals<'a>, /// Value stack. value_stack: StackWithLimit, /// Frame stack. @@ -62,19 +62,9 @@ impl Validator { ) -> Result, Error> { let (params, result_ty) = module.require_function_type(func.type_ref())?; - // locals = (params + vars) - let mut locals = params.to_vec(); - locals.extend( - body.locals() - .iter() - .flat_map(|l| repeat(l.value_type()) - .take(l.count() as usize) - ), - ); - let mut context = FunctionValidationContext::new( &module, - &locals, + Locals::new(params, body.locals()), DEFAULT_VALUE_STACK_LIMIT, DEFAULT_FRAME_STACK_LIMIT, result_ty, @@ -585,7 +575,7 @@ impl Validator { impl<'a> FunctionValidationContext<'a> { fn new( module: &'a ModuleContext, - locals: &'a [ValueType], + locals: Locals<'a>, value_stack_limit: usize, frame_stack_limit: usize, return_type: BlockType, @@ -707,10 +697,7 @@ impl<'a> FunctionValidationContext<'a> { } fn require_local(&self, idx: u32) -> Result { - self.locals.get(idx as usize) - .cloned() - .map(Into::into) - .ok_or(Error(format!("Trying to access local with index {} when there are only {} locals", idx, self.locals.len()))) + Ok(self.locals.type_of_local(idx).map(StackValueType::from)?) } fn into_labels(self) -> HashMap { diff --git a/src/validation/mod.rs b/src/validation/mod.rs index 268f420..3244173 100644 --- a/src/validation/mod.rs +++ b/src/validation/mod.rs @@ -12,6 +12,7 @@ use memory_units::Pages; mod context; mod func; +mod util; #[cfg(test)] mod tests; diff --git a/src/validation/util.rs b/src/validation/util.rs new file mode 100644 index 0000000..a219e4c --- /dev/null +++ b/src/validation/util.rs @@ -0,0 +1,112 @@ +use parity_wasm::elements::{Local, ValueType}; +use validation::Error; + +/// Locals are the concatenation of a slice of function parameters +/// with function declared local variables. +/// +/// Local variables are given in the form of groups represented by pairs +/// of a value_type and a count. +#[derive(Debug)] +pub struct Locals<'a> { + params: &'a [ValueType], + local_groups: &'a [Local], +} + +impl<'a> Locals<'a> { + pub fn new(params: &'a [ValueType], local_groups: &'a [Local]) -> Locals<'a> { + Locals { + params, + local_groups, + } + } + + /// Returns the type of a local variable (either a declared local or a param). + /// + /// Returns `Err` in the case of overflow or when idx falls out of range. + pub fn type_of_local(&self, idx: u32) -> Result { + if let Some(param) = self.params.get(idx as usize) { + return Ok(*param); + } + + // 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; + for locals_group in self.local_groups { + let end_idx = start_idx + .checked_add(locals_group.count()) + .ok_or_else(|| Error(String::from("Locals range not in 32-bit range")))?; + + if idx >= start_idx && idx < end_idx { + return Ok(locals_group.value_type()); + } + + start_idx = end_idx; + } + + // We didn't find anything, that's an error. + // At this moment `start_idx` should hold the count of all locals + // (since it's either set to the `end_idx` or equal to `params.len()`) + let total_count = start_idx; + + Err(Error(format!( + "Trying to access local with index {} when there are only {} locals", + idx, total_count + ))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + 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); + + assert_matches!(locals.type_of_local(0), Ok(ValueType::I32)); + assert_matches!(locals.type_of_local(1), Ok(ValueType::I64)); + assert_matches!(locals.type_of_local(2), Ok(ValueType::F32)); + assert_matches!(locals.type_of_local(3), Ok(ValueType::F32)); + assert_matches!(locals.type_of_local(4), Ok(ValueType::F64)); + assert_matches!(locals.type_of_local(5), Ok(ValueType::F64)); + assert_matches!(locals.type_of_local(6), Err(_)); + } + + #[test] + fn locals_no_declared_locals() { + let params = vec![ValueType::I32]; + let locals = Locals::new(¶ms, &[]); + + assert_matches!(locals.type_of_local(0), Ok(ValueType::I32)); + assert_matches!(locals.type_of_local(1), Err(_)); + } + + #[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); + + assert_matches!(locals.type_of_local(0), Ok(ValueType::I32)); + assert_matches!(locals.type_of_local(1), Ok(ValueType::I32)); + assert_matches!(locals.type_of_local(2), Ok(ValueType::I64)); + assert_matches!(locals.type_of_local(3), Ok(ValueType::I64)); + assert_matches!(locals.type_of_local(4), Ok(ValueType::I64)); + assert_matches!(locals.type_of_local(5), Err(_)); + } + + #[test] + fn locals_u32_overflow() { + let local_groups = vec![ + 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) + ); + assert_matches!(locals.type_of_local(u32::max_value()), Err(_)); + } +} From b95e11c4145efd40f41ace3f9b14fa9f8f170c35 Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Fri, 20 Apr 2018 17:59:16 +0300 Subject: [PATCH 04/28] Bump version to 0.1.3. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a383f85..3ae5d41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasmi" -version = "0.1.2" +version = "0.1.3" authors = ["Nikolay Volf ", "Svyatoslav Nikolsky ", "Sergey Pepyakin "] license = "MIT/Apache-2.0" readme = "README.md" From 3890dd379f0ceb5d7f147c5d688516c928ef4fab Mon Sep 17 00:00:00 2001 From: Jef Date: Wed, 25 Apr 2018 09:18:14 +0200 Subject: [PATCH 05/28] Preserve signalling bit in NaNs (#87) * Preserve signalling bit in NaNs * Fix warnings --- Cargo.toml | 9 +- examples/invoke.rs | 4 +- src/lib.rs | 8 +- src/memory.rs | 12 + src/module.rs | 2 +- src/runner.rs | 133 ++++----- src/value.rs | 200 +++++++++++--- tests/spec/run.rs | 658 +++++++++++++++++++++++---------------------- 8 files changed, 591 insertions(+), 435 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3ae5d41..f3c9faf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,13 +14,8 @@ exclude = [ "/res/*", "/tests/*", "/fuzz/*" ] parity-wasm = "0.27" byteorder = "1.0" memory_units = "0.3.0" +nan-preserving-float = "0.1.0" [dev-dependencies] -wabt = "~0.2.2" assert_matches = "1.1" - -[features] -# 32-bit platforms are not supported and not tested. Use this flag if you really want to use -# wasmi on these platforms. -# See https://github.com/pepyakin/wasmi/issues/43 -opt-in-32bit = [] +wabt = "^0.2.3" diff --git a/examples/invoke.rs b/examples/invoke.rs index f037287..2b7dd99 100644 --- a/examples/invoke.rs +++ b/examples/invoke.rs @@ -64,8 +64,8 @@ fn main() { function_type.params().iter().enumerate().map(|(i, value)| match value { &ValueType::I32 => RuntimeValue::I32(program_args[i].parse::().expect(&format!("Can't parse arg #{} as i32", program_args[i]))), &ValueType::I64 => RuntimeValue::I64(program_args[i].parse::().expect(&format!("Can't parse arg #{} as i64", program_args[i]))), - &ValueType::F32 => RuntimeValue::F32(program_args[i].parse::().expect(&format!("Can't parse arg #{} as f32", program_args[i]))), - &ValueType::F64 => RuntimeValue::F64(program_args[i].parse::().expect(&format!("Can't parse arg #{} as f64", program_args[i]))), + &ValueType::F32 => RuntimeValue::F32(program_args[i].parse::().expect(&format!("Can't parse arg #{} as f32", program_args[i])).into()), + &ValueType::F64 => RuntimeValue::F64(program_args[i].parse::().expect(&format!("Can't parse arg #{} as f64", program_args[i])).into()), }).collect::>() }; diff --git a/src/lib.rs b/src/lib.rs index 34ca058..b9a72b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -105,13 +105,7 @@ extern crate assert_matches; extern crate parity_wasm; extern crate byteorder; extern crate memory_units as memory_units_crate; - -#[cfg(all(not(feature = "opt-in-32bit"), target_pointer_width = "32"))] -compile_error! { -"32-bit targets are not supported at the moment. -You can use 'opt-in-32bit' feature. -See https://github.com/pepyakin/wasmi/issues/43" -} +extern crate nan_preserving_float; use std::fmt; use std::error; diff --git a/src/memory.rs b/src/memory.rs index 1f269f1..d120741 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -369,6 +369,7 @@ mod tests { #[test] fn alloc() { + #[cfg(target_pointer_width = "64")] let fixtures = &[ (0, None, true), (0, Some(0), true), @@ -381,6 +382,17 @@ mod tests { (65536, Some(0), false), (65536, None, true), ]; + + #[cfg(target_pointer_width = "32")] + let fixtures = &[ + (0, None, true), + (0, Some(0), true), + (1, None, true), + (1, Some(1), true), + (0, Some(1), true), + (1, Some(0), false), + ]; + for (index, &(initial, maybe_max, expected_ok)) in fixtures.iter().enumerate() { let initial: Pages = Pages(initial); let maximum: Option = maybe_max.map(|m| Pages(m)); diff --git a/src/module.rs b/src/module.rs index ba94bba..820e66a 100644 --- a/src/module.rs +++ b/src/module.rs @@ -420,7 +420,7 @@ impl ModuleInstance { // This check is not only for bailing out early, but also to check the case when // segment consist of 0 members. - if offset_val as usize + element_segment.members().len() > table_inst.current_size() as usize { + if offset_val as u64 + element_segment.members().len() as u64 > table_inst.current_size() as u64 { return Err( Error::Instantiation("elements segment does not fit".to_string()) ); diff --git a/src/runner.rs b/src/runner.rs index f075d21..51c9661 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -16,6 +16,7 @@ use host::Externals; use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX, BlockFrame, BlockFrameType}; use common::stack::StackWithLimit; use memory_units::Pages; +use nan_preserving_float::{F32, F64}; /// Maximum number of entries in value stack. pub const DEFAULT_VALUE_STACK_LIMIT: usize = 16384; @@ -196,8 +197,8 @@ impl<'a, E: Externals> Interpreter<'a, E> { &Opcode::I32Load(align, offset) => self.run_load::(context, align, offset), &Opcode::I64Load(align, offset) => self.run_load::(context, align, offset), - &Opcode::F32Load(align, offset) => self.run_load::(context, align, offset), - &Opcode::F64Load(align, offset) => self.run_load::(context, align, offset), + &Opcode::F32Load(align, offset) => self.run_load::(context, align, offset), + &Opcode::F64Load(align, offset) => self.run_load::(context, align, offset), &Opcode::I32Load8S(align, offset) => self.run_load_extend::(context, align, offset), &Opcode::I32Load8U(align, offset) => self.run_load_extend::(context, align, offset), &Opcode::I32Load16S(align, offset) => self.run_load_extend::(context, align, offset), @@ -211,8 +212,8 @@ impl<'a, E: Externals> Interpreter<'a, E> { &Opcode::I32Store(align, offset) => self.run_store::(context, align, offset), &Opcode::I64Store(align, offset) => self.run_store::(context, align, offset), - &Opcode::F32Store(align, offset) => self.run_store::(context, align, offset), - &Opcode::F64Store(align, offset) => self.run_store::(context, align, offset), + &Opcode::F32Store(align, offset) => self.run_store::(context, align, offset), + &Opcode::F64Store(align, offset) => self.run_store::(context, align, offset), &Opcode::I32Store8(align, offset) => self.run_store_wrap::(context, align, offset), &Opcode::I32Store16(align, offset) => self.run_store_wrap::(context, align, offset), &Opcode::I64Store8(align, offset) => self.run_store_wrap::(context, align, offset), @@ -251,19 +252,19 @@ impl<'a, E: Externals> Interpreter<'a, E> { &Opcode::I64GeS => self.run_gte::(context), &Opcode::I64GeU => self.run_gte::(context), - &Opcode::F32Eq => self.run_eq::(context), - &Opcode::F32Ne => self.run_ne::(context), - &Opcode::F32Lt => self.run_lt::(context), - &Opcode::F32Gt => self.run_gt::(context), - &Opcode::F32Le => self.run_lte::(context), - &Opcode::F32Ge => self.run_gte::(context), + &Opcode::F32Eq => self.run_eq::(context), + &Opcode::F32Ne => self.run_ne::(context), + &Opcode::F32Lt => self.run_lt::(context), + &Opcode::F32Gt => self.run_gt::(context), + &Opcode::F32Le => self.run_lte::(context), + &Opcode::F32Ge => self.run_gte::(context), - &Opcode::F64Eq => self.run_eq::(context), - &Opcode::F64Ne => self.run_ne::(context), - &Opcode::F64Lt => self.run_lt::(context), - &Opcode::F64Gt => self.run_gt::(context), - &Opcode::F64Le => self.run_lte::(context), - &Opcode::F64Ge => self.run_gte::(context), + &Opcode::F64Eq => self.run_eq::(context), + &Opcode::F64Ne => self.run_ne::(context), + &Opcode::F64Lt => self.run_lt::(context), + &Opcode::F64Gt => self.run_gt::(context), + &Opcode::F64Le => self.run_lte::(context), + &Opcode::F64Ge => self.run_gte::(context), &Opcode::I32Clz => self.run_clz::(context), &Opcode::I32Ctz => self.run_ctz::(context), @@ -303,62 +304,62 @@ impl<'a, E: Externals> Interpreter<'a, E> { &Opcode::I64Rotl => self.run_rotl::(context), &Opcode::I64Rotr => self.run_rotr::(context), - &Opcode::F32Abs => self.run_abs::(context), - &Opcode::F32Neg => self.run_neg::(context), - &Opcode::F32Ceil => self.run_ceil::(context), - &Opcode::F32Floor => self.run_floor::(context), - &Opcode::F32Trunc => self.run_trunc::(context), - &Opcode::F32Nearest => self.run_nearest::(context), - &Opcode::F32Sqrt => self.run_sqrt::(context), - &Opcode::F32Add => self.run_add::(context), - &Opcode::F32Sub => self.run_sub::(context), - &Opcode::F32Mul => self.run_mul::(context), - &Opcode::F32Div => self.run_div::(context), - &Opcode::F32Min => self.run_min::(context), - &Opcode::F32Max => self.run_max::(context), - &Opcode::F32Copysign => self.run_copysign::(context), + &Opcode::F32Abs => self.run_abs::(context), + &Opcode::F32Neg => self.run_neg::(context), + &Opcode::F32Ceil => self.run_ceil::(context), + &Opcode::F32Floor => self.run_floor::(context), + &Opcode::F32Trunc => self.run_trunc::(context), + &Opcode::F32Nearest => self.run_nearest::(context), + &Opcode::F32Sqrt => self.run_sqrt::(context), + &Opcode::F32Add => self.run_add::(context), + &Opcode::F32Sub => self.run_sub::(context), + &Opcode::F32Mul => self.run_mul::(context), + &Opcode::F32Div => self.run_div::(context), + &Opcode::F32Min => self.run_min::(context), + &Opcode::F32Max => self.run_max::(context), + &Opcode::F32Copysign => self.run_copysign::(context), - &Opcode::F64Abs => self.run_abs::(context), - &Opcode::F64Neg => self.run_neg::(context), - &Opcode::F64Ceil => self.run_ceil::(context), - &Opcode::F64Floor => self.run_floor::(context), - &Opcode::F64Trunc => self.run_trunc::(context), - &Opcode::F64Nearest => self.run_nearest::(context), - &Opcode::F64Sqrt => self.run_sqrt::(context), - &Opcode::F64Add => self.run_add::(context), - &Opcode::F64Sub => self.run_sub::(context), - &Opcode::F64Mul => self.run_mul::(context), - &Opcode::F64Div => self.run_div::(context), - &Opcode::F64Min => self.run_min::(context), - &Opcode::F64Max => self.run_max::(context), - &Opcode::F64Copysign => self.run_copysign::(context), + &Opcode::F64Abs => self.run_abs::(context), + &Opcode::F64Neg => self.run_neg::(context), + &Opcode::F64Ceil => self.run_ceil::(context), + &Opcode::F64Floor => self.run_floor::(context), + &Opcode::F64Trunc => self.run_trunc::(context), + &Opcode::F64Nearest => self.run_nearest::(context), + &Opcode::F64Sqrt => self.run_sqrt::(context), + &Opcode::F64Add => self.run_add::(context), + &Opcode::F64Sub => self.run_sub::(context), + &Opcode::F64Mul => self.run_mul::(context), + &Opcode::F64Div => self.run_div::(context), + &Opcode::F64Min => self.run_min::(context), + &Opcode::F64Max => self.run_max::(context), + &Opcode::F64Copysign => self.run_copysign::(context), &Opcode::I32WrapI64 => self.run_wrap::(context), - &Opcode::I32TruncSF32 => self.run_trunc_to_int::(context), - &Opcode::I32TruncUF32 => self.run_trunc_to_int::(context), - &Opcode::I32TruncSF64 => self.run_trunc_to_int::(context), - &Opcode::I32TruncUF64 => self.run_trunc_to_int::(context), + &Opcode::I32TruncSF32 => self.run_trunc_to_int::(context), + &Opcode::I32TruncUF32 => self.run_trunc_to_int::(context), + &Opcode::I32TruncSF64 => self.run_trunc_to_int::(context), + &Opcode::I32TruncUF64 => self.run_trunc_to_int::(context), &Opcode::I64ExtendSI32 => self.run_extend::(context), &Opcode::I64ExtendUI32 => self.run_extend::(context), - &Opcode::I64TruncSF32 => self.run_trunc_to_int::(context), - &Opcode::I64TruncUF32 => self.run_trunc_to_int::(context), - &Opcode::I64TruncSF64 => self.run_trunc_to_int::(context), - &Opcode::I64TruncUF64 => self.run_trunc_to_int::(context), - &Opcode::F32ConvertSI32 => self.run_extend::(context), - &Opcode::F32ConvertUI32 => self.run_extend::(context), - &Opcode::F32ConvertSI64 => self.run_wrap::(context), - &Opcode::F32ConvertUI64 => self.run_wrap::(context), - &Opcode::F32DemoteF64 => self.run_wrap::(context), - &Opcode::F64ConvertSI32 => self.run_extend::(context), - &Opcode::F64ConvertUI32 => self.run_extend::(context), - &Opcode::F64ConvertSI64 => self.run_extend::(context), - &Opcode::F64ConvertUI64 => self.run_extend::(context), - &Opcode::F64PromoteF32 => self.run_extend::(context), + &Opcode::I64TruncSF32 => self.run_trunc_to_int::(context), + &Opcode::I64TruncUF32 => self.run_trunc_to_int::(context), + &Opcode::I64TruncSF64 => self.run_trunc_to_int::(context), + &Opcode::I64TruncUF64 => self.run_trunc_to_int::(context), + &Opcode::F32ConvertSI32 => self.run_extend::(context), + &Opcode::F32ConvertUI32 => self.run_extend::(context), + &Opcode::F32ConvertSI64 => self.run_wrap::(context), + &Opcode::F32ConvertUI64 => self.run_wrap::(context), + &Opcode::F32DemoteF64 => self.run_wrap::(context), + &Opcode::F64ConvertSI32 => self.run_extend::(context), + &Opcode::F64ConvertUI32 => self.run_extend::(context), + &Opcode::F64ConvertSI64 => self.run_extend::(context), + &Opcode::F64ConvertUI64 => self.run_extend::(context), + &Opcode::F64PromoteF32 => self.run_extend::(context), - &Opcode::I32ReinterpretF32 => self.run_reinterpret::(context), - &Opcode::I64ReinterpretF64 => self.run_reinterpret::(context), - &Opcode::F32ReinterpretI32 => self.run_reinterpret::(context), - &Opcode::F64ReinterpretI64 => self.run_reinterpret::(context), + &Opcode::I32ReinterpretF32 => self.run_reinterpret::(context), + &Opcode::I64ReinterpretF64 => self.run_reinterpret::(context), + &Opcode::F32ReinterpretI32 => self.run_reinterpret::(context), + &Opcode::F64ReinterpretI64 => self.run_reinterpret::(context), } } diff --git a/src/value.rs b/src/value.rs index 892e4df..08cdff2 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,6 +1,7 @@ -use std::{i32, i64, u32, u64, f32}; -use std::io; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use nan_preserving_float::{F32, F64}; +use std::io; +use std::{f32, i32, i64, u32, u64}; use TrapKind; #[derive(Debug)] @@ -22,9 +23,9 @@ pub enum RuntimeValue { /// Value of 64-bit signed or unsigned integer. I64(i64), /// Value of 32-bit IEEE 754-2008 floating point number. - F32(f32), + F32(F32), /// Value of 64-bit IEEE 754-2008 floating point number. - F64(f64), + F64(F64), } /// Trait for creating value from a [`RuntimeValue`]. @@ -136,19 +137,19 @@ impl RuntimeValue { match value_type { ::types::ValueType::I32 => RuntimeValue::I32(0), ::types::ValueType::I64 => RuntimeValue::I64(0), - ::types::ValueType::F32 => RuntimeValue::F32(0f32), - ::types::ValueType::F64 => RuntimeValue::F64(0f64), + ::types::ValueType::F32 => RuntimeValue::F32(0f32.into()), + ::types::ValueType::F64 => RuntimeValue::F64(0f64.into()), } } /// Creates new value by interpreting passed u32 as f32. pub fn decode_f32(val: u32) -> Self { - RuntimeValue::F32(f32::from_bits(val)) + RuntimeValue::F32(F32::from_bits(val)) } /// Creates new value by interpreting passed u64 as f64. pub fn decode_f64(val: u64) -> Self { - RuntimeValue::F64(f64::from_bits(val)) + RuntimeValue::F64(F64::from_bits(val)) } /// Get variable type for this value. @@ -197,14 +198,14 @@ impl From for RuntimeValue { } } -impl From for RuntimeValue { - fn from(val: f32) -> Self { +impl From for RuntimeValue { + fn from(val: F32) -> Self { RuntimeValue::F32(val) } } -impl From for RuntimeValue { - fn from(val: f64) -> Self { +impl From for RuntimeValue { + fn from(val: F64) -> Self { RuntimeValue::F64(val) } } @@ -214,7 +215,7 @@ macro_rules! impl_from_runtime_value { impl FromRuntimeValue for $into { fn from_runtime_value(val: RuntimeValue) -> Option { match val { - RuntimeValue::$expected_rt_ty(val) => Some(val as $into), + RuntimeValue::$expected_rt_ty(val) => Some(val.transmute_into()), _ => None, } } @@ -237,19 +238,26 @@ impl FromRuntimeValue for bool { impl_from_runtime_value!(I32, i32); impl_from_runtime_value!(I64, i64); -impl_from_runtime_value!(F32, f32); -impl_from_runtime_value!(F64, f64); +impl_from_runtime_value!(F32, F32); +impl_from_runtime_value!(F64, F64); impl_from_runtime_value!(I32, u32); impl_from_runtime_value!(I64, u64); macro_rules! impl_wrap_into { - ($from: ident, $into: ident) => { + ($from:ident, $into:ident) => { impl WrapInto<$into> for $from { fn wrap_into(self) -> $into { self as $into } } - } + }; + ($from:ident, $intermediate:ident, $into:ident) => { + impl WrapInto<$into> for $from { + fn wrap_into(self) -> $into { + $into::from(self as $intermediate) + } + } + }; } impl_wrap_into!(i32, i8); @@ -257,13 +265,19 @@ impl_wrap_into!(i32, i16); impl_wrap_into!(i64, i8); impl_wrap_into!(i64, i16); impl_wrap_into!(i64, i32); -impl_wrap_into!(i64, f32); -impl_wrap_into!(u64, f32); +impl_wrap_into!(i64, f32, F32); +impl_wrap_into!(u64, f32, F32); // Casting from an f64 to an f32 will produce the closest possible value (rounding strategy unspecified) // NOTE: currently this will cause Undefined Behavior if the value is finite but larger or smaller than the // largest or smallest finite value representable by f32. This is a bug and will be fixed. impl_wrap_into!(f64, f32); +impl WrapInto for F64 { + fn wrap_into(self) -> F32 { + (f64::from(self) as f32).into() + } +} + macro_rules! impl_try_truncate_into { ($from: ident, $into: ident) => { impl TryTruncateInto<$into, TrapKind> for $from { @@ -284,7 +298,14 @@ macro_rules! impl_try_truncate_into { Ok(self as $into) } } - } + }; + ($from:ident, $intermediate:ident, $into:ident) => { + impl TryTruncateInto<$into, TrapKind> for $from { + fn try_truncate_into(self) -> Result<$into, TrapKind> { + $intermediate::from(self).try_truncate_into() + } + } + }; } impl_try_truncate_into!(f32, i32); @@ -295,15 +316,30 @@ impl_try_truncate_into!(f32, u32); impl_try_truncate_into!(f32, u64); impl_try_truncate_into!(f64, u32); impl_try_truncate_into!(f64, u64); +impl_try_truncate_into!(F32, f32, i32); +impl_try_truncate_into!(F32, f32, i64); +impl_try_truncate_into!(F64, f64, i32); +impl_try_truncate_into!(F64, f64, i64); +impl_try_truncate_into!(F32, f32, u32); +impl_try_truncate_into!(F32, f32, u64); +impl_try_truncate_into!(F64, f64, u32); +impl_try_truncate_into!(F64, f64, u64); macro_rules! impl_extend_into { - ($from: ident, $into: ident) => { + ($from:ident, $into:ident) => { impl ExtendInto<$into> for $from { fn extend_into(self) -> $into { self as $into } } - } + }; + ($from:ident, $intermediate:ident, $into:ident) => { + impl ExtendInto<$into> for $from { + fn extend_into(self) -> $into { + $into::from(self as $intermediate) + } + } + }; } impl_extend_into!(i8, i32); @@ -325,6 +361,20 @@ impl_extend_into!(i64, f64); impl_extend_into!(u64, f64); impl_extend_into!(f32, f64); +impl_extend_into!(i32, f32, F32); +impl_extend_into!(i32, f64, F64); +impl_extend_into!(u32, f32, F32); +impl_extend_into!(u32, f64, F64); +impl_extend_into!(i64, f64, F64); +impl_extend_into!(u64, f64, F64); +impl_extend_into!(f32, f64, F64); + +impl ExtendInto for F32 { + fn extend_into(self) -> F64 { + (f32::from(self) as f64).into() + } +} + macro_rules! impl_transmute_into_self { ($type: ident) => { impl TransmuteInto<$type> for $type { @@ -339,6 +389,8 @@ impl_transmute_into_self!(i32); impl_transmute_into_self!(i64); impl_transmute_into_self!(f32); impl_transmute_into_self!(f64); +impl_transmute_into_self!(F32); +impl_transmute_into_self!(F64); macro_rules! impl_transmute_into_as { ($from: ident, $into: ident) => { @@ -357,6 +409,49 @@ impl_transmute_into_as!(u32, i32); impl_transmute_into_as!(i64, u64); impl_transmute_into_as!(u64, i64); +macro_rules! impl_transmute_into_npf { + ($npf:ident, $float:ident, $signed:ident, $unsigned:ident) => { + impl TransmuteInto<$float> for $npf { + fn transmute_into(self) -> $float { + self.into() + } + } + + impl TransmuteInto<$npf> for $float { + fn transmute_into(self) -> $npf { + self.into() + } + } + + impl TransmuteInto<$signed> for $npf { + fn transmute_into(self) -> $signed { + self.to_bits() as _ + } + } + + impl TransmuteInto<$unsigned> for $npf { + fn transmute_into(self) -> $unsigned { + self.to_bits() + } + } + + impl TransmuteInto<$npf> for $signed { + fn transmute_into(self) -> $npf { + $npf::from_bits(self as _) + } + } + + impl TransmuteInto<$npf> for $unsigned { + fn transmute_into(self) -> $npf { + $npf::from_bits(self) + } + } + }; +} + +impl_transmute_into_npf!(F32, f32, i32, u32); +impl_transmute_into_npf!(F64, f64, i64, u64); + impl TransmuteInto for f32 { fn transmute_into(self) -> i32 { self.to_bits() as i32 } } @@ -497,6 +592,26 @@ impl LittleEndianConvert for f64 { } } +impl LittleEndianConvert for F32 { + fn into_little_endian(self) -> Vec { + (self.to_bits() as i32).into_little_endian() + } + + fn from_little_endian(buffer: &[u8]) -> Result { + i32::from_little_endian(buffer).map(|val| Self::from_bits(val as _)) + } +} + +impl LittleEndianConvert for F64 { + fn into_little_endian(self) -> Vec { + (self.to_bits() as i64).into_little_endian() + } + + fn from_little_endian(buffer: &[u8]) -> Result { + i64::from_little_endian(buffer).map(|val| Self::from_bits(val as _)) + } +} + macro_rules! impl_integer_arithmetic_ops { ($type: ident) => { impl ArithmeticOps<$type> for $type { @@ -538,6 +653,8 @@ macro_rules! impl_float_arithmetic_ops { impl_float_arithmetic_ops!(f32); impl_float_arithmetic_ops!(f64); +impl_float_arithmetic_ops!(F32); +impl_float_arithmetic_ops!(F64); macro_rules! impl_integer { ($type: ident) => { @@ -561,13 +678,26 @@ impl_integer!(i64); impl_integer!(u64); macro_rules! impl_float { - ($type: ident, $int_type: ident) => { + ($type:ident, $int_type:ident) => { + impl_float!($type, $type, $int_type); + }; + ($type:ident, $intermediate:ident, $int_type:ident) => { impl Float<$type> for $type { - fn abs(self) -> $type { self.abs() } - fn floor(self) -> $type { self.floor() } - fn ceil(self) -> $type { self.ceil() } - fn trunc(self) -> $type { self.trunc() } - fn round(self) -> $type { self.round() } + fn abs(self) -> $type { + $intermediate::abs(self.into()).into() + } + fn floor(self) -> $type { + $intermediate::floor(self.into()).into() + } + fn ceil(self) -> $type { + $intermediate::ceil(self.into()).into() + } + fn trunc(self) -> $type { + $intermediate::trunc(self.into()).into() + } + fn round(self) -> $type { + $intermediate::round(self.into()).into() + } fn nearest(self) -> $type { let round = self.round(); if self.fract().abs() != 0.5 { @@ -583,12 +713,13 @@ macro_rules! impl_float { round } } - fn sqrt(self) -> $type { self.sqrt() } + fn sqrt(self) -> $type { + $intermediate::sqrt(self.into()).into() + } // This instruction corresponds to what is sometimes called "minNaN" in other languages. fn min(self, other: $type) -> $type { if self.is_nan() || other.is_nan() { - use std::$type; - return $type::NAN; + return ::std::$intermediate::NAN.into(); } self.min(other) @@ -596,8 +727,7 @@ macro_rules! impl_float { // This instruction corresponds to what is sometimes called "maxNaN" in other languages. fn max(self, other: $type) -> $type { if self.is_nan() || other.is_nan() { - use std::$type; - return $type::NAN; + return ::std::$intermediate::NAN.into(); } self.max(other) @@ -623,8 +753,10 @@ macro_rules! impl_float { } } } - } + }; } impl_float!(f32, i32); impl_float!(f64, i64); +impl_float!(F32, f32, i32); +impl_float!(F64, f64, i64); diff --git a/tests/spec/run.rs b/tests/spec/run.rs index cb6eb8c..1fe12c6 100644 --- a/tests/spec/run.rs +++ b/tests/spec/run.rs @@ -2,22 +2,20 @@ use std::collections::HashMap; -use wasmi::{ - Error as InterpreterError, Externals, FuncInstance, FuncRef, GlobalDescriptor, - GlobalInstance, GlobalRef, ImportResolver, ImportsBuilder, MemoryDescriptor, - MemoryInstance, MemoryRef, Module, ModuleImportResolver, ModuleInstance, ModuleRef, - RuntimeArgs, RuntimeValue, Signature, TableDescriptor, TableInstance, TableRef, Trap, -}; -use wasmi::memory_units::Pages; use wabt::script::{self, Action, Command, CommandKind, ScriptParser, Value}; +use wasmi::memory_units::Pages; +use wasmi::{Error as InterpreterError, Externals, FuncInstance, FuncRef, GlobalDescriptor, + GlobalInstance, GlobalRef, ImportResolver, ImportsBuilder, MemoryDescriptor, + MemoryInstance, MemoryRef, Module, ModuleImportResolver, ModuleInstance, ModuleRef, + RuntimeArgs, RuntimeValue, Signature, TableDescriptor, TableInstance, TableRef, Trap}; -fn spec_to_runtime_value(value: Value) -> RuntimeValue { - match value { - Value::I32(v) => RuntimeValue::I32(v), - Value::I64(v) => RuntimeValue::I64(v), - Value::F32(v) => RuntimeValue::F32(v), - Value::F64(v) => RuntimeValue::F64(v), - } +fn spec_to_runtime_value(val: Value) -> RuntimeValue { + match val { + Value::I32(v) => RuntimeValue::I32(v), + Value::I64(v) => RuntimeValue::I64(v), + Value::F32(v) => RuntimeValue::F32(v.into()), + Value::F64(v) => RuntimeValue::F64(v.into()), + } } #[derive(Debug)] @@ -49,15 +47,15 @@ struct SpecModule { } impl SpecModule { - fn new() -> Self { - SpecModule { - table: TableInstance::alloc(10, Some(20)).unwrap(), - memory: MemoryInstance::alloc(Pages(1), Some(Pages(2))).unwrap(), - global_i32: GlobalInstance::alloc(RuntimeValue::I32(666), false), - global_f32: GlobalInstance::alloc(RuntimeValue::F32(666.0), false), - global_f64: GlobalInstance::alloc(RuntimeValue::F64(666.0), false), - } - } + fn new() -> Self { + SpecModule { + table: TableInstance::alloc(10, Some(20)).unwrap(), + memory: MemoryInstance::alloc(Pages(1), Some(Pages(2))).unwrap(), + global_i32: GlobalInstance::alloc(RuntimeValue::I32(666), false), + global_f32: GlobalInstance::alloc(RuntimeValue::F32(666.0.into()), false), + global_f64: GlobalInstance::alloc(RuntimeValue::F64(666.0.into()), false), + } + } } const PRINT_FUNC_INDEX: usize = 0; @@ -121,341 +119,365 @@ impl ModuleImportResolver for SpecModule { _ => Err(InterpreterError::Instantiation(format!( "Unknown host global import {}", field_name - ))) + ))), } - } + } - fn resolve_memory( - &self, - field_name: &str, - _memory_type: &MemoryDescriptor, - ) -> Result { - if field_name == "memory" { - return Ok(self.memory.clone()); - } + fn resolve_memory( + &self, + field_name: &str, + _memory_type: &MemoryDescriptor, + ) -> Result { + if field_name == "memory" { + return Ok(self.memory.clone()); + } - Err(InterpreterError::Instantiation(format!( - "Unknown host memory import {}", - field_name - ))) - } + Err(InterpreterError::Instantiation(format!( + "Unknown host memory import {}", + field_name + ))) + } - fn resolve_table( - &self, - field_name: &str, - _table_type: &TableDescriptor, - ) -> Result { - if field_name == "table" { - return Ok(self.table.clone()); - } + fn resolve_table( + &self, + field_name: &str, + _table_type: &TableDescriptor, + ) -> Result { + if field_name == "table" { + return Ok(self.table.clone()); + } - Err(InterpreterError::Instantiation(format!( - "Unknown host table import {}", - field_name - ))) - } + Err(InterpreterError::Instantiation(format!( + "Unknown host table import {}", + field_name + ))) + } } struct SpecDriver { - spec_module: SpecModule, - instances: HashMap, - last_module: Option, + spec_module: SpecModule, + instances: HashMap, + last_module: Option, } impl SpecDriver { - fn new() -> SpecDriver { - SpecDriver { - spec_module: SpecModule::new(), - instances: HashMap::new(), - last_module: None, - } - } + fn new() -> SpecDriver { + SpecDriver { + spec_module: SpecModule::new(), + instances: HashMap::new(), + last_module: None, + } + } - fn spec_module(&mut self) -> &mut SpecModule { - &mut self.spec_module - } + fn spec_module(&mut self) -> &mut SpecModule { + &mut self.spec_module + } - fn add_module(&mut self, name: Option, module: ModuleRef) { - self.last_module = Some(module.clone()); - if let Some(name) = name { - self.instances.insert(name, module); - } - } + fn add_module(&mut self, name: Option, module: ModuleRef) { + self.last_module = Some(module.clone()); + if let Some(name) = name { + self.instances.insert(name, module); + } + } - fn module(&self, name: &str) -> Result { - self.instances.get(name).cloned().ok_or_else(|| { - InterpreterError::Instantiation(format!("Module not registered {}", name)) - }) - } + fn module(&self, name: &str) -> Result { + self.instances.get(name).cloned().ok_or_else(|| { + InterpreterError::Instantiation(format!("Module not registered {}", name)) + }) + } - fn module_or_last(&self, name: Option<&str>) -> Result { - match name { - Some(name) => self.module(name), - None => self.last_module - .clone() - .ok_or_else(|| InterpreterError::Instantiation("No modules registered".into())), - } - } + fn module_or_last(&self, name: Option<&str>) -> Result { + match name { + Some(name) => self.module(name), + None => self.last_module + .clone() + .ok_or_else(|| InterpreterError::Instantiation("No modules registered".into())), + } + } } impl ImportResolver for SpecDriver { - fn resolve_func( - &self, - module_name: &str, - field_name: &str, - func_type: &Signature, - ) -> Result { - if module_name == "spectest" { - self.spec_module.resolve_func(field_name, func_type) - } else { - self.module(module_name)? - .resolve_func(field_name, func_type) - } - } + fn resolve_func( + &self, + module_name: &str, + field_name: &str, + func_type: &Signature, + ) -> Result { + if module_name == "spectest" { + self.spec_module.resolve_func(field_name, func_type) + } else { + self.module(module_name)? + .resolve_func(field_name, func_type) + } + } - fn resolve_global( - &self, - module_name: &str, - field_name: &str, - global_type: &GlobalDescriptor, - ) -> Result { - if module_name == "spectest" { - self.spec_module.resolve_global(field_name, global_type) - } else { - self.module(module_name)? - .resolve_global(field_name, global_type) - } - } + fn resolve_global( + &self, + module_name: &str, + field_name: &str, + global_type: &GlobalDescriptor, + ) -> Result { + if module_name == "spectest" { + self.spec_module.resolve_global(field_name, global_type) + } else { + self.module(module_name)? + .resolve_global(field_name, global_type) + } + } - fn resolve_memory( - &self, - module_name: &str, - field_name: &str, - memory_type: &MemoryDescriptor, - ) -> Result { - if module_name == "spectest" { - self.spec_module.resolve_memory(field_name, memory_type) - } else { - self.module(module_name)? - .resolve_memory(field_name, memory_type) - } - } + fn resolve_memory( + &self, + module_name: &str, + field_name: &str, + memory_type: &MemoryDescriptor, + ) -> Result { + if module_name == "spectest" { + self.spec_module.resolve_memory(field_name, memory_type) + } else { + self.module(module_name)? + .resolve_memory(field_name, memory_type) + } + } - fn resolve_table( - &self, - module_name: &str, - field_name: &str, - table_type: &TableDescriptor, - ) -> Result { - if module_name == "spectest" { - self.spec_module.resolve_table(field_name, table_type) - } else { - self.module(module_name)? - .resolve_table(field_name, table_type) - } - } + fn resolve_table( + &self, + module_name: &str, + field_name: &str, + table_type: &TableDescriptor, + ) -> Result { + if module_name == "spectest" { + self.spec_module.resolve_table(field_name, table_type) + } else { + self.module(module_name)? + .resolve_table(field_name, table_type) + } + } } fn try_load_module(wasm: &[u8]) -> Result { - Module::from_buffer(wasm).map_err(|e| Error::Load(e.to_string())) + Module::from_buffer(wasm).map_err(|e| Error::Load(e.to_string())) } fn try_load(wasm: &[u8], spec_driver: &mut SpecDriver) -> Result<(), Error> { - let module = try_load_module(wasm)?; - let instance = ModuleInstance::new(&module, &ImportsBuilder::default())?; - instance - .run_start(spec_driver.spec_module()) - .map_err(|trap| Error::Start(trap))?; - Ok(()) + let module = try_load_module(wasm)?; + let instance = ModuleInstance::new(&module, &ImportsBuilder::default())?; + instance + .run_start(spec_driver.spec_module()) + .map_err(|trap| Error::Start(trap))?; + Ok(()) } -fn load_module(wasm: &[u8], name: &Option, spec_driver: &mut SpecDriver) -> Result { - let module = try_load_module(wasm)?; - let instance = ModuleInstance::new(&module, spec_driver) - .map_err(|e| Error::Load(e.to_string()))? - .run_start(spec_driver.spec_module()) - .map_err(|trap| Error::Start(trap))?; +fn load_module( + wasm: &[u8], + name: &Option, + spec_driver: &mut SpecDriver, +) -> Result { + let module = try_load_module(wasm)?; + let instance = ModuleInstance::new(&module, spec_driver) + .map_err(|e| Error::Load(e.to_string()))? + .run_start(spec_driver.spec_module()) + .map_err(|trap| Error::Start(trap))?; - let module_name = name.clone(); - spec_driver.add_module(module_name, instance.clone()); + let module_name = name.clone(); + spec_driver.add_module(module_name, instance.clone()); - Ok(instance) + Ok(instance) } fn run_action( - program: &mut SpecDriver, - action: &Action, + program: &mut SpecDriver, + action: &Action, ) -> Result, InterpreterError> { - match *action { - Action::Invoke { - ref module, - ref field, - ref args, - } => { - let module = program - .module_or_last(module.as_ref().map(|x| x.as_ref())) - .expect(&format!( - "Expected program to have loaded module {:?}", - module - )); - module.invoke_export( - field, - &args.iter() - .cloned() - .map(spec_to_runtime_value) - .collect::>(), - program.spec_module(), - ) - } - Action::Get { - ref module, - ref field, - .. - } => { - let module = program - .module_or_last(module.as_ref().map(|x| x.as_ref())) - .expect(&format!( - "Expected program to have loaded module {:?}", - module - )); - let global = module - .export_by_name(&field) - .ok_or_else(|| { - InterpreterError::Global(format!("Expected to have export with name {}", field)) - })? - .as_global() - .cloned() - .ok_or_else(|| { - InterpreterError::Global(format!("Expected export {} to be a global", field)) - })?; - Ok(Some(global.get())) - } - } + match *action { + Action::Invoke { + ref module, + ref field, + ref args, + } => { + let module = program + .module_or_last(module.as_ref().map(|x| x.as_ref())) + .expect(&format!( + "Expected program to have loaded module {:?}", + module + )); + let vec_args = args.iter() + .cloned() + .map(spec_to_runtime_value) + .collect::>(); + module.invoke_export(field, &vec_args, program.spec_module()) + } + Action::Get { + ref module, + ref field, + .. + } => { + let module = program + .module_or_last(module.as_ref().map(|x| x.as_ref())) + .expect(&format!( + "Expected program to have loaded module {:?}", + module + )); + let global = module + .export_by_name(&field) + .ok_or_else(|| { + InterpreterError::Global(format!("Expected to have export with name {}", field)) + })? + .as_global() + .cloned() + .ok_or_else(|| { + InterpreterError::Global(format!("Expected export {} to be a global", field)) + })?; + Ok(Some(global.get())) + } + } } pub fn spec(name: &str) { - println!("running test: {}", name); - try_spec(name).expect("Failed to run spec"); + println!("running test: {}", name); + try_spec(name).expect("Failed to run spec"); } fn try_spec(name: &str) -> Result<(), Error> { - let mut spec_driver = SpecDriver::new(); - let spec_script_path = format!("tests/spec/testsuite/{}.wast", name); - let mut parser = ScriptParser::from_file(spec_script_path).expect("Can't read spec script"); - while let Some(Command { kind, line }) = parser.next()? { - println!("Line {}:", line); - match kind { - CommandKind::Module { name, module, .. } => { - load_module(&module.into_vec()?, &name, &mut spec_driver).expect("Failed to load module"); - } - CommandKind::AssertReturn { action, expected } => { - let result = run_action(&mut spec_driver, &action); - match result { - Ok(result) => { - let spec_expected = expected - .iter() - .cloned() - .map(spec_to_runtime_value) - .collect::>(); - let actual_result = result.into_iter().collect::>(); - for (actual_result, spec_expected) in - actual_result.iter().zip(spec_expected.iter()) - { - assert_eq!(actual_result.value_type(), spec_expected.value_type()); - // f32::NAN != f32::NAN - match spec_expected { - &RuntimeValue::F32(val) if val.is_nan() => match actual_result { - &RuntimeValue::F32(val) => assert!(val.is_nan()), - _ => unreachable!(), // checked above that types are same - }, - &RuntimeValue::F64(val) if val.is_nan() => match actual_result { - &RuntimeValue::F64(val) => assert!(val.is_nan()), - _ => unreachable!(), // checked above that types are same - }, - spec_expected @ _ => assert_eq!(actual_result, spec_expected), - } - } - println!("assert_return at line {} - success", line); - } - Err(e) => { - panic!("Expected action to return value, got error: {:?}", e); - } - } - } - CommandKind::AssertReturnCanonicalNan { action } - | CommandKind::AssertReturnArithmeticNan { action } => { - let result = run_action(&mut spec_driver, &action); - match result { - Ok(result) => { - for actual_result in result.into_iter().collect::>() { - match actual_result { - RuntimeValue::F32(val) => if !val.is_nan() { - panic!("Expected nan value, got {:?}", val) - }, - RuntimeValue::F64(val) => if !val.is_nan() { - panic!("Expected nan value, got {:?}", val) - }, - val @ _ => { - panic!("Expected action to return float value, got {:?}", val) - } - } - } - println!("assert_return_nan at line {} - success", line); - } - Err(e) => { - panic!("Expected action to return value, got error: {:?}", e); - } - } - } - CommandKind::AssertExhaustion { action, .. } => { - let result = run_action(&mut spec_driver, &action); - match result { - Ok(result) => panic!("Expected exhaustion, got result: {:?}", result), - Err(e) => println!("assert_exhaustion at line {} - success ({:?})", line, e), - } - } - CommandKind::AssertTrap { action, .. } => { - let result = run_action(&mut spec_driver, &action); - match result { - Ok(result) => { - panic!( - "Expected action to result in a trap, got result: {:?}", - result - ); - } - Err(e) => { - println!("assert_trap at line {} - success ({:?})", line, e); - } - } - } - CommandKind::AssertInvalid { module, .. } - | CommandKind::AssertMalformed { module, .. } - | CommandKind::AssertUnlinkable { module, .. } => { - let module_load = try_load(&module.into_vec()?, &mut spec_driver); - match module_load { - Ok(_) => panic!("Expected invalid module definition, got some module!"), - Err(e) => println!("assert_invalid at line {} - success ({:?})", line, e), - } - } - CommandKind::AssertUninstantiable { module, .. } => { - match try_load(&module.into_vec()?, &mut spec_driver) { - Ok(_) => panic!("Expected error running start function at line {}", line), - Err(e) => println!("assert_uninstantiable - success ({:?})", e), - } - } - CommandKind::Register { name, as_name, .. } => { - let module = match spec_driver.module_or_last(name.as_ref().map(|x| x.as_ref())) { - Ok(module) => module, - Err(e) => panic!("No such module, at line {} - ({:?})", e, line), - }; - spec_driver.add_module(Some(as_name.clone()), module); - } - CommandKind::PerformAction(action) => match run_action(&mut spec_driver, &action) { - Ok(_) => {} - Err(e) => panic!("Failed to invoke action at line {}: {:?}", line, e), - }, - } - } + let mut spec_driver = SpecDriver::new(); + let spec_script_path = format!("tests/spec/testsuite/{}.wast", name); + let mut parser = ScriptParser::from_file(spec_script_path).expect("Can't read spec script"); + let mut errors = vec![]; - Ok(()) + while let Some(Command { kind, line }) = parser.next()? { + macro_rules! assert_eq { + ($a:expr, $b:expr) => {{ + let (a, b) = ($a, $b); + + if a != b { + errors.push(format!( + r#"ERROR (line {}): + expected: {:?} + got: {:?} +"#, + line, b, a, + )); + } + }}; + } + + match kind { + CommandKind::Module { name, module, .. } => { + load_module(&module.into_vec()?, &name, &mut spec_driver) + .expect("Failed to load module"); + } + CommandKind::AssertReturn { action, expected } => { + let result = run_action(&mut spec_driver, &action); + match result { + Ok(result) => { + let spec_expected = expected + .iter() + .cloned() + .map(spec_to_runtime_value) + .collect::>(); + let actual_result = result.into_iter().collect::>(); + for (actual_result, spec_expected) in + actual_result.iter().zip(spec_expected.iter()) + { + assert_eq!(actual_result.value_type(), spec_expected.value_type()); + // f32::NAN != f32::NAN + match spec_expected { + &RuntimeValue::F32(val) if val.is_nan() => match actual_result { + &RuntimeValue::F32(val) => assert!(val.is_nan()), + _ => unreachable!(), // checked above that types are same + }, + &RuntimeValue::F64(val) if val.is_nan() => match actual_result { + &RuntimeValue::F64(val) => assert!(val.is_nan()), + _ => unreachable!(), // checked above that types are same + }, + spec_expected @ _ => assert_eq!(actual_result, spec_expected), + } + } + } + Err(e) => { + panic!("Expected action to return value, got error: {:?}", e); + } + } + } + CommandKind::AssertReturnCanonicalNan { action } + | CommandKind::AssertReturnArithmeticNan { action } => { + let result = run_action(&mut spec_driver, &action); + match result { + Ok(result) => { + for actual_result in result.into_iter().collect::>() { + match actual_result { + RuntimeValue::F32(val) => if !val.is_nan() { + panic!("Expected nan value, got {:?}", val) + }, + RuntimeValue::F64(val) => if !val.is_nan() { + panic!("Expected nan value, got {:?}", val) + }, + val @ _ => { + panic!("Expected action to return float value, got {:?}", val) + } + } + } + } + Err(e) => { + panic!("Expected action to return value, got error: {:?}", e); + } + } + } + CommandKind::AssertExhaustion { action, .. } => { + let result = run_action(&mut spec_driver, &action); + match result { + Ok(result) => panic!("Expected exhaustion, got result: {:?}", result), + Err(_e) => {}, + } + } + CommandKind::AssertTrap { action, .. } => { + let result = run_action(&mut spec_driver, &action); + match result { + Ok(result) => { + panic!( + "Expected action to result in a trap, got result: {:?}", + result + ); + } + Err(_e) => {} + } + } + CommandKind::AssertInvalid { module, .. } + | CommandKind::AssertMalformed { module, .. } + | CommandKind::AssertUnlinkable { module, .. } => { + let module_load = try_load(&module.into_vec()?, &mut spec_driver); + match module_load { + Ok(_) => panic!("Expected invalid module definition, got some module!"), + Err(_e) => {}, + } + } + CommandKind::AssertUninstantiable { module, .. } => { + match try_load(&module.into_vec()?, &mut spec_driver) { + Ok(_) => panic!("Expected error running start function at line {}", line), + Err(_e) => {}, + } + } + CommandKind::Register { name, as_name, .. } => { + let module = match spec_driver.module_or_last(name.as_ref().map(|x| x.as_ref())) { + Ok(module) => module, + Err(e) => panic!("No such module, at line {} - ({:?})", e, line), + }; + spec_driver.add_module(Some(as_name.clone()), module); + } + CommandKind::PerformAction(action) => match run_action(&mut spec_driver, &action) { + Ok(_) => {} + Err(e) => panic!("Failed to invoke action at line {}: {:?}", line, e), + }, + } + } + + if !errors.is_empty() { + use std::fmt::Write; + let mut out = "\n".to_owned(); + for err in errors { + write!(out, "{}", err).expect("Error formatting errors"); + } + panic!(out); + } + + Ok(()) } From 89c3a9286f9f7ceebc76678cc379ca7cfdec7316 Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Wed, 25 Apr 2018 10:25:33 +0300 Subject: [PATCH 06/28] Bump version 0.2.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f3c9faf..f5fdf64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasmi" -version = "0.1.3" +version = "0.2.0" authors = ["Nikolay Volf ", "Svyatoslav Nikolsky ", "Sergey Pepyakin "] license = "MIT/Apache-2.0" readme = "README.md" From d926993c6c796ba09ffb70bf53c6b921b3c9acef Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Wed, 25 Apr 2018 18:00:11 +0300 Subject: [PATCH 07/28] Bump wabt version to 0.3 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f5fdf64..6014301 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,4 +18,4 @@ nan-preserving-float = "0.1.0" [dev-dependencies] assert_matches = "1.1" -wabt = "^0.2.3" +wabt = "0.3" From 6cf0ebc79e2b32a68982d77e4c3fda97ad5f81b0 Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Thu, 24 May 2018 16:31:15 +0300 Subject: [PATCH 08/28] Add a simple bench (#90) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add first bench * Refactor travis.yml * Use assert_matches! * sha3_256 → keccak256 --- .travis.yml | 13 ++++---- Cargo.toml | 2 +- benches/.gitignore | 1 + benches/Cargo.toml | 8 +++++ benches/build.rs | 32 ++++++++++++++++++++ benches/src/lib.rs | 43 +++++++++++++++++++++++++++ benches/wasm-kernel/.gitignore | 1 + benches/wasm-kernel/Cargo.toml | 16 ++++++++++ benches/wasm-kernel/src/lib.rs | 54 ++++++++++++++++++++++++++++++++++ doc.sh | 6 +--- 10 files changed, 164 insertions(+), 12 deletions(-) create mode 100644 benches/.gitignore create mode 100644 benches/Cargo.toml create mode 100644 benches/build.rs create mode 100644 benches/src/lib.rs create mode 100644 benches/wasm-kernel/.gitignore create mode 100644 benches/wasm-kernel/Cargo.toml create mode 100644 benches/wasm-kernel/src/lib.rs diff --git a/.travis.yml b/.travis.yml index 03c14d8..3cf1344 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,9 @@ sudo: required language: - rust - cpp +rust: + - nightly + - stable addons: apt: sources: @@ -12,18 +15,16 @@ addons: - g++-6 - cmake env: -- NIGHTLY_TOOLCHAIN=nightly-2018-02-05 +- CC=/usr/bin/gcc-6 CXX=/usr/bin/g++-6 install: # Install `cargo-deadlinks` unless it is currently installed. - command -v cargo-deadlinks &> /dev/null || cargo install cargo-deadlinks -# Install nightly toolchain. -- rustup toolchain install $NIGHTLY_TOOLCHAIN +- if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then rustup target add wasm32-unknown-unknown; fi script: -- export CC=/usr/bin/gcc-6 -- export CXX=/usr/bin/g++-6 # Make sure fuzz targets are not broken. -- rustup run $NIGHTLY_TOOLCHAIN cargo check --tests --manifest-path=fuzz/Cargo.toml +- if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo check --tests --manifest-path=fuzz/Cargo.toml; fi +- if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo check --benches --manifest-path=benches/Cargo.toml; fi - ./test.sh - ./doc.sh after_success: | diff --git a/Cargo.toml b/Cargo.toml index 6014301..b74d752 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/paritytech/wasmi" documentation = "https://paritytech.github.io/wasmi/" description = "WebAssembly interpreter" keywords = ["wasm", "webassembly", "bytecode", "interpreter"] -exclude = [ "/res/*", "/tests/*", "/fuzz/*" ] +exclude = [ "/res/*", "/tests/*", "/fuzz/*", "/benches/*" ] [dependencies] parity-wasm = "0.27" diff --git a/benches/.gitignore b/benches/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/benches/.gitignore @@ -0,0 +1 @@ +/target diff --git a/benches/Cargo.toml b/benches/Cargo.toml new file mode 100644 index 0000000..0df6e79 --- /dev/null +++ b/benches/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "benches" +version = "0.1.0" +authors = ["Sergey Pepyakin "] + +[dependencies] +wasmi = { path = ".." } +assert_matches = "1.2" diff --git a/benches/build.rs b/benches/build.rs new file mode 100644 index 0000000..9cb9ac1 --- /dev/null +++ b/benches/build.rs @@ -0,0 +1,32 @@ +use std::env; +use std::process; + + +fn main() { + println!("cargo:rerun-if-changed=./wasm-kernel/"); + + // The CARGO environment variable provides a path to the executable that + // runs this build process. + let cargo_bin = env::var("CARGO").expect("CARGO env variable should be defined"); + + // Build a release version of wasm-kernel. The code in the output wasm binary + // will be used in benchmarks. + let output = process::Command::new(cargo_bin) + .arg("build") + .arg("--target=wasm32-unknown-unknown") + .arg("--release") + .arg("--manifest-path=./wasm-kernel/Cargo.toml") + .arg("--verbose") + .output() + .expect("failed to execute `cargo`"); + + if !output.status.success() { + let msg = format!( + "status: {status}\nstdout: {stdout}\nstderr: {stderr}\n", + status=output.status, + stdout=String::from_utf8_lossy(&output.stdout), + stderr=String::from_utf8_lossy(&output.stderr), + ); + panic!("{}", msg); + } +} diff --git a/benches/src/lib.rs b/benches/src/lib.rs new file mode 100644 index 0000000..db01ccd --- /dev/null +++ b/benches/src/lib.rs @@ -0,0 +1,43 @@ +#![feature(test)] + +extern crate test; +extern crate wasmi; +#[macro_use] +extern crate assert_matches; + +use std::error; +use std::fs::File; +use wasmi::{ImportsBuilder, Module, ModuleInstance, NopExternals, RuntimeValue}; + +use test::Bencher; + +// Load a module from a file. +fn load_from_file(filename: &str) -> Result> { + use std::io::prelude::*; + let mut file = File::open(filename)?; + let mut buf = Vec::new(); + file.read_to_end(&mut buf)?; + Ok(Module::from_buffer(buf)?) +} + +#[bench] +fn bench_tiny_keccak(b: &mut Bencher) { + let wasm_kernel = load_from_file( + "./wasm-kernel/target/wasm32-unknown-unknown/release/wasm_kernel.wasm", + ).expect("failed to load wasm_kernel. Is `build.rs` broken?"); + + let instance = ModuleInstance::new(&wasm_kernel, &ImportsBuilder::default()) + .expect("failed to instantiate wasm module") + .assert_no_start(); + + let test_data_ptr = assert_matches!( + instance.invoke_export("prepare_tiny_keccak", &[], &mut NopExternals), + Ok(Some(v @ RuntimeValue::I32(_))) => v + ); + + b.iter(|| { + instance + .invoke_export("bench_tiny_keccak", &[test_data_ptr], &mut NopExternals) + .unwrap(); + }); +} diff --git a/benches/wasm-kernel/.gitignore b/benches/wasm-kernel/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/benches/wasm-kernel/.gitignore @@ -0,0 +1 @@ +/target diff --git a/benches/wasm-kernel/Cargo.toml b/benches/wasm-kernel/Cargo.toml new file mode 100644 index 0000000..f9717f5 --- /dev/null +++ b/benches/wasm-kernel/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "wasm-kernel" +version = "0.1.0" +authors = ["Sergey Pepyakin "] + +[lib] +crate-type = ["cdylib"] + +[dependencies] +tiny-keccak = "1.4.2" +rlibc = "1.0" + +[profile.release] +panic = "abort" +lto = true +opt-level = "z" diff --git a/benches/wasm-kernel/src/lib.rs b/benches/wasm-kernel/src/lib.rs new file mode 100644 index 0000000..39a6372 --- /dev/null +++ b/benches/wasm-kernel/src/lib.rs @@ -0,0 +1,54 @@ +#![no_std] +#![feature(lang_items)] +#![feature(core_intrinsics)] + +extern crate rlibc; +extern crate tiny_keccak; + +use tiny_keccak::Keccak; + +#[no_mangle] +#[lang = "panic_fmt"] +pub extern "C" fn panic_fmt( + _args: ::core::fmt::Arguments, + _file: &'static str, + _line: u32, + _col: u32, +) -> ! { + use core::intrinsics; + unsafe { + intrinsics::abort(); + } +} + +pub struct TinyKeccakTestData { + data: &'static [u8], + result: &'static mut [u8], +} + +#[no_mangle] +pub extern "C" fn prepare_tiny_keccak() -> *const TinyKeccakTestData { + static DATA: [u8; 4096] = [254u8; 4096]; + static mut RESULT: [u8; 32] = [0u8; 32]; + + static mut TEST_DATA: Option = None; + + unsafe { + if let None = TEST_DATA { + TEST_DATA = Some(TinyKeccakTestData { + data: &DATA, + result: &mut RESULT, + }); + } + TEST_DATA.as_ref().unwrap() as *const TinyKeccakTestData + } +} + +#[no_mangle] +pub extern "C" fn bench_tiny_keccak(test_data: *const TinyKeccakTestData) { + unsafe { + let mut keccak = Keccak::new_keccak256(); + keccak.update((*test_data).data); + keccak.finalize((*test_data).result); + } +} diff --git a/doc.sh b/doc.sh index 5148060..652db79 100755 --- a/doc.sh +++ b/doc.sh @@ -4,11 +4,7 @@ set -eux cd $(dirname $0) -if [ -s NIGHTLY_TOOLCHAIN ]; then - rustup run $NIGHTLY_TOOLCHAIN cargo doc -else - cargo doc -fi; +cargo doc # cargo-deadlinks will check any links in docs generated by `cargo doc`. # This is useful as rustdoc uses raw links which are error prone. From 724a32ad6052269c75fd61a817aa9cf0c74373d1 Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Thu, 31 May 2018 16:01:15 +0200 Subject: [PATCH 09/28] Provide direct access to the underlying buffer (#91) This allows zero-copy access to the linear memory. --- src/memory.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/src/memory.rs b/src/memory.rs index d120741..339a445 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -48,7 +48,7 @@ impl ::std::ops::Deref for MemoryRef { /// /// [`LINEAR_MEMORY_PAGE_SIZE`]: constant.LINEAR_MEMORY_PAGE_SIZE.html pub struct MemoryInstance { - /// Memofy limits. + /// Memory limits. limits: ResizableLimits, /// Linear memory buffer. buffer: RefCell>, @@ -315,7 +315,7 @@ impl MemoryInstance { Ok(()) } - /// Fill memory region with a specified value. + /// Fill the memory region with the specified value. /// /// Semantically equivalent to `memset`. /// @@ -330,7 +330,7 @@ impl MemoryInstance { Ok(()) } - /// Fill specified memory region with zeroes. + /// Fill the specified memory region with zeroes. /// /// # Errors /// @@ -338,6 +338,35 @@ impl MemoryInstance { pub fn zero(&self, offset: usize, len: usize) -> Result<(), Error> { self.clear(offset, 0, len) } + + /// Provides direct access to the underlying memory buffer. + /// + /// # Panics + /// + /// Any call that requires write access to memory (such as [`set`], [`clear`], etc) made within + /// the closure will panic. Proceed with caution. + /// + /// [`set`]: #method.get + /// [`clear`]: #method.set + pub fn with_direct_access R>(&self, f: F) -> R { + let buf = self.buffer.borrow(); + f(&*buf) + } + + /// Provides direct mutable access to the underlying memory buffer. + /// + /// # Panics + /// + /// Any calls that requires either read or write access to memory (such as [`get`], [`set`], [`copy`], etc) made + /// within the closure will panic. Proceed with caution. + /// + /// [`get`]: #method.get + /// [`set`]: #method.set + /// [`copy`]: #method.copy + pub fn with_direct_access_mut R>(&self, f: F) -> R { + let mut buf = self.buffer.borrow_mut(); + f(&mut *buf) + } } pub fn validate_memory(initial: Pages, maximum: Option) -> Result<(), String> { @@ -484,4 +513,27 @@ mod tests { assert_eq!(data, [17, 129]); } + + #[test] + fn zero_copy() { + let mem = MemoryInstance::alloc(Pages(1), None).unwrap(); + mem.with_direct_access_mut(|buf| { + assert_eq!(buf.len(), 65536); + buf[..10].copy_from_slice(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + }); + mem.with_direct_access(|buf| { + assert_eq!(buf.len(), 65536); + assert_eq!(&buf[..10], &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + }); + } + + #[should_panic] + #[test] + fn zero_copy_panics_on_nested_access() { + let mem = MemoryInstance::alloc(Pages(1), None).unwrap(); + let mem_inner = mem.clone(); + mem.with_direct_access(move |_| { + let _ = mem_inner.set(0, &[11, 12, 13]); + }); + } } From f305b3cd1f0c5b035e6f7034e632d22637b768f9 Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Tue, 12 Jun 2018 14:00:57 +0300 Subject: [PATCH 10/28] Fix wasm benches on nightly (#93) --- .travis.yml | 2 +- benches/wasm-kernel/src/lib.rs | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3cf1344..68a5cda 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ install: - command -v cargo-deadlinks &> /dev/null || cargo install cargo-deadlinks - if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then rustup target add wasm32-unknown-unknown; fi script: -# Make sure fuzz targets are not broken. +# Make sure nightly targets are not broken. - if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo check --tests --manifest-path=fuzz/Cargo.toml; fi - if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo check --benches --manifest-path=benches/Cargo.toml; fi - ./test.sh diff --git a/benches/wasm-kernel/src/lib.rs b/benches/wasm-kernel/src/lib.rs index 39a6372..ee172ff 100644 --- a/benches/wasm-kernel/src/lib.rs +++ b/benches/wasm-kernel/src/lib.rs @@ -1,6 +1,7 @@ #![no_std] #![feature(lang_items)] #![feature(core_intrinsics)] +#![feature(panic_implementation)] extern crate rlibc; extern crate tiny_keccak; @@ -8,13 +9,8 @@ extern crate tiny_keccak; use tiny_keccak::Keccak; #[no_mangle] -#[lang = "panic_fmt"] -pub extern "C" fn panic_fmt( - _args: ::core::fmt::Arguments, - _file: &'static str, - _line: u32, - _col: u32, -) -> ! { +#[panic_implementation] +pub fn panic_fmt(_info: &::core::panic::PanicInfo) -> ! { use core::intrinsics; unsafe { intrinsics::abort(); From 1702372696a4d693976adbfc682a11c2b94f2778 Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Tue, 12 Jun 2018 16:09:31 +0300 Subject: [PATCH 11/28] Define Instruction Set. --- src/instructions.rs | 241 ++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 2 files changed, 242 insertions(+) create mode 100644 src/instructions.rs diff --git a/src/instructions.rs b/src/instructions.rs new file mode 100644 index 0000000..8af67a9 --- /dev/null +++ b/src/instructions.rs @@ -0,0 +1,241 @@ +/// Enumeration of instruction set used by wasmi. +/// +/// The instruction set is mostly derived from Wasm. However, +/// there is a substantial difference. +/// +/// # Structured Stack Machine vs Traditional 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 traditional 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: +/// +/// ``` +/// 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. +/// +/// # 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. +enum Instruction { + /// Keep the specified amount of operands and then + /// drop the rest. + DropKeep { + drop: u32, + keep: u32, + }, + + /// Push a local variable or an argument from the specified depth. + GetLocal { + depth: u32 + }, + + /// Pop a value and put it in at the specified depth. + SetLocal { + depth: 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(u32), + BrIf(u32), + BrTable(Box<[u32]>, u32), + + Unreachable, + Return, + + Call(u32), + CallIndirect(u32, u8), + + Drop, + Select, + + GetGlobal(u32), + SetGlobal(u32), + + // All store/load instructions operate with 'memory immediates' + // which represented here as (flag, offset) tuple + I32Load(u32, u32), + I64Load(u32, u32), + F32Load(u32, u32), + F64Load(u32, u32), + I32Load8S(u32, u32), + I32Load8U(u32, u32), + I32Load16S(u32, u32), + I32Load16U(u32, u32), + I64Load8S(u32, u32), + I64Load8U(u32, u32), + I64Load16S(u32, u32), + I64Load16U(u32, u32), + I64Load32S(u32, u32), + I64Load32U(u32, u32), + I32Store(u32, u32), + I64Store(u32, u32), + F32Store(u32, u32), + F64Store(u32, u32), + I32Store8(u32, u32), + I32Store16(u32, u32), + I64Store8(u32, u32), + I64Store16(u32, u32), + I64Store32(u32, u32), + + CurrentMemory(u8), + GrowMemory(u8), + + 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, +} diff --git a/src/lib.rs b/src/lib.rs index b9a72b9..c99d1db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -356,6 +356,7 @@ mod imports; mod global; mod func; mod types; +mod instructions; #[cfg(test)] mod tests; From 5653e2809fdcb6eb3709fb91bfaf7b50e52138fc Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Tue, 12 Jun 2018 22:13:37 +0300 Subject: [PATCH 12/28] WIP --- src/common/mod.rs | 2 + src/func.rs | 7 +- src/instructions.rs | 241 ------- src/isa.rs | 256 ++++++++ src/lib.rs | 12 +- src/module.rs | 7 +- src/runner.rs | 8 +- src/validation/func.rs | 1396 +++++++++++++++++++++++++++++++++------- src/validation/mod.rs | 11 +- src/validation/util.rs | 15 + 10 files changed, 1462 insertions(+), 493 deletions(-) delete mode 100644 src/instructions.rs create mode 100644 src/isa.rs diff --git a/src/common/mod.rs b/src/common/mod.rs index 0801984..4b90dc9 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -7,6 +7,8 @@ pub const DEFAULT_MEMORY_INDEX: u32 = 0; /// Index of default table. pub const DEFAULT_TABLE_INDEX: u32 = 0; +// TODO: Move BlockFrame under validation. + /// Control stack frame. #[derive(Debug, Clone)] pub struct BlockFrame { diff --git a/src/func.rs b/src/func.rs index b72107d..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, Opcodes}; +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 opcodes: Opcodes, - pub labels: HashMap, + pub code: isa::Instructions, } diff --git a/src/instructions.rs b/src/instructions.rs deleted file mode 100644 index 8af67a9..0000000 --- a/src/instructions.rs +++ /dev/null @@ -1,241 +0,0 @@ -/// Enumeration of instruction set used by wasmi. -/// -/// The instruction set is mostly derived from Wasm. However, -/// there is a substantial difference. -/// -/// # Structured Stack Machine vs Traditional 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 traditional 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: -/// -/// ``` -/// 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. -/// -/// # 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. -enum Instruction { - /// Keep the specified amount of operands and then - /// drop the rest. - DropKeep { - drop: u32, - keep: u32, - }, - - /// Push a local variable or an argument from the specified depth. - GetLocal { - depth: u32 - }, - - /// Pop a value and put it in at the specified depth. - SetLocal { - depth: 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(u32), - BrIf(u32), - BrTable(Box<[u32]>, u32), - - Unreachable, - Return, - - Call(u32), - CallIndirect(u32, u8), - - Drop, - Select, - - GetGlobal(u32), - SetGlobal(u32), - - // All store/load instructions operate with 'memory immediates' - // which represented here as (flag, offset) tuple - I32Load(u32, u32), - I64Load(u32, u32), - F32Load(u32, u32), - F64Load(u32, u32), - I32Load8S(u32, u32), - I32Load8U(u32, u32), - I32Load16S(u32, u32), - I32Load16U(u32, u32), - I64Load8S(u32, u32), - I64Load8U(u32, u32), - I64Load16S(u32, u32), - I64Load16U(u32, u32), - I64Load32S(u32, u32), - I64Load32U(u32, u32), - I32Store(u32, u32), - I64Store(u32, u32), - F32Store(u32, u32), - F64Store(u32, u32), - I32Store8(u32, u32), - I32Store16(u32, u32), - I64Store8(u32, u32), - I64Store16(u32, u32), - I64Store32(u32, u32), - - CurrentMemory(u8), - GrowMemory(u8), - - 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, -} diff --git a/src/isa.rs b/src/isa.rs new file mode 100644 index 0000000..c01dc2b --- /dev/null +++ b/src/isa.rs @@ -0,0 +1,256 @@ +//! An instruction set used by wasmi. +//! +//! The instruction set is mostly derived from Wasm. However, +//! there is a substantial difference. +//! +//! # Structured Stack Machine vs Traditional 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 traditional 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: +//! +//! ``` +//! 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. +//! +//! # 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`. +//! + +#[derive(Debug, Clone)] +pub struct Target { + pub dst_pc: u32, + pub drop: u32, + pub keep: u8, +} + +#[allow(unused)] // TODO: Remove +#[derive(Debug, Clone)] +pub enum Instruction { + /// Push a local variable or an argument from the specified depth. + GetLocal { + depth: u32 + }, + + /// Pop a value and put it in at the specified depth. + SetLocal { + depth: u32 + }, + + /// Copy a value to the specified depth. + TeeLocal { depth: u32 }, + + /// Similar to the Wasm ones, but instead of a label depth + /// they specify direct PC. + Br(Target), + BrIfEqz(Target), + BrIfNez(Target), + + /// Last one is the default. + /// + /// Can be less than zero. + BrTable(Box<[Target]>), + + Unreachable, + Return, + + 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 c99d1db..c46eff5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -356,7 +356,7 @@ mod imports; mod global; mod func; mod types; -mod instructions; +mod isa; #[cfg(test)] mod tests; @@ -379,7 +379,7 @@ pub mod memory_units { /// Deserialized module prepared for instantiation. pub struct Module { - labels: HashMap>, + code_map: Vec, module: parity_wasm::elements::Module, } @@ -419,12 +419,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, }) } @@ -525,7 +525,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 820e66a..a3a9fa0 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(), - opcodes: 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 51c9661..83c7e16 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -84,7 +84,7 @@ impl<'a, E: Externals> Interpreter<'a, E> { 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)?; + function_context.push_frame(BlockFrameType::Function, return_type).map_err(Trap::new)?; } let function_return = self.do_run_function(&mut function_context, function_body.opcodes.elements(), &function_body.labels).map_err(Trap::new)?; @@ -127,7 +127,7 @@ impl<'a, E: Externals> Interpreter<'a, E> { } } - fn do_run_function(&mut self, function_context: &mut FunctionContext, function_body: &[Opcode], function_labels: &HashMap) -> Result { + fn do_run_function(&mut self, function_context: &mut FunctionContext, function_body: &[Opcode]) -> Result { loop { let instruction = &function_body[function_context.position]; @@ -400,7 +400,7 @@ impl<'a, E: Externals> Interpreter<'a, E> { Ok(InstructionOutcome::RunNextInstruction) } - fn run_else(&mut self, context: &mut FunctionContext, labels: &HashMap) -> Result { + fn run_else(&mut self, context: &mut FunctionContext) -> Result { let end_pos = labels[&context.position]; context.pop_frame(false)?; context.position = end_pos; @@ -1144,7 +1144,7 @@ impl FunctionContext { &self.frame_stack } - pub fn push_frame(&mut self, labels: &HashMap, frame_type: BlockFrameType, block_type: BlockType) -> Result<(), TrapKind> { + pub fn push_frame(&mut self, frame_type: BlockFrameType, block_type: BlockType) -> Result<(), TrapKind> { let begin_position = self.position; let branch_position = match frame_type { BlockFrameType::Function => usize::MAX, diff --git a/src/validation/func.rs b/src/validation/func.rs index b15acc1..700d732 100644 --- a/src/validation/func.rs +++ b/src/validation/func.rs @@ -8,13 +8,47 @@ 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; +/// 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, + // TODO: + branch_label: LabelId, + /// 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 { + /// 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, +} + /// Function validation context. struct FunctionValidationContext<'a> { /// Wasm module @@ -29,8 +63,9 @@ struct FunctionValidationContext<'a> { frame_stack: StackWithLimit, /// Function return type. None if validating expression. return_type: Option, - /// Labels positions. - labels: HashMap, + + // TODO: comment + sink: Sink, } /// Value type on the stack. @@ -59,7 +94,7 @@ impl Validator { module: &ModuleContext, func: &Func, body: &FuncBody, - ) -> Result, Error> { + ) -> Result { let (params, result_ty) = module.require_function_type(func.type_ref())?; let mut context = FunctionValidationContext::new( @@ -70,13 +105,15 @@ impl Validator { result_ty, ); - context.push_label(BlockFrameType::Function, result_ty)?; + let func_label = context.sink.new_label(); + context.push_label(BlockFrameType::Function, result_ty, func_label)?; Validator::validate_function_block(&mut context, body.code().elements())?; while !context.frame_stack.is_empty() { + // TODO: resolve labels? context.pop_label()?; } - Ok(context.into_labels()) + Ok(context.into_code()) } fn validate_function_block(context: &mut FunctionValidationContext, body: &[Opcode]) -> Result<(), Error> { @@ -100,198 +137,943 @@ impl Validator { } fn validate_instruction(context: &mut FunctionValidationContext, opcode: &Opcode) -> Result { + // TODO: use InstructionOutcome::*; + use self::Opcode::*; match *opcode { - 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), + // Nop instruction doesn't do anything. It is safe to just skip it. + Nop => {}, - Call(index) => Validator::validate_call(context, index), - CallIndirect(index, _reserved) => Validator::validate_call_indirect(context, index), + Unreachable => { + return Ok(InstructionOutcome::Unreachable); + }, - Drop => Validator::validate_drop(context), - Select => Validator::validate_select(context), + Block(block_type) => { + let label = context.sink.new_label(); + context.push_label(BlockFrameType::Block, block_type, label)?; + }, + Loop(block_type) => { + // Resolve loop header right away. + let loop_header = context.top_label()?.branch_label; + context.sink.resolve_label(loop_header); - 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), + context.push_label(BlockFrameType::Loop, block_type, loop_header)?; + }, + If(block_type) => { + // if + // .. + // end + // + // translates to -> + // + // br_if_not $end + // .. + // $end + let end_label = context.sink.new_label(); + context.pop_value(ValueType::I32.into())?; + context.push_label(BlockFrameType::IfTrue, block_type, end_label)?; + }, + Else => { + // 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). + let end_target = context.require_target(0)?; + let end_label = end_target.label; + context.sink.emit_br(end_target); - 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), + // Then, we validate. Validator will pop the if..else block and the push else..end block. + 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()?; - 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), + if let BlockType::Value(value_type) = block_type { + context.pop_value(value_type.into())?; + } + context.push_label(BlockFrameType::IfFalse, block_type, end_label)?; + }, + End => { + { + let frame_type = context.top_label()?.frame_type; - CurrentMemory(_) => Validator::validate_current_memory(context), - GrowMemory(_) => Validator::validate_grow_memory(context), + // If this end for a non-loop frame then we resolve it's label location to here. + if frame_type != BlockFrameType::Loop { + let loop_header = context.top_label()?.branch_label; + context.sink.resolve_label(loop_header); + } + } - 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), + { + 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))); + } + } + } - 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), + context.pop_label()?; + }, + Br(depth) => { + Validator::validate_br(context, depth)?; + let target = context.require_target(depth)?; + context.sink.emit_br(target); + }, + BrIf(depth) => { + Validator::validate_br_if(context, depth)?; + let target = context.require_target(depth)?; + 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 = context.require_target(*depth)?; + targets.push(target); + } + let default = context.require_target(default)?; + context.sink.emit_br_table(&targets, default); + }, + Return => { + Validator::validate_return(context)?; + context.sink.emit(isa::Instruction::Return); + }, - 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), + 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)); + }, - 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), + Drop => { + Validator::validate_drop(context)?; + context.sink.emit(isa::Instruction::Drop); + }, + Select => { + Validator::validate_select(context)?; + context.sink.emit(isa::Instruction::Select); + }, - 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), + GetLocal(index) => { + // We need to calculate relative depth before validation since + // it will change value stack size. + let depth = context.relative_local_depth(index)?; + Validator::validate_get_local(context, index)?; + context.sink.emit( + isa::Instruction::GetLocal { depth }, + ); + }, + SetLocal(index) => { + // We need to calculate relative depth before validation since + // it will change value stack size. + let depth = context.relative_local_depth(index)?; + Validator::validate_set_local(context, index)?; + context.sink.emit( + isa::Instruction::SetLocal { depth }, + ); + }, + TeeLocal(index) => { + // We need to calculate relative depth before validation since + // it will change value stack size. + let depth = context.relative_local_depth(index)?; + Validator::validate_tee_local(context, index)?; + context.sink.emit( + isa::Instruction::TeeLocal { depth }, + ); + }, + GetGlobal(index) => { + Validator::validate_get_global(context, index)?; + context.sink.emit(isa::Instruction::GetGlobal(index)); - 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), + }, + SetGlobal(index) => { + Validator::validate_set_global(context, index)?; + context.sink.emit(isa::Instruction::SetGlobal(index)); - 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), + I32Load(align, offset) => { + Validator::validate_load(context, align, 4, ValueType::I32)?; + context.sink.emit(isa::Instruction::I32Load(offset)); - 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), + }, + I64Load(align, offset) => { + Validator::validate_load(context, align, 8, ValueType::I64)?; + context.sink.emit(isa::Instruction::I64Load(offset)); - 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), + }, + F32Load(align, offset) => { + Validator::validate_load(context, align, 4, ValueType::F32)?; + context.sink.emit(isa::Instruction::F32Load(offset)); - 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), + }, + 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(InstructionOutcome::ValidateNextInstruction) } fn validate_const(context: &mut FunctionValidationContext, value_type: ValueType) -> Result { @@ -408,48 +1190,6 @@ impl Validator { 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)?; @@ -587,7 +1327,7 @@ impl<'a> FunctionValidationContext<'a> { value_stack: StackWithLimit::with_limit(value_stack_limit), frame_stack: StackWithLimit::with_limit(frame_stack_limit), return_type: Some(return_type), - labels: HashMap::new(), + sink: Sink::new(), } } @@ -645,13 +1385,12 @@ impl<'a> FunctionValidationContext<'a> { Ok(self.frame_stack.top()?) } - fn push_label(&mut self, frame_type: BlockFrameType, block_type: BlockType) -> Result<(), Error> { + fn push_label(&mut self, frame_type: BlockFrameType, block_type: BlockType, branch_label: LabelId) -> 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, + branch_label, value_stack_len: self.value_stack.len(), polymorphic_stack: false, })?) @@ -678,9 +1417,6 @@ impl<'a> FunctionValidationContext<'a> { ))); } - 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())?; } @@ -700,8 +1436,44 @@ impl<'a> FunctionValidationContext<'a> { Ok(self.locals.type_of_local(idx).map(StackValueType::from)?) } - fn into_labels(self) -> HashMap { - self.labels + fn require_target(&self, depth: u32) -> Result { + let frame = self.require_label(depth)?; + let label = frame.branch_label; + + let keep: u8 = match (frame.frame_type, frame.block_type) { + (BlockFrameType::Loop, _) => 0, + (_, BlockType::NoResult) => 0, + (_, BlockType::Value(_)) => 1, + }; + + let value_stack_height = self.value_stack.len() as u32; + let drop = value_stack_height - frame.value_stack_len as u32 - keep as u32; + + Ok(Target { + label, + keep, + drop, + }) + } + + fn relative_local_depth(&mut self, idx: u32) -> Result { + // TODO: Comment stack layout + let value_stack_height = self.value_stack.len() as u32; + let locals_and_params_count = self.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 no in 32-bit range")) + )?; + Ok(depth) + } + + fn into_code(self) -> isa::Instructions { + isa::Instructions { + code: self.sink.into_inner(), + } } } @@ -752,3 +1524,169 @@ impl PartialEq for ValueType { other == self } } + +#[derive(Clone)] +struct Target { + label: LabelId, + drop: u32, + keep: u8, +} + +enum Reloc { + Br { + pc: u32, + }, + BrTable { + pc: u32, + idx: usize, + }, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +struct LabelId(usize); +enum Label { + Resolved(u32), + NotResolved, +} + +struct Sink { + ins: Vec, + labels: Vec