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"]
|
||||
path = tests/spec/testsuite
|
||||
path = wasmi/tests/spec/testsuite
|
||||
url = https://github.com/WebAssembly/testsuite.git
|
||||
|
|
38
Cargo.toml
38
Cargo.toml
|
@ -1,31 +1,9 @@
|
|||
[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/*" ]
|
||||
[workspace]
|
||||
members = [
|
||||
"wasmi",
|
||||
"derive",
|
||||
]
|
||||
|
||||
[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"]
|
||||
|
||||
[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"
|
||||
exclude = [
|
||||
"benches", # uses custom profile
|
||||
]
|
||||
|
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
|||
authors = ["Sergey Pepyakin <s.pepyakin@gmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
wasmi = { path = ".." }
|
||||
wasmi = { path = "../wasmi" }
|
||||
assert_matches = "1.2"
|
||||
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
|
||||
|
||||
[dependencies]
|
||||
wasmi = { path = ".." }
|
||||
wasmi = { path = "../wasmi" }
|
||||
wabt = "0.6.0"
|
||||
wasmparser = "0.14.1"
|
||||
tempdir = "0.3.6"
|
||||
|
|
3
test.sh
3
test.sh
|
@ -4,6 +4,9 @@ set -eux
|
|||
|
||||
cd $(dirname $0)
|
||||
|
||||
# Make sure that the testsuite submodule is checked out.
|
||||
git submodule update --init wasmi/tests/spec/testsuite
|
||||
|
||||
time cargo test
|
||||
|
||||
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 wasmi;
|
||||
extern crate wasmi_derive;
|
||||
|
||||
use std::env;
|
||||
use std::fmt;
|
||||
use std::fs::File;
|
||||
use wasmi::{
|
||||
Error as InterpreterError, Externals, FuncInstance, FuncRef, HostError, ImportsBuilder,
|
||||
ModuleImportResolver, ModuleInstance, ModuleRef, RuntimeArgs, RuntimeValue, Signature, Trap,
|
||||
ValueType,
|
||||
};
|
||||
use wasmi::{Error as InterpreterError, HostError, ImportsBuilder, ModuleInstance, ModuleRef};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
OutOfRange,
|
||||
AlreadyPlayed,
|
||||
AlreadyOccupied,
|
||||
Interpreter(InterpreterError),
|
||||
}
|
||||
|
@ -46,16 +51,6 @@ mod tictactoe {
|
|||
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)]
|
||||
pub struct Game {
|
||||
board: [Option<Player>; 9],
|
||||
|
@ -133,59 +128,40 @@ mod tictactoe {
|
|||
|
||||
struct Runtime<'a> {
|
||||
player: tictactoe::Player,
|
||||
made_turn: bool,
|
||||
game: &'a mut tictactoe::Game,
|
||||
}
|
||||
|
||||
const SET_FUNC_INDEX: usize = 0;
|
||||
const GET_FUNC_INDEX: usize = 1;
|
||||
#[wasmi_derive::derive_externals]
|
||||
impl<'a> Runtime<'a> {
|
||||
/// Puts a mark of the current player on the given cell.
|
||||
///
|
||||
/// Traps if the index is out of bounds of game field or if the player
|
||||
/// already made its turn.
|
||||
pub fn set(&mut self, idx: i32) -> Result<(), Error> {
|
||||
if self.made_turn {
|
||||
return Err(Error::AlreadyPlayed);
|
||||
}
|
||||
|
||||
impl<'a> Externals for Runtime<'a> {
|
||||
fn invoke_index(
|
||||
&mut self,
|
||||
index: usize,
|
||||
args: RuntimeArgs,
|
||||
) -> Result<Option<RuntimeValue>, Trap> {
|
||||
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"),
|
||||
}
|
||||
}
|
||||
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 {
|
||||
fn resolve_func(
|
||||
&self,
|
||||
field_name: &str,
|
||||
_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)
|
||||
Ok(match self.game.get(idx)? {
|
||||
None => 0,
|
||||
Some(Player::X) => 1,
|
||||
Some(Player::O) => 2,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,10 +174,14 @@ fn instantiate(path: &str) -> Result<ModuleRef, Error> {
|
|||
wasmi::Module::from_buffer(&wasm_buf)?
|
||||
};
|
||||
|
||||
let mut imports = ImportsBuilder::new();
|
||||
imports.push_resolver("env", &RuntimeModuleImportResolver);
|
||||
let instance = {
|
||||
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)
|
||||
}
|
||||
|
@ -222,6 +202,7 @@ fn play(
|
|||
let mut runtime = Runtime {
|
||||
player: turn_of,
|
||||
game: game,
|
||||
made_turn: false,
|
||||
};
|
||||
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.
|
||||
///
|
||||
/// You can use `wasmi-derive` or you can implement this trait manually.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
|
@ -396,6 +396,8 @@ mod types;
|
|||
mod validation;
|
||||
mod value;
|
||||
|
||||
pub mod derive_support;
|
||||
|
||||
#[cfg(test)]
|
||||
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