Compare commits

...

25 Commits

Author SHA1 Message Date
Sergey Pepyakin 0474402aca Explicitly use the core crate
This way it shouldn't matter if anybody e.g. imports other `Option` or redefines `Result`.
2019-01-26 12:57:50 +01:00
Sergey Pepyakin c9c83e44c7 Add tests 2019-01-25 17:27:47 +01:00
Sergey Pepyakin ed9488629d Return compile errors 2019-01-25 16:46:42 +01:00
Sergey Pepyakin 2b149f5bdf Fix Cargo.toml 2019-01-25 16:46:18 +01:00
Sergey Pepyakin 05027e617e Merge remote-tracking branch 'origin/master' into derive
# Conflicts:
#	Cargo.toml
#	wasmi/tests/spec/testsuite
2019-01-25 13:07:37 +01:00
Sergey Pepyakin 0f6b3e15f4 Clean 2019-01-25 13:06:04 +01:00
Sergey Pepyakin 27c5501ab0 Clean up 2019-01-25 13:02:11 +01:00
Sergey Pepyakin e9a414d504 Move examples and adapt. 2019-01-25 13:00:24 +01:00
Sergey Pepyakin 3bd8f8a250 Fix benches 2019-01-25 11:59:16 +01:00
Sergey Pepyakin 649818482d Fix fuzz 2019-01-25 11:56:47 +01:00
Sergey Pepyakin d49b9fe252 Test 2019-01-25 11:55:28 +01:00
Sergey Pepyakin 244648f40d fmt 2019-01-25 11:55:25 +01:00
Sergey Pepyakin 9410d17cb6 Rejig the repo structure 2019-01-25 11:52:12 +01:00
Sergey Pepyakin 1b8cf2705e Add docs. 2019-01-25 11:39:59 +01:00
Sergey Pepyakin cd9bcc073a Documentation. 2019-01-25 11:33:03 +01:00
Sergey Pepyakin da384ed27a Add derive feature 2019-01-25 11:32:54 +01:00
Sergey Pepyakin 0502619259 Cleanup 2019-01-25 11:03:47 +01:00
Sergey Pepyakin 0d5a87f64f Comments and renames 2019-01-25 11:02:03 +01:00
Sergey Pepyakin f578675ba6 Generate impl inside unused associated const 2019-01-25 10:52:05 +01:00
Sergey Pepyakin 6221f50545 Clean parser. 2019-01-25 10:51:42 +01:00
Sergey Pepyakin b83e6178b8 Start refactoring. 2019-01-25 10:45:39 +01:00
Sergey Pepyakin af19c66589 fmt 2019-01-25 10:43:03 +01:00
Sergey Pepyakin 4c51136e7c Renamings 2019-01-19 21:59:32 +01:00
Sergey Pepyakin 5f49943cfb Make it work. 2019-01-19 21:29:29 +01:00
Sergey Pepyakin 23b10d386c WIP 2019-01-19 01:31:47 +01:00
47 changed files with 1390 additions and 97 deletions

2
.gitmodules vendored
View File

@ -1,3 +1,3 @@
[submodule "tests/spec/testsuite"]
path = tests/spec/testsuite
path = wasmi/tests/spec/testsuite
url = https://github.com/WebAssembly/testsuite.git

View File

@ -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
]

View File

@ -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"

13
derive/Cargo.toml Normal file
View File

@ -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"

228
derive/src/codegen.rs Normal file
View File

@ -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 = &param.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 = &param.ident;
quote! {
let #ident = None;
}
})
.collect::<Vec<_>>();
let params_materialized_tys = func
.params
.iter()
.map(|param| {
let ident = &param.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);
}

37
derive/src/error.rs Normal file
View File

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

65
derive/src/lib.rs Normal file
View File

@ -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(),
}
}

97
derive/src/parser.rs Normal file
View File

@ -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(&param_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(),
})
}

View File

@ -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"

View File

@ -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 -

36
tests/derives.rs Normal file
View File

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

34
wasmi/Cargo.toml Normal file
View File

@ -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" }

View File

@ -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;
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"),
#[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);
}
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 {
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)?;
}

View File

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

View File

@ -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

View File

@ -396,6 +396,8 @@ mod types;
mod validation;
mod value;
pub mod derive_support;
#[cfg(test)]
mod tests;

152
wasmi/tests/derives.rs Normal file
View File

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

83
wasmi/tests/spec/mod.rs Normal file
View File

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

503
wasmi/tests/spec/run.rs Normal file
View File

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

6
wasmi/tests/spec_shim.rs Normal file
View File

@ -0,0 +1,6 @@
//! Official spec testsuite.
extern crate wabt;
extern crate wasmi;
mod spec;