Merge remote-tracking branch 'origin/flat-stack' into fuzz

This commit is contained in:
Sergey Pepyakin 2018-06-14 21:49:10 +03:00
commit 978b9ff2ea
25 changed files with 3875 additions and 1399 deletions

View File

@ -3,6 +3,9 @@ sudo: required
language: language:
- rust - rust
- cpp - cpp
rust:
- nightly
- stable
addons: addons:
apt: apt:
sources: sources:
@ -12,18 +15,16 @@ addons:
- g++-6 - g++-6
- cmake - cmake
env: env:
- NIGHTLY_TOOLCHAIN=nightly-2018-02-05 - CC=/usr/bin/gcc-6 CXX=/usr/bin/g++-6
install: install:
# Install `cargo-deadlinks` unless it is currently installed. # Install `cargo-deadlinks` unless it is currently installed.
- command -v cargo-deadlinks &> /dev/null || cargo install cargo-deadlinks - command -v cargo-deadlinks &> /dev/null || cargo install cargo-deadlinks
# Install nightly toolchain. - if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then rustup target add wasm32-unknown-unknown; fi
- rustup toolchain install $NIGHTLY_TOOLCHAIN
script: script:
- export CC=/usr/bin/gcc-6 # Make sure nightly targets are not broken.
- export CXX=/usr/bin/g++-6 - if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo check --tests --manifest-path=fuzz/Cargo.toml; fi
# Make sure fuzz targets are not broken. - if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo check --benches --manifest-path=benches/Cargo.toml; fi
- rustup run $NIGHTLY_TOOLCHAIN cargo check --tests --manifest-path=fuzz/Cargo.toml
- ./test.sh - ./test.sh
- ./doc.sh - ./doc.sh
after_success: | after_success: |

View File

@ -1,6 +1,6 @@
[package] [package]
name = "wasmi" name = "wasmi"
version = "0.1.2" version = "0.2.0"
authors = ["Nikolay Volf <nikvolf@gmail.com>", "Svyatoslav Nikolsky <svyatonik@yandex.ru>", "Sergey Pepyakin <s.pepyakin@gmail.com>"] authors = ["Nikolay Volf <nikvolf@gmail.com>", "Svyatoslav Nikolsky <svyatonik@yandex.ru>", "Sergey Pepyakin <s.pepyakin@gmail.com>"]
license = "MIT/Apache-2.0" license = "MIT/Apache-2.0"
readme = "README.md" readme = "README.md"
@ -8,19 +8,15 @@ repository = "https://github.com/paritytech/wasmi"
documentation = "https://paritytech.github.io/wasmi/" documentation = "https://paritytech.github.io/wasmi/"
description = "WebAssembly interpreter" description = "WebAssembly interpreter"
keywords = ["wasm", "webassembly", "bytecode", "interpreter"] keywords = ["wasm", "webassembly", "bytecode", "interpreter"]
exclude = [ "/res/*", "/tests/*", "/fuzz/*" ] exclude = [ "/res/*", "/tests/*", "/fuzz/*", "/benches/*" ]
[dependencies] [dependencies]
# parity-wasm = "0.27" # parity-wasm = "0.27"
parity-wasm = { git = "https://github.com/paritytech/parity-wasm.git", rev = "0a61083238d8d9d8d9f6451a5d0da17674b11c21" } parity-wasm = { git = "https://github.com/paritytech/parity-wasm.git", rev = "0a61083238d8d9d8d9f6451a5d0da17674b11c21" }
byteorder = "1.0" byteorder = "1.0"
memory_units = "0.3.0" memory_units = "0.3.0"
nan-preserving-float = "0.1.0"
[dev-dependencies] [dev-dependencies]
wabt = "~0.2.2" assert_matches = "1.1"
wabt = "0.3"
[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 = []

3
benches/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
*.trace

11
benches/Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "benches"
version = "0.1.0"
authors = ["Sergey Pepyakin <s.pepyakin@gmail.com>"]
[dependencies]
wasmi = { path = ".." }
assert_matches = "1.2"
[profile.bench]
debug = true

32
benches/build.rs Normal file
View File

@ -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);
}
}

43
benches/src/lib.rs Normal file
View File

@ -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<Module, Box<error::Error>> {
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();
});
}

1
benches/wasm-kernel/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

View File

@ -0,0 +1,16 @@
[package]
name = "wasm-kernel"
version = "0.1.0"
authors = ["Sergey Pepyakin <s.pepyakin@gmail.com>"]
[lib]
crate-type = ["cdylib"]
[dependencies]
tiny-keccak = "1.4.2"
rlibc = "1.0"
[profile.release]
panic = "abort"
lto = true
opt-level = "z"

View File

@ -0,0 +1,50 @@
#![no_std]
#![feature(lang_items)]
#![feature(core_intrinsics)]
#![feature(panic_implementation)]
extern crate rlibc;
extern crate tiny_keccak;
use tiny_keccak::Keccak;
#[no_mangle]
#[panic_implementation]
pub fn panic_fmt(_info: &::core::panic::PanicInfo) -> ! {
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<TinyKeccakTestData> = 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);
}
}

6
doc.sh
View File

@ -4,11 +4,7 @@ set -eux
cd $(dirname $0) cd $(dirname $0)
if [ -s NIGHTLY_TOOLCHAIN ]; then cargo doc
rustup run $NIGHTLY_TOOLCHAIN cargo doc
else
cargo doc
fi;
# cargo-deadlinks will check any links in docs generated by `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. # This is useful as rustdoc uses raw links which are error prone.

View File

@ -64,8 +64,8 @@ fn main() {
function_type.params().iter().enumerate().map(|(i, value)| match value { function_type.params().iter().enumerate().map(|(i, value)| match value {
&ValueType::I32 => RuntimeValue::I32(program_args[i].parse::<i32>().expect(&format!("Can't parse arg #{} as i32", program_args[i]))), &ValueType::I32 => RuntimeValue::I32(program_args[i].parse::<i32>().expect(&format!("Can't parse arg #{} as i32", program_args[i]))),
&ValueType::I64 => RuntimeValue::I64(program_args[i].parse::<i64>().expect(&format!("Can't parse arg #{} as i64", program_args[i]))), &ValueType::I64 => RuntimeValue::I64(program_args[i].parse::<i64>().expect(&format!("Can't parse arg #{} as i64", program_args[i]))),
&ValueType::F32 => RuntimeValue::F32(program_args[i].parse::<f32>().expect(&format!("Can't parse arg #{} as f32", program_args[i]))), &ValueType::F32 => RuntimeValue::F32(program_args[i].parse::<f32>().expect(&format!("Can't parse arg #{} as f32", program_args[i])).into()),
&ValueType::F64 => RuntimeValue::F64(program_args[i].parse::<f64>().expect(&format!("Can't parse arg #{} as f64", program_args[i]))), &ValueType::F64 => RuntimeValue::F64(program_args[i].parse::<f64>().expect(&format!("Can't parse arg #{} as f64", program_args[i])).into()),
}).collect::<Vec<RuntimeValue>>() }).collect::<Vec<RuntimeValue>>()
}; };

View File

@ -1,4 +1,3 @@
use parity_wasm::elements::BlockType;
pub mod stack; pub mod stack;
@ -7,38 +6,4 @@ pub const DEFAULT_MEMORY_INDEX: u32 = 0;
/// Index of default table. /// Index of default table.
pub const DEFAULT_TABLE_INDEX: u32 = 0; pub const DEFAULT_TABLE_INDEX: u32 = 0;
/// Control stack frame. // TODO: Move BlockFrame under validation.
#[derive(Debug, Clone)]
pub struct BlockFrame {
/// Frame type.
pub frame_type: BlockFrameType,
/// A signature, which is a block signature type indicating the number and types of result values of the region.
pub block_type: BlockType,
/// A label for reference to block instruction.
pub begin_position: usize,
/// A label for reference from branch instructions.
pub branch_position: usize,
/// A label for reference from end instructions.
pub end_position: usize,
/// A limit integer value, which is an index into the value stack indicating where to reset it to on a branch to that label.
pub value_stack_len: usize,
/// Boolean which signals whether value stack became polymorphic. Value stack starts in non-polymorphic state and
/// becomes polymorphic only after an instruction that never passes control further is executed,
/// i.e. `unreachable`, `br` (but not `br_if`!), etc.
pub polymorphic_stack: bool,
}
/// Type of block frame.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BlockFrameType {
/// Function frame.
Function,
/// Usual block frame.
Block,
/// Loop frame (branching to the beginning of block).
Loop,
/// True-subblock of if expression.
IfTrue,
/// False-subblock of if expression.
IfFalse,
}

View File

@ -1,5 +1,4 @@
use std::collections::VecDeque;
use std::error; use std::error;
use std::fmt; use std::fmt;
@ -22,7 +21,7 @@ impl error::Error for Error {
#[derive(Debug)] #[derive(Debug)]
pub struct StackWithLimit<T> where T: Clone { pub struct StackWithLimit<T> where T: Clone {
/// Stack values. /// Stack values.
values: VecDeque<T>, values: Vec<T>,
/// Stack limit (maximal stack len). /// Stack limit (maximal stack len).
limit: usize, limit: usize,
} }
@ -30,7 +29,7 @@ pub struct StackWithLimit<T> where T: Clone {
impl<T> StackWithLimit<T> where T: Clone { impl<T> StackWithLimit<T> where T: Clone {
pub fn with_limit(limit: usize) -> Self { pub fn with_limit(limit: usize) -> Self {
StackWithLimit { StackWithLimit {
values: VecDeque::new(), values: Vec::new(),
limit: limit limit: limit
} }
} }
@ -43,19 +42,17 @@ impl<T> StackWithLimit<T> where T: Clone {
self.values.len() self.values.len()
} }
pub fn limit(&self) -> usize {
self.limit
}
pub fn top(&self) -> Result<&T, Error> { pub fn top(&self) -> Result<&T, Error> {
let len = self.values.len();
self.values self.values
.back() .get(len - 1)
.ok_or_else(|| Error("non-empty stack expected".into())) .ok_or_else(|| Error("non-empty stack expected".into()))
} }
pub fn top_mut(&mut self) -> Result<&mut T, Error> { pub fn top_mut(&mut self) -> Result<&mut T, Error> {
let len = self.values.len();
self.values self.values
.back_mut() .get_mut(len - 1)
.ok_or_else(|| Error("non-empty stack expected".into())) .ok_or_else(|| Error("non-empty stack expected".into()))
} }
@ -72,13 +69,13 @@ impl<T> StackWithLimit<T> where T: Clone {
return Err(Error(format!("exceeded stack limit {}", self.limit))); return Err(Error(format!("exceeded stack limit {}", self.limit)));
} }
self.values.push_back(value); self.values.push(value);
Ok(()) Ok(())
} }
pub fn pop(&mut self) -> Result<T, Error> { pub fn pop(&mut self) -> Result<T, Error> {
self.values self.values
.pop_back() .pop()
.ok_or_else(|| Error("non-empty stack expected".into())) .ok_or_else(|| Error("non-empty stack expected".into()))
} }

View File

@ -1,12 +1,12 @@
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
use std::fmt; use std::fmt;
use std::collections::HashMap; use parity_wasm::elements::Local;
use parity_wasm::elements::{Local, Opcodes};
use {Trap, TrapKind, Signature}; use {Trap, TrapKind, Signature};
use host::Externals; use host::Externals;
use runner::{check_function_args, Interpreter}; use runner::{check_function_args, Interpreter};
use value::RuntimeValue; use value::RuntimeValue;
use module::ModuleInstance; use module::ModuleInstance;
use isa;
/// Reference to a function (See [`FuncInstance`] for details). /// Reference to a function (See [`FuncInstance`] for details).
/// ///
@ -158,6 +158,5 @@ impl FuncInstance {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct FuncBody { pub struct FuncBody {
pub locals: Vec<Local>, pub locals: Vec<Local>,
pub opcodes: Opcodes, pub code: isa::Instructions,
pub labels: HashMap<usize, usize>,
} }

255
src/isa.rs Normal file
View File

@ -0,0 +1,255 @@
//! 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:
//!
//! ```plain
//! loop
//! ...
//! if_true_jump_to_end
//! ...
//! end
//! ```
//!
//! To execute `if_true_jump_to_end` , the interpreter needs to skip all instructions
//! until it reaches the *matching* `end`. That's quite inefficient compared
//! to a plain goto to the specific position.
//!
//! # 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, PartialEq, Eq)]
pub struct Target {
pub dst_pc: u32,
pub drop: u32,
pub keep: u8,
}
#[allow(unused)] // TODO: Remove
#[derive(Debug, Clone, PartialEq)]
pub enum Instruction {
/// Push a local variable or an argument from the specified depth.
GetLocal(u32),
/// Pop a value and put it in at the specified depth.
SetLocal(u32),
/// Copy a value to the specified depth.
TeeLocal(u32),
/// Similar to the Wasm ones, but instead of a label depth
/// they specify direct PC.
Br(Target),
BrIfEqz(Target),
BrIfNez(Target),
/// Last one is the default.
///
/// Can be less than zero.
BrTable(Box<[Target]>),
Unreachable,
Return {
drop: u32,
keep: u8,
},
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<Instruction>,
}

View File

@ -98,20 +98,17 @@
#[cfg(test)] #[cfg(test)]
extern crate wabt; extern crate wabt;
#[cfg(test)]
#[macro_use]
extern crate assert_matches;
extern crate parity_wasm; extern crate parity_wasm;
extern crate byteorder; extern crate byteorder;
extern crate memory_units as memory_units_crate; extern crate memory_units as memory_units_crate;
extern crate nan_preserving_float;
#[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"
}
use std::fmt; use std::fmt;
use std::error; use std::error;
use std::collections::HashMap;
/// Error type which can thrown by wasm code or by host environment. /// Error type which can thrown by wasm code or by host environment.
/// ///
@ -358,6 +355,7 @@ mod imports;
mod global; mod global;
mod func; mod func;
mod types; mod types;
mod isa;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -380,12 +378,11 @@ pub mod memory_units {
/// Deserialized module prepared for instantiation. /// Deserialized module prepared for instantiation.
pub struct Module { pub struct Module {
labels: HashMap<usize, HashMap<usize, usize>>, code_map: Vec<isa::Instructions>,
module: parity_wasm::elements::Module, module: parity_wasm::elements::Module,
} }
impl Module { impl Module {
/// Create `Module` from `parity_wasm::elements::Module`. /// Create `Module` from `parity_wasm::elements::Module`.
/// ///
/// This function will load, validate and prepare a `parity_wasm`'s `Module`. /// This function will load, validate and prepare a `parity_wasm`'s `Module`.
@ -421,16 +418,76 @@ impl Module {
pub fn from_parity_wasm_module(module: parity_wasm::elements::Module) -> Result<Module, Error> { pub fn from_parity_wasm_module(module: parity_wasm::elements::Module) -> Result<Module, Error> {
use validation::{validate_module, ValidatedModule}; use validation::{validate_module, ValidatedModule};
let ValidatedModule { let ValidatedModule {
labels, code_map,
module, module,
} = validate_module(module)?; } = validate_module(module)?;
Ok(Module { Ok(Module {
labels, code_map,
module, 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<u8> =
/// 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<u8> =
/// 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<u8> =
/// 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. /// Create `Module` from a given buffer.
/// ///
/// This function will deserialize wasm module from a given module, /// This function will deserialize wasm module from a given module,
@ -467,7 +524,7 @@ impl Module {
&self.module &self.module
} }
pub(crate) fn labels(&self) -> &HashMap<usize, HashMap<usize, usize>> { pub(crate) fn code(&self) -> &Vec<isa::Instructions> {
&self.labels &self.code_map
} }
} }

View File

@ -48,7 +48,7 @@ impl ::std::ops::Deref for MemoryRef {
/// ///
/// [`LINEAR_MEMORY_PAGE_SIZE`]: constant.LINEAR_MEMORY_PAGE_SIZE.html /// [`LINEAR_MEMORY_PAGE_SIZE`]: constant.LINEAR_MEMORY_PAGE_SIZE.html
pub struct MemoryInstance { pub struct MemoryInstance {
/// Memofy limits. /// Memory limits.
limits: ResizableLimits, limits: ResizableLimits,
/// Linear memory buffer. /// Linear memory buffer.
buffer: RefCell<Vec<u8>>, buffer: RefCell<Vec<u8>>,
@ -315,7 +315,7 @@ impl MemoryInstance {
Ok(()) Ok(())
} }
/// Fill memory region with a specified value. /// Fill the memory region with the specified value.
/// ///
/// Semantically equivalent to `memset`. /// Semantically equivalent to `memset`.
/// ///
@ -330,7 +330,7 @@ impl MemoryInstance {
Ok(()) Ok(())
} }
/// Fill specified memory region with zeroes. /// Fill the specified memory region with zeroes.
/// ///
/// # Errors /// # Errors
/// ///
@ -338,6 +338,35 @@ impl MemoryInstance {
pub fn zero(&self, offset: usize, len: usize) -> Result<(), Error> { pub fn zero(&self, offset: usize, len: usize) -> Result<(), Error> {
self.clear(offset, 0, len) 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, F: FnOnce(&[u8]) -> 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, F: FnOnce(&mut [u8]) -> R>(&self, f: F) -> R {
let mut buf = self.buffer.borrow_mut();
f(&mut *buf)
}
} }
pub fn validate_memory(initial: Pages, maximum: Option<Pages>) -> Result<(), String> { pub fn validate_memory(initial: Pages, maximum: Option<Pages>) -> Result<(), String> {
@ -369,6 +398,7 @@ mod tests {
#[test] #[test]
fn alloc() { fn alloc() {
#[cfg(target_pointer_width = "64")]
let fixtures = &[ let fixtures = &[
(0, None, true), (0, None, true),
(0, Some(0), true), (0, Some(0), true),
@ -381,6 +411,17 @@ mod tests {
(65536, Some(0), false), (65536, Some(0), false),
(65536, None, true), (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() { for (index, &(initial, maybe_max, expected_ok)) in fixtures.iter().enumerate() {
let initial: Pages = Pages(initial); let initial: Pages = Pages(initial);
let maximum: Option<Pages> = maybe_max.map(|m| Pages(m)); let maximum: Option<Pages> = maybe_max.map(|m| Pages(m));
@ -472,4 +513,27 @@ mod tests {
assert_eq!(data, [17, 129]); 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]);
});
}
} }

View File

@ -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( 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( let signature = instance.signature_by_index(ty.type_ref()).expect(
"Due to validation type should exists", "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", "At func validation time labels are collected; Collected labels are added by index; qed",
).clone(); ).clone();
let func_body = FuncBody { let func_body = FuncBody {
locals: body.locals().to_vec(), locals: body.locals().to_vec(),
opcodes: body.code().clone(), code: code,
labels: labels,
}; };
let func_instance = let func_instance =
FuncInstance::alloc_internal(Rc::downgrade(&instance.0), signature, func_body); FuncInstance::alloc_internal(Rc::downgrade(&instance.0), signature, func_body);
@ -420,7 +419,7 @@ impl ModuleInstance {
// This check is not only for bailing out early, but also to check the case when // This check is not only for bailing out early, but also to check the case when
// segment consist of 0 members. // 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( return Err(
Error::Instantiation("elements segment does not fit".to_string()) Error::Instantiation("elements segment does not fit".to_string())
); );

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
use std::error; use std::error;
use std::fmt; use std::fmt;
use std::collections::{HashMap, HashSet}; use std::collections::HashSet;
use parity_wasm::elements::{ use parity_wasm::elements::{
BlockType, External, GlobalEntry, GlobalType, Internal, MemoryType, Module, Opcode, BlockType, External, GlobalEntry, GlobalType, Internal, MemoryType, Module, Opcode,
ResizableLimits, TableType, ValueType, InitExpr, Type ResizableLimits, TableType, ValueType, InitExpr, Type
@ -9,9 +9,11 @@ use common::stack;
use self::context::ModuleContextBuilder; use self::context::ModuleContextBuilder;
use self::func::Validator; use self::func::Validator;
use memory_units::Pages; use memory_units::Pages;
use isa;
mod context; mod context;
mod func; mod func;
mod util;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -39,7 +41,7 @@ impl From<stack::Error> for Error {
#[derive(Clone)] #[derive(Clone)]
pub struct ValidatedModule { pub struct ValidatedModule {
pub labels: HashMap<usize, HashMap<usize, usize>>, pub code_map: Vec<isa::Instructions>,
pub module: Module, pub module: Module,
} }
@ -50,10 +52,123 @@ 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<ValidatedModule, Error> { pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
let mut context_builder = ModuleContextBuilder::new(); let mut context_builder = ModuleContextBuilder::new();
let mut imported_globals = Vec::new(); let mut imported_globals = Vec::new();
let mut labels = HashMap::new(); let mut code_map = Vec::new();
// Copy types from module as is. // Copy types from module as is.
context_builder.set_types( context_builder.set_types(
@ -143,12 +258,12 @@ pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
index index
)), )),
)?; )?;
let func_labels = Validator::validate_function(&context, function, function_body) let code = Validator::validate_function(&context, function, function_body)
.map_err(|e| { .map_err(|e| {
let Error(ref msg) = e; let Error(ref msg) = e;
Error(format!("Function #{} validation error: {}", index, msg)) Error(format!("Function #{} validation error: {}", index, msg))
})?; })?;
labels.insert(index, func_labels); code_map.push(code);
} }
} }
@ -260,7 +375,7 @@ pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
Ok(ValidatedModule { Ok(ValidatedModule {
module, module,
labels code_map,
}) })
} }

View File

@ -1,9 +1,12 @@
use super::validate_module; use super::{validate_module, ValidatedModule};
use parity_wasm::builder::module; use parity_wasm::builder::module;
use parity_wasm::elements::{ use parity_wasm::elements::{
External, GlobalEntry, GlobalType, ImportEntry, InitExpr, MemoryType, External, GlobalEntry, GlobalType, ImportEntry, InitExpr, MemoryType,
Opcode, Opcodes, TableType, ValueType, BlockType Opcode, Opcodes, TableType, ValueType, BlockType, deserialize_buffer,
Module,
}; };
use isa;
use wabt;
#[test] #[test]
fn empty_is_valid() { fn empty_is_valid() {
@ -299,3 +302,584 @@ fn if_else_with_return_type_validation() {
.build(); .build();
validate_module(m).unwrap(); validate_module(m).unwrap();
} }
fn validate(wat: &str) -> ValidatedModule {
let wasm = wabt::wat2wasm(wat).unwrap();
let module = deserialize_buffer::<Module>(&wasm).unwrap();
let validated_module = validate_module(module).unwrap();
validated_module
}
fn compile(wat: &str) -> Vec<isa::Instruction> {
let validated_module = validate(wat);
let code = &validated_module.code_map[0];
code.code.clone()
}
#[test]
fn implicit_return_no_value() {
let code = compile(r#"
(module
(func (export "call")
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::Return {
drop: 0,
keep: 0,
}
]
)
}
#[test]
fn implicit_return_with_value() {
let code = compile(r#"
(module
(func (export "call") (result i32)
i32.const 0
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(0),
isa::Instruction::Return {
drop: 0,
keep: 1,
}
]
)
}
#[test]
fn implicit_return_param() {
let code = compile(r#"
(module
(func (export "call") (param i32)
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::Return {
drop: 1,
keep: 0,
}
]
)
}
#[test]
fn get_local() {
let code = compile(r#"
(module
(func (export "call") (param i32) (result i32)
get_local 0
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::GetLocal(1),
isa::Instruction::Return {
drop: 1,
keep: 1,
}
]
)
}
#[test]
fn explicit_return() {
let code = compile(r#"
(module
(func (export "call") (param i32) (result i32)
get_local 0
return
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::GetLocal(1),
isa::Instruction::Return {
drop: 1,
keep: 1,
},
isa::Instruction::Return {
drop: 1,
keep: 1,
}
]
)
}
#[test]
fn add_params() {
let code = compile(r#"
(module
(func (export "call") (param i32) (param i32) (result i32)
get_local 0
get_local 1
i32.add
)
)
"#);
assert_eq!(
code,
vec![
// This is tricky. Locals are now loaded from the stack. The load
// happens from address relative of the current stack pointer. The first load
// takes the value below the previous one (i.e the second argument) and then, it increments
// the stack pointer. And then the same thing hapens with the value below the previous one
// (which happens to be the value loaded by the first get_local).
isa::Instruction::GetLocal(2),
isa::Instruction::GetLocal(2),
isa::Instruction::I32Add,
isa::Instruction::Return {
drop: 2,
keep: 1,
}
]
)
}
#[test]
fn drop_locals() {
let code = compile(r#"
(module
(func (export "call") (param i32)
(local i32)
get_local 0
set_local 1
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::GetLocal(2),
isa::Instruction::SetLocal(1),
isa::Instruction::Return {
drop: 2,
keep: 0,
}
]
)
}
#[test]
fn if_without_else() {
let code = compile(r#"
(module
(func (export "call") (param i32) (result i32)
i32.const 1
if
i32.const 2
return
end
i32.const 3
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(1),
isa::Instruction::BrIfEqz(isa::Target {
dst_pc: 4,
drop: 0,
keep: 0,
}),
isa::Instruction::I32Const(2),
isa::Instruction::Return {
drop: 1, // 1 param
keep: 1, // 1 result
},
isa::Instruction::I32Const(3),
isa::Instruction::Return {
drop: 1,
keep: 1,
},
]
)
}
#[test]
fn if_else() {
let code = compile(r#"
(module
(func (export "call")
(local i32)
i32.const 1
if
i32.const 2
set_local 0
else
i32.const 3
set_local 0
end
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(1),
isa::Instruction::BrIfEqz(isa::Target {
dst_pc: 5,
drop: 0,
keep: 0,
}),
isa::Instruction::I32Const(2),
isa::Instruction::SetLocal(1),
isa::Instruction::Br(isa::Target {
dst_pc: 7,
drop: 0,
keep: 0,
}),
isa::Instruction::I32Const(3),
isa::Instruction::SetLocal(1),
isa::Instruction::Return {
drop: 1,
keep: 0,
},
]
)
}
#[test]
fn if_else_returns_result() {
let code = compile(r#"
(module
(func (export "call")
i32.const 1
if (result i32)
i32.const 2
else
i32.const 3
end
drop
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(1),
isa::Instruction::BrIfEqz(isa::Target {
dst_pc: 4,
drop: 0,
keep: 0,
}),
isa::Instruction::I32Const(2),
isa::Instruction::Br(isa::Target {
dst_pc: 5,
drop: 0,
keep: 0,
}),
isa::Instruction::I32Const(3),
isa::Instruction::Drop,
isa::Instruction::Return {
drop: 0,
keep: 0,
},
]
)
}
#[test]
fn if_else_branch_from_true_branch() {
let code = compile(r#"
(module
(func (export "call")
i32.const 1
if (result i32)
i32.const 1
i32.const 1
br_if 0
drop
i32.const 2
else
i32.const 3
end
drop
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(1),
isa::Instruction::BrIfEqz(isa::Target {
dst_pc: 8,
drop: 0,
keep: 0,
}),
isa::Instruction::I32Const(1),
isa::Instruction::I32Const(1),
isa::Instruction::BrIfNez(isa::Target {
dst_pc: 9,
drop: 0,
keep: 1,
}),
isa::Instruction::Drop,
isa::Instruction::I32Const(2),
isa::Instruction::Br(isa::Target {
dst_pc: 9,
drop: 0,
keep: 0,
}),
isa::Instruction::I32Const(3),
isa::Instruction::Drop,
isa::Instruction::Return {
drop: 0,
keep: 0,
},
]
)
}
#[test]
fn if_else_branch_from_false_branch() {
let code = compile(r#"
(module
(func (export "call")
i32.const 1
if (result i32)
i32.const 1
else
i32.const 2
i32.const 1
br_if 0
drop
i32.const 3
end
drop
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(1),
isa::Instruction::BrIfEqz(isa::Target {
dst_pc: 4,
drop: 0,
keep: 0,
}),
isa::Instruction::I32Const(1),
isa::Instruction::Br(isa::Target {
dst_pc: 9,
drop: 0,
keep: 0,
}),
isa::Instruction::I32Const(2),
isa::Instruction::I32Const(1),
isa::Instruction::BrIfNez(isa::Target {
dst_pc: 9,
drop: 0,
keep: 1,
}),
isa::Instruction::Drop,
isa::Instruction::I32Const(3),
isa::Instruction::Drop,
isa::Instruction::Return {
drop: 0,
keep: 0,
},
]
)
}
#[test]
fn loop_() {
let code = compile(r#"
(module
(func (export "call")
loop (result i32)
i32.const 1
br_if 0
i32.const 2
end
drop
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(1),
isa::Instruction::BrIfNez(isa::Target {
dst_pc: 0,
drop: 0,
keep: 0,
}),
isa::Instruction::I32Const(2),
isa::Instruction::Drop,
isa::Instruction::Return {
drop: 0,
keep: 0,
},
]
)
}
#[test]
fn loop_empty() {
let code = compile(r#"
(module
(func (export "call")
loop
end
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::Return {
drop: 0,
keep: 0,
},
]
)
}
#[test]
fn brtable() {
let code = compile(r#"
(module
(func (export "call")
block $1
loop $2
i32.const 0
br_table $2 $1
end
end
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(0),
isa::Instruction::BrTable(
vec![
isa::Target {
dst_pc: 0,
keep: 0,
drop: 0,
},
isa::Target {
dst_pc: 2,
keep: 0,
drop: 0,
},
].into_boxed_slice()
),
isa::Instruction::Return {
drop: 0,
keep: 0,
},
]
)
}
#[test]
fn brtable_returns_result() {
let code = compile(r#"
(module
(func (export "call")
block $1 (result i32)
block $2 (result i32)
i32.const 0
i32.const 1
br_table $2 $1
end
unreachable
end
drop
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(0),
isa::Instruction::I32Const(1),
isa::Instruction::BrTable(
vec![
isa::Target {
dst_pc: 3,
keep: 1,
drop: 0,
},
isa::Target {
dst_pc: 4,
keep: 1,
drop: 0,
},
].into_boxed_slice()
),
isa::Instruction::Unreachable,
isa::Instruction::Drop,
isa::Instruction::Return {
drop: 0,
keep: 0,
},
]
)
}
#[test]
fn wabt_example() {
let code = compile(r#"
(module
(func (export "call") (param i32) (result i32)
block $exit
get_local 0
br_if $exit
i32.const 1
return
end
i32.const 2
return
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::GetLocal(1),
isa::Instruction::BrIfNez(isa::Target {
dst_pc: 4,
keep: 0,
drop: 0,
}),
isa::Instruction::I32Const(1),
isa::Instruction::Return {
drop: 1, // 1 parameter
keep: 1, // return value
},
isa::Instruction::I32Const(2),
isa::Instruction::Return {
drop: 1,
keep: 1,
},
isa::Instruction::Return {
drop: 1,
keep: 1,
},
]
)
}

132
src/validation/util.rs Normal file
View File

@ -0,0 +1,132 @@
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 parameter count.
pub fn param_count(&self) -> u32 {
self.params.len() as u32
}
/// Returns total count of all declared locals and paramaterers.
///
/// Returns `Err` if count overflows 32-bit value.
pub fn count(&self) -> Result<u32, Error> {
let mut acc = self.param_count();
for locals_group in self.local_groups {
acc = acc
.checked_add(locals_group.count())
.ok_or_else(||
Error(String::from("Locals range no in 32-bit range"))
)?;
}
Ok(acc)
}
/// 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<ValueType, Error> {
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.param_count();
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(&params, &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(&params, &[]);
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(_));
}
}

View File

@ -1,6 +1,7 @@
use std::{i32, i64, u32, u64, f32};
use std::io;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use nan_preserving_float::{F32, F64};
use std::io;
use std::{f32, i32, i64, u32, u64};
use TrapKind; use TrapKind;
#[derive(Debug)] #[derive(Debug)]
@ -22,9 +23,9 @@ pub enum RuntimeValue {
/// Value of 64-bit signed or unsigned integer. /// Value of 64-bit signed or unsigned integer.
I64(i64), I64(i64),
/// Value of 32-bit IEEE 754-2008 floating point number. /// Value of 32-bit IEEE 754-2008 floating point number.
F32(f32), F32(F32),
/// Value of 64-bit IEEE 754-2008 floating point number. /// Value of 64-bit IEEE 754-2008 floating point number.
F64(f64), F64(F64),
} }
/// Trait for creating value from a [`RuntimeValue`]. /// Trait for creating value from a [`RuntimeValue`].
@ -136,19 +137,19 @@ impl RuntimeValue {
match value_type { match value_type {
::types::ValueType::I32 => RuntimeValue::I32(0), ::types::ValueType::I32 => RuntimeValue::I32(0),
::types::ValueType::I64 => RuntimeValue::I64(0), ::types::ValueType::I64 => RuntimeValue::I64(0),
::types::ValueType::F32 => RuntimeValue::F32(0f32), ::types::ValueType::F32 => RuntimeValue::F32(0f32.into()),
::types::ValueType::F64 => RuntimeValue::F64(0f64), ::types::ValueType::F64 => RuntimeValue::F64(0f64.into()),
} }
} }
/// Creates new value by interpreting passed u32 as f32. /// Creates new value by interpreting passed u32 as f32.
pub fn decode_f32(val: u32) -> Self { 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. /// Creates new value by interpreting passed u64 as f64.
pub fn decode_f64(val: u64) -> Self { 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. /// Get variable type for this value.
@ -197,14 +198,14 @@ impl From<u64> for RuntimeValue {
} }
} }
impl From<f32> for RuntimeValue { impl From<F32> for RuntimeValue {
fn from(val: f32) -> Self { fn from(val: F32) -> Self {
RuntimeValue::F32(val) RuntimeValue::F32(val)
} }
} }
impl From<f64> for RuntimeValue { impl From<F64> for RuntimeValue {
fn from(val: f64) -> Self { fn from(val: F64) -> Self {
RuntimeValue::F64(val) RuntimeValue::F64(val)
} }
} }
@ -214,7 +215,7 @@ macro_rules! impl_from_runtime_value {
impl FromRuntimeValue for $into { impl FromRuntimeValue for $into {
fn from_runtime_value(val: RuntimeValue) -> Option<Self> { fn from_runtime_value(val: RuntimeValue) -> Option<Self> {
match val { match val {
RuntimeValue::$expected_rt_ty(val) => Some(val as $into), RuntimeValue::$expected_rt_ty(val) => Some(val.transmute_into()),
_ => None, _ => None,
} }
} }
@ -237,19 +238,26 @@ impl FromRuntimeValue for bool {
impl_from_runtime_value!(I32, i32); impl_from_runtime_value!(I32, i32);
impl_from_runtime_value!(I64, i64); impl_from_runtime_value!(I64, i64);
impl_from_runtime_value!(F32, f32); impl_from_runtime_value!(F32, F32);
impl_from_runtime_value!(F64, f64); impl_from_runtime_value!(F64, F64);
impl_from_runtime_value!(I32, u32); impl_from_runtime_value!(I32, u32);
impl_from_runtime_value!(I64, u64); impl_from_runtime_value!(I64, u64);
macro_rules! impl_wrap_into { macro_rules! impl_wrap_into {
($from: ident, $into: ident) => { ($from:ident, $into:ident) => {
impl WrapInto<$into> for $from { impl WrapInto<$into> for $from {
fn wrap_into(self) -> $into { fn wrap_into(self) -> $into {
self as $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); impl_wrap_into!(i32, i8);
@ -257,13 +265,19 @@ impl_wrap_into!(i32, i16);
impl_wrap_into!(i64, i8); impl_wrap_into!(i64, i8);
impl_wrap_into!(i64, i16); impl_wrap_into!(i64, i16);
impl_wrap_into!(i64, i32); impl_wrap_into!(i64, i32);
impl_wrap_into!(i64, f32); impl_wrap_into!(i64, f32, F32);
impl_wrap_into!(u64, f32); impl_wrap_into!(u64, f32, F32);
// Casting from an f64 to an f32 will produce the closest possible value (rounding strategy unspecified) // 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 // 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. // largest or smallest finite value representable by f32. This is a bug and will be fixed.
impl_wrap_into!(f64, f32); impl_wrap_into!(f64, f32);
impl WrapInto<F32> for F64 {
fn wrap_into(self) -> F32 {
(f64::from(self) as f32).into()
}
}
macro_rules! impl_try_truncate_into { macro_rules! impl_try_truncate_into {
($from: ident, $into: ident) => { ($from: ident, $into: ident) => {
impl TryTruncateInto<$into, TrapKind> for $from { impl TryTruncateInto<$into, TrapKind> for $from {
@ -284,7 +298,14 @@ macro_rules! impl_try_truncate_into {
Ok(self as $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); 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!(f32, u64);
impl_try_truncate_into!(f64, u32); impl_try_truncate_into!(f64, u32);
impl_try_truncate_into!(f64, u64); 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 { macro_rules! impl_extend_into {
($from: ident, $into: ident) => { ($from:ident, $into:ident) => {
impl ExtendInto<$into> for $from { impl ExtendInto<$into> for $from {
fn extend_into(self) -> $into { fn extend_into(self) -> $into {
self as $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); impl_extend_into!(i8, i32);
@ -325,6 +361,20 @@ impl_extend_into!(i64, f64);
impl_extend_into!(u64, f64); impl_extend_into!(u64, f64);
impl_extend_into!(f32, 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<F64> for F32 {
fn extend_into(self) -> F64 {
(f32::from(self) as f64).into()
}
}
macro_rules! impl_transmute_into_self { macro_rules! impl_transmute_into_self {
($type: ident) => { ($type: ident) => {
impl TransmuteInto<$type> for $type { impl TransmuteInto<$type> for $type {
@ -339,6 +389,8 @@ impl_transmute_into_self!(i32);
impl_transmute_into_self!(i64); impl_transmute_into_self!(i64);
impl_transmute_into_self!(f32); impl_transmute_into_self!(f32);
impl_transmute_into_self!(f64); impl_transmute_into_self!(f64);
impl_transmute_into_self!(F32);
impl_transmute_into_self!(F64);
macro_rules! impl_transmute_into_as { macro_rules! impl_transmute_into_as {
($from: ident, $into: ident) => { ($from: ident, $into: ident) => {
@ -357,6 +409,49 @@ impl_transmute_into_as!(u32, i32);
impl_transmute_into_as!(i64, u64); impl_transmute_into_as!(i64, u64);
impl_transmute_into_as!(u64, i64); 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<i32> for f32 { impl TransmuteInto<i32> for f32 {
fn transmute_into(self) -> i32 { self.to_bits() as i32 } 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<u8> {
(self.to_bits() as i32).into_little_endian()
}
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
i32::from_little_endian(buffer).map(|val| Self::from_bits(val as _))
}
}
impl LittleEndianConvert for F64 {
fn into_little_endian(self) -> Vec<u8> {
(self.to_bits() as i64).into_little_endian()
}
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
i64::from_little_endian(buffer).map(|val| Self::from_bits(val as _))
}
}
macro_rules! impl_integer_arithmetic_ops { macro_rules! impl_integer_arithmetic_ops {
($type: ident) => { ($type: ident) => {
impl ArithmeticOps<$type> for $type { impl ArithmeticOps<$type> for $type {
@ -538,6 +653,8 @@ macro_rules! impl_float_arithmetic_ops {
impl_float_arithmetic_ops!(f32); impl_float_arithmetic_ops!(f32);
impl_float_arithmetic_ops!(f64); impl_float_arithmetic_ops!(f64);
impl_float_arithmetic_ops!(F32);
impl_float_arithmetic_ops!(F64);
macro_rules! impl_integer { macro_rules! impl_integer {
($type: ident) => { ($type: ident) => {
@ -561,13 +678,26 @@ impl_integer!(i64);
impl_integer!(u64); impl_integer!(u64);
macro_rules! impl_float { 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 { impl Float<$type> for $type {
fn abs(self) -> $type { self.abs() } fn abs(self) -> $type {
fn floor(self) -> $type { self.floor() } $intermediate::abs(self.into()).into()
fn ceil(self) -> $type { self.ceil() } }
fn trunc(self) -> $type { self.trunc() } fn floor(self) -> $type {
fn round(self) -> $type { self.round() } $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 { fn nearest(self) -> $type {
let round = self.round(); let round = self.round();
if self.fract().abs() != 0.5 { if self.fract().abs() != 0.5 {
@ -583,12 +713,13 @@ macro_rules! impl_float {
round 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. // This instruction corresponds to what is sometimes called "minNaN" in other languages.
fn min(self, other: $type) -> $type { fn min(self, other: $type) -> $type {
if self.is_nan() || other.is_nan() { if self.is_nan() || other.is_nan() {
use std::$type; return ::std::$intermediate::NAN.into();
return $type::NAN;
} }
self.min(other) self.min(other)
@ -596,8 +727,7 @@ macro_rules! impl_float {
// This instruction corresponds to what is sometimes called "maxNaN" in other languages. // This instruction corresponds to what is sometimes called "maxNaN" in other languages.
fn max(self, other: $type) -> $type { fn max(self, other: $type) -> $type {
if self.is_nan() || other.is_nan() { if self.is_nan() || other.is_nan() {
use std::$type; return ::std::$intermediate::NAN.into();
return $type::NAN;
} }
self.max(other) self.max(other)
@ -623,8 +753,10 @@ macro_rules! impl_float {
} }
} }
} }
} };
} }
impl_float!(f32, i32); impl_float!(f32, i32);
impl_float!(f64, i64); impl_float!(f64, i64);
impl_float!(F32, f32, i32);
impl_float!(F64, f64, i64);

View File

@ -2,21 +2,19 @@
use std::collections::HashMap; use std::collections::HashMap;
use wasmi::{ use wabt::script::{self, Action, Command, CommandKind, ScriptParser, Value};
Error as InterpreterError, Externals, FuncInstance, FuncRef, GlobalDescriptor, use wasmi::memory_units::Pages;
use wasmi::{Error as InterpreterError, Externals, FuncInstance, FuncRef, GlobalDescriptor,
GlobalInstance, GlobalRef, ImportResolver, ImportsBuilder, MemoryDescriptor, GlobalInstance, GlobalRef, ImportResolver, ImportsBuilder, MemoryDescriptor,
MemoryInstance, MemoryRef, Module, ModuleImportResolver, ModuleInstance, ModuleRef, MemoryInstance, MemoryRef, Module, ModuleImportResolver, ModuleInstance, ModuleRef,
RuntimeArgs, RuntimeValue, Signature, TableDescriptor, TableInstance, TableRef, Trap, RuntimeArgs, RuntimeValue, Signature, TableDescriptor, TableInstance, TableRef, Trap};
};
use wasmi::memory_units::Pages;
use wabt::script::{self, Action, Command, CommandKind, ScriptParser, Value};
fn spec_to_runtime_value(value: Value) -> RuntimeValue { fn spec_to_runtime_value(val: Value<u32, u64>) -> RuntimeValue {
match value { match val {
Value::I32(v) => RuntimeValue::I32(v), Value::I32(v) => RuntimeValue::I32(v),
Value::I64(v) => RuntimeValue::I64(v), Value::I64(v) => RuntimeValue::I64(v),
Value::F32(v) => RuntimeValue::F32(v), Value::F32(v) => RuntimeValue::F32(v.into()),
Value::F64(v) => RuntimeValue::F64(v), Value::F64(v) => RuntimeValue::F64(v.into()),
} }
} }
@ -54,8 +52,8 @@ impl SpecModule {
table: TableInstance::alloc(10, Some(20)).unwrap(), table: TableInstance::alloc(10, Some(20)).unwrap(),
memory: MemoryInstance::alloc(Pages(1), Some(Pages(2))).unwrap(), memory: MemoryInstance::alloc(Pages(1), Some(Pages(2))).unwrap(),
global_i32: GlobalInstance::alloc(RuntimeValue::I32(666), false), global_i32: GlobalInstance::alloc(RuntimeValue::I32(666), false),
global_f32: GlobalInstance::alloc(RuntimeValue::F32(666.0), false), global_f32: GlobalInstance::alloc(RuntimeValue::F32(666.0.into()), false),
global_f64: GlobalInstance::alloc(RuntimeValue::F64(666.0), false), global_f64: GlobalInstance::alloc(RuntimeValue::F64(666.0.into()), false),
} }
} }
} }
@ -121,7 +119,7 @@ impl ModuleImportResolver for SpecModule {
_ => Err(InterpreterError::Instantiation(format!( _ => Err(InterpreterError::Instantiation(format!(
"Unknown host global import {}", "Unknown host global import {}",
field_name field_name
))) ))),
} }
} }
@ -269,7 +267,11 @@ fn try_load(wasm: &[u8], spec_driver: &mut SpecDriver) -> Result<(), Error> {
Ok(()) Ok(())
} }
fn load_module(wasm: &[u8], name: &Option<String>, spec_driver: &mut SpecDriver) -> Result<ModuleRef, Error> { fn load_module(
wasm: &[u8],
name: &Option<String>,
spec_driver: &mut SpecDriver,
) -> Result<ModuleRef, Error> {
let module = try_load_module(wasm)?; let module = try_load_module(wasm)?;
let instance = ModuleInstance::new(&module, spec_driver) let instance = ModuleInstance::new(&module, spec_driver)
.map_err(|e| Error::Load(e.to_string()))? .map_err(|e| Error::Load(e.to_string()))?
@ -284,7 +286,7 @@ fn load_module(wasm: &[u8], name: &Option<String>, spec_driver: &mut SpecDriver)
fn run_action( fn run_action(
program: &mut SpecDriver, program: &mut SpecDriver,
action: &Action, action: &Action<u32, u64>,
) -> Result<Option<RuntimeValue>, InterpreterError> { ) -> Result<Option<RuntimeValue>, InterpreterError> {
match *action { match *action {
Action::Invoke { Action::Invoke {
@ -298,14 +300,11 @@ fn run_action(
"Expected program to have loaded module {:?}", "Expected program to have loaded module {:?}",
module module
)); ));
module.invoke_export( let vec_args = args.iter()
field,
&args.iter()
.cloned() .cloned()
.map(spec_to_runtime_value) .map(spec_to_runtime_value)
.collect::<Vec<_>>(), .collect::<Vec<_>>();
program.spec_module(), module.invoke_export(field, &vec_args, program.spec_module())
)
} }
Action::Get { Action::Get {
ref module, ref module,
@ -342,11 +341,31 @@ fn try_spec(name: &str) -> Result<(), Error> {
let mut spec_driver = SpecDriver::new(); let mut spec_driver = SpecDriver::new();
let spec_script_path = format!("tests/spec/testsuite/{}.wast", name); 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 parser = ScriptParser::from_file(spec_script_path).expect("Can't read spec script");
let mut errors = vec![];
while let Some(Command { kind, line }) = parser.next()? { while let Some(Command { kind, line }) = parser.next()? {
println!("Line {}:", line); 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,
));
}
}};
}
println!("Running spec cmd {}: {:?}", line, kind);
match kind { match kind {
CommandKind::Module { name, module, .. } => { CommandKind::Module { name, module, .. } => {
load_module(&module.into_vec()?, &name, &mut spec_driver).expect("Failed to load module"); load_module(&module.into_vec()?, &name, &mut spec_driver)
.expect("Failed to load module");
} }
CommandKind::AssertReturn { action, expected } => { CommandKind::AssertReturn { action, expected } => {
let result = run_action(&mut spec_driver, &action); let result = run_action(&mut spec_driver, &action);
@ -375,7 +394,6 @@ fn try_spec(name: &str) -> Result<(), Error> {
spec_expected @ _ => assert_eq!(actual_result, spec_expected), spec_expected @ _ => assert_eq!(actual_result, spec_expected),
} }
} }
println!("assert_return at line {} - success", line);
} }
Err(e) => { Err(e) => {
panic!("Expected action to return value, got error: {:?}", e); panic!("Expected action to return value, got error: {:?}", e);
@ -400,7 +418,6 @@ fn try_spec(name: &str) -> Result<(), Error> {
} }
} }
} }
println!("assert_return_nan at line {} - success", line);
} }
Err(e) => { Err(e) => {
panic!("Expected action to return value, got error: {:?}", e); panic!("Expected action to return value, got error: {:?}", e);
@ -411,7 +428,7 @@ fn try_spec(name: &str) -> Result<(), Error> {
let result = run_action(&mut spec_driver, &action); let result = run_action(&mut spec_driver, &action);
match result { match result {
Ok(result) => panic!("Expected exhaustion, got result: {:?}", result), Ok(result) => panic!("Expected exhaustion, got result: {:?}", result),
Err(e) => println!("assert_exhaustion at line {} - success ({:?})", line, e), Err(_e) => {},
} }
} }
CommandKind::AssertTrap { action, .. } => { CommandKind::AssertTrap { action, .. } => {
@ -423,9 +440,7 @@ fn try_spec(name: &str) -> Result<(), Error> {
result result
); );
} }
Err(e) => { Err(_e) => {}
println!("assert_trap at line {} - success ({:?})", line, e);
}
} }
} }
CommandKind::AssertInvalid { module, .. } CommandKind::AssertInvalid { module, .. }
@ -434,13 +449,13 @@ fn try_spec(name: &str) -> Result<(), Error> {
let module_load = try_load(&module.into_vec()?, &mut spec_driver); let module_load = try_load(&module.into_vec()?, &mut spec_driver);
match module_load { match module_load {
Ok(_) => panic!("Expected invalid module definition, got some module!"), Ok(_) => panic!("Expected invalid module definition, got some module!"),
Err(e) => println!("assert_invalid at line {} - success ({:?})", line, e), Err(_e) => {},
} }
} }
CommandKind::AssertUninstantiable { module, .. } => { CommandKind::AssertUninstantiable { module, .. } => {
match try_load(&module.into_vec()?, &mut spec_driver) { match try_load(&module.into_vec()?, &mut spec_driver) {
Ok(_) => panic!("Expected error running start function at line {}", line), Ok(_) => panic!("Expected error running start function at line {}", line),
Err(e) => println!("assert_uninstantiable - success ({:?})", e), Err(_e) => {},
} }
} }
CommandKind::Register { name, as_name, .. } => { CommandKind::Register { name, as_name, .. } => {
@ -457,5 +472,14 @@ fn try_spec(name: &str) -> Result<(), Error> {
} }
} }
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(()) Ok(())
} }