Compare commits
30 Commits
Author | SHA1 | Date |
---|---|---|
Cadey Ratio | 0b6dd64219 | |
adam-rhebo | d2ea44e37c | |
Sergei Pepyakin | f19e1c27fc | |
Sergei Pepyakin | 59ab1c8d78 | |
Sergei Pepyakin | e6bdaf76f6 | |
Pierre Krieger | 390f4b2c4a | |
Sergei Pepyakin | 08c09adbf2 | |
Sergei Pepyakin | 990e6698cb | |
DemiMarie-parity | 7b1e5820c3 | |
thiolliere | 9d998c7289 | |
Sergei Pepyakin | b1ea069c4a | |
Sergei Pepyakin | b67af25899 | |
NikVolf | 57cc6c6a3d | |
Sergey Pepyakin | 1bf3cbe5d0 | |
Sergei Pepyakin | 1a6e5b30de | |
adam-rhebo | f29f301e6e | |
adam-rhebo | 7fe6ef4e35 | |
adam-rhebo | 8dac328ea7 | |
adam-rhebo | 284c907b29 | |
Björn Wagner | 2520bfc5a8 | |
Elichai Turkel | 5be300c99f | |
Sergei Pepyakin | 2960f1b4ec | |
Sergey Pepyakin | b73996a794 | |
Niklas Adolfsson | 25429407fe | |
Sergei Pepyakin | a3aad8a549 | |
Sergey Pepyakin | 0267b20e6e | |
Elichai Turkel | b90fcaf2dd | |
Elichai Turkel | 8403cc3411 | |
Sergei Pepyakin | 188ad62955 | |
Elichai Turkel | e88d5d32e5 |
|
@ -0,0 +1,2 @@
|
||||||
|
[target.armv7-unknown-linux-gnueabihf]
|
||||||
|
linker = "arm-linux-gnueabihf-gcc"
|
|
@ -3,3 +3,4 @@
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
spec/target
|
spec/target
|
||||||
|
.idea
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
[submodule "tests/spec/testsuite"]
|
[submodule "tests/spec/testsuite"]
|
||||||
path = wasmi/tests/spec/testsuite
|
path = tests/spec/testsuite
|
||||||
url = https://github.com/WebAssembly/testsuite.git
|
url = https://github.com/WebAssembly/testsuite.git
|
||||||
|
|
46
.travis.yml
46
.travis.yml
|
@ -1,28 +1,24 @@
|
||||||
dist: trusty
|
dist: xenial
|
||||||
sudo: required
|
|
||||||
language:
|
language:
|
||||||
- rust
|
- rust
|
||||||
- cpp
|
- cpp
|
||||||
rust:
|
|
||||||
- nightly
|
|
||||||
- stable
|
|
||||||
matrix:
|
matrix:
|
||||||
allow_failures:
|
fast_finish: true
|
||||||
|
include:
|
||||||
- rust: nightly
|
- rust: nightly
|
||||||
addons:
|
- rust: stable
|
||||||
apt:
|
- rust: stable
|
||||||
sources:
|
env: TARGET=armv7-unknown-linux-gnueabihf
|
||||||
- ubuntu-toolchain-r-test
|
|
||||||
packages:
|
|
||||||
- gcc-8
|
|
||||||
- g++-8
|
|
||||||
- cmake
|
|
||||||
env:
|
|
||||||
- CC=/usr/bin/gcc-8 CXX=/usr/bin/g++-8
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then rustup target add wasm32-unknown-unknown; fi
|
- if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then rustup target add wasm32-unknown-unknown; fi
|
||||||
|
- if [ -n "$TARGET" ]; then rustup target add "$TARGET" && sudo apt-get install --yes qemu-user-static; fi
|
||||||
|
- if [ "$TARGET" == "armv7-unknown-linux-gnueabihf" ]; then sudo apt-get install --yes crossbuild-essential-armhf && export QEMU_LD_PREFIX=/usr/arm-linux-gnueabihf; fi
|
||||||
- rustup component add rustfmt
|
- rustup component add rustfmt
|
||||||
|
- sudo apt-get install --yes cmake
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- cargo fmt --all -- --check
|
- cargo fmt --all -- --check
|
||||||
# Make sure nightly targets are not broken.
|
# Make sure nightly targets are not broken.
|
||||||
|
@ -30,8 +26,11 @@ script:
|
||||||
- if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo check --benches --manifest-path=benches/Cargo.toml; fi
|
- if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo check --benches --manifest-path=benches/Cargo.toml; fi
|
||||||
# Make sure `no_std` version checks.
|
# Make sure `no_std` version checks.
|
||||||
- if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo +nightly check --no-default-features --features core; fi
|
- if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo +nightly check --no-default-features --features core; fi
|
||||||
- ./test.sh
|
# Check that `vec_memory` feature works.
|
||||||
|
- cargo check --features vec_memory
|
||||||
|
- travis_wait 60 ./test.sh
|
||||||
- ./doc.sh
|
- ./doc.sh
|
||||||
|
|
||||||
after_success: |
|
after_success: |
|
||||||
# Build documentation and deploy it to github pages.
|
# Build documentation and deploy it to github pages.
|
||||||
[ $TRAVIS_BRANCH = master ] &&
|
[ $TRAVIS_BRANCH = master ] &&
|
||||||
|
@ -40,7 +39,18 @@ after_success: |
|
||||||
sudo pip install ghp-import &&
|
sudo pip install ghp-import &&
|
||||||
ghp-import -n target/doc &&
|
ghp-import -n target/doc &&
|
||||||
git push -fq https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages
|
git push -fq https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages
|
||||||
cache: cargo
|
|
||||||
|
cache:
|
||||||
|
# Don't use `cache: cargo` since it adds the `target` directory and that can be huge.
|
||||||
|
# Saving and loading this directory dwarfes actual compilation and test times. But what is more
|
||||||
|
# important, is that travis timeouts the build since the job doesn't produce any output for more
|
||||||
|
# than 10 minutes.
|
||||||
|
#
|
||||||
|
# So we just cache ~/.cargo directory
|
||||||
|
directories:
|
||||||
|
- /home/travis/.cargo
|
||||||
before_cache:
|
before_cache:
|
||||||
# Travis can't cache files that are not readable by "others"
|
# Travis can't cache files that are not readable by "others"
|
||||||
- chmod -R a+r $HOME/.cargo
|
- chmod -R a+r $HOME/.cargo
|
||||||
|
# According to the Travis CI docs for building Rust project this is done by,
|
||||||
|
- rm -rf /home/travis/.cargo/registry
|
||||||
|
|
57
Cargo.toml
57
Cargo.toml
|
@ -1,9 +1,52 @@
|
||||||
[workspace]
|
[package]
|
||||||
members = [
|
name = "wasmi"
|
||||||
"wasmi",
|
version = "0.5.1"
|
||||||
"derive",
|
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/*" ]
|
||||||
|
|
||||||
exclude = [
|
[dependencies]
|
||||||
"benches", # uses custom profile
|
wasmi-validation = { version = "0.2", path = "validation", default-features = false }
|
||||||
|
parity-wasm = { version = "0.40.1", default-features = false }
|
||||||
|
memory_units = "0.3.0"
|
||||||
|
libm = { version = "0.1.2", optional = true }
|
||||||
|
num-rational = { version = "0.2.2", default-features = false }
|
||||||
|
num-traits = { version = "0.2.8", default-features = false }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
assert_matches = "1.1"
|
||||||
|
rand = "0.4.2"
|
||||||
|
wabt = "0.9"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["std"]
|
||||||
|
# Disable for no_std support
|
||||||
|
std = [
|
||||||
|
"parity-wasm/std",
|
||||||
|
"wasmi-validation/std",
|
||||||
|
"num-rational/std",
|
||||||
|
"num-rational/bigint-std",
|
||||||
|
"num-traits/std"
|
||||||
]
|
]
|
||||||
|
# Enable for no_std support
|
||||||
|
core = [
|
||||||
|
# `core` doesn't support vec_memory
|
||||||
|
"vec_memory",
|
||||||
|
"wasmi-validation/core",
|
||||||
|
"libm"
|
||||||
|
]
|
||||||
|
# Enforce using the linear memory implementation based on `Vec` instead of
|
||||||
|
# mmap on unix systems.
|
||||||
|
#
|
||||||
|
# Useful for tests and if you need to minimize unsafe usage at the cost of performance on some
|
||||||
|
# workloads.
|
||||||
|
vec_memory = []
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = ["validation"]
|
||||||
|
exclude = ["benches"]
|
||||||
|
|
25
README.md
25
README.md
|
@ -3,18 +3,11 @@
|
||||||
|
|
||||||
# `wasmi`
|
# `wasmi`
|
||||||
|
|
||||||
WASM interpreter (previously lived in [parity-wasm](https://github.com/paritytech/parity-wasm))
|
`wasmi` - a Wasm interpreter.
|
||||||
|
|
||||||
Primary purpose of `wasmi` is to be used with [parity](https://github.com/paritytech/parity) (ethereum-like contracts in wasm) and with [Polkadot](https://github.com/paritytech/polkadot). However, `wasmi` is designed to be as flexible as possible and might be suited well for other purposes.
|
`wasmi` was conceived as a component of [parity-ethereum](https://github.com/paritytech/parity-ethereum) (ethereum-like contracts in wasm) and [substrate](https://github.com/paritytech/substrate). These projects are related to blockchain and require a high degree of correctness, even if that might be over conservative. This specifically means that we are not trying to be involved in any implementation of any of work-in-progress Wasm proposals. We are also trying to be as close as possible to the spec, which means we are trying to avoid features that is not directly supported by the spec. This means that it is flexible on the one hand and on the other hand there shouldn't be a problem migrating to another spec compliant execution engine.
|
||||||
|
|
||||||
At the moment, the API is rather low-level (especially, in the part related to host functions). But some high-level API is on the roadmap.
|
With all that said, `wasmi` should be a good option for initial prototyping.
|
||||||
|
|
||||||
# License
|
|
||||||
|
|
||||||
`wasmi` is primarily distributed under the terms of both the MIT
|
|
||||||
license and the Apache License (Version 2.0), at your choice.
|
|
||||||
|
|
||||||
See LICENSE-APACHE, and LICENSE-MIT for details.
|
|
||||||
|
|
||||||
# Build & Test
|
# Build & Test
|
||||||
|
|
||||||
|
@ -28,12 +21,13 @@ cargo test
|
||||||
```
|
```
|
||||||
|
|
||||||
# `no_std` support
|
# `no_std` support
|
||||||
|
|
||||||
This crate supports `no_std` environments.
|
This crate supports `no_std` environments.
|
||||||
Enable the `core` feature and disable default features:
|
Enable the `core` feature and disable default features:
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
parity-wasm = {
|
wasmi = {
|
||||||
version = "0.31",
|
version = "*",
|
||||||
default-features = false,
|
default-features = false,
|
||||||
features = "core"
|
features = "core"
|
||||||
}
|
}
|
||||||
|
@ -45,6 +39,13 @@ Also, code related to `std::error` is disabled.
|
||||||
Floating point operations in `no_std` use [`libm`](https://crates.io/crates/libm), which sometimes panics in debug mode (https://github.com/japaric/libm/issues/4).
|
Floating point operations in `no_std` use [`libm`](https://crates.io/crates/libm), which sometimes panics in debug mode (https://github.com/japaric/libm/issues/4).
|
||||||
So make sure to either use release builds or avoid WASM with floating point operations, for example by using [`deny_floating_point`](https://docs.rs/wasmi/0.4.0/wasmi/struct.Module.html#method.deny_floating_point).
|
So make sure to either use release builds or avoid WASM with floating point operations, for example by using [`deny_floating_point`](https://docs.rs/wasmi/0.4.0/wasmi/struct.Module.html#method.deny_floating_point).
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
`wasmi` is primarily distributed under the terms of both the MIT
|
||||||
|
license and the Apache License (Version 2.0), at your choice.
|
||||||
|
|
||||||
|
See LICENSE-APACHE, and LICENSE-MIT for details.
|
||||||
|
|
||||||
## Contribution
|
## Contribution
|
||||||
|
|
||||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||||
|
|
|
@ -4,9 +4,9 @@ version = "0.1.0"
|
||||||
authors = ["Sergey Pepyakin <s.pepyakin@gmail.com>"]
|
authors = ["Sergey Pepyakin <s.pepyakin@gmail.com>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasmi = { path = "../wasmi" }
|
wasmi = { path = ".." }
|
||||||
assert_matches = "1.2"
|
assert_matches = "1.2"
|
||||||
wabt = "0.6"
|
wabt = "0.9"
|
||||||
|
|
||||||
[profile.bench]
|
[profile.bench]
|
||||||
debug = true
|
debug = true
|
||||||
|
|
|
@ -13,7 +13,7 @@ use wasmi::{ImportsBuilder, Module, ModuleInstance, NopExternals, RuntimeValue};
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
|
||||||
// Load a module from a file.
|
// Load a module from a file.
|
||||||
fn load_from_file(filename: &str) -> Result<Module, Box<error::Error>> {
|
fn load_from_file(filename: &str) -> Result<Module, Box<dyn error::Error>> {
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
let mut file = File::open(filename)?;
|
let mut file = File::open(filename)?;
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
|
|
|
@ -33,7 +33,7 @@ pub extern "C" fn prepare_tiny_keccak() -> *const TinyKeccakTestData {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn bench_tiny_keccak(test_data: *const TinyKeccakTestData) {
|
pub extern "C" fn bench_tiny_keccak(test_data: *mut TinyKeccakTestData) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut keccak = Keccak::new_keccak256();
|
let mut keccak = Keccak::new_keccak256();
|
||||||
keccak.update((*test_data).data);
|
keccak.update((*test_data).data);
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
[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"
|
|
|
@ -1,228 +0,0 @@
|
||||||
//! 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);
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
//! 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(),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
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(),
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,23 +1,18 @@
|
||||||
//! 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::{Error as InterpreterError, HostError, ImportsBuilder, ModuleInstance, ModuleRef};
|
use wasmi::{
|
||||||
|
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),
|
||||||
}
|
}
|
||||||
|
@ -51,6 +46,16 @@ 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],
|
||||||
|
@ -128,40 +133,59 @@ 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,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasmi_derive::derive_externals]
|
const SET_FUNC_INDEX: usize = 0;
|
||||||
impl<'a> Runtime<'a> {
|
const GET_FUNC_INDEX: usize = 1;
|
||||||
/// 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)?;
|
self.game.set(idx, self.player)?;
|
||||||
Ok(())
|
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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the player index at the specified cell.
|
struct RuntimeModuleImportResolver;
|
||||||
///
|
|
||||||
/// 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;
|
|
||||||
|
|
||||||
Ok(match self.game.get(idx)? {
|
impl<'a> ModuleImportResolver for RuntimeModuleImportResolver {
|
||||||
None => 0,
|
fn resolve_func(
|
||||||
Some(Player::X) => 1,
|
&self,
|
||||||
Some(Player::O) => 2,
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,14 +198,10 @@ fn instantiate(path: &str) -> Result<ModuleRef, Error> {
|
||||||
wasmi::Module::from_buffer(&wasm_buf)?
|
wasmi::Module::from_buffer(&wasm_buf)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let instance = {
|
|
||||||
let resolver = Runtime::resolver();
|
|
||||||
|
|
||||||
let mut imports = ImportsBuilder::new();
|
let mut imports = ImportsBuilder::new();
|
||||||
imports.push_resolver("env", &resolver);
|
imports.push_resolver("env", &RuntimeModuleImportResolver);
|
||||||
|
|
||||||
ModuleInstance::new(&module, &imports)?.assert_no_start()
|
let instance = ModuleInstance::new(&module, &imports)?.assert_no_start();
|
||||||
};
|
|
||||||
|
|
||||||
Ok(instance)
|
Ok(instance)
|
||||||
}
|
}
|
||||||
|
@ -202,7 +222,6 @@ 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)?;
|
||||||
}
|
}
|
|
@ -9,8 +9,8 @@ publish = false
|
||||||
cargo-fuzz = true
|
cargo-fuzz = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasmi = { path = "../wasmi" }
|
wasmi = { path = ".." }
|
||||||
wabt = "0.6.0"
|
wabt = "0.9"
|
||||||
wasmparser = "0.14.1"
|
wasmparser = "0.14.1"
|
||||||
tempdir = "0.3.6"
|
tempdir = "0.3.6"
|
||||||
|
|
||||||
|
|
|
@ -7,4 +7,4 @@ authors = ["Sergey Pepyakin <s.pepyakin@gmail.com>"]
|
||||||
honggfuzz = "=0.5.9" # Strict equal since hfuzz requires dep and cmd versions to match.
|
honggfuzz = "=0.5.9" # Strict equal since hfuzz requires dep and cmd versions to match.
|
||||||
wasmi = { path = ".." }
|
wasmi = { path = ".." }
|
||||||
tempdir = "0.3.6"
|
tempdir = "0.3.6"
|
||||||
wabt = "0.6.0"
|
wabt = "0.9"
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
#[allow(unused_imports)]
|
use alloc::{
|
||||||
use alloc::prelude::*;
|
borrow::Cow,
|
||||||
use alloc::rc::{Rc, Weak};
|
rc::{Rc, Weak},
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use host::Externals;
|
use host::Externals;
|
||||||
use isa;
|
use isa;
|
||||||
use module::ModuleInstance;
|
use module::ModuleInstance;
|
||||||
use parity_wasm::elements::Local;
|
use parity_wasm::elements::Local;
|
||||||
use runner::{check_function_args, Interpreter, InterpreterState};
|
use runner::{check_function_args, Interpreter, InterpreterState, StackRecycler};
|
||||||
use types::ValueType;
|
use types::ValueType;
|
||||||
use value::RuntimeValue;
|
use value::RuntimeValue;
|
||||||
use {Signature, Trap};
|
use {Signature, Trap};
|
||||||
|
@ -140,7 +142,7 @@ impl FuncInstance {
|
||||||
check_function_args(func.signature(), &args)?;
|
check_function_args(func.signature(), &args)?;
|
||||||
match *func.as_internal() {
|
match *func.as_internal() {
|
||||||
FuncInstanceInternal::Internal { .. } => {
|
FuncInstanceInternal::Internal { .. } => {
|
||||||
let mut interpreter = Interpreter::new(func, args)?;
|
let mut interpreter = Interpreter::new(func, args, None)?;
|
||||||
interpreter.start_execution(externals)
|
interpreter.start_execution(externals)
|
||||||
}
|
}
|
||||||
FuncInstanceInternal::Host {
|
FuncInstanceInternal::Host {
|
||||||
|
@ -150,6 +152,34 @@ impl FuncInstance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Invoke this function using recycled stacks.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Same as [`invoke`].
|
||||||
|
///
|
||||||
|
/// [`invoke`]: #method.invoke
|
||||||
|
pub fn invoke_with_stack<E: Externals>(
|
||||||
|
func: &FuncRef,
|
||||||
|
args: &[RuntimeValue],
|
||||||
|
externals: &mut E,
|
||||||
|
stack_recycler: &mut StackRecycler,
|
||||||
|
) -> Result<Option<RuntimeValue>, Trap> {
|
||||||
|
check_function_args(func.signature(), &args)?;
|
||||||
|
match *func.as_internal() {
|
||||||
|
FuncInstanceInternal::Internal { .. } => {
|
||||||
|
let mut interpreter = Interpreter::new(func, args, Some(stack_recycler))?;
|
||||||
|
let return_value = interpreter.start_execution(externals);
|
||||||
|
stack_recycler.recycle(interpreter);
|
||||||
|
return_value
|
||||||
|
}
|
||||||
|
FuncInstanceInternal::Host {
|
||||||
|
ref host_func_index,
|
||||||
|
..
|
||||||
|
} => externals.invoke_index(*host_func_index, args.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Invoke the function, get a resumable handle. This handle can then be used to [`start_execution`]. If a
|
/// Invoke the function, get a resumable handle. This handle can then be used to [`start_execution`]. If a
|
||||||
/// Host trap happens, caller can use [`resume_execution`] to feed the expected return value back in, and then
|
/// Host trap happens, caller can use [`resume_execution`] to feed the expected return value back in, and then
|
||||||
/// continue the execution.
|
/// continue the execution.
|
||||||
|
@ -166,12 +196,13 @@ impl FuncInstance {
|
||||||
/// [`resume_execution`]: struct.FuncInvocation.html#method.resume_execution
|
/// [`resume_execution`]: struct.FuncInvocation.html#method.resume_execution
|
||||||
pub fn invoke_resumable<'args>(
|
pub fn invoke_resumable<'args>(
|
||||||
func: &FuncRef,
|
func: &FuncRef,
|
||||||
args: &'args [RuntimeValue],
|
args: impl Into<Cow<'args, [RuntimeValue]>>,
|
||||||
) -> Result<FuncInvocation<'args>, Trap> {
|
) -> Result<FuncInvocation<'args>, Trap> {
|
||||||
|
let args = args.into();
|
||||||
check_function_args(func.signature(), &args)?;
|
check_function_args(func.signature(), &args)?;
|
||||||
match *func.as_internal() {
|
match *func.as_internal() {
|
||||||
FuncInstanceInternal::Internal { .. } => {
|
FuncInstanceInternal::Internal { .. } => {
|
||||||
let interpreter = Interpreter::new(func, args)?;
|
let interpreter = Interpreter::new(func, &*args, None)?;
|
||||||
Ok(FuncInvocation {
|
Ok(FuncInvocation {
|
||||||
kind: FuncInvocationKind::Internal(interpreter),
|
kind: FuncInvocationKind::Internal(interpreter),
|
||||||
})
|
})
|
||||||
|
@ -228,7 +259,7 @@ pub struct FuncInvocation<'args> {
|
||||||
enum FuncInvocationKind<'args> {
|
enum FuncInvocationKind<'args> {
|
||||||
Internal(Interpreter),
|
Internal(Interpreter),
|
||||||
Host {
|
Host {
|
||||||
args: &'args [RuntimeValue],
|
args: Cow<'args, [RuntimeValue]>,
|
||||||
host_func_index: usize,
|
host_func_index: usize,
|
||||||
finished: bool,
|
finished: bool,
|
||||||
},
|
},
|
||||||
|
@ -275,7 +306,7 @@ impl<'args> FuncInvocation<'args> {
|
||||||
return Err(ResumableError::AlreadyStarted);
|
return Err(ResumableError::AlreadyStarted);
|
||||||
}
|
}
|
||||||
*finished = true;
|
*finished = true;
|
||||||
Ok(externals.invoke_index(*host_func_index, args.clone().into())?)
|
Ok(externals.invoke_index(*host_func_index, args.as_ref().into())?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -114,11 +114,11 @@ pub trait HostError: 'static + ::core::fmt::Display + ::core::fmt::Debug + Send
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HostError {
|
impl dyn HostError {
|
||||||
/// Attempt to downcast this `HostError` to a concrete type by reference.
|
/// Attempt to downcast this `HostError` to a concrete type by reference.
|
||||||
pub fn downcast_ref<T: HostError>(&self) -> Option<&T> {
|
pub fn downcast_ref<T: HostError>(&self) -> Option<&T> {
|
||||||
if self.__private_get_type_id__() == TypeId::of::<T>() {
|
if self.__private_get_type_id__() == TypeId::of::<T>() {
|
||||||
unsafe { Some(&*(self as *const HostError as *const T)) }
|
unsafe { Some(&*(self as *const dyn HostError as *const T)) }
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -128,7 +128,7 @@ impl HostError {
|
||||||
/// reference.
|
/// reference.
|
||||||
pub fn downcast_mut<T: HostError>(&mut self) -> Option<&mut T> {
|
pub fn downcast_mut<T: HostError>(&mut self) -> Option<&mut T> {
|
||||||
if self.__private_get_type_id__() == TypeId::of::<T>() {
|
if self.__private_get_type_id__() == TypeId::of::<T>() {
|
||||||
unsafe { Some(&mut *(self as *mut HostError as *mut T)) }
|
unsafe { Some(&mut *(self as *mut dyn HostError as *mut T)) }
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -137,8 +137,6 @@ 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
|
||||||
|
@ -259,5 +257,5 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that `HostError` trait is object safe.
|
// Tests that `HostError` trait is object safe.
|
||||||
fn _host_error_is_object_safe(_: &HostError) {}
|
fn _host_error_is_object_safe(_: &dyn HostError) {}
|
||||||
}
|
}
|
|
@ -1,10 +1,4 @@
|
||||||
#[allow(unused_imports)]
|
use alloc::{collections::BTreeMap, string::String};
|
||||||
use alloc::prelude::*;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
|
||||||
use hashbrown::HashMap;
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use func::FuncRef;
|
use func::FuncRef;
|
||||||
use global::GlobalRef;
|
use global::GlobalRef;
|
||||||
|
@ -106,7 +100,7 @@ pub trait ImportResolver {
|
||||||
/// [`ImportResolver`]: trait.ImportResolver.html
|
/// [`ImportResolver`]: trait.ImportResolver.html
|
||||||
/// [`ModuleImportResolver`]: trait.ModuleImportResolver.html
|
/// [`ModuleImportResolver`]: trait.ModuleImportResolver.html
|
||||||
pub struct ImportsBuilder<'a> {
|
pub struct ImportsBuilder<'a> {
|
||||||
modules: HashMap<String, &'a ModuleImportResolver>,
|
modules: BTreeMap<String, &'a dyn ModuleImportResolver>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Default for ImportsBuilder<'a> {
|
impl<'a> Default for ImportsBuilder<'a> {
|
||||||
|
@ -119,7 +113,7 @@ impl<'a> ImportsBuilder<'a> {
|
||||||
/// Create an empty `ImportsBuilder`.
|
/// Create an empty `ImportsBuilder`.
|
||||||
pub fn new() -> ImportsBuilder<'a> {
|
pub fn new() -> ImportsBuilder<'a> {
|
||||||
ImportsBuilder {
|
ImportsBuilder {
|
||||||
modules: HashMap::new(),
|
modules: BTreeMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +121,7 @@ impl<'a> ImportsBuilder<'a> {
|
||||||
pub fn with_resolver<N: Into<String>>(
|
pub fn with_resolver<N: Into<String>>(
|
||||||
mut self,
|
mut self,
|
||||||
name: N,
|
name: N,
|
||||||
resolver: &'a ModuleImportResolver,
|
resolver: &'a dyn ModuleImportResolver,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.modules.insert(name.into(), resolver);
|
self.modules.insert(name.into(), resolver);
|
||||||
self
|
self
|
||||||
|
@ -136,11 +130,15 @@ impl<'a> ImportsBuilder<'a> {
|
||||||
/// Register an resolver by a name.
|
/// Register an resolver by a name.
|
||||||
///
|
///
|
||||||
/// Mutable borrowed version.
|
/// Mutable borrowed version.
|
||||||
pub fn push_resolver<N: Into<String>>(&mut self, name: N, resolver: &'a ModuleImportResolver) {
|
pub fn push_resolver<N: Into<String>>(
|
||||||
|
&mut self,
|
||||||
|
name: N,
|
||||||
|
resolver: &'a dyn ModuleImportResolver,
|
||||||
|
) {
|
||||||
self.modules.insert(name.into(), resolver);
|
self.modules.insert(name.into(), resolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolver(&self, name: &str) -> Option<&ModuleImportResolver> {
|
fn resolver(&self, name: &str) -> Option<&dyn ModuleImportResolver> {
|
||||||
self.modules.get(name).cloned()
|
self.modules.get(name).cloned()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -67,8 +67,7 @@
|
||||||
//! - Reserved immediates are ignored for `call_indirect`, `current_memory`, `grow_memory`.
|
//! - Reserved immediates are ignored for `call_indirect`, `current_memory`, `grow_memory`.
|
||||||
//!
|
//!
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
use alloc::vec::Vec;
|
||||||
use alloc::prelude::*;
|
|
||||||
|
|
||||||
/// Should we keep a value before "discarding" a stack frame?
|
/// Should we keep a value before "discarding" a stack frame?
|
||||||
///
|
///
|
||||||
|
@ -82,6 +81,16 @@ pub enum Keep {
|
||||||
Single,
|
Single,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Keep {
|
||||||
|
/// Reutrns a number of items that should be kept on the stack.
|
||||||
|
pub fn count(&self) -> u32 {
|
||||||
|
match *self {
|
||||||
|
Keep::None => 0,
|
||||||
|
Keep::Single => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Specifies how many values we should keep and how many we should drop.
|
/// Specifies how many values we should keep and how many we should drop.
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct DropKeep {
|
pub struct DropKeep {
|
|
@ -96,8 +96,6 @@
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
//// alloc is required in no_std
|
|
||||||
#![cfg_attr(not(feature = "std"), feature(alloc))]
|
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -110,19 +108,20 @@ extern crate std as alloc;
|
||||||
extern crate core;
|
extern crate core;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
extern crate wabt;
|
|
||||||
#[cfg(test)]
|
|
||||||
#[macro_use]
|
|
||||||
extern crate assert_matches;
|
extern crate assert_matches;
|
||||||
|
#[cfg(test)]
|
||||||
|
extern crate wabt;
|
||||||
|
|
||||||
extern crate byteorder;
|
|
||||||
#[cfg(not(feature = "std"))]
|
|
||||||
extern crate hashbrown;
|
|
||||||
extern crate memory_units as memory_units_crate;
|
extern crate memory_units as memory_units_crate;
|
||||||
extern crate parity_wasm;
|
extern crate parity_wasm;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
extern crate wasmi_validation as validation;
|
||||||
use alloc::prelude::*;
|
|
||||||
|
use alloc::{
|
||||||
|
boxed::Box,
|
||||||
|
string::{String, ToString},
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use std::error;
|
use std::error;
|
||||||
|
@ -130,6 +129,9 @@ use std::error;
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
extern crate libm;
|
extern crate libm;
|
||||||
|
|
||||||
|
extern crate num_rational;
|
||||||
|
extern crate num_traits;
|
||||||
|
|
||||||
/// Error type which can be thrown by wasm code or by host environment.
|
/// Error type which can be thrown by wasm code or by host environment.
|
||||||
///
|
///
|
||||||
/// Under some conditions, wasm execution may produce a `Trap`, which immediately aborts execution.
|
/// Under some conditions, wasm execution may produce a `Trap`, which immediately aborts execution.
|
||||||
|
@ -238,7 +240,7 @@ pub enum TrapKind {
|
||||||
/// Typically returned from an implementation of [`Externals`].
|
/// Typically returned from an implementation of [`Externals`].
|
||||||
///
|
///
|
||||||
/// [`Externals`]: trait.Externals.html
|
/// [`Externals`]: trait.Externals.html
|
||||||
Host(Box<host::HostError>),
|
Host(Box<dyn host::HostError>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TrapKind {
|
impl TrapKind {
|
||||||
|
@ -272,7 +274,7 @@ pub enum Error {
|
||||||
/// Trap.
|
/// Trap.
|
||||||
Trap(Trap),
|
Trap(Trap),
|
||||||
/// Custom embedder error.
|
/// Custom embedder error.
|
||||||
Host(Box<host::HostError>),
|
Host(Box<dyn host::HostError>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
|
@ -284,7 +286,7 @@ impl Error {
|
||||||
/// [`Host`]: enum.Error.html#variant.Host
|
/// [`Host`]: enum.Error.html#variant.Host
|
||||||
/// [`Trap`]: enum.Error.html#variant.Trap
|
/// [`Trap`]: enum.Error.html#variant.Trap
|
||||||
/// [`TrapKind::Host`]: enum.TrapKind.html#variant.Host
|
/// [`TrapKind::Host`]: enum.TrapKind.html#variant.Host
|
||||||
pub fn as_host_error(&self) -> Option<&host::HostError> {
|
pub fn as_host_error(&self) -> Option<&dyn host::HostError> {
|
||||||
match *self {
|
match *self {
|
||||||
Error::Host(ref host_err) => Some(&**host_err),
|
Error::Host(ref host_err) => Some(&**host_err),
|
||||||
Error::Trap(ref trap) => match *trap.kind() {
|
Error::Trap(ref trap) => match *trap.kind() {
|
||||||
|
@ -381,7 +383,6 @@ impl From<validation::Error> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod common;
|
|
||||||
mod func;
|
mod func;
|
||||||
mod global;
|
mod global;
|
||||||
mod host;
|
mod host;
|
||||||
|
@ -390,14 +391,12 @@ mod isa;
|
||||||
mod memory;
|
mod memory;
|
||||||
mod module;
|
mod module;
|
||||||
pub mod nan_preserving_float;
|
pub mod nan_preserving_float;
|
||||||
|
mod prepare;
|
||||||
mod runner;
|
mod runner;
|
||||||
mod table;
|
mod table;
|
||||||
mod types;
|
mod types;
|
||||||
mod validation;
|
|
||||||
mod value;
|
mod value;
|
||||||
|
|
||||||
pub mod derive_support;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
@ -407,6 +406,7 @@ pub use self::host::{Externals, HostError, NopExternals, RuntimeArgs};
|
||||||
pub use self::imports::{ImportResolver, ImportsBuilder, ModuleImportResolver};
|
pub use self::imports::{ImportResolver, ImportsBuilder, ModuleImportResolver};
|
||||||
pub use self::memory::{MemoryInstance, MemoryRef, LINEAR_MEMORY_PAGE_SIZE};
|
pub use self::memory::{MemoryInstance, MemoryRef, LINEAR_MEMORY_PAGE_SIZE};
|
||||||
pub use self::module::{ExternVal, ModuleInstance, ModuleRef, NotStartedModuleRef};
|
pub use self::module::{ExternVal, ModuleInstance, ModuleRef, NotStartedModuleRef};
|
||||||
|
pub use self::runner::{StackRecycler, DEFAULT_CALL_STACK_LIMIT, DEFAULT_VALUE_STACK_LIMIT};
|
||||||
pub use self::table::{TableInstance, TableRef};
|
pub use self::table::{TableInstance, TableRef};
|
||||||
pub use self::types::{GlobalDescriptor, MemoryDescriptor, Signature, TableDescriptor, ValueType};
|
pub use self::types::{GlobalDescriptor, MemoryDescriptor, Signature, TableDescriptor, ValueType};
|
||||||
pub use self::value::{Error as ValueError, FromRuntimeValue, LittleEndianConvert, RuntimeValue};
|
pub use self::value::{Error as ValueError, FromRuntimeValue, LittleEndianConvert, RuntimeValue};
|
||||||
|
@ -457,8 +457,7 @@ impl Module {
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn from_parity_wasm_module(module: parity_wasm::elements::Module) -> Result<Module, Error> {
|
pub fn from_parity_wasm_module(module: parity_wasm::elements::Module) -> Result<Module, Error> {
|
||||||
use validation::{validate_module, ValidatedModule};
|
let prepare::CompiledModule { code_map, module } = prepare::compile_module(module)?;
|
||||||
let ValidatedModule { code_map, module } = validate_module(module)?;
|
|
||||||
|
|
||||||
Ok(Module { code_map, module })
|
Ok(Module { code_map, module })
|
||||||
}
|
}
|
||||||
|
@ -520,7 +519,7 @@ impl Module {
|
||||||
/// assert!(module.deny_floating_point().is_err());
|
/// assert!(module.deny_floating_point().is_err());
|
||||||
/// ```
|
/// ```
|
||||||
pub fn deny_floating_point(&self) -> Result<(), Error> {
|
pub fn deny_floating_point(&self) -> Result<(), Error> {
|
||||||
validation::deny_floating_point(&self.module).map_err(Into::into)
|
prepare::deny_floating_point(&self.module).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create `Module` from a given buffer.
|
/// Create `Module` from a given buffer.
|
|
@ -0,0 +1,189 @@
|
||||||
|
//! An implementation of a `ByteBuf` based on virtual memory.
|
||||||
|
//!
|
||||||
|
//! This implementation uses `mmap` on POSIX systems (and should use `VirtualAlloc` on windows).
|
||||||
|
//! There are possibilities to improve the performance for the reallocating case by reserving
|
||||||
|
//! memory up to maximum. This might be a problem for systems that don't have a lot of virtual
|
||||||
|
//! memory (i.e. 32-bit platforms).
|
||||||
|
|
||||||
|
use std::ptr::{self, NonNull};
|
||||||
|
use std::slice;
|
||||||
|
|
||||||
|
struct Mmap {
|
||||||
|
/// The pointer that points to the start of the mapping.
|
||||||
|
///
|
||||||
|
/// This value doesn't change after creation.
|
||||||
|
ptr: NonNull<u8>,
|
||||||
|
/// The length of this mapping.
|
||||||
|
///
|
||||||
|
/// Cannot be more than `isize::max_value()`. This value doesn't change after creation.
|
||||||
|
len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mmap {
|
||||||
|
/// Create a new mmap mapping
|
||||||
|
///
|
||||||
|
/// Returns `Err` if:
|
||||||
|
/// - `len` should not exceed `isize::max_value()`
|
||||||
|
/// - `len` should be greater than 0.
|
||||||
|
/// - `mmap` returns an error (almost certainly means out of memory).
|
||||||
|
fn new(len: usize) -> Result<Self, &'static str> {
|
||||||
|
if len > isize::max_value() as usize {
|
||||||
|
return Err("`len` should not exceed `isize::max_value()`");
|
||||||
|
}
|
||||||
|
if len == 0 {
|
||||||
|
return Err("`len` should be greater than 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
let ptr_or_err = unsafe {
|
||||||
|
// Safety Proof:
|
||||||
|
// There are not specific safety proofs are required for this call, since the call
|
||||||
|
// by itself can't invoke any safety problems (however, misusing its result can).
|
||||||
|
libc::mmap(
|
||||||
|
// `addr` - let the system to choose the address at which to create the mapping.
|
||||||
|
ptr::null_mut(),
|
||||||
|
// the length of the mapping in bytes.
|
||||||
|
len,
|
||||||
|
// `prot` - protection flags: READ WRITE !EXECUTE
|
||||||
|
libc::PROT_READ | libc::PROT_WRITE,
|
||||||
|
// `flags`
|
||||||
|
// `MAP_ANON` - mapping is not backed by any file and initial contents are
|
||||||
|
// initialized to zero.
|
||||||
|
// `MAP_PRIVATE` - the mapping is private to this process.
|
||||||
|
libc::MAP_ANON | libc::MAP_PRIVATE,
|
||||||
|
// `fildes` - a file descriptor. Pass -1 as this is required for some platforms
|
||||||
|
// when the `MAP_ANON` is passed.
|
||||||
|
-1,
|
||||||
|
// `offset` - offset from the file.
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
match ptr_or_err {
|
||||||
|
// With the current parameters, the error can only be returned in case of insufficient
|
||||||
|
// memory.
|
||||||
|
libc::MAP_FAILED => Err("mmap returned an error"),
|
||||||
|
_ => {
|
||||||
|
let ptr = NonNull::new(ptr_or_err as *mut u8).ok_or("mmap returned 0")?;
|
||||||
|
Ok(Self { ptr, len })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_slice(&self) -> &[u8] {
|
||||||
|
unsafe {
|
||||||
|
// Safety Proof:
|
||||||
|
// - Aliasing guarantees of `self.ptr` are not violated since `self` is the only owner.
|
||||||
|
// - This pointer was allocated for `self.len` bytes and thus is a valid slice.
|
||||||
|
// - `self.len` doesn't change throughout the lifetime of `self`.
|
||||||
|
// - The value is returned valid for the duration of lifetime of `self`.
|
||||||
|
// `self` cannot be destroyed while the returned slice is alive.
|
||||||
|
// - `self.ptr` is of `NonNull` type and thus `.as_ptr()` can never return NULL.
|
||||||
|
// - `self.len` cannot be larger than `isize::max_value()`.
|
||||||
|
slice::from_raw_parts(self.ptr.as_ptr(), self.len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_slice_mut(&mut self) -> &mut [u8] {
|
||||||
|
unsafe {
|
||||||
|
// Safety Proof:
|
||||||
|
// - See the proof for `Self::as_slice`
|
||||||
|
// - Additionally, it is not possible to obtain two mutable references for `self.ptr`
|
||||||
|
slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Mmap {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let ret_val = unsafe {
|
||||||
|
// Safety proof:
|
||||||
|
// - `self.ptr` was allocated by a call to `mmap`.
|
||||||
|
// - `self.len` was saved at the same time and it doesn't change throughout the lifetime
|
||||||
|
// of `self`.
|
||||||
|
libc::munmap(self.ptr.as_ptr() as *mut libc::c_void, self.len)
|
||||||
|
};
|
||||||
|
|
||||||
|
// There is no reason for `munmap` to fail to deallocate a private annonymous mapping
|
||||||
|
// allocated by `mmap`.
|
||||||
|
// However, for the cases when it actually fails prefer to fail, in order to not leak
|
||||||
|
// and exhaust the virtual memory.
|
||||||
|
assert_eq!(ret_val, 0, "munmap failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ByteBuf {
|
||||||
|
mmap: Option<Mmap>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ByteBuf {
|
||||||
|
pub fn new(len: usize) -> Result<Self, &'static str> {
|
||||||
|
let mmap = if len == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Mmap::new(len)?)
|
||||||
|
};
|
||||||
|
Ok(Self { mmap })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn realloc(&mut self, new_len: usize) -> Result<(), &'static str> {
|
||||||
|
let new_mmap = if new_len == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let mut new_mmap = Mmap::new(new_len)?;
|
||||||
|
if let Some(cur_mmap) = self.mmap.take() {
|
||||||
|
let src = cur_mmap.as_slice();
|
||||||
|
let dst = new_mmap.as_slice_mut();
|
||||||
|
let amount = src.len().min(dst.len());
|
||||||
|
dst[..amount].copy_from_slice(&src[..amount]);
|
||||||
|
}
|
||||||
|
Some(new_mmap)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.mmap = new_mmap;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.mmap.as_ref().map(|m| m.len).unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_slice(&self) -> &[u8] {
|
||||||
|
self.mmap.as_ref().map(|m| m.as_slice()).unwrap_or(&[])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_slice_mut(&mut self) -> &mut [u8] {
|
||||||
|
self.mmap
|
||||||
|
.as_mut()
|
||||||
|
.map(|m| m.as_slice_mut())
|
||||||
|
.unwrap_or(&mut [])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn erase(&mut self) -> Result<(), &'static str> {
|
||||||
|
let len = self.len();
|
||||||
|
if len > 0 {
|
||||||
|
// The order is important.
|
||||||
|
//
|
||||||
|
// 1. First we clear, and thus drop, the current mmap if any.
|
||||||
|
// 2. And then we create a new one.
|
||||||
|
//
|
||||||
|
// Otherwise we double the peak memory consumption.
|
||||||
|
self.mmap = None;
|
||||||
|
self.mmap = Some(Mmap::new(len)?);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::ByteBuf;
|
||||||
|
|
||||||
|
const PAGE_SIZE: usize = 4096;
|
||||||
|
|
||||||
|
// This is not required since wasm memories can only grow but nice to have.
|
||||||
|
#[test]
|
||||||
|
fn byte_buf_shrink() {
|
||||||
|
let mut byte_buf = ByteBuf::new(PAGE_SIZE * 3).unwrap();
|
||||||
|
byte_buf.realloc(PAGE_SIZE * 2).unwrap();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,25 @@
|
||||||
#[allow(unused_imports)]
|
use alloc::{rc::Rc, string::ToString, vec::Vec};
|
||||||
use alloc::prelude::*;
|
use core::{
|
||||||
use alloc::rc::Rc;
|
cell::{Cell, RefCell},
|
||||||
use core::cell::{Cell, RefCell};
|
cmp, fmt,
|
||||||
use core::cmp;
|
ops::Range,
|
||||||
use core::fmt;
|
u32,
|
||||||
use core::ops::Range;
|
};
|
||||||
use core::u32;
|
|
||||||
use memory_units::{Bytes, Pages, RoundUpTo};
|
use memory_units::{Bytes, Pages, RoundUpTo};
|
||||||
use parity_wasm::elements::ResizableLimits;
|
use parity_wasm::elements::ResizableLimits;
|
||||||
use value::LittleEndianConvert;
|
use value::LittleEndianConvert;
|
||||||
use Error;
|
use Error;
|
||||||
|
|
||||||
|
#[cfg(all(unix, not(feature = "vec_memory")))]
|
||||||
|
#[path = "mmap_bytebuf.rs"]
|
||||||
|
mod bytebuf;
|
||||||
|
|
||||||
|
#[cfg(any(not(unix), feature = "vec_memory"))]
|
||||||
|
#[path = "vec_bytebuf.rs"]
|
||||||
|
mod bytebuf;
|
||||||
|
|
||||||
|
use self::bytebuf::ByteBuf;
|
||||||
|
|
||||||
/// Size of a page of [linear memory][`MemoryInstance`] - 64KiB.
|
/// Size of a page of [linear memory][`MemoryInstance`] - 64KiB.
|
||||||
///
|
///
|
||||||
/// The size of a memory is always a integer multiple of a page size.
|
/// The size of a memory is always a integer multiple of a page size.
|
||||||
|
@ -18,9 +27,6 @@ use Error;
|
||||||
/// [`MemoryInstance`]: struct.MemoryInstance.html
|
/// [`MemoryInstance`]: struct.MemoryInstance.html
|
||||||
pub const LINEAR_MEMORY_PAGE_SIZE: Bytes = Bytes(65536);
|
pub const LINEAR_MEMORY_PAGE_SIZE: Bytes = Bytes(65536);
|
||||||
|
|
||||||
/// Maximal number of pages.
|
|
||||||
const LINEAR_MEMORY_MAX_PAGES: Pages = Pages(65536);
|
|
||||||
|
|
||||||
/// Reference to a memory (See [`MemoryInstance`] for details).
|
/// Reference to a memory (See [`MemoryInstance`] for details).
|
||||||
///
|
///
|
||||||
/// This reference has a reference-counting semantics.
|
/// This reference has a reference-counting semantics.
|
||||||
|
@ -54,11 +60,10 @@ pub struct MemoryInstance {
|
||||||
/// Memory limits.
|
/// Memory limits.
|
||||||
limits: ResizableLimits,
|
limits: ResizableLimits,
|
||||||
/// Linear memory buffer with lazy allocation.
|
/// Linear memory buffer with lazy allocation.
|
||||||
buffer: RefCell<Vec<u8>>,
|
buffer: RefCell<ByteBuf>,
|
||||||
initial: Pages,
|
initial: Pages,
|
||||||
current_size: Cell<usize>,
|
current_size: Cell<usize>,
|
||||||
maximum: Option<Pages>,
|
maximum: Option<Pages>,
|
||||||
lowest_used: Cell<u32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for MemoryInstance {
|
impl fmt::Debug for MemoryInstance {
|
||||||
|
@ -111,25 +116,41 @@ impl MemoryInstance {
|
||||||
///
|
///
|
||||||
/// [`LINEAR_MEMORY_PAGE_SIZE`]: constant.LINEAR_MEMORY_PAGE_SIZE.html
|
/// [`LINEAR_MEMORY_PAGE_SIZE`]: constant.LINEAR_MEMORY_PAGE_SIZE.html
|
||||||
pub fn alloc(initial: Pages, maximum: Option<Pages>) -> Result<MemoryRef, Error> {
|
pub fn alloc(initial: Pages, maximum: Option<Pages>) -> Result<MemoryRef, Error> {
|
||||||
validate_memory(initial, maximum).map_err(Error::Memory)?;
|
{
|
||||||
|
use core::convert::TryInto;
|
||||||
|
let initial_u32: u32 = initial.0.try_into().map_err(|_| {
|
||||||
|
Error::Memory(format!("initial ({}) can't be coerced to u32", initial.0))
|
||||||
|
})?;
|
||||||
|
let maximum_u32: Option<u32> = match maximum {
|
||||||
|
Some(maximum_pages) => Some(maximum_pages.0.try_into().map_err(|_| {
|
||||||
|
Error::Memory(format!(
|
||||||
|
"maximum ({}) can't be coerced to u32",
|
||||||
|
maximum_pages.0
|
||||||
|
))
|
||||||
|
})?),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
validation::validate_memory(initial_u32, maximum_u32).map_err(Error::Memory)?;
|
||||||
|
}
|
||||||
|
|
||||||
let memory = MemoryInstance::new(initial, maximum);
|
let memory = MemoryInstance::new(initial, maximum)?;
|
||||||
Ok(MemoryRef(Rc::new(memory)))
|
Ok(MemoryRef(Rc::new(memory)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create new linear memory instance.
|
/// Create new linear memory instance.
|
||||||
fn new(initial: Pages, maximum: Option<Pages>) -> Self {
|
fn new(initial: Pages, maximum: Option<Pages>) -> Result<Self, Error> {
|
||||||
let limits = ResizableLimits::new(initial.0 as u32, maximum.map(|p| p.0 as u32));
|
let limits = ResizableLimits::new(initial.0 as u32, maximum.map(|p| p.0 as u32));
|
||||||
|
|
||||||
let initial_size: Bytes = initial.into();
|
let initial_size: Bytes = initial.into();
|
||||||
MemoryInstance {
|
Ok(MemoryInstance {
|
||||||
limits: limits,
|
limits: limits,
|
||||||
buffer: RefCell::new(Vec::with_capacity(4096)),
|
buffer: RefCell::new(
|
||||||
|
ByteBuf::new(initial_size.0).map_err(|err| Error::Memory(err.to_string()))?,
|
||||||
|
),
|
||||||
initial: initial,
|
initial: initial,
|
||||||
current_size: Cell::new(initial_size.0),
|
current_size: Cell::new(initial_size.0),
|
||||||
maximum: maximum,
|
maximum: maximum,
|
||||||
lowest_used: Cell::new(u32::max_value()),
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return linear memory limits.
|
/// Return linear memory limits.
|
||||||
|
@ -150,16 +171,6 @@ impl MemoryInstance {
|
||||||
self.maximum
|
self.maximum
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns lowest offset ever written or `u32::max_value()` if none.
|
|
||||||
pub fn lowest_used(&self) -> u32 {
|
|
||||||
self.lowest_used.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resets tracked lowest offset.
|
|
||||||
pub fn reset_lowest_used(&self, addr: u32) {
|
|
||||||
self.lowest_used.set(addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns current linear memory size.
|
/// Returns current linear memory size.
|
||||||
///
|
///
|
||||||
/// Maximum memory size cannot exceed `65536` pages or 4GiB.
|
/// Maximum memory size cannot exceed `65536` pages or 4GiB.
|
||||||
|
@ -180,13 +191,7 @@ impl MemoryInstance {
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
pub fn current_size(&self) -> Pages {
|
pub fn current_size(&self) -> Pages {
|
||||||
Bytes(self.current_size.get()).round_up_to()
|
Bytes(self.buffer.borrow().len()).round_up_to()
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns current used memory size in bytes.
|
|
||||||
/// This is one more than the highest memory address that had been written to.
|
|
||||||
pub fn used_size(&self) -> Bytes {
|
|
||||||
Bytes(self.buffer.borrow().len())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get value from memory at given offset.
|
/// Get value from memory at given offset.
|
||||||
|
@ -194,7 +199,10 @@ impl MemoryInstance {
|
||||||
let mut buffer = self.buffer.borrow_mut();
|
let mut buffer = self.buffer.borrow_mut();
|
||||||
let region =
|
let region =
|
||||||
self.checked_region(&mut buffer, offset as usize, ::core::mem::size_of::<T>())?;
|
self.checked_region(&mut buffer, offset as usize, ::core::mem::size_of::<T>())?;
|
||||||
Ok(T::from_little_endian(&buffer[region.range()]).expect("Slice size is checked"))
|
Ok(
|
||||||
|
T::from_little_endian(&buffer.as_slice_mut()[region.range()])
|
||||||
|
.expect("Slice size is checked"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copy data from memory at given offset.
|
/// Copy data from memory at given offset.
|
||||||
|
@ -207,7 +215,7 @@ impl MemoryInstance {
|
||||||
let mut buffer = self.buffer.borrow_mut();
|
let mut buffer = self.buffer.borrow_mut();
|
||||||
let region = self.checked_region(&mut buffer, offset as usize, size)?;
|
let region = self.checked_region(&mut buffer, offset as usize, size)?;
|
||||||
|
|
||||||
Ok(buffer[region.range()].to_vec())
|
Ok(buffer.as_slice_mut()[region.range()].to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copy data from given offset in the memory into `target` slice.
|
/// Copy data from given offset in the memory into `target` slice.
|
||||||
|
@ -219,7 +227,7 @@ impl MemoryInstance {
|
||||||
let mut buffer = self.buffer.borrow_mut();
|
let mut buffer = self.buffer.borrow_mut();
|
||||||
let region = self.checked_region(&mut buffer, offset as usize, target.len())?;
|
let region = self.checked_region(&mut buffer, offset as usize, target.len())?;
|
||||||
|
|
||||||
target.copy_from_slice(&buffer[region.range()]);
|
target.copy_from_slice(&buffer.as_slice_mut()[region.range()]);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -231,10 +239,7 @@ impl MemoryInstance {
|
||||||
.checked_region(&mut buffer, offset as usize, value.len())?
|
.checked_region(&mut buffer, offset as usize, value.len())?
|
||||||
.range();
|
.range();
|
||||||
|
|
||||||
if offset < self.lowest_used.get() {
|
buffer.as_slice_mut()[range].copy_from_slice(value);
|
||||||
self.lowest_used.set(offset);
|
|
||||||
}
|
|
||||||
buffer[range].copy_from_slice(value);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -245,10 +250,7 @@ impl MemoryInstance {
|
||||||
let range = self
|
let range = self
|
||||||
.checked_region(&mut buffer, offset as usize, ::core::mem::size_of::<T>())?
|
.checked_region(&mut buffer, offset as usize, ::core::mem::size_of::<T>())?
|
||||||
.range();
|
.range();
|
||||||
if offset < self.lowest_used.get() {
|
value.into_little_endian(&mut buffer.as_slice_mut()[range]);
|
||||||
self.lowest_used.set(offset);
|
|
||||||
}
|
|
||||||
value.into_little_endian(&mut buffer[range]);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,7 +273,9 @@ impl MemoryInstance {
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_size: Pages = size_before_grow + additional;
|
let new_size: Pages = size_before_grow + additional;
|
||||||
let maximum = self.maximum.unwrap_or(LINEAR_MEMORY_MAX_PAGES);
|
let maximum = self
|
||||||
|
.maximum
|
||||||
|
.unwrap_or(Pages(validation::LINEAR_MEMORY_MAX_PAGES as usize));
|
||||||
if new_size > maximum {
|
if new_size > maximum {
|
||||||
return Err(Error::Memory(format!(
|
return Err(Error::Memory(format!(
|
||||||
"Trying to grow memory by {} pages when already have {}",
|
"Trying to grow memory by {} pages when already have {}",
|
||||||
|
@ -280,19 +284,22 @@ impl MemoryInstance {
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_buffer_length: Bytes = new_size.into();
|
let new_buffer_length: Bytes = new_size.into();
|
||||||
|
self.buffer
|
||||||
|
.borrow_mut()
|
||||||
|
.realloc(new_buffer_length.0)
|
||||||
|
.map_err(|err| Error::Memory(err.to_string()))?;
|
||||||
|
|
||||||
self.current_size.set(new_buffer_length.0);
|
self.current_size.set(new_buffer_length.0);
|
||||||
|
|
||||||
Ok(size_before_grow)
|
Ok(size_before_grow)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn checked_region<B>(
|
fn checked_region(
|
||||||
&self,
|
&self,
|
||||||
buffer: &mut B,
|
buffer: &mut ByteBuf,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
size: usize,
|
size: usize,
|
||||||
) -> Result<CheckedRegion, Error>
|
) -> Result<CheckedRegion, Error> {
|
||||||
where
|
|
||||||
B: ::core::ops::DerefMut<Target = Vec<u8>>,
|
|
||||||
{
|
|
||||||
let end = offset.checked_add(size).ok_or_else(|| {
|
let end = offset.checked_add(size).ok_or_else(|| {
|
||||||
Error::Memory(format!(
|
Error::Memory(format!(
|
||||||
"trying to access memory block of size {} from offset {}",
|
"trying to access memory block of size {} from offset {}",
|
||||||
|
@ -300,10 +307,6 @@ impl MemoryInstance {
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if end <= self.current_size.get() && buffer.len() < end {
|
|
||||||
buffer.resize(end, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if end > buffer.len() {
|
if end > buffer.len() {
|
||||||
return Err(Error::Memory(format!(
|
return Err(Error::Memory(format!(
|
||||||
"trying to access region [{}..{}] in memory [0..{}]",
|
"trying to access region [{}..{}] in memory [0..{}]",
|
||||||
|
@ -319,17 +322,14 @@ impl MemoryInstance {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn checked_region_pair<B>(
|
fn checked_region_pair(
|
||||||
&self,
|
&self,
|
||||||
buffer: &mut B,
|
buffer: &mut ByteBuf,
|
||||||
offset1: usize,
|
offset1: usize,
|
||||||
size1: usize,
|
size1: usize,
|
||||||
offset2: usize,
|
offset2: usize,
|
||||||
size2: usize,
|
size2: usize,
|
||||||
) -> Result<(CheckedRegion, CheckedRegion), Error>
|
) -> Result<(CheckedRegion, CheckedRegion), Error> {
|
||||||
where
|
|
||||||
B: ::core::ops::DerefMut<Target = Vec<u8>>,
|
|
||||||
{
|
|
||||||
let end1 = offset1.checked_add(size1).ok_or_else(|| {
|
let end1 = offset1.checked_add(size1).ok_or_else(|| {
|
||||||
Error::Memory(format!(
|
Error::Memory(format!(
|
||||||
"trying to access memory block of size {} from offset {}",
|
"trying to access memory block of size {} from offset {}",
|
||||||
|
@ -344,11 +344,6 @@ impl MemoryInstance {
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let max = cmp::max(end1, end2);
|
|
||||||
if max <= self.current_size.get() && buffer.len() < max {
|
|
||||||
buffer.resize(max, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if end1 > buffer.len() {
|
if end1 > buffer.len() {
|
||||||
return Err(Error::Memory(format!(
|
return Err(Error::Memory(format!(
|
||||||
"trying to access region [{}..{}] in memory [0..{}]",
|
"trying to access region [{}..{}] in memory [0..{}]",
|
||||||
|
@ -392,14 +387,10 @@ impl MemoryInstance {
|
||||||
let (read_region, write_region) =
|
let (read_region, write_region) =
|
||||||
self.checked_region_pair(&mut buffer, src_offset, len, dst_offset, len)?;
|
self.checked_region_pair(&mut buffer, src_offset, len, dst_offset, len)?;
|
||||||
|
|
||||||
if dst_offset < self.lowest_used.get() as usize {
|
|
||||||
self.lowest_used.set(dst_offset as u32);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
::core::ptr::copy(
|
::core::ptr::copy(
|
||||||
buffer[read_region.range()].as_ptr(),
|
buffer.as_slice()[read_region.range()].as_ptr(),
|
||||||
buffer[write_region.range()].as_mut_ptr(),
|
buffer.as_slice_mut()[write_region.range()].as_mut_ptr(),
|
||||||
len,
|
len,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -435,14 +426,10 @@ impl MemoryInstance {
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if dst_offset < self.lowest_used.get() as usize {
|
|
||||||
self.lowest_used.set(dst_offset as u32);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
::core::ptr::copy_nonoverlapping(
|
::core::ptr::copy_nonoverlapping(
|
||||||
buffer[read_region.range()].as_ptr(),
|
buffer.as_slice()[read_region.range()].as_ptr(),
|
||||||
buffer[write_region.range()].as_mut_ptr(),
|
buffer.as_slice_mut()[write_region.range()].as_mut_ptr(),
|
||||||
len,
|
len,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -478,11 +465,7 @@ impl MemoryInstance {
|
||||||
.checked_region(&mut dst_buffer, dst_offset, len)?
|
.checked_region(&mut dst_buffer, dst_offset, len)?
|
||||||
.range();
|
.range();
|
||||||
|
|
||||||
if dst_offset < dst.lowest_used.get() as usize {
|
dst_buffer.as_slice_mut()[dst_range].copy_from_slice(&src_buffer.as_slice()[src_range]);
|
||||||
dst.lowest_used.set(dst_offset as u32);
|
|
||||||
}
|
|
||||||
|
|
||||||
dst_buffer[dst_range].copy_from_slice(&src_buffer[src_range]);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -499,11 +482,7 @@ impl MemoryInstance {
|
||||||
|
|
||||||
let range = self.checked_region(&mut buffer, offset, len)?.range();
|
let range = self.checked_region(&mut buffer, offset, len)?.range();
|
||||||
|
|
||||||
if offset < self.lowest_used.get() as usize {
|
for val in &mut buffer.as_slice_mut()[range] {
|
||||||
self.lowest_used.set(offset as u32);
|
|
||||||
}
|
|
||||||
|
|
||||||
for val in &mut buffer[range] {
|
|
||||||
*val = new_val
|
*val = new_val
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -518,18 +497,28 @@ impl MemoryInstance {
|
||||||
self.clear(offset, 0, len)
|
self.clear(offset, 0, len)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set every byte in the entire linear memory to 0, preserving its size.
|
||||||
|
///
|
||||||
|
/// Might be useful for some optimization shenanigans.
|
||||||
|
pub fn erase(&self) -> Result<(), Error> {
|
||||||
|
self.buffer
|
||||||
|
.borrow_mut()
|
||||||
|
.erase()
|
||||||
|
.map_err(|err| Error::Memory(err.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
/// Provides direct access to the underlying memory buffer.
|
/// Provides direct access to the underlying memory buffer.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Any call that requires write access to memory (such as [`set`], [`clear`], etc) made within
|
/// Any call that requires write access to memory (such as [`set`], [`clear`], etc) made within
|
||||||
/// the closure will panic. Note that the buffer size may be arbitraty. Proceed with caution.
|
/// the closure will panic.
|
||||||
///
|
///
|
||||||
/// [`set`]: #method.get
|
/// [`set`]: #method.get
|
||||||
/// [`clear`]: #method.set
|
/// [`clear`]: #method.set
|
||||||
pub fn with_direct_access<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
|
pub fn with_direct_access<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
|
||||||
let buf = self.buffer.borrow();
|
let buf = self.buffer.borrow();
|
||||||
f(&*buf)
|
f(buf.as_slice())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provides direct mutable access to the underlying memory buffer.
|
/// Provides direct mutable access to the underlying memory buffer.
|
||||||
|
@ -537,43 +526,16 @@ impl MemoryInstance {
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Any calls that requires either read or write access to memory (such as [`get`], [`set`], [`copy`], etc) made
|
/// Any calls that requires either read or write access to memory (such as [`get`], [`set`], [`copy`], etc) made
|
||||||
/// within the closure will panic. Note that the buffer size may be arbitraty.
|
/// within the closure will panic. Proceed with caution.
|
||||||
/// The closure may however resize it. Proceed with caution.
|
|
||||||
///
|
///
|
||||||
/// [`get`]: #method.get
|
/// [`get`]: #method.get
|
||||||
/// [`set`]: #method.set
|
/// [`set`]: #method.set
|
||||||
/// [`copy`]: #method.copy
|
pub fn with_direct_access_mut<R, F: FnOnce(&mut [u8]) -> R>(&self, f: F) -> R {
|
||||||
pub fn with_direct_access_mut<R, F: FnOnce(&mut Vec<u8>) -> R>(&self, f: F) -> R {
|
|
||||||
let mut buf = self.buffer.borrow_mut();
|
let mut buf = self.buffer.borrow_mut();
|
||||||
f(&mut buf)
|
f(buf.as_slice_mut())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate_memory(initial: Pages, maximum: Option<Pages>) -> Result<(), String> {
|
|
||||||
if initial > LINEAR_MEMORY_MAX_PAGES {
|
|
||||||
return Err(format!(
|
|
||||||
"initial memory size must be at most {} pages",
|
|
||||||
LINEAR_MEMORY_MAX_PAGES.0
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if let Some(maximum) = maximum {
|
|
||||||
if initial > maximum {
|
|
||||||
return Err(format!(
|
|
||||||
"maximum limit {} is less than minimum {}",
|
|
||||||
maximum.0, initial.0,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if maximum > LINEAR_MEMORY_MAX_PAGES {
|
|
||||||
return Err(format!(
|
|
||||||
"maximum memory size must be at most {} pages",
|
|
||||||
LINEAR_MEMORY_MAX_PAGES.0
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
|
@ -584,29 +546,21 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn alloc() {
|
fn alloc() {
|
||||||
#[cfg(target_pointer_width = "64")]
|
let mut fixtures = vec![
|
||||||
let fixtures = &[
|
|
||||||
(0, None, true),
|
(0, None, true),
|
||||||
(0, Some(0), true),
|
(0, Some(0), true),
|
||||||
(1, None, true),
|
(1, None, true),
|
||||||
(1, Some(1), true),
|
(1, Some(1), true),
|
||||||
(0, Some(1), true),
|
(0, Some(1), true),
|
||||||
(1, Some(0), false),
|
(1, Some(0), false),
|
||||||
(0, Some(65536), true),
|
];
|
||||||
|
|
||||||
|
#[cfg(target_pointer_width = "64")]
|
||||||
|
fixtures.extend(&[
|
||||||
(65536, Some(65536), true),
|
(65536, Some(65536), true),
|
||||||
(65536, Some(0), false),
|
(65536, Some(0), false),
|
||||||
(65536, None, true),
|
(65536, None, true),
|
||||||
];
|
]);
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "32")]
|
|
||||||
let fixtures = &[
|
|
||||||
(0, None, true),
|
|
||||||
(0, Some(0), true),
|
|
||||||
(1, None, true),
|
|
||||||
(1, Some(1), true),
|
|
||||||
(0, Some(1), true),
|
|
||||||
(1, Some(0), false),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (index, &(initial, maybe_max, expected_ok)) in fixtures.iter().enumerate() {
|
for (index, &(initial, maybe_max, expected_ok)) in fixtures.iter().enumerate() {
|
||||||
let initial: Pages = Pages(initial);
|
let initial: Pages = Pages(initial);
|
||||||
|
@ -628,7 +582,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_memory(initial_content: &[u8]) -> MemoryInstance {
|
fn create_memory(initial_content: &[u8]) -> MemoryInstance {
|
||||||
let mem = MemoryInstance::new(Pages(1), Some(Pages(1)));
|
let mem = MemoryInstance::new(Pages(1), Some(Pages(1))).unwrap();
|
||||||
mem.set(0, initial_content)
|
mem.set(0, initial_content)
|
||||||
.expect("Successful initialize the memory");
|
.expect("Successful initialize the memory");
|
||||||
mem
|
mem
|
||||||
|
@ -741,7 +695,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn get_into() {
|
fn get_into() {
|
||||||
let mem = MemoryInstance::new(Pages(1), None);
|
let mem = MemoryInstance::new(Pages(1), None).unwrap();
|
||||||
mem.set(6, &[13, 17, 129])
|
mem.set(6, &[13, 17, 129])
|
||||||
.expect("memory set should not fail");
|
.expect("memory set should not fail");
|
||||||
|
|
||||||
|
@ -757,11 +711,19 @@ mod tests {
|
||||||
let mem = MemoryInstance::alloc(Pages(1), None).unwrap();
|
let mem = MemoryInstance::alloc(Pages(1), None).unwrap();
|
||||||
mem.set(100, &[0]).expect("memory set should not fail");
|
mem.set(100, &[0]).expect("memory set should not fail");
|
||||||
mem.with_direct_access_mut(|buf| {
|
mem.with_direct_access_mut(|buf| {
|
||||||
assert_eq!(buf.len(), 101);
|
assert_eq!(
|
||||||
|
buf.len(),
|
||||||
|
65536,
|
||||||
|
"the buffer length is expected to be 1 page long"
|
||||||
|
);
|
||||||
buf[..10].copy_from_slice(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
buf[..10].copy_from_slice(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||||
});
|
});
|
||||||
mem.with_direct_access(|buf| {
|
mem.with_direct_access(|buf| {
|
||||||
assert_eq!(buf.len(), 101);
|
assert_eq!(
|
||||||
|
buf.len(),
|
||||||
|
65536,
|
||||||
|
"the buffer length is expected to be 1 page long"
|
||||||
|
);
|
||||||
assert_eq!(&buf[..10], &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
assert_eq!(&buf[..10], &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
//! An implementation of `ByteBuf` based on a plain `Vec`.
|
||||||
|
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
pub struct ByteBuf {
|
||||||
|
buf: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ByteBuf {
|
||||||
|
pub fn new(len: usize) -> Result<Self, &'static str> {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
buf.resize(len, 0u8);
|
||||||
|
Ok(Self { buf })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn realloc(&mut self, new_len: usize) -> Result<(), &'static str> {
|
||||||
|
self.buf.resize(new_len, 0u8);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.buf.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_slice(&self) -> &[u8] {
|
||||||
|
self.buf.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_slice_mut(&mut self) -> &mut [u8] {
|
||||||
|
self.buf.as_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn erase(&mut self) -> Result<(), &'static str> {
|
||||||
|
for v in &mut self.buf {
|
||||||
|
*v = 0;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,15 @@
|
||||||
#[allow(unused_imports)]
|
use alloc::{
|
||||||
use alloc::prelude::*;
|
borrow::ToOwned,
|
||||||
use alloc::rc::Rc;
|
rc::Rc,
|
||||||
|
string::{String, ToString},
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
use core::cell::RefCell;
|
use core::cell::RefCell;
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use Trap;
|
use Trap;
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
use alloc::collections::BTreeMap;
|
||||||
use hashbrown::HashMap;
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
|
|
||||||
use core::cell::Ref;
|
use core::cell::Ref;
|
||||||
use func::{FuncBody, FuncInstance, FuncRef};
|
use func::{FuncBody, FuncInstance, FuncRef};
|
||||||
use global::{GlobalInstance, GlobalRef};
|
use global::{GlobalInstance, GlobalRef};
|
||||||
|
@ -19,8 +18,10 @@ use imports::ImportResolver;
|
||||||
use memory::MemoryRef;
|
use memory::MemoryRef;
|
||||||
use memory_units::Pages;
|
use memory_units::Pages;
|
||||||
use parity_wasm::elements::{External, InitExpr, Instruction, Internal, ResizableLimits, Type};
|
use parity_wasm::elements::{External, InitExpr, Instruction, Internal, ResizableLimits, Type};
|
||||||
|
use runner::StackRecycler;
|
||||||
use table::TableRef;
|
use table::TableRef;
|
||||||
use types::{GlobalDescriptor, MemoryDescriptor, TableDescriptor};
|
use types::{GlobalDescriptor, MemoryDescriptor, TableDescriptor};
|
||||||
|
use validation::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
|
||||||
use {Error, MemoryInstance, Module, RuntimeValue, Signature, TableInstance};
|
use {Error, MemoryInstance, Module, RuntimeValue, Signature, TableInstance};
|
||||||
|
|
||||||
/// Reference to a [`ModuleInstance`].
|
/// Reference to a [`ModuleInstance`].
|
||||||
|
@ -161,7 +162,7 @@ pub struct ModuleInstance {
|
||||||
funcs: RefCell<Vec<FuncRef>>,
|
funcs: RefCell<Vec<FuncRef>>,
|
||||||
memories: RefCell<Vec<MemoryRef>>,
|
memories: RefCell<Vec<MemoryRef>>,
|
||||||
globals: RefCell<Vec<GlobalRef>>,
|
globals: RefCell<Vec<GlobalRef>>,
|
||||||
exports: RefCell<HashMap<String, ExternVal>>,
|
exports: RefCell<BTreeMap<String, ExternVal>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModuleInstance {
|
impl ModuleInstance {
|
||||||
|
@ -172,7 +173,7 @@ impl ModuleInstance {
|
||||||
tables: RefCell::new(Vec::new()),
|
tables: RefCell::new(Vec::new()),
|
||||||
memories: RefCell::new(Vec::new()),
|
memories: RefCell::new(Vec::new()),
|
||||||
globals: RefCell::new(Vec::new()),
|
globals: RefCell::new(Vec::new()),
|
||||||
exports: RefCell::new(HashMap::new()),
|
exports: RefCell::new(BTreeMap::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -420,7 +421,11 @@ impl ModuleInstance {
|
||||||
.map(|es| es.entries())
|
.map(|es| es.entries())
|
||||||
.unwrap_or(&[])
|
.unwrap_or(&[])
|
||||||
{
|
{
|
||||||
let offset_val = match eval_init_expr(element_segment.offset(), &module_ref) {
|
let offset = element_segment
|
||||||
|
.offset()
|
||||||
|
.as_ref()
|
||||||
|
.expect("passive segments are rejected due to validation");
|
||||||
|
let offset_val = match eval_init_expr(offset, &module_ref) {
|
||||||
RuntimeValue::I32(v) => v as u32,
|
RuntimeValue::I32(v) => v as u32,
|
||||||
_ => panic!("Due to validation elem segment offset should evaluate to i32"),
|
_ => panic!("Due to validation elem segment offset should evaluate to i32"),
|
||||||
};
|
};
|
||||||
|
@ -449,7 +454,11 @@ impl ModuleInstance {
|
||||||
}
|
}
|
||||||
|
|
||||||
for data_segment in module.data_section().map(|ds| ds.entries()).unwrap_or(&[]) {
|
for data_segment in module.data_section().map(|ds| ds.entries()).unwrap_or(&[]) {
|
||||||
let offset_val = match eval_init_expr(data_segment.offset(), &module_ref) {
|
let offset = data_segment
|
||||||
|
.offset()
|
||||||
|
.as_ref()
|
||||||
|
.expect("passive segments are rejected due to validation");
|
||||||
|
let offset_val = match eval_init_expr(offset, &module_ref) {
|
||||||
RuntimeValue::I32(v) => v as u32,
|
RuntimeValue::I32(v) => v as u32,
|
||||||
_ => panic!("Due to validation data segment offset should evaluate to i32"),
|
_ => panic!("Due to validation data segment offset should evaluate to i32"),
|
||||||
};
|
};
|
||||||
|
@ -625,21 +634,43 @@ impl ModuleInstance {
|
||||||
args: &[RuntimeValue],
|
args: &[RuntimeValue],
|
||||||
externals: &mut E,
|
externals: &mut E,
|
||||||
) -> Result<Option<RuntimeValue>, Error> {
|
) -> Result<Option<RuntimeValue>, Error> {
|
||||||
|
let func_instance = self.func_by_name(func_name)?;
|
||||||
|
|
||||||
|
FuncInstance::invoke(&func_instance, args, externals).map_err(|t| Error::Trap(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invoke exported function by a name using recycled stacks.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Same as [`invoke_export`].
|
||||||
|
///
|
||||||
|
/// [`invoke_export`]: #method.invoke_export
|
||||||
|
pub fn invoke_export_with_stack<E: Externals>(
|
||||||
|
&self,
|
||||||
|
func_name: &str,
|
||||||
|
args: &[RuntimeValue],
|
||||||
|
externals: &mut E,
|
||||||
|
stack_recycler: &mut StackRecycler,
|
||||||
|
) -> Result<Option<RuntimeValue>, Error> {
|
||||||
|
let func_instance = self.func_by_name(func_name)?;
|
||||||
|
|
||||||
|
FuncInstance::invoke_with_stack(&func_instance, args, externals, stack_recycler)
|
||||||
|
.map_err(|t| Error::Trap(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn func_by_name(&self, func_name: &str) -> Result<FuncRef, Error> {
|
||||||
let extern_val = self
|
let extern_val = self
|
||||||
.export_by_name(func_name)
|
.export_by_name(func_name)
|
||||||
.ok_or_else(|| Error::Function(format!("Module doesn't have export {}", func_name)))?;
|
.ok_or_else(|| Error::Function(format!("Module doesn't have export {}", func_name)))?;
|
||||||
|
|
||||||
let func_instance = match extern_val {
|
match extern_val {
|
||||||
ExternVal::Func(func_instance) => func_instance,
|
ExternVal::Func(func_instance) => Ok(func_instance),
|
||||||
unexpected => {
|
unexpected => Err(Error::Function(format!(
|
||||||
return Err(Error::Function(format!(
|
|
||||||
"Export {} is not a function, but {:?}",
|
"Export {} is not a function, but {:?}",
|
||||||
func_name, unexpected
|
func_name, unexpected
|
||||||
)));
|
))),
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
FuncInstance::invoke(&func_instance, args, externals).map_err(|t| Error::Trap(t))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find export by a name.
|
/// Find export by a name.
|
||||||
|
@ -714,6 +745,13 @@ impl<'a> NotStartedModuleRef<'a> {
|
||||||
}
|
}
|
||||||
self.instance
|
self.instance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether or not the module has a `start` function.
|
||||||
|
///
|
||||||
|
/// Returns `true` if it has a `start` function.
|
||||||
|
pub fn has_start(&self) -> bool {
|
||||||
|
self.loaded_module.module().start_section().is_some()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_init_expr(init_expr: &InitExpr, module: &ModuleInstance) -> RuntimeValue {
|
fn eval_init_expr(init_expr: &InitExpr, module: &ModuleInstance) -> RuntimeValue {
|
||||||
|
@ -793,9 +831,9 @@ mod tests {
|
||||||
(start $f))
|
(start $f))
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
ModuleInstance::new(&module_with_start, &ImportsBuilder::default())
|
let module = ModuleInstance::new(&module_with_start, &ImportsBuilder::default()).unwrap();
|
||||||
.unwrap()
|
assert!(!module.has_start());
|
||||||
.assert_no_start();
|
module.assert_no_start();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
|
@ -153,9 +153,11 @@ mod tests {
|
||||||
|
|
||||||
use super::{F32, F64};
|
use super::{F32, F64};
|
||||||
|
|
||||||
use core::fmt::Debug;
|
use core::{
|
||||||
use core::iter;
|
fmt::Debug,
|
||||||
use core::ops::{Add, Div, Mul, Neg, Sub};
|
iter,
|
||||||
|
ops::{Add, Div, Mul, Neg, Sub},
|
||||||
|
};
|
||||||
|
|
||||||
fn test_ops<T, F, I>(iter: I)
|
fn test_ops<T, F, I>(iter: I)
|
||||||
where
|
where
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,169 @@
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
isa,
|
||||||
|
validation::{validate_module, Error, Validator},
|
||||||
|
};
|
||||||
|
use parity_wasm::elements::Module;
|
||||||
|
|
||||||
|
mod compile;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CompiledModule {
|
||||||
|
pub code_map: Vec<isa::Instructions>,
|
||||||
|
pub module: Module,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WasmiValidation {
|
||||||
|
code_map: Vec<isa::Instructions>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// This implementation of `Validation` is compiling wasm code at the
|
||||||
|
// validation time.
|
||||||
|
impl Validator for WasmiValidation {
|
||||||
|
type Output = Vec<isa::Instructions>;
|
||||||
|
type FuncValidator = compile::Compiler;
|
||||||
|
fn new(_module: &Module) -> Self {
|
||||||
|
WasmiValidation {
|
||||||
|
// TODO: with capacity?
|
||||||
|
code_map: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn on_function_validated(&mut self, _index: u32, output: isa::Instructions) {
|
||||||
|
self.code_map.push(output);
|
||||||
|
}
|
||||||
|
fn finish(self) -> Vec<isa::Instructions> {
|
||||||
|
self.code_map
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validate a module and compile it to the internal representation.
|
||||||
|
pub fn compile_module(module: Module) -> Result<CompiledModule, Error> {
|
||||||
|
let code_map = validate_module::<WasmiValidation>(&module)?;
|
||||||
|
Ok(CompiledModule { module, code_map })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify that the module doesn't use floating point instructions or types.
|
||||||
|
///
|
||||||
|
/// Returns `Err` if
|
||||||
|
///
|
||||||
|
/// - Any of function bodies uses a floating pointer instruction (an instruction that
|
||||||
|
/// consumes or produces a value of a floating point type)
|
||||||
|
/// - If a floating point type used in a definition of a function.
|
||||||
|
pub fn deny_floating_point(module: &Module) -> Result<(), Error> {
|
||||||
|
use parity_wasm::elements::{
|
||||||
|
Instruction::{self, *},
|
||||||
|
Type, ValueType,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(code) = module.code_section() {
|
||||||
|
for op in code.bodies().iter().flat_map(|body| body.code().elements()) {
|
||||||
|
macro_rules! match_eq {
|
||||||
|
($pattern:pat) => {
|
||||||
|
|val| if let $pattern = *val { true } else { false }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const DENIED: &[fn(&Instruction) -> bool] = &[
|
||||||
|
match_eq!(F32Load(_, _)),
|
||||||
|
match_eq!(F64Load(_, _)),
|
||||||
|
match_eq!(F32Store(_, _)),
|
||||||
|
match_eq!(F64Store(_, _)),
|
||||||
|
match_eq!(F32Const(_)),
|
||||||
|
match_eq!(F64Const(_)),
|
||||||
|
match_eq!(F32Eq),
|
||||||
|
match_eq!(F32Ne),
|
||||||
|
match_eq!(F32Lt),
|
||||||
|
match_eq!(F32Gt),
|
||||||
|
match_eq!(F32Le),
|
||||||
|
match_eq!(F32Ge),
|
||||||
|
match_eq!(F64Eq),
|
||||||
|
match_eq!(F64Ne),
|
||||||
|
match_eq!(F64Lt),
|
||||||
|
match_eq!(F64Gt),
|
||||||
|
match_eq!(F64Le),
|
||||||
|
match_eq!(F64Ge),
|
||||||
|
match_eq!(F32Abs),
|
||||||
|
match_eq!(F32Neg),
|
||||||
|
match_eq!(F32Ceil),
|
||||||
|
match_eq!(F32Floor),
|
||||||
|
match_eq!(F32Trunc),
|
||||||
|
match_eq!(F32Nearest),
|
||||||
|
match_eq!(F32Sqrt),
|
||||||
|
match_eq!(F32Add),
|
||||||
|
match_eq!(F32Sub),
|
||||||
|
match_eq!(F32Mul),
|
||||||
|
match_eq!(F32Div),
|
||||||
|
match_eq!(F32Min),
|
||||||
|
match_eq!(F32Max),
|
||||||
|
match_eq!(F32Copysign),
|
||||||
|
match_eq!(F64Abs),
|
||||||
|
match_eq!(F64Neg),
|
||||||
|
match_eq!(F64Ceil),
|
||||||
|
match_eq!(F64Floor),
|
||||||
|
match_eq!(F64Trunc),
|
||||||
|
match_eq!(F64Nearest),
|
||||||
|
match_eq!(F64Sqrt),
|
||||||
|
match_eq!(F64Add),
|
||||||
|
match_eq!(F64Sub),
|
||||||
|
match_eq!(F64Mul),
|
||||||
|
match_eq!(F64Div),
|
||||||
|
match_eq!(F64Min),
|
||||||
|
match_eq!(F64Max),
|
||||||
|
match_eq!(F64Copysign),
|
||||||
|
match_eq!(F32ConvertSI32),
|
||||||
|
match_eq!(F32ConvertUI32),
|
||||||
|
match_eq!(F32ConvertSI64),
|
||||||
|
match_eq!(F32ConvertUI64),
|
||||||
|
match_eq!(F32DemoteF64),
|
||||||
|
match_eq!(F64ConvertSI32),
|
||||||
|
match_eq!(F64ConvertUI32),
|
||||||
|
match_eq!(F64ConvertSI64),
|
||||||
|
match_eq!(F64ConvertUI64),
|
||||||
|
match_eq!(F64PromoteF32),
|
||||||
|
match_eq!(F32ReinterpretI32),
|
||||||
|
match_eq!(F64ReinterpretI64),
|
||||||
|
match_eq!(I32TruncSF32),
|
||||||
|
match_eq!(I32TruncUF32),
|
||||||
|
match_eq!(I32TruncSF64),
|
||||||
|
match_eq!(I32TruncUF64),
|
||||||
|
match_eq!(I64TruncSF32),
|
||||||
|
match_eq!(I64TruncUF32),
|
||||||
|
match_eq!(I64TruncSF64),
|
||||||
|
match_eq!(I64TruncUF64),
|
||||||
|
match_eq!(I32ReinterpretF32),
|
||||||
|
match_eq!(I64ReinterpretF64),
|
||||||
|
];
|
||||||
|
|
||||||
|
if DENIED.iter().any(|is_denied| is_denied(op)) {
|
||||||
|
return Err(Error(format!("Floating point operation denied: {:?}", op)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (Some(sec), Some(types)) = (module.function_section(), module.type_section()) {
|
||||||
|
let types = types.types();
|
||||||
|
|
||||||
|
for sig in sec.entries() {
|
||||||
|
if let Some(typ) = types.get(sig.type_ref() as usize) {
|
||||||
|
match *typ {
|
||||||
|
Type::Function(ref func) => {
|
||||||
|
if func
|
||||||
|
.params()
|
||||||
|
.iter()
|
||||||
|
.chain(func.return_type().as_ref())
|
||||||
|
.any(|&typ| typ == ValueType::F32 || typ == ValueType::F64)
|
||||||
|
{
|
||||||
|
return Err(Error(format!("Use of floating point types denied")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,285 +1,17 @@
|
||||||
use super::{validate_module, ValidatedModule};
|
use super::{compile_module, CompiledModule};
|
||||||
|
use parity_wasm::{deserialize_buffer, elements::Module};
|
||||||
|
|
||||||
use isa;
|
use isa;
|
||||||
use parity_wasm::builder::module;
|
|
||||||
use parity_wasm::elements::{
|
|
||||||
deserialize_buffer, BlockType, External, GlobalEntry, GlobalType, ImportEntry, InitExpr,
|
|
||||||
Instruction, Instructions, MemoryType, Module, TableType, ValueType,
|
|
||||||
};
|
|
||||||
use wabt;
|
use wabt;
|
||||||
|
|
||||||
#[test]
|
fn validate(wat: &str) -> CompiledModule {
|
||||||
fn empty_is_valid() {
|
|
||||||
let module = module().build();
|
|
||||||
assert!(validate_module(module).is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn limits() {
|
|
||||||
let test_cases = vec![
|
|
||||||
// min > max
|
|
||||||
(10, Some(9), false),
|
|
||||||
// min = max
|
|
||||||
(10, Some(10), true),
|
|
||||||
// table/memory is always valid without max
|
|
||||||
(10, None, true),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (min, max, is_valid) in test_cases {
|
|
||||||
// defined table
|
|
||||||
let m = module().table().with_min(min).with_max(max).build().build();
|
|
||||||
assert_eq!(validate_module(m).is_ok(), is_valid);
|
|
||||||
|
|
||||||
// imported table
|
|
||||||
let m = module()
|
|
||||||
.with_import(ImportEntry::new(
|
|
||||||
"core".into(),
|
|
||||||
"table".into(),
|
|
||||||
External::Table(TableType::new(min, max)),
|
|
||||||
))
|
|
||||||
.build();
|
|
||||||
assert_eq!(validate_module(m).is_ok(), is_valid);
|
|
||||||
|
|
||||||
// defined memory
|
|
||||||
let m = module()
|
|
||||||
.memory()
|
|
||||||
.with_min(min)
|
|
||||||
.with_max(max)
|
|
||||||
.build()
|
|
||||||
.build();
|
|
||||||
assert_eq!(validate_module(m).is_ok(), is_valid);
|
|
||||||
|
|
||||||
// imported table
|
|
||||||
let m = module()
|
|
||||||
.with_import(ImportEntry::new(
|
|
||||||
"core".into(),
|
|
||||||
"memory".into(),
|
|
||||||
External::Memory(MemoryType::new(min, max)),
|
|
||||||
))
|
|
||||||
.build();
|
|
||||||
assert_eq!(validate_module(m).is_ok(), is_valid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn global_init_const() {
|
|
||||||
let m = module()
|
|
||||||
.with_global(GlobalEntry::new(
|
|
||||||
GlobalType::new(ValueType::I32, true),
|
|
||||||
InitExpr::new(vec![Instruction::I32Const(42), Instruction::End]),
|
|
||||||
))
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(m).is_ok());
|
|
||||||
|
|
||||||
// init expr type differs from declared global type
|
|
||||||
let m = module()
|
|
||||||
.with_global(GlobalEntry::new(
|
|
||||||
GlobalType::new(ValueType::I64, true),
|
|
||||||
InitExpr::new(vec![Instruction::I32Const(42), Instruction::End]),
|
|
||||||
))
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(m).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn global_init_global() {
|
|
||||||
let m = module()
|
|
||||||
.with_import(ImportEntry::new(
|
|
||||||
"env".into(),
|
|
||||||
"ext_global".into(),
|
|
||||||
External::Global(GlobalType::new(ValueType::I32, false)),
|
|
||||||
))
|
|
||||||
.with_global(GlobalEntry::new(
|
|
||||||
GlobalType::new(ValueType::I32, true),
|
|
||||||
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
|
|
||||||
))
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(m).is_ok());
|
|
||||||
|
|
||||||
// get_global can reference only previously defined globals
|
|
||||||
let m = module()
|
|
||||||
.with_global(GlobalEntry::new(
|
|
||||||
GlobalType::new(ValueType::I32, true),
|
|
||||||
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
|
|
||||||
))
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(m).is_err());
|
|
||||||
|
|
||||||
// get_global can reference only const globals
|
|
||||||
let m = module()
|
|
||||||
.with_import(ImportEntry::new(
|
|
||||||
"env".into(),
|
|
||||||
"ext_global".into(),
|
|
||||||
External::Global(GlobalType::new(ValueType::I32, true)),
|
|
||||||
))
|
|
||||||
.with_global(GlobalEntry::new(
|
|
||||||
GlobalType::new(ValueType::I32, true),
|
|
||||||
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
|
|
||||||
))
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(m).is_err());
|
|
||||||
|
|
||||||
// get_global in init_expr can only refer to imported globals.
|
|
||||||
let m = module()
|
|
||||||
.with_global(GlobalEntry::new(
|
|
||||||
GlobalType::new(ValueType::I32, false),
|
|
||||||
InitExpr::new(vec![Instruction::I32Const(0), Instruction::End]),
|
|
||||||
))
|
|
||||||
.with_global(GlobalEntry::new(
|
|
||||||
GlobalType::new(ValueType::I32, true),
|
|
||||||
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
|
|
||||||
))
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(m).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn global_init_misc() {
|
|
||||||
// without delimiting End opcode
|
|
||||||
let m = module()
|
|
||||||
.with_global(GlobalEntry::new(
|
|
||||||
GlobalType::new(ValueType::I32, true),
|
|
||||||
InitExpr::new(vec![Instruction::I32Const(42)]),
|
|
||||||
))
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(m).is_err());
|
|
||||||
|
|
||||||
// empty init expr
|
|
||||||
let m = module()
|
|
||||||
.with_global(GlobalEntry::new(
|
|
||||||
GlobalType::new(ValueType::I32, true),
|
|
||||||
InitExpr::new(vec![Instruction::End]),
|
|
||||||
))
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(m).is_err());
|
|
||||||
|
|
||||||
// not an constant opcode used
|
|
||||||
let m = module()
|
|
||||||
.with_global(GlobalEntry::new(
|
|
||||||
GlobalType::new(ValueType::I32, true),
|
|
||||||
InitExpr::new(vec![Instruction::Unreachable, Instruction::End]),
|
|
||||||
))
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(m).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn module_limits_validity() {
|
|
||||||
// module cannot contain more than 1 memory atm.
|
|
||||||
let m = module()
|
|
||||||
.with_import(ImportEntry::new(
|
|
||||||
"core".into(),
|
|
||||||
"memory".into(),
|
|
||||||
External::Memory(MemoryType::new(10, None)),
|
|
||||||
))
|
|
||||||
.memory()
|
|
||||||
.with_min(10)
|
|
||||||
.build()
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(m).is_err());
|
|
||||||
|
|
||||||
// module cannot contain more than 1 table atm.
|
|
||||||
let m = module()
|
|
||||||
.with_import(ImportEntry::new(
|
|
||||||
"core".into(),
|
|
||||||
"table".into(),
|
|
||||||
External::Table(TableType::new(10, None)),
|
|
||||||
))
|
|
||||||
.table()
|
|
||||||
.with_min(10)
|
|
||||||
.build()
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(m).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn funcs() {
|
|
||||||
// recursive function calls is legal.
|
|
||||||
let m = module()
|
|
||||||
.function()
|
|
||||||
.signature()
|
|
||||||
.return_type()
|
|
||||||
.i32()
|
|
||||||
.build()
|
|
||||||
.body()
|
|
||||||
.with_instructions(Instructions::new(vec![
|
|
||||||
Instruction::Call(1),
|
|
||||||
Instruction::End,
|
|
||||||
]))
|
|
||||||
.build()
|
|
||||||
.build()
|
|
||||||
.function()
|
|
||||||
.signature()
|
|
||||||
.return_type()
|
|
||||||
.i32()
|
|
||||||
.build()
|
|
||||||
.body()
|
|
||||||
.with_instructions(Instructions::new(vec![
|
|
||||||
Instruction::Call(0),
|
|
||||||
Instruction::End,
|
|
||||||
]))
|
|
||||||
.build()
|
|
||||||
.build()
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(m).is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn globals() {
|
|
||||||
// import immutable global is legal.
|
|
||||||
let m = module()
|
|
||||||
.with_import(ImportEntry::new(
|
|
||||||
"env".into(),
|
|
||||||
"ext_global".into(),
|
|
||||||
External::Global(GlobalType::new(ValueType::I32, false)),
|
|
||||||
))
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(m).is_ok());
|
|
||||||
|
|
||||||
// import mutable global is invalid.
|
|
||||||
let m = module()
|
|
||||||
.with_import(ImportEntry::new(
|
|
||||||
"env".into(),
|
|
||||||
"ext_global".into(),
|
|
||||||
External::Global(GlobalType::new(ValueType::I32, true)),
|
|
||||||
))
|
|
||||||
.build();
|
|
||||||
assert!(validate_module(m).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn if_else_with_return_type_validation() {
|
|
||||||
let m = module()
|
|
||||||
.function()
|
|
||||||
.signature()
|
|
||||||
.build()
|
|
||||||
.body()
|
|
||||||
.with_instructions(Instructions::new(vec![
|
|
||||||
Instruction::I32Const(1),
|
|
||||||
Instruction::If(BlockType::NoResult),
|
|
||||||
Instruction::I32Const(1),
|
|
||||||
Instruction::If(BlockType::Value(ValueType::I32)),
|
|
||||||
Instruction::I32Const(1),
|
|
||||||
Instruction::Else,
|
|
||||||
Instruction::I32Const(2),
|
|
||||||
Instruction::End,
|
|
||||||
Instruction::Drop,
|
|
||||||
Instruction::End,
|
|
||||||
Instruction::End,
|
|
||||||
]))
|
|
||||||
.build()
|
|
||||||
.build()
|
|
||||||
.build();
|
|
||||||
validate_module(m).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate(wat: &str) -> ValidatedModule {
|
|
||||||
let wasm = wabt::wat2wasm(wat).unwrap();
|
let wasm = wabt::wat2wasm(wat).unwrap();
|
||||||
let module = deserialize_buffer::<Module>(&wasm).unwrap();
|
let module = deserialize_buffer::<Module>(&wasm).unwrap();
|
||||||
let validated_module = validate_module(module).unwrap();
|
let compiled_module = compile_module(module).unwrap();
|
||||||
validated_module
|
compiled_module
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile(module: &ValidatedModule) -> (Vec<isa::Instruction>, Vec<u32>) {
|
fn compile(module: &CompiledModule) -> (Vec<isa::Instruction>, Vec<u32>) {
|
||||||
let code = &module.code_map[0];
|
let code = &module.code_map[0];
|
||||||
let mut instructions = Vec::new();
|
let mut instructions = Vec::new();
|
||||||
let mut pcs = Vec::new();
|
let mut pcs = Vec::new();
|
||||||
|
@ -806,6 +538,68 @@ fn loop_empty() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn spec_as_br_if_value_cond() {
|
||||||
|
use self::isa::Instruction::*;
|
||||||
|
|
||||||
|
let module = validate(
|
||||||
|
r#"
|
||||||
|
(func (export "as-br_if-value-cond") (result i32)
|
||||||
|
(block (result i32)
|
||||||
|
(drop
|
||||||
|
(br_if 0
|
||||||
|
(i32.const 6)
|
||||||
|
(br_table 0 0
|
||||||
|
(i32.const 9)
|
||||||
|
(i32.const 0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(i32.const 7)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
let (code, _) = compile(&module);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
I32Const(6),
|
||||||
|
I32Const(9),
|
||||||
|
I32Const(0),
|
||||||
|
isa::Instruction::BrTable(targets![
|
||||||
|
isa::Target {
|
||||||
|
dst_pc: 9,
|
||||||
|
drop_keep: isa::DropKeep {
|
||||||
|
drop: 1,
|
||||||
|
keep: isa::Keep::Single
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isa::Target {
|
||||||
|
dst_pc: 9,
|
||||||
|
drop_keep: isa::DropKeep {
|
||||||
|
drop: 1,
|
||||||
|
keep: isa::Keep::Single
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]),
|
||||||
|
BrIfNez(isa::Target {
|
||||||
|
dst_pc: 9,
|
||||||
|
drop_keep: isa::DropKeep {
|
||||||
|
drop: 0,
|
||||||
|
keep: isa::Keep::Single
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Drop,
|
||||||
|
I32Const(7),
|
||||||
|
Return(isa::DropKeep {
|
||||||
|
drop: 0,
|
||||||
|
keep: isa::Keep::Single
|
||||||
|
})
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn brtable() {
|
fn brtable() {
|
||||||
let module = validate(
|
let module = validate(
|
|
@ -1,6 +1,4 @@
|
||||||
#[allow(unused_imports)]
|
use alloc::{boxed::Box, vec::Vec};
|
||||||
use alloc::prelude::*;
|
|
||||||
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
|
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use core::ops;
|
use core::ops;
|
||||||
use core::{u32, usize};
|
use core::{u32, usize};
|
||||||
|
@ -12,16 +10,17 @@ use memory_units::Pages;
|
||||||
use module::ModuleRef;
|
use module::ModuleRef;
|
||||||
use nan_preserving_float::{F32, F64};
|
use nan_preserving_float::{F32, F64};
|
||||||
use parity_wasm::elements::Local;
|
use parity_wasm::elements::Local;
|
||||||
|
use validation::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
|
||||||
use value::{
|
use value::{
|
||||||
ArithmeticOps, ExtendInto, Float, Integer, LittleEndianConvert, RuntimeValue, TransmuteInto,
|
ArithmeticOps, ExtendInto, Float, Integer, LittleEndianConvert, RuntimeValue, TransmuteInto,
|
||||||
TryTruncateInto, WrapInto,
|
TryTruncateInto, WrapInto,
|
||||||
};
|
};
|
||||||
use {Signature, Trap, TrapKind, ValueType};
|
use {Signature, Trap, TrapKind, ValueType};
|
||||||
|
|
||||||
/// Maximum number of entries in value stack.
|
/// Maximum number of bytes on the value stack.
|
||||||
pub const DEFAULT_VALUE_STACK_LIMIT: usize = (1024 * 1024) / ::core::mem::size_of::<RuntimeValue>();
|
pub const DEFAULT_VALUE_STACK_LIMIT: usize = 1024 * 1024;
|
||||||
|
|
||||||
// TODO: Make these parameters changeble.
|
/// Maximum number of levels on the call stack.
|
||||||
pub const DEFAULT_CALL_STACK_LIMIT: usize = 64 * 1024;
|
pub const DEFAULT_CALL_STACK_LIMIT: usize = 64 * 1024;
|
||||||
|
|
||||||
/// This is a wrapper around u64 to allow us to treat runtime values as a tag-free `u64`
|
/// This is a wrapper around u64 to allow us to treat runtime values as a tag-free `u64`
|
||||||
|
@ -166,14 +165,18 @@ enum RunResult {
|
||||||
/// Function interpreter.
|
/// Function interpreter.
|
||||||
pub struct Interpreter {
|
pub struct Interpreter {
|
||||||
value_stack: ValueStack,
|
value_stack: ValueStack,
|
||||||
call_stack: Vec<FunctionContext>,
|
call_stack: CallStack,
|
||||||
return_type: Option<ValueType>,
|
return_type: Option<ValueType>,
|
||||||
state: InterpreterState,
|
state: InterpreterState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Interpreter {
|
impl Interpreter {
|
||||||
pub fn new(func: &FuncRef, args: &[RuntimeValue]) -> Result<Interpreter, Trap> {
|
pub fn new(
|
||||||
let mut value_stack = ValueStack::with_limit(DEFAULT_VALUE_STACK_LIMIT);
|
func: &FuncRef,
|
||||||
|
args: &[RuntimeValue],
|
||||||
|
mut stack_recycler: Option<&mut StackRecycler>,
|
||||||
|
) -> Result<Interpreter, Trap> {
|
||||||
|
let mut value_stack = StackRecycler::recreate_value_stack(&mut stack_recycler);
|
||||||
for &arg in args {
|
for &arg in args {
|
||||||
let arg = arg.into();
|
let arg = arg.into();
|
||||||
value_stack.push(arg).map_err(
|
value_stack.push(arg).map_err(
|
||||||
|
@ -183,7 +186,7 @@ impl Interpreter {
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut call_stack = Vec::new();
|
let mut call_stack = StackRecycler::recreate_call_stack(&mut stack_recycler);
|
||||||
let initial_frame = FunctionContext::new(func.clone());
|
let initial_frame = FunctionContext::new(func.clone());
|
||||||
call_stack.push(initial_frame);
|
call_stack.push(initial_frame);
|
||||||
|
|
||||||
|
@ -278,14 +281,14 @@ impl Interpreter {
|
||||||
|
|
||||||
match function_return {
|
match function_return {
|
||||||
RunResult::Return => {
|
RunResult::Return => {
|
||||||
if self.call_stack.last().is_none() {
|
if self.call_stack.is_empty() {
|
||||||
// This was the last frame in the call stack. This means we
|
// This was the last frame in the call stack. This means we
|
||||||
// are done executing.
|
// are done executing.
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RunResult::NestedCall(nested_func) => {
|
RunResult::NestedCall(nested_func) => {
|
||||||
if self.call_stack.len() + 1 >= DEFAULT_CALL_STACK_LIMIT {
|
if self.call_stack.is_full() {
|
||||||
return Err(TrapKind::StackOverflow.into());
|
return Err(TrapKind::StackOverflow.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1286,14 +1289,8 @@ impl FunctionContext {
|
||||||
debug_assert!(!self.is_initialized);
|
debug_assert!(!self.is_initialized);
|
||||||
|
|
||||||
let num_locals = locals.iter().map(|l| l.count() as usize).sum();
|
let num_locals = locals.iter().map(|l| l.count() as usize).sum();
|
||||||
let locals = vec![Default::default(); num_locals];
|
|
||||||
|
|
||||||
// TODO: Replace with extend.
|
value_stack.extend(num_locals)?;
|
||||||
for local in locals {
|
|
||||||
value_stack
|
|
||||||
.push(local)
|
|
||||||
.map_err(|_| TrapKind::StackOverflow)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.is_initialized = true;
|
self.is_initialized = true;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1363,16 +1360,6 @@ struct ValueStack {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ValueStack {
|
impl ValueStack {
|
||||||
fn with_limit(limit: usize) -> ValueStack {
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
buf.resize(limit, RuntimeValueInternal(0));
|
|
||||||
|
|
||||||
ValueStack {
|
|
||||||
buf: buf.into_boxed_slice(),
|
|
||||||
sp: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn drop_keep(&mut self, drop_keep: isa::DropKeep) {
|
fn drop_keep(&mut self, drop_keep: isa::DropKeep) {
|
||||||
if drop_keep.keep == isa::Keep::Single {
|
if drop_keep.keep == isa::Keep::Single {
|
||||||
|
@ -1449,8 +1436,126 @@ impl ValueStack {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extend(&mut self, len: usize) -> Result<(), TrapKind> {
|
||||||
|
let cells = self
|
||||||
|
.buf
|
||||||
|
.get_mut(self.sp..self.sp + len)
|
||||||
|
.ok_or_else(|| TrapKind::StackOverflow)?;
|
||||||
|
for cell in cells {
|
||||||
|
*cell = Default::default();
|
||||||
|
}
|
||||||
|
self.sp += len;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn len(&self) -> usize {
|
fn len(&self) -> usize {
|
||||||
self.sp
|
self.sp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct CallStack {
|
||||||
|
buf: Vec<FunctionContext>,
|
||||||
|
limit: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CallStack {
|
||||||
|
fn push(&mut self, ctx: FunctionContext) {
|
||||||
|
self.buf.push(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop(&mut self) -> Option<FunctionContext> {
|
||||||
|
self.buf.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.buf.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_full(&self) -> bool {
|
||||||
|
self.buf.len() + 1 >= self.limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used to recycle stacks instead of allocating them repeatedly.
|
||||||
|
pub struct StackRecycler {
|
||||||
|
value_stack_buf: Option<Box<[RuntimeValueInternal]>>,
|
||||||
|
value_stack_limit: usize,
|
||||||
|
call_stack_buf: Option<Vec<FunctionContext>>,
|
||||||
|
call_stack_limit: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StackRecycler {
|
||||||
|
/// Limit stacks created by this recycler to
|
||||||
|
/// - `value_stack_limit` bytes for values and
|
||||||
|
/// - `call_stack_limit` levels for calls.
|
||||||
|
pub fn with_limits(value_stack_limit: usize, call_stack_limit: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
value_stack_buf: None,
|
||||||
|
value_stack_limit,
|
||||||
|
call_stack_buf: None,
|
||||||
|
call_stack_limit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears any values left on the stack to avoid
|
||||||
|
/// leaking them to future export invocations.
|
||||||
|
///
|
||||||
|
/// This is a secondary defense to prevent modules from
|
||||||
|
/// exploiting faulty stack handling in the interpreter.
|
||||||
|
///
|
||||||
|
/// Do note that there are additional channels that
|
||||||
|
/// can leak information into an untrusted module.
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
if let Some(buf) = &mut self.value_stack_buf {
|
||||||
|
for cell in buf.iter_mut() {
|
||||||
|
*cell = RuntimeValueInternal(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recreate_value_stack(this: &mut Option<&mut Self>) -> ValueStack {
|
||||||
|
let limit = this
|
||||||
|
.as_ref()
|
||||||
|
.map_or(DEFAULT_VALUE_STACK_LIMIT, |this| this.value_stack_limit)
|
||||||
|
/ ::core::mem::size_of::<RuntimeValueInternal>();
|
||||||
|
|
||||||
|
let buf = this
|
||||||
|
.as_mut()
|
||||||
|
.and_then(|this| this.value_stack_buf.take())
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
buf.reserve_exact(limit);
|
||||||
|
buf.resize(limit, RuntimeValueInternal(0));
|
||||||
|
buf.into_boxed_slice()
|
||||||
|
});
|
||||||
|
|
||||||
|
ValueStack { buf, sp: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recreate_call_stack(this: &mut Option<&mut Self>) -> CallStack {
|
||||||
|
let limit = this
|
||||||
|
.as_ref()
|
||||||
|
.map_or(DEFAULT_CALL_STACK_LIMIT, |this| this.call_stack_limit);
|
||||||
|
|
||||||
|
let buf = this
|
||||||
|
.as_mut()
|
||||||
|
.and_then(|this| this.call_stack_buf.take())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
CallStack { buf, limit }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn recycle(&mut self, mut interpreter: Interpreter) {
|
||||||
|
interpreter.call_stack.buf.clear();
|
||||||
|
|
||||||
|
self.value_stack_buf = Some(interpreter.value_stack.buf);
|
||||||
|
self.call_stack_buf = Some(interpreter.call_stack.buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for StackRecycler {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::with_limits(DEFAULT_VALUE_STACK_LIMIT, DEFAULT_CALL_STACK_LIMIT)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,4 @@
|
||||||
#[allow(unused_imports)]
|
use alloc::{rc::Rc, vec::Vec};
|
||||||
use alloc::prelude::*;
|
|
||||||
use alloc::rc::Rc;
|
|
||||||
use core::cell::RefCell;
|
use core::cell::RefCell;
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use core::u32;
|
use core::u32;
|
|
@ -285,7 +285,7 @@ fn resume_call_host_func() {
|
||||||
let export = instance.export_by_name("test").unwrap();
|
let export = instance.export_by_name("test").unwrap();
|
||||||
let func_instance = export.as_func().unwrap();
|
let func_instance = export.as_func().unwrap();
|
||||||
|
|
||||||
let mut invocation = FuncInstance::invoke_resumable(&func_instance, &[]).unwrap();
|
let mut invocation = FuncInstance::invoke_resumable(&func_instance, &[][..]).unwrap();
|
||||||
let result = invocation.start_execution(&mut env);
|
let result = invocation.start_execution(&mut env);
|
||||||
match result {
|
match result {
|
||||||
Err(ResumableError::Trap(_)) => {}
|
Err(ResumableError::Trap(_)) => {}
|
||||||
|
@ -330,7 +330,7 @@ fn resume_call_host_func_type_mismatch() {
|
||||||
let export = instance.export_by_name("test").unwrap();
|
let export = instance.export_by_name("test").unwrap();
|
||||||
let func_instance = export.as_func().unwrap();
|
let func_instance = export.as_func().unwrap();
|
||||||
|
|
||||||
let mut invocation = FuncInstance::invoke_resumable(&func_instance, &[]).unwrap();
|
let mut invocation = FuncInstance::invoke_resumable(&func_instance, &[][..]).unwrap();
|
||||||
let result = invocation.start_execution(&mut env);
|
let result = invocation.start_execution(&mut env);
|
||||||
match result {
|
match result {
|
||||||
Err(ResumableError::Trap(_)) => {}
|
Err(ResumableError::Trap(_)) => {}
|
|
@ -1,4 +1,3 @@
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
|
||||||
use core::{f32, i32, i64, u32, u64};
|
use core::{f32, i32, i64, u32, u64};
|
||||||
use nan_preserving_float::{F32, F64};
|
use nan_preserving_float::{F32, F64};
|
||||||
use types::ValueType;
|
use types::ValueType;
|
||||||
|
@ -367,8 +366,17 @@ impl WrapInto<F32> for F64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_try_truncate_into {
|
macro_rules! impl_try_truncate_into {
|
||||||
($from: ident, $into: ident) => {
|
(@primitive $from: ident, $into: ident, $to_primitive:path) => {
|
||||||
impl TryTruncateInto<$into, TrapKind> for $from {
|
impl TryTruncateInto<$into, TrapKind> for $from {
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
fn try_truncate_into(self) -> Result<$into, TrapKind> {
|
||||||
|
// Casting from a float to an integer will round the float towards zero
|
||||||
|
num_rational::BigRational::from_float(self)
|
||||||
|
.map(|val| val.to_integer())
|
||||||
|
.and_then(|val| $to_primitive(&val))
|
||||||
|
.ok_or(TrapKind::InvalidConversionToInt)
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
fn try_truncate_into(self) -> Result<$into, TrapKind> {
|
fn try_truncate_into(self) -> Result<$into, TrapKind> {
|
||||||
// Casting from a float to an integer will round the float towards zero
|
// Casting from a float to an integer will round the float towards zero
|
||||||
// NOTE: currently this will cause Undefined Behavior if the rounded value cannot be represented by the
|
// NOTE: currently this will cause Undefined Behavior if the rounded value cannot be represented by the
|
||||||
|
@ -387,7 +395,7 @@ macro_rules! impl_try_truncate_into {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
($from:ident, $intermediate:ident, $into:ident) => {
|
(@wrapped $from:ident, $intermediate:ident, $into:ident) => {
|
||||||
impl TryTruncateInto<$into, TrapKind> for $from {
|
impl TryTruncateInto<$into, TrapKind> for $from {
|
||||||
fn try_truncate_into(self) -> Result<$into, TrapKind> {
|
fn try_truncate_into(self) -> Result<$into, TrapKind> {
|
||||||
$intermediate::from(self).try_truncate_into()
|
$intermediate::from(self).try_truncate_into()
|
||||||
|
@ -396,22 +404,22 @@ macro_rules! impl_try_truncate_into {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_try_truncate_into!(f32, i32);
|
impl_try_truncate_into!(@primitive f32, i32, num_traits::cast::ToPrimitive::to_i32);
|
||||||
impl_try_truncate_into!(f32, i64);
|
impl_try_truncate_into!(@primitive f32, i64, num_traits::cast::ToPrimitive::to_i64);
|
||||||
impl_try_truncate_into!(f64, i32);
|
impl_try_truncate_into!(@primitive f64, i32, num_traits::cast::ToPrimitive::to_i32);
|
||||||
impl_try_truncate_into!(f64, i64);
|
impl_try_truncate_into!(@primitive f64, i64, num_traits::cast::ToPrimitive::to_i64);
|
||||||
impl_try_truncate_into!(f32, u32);
|
impl_try_truncate_into!(@primitive f32, u32, num_traits::cast::ToPrimitive::to_u32);
|
||||||
impl_try_truncate_into!(f32, u64);
|
impl_try_truncate_into!(@primitive f32, u64, num_traits::cast::ToPrimitive::to_u64);
|
||||||
impl_try_truncate_into!(f64, u32);
|
impl_try_truncate_into!(@primitive f64, u32, num_traits::cast::ToPrimitive::to_u32);
|
||||||
impl_try_truncate_into!(f64, u64);
|
impl_try_truncate_into!(@primitive f64, u64, num_traits::cast::ToPrimitive::to_u64);
|
||||||
impl_try_truncate_into!(F32, f32, i32);
|
impl_try_truncate_into!(@wrapped F32, f32, i32);
|
||||||
impl_try_truncate_into!(F32, f32, i64);
|
impl_try_truncate_into!(@wrapped F32, f32, i64);
|
||||||
impl_try_truncate_into!(F64, f64, i32);
|
impl_try_truncate_into!(@wrapped F64, f64, i32);
|
||||||
impl_try_truncate_into!(F64, f64, i64);
|
impl_try_truncate_into!(@wrapped F64, f64, i64);
|
||||||
impl_try_truncate_into!(F32, f32, u32);
|
impl_try_truncate_into!(@wrapped F32, f32, u32);
|
||||||
impl_try_truncate_into!(F32, f32, u64);
|
impl_try_truncate_into!(@wrapped F32, f32, u64);
|
||||||
impl_try_truncate_into!(F64, f64, u32);
|
impl_try_truncate_into!(@wrapped F64, f64, u32);
|
||||||
impl_try_truncate_into!(F64, f64, u64);
|
impl_try_truncate_into!(@wrapped F64, f64, u64);
|
||||||
|
|
||||||
macro_rules! impl_extend_into {
|
macro_rules! impl_extend_into {
|
||||||
($from:ident, $into:ident) => {
|
($from:ident, $into:ident) => {
|
||||||
|
@ -601,91 +609,119 @@ impl LittleEndianConvert for u8 {
|
||||||
|
|
||||||
impl LittleEndianConvert for i16 {
|
impl LittleEndianConvert for i16 {
|
||||||
fn into_little_endian(self, buffer: &mut [u8]) {
|
fn into_little_endian(self, buffer: &mut [u8]) {
|
||||||
LittleEndian::write_i16(buffer, self);
|
buffer.copy_from_slice(&self.to_le_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
||||||
|
let mut res = [0u8; 2];
|
||||||
buffer
|
buffer
|
||||||
.get(0..2)
|
.get(0..2)
|
||||||
.map(LittleEndian::read_i16)
|
.map(|s| {
|
||||||
|
res.copy_from_slice(s);
|
||||||
|
Self::from_le_bytes(res)
|
||||||
|
})
|
||||||
.ok_or_else(|| Error::InvalidLittleEndianBuffer)
|
.ok_or_else(|| Error::InvalidLittleEndianBuffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LittleEndianConvert for u16 {
|
impl LittleEndianConvert for u16 {
|
||||||
fn into_little_endian(self, buffer: &mut [u8]) {
|
fn into_little_endian(self, buffer: &mut [u8]) {
|
||||||
LittleEndian::write_u16(buffer, self);
|
buffer.copy_from_slice(&self.to_le_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
||||||
|
let mut res = [0u8; 2];
|
||||||
buffer
|
buffer
|
||||||
.get(0..2)
|
.get(0..2)
|
||||||
.map(LittleEndian::read_u16)
|
.map(|s| {
|
||||||
|
res.copy_from_slice(s);
|
||||||
|
Self::from_le_bytes(res)
|
||||||
|
})
|
||||||
.ok_or_else(|| Error::InvalidLittleEndianBuffer)
|
.ok_or_else(|| Error::InvalidLittleEndianBuffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LittleEndianConvert for i32 {
|
impl LittleEndianConvert for i32 {
|
||||||
fn into_little_endian(self, buffer: &mut [u8]) {
|
fn into_little_endian(self, buffer: &mut [u8]) {
|
||||||
LittleEndian::write_i32(buffer, self);
|
buffer.copy_from_slice(&self.to_le_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
||||||
|
let mut res = [0u8; 4];
|
||||||
buffer
|
buffer
|
||||||
.get(0..4)
|
.get(0..4)
|
||||||
.map(LittleEndian::read_i32)
|
.map(|s| {
|
||||||
|
res.copy_from_slice(s);
|
||||||
|
Self::from_le_bytes(res)
|
||||||
|
})
|
||||||
.ok_or_else(|| Error::InvalidLittleEndianBuffer)
|
.ok_or_else(|| Error::InvalidLittleEndianBuffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LittleEndianConvert for u32 {
|
impl LittleEndianConvert for u32 {
|
||||||
fn into_little_endian(self, buffer: &mut [u8]) {
|
fn into_little_endian(self, buffer: &mut [u8]) {
|
||||||
LittleEndian::write_u32(buffer, self);
|
buffer.copy_from_slice(&self.to_le_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
||||||
|
let mut res = [0u8; 4];
|
||||||
buffer
|
buffer
|
||||||
.get(0..4)
|
.get(0..4)
|
||||||
.map(LittleEndian::read_u32)
|
.map(|s| {
|
||||||
|
res.copy_from_slice(s);
|
||||||
|
Self::from_le_bytes(res)
|
||||||
|
})
|
||||||
.ok_or_else(|| Error::InvalidLittleEndianBuffer)
|
.ok_or_else(|| Error::InvalidLittleEndianBuffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LittleEndianConvert for i64 {
|
impl LittleEndianConvert for i64 {
|
||||||
fn into_little_endian(self, buffer: &mut [u8]) {
|
fn into_little_endian(self, buffer: &mut [u8]) {
|
||||||
LittleEndian::write_i64(buffer, self);
|
buffer.copy_from_slice(&self.to_le_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
||||||
|
let mut res = [0u8; 8];
|
||||||
buffer
|
buffer
|
||||||
.get(0..8)
|
.get(0..8)
|
||||||
.map(LittleEndian::read_i64)
|
.map(|s| {
|
||||||
|
res.copy_from_slice(s);
|
||||||
|
Self::from_le_bytes(res)
|
||||||
|
})
|
||||||
.ok_or_else(|| Error::InvalidLittleEndianBuffer)
|
.ok_or_else(|| Error::InvalidLittleEndianBuffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LittleEndianConvert for f32 {
|
impl LittleEndianConvert for f32 {
|
||||||
fn into_little_endian(self, buffer: &mut [u8]) {
|
fn into_little_endian(self, buffer: &mut [u8]) {
|
||||||
LittleEndian::write_f32(buffer, self);
|
buffer.copy_from_slice(&self.to_bits().to_le_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
||||||
|
let mut res = [0u8; 4];
|
||||||
buffer
|
buffer
|
||||||
.get(0..4)
|
.get(0..4)
|
||||||
.map(LittleEndian::read_f32)
|
.map(|s| {
|
||||||
|
res.copy_from_slice(s);
|
||||||
|
Self::from_bits(u32::from_le_bytes(res))
|
||||||
|
})
|
||||||
.ok_or_else(|| Error::InvalidLittleEndianBuffer)
|
.ok_or_else(|| Error::InvalidLittleEndianBuffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LittleEndianConvert for f64 {
|
impl LittleEndianConvert for f64 {
|
||||||
fn into_little_endian(self, buffer: &mut [u8]) {
|
fn into_little_endian(self, buffer: &mut [u8]) {
|
||||||
LittleEndian::write_f64(buffer, self);
|
buffer.copy_from_slice(&self.to_bits().to_le_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
||||||
|
let mut res = [0u8; 8];
|
||||||
buffer
|
buffer
|
||||||
.get(0..8)
|
.get(0..8)
|
||||||
.map(LittleEndian::read_f64)
|
.map(|s| {
|
||||||
|
res.copy_from_slice(s);
|
||||||
|
Self::from_bits(u64::from_le_bytes(res))
|
||||||
|
})
|
||||||
.ok_or_else(|| Error::InvalidLittleEndianBuffer)
|
.ok_or_else(|| Error::InvalidLittleEndianBuffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -801,15 +837,6 @@ impl_integer!(u32);
|
||||||
impl_integer!(i64);
|
impl_integer!(i64);
|
||||||
impl_integer!(u64);
|
impl_integer!(u64);
|
||||||
|
|
||||||
// Use std float functions in std environment.
|
|
||||||
// And libm's implementation in no_std
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
macro_rules! call_math {
|
|
||||||
($op:ident, $e:expr, $fXX:ident, $FXXExt:ident) => {
|
|
||||||
$fXX::$op($e)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "std"))]
|
|
||||||
macro_rules! call_math {
|
macro_rules! call_math {
|
||||||
($op:ident, $e:expr, $fXX:ident, $FXXExt:ident) => {
|
($op:ident, $e:expr, $fXX:ident, $FXXExt:ident) => {
|
||||||
::libm::$FXXExt::$op($e)
|
::libm::$FXXExt::$op($e)
|
15
test.sh
15
test.sh
|
@ -2,11 +2,18 @@
|
||||||
|
|
||||||
set -eux
|
set -eux
|
||||||
|
|
||||||
|
EXTRA_ARGS=""
|
||||||
|
|
||||||
|
if [ -n "${TARGET-}" ]; then
|
||||||
|
# Tests build in debug mode are prohibitively
|
||||||
|
# slow when ran under emulation so that
|
||||||
|
# e.g. Travis CI will hit timeouts.
|
||||||
|
EXTRA_ARGS="--release --target=${TARGET}"
|
||||||
|
export RUSTFLAGS="--cfg debug_assertions"
|
||||||
|
fi
|
||||||
|
|
||||||
cd $(dirname $0)
|
cd $(dirname $0)
|
||||||
|
|
||||||
# Make sure that the testsuite submodule is checked out.
|
time cargo test --all ${EXTRA_ARGS}
|
||||||
git submodule update --init wasmi/tests/spec/testsuite
|
|
||||||
|
|
||||||
time cargo test
|
|
||||||
|
|
||||||
cd -
|
cd -
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,6 +18,7 @@ fn spec_to_runtime_value(val: Value<u32, u64>) -> RuntimeValue {
|
||||||
Value::I64(v) => RuntimeValue::I64(v),
|
Value::I64(v) => RuntimeValue::I64(v),
|
||||||
Value::F32(v) => RuntimeValue::F32(v.into()),
|
Value::F32(v) => RuntimeValue::F32(v.into()),
|
||||||
Value::F64(v) => RuntimeValue::F64(v.into()),
|
Value::F64(v) => RuntimeValue::F64(v.into()),
|
||||||
|
Value::V128(_) => panic!("v128 is not supported"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
[package]
|
||||||
|
name = "wasmi-validation"
|
||||||
|
version = "0.2.0"
|
||||||
|
authors = ["Parity Technologies <admin@parity.io>"]
|
||||||
|
edition = "2018"
|
||||||
|
license = "MIT/Apache-2.0"
|
||||||
|
repository = "https://github.com/paritytech/wasmi"
|
||||||
|
description = "Wasm code validator"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
parity-wasm = { version = "0.40.1", default-features = false }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
assert_matches = "1.1"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["std"]
|
||||||
|
std = ["parity-wasm/std"]
|
||||||
|
core = []
|
|
@ -1,9 +1,8 @@
|
||||||
#[allow(unused_imports)]
|
use crate::Error;
|
||||||
use alloc::prelude::*;
|
use alloc::vec::Vec;
|
||||||
use parity_wasm::elements::{
|
use parity_wasm::elements::{
|
||||||
BlockType, FunctionType, GlobalType, MemoryType, TableType, ValueType,
|
BlockType, FunctionType, GlobalType, MemoryType, TableType, ValueType,
|
||||||
};
|
};
|
||||||
use validation::Error;
|
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct ModuleContext {
|
pub struct ModuleContext {
|
File diff suppressed because it is too large
Load Diff
|
@ -1,33 +1,46 @@
|
||||||
#[allow(unused_imports)]
|
// TODO: Uncomment
|
||||||
use alloc::prelude::*;
|
// #![warn(missing_docs)]
|
||||||
|
|
||||||
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate alloc;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
extern crate std as alloc;
|
||||||
|
|
||||||
|
pub mod stack;
|
||||||
|
|
||||||
|
/// Index of default linear memory.
|
||||||
|
pub const DEFAULT_MEMORY_INDEX: u32 = 0;
|
||||||
|
/// Index of default table.
|
||||||
|
pub const DEFAULT_TABLE_INDEX: u32 = 0;
|
||||||
|
|
||||||
|
/// Maximal number of pages that a wasm instance supports.
|
||||||
|
pub const LINEAR_MEMORY_MAX_PAGES: u32 = 65536;
|
||||||
|
|
||||||
|
use alloc::{string::String, vec::Vec};
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use std::error;
|
use std::error;
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
|
||||||
use hashbrown::HashSet;
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use self::context::ModuleContextBuilder;
|
use self::context::ModuleContextBuilder;
|
||||||
use self::func::FunctionReader;
|
|
||||||
use common::stack;
|
|
||||||
use isa;
|
|
||||||
use memory_units::Pages;
|
|
||||||
use parity_wasm::elements::{
|
use parity_wasm::elements::{
|
||||||
BlockType, External, GlobalEntry, GlobalType, InitExpr, Instruction, Internal, MemoryType,
|
BlockType, ExportEntry, External, FuncBody, GlobalEntry, GlobalType, InitExpr, Instruction,
|
||||||
Module, ResizableLimits, TableType, Type, ValueType,
|
Internal, MemoryType, Module, ResizableLimits, TableType, Type, ValueType,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod context;
|
pub mod context;
|
||||||
mod func;
|
pub mod func;
|
||||||
mod util;
|
pub mod util;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
// TODO: Consider using a type other than String, because
|
||||||
|
// of formatting machinary is not welcomed in substrate runtimes.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Error(String);
|
pub struct Error(pub String);
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
@ -48,137 +61,77 @@ impl From<stack::Error> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
pub trait Validator {
|
||||||
pub struct ValidatedModule {
|
type Output;
|
||||||
pub code_map: Vec<isa::Instructions>,
|
type FuncValidator: FuncValidator;
|
||||||
pub module: Module,
|
fn new(module: &Module) -> Self;
|
||||||
|
fn on_function_validated(
|
||||||
|
&mut self,
|
||||||
|
index: u32,
|
||||||
|
output: <<Self as Validator>::FuncValidator as FuncValidator>::Output,
|
||||||
|
);
|
||||||
|
fn finish(self) -> Self::Output;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ::core::ops::Deref for ValidatedModule {
|
pub trait FuncValidator {
|
||||||
type Target = Module;
|
type Output;
|
||||||
fn deref(&self) -> &Module {
|
fn new(ctx: &func::FunctionValidationContext, body: &FuncBody) -> Self;
|
||||||
&self.module
|
fn next_instruction(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut func::FunctionValidationContext,
|
||||||
|
instruction: &Instruction,
|
||||||
|
) -> Result<(), Error>;
|
||||||
|
fn finish(self) -> Self::Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A module validator that just validates modules and produces no result.
|
||||||
|
pub struct PlainValidator;
|
||||||
|
|
||||||
|
impl Validator for PlainValidator {
|
||||||
|
type Output = ();
|
||||||
|
type FuncValidator = PlainFuncValidator;
|
||||||
|
fn new(_module: &Module) -> PlainValidator {
|
||||||
|
PlainValidator
|
||||||
|
}
|
||||||
|
fn on_function_validated(
|
||||||
|
&mut self,
|
||||||
|
_index: u32,
|
||||||
|
_output: <<Self as Validator>::FuncValidator as FuncValidator>::Output,
|
||||||
|
) -> () {
|
||||||
|
()
|
||||||
|
}
|
||||||
|
fn finish(self) -> () {
|
||||||
|
()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deny_floating_point(module: &Module) -> Result<(), Error> {
|
/// A function validator that just validates modules and produces no result.
|
||||||
if let Some(code) = module.code_section() {
|
pub struct PlainFuncValidator;
|
||||||
for op in code.bodies().iter().flat_map(|body| body.code().elements()) {
|
|
||||||
use parity_wasm::elements::Instruction::*;
|
|
||||||
|
|
||||||
macro_rules! match_eq {
|
impl FuncValidator for PlainFuncValidator {
|
||||||
($pattern:pat) => {
|
type Output = ();
|
||||||
|val| if let $pattern = *val { true } else { false }
|
|
||||||
};
|
fn new(_ctx: &func::FunctionValidationContext, _body: &FuncBody) -> PlainFuncValidator {
|
||||||
|
PlainFuncValidator
|
||||||
}
|
}
|
||||||
|
|
||||||
const DENIED: &[fn(&Instruction) -> bool] = &[
|
fn next_instruction(
|
||||||
match_eq!(F32Load(_, _)),
|
&mut self,
|
||||||
match_eq!(F64Load(_, _)),
|
ctx: &mut func::FunctionValidationContext,
|
||||||
match_eq!(F32Store(_, _)),
|
instruction: &Instruction,
|
||||||
match_eq!(F64Store(_, _)),
|
) -> Result<(), Error> {
|
||||||
match_eq!(F32Const(_)),
|
ctx.step(instruction)
|
||||||
match_eq!(F64Const(_)),
|
|
||||||
match_eq!(F32Eq),
|
|
||||||
match_eq!(F32Ne),
|
|
||||||
match_eq!(F32Lt),
|
|
||||||
match_eq!(F32Gt),
|
|
||||||
match_eq!(F32Le),
|
|
||||||
match_eq!(F32Ge),
|
|
||||||
match_eq!(F64Eq),
|
|
||||||
match_eq!(F64Ne),
|
|
||||||
match_eq!(F64Lt),
|
|
||||||
match_eq!(F64Gt),
|
|
||||||
match_eq!(F64Le),
|
|
||||||
match_eq!(F64Ge),
|
|
||||||
match_eq!(F32Abs),
|
|
||||||
match_eq!(F32Neg),
|
|
||||||
match_eq!(F32Ceil),
|
|
||||||
match_eq!(F32Floor),
|
|
||||||
match_eq!(F32Trunc),
|
|
||||||
match_eq!(F32Nearest),
|
|
||||||
match_eq!(F32Sqrt),
|
|
||||||
match_eq!(F32Add),
|
|
||||||
match_eq!(F32Sub),
|
|
||||||
match_eq!(F32Mul),
|
|
||||||
match_eq!(F32Div),
|
|
||||||
match_eq!(F32Min),
|
|
||||||
match_eq!(F32Max),
|
|
||||||
match_eq!(F32Copysign),
|
|
||||||
match_eq!(F64Abs),
|
|
||||||
match_eq!(F64Neg),
|
|
||||||
match_eq!(F64Ceil),
|
|
||||||
match_eq!(F64Floor),
|
|
||||||
match_eq!(F64Trunc),
|
|
||||||
match_eq!(F64Nearest),
|
|
||||||
match_eq!(F64Sqrt),
|
|
||||||
match_eq!(F64Add),
|
|
||||||
match_eq!(F64Sub),
|
|
||||||
match_eq!(F64Mul),
|
|
||||||
match_eq!(F64Div),
|
|
||||||
match_eq!(F64Min),
|
|
||||||
match_eq!(F64Max),
|
|
||||||
match_eq!(F64Copysign),
|
|
||||||
match_eq!(F32ConvertSI32),
|
|
||||||
match_eq!(F32ConvertUI32),
|
|
||||||
match_eq!(F32ConvertSI64),
|
|
||||||
match_eq!(F32ConvertUI64),
|
|
||||||
match_eq!(F32DemoteF64),
|
|
||||||
match_eq!(F64ConvertSI32),
|
|
||||||
match_eq!(F64ConvertUI32),
|
|
||||||
match_eq!(F64ConvertSI64),
|
|
||||||
match_eq!(F64ConvertUI64),
|
|
||||||
match_eq!(F64PromoteF32),
|
|
||||||
match_eq!(F32ReinterpretI32),
|
|
||||||
match_eq!(F64ReinterpretI64),
|
|
||||||
match_eq!(I32TruncSF32),
|
|
||||||
match_eq!(I32TruncUF32),
|
|
||||||
match_eq!(I32TruncSF64),
|
|
||||||
match_eq!(I32TruncUF64),
|
|
||||||
match_eq!(I64TruncSF32),
|
|
||||||
match_eq!(I64TruncUF32),
|
|
||||||
match_eq!(I64TruncSF64),
|
|
||||||
match_eq!(I64TruncUF64),
|
|
||||||
match_eq!(I32ReinterpretF32),
|
|
||||||
match_eq!(I64ReinterpretF64),
|
|
||||||
];
|
|
||||||
|
|
||||||
if DENIED.iter().any(|is_denied| is_denied(op)) {
|
|
||||||
return Err(Error(format!("Floating point operation denied: {:?}", op)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (Some(sec), Some(types)) = (module.function_section(), module.type_section()) {
|
fn finish(self) -> () {
|
||||||
use parity_wasm::elements::{Type, ValueType};
|
()
|
||||||
|
|
||||||
let types = types.types();
|
|
||||||
|
|
||||||
for sig in sec.entries() {
|
|
||||||
if let Some(typ) = types.get(sig.type_ref() as usize) {
|
|
||||||
match *typ {
|
|
||||||
Type::Function(ref func) => {
|
|
||||||
if func
|
|
||||||
.params()
|
|
||||||
.iter()
|
|
||||||
.chain(func.return_type().as_ref())
|
|
||||||
.any(|&typ| typ == ValueType::F32 || typ == ValueType::F64)
|
|
||||||
{
|
|
||||||
return Err(Error(format!("Use of floating point types denied")));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
|
pub fn validate_module<V: Validator>(module: &Module) -> Result<V::Output, Error> {
|
||||||
let mut context_builder = ModuleContextBuilder::new();
|
let mut context_builder = ModuleContextBuilder::new();
|
||||||
let mut imported_globals = Vec::new();
|
let mut imported_globals = Vec::new();
|
||||||
let mut code_map = Vec::new();
|
let mut validation = V::new(&module);
|
||||||
|
|
||||||
// Copy types from module as is.
|
// Copy types from module as is.
|
||||||
context_builder.set_types(
|
context_builder.set_types(
|
||||||
|
@ -265,15 +218,15 @@ pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
|
||||||
.bodies()
|
.bodies()
|
||||||
.get(index as usize)
|
.get(index as usize)
|
||||||
.ok_or(Error(format!("Missing body for function {}", index)))?;
|
.ok_or(Error(format!("Missing body for function {}", index)))?;
|
||||||
let code =
|
|
||||||
FunctionReader::read_function(&context, function, function_body).map_err(|e| {
|
let output = func::drive::<V::FuncValidator>(&context, function, function_body)
|
||||||
let Error(ref msg) = e;
|
.map_err(|Error(ref msg)| {
|
||||||
Error(format!(
|
Error(format!(
|
||||||
"Function #{} reading/validation error: {}",
|
"Function #{} reading/validation error: {}",
|
||||||
index, msg
|
index, msg
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
code_map.push(code);
|
validation.on_function_validated(index as u32, output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,13 +242,21 @@ pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
|
||||||
|
|
||||||
// validate export section
|
// validate export section
|
||||||
if let Some(export_section) = module.export_section() {
|
if let Some(export_section) = module.export_section() {
|
||||||
let mut export_names = HashSet::with_capacity(export_section.entries().len());
|
let mut export_names = export_section
|
||||||
for export in export_section.entries() {
|
.entries()
|
||||||
// HashSet::insert returns false if item already in set.
|
.iter()
|
||||||
let duplicate = export_names.insert(export.field()) == false;
|
.map(ExportEntry::field)
|
||||||
if duplicate {
|
.collect::<Vec<_>>();
|
||||||
return Err(Error(format!("duplicate export {}", export.field())));
|
|
||||||
|
export_names.sort_unstable();
|
||||||
|
|
||||||
|
for (fst, snd) in export_names.iter().zip(export_names.iter().skip(1)) {
|
||||||
|
if fst == snd {
|
||||||
|
return Err(Error(format!("duplicate export {}", fst)));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for export in export_section.entries() {
|
||||||
match *export.internal() {
|
match *export.internal() {
|
||||||
Internal::Function(function_index) => {
|
Internal::Function(function_index) => {
|
||||||
context.require_function(function_index)?;
|
context.require_function(function_index)?;
|
||||||
|
@ -358,7 +319,11 @@ pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
|
||||||
if let Some(data_section) = module.data_section() {
|
if let Some(data_section) = module.data_section() {
|
||||||
for data_segment in data_section.entries() {
|
for data_segment in data_section.entries() {
|
||||||
context.require_memory(data_segment.index())?;
|
context.require_memory(data_segment.index())?;
|
||||||
let init_ty = expr_const_type(data_segment.offset(), context.globals())?;
|
let offset = data_segment
|
||||||
|
.offset()
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| Error("passive memory segments are not supported".into()))?;
|
||||||
|
let init_ty = expr_const_type(&offset, context.globals())?;
|
||||||
if init_ty != ValueType::I32 {
|
if init_ty != ValueType::I32 {
|
||||||
return Err(Error("segment offset should return I32".into()));
|
return Err(Error("segment offset should return I32".into()));
|
||||||
}
|
}
|
||||||
|
@ -369,8 +334,11 @@ pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
|
||||||
if let Some(element_section) = module.elements_section() {
|
if let Some(element_section) = module.elements_section() {
|
||||||
for element_segment in element_section.entries() {
|
for element_segment in element_section.entries() {
|
||||||
context.require_table(element_segment.index())?;
|
context.require_table(element_segment.index())?;
|
||||||
|
let offset = element_segment
|
||||||
let init_ty = expr_const_type(element_segment.offset(), context.globals())?;
|
.offset()
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| Error("passive element segments are not supported".into()))?;
|
||||||
|
let init_ty = expr_const_type(&offset, context.globals())?;
|
||||||
if init_ty != ValueType::I32 {
|
if init_ty != ValueType::I32 {
|
||||||
return Err(Error("segment offset should return I32".into()));
|
return Err(Error("segment offset should return I32".into()));
|
||||||
}
|
}
|
||||||
|
@ -381,7 +349,7 @@ pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ValidatedModule { module, code_map })
|
Ok(validation.finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_limits(limits: &ResizableLimits) -> Result<(), Error> {
|
fn validate_limits(limits: &ResizableLimits) -> Result<(), Error> {
|
||||||
|
@ -398,9 +366,34 @@ fn validate_limits(limits: &ResizableLimits) -> Result<(), Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_memory_type(memory_type: &MemoryType) -> Result<(), Error> {
|
fn validate_memory_type(memory_type: &MemoryType) -> Result<(), Error> {
|
||||||
let initial: Pages = Pages(memory_type.limits().initial() as usize);
|
let initial = memory_type.limits().initial();
|
||||||
let maximum: Option<Pages> = memory_type.limits().maximum().map(|m| Pages(m as usize));
|
let maximum: Option<u32> = memory_type.limits().maximum();
|
||||||
::memory::validate_memory(initial, maximum).map_err(Error)
|
validate_memory(initial, maximum).map_err(Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate_memory(initial: u32, maximum: Option<u32>) -> Result<(), String> {
|
||||||
|
if initial > LINEAR_MEMORY_MAX_PAGES {
|
||||||
|
return Err(format!(
|
||||||
|
"initial memory size must be at most {} pages",
|
||||||
|
LINEAR_MEMORY_MAX_PAGES
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if let Some(maximum) = maximum {
|
||||||
|
if initial > maximum {
|
||||||
|
return Err(format!(
|
||||||
|
"maximum limit {} is less than minimum {}",
|
||||||
|
maximum, initial,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if maximum > LINEAR_MEMORY_MAX_PAGES {
|
||||||
|
return Err(format!(
|
||||||
|
"maximum memory size must be at most {} pages",
|
||||||
|
LINEAR_MEMORY_MAX_PAGES
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_table_type(table_type: &TableType) -> Result<(), Error> {
|
fn validate_table_type(table_type: &TableType) -> Result<(), Error> {
|
|
@ -1,5 +1,4 @@
|
||||||
#[allow(unused_imports)]
|
use alloc::{string::String, vec::Vec};
|
||||||
use alloc::prelude::*;
|
|
||||||
|
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
|
@ -0,0 +1,277 @@
|
||||||
|
use crate::{Error, PlainValidator};
|
||||||
|
use parity_wasm::{
|
||||||
|
builder::module,
|
||||||
|
elements::{
|
||||||
|
BlockType, External, GlobalEntry, GlobalType, ImportEntry, InitExpr, Instruction,
|
||||||
|
Instructions, MemoryType, Module, TableType, ValueType,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
fn validate_module(module: &Module) -> Result<(), Error> {
|
||||||
|
super::validate_module::<PlainValidator>(module)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_is_valid() {
|
||||||
|
let module = module().build();
|
||||||
|
assert!(validate_module(&module).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn limits() {
|
||||||
|
let test_cases = vec![
|
||||||
|
// min > max
|
||||||
|
(10, Some(9), false),
|
||||||
|
// min = max
|
||||||
|
(10, Some(10), true),
|
||||||
|
// table/memory is always valid without max
|
||||||
|
(10, None, true),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (min, max, is_valid) in test_cases {
|
||||||
|
// defined table
|
||||||
|
let m = module().table().with_min(min).with_max(max).build().build();
|
||||||
|
assert_eq!(validate_module(&m).is_ok(), is_valid);
|
||||||
|
|
||||||
|
// imported table
|
||||||
|
let m = module()
|
||||||
|
.with_import(ImportEntry::new(
|
||||||
|
"core".into(),
|
||||||
|
"table".into(),
|
||||||
|
External::Table(TableType::new(min, max)),
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
assert_eq!(validate_module(&m).is_ok(), is_valid);
|
||||||
|
|
||||||
|
// defined memory
|
||||||
|
let m = module()
|
||||||
|
.memory()
|
||||||
|
.with_min(min)
|
||||||
|
.with_max(max)
|
||||||
|
.build()
|
||||||
|
.build();
|
||||||
|
assert_eq!(validate_module(&m).is_ok(), is_valid);
|
||||||
|
|
||||||
|
// imported table
|
||||||
|
let m = module()
|
||||||
|
.with_import(ImportEntry::new(
|
||||||
|
"core".into(),
|
||||||
|
"memory".into(),
|
||||||
|
External::Memory(MemoryType::new(min, max)),
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
assert_eq!(validate_module(&m).is_ok(), is_valid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn global_init_const() {
|
||||||
|
let m = module()
|
||||||
|
.with_global(GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, true),
|
||||||
|
InitExpr::new(vec![Instruction::I32Const(42), Instruction::End]),
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_ok());
|
||||||
|
|
||||||
|
// init expr type differs from declared global type
|
||||||
|
let m = module()
|
||||||
|
.with_global(GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I64, true),
|
||||||
|
InitExpr::new(vec![Instruction::I32Const(42), Instruction::End]),
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn global_init_global() {
|
||||||
|
let m = module()
|
||||||
|
.with_import(ImportEntry::new(
|
||||||
|
"env".into(),
|
||||||
|
"ext_global".into(),
|
||||||
|
External::Global(GlobalType::new(ValueType::I32, false)),
|
||||||
|
))
|
||||||
|
.with_global(GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, true),
|
||||||
|
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_ok());
|
||||||
|
|
||||||
|
// get_global can reference only previously defined globals
|
||||||
|
let m = module()
|
||||||
|
.with_global(GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, true),
|
||||||
|
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_err());
|
||||||
|
|
||||||
|
// get_global can reference only const globals
|
||||||
|
let m = module()
|
||||||
|
.with_import(ImportEntry::new(
|
||||||
|
"env".into(),
|
||||||
|
"ext_global".into(),
|
||||||
|
External::Global(GlobalType::new(ValueType::I32, true)),
|
||||||
|
))
|
||||||
|
.with_global(GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, true),
|
||||||
|
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_err());
|
||||||
|
|
||||||
|
// get_global in init_expr can only refer to imported globals.
|
||||||
|
let m = module()
|
||||||
|
.with_global(GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, false),
|
||||||
|
InitExpr::new(vec![Instruction::I32Const(0), Instruction::End]),
|
||||||
|
))
|
||||||
|
.with_global(GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, true),
|
||||||
|
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn global_init_misc() {
|
||||||
|
// without delimiting End opcode
|
||||||
|
let m = module()
|
||||||
|
.with_global(GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, true),
|
||||||
|
InitExpr::new(vec![Instruction::I32Const(42)]),
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_err());
|
||||||
|
|
||||||
|
// empty init expr
|
||||||
|
let m = module()
|
||||||
|
.with_global(GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, true),
|
||||||
|
InitExpr::new(vec![Instruction::End]),
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_err());
|
||||||
|
|
||||||
|
// not an constant opcode used
|
||||||
|
let m = module()
|
||||||
|
.with_global(GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, true),
|
||||||
|
InitExpr::new(vec![Instruction::Unreachable, Instruction::End]),
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_limits_validity() {
|
||||||
|
// module cannot contain more than 1 memory atm.
|
||||||
|
let m = module()
|
||||||
|
.with_import(ImportEntry::new(
|
||||||
|
"core".into(),
|
||||||
|
"memory".into(),
|
||||||
|
External::Memory(MemoryType::new(10, None)),
|
||||||
|
))
|
||||||
|
.memory()
|
||||||
|
.with_min(10)
|
||||||
|
.build()
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_err());
|
||||||
|
|
||||||
|
// module cannot contain more than 1 table atm.
|
||||||
|
let m = module()
|
||||||
|
.with_import(ImportEntry::new(
|
||||||
|
"core".into(),
|
||||||
|
"table".into(),
|
||||||
|
External::Table(TableType::new(10, None)),
|
||||||
|
))
|
||||||
|
.table()
|
||||||
|
.with_min(10)
|
||||||
|
.build()
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn funcs() {
|
||||||
|
// recursive function calls is legal.
|
||||||
|
let m = module()
|
||||||
|
.function()
|
||||||
|
.signature()
|
||||||
|
.return_type()
|
||||||
|
.i32()
|
||||||
|
.build()
|
||||||
|
.body()
|
||||||
|
.with_instructions(Instructions::new(vec![
|
||||||
|
Instruction::Call(1),
|
||||||
|
Instruction::End,
|
||||||
|
]))
|
||||||
|
.build()
|
||||||
|
.build()
|
||||||
|
.function()
|
||||||
|
.signature()
|
||||||
|
.return_type()
|
||||||
|
.i32()
|
||||||
|
.build()
|
||||||
|
.body()
|
||||||
|
.with_instructions(Instructions::new(vec![
|
||||||
|
Instruction::Call(0),
|
||||||
|
Instruction::End,
|
||||||
|
]))
|
||||||
|
.build()
|
||||||
|
.build()
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn globals() {
|
||||||
|
// import immutable global is legal.
|
||||||
|
let m = module()
|
||||||
|
.with_import(ImportEntry::new(
|
||||||
|
"env".into(),
|
||||||
|
"ext_global".into(),
|
||||||
|
External::Global(GlobalType::new(ValueType::I32, false)),
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_ok());
|
||||||
|
|
||||||
|
// import mutable global is invalid.
|
||||||
|
let m = module()
|
||||||
|
.with_import(ImportEntry::new(
|
||||||
|
"env".into(),
|
||||||
|
"ext_global".into(),
|
||||||
|
External::Global(GlobalType::new(ValueType::I32, true)),
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn if_else_with_return_type_validation() {
|
||||||
|
let m = module()
|
||||||
|
.function()
|
||||||
|
.signature()
|
||||||
|
.build()
|
||||||
|
.body()
|
||||||
|
.with_instructions(Instructions::new(vec![
|
||||||
|
Instruction::I32Const(1),
|
||||||
|
Instruction::If(BlockType::NoResult),
|
||||||
|
Instruction::I32Const(1),
|
||||||
|
Instruction::If(BlockType::Value(ValueType::I32)),
|
||||||
|
Instruction::I32Const(1),
|
||||||
|
Instruction::Else,
|
||||||
|
Instruction::I32Const(2),
|
||||||
|
Instruction::End,
|
||||||
|
Instruction::Drop,
|
||||||
|
Instruction::End,
|
||||||
|
Instruction::End,
|
||||||
|
]))
|
||||||
|
.build()
|
||||||
|
.build()
|
||||||
|
.build();
|
||||||
|
validate_module(&m).unwrap();
|
||||||
|
}
|
|
@ -1,7 +1,9 @@
|
||||||
#[allow(unused_imports)]
|
use crate::Error;
|
||||||
use alloc::prelude::*;
|
use alloc::string::String;
|
||||||
use parity_wasm::elements::{Local, ValueType};
|
use parity_wasm::elements::{Local, ValueType};
|
||||||
use validation::Error;
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use assert_matches::assert_matches;
|
||||||
|
|
||||||
/// Locals are the concatenation of a slice of function parameters
|
/// Locals are the concatenation of a slice of function parameters
|
||||||
/// with function declared local variables.
|
/// with function declared local variables.
|
|
@ -1,34 +0,0 @@
|
||||||
[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,8 +0,0 @@
|
||||||
pub mod stack;
|
|
||||||
|
|
||||||
/// Index of default linear memory.
|
|
||||||
pub const DEFAULT_MEMORY_INDEX: u32 = 0;
|
|
||||||
/// Index of default table.
|
|
||||||
pub const DEFAULT_TABLE_INDEX: u32 = 0;
|
|
||||||
|
|
||||||
// TODO: Move BlockFrame under validation.
|
|
|
@ -1,73 +0,0 @@
|
||||||
//! 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()))
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,152 +0,0 @@
|
||||||
// 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")"#,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
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);
|
|
|
@ -1,503 +0,0 @@
|
||||||
#![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(())
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
//! Official spec testsuite.
|
|
||||||
|
|
||||||
extern crate wabt;
|
|
||||||
extern crate wasmi;
|
|
||||||
|
|
||||||
mod spec;
|
|
Loading…
Reference in New Issue