From 58a7e2c55c0ded6f00407561a4381ec899a991e2 Mon Sep 17 00:00:00 2001 From: Jef Date: Tue, 24 Apr 2018 12:28:18 +0200 Subject: [PATCH] Preserve signalling bit in NaNs --- 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 | 659 +++++++++++++++++++++++---------------------- 8 files changed, 592 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..4af05c3 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,366 @@ 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); + } + panic!(out); + } + + Ok(()) }