Compare commits
76 Commits
lift-limit
...
master
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 | |
Elichai Turkel | 23b054c0e5 | |
Leonardo Yvens | ad14d82bce | |
Arkadiy Paronyan | 073e4e7f1f | |
Sergey Pepyakin | 7740f6b690 | |
Sergei Pepyakin | c3b21b337d | |
Sergei Pepyakin | d52ba8849a | |
Arkadiy Paronyan | e047f508fa | |
Jef | 617be0198d | |
Jef | 899cc32e45 | |
Ivan Enderlin | da558c7ce7 | |
Jef | c7f9196df6 | |
Sergey Pepyakin | 7191998216 | |
Sergey Pepyakin | 15e9461bae | |
Jef | e11ba15373 | |
Eric Findlay | 7b4c648acb | |
Jef | c877d64508 | |
Wei Tang | 1c04be64f8 | |
Julius Rakow | 20154c5e24 | |
Sergey Pepyakin | 2f7505d120 | |
Jef | 3854ecdad6 | |
Sergey Pepyakin | ad4236263a | |
Will Glynn | 7509477a61 | |
Sergey Pepyakin | 9170303aad | |
Sergey Pepyakin | 36582c32b6 | |
Sergey Pepyakin | 438eab9ada | |
Tobias Bucher | 167e4845ef | |
Guanqun Lu | 0409913a26 | |
Guanqun Lu | 929ac564a5 | |
Guanqun Lu | 8ca6b4b860 | |
Sergey Pepyakin | 0a34f1d0f6 | |
Arkadiy Paronyan | 118396851a | |
Guanqun Lu | 5c86c1c753 | |
Guanqun Lu | 43b8d52bca | |
Arkadiy Paronyan | 9ed95e49c1 | |
Wei Tang | a605175abe | |
Sergey Pepyakin | df0e3ddd46 | |
Sergey Pepyakin | dc5052aadb | |
Sergey Pepyakin | 5d99077e17 | |
Sergey Pepyakin | f6657bace4 | |
Sergey Pepyakin | 3a96a8399f | |
Wei Tang | f91dc92119 | |
Sergey Pepyakin | 2fb793c8b8 | |
Leonardo Yvens | 9db7896e48 | |
Leonardo Yvens | 75406dd8ff | |
Sergey Pepyakin | 94b797de44 | |
Sergey Pepyakin | f305b3cd1f |
|
@ -0,0 +1,2 @@
|
|||
[target.armv7-unknown-linux-gnueabihf]
|
||||
linker = "arm-linux-gnueabihf-gcc"
|
|
@ -1,11 +1,9 @@
|
|||
root = true
|
||||
[*]
|
||||
indent_style=tab
|
||||
indent_size=tab
|
||||
tab_width=4
|
||||
indent_style=space
|
||||
indent_size = 4
|
||||
end_of_line=lf
|
||||
charset=utf-8
|
||||
trim_trailing_whitespace=true
|
||||
max_line_length=120
|
||||
insert_final_newline=true
|
||||
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
spec/target
|
||||
.idea
|
||||
|
|
57
.travis.yml
57
.travis.yml
|
@ -1,34 +1,36 @@
|
|||
dist: trusty
|
||||
sudo: required
|
||||
dist: xenial
|
||||
|
||||
language:
|
||||
- rust
|
||||
- cpp
|
||||
rust:
|
||||
- nightly
|
||||
- stable
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- gcc-6
|
||||
- g++-6
|
||||
- cmake
|
||||
env:
|
||||
- CC=/usr/bin/gcc-6 CXX=/usr/bin/g++-6
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- rust: nightly
|
||||
- rust: stable
|
||||
- rust: stable
|
||||
env: TARGET=armv7-unknown-linux-gnueabihf
|
||||
|
||||
install:
|
||||
# Install `cargo-deadlinks` unless it is currently installed.
|
||||
- command -v cargo-deadlinks &> /dev/null || cargo install cargo-deadlinks
|
||||
- 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
|
||||
- sudo apt-get install --yes cmake
|
||||
|
||||
script:
|
||||
# Make sure fuzz targets are not broken.
|
||||
- cargo fmt --all -- --check
|
||||
# Make sure nightly targets are not broken.
|
||||
- if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo check --tests --manifest-path=fuzz/Cargo.toml; fi
|
||||
- if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo check --benches --manifest-path=benches/Cargo.toml; fi
|
||||
# Run tests in the release mode since some of the tests from the spec
|
||||
# perfrom reaaally bad in the debug mode.
|
||||
- CARGOFLAGS=--release ./test.sh
|
||||
# Make sure `no_std` version checks.
|
||||
- if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo +nightly check --no-default-features --features core; fi
|
||||
# Check that `vec_memory` feature works.
|
||||
- cargo check --features vec_memory
|
||||
- travis_wait 60 ./test.sh
|
||||
- ./doc.sh
|
||||
|
||||
after_success: |
|
||||
# Build documentation and deploy it to github pages.
|
||||
[ $TRAVIS_BRANCH = master ] &&
|
||||
|
@ -37,7 +39,18 @@ after_success: |
|
|||
sudo pip install ghp-import &&
|
||||
ghp-import -n target/doc &&
|
||||
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:
|
||||
# Travis can't cache files that are not readable by "others"
|
||||
- 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
|
||||
|
|
41
Cargo.toml
41
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "wasmi"
|
||||
version = "0.2.0"
|
||||
version = "0.5.1"
|
||||
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"
|
||||
|
@ -11,11 +11,42 @@ keywords = ["wasm", "webassembly", "bytecode", "interpreter"]
|
|||
exclude = [ "/res/*", "/tests/*", "/fuzz/*", "/benches/*" ]
|
||||
|
||||
[dependencies]
|
||||
parity-wasm = "0.27"
|
||||
byteorder = "1.0"
|
||||
wasmi-validation = { version = "0.2", path = "validation", default-features = false }
|
||||
parity-wasm = { version = "0.40.1", default-features = false }
|
||||
memory_units = "0.3.0"
|
||||
nan-preserving-float = "0.1.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"
|
||||
wabt = "0.3"
|
||||
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"]
|
||||
|
|
36
README.md
36
README.md
|
@ -3,11 +3,41 @@
|
|||
|
||||
# `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.
|
||||
|
||||
# Build & Test
|
||||
|
||||
As `wasmi` contains a git submodule, you need to use `--recursive` for cloning or to checkout the submodule explicitly, otherwise the testing would fail.
|
||||
|
||||
```
|
||||
git clone https://github.com/paritytech/wasmi.git --recursive
|
||||
cd wasmi
|
||||
cargo build
|
||||
cargo test
|
||||
```
|
||||
|
||||
# `no_std` support
|
||||
|
||||
This crate supports `no_std` environments.
|
||||
Enable the `core` feature and disable default features:
|
||||
```toml
|
||||
[dependencies]
|
||||
wasmi = {
|
||||
version = "*",
|
||||
default-features = false,
|
||||
features = "core"
|
||||
}
|
||||
```
|
||||
|
||||
The `core` feature requires the `core` and `alloc` libraries and a nightly compiler.
|
||||
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).
|
||||
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
|
||||
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
/target
|
||||
*.trace
|
||||
|
||||
|
|
|
@ -6,3 +6,7 @@ authors = ["Sergey Pepyakin <s.pepyakin@gmail.com>"]
|
|||
[dependencies]
|
||||
wasmi = { path = ".." }
|
||||
assert_matches = "1.2"
|
||||
wabt = "0.9"
|
||||
|
||||
[profile.bench]
|
||||
debug = true
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use std::env;
|
||||
use std::process;
|
||||
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=./wasm-kernel/");
|
||||
|
||||
|
@ -23,9 +22,9 @@ fn main() {
|
|||
if !output.status.success() {
|
||||
let msg = format!(
|
||||
"status: {status}\nstdout: {stdout}\nstderr: {stderr}\n",
|
||||
status=output.status,
|
||||
stdout=String::from_utf8_lossy(&output.stdout),
|
||||
stderr=String::from_utf8_lossy(&output.stderr),
|
||||
status = output.status,
|
||||
stdout = String::from_utf8_lossy(&output.stdout),
|
||||
stderr = String::from_utf8_lossy(&output.stderr),
|
||||
);
|
||||
panic!("{}", msg);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ extern crate test;
|
|||
extern crate wasmi;
|
||||
#[macro_use]
|
||||
extern crate assert_matches;
|
||||
extern crate wabt;
|
||||
|
||||
use std::error;
|
||||
use std::fs::File;
|
||||
|
@ -12,7 +13,7 @@ use wasmi::{ImportsBuilder, Module, ModuleInstance, NopExternals, RuntimeValue};
|
|||
use test::Bencher;
|
||||
|
||||
// Load a module from a file.
|
||||
fn load_from_file(filename: &str) -> Result<Module, Box<error::Error>> {
|
||||
fn load_from_file(filename: &str) -> Result<Module, Box<dyn error::Error>> {
|
||||
use std::io::prelude::*;
|
||||
let mut file = File::open(filename)?;
|
||||
let mut buf = Vec::new();
|
||||
|
@ -20,6 +21,9 @@ fn load_from_file(filename: &str) -> Result<Module, Box<error::Error>> {
|
|||
Ok(Module::from_buffer(buf)?)
|
||||
}
|
||||
|
||||
const REVCOMP_INPUT: &'static [u8] = include_bytes!("./revcomp-input.txt");
|
||||
const REVCOMP_OUTPUT: &'static [u8] = include_bytes!("./revcomp-output.txt");
|
||||
|
||||
#[bench]
|
||||
fn bench_tiny_keccak(b: &mut Bencher) {
|
||||
let wasm_kernel = load_from_file(
|
||||
|
@ -41,3 +45,234 @@ fn bench_tiny_keccak(b: &mut Bencher) {
|
|||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_rev_comp(b: &mut Bencher) {
|
||||
let wasm_kernel = load_from_file(
|
||||
"./wasm-kernel/target/wasm32-unknown-unknown/release/wasm_kernel.wasm",
|
||||
).expect("failed to load wasm_kernel. Is `build.rs` broken?");
|
||||
|
||||
let instance = ModuleInstance::new(&wasm_kernel, &ImportsBuilder::default())
|
||||
.expect("failed to instantiate wasm module")
|
||||
.assert_no_start();
|
||||
|
||||
// Allocate buffers for the input and output.
|
||||
let test_data_ptr: RuntimeValue = {
|
||||
let input_size = RuntimeValue::I32(REVCOMP_INPUT.len() as i32);
|
||||
assert_matches!(
|
||||
instance.invoke_export("prepare_rev_complement", &[input_size], &mut NopExternals),
|
||||
Ok(Some(v @ RuntimeValue::I32(_))) => v,
|
||||
"",
|
||||
)
|
||||
};
|
||||
|
||||
// Get the pointer to the input buffer.
|
||||
let input_data_mem_offset = assert_matches!(
|
||||
instance.invoke_export("rev_complement_input_ptr", &[test_data_ptr], &mut NopExternals),
|
||||
Ok(Some(RuntimeValue::I32(v))) => v as u32,
|
||||
"",
|
||||
);
|
||||
|
||||
// Copy test data inside the wasm memory.
|
||||
let memory = instance.export_by_name("memory")
|
||||
.expect("Expected export with a name 'memory'")
|
||||
.as_memory()
|
||||
.expect("'memory' should be a memory instance")
|
||||
.clone();
|
||||
memory
|
||||
.set(input_data_mem_offset, REVCOMP_INPUT)
|
||||
.expect("can't load test data into a wasm memory");
|
||||
|
||||
b.iter(|| {
|
||||
instance
|
||||
.invoke_export("bench_rev_complement", &[test_data_ptr], &mut NopExternals)
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
// Verify the result.
|
||||
let output_data_mem_offset = assert_matches!(
|
||||
instance.invoke_export("rev_complement_output_ptr", &[test_data_ptr], &mut NopExternals),
|
||||
Ok(Some(RuntimeValue::I32(v))) => v as u32,
|
||||
"",
|
||||
);
|
||||
let result = memory
|
||||
.get(output_data_mem_offset, REVCOMP_OUTPUT.len())
|
||||
.expect("can't get result data from a wasm memory");
|
||||
assert_eq!(&*result, REVCOMP_OUTPUT);
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_regex_redux(b: &mut Bencher) {
|
||||
let wasm_kernel = load_from_file(
|
||||
"./wasm-kernel/target/wasm32-unknown-unknown/release/wasm_kernel.wasm",
|
||||
).expect("failed to load wasm_kernel. Is `build.rs` broken?");
|
||||
|
||||
let instance = ModuleInstance::new(&wasm_kernel, &ImportsBuilder::default())
|
||||
.expect("failed to instantiate wasm module")
|
||||
.assert_no_start();
|
||||
|
||||
// Allocate buffers for the input and output.
|
||||
let test_data_ptr: RuntimeValue = {
|
||||
let input_size = RuntimeValue::I32(REVCOMP_INPUT.len() as i32);
|
||||
assert_matches!(
|
||||
instance.invoke_export("prepare_regex_redux", &[input_size], &mut NopExternals),
|
||||
Ok(Some(v @ RuntimeValue::I32(_))) => v,
|
||||
"",
|
||||
)
|
||||
};
|
||||
|
||||
// Get the pointer to the input buffer.
|
||||
let input_data_mem_offset = assert_matches!(
|
||||
instance.invoke_export("regex_redux_input_ptr", &[test_data_ptr], &mut NopExternals),
|
||||
Ok(Some(RuntimeValue::I32(v))) => v as u32,
|
||||
"",
|
||||
);
|
||||
|
||||
// Copy test data inside the wasm memory.
|
||||
let memory = instance.export_by_name("memory")
|
||||
.expect("Expected export with a name 'memory'")
|
||||
.as_memory()
|
||||
.expect("'memory' should be a memory instance")
|
||||
.clone();
|
||||
memory
|
||||
.set(input_data_mem_offset, REVCOMP_INPUT)
|
||||
.expect("can't load test data into a wasm memory");
|
||||
|
||||
b.iter(|| {
|
||||
instance
|
||||
.invoke_export("bench_regex_redux", &[test_data_ptr], &mut NopExternals)
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn fac_recursive(b: &mut Bencher) {
|
||||
let wasm = wabt::wat2wasm(
|
||||
r#"
|
||||
;; Recursive factorial
|
||||
(func (export "fac-rec") (param i64) (result i64)
|
||||
(if (result i64) (i64.eq (get_local 0) (i64.const 0))
|
||||
(then (i64.const 1))
|
||||
(else
|
||||
(i64.mul (get_local 0) (call 0 (i64.sub (get_local 0) (i64.const 1))))
|
||||
)
|
||||
)
|
||||
)
|
||||
"#
|
||||
).unwrap();
|
||||
|
||||
let module = Module::from_buffer(&wasm).unwrap();
|
||||
|
||||
let instance = ModuleInstance::new(&module, &ImportsBuilder::default())
|
||||
.expect("failed to instantiate wasm module")
|
||||
.assert_no_start();
|
||||
|
||||
b.iter(|| {
|
||||
let value = instance
|
||||
.invoke_export("fac-rec", &[RuntimeValue::I64(25)], &mut NopExternals);
|
||||
assert_matches!(value, Ok(Some(RuntimeValue::I64(7034535277573963776))));
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn fac_opt(b: &mut Bencher) {
|
||||
let wasm = wabt::wat2wasm(
|
||||
r#"
|
||||
;; Optimized factorial.
|
||||
(func (export "fac-opt") (param i64) (result i64)
|
||||
(local i64)
|
||||
(set_local 1 (i64.const 1))
|
||||
(block
|
||||
(br_if 0 (i64.lt_s (get_local 0) (i64.const 2)))
|
||||
(loop
|
||||
(set_local 1 (i64.mul (get_local 1) (get_local 0)))
|
||||
(set_local 0 (i64.add (get_local 0) (i64.const -1)))
|
||||
(br_if 0 (i64.gt_s (get_local 0) (i64.const 1)))
|
||||
)
|
||||
)
|
||||
(get_local 1)
|
||||
)
|
||||
"#
|
||||
).unwrap();
|
||||
|
||||
let module = Module::from_buffer(&wasm).unwrap();
|
||||
|
||||
let instance = ModuleInstance::new(&module, &ImportsBuilder::default())
|
||||
.expect("failed to instantiate wasm module")
|
||||
.assert_no_start();
|
||||
|
||||
b.iter(|| {
|
||||
let value = instance
|
||||
.invoke_export("fac-opt", &[RuntimeValue::I64(25)], &mut NopExternals);
|
||||
assert_matches!(value, Ok(Some(RuntimeValue::I64(7034535277573963776))));
|
||||
});
|
||||
}
|
||||
|
||||
// This is used for testing overhead of a function call
|
||||
// is not too large.
|
||||
#[bench]
|
||||
fn recursive_ok(b: &mut Bencher) {
|
||||
let wasm = wabt::wat2wasm(
|
||||
r#"
|
||||
(module
|
||||
(func $call (export "call") (param i32) (result i32)
|
||||
block (result i32)
|
||||
get_local 0
|
||||
get_local 0
|
||||
i32.eqz
|
||||
br_if 0
|
||||
|
||||
i32.const 1
|
||||
i32.sub
|
||||
call $call
|
||||
end
|
||||
)
|
||||
)
|
||||
"#
|
||||
).unwrap();
|
||||
let module = Module::from_buffer(&wasm).unwrap();
|
||||
|
||||
let instance = ModuleInstance::new(&module, &ImportsBuilder::default())
|
||||
.expect("failed to instantiate wasm module")
|
||||
.assert_no_start();
|
||||
|
||||
b.iter(|| {
|
||||
let value = instance
|
||||
.invoke_export("call", &[RuntimeValue::I32(8000)], &mut NopExternals);
|
||||
assert_matches!(value, Ok(Some(RuntimeValue::I32(0))));
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn recursive_trap(b: &mut Bencher) {
|
||||
let wasm = wabt::wat2wasm(
|
||||
r#"
|
||||
(module
|
||||
(func $call (export "call") (param i32) (result i32)
|
||||
block (result i32)
|
||||
get_local 0
|
||||
get_local 0
|
||||
i32.eqz
|
||||
br_if 0
|
||||
|
||||
i32.const 1
|
||||
i32.sub
|
||||
call $call
|
||||
end
|
||||
unreachable
|
||||
)
|
||||
)
|
||||
"#
|
||||
).unwrap();
|
||||
let module = Module::from_buffer(&wasm).unwrap();
|
||||
|
||||
let instance = ModuleInstance::new(&module, &ImportsBuilder::default())
|
||||
.expect("failed to instantiate wasm module")
|
||||
.assert_no_start();
|
||||
|
||||
b.iter(|| {
|
||||
let value = instance
|
||||
.invoke_export("call", &[RuntimeValue::I32(1000)], &mut NopExternals);
|
||||
assert_matches!(value, Err(_));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
>ONE Homo sapiens alu
|
||||
GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGA
|
||||
TCACCTGAGGTCAGGAGTTCGAGACCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACT
|
||||
AAAAATACAAAAATTAGCCGGGCGTGGTGGCGCGCGCCTGTAATCCCAGCTACTCGGGAG
|
||||
GCTGAGGCAGGAGAATCGCTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCG
|
||||
CCACTGCACTCCAGCCTGGGCGACAGAGCGAGACTCCGTCTCAAAAAGGCCGGGCGCGGT
|
||||
GGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGATCACCTGAGGTCA
|
||||
GGAGTTCGAGACCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACTAAAAATACAAAAA
|
||||
TTAGCCGGGCGTGGTGGCGCGCGCCTGTAATCCCAGCTACTCGGGAGGCTGAGGCAGGAG
|
||||
AATCGCTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCGCCACTGCACTCCA
|
||||
GCCTGGGCGACAGAGCGAGACTCCGTCTCAAAAAGGCCGGGCGCGGTGGCTCACGCCTGT
|
||||
AATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGATCACCTGAGGTCAGGAGTTCGAGACC
|
||||
AGCCTGGCCAACATGGTGAAACCCCGTCTCTACTAAAAATACAAAAATTAGCCGGGCGTG
|
||||
GTGGCGCGCGCCTGTAATCCCAGCTACTCGGGAGGCTGAGGCAGGAGAATCGCTTGAACC
|
||||
CGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCGCCACTGCACTCCAGCCTGGGCGACAG
|
||||
AGCGAGACTCCGTCTCAAAAAGGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTT
|
||||
TGGGAGGCCGAGGCGGGCGGATCACCTGAGGTCAGGAGTTCGAGACCAGCCTGGCCAACA
|
||||
TGGTGAAACCCCGTCTCTACTAAAAATACAAAAATTAGCCGGGCGTGGTGGCGCGCGCCT
|
||||
GTAATCCCAGCTACTCGGGAGGCTGAGGCAGGAGAATCGCTTGAACCCGGGAGGCGGAGG
|
||||
TTGCAGTGAGCCGAGATCGCGCCACTGCACTCCAGCCTGGGCGACAGAGCGAGACTCCGT
|
||||
CTCAAAAAGGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGG
|
||||
CGGGCGGATCACCTGAGGTCAGGAGTTCGAGACCAGCCTGGCCAACATGGTGAAACCCCG
|
||||
TCTCTACTAAAAATACAAAAATTAGCCGGGCGTGGTGGCGCGCGCCTGTAATCCCAGCTA
|
||||
CTCGGGAGGCTGAGGCAGGAGAATCGCTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCCG
|
||||
AGATCGCGCCACTGCACTCCAGCCTGGGCGACAGAGCGAGACTCCGTCTCAAAAAGGCCG
|
||||
GGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGATCACC
|
||||
TGAGGTCAGGAGTTCGAGACCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACTAAAAA
|
||||
TACAAAAATTAGCCGGGCGTGGTGGCGCGCGCCTGTAATCCCAGCTACTCGGGAGGCTGA
|
||||
GGCAGGAGAATCGCTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCGCCACT
|
||||
GCACTCCAGCCTGGGCGACAGAGCGAGACTCCGTCTCAAAAAGGCCGGGCGCGGTGGCTC
|
||||
ACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGATCACCTGAGGTCAGGAGT
|
||||
TCGAGACCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACTAAAAATACAAAAATTAGC
|
||||
CGGGCGTGGTGGCGCGCGCCTGTAATCCCAGCTACTCGGGAGGCTGAGGCAGGAGAATCG
|
||||
CTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCGCCACTGCACTCCAGCCTG
|
||||
GGCGACAGAGCGAGACTCCG
|
||||
>TWO IUB ambiguity codes
|
||||
cttBtatcatatgctaKggNcataaaSatgtaaaDcDRtBggDtctttataattcBgtcg
|
||||
tactDtDagcctatttSVHtHttKtgtHMaSattgWaHKHttttagacatWatgtRgaaa
|
||||
NtactMcSMtYtcMgRtacttctWBacgaaatatagScDtttgaagacacatagtVgYgt
|
||||
cattHWtMMWcStgttaggKtSgaYaaccWStcgBttgcgaMttBYatcWtgacaYcaga
|
||||
gtaBDtRacttttcWatMttDBcatWtatcttactaBgaYtcttgttttttttYaaScYa
|
||||
HgtgttNtSatcMtcVaaaStccRcctDaataataStcYtRDSaMtDttgttSagtRRca
|
||||
tttHatSttMtWgtcgtatSSagactYaaattcaMtWatttaSgYttaRgKaRtccactt
|
||||
tattRggaMcDaWaWagttttgacatgttctacaaaRaatataataaMttcgDacgaSSt
|
||||
acaStYRctVaNMtMgtaggcKatcttttattaaaaagVWaHKYagtttttatttaacct
|
||||
tacgtVtcVaattVMBcttaMtttaStgacttagattWWacVtgWYagWVRctDattBYt
|
||||
gtttaagaagattattgacVatMaacattVctgtBSgaVtgWWggaKHaatKWcBScSWa
|
||||
accRVacacaaactaccScattRatatKVtactatatttHttaagtttSKtRtacaaagt
|
||||
RDttcaaaaWgcacatWaDgtDKacgaacaattacaRNWaatHtttStgttattaaMtgt
|
||||
tgDcgtMgcatBtgcttcgcgaDWgagctgcgaggggVtaaScNatttacttaatgacag
|
||||
cccccacatYScaMgtaggtYaNgttctgaMaacNaMRaacaaacaKctacatagYWctg
|
||||
ttWaaataaaataRattagHacacaagcgKatacBttRttaagtatttccgatctHSaat
|
||||
actcNttMaagtattMtgRtgaMgcataatHcMtaBSaRattagttgatHtMttaaKagg
|
||||
YtaaBataSaVatactWtataVWgKgttaaaacagtgcgRatatacatVtHRtVYataSa
|
||||
KtWaStVcNKHKttactatccctcatgWHatWaRcttactaggatctataDtDHBttata
|
||||
aaaHgtacVtagaYttYaKcctattcttcttaataNDaaggaaaDYgcggctaaWSctBa
|
||||
aNtgctggMBaKctaMVKagBaactaWaDaMaccYVtNtaHtVWtKgRtcaaNtYaNacg
|
||||
gtttNattgVtttctgtBaWgtaattcaagtcaVWtactNggattctttaYtaaagccgc
|
||||
tcttagHVggaYtgtNcDaVagctctctKgacgtatagYcctRYHDtgBattDaaDgccK
|
||||
tcHaaStttMcctagtattgcRgWBaVatHaaaataYtgtttagMDMRtaataaggatMt
|
||||
ttctWgtNtgtgaaaaMaatatRtttMtDgHHtgtcattttcWattRSHcVagaagtacg
|
||||
ggtaKVattKYagactNaatgtttgKMMgYNtcccgSKttctaStatatNVataYHgtNa
|
||||
BKRgNacaactgatttcctttaNcgatttctctataScaHtataRagtcRVttacDSDtt
|
||||
aRtSatacHgtSKacYagttMHtWataggatgactNtatSaNctataVtttRNKtgRacc
|
||||
tttYtatgttactttttcctttaaacatacaHactMacacggtWataMtBVacRaSaatc
|
||||
cgtaBVttccagccBcttaRKtgtgcctttttRtgtcagcRttKtaaacKtaaatctcac
|
||||
aattgcaNtSBaaccgggttattaaBcKatDagttactcttcattVtttHaaggctKKga
|
||||
tacatcBggScagtVcacattttgaHaDSgHatRMaHWggtatatRgccDttcgtatcga
|
||||
aacaHtaagttaRatgaVacttagattVKtaaYttaaatcaNatccRttRRaMScNaaaD
|
||||
gttVHWgtcHaaHgacVaWtgttScactaagSgttatcttagggDtaccagWattWtRtg
|
||||
ttHWHacgattBtgVcaYatcggttgagKcWtKKcaVtgaYgWctgYggVctgtHgaNcV
|
||||
taBtWaaYatcDRaaRtSctgaHaYRttagatMatgcatttNattaDttaattgttctaa
|
||||
ccctcccctagaWBtttHtBccttagaVaatMcBHagaVcWcagBVttcBtaYMccagat
|
||||
gaaaaHctctaacgttagNWRtcggattNatcRaNHttcagtKttttgWatWttcSaNgg
|
||||
gaWtactKKMaacatKatacNattgctWtatctaVgagctatgtRaHtYcWcttagccaa
|
||||
tYttWttaWSSttaHcaaaaagVacVgtaVaRMgattaVcDactttcHHggHRtgNcctt
|
||||
tYatcatKgctcctctatVcaaaaKaaaagtatatctgMtWtaaaacaStttMtcgactt
|
||||
taSatcgDataaactaaacaagtaaVctaggaSccaatMVtaaSKNVattttgHccatca
|
||||
cBVctgcaVatVttRtactgtVcaattHgtaaattaaattttYtatattaaRSgYtgBag
|
||||
aHSBDgtagcacRHtYcBgtcacttacactaYcgctWtattgSHtSatcataaatataHt
|
||||
cgtYaaMNgBaatttaRgaMaatatttBtttaaaHHKaatctgatWatYaacttMctctt
|
||||
ttVctagctDaaagtaVaKaKRtaacBgtatccaaccactHHaagaagaaggaNaaatBW
|
||||
attccgStaMSaMatBttgcatgRSacgttVVtaaDMtcSgVatWcaSatcttttVatag
|
||||
ttactttacgatcaccNtaDVgSRcgVcgtgaacgaNtaNatatagtHtMgtHcMtagaa
|
||||
attBgtataRaaaacaYKgtRccYtatgaagtaataKgtaaMttgaaRVatgcagaKStc
|
||||
tHNaaatctBBtcttaYaBWHgtVtgacagcaRcataWctcaBcYacYgatDgtDHccta
|
||||
>THREE Homo sapiens frequency
|
||||
aacacttcaccaggtatcgtgaaggctcaagattacccagagaacctttgcaatataaga
|
||||
atatgtatgcagcattaccctaagtaattatattctttttctgactcaaagtgacaagcc
|
||||
ctagtgtatattaaatcggtatatttgggaaattcctcaaactatcctaatcaggtagcc
|
||||
atgaaagtgatcaaaaaagttcgtacttataccatacatgaattctggccaagtaaaaaa
|
||||
tagattgcgcaaaattcgtaccttaagtctctcgccaagatattaggatcctattactca
|
||||
tatcgtgtttttctttattgccgccatccccggagtatctcacccatccttctcttaaag
|
||||
gcctaatattacctatgcaaataaacatatattgttgaaaattgagaacctgatcgtgat
|
||||
tcttatgtgtaccatatgtatagtaatcacgcgactatatagtgctttagtatcgcccgt
|
||||
gggtgagtgaatattctgggctagcgtgagatagtttcttgtcctaatatttttcagatc
|
||||
gaatagcttctatttttgtgtttattgacatatgtcgaaactccttactcagtgaaagtc
|
||||
atgaccagatccacgaacaatcttcggaatcagtctcgttttacggcggaatcttgagtc
|
||||
taacttatatcccgtcgcttactttctaacaccccttatgtatttttaaaattacgttta
|
||||
ttcgaacgtacttggcggaagcgttattttttgaagtaagttacattgggcagactcttg
|
||||
acattttcgatacgactttctttcatccatcacaggactcgttcgtattgatatcagaag
|
||||
ctcgtgatgattagttgtcttctttaccaatactttgaggcctattctgcgaaatttttg
|
||||
ttgccctgcgaacttcacataccaaggaacacctcgcaacatgccttcatatccatcgtt
|
||||
cattgtaattcttacacaatgaatcctaagtaattacatccctgcgtaaaagatggtagg
|
||||
ggcactgaggatatattaccaagcatttagttatgagtaatcagcaatgtttcttgtatt
|
||||
aagttctctaaaatagttacatcgtaatgttatctcgggttccgcgaataaacgagatag
|
||||
attcattatatatggccctaagcaaaaacctcctcgtattctgttggtaattagaatcac
|
||||
acaatacgggttgagatattaattatttgtagtacgaagagatataaaaagatgaacaat
|
||||
tactcaagtcaagatgtatacgggatttataataaaaatcgggtagagatctgctttgca
|
||||
attcagacgtgccactaaatcgtaatatgtcgcgttacatcagaaagggtaactattatt
|
||||
aattaataaagggcttaatcactacatattagatcttatccgatagtcttatctattcgt
|
||||
tgtatttttaagcggttctaattcagtcattatatcagtgctccgagttctttattattg
|
||||
ttttaaggatgacaaaatgcctcttgttataacgctgggagaagcagactaagagtcgga
|
||||
gcagttggtagaatgaggctgcaaaagacggtctcgacgaatggacagactttactaaac
|
||||
caatgaaagacagaagtagagcaaagtctgaagtggtatcagcttaattatgacaaccct
|
||||
taatacttccctttcgccgaatactggcgtggaaaggttttaaaagtcgaagtagttaga
|
||||
ggcatctctcgctcataaataggtagactactcgcaatccaatgtgactatgtaatactg
|
||||
ggaacatcagtccgcgatgcagcgtgtttatcaaccgtccccactcgcctggggagacat
|
||||
gagaccacccccgtggggattattagtccgcagtaatcgactcttgacaatccttttcga
|
||||
ttatgtcatagcaatttacgacagttcagcgaagtgactactcggcgaaatggtattact
|
||||
aaagcattcgaacccacatgaatgtgattcttggcaatttctaatccactaaagcttttc
|
||||
cgttgaatctggttgtagatatttatataagttcactaattaagatcacggtagtatatt
|
||||
gatagtgatgtctttgcaagaggttggccgaggaatttacggattctctattgatacaat
|
||||
ttgtctggcttataactcttaaggctgaaccaggcgtttttagacgacttgatcagctgt
|
||||
tagaatggtttggactccctctttcatgtcagtaacatttcagccgttattgttacgata
|
||||
tgcttgaacaatattgatctaccacacacccatagtatattttataggtcatgctgttac
|
||||
ctacgagcatggtattccacttcccattcaatgagtattcaacatcactagcctcagaga
|
||||
tgatgacccacctctaataacgtcacgttgcggccatgtgaaacctgaacttgagtagac
|
||||
gatatcaagcgctttaaattgcatataacatttgagggtaaagctaagcggatgctttat
|
||||
ataatcaatactcaataataagatttgattgcattttagagttatgacacgacatagttc
|
||||
actaacgagttactattcccagatctagactgaagtactgatcgagacgatccttacgtc
|
||||
gatgatcgttagttatcgacttaggtcgggtctctagcggtattggtacttaaccggaca
|
||||
ctatactaataacccatgatcaaagcataacagaatacagacgataatttcgccaacata
|
||||
tatgtacagaccccaagcatgagaagctcattgaaagctatcattgaagtcccgctcaca
|
||||
atgtgtcttttccagacggtttaactggttcccgggagtcctggagtttcgacttacata
|
||||
aatggaaacaatgtattttgctaatttatctatagcgtcatttggaccaatacagaatat
|
||||
tatgttgcctagtaatccactataacccgcaagtgctgatagaaaatttttagacgattt
|
||||
ataaatgccccaagtatccctcccgtgaatcctccgttatactaattagtattcgttcat
|
||||
acgtataccgcgcatatatgaacatttggcgataaggcgcgtgaattgttacgtgacaga
|
||||
gatagcagtttcttgtgatatggttaacagacgtacatgaagggaaactttatatctata
|
||||
gtgatgcttccgtagaaataccgccactggtctgccaatgatgaagtatgtagctttagg
|
||||
tttgtactatgaggctttcgtttgtttgcagagtataacagttgcgagtgaaaaaccgac
|
||||
gaatttatactaatacgctttcactattggctacaaaatagggaagagtttcaatcatga
|
||||
gagggagtatatggatgctttgtagctaaaggtagaacgtatgtatatgctgccgttcat
|
||||
tcttgaaagatacataagcgataagttacgacaattataagcaacatccctaccttcgta
|
||||
acgatttcactgttactgcgcttgaaatacactatggggctattggcggagagaagcaga
|
||||
tcgcgccgagcatatacgagacctataatgttgatgatagagaaggcgtctgaattgata
|
||||
catcgaagtacactttctttcgtagtatctctcgtcctctttctatctccggacacaaga
|
||||
attaagttatatatatagagtcttaccaatcatgttgaatcctgattctcagagttcttt
|
||||
ggcgggccttgtgatgactgagaaacaatgcaatattgctccaaatttcctaagcaaatt
|
||||
ctcggttatgttatgttatcagcaaagcgttacgttatgttatttaaatctggaatgacg
|
||||
gagcgaagttcttatgtcggtgtgggaataattcttttgaagacagcactccttaaataa
|
||||
tatcgctccgtgtttgtatttatcgaatgggtctgtaaccttgcacaagcaaatcggtgg
|
||||
tgtatatatcggataacaattaatacgatgttcatagtgacagtatactgatcgagtcct
|
||||
ctaaagtcaattacctcacttaacaatctcattgatgttgtgtcattcccggtatcgccc
|
||||
gtagtatgtgctctgattgaccgagtgtgaaccaaggaacatctactaatgcctttgtta
|
||||
ggtaagatctctctgaattccttcgtgccaacttaaaacattatcaaaatttcttctact
|
||||
tggattaactacttttacgagcatggcaaattcccctgtggaagacggttcattattatc
|
||||
ggaaaccttatagaaattgcgtgttgactgaaattagatttttattgtaagagttgcatc
|
||||
tttgcgattcctctggtctagcttccaatgaacagtcctcccttctattcgacatcgggt
|
||||
ccttcgtacatgtctttgcgatgtaataattaggttcggagtgtggccttaatgggtgca
|
||||
actaggaatacaacgcaaatttgctgacatgatagcaaatcggtatgccggcaccaaaac
|
||||
gtgctccttgcttagcttgtgaatgagactcagtagttaaataaatccatatctgcaatc
|
||||
gattccacaggtattgtccactatctttgaactactctaagagatacaagcttagctgag
|
||||
accgaggtgtatatgactacgctgatatctgtaaggtaccaatgcaggcaaagtatgcga
|
||||
gaagctaataccggctgtttccagctttataagattaaaatttggctgtcctggcggcct
|
||||
cagaattgttctatcgtaatcagttggttcattaattagctaagtacgaggtacaactta
|
||||
tctgtcccagaacagctccacaagtttttttacagccgaaacccctgtgtgaatcttaat
|
||||
atccaagcgcgttatctgattagagtttacaactcagtattttatcagtacgttttgttt
|
||||
ccaacattacccggtatgacaaaatgacgccacgtgtcgaataatggtctgaccaatgta
|
||||
ggaagtgaaaagataaatat
|
|
@ -0,0 +1,171 @@
|
|||
>ONE Homo sapiens alu
|
||||
CGGAGTCTCGCTCTGTCGCCCAGGCTGGAGTGCAGTGGCGCGATCTCGGCTCACTGCAAC
|
||||
CTCCGCCTCCCGGGTTCAAGCGATTCTCCTGCCTCAGCCTCCCGAGTAGCTGGGATTACA
|
||||
GGCGCGCGCCACCACGCCCGGCTAATTTTTGTATTTTTAGTAGAGACGGGGTTTCACCAT
|
||||
GTTGGCCAGGCTGGTCTCGAACTCCTGACCTCAGGTGATCCGCCCGCCTCGGCCTCCCAA
|
||||
AGTGCTGGGATTACAGGCGTGAGCCACCGCGCCCGGCCTTTTTGAGACGGAGTCTCGCTC
|
||||
TGTCGCCCAGGCTGGAGTGCAGTGGCGCGATCTCGGCTCACTGCAACCTCCGCCTCCCGG
|
||||
GTTCAAGCGATTCTCCTGCCTCAGCCTCCCGAGTAGCTGGGATTACAGGCGCGCGCCACC
|
||||
ACGCCCGGCTAATTTTTGTATTTTTAGTAGAGACGGGGTTTCACCATGTTGGCCAGGCTG
|
||||
GTCTCGAACTCCTGACCTCAGGTGATCCGCCCGCCTCGGCCTCCCAAAGTGCTGGGATTA
|
||||
CAGGCGTGAGCCACCGCGCCCGGCCTTTTTGAGACGGAGTCTCGCTCTGTCGCCCAGGCT
|
||||
GGAGTGCAGTGGCGCGATCTCGGCTCACTGCAACCTCCGCCTCCCGGGTTCAAGCGATTC
|
||||
TCCTGCCTCAGCCTCCCGAGTAGCTGGGATTACAGGCGCGCGCCACCACGCCCGGCTAAT
|
||||
TTTTGTATTTTTAGTAGAGACGGGGTTTCACCATGTTGGCCAGGCTGGTCTCGAACTCCT
|
||||
GACCTCAGGTGATCCGCCCGCCTCGGCCTCCCAAAGTGCTGGGATTACAGGCGTGAGCCA
|
||||
CCGCGCCCGGCCTTTTTGAGACGGAGTCTCGCTCTGTCGCCCAGGCTGGAGTGCAGTGGC
|
||||
GCGATCTCGGCTCACTGCAACCTCCGCCTCCCGGGTTCAAGCGATTCTCCTGCCTCAGCC
|
||||
TCCCGAGTAGCTGGGATTACAGGCGCGCGCCACCACGCCCGGCTAATTTTTGTATTTTTA
|
||||
GTAGAGACGGGGTTTCACCATGTTGGCCAGGCTGGTCTCGAACTCCTGACCTCAGGTGAT
|
||||
CCGCCCGCCTCGGCCTCCCAAAGTGCTGGGATTACAGGCGTGAGCCACCGCGCCCGGCCT
|
||||
TTTTGAGACGGAGTCTCGCTCTGTCGCCCAGGCTGGAGTGCAGTGGCGCGATCTCGGCTC
|
||||
ACTGCAACCTCCGCCTCCCGGGTTCAAGCGATTCTCCTGCCTCAGCCTCCCGAGTAGCTG
|
||||
GGATTACAGGCGCGCGCCACCACGCCCGGCTAATTTTTGTATTTTTAGTAGAGACGGGGT
|
||||
TTCACCATGTTGGCCAGGCTGGTCTCGAACTCCTGACCTCAGGTGATCCGCCCGCCTCGG
|
||||
CCTCCCAAAGTGCTGGGATTACAGGCGTGAGCCACCGCGCCCGGCCTTTTTGAGACGGAG
|
||||
TCTCGCTCTGTCGCCCAGGCTGGAGTGCAGTGGCGCGATCTCGGCTCACTGCAACCTCCG
|
||||
CCTCCCGGGTTCAAGCGATTCTCCTGCCTCAGCCTCCCGAGTAGCTGGGATTACAGGCGC
|
||||
GCGCCACCACGCCCGGCTAATTTTTGTATTTTTAGTAGAGACGGGGTTTCACCATGTTGG
|
||||
CCAGGCTGGTCTCGAACTCCTGACCTCAGGTGATCCGCCCGCCTCGGCCTCCCAAAGTGC
|
||||
TGGGATTACAGGCGTGAGCCACCGCGCCCGGCCTTTTTGAGACGGAGTCTCGCTCTGTCG
|
||||
CCCAGGCTGGAGTGCAGTGGCGCGATCTCGGCTCACTGCAACCTCCGCCTCCCGGGTTCA
|
||||
AGCGATTCTCCTGCCTCAGCCTCCCGAGTAGCTGGGATTACAGGCGCGCGCCACCACGCC
|
||||
CGGCTAATTTTTGTATTTTTAGTAGAGACGGGGTTTCACCATGTTGGCCAGGCTGGTCTC
|
||||
GAACTCCTGACCTCAGGTGATCCGCCCGCCTCGGCCTCCCAAAGTGCTGGGATTACAGGC
|
||||
GTGAGCCACCGCGCCCGGCC
|
||||
>TWO IUB ambiguity codes
|
||||
TAGGDHACHATCRGTRGVTGAGWTATGYTGCTGTCABACDWVTRTAAGAVVAGATTTNDA
|
||||
GASMTCTGCATBYTTCAAKTTACMTATTACTTCATARGGYACMRTGTTTTYTATACVAAT
|
||||
TTCTAKGDACKADACTATATNTANTCGTTCACGBCGYSCBHTANGGTGATCGTAAAGTAA
|
||||
CTATBAAAAGATSTGWATBCSGAKHTTABBAACGTSYCATGCAAVATKTSKTASCGGAAT
|
||||
WVATTTNTCCTTCTTCTTDDAGTGGTTGGATACVGTTAYMTMTBTACTTTHAGCTAGBAA
|
||||
AAGAGKAAGTTRATWATCAGATTMDDTTTAAAVAAATATTKTCYTAAATTVCNKTTRACG
|
||||
ADTATATTTATGATSADSCAATAWAGCGRTAGTGTAAGTGACVGRADYGTGCTACHVSDT
|
||||
CTVCARCSYTTAATATARAAAATTTAATTTACDAATTGBACAGTAYAABATBTGCAGBVG
|
||||
TGATGGDCAAAATBNMSTTABKATTGGSTCCTAGBTTACTTGTTTAGTTTATHCGATSTA
|
||||
AAGTCGAKAAASTGTTTTAWAKCAGATATACTTTTMTTTTGBATAGAGGAGCMATGATRA
|
||||
AAGGNCAYDCCDDGAAAGTHGBTAATCKYTBTACBGTBCTTTTTGDTAASSWTAAWAARA
|
||||
TTGGCTAAGWGRADTYACATAGCTCBTAGATAWAGCAATNGTATMATGTTKMMAGTAWTC
|
||||
CCNTSGAAWATWCAAAAMACTGAADNTYGATNAATCCGAYWNCTAACGTTAGAGDTTTTC
|
||||
ATCTGGKRTAVGAABVCTGWGBTCTDVGKATTBTCTAAGGVADAAAVWTCTAGGGGAGGG
|
||||
TTAGAACAATTAAHTAATNAAATGCATKATCTAAYRTDTCAGSAYTTYHGATRTTWAVTA
|
||||
BGNTCDACAGBCCRCAGWCRTCABTGMMAWGMCTCAACCGATRTGBCAVAATCGTDWDAA
|
||||
CAYAWAATWCTGGTAHCCCTAAGATAACSCTTAGTGSAACAWTBGTCDTTDGACWDBAAC
|
||||
HTTTNGSKTYYAAYGGATNTGATTTAARTTAMBAATCTAAGTBTCATYTAACTTADTGTT
|
||||
TCGATACGAAHGGCYATATACCWDTKYATDCSHTDTCAAAATGTGBACTGSCCVGATGTA
|
||||
TCMMAGCCTTDAAABAATGAAGAGTAACTHATMGVTTAATAACCCGGTTVSANTGCAATT
|
||||
GTGAGATTTAMGTTTAMAAYGCTGACAYAAAAAGGCACAMYTAAGVGGCTGGAABVTACG
|
||||
GATTSTYGTBVAKTATWACCGTGTKAGTDTGTATGTTTAAAGGAAAAAGTAACATARAAA
|
||||
GGTYCAMNYAAABTATAGNTSATANAGTCATCCTATWADKAACTRGTMSACDGTATSAYT
|
||||
AAHSHGTAABYGACTYTATADTGSTATAGAGAAATCGNTAAAGGAAATCAGTTGTNCYMV
|
||||
TNACDRTATBNATATASTAGAAMSCGGGANRCKKMCAAACATTNAGTCTRMAATBMTACC
|
||||
CGTACTTCTBGDSYAATWGAAAATGACADDCHAKAAAYATATTKTTTTCACANACWAGAA
|
||||
AKATCCTTATTAYKHKCTAAACARTATTTTDATBTVWCYGCAATACTAGGKAAASTTDGA
|
||||
MGGCHTTHAATVCAHDRYAGGRCTATACGTCMAGAGAGCTBTHGNACARTCCBDCTAAGA
|
||||
GCGGCTTTARTAAAGAATCCNAGTAWBTGACTTGAATTACWTVACAGAAABCAATNAAAC
|
||||
CGTNTRANTTGAYCMAWBADTANABRGGTKTHTWTAGTTVCTMBKTAGMTVKCCAGCANT
|
||||
TVAGSWTTAGCCGCRHTTTCCTTHNTATTAAGAAGAATAGGMTRAARTCTABGTACDTTT
|
||||
TATAAVDHAHTATAGATCCTAGTAAGYTWATDWCATGAGGGATAGTAAMDMNGBASTWAM
|
||||
TSTATRBAYDABATGTATATYCGCACTGTTTTAACMCWBTATAWAGTATBTSTATVTTAR
|
||||
CCTMTTAAKADATCAACTAATYTSVTAKGDATTATGCKTCAYCAKAATACTTKAANGAGT
|
||||
ATTSDAGATCGGAAATACTTAAYAAVGTATMCGCTTGTGTDCTAATYTATTTTATTTWAA
|
||||
CAGWRCTATGTAGMTGTTTGTTYKTNGTTKTCAGAACNTRACCTACKTGSRATGTGGGGG
|
||||
CTGTCATTAAGTAAATNGSTTABCCCCTCGCAGCTCWHTCGCGAAGCAVATGCKACGHCA
|
||||
ACAKTTAATAACASAAADATTWNYTGTAATTGTTCGTMHACHTWATGTGCWTTTTGAAHY
|
||||
ACTTTGTAYAMSAAACTTAADAAATATAGTABMATATYAATGSGGTAGTTTGTGTBYGGT
|
||||
TWSGSVGWMATTDMTCCWWCABTCSVACAGBAATGTTKATBGTCAATAATCTTCTTAAAC
|
||||
ARVAATHAGYBWCTRWCABGTWWAATCTAAGTCASTAAAKTAAGVKBAATTBGABACGTA
|
||||
AGGTTAAATAAAAACTRMDTWBCTTTTTAATAAAAGATMGCCTACKAKNTBAGYRASTGT
|
||||
ASSTCGTHCGAAKTTATTATATTYTTTGTAGAACATGTCAAAACTWTWTHGKTCCYAATA
|
||||
AAGTGGAYTMCYTAARCSTAAATWAKTGAATTTRAGTCTSSATACGACWAKAASATDAAA
|
||||
TGYYACTSAACAAHAKTSHYARGASTATTATTHAGGYGGASTTTBGAKGATSANAACACD
|
||||
TRGSTTRAAAAAAAACAAGARTCVTAGTAAGATAWATGVHAAKATWGAAAAGTYAHVTAC
|
||||
TCTGRTGTCAWGATRVAAKTCGCAAVCGASWGGTTRTCSAMCCTAACASGWKKAWDAATG
|
||||
ACRCBACTATGTGTCTTCAAAHGSCTATATTTCGTVWAGAAGTAYCKGARAKSGKAGTAN
|
||||
TTTCYACATWATGTCTAAAADMDTWCAATSTKDACAMAADADBSAAATAGGCTHAHAGTA
|
||||
CGACVGAATTATAAAGAHCCVAYHGHTTTACATSTTTATGNCCMTAGCATATGATAVAAG
|
||||
>THREE Homo sapiens frequency
|
||||
ATATTTATCTTTTCACTTCCTACATTGGTCAGACCATTATTCGACACGTGGCGTCATTTT
|
||||
GTCATACCGGGTAATGTTGGAAACAAAACGTACTGATAAAATACTGAGTTGTAAACTCTA
|
||||
ATCAGATAACGCGCTTGGATATTAAGATTCACACAGGGGTTTCGGCTGTAAAAAAACTTG
|
||||
TGGAGCTGTTCTGGGACAGATAAGTTGTACCTCGTACTTAGCTAATTAATGAACCAACTG
|
||||
ATTACGATAGAACAATTCTGAGGCCGCCAGGACAGCCAAATTTTAATCTTATAAAGCTGG
|
||||
AAACAGCCGGTATTAGCTTCTCGCATACTTTGCCTGCATTGGTACCTTACAGATATCAGC
|
||||
GTAGTCATATACACCTCGGTCTCAGCTAAGCTTGTATCTCTTAGAGTAGTTCAAAGATAG
|
||||
TGGACAATACCTGTGGAATCGATTGCAGATATGGATTTATTTAACTACTGAGTCTCATTC
|
||||
ACAAGCTAAGCAAGGAGCACGTTTTGGTGCCGGCATACCGATTTGCTATCATGTCAGCAA
|
||||
ATTTGCGTTGTATTCCTAGTTGCACCCATTAAGGCCACACTCCGAACCTAATTATTACAT
|
||||
CGCAAAGACATGTACGAAGGACCCGATGTCGAATAGAAGGGAGGACTGTTCATTGGAAGC
|
||||
TAGACCAGAGGAATCGCAAAGATGCAACTCTTACAATAAAAATCTAATTTCAGTCAACAC
|
||||
GCAATTTCTATAAGGTTTCCGATAATAATGAACCGTCTTCCACAGGGGAATTTGCCATGC
|
||||
TCGTAAAAGTAGTTAATCCAAGTAGAAGAAATTTTGATAATGTTTTAAGTTGGCACGAAG
|
||||
GAATTCAGAGAGATCTTACCTAACAAAGGCATTAGTAGATGTTCCTTGGTTCACACTCGG
|
||||
TCAATCAGAGCACATACTACGGGCGATACCGGGAATGACACAACATCAATGAGATTGTTA
|
||||
AGTGAGGTAATTGACTTTAGAGGACTCGATCAGTATACTGTCACTATGAACATCGTATTA
|
||||
ATTGTTATCCGATATATACACCACCGATTTGCTTGTGCAAGGTTACAGACCCATTCGATA
|
||||
AATACAAACACGGAGCGATATTATTTAAGGAGTGCTGTCTTCAAAAGAATTATTCCCACA
|
||||
CCGACATAAGAACTTCGCTCCGTCATTCCAGATTTAAATAACATAACGTAACGCTTTGCT
|
||||
GATAACATAACATAACCGAGAATTTGCTTAGGAAATTTGGAGCAATATTGCATTGTTTCT
|
||||
CAGTCATCACAAGGCCCGCCAAAGAACTCTGAGAATCAGGATTCAACATGATTGGTAAGA
|
||||
CTCTATATATATAACTTAATTCTTGTGTCCGGAGATAGAAAGAGGACGAGAGATACTACG
|
||||
AAAGAAAGTGTACTTCGATGTATCAATTCAGACGCCTTCTCTATCATCAACATTATAGGT
|
||||
CTCGTATATGCTCGGCGCGATCTGCTTCTCTCCGCCAATAGCCCCATAGTGTATTTCAAG
|
||||
CGCAGTAACAGTGAAATCGTTACGAAGGTAGGGATGTTGCTTATAATTGTCGTAACTTAT
|
||||
CGCTTATGTATCTTTCAAGAATGAACGGCAGCATATACATACGTTCTACCTTTAGCTACA
|
||||
AAGCATCCATATACTCCCTCTCATGATTGAAACTCTTCCCTATTTTGTAGCCAATAGTGA
|
||||
AAGCGTATTAGTATAAATTCGTCGGTTTTTCACTCGCAACTGTTATACTCTGCAAACAAA
|
||||
CGAAAGCCTCATAGTACAAACCTAAAGCTACATACTTCATCATTGGCAGACCAGTGGCGG
|
||||
TATTTCTACGGAAGCATCACTATAGATATAAAGTTTCCCTTCATGTACGTCTGTTAACCA
|
||||
TATCACAAGAAACTGCTATCTCTGTCACGTAACAATTCACGCGCCTTATCGCCAAATGTT
|
||||
CATATATGCGCGGTATACGTATGAACGAATACTAATTAGTATAACGGAGGATTCACGGGA
|
||||
GGGATACTTGGGGCATTTATAAATCGTCTAAAAATTTTCTATCAGCACTTGCGGGTTATA
|
||||
GTGGATTACTAGGCAACATAATATTCTGTATTGGTCCAAATGACGCTATAGATAAATTAG
|
||||
CAAAATACATTGTTTCCATTTATGTAAGTCGAAACTCCAGGACTCCCGGGAACCAGTTAA
|
||||
ACCGTCTGGAAAAGACACATTGTGAGCGGGACTTCAATGATAGCTTTCAATGAGCTTCTC
|
||||
ATGCTTGGGGTCTGTACATATATGTTGGCGAAATTATCGTCTGTATTCTGTTATGCTTTG
|
||||
ATCATGGGTTATTAGTATAGTGTCCGGTTAAGTACCAATACCGCTAGAGACCCGACCTAA
|
||||
GTCGATAACTAACGATCATCGACGTAAGGATCGTCTCGATCAGTACTTCAGTCTAGATCT
|
||||
GGGAATAGTAACTCGTTAGTGAACTATGTCGTGTCATAACTCTAAAATGCAATCAAATCT
|
||||
TATTATTGAGTATTGATTATATAAAGCATCCGCTTAGCTTTACCCTCAAATGTTATATGC
|
||||
AATTTAAAGCGCTTGATATCGTCTACTCAAGTTCAGGTTTCACATGGCCGCAACGTGACG
|
||||
TTATTAGAGGTGGGTCATCATCTCTGAGGCTAGTGATGTTGAATACTCATTGAATGGGAA
|
||||
GTGGAATACCATGCTCGTAGGTAACAGCATGACCTATAAAATATACTATGGGTGTGTGGT
|
||||
AGATCAATATTGTTCAAGCATATCGTAACAATAACGGCTGAAATGTTACTGACATGAAAG
|
||||
AGGGAGTCCAAACCATTCTAACAGCTGATCAAGTCGTCTAAAAACGCCTGGTTCAGCCTT
|
||||
AAGAGTTATAAGCCAGACAAATTGTATCAATAGAGAATCCGTAAATTCCTCGGCCAACCT
|
||||
CTTGCAAAGACATCACTATCAATATACTACCGTGATCTTAATTAGTGAACTTATATAAAT
|
||||
ATCTACAACCAGATTCAACGGAAAAGCTTTAGTGGATTAGAAATTGCCAAGAATCACATT
|
||||
CATGTGGGTTCGAATGCTTTAGTAATACCATTTCGCCGAGTAGTCACTTCGCTGAACTGT
|
||||
CGTAAATTGCTATGACATAATCGAAAAGGATTGTCAAGAGTCGATTACTGCGGACTAATA
|
||||
ATCCCCACGGGGGTGGTCTCATGTCTCCCCAGGCGAGTGGGGACGGTTGATAAACACGCT
|
||||
GCATCGCGGACTGATGTTCCCAGTATTACATAGTCACATTGGATTGCGAGTAGTCTACCT
|
||||
ATTTATGAGCGAGAGATGCCTCTAACTACTTCGACTTTTAAAACCTTTCCACGCCAGTAT
|
||||
TCGGCGAAAGGGAAGTATTAAGGGTTGTCATAATTAAGCTGATACCACTTCAGACTTTGC
|
||||
TCTACTTCTGTCTTTCATTGGTTTAGTAAAGTCTGTCCATTCGTCGAGACCGTCTTTTGC
|
||||
AGCCTCATTCTACCAACTGCTCCGACTCTTAGTCTGCTTCTCCCAGCGTTATAACAAGAG
|
||||
GCATTTTGTCATCCTTAAAACAATAATAAAGAACTCGGAGCACTGATATAATGACTGAAT
|
||||
TAGAACCGCTTAAAAATACAACGAATAGATAAGACTATCGGATAAGATCTAATATGTAGT
|
||||
GATTAAGCCCTTTATTAATTAATAATAGTTACCCTTTCTGATGTAACGCGACATATTACG
|
||||
ATTTAGTGGCACGTCTGAATTGCAAAGCAGATCTCTACCCGATTTTTATTATAAATCCCG
|
||||
TATACATCTTGACTTGAGTAATTGTTCATCTTTTTATATCTCTTCGTACTACAAATAATT
|
||||
AATATCTCAACCCGTATTGTGTGATTCTAATTACCAACAGAATACGAGGAGGTTTTTGCT
|
||||
TAGGGCCATATATAATGAATCTATCTCGTTTATTCGCGGAACCCGAGATAACATTACGAT
|
||||
GTAACTATTTTAGAGAACTTAATACAAGAAACATTGCTGATTACTCATAACTAAATGCTT
|
||||
GGTAATATATCCTCAGTGCCCCTACCATCTTTTACGCAGGGATGTAATTACTTAGGATTC
|
||||
ATTGTGTAAGAATTACAATGAACGATGGATATGAAGGCATGTTGCGAGGTGTTCCTTGGT
|
||||
ATGTGAAGTTCGCAGGGCAACAAAAATTTCGCAGAATAGGCCTCAAAGTATTGGTAAAGA
|
||||
AGACAACTAATCATCACGAGCTTCTGATATCAATACGAACGAGTCCTGTGATGGATGAAA
|
||||
GAAAGTCGTATCGAAAATGTCAAGAGTCTGCCCAATGTAACTTACTTCAAAAAATAACGC
|
||||
TTCCGCCAAGTACGTTCGAATAAACGTAATTTTAAAAATACATAAGGGGTGTTAGAAAGT
|
||||
AAGCGACGGGATATAAGTTAGACTCAAGATTCCGCCGTAAAACGAGACTGATTCCGAAGA
|
||||
TTGTTCGTGGATCTGGTCATGACTTTCACTGAGTAAGGAGTTTCGACATATGTCAATAAA
|
||||
CACAAAAATAGAAGCTATTCGATCTGAAAAATATTAGGACAAGAAACTATCTCACGCTAG
|
||||
CCCAGAATATTCACTCACCCACGGGCGATACTAAAGCACTATATAGTCGCGTGATTACTA
|
||||
TACATATGGTACACATAAGAATCACGATCAGGTTCTCAATTTTCAACAATATATGTTTAT
|
||||
TTGCATAGGTAATATTAGGCCTTTAAGAGAAGGATGGGTGAGATACTCCGGGGATGGCGG
|
||||
CAATAAAGAAAAACACGATATGAGTAATAGGATCCTAATATCTTGGCGAGAGACTTAAGG
|
||||
TACGAATTTTGCGCAATCTATTTTTTACTTGGCCAGAATTCATGTATGGTATAAGTACGA
|
||||
ACTTTTTTGATCACTTTCATGGCTACCTGATTAGGATAGTTTGAGGAATTTCCCAAATAT
|
||||
ACCGATTTAATATACACTAGGGCTTGTCACTTTGAGTCAGAAAAAGAATATAATTACTTA
|
||||
GGGTAATGCTGCATACATATTCTTATATTGCAAAGGTTCTCTGGGTAATCTTGAGCCTTC
|
||||
ACGATACCTGGTGAAGTGTT
|
|
@ -8,7 +8,8 @@ crate-type = ["cdylib"]
|
|||
|
||||
[dependencies]
|
||||
tiny-keccak = "1.4.2"
|
||||
rlibc = "1.0"
|
||||
regex = "0.2.10"
|
||||
lazy_static = "1.0"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
|
|
|
@ -1,25 +1,13 @@
|
|||
#![no_std]
|
||||
#![feature(lang_items)]
|
||||
#![feature(core_intrinsics)]
|
||||
|
||||
extern crate rlibc;
|
||||
extern crate tiny_keccak;
|
||||
extern crate regex;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
use std::mem::ManuallyDrop;
|
||||
use tiny_keccak::Keccak;
|
||||
|
||||
#[no_mangle]
|
||||
#[lang = "panic_fmt"]
|
||||
pub extern "C" fn panic_fmt(
|
||||
_args: ::core::fmt::Arguments,
|
||||
_file: &'static str,
|
||||
_line: u32,
|
||||
_col: u32,
|
||||
) -> ! {
|
||||
use core::intrinsics;
|
||||
unsafe {
|
||||
intrinsics::abort();
|
||||
}
|
||||
}
|
||||
mod rev_complement;
|
||||
mod regex_redux;
|
||||
|
||||
pub struct TinyKeccakTestData {
|
||||
data: &'static [u8],
|
||||
|
@ -45,10 +33,91 @@ pub extern "C" fn prepare_tiny_keccak() -> *const TinyKeccakTestData {
|
|||
}
|
||||
|
||||
#[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 {
|
||||
let mut keccak = Keccak::new_keccak256();
|
||||
keccak.update((*test_data).data);
|
||||
keccak.finalize((*test_data).result);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RevComplementTestData {
|
||||
input: ManuallyDrop<Box<[u8]>>,
|
||||
output: ManuallyDrop<Box<[u8]>>,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn prepare_rev_complement(size: usize) -> *mut RevComplementTestData {
|
||||
let input = vec![0; size];
|
||||
let output = vec![0; size];
|
||||
|
||||
let test_data = Box::new(
|
||||
RevComplementTestData {
|
||||
input: ManuallyDrop::new(input.into_boxed_slice()),
|
||||
output: ManuallyDrop::new(output.into_boxed_slice()),
|
||||
}
|
||||
);
|
||||
|
||||
// Basically leak the pointer to the test data. This shouldn't be harmful since `prepare` is called
|
||||
// only once per bench run (not for the iteration), and afterwards whole memory instance is discarded.
|
||||
Box::into_raw(test_data)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rev_complement_input_ptr(test_data: *mut RevComplementTestData) -> *mut u8 {
|
||||
unsafe {
|
||||
(*test_data).input.as_mut_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rev_complement_output_ptr(test_data: *mut RevComplementTestData) -> *const u8 {
|
||||
unsafe {
|
||||
(*test_data).output.as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn bench_rev_complement(test_data: *mut RevComplementTestData) {
|
||||
unsafe {
|
||||
let result = rev_complement::run(&*(*test_data).input);
|
||||
(*test_data).output.copy_from_slice(&result);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RegexReduxTestData {
|
||||
input: ManuallyDrop<Box<[u8]>>,
|
||||
output: Option<usize>,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn prepare_regex_redux(size: usize) -> *mut RegexReduxTestData {
|
||||
regex_redux::prepare();
|
||||
|
||||
let input = vec![0; size];
|
||||
let test_data = Box::new(
|
||||
RegexReduxTestData {
|
||||
input: ManuallyDrop::new(input.into_boxed_slice()),
|
||||
output: None,
|
||||
}
|
||||
);
|
||||
|
||||
// Basically leak the pointer to the test data. This shouldn't be harmful since `prepare` is called
|
||||
// only once per bench run (not for the iteration), and afterwards whole memory instance is discarded.
|
||||
Box::into_raw(test_data)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regex_redux_input_ptr(test_data: *mut RegexReduxTestData) -> *mut u8 {
|
||||
unsafe {
|
||||
(*test_data).input.as_mut_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn bench_regex_redux(test_data: *mut RegexReduxTestData) {
|
||||
unsafe {
|
||||
let result = regex_redux::run(&*(*test_data).input);
|
||||
(*test_data).output = Some(result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
//! Initially it supposed to be like [1]. However it turned out
|
||||
//! that executing this code in wasmi way too slow.
|
||||
//!
|
||||
//! [1]: https://benchmarksgame-team.pages.debian.net/benchmarksgame/program/regexredux-rust-2.html
|
||||
|
||||
lazy_static! {
|
||||
static ref REGEX: ::regex::bytes::Regex =
|
||||
{ ::regex::bytes::Regex::new("agggtaa[cgt]|[acg]ttaccct").unwrap() };
|
||||
}
|
||||
|
||||
pub fn prepare() {
|
||||
::lazy_static::initialize(®EX);
|
||||
}
|
||||
|
||||
pub fn run(seq: &[u8]) -> usize {
|
||||
REGEX.find_iter(seq).count()
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
// Adapted version from benchmarks game. In particular
|
||||
// rayon is removed.
|
||||
//
|
||||
// https://benchmarksgame-team.pages.debian.net/benchmarksgame/program/revcomp-rust-3.html
|
||||
|
||||
// The Computer Language Benchmarks Game
|
||||
// https://salsa.debian.org/benchmarksgame-team/benchmarksgame/
|
||||
//
|
||||
// contributed by the Rust Project Developers
|
||||
// contributed by Cristi Cobzarenco
|
||||
// contributed by TeXitoi
|
||||
// contributed by Matt Brubeck
|
||||
|
||||
use std::io::BufRead;
|
||||
use std::mem::replace;
|
||||
use std::{cmp, io};
|
||||
|
||||
/// Lookup table to find the complement of a single FASTA code.
|
||||
fn build_table() -> [u8; 256] {
|
||||
let mut table = [0; 256];
|
||||
for (i, x) in table.iter_mut().enumerate() {
|
||||
*x = match i as u8 as char {
|
||||
'A' | 'a' => 'T',
|
||||
'C' | 'c' => 'G',
|
||||
'G' | 'g' => 'C',
|
||||
'T' | 't' => 'A',
|
||||
'U' | 'u' => 'A',
|
||||
'M' | 'm' => 'K',
|
||||
'R' | 'r' => 'Y',
|
||||
'W' | 'w' => 'W',
|
||||
'S' | 's' => 'S',
|
||||
'Y' | 'y' => 'R',
|
||||
'K' | 'k' => 'M',
|
||||
'V' | 'v' => 'B',
|
||||
'H' | 'h' => 'D',
|
||||
'D' | 'd' => 'H',
|
||||
'B' | 'b' => 'V',
|
||||
'N' | 'n' => 'N',
|
||||
i => i,
|
||||
} as u8;
|
||||
}
|
||||
table
|
||||
}
|
||||
|
||||
/// Utilities for splitting chunks off of slices.
|
||||
trait SplitOff {
|
||||
fn split_off_left(&mut self, n: usize) -> Self;
|
||||
fn split_off_right(&mut self, n: usize) -> Self;
|
||||
}
|
||||
impl<'a, T> SplitOff for &'a mut [T] {
|
||||
/// Split the left `n` items from self and return them as a separate slice.
|
||||
fn split_off_left(&mut self, n: usize) -> Self {
|
||||
let n = cmp::min(self.len(), n);
|
||||
let data = replace(self, &mut []);
|
||||
let (left, data) = data.split_at_mut(n);
|
||||
*self = data;
|
||||
left
|
||||
}
|
||||
/// Split the right `n` items from self and return them as a separate slice.
|
||||
fn split_off_right(&mut self, n: usize) -> Self {
|
||||
let len = self.len();
|
||||
let n = cmp::min(len, n);
|
||||
let data = replace(self, &mut []);
|
||||
let (data, right) = data.split_at_mut(len - n);
|
||||
*self = data;
|
||||
right
|
||||
}
|
||||
}
|
||||
|
||||
/// Length of a normal line including the terminating \n.
|
||||
const LINE_LEN: usize = 61;
|
||||
|
||||
/// Compute the reverse complement for two contiguous chunks without line breaks.
|
||||
fn reverse_chunks(left: &mut [u8], right: &mut [u8], table: &[u8; 256]) {
|
||||
for (x, y) in left.iter_mut().zip(right.iter_mut().rev()) {
|
||||
*y = table[replace(x, table[*y as usize]) as usize];
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the reverse complement on chunks from opposite ends of a sequence.
|
||||
///
|
||||
/// `left` must start at the beginning of a line. If there are an odd number of
|
||||
/// bytes, `right` will initially be 1 byte longer than `left`; otherwise they
|
||||
/// will have equal lengths.
|
||||
fn reverse_complement_left_right(
|
||||
mut left: &mut [u8],
|
||||
mut right: &mut [u8],
|
||||
trailing_len: usize,
|
||||
table: &[u8; 256],
|
||||
) {
|
||||
// Each iteration swaps one line from the start of the sequence with one
|
||||
// from the end.
|
||||
while left.len() > 0 || right.len() > 0 {
|
||||
// Get the chunk up to the newline in `right`.
|
||||
let mut a = left.split_off_left(trailing_len);
|
||||
let mut b = right.split_off_right(trailing_len);
|
||||
right.split_off_right(1); // Skip the newline in `right`.
|
||||
|
||||
// If we've reached the middle of the sequence here and there is an
|
||||
// odd number of bytes remaining, the odd one will be on the right.
|
||||
if b.len() > a.len() {
|
||||
let mid = b.split_off_left(1);
|
||||
mid[0] = table[mid[0] as usize];
|
||||
}
|
||||
|
||||
reverse_chunks(a, b, table);
|
||||
|
||||
// Get the chunk up to the newline in `left`.
|
||||
let n = LINE_LEN - 1 - trailing_len;
|
||||
a = left.split_off_left(n);
|
||||
b = right.split_off_right(n);
|
||||
left.split_off_left(1); // Skip the newline in `left`.
|
||||
|
||||
// If we've reached the middle of the sequence and there is an odd
|
||||
// number of bytes remaining, the odd one will now be on the left.
|
||||
if a.len() > b.len() {
|
||||
let mid = a.split_off_right(1);
|
||||
mid[0] = table[mid[0] as usize]
|
||||
}
|
||||
|
||||
reverse_chunks(a, b, table);
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the reverse complement of one sequence.
|
||||
fn reverse_complement(seq: &mut [u8], table: &[u8; 256]) {
|
||||
let len = seq.len() - 1;
|
||||
let seq = &mut seq[..len]; // Drop the last newline
|
||||
let trailing_len = len % LINE_LEN;
|
||||
let (left, right) = seq.split_at_mut(len / 2);
|
||||
reverse_complement_left_right(left, right, trailing_len, table);
|
||||
}
|
||||
|
||||
/// Read sequences from stdin and print the reverse complement to stdout.
|
||||
pub fn run(input: &[u8]) -> Vec<u8> {
|
||||
let mut buf = Vec::with_capacity(input.len());
|
||||
|
||||
let mut input = io::Cursor::new(input);
|
||||
|
||||
// Read the first header line.
|
||||
input.read_until(b'\n', &mut buf).unwrap();
|
||||
|
||||
// Read sequence data line-by-line, splitting on headers.
|
||||
let mut line_start = buf.len();
|
||||
let mut seq_start = line_start;
|
||||
let mut seqs = vec![];
|
||||
while input.read_until(b'\n', &mut buf).unwrap() > 0 {
|
||||
if buf[line_start] == b'>' {
|
||||
// Found the start of a new sequence.
|
||||
seqs.push(seq_start..line_start);
|
||||
seq_start = buf.len();
|
||||
}
|
||||
line_start = buf.len();
|
||||
}
|
||||
seqs.push(seq_start..buf.len());
|
||||
|
||||
// Compute the reverse complements of each sequence.
|
||||
let table = build_table();
|
||||
for seq in seqs {
|
||||
reverse_complement(&mut buf[seq], &table);
|
||||
}
|
||||
|
||||
buf
|
||||
}
|
|
@ -4,7 +4,7 @@ extern crate wasmi;
|
|||
|
||||
use std::env::args;
|
||||
use std::fs::File;
|
||||
use wasmi::{ModuleInstance, NopExternals, RuntimeValue, ImportsBuilder, Module};
|
||||
use wasmi::{ImportsBuilder, Module, ModuleInstance, NopExternals, RuntimeValue};
|
||||
|
||||
fn load_from_file(filename: &str) -> Module {
|
||||
use std::io::prelude::*;
|
||||
|
@ -40,5 +40,8 @@ fn main() {
|
|||
let argument: i32 = args[2].parse().expect("Integer argument required");
|
||||
|
||||
// "_call" export of function to be executed with an i32 argument and prints the result of execution
|
||||
println!("Result: {:?}", main.invoke_export("_call", &[RuntimeValue::I32(argument)], &mut NopExternals));
|
||||
println!(
|
||||
"Result: {:?}",
|
||||
main.invoke_export("_call", &[RuntimeValue::I32(argument)], &mut NopExternals)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,9 +3,8 @@ extern crate wasmi;
|
|||
|
||||
use std::env::args;
|
||||
|
||||
use parity_wasm::elements::{Internal, External, Type, FunctionType, ValueType};
|
||||
use wasmi::{RuntimeValue, ModuleInstance, NopExternals, ImportsBuilder};
|
||||
|
||||
use parity_wasm::elements::{External, FunctionType, Internal, Type, ValueType};
|
||||
use wasmi::{ImportsBuilder, ModuleInstance, NopExternals, RuntimeValue};
|
||||
|
||||
fn main() {
|
||||
let args: Vec<_> = args().collect();
|
||||
|
@ -23,14 +22,19 @@ fn main() {
|
|||
// Export section has an entry with a func_name with an index inside a module
|
||||
let export_section = module.export_section().expect("No export section found");
|
||||
// It's a section with function declarations (which are references to the type section entries)
|
||||
let function_section = module.function_section().expect("No function section found");
|
||||
let function_section = module
|
||||
.function_section()
|
||||
.expect("No function section found");
|
||||
// Type section stores function types which are referenced by function_section entries
|
||||
let type_section = module.type_section().expect("No type section found");
|
||||
|
||||
// Given function name used to find export section entry which contains
|
||||
// an `internal` field which points to the index in the function index space
|
||||
let found_entry = export_section.entries().iter()
|
||||
.find(|entry| func_name == entry.field()).expect(&format!("No export with name {} found", func_name));
|
||||
let found_entry = export_section
|
||||
.entries()
|
||||
.iter()
|
||||
.find(|entry| func_name == entry.field())
|
||||
.expect(&format!("No export with name {} found", func_name));
|
||||
|
||||
// Function index in the function index space (internally-defined + imported)
|
||||
let function_index: usize = match found_entry.internal() {
|
||||
|
@ -41,11 +45,14 @@ fn main() {
|
|||
// We need to count import section entries (functions only!) to subtract it from function_index
|
||||
// and obtain the index within the function section
|
||||
let import_section_len: usize = match module.import_section() {
|
||||
Some(import) =>
|
||||
import.entries().iter().filter(|entry| match entry.external() {
|
||||
Some(import) => import
|
||||
.entries()
|
||||
.iter()
|
||||
.filter(|entry| match entry.external() {
|
||||
&External::Function(_) => true,
|
||||
_ => false,
|
||||
}).count(),
|
||||
})
|
||||
.count(),
|
||||
None => 0,
|
||||
};
|
||||
|
||||
|
@ -53,7 +60,8 @@ fn main() {
|
|||
let function_index_in_section = function_index - import_section_len;
|
||||
|
||||
// Getting a type reference from a function section entry
|
||||
let func_type_ref: usize = function_section.entries()[function_index_in_section].type_ref() as usize;
|
||||
let func_type_ref: usize =
|
||||
function_section.entries()[function_index_in_section].type_ref() as usize;
|
||||
|
||||
// Use the reference to get an actual function type
|
||||
let function_type: &FunctionType = match &type_section.types()[func_type_ref] {
|
||||
|
@ -61,12 +69,35 @@ fn main() {
|
|||
};
|
||||
|
||||
// Parses arguments and constructs runtime values in correspondence of their types
|
||||
function_type.params().iter().enumerate().map(|(i, value)| match value {
|
||||
&ValueType::I32 => RuntimeValue::I32(program_args[i].parse::<i32>().expect(&format!("Can't parse arg #{} as i32", program_args[i]))),
|
||||
&ValueType::I64 => RuntimeValue::I64(program_args[i].parse::<i64>().expect(&format!("Can't parse arg #{} as i64", program_args[i]))),
|
||||
&ValueType::F32 => RuntimeValue::F32(program_args[i].parse::<f32>().expect(&format!("Can't parse arg #{} as f32", program_args[i])).into()),
|
||||
&ValueType::F64 => RuntimeValue::F64(program_args[i].parse::<f64>().expect(&format!("Can't parse arg #{} as f64", program_args[i])).into()),
|
||||
}).collect::<Vec<RuntimeValue>>()
|
||||
function_type
|
||||
.params()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, value)| match value {
|
||||
&ValueType::I32 => RuntimeValue::I32(
|
||||
program_args[i]
|
||||
.parse::<i32>()
|
||||
.expect(&format!("Can't parse arg #{} as i32", program_args[i])),
|
||||
),
|
||||
&ValueType::I64 => RuntimeValue::I64(
|
||||
program_args[i]
|
||||
.parse::<i64>()
|
||||
.expect(&format!("Can't parse arg #{} as i64", program_args[i])),
|
||||
),
|
||||
&ValueType::F32 => RuntimeValue::F32(
|
||||
program_args[i]
|
||||
.parse::<f32>()
|
||||
.expect(&format!("Can't parse arg #{} as f32", program_args[i]))
|
||||
.into(),
|
||||
),
|
||||
&ValueType::F64 => RuntimeValue::F64(
|
||||
program_args[i]
|
||||
.parse::<f64>()
|
||||
.expect(&format!("Can't parse arg #{} as f64", program_args[i]))
|
||||
.into(),
|
||||
),
|
||||
})
|
||||
.collect::<Vec<RuntimeValue>>()
|
||||
};
|
||||
|
||||
let loaded_module = wasmi::Module::from_parity_wasm_module(module).expect("Module to be valid");
|
||||
|
@ -81,5 +112,9 @@ fn main() {
|
|||
.run_start(&mut NopExternals)
|
||||
.expect("Failed to run start function in module");
|
||||
|
||||
println!("Result: {:?}", main.invoke_export(func_name, &args, &mut NopExternals).expect(""));
|
||||
println!(
|
||||
"Result: {:?}",
|
||||
main.invoke_export(func_name, &args, &mut NopExternals)
|
||||
.expect("")
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
extern crate wasmi;
|
||||
extern crate parity_wasm;
|
||||
extern crate wasmi;
|
||||
|
||||
use std::env;
|
||||
use std::fmt;
|
||||
use std::fs::File;
|
||||
use wasmi::{
|
||||
Error as InterpreterError, ModuleInstance, ModuleRef,
|
||||
Externals, RuntimeValue, FuncRef, ModuleImportResolver,
|
||||
FuncInstance, HostError, ImportsBuilder, Signature, ValueType,
|
||||
RuntimeArgs, Trap,
|
||||
Error as InterpreterError, Externals, FuncInstance, FuncRef, HostError, ImportsBuilder,
|
||||
ModuleImportResolver, ModuleInstance, ModuleRef, RuntimeArgs, RuntimeValue, Signature, Trap,
|
||||
ValueType,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -64,9 +63,7 @@ mod tictactoe {
|
|||
|
||||
impl Game {
|
||||
pub fn new() -> Game {
|
||||
Game {
|
||||
board: [None; 9],
|
||||
}
|
||||
Game { board: [None; 9] }
|
||||
}
|
||||
|
||||
pub fn set(&mut self, idx: i32, player: Player) -> Result<(), Error> {
|
||||
|
@ -96,12 +93,10 @@ mod tictactoe {
|
|||
(0, 1, 2),
|
||||
(3, 4, 5),
|
||||
(6, 7, 8),
|
||||
|
||||
// Columns
|
||||
(0, 3, 6),
|
||||
(1, 4, 7),
|
||||
(2, 5, 8),
|
||||
|
||||
// Diagonals
|
||||
(0, 4, 8),
|
||||
(2, 4, 6),
|
||||
|
@ -161,7 +156,7 @@ impl<'a> Externals for Runtime<'a> {
|
|||
let val: i32 = tictactoe::Player::into_i32(self.game.get(idx)?);
|
||||
Ok(Some(val.into()))
|
||||
}
|
||||
_ => panic!("unknown function index")
|
||||
_ => panic!("unknown function index"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -175,15 +170,20 @@ impl<'a> ModuleImportResolver for RuntimeModuleImportResolver {
|
|||
_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)
|
||||
)
|
||||
)
|
||||
"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)
|
||||
}
|
||||
|
@ -201,8 +201,7 @@ fn instantiate(path: &str) -> Result<ModuleRef, Error> {
|
|||
let mut imports = ImportsBuilder::new();
|
||||
imports.push_resolver("env", &RuntimeModuleImportResolver);
|
||||
|
||||
let instance = ModuleInstance::new(&module, &imports)?
|
||||
.assert_no_start();
|
||||
let instance = ModuleInstance::new(&module, &imports)?.assert_no_start();
|
||||
|
||||
Ok(instance)
|
||||
}
|
||||
|
|
|
@ -10,11 +10,13 @@ cargo-fuzz = true
|
|||
|
||||
[dependencies]
|
||||
wasmi = { path = ".." }
|
||||
wabt = "0.2.0"
|
||||
wabt = "0.9"
|
||||
wasmparser = "0.14.1"
|
||||
tempdir = "0.3.6"
|
||||
|
||||
[dependencies.libfuzzer-sys]
|
||||
git = "https://github.com/rust-fuzz/libfuzzer-sys.git"
|
||||
rev = "737524f7de1e85342b8b6cd1c01edc71018183ba"
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
|
@ -31,3 +33,7 @@ path = "fuzz_targets/load_wabt.rs"
|
|||
[[bin]]
|
||||
name = "load_wasmparser"
|
||||
path = "fuzz_targets/load_wasmparser.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "load_spec"
|
||||
path = "fuzz_targets/load_spec.rs"
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
#![no_main]
|
||||
#[macro_use]
|
||||
extern crate libfuzzer_sys;
|
||||
extern crate wabt;
|
||||
extern crate wasmi;
|
||||
extern crate tempdir;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
fn run_spec(data: &[u8]) -> Result<(), ()> {
|
||||
let temp_dir = tempdir::TempDir::new("spec").unwrap();
|
||||
let mut seed_path = temp_dir.path().to_path_buf();
|
||||
seed_path.push("test.wasm");
|
||||
|
||||
{
|
||||
let mut seedfile =
|
||||
File::create(&seed_path).expect("open temporary file for writing to store fuzzer input");
|
||||
seedfile.write_all(data).expect(
|
||||
"write fuzzer input to temporary file",
|
||||
);
|
||||
seedfile.flush().expect(
|
||||
"flush fuzzer input to temporary file before starting wasm-opt",
|
||||
);
|
||||
}
|
||||
|
||||
let exit_status = Command::new("wasm")
|
||||
.arg("-d")
|
||||
.arg(&seed_path)
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status()
|
||||
.expect("failed to execute `wasm`");
|
||||
|
||||
if exit_status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
fn run_wasmi(data: &[u8]) -> Result<(), ()> {
|
||||
let _ = wasmi::Module::from_buffer(data).map_err(|_| ())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
let wasmi_result = run_wasmi(data);
|
||||
let wasm_result = run_spec(data);
|
||||
|
||||
assert_eq!(wasmi_result.is_ok(), wasm_result.is_ok());
|
||||
});
|
|
@ -0,0 +1,2 @@
|
|||
hfuzz_workspace/
|
||||
hfuzz_target/
|
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "hfuzz"
|
||||
version = "0.1.0"
|
||||
authors = ["Sergey Pepyakin <s.pepyakin@gmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
honggfuzz = "=0.5.9" # Strict equal since hfuzz requires dep and cmd versions to match.
|
||||
wasmi = { path = ".." }
|
||||
tempdir = "0.3.6"
|
||||
wabt = "0.9"
|
|
@ -0,0 +1,70 @@
|
|||
#[macro_use] extern crate honggfuzz;
|
||||
|
||||
extern crate wabt;
|
||||
extern crate wasmi;
|
||||
extern crate tempdir;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
fn dump_all_into_buf(src: &[u8], buf: &mut [u8; 64]) {
|
||||
let common_len = usize::min(src.len(), buf.len());
|
||||
buf[0..common_len].copy_from_slice(&src[0..common_len]);
|
||||
}
|
||||
|
||||
fn run_spec(data: &[u8], stdout_msg_buf: &mut [u8; 64], stderr_msg_buf: &mut [u8; 64]) -> Result<(), ()> {
|
||||
let temp_dir = tempdir::TempDir::new("spec").unwrap();
|
||||
let mut seed_path = temp_dir.path().to_path_buf();
|
||||
seed_path.push("test.wasm");
|
||||
|
||||
{
|
||||
let mut seedfile =
|
||||
File::create(&seed_path).expect("open temporary file for writing to store fuzzer input");
|
||||
seedfile.write_all(data).expect(
|
||||
"write fuzzer input to temporary file",
|
||||
);
|
||||
seedfile.flush().expect(
|
||||
"flush fuzzer input to temporary file before starting wasm-opt",
|
||||
);
|
||||
}
|
||||
|
||||
let output = Command::new("wasm")
|
||||
.arg("-d")
|
||||
.arg(&seed_path)
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.output()
|
||||
.expect("failed to execute `wasm`");
|
||||
|
||||
if output.status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
dump_all_into_buf(&output.stdout, stdout_msg_buf);
|
||||
dump_all_into_buf(&output.stderr, stderr_msg_buf);
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
fn run_wasmi(data: &[u8]) -> Result<(), ()> {
|
||||
let _ = wasmi::Module::from_buffer(data).map_err(|_| ())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
loop {
|
||||
fuzz!(|data: &[u8]| {
|
||||
// Keep messages on stack. This should lead to a different stack hashes for
|
||||
// different error messages.
|
||||
let mut stdout_msg_buf: [u8; 64] = [0; 64];
|
||||
let mut stderr_msg_buf: [u8; 64] = [0; 64];
|
||||
|
||||
let wasmi_result = run_wasmi(data);
|
||||
let wasm_result = run_spec(data, &mut stdout_msg_buf, &mut stderr_msg_buf);
|
||||
|
||||
if wasmi_result.is_ok() != wasm_result.is_ok() {
|
||||
panic!("stdout: {:?}, stderr: {:?}", &stdout_msg_buf[..], &stderr_msg_buf as &[u8]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash
|
||||
|
||||
export HFUZZ_RUN_ARGS="--max_file_size 2048"
|
||||
|
||||
die() { echo "$*"; exit 1; }
|
||||
|
||||
command -v wasm || die "spec interpreter 'wasm' is not on PATH";
|
||||
|
||||
rustup run nightly cargo hfuzz run hfuzz
|
|
@ -5,12 +5,12 @@ extern crate wasmi;
|
|||
|
||||
use std::env::args;
|
||||
use std::fs::File;
|
||||
use wasmi::{
|
||||
Error, FuncInstance, FuncRef, GlobalDescriptor, GlobalInstance, GlobalRef,
|
||||
ImportsBuilder, MemoryDescriptor, MemoryInstance, MemoryRef, Module,
|
||||
ModuleImportResolver, ModuleInstance, NopExternals, RuntimeValue, Signature,
|
||||
TableDescriptor, TableInstance, TableRef};
|
||||
use wasmi::memory_units::*;
|
||||
use wasmi::{
|
||||
Error, FuncInstance, FuncRef, GlobalDescriptor, GlobalInstance, GlobalRef, ImportsBuilder,
|
||||
MemoryDescriptor, MemoryInstance, MemoryRef, Module, ModuleImportResolver, ModuleInstance,
|
||||
NopExternals, RuntimeValue, Signature, TableDescriptor, TableInstance, TableRef,
|
||||
};
|
||||
|
||||
fn load_from_file(filename: &str) -> Module {
|
||||
use std::io::prelude::*;
|
||||
|
@ -46,7 +46,8 @@ impl ModuleImportResolver for ResolveAll {
|
|||
Ok(MemoryInstance::alloc(
|
||||
Pages(memory_type.initial() as usize),
|
||||
memory_type.maximum().map(|m| Pages(m as usize)),
|
||||
).unwrap())
|
||||
)
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
fn resolve_table(
|
||||
|
@ -75,7 +76,8 @@ fn main() {
|
|||
.with_resolver("global.Math", &ResolveAll)
|
||||
.with_resolver("asm2wasm", &ResolveAll)
|
||||
.with_resolver("spectest", &ResolveAll),
|
||||
).expect("Failed to instantiate module")
|
||||
)
|
||||
.expect("Failed to instantiate module")
|
||||
.run_start(&mut NopExternals)
|
||||
.expect("Failed to run start function in module");
|
||||
}
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
use parity_wasm::elements::BlockType;
|
||||
|
||||
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;
|
||||
|
||||
/// Control stack frame.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BlockFrame {
|
||||
/// Frame type.
|
||||
pub frame_type: BlockFrameType,
|
||||
/// A signature, which is a block signature type indicating the number and types of result values of the region.
|
||||
pub block_type: BlockType,
|
||||
/// A label for reference to block instruction.
|
||||
pub begin_position: usize,
|
||||
/// A label for reference from branch instructions.
|
||||
pub branch_position: usize,
|
||||
/// A label for reference from end instructions.
|
||||
pub end_position: usize,
|
||||
/// A limit integer value, which is an index into the value stack indicating where to reset it to on a branch to that label.
|
||||
pub value_stack_len: usize,
|
||||
/// Boolean which signals whether value stack became polymorphic. Value stack starts in non-polymorphic state and
|
||||
/// becomes polymorphic only after an instruction that never passes control further is executed,
|
||||
/// i.e. `unreachable`, `br` (but not `br_if`!), etc.
|
||||
pub polymorphic_stack: bool,
|
||||
}
|
||||
|
||||
/// Type of block frame.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum BlockFrameType {
|
||||
/// Function frame.
|
||||
Function,
|
||||
/// Usual block frame.
|
||||
Block,
|
||||
/// Loop frame (branching to the beginning of block).
|
||||
Loop,
|
||||
/// True-subblock of if expression.
|
||||
IfTrue,
|
||||
/// False-subblock of if expression.
|
||||
IfFalse,
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
|
||||
use std::collections::VecDeque;
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error(String);
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn description(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Stack with limit.
|
||||
#[derive(Debug)]
|
||||
pub struct StackWithLimit<T> where T: Clone {
|
||||
/// Stack values.
|
||||
values: VecDeque<T>,
|
||||
/// Stack limit (maximal stack len).
|
||||
limit: usize,
|
||||
}
|
||||
|
||||
impl<T> StackWithLimit<T> where T: Clone {
|
||||
pub fn with_limit(limit: usize) -> Self {
|
||||
StackWithLimit {
|
||||
values: VecDeque::new(),
|
||||
limit: limit
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.values.is_empty()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.values.len()
|
||||
}
|
||||
|
||||
pub fn limit(&self) -> usize {
|
||||
self.limit
|
||||
}
|
||||
|
||||
pub fn top(&self) -> Result<&T, Error> {
|
||||
self.values
|
||||
.back()
|
||||
.ok_or_else(|| Error("non-empty stack expected".into()))
|
||||
}
|
||||
|
||||
pub fn top_mut(&mut self) -> Result<&mut T, Error> {
|
||||
self.values
|
||||
.back_mut()
|
||||
.ok_or_else(|| Error("non-empty stack expected".into()))
|
||||
}
|
||||
|
||||
pub fn get(&self, index: usize) -> Result<&T, Error> {
|
||||
if index >= self.values.len() {
|
||||
return Err(Error(format!("trying to get value at position {} on stack of size {}", index, self.values.len())));
|
||||
}
|
||||
|
||||
Ok(self.values.get(self.values.len() - 1 - index).expect("checked couple of lines above"))
|
||||
}
|
||||
|
||||
pub fn push(&mut self, value: T) -> Result<(), Error> {
|
||||
if self.values.len() >= self.limit {
|
||||
return Err(Error(format!("exceeded stack limit {}", self.limit)));
|
||||
}
|
||||
|
||||
self.values.push_back(value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) -> Result<T, Error> {
|
||||
self.values
|
||||
.pop_back()
|
||||
.ok_or_else(|| Error("non-empty stack expected".into()))
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, new_size: usize, dummy: T) {
|
||||
debug_assert!(new_size <= self.values.len());
|
||||
self.values.resize(new_size, dummy);
|
||||
}
|
||||
}
|
235
src/func.rs
235
src/func.rs
|
@ -1,12 +1,17 @@
|
|||
use std::rc::{Rc, Weak};
|
||||
use std::fmt;
|
||||
use std::collections::HashMap;
|
||||
use parity_wasm::elements::{Local, Opcodes};
|
||||
use {Trap, TrapKind, Signature};
|
||||
use alloc::{
|
||||
borrow::Cow,
|
||||
rc::{Rc, Weak},
|
||||
vec::Vec,
|
||||
};
|
||||
use core::fmt;
|
||||
use host::Externals;
|
||||
use runner::{check_function_args, Interpreter};
|
||||
use value::RuntimeValue;
|
||||
use isa;
|
||||
use module::ModuleInstance;
|
||||
use parity_wasm::elements::Local;
|
||||
use runner::{check_function_args, Interpreter, InterpreterState, StackRecycler};
|
||||
use types::ValueType;
|
||||
use value::RuntimeValue;
|
||||
use {Signature, Trap};
|
||||
|
||||
/// Reference to a function (See [`FuncInstance`] for details).
|
||||
///
|
||||
|
@ -16,7 +21,7 @@ use module::ModuleInstance;
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct FuncRef(Rc<FuncInstance>);
|
||||
|
||||
impl ::std::ops::Deref for FuncRef {
|
||||
impl ::core::ops::Deref for FuncRef {
|
||||
type Target = FuncInstance;
|
||||
fn deref(&self) -> &FuncInstance {
|
||||
&self.0
|
||||
|
@ -25,7 +30,7 @@ impl ::std::ops::Deref for FuncRef {
|
|||
|
||||
/// Runtime representation of a function.
|
||||
///
|
||||
/// Functions are the unit of orgianization of code in WebAssembly. Each function takes a sequence of values
|
||||
/// Functions are the unit of organization of code in WebAssembly. Each function takes a sequence of values
|
||||
/// as parameters and either optionally return a value or trap.
|
||||
/// Functions can call other function including itself (i.e recursive calls are allowed) and imported functions
|
||||
/// (i.e functions defined in another module or by the host environment).
|
||||
|
@ -55,17 +60,10 @@ pub(crate) enum FuncInstanceInternal {
|
|||
impl fmt::Debug for FuncInstance {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.as_internal() {
|
||||
&FuncInstanceInternal::Internal {
|
||||
ref signature,
|
||||
..
|
||||
} => {
|
||||
&FuncInstanceInternal::Internal { ref signature, .. } => {
|
||||
// We can't write description of self.module here, because it generate
|
||||
// debug string for function instances and this will lead to infinite loop.
|
||||
write!(
|
||||
f,
|
||||
"Internal {{ signature={:?} }}",
|
||||
signature,
|
||||
)
|
||||
write!(f, "Internal {{ signature={:?} }}", signature,)
|
||||
}
|
||||
&FuncInstanceInternal::Host { ref signature, .. } => {
|
||||
write!(f, "Host {{ signature={:?} }}", signature)
|
||||
|
@ -141,11 +139,11 @@ impl FuncInstance {
|
|||
args: &[RuntimeValue],
|
||||
externals: &mut E,
|
||||
) -> Result<Option<RuntimeValue>, Trap> {
|
||||
check_function_args(func.signature(), &args).map_err(|_| TrapKind::UnexpectedSignature)?;
|
||||
check_function_args(func.signature(), &args)?;
|
||||
match *func.as_internal() {
|
||||
FuncInstanceInternal::Internal { .. } => {
|
||||
let mut interpreter = Interpreter::new(externals);
|
||||
interpreter.start_execution(func, args)
|
||||
let mut interpreter = Interpreter::new(func, args, None)?;
|
||||
interpreter.start_execution(externals)
|
||||
}
|
||||
FuncInstanceInternal::Host {
|
||||
ref host_func_index,
|
||||
|
@ -153,11 +151,202 @@ impl FuncInstance {
|
|||
} => externals.invoke_index(*host_func_index, args.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// Host trap happens, caller can use [`resume_execution`] to feed the expected return value back in, and then
|
||||
/// continue the execution.
|
||||
///
|
||||
/// This is an experimental API, and this functionality may not be available in other WebAssembly engines.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if `args` types is not match function [`signature`].
|
||||
///
|
||||
/// [`signature`]: #method.signature
|
||||
/// [`Trap`]: #enum.Trap.html
|
||||
/// [`start_execution`]: struct.FuncInvocation.html#method.start_execution
|
||||
/// [`resume_execution`]: struct.FuncInvocation.html#method.resume_execution
|
||||
pub fn invoke_resumable<'args>(
|
||||
func: &FuncRef,
|
||||
args: impl Into<Cow<'args, [RuntimeValue]>>,
|
||||
) -> Result<FuncInvocation<'args>, Trap> {
|
||||
let args = args.into();
|
||||
check_function_args(func.signature(), &args)?;
|
||||
match *func.as_internal() {
|
||||
FuncInstanceInternal::Internal { .. } => {
|
||||
let interpreter = Interpreter::new(func, &*args, None)?;
|
||||
Ok(FuncInvocation {
|
||||
kind: FuncInvocationKind::Internal(interpreter),
|
||||
})
|
||||
}
|
||||
FuncInstanceInternal::Host {
|
||||
ref host_func_index,
|
||||
..
|
||||
} => Ok(FuncInvocation {
|
||||
kind: FuncInvocationKind::Host {
|
||||
args,
|
||||
host_func_index: *host_func_index,
|
||||
finished: false,
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A resumable invocation error.
|
||||
#[derive(Debug)]
|
||||
pub enum ResumableError {
|
||||
/// Trap happened.
|
||||
Trap(Trap),
|
||||
/// The invocation is not resumable.
|
||||
///
|
||||
/// Invocations are only resumable if a host function is called, and the host function returns a trap of `Host` kind. For other cases, this error will be returned. This includes:
|
||||
/// - The invocation is directly a host function.
|
||||
/// - The invocation has not been started.
|
||||
/// - The invocation returns normally or returns any trap other than `Host` kind.
|
||||
///
|
||||
/// This error is returned by [`resume_execution`].
|
||||
///
|
||||
/// [`resume_execution`]: struct.FuncInvocation.html#method.resume_execution
|
||||
NotResumable,
|
||||
/// The invocation has already been started.
|
||||
///
|
||||
/// This error is returned by [`start_execution`].
|
||||
///
|
||||
/// [`start_execution`]: struct.FuncInvocation.html#method.start_execution
|
||||
AlreadyStarted,
|
||||
}
|
||||
|
||||
impl From<Trap> for ResumableError {
|
||||
fn from(trap: Trap) -> Self {
|
||||
ResumableError::Trap(trap)
|
||||
}
|
||||
}
|
||||
|
||||
/// A resumable invocation handle. This struct is returned by `FuncInstance::invoke_resumable`.
|
||||
pub struct FuncInvocation<'args> {
|
||||
kind: FuncInvocationKind<'args>,
|
||||
}
|
||||
|
||||
enum FuncInvocationKind<'args> {
|
||||
Internal(Interpreter),
|
||||
Host {
|
||||
args: Cow<'args, [RuntimeValue]>,
|
||||
host_func_index: usize,
|
||||
finished: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'args> FuncInvocation<'args> {
|
||||
/// Whether this invocation is currently resumable.
|
||||
pub fn is_resumable(&self) -> bool {
|
||||
match &self.kind {
|
||||
&FuncInvocationKind::Internal(ref interpreter) => interpreter.state().is_resumable(),
|
||||
&FuncInvocationKind::Host { .. } => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// If the invocation is resumable, the expected return value type to be feed back in.
|
||||
pub fn resumable_value_type(&self) -> Option<ValueType> {
|
||||
match &self.kind {
|
||||
&FuncInvocationKind::Internal(ref interpreter) => match interpreter.state() {
|
||||
&InterpreterState::Resumable(ref value_type) => value_type.clone(),
|
||||
_ => None,
|
||||
},
|
||||
&FuncInvocationKind::Host { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the invocation execution.
|
||||
pub fn start_execution<'externals, E: Externals + 'externals>(
|
||||
&mut self,
|
||||
externals: &'externals mut E,
|
||||
) -> Result<Option<RuntimeValue>, ResumableError> {
|
||||
match self.kind {
|
||||
FuncInvocationKind::Internal(ref mut interpreter) => {
|
||||
if interpreter.state() != &InterpreterState::Initialized {
|
||||
return Err(ResumableError::AlreadyStarted);
|
||||
}
|
||||
Ok(interpreter.start_execution(externals)?)
|
||||
}
|
||||
FuncInvocationKind::Host {
|
||||
ref args,
|
||||
ref mut finished,
|
||||
ref host_func_index,
|
||||
} => {
|
||||
if *finished {
|
||||
return Err(ResumableError::AlreadyStarted);
|
||||
}
|
||||
*finished = true;
|
||||
Ok(externals.invoke_index(*host_func_index, args.as_ref().into())?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resume an execution if a previous trap of Host kind happened.
|
||||
///
|
||||
/// `return_val` must be of the value type [`resumable_value_type`], defined by the host function import. Otherwise,
|
||||
/// `UnexpectedSignature` trap will be returned. The current invocation must also be resumable
|
||||
/// [`is_resumable`]. Otherwise, a `NotResumable` error will be returned.
|
||||
///
|
||||
/// [`resumable_value_type`]: #method.resumable_value_type
|
||||
/// [`is_resumable`]: #method.is_resumable
|
||||
pub fn resume_execution<'externals, E: Externals + 'externals>(
|
||||
&mut self,
|
||||
return_val: Option<RuntimeValue>,
|
||||
externals: &'externals mut E,
|
||||
) -> Result<Option<RuntimeValue>, ResumableError> {
|
||||
use crate::TrapKind;
|
||||
|
||||
if return_val.map(|v| v.value_type()) != self.resumable_value_type() {
|
||||
return Err(ResumableError::Trap(Trap::new(
|
||||
TrapKind::UnexpectedSignature,
|
||||
)));
|
||||
}
|
||||
|
||||
match &mut self.kind {
|
||||
FuncInvocationKind::Internal(interpreter) => {
|
||||
if interpreter.state().is_resumable() {
|
||||
Ok(interpreter.resume_execution(return_val, externals)?)
|
||||
} else {
|
||||
Err(ResumableError::AlreadyStarted)
|
||||
}
|
||||
}
|
||||
FuncInvocationKind::Host { .. } => Err(ResumableError::NotResumable),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FuncBody {
|
||||
pub locals: Vec<Local>,
|
||||
pub opcodes: Opcodes,
|
||||
pub labels: HashMap<usize, usize>,
|
||||
pub code: isa::Instructions,
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use std::rc::Rc;
|
||||
use std::cell::Cell;
|
||||
use alloc::rc::Rc;
|
||||
use core::cell::Cell;
|
||||
use parity_wasm::elements::ValueType as EValueType;
|
||||
use types::ValueType;
|
||||
use value::RuntimeValue;
|
||||
use Error;
|
||||
use types::ValueType;
|
||||
use parity_wasm::elements::{ValueType as EValueType};
|
||||
|
||||
/// Reference to a global variable (See [`GlobalInstance`] for details).
|
||||
///
|
||||
|
@ -13,7 +13,7 @@ use parity_wasm::elements::{ValueType as EValueType};
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct GlobalRef(Rc<GlobalInstance>);
|
||||
|
||||
impl ::std::ops::Deref for GlobalRef {
|
||||
impl ::core::ops::Deref for GlobalRef {
|
||||
type Target = GlobalInstance;
|
||||
fn deref(&self) -> &GlobalInstance {
|
||||
&self.0
|
||||
|
@ -57,7 +57,9 @@ impl GlobalInstance {
|
|||
/// type of `val` doesn't match global's type.
|
||||
pub fn set(&self, val: RuntimeValue) -> Result<(), Error> {
|
||||
if !self.mutable {
|
||||
return Err(Error::Global("Attempt to change an immutable variable".into()));
|
||||
return Err(Error::Global(
|
||||
"Attempt to change an immutable variable".into(),
|
||||
));
|
||||
}
|
||||
if self.value_type() != val.value_type() {
|
||||
return Err(Error::Global("Attempt to change variable type".into()));
|
||||
|
|
42
src/host.rs
42
src/host.rs
|
@ -1,6 +1,6 @@
|
|||
use std::any::TypeId;
|
||||
use value::{RuntimeValue, FromRuntimeValue};
|
||||
use {TrapKind, Trap};
|
||||
use core::any::TypeId;
|
||||
use value::{FromRuntimeValue, RuntimeValue};
|
||||
use {Trap, TrapKind};
|
||||
|
||||
/// Wrapper around slice of [`RuntimeValue`] for using it
|
||||
/// as an argument list conveniently.
|
||||
|
@ -27,8 +27,14 @@ impl<'a> RuntimeArgs<'a> {
|
|||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if cast is invalid or not enough arguments.
|
||||
pub fn nth_checked<T>(&self, idx: usize) -> Result<T, Trap> where T: FromRuntimeValue {
|
||||
Ok(self.nth_value_checked(idx)?.try_into().ok_or_else(|| TrapKind::UnexpectedSignature)?)
|
||||
pub fn nth_checked<T>(&self, idx: usize) -> Result<T, Trap>
|
||||
where
|
||||
T: FromRuntimeValue,
|
||||
{
|
||||
Ok(self
|
||||
.nth_value_checked(idx)?
|
||||
.try_into()
|
||||
.ok_or_else(|| TrapKind::UnexpectedSignature)?)
|
||||
}
|
||||
|
||||
/// Extract argument as a [`RuntimeValue`] by index `idx`.
|
||||
|
@ -50,7 +56,10 @@ impl<'a> RuntimeArgs<'a> {
|
|||
/// # Panics
|
||||
///
|
||||
/// Panics if cast is invalid or not enough arguments.
|
||||
pub fn nth<T>(&self, idx: usize) -> T where T: FromRuntimeValue {
|
||||
pub fn nth<T>(&self, idx: usize) -> T
|
||||
where
|
||||
T: FromRuntimeValue,
|
||||
{
|
||||
let value = self.nth_value_checked(idx).expect("Invalid argument index");
|
||||
value.try_into().expect("Unexpected argument type")
|
||||
}
|
||||
|
@ -98,18 +107,18 @@ impl<'a> RuntimeArgs<'a> {
|
|||
/// _ => panic!(),
|
||||
/// }
|
||||
/// ```
|
||||
pub trait HostError: 'static + ::std::fmt::Display + ::std::fmt::Debug + Send + Sync {
|
||||
pub trait HostError: 'static + ::core::fmt::Display + ::core::fmt::Debug + Send + Sync {
|
||||
#[doc(hidden)]
|
||||
fn __private_get_type_id__(&self) -> TypeId {
|
||||
TypeId::of::<Self>()
|
||||
}
|
||||
}
|
||||
|
||||
impl HostError {
|
||||
impl dyn HostError {
|
||||
/// Attempt to downcast this `HostError` to a concrete type by reference.
|
||||
pub fn downcast_ref<T: HostError>(&self) -> Option<&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 {
|
||||
None
|
||||
}
|
||||
|
@ -119,7 +128,7 @@ impl HostError {
|
|||
/// reference.
|
||||
pub fn downcast_mut<T: HostError>(&mut self) -> Option<&mut 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 {
|
||||
None
|
||||
}
|
||||
|
@ -190,9 +199,15 @@ impl HostError {
|
|||
/// }
|
||||
/// };
|
||||
///
|
||||
/// if !self.check_signature(index, signature) {
|
||||
/// return Err(Error::Instantiation(
|
||||
/// format!("Export {} has a bad signature", field_name)
|
||||
/// ));
|
||||
/// }
|
||||
///
|
||||
/// Ok(FuncInstance::alloc_host(
|
||||
/// Signature::new(&[ValueType::I32, ValueType::I32][..], Some(ValueType::I32)),
|
||||
/// ADD_FUNC_INDEX,
|
||||
/// index,
|
||||
/// ))
|
||||
/// }
|
||||
/// }
|
||||
|
@ -225,8 +240,8 @@ impl Externals for NopExternals {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::{HostError, RuntimeArgs};
|
||||
use value::RuntimeValue;
|
||||
use super::{RuntimeArgs, HostError};
|
||||
|
||||
#[test]
|
||||
fn i32_runtime_args() {
|
||||
|
@ -242,6 +257,5 @@ mod tests {
|
|||
}
|
||||
|
||||
// Tests that `HostError` trait is object safe.
|
||||
fn _host_error_is_object_safe(_: &HostError) {
|
||||
}
|
||||
fn _host_error_is_object_safe(_: &dyn HostError) {}
|
||||
}
|
||||
|
|
123
src/imports.rs
123
src/imports.rs
|
@ -1,13 +1,13 @@
|
|||
use std::collections::HashMap;
|
||||
use alloc::{collections::BTreeMap, string::String};
|
||||
|
||||
use func::FuncRef;
|
||||
use global::GlobalRef;
|
||||
use memory::MemoryRef;
|
||||
use func::FuncRef;
|
||||
use table::TableRef;
|
||||
use module::ModuleRef;
|
||||
use types::{GlobalDescriptor, TableDescriptor, MemoryDescriptor};
|
||||
use table::TableRef;
|
||||
use types::{GlobalDescriptor, MemoryDescriptor, TableDescriptor};
|
||||
use {Error, Signature};
|
||||
|
||||
|
||||
/// Resolver of a module's dependencies.
|
||||
///
|
||||
/// A module have dependencies in a form of a list of imports (i.e.
|
||||
|
@ -20,7 +20,6 @@ use {Error, Signature};
|
|||
///
|
||||
/// [`ImportsBuilder`]: struct.ImportsBuilder.html
|
||||
pub trait ImportResolver {
|
||||
|
||||
/// Resolve a function.
|
||||
///
|
||||
/// Returned function should match given `signature`, i.e. all parameter types and return value should have exact match.
|
||||
|
@ -101,7 +100,7 @@ pub trait ImportResolver {
|
|||
/// [`ImportResolver`]: trait.ImportResolver.html
|
||||
/// [`ModuleImportResolver`]: trait.ModuleImportResolver.html
|
||||
pub struct ImportsBuilder<'a> {
|
||||
modules: HashMap<String, &'a ModuleImportResolver>,
|
||||
modules: BTreeMap<String, &'a dyn ModuleImportResolver>,
|
||||
}
|
||||
|
||||
impl<'a> Default for ImportsBuilder<'a> {
|
||||
|
@ -113,14 +112,16 @@ impl<'a> Default for ImportsBuilder<'a> {
|
|||
impl<'a> ImportsBuilder<'a> {
|
||||
/// Create an empty `ImportsBuilder`.
|
||||
pub fn new() -> ImportsBuilder<'a> {
|
||||
ImportsBuilder { modules: HashMap::new() }
|
||||
ImportsBuilder {
|
||||
modules: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Register an resolver by a name.
|
||||
pub fn with_resolver<N: Into<String>>(
|
||||
mut self,
|
||||
name: N,
|
||||
resolver: &'a ModuleImportResolver,
|
||||
resolver: &'a dyn ModuleImportResolver,
|
||||
) -> Self {
|
||||
self.modules.insert(name.into(), resolver);
|
||||
self
|
||||
|
@ -129,11 +130,15 @@ impl<'a> ImportsBuilder<'a> {
|
|||
/// Register an resolver by a name.
|
||||
///
|
||||
/// 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);
|
||||
}
|
||||
|
||||
fn resolver(&self, name: &str) -> Option<&ModuleImportResolver> {
|
||||
fn resolver(&self, name: &str) -> Option<&dyn ModuleImportResolver> {
|
||||
self.modules.get(name).cloned()
|
||||
}
|
||||
}
|
||||
|
@ -145,9 +150,9 @@ impl<'a> ImportResolver for ImportsBuilder<'a> {
|
|||
field_name: &str,
|
||||
signature: &Signature,
|
||||
) -> Result<FuncRef, Error> {
|
||||
self.resolver(module_name).ok_or_else(||
|
||||
Error::Instantiation(format!("Module {} not found", module_name))
|
||||
)?.resolve_func(field_name, signature)
|
||||
self.resolver(module_name)
|
||||
.ok_or_else(|| Error::Instantiation(format!("Module {} not found", module_name)))?
|
||||
.resolve_func(field_name, signature)
|
||||
}
|
||||
|
||||
fn resolve_global(
|
||||
|
@ -156,9 +161,9 @@ impl<'a> ImportResolver for ImportsBuilder<'a> {
|
|||
field_name: &str,
|
||||
global_type: &GlobalDescriptor,
|
||||
) -> Result<GlobalRef, Error> {
|
||||
self.resolver(module_name).ok_or_else(||
|
||||
Error::Instantiation(format!("Module {} not found", module_name))
|
||||
)?.resolve_global(field_name, global_type)
|
||||
self.resolver(module_name)
|
||||
.ok_or_else(|| Error::Instantiation(format!("Module {} not found", module_name)))?
|
||||
.resolve_global(field_name, global_type)
|
||||
}
|
||||
|
||||
fn resolve_memory(
|
||||
|
@ -167,9 +172,9 @@ impl<'a> ImportResolver for ImportsBuilder<'a> {
|
|||
field_name: &str,
|
||||
memory_type: &MemoryDescriptor,
|
||||
) -> Result<MemoryRef, Error> {
|
||||
self.resolver(module_name).ok_or_else(||
|
||||
Error::Instantiation(format!("Module {} not found", module_name))
|
||||
)?.resolve_memory(field_name, memory_type)
|
||||
self.resolver(module_name)
|
||||
.ok_or_else(|| Error::Instantiation(format!("Module {} not found", module_name)))?
|
||||
.resolve_memory(field_name, memory_type)
|
||||
}
|
||||
|
||||
fn resolve_table(
|
||||
|
@ -178,9 +183,9 @@ impl<'a> ImportResolver for ImportsBuilder<'a> {
|
|||
field_name: &str,
|
||||
table_type: &TableDescriptor,
|
||||
) -> Result<TableRef, Error> {
|
||||
self.resolver(module_name).ok_or_else(||
|
||||
Error::Instantiation(format!("Module {} not found", module_name))
|
||||
)?.resolve_table(field_name, table_type)
|
||||
self.resolver(module_name)
|
||||
.ok_or_else(|| Error::Instantiation(format!("Module {} not found", module_name)))?
|
||||
.resolve_table(field_name, table_type)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -193,14 +198,11 @@ pub trait ModuleImportResolver {
|
|||
/// See [`ImportResolver::resolve_func`] for details.
|
||||
///
|
||||
/// [`ImportResolver::resolve_func`]: trait.ImportResolver.html#tymethod.resolve_func
|
||||
fn resolve_func(
|
||||
&self,
|
||||
field_name: &str,
|
||||
_signature: &Signature,
|
||||
) -> Result<FuncRef, Error> {
|
||||
Err(Error::Instantiation(
|
||||
format!("Export {} not found", field_name),
|
||||
))
|
||||
fn resolve_func(&self, field_name: &str, _signature: &Signature) -> Result<FuncRef, Error> {
|
||||
Err(Error::Instantiation(format!(
|
||||
"Export {} not found",
|
||||
field_name
|
||||
)))
|
||||
}
|
||||
|
||||
/// Resolve a global variable.
|
||||
|
@ -213,9 +215,10 @@ pub trait ModuleImportResolver {
|
|||
field_name: &str,
|
||||
_global_type: &GlobalDescriptor,
|
||||
) -> Result<GlobalRef, Error> {
|
||||
Err(Error::Instantiation(
|
||||
format!("Export {} not found", field_name),
|
||||
))
|
||||
Err(Error::Instantiation(format!(
|
||||
"Export {} not found",
|
||||
field_name
|
||||
)))
|
||||
}
|
||||
|
||||
/// Resolve a memory.
|
||||
|
@ -228,9 +231,10 @@ pub trait ModuleImportResolver {
|
|||
field_name: &str,
|
||||
_memory_type: &MemoryDescriptor,
|
||||
) -> Result<MemoryRef, Error> {
|
||||
Err(Error::Instantiation(
|
||||
format!("Export {} not found", field_name),
|
||||
))
|
||||
Err(Error::Instantiation(format!(
|
||||
"Export {} not found",
|
||||
field_name
|
||||
)))
|
||||
}
|
||||
|
||||
/// Resolve a table.
|
||||
|
@ -243,22 +247,18 @@ pub trait ModuleImportResolver {
|
|||
field_name: &str,
|
||||
_table_type: &TableDescriptor,
|
||||
) -> Result<TableRef, Error> {
|
||||
Err(Error::Instantiation(
|
||||
format!("Export {} not found", field_name),
|
||||
))
|
||||
Err(Error::Instantiation(format!(
|
||||
"Export {} not found",
|
||||
field_name
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleImportResolver for ModuleRef {
|
||||
fn resolve_func(
|
||||
&self,
|
||||
field_name: &str,
|
||||
_signature: &Signature,
|
||||
) -> Result<FuncRef, Error> {
|
||||
Ok(self.export_by_name(field_name)
|
||||
.ok_or_else(|| {
|
||||
Error::Instantiation(format!("Export {} not found", field_name))
|
||||
})?
|
||||
fn resolve_func(&self, field_name: &str, _signature: &Signature) -> Result<FuncRef, Error> {
|
||||
Ok(self
|
||||
.export_by_name(field_name)
|
||||
.ok_or_else(|| Error::Instantiation(format!("Export {} not found", field_name)))?
|
||||
.as_func()
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
|
@ -271,10 +271,9 @@ impl ModuleImportResolver for ModuleRef {
|
|||
field_name: &str,
|
||||
_global_type: &GlobalDescriptor,
|
||||
) -> Result<GlobalRef, Error> {
|
||||
Ok(self.export_by_name(field_name)
|
||||
.ok_or_else(|| {
|
||||
Error::Instantiation(format!("Export {} not found", field_name))
|
||||
})?
|
||||
Ok(self
|
||||
.export_by_name(field_name)
|
||||
.ok_or_else(|| Error::Instantiation(format!("Export {} not found", field_name)))?
|
||||
.as_global()
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
|
@ -287,10 +286,9 @@ impl ModuleImportResolver for ModuleRef {
|
|||
field_name: &str,
|
||||
_memory_type: &MemoryDescriptor,
|
||||
) -> Result<MemoryRef, Error> {
|
||||
Ok(self.export_by_name(field_name)
|
||||
.ok_or_else(|| {
|
||||
Error::Instantiation(format!("Export {} not found", field_name))
|
||||
})?
|
||||
Ok(self
|
||||
.export_by_name(field_name)
|
||||
.ok_or_else(|| Error::Instantiation(format!("Export {} not found", field_name)))?
|
||||
.as_memory()
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
|
@ -303,14 +301,11 @@ impl ModuleImportResolver for ModuleRef {
|
|||
field_name: &str,
|
||||
_table_type: &TableDescriptor,
|
||||
) -> Result<TableRef, Error> {
|
||||
Ok(self.export_by_name(field_name)
|
||||
.ok_or_else(|| {
|
||||
Error::Instantiation(format!("Export {} not found", field_name))
|
||||
})?
|
||||
Ok(self
|
||||
.export_by_name(field_name)
|
||||
.ok_or_else(|| Error::Instantiation(format!("Export {} not found", field_name)))?
|
||||
.as_table()
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
Error::Instantiation(format!("Export {} is not a table", field_name))
|
||||
})?)
|
||||
.ok_or_else(|| Error::Instantiation(format!("Export {} is not a table", field_name)))?)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,802 @@
|
|||
//! An instruction set used by wasmi.
|
||||
//!
|
||||
//! The instruction set is mostly derived from Wasm. However,
|
||||
//! there is a substantial difference.
|
||||
//!
|
||||
//! # Structured Stack Machine vs Plain One
|
||||
//!
|
||||
//! Wasm is a structured stack machine. Wasm encodes control flow in structures
|
||||
//! similar to that commonly found in a programming languages
|
||||
//! such as if, while. That contrasts to a plain stack machine which
|
||||
//! encodes all control flow with goto-like instructions.
|
||||
//!
|
||||
//! Structured stack machine code aligns well with goals of Wasm,
|
||||
//! namely providing fast validation of Wasm code and compilation to native code.
|
||||
//!
|
||||
//! Unfortunately, the downside of structured stack machine code is
|
||||
//! that it is less convenient to interpret. For example, let's look at
|
||||
//! the following example in hypothetical structured stack machine:
|
||||
//!
|
||||
//! ```plain
|
||||
//! loop
|
||||
//! ...
|
||||
//! if_true_jump_to_end
|
||||
//! ...
|
||||
//! end
|
||||
//! ```
|
||||
//!
|
||||
//! To execute `if_true_jump_to_end` , the interpreter needs to skip all instructions
|
||||
//! until it reaches the *matching* `end`. That's quite inefficient compared
|
||||
//! to a plain goto to the specific position.
|
||||
//!
|
||||
//! Because of this, the translation from the Wasm structured stack machine into a
|
||||
//! plain one is taking place.
|
||||
//!
|
||||
//! # Locals
|
||||
//!
|
||||
//! In a plain stack machine local variables and arguments live on the stack. Instead of
|
||||
//! accessing predefined locals slots in a plain stack machine locals are addressed relative
|
||||
//! to the current stack pointer. Because of this instead of taking an index of a local
|
||||
//! in {get,set,tee}_local operations, they take a relative depth as immediate. This works
|
||||
//! because at each instruction we always know the current stack height.
|
||||
//!
|
||||
//! Roughly, the stack layout looks like this
|
||||
//!
|
||||
//! | caller arguments |
|
||||
//! | - arg 1 |
|
||||
//! | - arg 2 |
|
||||
//! +------------------+
|
||||
//! | callee locals |
|
||||
//! | - var 1 |
|
||||
//! | - var 2 |
|
||||
//! +------------------+
|
||||
//! | operands |
|
||||
//! | - op 1 |
|
||||
//! | - op 2 |
|
||||
//! | | <-- current stack pointer
|
||||
//! +------------------+
|
||||
//!
|
||||
//! # Differences from Wasm
|
||||
//!
|
||||
//! - There is no `nop` instruction.
|
||||
//! - All control flow structures are flattened to plain gotos.
|
||||
//! - Implicit returns via reaching function scope `End` are replaced with an explicit `return` instruction.
|
||||
//! - Locals live on the value stack now.
|
||||
//! - Load/store instructions doesn't take `align` parameter.
|
||||
//! - *.const store value in straight encoding.
|
||||
//! - Reserved immediates are ignored for `call_indirect`, `current_memory`, `grow_memory`.
|
||||
//!
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
/// Should we keep a value before "discarding" a stack frame?
|
||||
///
|
||||
/// Note that this is a `enum` since Wasm doesn't support multiple return
|
||||
/// values at the moment.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Keep {
|
||||
None,
|
||||
/// Pop one value from the yet-to-be-discarded stack frame to the
|
||||
/// current stack frame.
|
||||
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.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct DropKeep {
|
||||
pub drop: u32,
|
||||
pub keep: Keep,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Target {
|
||||
pub dst_pc: u32,
|
||||
pub drop_keep: DropKeep,
|
||||
}
|
||||
|
||||
/// A relocation entry that specifies.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Reloc {
|
||||
/// Patch the destination of the branch instruction (br, br_eqz, br_nez)
|
||||
/// at the specified pc.
|
||||
Br { pc: u32 },
|
||||
/// Patch the specified destination index inside of br_table instruction at
|
||||
/// the specified pc.
|
||||
BrTable { pc: u32, idx: usize },
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct BrTargets<'a> {
|
||||
stream: &'a [InstructionInternal],
|
||||
}
|
||||
|
||||
impl<'a> BrTargets<'a> {
|
||||
pub(crate) fn from_internal(targets: &'a [InstructionInternal]) -> Self {
|
||||
BrTargets { stream: targets }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(&self, index: u32) -> Target {
|
||||
match self.stream[index.min(self.stream.len() as u32 - 1) as usize] {
|
||||
InstructionInternal::BrTableTarget(target) => target,
|
||||
_ => panic!("BrTable has incorrect target count"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The main interpreted instruction type. This is what is returned by `InstructionIter`, but
|
||||
/// it is not what is stored internally. For that, see `InstructionInternal`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Instruction<'a> {
|
||||
/// Push a local variable or an argument from the specified depth.
|
||||
GetLocal(u32),
|
||||
|
||||
/// Pop a value and put it in at the specified depth.
|
||||
SetLocal(u32),
|
||||
|
||||
/// Copy a value to the specified depth.
|
||||
TeeLocal(u32),
|
||||
|
||||
/// Similar to the Wasm ones, but instead of a label depth
|
||||
/// they specify direct PC.
|
||||
Br(Target),
|
||||
BrIfEqz(Target),
|
||||
BrIfNez(Target),
|
||||
|
||||
/// br_table [t1 t2 t3 .. tn] tdefault
|
||||
///
|
||||
/// Pops the value from the stack. Then this value is used as an index
|
||||
/// to the branch table.
|
||||
///
|
||||
/// However, the last target represents the default target. So if the index
|
||||
/// is greater than length of the branch table, then the last index will be used.
|
||||
///
|
||||
/// Validation ensures that there should be at least one target.
|
||||
BrTable(BrTargets<'a>),
|
||||
|
||||
Unreachable,
|
||||
Return(DropKeep),
|
||||
|
||||
Call(u32),
|
||||
CallIndirect(u32),
|
||||
|
||||
Drop,
|
||||
Select,
|
||||
|
||||
GetGlobal(u32),
|
||||
SetGlobal(u32),
|
||||
|
||||
I32Load(u32),
|
||||
I64Load(u32),
|
||||
F32Load(u32),
|
||||
F64Load(u32),
|
||||
I32Load8S(u32),
|
||||
I32Load8U(u32),
|
||||
I32Load16S(u32),
|
||||
I32Load16U(u32),
|
||||
I64Load8S(u32),
|
||||
I64Load8U(u32),
|
||||
I64Load16S(u32),
|
||||
I64Load16U(u32),
|
||||
I64Load32S(u32),
|
||||
I64Load32U(u32),
|
||||
I32Store(u32),
|
||||
I64Store(u32),
|
||||
F32Store(u32),
|
||||
F64Store(u32),
|
||||
I32Store8(u32),
|
||||
I32Store16(u32),
|
||||
I64Store8(u32),
|
||||
I64Store16(u32),
|
||||
I64Store32(u32),
|
||||
|
||||
CurrentMemory,
|
||||
GrowMemory,
|
||||
|
||||
I32Const(i32),
|
||||
I64Const(i64),
|
||||
F32Const(u32),
|
||||
F64Const(u64),
|
||||
|
||||
I32Eqz,
|
||||
I32Eq,
|
||||
I32Ne,
|
||||
I32LtS,
|
||||
I32LtU,
|
||||
I32GtS,
|
||||
I32GtU,
|
||||
I32LeS,
|
||||
I32LeU,
|
||||
I32GeS,
|
||||
I32GeU,
|
||||
|
||||
I64Eqz,
|
||||
I64Eq,
|
||||
I64Ne,
|
||||
I64LtS,
|
||||
I64LtU,
|
||||
I64GtS,
|
||||
I64GtU,
|
||||
I64LeS,
|
||||
I64LeU,
|
||||
I64GeS,
|
||||
I64GeU,
|
||||
|
||||
F32Eq,
|
||||
F32Ne,
|
||||
F32Lt,
|
||||
F32Gt,
|
||||
F32Le,
|
||||
F32Ge,
|
||||
|
||||
F64Eq,
|
||||
F64Ne,
|
||||
F64Lt,
|
||||
F64Gt,
|
||||
F64Le,
|
||||
F64Ge,
|
||||
|
||||
I32Clz,
|
||||
I32Ctz,
|
||||
I32Popcnt,
|
||||
I32Add,
|
||||
I32Sub,
|
||||
I32Mul,
|
||||
I32DivS,
|
||||
I32DivU,
|
||||
I32RemS,
|
||||
I32RemU,
|
||||
I32And,
|
||||
I32Or,
|
||||
I32Xor,
|
||||
I32Shl,
|
||||
I32ShrS,
|
||||
I32ShrU,
|
||||
I32Rotl,
|
||||
I32Rotr,
|
||||
|
||||
I64Clz,
|
||||
I64Ctz,
|
||||
I64Popcnt,
|
||||
I64Add,
|
||||
I64Sub,
|
||||
I64Mul,
|
||||
I64DivS,
|
||||
I64DivU,
|
||||
I64RemS,
|
||||
I64RemU,
|
||||
I64And,
|
||||
I64Or,
|
||||
I64Xor,
|
||||
I64Shl,
|
||||
I64ShrS,
|
||||
I64ShrU,
|
||||
I64Rotl,
|
||||
I64Rotr,
|
||||
F32Abs,
|
||||
F32Neg,
|
||||
F32Ceil,
|
||||
F32Floor,
|
||||
F32Trunc,
|
||||
F32Nearest,
|
||||
F32Sqrt,
|
||||
F32Add,
|
||||
F32Sub,
|
||||
F32Mul,
|
||||
F32Div,
|
||||
F32Min,
|
||||
F32Max,
|
||||
F32Copysign,
|
||||
F64Abs,
|
||||
F64Neg,
|
||||
F64Ceil,
|
||||
F64Floor,
|
||||
F64Trunc,
|
||||
F64Nearest,
|
||||
F64Sqrt,
|
||||
F64Add,
|
||||
F64Sub,
|
||||
F64Mul,
|
||||
F64Div,
|
||||
F64Min,
|
||||
F64Max,
|
||||
F64Copysign,
|
||||
|
||||
I32WrapI64,
|
||||
I32TruncSF32,
|
||||
I32TruncUF32,
|
||||
I32TruncSF64,
|
||||
I32TruncUF64,
|
||||
I64ExtendSI32,
|
||||
I64ExtendUI32,
|
||||
I64TruncSF32,
|
||||
I64TruncUF32,
|
||||
I64TruncSF64,
|
||||
I64TruncUF64,
|
||||
F32ConvertSI32,
|
||||
F32ConvertUI32,
|
||||
F32ConvertSI64,
|
||||
F32ConvertUI64,
|
||||
F32DemoteF64,
|
||||
F64ConvertSI32,
|
||||
F64ConvertUI32,
|
||||
F64ConvertSI64,
|
||||
F64ConvertUI64,
|
||||
F64PromoteF32,
|
||||
|
||||
I32ReinterpretF32,
|
||||
I64ReinterpretF64,
|
||||
F32ReinterpretI32,
|
||||
F64ReinterpretI64,
|
||||
}
|
||||
|
||||
/// The internally-stored instruction type. This differs from `Instruction` in that the `BrTable`
|
||||
/// target list is "unrolled" into seperate instructions in order to be able to A) improve cache
|
||||
/// usage and B) allow this struct to be `Copy` and therefore allow `Instructions::clone` to be
|
||||
/// a `memcpy`. It also means that `Instructions::drop` is trivial. The overall speedup on some
|
||||
/// benchmarks is as high as 13%.
|
||||
///
|
||||
/// When returning instructions we convert to `Instruction`, whose `BrTable` variant internally
|
||||
/// borrows the list of instructions and returns targets by reading it.
|
||||
#[derive(Copy, Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) enum InstructionInternal {
|
||||
GetLocal(u32),
|
||||
SetLocal(u32),
|
||||
TeeLocal(u32),
|
||||
Br(Target),
|
||||
BrIfEqz(Target),
|
||||
BrIfNez(Target),
|
||||
BrTable { count: u32 },
|
||||
BrTableTarget(Target),
|
||||
|
||||
Unreachable,
|
||||
Return(DropKeep),
|
||||
|
||||
Call(u32),
|
||||
CallIndirect(u32),
|
||||
|
||||
Drop,
|
||||
Select,
|
||||
|
||||
GetGlobal(u32),
|
||||
SetGlobal(u32),
|
||||
|
||||
I32Load(u32),
|
||||
I64Load(u32),
|
||||
F32Load(u32),
|
||||
F64Load(u32),
|
||||
I32Load8S(u32),
|
||||
I32Load8U(u32),
|
||||
I32Load16S(u32),
|
||||
I32Load16U(u32),
|
||||
I64Load8S(u32),
|
||||
I64Load8U(u32),
|
||||
I64Load16S(u32),
|
||||
I64Load16U(u32),
|
||||
I64Load32S(u32),
|
||||
I64Load32U(u32),
|
||||
I32Store(u32),
|
||||
I64Store(u32),
|
||||
F32Store(u32),
|
||||
F64Store(u32),
|
||||
I32Store8(u32),
|
||||
I32Store16(u32),
|
||||
I64Store8(u32),
|
||||
I64Store16(u32),
|
||||
I64Store32(u32),
|
||||
|
||||
CurrentMemory,
|
||||
GrowMemory,
|
||||
|
||||
I32Const(i32),
|
||||
I64Const(i64),
|
||||
F32Const(u32),
|
||||
F64Const(u64),
|
||||
|
||||
I32Eqz,
|
||||
I32Eq,
|
||||
I32Ne,
|
||||
I32LtS,
|
||||
I32LtU,
|
||||
I32GtS,
|
||||
I32GtU,
|
||||
I32LeS,
|
||||
I32LeU,
|
||||
I32GeS,
|
||||
I32GeU,
|
||||
|
||||
I64Eqz,
|
||||
I64Eq,
|
||||
I64Ne,
|
||||
I64LtS,
|
||||
I64LtU,
|
||||
I64GtS,
|
||||
I64GtU,
|
||||
I64LeS,
|
||||
I64LeU,
|
||||
I64GeS,
|
||||
I64GeU,
|
||||
|
||||
F32Eq,
|
||||
F32Ne,
|
||||
F32Lt,
|
||||
F32Gt,
|
||||
F32Le,
|
||||
F32Ge,
|
||||
|
||||
F64Eq,
|
||||
F64Ne,
|
||||
F64Lt,
|
||||
F64Gt,
|
||||
F64Le,
|
||||
F64Ge,
|
||||
|
||||
I32Clz,
|
||||
I32Ctz,
|
||||
I32Popcnt,
|
||||
I32Add,
|
||||
I32Sub,
|
||||
I32Mul,
|
||||
I32DivS,
|
||||
I32DivU,
|
||||
I32RemS,
|
||||
I32RemU,
|
||||
I32And,
|
||||
I32Or,
|
||||
I32Xor,
|
||||
I32Shl,
|
||||
I32ShrS,
|
||||
I32ShrU,
|
||||
I32Rotl,
|
||||
I32Rotr,
|
||||
|
||||
I64Clz,
|
||||
I64Ctz,
|
||||
I64Popcnt,
|
||||
I64Add,
|
||||
I64Sub,
|
||||
I64Mul,
|
||||
I64DivS,
|
||||
I64DivU,
|
||||
I64RemS,
|
||||
I64RemU,
|
||||
I64And,
|
||||
I64Or,
|
||||
I64Xor,
|
||||
I64Shl,
|
||||
I64ShrS,
|
||||
I64ShrU,
|
||||
I64Rotl,
|
||||
I64Rotr,
|
||||
F32Abs,
|
||||
F32Neg,
|
||||
F32Ceil,
|
||||
F32Floor,
|
||||
F32Trunc,
|
||||
F32Nearest,
|
||||
F32Sqrt,
|
||||
F32Add,
|
||||
F32Sub,
|
||||
F32Mul,
|
||||
F32Div,
|
||||
F32Min,
|
||||
F32Max,
|
||||
F32Copysign,
|
||||
F64Abs,
|
||||
F64Neg,
|
||||
F64Ceil,
|
||||
F64Floor,
|
||||
F64Trunc,
|
||||
F64Nearest,
|
||||
F64Sqrt,
|
||||
F64Add,
|
||||
F64Sub,
|
||||
F64Mul,
|
||||
F64Div,
|
||||
F64Min,
|
||||
F64Max,
|
||||
F64Copysign,
|
||||
|
||||
I32WrapI64,
|
||||
I32TruncSF32,
|
||||
I32TruncUF32,
|
||||
I32TruncSF64,
|
||||
I32TruncUF64,
|
||||
I64ExtendSI32,
|
||||
I64ExtendUI32,
|
||||
I64TruncSF32,
|
||||
I64TruncUF32,
|
||||
I64TruncSF64,
|
||||
I64TruncUF64,
|
||||
F32ConvertSI32,
|
||||
F32ConvertUI32,
|
||||
F32ConvertSI64,
|
||||
F32ConvertUI64,
|
||||
F32DemoteF64,
|
||||
F64ConvertSI32,
|
||||
F64ConvertUI32,
|
||||
F64ConvertSI64,
|
||||
F64ConvertUI64,
|
||||
F64PromoteF32,
|
||||
|
||||
I32ReinterpretF32,
|
||||
I64ReinterpretF64,
|
||||
F32ReinterpretI32,
|
||||
F64ReinterpretI64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Instructions {
|
||||
vec: Vec<InstructionInternal>,
|
||||
}
|
||||
|
||||
impl Instructions {
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Instructions {
|
||||
vec: Vec::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current_pc(&self) -> u32 {
|
||||
self.vec.len() as u32
|
||||
}
|
||||
|
||||
pub(crate) fn push(&mut self, instruction: InstructionInternal) {
|
||||
self.vec.push(instruction);
|
||||
}
|
||||
|
||||
pub fn patch_relocation(&mut self, reloc: Reloc, dst_pc: u32) {
|
||||
match reloc {
|
||||
Reloc::Br { pc } => match self.vec[pc as usize] {
|
||||
InstructionInternal::Br(ref mut target)
|
||||
| InstructionInternal::BrIfEqz(ref mut target)
|
||||
| InstructionInternal::BrIfNez(ref mut target) => target.dst_pc = dst_pc,
|
||||
_ => panic!("branch relocation points to a non-branch instruction"),
|
||||
},
|
||||
Reloc::BrTable { pc, idx } => match &mut self.vec[pc as usize + idx + 1] {
|
||||
InstructionInternal::BrTableTarget(target) => target.dst_pc = dst_pc,
|
||||
_ => panic!("brtable relocation points to not brtable instruction"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iterate_from(&self, position: u32) -> InstructionIter {
|
||||
InstructionIter {
|
||||
instructions: &self.vec,
|
||||
position,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InstructionIter<'a> {
|
||||
instructions: &'a [InstructionInternal],
|
||||
position: u32,
|
||||
}
|
||||
|
||||
impl<'a> InstructionIter<'a> {
|
||||
#[inline]
|
||||
pub fn position(&self) -> u32 {
|
||||
self.position
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for InstructionIter<'a> {
|
||||
type Item = Instruction<'a>;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let internal = if let Some(i) = self.instructions.get(self.position as usize) {
|
||||
i
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let out = match *internal {
|
||||
InstructionInternal::GetLocal(x) => Instruction::GetLocal(x),
|
||||
InstructionInternal::SetLocal(x) => Instruction::SetLocal(x),
|
||||
InstructionInternal::TeeLocal(x) => Instruction::TeeLocal(x),
|
||||
InstructionInternal::Br(x) => Instruction::Br(x),
|
||||
InstructionInternal::BrIfEqz(x) => Instruction::BrIfEqz(x),
|
||||
InstructionInternal::BrIfNez(x) => Instruction::BrIfNez(x),
|
||||
InstructionInternal::BrTable { count } => {
|
||||
let start = self.position as usize + 1;
|
||||
|
||||
self.position += count;
|
||||
|
||||
Instruction::BrTable(BrTargets::from_internal(
|
||||
&self.instructions[start..start + count as usize],
|
||||
))
|
||||
}
|
||||
InstructionInternal::BrTableTarget(_) => panic!("Executed BrTableTarget"),
|
||||
|
||||
InstructionInternal::Unreachable => Instruction::Unreachable,
|
||||
InstructionInternal::Return(x) => Instruction::Return(x),
|
||||
|
||||
InstructionInternal::Call(x) => Instruction::Call(x),
|
||||
InstructionInternal::CallIndirect(x) => Instruction::CallIndirect(x),
|
||||
|
||||
InstructionInternal::Drop => Instruction::Drop,
|
||||
InstructionInternal::Select => Instruction::Select,
|
||||
|
||||
InstructionInternal::GetGlobal(x) => Instruction::GetGlobal(x),
|
||||
InstructionInternal::SetGlobal(x) => Instruction::SetGlobal(x),
|
||||
|
||||
InstructionInternal::I32Load(x) => Instruction::I32Load(x),
|
||||
InstructionInternal::I64Load(x) => Instruction::I64Load(x),
|
||||
InstructionInternal::F32Load(x) => Instruction::F32Load(x),
|
||||
InstructionInternal::F64Load(x) => Instruction::F64Load(x),
|
||||
InstructionInternal::I32Load8S(x) => Instruction::I32Load8S(x),
|
||||
InstructionInternal::I32Load8U(x) => Instruction::I32Load8U(x),
|
||||
InstructionInternal::I32Load16S(x) => Instruction::I32Load16S(x),
|
||||
InstructionInternal::I32Load16U(x) => Instruction::I32Load16U(x),
|
||||
InstructionInternal::I64Load8S(x) => Instruction::I64Load8S(x),
|
||||
InstructionInternal::I64Load8U(x) => Instruction::I64Load8U(x),
|
||||
InstructionInternal::I64Load16S(x) => Instruction::I64Load16S(x),
|
||||
InstructionInternal::I64Load16U(x) => Instruction::I64Load16U(x),
|
||||
InstructionInternal::I64Load32S(x) => Instruction::I64Load32S(x),
|
||||
InstructionInternal::I64Load32U(x) => Instruction::I64Load32U(x),
|
||||
InstructionInternal::I32Store(x) => Instruction::I32Store(x),
|
||||
InstructionInternal::I64Store(x) => Instruction::I64Store(x),
|
||||
InstructionInternal::F32Store(x) => Instruction::F32Store(x),
|
||||
InstructionInternal::F64Store(x) => Instruction::F64Store(x),
|
||||
InstructionInternal::I32Store8(x) => Instruction::I32Store8(x),
|
||||
InstructionInternal::I32Store16(x) => Instruction::I32Store16(x),
|
||||
InstructionInternal::I64Store8(x) => Instruction::I64Store8(x),
|
||||
InstructionInternal::I64Store16(x) => Instruction::I64Store16(x),
|
||||
InstructionInternal::I64Store32(x) => Instruction::I64Store32(x),
|
||||
|
||||
InstructionInternal::CurrentMemory => Instruction::CurrentMemory,
|
||||
InstructionInternal::GrowMemory => Instruction::GrowMemory,
|
||||
|
||||
InstructionInternal::I32Const(x) => Instruction::I32Const(x),
|
||||
InstructionInternal::I64Const(x) => Instruction::I64Const(x),
|
||||
InstructionInternal::F32Const(x) => Instruction::F32Const(x),
|
||||
InstructionInternal::F64Const(x) => Instruction::F64Const(x),
|
||||
|
||||
InstructionInternal::I32Eqz => Instruction::I32Eqz,
|
||||
InstructionInternal::I32Eq => Instruction::I32Eq,
|
||||
InstructionInternal::I32Ne => Instruction::I32Ne,
|
||||
InstructionInternal::I32LtS => Instruction::I32LtS,
|
||||
InstructionInternal::I32LtU => Instruction::I32LtU,
|
||||
InstructionInternal::I32GtS => Instruction::I32GtS,
|
||||
InstructionInternal::I32GtU => Instruction::I32GtU,
|
||||
InstructionInternal::I32LeS => Instruction::I32LeS,
|
||||
InstructionInternal::I32LeU => Instruction::I32LeU,
|
||||
InstructionInternal::I32GeS => Instruction::I32GeS,
|
||||
InstructionInternal::I32GeU => Instruction::I32GeU,
|
||||
|
||||
InstructionInternal::I64Eqz => Instruction::I64Eqz,
|
||||
InstructionInternal::I64Eq => Instruction::I64Eq,
|
||||
InstructionInternal::I64Ne => Instruction::I64Ne,
|
||||
InstructionInternal::I64LtS => Instruction::I64LtS,
|
||||
InstructionInternal::I64LtU => Instruction::I64LtU,
|
||||
InstructionInternal::I64GtS => Instruction::I64GtS,
|
||||
InstructionInternal::I64GtU => Instruction::I64GtU,
|
||||
InstructionInternal::I64LeS => Instruction::I64LeS,
|
||||
InstructionInternal::I64LeU => Instruction::I64LeU,
|
||||
InstructionInternal::I64GeS => Instruction::I64GeS,
|
||||
InstructionInternal::I64GeU => Instruction::I64GeU,
|
||||
|
||||
InstructionInternal::F32Eq => Instruction::F32Eq,
|
||||
InstructionInternal::F32Ne => Instruction::F32Ne,
|
||||
InstructionInternal::F32Lt => Instruction::F32Lt,
|
||||
InstructionInternal::F32Gt => Instruction::F32Gt,
|
||||
InstructionInternal::F32Le => Instruction::F32Le,
|
||||
InstructionInternal::F32Ge => Instruction::F32Ge,
|
||||
|
||||
InstructionInternal::F64Eq => Instruction::F64Eq,
|
||||
InstructionInternal::F64Ne => Instruction::F64Ne,
|
||||
InstructionInternal::F64Lt => Instruction::F64Lt,
|
||||
InstructionInternal::F64Gt => Instruction::F64Gt,
|
||||
InstructionInternal::F64Le => Instruction::F64Le,
|
||||
InstructionInternal::F64Ge => Instruction::F64Ge,
|
||||
|
||||
InstructionInternal::I32Clz => Instruction::I32Clz,
|
||||
InstructionInternal::I32Ctz => Instruction::I32Ctz,
|
||||
InstructionInternal::I32Popcnt => Instruction::I32Popcnt,
|
||||
InstructionInternal::I32Add => Instruction::I32Add,
|
||||
InstructionInternal::I32Sub => Instruction::I32Sub,
|
||||
InstructionInternal::I32Mul => Instruction::I32Mul,
|
||||
InstructionInternal::I32DivS => Instruction::I32DivS,
|
||||
InstructionInternal::I32DivU => Instruction::I32DivU,
|
||||
InstructionInternal::I32RemS => Instruction::I32RemS,
|
||||
InstructionInternal::I32RemU => Instruction::I32RemU,
|
||||
InstructionInternal::I32And => Instruction::I32And,
|
||||
InstructionInternal::I32Or => Instruction::I32Or,
|
||||
InstructionInternal::I32Xor => Instruction::I32Xor,
|
||||
InstructionInternal::I32Shl => Instruction::I32Shl,
|
||||
InstructionInternal::I32ShrS => Instruction::I32ShrS,
|
||||
InstructionInternal::I32ShrU => Instruction::I32ShrU,
|
||||
InstructionInternal::I32Rotl => Instruction::I32Rotl,
|
||||
InstructionInternal::I32Rotr => Instruction::I32Rotr,
|
||||
|
||||
InstructionInternal::I64Clz => Instruction::I64Clz,
|
||||
InstructionInternal::I64Ctz => Instruction::I64Ctz,
|
||||
InstructionInternal::I64Popcnt => Instruction::I64Popcnt,
|
||||
InstructionInternal::I64Add => Instruction::I64Add,
|
||||
InstructionInternal::I64Sub => Instruction::I64Sub,
|
||||
InstructionInternal::I64Mul => Instruction::I64Mul,
|
||||
InstructionInternal::I64DivS => Instruction::I64DivS,
|
||||
InstructionInternal::I64DivU => Instruction::I64DivU,
|
||||
InstructionInternal::I64RemS => Instruction::I64RemS,
|
||||
InstructionInternal::I64RemU => Instruction::I64RemU,
|
||||
InstructionInternal::I64And => Instruction::I64And,
|
||||
InstructionInternal::I64Or => Instruction::I64Or,
|
||||
InstructionInternal::I64Xor => Instruction::I64Xor,
|
||||
InstructionInternal::I64Shl => Instruction::I64Shl,
|
||||
InstructionInternal::I64ShrS => Instruction::I64ShrS,
|
||||
InstructionInternal::I64ShrU => Instruction::I64ShrU,
|
||||
InstructionInternal::I64Rotl => Instruction::I64Rotl,
|
||||
InstructionInternal::I64Rotr => Instruction::I64Rotr,
|
||||
InstructionInternal::F32Abs => Instruction::F32Abs,
|
||||
InstructionInternal::F32Neg => Instruction::F32Neg,
|
||||
InstructionInternal::F32Ceil => Instruction::F32Ceil,
|
||||
InstructionInternal::F32Floor => Instruction::F32Floor,
|
||||
InstructionInternal::F32Trunc => Instruction::F32Trunc,
|
||||
InstructionInternal::F32Nearest => Instruction::F32Nearest,
|
||||
InstructionInternal::F32Sqrt => Instruction::F32Sqrt,
|
||||
InstructionInternal::F32Add => Instruction::F32Add,
|
||||
InstructionInternal::F32Sub => Instruction::F32Sub,
|
||||
InstructionInternal::F32Mul => Instruction::F32Mul,
|
||||
InstructionInternal::F32Div => Instruction::F32Div,
|
||||
InstructionInternal::F32Min => Instruction::F32Min,
|
||||
InstructionInternal::F32Max => Instruction::F32Max,
|
||||
InstructionInternal::F32Copysign => Instruction::F32Copysign,
|
||||
InstructionInternal::F64Abs => Instruction::F64Abs,
|
||||
InstructionInternal::F64Neg => Instruction::F64Neg,
|
||||
InstructionInternal::F64Ceil => Instruction::F64Ceil,
|
||||
InstructionInternal::F64Floor => Instruction::F64Floor,
|
||||
InstructionInternal::F64Trunc => Instruction::F64Trunc,
|
||||
InstructionInternal::F64Nearest => Instruction::F64Nearest,
|
||||
InstructionInternal::F64Sqrt => Instruction::F64Sqrt,
|
||||
InstructionInternal::F64Add => Instruction::F64Add,
|
||||
InstructionInternal::F64Sub => Instruction::F64Sub,
|
||||
InstructionInternal::F64Mul => Instruction::F64Mul,
|
||||
InstructionInternal::F64Div => Instruction::F64Div,
|
||||
InstructionInternal::F64Min => Instruction::F64Min,
|
||||
InstructionInternal::F64Max => Instruction::F64Max,
|
||||
InstructionInternal::F64Copysign => Instruction::F64Copysign,
|
||||
|
||||
InstructionInternal::I32WrapI64 => Instruction::I32WrapI64,
|
||||
InstructionInternal::I32TruncSF32 => Instruction::I32TruncSF32,
|
||||
InstructionInternal::I32TruncUF32 => Instruction::I32TruncUF32,
|
||||
InstructionInternal::I32TruncSF64 => Instruction::I32TruncSF64,
|
||||
InstructionInternal::I32TruncUF64 => Instruction::I32TruncUF64,
|
||||
InstructionInternal::I64ExtendSI32 => Instruction::I64ExtendSI32,
|
||||
InstructionInternal::I64ExtendUI32 => Instruction::I64ExtendUI32,
|
||||
InstructionInternal::I64TruncSF32 => Instruction::I64TruncSF32,
|
||||
InstructionInternal::I64TruncUF32 => Instruction::I64TruncUF32,
|
||||
InstructionInternal::I64TruncSF64 => Instruction::I64TruncSF64,
|
||||
InstructionInternal::I64TruncUF64 => Instruction::I64TruncUF64,
|
||||
InstructionInternal::F32ConvertSI32 => Instruction::F32ConvertSI32,
|
||||
InstructionInternal::F32ConvertUI32 => Instruction::F32ConvertUI32,
|
||||
InstructionInternal::F32ConvertSI64 => Instruction::F32ConvertSI64,
|
||||
InstructionInternal::F32ConvertUI64 => Instruction::F32ConvertUI64,
|
||||
InstructionInternal::F32DemoteF64 => Instruction::F32DemoteF64,
|
||||
InstructionInternal::F64ConvertSI32 => Instruction::F64ConvertSI32,
|
||||
InstructionInternal::F64ConvertUI32 => Instruction::F64ConvertUI32,
|
||||
InstructionInternal::F64ConvertSI64 => Instruction::F64ConvertSI64,
|
||||
InstructionInternal::F64ConvertUI64 => Instruction::F64ConvertUI64,
|
||||
InstructionInternal::F64PromoteF32 => Instruction::F64PromoteF32,
|
||||
|
||||
InstructionInternal::I32ReinterpretF32 => Instruction::I32ReinterpretF32,
|
||||
InstructionInternal::I64ReinterpretF64 => Instruction::I64ReinterpretF64,
|
||||
InstructionInternal::F32ReinterpretI32 => Instruction::F32ReinterpretI32,
|
||||
InstructionInternal::F64ReinterpretI64 => Instruction::F64ReinterpretI64,
|
||||
};
|
||||
|
||||
self.position += 1;
|
||||
|
||||
Some(out)
|
||||
}
|
||||
}
|
172
src/lib.rs
172
src/lib.rs
|
@ -1,48 +1,48 @@
|
|||
//! # wasmi
|
||||
//!
|
||||
//! This library allows to load WebAssembly modules in binary format and invoke functions on them.
|
||||
//! This library allows WebAssembly modules to be loaded in binary format and their functions invoked.
|
||||
//!
|
||||
//! # Introduction
|
||||
//!
|
||||
//! WebAssembly (wasm) is a safe, portable, compact format that designed for efficient execution.
|
||||
//! WebAssembly (wasm) is a safe, portable and compact format that is designed for efficient execution.
|
||||
//!
|
||||
//! Wasm code is distributed in a form of modules, that contains definitions of:
|
||||
//! Wasm code is distributed in the form of modules that contains definitions of:
|
||||
//!
|
||||
//! - functions,
|
||||
//! - global variables,
|
||||
//! - linear memories,
|
||||
//! - linear memory instances and
|
||||
//! - tables.
|
||||
//!
|
||||
//! and this definitions can be imported. Also, each definition can be exported.
|
||||
//! Each of these definitions can be imported and exported.
|
||||
//!
|
||||
//! In addition to definitions, modules can define initialization data for their memories or tables that takes the
|
||||
//! form of segments copied to given offsets. They can also define a `start` function that is automatically executed.
|
||||
//! In addition to these definitions, modules can define initialization data for their memory or tables. This initialization data can take the
|
||||
//! form of segments, copied to given offsets. They can also define a `start` function that is automatically executed when the module is loaded.
|
||||
//!
|
||||
//! ## Loading and Validation
|
||||
//!
|
||||
//! Before execution a module should be validated. This process checks that module is well-formed
|
||||
//! Before execution, a module must be validated. This process checks that the module is well-formed
|
||||
//! and makes only allowed operations.
|
||||
//!
|
||||
//! Valid modules can't access memory out of it's sandbox, can't cause stack underflow
|
||||
//! and can call functions only with correct signatures.
|
||||
//! A valid module can't access memory outside its sandbox, can't cause stack underflows
|
||||
//! and can only call functions with correct signatures.
|
||||
//!
|
||||
//! ## Instantiatiation
|
||||
//! ## Instantiation
|
||||
//!
|
||||
//! In order to execute code in wasm module it should be instatiated.
|
||||
//! In order to execute code from a wasm module, it must be instantiated.
|
||||
//! Instantiation includes the following steps:
|
||||
//!
|
||||
//! 1. Create an empty module instance,
|
||||
//! 2. Resolve definition instances for each declared import in the module,
|
||||
//! 3. Instantiate definitions declared in the module (e.g. allocate global variables, allocate linear memory, etc),
|
||||
//! 4. Initialize memory and table contents by copiying segments into them,
|
||||
//! 5. Execute `start` function, if any.
|
||||
//! 1. Creating an empty module instance.
|
||||
//! 2. Resolving the definition instances for each declared import in the module.
|
||||
//! 3. Instantiating definitions declared in the module (e.g. allocate global variables, allocate linear memory, etc.).
|
||||
//! 4. Initializing memory and table contents by copying segments into them.
|
||||
//! 5. Executing the `start` function, if any.
|
||||
//!
|
||||
//! After these steps, module instance are ready to execute functions.
|
||||
//! After these steps, the module instance is ready to execute functions.
|
||||
//!
|
||||
//! ## Execution
|
||||
//!
|
||||
//! It is allowed to only execute functions which are exported by a module.
|
||||
//! Functions can either return a result or trap (e.g. there can't be linking-error at the middle of execution).
|
||||
//! It only is allowed to call functions which are exported by the module.
|
||||
//! Functions can either return a result or trap (e.g. there can't be linking error in the middle of the function execution).
|
||||
//! This property is ensured by the validation process.
|
||||
//!
|
||||
//! # Examples
|
||||
|
@ -72,7 +72,7 @@
|
|||
//! .expect("failed to load wasm");
|
||||
//!
|
||||
//! // Instantiate a module with empty imports and
|
||||
//! // asserting that there is no `start` function.
|
||||
//! // assert that there is no `start` function.
|
||||
//! let instance =
|
||||
//! ModuleInstance::new(
|
||||
//! &module,
|
||||
|
@ -81,7 +81,7 @@
|
|||
//! .expect("failed to instantiate wasm module")
|
||||
//! .assert_no_start();
|
||||
//!
|
||||
//! // Finally, invoke exported function "test" with no parameters
|
||||
//! // Finally, invoke the exported function "test" with no parameters
|
||||
//! // and empty external function executor.
|
||||
//! assert_eq!(
|
||||
//! instance.invoke_export(
|
||||
|
@ -95,23 +95,44 @@
|
|||
//! ```
|
||||
|
||||
#![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;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[macro_use]
|
||||
extern crate core;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate assert_matches;
|
||||
#[cfg(test)]
|
||||
extern crate wabt;
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate assert_matches;
|
||||
|
||||
extern crate parity_wasm;
|
||||
extern crate byteorder;
|
||||
extern crate memory_units as memory_units_crate;
|
||||
extern crate nan_preserving_float;
|
||||
extern crate parity_wasm;
|
||||
|
||||
use std::fmt;
|
||||
extern crate wasmi_validation as validation;
|
||||
|
||||
use alloc::{
|
||||
boxed::Box,
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
use core::fmt;
|
||||
#[cfg(feature = "std")]
|
||||
use std::error;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Error type which can thrown by wasm code or by host environment.
|
||||
#[cfg(not(feature = "std"))]
|
||||
extern crate libm;
|
||||
|
||||
extern crate num_rational;
|
||||
extern crate num_traits;
|
||||
|
||||
/// 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.
|
||||
/// Traps can't be handled by WebAssembly code, but are reported to the embedder.
|
||||
|
@ -138,13 +159,14 @@ impl fmt::Display for Trap {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl error::Error for Trap {
|
||||
fn description(&self) -> &str {
|
||||
"runtime trap"
|
||||
}
|
||||
}
|
||||
|
||||
/// Error type which can thrown by wasm code or by host environment.
|
||||
/// Error type which can be thrown by wasm code or by host environment.
|
||||
///
|
||||
/// See [`Trap`] for details.
|
||||
///
|
||||
|
@ -218,7 +240,17 @@ pub enum TrapKind {
|
|||
/// Typically returned from an implementation of [`Externals`].
|
||||
///
|
||||
/// [`Externals`]: trait.Externals.html
|
||||
Host(Box<host::HostError>),
|
||||
Host(Box<dyn host::HostError>),
|
||||
}
|
||||
|
||||
impl TrapKind {
|
||||
/// Whether this trap is specified by the host.
|
||||
pub fn is_host(&self) -> bool {
|
||||
match self {
|
||||
&TrapKind::Host(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal interpreter error.
|
||||
|
@ -242,7 +274,7 @@ pub enum Error {
|
|||
/// Trap.
|
||||
Trap(Trap),
|
||||
/// Custom embedder error.
|
||||
Host(Box<host::HostError>),
|
||||
Host(Box<dyn host::HostError>),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
|
@ -254,13 +286,13 @@ impl Error {
|
|||
/// [`Host`]: enum.Error.html#variant.Host
|
||||
/// [`Trap`]: enum.Error.html#variant.Trap
|
||||
/// [`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 {
|
||||
Error::Host(ref host_err) => Some(&**host_err),
|
||||
Error::Trap(ref trap) => match *trap.kind() {
|
||||
TrapKind::Host(ref host_err) => Some(&**host_err),
|
||||
_ => None,
|
||||
}
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -298,6 +330,7 @@ impl fmt::Display for Error {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl error::Error for Error {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
|
@ -314,13 +347,19 @@ impl error::Error for Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl<U> From<U> for Error where U: host::HostError + Sized {
|
||||
impl<U> From<U> for Error
|
||||
where
|
||||
U: host::HostError + Sized,
|
||||
{
|
||||
fn from(e: U) -> Self {
|
||||
Error::Host(Box::new(e))
|
||||
}
|
||||
}
|
||||
|
||||
impl<U> From<U> for Trap where U: host::HostError + Sized {
|
||||
impl<U> From<U> for Trap
|
||||
where
|
||||
U: host::HostError + Sized,
|
||||
{
|
||||
fn from(e: U) -> Self {
|
||||
Trap::new(TrapKind::Host(Box::new(e)))
|
||||
}
|
||||
|
@ -344,41 +383,43 @@ impl From<validation::Error> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
mod validation;
|
||||
mod common;
|
||||
mod memory;
|
||||
mod module;
|
||||
mod runner;
|
||||
mod table;
|
||||
mod value;
|
||||
mod func;
|
||||
mod global;
|
||||
mod host;
|
||||
mod imports;
|
||||
mod global;
|
||||
mod func;
|
||||
mod isa;
|
||||
mod memory;
|
||||
mod module;
|
||||
pub mod nan_preserving_float;
|
||||
mod prepare;
|
||||
mod runner;
|
||||
mod table;
|
||||
mod types;
|
||||
mod value;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use self::memory::{MemoryInstance, MemoryRef, LINEAR_MEMORY_PAGE_SIZE};
|
||||
pub use self::table::{TableInstance, TableRef};
|
||||
pub use self::value::{RuntimeValue, FromRuntimeValue};
|
||||
pub use self::host::{Externals, NopExternals, HostError, RuntimeArgs};
|
||||
pub use self::imports::{ModuleImportResolver, ImportResolver, ImportsBuilder};
|
||||
pub use self::module::{ModuleInstance, ModuleRef, ExternVal, NotStartedModuleRef};
|
||||
pub use self::func::{FuncInstance, FuncInvocation, FuncRef, ResumableError};
|
||||
pub use self::global::{GlobalInstance, GlobalRef};
|
||||
pub use self::func::{FuncInstance, FuncRef};
|
||||
pub use self::types::{Signature, ValueType, GlobalDescriptor, TableDescriptor, MemoryDescriptor};
|
||||
pub use self::host::{Externals, HostError, NopExternals, RuntimeArgs};
|
||||
pub use self::imports::{ImportResolver, ImportsBuilder, ModuleImportResolver};
|
||||
pub use self::memory::{MemoryInstance, MemoryRef, LINEAR_MEMORY_PAGE_SIZE};
|
||||
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::types::{GlobalDescriptor, MemoryDescriptor, Signature, TableDescriptor, ValueType};
|
||||
pub use self::value::{Error as ValueError, FromRuntimeValue, LittleEndianConvert, RuntimeValue};
|
||||
|
||||
/// WebAssembly-specific sizes and units.
|
||||
pub mod memory_units {
|
||||
pub use memory_units_crate::wasm32::*;
|
||||
pub use memory_units_crate::{Bytes, ByteSize, RoundUpTo, size_of};
|
||||
pub use memory_units_crate::{size_of, ByteSize, Bytes, RoundUpTo};
|
||||
}
|
||||
|
||||
/// Deserialized module prepared for instantiation.
|
||||
pub struct Module {
|
||||
labels: HashMap<usize, HashMap<usize, usize>>,
|
||||
code_map: Vec<isa::Instructions>,
|
||||
module: parity_wasm::elements::Module,
|
||||
}
|
||||
|
||||
|
@ -416,16 +457,9 @@ impl Module {
|
|||
/// }
|
||||
/// ```
|
||||
pub fn from_parity_wasm_module(module: parity_wasm::elements::Module) -> Result<Module, Error> {
|
||||
use validation::{validate_module, ValidatedModule};
|
||||
let ValidatedModule {
|
||||
labels,
|
||||
module,
|
||||
} = validate_module(module)?;
|
||||
let prepare::CompiledModule { code_map, module } = prepare::compile_module(module)?;
|
||||
|
||||
Ok(Module {
|
||||
labels,
|
||||
module,
|
||||
})
|
||||
Ok(Module { code_map, module })
|
||||
}
|
||||
|
||||
/// Fail if the module contains any floating-point operations
|
||||
|
@ -485,7 +519,7 @@ impl Module {
|
|||
/// assert!(module.deny_floating_point().is_err());
|
||||
/// ```
|
||||
pub fn deny_floating_point(&self) -> Result<(), Error> {
|
||||
validation::deny_floating_point(&self.module).map_err(Into::into)
|
||||
prepare::deny_floating_point(&self.module).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Create `Module` from a given buffer.
|
||||
|
@ -524,7 +558,7 @@ impl Module {
|
|||
&self.module
|
||||
}
|
||||
|
||||
pub(crate) fn labels(&self) -> &HashMap<usize, HashMap<usize, usize>> {
|
||||
&self.labels
|
||||
pub(crate) fn code(&self) -> &Vec<isa::Instructions> {
|
||||
&self.code_map
|
||||
}
|
||||
}
|
||||
|
|
539
src/memory.rs
539
src/memory.rs
|
@ -1,539 +0,0 @@
|
|||
use std::u32;
|
||||
use std::ops::Range;
|
||||
use std::cmp;
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use parity_wasm::elements::ResizableLimits;
|
||||
use Error;
|
||||
use memory_units::{RoundUpTo, Pages, Bytes};
|
||||
|
||||
/// Size of a page of [linear memory][`MemoryInstance`] - 64KiB.
|
||||
///
|
||||
/// The size of a memory is always a integer multiple of a page size.
|
||||
///
|
||||
/// [`MemoryInstance`]: struct.MemoryInstance.html
|
||||
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).
|
||||
///
|
||||
/// This reference has a reference-counting semantics.
|
||||
///
|
||||
/// [`MemoryInstance`]: struct.MemoryInstance.html
|
||||
///
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MemoryRef(Rc<MemoryInstance>);
|
||||
|
||||
impl ::std::ops::Deref for MemoryRef {
|
||||
type Target = MemoryInstance;
|
||||
fn deref(&self) -> &MemoryInstance {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Runtime representation of a linear memory (or `memory` for short).
|
||||
///
|
||||
/// A memory is a contiguous, mutable array of raw bytes. Wasm code can load and store values
|
||||
/// from/to a linear memory at any byte address.
|
||||
/// A trap occurs if an access is not within the bounds of the current memory size.
|
||||
///
|
||||
/// A memory is created with an initial size but can be grown dynamically.
|
||||
/// The growth can be limited by specifying maximum size.
|
||||
/// The size of a memory is always a integer multiple of a [page size][`LINEAR_MEMORY_PAGE_SIZE`] - 64KiB.
|
||||
///
|
||||
/// At the moment, wasm doesn't provide any way to shrink the memory.
|
||||
///
|
||||
/// [`LINEAR_MEMORY_PAGE_SIZE`]: constant.LINEAR_MEMORY_PAGE_SIZE.html
|
||||
pub struct MemoryInstance {
|
||||
/// Memory limits.
|
||||
limits: ResizableLimits,
|
||||
/// Linear memory buffer.
|
||||
buffer: RefCell<Vec<u8>>,
|
||||
initial: Pages,
|
||||
maximum: Option<Pages>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for MemoryInstance {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("MemoryInstance")
|
||||
.field("limits", &self.limits)
|
||||
.field("buffer.len", &self.buffer.borrow().len())
|
||||
.field("maximum", &self.maximum)
|
||||
.field("initial", &self.initial)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
struct CheckedRegion<'a, B: 'a> where B: ::std::ops::Deref<Target=Vec<u8>> {
|
||||
buffer: &'a B,
|
||||
offset: usize,
|
||||
size: usize,
|
||||
}
|
||||
|
||||
impl<'a, B: 'a> CheckedRegion<'a, B> where B: ::std::ops::Deref<Target=Vec<u8>> {
|
||||
fn range(&self) -> Range<usize> {
|
||||
self.offset..self.offset+self.size
|
||||
}
|
||||
|
||||
fn slice(&self) -> &[u8] {
|
||||
&self.buffer[self.range()]
|
||||
}
|
||||
|
||||
fn intersects(&self, other: &Self) -> bool {
|
||||
let low = cmp::max(self.offset, other.offset);
|
||||
let high = cmp::min(self.offset + self.size, other.offset + other.size);
|
||||
|
||||
low < high
|
||||
}
|
||||
}
|
||||
|
||||
impl MemoryInstance {
|
||||
/// Allocate a memory instance.
|
||||
///
|
||||
/// The memory allocated with initial number of pages specified by `initial`.
|
||||
/// Minimal possible value for `initial` is 0 and maximum possible is `65536`.
|
||||
/// (Since maximum addressible memory is 2<sup>32</sup> = 4GiB = 65536 * [64KiB][`LINEAR_MEMORY_PAGE_SIZE`]).
|
||||
///
|
||||
/// It is possible to limit maximum number of pages this memory instance can have by specifying
|
||||
/// `maximum`. If not specified, this memory instance would be able to allocate up to 4GiB.
|
||||
///
|
||||
/// Allocated memory is always zeroed.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if:
|
||||
///
|
||||
/// - `initial` is greater than `maximum`
|
||||
/// - either `initial` or `maximum` is greater than `65536`.
|
||||
///
|
||||
/// [`LINEAR_MEMORY_PAGE_SIZE`]: constant.LINEAR_MEMORY_PAGE_SIZE.html
|
||||
pub fn alloc(initial: Pages, maximum: Option<Pages>) -> Result<MemoryRef, Error> {
|
||||
validate_memory(initial, maximum).map_err(Error::Memory)?;
|
||||
|
||||
let memory = MemoryInstance::new(initial, maximum);
|
||||
Ok(MemoryRef(Rc::new(memory)))
|
||||
}
|
||||
|
||||
/// Create new linear memory instance.
|
||||
fn new(initial: Pages, maximum: Option<Pages>) -> Self {
|
||||
let limits = ResizableLimits::new(initial.0 as u32, maximum.map(|p| p.0 as u32));
|
||||
|
||||
let initial_size: Bytes = initial.into();
|
||||
MemoryInstance {
|
||||
limits: limits,
|
||||
buffer: RefCell::new(vec![0; initial_size.0]),
|
||||
initial: initial,
|
||||
maximum: maximum,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return linear memory limits.
|
||||
pub(crate) fn limits(&self) -> &ResizableLimits {
|
||||
&self.limits
|
||||
}
|
||||
|
||||
/// Returns number of pages this `MemoryInstance` was created with.
|
||||
pub fn initial(&self) -> Pages {
|
||||
self.initial
|
||||
}
|
||||
|
||||
/// Returns maximum amount of pages this `MemoryInstance` can grow to.
|
||||
///
|
||||
/// Returns `None` if there is no limit set.
|
||||
/// Maximum memory size cannot exceed `65536` pages or 4GiB.
|
||||
pub fn maximum(&self) -> Option<Pages> {
|
||||
self.maximum
|
||||
}
|
||||
|
||||
/// Returns current linear memory size.
|
||||
///
|
||||
/// Maximum memory size cannot exceed `65536` pages or 4GiB.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// To convert number of pages to number of bytes you can use the following code:
|
||||
///
|
||||
/// ```rust
|
||||
/// use wasmi::MemoryInstance;
|
||||
/// use wasmi::memory_units::*;
|
||||
///
|
||||
/// let memory = MemoryInstance::alloc(Pages(1), None).unwrap();
|
||||
/// let byte_size: Bytes = memory.current_size().into();
|
||||
/// assert_eq!(
|
||||
/// byte_size,
|
||||
/// Bytes(65536),
|
||||
/// );
|
||||
/// ```
|
||||
pub fn current_size(&self) -> Pages {
|
||||
Bytes(self.buffer.borrow().len()).round_up_to()
|
||||
}
|
||||
|
||||
/// Copy data from memory at given offset.
|
||||
///
|
||||
/// This will allocate vector for you.
|
||||
/// If you can provide a mutable slice you can use [`get_into`].
|
||||
///
|
||||
/// [`get_into`]: #method.get_into
|
||||
pub fn get(&self, offset: u32, size: usize) -> Result<Vec<u8>, Error> {
|
||||
let buffer = self.buffer.borrow();
|
||||
let region = self.checked_region(&buffer, offset as usize, size)?;
|
||||
|
||||
Ok(region.slice().to_vec())
|
||||
}
|
||||
|
||||
/// Copy data from given offset in the memory into `target` slice.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if the specified region is out of bounds.
|
||||
pub fn get_into(&self, offset: u32, target: &mut [u8]) -> Result<(), Error> {
|
||||
let buffer = self.buffer.borrow();
|
||||
let region = self.checked_region(&buffer, offset as usize, target.len())?;
|
||||
|
||||
target.copy_from_slice(region.slice());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Copy data in the memory at given offset.
|
||||
pub fn set(&self, offset: u32, value: &[u8]) -> Result<(), Error> {
|
||||
let mut buffer = self.buffer.borrow_mut();
|
||||
let range = self.checked_region(&buffer, offset as usize, value.len())?.range();
|
||||
|
||||
buffer[range].copy_from_slice(value);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Increases the size of the linear memory by given number of pages.
|
||||
/// Returns previous memory size if succeeds.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if attempted to allocate more memory than permited by the limit.
|
||||
pub fn grow(&self, additional: Pages) -> Result<Pages, Error> {
|
||||
let size_before_grow: Pages = self.current_size();
|
||||
|
||||
if additional == Pages(0) {
|
||||
return Ok(size_before_grow);
|
||||
}
|
||||
if additional > Pages(65536) {
|
||||
return Err(Error::Memory(format!(
|
||||
"Trying to grow memory by more than 65536 pages"
|
||||
)));
|
||||
}
|
||||
|
||||
let new_size: Pages = size_before_grow + additional;
|
||||
let maximum = self.maximum.unwrap_or(LINEAR_MEMORY_MAX_PAGES);
|
||||
if new_size > maximum {
|
||||
return Err(Error::Memory(format!(
|
||||
"Trying to grow memory by {} pages when already have {}",
|
||||
additional.0, size_before_grow.0,
|
||||
)));
|
||||
}
|
||||
|
||||
// Resize underlying buffer up to a new size filling newly allocated space with zeroes.
|
||||
// This size is guaranteed to be larger than current size.
|
||||
let new_buffer_length: Bytes = new_size.into();
|
||||
{
|
||||
let mut buffer = self.buffer.borrow_mut();
|
||||
debug_assert!(new_buffer_length.0 > buffer.len());
|
||||
buffer.resize(new_buffer_length.0, 0);
|
||||
}
|
||||
Ok(size_before_grow)
|
||||
}
|
||||
|
||||
fn checked_region<'a, B>(&self, buffer: &'a B, offset: usize, size: usize) -> Result<CheckedRegion<'a, B>, Error>
|
||||
where B: ::std::ops::Deref<Target=Vec<u8>>
|
||||
{
|
||||
let end = offset.checked_add(size)
|
||||
.ok_or_else(|| Error::Memory(format!("trying to access memory block of size {} from offset {}", size, offset)))?;
|
||||
|
||||
if end > buffer.len() {
|
||||
return Err(Error::Memory(format!("trying to access region [{}..{}] in memory [0..{}]", offset, end, buffer.len())));
|
||||
}
|
||||
|
||||
Ok(CheckedRegion {
|
||||
buffer: buffer,
|
||||
offset: offset,
|
||||
size: size,
|
||||
})
|
||||
}
|
||||
|
||||
/// Copy contents of one memory region to another.
|
||||
///
|
||||
/// Semantically equivalent to `memmove`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if either of specified regions is out of bounds.
|
||||
pub fn copy(&self, src_offset: usize, dst_offset: usize, len: usize) -> Result<(), Error> {
|
||||
let buffer = self.buffer.borrow_mut();
|
||||
|
||||
let read_region = self.checked_region(&buffer, src_offset, len)?;
|
||||
let write_region = self.checked_region(&buffer, dst_offset, len)?;
|
||||
|
||||
unsafe { ::std::ptr::copy(
|
||||
buffer[read_region.range()].as_ptr(),
|
||||
buffer[write_region.range()].as_ptr() as *mut _,
|
||||
len,
|
||||
)}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Copy contents of one memory region to another (non-overlapping version).
|
||||
///
|
||||
/// Semantically equivalent to `memcpy`.
|
||||
/// but returns Error if source overlaping with destination.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if:
|
||||
///
|
||||
/// - either of specified regions is out of bounds,
|
||||
/// - these regions overlaps.
|
||||
pub fn copy_nonoverlapping(&self, src_offset: usize, dst_offset: usize, len: usize) -> Result<(), Error> {
|
||||
let buffer = self.buffer.borrow_mut();
|
||||
|
||||
let read_region = self.checked_region(&buffer, src_offset, len)?;
|
||||
let write_region = self.checked_region(&buffer, dst_offset, len)?;
|
||||
|
||||
if read_region.intersects(&write_region) {
|
||||
return Err(Error::Memory(format!("non-overlapping copy is used for overlapping regions")))
|
||||
}
|
||||
|
||||
unsafe { ::std::ptr::copy_nonoverlapping(
|
||||
buffer[read_region.range()].as_ptr(),
|
||||
buffer[write_region.range()].as_ptr() as *mut _,
|
||||
len,
|
||||
)}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fill the memory region with the specified value.
|
||||
///
|
||||
/// Semantically equivalent to `memset`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if the specified region is out of bounds.
|
||||
pub fn clear(&self, offset: usize, new_val: u8, len: usize) -> Result<(), Error> {
|
||||
let mut buffer = self.buffer.borrow_mut();
|
||||
|
||||
let range = self.checked_region(&buffer, offset, len)?.range();
|
||||
for val in &mut buffer[range] { *val = new_val }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fill the specified memory region with zeroes.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if the specified region is out of bounds.
|
||||
pub fn zero(&self, offset: usize, len: usize) -> Result<(), Error> {
|
||||
self.clear(offset, 0, len)
|
||||
}
|
||||
|
||||
/// Provides direct access to the underlying memory buffer.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Any call that requires write access to memory (such as [`set`], [`clear`], etc) made within
|
||||
/// the closure will panic. Proceed with caution.
|
||||
///
|
||||
/// [`set`]: #method.get
|
||||
/// [`clear`]: #method.set
|
||||
pub fn with_direct_access<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
|
||||
let buf = self.buffer.borrow();
|
||||
f(&*buf)
|
||||
}
|
||||
|
||||
/// Provides direct mutable access to the underlying memory buffer.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Any calls that requires either read or write access to memory (such as [`get`], [`set`], [`copy`], etc) made
|
||||
/// within the closure will panic. Proceed with caution.
|
||||
///
|
||||
/// [`get`]: #method.get
|
||||
/// [`set`]: #method.set
|
||||
/// [`copy`]: #method.copy
|
||||
pub fn with_direct_access_mut<R, F: FnOnce(&mut [u8]) -> R>(&self, f: F) -> R {
|
||||
let mut buf = self.buffer.borrow_mut();
|
||||
f(&mut *buf)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate_memory(initial: Pages, maximum: Option<Pages>) -> Result<(), String> {
|
||||
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)]
|
||||
mod tests {
|
||||
|
||||
use super::{MemoryInstance, LINEAR_MEMORY_PAGE_SIZE};
|
||||
use Error;
|
||||
use memory_units::Pages;
|
||||
|
||||
#[test]
|
||||
fn alloc() {
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
let fixtures = &[
|
||||
(0, None, true),
|
||||
(0, Some(0), true),
|
||||
(1, None, true),
|
||||
(1, Some(1), true),
|
||||
(0, Some(1), true),
|
||||
(1, Some(0), false),
|
||||
(0, Some(65536), true),
|
||||
(65536, Some(65536), true),
|
||||
(65536, Some(0), false),
|
||||
(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() {
|
||||
let initial: Pages = Pages(initial);
|
||||
let maximum: Option<Pages> = maybe_max.map(|m| Pages(m));
|
||||
let result = MemoryInstance::alloc(initial, maximum);
|
||||
if result.is_ok() != expected_ok {
|
||||
panic!(
|
||||
"unexpected error at {}, initial={:?}, max={:?}, expected={}, result={:?}",
|
||||
index,
|
||||
initial,
|
||||
maybe_max,
|
||||
expected_ok,
|
||||
result,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_page_size() {
|
||||
use memory_units::ByteSize;
|
||||
assert_eq!(LINEAR_MEMORY_PAGE_SIZE, Pages::byte_size());
|
||||
}
|
||||
|
||||
fn create_memory(initial_content: &[u8]) -> MemoryInstance {
|
||||
let mem = MemoryInstance::new(Pages(1), Some(Pages(1)));
|
||||
mem.set(0, initial_content).expect("Successful initialize the memory");
|
||||
mem
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copy_overlaps_1() {
|
||||
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
mem.copy(0, 4, 6).expect("Successfully copy the elements");
|
||||
let result = mem.get(0, 10).expect("Successfully retrieve the result");
|
||||
assert_eq!(result, &[0, 1, 2, 3, 0, 1, 2, 3, 4, 5]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copy_overlaps_2() {
|
||||
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
mem.copy(4, 0, 6).expect("Successfully copy the elements");
|
||||
let result = mem.get(0, 10).expect("Successfully retrieve the result");
|
||||
assert_eq!(result, &[4, 5, 6, 7, 8, 9, 6, 7, 8, 9]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copy_nonoverlapping() {
|
||||
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
mem.copy_nonoverlapping(0, 10, 10).expect("Successfully copy the elements");
|
||||
let result = mem.get(10, 10).expect("Successfully retrieve the result");
|
||||
assert_eq!(result, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copy_nonoverlapping_overlaps_1() {
|
||||
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
let result = mem.copy_nonoverlapping(0, 4, 6);
|
||||
match result {
|
||||
Err(Error::Memory(_)) => {},
|
||||
_ => panic!("Expected Error::Memory(_) result, but got {:?}", result),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copy_nonoverlapping_overlaps_2() {
|
||||
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
let result = mem.copy_nonoverlapping(4, 0, 6);
|
||||
match result {
|
||||
Err(Error::Memory(_)) => {},
|
||||
_ => panic!("Expected Error::Memory(_), but got {:?}", result),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear() {
|
||||
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
mem.clear(0, 0x4A, 10).expect("To successfully clear the memory");
|
||||
let result = mem.get(0, 10).expect("To successfully retrieve the result");
|
||||
assert_eq!(result, &[0x4A; 10]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_into() {
|
||||
let mem = MemoryInstance::new(Pages(1), None);
|
||||
mem.set(6, &[13, 17, 129]).expect("memory set should not fail");
|
||||
|
||||
let mut data = [0u8; 2];
|
||||
mem.get_into(7, &mut data[..]).expect("get_into should not fail");
|
||||
|
||||
assert_eq!(data, [17, 129]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_copy() {
|
||||
let mem = MemoryInstance::alloc(Pages(1), None).unwrap();
|
||||
mem.with_direct_access_mut(|buf| {
|
||||
assert_eq!(buf.len(), 65536);
|
||||
buf[..10].copy_from_slice(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
});
|
||||
mem.with_direct_access(|buf| {
|
||||
assert_eq!(buf.len(), 65536);
|
||||
assert_eq!(&buf[..10], &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
});
|
||||
}
|
||||
|
||||
#[should_panic]
|
||||
#[test]
|
||||
fn zero_copy_panics_on_nested_access() {
|
||||
let mem = MemoryInstance::alloc(Pages(1), None).unwrap();
|
||||
let mem_inner = mem.clone();
|
||||
mem.with_direct_access(move |_| {
|
||||
let _ = mem_inner.set(0, &[11, 12, 13]);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,740 @@
|
|||
use alloc::{rc::Rc, string::ToString, vec::Vec};
|
||||
use core::{
|
||||
cell::{Cell, RefCell},
|
||||
cmp, fmt,
|
||||
ops::Range,
|
||||
u32,
|
||||
};
|
||||
use memory_units::{Bytes, Pages, RoundUpTo};
|
||||
use parity_wasm::elements::ResizableLimits;
|
||||
use value::LittleEndianConvert;
|
||||
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.
|
||||
///
|
||||
/// The size of a memory is always a integer multiple of a page size.
|
||||
///
|
||||
/// [`MemoryInstance`]: struct.MemoryInstance.html
|
||||
pub const LINEAR_MEMORY_PAGE_SIZE: Bytes = Bytes(65536);
|
||||
|
||||
/// Reference to a memory (See [`MemoryInstance`] for details).
|
||||
///
|
||||
/// This reference has a reference-counting semantics.
|
||||
///
|
||||
/// [`MemoryInstance`]: struct.MemoryInstance.html
|
||||
///
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MemoryRef(Rc<MemoryInstance>);
|
||||
|
||||
impl ::core::ops::Deref for MemoryRef {
|
||||
type Target = MemoryInstance;
|
||||
fn deref(&self) -> &MemoryInstance {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Runtime representation of a linear memory (or `memory` for short).
|
||||
///
|
||||
/// A memory is a contiguous, mutable array of raw bytes. Wasm code can load and store values
|
||||
/// from/to a linear memory at any byte address.
|
||||
/// A trap occurs if an access is not within the bounds of the current memory size.
|
||||
///
|
||||
/// A memory is created with an initial size but can be grown dynamically.
|
||||
/// The growth can be limited by specifying maximum size.
|
||||
/// The size of a memory is always a integer multiple of a [page size][`LINEAR_MEMORY_PAGE_SIZE`] - 64KiB.
|
||||
///
|
||||
/// At the moment, wasm doesn't provide any way to shrink the memory.
|
||||
///
|
||||
/// [`LINEAR_MEMORY_PAGE_SIZE`]: constant.LINEAR_MEMORY_PAGE_SIZE.html
|
||||
pub struct MemoryInstance {
|
||||
/// Memory limits.
|
||||
limits: ResizableLimits,
|
||||
/// Linear memory buffer with lazy allocation.
|
||||
buffer: RefCell<ByteBuf>,
|
||||
initial: Pages,
|
||||
current_size: Cell<usize>,
|
||||
maximum: Option<Pages>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for MemoryInstance {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("MemoryInstance")
|
||||
.field("limits", &self.limits)
|
||||
.field("buffer.len", &self.buffer.borrow().len())
|
||||
.field("maximum", &self.maximum)
|
||||
.field("initial", &self.initial)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
struct CheckedRegion {
|
||||
offset: usize,
|
||||
size: usize,
|
||||
}
|
||||
|
||||
impl CheckedRegion {
|
||||
fn range(&self) -> Range<usize> {
|
||||
self.offset..self.offset + self.size
|
||||
}
|
||||
|
||||
fn intersects(&self, other: &Self) -> bool {
|
||||
let low = cmp::max(self.offset, other.offset);
|
||||
let high = cmp::min(self.offset + self.size, other.offset + other.size);
|
||||
|
||||
low < high
|
||||
}
|
||||
}
|
||||
|
||||
impl MemoryInstance {
|
||||
/// Allocate a memory instance.
|
||||
///
|
||||
/// The memory allocated with initial number of pages specified by `initial`.
|
||||
/// Minimal possible value for `initial` is 0 and maximum possible is `65536`.
|
||||
/// (Since maximum addressible memory is 2<sup>32</sup> = 4GiB = 65536 * [64KiB][`LINEAR_MEMORY_PAGE_SIZE`]).
|
||||
///
|
||||
/// It is possible to limit maximum number of pages this memory instance can have by specifying
|
||||
/// `maximum`. If not specified, this memory instance would be able to allocate up to 4GiB.
|
||||
///
|
||||
/// Allocated memory is always zeroed.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if:
|
||||
///
|
||||
/// - `initial` is greater than `maximum`
|
||||
/// - either `initial` or `maximum` is greater than `65536`.
|
||||
///
|
||||
/// [`LINEAR_MEMORY_PAGE_SIZE`]: constant.LINEAR_MEMORY_PAGE_SIZE.html
|
||||
pub fn alloc(initial: Pages, maximum: Option<Pages>) -> Result<MemoryRef, Error> {
|
||||
{
|
||||
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)?;
|
||||
Ok(MemoryRef(Rc::new(memory)))
|
||||
}
|
||||
|
||||
/// Create new linear memory instance.
|
||||
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 initial_size: Bytes = initial.into();
|
||||
Ok(MemoryInstance {
|
||||
limits: limits,
|
||||
buffer: RefCell::new(
|
||||
ByteBuf::new(initial_size.0).map_err(|err| Error::Memory(err.to_string()))?,
|
||||
),
|
||||
initial: initial,
|
||||
current_size: Cell::new(initial_size.0),
|
||||
maximum: maximum,
|
||||
})
|
||||
}
|
||||
|
||||
/// Return linear memory limits.
|
||||
pub(crate) fn limits(&self) -> &ResizableLimits {
|
||||
&self.limits
|
||||
}
|
||||
|
||||
/// Returns number of pages this `MemoryInstance` was created with.
|
||||
pub fn initial(&self) -> Pages {
|
||||
self.initial
|
||||
}
|
||||
|
||||
/// Returns maximum amount of pages this `MemoryInstance` can grow to.
|
||||
///
|
||||
/// Returns `None` if there is no limit set.
|
||||
/// Maximum memory size cannot exceed `65536` pages or 4GiB.
|
||||
pub fn maximum(&self) -> Option<Pages> {
|
||||
self.maximum
|
||||
}
|
||||
|
||||
/// Returns current linear memory size.
|
||||
///
|
||||
/// Maximum memory size cannot exceed `65536` pages or 4GiB.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// To convert number of pages to number of bytes you can use the following code:
|
||||
///
|
||||
/// ```rust
|
||||
/// use wasmi::MemoryInstance;
|
||||
/// use wasmi::memory_units::*;
|
||||
///
|
||||
/// let memory = MemoryInstance::alloc(Pages(1), None).unwrap();
|
||||
/// let byte_size: Bytes = memory.current_size().into();
|
||||
/// assert_eq!(
|
||||
/// byte_size,
|
||||
/// Bytes(65536),
|
||||
/// );
|
||||
/// ```
|
||||
pub fn current_size(&self) -> Pages {
|
||||
Bytes(self.buffer.borrow().len()).round_up_to()
|
||||
}
|
||||
|
||||
/// Get value from memory at given offset.
|
||||
pub fn get_value<T: LittleEndianConvert>(&self, offset: u32) -> Result<T, Error> {
|
||||
let mut buffer = self.buffer.borrow_mut();
|
||||
let region =
|
||||
self.checked_region(&mut buffer, offset as usize, ::core::mem::size_of::<T>())?;
|
||||
Ok(
|
||||
T::from_little_endian(&buffer.as_slice_mut()[region.range()])
|
||||
.expect("Slice size is checked"),
|
||||
)
|
||||
}
|
||||
|
||||
/// Copy data from memory at given offset.
|
||||
///
|
||||
/// This will allocate vector for you.
|
||||
/// If you can provide a mutable slice you can use [`get_into`].
|
||||
///
|
||||
/// [`get_into`]: #method.get_into
|
||||
pub fn get(&self, offset: u32, size: usize) -> Result<Vec<u8>, Error> {
|
||||
let mut buffer = self.buffer.borrow_mut();
|
||||
let region = self.checked_region(&mut buffer, offset as usize, size)?;
|
||||
|
||||
Ok(buffer.as_slice_mut()[region.range()].to_vec())
|
||||
}
|
||||
|
||||
/// Copy data from given offset in the memory into `target` slice.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if the specified region is out of bounds.
|
||||
pub fn get_into(&self, offset: u32, target: &mut [u8]) -> Result<(), Error> {
|
||||
let mut buffer = self.buffer.borrow_mut();
|
||||
let region = self.checked_region(&mut buffer, offset as usize, target.len())?;
|
||||
|
||||
target.copy_from_slice(&buffer.as_slice_mut()[region.range()]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Copy data in the memory at given offset.
|
||||
pub fn set(&self, offset: u32, value: &[u8]) -> Result<(), Error> {
|
||||
let mut buffer = self.buffer.borrow_mut();
|
||||
let range = self
|
||||
.checked_region(&mut buffer, offset as usize, value.len())?
|
||||
.range();
|
||||
|
||||
buffer.as_slice_mut()[range].copy_from_slice(value);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Copy value in the memory at given offset.
|
||||
pub fn set_value<T: LittleEndianConvert>(&self, offset: u32, value: T) -> Result<(), Error> {
|
||||
let mut buffer = self.buffer.borrow_mut();
|
||||
let range = self
|
||||
.checked_region(&mut buffer, offset as usize, ::core::mem::size_of::<T>())?
|
||||
.range();
|
||||
value.into_little_endian(&mut buffer.as_slice_mut()[range]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Increases the size of the linear memory by given number of pages.
|
||||
/// Returns previous memory size if succeeds.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if attempted to allocate more memory than permited by the limit.
|
||||
pub fn grow(&self, additional: Pages) -> Result<Pages, Error> {
|
||||
let size_before_grow: Pages = self.current_size();
|
||||
|
||||
if additional == Pages(0) {
|
||||
return Ok(size_before_grow);
|
||||
}
|
||||
if additional > Pages(65536) {
|
||||
return Err(Error::Memory(format!(
|
||||
"Trying to grow memory by more than 65536 pages"
|
||||
)));
|
||||
}
|
||||
|
||||
let new_size: Pages = size_before_grow + additional;
|
||||
let maximum = self
|
||||
.maximum
|
||||
.unwrap_or(Pages(validation::LINEAR_MEMORY_MAX_PAGES as usize));
|
||||
if new_size > maximum {
|
||||
return Err(Error::Memory(format!(
|
||||
"Trying to grow memory by {} pages when already have {}",
|
||||
additional.0, size_before_grow.0,
|
||||
)));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
Ok(size_before_grow)
|
||||
}
|
||||
|
||||
fn checked_region(
|
||||
&self,
|
||||
buffer: &mut ByteBuf,
|
||||
offset: usize,
|
||||
size: usize,
|
||||
) -> Result<CheckedRegion, Error> {
|
||||
let end = offset.checked_add(size).ok_or_else(|| {
|
||||
Error::Memory(format!(
|
||||
"trying to access memory block of size {} from offset {}",
|
||||
size, offset
|
||||
))
|
||||
})?;
|
||||
|
||||
if end > buffer.len() {
|
||||
return Err(Error::Memory(format!(
|
||||
"trying to access region [{}..{}] in memory [0..{}]",
|
||||
offset,
|
||||
end,
|
||||
buffer.len()
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(CheckedRegion {
|
||||
offset: offset,
|
||||
size: size,
|
||||
})
|
||||
}
|
||||
|
||||
fn checked_region_pair(
|
||||
&self,
|
||||
buffer: &mut ByteBuf,
|
||||
offset1: usize,
|
||||
size1: usize,
|
||||
offset2: usize,
|
||||
size2: usize,
|
||||
) -> Result<(CheckedRegion, CheckedRegion), Error> {
|
||||
let end1 = offset1.checked_add(size1).ok_or_else(|| {
|
||||
Error::Memory(format!(
|
||||
"trying to access memory block of size {} from offset {}",
|
||||
size1, offset1
|
||||
))
|
||||
})?;
|
||||
|
||||
let end2 = offset2.checked_add(size2).ok_or_else(|| {
|
||||
Error::Memory(format!(
|
||||
"trying to access memory block of size {} from offset {}",
|
||||
size2, offset2
|
||||
))
|
||||
})?;
|
||||
|
||||
if end1 > buffer.len() {
|
||||
return Err(Error::Memory(format!(
|
||||
"trying to access region [{}..{}] in memory [0..{}]",
|
||||
offset1,
|
||||
end1,
|
||||
buffer.len()
|
||||
)));
|
||||
}
|
||||
|
||||
if end2 > buffer.len() {
|
||||
return Err(Error::Memory(format!(
|
||||
"trying to access region [{}..{}] in memory [0..{}]",
|
||||
offset2,
|
||||
end2,
|
||||
buffer.len()
|
||||
)));
|
||||
}
|
||||
|
||||
Ok((
|
||||
CheckedRegion {
|
||||
offset: offset1,
|
||||
size: size1,
|
||||
},
|
||||
CheckedRegion {
|
||||
offset: offset2,
|
||||
size: size2,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
/// Copy contents of one memory region to another.
|
||||
///
|
||||
/// Semantically equivalent to `memmove`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if either of specified regions is out of bounds.
|
||||
pub fn copy(&self, src_offset: usize, dst_offset: usize, len: usize) -> Result<(), Error> {
|
||||
let mut buffer = self.buffer.borrow_mut();
|
||||
|
||||
let (read_region, write_region) =
|
||||
self.checked_region_pair(&mut buffer, src_offset, len, dst_offset, len)?;
|
||||
|
||||
unsafe {
|
||||
::core::ptr::copy(
|
||||
buffer.as_slice()[read_region.range()].as_ptr(),
|
||||
buffer.as_slice_mut()[write_region.range()].as_mut_ptr(),
|
||||
len,
|
||||
)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Copy contents of one memory region to another (non-overlapping version).
|
||||
///
|
||||
/// Semantically equivalent to `memcpy`.
|
||||
/// but returns Error if source overlaping with destination.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if:
|
||||
///
|
||||
/// - either of specified regions is out of bounds,
|
||||
/// - these regions overlaps.
|
||||
pub fn copy_nonoverlapping(
|
||||
&self,
|
||||
src_offset: usize,
|
||||
dst_offset: usize,
|
||||
len: usize,
|
||||
) -> Result<(), Error> {
|
||||
let mut buffer = self.buffer.borrow_mut();
|
||||
|
||||
let (read_region, write_region) =
|
||||
self.checked_region_pair(&mut buffer, src_offset, len, dst_offset, len)?;
|
||||
|
||||
if read_region.intersects(&write_region) {
|
||||
return Err(Error::Memory(format!(
|
||||
"non-overlapping copy is used for overlapping regions"
|
||||
)));
|
||||
}
|
||||
|
||||
unsafe {
|
||||
::core::ptr::copy_nonoverlapping(
|
||||
buffer.as_slice()[read_region.range()].as_ptr(),
|
||||
buffer.as_slice_mut()[write_region.range()].as_mut_ptr(),
|
||||
len,
|
||||
)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Copy memory between two (possibly distinct) memory instances.
|
||||
///
|
||||
/// If the same memory instance passed as `src` and `dst` then usual `copy` will be used.
|
||||
pub fn transfer(
|
||||
src: &MemoryRef,
|
||||
src_offset: usize,
|
||||
dst: &MemoryRef,
|
||||
dst_offset: usize,
|
||||
len: usize,
|
||||
) -> Result<(), Error> {
|
||||
if Rc::ptr_eq(&src.0, &dst.0) {
|
||||
// `transfer` is invoked with with same source and destination. Let's assume that regions may
|
||||
// overlap and use `copy`.
|
||||
return src.copy(src_offset, dst_offset, len);
|
||||
}
|
||||
|
||||
// Because memory references point to different memory instances, it is safe to `borrow_mut`
|
||||
// both buffers at once (modulo `with_direct_access_mut`).
|
||||
let mut src_buffer = src.buffer.borrow_mut();
|
||||
let mut dst_buffer = dst.buffer.borrow_mut();
|
||||
|
||||
let src_range = src
|
||||
.checked_region(&mut src_buffer, src_offset, len)?
|
||||
.range();
|
||||
let dst_range = dst
|
||||
.checked_region(&mut dst_buffer, dst_offset, len)?
|
||||
.range();
|
||||
|
||||
dst_buffer.as_slice_mut()[dst_range].copy_from_slice(&src_buffer.as_slice()[src_range]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fill the memory region with the specified value.
|
||||
///
|
||||
/// Semantically equivalent to `memset`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if the specified region is out of bounds.
|
||||
pub fn clear(&self, offset: usize, new_val: u8, len: usize) -> Result<(), Error> {
|
||||
let mut buffer = self.buffer.borrow_mut();
|
||||
|
||||
let range = self.checked_region(&mut buffer, offset, len)?.range();
|
||||
|
||||
for val in &mut buffer.as_slice_mut()[range] {
|
||||
*val = new_val
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fill the specified memory region with zeroes.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if the specified region is out of bounds.
|
||||
pub fn zero(&self, offset: usize, len: usize) -> Result<(), Error> {
|
||||
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.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Any call that requires write access to memory (such as [`set`], [`clear`], etc) made within
|
||||
/// the closure will panic.
|
||||
///
|
||||
/// [`set`]: #method.get
|
||||
/// [`clear`]: #method.set
|
||||
pub fn with_direct_access<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
|
||||
let buf = self.buffer.borrow();
|
||||
f(buf.as_slice())
|
||||
}
|
||||
|
||||
/// Provides direct mutable access to the underlying memory buffer.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Any calls that requires either read or write access to memory (such as [`get`], [`set`], [`copy`], etc) made
|
||||
/// within the closure will panic. Proceed with caution.
|
||||
///
|
||||
/// [`get`]: #method.get
|
||||
/// [`set`]: #method.set
|
||||
pub fn with_direct_access_mut<R, F: FnOnce(&mut [u8]) -> R>(&self, f: F) -> R {
|
||||
let mut buf = self.buffer.borrow_mut();
|
||||
f(buf.as_slice_mut())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::{MemoryInstance, MemoryRef, LINEAR_MEMORY_PAGE_SIZE};
|
||||
use memory_units::Pages;
|
||||
use std::rc::Rc;
|
||||
use Error;
|
||||
|
||||
#[test]
|
||||
fn alloc() {
|
||||
let mut fixtures = vec![
|
||||
(0, None, true),
|
||||
(0, Some(0), true),
|
||||
(1, None, true),
|
||||
(1, Some(1), true),
|
||||
(0, Some(1), true),
|
||||
(1, Some(0), false),
|
||||
];
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
fixtures.extend(&[
|
||||
(65536, Some(65536), true),
|
||||
(65536, Some(0), false),
|
||||
(65536, None, true),
|
||||
]);
|
||||
|
||||
for (index, &(initial, maybe_max, expected_ok)) in fixtures.iter().enumerate() {
|
||||
let initial: Pages = Pages(initial);
|
||||
let maximum: Option<Pages> = maybe_max.map(|m| Pages(m));
|
||||
let result = MemoryInstance::alloc(initial, maximum);
|
||||
if result.is_ok() != expected_ok {
|
||||
panic!(
|
||||
"unexpected error at {}, initial={:?}, max={:?}, expected={}, result={:?}",
|
||||
index, initial, maybe_max, expected_ok, result,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_page_size() {
|
||||
use memory_units::ByteSize;
|
||||
assert_eq!(LINEAR_MEMORY_PAGE_SIZE, Pages::byte_size());
|
||||
}
|
||||
|
||||
fn create_memory(initial_content: &[u8]) -> MemoryInstance {
|
||||
let mem = MemoryInstance::new(Pages(1), Some(Pages(1))).unwrap();
|
||||
mem.set(0, initial_content)
|
||||
.expect("Successful initialize the memory");
|
||||
mem
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copy_overlaps_1() {
|
||||
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
mem.copy(0, 4, 6).expect("Successfully copy the elements");
|
||||
let result = mem.get(0, 10).expect("Successfully retrieve the result");
|
||||
assert_eq!(result, &[0, 1, 2, 3, 0, 1, 2, 3, 4, 5]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copy_overlaps_2() {
|
||||
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
mem.copy(4, 0, 6).expect("Successfully copy the elements");
|
||||
let result = mem.get(0, 10).expect("Successfully retrieve the result");
|
||||
assert_eq!(result, &[4, 5, 6, 7, 8, 9, 6, 7, 8, 9]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copy_nonoverlapping() {
|
||||
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
mem.copy_nonoverlapping(0, 10, 10)
|
||||
.expect("Successfully copy the elements");
|
||||
let result = mem.get(10, 10).expect("Successfully retrieve the result");
|
||||
assert_eq!(result, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copy_nonoverlapping_overlaps_1() {
|
||||
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
let result = mem.copy_nonoverlapping(0, 4, 6);
|
||||
match result {
|
||||
Err(Error::Memory(_)) => {}
|
||||
_ => panic!("Expected Error::Memory(_) result, but got {:?}", result),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copy_nonoverlapping_overlaps_2() {
|
||||
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
let result = mem.copy_nonoverlapping(4, 0, 6);
|
||||
match result {
|
||||
Err(Error::Memory(_)) => {}
|
||||
_ => panic!("Expected Error::Memory(_), but got {:?}", result),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_works() {
|
||||
let src = MemoryRef(Rc::new(create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])));
|
||||
let dst = MemoryRef(Rc::new(create_memory(&[
|
||||
10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
|
||||
])));
|
||||
|
||||
MemoryInstance::transfer(&src, 4, &dst, 0, 3).unwrap();
|
||||
|
||||
assert_eq!(src.get(0, 10).unwrap(), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
assert_eq!(
|
||||
dst.get(0, 10).unwrap(),
|
||||
&[4, 5, 6, 13, 14, 15, 16, 17, 18, 19]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_still_works_with_same_memory() {
|
||||
let src = MemoryRef(Rc::new(create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])));
|
||||
|
||||
MemoryInstance::transfer(&src, 4, &src, 0, 3).unwrap();
|
||||
|
||||
assert_eq!(src.get(0, 10).unwrap(), &[4, 5, 6, 3, 4, 5, 6, 7, 8, 9]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_oob_with_same_memory_errors() {
|
||||
let src = MemoryRef(Rc::new(create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])));
|
||||
assert!(MemoryInstance::transfer(&src, 65535, &src, 0, 3).is_err());
|
||||
|
||||
// Check that memories content left untouched
|
||||
assert_eq!(src.get(0, 10).unwrap(), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_oob_errors() {
|
||||
let src = MemoryRef(Rc::new(create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])));
|
||||
let dst = MemoryRef(Rc::new(create_memory(&[
|
||||
10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
|
||||
])));
|
||||
|
||||
assert!(MemoryInstance::transfer(&src, 65535, &dst, 0, 3).is_err());
|
||||
|
||||
// Check that memories content left untouched
|
||||
assert_eq!(src.get(0, 10).unwrap(), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
assert_eq!(
|
||||
dst.get(0, 10).unwrap(),
|
||||
&[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear() {
|
||||
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
mem.clear(0, 0x4A, 10)
|
||||
.expect("To successfully clear the memory");
|
||||
let result = mem.get(0, 10).expect("To successfully retrieve the result");
|
||||
assert_eq!(result, &[0x4A; 10]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_into() {
|
||||
let mem = MemoryInstance::new(Pages(1), None).unwrap();
|
||||
mem.set(6, &[13, 17, 129])
|
||||
.expect("memory set should not fail");
|
||||
|
||||
let mut data = [0u8; 2];
|
||||
mem.get_into(7, &mut data[..])
|
||||
.expect("get_into should not fail");
|
||||
|
||||
assert_eq!(data, [17, 129]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_copy() {
|
||||
let mem = MemoryInstance::alloc(Pages(1), None).unwrap();
|
||||
mem.set(100, &[0]).expect("memory set should not fail");
|
||||
mem.with_direct_access_mut(|buf| {
|
||||
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]);
|
||||
});
|
||||
mem.with_direct_access(|buf| {
|
||||
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]);
|
||||
});
|
||||
}
|
||||
|
||||
#[should_panic]
|
||||
#[test]
|
||||
fn zero_copy_panics_on_nested_access() {
|
||||
let mem = MemoryInstance::alloc(Pages(1), None).unwrap();
|
||||
let mem_inner = mem.clone();
|
||||
mem.with_direct_access(move |_| {
|
||||
let _ = mem_inner.set(0, &[11, 12, 13]);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
325
src/module.rs
325
src/module.rs
|
@ -1,20 +1,28 @@
|
|||
use runner::check_function_args;
|
||||
use alloc::{
|
||||
borrow::ToOwned,
|
||||
rc::Rc,
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
use core::cell::RefCell;
|
||||
use core::fmt;
|
||||
use Trap;
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
use std::collections::HashMap;
|
||||
use parity_wasm::elements::{External, InitExpr, Internal, Opcode, ResizableLimits, Type};
|
||||
use {Module, Error, Signature, MemoryInstance, RuntimeValue, TableInstance};
|
||||
use imports::ImportResolver;
|
||||
|
||||
use alloc::collections::BTreeMap;
|
||||
|
||||
use core::cell::Ref;
|
||||
use func::{FuncBody, FuncInstance, FuncRef};
|
||||
use global::{GlobalInstance, GlobalRef};
|
||||
use func::{FuncRef, FuncBody, FuncInstance};
|
||||
use table::TableRef;
|
||||
use memory::MemoryRef;
|
||||
use host::Externals;
|
||||
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
|
||||
use types::{GlobalDescriptor, TableDescriptor, MemoryDescriptor};
|
||||
use imports::ImportResolver;
|
||||
use memory::MemoryRef;
|
||||
use memory_units::Pages;
|
||||
use parity_wasm::elements::{External, InitExpr, Instruction, Internal, ResizableLimits, Type};
|
||||
use runner::StackRecycler;
|
||||
use table::TableRef;
|
||||
use types::{GlobalDescriptor, MemoryDescriptor, TableDescriptor};
|
||||
use validation::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
|
||||
use {Error, MemoryInstance, Module, RuntimeValue, Signature, TableInstance};
|
||||
|
||||
/// Reference to a [`ModuleInstance`].
|
||||
///
|
||||
|
@ -32,7 +40,7 @@ use memory_units::Pages;
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct ModuleRef(pub(crate) Rc<ModuleInstance>);
|
||||
|
||||
impl ::std::ops::Deref for ModuleRef {
|
||||
impl ::core::ops::Deref for ModuleRef {
|
||||
type Target = ModuleInstance;
|
||||
fn deref(&self) -> &ModuleInstance {
|
||||
&self.0
|
||||
|
@ -154,7 +162,7 @@ pub struct ModuleInstance {
|
|||
funcs: RefCell<Vec<FuncRef>>,
|
||||
memories: RefCell<Vec<MemoryRef>>,
|
||||
globals: RefCell<Vec<GlobalRef>>,
|
||||
exports: RefCell<HashMap<String, ExternVal>>,
|
||||
exports: RefCell<BTreeMap<String, ExternVal>>,
|
||||
}
|
||||
|
||||
impl ModuleInstance {
|
||||
|
@ -165,7 +173,7 @@ impl ModuleInstance {
|
|||
tables: RefCell::new(Vec::new()),
|
||||
memories: RefCell::new(Vec::new()),
|
||||
globals: RefCell::new(Vec::new()),
|
||||
exports: RefCell::new(HashMap::new()),
|
||||
exports: RefCell::new(BTreeMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -209,6 +217,12 @@ impl ModuleInstance {
|
|||
self.globals.borrow_mut().push(global)
|
||||
}
|
||||
|
||||
/// Access all globals. This is a non-standard API so it's unlikely to be
|
||||
/// portable to other engines.
|
||||
pub fn globals<'a>(&self) -> Ref<Vec<GlobalRef>> {
|
||||
self.globals.borrow()
|
||||
}
|
||||
|
||||
fn insert_export<N: Into<String>>(&self, name: N, extern_val: ExternVal) {
|
||||
self.exports.borrow_mut().insert(name.into(), extern_val);
|
||||
}
|
||||
|
@ -248,9 +262,9 @@ impl ModuleInstance {
|
|||
|
||||
match (import.external(), extern_val) {
|
||||
(&External::Function(fn_type_idx), &ExternVal::Func(ref func)) => {
|
||||
let expected_fn_type = instance.signature_by_index(fn_type_idx).expect(
|
||||
"Due to validation function type should exists",
|
||||
);
|
||||
let expected_fn_type = instance
|
||||
.signature_by_index(fn_type_idx)
|
||||
.expect("Due to validation function type should exists");
|
||||
let actual_fn_type = func.signature();
|
||||
if &*expected_fn_type != actual_fn_type {
|
||||
return Err(Error::Instantiation(format!(
|
||||
|
@ -283,19 +297,19 @@ impl ModuleInstance {
|
|||
(expected_import, actual_extern_val) => {
|
||||
return Err(Error::Instantiation(format!(
|
||||
"Expected {:?} type, but provided {:?} extern_val",
|
||||
expected_import,
|
||||
actual_extern_val
|
||||
expected_import, actual_extern_val
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let labels = loaded_module.labels();
|
||||
let code = loaded_module.code();
|
||||
{
|
||||
let funcs = module.function_section().map(|fs| fs.entries()).unwrap_or(
|
||||
&[],
|
||||
);
|
||||
let funcs = module
|
||||
.function_section()
|
||||
.map(|fs| fs.entries())
|
||||
.unwrap_or(&[]);
|
||||
let bodies = module.code_section().map(|cs| cs.bodies()).unwrap_or(&[]);
|
||||
debug_assert!(
|
||||
funcs.len() == bodies.len(),
|
||||
|
@ -305,16 +319,15 @@ impl ModuleInstance {
|
|||
for (index, (ty, body)) in
|
||||
Iterator::zip(funcs.into_iter(), bodies.into_iter()).enumerate()
|
||||
{
|
||||
let signature = instance.signature_by_index(ty.type_ref()).expect(
|
||||
"Due to validation type should exists",
|
||||
);
|
||||
let labels = labels.get(&index).expect(
|
||||
let signature = instance
|
||||
.signature_by_index(ty.type_ref())
|
||||
.expect("Due to validation type should exists");
|
||||
let code = code.get(index).expect(
|
||||
"At func validation time labels are collected; Collected labels are added by index; qed",
|
||||
).clone();
|
||||
let func_body = FuncBody {
|
||||
locals: body.locals().to_vec(),
|
||||
opcodes: body.code().clone(),
|
||||
labels: labels,
|
||||
code: code,
|
||||
};
|
||||
let func_instance =
|
||||
FuncInstance::alloc_internal(Rc::downgrade(&instance.0), signature, func_body);
|
||||
|
@ -323,16 +336,15 @@ impl ModuleInstance {
|
|||
}
|
||||
|
||||
for table_type in module.table_section().map(|ts| ts.entries()).unwrap_or(&[]) {
|
||||
let table = TableInstance::alloc(
|
||||
table_type.limits().initial(),
|
||||
table_type.limits().maximum(),
|
||||
)?;
|
||||
let table =
|
||||
TableInstance::alloc(table_type.limits().initial(), table_type.limits().maximum())?;
|
||||
instance.push_table(table);
|
||||
}
|
||||
|
||||
for memory_type in module.memory_section().map(|ms| ms.entries()).unwrap_or(
|
||||
&[],
|
||||
)
|
||||
for memory_type in module
|
||||
.memory_section()
|
||||
.map(|ms| ms.entries())
|
||||
.unwrap_or(&[])
|
||||
{
|
||||
let initial: Pages = Pages(memory_type.limits().initial() as usize);
|
||||
let maximum: Option<Pages> = memory_type.limits().maximum().map(|m| Pages(m as usize));
|
||||
|
@ -342,46 +354,45 @@ impl ModuleInstance {
|
|||
instance.push_memory(memory);
|
||||
}
|
||||
|
||||
for global_entry in module.global_section().map(|gs| gs.entries()).unwrap_or(
|
||||
&[],
|
||||
)
|
||||
for global_entry in module
|
||||
.global_section()
|
||||
.map(|gs| gs.entries())
|
||||
.unwrap_or(&[])
|
||||
{
|
||||
let init_val = eval_init_expr(global_entry.init_expr(), &*instance);
|
||||
let global = GlobalInstance::alloc(
|
||||
init_val,
|
||||
global_entry.global_type().is_mutable(),
|
||||
);
|
||||
let global = GlobalInstance::alloc(init_val, global_entry.global_type().is_mutable());
|
||||
instance.push_global(global);
|
||||
}
|
||||
|
||||
for export in module.export_section().map(|es| es.entries()).unwrap_or(
|
||||
&[],
|
||||
)
|
||||
for export in module
|
||||
.export_section()
|
||||
.map(|es| es.entries())
|
||||
.unwrap_or(&[])
|
||||
{
|
||||
let field = export.field();
|
||||
let extern_val: ExternVal = match *export.internal() {
|
||||
Internal::Function(idx) => {
|
||||
let func = instance.func_by_index(idx).expect(
|
||||
"Due to validation func should exists",
|
||||
);
|
||||
let func = instance
|
||||
.func_by_index(idx)
|
||||
.expect("Due to validation func should exists");
|
||||
ExternVal::Func(func)
|
||||
}
|
||||
Internal::Global(idx) => {
|
||||
let global = instance.global_by_index(idx).expect(
|
||||
"Due to validation global should exists",
|
||||
);
|
||||
let global = instance
|
||||
.global_by_index(idx)
|
||||
.expect("Due to validation global should exists");
|
||||
ExternVal::Global(global)
|
||||
}
|
||||
Internal::Memory(idx) => {
|
||||
let memory = instance.memory_by_index(idx).expect(
|
||||
"Due to validation memory should exists",
|
||||
);
|
||||
let memory = instance
|
||||
.memory_by_index(idx)
|
||||
.expect("Due to validation memory should exists");
|
||||
ExternVal::Memory(memory)
|
||||
}
|
||||
Internal::Table(idx) => {
|
||||
let table = instance.table_by_index(idx).expect(
|
||||
"Due to validation table should exists",
|
||||
);
|
||||
let table = instance
|
||||
.table_by_index(idx)
|
||||
.expect("Due to validation table should exists");
|
||||
ExternVal::Table(table)
|
||||
}
|
||||
};
|
||||
|
@ -405,45 +416,56 @@ impl ModuleInstance {
|
|||
|
||||
let module_ref = ModuleInstance::alloc_module(loaded_module, extern_vals)?;
|
||||
|
||||
for element_segment in module.elements_section().map(|es| es.entries()).unwrap_or(
|
||||
&[],
|
||||
)
|
||||
for element_segment in module
|
||||
.elements_section()
|
||||
.map(|es| es.entries())
|
||||
.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,
|
||||
_ => panic!("Due to validation elem segment offset should evaluate to i32"),
|
||||
};
|
||||
|
||||
let table_inst = module_ref.table_by_index(DEFAULT_TABLE_INDEX).expect(
|
||||
"Due to validation default table should exists",
|
||||
);
|
||||
let table_inst = module_ref
|
||||
.table_by_index(DEFAULT_TABLE_INDEX)
|
||||
.expect("Due to validation default table should exists");
|
||||
|
||||
// This check is not only for bailing out early, but also to check the case when
|
||||
// segment consist of 0 members.
|
||||
if offset_val as u64 + element_segment.members().len() as u64 > table_inst.current_size() as u64 {
|
||||
return Err(
|
||||
Error::Instantiation("elements segment does not fit".to_string())
|
||||
);
|
||||
if offset_val as u64 + element_segment.members().len() as u64
|
||||
> table_inst.current_size() as u64
|
||||
{
|
||||
return Err(Error::Instantiation(
|
||||
"elements segment does not fit".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
for (j, func_idx) in element_segment.members().into_iter().enumerate() {
|
||||
let func = module_ref.func_by_index(*func_idx).expect(
|
||||
"Due to validation funcs from element segments should exists",
|
||||
);
|
||||
let func = module_ref
|
||||
.func_by_index(*func_idx)
|
||||
.expect("Due to validation funcs from element segments should exists");
|
||||
|
||||
table_inst.set(offset_val + j as u32, Some(func))?;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
_ => panic!("Due to validation data segment offset should evaluate to i32"),
|
||||
};
|
||||
|
||||
let memory_inst = module_ref.memory_by_index(DEFAULT_MEMORY_INDEX).expect(
|
||||
"Due to validation default memory should exists",
|
||||
);
|
||||
let memory_inst = module_ref
|
||||
.memory_by_index(DEFAULT_MEMORY_INDEX)
|
||||
.expect("Due to validation default memory should exists");
|
||||
memory_inst.set(offset_val, data_segment.value())?;
|
||||
}
|
||||
|
||||
|
@ -535,17 +557,20 @@ impl ModuleInstance {
|
|||
}
|
||||
External::Table(ref table_type) => {
|
||||
let table_descriptor = TableDescriptor::from_elements(table_type);
|
||||
let table = imports.resolve_table(module_name, field_name, &table_descriptor)?;
|
||||
let table =
|
||||
imports.resolve_table(module_name, field_name, &table_descriptor)?;
|
||||
ExternVal::Table(table)
|
||||
}
|
||||
External::Memory(ref memory_type) => {
|
||||
let memory_descriptor = MemoryDescriptor::from_elements(memory_type);
|
||||
let memory = imports.resolve_memory(module_name, field_name, &memory_descriptor)?;
|
||||
let memory =
|
||||
imports.resolve_memory(module_name, field_name, &memory_descriptor)?;
|
||||
ExternVal::Memory(memory)
|
||||
}
|
||||
External::Global(ref global_type) => {
|
||||
let global_descriptor = GlobalDescriptor::from_elements(global_type);
|
||||
let global = imports.resolve_global(module_name, field_name, &global_descriptor)?;
|
||||
let global =
|
||||
imports.resolve_global(module_name, field_name, &global_descriptor)?;
|
||||
ExternVal::Global(global)
|
||||
}
|
||||
};
|
||||
|
@ -566,7 +591,7 @@ impl ModuleInstance {
|
|||
///
|
||||
/// - there are no export with a given name or this export is not a function,
|
||||
/// - given arguments doesn't match to function signature,
|
||||
/// - trap occured at the execution time,
|
||||
/// - trap occurred at the execution time,
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -609,26 +634,45 @@ impl ModuleInstance {
|
|||
args: &[RuntimeValue],
|
||||
externals: &mut E,
|
||||
) -> Result<Option<RuntimeValue>, Error> {
|
||||
let extern_val = self.export_by_name(func_name).ok_or_else(|| {
|
||||
Error::Function(format!("Module doesn't have export {}", func_name))
|
||||
})?;
|
||||
let func_instance = self.func_by_name(func_name)?;
|
||||
|
||||
let func_instance = match extern_val {
|
||||
ExternVal::Func(func_instance) => func_instance,
|
||||
unexpected => {
|
||||
return Err(Error::Function(format!(
|
||||
"Export {} is not a function, but {:?}",
|
||||
func_name,
|
||||
unexpected
|
||||
)));
|
||||
FuncInstance::invoke(&func_instance, args, externals).map_err(|t| Error::Trap(t))
|
||||
}
|
||||
};
|
||||
|
||||
check_function_args(func_instance.signature(), &args)?;
|
||||
FuncInstance::invoke(&func_instance, args, externals)
|
||||
/// 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
|
||||
.export_by_name(func_name)
|
||||
.ok_or_else(|| Error::Function(format!("Module doesn't have export {}", func_name)))?;
|
||||
|
||||
match extern_val {
|
||||
ExternVal::Func(func_instance) => Ok(func_instance),
|
||||
unexpected => Err(Error::Function(format!(
|
||||
"Export {} is not a function, but {:?}",
|
||||
func_name, unexpected
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Find export by a name.
|
||||
///
|
||||
/// Returns `None` if there is no export with such name.
|
||||
|
@ -644,7 +688,7 @@ impl ModuleInstance {
|
|||
///
|
||||
/// You can still access not fully initialized instance by calling [`not_started_instance`],
|
||||
/// but keep in mind, that this is sort of escape hatch: module really might depend on initialization
|
||||
/// done in `start` function. It's definetely not recommended to call any exports on [`ModuleRef`]
|
||||
/// done in `start` function. It's definitely not recommended to call any exports on [`ModuleRef`]
|
||||
/// returned by this function.
|
||||
///
|
||||
/// If you sure, that there is no `start` function (e.g. because you created it without one), you can
|
||||
|
@ -681,9 +725,10 @@ impl<'a> NotStartedModuleRef<'a> {
|
|||
/// Returns `Err` if start function traps.
|
||||
pub fn run_start<E: Externals>(self, state: &mut E) -> Result<ModuleRef, Trap> {
|
||||
if let Some(start_fn_idx) = self.loaded_module.module().start_section() {
|
||||
let start_func = self.instance.func_by_index(start_fn_idx).expect(
|
||||
"Due to validation start function should exists",
|
||||
);
|
||||
let start_func = self
|
||||
.instance
|
||||
.func_by_index(start_fn_idx)
|
||||
.expect("Due to validation start function should exists");
|
||||
FuncInstance::invoke(&start_func, &[], state)?;
|
||||
}
|
||||
Ok(self.instance)
|
||||
|
@ -700,6 +745,13 @@ impl<'a> NotStartedModuleRef<'a> {
|
|||
}
|
||||
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 {
|
||||
|
@ -709,14 +761,14 @@ fn eval_init_expr(init_expr: &InitExpr, module: &ModuleInstance) -> RuntimeValue
|
|||
"Due to validation `code`.len() should be 2"
|
||||
);
|
||||
match code[0] {
|
||||
Opcode::I32Const(v) => v.into(),
|
||||
Opcode::I64Const(v) => v.into(),
|
||||
Opcode::F32Const(v) => RuntimeValue::decode_f32(v),
|
||||
Opcode::F64Const(v) => RuntimeValue::decode_f64(v),
|
||||
Opcode::GetGlobal(idx) => {
|
||||
let global = module.global_by_index(idx).expect(
|
||||
"Due to validation global should exists in module",
|
||||
);
|
||||
Instruction::I32Const(v) => v.into(),
|
||||
Instruction::I64Const(v) => v.into(),
|
||||
Instruction::F32Const(v) => RuntimeValue::decode_f32(v),
|
||||
Instruction::F64Const(v) => RuntimeValue::decode_f64(v),
|
||||
Instruction::GetGlobal(idx) => {
|
||||
let global = module
|
||||
.global_by_index(idx)
|
||||
.expect("Due to validation global should exists in module");
|
||||
global.get()
|
||||
}
|
||||
_ => panic!("Due to validation init should be a const expr"),
|
||||
|
@ -740,7 +792,7 @@ fn match_limits(l1: &ResizableLimits, l2: &ResizableLimits) -> Result<(), Error>
|
|||
"trying to import with limits l1.max={:?} and l2.max={:?}",
|
||||
l1.maximum(),
|
||||
l2.maximum()
|
||||
)))
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -761,14 +813,13 @@ pub fn check_limits(limits: &ResizableLimits) -> Result<(), Error> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use imports::ImportsBuilder;
|
||||
use super::{ExternVal, ModuleInstance};
|
||||
use func::FuncInstance;
|
||||
use types::{Signature, ValueType};
|
||||
use super::{ModuleInstance, ExternVal};
|
||||
use imports::ImportsBuilder;
|
||||
use tests::parse_wat;
|
||||
use types::{Signature, ValueType};
|
||||
|
||||
#[should_panic]
|
||||
#[test]
|
||||
|
@ -778,12 +829,11 @@ mod tests {
|
|||
(module
|
||||
(func $f)
|
||||
(start $f))
|
||||
"#
|
||||
"#,
|
||||
);
|
||||
ModuleInstance::new(
|
||||
&module_with_start,
|
||||
&ImportsBuilder::default()
|
||||
).unwrap().assert_no_start();
|
||||
let module = ModuleInstance::new(&module_with_start, &ImportsBuilder::default()).unwrap();
|
||||
assert!(!module.has_start());
|
||||
module.assert_no_start();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -796,40 +846,39 @@ mod tests {
|
|||
"#,
|
||||
);
|
||||
|
||||
assert!(
|
||||
ModuleInstance::with_externvals(
|
||||
assert!(ModuleInstance::with_externvals(
|
||||
&module_with_single_import,
|
||||
[
|
||||
ExternVal::Func(FuncInstance::alloc_host(Signature::new(&[][..], None), 0),)
|
||||
].iter(),
|
||||
).is_ok()
|
||||
);
|
||||
[ExternVal::Func(FuncInstance::alloc_host(
|
||||
Signature::new(&[][..], None),
|
||||
0
|
||||
),)]
|
||||
.iter(),
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
// externval vector is longer than import count.
|
||||
assert!(
|
||||
ModuleInstance::with_externvals(
|
||||
assert!(ModuleInstance::with_externvals(
|
||||
&module_with_single_import,
|
||||
[
|
||||
ExternVal::Func(FuncInstance::alloc_host(Signature::new(&[][..], None), 0)),
|
||||
ExternVal::Func(FuncInstance::alloc_host(Signature::new(&[][..], None), 1)),
|
||||
].iter(),
|
||||
).is_err()
|
||||
);
|
||||
]
|
||||
.iter(),
|
||||
)
|
||||
.is_err());
|
||||
|
||||
// externval vector is shorter than import count.
|
||||
assert!(ModuleInstance::with_externvals(&module_with_single_import, [].iter(),).is_err());
|
||||
|
||||
// externval vector has an unexpected type.
|
||||
assert!(
|
||||
ModuleInstance::with_externvals(
|
||||
assert!(ModuleInstance::with_externvals(
|
||||
&module_with_single_import,
|
||||
[
|
||||
ExternVal::Func(FuncInstance::alloc_host(
|
||||
[ExternVal::Func(FuncInstance::alloc_host(
|
||||
Signature::new(&[][..], Some(ValueType::I32)),
|
||||
0
|
||||
),)
|
||||
].iter(),
|
||||
).is_err()
|
||||
);
|
||||
),)]
|
||||
.iter(),
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,217 @@
|
|||
#![allow(missing_docs)]
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use libm::{F32Ext, F64Ext};
|
||||
|
||||
use core::cmp::{Ordering, PartialEq, PartialOrd};
|
||||
use core::ops::{Add, Div, Mul, Neg, Rem, Sub};
|
||||
|
||||
macro_rules! impl_binop {
|
||||
($for:ident, $is:ident, $op:ident, $func_name:ident) => {
|
||||
impl<T: Into<$for>> $op<T> for $for {
|
||||
type Output = Self;
|
||||
|
||||
fn $func_name(self, other: T) -> Self {
|
||||
$for(
|
||||
$op::$func_name($is::from_bits(self.0), $is::from_bits(other.into().0))
|
||||
.to_bits(),
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! float {
|
||||
($for:ident, $rep:ident, $is:ident) => {
|
||||
float!(
|
||||
$for,
|
||||
$rep,
|
||||
$is,
|
||||
1 << (::core::mem::size_of::<$is>() * 8 - 1)
|
||||
);
|
||||
};
|
||||
($for:ident, $rep:ident, $is:ident, $sign_bit:expr) => {
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct $for($rep);
|
||||
|
||||
impl_binop!($for, $is, Add, add);
|
||||
impl_binop!($for, $is, Sub, sub);
|
||||
impl_binop!($for, $is, Mul, mul);
|
||||
impl_binop!($for, $is, Div, div);
|
||||
impl_binop!($for, $is, Rem, rem);
|
||||
|
||||
impl $for {
|
||||
pub fn from_bits(other: $rep) -> Self {
|
||||
$for(other)
|
||||
}
|
||||
|
||||
pub fn to_bits(self) -> $rep {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn from_float(fl: $is) -> Self {
|
||||
fl.into()
|
||||
}
|
||||
|
||||
pub fn to_float(self) -> $is {
|
||||
self.into()
|
||||
}
|
||||
|
||||
pub fn is_nan(self) -> bool {
|
||||
self.to_float().is_nan()
|
||||
}
|
||||
|
||||
pub fn abs(self) -> Self {
|
||||
$for(self.0 & !$sign_bit)
|
||||
}
|
||||
|
||||
pub fn fract(self) -> Self {
|
||||
self.to_float().fract().into()
|
||||
}
|
||||
|
||||
pub fn min(self, other: Self) -> Self {
|
||||
Self::from(self.to_float().min(other.to_float()))
|
||||
}
|
||||
|
||||
pub fn max(self, other: Self) -> Self {
|
||||
Self::from(self.to_float().max(other.to_float()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$is> for $for {
|
||||
fn from(other: $is) -> $for {
|
||||
$for(other.to_bits())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$for> for $is {
|
||||
fn from(other: $for) -> $is {
|
||||
<$is>::from_bits(other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for $for {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self {
|
||||
$for(self.0 ^ $sign_bit)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<$for> + Copy> PartialEq<T> for $for {
|
||||
fn eq(&self, other: &T) -> bool {
|
||||
$is::from(*self) == $is::from((*other).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<$for> + Copy> PartialOrd<T> for $for {
|
||||
fn partial_cmp(&self, other: &T) -> Option<Ordering> {
|
||||
$is::from(*self).partial_cmp(&$is::from((*other).into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ::core::fmt::Debug for $for {
|
||||
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
|
||||
$is::from(*self).fmt(f)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
float!(F32, u32, f32);
|
||||
float!(F64, u64, f64);
|
||||
|
||||
impl From<u32> for F32 {
|
||||
fn from(other: u32) -> Self {
|
||||
Self::from_bits(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<F32> for u32 {
|
||||
fn from(other: F32) -> Self {
|
||||
other.to_bits()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for F64 {
|
||||
fn from(other: u64) -> Self {
|
||||
Self::from_bits(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<F64> for u64 {
|
||||
fn from(other: F64) -> Self {
|
||||
other.to_bits()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
extern crate rand;
|
||||
|
||||
use self::rand::Rng;
|
||||
|
||||
use super::{F32, F64};
|
||||
|
||||
use core::{
|
||||
fmt::Debug,
|
||||
iter,
|
||||
ops::{Add, Div, Mul, Neg, Sub},
|
||||
};
|
||||
|
||||
fn test_ops<T, F, I>(iter: I)
|
||||
where
|
||||
T: Add<Output = T>
|
||||
+ Div<Output = T>
|
||||
+ Mul<Output = T>
|
||||
+ Sub<Output = T>
|
||||
+ Neg<Output = T>
|
||||
+ Copy
|
||||
+ Debug
|
||||
+ PartialEq,
|
||||
F: Into<T>
|
||||
+ Add<Output = F>
|
||||
+ Div<Output = F>
|
||||
+ Mul<Output = F>
|
||||
+ Sub<Output = F>
|
||||
+ Neg<Output = F>
|
||||
+ Copy
|
||||
+ Debug,
|
||||
I: IntoIterator<Item = (F, F)>,
|
||||
{
|
||||
for (a, b) in iter {
|
||||
assert_eq!((a + b).into(), a.into() + b.into());
|
||||
assert_eq!((a - b).into(), a.into() - b.into());
|
||||
assert_eq!((a * b).into(), a.into() * b.into());
|
||||
assert_eq!((a / b).into(), a.into() / b.into());
|
||||
assert_eq!((-a).into(), -a.into());
|
||||
assert_eq!((-b).into(), -b.into());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_f32() {
|
||||
let mut rng = rand::thread_rng();
|
||||
let iter = iter::repeat(()).map(|_| rng.gen());
|
||||
|
||||
test_ops::<F32, f32, _>(iter.take(1000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ops_f64() {
|
||||
let mut rng = rand::thread_rng();
|
||||
let iter = iter::repeat(()).map(|_| rng.gen());
|
||||
|
||||
test_ops::<F64, f64, _>(iter.take(1000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_neg_nan_f32() {
|
||||
assert_eq!((-F32(0xff80_3210)).0, 0x7f80_3210);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_neg_nan_f64() {
|
||||
assert_eq!((-F64(0xff80_3210_0000_0000)).0, 0x7f80_3210_0000_0000);
|
||||
}
|
||||
}
|
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(())
|
||||
}
|
|
@ -0,0 +1,746 @@
|
|||
use super::{compile_module, CompiledModule};
|
||||
use parity_wasm::{deserialize_buffer, elements::Module};
|
||||
|
||||
use isa;
|
||||
use wabt;
|
||||
|
||||
fn validate(wat: &str) -> CompiledModule {
|
||||
let wasm = wabt::wat2wasm(wat).unwrap();
|
||||
let module = deserialize_buffer::<Module>(&wasm).unwrap();
|
||||
let compiled_module = compile_module(module).unwrap();
|
||||
compiled_module
|
||||
}
|
||||
|
||||
fn compile(module: &CompiledModule) -> (Vec<isa::Instruction>, Vec<u32>) {
|
||||
let code = &module.code_map[0];
|
||||
let mut instructions = Vec::new();
|
||||
let mut pcs = Vec::new();
|
||||
let mut iter = code.iterate_from(0);
|
||||
loop {
|
||||
let pc = iter.position();
|
||||
if let Some(instruction) = iter.next() {
|
||||
instructions.push(instruction.clone());
|
||||
pcs.push(pc);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
(instructions, pcs)
|
||||
}
|
||||
|
||||
macro_rules! targets {
|
||||
($($target:expr),*) => {
|
||||
::isa::BrTargets::from_internal(
|
||||
&[$($target,)*]
|
||||
.iter()
|
||||
.map(|&target| ::isa::InstructionInternal::BrTableTarget(target))
|
||||
.collect::<Vec<_>>()[..]
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn implicit_return_no_value() {
|
||||
let module = validate(
|
||||
r#"
|
||||
(module
|
||||
(func (export "call")
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
let (code, _) = compile(&module);
|
||||
assert_eq!(
|
||||
code,
|
||||
vec![isa::Instruction::Return(isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::None,
|
||||
})]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn implicit_return_with_value() {
|
||||
let module = validate(
|
||||
r#"
|
||||
(module
|
||||
(func (export "call") (result i32)
|
||||
i32.const 0
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
let (code, _) = compile(&module);
|
||||
assert_eq!(
|
||||
code,
|
||||
vec![
|
||||
isa::Instruction::I32Const(0),
|
||||
isa::Instruction::Return(isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::Single,
|
||||
}),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn implicit_return_param() {
|
||||
let module = validate(
|
||||
r#"
|
||||
(module
|
||||
(func (export "call") (param i32)
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
let (code, _) = compile(&module);
|
||||
assert_eq!(
|
||||
code,
|
||||
vec![isa::Instruction::Return(isa::DropKeep {
|
||||
drop: 1,
|
||||
keep: isa::Keep::None,
|
||||
}),]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_local() {
|
||||
let module = validate(
|
||||
r#"
|
||||
(module
|
||||
(func (export "call") (param i32) (result i32)
|
||||
get_local 0
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
let (code, _) = compile(&module);
|
||||
assert_eq!(
|
||||
code,
|
||||
vec![
|
||||
isa::Instruction::GetLocal(1),
|
||||
isa::Instruction::Return(isa::DropKeep {
|
||||
drop: 1,
|
||||
keep: isa::Keep::Single,
|
||||
}),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_return() {
|
||||
let module = validate(
|
||||
r#"
|
||||
(module
|
||||
(func (export "call") (param i32) (result i32)
|
||||
get_local 0
|
||||
return
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
let (code, _) = compile(&module);
|
||||
assert_eq!(
|
||||
code,
|
||||
vec![
|
||||
isa::Instruction::GetLocal(1),
|
||||
isa::Instruction::Return(isa::DropKeep {
|
||||
drop: 1,
|
||||
keep: isa::Keep::Single,
|
||||
}),
|
||||
isa::Instruction::Return(isa::DropKeep {
|
||||
drop: 1,
|
||||
keep: isa::Keep::Single,
|
||||
}),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_params() {
|
||||
let module = validate(
|
||||
r#"
|
||||
(module
|
||||
(func (export "call") (param i32) (param i32) (result i32)
|
||||
get_local 0
|
||||
get_local 1
|
||||
i32.add
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
let (code, _) = compile(&module);
|
||||
assert_eq!(
|
||||
code,
|
||||
vec![
|
||||
// This is tricky. Locals are now loaded from the stack. The load
|
||||
// happens from address relative of the current stack pointer. The first load
|
||||
// takes the value below the previous one (i.e the second argument) and then, it increments
|
||||
// the stack pointer. And then the same thing hapens with the value below the previous one
|
||||
// (which happens to be the value loaded by the first get_local).
|
||||
isa::Instruction::GetLocal(2),
|
||||
isa::Instruction::GetLocal(2),
|
||||
isa::Instruction::I32Add,
|
||||
isa::Instruction::Return(isa::DropKeep {
|
||||
drop: 2,
|
||||
keep: isa::Keep::Single,
|
||||
}),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop_locals() {
|
||||
let module = validate(
|
||||
r#"
|
||||
(module
|
||||
(func (export "call") (param i32)
|
||||
(local i32)
|
||||
get_local 0
|
||||
set_local 1
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
let (code, _) = compile(&module);
|
||||
assert_eq!(
|
||||
code,
|
||||
vec![
|
||||
isa::Instruction::GetLocal(2),
|
||||
isa::Instruction::SetLocal(1),
|
||||
isa::Instruction::Return(isa::DropKeep {
|
||||
drop: 2,
|
||||
keep: isa::Keep::None,
|
||||
}),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_without_else() {
|
||||
let module = validate(
|
||||
r#"
|
||||
(module
|
||||
(func (export "call") (param i32) (result i32)
|
||||
i32.const 1
|
||||
if
|
||||
i32.const 2
|
||||
return
|
||||
end
|
||||
i32.const 3
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
let (code, pcs) = compile(&module);
|
||||
assert_eq!(
|
||||
code,
|
||||
vec![
|
||||
isa::Instruction::I32Const(1),
|
||||
isa::Instruction::BrIfEqz(isa::Target {
|
||||
dst_pc: pcs[4],
|
||||
drop_keep: isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::None,
|
||||
},
|
||||
}),
|
||||
isa::Instruction::I32Const(2),
|
||||
isa::Instruction::Return(isa::DropKeep {
|
||||
drop: 1, // 1 param
|
||||
keep: isa::Keep::Single, // 1 result
|
||||
}),
|
||||
isa::Instruction::I32Const(3),
|
||||
isa::Instruction::Return(isa::DropKeep {
|
||||
drop: 1,
|
||||
keep: isa::Keep::Single,
|
||||
}),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_else() {
|
||||
let module = validate(
|
||||
r#"
|
||||
(module
|
||||
(func (export "call")
|
||||
(local i32)
|
||||
i32.const 1
|
||||
if
|
||||
i32.const 2
|
||||
set_local 0
|
||||
else
|
||||
i32.const 3
|
||||
set_local 0
|
||||
end
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
let (code, pcs) = compile(&module);
|
||||
assert_eq!(
|
||||
code,
|
||||
vec![
|
||||
isa::Instruction::I32Const(1),
|
||||
isa::Instruction::BrIfEqz(isa::Target {
|
||||
dst_pc: pcs[5],
|
||||
drop_keep: isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::None,
|
||||
},
|
||||
}),
|
||||
isa::Instruction::I32Const(2),
|
||||
isa::Instruction::SetLocal(1),
|
||||
isa::Instruction::Br(isa::Target {
|
||||
dst_pc: pcs[7],
|
||||
drop_keep: isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::None,
|
||||
},
|
||||
}),
|
||||
isa::Instruction::I32Const(3),
|
||||
isa::Instruction::SetLocal(1),
|
||||
isa::Instruction::Return(isa::DropKeep {
|
||||
drop: 1,
|
||||
keep: isa::Keep::None,
|
||||
}),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_else_returns_result() {
|
||||
let module = validate(
|
||||
r#"
|
||||
(module
|
||||
(func (export "call")
|
||||
i32.const 1
|
||||
if (result i32)
|
||||
i32.const 2
|
||||
else
|
||||
i32.const 3
|
||||
end
|
||||
drop
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
let (code, pcs) = compile(&module);
|
||||
assert_eq!(
|
||||
code,
|
||||
vec![
|
||||
isa::Instruction::I32Const(1),
|
||||
isa::Instruction::BrIfEqz(isa::Target {
|
||||
dst_pc: pcs[4],
|
||||
drop_keep: isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::None,
|
||||
},
|
||||
}),
|
||||
isa::Instruction::I32Const(2),
|
||||
isa::Instruction::Br(isa::Target {
|
||||
dst_pc: pcs[5],
|
||||
drop_keep: isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::None,
|
||||
},
|
||||
}),
|
||||
isa::Instruction::I32Const(3),
|
||||
isa::Instruction::Drop,
|
||||
isa::Instruction::Return(isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::None,
|
||||
}),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_else_branch_from_true_branch() {
|
||||
let module = validate(
|
||||
r#"
|
||||
(module
|
||||
(func (export "call")
|
||||
i32.const 1
|
||||
if (result i32)
|
||||
i32.const 1
|
||||
i32.const 1
|
||||
br_if 0
|
||||
drop
|
||||
i32.const 2
|
||||
else
|
||||
i32.const 3
|
||||
end
|
||||
drop
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
let (code, pcs) = compile(&module);
|
||||
assert_eq!(
|
||||
code,
|
||||
vec![
|
||||
isa::Instruction::I32Const(1),
|
||||
isa::Instruction::BrIfEqz(isa::Target {
|
||||
dst_pc: pcs[8],
|
||||
drop_keep: isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::None,
|
||||
},
|
||||
}),
|
||||
isa::Instruction::I32Const(1),
|
||||
isa::Instruction::I32Const(1),
|
||||
isa::Instruction::BrIfNez(isa::Target {
|
||||
dst_pc: pcs[9],
|
||||
drop_keep: isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::Single,
|
||||
},
|
||||
}),
|
||||
isa::Instruction::Drop,
|
||||
isa::Instruction::I32Const(2),
|
||||
isa::Instruction::Br(isa::Target {
|
||||
dst_pc: pcs[9],
|
||||
drop_keep: isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::None,
|
||||
},
|
||||
}),
|
||||
isa::Instruction::I32Const(3),
|
||||
isa::Instruction::Drop,
|
||||
isa::Instruction::Return(isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::None,
|
||||
}),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_else_branch_from_false_branch() {
|
||||
let module = validate(
|
||||
r#"
|
||||
(module
|
||||
(func (export "call")
|
||||
i32.const 1
|
||||
if (result i32)
|
||||
i32.const 1
|
||||
else
|
||||
i32.const 2
|
||||
i32.const 1
|
||||
br_if 0
|
||||
drop
|
||||
i32.const 3
|
||||
end
|
||||
drop
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
let (code, pcs) = compile(&module);
|
||||
assert_eq!(
|
||||
code,
|
||||
vec![
|
||||
isa::Instruction::I32Const(1),
|
||||
isa::Instruction::BrIfEqz(isa::Target {
|
||||
dst_pc: pcs[4],
|
||||
drop_keep: isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::None,
|
||||
},
|
||||
}),
|
||||
isa::Instruction::I32Const(1),
|
||||
isa::Instruction::Br(isa::Target {
|
||||
dst_pc: pcs[9],
|
||||
drop_keep: isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::None,
|
||||
},
|
||||
}),
|
||||
isa::Instruction::I32Const(2),
|
||||
isa::Instruction::I32Const(1),
|
||||
isa::Instruction::BrIfNez(isa::Target {
|
||||
dst_pc: pcs[9],
|
||||
drop_keep: isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::Single,
|
||||
},
|
||||
}),
|
||||
isa::Instruction::Drop,
|
||||
isa::Instruction::I32Const(3),
|
||||
isa::Instruction::Drop,
|
||||
isa::Instruction::Return(isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::None,
|
||||
}),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loop_() {
|
||||
let module = validate(
|
||||
r#"
|
||||
(module
|
||||
(func (export "call")
|
||||
loop (result i32)
|
||||
i32.const 1
|
||||
br_if 0
|
||||
i32.const 2
|
||||
end
|
||||
drop
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
let (code, _) = compile(&module);
|
||||
assert_eq!(
|
||||
code,
|
||||
vec![
|
||||
isa::Instruction::I32Const(1),
|
||||
isa::Instruction::BrIfNez(isa::Target {
|
||||
dst_pc: 0,
|
||||
drop_keep: isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::None,
|
||||
},
|
||||
}),
|
||||
isa::Instruction::I32Const(2),
|
||||
isa::Instruction::Drop,
|
||||
isa::Instruction::Return(isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::None,
|
||||
}),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loop_empty() {
|
||||
let module = validate(
|
||||
r#"
|
||||
(module
|
||||
(func (export "call")
|
||||
loop
|
||||
end
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
let (code, _) = compile(&module);
|
||||
assert_eq!(
|
||||
code,
|
||||
vec![isa::Instruction::Return(isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::None,
|
||||
}),]
|
||||
)
|
||||
}
|
||||
|
||||
#[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]
|
||||
fn brtable() {
|
||||
let module = validate(
|
||||
r#"
|
||||
(module
|
||||
(func (export "call")
|
||||
block $1
|
||||
loop $2
|
||||
i32.const 0
|
||||
br_table $2 $1
|
||||
end
|
||||
end
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
let (code, pcs) = compile(&module);
|
||||
assert_eq!(
|
||||
code,
|
||||
vec![
|
||||
isa::Instruction::I32Const(0),
|
||||
isa::Instruction::BrTable(targets![
|
||||
isa::Target {
|
||||
dst_pc: 0,
|
||||
drop_keep: isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::None,
|
||||
},
|
||||
},
|
||||
isa::Target {
|
||||
dst_pc: pcs[2],
|
||||
drop_keep: isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::None,
|
||||
},
|
||||
}
|
||||
]),
|
||||
isa::Instruction::Return(isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::None,
|
||||
}),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn brtable_returns_result() {
|
||||
let module = validate(
|
||||
r#"
|
||||
(module
|
||||
(func (export "call")
|
||||
block $1 (result i32)
|
||||
block $2 (result i32)
|
||||
i32.const 0
|
||||
i32.const 1
|
||||
br_table $2 $1
|
||||
end
|
||||
unreachable
|
||||
end
|
||||
drop
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
let (code, pcs) = compile(&module);
|
||||
println!("{:?}", (&code, &pcs));
|
||||
assert_eq!(
|
||||
code,
|
||||
vec![
|
||||
isa::Instruction::I32Const(0),
|
||||
isa::Instruction::I32Const(1),
|
||||
isa::Instruction::BrTable(targets![
|
||||
isa::Target {
|
||||
dst_pc: pcs[3],
|
||||
drop_keep: isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::Single,
|
||||
},
|
||||
},
|
||||
isa::Target {
|
||||
dst_pc: pcs[4],
|
||||
drop_keep: isa::DropKeep {
|
||||
keep: isa::Keep::Single,
|
||||
drop: 0,
|
||||
},
|
||||
}
|
||||
]),
|
||||
isa::Instruction::Unreachable,
|
||||
isa::Instruction::Drop,
|
||||
isa::Instruction::Return(isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::None,
|
||||
}),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wabt_example() {
|
||||
let module = validate(
|
||||
r#"
|
||||
(module
|
||||
(func (export "call") (param i32) (result i32)
|
||||
block $exit
|
||||
get_local 0
|
||||
br_if $exit
|
||||
i32.const 1
|
||||
return
|
||||
end
|
||||
i32.const 2
|
||||
return
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
let (code, pcs) = compile(&module);
|
||||
assert_eq!(
|
||||
code,
|
||||
vec![
|
||||
isa::Instruction::GetLocal(1),
|
||||
isa::Instruction::BrIfNez(isa::Target {
|
||||
dst_pc: pcs[4],
|
||||
drop_keep: isa::DropKeep {
|
||||
drop: 0,
|
||||
keep: isa::Keep::None,
|
||||
},
|
||||
}),
|
||||
isa::Instruction::I32Const(1),
|
||||
isa::Instruction::Return(isa::DropKeep {
|
||||
drop: 1, // 1 parameter
|
||||
keep: isa::Keep::Single,
|
||||
}),
|
||||
isa::Instruction::I32Const(2),
|
||||
isa::Instruction::Return(isa::DropKeep {
|
||||
drop: 1,
|
||||
keep: isa::Keep::Single,
|
||||
}),
|
||||
isa::Instruction::Return(isa::DropKeep {
|
||||
drop: 1,
|
||||
keep: isa::Keep::Single,
|
||||
}),
|
||||
]
|
||||
)
|
||||
}
|
1935
src/runner.rs
1935
src/runner.rs
File diff suppressed because it is too large
Load Diff
38
src/table.rs
38
src/table.rs
|
@ -1,11 +1,11 @@
|
|||
use std::u32;
|
||||
use std::fmt;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use parity_wasm::elements::ResizableLimits;
|
||||
use Error;
|
||||
use alloc::{rc::Rc, vec::Vec};
|
||||
use core::cell::RefCell;
|
||||
use core::fmt;
|
||||
use core::u32;
|
||||
use func::FuncRef;
|
||||
use module::check_limits;
|
||||
use parity_wasm::elements::ResizableLimits;
|
||||
use Error;
|
||||
|
||||
/// Reference to a table (See [`TableInstance`] for details).
|
||||
///
|
||||
|
@ -16,7 +16,7 @@ use module::check_limits;
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct TableRef(Rc<TableInstance>);
|
||||
|
||||
impl ::std::ops::Deref for TableRef {
|
||||
impl ::core::ops::Deref for TableRef {
|
||||
type Target = TableInstance;
|
||||
fn deref(&self) -> &TableInstance {
|
||||
&self.0
|
||||
|
@ -104,7 +104,9 @@ impl TableInstance {
|
|||
pub fn grow(&self, by: u32) -> Result<(), Error> {
|
||||
let mut buffer = self.buffer.borrow_mut();
|
||||
let maximum_size = self.maximum_size().unwrap_or(u32::MAX);
|
||||
let new_size = self.current_size().checked_add(by)
|
||||
let new_size = self
|
||||
.current_size()
|
||||
.checked_add(by)
|
||||
.and_then(|new_size| {
|
||||
if maximum_size < new_size {
|
||||
None
|
||||
|
@ -112,13 +114,13 @@ impl TableInstance {
|
|||
Some(new_size)
|
||||
}
|
||||
})
|
||||
.ok_or_else(||
|
||||
.ok_or_else(|| {
|
||||
Error::Table(format!(
|
||||
"Trying to grow table by {} items when there are already {} items",
|
||||
by,
|
||||
self.current_size(),
|
||||
))
|
||||
)?;
|
||||
})?;
|
||||
buffer.resize(new_size as usize, None);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -127,13 +129,12 @@ impl TableInstance {
|
|||
pub fn get(&self, offset: u32) -> Result<Option<FuncRef>, Error> {
|
||||
let buffer = self.buffer.borrow();
|
||||
let buffer_len = buffer.len();
|
||||
let table_elem = buffer.get(offset as usize).cloned().ok_or_else(||
|
||||
let table_elem = buffer.get(offset as usize).cloned().ok_or_else(|| {
|
||||
Error::Table(format!(
|
||||
"trying to read table item with index {} when there are only {} items",
|
||||
offset,
|
||||
buffer_len
|
||||
)),
|
||||
)?;
|
||||
offset, buffer_len
|
||||
))
|
||||
})?;
|
||||
Ok(table_elem)
|
||||
}
|
||||
|
||||
|
@ -141,13 +142,12 @@ impl TableInstance {
|
|||
pub fn set(&self, offset: u32, value: Option<FuncRef>) -> Result<(), Error> {
|
||||
let mut buffer = self.buffer.borrow_mut();
|
||||
let buffer_len = buffer.len();
|
||||
let table_elem = buffer.get_mut(offset as usize).ok_or_else(||
|
||||
let table_elem = buffer.get_mut(offset as usize).ok_or_else(|| {
|
||||
Error::Table(format!(
|
||||
"trying to update table item with index {} when there are only {} items",
|
||||
offset,
|
||||
buffer_len
|
||||
offset, buffer_len
|
||||
))
|
||||
)?;
|
||||
})?;
|
||||
*table_elem = value;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
use {
|
||||
Error, Signature, Externals, FuncInstance, FuncRef, HostError, ImportsBuilder,
|
||||
MemoryInstance, MemoryRef, TableInstance, TableRef, ModuleImportResolver, ModuleInstance, ModuleRef,
|
||||
RuntimeValue, RuntimeArgs, TableDescriptor, MemoryDescriptor, Trap, TrapKind,
|
||||
};
|
||||
use types::ValueType;
|
||||
use memory_units::Pages;
|
||||
use super::parse_wat;
|
||||
use memory_units::Pages;
|
||||
use types::ValueType;
|
||||
use {
|
||||
Error, Externals, FuncInstance, FuncRef, HostError, ImportsBuilder, MemoryDescriptor,
|
||||
MemoryInstance, MemoryRef, ModuleImportResolver, ModuleInstance, ModuleRef, ResumableError,
|
||||
RuntimeArgs, RuntimeValue, Signature, TableDescriptor, TableInstance, TableRef, Trap, TrapKind,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
struct HostErrorWithCode {
|
||||
error_code: u32,
|
||||
}
|
||||
|
||||
impl ::std::fmt::Display for HostErrorWithCode {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> {
|
||||
impl ::core::fmt::Display for HostErrorWithCode {
|
||||
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> Result<(), ::core::fmt::Error> {
|
||||
write!(f, "{}", self.error_code)
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,8 @@ impl HostError for HostErrorWithCode {}
|
|||
struct TestHost {
|
||||
memory: Option<MemoryRef>,
|
||||
instance: Option<ModuleRef>,
|
||||
|
||||
trap_sub_result: Option<RuntimeValue>,
|
||||
}
|
||||
|
||||
impl TestHost {
|
||||
|
@ -39,6 +41,8 @@ impl TestHost {
|
|||
TestHost {
|
||||
memory: Some(MemoryInstance::alloc(Pages(1), Some(Pages(1))).unwrap()),
|
||||
instance: None,
|
||||
|
||||
trap_sub_result: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -75,6 +79,11 @@ const GET_MEM_FUNC_INDEX: usize = 3;
|
|||
/// This function requires attached module instance.
|
||||
const RECURSE_FUNC_INDEX: usize = 4;
|
||||
|
||||
/// trap_sub(a: i32, b: i32) -> i32
|
||||
///
|
||||
/// This function is the same as sub(a, b), but it will send a Host trap which pauses the interpreter execution.
|
||||
const TRAP_SUB_FUNC_INDEX: usize = 5;
|
||||
|
||||
impl Externals for TestHost {
|
||||
fn invoke_index(
|
||||
&mut self,
|
||||
|
@ -98,9 +107,10 @@ impl Externals for TestHost {
|
|||
INC_MEM_FUNC_INDEX => {
|
||||
let ptr: u32 = args.nth(0);
|
||||
|
||||
let memory = self.memory.as_ref().expect(
|
||||
"Function 'inc_mem' expects attached memory",
|
||||
);
|
||||
let memory = self
|
||||
.memory
|
||||
.as_ref()
|
||||
.expect("Function 'inc_mem' expects attached memory");
|
||||
let mut buf = [0u8; 1];
|
||||
memory.get_into(ptr, &mut buf).unwrap();
|
||||
buf[0] += 1;
|
||||
|
@ -111,18 +121,22 @@ impl Externals for TestHost {
|
|||
GET_MEM_FUNC_INDEX => {
|
||||
let ptr: u32 = args.nth(0);
|
||||
|
||||
let memory = self.memory.as_ref().expect(
|
||||
"Function 'get_mem' expects attached memory",
|
||||
);
|
||||
let memory = self
|
||||
.memory
|
||||
.as_ref()
|
||||
.expect("Function 'get_mem' expects attached memory");
|
||||
let mut buf = [0u8; 1];
|
||||
memory.get_into(ptr, &mut buf).unwrap();
|
||||
|
||||
Ok(Some(RuntimeValue::I32(buf[0] as i32)))
|
||||
}
|
||||
RECURSE_FUNC_INDEX => {
|
||||
let val = args.nth_value_checked(0).expect("Exactly one argument expected");
|
||||
let val = args
|
||||
.nth_value_checked(0)
|
||||
.expect("Exactly one argument expected");
|
||||
|
||||
let instance = self.instance
|
||||
let instance = self
|
||||
.instance
|
||||
.as_ref()
|
||||
.expect("Function 'recurse' expects attached module instance")
|
||||
.clone();
|
||||
|
@ -132,10 +146,20 @@ impl Externals for TestHost {
|
|||
.expect("expected to be Some");
|
||||
|
||||
if val.value_type() != result.value_type() {
|
||||
return Err(TrapKind::Host(Box::new(HostErrorWithCode { error_code: 123 })).into());
|
||||
return Err(
|
||||
TrapKind::Host(Box::new(HostErrorWithCode { error_code: 123 })).into(),
|
||||
);
|
||||
}
|
||||
Ok(Some(result))
|
||||
}
|
||||
TRAP_SUB_FUNC_INDEX => {
|
||||
let a: i32 = args.nth(0);
|
||||
let b: i32 = args.nth(1);
|
||||
|
||||
let result: RuntimeValue = (a - b).into();
|
||||
self.trap_sub_result = Some(result);
|
||||
return Err(TrapKind::Host(Box::new(HostErrorWithCode { error_code: 301 })).into());
|
||||
}
|
||||
_ => panic!("env doesn't provide function at index {}", index),
|
||||
}
|
||||
}
|
||||
|
@ -157,6 +181,7 @@ impl TestHost {
|
|||
ERR_FUNC_INDEX => (&[ValueType::I32], None),
|
||||
INC_MEM_FUNC_INDEX => (&[ValueType::I32], None),
|
||||
GET_MEM_FUNC_INDEX => (&[ValueType::I32], Some(ValueType::I32)),
|
||||
TRAP_SUB_FUNC_INDEX => (&[ValueType::I32, ValueType::I32], Some(ValueType::I32)),
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
|
@ -172,18 +197,19 @@ impl ModuleImportResolver for TestHost {
|
|||
"inc_mem" => INC_MEM_FUNC_INDEX,
|
||||
"get_mem" => GET_MEM_FUNC_INDEX,
|
||||
"recurse" => RECURSE_FUNC_INDEX,
|
||||
"trap_sub" => TRAP_SUB_FUNC_INDEX,
|
||||
_ => {
|
||||
return Err(Error::Instantiation(
|
||||
format!("Export {} not found", field_name),
|
||||
))
|
||||
return Err(Error::Instantiation(format!(
|
||||
"Export {} not found",
|
||||
field_name
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
if !self.check_signature(index, signature) {
|
||||
return Err(Error::Instantiation(format!(
|
||||
"Export `{}` doesnt match expected type {:?}",
|
||||
field_name,
|
||||
signature
|
||||
field_name, signature
|
||||
)));
|
||||
}
|
||||
|
||||
|
@ -195,9 +221,10 @@ impl ModuleImportResolver for TestHost {
|
|||
field_name: &str,
|
||||
_memory_type: &MemoryDescriptor,
|
||||
) -> Result<MemoryRef, Error> {
|
||||
Err(Error::Instantiation(
|
||||
format!("Export {} not found", field_name),
|
||||
))
|
||||
Err(Error::Instantiation(format!(
|
||||
"Export {} not found",
|
||||
field_name
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -225,13 +252,116 @@ fn call_host_func() {
|
|||
.assert_no_start();
|
||||
|
||||
assert_eq!(
|
||||
instance.invoke_export("test", &[], &mut env).expect(
|
||||
"Failed to invoke 'test' function",
|
||||
),
|
||||
instance
|
||||
.invoke_export("test", &[], &mut env)
|
||||
.expect("Failed to invoke 'test' function",),
|
||||
Some(RuntimeValue::I32(-2))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resume_call_host_func() {
|
||||
let module = parse_wat(
|
||||
r#"
|
||||
(module
|
||||
(import "env" "trap_sub" (func $trap_sub (param i32 i32) (result i32)))
|
||||
|
||||
(func (export "test") (result i32)
|
||||
(call $trap_sub
|
||||
(i32.const 5)
|
||||
(i32.const 7)
|
||||
)
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
|
||||
let mut env = TestHost::new();
|
||||
|
||||
let instance = ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &env))
|
||||
.expect("Failed to instantiate module")
|
||||
.assert_no_start();
|
||||
|
||||
let export = instance.export_by_name("test").unwrap();
|
||||
let func_instance = export.as_func().unwrap();
|
||||
|
||||
let mut invocation = FuncInstance::invoke_resumable(&func_instance, &[][..]).unwrap();
|
||||
let result = invocation.start_execution(&mut env);
|
||||
match result {
|
||||
Err(ResumableError::Trap(_)) => {}
|
||||
_ => panic!(),
|
||||
}
|
||||
|
||||
assert!(invocation.is_resumable());
|
||||
let trap_sub_result = env.trap_sub_result.take();
|
||||
assert_eq!(
|
||||
invocation
|
||||
.resume_execution(trap_sub_result, &mut env)
|
||||
.expect("Failed to invoke 'test' function",),
|
||||
Some(RuntimeValue::I32(-2))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resume_call_host_func_type_mismatch() {
|
||||
fn resume_with_val(val: Option<RuntimeValue>) {
|
||||
let module = parse_wat(
|
||||
r#"
|
||||
(module
|
||||
(import "env" "trap_sub" (func $trap_sub (param i32 i32) (result i32)))
|
||||
|
||||
(func (export "test") (result i32)
|
||||
(call $trap_sub
|
||||
(i32.const 5)
|
||||
(i32.const 7)
|
||||
)
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
|
||||
let mut env = TestHost::new();
|
||||
|
||||
let instance =
|
||||
ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &env))
|
||||
.expect("Failed to instantiate module")
|
||||
.assert_no_start();
|
||||
|
||||
let export = instance.export_by_name("test").unwrap();
|
||||
let func_instance = export.as_func().unwrap();
|
||||
|
||||
let mut invocation = FuncInstance::invoke_resumable(&func_instance, &[][..]).unwrap();
|
||||
let result = invocation.start_execution(&mut env);
|
||||
match result {
|
||||
Err(ResumableError::Trap(_)) => {}
|
||||
_ => panic!(),
|
||||
}
|
||||
|
||||
assert!(invocation.is_resumable());
|
||||
let err = invocation.resume_execution(val, &mut env).unwrap_err();
|
||||
|
||||
match &err {
|
||||
ResumableError::Trap(trap) => {
|
||||
if let TrapKind::UnexpectedSignature = trap.kind() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// If didn't return in the previous `match`...
|
||||
|
||||
panic!(
|
||||
"Expected `ResumableError::Trap(Trap {{ kind: \
|
||||
TrapKind::UnexpectedSignature, }})`, got `{:?}`",
|
||||
err
|
||||
)
|
||||
}
|
||||
|
||||
resume_with_val(None);
|
||||
resume_with_val(Some((-1i64).into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn host_err() {
|
||||
let module = parse_wat(
|
||||
|
@ -254,13 +384,15 @@ fn host_err() {
|
|||
.expect("Failed to instantiate module")
|
||||
.assert_no_start();
|
||||
|
||||
let error = instance.invoke_export("test", &[], &mut env).expect_err(
|
||||
"`test` expected to return error",
|
||||
);
|
||||
let error = instance
|
||||
.invoke_export("test", &[], &mut env)
|
||||
.expect_err("`test` expected to return error");
|
||||
|
||||
let error_with_code = error.as_host_error().expect("Expected host error").downcast_ref::<HostErrorWithCode>().expect(
|
||||
"Failed to downcast to expected error type",
|
||||
);
|
||||
let error_with_code = error
|
||||
.as_host_error()
|
||||
.expect("Expected host error")
|
||||
.downcast_ref::<HostErrorWithCode>()
|
||||
.expect("Failed to downcast to expected error type");
|
||||
assert_eq!(error_with_code.error_code, 228);
|
||||
}
|
||||
|
||||
|
@ -289,9 +421,9 @@ fn modify_mem_with_host_funcs() {
|
|||
.expect("Failed to instantiate module")
|
||||
.assert_no_start();
|
||||
|
||||
instance.invoke_export("modify_mem", &[], &mut env).expect(
|
||||
"Failed to invoke 'test' function",
|
||||
);
|
||||
instance
|
||||
.invoke_export("modify_mem", &[], &mut env)
|
||||
.expect("Failed to invoke 'test' function");
|
||||
|
||||
// Check contents of memory at address 12.
|
||||
let mut buf = [0u8; 1];
|
||||
|
@ -325,6 +457,8 @@ fn pull_internal_mem_from_module() {
|
|||
let mut env = TestHost {
|
||||
memory: None,
|
||||
instance: None,
|
||||
|
||||
trap_sub_result: None,
|
||||
};
|
||||
|
||||
let instance = ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &env))
|
||||
|
@ -387,9 +521,9 @@ fn recursion() {
|
|||
env.instance = Some(instance.clone());
|
||||
|
||||
assert_eq!(
|
||||
instance.invoke_export("test", &[], &mut env).expect(
|
||||
"Failed to invoke 'test' function",
|
||||
),
|
||||
instance
|
||||
.invoke_export("test", &[], &mut env)
|
||||
.expect("Failed to invoke 'test' function",),
|
||||
// 363 = 321 + 42
|
||||
Some(RuntimeValue::I64(363))
|
||||
);
|
||||
|
@ -408,21 +542,17 @@ fn defer_providing_externals() {
|
|||
}
|
||||
|
||||
impl ModuleImportResolver for HostImportResolver {
|
||||
fn resolve_func(
|
||||
&self,
|
||||
field_name: &str,
|
||||
signature: &Signature,
|
||||
) -> Result<FuncRef, Error> {
|
||||
fn resolve_func(&self, field_name: &str, signature: &Signature) -> Result<FuncRef, Error> {
|
||||
if field_name != "inc" {
|
||||
return Err(Error::Instantiation(
|
||||
format!("Export {} not found", field_name),
|
||||
));
|
||||
return Err(Error::Instantiation(format!(
|
||||
"Export {} not found",
|
||||
field_name
|
||||
)));
|
||||
}
|
||||
if signature.params() != &[ValueType::I32] || signature.return_type() != None {
|
||||
return Err(Error::Instantiation(format!(
|
||||
"Export `{}` doesnt match expected type {:?}",
|
||||
field_name,
|
||||
signature
|
||||
field_name, signature
|
||||
)));
|
||||
}
|
||||
|
||||
|
@ -437,9 +567,10 @@ fn defer_providing_externals() {
|
|||
if field_name == "mem" {
|
||||
Ok(self.mem.clone())
|
||||
} else {
|
||||
Err(Error::Instantiation(
|
||||
format!("Export {} not found", field_name),
|
||||
))
|
||||
Err(Error::Instantiation(format!(
|
||||
"Export {} not found",
|
||||
field_name
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -483,14 +614,16 @@ fn defer_providing_externals() {
|
|||
|
||||
// Create HostImportResolver with some initialized memory instance.
|
||||
// This memory instance will be provided as 'mem' export.
|
||||
let host_import_resolver =
|
||||
HostImportResolver { mem: MemoryInstance::alloc(Pages(1), Some(Pages(1))).unwrap() };
|
||||
let host_import_resolver = HostImportResolver {
|
||||
mem: MemoryInstance::alloc(Pages(1), Some(Pages(1))).unwrap(),
|
||||
};
|
||||
|
||||
// Instantiate module with `host_import_resolver` as import resolver for "host" module.
|
||||
let instance = ModuleInstance::new(
|
||||
&module,
|
||||
&ImportsBuilder::new().with_resolver("host", &host_import_resolver),
|
||||
).expect("Failed to instantiate module")
|
||||
)
|
||||
.expect("Failed to instantiate module")
|
||||
.assert_no_start();
|
||||
|
||||
let mut acc = 89;
|
||||
|
@ -535,18 +668,15 @@ fn two_envs_one_externals() {
|
|||
struct OrdinaryResolver;
|
||||
|
||||
impl ModuleImportResolver for PrivilegedResolver {
|
||||
fn resolve_func(
|
||||
&self,
|
||||
field_name: &str,
|
||||
signature: &Signature,
|
||||
) -> Result<FuncRef, Error> {
|
||||
fn resolve_func(&self, field_name: &str, signature: &Signature) -> Result<FuncRef, Error> {
|
||||
let index = match field_name {
|
||||
"ordinary" => ORDINARY_FUNC_INDEX,
|
||||
"privileged" => PRIVILEGED_FUNC_INDEX,
|
||||
_ => {
|
||||
return Err(Error::Instantiation(
|
||||
format!("Export {} not found", field_name),
|
||||
))
|
||||
return Err(Error::Instantiation(format!(
|
||||
"Export {} not found",
|
||||
field_name
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -555,22 +685,19 @@ fn two_envs_one_externals() {
|
|||
}
|
||||
|
||||
impl ModuleImportResolver for OrdinaryResolver {
|
||||
fn resolve_func(
|
||||
&self,
|
||||
field_name: &str,
|
||||
signature: &Signature,
|
||||
) -> Result<FuncRef, Error> {
|
||||
fn resolve_func(&self, field_name: &str, signature: &Signature) -> Result<FuncRef, Error> {
|
||||
let index = match field_name {
|
||||
"ordinary" => ORDINARY_FUNC_INDEX,
|
||||
"privileged" => {
|
||||
return Err(Error::Instantiation(
|
||||
"'priveleged' can be imported only in privileged context".into(),
|
||||
))
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::Instantiation(
|
||||
format!("Export {} not found", field_name),
|
||||
))
|
||||
return Err(Error::Instantiation(format!(
|
||||
"Export {} not found",
|
||||
field_name
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -609,7 +736,8 @@ fn two_envs_one_externals() {
|
|||
let trusted_instance = ModuleInstance::new(
|
||||
&trusted_module,
|
||||
&ImportsBuilder::new().with_resolver("env", &PrivilegedResolver),
|
||||
).expect("Failed to instantiate module")
|
||||
)
|
||||
.expect("Failed to instantiate module")
|
||||
.assert_no_start();
|
||||
|
||||
let untrusted_instance = ModuleInstance::new(
|
||||
|
@ -617,7 +745,8 @@ fn two_envs_one_externals() {
|
|||
&ImportsBuilder::new()
|
||||
.with_resolver("env", &OrdinaryResolver)
|
||||
.with_resolver("trusted", &trusted_instance),
|
||||
).expect("Failed to instantiate module")
|
||||
)
|
||||
.expect("Failed to instantiate module")
|
||||
.assert_no_start();
|
||||
|
||||
untrusted_instance
|
||||
|
@ -652,7 +781,8 @@ fn dynamically_add_host_func() {
|
|||
Signature::new(&[][..], Some(ValueType::I32)),
|
||||
host_func_index as usize,
|
||||
);
|
||||
self.table.set(table_index, Some(added_func))
|
||||
self.table
|
||||
.set(table_index, Some(added_func))
|
||||
.map_err(|_| TrapKind::TableAccessOutOfBounds)?;
|
||||
|
||||
Ok(Some(RuntimeValue::I32(table_index as i32)))
|
||||
|
@ -666,17 +796,14 @@ fn dynamically_add_host_func() {
|
|||
}
|
||||
|
||||
impl ModuleImportResolver for HostExternals {
|
||||
fn resolve_func(
|
||||
&self,
|
||||
field_name: &str,
|
||||
signature: &Signature,
|
||||
) -> Result<FuncRef, Error> {
|
||||
fn resolve_func(&self, field_name: &str, signature: &Signature) -> Result<FuncRef, Error> {
|
||||
let index = match field_name {
|
||||
"add_func" => ADD_FUNC_FUNC_INDEX,
|
||||
_ => {
|
||||
return Err(Error::Instantiation(
|
||||
format!("Export {} not found", field_name),
|
||||
))
|
||||
return Err(Error::Instantiation(format!(
|
||||
"Export {} not found",
|
||||
field_name
|
||||
)));
|
||||
}
|
||||
};
|
||||
Ok(FuncInstance::alloc_host(signature.clone(), index))
|
||||
|
@ -690,9 +817,10 @@ fn dynamically_add_host_func() {
|
|||
if field_name == "table" {
|
||||
Ok(self.table.clone())
|
||||
} else {
|
||||
Err(Error::Instantiation(
|
||||
format!("Export {} not found", field_name),
|
||||
))
|
||||
Err(Error::Instantiation(format!(
|
||||
"Export {} not found",
|
||||
field_name
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -724,7 +852,8 @@ fn dynamically_add_host_func() {
|
|||
let instance = ModuleInstance::new(
|
||||
&module,
|
||||
&ImportsBuilder::new().with_resolver("env", &host_externals),
|
||||
).expect("Failed to instantiate module")
|
||||
)
|
||||
.expect("Failed to instantiate module")
|
||||
.assert_no_start();
|
||||
|
||||
assert_eq!(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use wabt;
|
||||
use {Module};
|
||||
use Module;
|
||||
|
||||
mod host;
|
||||
mod wasm;
|
||||
|
@ -17,6 +17,25 @@ fn assert_error_properties() {
|
|||
assert_std_err_impl::<Error>();
|
||||
}
|
||||
|
||||
/// Test that converting an u32 (u64) that does not fit in an i32 (i64)
|
||||
/// to a RuntimeValue and back works as expected and the number remains unchanged.
|
||||
#[test]
|
||||
fn unsigned_to_runtime_value() {
|
||||
use super::RuntimeValue;
|
||||
|
||||
let overflow_i32: u32 = ::core::i32::MAX as u32 + 1;
|
||||
assert_eq!(
|
||||
RuntimeValue::from(overflow_i32).try_into::<u32>().unwrap(),
|
||||
overflow_i32
|
||||
);
|
||||
|
||||
let overflow_i64: u64 = ::core::i64::MAX as u64 + 1;
|
||||
assert_eq!(
|
||||
RuntimeValue::from(overflow_i64).try_into::<u64>().unwrap(),
|
||||
overflow_i64
|
||||
);
|
||||
}
|
||||
|
||||
pub fn parse_wat(source: &str) -> Module {
|
||||
let wasm_binary = wabt::wat2wasm(source).expect("Failed to parse wat source");
|
||||
Module::from_buffer(wasm_binary).expect("Failed to load parsed module")
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use {
|
||||
Error, Signature, FuncRef, GlobalInstance, GlobalRef, ImportsBuilder, MemoryInstance,
|
||||
MemoryRef, ModuleImportResolver, ModuleInstance, NopExternals, RuntimeValue,
|
||||
TableInstance, TableRef, Module, GlobalDescriptor, TableDescriptor, MemoryDescriptor,
|
||||
};
|
||||
use memory_units::Pages;
|
||||
use std::fs::File;
|
||||
use {
|
||||
Error, FuncRef, GlobalDescriptor, GlobalInstance, GlobalRef, ImportsBuilder, MemoryDescriptor,
|
||||
MemoryInstance, MemoryRef, Module, ModuleImportResolver, ModuleInstance, NopExternals,
|
||||
RuntimeValue, Signature, TableDescriptor, TableInstance, TableRef,
|
||||
};
|
||||
|
||||
struct Env {
|
||||
table_base: GlobalRef,
|
||||
|
@ -60,12 +60,17 @@ impl ModuleImportResolver for Env {
|
|||
}
|
||||
}
|
||||
|
||||
fn resolve_table(&self, field_name: &str, _table_type: &TableDescriptor) -> Result<TableRef, Error> {
|
||||
fn resolve_table(
|
||||
&self,
|
||||
field_name: &str,
|
||||
_table_type: &TableDescriptor,
|
||||
) -> Result<TableRef, Error> {
|
||||
match field_name {
|
||||
"table" => Ok(self.table.clone()),
|
||||
_ => Err(Error::Instantiation(
|
||||
format!("env module doesn't provide table '{}'", field_name),
|
||||
)),
|
||||
_ => Err(Error::Instantiation(format!(
|
||||
"env module doesn't provide table '{}'",
|
||||
field_name
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -90,10 +95,8 @@ fn interpreter_inc_i32() {
|
|||
|
||||
let env = Env::new();
|
||||
|
||||
let instance = ModuleInstance::new(
|
||||
&module,
|
||||
&ImportsBuilder::new().with_resolver("env", &env),
|
||||
).expect("Failed to instantiate module")
|
||||
let instance = ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &env))
|
||||
.expect("Failed to instantiate module")
|
||||
.assert_no_start();
|
||||
|
||||
let i32_val = 42;
|
||||
|
@ -114,17 +117,14 @@ fn interpreter_accumulate_u8() {
|
|||
// The WASM file containing the module and function
|
||||
const WASM_FILE: &str = &"res/fixtures/accumulate_u8.wast";
|
||||
// The octet sequence being accumulated
|
||||
const BUF: &[u8] = &[9,8,7,6,5,4,3,2,1];
|
||||
|
||||
const BUF: &[u8] = &[9, 8, 7, 6, 5, 4, 3, 2, 1];
|
||||
|
||||
// Load the module-structure from wasm-file and add to program
|
||||
let module = load_from_file(WASM_FILE);
|
||||
|
||||
let env = Env::new();
|
||||
let instance = ModuleInstance::new(
|
||||
&module,
|
||||
&ImportsBuilder::new().with_resolver("env", &env),
|
||||
).expect("Failed to instantiate module")
|
||||
let instance = ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &env))
|
||||
.expect("Failed to instantiate module")
|
||||
.assert_no_start();
|
||||
|
||||
let env_memory = env.memory.clone();
|
||||
|
@ -134,7 +134,10 @@ fn interpreter_accumulate_u8() {
|
|||
let _ = env_memory.set(offset, BUF);
|
||||
|
||||
// Set up the function argument list and invoke the function
|
||||
let args = &[RuntimeValue::I32(BUF.len() as i32), RuntimeValue::I32(offset as i32)];
|
||||
let args = &[
|
||||
RuntimeValue::I32(BUF.len() as i32),
|
||||
RuntimeValue::I32(offset as i32),
|
||||
];
|
||||
let retval = instance
|
||||
.invoke_export(FUNCTION_NAME, args, &mut NopExternals)
|
||||
.expect("Failed to execute function");
|
||||
|
|
14
src/types.rs
14
src/types.rs
|
@ -1,7 +1,8 @@
|
|||
use std::borrow::Cow;
|
||||
use alloc::borrow::Cow;
|
||||
|
||||
use parity_wasm::elements::{
|
||||
FunctionType, ValueType as EValueType, GlobalType, TableType, MemoryType};
|
||||
FunctionType, GlobalType, MemoryType, TableType, ValueType as EValueType,
|
||||
};
|
||||
|
||||
/// Signature of a [function].
|
||||
///
|
||||
|
@ -38,7 +39,7 @@ impl Signature {
|
|||
/// ```
|
||||
pub fn new<C: Into<Cow<'static, [ValueType]>>>(
|
||||
params: C,
|
||||
return_type: Option<ValueType>
|
||||
return_type: Option<ValueType>,
|
||||
) -> Signature {
|
||||
Signature {
|
||||
params: params.into(),
|
||||
|
@ -58,7 +59,12 @@ impl Signature {
|
|||
|
||||
pub(crate) fn from_elements(func_type: &FunctionType) -> Signature {
|
||||
Signature {
|
||||
params: func_type.params().iter().cloned().map(ValueType::from_elements).collect(),
|
||||
params: func_type
|
||||
.params()
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(ValueType::from_elements)
|
||||
.collect(),
|
||||
return_type: func_type.return_type().map(ValueType::from_elements),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,134 +0,0 @@
|
|||
use parity_wasm::elements::{MemoryType, TableType, GlobalType, BlockType, ValueType, FunctionType};
|
||||
use validation::Error;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ModuleContext {
|
||||
pub memories: Vec<MemoryType>,
|
||||
pub tables: Vec<TableType>,
|
||||
pub globals: Vec<GlobalType>,
|
||||
pub types: Vec<FunctionType>,
|
||||
pub func_type_indexes: Vec<u32>,
|
||||
}
|
||||
|
||||
impl ModuleContext {
|
||||
pub fn memories(&self) -> &[MemoryType] {
|
||||
&self.memories
|
||||
}
|
||||
|
||||
pub fn tables(&self) -> &[TableType] {
|
||||
&self.tables
|
||||
}
|
||||
|
||||
pub fn globals(&self) -> &[GlobalType] {
|
||||
&self.globals
|
||||
}
|
||||
|
||||
pub fn types(&self) -> &[FunctionType] {
|
||||
&self.types
|
||||
}
|
||||
|
||||
pub fn func_type_indexes(&self) -> &[u32] {
|
||||
&self.func_type_indexes
|
||||
}
|
||||
|
||||
pub fn require_memory(&self, idx: u32) -> Result<(), Error> {
|
||||
if self.memories().get(idx as usize).is_none() {
|
||||
return Err(Error(format!("Memory at index {} doesn't exists", idx)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn require_table(&self, idx: u32) -> Result<&TableType, Error> {
|
||||
self.tables()
|
||||
.get(idx as usize)
|
||||
.ok_or_else(|| Error(format!("Table at index {} doesn't exists", idx)))
|
||||
}
|
||||
|
||||
pub fn require_function(&self, idx: u32) -> Result<(&[ValueType], BlockType), Error> {
|
||||
let ty_idx = self.func_type_indexes()
|
||||
.get(idx as usize)
|
||||
.ok_or_else(|| Error(format!("Function at index {} doesn't exists", idx)))?;
|
||||
self.require_function_type(*ty_idx)
|
||||
}
|
||||
|
||||
pub fn require_function_type(&self, idx: u32) -> Result<(&[ValueType], BlockType), Error> {
|
||||
let ty = self.types()
|
||||
.get(idx as usize)
|
||||
.ok_or_else(|| Error(format!("Type at index {} doesn't exists", idx)))?;
|
||||
|
||||
let params = ty.params();
|
||||
let return_ty = ty.return_type()
|
||||
.map(BlockType::Value)
|
||||
.unwrap_or(BlockType::NoResult);
|
||||
Ok((params, return_ty))
|
||||
}
|
||||
|
||||
pub fn require_global(&self, idx: u32, mutability: Option<bool>) -> Result<&GlobalType, Error> {
|
||||
let global = self.globals()
|
||||
.get(idx as usize)
|
||||
.ok_or_else(|| Error(format!("Global at index {} doesn't exists", idx)))?;
|
||||
|
||||
if let Some(expected_mutable) = mutability {
|
||||
if expected_mutable && !global.is_mutable() {
|
||||
return Err(Error(format!("Expected global {} to be mutable", idx)));
|
||||
}
|
||||
if !expected_mutable && global.is_mutable() {
|
||||
return Err(Error(format!("Expected global {} to be immutable", idx)));
|
||||
}
|
||||
}
|
||||
Ok(global)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ModuleContextBuilder {
|
||||
memories: Vec<MemoryType>,
|
||||
tables: Vec<TableType>,
|
||||
globals: Vec<GlobalType>,
|
||||
types: Vec<FunctionType>,
|
||||
func_type_indexes: Vec<u32>,
|
||||
}
|
||||
|
||||
impl ModuleContextBuilder {
|
||||
pub fn new() -> ModuleContextBuilder {
|
||||
ModuleContextBuilder::default()
|
||||
}
|
||||
|
||||
pub fn push_memory(&mut self, memory: MemoryType) {
|
||||
self.memories.push(memory);
|
||||
}
|
||||
|
||||
pub fn push_table(&mut self, table: TableType) {
|
||||
self.tables.push(table);
|
||||
}
|
||||
|
||||
pub fn push_global(&mut self, global: GlobalType) {
|
||||
self.globals.push(global);
|
||||
}
|
||||
|
||||
pub fn set_types(&mut self, types: Vec<FunctionType>) {
|
||||
self.types = types;
|
||||
}
|
||||
|
||||
pub fn push_func_type_index(&mut self, func_type_index: u32) {
|
||||
self.func_type_indexes.push(func_type_index);
|
||||
}
|
||||
|
||||
pub fn build(self) -> ModuleContext {
|
||||
let ModuleContextBuilder {
|
||||
memories,
|
||||
tables,
|
||||
globals,
|
||||
types,
|
||||
func_type_indexes,
|
||||
} = self;
|
||||
|
||||
ModuleContext {
|
||||
memories,
|
||||
tables,
|
||||
globals,
|
||||
types,
|
||||
func_type_indexes,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,754 +0,0 @@
|
|||
use std::u32;
|
||||
use std::collections::HashMap;
|
||||
use parity_wasm::elements::{Opcode, BlockType, ValueType, TableElementType, Func, FuncBody};
|
||||
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
|
||||
use validation::context::ModuleContext;
|
||||
|
||||
use validation::Error;
|
||||
use validation::util::Locals;
|
||||
|
||||
use common::stack::StackWithLimit;
|
||||
use common::{BlockFrame, BlockFrameType};
|
||||
|
||||
/// Maximum number of entries in value stack per function.
|
||||
const DEFAULT_VALUE_STACK_LIMIT: usize = 16384;
|
||||
/// Maximum number of entries in frame stack per function.
|
||||
const DEFAULT_FRAME_STACK_LIMIT: usize = 16384;
|
||||
|
||||
/// Function validation context.
|
||||
struct FunctionValidationContext<'a> {
|
||||
/// Wasm module
|
||||
module: &'a ModuleContext,
|
||||
/// Current instruction position.
|
||||
position: usize,
|
||||
/// Local variables.
|
||||
locals: Locals<'a>,
|
||||
/// Value stack.
|
||||
value_stack: StackWithLimit<StackValueType>,
|
||||
/// Frame stack.
|
||||
frame_stack: StackWithLimit<BlockFrame>,
|
||||
/// Function return type. None if validating expression.
|
||||
return_type: Option<BlockType>,
|
||||
/// Labels positions.
|
||||
labels: HashMap<usize, usize>,
|
||||
}
|
||||
|
||||
/// Value type on the stack.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum StackValueType {
|
||||
/// Any value type.
|
||||
Any,
|
||||
/// Concrete value type.
|
||||
Specific(ValueType),
|
||||
}
|
||||
|
||||
/// Function validator.
|
||||
pub struct Validator;
|
||||
|
||||
/// Instruction outcome.
|
||||
#[derive(Debug, Clone)]
|
||||
enum InstructionOutcome {
|
||||
/// Continue with next instruction.
|
||||
ValidateNextInstruction,
|
||||
/// Unreachable instruction reached.
|
||||
Unreachable,
|
||||
}
|
||||
|
||||
impl Validator {
|
||||
pub fn validate_function(
|
||||
module: &ModuleContext,
|
||||
func: &Func,
|
||||
body: &FuncBody,
|
||||
) -> Result<HashMap<usize, usize>, Error> {
|
||||
let (params, result_ty) = module.require_function_type(func.type_ref())?;
|
||||
|
||||
let mut context = FunctionValidationContext::new(
|
||||
&module,
|
||||
Locals::new(params, body.locals()),
|
||||
DEFAULT_VALUE_STACK_LIMIT,
|
||||
DEFAULT_FRAME_STACK_LIMIT,
|
||||
result_ty,
|
||||
);
|
||||
|
||||
context.push_label(BlockFrameType::Function, result_ty)?;
|
||||
Validator::validate_function_block(&mut context, body.code().elements())?;
|
||||
while !context.frame_stack.is_empty() {
|
||||
context.pop_label()?;
|
||||
}
|
||||
|
||||
Ok(context.into_labels())
|
||||
}
|
||||
|
||||
fn validate_function_block(context: &mut FunctionValidationContext, body: &[Opcode]) -> Result<(), Error> {
|
||||
let body_len = body.len();
|
||||
if body_len == 0 {
|
||||
return Err(Error("Non-empty function body expected".into()));
|
||||
}
|
||||
|
||||
loop {
|
||||
let opcode = &body[context.position];
|
||||
match Validator::validate_instruction(context, opcode)? {
|
||||
InstructionOutcome::ValidateNextInstruction => (),
|
||||
InstructionOutcome::Unreachable => context.unreachable()?,
|
||||
}
|
||||
|
||||
context.position += 1;
|
||||
if context.position == body_len {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_instruction(context: &mut FunctionValidationContext, opcode: &Opcode) -> Result<InstructionOutcome, Error> {
|
||||
use self::Opcode::*;
|
||||
match *opcode {
|
||||
Unreachable => Ok(InstructionOutcome::Unreachable),
|
||||
Nop => Ok(InstructionOutcome::ValidateNextInstruction),
|
||||
Block(block_type) => Validator::validate_block(context, block_type),
|
||||
Loop(block_type) => Validator::validate_loop(context, block_type),
|
||||
If(block_type) => Validator::validate_if(context, block_type),
|
||||
Else => Validator::validate_else(context),
|
||||
End => Validator::validate_end(context),
|
||||
Br(idx) => Validator::validate_br(context, idx),
|
||||
BrIf(idx) => Validator::validate_br_if(context, idx),
|
||||
BrTable(ref table, default) => Validator::validate_br_table(context, table, default),
|
||||
Return => Validator::validate_return(context),
|
||||
|
||||
Call(index) => Validator::validate_call(context, index),
|
||||
CallIndirect(index, _reserved) => Validator::validate_call_indirect(context, index),
|
||||
|
||||
Drop => Validator::validate_drop(context),
|
||||
Select => Validator::validate_select(context),
|
||||
|
||||
GetLocal(index) => Validator::validate_get_local(context, index),
|
||||
SetLocal(index) => Validator::validate_set_local(context, index),
|
||||
TeeLocal(index) => Validator::validate_tee_local(context, index),
|
||||
GetGlobal(index) => Validator::validate_get_global(context, index),
|
||||
SetGlobal(index) => Validator::validate_set_global(context, index),
|
||||
|
||||
I32Load(align, _) => Validator::validate_load(context, align, 4, ValueType::I32),
|
||||
I64Load(align, _) => Validator::validate_load(context, align, 8, ValueType::I64),
|
||||
F32Load(align, _) => Validator::validate_load(context, align, 4, ValueType::F32),
|
||||
F64Load(align, _) => Validator::validate_load(context, align, 8, ValueType::F64),
|
||||
I32Load8S(align, _) => Validator::validate_load(context, align, 1, ValueType::I32),
|
||||
I32Load8U(align, _) => Validator::validate_load(context, align, 1, ValueType::I32),
|
||||
I32Load16S(align, _) => Validator::validate_load(context, align, 2, ValueType::I32),
|
||||
I32Load16U(align, _) => Validator::validate_load(context, align, 2, ValueType::I32),
|
||||
I64Load8S(align, _) => Validator::validate_load(context, align, 1, ValueType::I64),
|
||||
I64Load8U(align, _) => Validator::validate_load(context, align, 1, ValueType::I64),
|
||||
I64Load16S(align, _) => Validator::validate_load(context, align, 2, ValueType::I64),
|
||||
I64Load16U(align, _) => Validator::validate_load(context, align, 2, ValueType::I64),
|
||||
I64Load32S(align, _) => Validator::validate_load(context, align, 4, ValueType::I64),
|
||||
I64Load32U(align, _) => Validator::validate_load(context, align, 4, ValueType::I64),
|
||||
|
||||
I32Store(align, _) => Validator::validate_store(context, align, 4, ValueType::I32),
|
||||
I64Store(align, _) => Validator::validate_store(context, align, 8, ValueType::I64),
|
||||
F32Store(align, _) => Validator::validate_store(context, align, 4, ValueType::F32),
|
||||
F64Store(align, _) => Validator::validate_store(context, align, 8, ValueType::F64),
|
||||
I32Store8(align, _) => Validator::validate_store(context, align, 1, ValueType::I32),
|
||||
I32Store16(align, _) => Validator::validate_store(context, align, 2, ValueType::I32),
|
||||
I64Store8(align, _) => Validator::validate_store(context, align, 1, ValueType::I64),
|
||||
I64Store16(align, _) => Validator::validate_store(context, align, 2, ValueType::I64),
|
||||
I64Store32(align, _) => Validator::validate_store(context, align, 4, ValueType::I64),
|
||||
|
||||
CurrentMemory(_) => Validator::validate_current_memory(context),
|
||||
GrowMemory(_) => Validator::validate_grow_memory(context),
|
||||
|
||||
I32Const(_) => Validator::validate_const(context, ValueType::I32),
|
||||
I64Const(_) => Validator::validate_const(context, ValueType::I64),
|
||||
F32Const(_) => Validator::validate_const(context, ValueType::F32),
|
||||
F64Const(_) => Validator::validate_const(context, ValueType::F64),
|
||||
|
||||
I32Eqz => Validator::validate_testop(context, ValueType::I32),
|
||||
I32Eq => Validator::validate_relop(context, ValueType::I32),
|
||||
I32Ne => Validator::validate_relop(context, ValueType::I32),
|
||||
I32LtS => Validator::validate_relop(context, ValueType::I32),
|
||||
I32LtU => Validator::validate_relop(context, ValueType::I32),
|
||||
I32GtS => Validator::validate_relop(context, ValueType::I32),
|
||||
I32GtU => Validator::validate_relop(context, ValueType::I32),
|
||||
I32LeS => Validator::validate_relop(context, ValueType::I32),
|
||||
I32LeU => Validator::validate_relop(context, ValueType::I32),
|
||||
I32GeS => Validator::validate_relop(context, ValueType::I32),
|
||||
I32GeU => Validator::validate_relop(context, ValueType::I32),
|
||||
|
||||
I64Eqz => Validator::validate_testop(context, ValueType::I64),
|
||||
I64Eq => Validator::validate_relop(context, ValueType::I64),
|
||||
I64Ne => Validator::validate_relop(context, ValueType::I64),
|
||||
I64LtS => Validator::validate_relop(context, ValueType::I64),
|
||||
I64LtU => Validator::validate_relop(context, ValueType::I64),
|
||||
I64GtS => Validator::validate_relop(context, ValueType::I64),
|
||||
I64GtU => Validator::validate_relop(context, ValueType::I64),
|
||||
I64LeS => Validator::validate_relop(context, ValueType::I64),
|
||||
I64LeU => Validator::validate_relop(context, ValueType::I64),
|
||||
I64GeS => Validator::validate_relop(context, ValueType::I64),
|
||||
I64GeU => Validator::validate_relop(context, ValueType::I64),
|
||||
|
||||
F32Eq => Validator::validate_relop(context, ValueType::F32),
|
||||
F32Ne => Validator::validate_relop(context, ValueType::F32),
|
||||
F32Lt => Validator::validate_relop(context, ValueType::F32),
|
||||
F32Gt => Validator::validate_relop(context, ValueType::F32),
|
||||
F32Le => Validator::validate_relop(context, ValueType::F32),
|
||||
F32Ge => Validator::validate_relop(context, ValueType::F32),
|
||||
|
||||
F64Eq => Validator::validate_relop(context, ValueType::F64),
|
||||
F64Ne => Validator::validate_relop(context, ValueType::F64),
|
||||
F64Lt => Validator::validate_relop(context, ValueType::F64),
|
||||
F64Gt => Validator::validate_relop(context, ValueType::F64),
|
||||
F64Le => Validator::validate_relop(context, ValueType::F64),
|
||||
F64Ge => Validator::validate_relop(context, ValueType::F64),
|
||||
|
||||
I32Clz => Validator::validate_unop(context, ValueType::I32),
|
||||
I32Ctz => Validator::validate_unop(context, ValueType::I32),
|
||||
I32Popcnt => Validator::validate_unop(context, ValueType::I32),
|
||||
I32Add => Validator::validate_binop(context, ValueType::I32),
|
||||
I32Sub => Validator::validate_binop(context, ValueType::I32),
|
||||
I32Mul => Validator::validate_binop(context, ValueType::I32),
|
||||
I32DivS => Validator::validate_binop(context, ValueType::I32),
|
||||
I32DivU => Validator::validate_binop(context, ValueType::I32),
|
||||
I32RemS => Validator::validate_binop(context, ValueType::I32),
|
||||
I32RemU => Validator::validate_binop(context, ValueType::I32),
|
||||
I32And => Validator::validate_binop(context, ValueType::I32),
|
||||
I32Or => Validator::validate_binop(context, ValueType::I32),
|
||||
I32Xor => Validator::validate_binop(context, ValueType::I32),
|
||||
I32Shl => Validator::validate_binop(context, ValueType::I32),
|
||||
I32ShrS => Validator::validate_binop(context, ValueType::I32),
|
||||
I32ShrU => Validator::validate_binop(context, ValueType::I32),
|
||||
I32Rotl => Validator::validate_binop(context, ValueType::I32),
|
||||
I32Rotr => Validator::validate_binop(context, ValueType::I32),
|
||||
|
||||
I64Clz => Validator::validate_unop(context, ValueType::I64),
|
||||
I64Ctz => Validator::validate_unop(context, ValueType::I64),
|
||||
I64Popcnt => Validator::validate_unop(context, ValueType::I64),
|
||||
I64Add => Validator::validate_binop(context, ValueType::I64),
|
||||
I64Sub => Validator::validate_binop(context, ValueType::I64),
|
||||
I64Mul => Validator::validate_binop(context, ValueType::I64),
|
||||
I64DivS => Validator::validate_binop(context, ValueType::I64),
|
||||
I64DivU => Validator::validate_binop(context, ValueType::I64),
|
||||
I64RemS => Validator::validate_binop(context, ValueType::I64),
|
||||
I64RemU => Validator::validate_binop(context, ValueType::I64),
|
||||
I64And => Validator::validate_binop(context, ValueType::I64),
|
||||
I64Or => Validator::validate_binop(context, ValueType::I64),
|
||||
I64Xor => Validator::validate_binop(context, ValueType::I64),
|
||||
I64Shl => Validator::validate_binop(context, ValueType::I64),
|
||||
I64ShrS => Validator::validate_binop(context, ValueType::I64),
|
||||
I64ShrU => Validator::validate_binop(context, ValueType::I64),
|
||||
I64Rotl => Validator::validate_binop(context, ValueType::I64),
|
||||
I64Rotr => Validator::validate_binop(context, ValueType::I64),
|
||||
|
||||
F32Abs => Validator::validate_unop(context, ValueType::F32),
|
||||
F32Neg => Validator::validate_unop(context, ValueType::F32),
|
||||
F32Ceil => Validator::validate_unop(context, ValueType::F32),
|
||||
F32Floor => Validator::validate_unop(context, ValueType::F32),
|
||||
F32Trunc => Validator::validate_unop(context, ValueType::F32),
|
||||
F32Nearest => Validator::validate_unop(context, ValueType::F32),
|
||||
F32Sqrt => Validator::validate_unop(context, ValueType::F32),
|
||||
F32Add => Validator::validate_binop(context, ValueType::F32),
|
||||
F32Sub => Validator::validate_binop(context, ValueType::F32),
|
||||
F32Mul => Validator::validate_binop(context, ValueType::F32),
|
||||
F32Div => Validator::validate_binop(context, ValueType::F32),
|
||||
F32Min => Validator::validate_binop(context, ValueType::F32),
|
||||
F32Max => Validator::validate_binop(context, ValueType::F32),
|
||||
F32Copysign => Validator::validate_binop(context, ValueType::F32),
|
||||
|
||||
F64Abs => Validator::validate_unop(context, ValueType::F64),
|
||||
F64Neg => Validator::validate_unop(context, ValueType::F64),
|
||||
F64Ceil => Validator::validate_unop(context, ValueType::F64),
|
||||
F64Floor => Validator::validate_unop(context, ValueType::F64),
|
||||
F64Trunc => Validator::validate_unop(context, ValueType::F64),
|
||||
F64Nearest => Validator::validate_unop(context, ValueType::F64),
|
||||
F64Sqrt => Validator::validate_unop(context, ValueType::F64),
|
||||
F64Add => Validator::validate_binop(context, ValueType::F64),
|
||||
F64Sub => Validator::validate_binop(context, ValueType::F64),
|
||||
F64Mul => Validator::validate_binop(context, ValueType::F64),
|
||||
F64Div => Validator::validate_binop(context, ValueType::F64),
|
||||
F64Min => Validator::validate_binop(context, ValueType::F64),
|
||||
F64Max => Validator::validate_binop(context, ValueType::F64),
|
||||
F64Copysign => Validator::validate_binop(context, ValueType::F64),
|
||||
|
||||
I32WrapI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::I32),
|
||||
I32TruncSF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I32),
|
||||
I32TruncUF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I32),
|
||||
I32TruncSF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I32),
|
||||
I32TruncUF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I32),
|
||||
I64ExtendSI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::I64),
|
||||
I64ExtendUI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::I64),
|
||||
I64TruncSF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I64),
|
||||
I64TruncUF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I64),
|
||||
I64TruncSF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I64),
|
||||
I64TruncUF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I64),
|
||||
F32ConvertSI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F32),
|
||||
F32ConvertUI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F32),
|
||||
F32ConvertSI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F32),
|
||||
F32ConvertUI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F32),
|
||||
F32DemoteF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::F32),
|
||||
F64ConvertSI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F64),
|
||||
F64ConvertUI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F64),
|
||||
F64ConvertSI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F64),
|
||||
F64ConvertUI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F64),
|
||||
F64PromoteF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::F64),
|
||||
|
||||
I32ReinterpretF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I32),
|
||||
I64ReinterpretF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I64),
|
||||
F32ReinterpretI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F32),
|
||||
F64ReinterpretI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F64),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_const(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
||||
context.push_value(value_type.into())?;
|
||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn validate_unop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
||||
context.pop_value(value_type.into())?;
|
||||
context.push_value(value_type.into())?;
|
||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn validate_binop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
||||
context.pop_value(value_type.into())?;
|
||||
context.pop_value(value_type.into())?;
|
||||
context.push_value(value_type.into())?;
|
||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn validate_testop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
||||
context.pop_value(value_type.into())?;
|
||||
context.push_value(ValueType::I32.into())?;
|
||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn validate_relop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
||||
context.pop_value(value_type.into())?;
|
||||
context.pop_value(value_type.into())?;
|
||||
context.push_value(ValueType::I32.into())?;
|
||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn validate_cvtop(context: &mut FunctionValidationContext, value_type1: ValueType, value_type2: ValueType) -> Result<InstructionOutcome, Error> {
|
||||
context.pop_value(value_type1.into())?;
|
||||
context.push_value(value_type2.into())?;
|
||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn validate_drop(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||
context.pop_value(StackValueType::Any).map(|_| ())?;
|
||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn validate_select(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||
context.pop_value(ValueType::I32.into())?;
|
||||
let select_type = context.pop_value(StackValueType::Any)?;
|
||||
context.pop_value(select_type)?;
|
||||
context.push_value(select_type)?;
|
||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn validate_get_local(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
||||
let local_type = context.require_local(index)?;
|
||||
context.push_value(local_type)?;
|
||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn validate_set_local(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
||||
let local_type = context.require_local(index)?;
|
||||
let value_type = context.pop_value(StackValueType::Any)?;
|
||||
if local_type != value_type {
|
||||
return Err(Error(format!("Trying to update local {} of type {:?} with value of type {:?}", index, local_type, value_type)));
|
||||
}
|
||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn validate_tee_local(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
||||
let local_type = context.require_local(index)?;
|
||||
context.tee_value(local_type)?;
|
||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn validate_get_global(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
||||
let global_type: StackValueType = {
|
||||
let global = context.module.require_global(index, None)?;
|
||||
global.content_type().into()
|
||||
};
|
||||
context.push_value(global_type)?;
|
||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn validate_set_global(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
||||
let global_type: StackValueType = {
|
||||
let global = context.module.require_global(index, Some(true))?;
|
||||
global.content_type().into()
|
||||
};
|
||||
let value_type = context.pop_value(StackValueType::Any)?;
|
||||
if global_type != value_type {
|
||||
return Err(Error(format!("Trying to update global {} of type {:?} with value of type {:?}", index, global_type, value_type)));
|
||||
}
|
||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn validate_load(context: &mut FunctionValidationContext, align: u32, max_align: u32, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
||||
if 1u32.checked_shl(align).unwrap_or(u32::MAX) > max_align {
|
||||
return Err(Error(format!("Too large memory alignment 2^{} (expected at most {})", align, max_align)));
|
||||
}
|
||||
|
||||
context.pop_value(ValueType::I32.into())?;
|
||||
context.module.require_memory(DEFAULT_MEMORY_INDEX)?;
|
||||
context.push_value(value_type.into())?;
|
||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn validate_store(context: &mut FunctionValidationContext, align: u32, max_align: u32, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
||||
if 1u32.checked_shl(align).unwrap_or(u32::MAX) > max_align {
|
||||
return Err(Error(format!("Too large memory alignment 2^{} (expected at most {})", align, max_align)));
|
||||
}
|
||||
|
||||
context.module.require_memory(DEFAULT_MEMORY_INDEX)?;
|
||||
context.pop_value(value_type.into())?;
|
||||
context.pop_value(ValueType::I32.into())?;
|
||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn validate_block(context: &mut FunctionValidationContext, block_type: BlockType) -> Result<InstructionOutcome, Error> {
|
||||
context.push_label(BlockFrameType::Block, block_type).map(|_| InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn validate_loop(context: &mut FunctionValidationContext, block_type: BlockType) -> Result<InstructionOutcome, Error> {
|
||||
context.push_label(BlockFrameType::Loop, block_type).map(|_| InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn validate_if(context: &mut FunctionValidationContext, block_type: BlockType) -> Result<InstructionOutcome, Error> {
|
||||
context.pop_value(ValueType::I32.into())?;
|
||||
context.push_label(BlockFrameType::IfTrue, block_type).map(|_| InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn validate_else(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||
let block_type = {
|
||||
let top_frame = context.top_label()?;
|
||||
if top_frame.frame_type != BlockFrameType::IfTrue {
|
||||
return Err(Error("Misplaced else instruction".into()));
|
||||
}
|
||||
top_frame.block_type
|
||||
};
|
||||
context.pop_label()?;
|
||||
|
||||
if let BlockType::Value(value_type) = block_type {
|
||||
context.pop_value(value_type.into())?;
|
||||
}
|
||||
context.push_label(BlockFrameType::IfFalse, block_type).map(|_| InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn validate_end(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||
{
|
||||
let top_frame = context.top_label()?;
|
||||
if top_frame.frame_type == BlockFrameType::IfTrue {
|
||||
if top_frame.block_type != BlockType::NoResult {
|
||||
return Err(Error(format!("If block without else required to have NoResult block type. But it have {:?} type", top_frame.block_type)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.pop_label().map(|_| InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn validate_br(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
|
||||
let (frame_type, frame_block_type) = {
|
||||
let frame = context.require_label(idx)?;
|
||||
(frame.frame_type, frame.block_type)
|
||||
};
|
||||
if frame_type != BlockFrameType::Loop {
|
||||
if let BlockType::Value(value_type) = frame_block_type {
|
||||
context.tee_value(value_type.into())?;
|
||||
}
|
||||
}
|
||||
Ok(InstructionOutcome::Unreachable)
|
||||
}
|
||||
|
||||
fn validate_br_if(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
|
||||
context.pop_value(ValueType::I32.into())?;
|
||||
|
||||
let (frame_type, frame_block_type) = {
|
||||
let frame = context.require_label(idx)?;
|
||||
(frame.frame_type, frame.block_type)
|
||||
};
|
||||
if frame_type != BlockFrameType::Loop {
|
||||
if let BlockType::Value(value_type) = frame_block_type {
|
||||
context.tee_value(value_type.into())?;
|
||||
}
|
||||
}
|
||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn validate_br_table(context: &mut FunctionValidationContext, table: &[u32], default: u32) -> Result<InstructionOutcome, Error> {
|
||||
let required_block_type: BlockType = {
|
||||
let default_block = context.require_label(default)?;
|
||||
let required_block_type = if default_block.frame_type != BlockFrameType::Loop {
|
||||
default_block.block_type
|
||||
} else {
|
||||
BlockType::NoResult
|
||||
};
|
||||
|
||||
for label in table {
|
||||
let label_block = context.require_label(*label)?;
|
||||
let label_block_type = if label_block.frame_type != BlockFrameType::Loop {
|
||||
label_block.block_type
|
||||
} else {
|
||||
BlockType::NoResult
|
||||
};
|
||||
if required_block_type != label_block_type {
|
||||
return Err(
|
||||
Error(
|
||||
format!(
|
||||
"Labels in br_table points to block of different types: {:?} and {:?}",
|
||||
required_block_type,
|
||||
label_block.block_type
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
required_block_type
|
||||
};
|
||||
|
||||
context.pop_value(ValueType::I32.into())?;
|
||||
if let BlockType::Value(value_type) = required_block_type {
|
||||
context.tee_value(value_type.into())?;
|
||||
}
|
||||
|
||||
Ok(InstructionOutcome::Unreachable)
|
||||
}
|
||||
|
||||
fn validate_return(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||
if let BlockType::Value(value_type) = context.return_type()? {
|
||||
context.tee_value(value_type.into())?;
|
||||
}
|
||||
Ok(InstructionOutcome::Unreachable)
|
||||
}
|
||||
|
||||
fn validate_call(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
|
||||
let (argument_types, return_type) = context.module.require_function(idx)?;
|
||||
for argument_type in argument_types.iter().rev() {
|
||||
context.pop_value((*argument_type).into())?;
|
||||
}
|
||||
if let BlockType::Value(value_type) = return_type {
|
||||
context.push_value(value_type.into())?;
|
||||
}
|
||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn validate_call_indirect(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
|
||||
{
|
||||
let table = context.module.require_table(DEFAULT_TABLE_INDEX)?;
|
||||
if table.elem_type() != TableElementType::AnyFunc {
|
||||
return Err(Error(format!(
|
||||
"Table {} has element type {:?} while `anyfunc` expected",
|
||||
idx,
|
||||
table.elem_type()
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
context.pop_value(ValueType::I32.into())?;
|
||||
let (argument_types, return_type) = context.module.require_function_type(idx)?;
|
||||
for argument_type in argument_types.iter().rev() {
|
||||
context.pop_value((*argument_type).into())?;
|
||||
}
|
||||
if let BlockType::Value(value_type) = return_type {
|
||||
context.push_value(value_type.into())?;
|
||||
}
|
||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn validate_current_memory(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||
context.module.require_memory(DEFAULT_MEMORY_INDEX)?;
|
||||
context.push_value(ValueType::I32.into())?;
|
||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn validate_grow_memory(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||
context.module.require_memory(DEFAULT_MEMORY_INDEX)?;
|
||||
context.pop_value(ValueType::I32.into())?;
|
||||
context.push_value(ValueType::I32.into())?;
|
||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FunctionValidationContext<'a> {
|
||||
fn new(
|
||||
module: &'a ModuleContext,
|
||||
locals: Locals<'a>,
|
||||
value_stack_limit: usize,
|
||||
frame_stack_limit: usize,
|
||||
return_type: BlockType,
|
||||
) -> Self {
|
||||
FunctionValidationContext {
|
||||
module: module,
|
||||
position: 0,
|
||||
locals: locals,
|
||||
value_stack: StackWithLimit::with_limit(value_stack_limit),
|
||||
frame_stack: StackWithLimit::with_limit(frame_stack_limit),
|
||||
return_type: Some(return_type),
|
||||
labels: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn push_value(&mut self, value_type: StackValueType) -> Result<(), Error> {
|
||||
Ok(self.value_stack.push(value_type.into())?)
|
||||
}
|
||||
|
||||
fn pop_value(&mut self, value_type: StackValueType) -> Result<StackValueType, Error> {
|
||||
let (is_stack_polymorphic, label_value_stack_len) = {
|
||||
let frame = self.top_label()?;
|
||||
(frame.polymorphic_stack, frame.value_stack_len)
|
||||
};
|
||||
let stack_is_empty = self.value_stack.len() == label_value_stack_len;
|
||||
let actual_value = if stack_is_empty && is_stack_polymorphic {
|
||||
StackValueType::Any
|
||||
} else {
|
||||
self.check_stack_access()?;
|
||||
self.value_stack.pop()?
|
||||
};
|
||||
match actual_value {
|
||||
StackValueType::Specific(stack_value_type) if stack_value_type == value_type => {
|
||||
Ok(actual_value)
|
||||
}
|
||||
StackValueType::Any => Ok(actual_value),
|
||||
stack_value_type @ _ => Err(Error(format!(
|
||||
"Expected value of type {:?} on top of stack. Got {:?}",
|
||||
value_type, stack_value_type
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_stack_access(&self) -> Result<(), Error> {
|
||||
let value_stack_min = self.frame_stack.top().expect("at least 1 topmost block").value_stack_len;
|
||||
if self.value_stack.len() > value_stack_min {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error("Trying to access parent frame stack values.".into()))
|
||||
}
|
||||
}
|
||||
|
||||
fn tee_value(&mut self, value_type: StackValueType) -> Result<(), Error> {
|
||||
let _ = self.pop_value(value_type)?;
|
||||
self.push_value(value_type)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unreachable(&mut self) -> Result<(), Error> {
|
||||
let frame = self.frame_stack.top_mut()?;
|
||||
self.value_stack.resize(frame.value_stack_len, StackValueType::Any);
|
||||
frame.polymorphic_stack = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn top_label(&self) -> Result<&BlockFrame, Error> {
|
||||
Ok(self.frame_stack.top()?)
|
||||
}
|
||||
|
||||
fn push_label(&mut self, frame_type: BlockFrameType, block_type: BlockType) -> Result<(), Error> {
|
||||
Ok(self.frame_stack.push(BlockFrame {
|
||||
frame_type: frame_type,
|
||||
block_type: block_type,
|
||||
begin_position: self.position,
|
||||
branch_position: self.position,
|
||||
end_position: self.position,
|
||||
value_stack_len: self.value_stack.len(),
|
||||
polymorphic_stack: false,
|
||||
})?)
|
||||
}
|
||||
|
||||
fn pop_label(&mut self) -> Result<InstructionOutcome, Error> {
|
||||
// Don't pop frame yet. This is essential since we still might pop values from the value stack
|
||||
// and this in turn requires current frame to check whether or not we've reached
|
||||
// unreachable.
|
||||
let block_type = self.frame_stack.top()?.block_type;
|
||||
match block_type {
|
||||
BlockType::NoResult => (),
|
||||
BlockType::Value(required_value_type) => {
|
||||
self.pop_value(StackValueType::Specific(required_value_type))?;
|
||||
}
|
||||
}
|
||||
|
||||
let frame = self.frame_stack.pop()?;
|
||||
if self.value_stack.len() != frame.value_stack_len {
|
||||
return Err(Error(format!(
|
||||
"Unexpected stack height {}, expected {}",
|
||||
self.value_stack.len(),
|
||||
frame.value_stack_len
|
||||
)));
|
||||
}
|
||||
|
||||
if !self.frame_stack.is_empty() {
|
||||
self.labels.insert(frame.begin_position, self.position);
|
||||
}
|
||||
if let BlockType::Value(value_type) = frame.block_type {
|
||||
self.push_value(value_type.into())?;
|
||||
}
|
||||
|
||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||
}
|
||||
|
||||
fn require_label(&self, idx: u32) -> Result<&BlockFrame, Error> {
|
||||
Ok(self.frame_stack.get(idx as usize)?)
|
||||
}
|
||||
|
||||
fn return_type(&self) -> Result<BlockType, Error> {
|
||||
self.return_type.ok_or(Error("Trying to return from expression".into()))
|
||||
}
|
||||
|
||||
fn require_local(&self, idx: u32) -> Result<StackValueType, Error> {
|
||||
Ok(self.locals.type_of_local(idx).map(StackValueType::from)?)
|
||||
}
|
||||
|
||||
fn into_labels(self) -> HashMap<usize, usize> {
|
||||
self.labels
|
||||
}
|
||||
}
|
||||
|
||||
impl StackValueType {
|
||||
fn is_any(&self) -> bool {
|
||||
match self {
|
||||
&StackValueType::Any => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn value_type(&self) -> ValueType {
|
||||
match self {
|
||||
&StackValueType::Any => unreachable!("must be checked by caller"),
|
||||
&StackValueType::Specific(value_type) => value_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ValueType> for StackValueType {
|
||||
fn from(value_type: ValueType) -> Self {
|
||||
StackValueType::Specific(value_type)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<StackValueType> for StackValueType {
|
||||
fn eq(&self, other: &StackValueType) -> bool {
|
||||
if self.is_any() || other.is_any() {
|
||||
true
|
||||
} else {
|
||||
self.value_type() == other.value_type()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<ValueType> for StackValueType {
|
||||
fn eq(&self, other: &ValueType) -> bool {
|
||||
if self.is_any() {
|
||||
true
|
||||
} else {
|
||||
self.value_type() == *other
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<StackValueType> for ValueType {
|
||||
fn eq(&self, other: &StackValueType) -> bool {
|
||||
other == self
|
||||
}
|
||||
}
|
|
@ -1,451 +0,0 @@
|
|||
use std::error;
|
||||
use std::fmt;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use parity_wasm::elements::{
|
||||
BlockType, External, GlobalEntry, GlobalType, Internal, MemoryType, Module, Opcode,
|
||||
ResizableLimits, TableType, ValueType, InitExpr, Type
|
||||
};
|
||||
use common::stack;
|
||||
use self::context::ModuleContextBuilder;
|
||||
use self::func::Validator;
|
||||
use memory_units::Pages;
|
||||
|
||||
mod context;
|
||||
mod func;
|
||||
mod util;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error(String);
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn description(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<stack::Error> for Error {
|
||||
fn from(e: stack::Error) -> Error {
|
||||
Error(format!("Stack: {}", e))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ValidatedModule {
|
||||
pub labels: HashMap<usize, HashMap<usize, usize>>,
|
||||
pub module: Module,
|
||||
}
|
||||
|
||||
impl ::std::ops::Deref for ValidatedModule {
|
||||
type Target = Module;
|
||||
fn deref(&self) -> &Module {
|
||||
&self.module
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deny_floating_point(module: &Module) -> Result<(), Error> {
|
||||
if let Some(code) = module.code_section() {
|
||||
for op in code.bodies().iter().flat_map(|body| body.code().elements()) {
|
||||
use parity_wasm::elements::Opcode::*;
|
||||
|
||||
macro_rules! match_eq {
|
||||
($pattern:pat) => {
|
||||
|val| if let $pattern = *val { true } else { false }
|
||||
};
|
||||
}
|
||||
|
||||
const DENIED: &[fn(&Opcode) -> bool] = &[
|
||||
match_eq!(F32Load(_, _)),
|
||||
match_eq!(F64Load(_, _)),
|
||||
match_eq!(F32Store(_, _)),
|
||||
match_eq!(F64Store(_, _)),
|
||||
match_eq!(F32Const(_)),
|
||||
match_eq!(F64Const(_)),
|
||||
match_eq!(F32Eq),
|
||||
match_eq!(F32Ne),
|
||||
match_eq!(F32Lt),
|
||||
match_eq!(F32Gt),
|
||||
match_eq!(F32Le),
|
||||
match_eq!(F32Ge),
|
||||
match_eq!(F64Eq),
|
||||
match_eq!(F64Ne),
|
||||
match_eq!(F64Lt),
|
||||
match_eq!(F64Gt),
|
||||
match_eq!(F64Le),
|
||||
match_eq!(F64Ge),
|
||||
match_eq!(F32Abs),
|
||||
match_eq!(F32Neg),
|
||||
match_eq!(F32Ceil),
|
||||
match_eq!(F32Floor),
|
||||
match_eq!(F32Trunc),
|
||||
match_eq!(F32Nearest),
|
||||
match_eq!(F32Sqrt),
|
||||
match_eq!(F32Add),
|
||||
match_eq!(F32Sub),
|
||||
match_eq!(F32Mul),
|
||||
match_eq!(F32Div),
|
||||
match_eq!(F32Min),
|
||||
match_eq!(F32Max),
|
||||
match_eq!(F32Copysign),
|
||||
match_eq!(F64Abs),
|
||||
match_eq!(F64Neg),
|
||||
match_eq!(F64Ceil),
|
||||
match_eq!(F64Floor),
|
||||
match_eq!(F64Trunc),
|
||||
match_eq!(F64Nearest),
|
||||
match_eq!(F64Sqrt),
|
||||
match_eq!(F64Add),
|
||||
match_eq!(F64Sub),
|
||||
match_eq!(F64Mul),
|
||||
match_eq!(F64Div),
|
||||
match_eq!(F64Min),
|
||||
match_eq!(F64Max),
|
||||
match_eq!(F64Copysign),
|
||||
match_eq!(F32ConvertSI32),
|
||||
match_eq!(F32ConvertUI32),
|
||||
match_eq!(F32ConvertSI64),
|
||||
match_eq!(F32ConvertUI64),
|
||||
match_eq!(F32DemoteF64),
|
||||
match_eq!(F64ConvertSI32),
|
||||
match_eq!(F64ConvertUI32),
|
||||
match_eq!(F64ConvertSI64),
|
||||
match_eq!(F64ConvertUI64),
|
||||
match_eq!(F64PromoteF32),
|
||||
match_eq!(F32ReinterpretI32),
|
||||
match_eq!(F64ReinterpretI64),
|
||||
match_eq!(I32TruncSF32),
|
||||
match_eq!(I32TruncUF32),
|
||||
match_eq!(I32TruncSF64),
|
||||
match_eq!(I32TruncUF64),
|
||||
match_eq!(I64TruncSF32),
|
||||
match_eq!(I64TruncUF32),
|
||||
match_eq!(I64TruncSF64),
|
||||
match_eq!(I64TruncUF64),
|
||||
match_eq!(I32ReinterpretF32),
|
||||
match_eq!(I64ReinterpretF64),
|
||||
];
|
||||
|
||||
if DENIED.iter().any(|is_denied| is_denied(op)) {
|
||||
return Err(Error(format!("Floating point operation denied: {:?}", op)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let (Some(sec), Some(types)) = (module.function_section(), module.type_section()) {
|
||||
use parity_wasm::elements::{Type, ValueType};
|
||||
|
||||
let types = types.types();
|
||||
|
||||
for sig in sec.entries() {
|
||||
if let Some(typ) = types.get(sig.type_ref() as usize) {
|
||||
match *typ {
|
||||
Type::Function(ref func) => {
|
||||
if func.params()
|
||||
.iter()
|
||||
.chain(func.return_type().as_ref())
|
||||
.any(|&typ| typ == ValueType::F32 || typ == ValueType::F64)
|
||||
{
|
||||
return Err(Error(format!("Use of floating point types denied")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
|
||||
let mut context_builder = ModuleContextBuilder::new();
|
||||
let mut imported_globals = Vec::new();
|
||||
let mut labels = HashMap::new();
|
||||
|
||||
// Copy types from module as is.
|
||||
context_builder.set_types(
|
||||
module
|
||||
.type_section()
|
||||
.map(|ts| {
|
||||
ts.types()
|
||||
.into_iter()
|
||||
.map(|&Type::Function(ref ty)| ty)
|
||||
.cloned()
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
|
||||
// Fill elements with imported values.
|
||||
for import_entry in module
|
||||
.import_section()
|
||||
.map(|i| i.entries())
|
||||
.unwrap_or_default()
|
||||
{
|
||||
match *import_entry.external() {
|
||||
External::Function(idx) => context_builder.push_func_type_index(idx),
|
||||
External::Table(ref table) => context_builder.push_table(table.clone()),
|
||||
External::Memory(ref memory) => context_builder.push_memory(memory.clone()),
|
||||
External::Global(ref global) => {
|
||||
context_builder.push_global(global.clone());
|
||||
imported_globals.push(global.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Concatenate elements with defined in the module.
|
||||
if let Some(function_section) = module.function_section() {
|
||||
for func_entry in function_section.entries() {
|
||||
context_builder.push_func_type_index(func_entry.type_ref())
|
||||
}
|
||||
}
|
||||
if let Some(table_section) = module.table_section() {
|
||||
for table_entry in table_section.entries() {
|
||||
validate_table_type(table_entry)?;
|
||||
context_builder.push_table(table_entry.clone());
|
||||
}
|
||||
}
|
||||
if let Some(mem_section) = module.memory_section() {
|
||||
for mem_entry in mem_section.entries() {
|
||||
validate_memory_type(mem_entry)?;
|
||||
context_builder.push_memory(mem_entry.clone());
|
||||
}
|
||||
}
|
||||
if let Some(global_section) = module.global_section() {
|
||||
for global_entry in global_section.entries() {
|
||||
validate_global_entry(global_entry, &imported_globals)?;
|
||||
context_builder.push_global(global_entry.global_type().clone());
|
||||
}
|
||||
}
|
||||
|
||||
let context = context_builder.build();
|
||||
|
||||
let function_section_len = module
|
||||
.function_section()
|
||||
.map(|s| s.entries().len())
|
||||
.unwrap_or(0);
|
||||
let code_section_len = module.code_section().map(|s| s.bodies().len()).unwrap_or(0);
|
||||
if function_section_len != code_section_len {
|
||||
return Err(Error(format!(
|
||||
"length of function section is {}, while len of code section is {}",
|
||||
function_section_len,
|
||||
code_section_len
|
||||
)));
|
||||
}
|
||||
|
||||
// validate every function body in user modules
|
||||
if function_section_len != 0 {
|
||||
// tests use invalid code
|
||||
let function_section = module.function_section().expect(
|
||||
"function_section_len != 0; qed",
|
||||
);
|
||||
let code_section = module.code_section().expect(
|
||||
"function_section_len != 0; function_section_len == code_section_len; qed",
|
||||
);
|
||||
// check every function body
|
||||
for (index, function) in function_section.entries().iter().enumerate() {
|
||||
let function_body = code_section.bodies().get(index as usize).ok_or(
|
||||
Error(format!(
|
||||
"Missing body for function {}",
|
||||
index
|
||||
)),
|
||||
)?;
|
||||
let func_labels = Validator::validate_function(&context, function, function_body)
|
||||
.map_err(|e| {
|
||||
let Error(ref msg) = e;
|
||||
Error(format!("Function #{} validation error: {}", index, msg))
|
||||
})?;
|
||||
labels.insert(index, func_labels);
|
||||
}
|
||||
}
|
||||
|
||||
// validate start section
|
||||
if let Some(start_fn_idx) = module.start_section() {
|
||||
let (params, return_ty) = context.require_function(start_fn_idx)?;
|
||||
if return_ty != BlockType::NoResult || params.len() != 0 {
|
||||
return Err(Error(
|
||||
"start function expected to have type [] -> []".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// validate export section
|
||||
if let Some(export_section) = module.export_section() {
|
||||
let mut export_names = HashSet::with_capacity(export_section.entries().len());
|
||||
for export in export_section.entries() {
|
||||
// HashSet::insert returns false if item already in set.
|
||||
let duplicate = export_names.insert(export.field()) == false;
|
||||
if duplicate {
|
||||
return Err(Error(
|
||||
format!("duplicate export {}", export.field()),
|
||||
));
|
||||
}
|
||||
match *export.internal() {
|
||||
Internal::Function(function_index) => {
|
||||
context.require_function(function_index)?;
|
||||
}
|
||||
Internal::Global(global_index) => {
|
||||
context.require_global(global_index, Some(false))?;
|
||||
}
|
||||
Internal::Memory(memory_index) => {
|
||||
context.require_memory(memory_index)?;
|
||||
}
|
||||
Internal::Table(table_index) => {
|
||||
context.require_table(table_index)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// validate import section
|
||||
if let Some(import_section) = module.import_section() {
|
||||
for import in import_section.entries() {
|
||||
match *import.external() {
|
||||
External::Function(function_type_index) => {
|
||||
context.require_function_type(function_type_index)?;
|
||||
}
|
||||
External::Global(ref global_type) => {
|
||||
if global_type.is_mutable() {
|
||||
return Err(Error(format!(
|
||||
"trying to import mutable global {}",
|
||||
import.field()
|
||||
)));
|
||||
}
|
||||
}
|
||||
External::Memory(ref memory_type) => {
|
||||
validate_memory_type(memory_type)?;
|
||||
}
|
||||
External::Table(ref table_type) => {
|
||||
validate_table_type(table_type)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// there must be no greater than 1 table in tables index space
|
||||
if context.tables().len() > 1 {
|
||||
return Err(Error(format!(
|
||||
"too many tables in index space: {}",
|
||||
context.tables().len()
|
||||
)));
|
||||
}
|
||||
|
||||
// there must be no greater than 1 linear memory in memory index space
|
||||
if context.memories().len() > 1 {
|
||||
return Err(Error(format!(
|
||||
"too many memory regions in index space: {}",
|
||||
context.memories().len()
|
||||
)));
|
||||
}
|
||||
|
||||
// use data section to initialize linear memory regions
|
||||
if let Some(data_section) = module.data_section() {
|
||||
for data_segment in data_section.entries() {
|
||||
context.require_memory(data_segment.index())?;
|
||||
let init_ty = expr_const_type(data_segment.offset(), context.globals())?;
|
||||
if init_ty != ValueType::I32 {
|
||||
return Err(Error("segment offset should return I32".into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// use element section to fill tables
|
||||
if let Some(element_section) = module.elements_section() {
|
||||
for element_segment in element_section.entries() {
|
||||
context.require_table(element_segment.index())?;
|
||||
|
||||
let init_ty = expr_const_type(element_segment.offset(), context.globals())?;
|
||||
if init_ty != ValueType::I32 {
|
||||
return Err(Error("segment offset should return I32".into()));
|
||||
}
|
||||
|
||||
for function_index in element_segment.members() {
|
||||
context.require_function(*function_index)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ValidatedModule {
|
||||
module,
|
||||
labels
|
||||
})
|
||||
}
|
||||
|
||||
fn validate_limits(limits: &ResizableLimits) -> Result<(), Error> {
|
||||
if let Some(maximum) = limits.maximum() {
|
||||
if limits.initial() > maximum {
|
||||
return Err(Error(format!(
|
||||
"maximum limit {} is less than minimum {}",
|
||||
maximum,
|
||||
limits.initial()
|
||||
)));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_memory_type(memory_type: &MemoryType) -> Result<(), Error> {
|
||||
let initial: Pages = Pages(memory_type.limits().initial() as usize);
|
||||
let maximum: Option<Pages> = memory_type.limits().maximum().map(|m| Pages(m as usize));
|
||||
::memory::validate_memory(initial, maximum).map_err(Error)
|
||||
}
|
||||
|
||||
fn validate_table_type(table_type: &TableType) -> Result<(), Error> {
|
||||
validate_limits(table_type.limits())
|
||||
}
|
||||
|
||||
fn validate_global_entry(global_entry: &GlobalEntry, globals: &[GlobalType]) -> Result<(), Error> {
|
||||
let init = global_entry.init_expr();
|
||||
let init_expr_ty = expr_const_type(init, globals)?;
|
||||
if init_expr_ty != global_entry.global_type().content_type() {
|
||||
return Err(Error(format!(
|
||||
"Trying to initialize variable of type {:?} with value of type {:?}",
|
||||
global_entry.global_type().content_type(),
|
||||
init_expr_ty
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns type of this constant expression.
|
||||
fn expr_const_type(init_expr: &InitExpr, globals: &[GlobalType]) -> Result<ValueType, Error> {
|
||||
let code = init_expr.code();
|
||||
if code.len() != 2 {
|
||||
return Err(Error(
|
||||
"Init expression should always be with length 2".into(),
|
||||
));
|
||||
}
|
||||
let expr_ty: ValueType = match code[0] {
|
||||
Opcode::I32Const(_) => ValueType::I32,
|
||||
Opcode::I64Const(_) => ValueType::I64,
|
||||
Opcode::F32Const(_) => ValueType::F32,
|
||||
Opcode::F64Const(_) => ValueType::F64,
|
||||
Opcode::GetGlobal(idx) => {
|
||||
match globals.get(idx as usize) {
|
||||
Some(target_global) => {
|
||||
if target_global.is_mutable() {
|
||||
return Err(Error(format!("Global {} is mutable", idx)));
|
||||
}
|
||||
target_global.content_type()
|
||||
}
|
||||
None => {
|
||||
return Err(Error(
|
||||
format!("Global {} doesn't exists or not yet defined", idx),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => return Err(Error("Non constant opcode in init expr".into())),
|
||||
};
|
||||
if code[1] != Opcode::End {
|
||||
return Err(Error("Expression doesn't ends with `end` opcode".into()));
|
||||
}
|
||||
Ok(expr_ty)
|
||||
}
|
|
@ -1,301 +0,0 @@
|
|||
use super::validate_module;
|
||||
use parity_wasm::builder::module;
|
||||
use parity_wasm::elements::{
|
||||
External, GlobalEntry, GlobalType, ImportEntry, InitExpr, MemoryType,
|
||||
Opcode, Opcodes, TableType, ValueType, BlockType
|
||||
};
|
||||
|
||||
#[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![Opcode::I32Const(42), Opcode::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![Opcode::I32Const(42), Opcode::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![Opcode::GetGlobal(0), Opcode::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![Opcode::GetGlobal(0), Opcode::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![Opcode::GetGlobal(0), Opcode::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![Opcode::I32Const(0), Opcode::End])
|
||||
)
|
||||
)
|
||||
.with_global(
|
||||
GlobalEntry::new(
|
||||
GlobalType::new(ValueType::I32, true),
|
||||
InitExpr::new(vec![Opcode::GetGlobal(0), Opcode::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![Opcode::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![Opcode::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![Opcode::Unreachable, Opcode::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_opcodes(Opcodes::new(vec![
|
||||
Opcode::Call(1),
|
||||
Opcode::End,
|
||||
])).build()
|
||||
.build()
|
||||
.function()
|
||||
.signature().return_type().i32().build()
|
||||
.body().with_opcodes(Opcodes::new(vec![
|
||||
Opcode::Call(0),
|
||||
Opcode::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_opcodes(Opcodes::new(vec![
|
||||
Opcode::I32Const(1),
|
||||
Opcode::If(BlockType::NoResult),
|
||||
Opcode::I32Const(1),
|
||||
Opcode::If(BlockType::Value(ValueType::I32)),
|
||||
Opcode::I32Const(1),
|
||||
Opcode::Else,
|
||||
Opcode::I32Const(2),
|
||||
Opcode::End,
|
||||
Opcode::Drop,
|
||||
Opcode::End,
|
||||
Opcode::End,
|
||||
])).build()
|
||||
.build()
|
||||
.build();
|
||||
validate_module(m).unwrap();
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
use parity_wasm::elements::{Local, ValueType};
|
||||
use validation::Error;
|
||||
|
||||
/// Locals are the concatenation of a slice of function parameters
|
||||
/// with function declared local variables.
|
||||
///
|
||||
/// Local variables are given in the form of groups represented by pairs
|
||||
/// of a value_type and a count.
|
||||
#[derive(Debug)]
|
||||
pub struct Locals<'a> {
|
||||
params: &'a [ValueType],
|
||||
local_groups: &'a [Local],
|
||||
}
|
||||
|
||||
impl<'a> Locals<'a> {
|
||||
pub fn new(params: &'a [ValueType], local_groups: &'a [Local]) -> Locals<'a> {
|
||||
Locals {
|
||||
params,
|
||||
local_groups,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the type of a local variable (either a declared local or a param).
|
||||
///
|
||||
/// Returns `Err` in the case of overflow or when idx falls out of range.
|
||||
pub fn type_of_local(&self, idx: u32) -> Result<ValueType, Error> {
|
||||
if let Some(param) = self.params.get(idx as usize) {
|
||||
return Ok(*param);
|
||||
}
|
||||
|
||||
// If an index doesn't point to a param, then we have to look into local declarations.
|
||||
let mut start_idx = self.params.len() as u32;
|
||||
for locals_group in self.local_groups {
|
||||
let end_idx = start_idx
|
||||
.checked_add(locals_group.count())
|
||||
.ok_or_else(|| Error(String::from("Locals range not in 32-bit range")))?;
|
||||
|
||||
if idx >= start_idx && idx < end_idx {
|
||||
return Ok(locals_group.value_type());
|
||||
}
|
||||
|
||||
start_idx = end_idx;
|
||||
}
|
||||
|
||||
// We didn't find anything, that's an error.
|
||||
// At this moment `start_idx` should hold the count of all locals
|
||||
// (since it's either set to the `end_idx` or equal to `params.len()`)
|
||||
let total_count = start_idx;
|
||||
|
||||
Err(Error(format!(
|
||||
"Trying to access local with index {} when there are only {} locals",
|
||||
idx, total_count
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn locals_it_works() {
|
||||
let params = vec![ValueType::I32, ValueType::I64];
|
||||
let local_groups = vec![Local::new(2, ValueType::F32), Local::new(2, ValueType::F64)];
|
||||
let locals = Locals::new(¶ms, &local_groups);
|
||||
|
||||
assert_matches!(locals.type_of_local(0), Ok(ValueType::I32));
|
||||
assert_matches!(locals.type_of_local(1), Ok(ValueType::I64));
|
||||
assert_matches!(locals.type_of_local(2), Ok(ValueType::F32));
|
||||
assert_matches!(locals.type_of_local(3), Ok(ValueType::F32));
|
||||
assert_matches!(locals.type_of_local(4), Ok(ValueType::F64));
|
||||
assert_matches!(locals.type_of_local(5), Ok(ValueType::F64));
|
||||
assert_matches!(locals.type_of_local(6), Err(_));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn locals_no_declared_locals() {
|
||||
let params = vec![ValueType::I32];
|
||||
let locals = Locals::new(¶ms, &[]);
|
||||
|
||||
assert_matches!(locals.type_of_local(0), Ok(ValueType::I32));
|
||||
assert_matches!(locals.type_of_local(1), Err(_));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn locals_no_params() {
|
||||
let local_groups = vec![Local::new(2, ValueType::I32), Local::new(3, ValueType::I64)];
|
||||
let locals = Locals::new(&[], &local_groups);
|
||||
|
||||
assert_matches!(locals.type_of_local(0), Ok(ValueType::I32));
|
||||
assert_matches!(locals.type_of_local(1), Ok(ValueType::I32));
|
||||
assert_matches!(locals.type_of_local(2), Ok(ValueType::I64));
|
||||
assert_matches!(locals.type_of_local(3), Ok(ValueType::I64));
|
||||
assert_matches!(locals.type_of_local(4), Ok(ValueType::I64));
|
||||
assert_matches!(locals.type_of_local(5), Err(_));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn locals_u32_overflow() {
|
||||
let local_groups = vec![
|
||||
Local::new(u32::max_value(), ValueType::I32),
|
||||
Local::new(1, ValueType::I64),
|
||||
];
|
||||
let locals = Locals::new(&[], &local_groups);
|
||||
|
||||
assert_matches!(
|
||||
locals.type_of_local(u32::max_value() - 1),
|
||||
Ok(ValueType::I32)
|
||||
);
|
||||
assert_matches!(locals.type_of_local(u32::max_value()), Err(_));
|
||||
}
|
||||
}
|
469
src/value.rs
469
src/value.rs
|
@ -1,11 +1,12 @@
|
|||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
use core::{f32, i32, i64, u32, u64};
|
||||
use nan_preserving_float::{F32, F64};
|
||||
use std::io;
|
||||
use std::{f32, i32, i64, u32, u64};
|
||||
use types::ValueType;
|
||||
use TrapKind;
|
||||
|
||||
/// Error for `LittleEndianConvert`
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// The buffer is too short for the type being deserialized
|
||||
InvalidLittleEndianBuffer,
|
||||
}
|
||||
|
||||
|
@ -37,7 +38,10 @@ pub enum RuntimeValue {
|
|||
/// [`I32`]: enum.RuntimeValue.html#variant.I32
|
||||
/// [`F64`]: enum.RuntimeValue.html#variant.F64
|
||||
/// [`RuntimeValue`]: enum.RuntimeValue.html
|
||||
pub trait FromRuntimeValue where Self: Sized {
|
||||
pub trait FromRuntimeValue
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
/// Create a value of type `Self` from a given [`RuntimeValue`].
|
||||
///
|
||||
/// Returns `None` if the [`RuntimeValue`] is of type different than
|
||||
|
@ -72,9 +76,12 @@ pub trait TransmuteInto<T> {
|
|||
}
|
||||
|
||||
/// Convert from and to little endian.
|
||||
pub trait LittleEndianConvert where Self: Sized {
|
||||
pub trait LittleEndianConvert
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
/// Convert to little endian buffer.
|
||||
fn into_little_endian(self) -> Vec<u8>;
|
||||
fn into_little_endian(self, buffer: &mut [u8]);
|
||||
/// Convert from little endian buffer.
|
||||
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error>;
|
||||
}
|
||||
|
@ -133,12 +140,12 @@ pub trait Float<T>: ArithmeticOps<T> {
|
|||
|
||||
impl RuntimeValue {
|
||||
/// Creates new default value of given type.
|
||||
pub fn default(value_type: ::types::ValueType) -> Self {
|
||||
pub fn default(value_type: ValueType) -> Self {
|
||||
match value_type {
|
||||
::types::ValueType::I32 => RuntimeValue::I32(0),
|
||||
::types::ValueType::I64 => RuntimeValue::I64(0),
|
||||
::types::ValueType::F32 => RuntimeValue::F32(0f32.into()),
|
||||
::types::ValueType::F64 => RuntimeValue::F64(0f64.into()),
|
||||
ValueType::I32 => RuntimeValue::I32(0),
|
||||
ValueType::I64 => RuntimeValue::I64(0),
|
||||
ValueType::F32 => RuntimeValue::F32(0f32.into()),
|
||||
ValueType::F64 => RuntimeValue::F64(0f64.into()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,12 +160,12 @@ impl RuntimeValue {
|
|||
}
|
||||
|
||||
/// Get variable type for this value.
|
||||
pub fn value_type(&self) -> ::types::ValueType {
|
||||
pub fn value_type(&self) -> ValueType {
|
||||
match *self {
|
||||
RuntimeValue::I32(_) => ::types::ValueType::I32,
|
||||
RuntimeValue::I64(_) => ::types::ValueType::I64,
|
||||
RuntimeValue::F32(_) => ::types::ValueType::F32,
|
||||
RuntimeValue::F64(_) => ::types::ValueType::F64,
|
||||
RuntimeValue::I32(_) => ValueType::I32,
|
||||
RuntimeValue::I64(_) => ValueType::I64,
|
||||
RuntimeValue::F32(_) => ValueType::F32,
|
||||
RuntimeValue::F64(_) => ValueType::F64,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,6 +181,18 @@ impl RuntimeValue {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<i8> for RuntimeValue {
|
||||
fn from(val: i8) -> Self {
|
||||
RuntimeValue::I32(val as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i16> for RuntimeValue {
|
||||
fn from(val: i16) -> Self {
|
||||
RuntimeValue::I32(val as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for RuntimeValue {
|
||||
fn from(val: i32) -> Self {
|
||||
RuntimeValue::I32(val)
|
||||
|
@ -186,15 +205,27 @@ impl From<i64> for RuntimeValue {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<u8> for RuntimeValue {
|
||||
fn from(val: u8) -> Self {
|
||||
RuntimeValue::I32(val as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u16> for RuntimeValue {
|
||||
fn from(val: u16) -> Self {
|
||||
RuntimeValue::I32(val as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for RuntimeValue {
|
||||
fn from(val: u32) -> Self {
|
||||
RuntimeValue::I32(val as i32)
|
||||
RuntimeValue::I32(val.transmute_into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for RuntimeValue {
|
||||
fn from(val: u64) -> Self {
|
||||
RuntimeValue::I64(val as i64)
|
||||
RuntimeValue::I64(val.transmute_into())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -236,6 +267,62 @@ impl FromRuntimeValue for bool {
|
|||
}
|
||||
}
|
||||
|
||||
/// This conversion assumes that `i8` is represented as an [`I32`].
|
||||
///
|
||||
/// [`I32`]: enum.RuntimeValue.html#variant.I32
|
||||
impl FromRuntimeValue for i8 {
|
||||
fn from_runtime_value(val: RuntimeValue) -> Option<Self> {
|
||||
let min = i8::min_value() as i32;
|
||||
let max = i8::max_value() as i32;
|
||||
match val {
|
||||
RuntimeValue::I32(val) if min <= val && val <= max => Some(val as i8),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This conversion assumes that `i16` is represented as an [`I32`].
|
||||
///
|
||||
/// [`I32`]: enum.RuntimeValue.html#variant.I32
|
||||
impl FromRuntimeValue for i16 {
|
||||
fn from_runtime_value(val: RuntimeValue) -> Option<Self> {
|
||||
let min = i16::min_value() as i32;
|
||||
let max = i16::max_value() as i32;
|
||||
match val {
|
||||
RuntimeValue::I32(val) if min <= val && val <= max => Some(val as i16),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This conversion assumes that `u8` is represented as an [`I32`].
|
||||
///
|
||||
/// [`I32`]: enum.RuntimeValue.html#variant.I32
|
||||
impl FromRuntimeValue for u8 {
|
||||
fn from_runtime_value(val: RuntimeValue) -> Option<Self> {
|
||||
let min = u8::min_value() as i32;
|
||||
let max = u8::max_value() as i32;
|
||||
match val {
|
||||
RuntimeValue::I32(val) if min <= val && val <= max => Some(val as u8),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This conversion assumes that `u16` is represented as an [`I32`].
|
||||
///
|
||||
/// [`I32`]: enum.RuntimeValue.html#variant.I32
|
||||
impl FromRuntimeValue for u16 {
|
||||
fn from_runtime_value(val: RuntimeValue) -> Option<Self> {
|
||||
let min = u16::min_value() as i32;
|
||||
let max = u16::max_value() as i32;
|
||||
match val {
|
||||
RuntimeValue::I32(val) if min <= val && val <= max => Some(val as u16),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_from_runtime_value!(I32, i32);
|
||||
impl_from_runtime_value!(I64, i64);
|
||||
impl_from_runtime_value!(F32, F32);
|
||||
|
@ -279,8 +366,17 @@ impl WrapInto<F32> for F64 {
|
|||
}
|
||||
|
||||
macro_rules! impl_try_truncate_into {
|
||||
($from: ident, $into: ident) => {
|
||||
(@primitive $from: ident, $into: ident, $to_primitive:path) => {
|
||||
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> {
|
||||
// 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
|
||||
|
@ -299,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 {
|
||||
fn try_truncate_into(self) -> Result<$into, TrapKind> {
|
||||
$intermediate::from(self).try_truncate_into()
|
||||
|
@ -308,22 +404,22 @@ macro_rules! impl_try_truncate_into {
|
|||
};
|
||||
}
|
||||
|
||||
impl_try_truncate_into!(f32, i32);
|
||||
impl_try_truncate_into!(f32, i64);
|
||||
impl_try_truncate_into!(f64, i32);
|
||||
impl_try_truncate_into!(f64, i64);
|
||||
impl_try_truncate_into!(f32, u32);
|
||||
impl_try_truncate_into!(f32, u64);
|
||||
impl_try_truncate_into!(f64, u32);
|
||||
impl_try_truncate_into!(f64, u64);
|
||||
impl_try_truncate_into!(F32, f32, i32);
|
||||
impl_try_truncate_into!(F32, f32, i64);
|
||||
impl_try_truncate_into!(F64, f64, i32);
|
||||
impl_try_truncate_into!(F64, f64, i64);
|
||||
impl_try_truncate_into!(F32, f32, u32);
|
||||
impl_try_truncate_into!(F32, f32, u64);
|
||||
impl_try_truncate_into!(F64, f64, u32);
|
||||
impl_try_truncate_into!(F64, f64, u64);
|
||||
impl_try_truncate_into!(@primitive f32, i32, num_traits::cast::ToPrimitive::to_i32);
|
||||
impl_try_truncate_into!(@primitive f32, i64, num_traits::cast::ToPrimitive::to_i64);
|
||||
impl_try_truncate_into!(@primitive f64, i32, num_traits::cast::ToPrimitive::to_i32);
|
||||
impl_try_truncate_into!(@primitive f64, i64, num_traits::cast::ToPrimitive::to_i64);
|
||||
impl_try_truncate_into!(@primitive f32, u32, num_traits::cast::ToPrimitive::to_u32);
|
||||
impl_try_truncate_into!(@primitive f32, u64, num_traits::cast::ToPrimitive::to_u64);
|
||||
impl_try_truncate_into!(@primitive f64, u32, num_traits::cast::ToPrimitive::to_u32);
|
||||
impl_try_truncate_into!(@primitive f64, u64, num_traits::cast::ToPrimitive::to_u64);
|
||||
impl_try_truncate_into!(@wrapped F32, f32, i32);
|
||||
impl_try_truncate_into!(@wrapped F32, f32, i64);
|
||||
impl_try_truncate_into!(@wrapped F64, f64, i32);
|
||||
impl_try_truncate_into!(@wrapped F64, f64, i64);
|
||||
impl_try_truncate_into!(@wrapped F32, f32, u32);
|
||||
impl_try_truncate_into!(@wrapped F32, f32, u64);
|
||||
impl_try_truncate_into!(@wrapped F64, f64, u32);
|
||||
impl_try_truncate_into!(@wrapped F64, f64, u64);
|
||||
|
||||
macro_rules! impl_extend_into {
|
||||
($from:ident, $into:ident) => {
|
||||
|
@ -382,7 +478,7 @@ macro_rules! impl_transmute_into_self {
|
|||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_transmute_into_self!(i32);
|
||||
|
@ -399,15 +495,12 @@ macro_rules! impl_transmute_into_as {
|
|||
self as $into
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_transmute_into_as!(i8, u8);
|
||||
impl_transmute_into_as!(u8, i8);
|
||||
impl_transmute_into_as!(i32, u32);
|
||||
impl_transmute_into_as!(u32, i32);
|
||||
impl_transmute_into_as!(i64, u64);
|
||||
impl_transmute_into_as!(u64, i64);
|
||||
|
||||
macro_rules! impl_transmute_into_npf {
|
||||
($npf:ident, $float:ident, $signed:ident, $unsigned:ident) => {
|
||||
|
@ -453,148 +546,189 @@ impl_transmute_into_npf!(F32, f32, i32, u32);
|
|||
impl_transmute_into_npf!(F64, f64, i64, u64);
|
||||
|
||||
impl TransmuteInto<i32> for f32 {
|
||||
fn transmute_into(self) -> i32 { self.to_bits() as i32 }
|
||||
fn transmute_into(self) -> i32 {
|
||||
self.to_bits() as i32
|
||||
}
|
||||
}
|
||||
|
||||
impl TransmuteInto<i64> for f64 {
|
||||
fn transmute_into(self) -> i64 { self.to_bits() as i64 }
|
||||
fn transmute_into(self) -> i64 {
|
||||
self.to_bits() as i64
|
||||
}
|
||||
}
|
||||
|
||||
impl TransmuteInto<f32> for i32 {
|
||||
fn transmute_into(self) -> f32 { f32::from_bits(self as u32) }
|
||||
fn transmute_into(self) -> f32 {
|
||||
f32::from_bits(self as u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl TransmuteInto<f64> for i64 {
|
||||
fn transmute_into(self) -> f64 { f64::from_bits(self as u64) }
|
||||
fn transmute_into(self) -> f64 {
|
||||
f64::from_bits(self as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl TransmuteInto<i32> for u32 {
|
||||
fn transmute_into(self) -> i32 {
|
||||
self as _
|
||||
}
|
||||
}
|
||||
|
||||
impl TransmuteInto<i64> for u64 {
|
||||
fn transmute_into(self) -> i64 {
|
||||
self as _
|
||||
}
|
||||
}
|
||||
|
||||
impl LittleEndianConvert for i8 {
|
||||
fn into_little_endian(self) -> Vec<u8> {
|
||||
vec![self as u8]
|
||||
fn into_little_endian(self, buffer: &mut [u8]) {
|
||||
buffer[0] = self as u8;
|
||||
}
|
||||
|
||||
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
||||
buffer.get(0)
|
||||
buffer
|
||||
.get(0)
|
||||
.map(|v| *v as i8)
|
||||
.ok_or_else(|| Error::InvalidLittleEndianBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl LittleEndianConvert for u8 {
|
||||
fn into_little_endian(self) -> Vec<u8> {
|
||||
vec![self]
|
||||
fn into_little_endian(self, buffer: &mut [u8]) {
|
||||
buffer[0] = self;
|
||||
}
|
||||
|
||||
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
||||
buffer.get(0)
|
||||
buffer
|
||||
.get(0)
|
||||
.cloned()
|
||||
.ok_or_else(|| Error::InvalidLittleEndianBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl LittleEndianConvert for i16 {
|
||||
fn into_little_endian(self) -> Vec<u8> {
|
||||
let mut vec = Vec::with_capacity(2);
|
||||
vec.write_i16::<LittleEndian>(self)
|
||||
.expect("i16 is written without any errors");
|
||||
vec
|
||||
fn into_little_endian(self, buffer: &mut [u8]) {
|
||||
buffer.copy_from_slice(&self.to_le_bytes());
|
||||
}
|
||||
|
||||
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
||||
io::Cursor::new(buffer).read_i16::<LittleEndian>()
|
||||
.map_err(|_| Error::InvalidLittleEndianBuffer)
|
||||
let mut res = [0u8; 2];
|
||||
buffer
|
||||
.get(0..2)
|
||||
.map(|s| {
|
||||
res.copy_from_slice(s);
|
||||
Self::from_le_bytes(res)
|
||||
})
|
||||
.ok_or_else(|| Error::InvalidLittleEndianBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl LittleEndianConvert for u16 {
|
||||
fn into_little_endian(self) -> Vec<u8> {
|
||||
let mut vec = Vec::with_capacity(2);
|
||||
vec.write_u16::<LittleEndian>(self)
|
||||
.expect("u16 is written without any errors");
|
||||
vec
|
||||
fn into_little_endian(self, buffer: &mut [u8]) {
|
||||
buffer.copy_from_slice(&self.to_le_bytes());
|
||||
}
|
||||
|
||||
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
||||
io::Cursor::new(buffer).read_u16::<LittleEndian>()
|
||||
.map_err(|_| Error::InvalidLittleEndianBuffer)
|
||||
let mut res = [0u8; 2];
|
||||
buffer
|
||||
.get(0..2)
|
||||
.map(|s| {
|
||||
res.copy_from_slice(s);
|
||||
Self::from_le_bytes(res)
|
||||
})
|
||||
.ok_or_else(|| Error::InvalidLittleEndianBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl LittleEndianConvert for i32 {
|
||||
fn into_little_endian(self) -> Vec<u8> {
|
||||
let mut vec = Vec::with_capacity(4);
|
||||
vec.write_i32::<LittleEndian>(self)
|
||||
.expect("i32 is written without any errors");
|
||||
vec
|
||||
fn into_little_endian(self, buffer: &mut [u8]) {
|
||||
buffer.copy_from_slice(&self.to_le_bytes());
|
||||
}
|
||||
|
||||
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
||||
io::Cursor::new(buffer).read_i32::<LittleEndian>()
|
||||
.map_err(|_| Error::InvalidLittleEndianBuffer)
|
||||
let mut res = [0u8; 4];
|
||||
buffer
|
||||
.get(0..4)
|
||||
.map(|s| {
|
||||
res.copy_from_slice(s);
|
||||
Self::from_le_bytes(res)
|
||||
})
|
||||
.ok_or_else(|| Error::InvalidLittleEndianBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl LittleEndianConvert for u32 {
|
||||
fn into_little_endian(self) -> Vec<u8> {
|
||||
let mut vec = Vec::with_capacity(4);
|
||||
vec.write_u32::<LittleEndian>(self)
|
||||
.expect("u32 is written without any errors");
|
||||
vec
|
||||
fn into_little_endian(self, buffer: &mut [u8]) {
|
||||
buffer.copy_from_slice(&self.to_le_bytes());
|
||||
}
|
||||
|
||||
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
||||
io::Cursor::new(buffer).read_u32::<LittleEndian>()
|
||||
.map_err(|_| Error::InvalidLittleEndianBuffer)
|
||||
let mut res = [0u8; 4];
|
||||
buffer
|
||||
.get(0..4)
|
||||
.map(|s| {
|
||||
res.copy_from_slice(s);
|
||||
Self::from_le_bytes(res)
|
||||
})
|
||||
.ok_or_else(|| Error::InvalidLittleEndianBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl LittleEndianConvert for i64 {
|
||||
fn into_little_endian(self) -> Vec<u8> {
|
||||
let mut vec = Vec::with_capacity(8);
|
||||
vec.write_i64::<LittleEndian>(self)
|
||||
.expect("i64 is written without any errors");
|
||||
vec
|
||||
fn into_little_endian(self, buffer: &mut [u8]) {
|
||||
buffer.copy_from_slice(&self.to_le_bytes());
|
||||
}
|
||||
|
||||
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
||||
io::Cursor::new(buffer).read_i64::<LittleEndian>()
|
||||
.map_err(|_| Error::InvalidLittleEndianBuffer)
|
||||
let mut res = [0u8; 8];
|
||||
buffer
|
||||
.get(0..8)
|
||||
.map(|s| {
|
||||
res.copy_from_slice(s);
|
||||
Self::from_le_bytes(res)
|
||||
})
|
||||
.ok_or_else(|| Error::InvalidLittleEndianBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl LittleEndianConvert for f32 {
|
||||
fn into_little_endian(self) -> Vec<u8> {
|
||||
let mut vec = Vec::with_capacity(4);
|
||||
vec.write_f32::<LittleEndian>(self)
|
||||
.expect("f32 is written without any errors");
|
||||
vec
|
||||
fn into_little_endian(self, buffer: &mut [u8]) {
|
||||
buffer.copy_from_slice(&self.to_bits().to_le_bytes());
|
||||
}
|
||||
|
||||
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
||||
io::Cursor::new(buffer).read_u32::<LittleEndian>()
|
||||
.map(f32::from_bits)
|
||||
.map_err(|_| Error::InvalidLittleEndianBuffer)
|
||||
let mut res = [0u8; 4];
|
||||
buffer
|
||||
.get(0..4)
|
||||
.map(|s| {
|
||||
res.copy_from_slice(s);
|
||||
Self::from_bits(u32::from_le_bytes(res))
|
||||
})
|
||||
.ok_or_else(|| Error::InvalidLittleEndianBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl LittleEndianConvert for f64 {
|
||||
fn into_little_endian(self) -> Vec<u8> {
|
||||
let mut vec = Vec::with_capacity(8);
|
||||
vec.write_f64::<LittleEndian>(self)
|
||||
.expect("i64 is written without any errors");
|
||||
vec
|
||||
fn into_little_endian(self, buffer: &mut [u8]) {
|
||||
buffer.copy_from_slice(&self.to_bits().to_le_bytes());
|
||||
}
|
||||
|
||||
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
||||
io::Cursor::new(buffer).read_u64::<LittleEndian>()
|
||||
.map(f64::from_bits)
|
||||
.map_err(|_| Error::InvalidLittleEndianBuffer)
|
||||
let mut res = [0u8; 8];
|
||||
buffer
|
||||
.get(0..8)
|
||||
.map(|s| {
|
||||
res.copy_from_slice(s);
|
||||
Self::from_bits(u64::from_le_bytes(res))
|
||||
})
|
||||
.ok_or_else(|| Error::InvalidLittleEndianBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl LittleEndianConvert for F32 {
|
||||
fn into_little_endian(self) -> Vec<u8> {
|
||||
(self.to_bits() as i32).into_little_endian()
|
||||
fn into_little_endian(self, buffer: &mut [u8]) {
|
||||
(self.to_bits() as i32).into_little_endian(buffer)
|
||||
}
|
||||
|
||||
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
||||
|
@ -603,8 +737,8 @@ impl LittleEndianConvert for F32 {
|
|||
}
|
||||
|
||||
impl LittleEndianConvert for F64 {
|
||||
fn into_little_endian(self) -> Vec<u8> {
|
||||
(self.to_bits() as i64).into_little_endian()
|
||||
fn into_little_endian(self, buffer: &mut [u8]) {
|
||||
(self.to_bits() as i64).into_little_endian(buffer)
|
||||
}
|
||||
|
||||
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
||||
|
@ -615,14 +749,19 @@ impl LittleEndianConvert for F64 {
|
|||
macro_rules! impl_integer_arithmetic_ops {
|
||||
($type: ident) => {
|
||||
impl ArithmeticOps<$type> for $type {
|
||||
fn add(self, other: $type) -> $type { self.wrapping_add(other) }
|
||||
fn sub(self, other: $type) -> $type { self.wrapping_sub(other) }
|
||||
fn mul(self, other: $type) -> $type { self.wrapping_mul(other) }
|
||||
fn add(self, other: $type) -> $type {
|
||||
self.wrapping_add(other)
|
||||
}
|
||||
fn sub(self, other: $type) -> $type {
|
||||
self.wrapping_sub(other)
|
||||
}
|
||||
fn mul(self, other: $type) -> $type {
|
||||
self.wrapping_mul(other)
|
||||
}
|
||||
fn div(self, other: $type) -> Result<$type, TrapKind> {
|
||||
if other == 0 {
|
||||
Err(TrapKind::DivisionByZero)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
let (result, overflow) = self.overflowing_div(other);
|
||||
if overflow {
|
||||
Err(TrapKind::InvalidConversionToInt)
|
||||
|
@ -632,7 +771,7 @@ macro_rules! impl_integer_arithmetic_ops {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_integer_arithmetic_ops!(i32);
|
||||
|
@ -643,12 +782,20 @@ impl_integer_arithmetic_ops!(u64);
|
|||
macro_rules! impl_float_arithmetic_ops {
|
||||
($type: ident) => {
|
||||
impl ArithmeticOps<$type> for $type {
|
||||
fn add(self, other: $type) -> $type { self + other }
|
||||
fn sub(self, other: $type) -> $type { self - other }
|
||||
fn mul(self, other: $type) -> $type { self * other }
|
||||
fn div(self, other: $type) -> Result<$type, TrapKind> { Ok(self / other) }
|
||||
fn add(self, other: $type) -> $type {
|
||||
self + other
|
||||
}
|
||||
fn sub(self, other: $type) -> $type {
|
||||
self - other
|
||||
}
|
||||
fn mul(self, other: $type) -> $type {
|
||||
self * other
|
||||
}
|
||||
fn div(self, other: $type) -> Result<$type, TrapKind> {
|
||||
Ok(self / other)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_float_arithmetic_ops!(f32);
|
||||
|
@ -659,17 +806,30 @@ impl_float_arithmetic_ops!(F64);
|
|||
macro_rules! impl_integer {
|
||||
($type: ident) => {
|
||||
impl Integer<$type> for $type {
|
||||
fn leading_zeros(self) -> $type { self.leading_zeros() as $type }
|
||||
fn trailing_zeros(self) -> $type { self.trailing_zeros() as $type }
|
||||
fn count_ones(self) -> $type { self.count_ones() as $type }
|
||||
fn rotl(self, other: $type) -> $type { self.rotate_left(other as u32) }
|
||||
fn rotr(self, other: $type) -> $type { self.rotate_right(other as u32) }
|
||||
fn leading_zeros(self) -> $type {
|
||||
self.leading_zeros() as $type
|
||||
}
|
||||
fn trailing_zeros(self) -> $type {
|
||||
self.trailing_zeros() as $type
|
||||
}
|
||||
fn count_ones(self) -> $type {
|
||||
self.count_ones() as $type
|
||||
}
|
||||
fn rotl(self, other: $type) -> $type {
|
||||
self.rotate_left(other as u32)
|
||||
}
|
||||
fn rotr(self, other: $type) -> $type {
|
||||
self.rotate_right(other as u32)
|
||||
}
|
||||
fn rem(self, other: $type) -> Result<$type, TrapKind> {
|
||||
if other == 0 { Err(TrapKind::DivisionByZero) }
|
||||
else { Ok(self.wrapping_rem(other)) }
|
||||
if other == 0 {
|
||||
Err(TrapKind::DivisionByZero)
|
||||
} else {
|
||||
Ok(self.wrapping_rem(other))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_integer!(i32);
|
||||
|
@ -677,34 +837,43 @@ impl_integer!(u32);
|
|||
impl_integer!(i64);
|
||||
impl_integer!(u64);
|
||||
|
||||
macro_rules! impl_float {
|
||||
($type:ident, $int_type:ident) => {
|
||||
impl_float!($type, $type, $int_type);
|
||||
macro_rules! call_math {
|
||||
($op:ident, $e:expr, $fXX:ident, $FXXExt:ident) => {
|
||||
::libm::$FXXExt::$op($e)
|
||||
};
|
||||
($type:ident, $intermediate:ident, $int_type:ident) => {
|
||||
}
|
||||
|
||||
// We cannot call the math functions directly, because there are multiple available implementaitons in no_std.
|
||||
// In std, there are only `Value::$op` and `std::$fXX:$op`.
|
||||
// The `std` ones are preferred, because they are not from a trait.
|
||||
// For `no_std`, the implementations are `Value::$op` and `libm::FXXExt::$op`,
|
||||
// both of which are trait implementations and hence ambiguous.
|
||||
// So we have to use a full path, which is what `call_math!` does.
|
||||
macro_rules! impl_float {
|
||||
($type:ident, $fXX:ident, $FXXExt:ident, $iXX:ident) => {
|
||||
impl Float<$type> for $type {
|
||||
fn abs(self) -> $type {
|
||||
$intermediate::abs(self.into()).into()
|
||||
call_math!(abs, $fXX::from(self), $fXX, $FXXExt).into()
|
||||
}
|
||||
fn floor(self) -> $type {
|
||||
$intermediate::floor(self.into()).into()
|
||||
call_math!(floor, $fXX::from(self), $fXX, $FXXExt).into()
|
||||
}
|
||||
fn ceil(self) -> $type {
|
||||
$intermediate::ceil(self.into()).into()
|
||||
call_math!(ceil, $fXX::from(self), $fXX, $FXXExt).into()
|
||||
}
|
||||
fn trunc(self) -> $type {
|
||||
$intermediate::trunc(self.into()).into()
|
||||
call_math!(trunc, $fXX::from(self), $fXX, $FXXExt).into()
|
||||
}
|
||||
fn round(self) -> $type {
|
||||
$intermediate::round(self.into()).into()
|
||||
call_math!(round, $fXX::from(self), $fXX, $FXXExt).into()
|
||||
}
|
||||
fn nearest(self) -> $type {
|
||||
let round = self.round();
|
||||
if self.fract().abs() != 0.5 {
|
||||
if call_math!(fract, $fXX::from(self), $fXX, $FXXExt).abs() != 0.5 {
|
||||
return round;
|
||||
}
|
||||
|
||||
use std::ops::Rem;
|
||||
use core::ops::Rem;
|
||||
if round.rem(2.0) == 1.0 {
|
||||
self.floor()
|
||||
} else if round.rem(2.0) == -1.0 {
|
||||
|
@ -714,34 +883,40 @@ macro_rules! impl_float {
|
|||
}
|
||||
}
|
||||
fn sqrt(self) -> $type {
|
||||
$intermediate::sqrt(self.into()).into()
|
||||
call_math!(sqrt, $fXX::from(self), $fXX, $FXXExt).into()
|
||||
}
|
||||
// This instruction corresponds to what is sometimes called "minNaN" in other languages.
|
||||
fn min(self, other: $type) -> $type {
|
||||
if self.is_nan() || other.is_nan() {
|
||||
return ::std::$intermediate::NAN.into();
|
||||
if self.is_nan() {
|
||||
return self;
|
||||
}
|
||||
if other.is_nan() {
|
||||
return other;
|
||||
}
|
||||
|
||||
self.min(other)
|
||||
}
|
||||
// This instruction corresponds to what is sometimes called "maxNaN" in other languages.
|
||||
fn max(self, other: $type) -> $type {
|
||||
if self.is_nan() || other.is_nan() {
|
||||
return ::std::$intermediate::NAN.into();
|
||||
if self.is_nan() {
|
||||
return self;
|
||||
}
|
||||
if other.is_nan() {
|
||||
return other;
|
||||
}
|
||||
|
||||
self.max(other)
|
||||
}
|
||||
fn copysign(self, other: $type) -> $type {
|
||||
use std::mem::size_of;
|
||||
use core::mem::size_of;
|
||||
|
||||
if self.is_nan() {
|
||||
return self;
|
||||
}
|
||||
|
||||
let sign_mask: $int_type = 1 << ((size_of::<$int_type>() << 3) - 1);
|
||||
let self_int: $int_type = self.transmute_into();
|
||||
let other_int: $int_type = other.transmute_into();
|
||||
let sign_mask: $iXX = 1 << ((size_of::<$iXX>() << 3) - 1);
|
||||
let self_int: $iXX = self.transmute_into();
|
||||
let other_int: $iXX = other.transmute_into();
|
||||
let is_self_sign_set = (self_int & sign_mask) != 0;
|
||||
let is_other_sign_set = (other_int & sign_mask) != 0;
|
||||
if is_self_sign_set == is_other_sign_set {
|
||||
|
@ -756,7 +931,7 @@ macro_rules! impl_float {
|
|||
};
|
||||
}
|
||||
|
||||
impl_float!(f32, i32);
|
||||
impl_float!(f64, i64);
|
||||
impl_float!(F32, f32, i32);
|
||||
impl_float!(F64, f64, i64);
|
||||
impl_float!(f32, f32, F32Ext, i32);
|
||||
impl_float!(f64, f64, F64Ext, i64);
|
||||
impl_float!(F32, f32, F32Ext, i32);
|
||||
impl_float!(F64, f64, F64Ext, i64);
|
||||
|
|
14
test.sh
14
test.sh
|
@ -1,9 +1,19 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
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)
|
||||
|
||||
time cargo test $CARGOFLAGS
|
||||
time cargo test --all ${EXTRA_ARGS}
|
||||
|
||||
cd -
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
mod run;
|
||||
|
||||
macro_rules! run_test {
|
||||
($label: expr, $test_name: ident) => (
|
||||
($label: expr, $test_name: ident) => {
|
||||
#[test]
|
||||
fn $test_name() {
|
||||
self::run::spec($label)
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
run_test!("address", wasm_address);
|
||||
|
@ -22,7 +22,9 @@ 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);
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
#![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};
|
||||
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 {
|
||||
|
@ -15,6 +18,7 @@ fn spec_to_runtime_value(val: Value<u32, u64>) -> RuntimeValue {
|
|||
Value::I64(v) => RuntimeValue::I64(v),
|
||||
Value::F32(v) => RuntimeValue::F32(v.into()),
|
||||
Value::F64(v) => RuntimeValue::F64(v.into()),
|
||||
Value::V128(_) => panic!("v128 is not supported"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,7 +193,8 @@ impl SpecDriver {
|
|||
fn module_or_last(&self, name: Option<&str>) -> Result<ModuleRef, InterpreterError> {
|
||||
match name {
|
||||
Some(name) => self.module(name),
|
||||
None => self.last_module
|
||||
None => self
|
||||
.last_module
|
||||
.clone()
|
||||
.ok_or_else(|| InterpreterError::Instantiation("No modules registered".into())),
|
||||
}
|
||||
|
@ -300,7 +305,8 @@ fn run_action(
|
|||
"Expected program to have loaded module {:?}",
|
||||
module
|
||||
));
|
||||
let vec_args = args.iter()
|
||||
let vec_args = args
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(spec_to_runtime_value)
|
||||
.collect::<Vec<_>>();
|
||||
|
@ -340,7 +346,16 @@ pub fn spec(name: &str) {
|
|||
fn try_spec(name: &str) -> Result<(), Error> {
|
||||
let mut spec_driver = SpecDriver::new();
|
||||
let spec_script_path = format!("tests/spec/testsuite/{}.wast", name);
|
||||
let mut parser = ScriptParser::from_file(spec_script_path).expect("Can't read spec script");
|
||||
|
||||
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()? {
|
||||
|
@ -360,9 +375,11 @@ fn try_spec(name: &str) -> Result<(), Error> {
|
|||
}};
|
||||
}
|
||||
|
||||
println!("Running spec cmd {}: {:?}", line, kind);
|
||||
|
||||
match kind {
|
||||
CommandKind::Module { name, module, .. } => {
|
||||
load_module(&module.into_vec()?, &name, &mut spec_driver)
|
||||
load_module(&module.into_vec(), &name, &mut spec_driver)
|
||||
.expect("Failed to load module");
|
||||
}
|
||||
CommandKind::AssertReturn { action, expected } => {
|
||||
|
@ -405,12 +422,16 @@ fn try_spec(name: &str) -> Result<(), Error> {
|
|||
Ok(result) => {
|
||||
for actual_result in result.into_iter().collect::<Vec<RuntimeValue>>() {
|
||||
match actual_result {
|
||||
RuntimeValue::F32(val) => if !val.is_nan() {
|
||||
RuntimeValue::F32(val) => {
|
||||
if !val.is_nan() {
|
||||
panic!("Expected nan value, got {:?}", val)
|
||||
},
|
||||
RuntimeValue::F64(val) => if !val.is_nan() {
|
||||
}
|
||||
}
|
||||
RuntimeValue::F64(val) => {
|
||||
if !val.is_nan() {
|
||||
panic!("Expected nan value, got {:?}", val)
|
||||
},
|
||||
}
|
||||
}
|
||||
val @ _ => {
|
||||
panic!("Expected action to return float value, got {:?}", val)
|
||||
}
|
||||
|
@ -426,7 +447,7 @@ fn try_spec(name: &str) -> Result<(), Error> {
|
|||
let result = run_action(&mut spec_driver, &action);
|
||||
match result {
|
||||
Ok(result) => panic!("Expected exhaustion, got result: {:?}", result),
|
||||
Err(_e) => {},
|
||||
Err(_e) => {}
|
||||
}
|
||||
}
|
||||
CommandKind::AssertTrap { action, .. } => {
|
||||
|
@ -444,16 +465,16 @@ fn try_spec(name: &str) -> Result<(), Error> {
|
|||
CommandKind::AssertInvalid { module, .. }
|
||||
| CommandKind::AssertMalformed { module, .. }
|
||||
| CommandKind::AssertUnlinkable { module, .. } => {
|
||||
let module_load = try_load(&module.into_vec()?, &mut spec_driver);
|
||||
let module_load = try_load(&module.into_vec(), &mut spec_driver);
|
||||
match module_load {
|
||||
Ok(_) => panic!("Expected invalid module definition, got some module!"),
|
||||
Err(_e) => {},
|
||||
Err(_e) => {}
|
||||
}
|
||||
}
|
||||
CommandKind::AssertUninstantiable { module, .. } => {
|
||||
match try_load(&module.into_vec()?, &mut spec_driver) {
|
||||
match try_load(&module.into_vec(), &mut spec_driver) {
|
||||
Ok(_) => panic!("Expected error running start function at line {}", line),
|
||||
Err(_e) => {},
|
||||
Err(_e) => {}
|
||||
}
|
||||
}
|
||||
CommandKind::Register { name, as_name, .. } => {
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit c538faa43217146f458b9bc2d4b704d0a4d80963
|
||||
Subproject commit c6a690f89a0dda3c79700aa6377d8b5d8a970eba
|
|
@ -1,6 +1,6 @@
|
|||
//! Official spec testsuite.
|
||||
|
||||
extern crate wasmi;
|
||||
extern crate wabt;
|
||||
extern crate wasmi;
|
||||
|
||||
mod spec;
|
||||
|
|
|
@ -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 = []
|
|
@ -0,0 +1,141 @@
|
|||
use crate::Error;
|
||||
use alloc::vec::Vec;
|
||||
use parity_wasm::elements::{
|
||||
BlockType, FunctionType, GlobalType, MemoryType, TableType, ValueType,
|
||||
};
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ModuleContext {
|
||||
pub memories: Vec<MemoryType>,
|
||||
pub tables: Vec<TableType>,
|
||||
pub globals: Vec<GlobalType>,
|
||||
pub types: Vec<FunctionType>,
|
||||
pub func_type_indexes: Vec<u32>,
|
||||
}
|
||||
|
||||
impl ModuleContext {
|
||||
pub fn memories(&self) -> &[MemoryType] {
|
||||
&self.memories
|
||||
}
|
||||
|
||||
pub fn tables(&self) -> &[TableType] {
|
||||
&self.tables
|
||||
}
|
||||
|
||||
pub fn globals(&self) -> &[GlobalType] {
|
||||
&self.globals
|
||||
}
|
||||
|
||||
pub fn types(&self) -> &[FunctionType] {
|
||||
&self.types
|
||||
}
|
||||
|
||||
pub fn func_type_indexes(&self) -> &[u32] {
|
||||
&self.func_type_indexes
|
||||
}
|
||||
|
||||
pub fn require_memory(&self, idx: u32) -> Result<(), Error> {
|
||||
if self.memories().get(idx as usize).is_none() {
|
||||
return Err(Error(format!("Memory at index {} doesn't exists", idx)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn require_table(&self, idx: u32) -> Result<&TableType, Error> {
|
||||
self.tables()
|
||||
.get(idx as usize)
|
||||
.ok_or_else(|| Error(format!("Table at index {} doesn't exists", idx)))
|
||||
}
|
||||
|
||||
pub fn require_function(&self, idx: u32) -> Result<(&[ValueType], BlockType), Error> {
|
||||
let ty_idx = self
|
||||
.func_type_indexes()
|
||||
.get(idx as usize)
|
||||
.ok_or_else(|| Error(format!("Function at index {} doesn't exists", idx)))?;
|
||||
self.require_function_type(*ty_idx)
|
||||
}
|
||||
|
||||
pub fn require_function_type(&self, idx: u32) -> Result<(&[ValueType], BlockType), Error> {
|
||||
let ty = self
|
||||
.types()
|
||||
.get(idx as usize)
|
||||
.ok_or_else(|| Error(format!("Type at index {} doesn't exists", idx)))?;
|
||||
|
||||
let params = ty.params();
|
||||
let return_ty = ty
|
||||
.return_type()
|
||||
.map(BlockType::Value)
|
||||
.unwrap_or(BlockType::NoResult);
|
||||
Ok((params, return_ty))
|
||||
}
|
||||
|
||||
pub fn require_global(&self, idx: u32, mutability: Option<bool>) -> Result<&GlobalType, Error> {
|
||||
let global = self
|
||||
.globals()
|
||||
.get(idx as usize)
|
||||
.ok_or_else(|| Error(format!("Global at index {} doesn't exists", idx)))?;
|
||||
|
||||
if let Some(expected_mutable) = mutability {
|
||||
if expected_mutable && !global.is_mutable() {
|
||||
return Err(Error(format!("Expected global {} to be mutable", idx)));
|
||||
}
|
||||
if !expected_mutable && global.is_mutable() {
|
||||
return Err(Error(format!("Expected global {} to be immutable", idx)));
|
||||
}
|
||||
}
|
||||
Ok(global)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ModuleContextBuilder {
|
||||
memories: Vec<MemoryType>,
|
||||
tables: Vec<TableType>,
|
||||
globals: Vec<GlobalType>,
|
||||
types: Vec<FunctionType>,
|
||||
func_type_indexes: Vec<u32>,
|
||||
}
|
||||
|
||||
impl ModuleContextBuilder {
|
||||
pub fn new() -> ModuleContextBuilder {
|
||||
ModuleContextBuilder::default()
|
||||
}
|
||||
|
||||
pub fn push_memory(&mut self, memory: MemoryType) {
|
||||
self.memories.push(memory);
|
||||
}
|
||||
|
||||
pub fn push_table(&mut self, table: TableType) {
|
||||
self.tables.push(table);
|
||||
}
|
||||
|
||||
pub fn push_global(&mut self, global: GlobalType) {
|
||||
self.globals.push(global);
|
||||
}
|
||||
|
||||
pub fn set_types(&mut self, types: Vec<FunctionType>) {
|
||||
self.types = types;
|
||||
}
|
||||
|
||||
pub fn push_func_type_index(&mut self, func_type_index: u32) {
|
||||
self.func_type_indexes.push(func_type_index);
|
||||
}
|
||||
|
||||
pub fn build(self) -> ModuleContext {
|
||||
let ModuleContextBuilder {
|
||||
memories,
|
||||
tables,
|
||||
globals,
|
||||
types,
|
||||
func_type_indexes,
|
||||
} = self;
|
||||
|
||||
ModuleContext {
|
||||
memories,
|
||||
tables,
|
||||
globals,
|
||||
types,
|
||||
func_type_indexes,
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,449 @@
|
|||
// TODO: Uncomment
|
||||
// #![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;
|
||||
#[cfg(feature = "std")]
|
||||
use std::error;
|
||||
|
||||
use self::context::ModuleContextBuilder;
|
||||
use parity_wasm::elements::{
|
||||
BlockType, ExportEntry, External, FuncBody, GlobalEntry, GlobalType, InitExpr, Instruction,
|
||||
Internal, MemoryType, Module, ResizableLimits, TableType, Type, ValueType,
|
||||
};
|
||||
|
||||
pub mod context;
|
||||
pub mod func;
|
||||
pub mod util;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
// TODO: Consider using a type other than String, because
|
||||
// of formatting machinary is not welcomed in substrate runtimes.
|
||||
#[derive(Debug)]
|
||||
pub struct Error(pub String);
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl error::Error for Error {
|
||||
fn description(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<stack::Error> for Error {
|
||||
fn from(e: stack::Error) -> Error {
|
||||
Error(format!("Stack: {}", e))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Validator {
|
||||
type Output;
|
||||
type FuncValidator: FuncValidator;
|
||||
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;
|
||||
}
|
||||
|
||||
pub trait FuncValidator {
|
||||
type Output;
|
||||
fn new(ctx: &func::FunctionValidationContext, body: &FuncBody) -> Self;
|
||||
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) -> () {
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
/// A function validator that just validates modules and produces no result.
|
||||
pub struct PlainFuncValidator;
|
||||
|
||||
impl FuncValidator for PlainFuncValidator {
|
||||
type Output = ();
|
||||
|
||||
fn new(_ctx: &func::FunctionValidationContext, _body: &FuncBody) -> PlainFuncValidator {
|
||||
PlainFuncValidator
|
||||
}
|
||||
|
||||
fn next_instruction(
|
||||
&mut self,
|
||||
ctx: &mut func::FunctionValidationContext,
|
||||
instruction: &Instruction,
|
||||
) -> Result<(), Error> {
|
||||
ctx.step(instruction)
|
||||
}
|
||||
|
||||
fn finish(self) -> () {
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate_module<V: Validator>(module: &Module) -> Result<V::Output, Error> {
|
||||
let mut context_builder = ModuleContextBuilder::new();
|
||||
let mut imported_globals = Vec::new();
|
||||
let mut validation = V::new(&module);
|
||||
|
||||
// Copy types from module as is.
|
||||
context_builder.set_types(
|
||||
module
|
||||
.type_section()
|
||||
.map(|ts| {
|
||||
ts.types()
|
||||
.into_iter()
|
||||
.map(|&Type::Function(ref ty)| ty)
|
||||
.cloned()
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
|
||||
// Fill elements with imported values.
|
||||
for import_entry in module
|
||||
.import_section()
|
||||
.map(|i| i.entries())
|
||||
.unwrap_or_default()
|
||||
{
|
||||
match *import_entry.external() {
|
||||
External::Function(idx) => context_builder.push_func_type_index(idx),
|
||||
External::Table(ref table) => context_builder.push_table(table.clone()),
|
||||
External::Memory(ref memory) => context_builder.push_memory(memory.clone()),
|
||||
External::Global(ref global) => {
|
||||
context_builder.push_global(global.clone());
|
||||
imported_globals.push(global.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Concatenate elements with defined in the module.
|
||||
if let Some(function_section) = module.function_section() {
|
||||
for func_entry in function_section.entries() {
|
||||
context_builder.push_func_type_index(func_entry.type_ref())
|
||||
}
|
||||
}
|
||||
if let Some(table_section) = module.table_section() {
|
||||
for table_entry in table_section.entries() {
|
||||
validate_table_type(table_entry)?;
|
||||
context_builder.push_table(table_entry.clone());
|
||||
}
|
||||
}
|
||||
if let Some(mem_section) = module.memory_section() {
|
||||
for mem_entry in mem_section.entries() {
|
||||
validate_memory_type(mem_entry)?;
|
||||
context_builder.push_memory(mem_entry.clone());
|
||||
}
|
||||
}
|
||||
if let Some(global_section) = module.global_section() {
|
||||
for global_entry in global_section.entries() {
|
||||
validate_global_entry(global_entry, &imported_globals)?;
|
||||
context_builder.push_global(global_entry.global_type().clone());
|
||||
}
|
||||
}
|
||||
|
||||
let context = context_builder.build();
|
||||
|
||||
let function_section_len = module
|
||||
.function_section()
|
||||
.map(|s| s.entries().len())
|
||||
.unwrap_or(0);
|
||||
let code_section_len = module.code_section().map(|s| s.bodies().len()).unwrap_or(0);
|
||||
if function_section_len != code_section_len {
|
||||
return Err(Error(format!(
|
||||
"length of function section is {}, while len of code section is {}",
|
||||
function_section_len, code_section_len
|
||||
)));
|
||||
}
|
||||
|
||||
// validate every function body in user modules
|
||||
if function_section_len != 0 {
|
||||
// tests use invalid code
|
||||
let function_section = module
|
||||
.function_section()
|
||||
.expect("function_section_len != 0; qed");
|
||||
let code_section = module
|
||||
.code_section()
|
||||
.expect("function_section_len != 0; function_section_len == code_section_len; qed");
|
||||
// check every function body
|
||||
for (index, function) in function_section.entries().iter().enumerate() {
|
||||
let function_body = code_section
|
||||
.bodies()
|
||||
.get(index as usize)
|
||||
.ok_or(Error(format!("Missing body for function {}", index)))?;
|
||||
|
||||
let output = func::drive::<V::FuncValidator>(&context, function, function_body)
|
||||
.map_err(|Error(ref msg)| {
|
||||
Error(format!(
|
||||
"Function #{} reading/validation error: {}",
|
||||
index, msg
|
||||
))
|
||||
})?;
|
||||
validation.on_function_validated(index as u32, output);
|
||||
}
|
||||
}
|
||||
|
||||
// validate start section
|
||||
if let Some(start_fn_idx) = module.start_section() {
|
||||
let (params, return_ty) = context.require_function(start_fn_idx)?;
|
||||
if return_ty != BlockType::NoResult || params.len() != 0 {
|
||||
return Err(Error(
|
||||
"start function expected to have type [] -> []".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// validate export section
|
||||
if let Some(export_section) = module.export_section() {
|
||||
let mut export_names = export_section
|
||||
.entries()
|
||||
.iter()
|
||||
.map(ExportEntry::field)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
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() {
|
||||
Internal::Function(function_index) => {
|
||||
context.require_function(function_index)?;
|
||||
}
|
||||
Internal::Global(global_index) => {
|
||||
context.require_global(global_index, Some(false))?;
|
||||
}
|
||||
Internal::Memory(memory_index) => {
|
||||
context.require_memory(memory_index)?;
|
||||
}
|
||||
Internal::Table(table_index) => {
|
||||
context.require_table(table_index)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// validate import section
|
||||
if let Some(import_section) = module.import_section() {
|
||||
for import in import_section.entries() {
|
||||
match *import.external() {
|
||||
External::Function(function_type_index) => {
|
||||
context.require_function_type(function_type_index)?;
|
||||
}
|
||||
External::Global(ref global_type) => {
|
||||
if global_type.is_mutable() {
|
||||
return Err(Error(format!(
|
||||
"trying to import mutable global {}",
|
||||
import.field()
|
||||
)));
|
||||
}
|
||||
}
|
||||
External::Memory(ref memory_type) => {
|
||||
validate_memory_type(memory_type)?;
|
||||
}
|
||||
External::Table(ref table_type) => {
|
||||
validate_table_type(table_type)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// there must be no greater than 1 table in tables index space
|
||||
if context.tables().len() > 1 {
|
||||
return Err(Error(format!(
|
||||
"too many tables in index space: {}",
|
||||
context.tables().len()
|
||||
)));
|
||||
}
|
||||
|
||||
// there must be no greater than 1 linear memory in memory index space
|
||||
if context.memories().len() > 1 {
|
||||
return Err(Error(format!(
|
||||
"too many memory regions in index space: {}",
|
||||
context.memories().len()
|
||||
)));
|
||||
}
|
||||
|
||||
// use data section to initialize linear memory regions
|
||||
if let Some(data_section) = module.data_section() {
|
||||
for data_segment in data_section.entries() {
|
||||
context.require_memory(data_segment.index())?;
|
||||
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 {
|
||||
return Err(Error("segment offset should return I32".into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// use element section to fill tables
|
||||
if let Some(element_section) = module.elements_section() {
|
||||
for element_segment in element_section.entries() {
|
||||
context.require_table(element_segment.index())?;
|
||||
let offset = element_segment
|
||||
.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 {
|
||||
return Err(Error("segment offset should return I32".into()));
|
||||
}
|
||||
|
||||
for function_index in element_segment.members() {
|
||||
context.require_function(*function_index)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(validation.finish())
|
||||
}
|
||||
|
||||
fn validate_limits(limits: &ResizableLimits) -> Result<(), Error> {
|
||||
if let Some(maximum) = limits.maximum() {
|
||||
if limits.initial() > maximum {
|
||||
return Err(Error(format!(
|
||||
"maximum limit {} is less than minimum {}",
|
||||
maximum,
|
||||
limits.initial()
|
||||
)));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_memory_type(memory_type: &MemoryType) -> Result<(), Error> {
|
||||
let initial = memory_type.limits().initial();
|
||||
let maximum: Option<u32> = memory_type.limits().maximum();
|
||||
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> {
|
||||
validate_limits(table_type.limits())
|
||||
}
|
||||
|
||||
fn validate_global_entry(global_entry: &GlobalEntry, globals: &[GlobalType]) -> Result<(), Error> {
|
||||
let init = global_entry.init_expr();
|
||||
let init_expr_ty = expr_const_type(init, globals)?;
|
||||
if init_expr_ty != global_entry.global_type().content_type() {
|
||||
return Err(Error(format!(
|
||||
"Trying to initialize variable of type {:?} with value of type {:?}",
|
||||
global_entry.global_type().content_type(),
|
||||
init_expr_ty
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns type of this constant expression.
|
||||
fn expr_const_type(init_expr: &InitExpr, globals: &[GlobalType]) -> Result<ValueType, Error> {
|
||||
let code = init_expr.code();
|
||||
if code.len() != 2 {
|
||||
return Err(Error(
|
||||
"Init expression should always be with length 2".into(),
|
||||
));
|
||||
}
|
||||
let expr_ty: ValueType = match code[0] {
|
||||
Instruction::I32Const(_) => ValueType::I32,
|
||||
Instruction::I64Const(_) => ValueType::I64,
|
||||
Instruction::F32Const(_) => ValueType::F32,
|
||||
Instruction::F64Const(_) => ValueType::F64,
|
||||
Instruction::GetGlobal(idx) => match globals.get(idx as usize) {
|
||||
Some(target_global) => {
|
||||
if target_global.is_mutable() {
|
||||
return Err(Error(format!("Global {} is mutable", idx)));
|
||||
}
|
||||
target_global.content_type()
|
||||
}
|
||||
None => {
|
||||
return Err(Error(format!(
|
||||
"Global {} doesn't exists or not yet defined",
|
||||
idx
|
||||
)));
|
||||
}
|
||||
},
|
||||
_ => return Err(Error("Non constant opcode in init expr".into())),
|
||||
};
|
||||
if code[1] != Instruction::End {
|
||||
return Err(Error("Expression doesn't ends with `end` opcode".into()));
|
||||
}
|
||||
Ok(expr_ty)
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
use alloc::{string::String, vec::Vec};
|
||||
|
||||
use core::fmt;
|
||||
#[cfg(feature = "std")]
|
||||
use std::error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error(String);
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl error::Error for Error {
|
||||
fn description(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Stack with limit.
|
||||
#[derive(Debug)]
|
||||
pub struct StackWithLimit<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
/// Stack values.
|
||||
values: Vec<T>,
|
||||
/// Stack limit (maximal stack len).
|
||||
limit: usize,
|
||||
}
|
||||
|
||||
impl<T> StackWithLimit<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
pub fn with_limit(limit: usize) -> Self {
|
||||
StackWithLimit {
|
||||
values: Vec::new(),
|
||||
limit: limit,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.values.is_empty()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.values.len()
|
||||
}
|
||||
|
||||
pub fn top(&self) -> Result<&T, Error> {
|
||||
self.values
|
||||
.last()
|
||||
.ok_or_else(|| Error("non-empty stack expected".into()))
|
||||
}
|
||||
|
||||
pub fn top_mut(&mut self) -> Result<&mut T, Error> {
|
||||
self.values
|
||||
.last_mut()
|
||||
.ok_or_else(|| Error("non-empty stack expected".into()))
|
||||
}
|
||||
|
||||
pub fn get(&self, index: usize) -> Result<&T, Error> {
|
||||
if index >= self.values.len() {
|
||||
return Err(Error(format!(
|
||||
"trying to get value at position {} on stack of size {}",
|
||||
index,
|
||||
self.values.len()
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(self
|
||||
.values
|
||||
.get(self.values.len() - 1 - index)
|
||||
.expect("checked couple of lines above"))
|
||||
}
|
||||
|
||||
pub fn push(&mut self, value: T) -> Result<(), Error> {
|
||||
if self.values.len() >= self.limit {
|
||||
return Err(Error(format!("exceeded stack limit {}", self.limit)));
|
||||
}
|
||||
|
||||
self.values.push(value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) -> Result<T, Error> {
|
||||
self.values
|
||||
.pop()
|
||||
.ok_or_else(|| Error("non-empty stack expected".into()))
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, new_size: usize, dummy: T) {
|
||||
debug_assert!(new_size <= self.values.len());
|
||||
self.values.resize(new_size, dummy);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
use crate::Error;
|
||||
use alloc::string::String;
|
||||
use parity_wasm::elements::{Local, ValueType};
|
||||
|
||||
#[cfg(test)]
|
||||
use assert_matches::assert_matches;
|
||||
|
||||
/// Locals are the concatenation of a slice of function parameters
|
||||
/// with function declared local variables.
|
||||
///
|
||||
/// Local variables are given in the form of groups represented by pairs
|
||||
/// of a value_type and a count.
|
||||
#[derive(Debug)]
|
||||
pub struct Locals<'a> {
|
||||
params: &'a [ValueType],
|
||||
local_groups: &'a [Local],
|
||||
count: u32,
|
||||
}
|
||||
|
||||
impl<'a> Locals<'a> {
|
||||
/// Create a new wrapper around declared variables and parameters.
|
||||
pub fn new(params: &'a [ValueType], local_groups: &'a [Local]) -> Result<Locals<'a>, Error> {
|
||||
let mut acc = params.len() as u32;
|
||||
for locals_group in local_groups {
|
||||
acc = acc
|
||||
.checked_add(locals_group.count())
|
||||
.ok_or_else(|| Error(String::from("Locals range not in 32-bit range")))?;
|
||||
}
|
||||
|
||||
Ok(Locals {
|
||||
params,
|
||||
local_groups,
|
||||
count: acc,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns parameter count.
|
||||
pub fn param_count(&self) -> u32 {
|
||||
self.params.len() as u32
|
||||
}
|
||||
|
||||
/// Returns total count of all declared locals and paramaterers.
|
||||
pub fn count(&self) -> u32 {
|
||||
self.count
|
||||
}
|
||||
|
||||
/// Returns the type of a local variable (either a declared local or a param).
|
||||
///
|
||||
/// Returns `Err` in the case of overflow or when idx falls out of range.
|
||||
pub fn type_of_local(&self, idx: u32) -> Result<ValueType, Error> {
|
||||
if let Some(param) = self.params.get(idx as usize) {
|
||||
return Ok(*param);
|
||||
}
|
||||
|
||||
// If an index doesn't point to a param, then we have to look into local declarations.
|
||||
let mut start_idx = self.param_count();
|
||||
for locals_group in self.local_groups {
|
||||
let end_idx = start_idx
|
||||
.checked_add(locals_group.count())
|
||||
.ok_or_else(|| Error(String::from("Locals range not in 32-bit range")))?;
|
||||
|
||||
if idx >= start_idx && idx < end_idx {
|
||||
return Ok(locals_group.value_type());
|
||||
}
|
||||
|
||||
start_idx = end_idx;
|
||||
}
|
||||
|
||||
// We didn't find anything, that's an error.
|
||||
// At this moment `start_idx` should hold the count of all locals
|
||||
// (since it's either set to the `end_idx` or equal to `params.len()`)
|
||||
let total_count = start_idx;
|
||||
|
||||
Err(Error(format!(
|
||||
"Trying to access local with index {} when there are only {} locals",
|
||||
idx, total_count
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn locals_it_works() {
|
||||
let params = vec![ValueType::I32, ValueType::I64];
|
||||
let local_groups = vec![Local::new(2, ValueType::F32), Local::new(2, ValueType::F64)];
|
||||
let locals = Locals::new(¶ms, &local_groups).unwrap();
|
||||
|
||||
assert_matches!(locals.type_of_local(0), Ok(ValueType::I32));
|
||||
assert_matches!(locals.type_of_local(1), Ok(ValueType::I64));
|
||||
assert_matches!(locals.type_of_local(2), Ok(ValueType::F32));
|
||||
assert_matches!(locals.type_of_local(3), Ok(ValueType::F32));
|
||||
assert_matches!(locals.type_of_local(4), Ok(ValueType::F64));
|
||||
assert_matches!(locals.type_of_local(5), Ok(ValueType::F64));
|
||||
assert_matches!(locals.type_of_local(6), Err(_));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn locals_no_declared_locals() {
|
||||
let params = vec![ValueType::I32];
|
||||
let locals = Locals::new(¶ms, &[]).unwrap();
|
||||
|
||||
assert_matches!(locals.type_of_local(0), Ok(ValueType::I32));
|
||||
assert_matches!(locals.type_of_local(1), Err(_));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn locals_no_params() {
|
||||
let local_groups = vec![Local::new(2, ValueType::I32), Local::new(3, ValueType::I64)];
|
||||
let locals = Locals::new(&[], &local_groups).unwrap();
|
||||
|
||||
assert_matches!(locals.type_of_local(0), Ok(ValueType::I32));
|
||||
assert_matches!(locals.type_of_local(1), Ok(ValueType::I32));
|
||||
assert_matches!(locals.type_of_local(2), Ok(ValueType::I64));
|
||||
assert_matches!(locals.type_of_local(3), Ok(ValueType::I64));
|
||||
assert_matches!(locals.type_of_local(4), Ok(ValueType::I64));
|
||||
assert_matches!(locals.type_of_local(5), Err(_));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn locals_u32_overflow() {
|
||||
let local_groups = vec![
|
||||
Local::new(u32::max_value(), ValueType::I32),
|
||||
Local::new(1, ValueType::I64),
|
||||
];
|
||||
assert_matches!(Locals::new(&[], &local_groups), Err(_));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue