Compare commits
25 Commits
Author | SHA1 | Date |
---|---|---|
Sergey Pepyakin | 0474402aca | |
Sergey Pepyakin | c9c83e44c7 | |
Sergey Pepyakin | ed9488629d | |
Sergey Pepyakin | 2b149f5bdf | |
Sergey Pepyakin | 05027e617e | |
Sergey Pepyakin | 0f6b3e15f4 | |
Sergey Pepyakin | 27c5501ab0 | |
Sergey Pepyakin | e9a414d504 | |
Sergey Pepyakin | 3bd8f8a250 | |
Sergey Pepyakin | 649818482d | |
Sergey Pepyakin | d49b9fe252 | |
Sergey Pepyakin | 244648f40d | |
Sergey Pepyakin | 9410d17cb6 | |
Sergey Pepyakin | 1b8cf2705e | |
Sergey Pepyakin | cd9bcc073a | |
Sergey Pepyakin | da384ed27a | |
Sergey Pepyakin | 0502619259 | |
Sergey Pepyakin | 0d5a87f64f | |
Sergey Pepyakin | f578675ba6 | |
Sergey Pepyakin | 6221f50545 | |
Sergey Pepyakin | b83e6178b8 | |
Sergey Pepyakin | af19c66589 | |
Sergey Pepyakin | 4c51136e7c | |
Sergey Pepyakin | 5f49943cfb | |
Sergey Pepyakin | 23b10d386c |
|
@ -1,3 +1,3 @@
|
||||||
[submodule "tests/spec/testsuite"]
|
[submodule "tests/spec/testsuite"]
|
||||||
path = tests/spec/testsuite
|
path = wasmi/tests/spec/testsuite
|
||||||
url = https://github.com/WebAssembly/testsuite.git
|
url = https://github.com/WebAssembly/testsuite.git
|
||||||
|
|
38
Cargo.toml
38
Cargo.toml
|
@ -1,31 +1,9 @@
|
||||||
[package]
|
[workspace]
|
||||||
name = "wasmi"
|
members = [
|
||||||
version = "0.4.3"
|
"wasmi",
|
||||||
authors = ["Nikolay Volf <nikvolf@gmail.com>", "Svyatoslav Nikolsky <svyatonik@yandex.ru>", "Sergey Pepyakin <s.pepyakin@gmail.com>"]
|
"derive",
|
||||||
license = "MIT/Apache-2.0"
|
]
|
||||||
readme = "README.md"
|
|
||||||
repository = "https://github.com/paritytech/wasmi"
|
|
||||||
documentation = "https://paritytech.github.io/wasmi/"
|
|
||||||
description = "WebAssembly interpreter"
|
|
||||||
keywords = ["wasm", "webassembly", "bytecode", "interpreter"]
|
|
||||||
exclude = [ "/res/*", "/tests/*", "/fuzz/*", "/benches/*" ]
|
|
||||||
|
|
||||||
[features]
|
exclude = [
|
||||||
default = ["std"]
|
"benches", # uses custom profile
|
||||||
# Disable for no_std support
|
]
|
||||||
std = ["parity-wasm/std", "byteorder/std"]
|
|
||||||
# Enable for no_std support
|
|
||||||
# hashbrown only works on no_std
|
|
||||||
core = ["hashbrown", "libm"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
parity-wasm = { version = "0.31", default-features = false }
|
|
||||||
byteorder = { version = "1.0", default-features = false }
|
|
||||||
hashbrown = { version = "0.1.8", optional = true }
|
|
||||||
memory_units = "0.3.0"
|
|
||||||
libm = { version = "0.1.2", optional = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
assert_matches = "1.1"
|
|
||||||
rand = "0.4.2"
|
|
||||||
wabt = "0.6"
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
authors = ["Sergey Pepyakin <s.pepyakin@gmail.com>"]
|
authors = ["Sergey Pepyakin <s.pepyakin@gmail.com>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasmi = { path = ".." }
|
wasmi = { path = "../wasmi" }
|
||||||
assert_matches = "1.2"
|
assert_matches = "1.2"
|
||||||
wabt = "0.6"
|
wabt = "0.6"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "wasmi-derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Sergey Pepyakin <sergei@parity.io>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
quote = "0.6"
|
||||||
|
syn = { version = "0.15.0", features = ['full'] }
|
||||||
|
proc-macro2 = "0.4.9"
|
|
@ -0,0 +1,228 @@
|
||||||
|
//! This module generates a trait implementation for `Externals` on the target type.
|
||||||
|
//! It also generates a function called `resolve` that returns a `ModuleImportResolved`.
|
||||||
|
//!
|
||||||
|
//! The code generation is rather simple but it relies heavily on type inference.
|
||||||
|
|
||||||
|
use crate::parser::{FuncDef, ImplBlockDef};
|
||||||
|
use proc_macro2::{Ident, Span, TokenStream};
|
||||||
|
use quote::{quote, quote_spanned, ToTokens};
|
||||||
|
|
||||||
|
pub fn codegen(ext_def: &ImplBlockDef, to: &mut TokenStream) {
|
||||||
|
let mut externals = TokenStream::new();
|
||||||
|
let mut module_resolver = TokenStream::new();
|
||||||
|
|
||||||
|
derive_externals(ext_def, &mut externals);
|
||||||
|
derive_module_resolver(ext_def, &mut module_resolver);
|
||||||
|
|
||||||
|
let (impl_generics, _, where_clause) = ext_def.generics.split_for_impl();
|
||||||
|
let ty = &ext_def.ty;
|
||||||
|
|
||||||
|
(quote! {
|
||||||
|
impl #impl_generics #ty #where_clause {
|
||||||
|
const __WASMI_DERIVE_IMPL: () = {
|
||||||
|
extern crate wasmi as _wasmi;
|
||||||
|
extern crate core as _core;
|
||||||
|
|
||||||
|
use _core::{
|
||||||
|
result::Result,
|
||||||
|
option::Option,
|
||||||
|
};
|
||||||
|
use _wasmi::{
|
||||||
|
Trap, RuntimeValue, RuntimeArgs, Externals, ValueType, ModuleImportResolver,
|
||||||
|
Signature, FuncRef, Error, FuncInstance,
|
||||||
|
derive_support::{
|
||||||
|
IntoWasmResult,
|
||||||
|
IntoWasmValue,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn materialize_arg_ty<W: IntoWasmValue>(_w: Option<W>) -> ValueType {
|
||||||
|
W::VALUE_TYPE
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn materialize_ret_type<W: IntoWasmResult>(_w: Option<W>) -> Option<ValueType> {
|
||||||
|
W::VALUE_TYPE
|
||||||
|
}
|
||||||
|
|
||||||
|
#externals
|
||||||
|
#module_resolver
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.to_tokens(to);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_dispatch_func_arm(func: &FuncDef) -> TokenStream {
|
||||||
|
let index = func.index as usize;
|
||||||
|
let return_ty_span = func.return_ty.clone().unwrap_or_else(|| Span::call_site());
|
||||||
|
|
||||||
|
let mut unmarshall_args = TokenStream::new();
|
||||||
|
for param in &func.params {
|
||||||
|
let param_span = param.ident.span();
|
||||||
|
let ident = ¶m.ident;
|
||||||
|
|
||||||
|
(quote_spanned! {param_span=>
|
||||||
|
let #ident =
|
||||||
|
args.next()
|
||||||
|
.and_then(|rt_val| rt_val.try_into())
|
||||||
|
.unwrap();
|
||||||
|
})
|
||||||
|
.to_tokens(&mut unmarshall_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
let prologue = quote! {
|
||||||
|
let mut args = args.as_ref().iter();
|
||||||
|
#unmarshall_args
|
||||||
|
};
|
||||||
|
let epilogue = quote_spanned! {return_ty_span=>
|
||||||
|
IntoWasmResult::into_wasm_result(r)
|
||||||
|
};
|
||||||
|
|
||||||
|
let call = {
|
||||||
|
let params = func.params.iter().map(|param| param.ident.clone());
|
||||||
|
let name = Ident::new(&func.name, Span::call_site());
|
||||||
|
quote! {
|
||||||
|
#name( #(#params),* )
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(quote! {
|
||||||
|
#index => {
|
||||||
|
#prologue
|
||||||
|
let r = self.#call;
|
||||||
|
#epilogue
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn derive_externals(ext_def: &ImplBlockDef, to: &mut TokenStream) {
|
||||||
|
let (impl_generics, _, where_clause) = ext_def.generics.split_for_impl();
|
||||||
|
let ty = &ext_def.ty;
|
||||||
|
|
||||||
|
let mut match_arms = vec![];
|
||||||
|
for func in &ext_def.funcs {
|
||||||
|
match_arms.push(emit_dispatch_func_arm(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
(quote::quote! {
|
||||||
|
impl #impl_generics Externals for #ty #where_clause {
|
||||||
|
fn invoke_index(
|
||||||
|
&mut self,
|
||||||
|
index: usize,
|
||||||
|
args: RuntimeArgs,
|
||||||
|
) -> Result<Option<RuntimeValue>, Trap> {
|
||||||
|
match index {
|
||||||
|
#(#match_arms),*
|
||||||
|
_ => panic!("fn with index {} is undefined", index),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.to_tokens(to);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_resolve_func_arm(func: &FuncDef) -> TokenStream {
|
||||||
|
let index = func.index as usize;
|
||||||
|
let string_ident = &func.name;
|
||||||
|
let return_ty_span = func.return_ty.clone().unwrap_or_else(|| Span::call_site());
|
||||||
|
|
||||||
|
let call = {
|
||||||
|
let params = func.params.iter().map(|param| {
|
||||||
|
let ident = param.ident.clone();
|
||||||
|
let span = param.ident.span();
|
||||||
|
quote_spanned! {span=> #ident.unwrap() }
|
||||||
|
});
|
||||||
|
let name = Ident::new(&func.name, Span::call_site());
|
||||||
|
quote! {
|
||||||
|
Self::#name( panic!(), #(#params),* )
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let init = func
|
||||||
|
.params
|
||||||
|
.iter()
|
||||||
|
.map(|param| {
|
||||||
|
let ident = ¶m.ident;
|
||||||
|
quote! {
|
||||||
|
let #ident = None;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let params_materialized_tys = func
|
||||||
|
.params
|
||||||
|
.iter()
|
||||||
|
.map(|param| {
|
||||||
|
let ident = ¶m.ident;
|
||||||
|
let span = param.ident.span();
|
||||||
|
quote_spanned! {span=> materialize_arg_ty(#ident) }
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let materialized_return_ty = quote_spanned! { return_ty_span=>
|
||||||
|
materialize_ret_type(return_val)
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
if name == #string_ident {
|
||||||
|
// initialize variables
|
||||||
|
#(#init)*
|
||||||
|
|
||||||
|
#[allow(unreachable_code)]
|
||||||
|
let return_val = if false {
|
||||||
|
// calling self for typeinference
|
||||||
|
Some(#call)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// at this point types of all variables and return_val are inferred.
|
||||||
|
if signature.params() != &[#(#params_materialized_tys),*]
|
||||||
|
|| signature.return_type() != #materialized_return_ty
|
||||||
|
{
|
||||||
|
return Err(Error::Instantiation(
|
||||||
|
format!("Export {} has different signature {:?}", #string_ident, signature),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(FuncInstance::alloc_host(signature.clone(), #index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn derive_module_resolver(ext_def: &ImplBlockDef, to: &mut TokenStream) {
|
||||||
|
let (impl_generics, _, where_clause) = ext_def.generics.split_for_impl();
|
||||||
|
let ty = &ext_def.ty;
|
||||||
|
|
||||||
|
let mut match_arms = vec![];
|
||||||
|
for func in &ext_def.funcs {
|
||||||
|
match_arms.push(emit_resolve_func_arm(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
(quote::quote! {
|
||||||
|
impl #impl_generics #ty #where_clause {
|
||||||
|
fn resolver() -> impl ModuleImportResolver {
|
||||||
|
// Use a closure to have an ability to use `Self` type
|
||||||
|
let resolve_func = |name: &str, signature: &Signature| -> Result<FuncRef, Error> {
|
||||||
|
#(#match_arms)*
|
||||||
|
|
||||||
|
Err(Error::Instantiation(
|
||||||
|
format!("Export {} not found", name),
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Resolver(fn(&str, &Signature) -> Result<FuncRef, Error>);
|
||||||
|
impl ModuleImportResolver for Resolver {
|
||||||
|
#[inline(always)]
|
||||||
|
fn resolve_func(&self, name: &str, signature: &Signature) -> Result<FuncRef, Error> {
|
||||||
|
(self.0)(name, signature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Resolver(resolve_func)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).to_tokens(to);
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
use proc_macro2::{Span, TokenStream};
|
||||||
|
use quote::{quote_spanned, ToTokens};
|
||||||
|
|
||||||
|
macro_rules! err_span {
|
||||||
|
($span:expr, $($msg:tt)*) => (
|
||||||
|
$crate::error::CompileError::new_spanned(&$span, format!($($msg)*))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CompileError {
|
||||||
|
msg: String,
|
||||||
|
span: Option<Span>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompileError {
|
||||||
|
pub fn new_spanned(span: &Span, msg: String) -> Self {
|
||||||
|
CompileError {
|
||||||
|
span: Some(*span),
|
||||||
|
msg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(msg: String) -> Self {
|
||||||
|
CompileError { span: None, msg }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for CompileError {
|
||||||
|
fn to_tokens(&self, dst: &mut TokenStream) {
|
||||||
|
let msg = &self.msg;
|
||||||
|
let span = self.span.unwrap_or_else(|| Span::call_site());
|
||||||
|
(quote_spanned! { span=>
|
||||||
|
compile_error!(#msg);
|
||||||
|
})
|
||||||
|
.to_tokens(dst);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
//! A derive macro for generation of simple `Externals`.
|
||||||
|
//!
|
||||||
|
//! ```nocompile
|
||||||
|
//! #// no compile because we can't depend on wasmi here, or otherwise it will be a circular dependency.
|
||||||
|
//! extern crate wasmi;
|
||||||
|
//! extern crate wasmi_derive;
|
||||||
|
//!
|
||||||
|
//! use std::fmt;
|
||||||
|
//! use wasmi::HostError;
|
||||||
|
//! use wasmi_derive::derive_externals;
|
||||||
|
//!
|
||||||
|
//! #[derive(Debug)]
|
||||||
|
//! struct NoInfoError;
|
||||||
|
//! impl HostError for NoInfoError {}
|
||||||
|
//! impl fmt::Display for NoInfoError {
|
||||||
|
//! fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
//! write!(f, "NoInfoError")
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! struct NonStaticExternals<'a> {
|
||||||
|
//! state: &'a mut usize,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[derive_externals]
|
||||||
|
//! impl<'a> NonStaticExternals<'a> {
|
||||||
|
//! pub fn add(&self, a: u32, b: u32) -> u32 {
|
||||||
|
//! a + b
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! pub fn increment(&mut self) {
|
||||||
|
//! *self.state += 1;
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! pub fn traps(&self) -> Result<(), NoInfoError> {
|
||||||
|
//! Err(NoInfoError)
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
|
||||||
|
// We reached the `recursion_limit` in quote macro.
|
||||||
|
#![recursion_limit = "128"]
|
||||||
|
|
||||||
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod error;
|
||||||
|
mod codegen;
|
||||||
|
mod parser;
|
||||||
|
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn derive_externals(_attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
let mut input: proc_macro2::TokenStream = input.into();
|
||||||
|
|
||||||
|
match parser::parse(input.clone()) {
|
||||||
|
Ok(ext_def) => {
|
||||||
|
codegen::codegen(&ext_def, &mut input);
|
||||||
|
input.into()
|
||||||
|
}
|
||||||
|
Err(err) => (quote::quote! { #err }).into(),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
use crate::error::CompileError;
|
||||||
|
use syn::{spanned::Spanned, FnArg, Ident, ImplItem, ImplItemMethod, ReturnType};
|
||||||
|
|
||||||
|
/// A parameter. This doesn't used for modeling `&self` or `&mut self` parameters.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Param {
|
||||||
|
/// A generated identifier used to name temporary variables
|
||||||
|
/// used for storing this parameter in generated code.
|
||||||
|
///
|
||||||
|
/// This ident is used primary used for its' span.
|
||||||
|
pub ident: syn::Ident,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A function definition parsed from an impl block.
|
||||||
|
pub struct FuncDef {
|
||||||
|
/// Assigned index of this function.
|
||||||
|
pub index: u32,
|
||||||
|
pub name: String,
|
||||||
|
/// The parameter of this function. This excludes the `&self` or `&mut self`.
|
||||||
|
pub params: Vec<Param>,
|
||||||
|
pub return_ty: Option<proc_macro2::Span>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is the core data structure which contains the list of all defined functions
|
||||||
|
/// and the data required for the code generator (e.g. for implementing a trait).
|
||||||
|
pub struct ImplBlockDef {
|
||||||
|
/// List of all defined external functions.
|
||||||
|
pub funcs: Vec<FuncDef>,
|
||||||
|
/// The generics required to implement a trait for this type.
|
||||||
|
pub generics: syn::Generics,
|
||||||
|
/// The type declaration to implement a trait, most typically
|
||||||
|
/// represented by a structure.
|
||||||
|
///
|
||||||
|
/// E.g.: `Foo<'a>`, `()`
|
||||||
|
pub ty: Box<syn::Type>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse an incoming stream of tokens into externalities definition.
|
||||||
|
pub fn parse(input: proc_macro2::TokenStream) -> Result<ImplBlockDef, CompileError> {
|
||||||
|
let item_impl = syn::parse2::<syn::ItemImpl>(input)
|
||||||
|
.map_err(|_| CompileError::new("failed to parse".to_string()))?;
|
||||||
|
|
||||||
|
let mut funcs = vec![];
|
||||||
|
|
||||||
|
for item in item_impl.items {
|
||||||
|
match item {
|
||||||
|
ImplItem::Method(ImplItemMethod { sig, .. }) => {
|
||||||
|
let index = funcs.len() as u32;
|
||||||
|
|
||||||
|
let params = sig
|
||||||
|
.decl
|
||||||
|
.inputs
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(idx, input)| {
|
||||||
|
// The first parameter should be either &self or &mut self.
|
||||||
|
// This makes code generation simpler.
|
||||||
|
if idx == 0 {
|
||||||
|
match input {
|
||||||
|
FnArg::SelfRef(_) => return None,
|
||||||
|
_ => {
|
||||||
|
return Some(Err(err_span!(
|
||||||
|
input.span(),
|
||||||
|
"only &self and &mut self supported as first argument"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let param_name = format!("arg{}", idx);
|
||||||
|
let ident = Ident::new(¶m_name, input.span());
|
||||||
|
Some(Ok(Param { ident }))
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<Param>, CompileError>>()?;
|
||||||
|
|
||||||
|
let return_ty = match sig.decl.output {
|
||||||
|
ReturnType::Default => None,
|
||||||
|
ReturnType::Type(_, ty) => Some(ty.span()),
|
||||||
|
};
|
||||||
|
|
||||||
|
funcs.push(FuncDef {
|
||||||
|
index,
|
||||||
|
name: sig.ident.to_string(),
|
||||||
|
params,
|
||||||
|
return_ty,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ImplBlockDef {
|
||||||
|
funcs,
|
||||||
|
generics: item_impl.generics.clone(),
|
||||||
|
ty: item_impl.self_ty.clone(),
|
||||||
|
})
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ publish = false
|
||||||
cargo-fuzz = true
|
cargo-fuzz = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasmi = { path = ".." }
|
wasmi = { path = "../wasmi" }
|
||||||
wabt = "0.6.0"
|
wabt = "0.6.0"
|
||||||
wasmparser = "0.14.1"
|
wasmparser = "0.14.1"
|
||||||
tempdir = "0.3.6"
|
tempdir = "0.3.6"
|
||||||
|
|
3
test.sh
3
test.sh
|
@ -4,6 +4,9 @@ set -eux
|
||||||
|
|
||||||
cd $(dirname $0)
|
cd $(dirname $0)
|
||||||
|
|
||||||
|
# Make sure that the testsuite submodule is checked out.
|
||||||
|
git submodule update --init wasmi/tests/spec/testsuite
|
||||||
|
|
||||||
time cargo test
|
time cargo test
|
||||||
|
|
||||||
cd -
|
cd -
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
// If you are to update this code, make sure you update the example in `wasmi-derive`.
|
||||||
|
|
||||||
|
extern crate wasmi;
|
||||||
|
extern crate wasmi_derive;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
use wasmi::HostError;
|
||||||
|
use wasmi_derive::derive_externals;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct NoInfoError;
|
||||||
|
impl HostError for NoInfoError {}
|
||||||
|
impl fmt::Display for NoInfoError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "NoInfoError")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NonStaticExternals<'a> {
|
||||||
|
state: &'a mut usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive_externals]
|
||||||
|
impl<'a> NonStaticExternals<'a> {
|
||||||
|
pub fn hello(&self, a: u32, b: u32) -> u32 {
|
||||||
|
a + b
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn increment(&mut self) {
|
||||||
|
*self.state += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn traps(&self) -> Result<(), NoInfoError> {
|
||||||
|
Err(NoInfoError)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
[package]
|
||||||
|
name = "wasmi"
|
||||||
|
version = "0.4.3"
|
||||||
|
authors = ["Nikolay Volf <nikvolf@gmail.com>", "Svyatoslav Nikolsky <svyatonik@yandex.ru>", "Sergey Pepyakin <s.pepyakin@gmail.com>"]
|
||||||
|
license = "MIT/Apache-2.0"
|
||||||
|
readme = "README.md"
|
||||||
|
repository = "https://github.com/paritytech/wasmi"
|
||||||
|
documentation = "https://paritytech.github.io/wasmi/"
|
||||||
|
description = "WebAssembly interpreter"
|
||||||
|
keywords = ["wasm", "webassembly", "bytecode", "interpreter"]
|
||||||
|
exclude = [ "/res/*", "/tests/*", "/fuzz/*", "/benches/*" ]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["std"]
|
||||||
|
# Disable for no_std support
|
||||||
|
std = ["parity-wasm/std", "byteorder/std"]
|
||||||
|
# Enable for no_std support
|
||||||
|
# hashbrown only works on no_std
|
||||||
|
core = ["hashbrown", "libm"]
|
||||||
|
derive = ["wasmi-derive"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
parity-wasm = { version = "0.31", default-features = false }
|
||||||
|
byteorder = { version = "1.0", default-features = false }
|
||||||
|
hashbrown = { version = "0.1.8", optional = true }
|
||||||
|
memory_units = "0.3.0"
|
||||||
|
libm = { version = "0.1.2", optional = true }
|
||||||
|
wasmi-derive = { version = "0.1", path = "../derive", optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
assert_matches = "1.1"
|
||||||
|
rand = "0.4.2"
|
||||||
|
wabt = "0.6"
|
||||||
|
wasmi-derive = { version = "0.1", path = "../derive" }
|
|
@ -1,18 +1,23 @@
|
||||||
|
//! A simple tic tac toe implementation.
|
||||||
|
//!
|
||||||
|
//! You specify two wasm modules with a certain ABI and this
|
||||||
|
//! program instantiates these modules and runs a module
|
||||||
|
//! on turn-by-turn basis.
|
||||||
|
//!
|
||||||
|
|
||||||
extern crate parity_wasm;
|
extern crate parity_wasm;
|
||||||
extern crate wasmi;
|
extern crate wasmi;
|
||||||
|
extern crate wasmi_derive;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use wasmi::{
|
use wasmi::{Error as InterpreterError, HostError, ImportsBuilder, ModuleInstance, ModuleRef};
|
||||||
Error as InterpreterError, Externals, FuncInstance, FuncRef, HostError, ImportsBuilder,
|
|
||||||
ModuleImportResolver, ModuleInstance, ModuleRef, RuntimeArgs, RuntimeValue, Signature, Trap,
|
|
||||||
ValueType,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
OutOfRange,
|
OutOfRange,
|
||||||
|
AlreadyPlayed,
|
||||||
AlreadyOccupied,
|
AlreadyOccupied,
|
||||||
Interpreter(InterpreterError),
|
Interpreter(InterpreterError),
|
||||||
}
|
}
|
||||||
|
@ -46,16 +51,6 @@ mod tictactoe {
|
||||||
Won(Player),
|
Won(Player),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Player {
|
|
||||||
pub fn into_i32(maybe_player: Option<Player>) -> i32 {
|
|
||||||
match maybe_player {
|
|
||||||
None => 0,
|
|
||||||
Some(Player::X) => 1,
|
|
||||||
Some(Player::O) => 2,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Game {
|
pub struct Game {
|
||||||
board: [Option<Player>; 9],
|
board: [Option<Player>; 9],
|
||||||
|
@ -133,59 +128,40 @@ mod tictactoe {
|
||||||
|
|
||||||
struct Runtime<'a> {
|
struct Runtime<'a> {
|
||||||
player: tictactoe::Player,
|
player: tictactoe::Player,
|
||||||
|
made_turn: bool,
|
||||||
game: &'a mut tictactoe::Game,
|
game: &'a mut tictactoe::Game,
|
||||||
}
|
}
|
||||||
|
|
||||||
const SET_FUNC_INDEX: usize = 0;
|
#[wasmi_derive::derive_externals]
|
||||||
const GET_FUNC_INDEX: usize = 1;
|
impl<'a> Runtime<'a> {
|
||||||
|
/// Puts a mark of the current player on the given cell.
|
||||||
impl<'a> Externals for Runtime<'a> {
|
///
|
||||||
fn invoke_index(
|
/// Traps if the index is out of bounds of game field or if the player
|
||||||
&mut self,
|
/// already made its turn.
|
||||||
index: usize,
|
pub fn set(&mut self, idx: i32) -> Result<(), Error> {
|
||||||
args: RuntimeArgs,
|
if self.made_turn {
|
||||||
) -> Result<Option<RuntimeValue>, Trap> {
|
return Err(Error::AlreadyPlayed);
|
||||||
match index {
|
|
||||||
SET_FUNC_INDEX => {
|
|
||||||
let idx: i32 = args.nth(0);
|
|
||||||
self.game.set(idx, self.player)?;
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
GET_FUNC_INDEX => {
|
|
||||||
let idx: i32 = args.nth(0);
|
|
||||||
let val: i32 = tictactoe::Player::into_i32(self.game.get(idx)?);
|
|
||||||
Ok(Some(val.into()))
|
|
||||||
}
|
|
||||||
_ => panic!("unknown function index"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.game.set(idx, self.player)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
struct RuntimeModuleImportResolver;
|
/// Returns the player index at the specified cell.
|
||||||
|
///
|
||||||
|
/// 0 - unoccupied
|
||||||
|
/// 1 - player X
|
||||||
|
/// 2 - player O
|
||||||
|
///
|
||||||
|
/// Traps if the index is out of bounds of game field.
|
||||||
|
pub fn get(&self, idx: i32) -> Result<i32, Error> {
|
||||||
|
use tictactoe::Player;
|
||||||
|
|
||||||
impl<'a> ModuleImportResolver for RuntimeModuleImportResolver {
|
Ok(match self.game.get(idx)? {
|
||||||
fn resolve_func(
|
None => 0,
|
||||||
&self,
|
Some(Player::X) => 1,
|
||||||
field_name: &str,
|
Some(Player::O) => 2,
|
||||||
_signature: &Signature,
|
})
|
||||||
) -> Result<FuncRef, InterpreterError> {
|
|
||||||
let func_ref = match field_name {
|
|
||||||
"set" => FuncInstance::alloc_host(
|
|
||||||
Signature::new(&[ValueType::I32][..], None),
|
|
||||||
SET_FUNC_INDEX,
|
|
||||||
),
|
|
||||||
"get" => FuncInstance::alloc_host(
|
|
||||||
Signature::new(&[ValueType::I32][..], Some(ValueType::I32)),
|
|
||||||
GET_FUNC_INDEX,
|
|
||||||
),
|
|
||||||
_ => {
|
|
||||||
return Err(InterpreterError::Function(format!(
|
|
||||||
"host module doesn't export function with name {}",
|
|
||||||
field_name
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(func_ref)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,10 +174,14 @@ fn instantiate(path: &str) -> Result<ModuleRef, Error> {
|
||||||
wasmi::Module::from_buffer(&wasm_buf)?
|
wasmi::Module::from_buffer(&wasm_buf)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut imports = ImportsBuilder::new();
|
let instance = {
|
||||||
imports.push_resolver("env", &RuntimeModuleImportResolver);
|
let resolver = Runtime::resolver();
|
||||||
|
|
||||||
let instance = ModuleInstance::new(&module, &imports)?.assert_no_start();
|
let mut imports = ImportsBuilder::new();
|
||||||
|
imports.push_resolver("env", &resolver);
|
||||||
|
|
||||||
|
ModuleInstance::new(&module, &imports)?.assert_no_start()
|
||||||
|
};
|
||||||
|
|
||||||
Ok(instance)
|
Ok(instance)
|
||||||
}
|
}
|
||||||
|
@ -222,6 +202,7 @@ fn play(
|
||||||
let mut runtime = Runtime {
|
let mut runtime = Runtime {
|
||||||
player: turn_of,
|
player: turn_of,
|
||||||
game: game,
|
game: game,
|
||||||
|
made_turn: false,
|
||||||
};
|
};
|
||||||
let _ = instance.invoke_export("mk_turn", &[], &mut runtime)?;
|
let _ = instance.invoke_export("mk_turn", &[], &mut runtime)?;
|
||||||
}
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
//! This module contains auxilary functions which one might find useful for
|
||||||
|
//! generating implementations of host related functionality like `Externals`.
|
||||||
|
|
||||||
|
use nan_preserving_float::{F32, F64};
|
||||||
|
use {RuntimeValue, Trap, ValueType};
|
||||||
|
|
||||||
|
/// A trait that represents a value that can be directly coerced to one of
|
||||||
|
/// wasm base value types.
|
||||||
|
pub trait IntoWasmValue {
|
||||||
|
/// The value type into which the self type is converted.
|
||||||
|
const VALUE_TYPE: ValueType;
|
||||||
|
/// Perform the conversion.
|
||||||
|
fn into_wasm_value(self) -> RuntimeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_convertible_to_wasm {
|
||||||
|
// TODO: Replace it to Kleene ? operator
|
||||||
|
($ty:ty, $wasm_ty:ident $(, as $cast_to:ty)* ) => {
|
||||||
|
impl IntoWasmValue for $ty {
|
||||||
|
const VALUE_TYPE: ValueType = ValueType::$wasm_ty;
|
||||||
|
fn into_wasm_value(self) -> RuntimeValue {
|
||||||
|
RuntimeValue::$wasm_ty(self $( as $cast_to)*)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_convertible_to_wasm!(i32, I32);
|
||||||
|
impl_convertible_to_wasm!(u32, I32, as i32);
|
||||||
|
impl_convertible_to_wasm!(i64, I64);
|
||||||
|
impl_convertible_to_wasm!(u64, I64, as i64);
|
||||||
|
impl_convertible_to_wasm!(F32, F32);
|
||||||
|
impl_convertible_to_wasm!(F64, F64);
|
||||||
|
|
||||||
|
/// A trait that represents a value that can be returned from a function.
|
||||||
|
///
|
||||||
|
/// Basically it is superset of `IntoWasmValue` types, adding the ability to return
|
||||||
|
/// the unit value (i.e. `()`) and return a value that signals a trap.
|
||||||
|
pub trait IntoWasmResult {
|
||||||
|
/// The value type into which the self type is converted or `None` in case
|
||||||
|
/// of the unit value (aka `()` aka `void`).
|
||||||
|
const VALUE_TYPE: Option<ValueType>;
|
||||||
|
/// Perform the conversion.
|
||||||
|
fn into_wasm_result(self) -> Result<Option<RuntimeValue>, Trap>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoWasmResult for () {
|
||||||
|
const VALUE_TYPE: Option<ValueType> = None;
|
||||||
|
fn into_wasm_result(self) -> Result<Option<RuntimeValue>, Trap> {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: IntoWasmValue, E: Into<Trap>> IntoWasmResult for Result<R, E> {
|
||||||
|
const VALUE_TYPE: Option<ValueType> = Some(R::VALUE_TYPE);
|
||||||
|
fn into_wasm_result(self) -> Result<Option<RuntimeValue>, Trap> {
|
||||||
|
self.map(|v| Some(v.into_wasm_value())).map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Into<Trap>> IntoWasmResult for Result<(), E> {
|
||||||
|
const VALUE_TYPE: Option<ValueType> = None;
|
||||||
|
fn into_wasm_result(self) -> Result<Option<RuntimeValue>, Trap> {
|
||||||
|
self.map(|_| None).map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: IntoWasmValue> IntoWasmResult for R {
|
||||||
|
const VALUE_TYPE: Option<ValueType> = Some(R::VALUE_TYPE);
|
||||||
|
fn into_wasm_result(self) -> Result<Option<RuntimeValue>, Trap> {
|
||||||
|
Ok(Some(self.into_wasm_value()))
|
||||||
|
}
|
||||||
|
}
|
|
@ -137,6 +137,8 @@ impl HostError {
|
||||||
|
|
||||||
/// Trait that allows to implement host functions.
|
/// Trait that allows to implement host functions.
|
||||||
///
|
///
|
||||||
|
/// You can use `wasmi-derive` or you can implement this trait manually.
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
|
@ -396,6 +396,8 @@ mod types;
|
||||||
mod validation;
|
mod validation;
|
||||||
mod value;
|
mod value;
|
||||||
|
|
||||||
|
pub mod derive_support;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
|
@ -0,0 +1,152 @@
|
||||||
|
// If you are to update this code, make sure you update the example in `wasmi-derive`.
|
||||||
|
|
||||||
|
extern crate wasmi;
|
||||||
|
extern crate wasmi_derive;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate assert_matches;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
use wasmi::HostError;
|
||||||
|
use wasmi_derive::derive_externals;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct NoInfoError;
|
||||||
|
impl HostError for NoInfoError {}
|
||||||
|
impl fmt::Display for NoInfoError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "NoInfoError")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NonStaticExternals<'a> {
|
||||||
|
state: &'a mut usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive_externals]
|
||||||
|
impl<'a> NonStaticExternals<'a> {
|
||||||
|
pub fn add(&self, a: u32, b: u32) -> u32 {
|
||||||
|
a + b
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn increment(&mut self) {
|
||||||
|
*self.state += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn traps(&self) -> Result<(), NoInfoError> {
|
||||||
|
Err(NoInfoError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod tests {
|
||||||
|
extern crate wabt;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use wasmi::{ImportsBuilder, Module, ModuleInstance, RuntimeValue};
|
||||||
|
|
||||||
|
macro_rules! gen_test {
|
||||||
|
($test_name:ident, $wat:expr, |$instance:ident| $verify:expr) => {
|
||||||
|
#[test]
|
||||||
|
fn $test_name() {
|
||||||
|
// We don't test wat compiliation, loading or decoding.
|
||||||
|
let wasm = &wabt::wat2wasm($wat).expect("invalid wat");
|
||||||
|
let module = Module::from_buffer(&wasm).expect("can't load module");
|
||||||
|
|
||||||
|
let resolver = NonStaticExternals::resolver();
|
||||||
|
|
||||||
|
let mut imports = ImportsBuilder::new();
|
||||||
|
imports.push_resolver("env", &resolver);
|
||||||
|
let $instance = ModuleInstance::new(&module, &imports);
|
||||||
|
|
||||||
|
$verify
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_test! { it_works,
|
||||||
|
r#"
|
||||||
|
(module
|
||||||
|
(import "env" "add" (func $add (param i32 i32) (result i32)))
|
||||||
|
(import "env" "increment" (func $increment))
|
||||||
|
(import "env" "traps" (func $traps))
|
||||||
|
|
||||||
|
(export "add" (func $add))
|
||||||
|
(export "increment" (func $increment))
|
||||||
|
(export "traps" (func $traps))
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
|not_started_instance_result| {
|
||||||
|
let mut state = 0;
|
||||||
|
let mut externals = NonStaticExternals {
|
||||||
|
state: &mut state,
|
||||||
|
};
|
||||||
|
|
||||||
|
let instance = not_started_instance_result.unwrap().assert_no_start();
|
||||||
|
assert_matches!(
|
||||||
|
instance.invoke_export(
|
||||||
|
"traps",
|
||||||
|
&[],
|
||||||
|
&mut externals,
|
||||||
|
),
|
||||||
|
Err(_)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(*externals.state, 0);
|
||||||
|
assert_matches!(
|
||||||
|
instance.invoke_export(
|
||||||
|
"increment",
|
||||||
|
&[],
|
||||||
|
&mut externals,
|
||||||
|
),
|
||||||
|
Ok(None)
|
||||||
|
);
|
||||||
|
assert_eq!(*externals.state, 1);
|
||||||
|
|
||||||
|
assert_matches!(
|
||||||
|
instance.invoke_export(
|
||||||
|
"add",
|
||||||
|
&[RuntimeValue::I32(5), RuntimeValue::I32(2)],
|
||||||
|
&mut externals,
|
||||||
|
),
|
||||||
|
Ok(Some(RuntimeValue::I32(7)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_test! { wrong_signature,
|
||||||
|
r#"
|
||||||
|
(module
|
||||||
|
(import "env" "add" (func $add (param i64 i32) (result i32)))
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
|result| {
|
||||||
|
match result {
|
||||||
|
Ok(_) => panic!(),
|
||||||
|
Err(err) => {
|
||||||
|
assert_eq!(
|
||||||
|
&format!("{:?}", err),
|
||||||
|
r#"Instantiation("Export add has different signature Signature { params: [I64, I32], return_type: Some(I32) }")"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_test! { nonexistent_name,
|
||||||
|
r#"
|
||||||
|
(module
|
||||||
|
(import "env" "foo" (func $foo))
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
|result| {
|
||||||
|
match result {
|
||||||
|
Ok(_) => panic!(),
|
||||||
|
Err(err) => {
|
||||||
|
assert_eq!(
|
||||||
|
&format!("{:?}", err),
|
||||||
|
r#"Instantiation("Export foo not found")"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
mod run;
|
||||||
|
|
||||||
|
macro_rules! run_test {
|
||||||
|
($label: expr, $test_name: ident) => {
|
||||||
|
#[test]
|
||||||
|
fn $test_name() {
|
||||||
|
self::run::spec($label)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
run_test!("address", wasm_address);
|
||||||
|
run_test!("align", wasm_align);
|
||||||
|
run_test!("binary", wasm_binary);
|
||||||
|
run_test!("block", wasm_block);
|
||||||
|
run_test!("br", wasm_br);
|
||||||
|
run_test!("br_if", wasm_br_if);
|
||||||
|
run_test!("br_table", wasm_br_table);
|
||||||
|
run_test!("break-drop", wasm_break_drop);
|
||||||
|
run_test!("call", wasm_call);
|
||||||
|
run_test!("call_indirect", wasm_call_indirect);
|
||||||
|
run_test!("comments", wasm_comments);
|
||||||
|
run_test!("const", wasm_const);
|
||||||
|
run_test!("conversions", wasm_conversions);
|
||||||
|
run_test!("custom", wasm_custom);
|
||||||
|
run_test!("custom_section", wasm_custom_section);
|
||||||
|
run_test!("data", wasm_data);
|
||||||
|
run_test!("elem", wasm_elem);
|
||||||
|
run_test!("endianness", wasm_endianness);
|
||||||
|
run_test!("exports", wasm_exports);
|
||||||
|
run_test!("f32", wasm_f32);
|
||||||
|
run_test!("f32_bitwise", wasm_f32_bitwise);
|
||||||
|
run_test!("f32_cmp", wasm_f32_cmp);
|
||||||
|
run_test!("f64", wasm_f64);
|
||||||
|
run_test!("f64_bitwise", wasm_f64_bitwise);
|
||||||
|
run_test!("f64_cmp", wasm_f64_cmp);
|
||||||
|
run_test!("fac", wasm_fac);
|
||||||
|
run_test!("float_exprs", wasm_float_exprs);
|
||||||
|
run_test!("float_literals", wasm_float_literals);
|
||||||
|
run_test!("float_memory", wasm_float_memory);
|
||||||
|
run_test!("float_misc", wasm_float_misc);
|
||||||
|
run_test!("forward", wasm_forward);
|
||||||
|
run_test!("func", wasm_func);
|
||||||
|
run_test!("func_ptrs", wasm_func_ptrs);
|
||||||
|
run_test!("get_local", wasm_get_local);
|
||||||
|
run_test!("globals", wasm_globals);
|
||||||
|
run_test!("i32", wasm_i32);
|
||||||
|
run_test!("i64", wasm_i64);
|
||||||
|
run_test!("if", wasm_if);
|
||||||
|
run_test!("imports", wasm_imports);
|
||||||
|
run_test!("inline-module", inline_module);
|
||||||
|
run_test!("int_exprs", wasm_int_exprs);
|
||||||
|
run_test!("int_literals", wasm_int_literals);
|
||||||
|
run_test!("labels", wasm_labels);
|
||||||
|
run_test!("left-to-right", wasm_left_to_right);
|
||||||
|
run_test!("linking", wasm_linking);
|
||||||
|
run_test!("loop", wasm_loop);
|
||||||
|
run_test!("memory", wasm_memory);
|
||||||
|
run_test!("memory_redundancy", wasm_memory_redundancy);
|
||||||
|
run_test!("memory_trap", wasm_memory_trap);
|
||||||
|
run_test!("names", wasm_names);
|
||||||
|
run_test!("nop", wasm_nop);
|
||||||
|
run_test!("resizing", wasm_resizing);
|
||||||
|
run_test!("return", wasm_return);
|
||||||
|
run_test!("select", wasm_select);
|
||||||
|
run_test!("set_local", wasm_set_local);
|
||||||
|
run_test!("skip-stack-guard-page", wasm_skip_stack_guard_page);
|
||||||
|
run_test!("stack", wasm_stack);
|
||||||
|
run_test!("start", wasm_start);
|
||||||
|
run_test!("store_retval", wasm_store_retval);
|
||||||
|
run_test!("switch", wasm_switch);
|
||||||
|
run_test!("tee_local", wasm_tee_local);
|
||||||
|
run_test!("token", wasm_token);
|
||||||
|
run_test!("traps", wasm_traps);
|
||||||
|
run_test!("type", wasm_type);
|
||||||
|
run_test!("typecheck", wasm_typecheck);
|
||||||
|
run_test!("unreachable", wasm_unreachable);
|
||||||
|
run_test!("unreached-invalid", wasm_unreached_invalid);
|
||||||
|
run_test!("unwind", wasm_unwind);
|
||||||
|
run_test!("utf8-custom-section-id", wasm_utf8_custom_section_id);
|
||||||
|
run_test!("utf8-import-field", wasm_utf8_import_field);
|
||||||
|
run_test!("utf8-import-module", wasm_utf8_import_module);
|
||||||
|
run_test!("utf8-invalid-encoding", wasm_utf8_invalid_encoding);
|
|
@ -0,0 +1,503 @@
|
||||||
|
#![cfg(test)]
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fs::File;
|
||||||
|
|
||||||
|
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(val: Value<u32, u64>) -> 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)]
|
||||||
|
enum Error {
|
||||||
|
Load(String),
|
||||||
|
Start(Trap),
|
||||||
|
Script(script::Error),
|
||||||
|
Interpreter(InterpreterError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<InterpreterError> for Error {
|
||||||
|
fn from(e: InterpreterError) -> Error {
|
||||||
|
Error::Interpreter(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<script::Error> for Error {
|
||||||
|
fn from(e: script::Error) -> Error {
|
||||||
|
Error::Script(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SpecModule {
|
||||||
|
table: TableRef,
|
||||||
|
memory: MemoryRef,
|
||||||
|
global_i32: GlobalRef,
|
||||||
|
global_f32: GlobalRef,
|
||||||
|
global_f64: GlobalRef,
|
||||||
|
}
|
||||||
|
|
||||||
|
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.into()), false),
|
||||||
|
global_f64: GlobalInstance::alloc(RuntimeValue::F64(666.0.into()), false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PRINT_FUNC_INDEX: usize = 0;
|
||||||
|
|
||||||
|
impl Externals for SpecModule {
|
||||||
|
fn invoke_index(
|
||||||
|
&mut self,
|
||||||
|
index: usize,
|
||||||
|
args: RuntimeArgs,
|
||||||
|
) -> Result<Option<RuntimeValue>, Trap> {
|
||||||
|
match index {
|
||||||
|
PRINT_FUNC_INDEX => {
|
||||||
|
println!("print: {:?}", args);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
_ => panic!("SpecModule doesn't provide function at index {}", index),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModuleImportResolver for SpecModule {
|
||||||
|
fn resolve_func(
|
||||||
|
&self,
|
||||||
|
field_name: &str,
|
||||||
|
func_type: &Signature,
|
||||||
|
) -> Result<FuncRef, InterpreterError> {
|
||||||
|
let index = match field_name {
|
||||||
|
"print" => PRINT_FUNC_INDEX,
|
||||||
|
"print_i32" => PRINT_FUNC_INDEX,
|
||||||
|
"print_i32_f32" => PRINT_FUNC_INDEX,
|
||||||
|
"print_f64_f64" => PRINT_FUNC_INDEX,
|
||||||
|
"print_f32" => PRINT_FUNC_INDEX,
|
||||||
|
"print_f64" => PRINT_FUNC_INDEX,
|
||||||
|
_ => {
|
||||||
|
return Err(InterpreterError::Instantiation(format!(
|
||||||
|
"Unknown host func import {}",
|
||||||
|
field_name
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if func_type.return_type().is_some() {
|
||||||
|
return Err(InterpreterError::Instantiation(
|
||||||
|
"Function `print_` have unit return type".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let func = FuncInstance::alloc_host(func_type.clone(), index);
|
||||||
|
return Ok(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_global(
|
||||||
|
&self,
|
||||||
|
field_name: &str,
|
||||||
|
_global_type: &GlobalDescriptor,
|
||||||
|
) -> Result<GlobalRef, InterpreterError> {
|
||||||
|
match field_name {
|
||||||
|
"global_i32" => Ok(self.global_i32.clone()),
|
||||||
|
"global_f32" => Ok(self.global_f32.clone()),
|
||||||
|
"global_f64" => Ok(self.global_f64.clone()),
|
||||||
|
_ => Err(InterpreterError::Instantiation(format!(
|
||||||
|
"Unknown host global import {}",
|
||||||
|
field_name
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_memory(
|
||||||
|
&self,
|
||||||
|
field_name: &str,
|
||||||
|
_memory_type: &MemoryDescriptor,
|
||||||
|
) -> Result<MemoryRef, InterpreterError> {
|
||||||
|
if field_name == "memory" {
|
||||||
|
return Ok(self.memory.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(InterpreterError::Instantiation(format!(
|
||||||
|
"Unknown host memory import {}",
|
||||||
|
field_name
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_table(
|
||||||
|
&self,
|
||||||
|
field_name: &str,
|
||||||
|
_table_type: &TableDescriptor,
|
||||||
|
) -> Result<TableRef, InterpreterError> {
|
||||||
|
if field_name == "table" {
|
||||||
|
return Ok(self.table.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(InterpreterError::Instantiation(format!(
|
||||||
|
"Unknown host table import {}",
|
||||||
|
field_name
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SpecDriver {
|
||||||
|
spec_module: SpecModule,
|
||||||
|
instances: HashMap<String, ModuleRef>,
|
||||||
|
last_module: Option<ModuleRef>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpecDriver {
|
||||||
|
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 add_module(&mut self, name: Option<String>, module: ModuleRef) {
|
||||||
|
self.last_module = Some(module.clone());
|
||||||
|
if let Some(name) = name {
|
||||||
|
self.instances.insert(name, module);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn module(&self, name: &str) -> Result<ModuleRef, InterpreterError> {
|
||||||
|
self.instances.get(name).cloned().ok_or_else(|| {
|
||||||
|
InterpreterError::Instantiation(format!("Module not registered {}", name))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn module_or_last(&self, name: Option<&str>) -> Result<ModuleRef, InterpreterError> {
|
||||||
|
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<FuncRef, InterpreterError> {
|
||||||
|
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<GlobalRef, InterpreterError> {
|
||||||
|
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<MemoryRef, InterpreterError> {
|
||||||
|
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<TableRef, InterpreterError> {
|
||||||
|
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, Error> {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_module(
|
||||||
|
wasm: &[u8],
|
||||||
|
name: &Option<String>,
|
||||||
|
spec_driver: &mut SpecDriver,
|
||||||
|
) -> Result<ModuleRef, Error> {
|
||||||
|
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());
|
||||||
|
|
||||||
|
Ok(instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_action(
|
||||||
|
program: &mut SpecDriver,
|
||||||
|
action: &Action<u32, u64>,
|
||||||
|
) -> Result<Option<RuntimeValue>, 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
|
||||||
|
));
|
||||||
|
let vec_args = args
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(spec_to_runtime_value)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_spec(name: &str) -> Result<(), Error> {
|
||||||
|
let mut spec_driver = SpecDriver::new();
|
||||||
|
let spec_script_path = format!("tests/spec/testsuite/{}.wast", name);
|
||||||
|
|
||||||
|
use std::io::Read;
|
||||||
|
let mut spec_source = Vec::new();
|
||||||
|
let mut spec_file = File::open(&spec_script_path).expect("Can't open file");
|
||||||
|
spec_file
|
||||||
|
.read_to_end(&mut spec_source)
|
||||||
|
.expect("Can't read file");
|
||||||
|
|
||||||
|
let mut parser = ScriptParser::from_source_and_name(&spec_source, &format!("{}.wast", name))
|
||||||
|
.expect("Can't read spec script");
|
||||||
|
let mut errors = vec![];
|
||||||
|
|
||||||
|
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,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Running spec cmd {}: {:?}", line, kind);
|
||||||
|
|
||||||
|
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::<Vec<_>>();
|
||||||
|
let actual_result = result.into_iter().collect::<Vec<RuntimeValue>>();
|
||||||
|
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::<Vec<RuntimeValue>>() {
|
||||||
|
match actual_result {
|
||||||
|
RuntimeValue::F32(val) => {
|
||||||
|
if !val.is_nan() {
|
||||||
|
panic!("Expected nan value, got {:?}", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RuntimeValue::F64(val) => {
|
||||||
|
if !val.is_nan() {
|
||||||
|
panic!("Expected nan value, got {:?}", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val @ _ => {
|
||||||
|
panic!("Expected action to return float value, got {:?}", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
panic!("Expected action to return value, got error: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CommandKind::AssertExhaustion { action, .. } => {
|
||||||
|
let result = run_action(&mut spec_driver, &action);
|
||||||
|
match result {
|
||||||
|
Ok(result) => panic!("Expected exhaustion, got result: {:?}", result),
|
||||||
|
Err(_e) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CommandKind::AssertTrap { action, .. } => {
|
||||||
|
let result = run_action(&mut spec_driver, &action);
|
||||||
|
match result {
|
||||||
|
Ok(result) => {
|
||||||
|
panic!(
|
||||||
|
"Expected action to result in a trap, got result: {:?}",
|
||||||
|
result
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(_e) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CommandKind::AssertInvalid { module, .. }
|
||||||
|
| CommandKind::AssertMalformed { module, .. }
|
||||||
|
| CommandKind::AssertUnlinkable { module, .. } => {
|
||||||
|
let module_load = try_load(&module.into_vec(), &mut spec_driver);
|
||||||
|
match module_load {
|
||||||
|
Ok(_) => panic!("Expected invalid module definition, got some module!"),
|
||||||
|
Err(_e) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CommandKind::AssertUninstantiable { module, .. } => {
|
||||||
|
match try_load(&module.into_vec(), &mut spec_driver) {
|
||||||
|
Ok(_) => panic!("Expected error running start function at line {}", line),
|
||||||
|
Err(_e) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CommandKind::Register { name, as_name, .. } => {
|
||||||
|
let module = match spec_driver.module_or_last(name.as_ref().map(|x| x.as_ref())) {
|
||||||
|
Ok(module) => module,
|
||||||
|
Err(e) => panic!("No such module, at line {} - ({:?})", e, line),
|
||||||
|
};
|
||||||
|
spec_driver.add_module(Some(as_name.clone()), module);
|
||||||
|
}
|
||||||
|
CommandKind::PerformAction(action) => match run_action(&mut spec_driver, &action) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => panic!("Failed to invoke action at line {}: {:?}", line, e),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.is_empty() {
|
||||||
|
use std::fmt::Write;
|
||||||
|
let mut out = "\n".to_owned();
|
||||||
|
for err in errors {
|
||||||
|
write!(out, "{}", err).expect("Error formatting errors");
|
||||||
|
}
|
||||||
|
panic!(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
//! Official spec testsuite.
|
||||||
|
|
||||||
|
extern crate wabt;
|
||||||
|
extern crate wasmi;
|
||||||
|
|
||||||
|
mod spec;
|
Loading…
Reference in New Issue