Merge remote-tracking branch 'origin/flat-stack' into fuzz
This commit is contained in:
commit
978b9ff2ea
15
.travis.yml
15
.travis.yml
|
@ -3,6 +3,9 @@ sudo: required
|
||||||
language:
|
language:
|
||||||
- rust
|
- rust
|
||||||
- cpp
|
- cpp
|
||||||
|
rust:
|
||||||
|
- nightly
|
||||||
|
- stable
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
sources:
|
sources:
|
||||||
|
@ -12,18 +15,16 @@ addons:
|
||||||
- g++-6
|
- g++-6
|
||||||
- cmake
|
- cmake
|
||||||
env:
|
env:
|
||||||
- NIGHTLY_TOOLCHAIN=nightly-2018-02-05
|
- CC=/usr/bin/gcc-6 CXX=/usr/bin/g++-6
|
||||||
|
|
||||||
install:
|
install:
|
||||||
# Install `cargo-deadlinks` unless it is currently installed.
|
# Install `cargo-deadlinks` unless it is currently installed.
|
||||||
- command -v cargo-deadlinks &> /dev/null || cargo install cargo-deadlinks
|
- command -v cargo-deadlinks &> /dev/null || cargo install cargo-deadlinks
|
||||||
# Install nightly toolchain.
|
- if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then rustup target add wasm32-unknown-unknown; fi
|
||||||
- rustup toolchain install $NIGHTLY_TOOLCHAIN
|
|
||||||
script:
|
script:
|
||||||
- export CC=/usr/bin/gcc-6
|
# Make sure nightly targets are not broken.
|
||||||
- export CXX=/usr/bin/g++-6
|
- if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo check --tests --manifest-path=fuzz/Cargo.toml; fi
|
||||||
# Make sure fuzz targets are not broken.
|
- if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo check --benches --manifest-path=benches/Cargo.toml; fi
|
||||||
- rustup run $NIGHTLY_TOOLCHAIN cargo check --tests --manifest-path=fuzz/Cargo.toml
|
|
||||||
- ./test.sh
|
- ./test.sh
|
||||||
- ./doc.sh
|
- ./doc.sh
|
||||||
after_success: |
|
after_success: |
|
||||||
|
|
14
Cargo.toml
14
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "wasmi"
|
name = "wasmi"
|
||||||
version = "0.1.2"
|
version = "0.2.0"
|
||||||
authors = ["Nikolay Volf <nikvolf@gmail.com>", "Svyatoslav Nikolsky <svyatonik@yandex.ru>", "Sergey Pepyakin <s.pepyakin@gmail.com>"]
|
authors = ["Nikolay Volf <nikvolf@gmail.com>", "Svyatoslav Nikolsky <svyatonik@yandex.ru>", "Sergey Pepyakin <s.pepyakin@gmail.com>"]
|
||||||
license = "MIT/Apache-2.0"
|
license = "MIT/Apache-2.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -8,19 +8,15 @@ repository = "https://github.com/paritytech/wasmi"
|
||||||
documentation = "https://paritytech.github.io/wasmi/"
|
documentation = "https://paritytech.github.io/wasmi/"
|
||||||
description = "WebAssembly interpreter"
|
description = "WebAssembly interpreter"
|
||||||
keywords = ["wasm", "webassembly", "bytecode", "interpreter"]
|
keywords = ["wasm", "webassembly", "bytecode", "interpreter"]
|
||||||
exclude = [ "/res/*", "/tests/*", "/fuzz/*" ]
|
exclude = [ "/res/*", "/tests/*", "/fuzz/*", "/benches/*" ]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# parity-wasm = "0.27"
|
# parity-wasm = "0.27"
|
||||||
parity-wasm = { git = "https://github.com/paritytech/parity-wasm.git", rev = "0a61083238d8d9d8d9f6451a5d0da17674b11c21" }
|
parity-wasm = { git = "https://github.com/paritytech/parity-wasm.git", rev = "0a61083238d8d9d8d9f6451a5d0da17674b11c21" }
|
||||||
byteorder = "1.0"
|
byteorder = "1.0"
|
||||||
memory_units = "0.3.0"
|
memory_units = "0.3.0"
|
||||||
|
nan-preserving-float = "0.1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
wabt = "~0.2.2"
|
assert_matches = "1.1"
|
||||||
|
wabt = "0.3"
|
||||||
[features]
|
|
||||||
# 32-bit platforms are not supported and not tested. Use this flag if you really want to use
|
|
||||||
# wasmi on these platforms.
|
|
||||||
# See https://github.com/pepyakin/wasmi/issues/43
|
|
||||||
opt-in-32bit = []
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
/target
|
||||||
|
*.trace
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "benches"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Sergey Pepyakin <s.pepyakin@gmail.com>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
wasmi = { path = ".." }
|
||||||
|
assert_matches = "1.2"
|
||||||
|
|
||||||
|
[profile.bench]
|
||||||
|
debug = true
|
|
@ -0,0 +1,32 @@
|
||||||
|
use std::env;
|
||||||
|
use std::process;
|
||||||
|
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("cargo:rerun-if-changed=./wasm-kernel/");
|
||||||
|
|
||||||
|
// The CARGO environment variable provides a path to the executable that
|
||||||
|
// runs this build process.
|
||||||
|
let cargo_bin = env::var("CARGO").expect("CARGO env variable should be defined");
|
||||||
|
|
||||||
|
// Build a release version of wasm-kernel. The code in the output wasm binary
|
||||||
|
// will be used in benchmarks.
|
||||||
|
let output = process::Command::new(cargo_bin)
|
||||||
|
.arg("build")
|
||||||
|
.arg("--target=wasm32-unknown-unknown")
|
||||||
|
.arg("--release")
|
||||||
|
.arg("--manifest-path=./wasm-kernel/Cargo.toml")
|
||||||
|
.arg("--verbose")
|
||||||
|
.output()
|
||||||
|
.expect("failed to execute `cargo`");
|
||||||
|
|
||||||
|
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),
|
||||||
|
);
|
||||||
|
panic!("{}", msg);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
#![feature(test)]
|
||||||
|
|
||||||
|
extern crate test;
|
||||||
|
extern crate wasmi;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate assert_matches;
|
||||||
|
|
||||||
|
use std::error;
|
||||||
|
use std::fs::File;
|
||||||
|
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>> {
|
||||||
|
use std::io::prelude::*;
|
||||||
|
let mut file = File::open(filename)?;
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
file.read_to_end(&mut buf)?;
|
||||||
|
Ok(Module::from_buffer(buf)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn bench_tiny_keccak(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();
|
||||||
|
|
||||||
|
let test_data_ptr = assert_matches!(
|
||||||
|
instance.invoke_export("prepare_tiny_keccak", &[], &mut NopExternals),
|
||||||
|
Ok(Some(v @ RuntimeValue::I32(_))) => v
|
||||||
|
);
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
instance
|
||||||
|
.invoke_export("bench_tiny_keccak", &[test_data_ptr], &mut NopExternals)
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
/target
|
|
@ -0,0 +1,16 @@
|
||||||
|
[package]
|
||||||
|
name = "wasm-kernel"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Sergey Pepyakin <s.pepyakin@gmail.com>"]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tiny-keccak = "1.4.2"
|
||||||
|
rlibc = "1.0"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
panic = "abort"
|
||||||
|
lto = true
|
||||||
|
opt-level = "z"
|
|
@ -0,0 +1,50 @@
|
||||||
|
#![no_std]
|
||||||
|
#![feature(lang_items)]
|
||||||
|
#![feature(core_intrinsics)]
|
||||||
|
#![feature(panic_implementation)]
|
||||||
|
|
||||||
|
extern crate rlibc;
|
||||||
|
extern crate tiny_keccak;
|
||||||
|
|
||||||
|
use tiny_keccak::Keccak;
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
#[panic_implementation]
|
||||||
|
pub fn panic_fmt(_info: &::core::panic::PanicInfo) -> ! {
|
||||||
|
use core::intrinsics;
|
||||||
|
unsafe {
|
||||||
|
intrinsics::abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TinyKeccakTestData {
|
||||||
|
data: &'static [u8],
|
||||||
|
result: &'static mut [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn prepare_tiny_keccak() -> *const TinyKeccakTestData {
|
||||||
|
static DATA: [u8; 4096] = [254u8; 4096];
|
||||||
|
static mut RESULT: [u8; 32] = [0u8; 32];
|
||||||
|
|
||||||
|
static mut TEST_DATA: Option<TinyKeccakTestData> = None;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
if let None = TEST_DATA {
|
||||||
|
TEST_DATA = Some(TinyKeccakTestData {
|
||||||
|
data: &DATA,
|
||||||
|
result: &mut RESULT,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
TEST_DATA.as_ref().unwrap() as *const TinyKeccakTestData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn bench_tiny_keccak(test_data: *const TinyKeccakTestData) {
|
||||||
|
unsafe {
|
||||||
|
let mut keccak = Keccak::new_keccak256();
|
||||||
|
keccak.update((*test_data).data);
|
||||||
|
keccak.finalize((*test_data).result);
|
||||||
|
}
|
||||||
|
}
|
6
doc.sh
6
doc.sh
|
@ -4,11 +4,7 @@ set -eux
|
||||||
|
|
||||||
cd $(dirname $0)
|
cd $(dirname $0)
|
||||||
|
|
||||||
if [ -s NIGHTLY_TOOLCHAIN ]; then
|
cargo doc
|
||||||
rustup run $NIGHTLY_TOOLCHAIN cargo doc
|
|
||||||
else
|
|
||||||
cargo doc
|
|
||||||
fi;
|
|
||||||
|
|
||||||
# cargo-deadlinks will check any links in docs generated by `cargo doc`.
|
# cargo-deadlinks will check any links in docs generated by `cargo doc`.
|
||||||
# This is useful as rustdoc uses raw links which are error prone.
|
# This is useful as rustdoc uses raw links which are error prone.
|
||||||
|
|
|
@ -64,8 +64,8 @@ fn main() {
|
||||||
function_type.params().iter().enumerate().map(|(i, value)| match value {
|
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::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::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]))),
|
&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]))),
|
&ValueType::F64 => RuntimeValue::F64(program_args[i].parse::<f64>().expect(&format!("Can't parse arg #{} as f64", program_args[i])).into()),
|
||||||
}).collect::<Vec<RuntimeValue>>()
|
}).collect::<Vec<RuntimeValue>>()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use parity_wasm::elements::BlockType;
|
|
||||||
|
|
||||||
pub mod stack;
|
pub mod stack;
|
||||||
|
|
||||||
|
@ -7,38 +6,4 @@ pub const DEFAULT_MEMORY_INDEX: u32 = 0;
|
||||||
/// Index of default table.
|
/// Index of default table.
|
||||||
pub const DEFAULT_TABLE_INDEX: u32 = 0;
|
pub const DEFAULT_TABLE_INDEX: u32 = 0;
|
||||||
|
|
||||||
/// Control stack frame.
|
// TODO: Move BlockFrame under validation.
|
||||||
#[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,5 +1,4 @@
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
use std::error;
|
use std::error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
@ -22,7 +21,7 @@ impl error::Error for Error {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct StackWithLimit<T> where T: Clone {
|
pub struct StackWithLimit<T> where T: Clone {
|
||||||
/// Stack values.
|
/// Stack values.
|
||||||
values: VecDeque<T>,
|
values: Vec<T>,
|
||||||
/// Stack limit (maximal stack len).
|
/// Stack limit (maximal stack len).
|
||||||
limit: usize,
|
limit: usize,
|
||||||
}
|
}
|
||||||
|
@ -30,7 +29,7 @@ pub struct StackWithLimit<T> where T: Clone {
|
||||||
impl<T> StackWithLimit<T> where T: Clone {
|
impl<T> StackWithLimit<T> where T: Clone {
|
||||||
pub fn with_limit(limit: usize) -> Self {
|
pub fn with_limit(limit: usize) -> Self {
|
||||||
StackWithLimit {
|
StackWithLimit {
|
||||||
values: VecDeque::new(),
|
values: Vec::new(),
|
||||||
limit: limit
|
limit: limit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,19 +42,17 @@ impl<T> StackWithLimit<T> where T: Clone {
|
||||||
self.values.len()
|
self.values.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn limit(&self) -> usize {
|
|
||||||
self.limit
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn top(&self) -> Result<&T, Error> {
|
pub fn top(&self) -> Result<&T, Error> {
|
||||||
|
let len = self.values.len();
|
||||||
self.values
|
self.values
|
||||||
.back()
|
.get(len - 1)
|
||||||
.ok_or_else(|| Error("non-empty stack expected".into()))
|
.ok_or_else(|| Error("non-empty stack expected".into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn top_mut(&mut self) -> Result<&mut T, Error> {
|
pub fn top_mut(&mut self) -> Result<&mut T, Error> {
|
||||||
|
let len = self.values.len();
|
||||||
self.values
|
self.values
|
||||||
.back_mut()
|
.get_mut(len - 1)
|
||||||
.ok_or_else(|| Error("non-empty stack expected".into()))
|
.ok_or_else(|| Error("non-empty stack expected".into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,13 +69,13 @@ impl<T> StackWithLimit<T> where T: Clone {
|
||||||
return Err(Error(format!("exceeded stack limit {}", self.limit)));
|
return Err(Error(format!("exceeded stack limit {}", self.limit)));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.values.push_back(value);
|
self.values.push(value);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pop(&mut self) -> Result<T, Error> {
|
pub fn pop(&mut self) -> Result<T, Error> {
|
||||||
self.values
|
self.values
|
||||||
.pop_back()
|
.pop()
|
||||||
.ok_or_else(|| Error("non-empty stack expected".into()))
|
.ok_or_else(|| Error("non-empty stack expected".into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use std::rc::{Rc, Weak};
|
use std::rc::{Rc, Weak};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::collections::HashMap;
|
use parity_wasm::elements::Local;
|
||||||
use parity_wasm::elements::{Local, Opcodes};
|
|
||||||
use {Trap, TrapKind, Signature};
|
use {Trap, TrapKind, Signature};
|
||||||
use host::Externals;
|
use host::Externals;
|
||||||
use runner::{check_function_args, Interpreter};
|
use runner::{check_function_args, Interpreter};
|
||||||
use value::RuntimeValue;
|
use value::RuntimeValue;
|
||||||
use module::ModuleInstance;
|
use module::ModuleInstance;
|
||||||
|
use isa;
|
||||||
|
|
||||||
/// Reference to a function (See [`FuncInstance`] for details).
|
/// Reference to a function (See [`FuncInstance`] for details).
|
||||||
///
|
///
|
||||||
|
@ -158,6 +158,5 @@ impl FuncInstance {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct FuncBody {
|
pub struct FuncBody {
|
||||||
pub locals: Vec<Local>,
|
pub locals: Vec<Local>,
|
||||||
pub opcodes: Opcodes,
|
pub code: isa::Instructions,
|
||||||
pub labels: HashMap<usize, usize>,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,255 @@
|
||||||
|
//! An instruction set used by wasmi.
|
||||||
|
//!
|
||||||
|
//! The instruction set is mostly derived from Wasm. However,
|
||||||
|
//! there is a substantial difference.
|
||||||
|
//!
|
||||||
|
//! # Structured Stack Machine vs Traditional 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 traditional 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.
|
||||||
|
//!
|
||||||
|
//! # Differences from Wasm
|
||||||
|
//!
|
||||||
|
//! - There is no `nop` instruction.
|
||||||
|
//! - All control flow strucutres 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`.
|
||||||
|
//!
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Target {
|
||||||
|
pub dst_pc: u32,
|
||||||
|
pub drop: u32,
|
||||||
|
pub keep: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)] // TODO: Remove
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum Instruction {
|
||||||
|
/// 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),
|
||||||
|
|
||||||
|
/// Last one is the default.
|
||||||
|
///
|
||||||
|
/// Can be less than zero.
|
||||||
|
BrTable(Box<[Target]>),
|
||||||
|
|
||||||
|
Unreachable,
|
||||||
|
Return {
|
||||||
|
drop: u32,
|
||||||
|
keep: u8,
|
||||||
|
},
|
||||||
|
|
||||||
|
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 {
|
||||||
|
pub code: Vec<Instruction>,
|
||||||
|
}
|
85
src/lib.rs
85
src/lib.rs
|
@ -98,20 +98,17 @@
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
extern crate wabt;
|
extern crate wabt;
|
||||||
|
#[cfg(test)]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate assert_matches;
|
||||||
|
|
||||||
extern crate parity_wasm;
|
extern crate parity_wasm;
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
extern crate memory_units as memory_units_crate;
|
extern crate memory_units as memory_units_crate;
|
||||||
|
extern crate nan_preserving_float;
|
||||||
#[cfg(all(not(feature = "opt-in-32bit"), target_pointer_width = "32"))]
|
|
||||||
compile_error! {
|
|
||||||
"32-bit targets are not supported at the moment.
|
|
||||||
You can use 'opt-in-32bit' feature.
|
|
||||||
See https://github.com/pepyakin/wasmi/issues/43"
|
|
||||||
}
|
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::error;
|
use std::error;
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
/// Error type which can thrown by wasm code or by host environment.
|
/// Error type which can thrown by wasm code or by host environment.
|
||||||
///
|
///
|
||||||
|
@ -358,6 +355,7 @@ mod imports;
|
||||||
mod global;
|
mod global;
|
||||||
mod func;
|
mod func;
|
||||||
mod types;
|
mod types;
|
||||||
|
mod isa;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
@ -380,12 +378,11 @@ pub mod memory_units {
|
||||||
|
|
||||||
/// Deserialized module prepared for instantiation.
|
/// Deserialized module prepared for instantiation.
|
||||||
pub struct Module {
|
pub struct Module {
|
||||||
labels: HashMap<usize, HashMap<usize, usize>>,
|
code_map: Vec<isa::Instructions>,
|
||||||
module: parity_wasm::elements::Module,
|
module: parity_wasm::elements::Module,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Module {
|
impl Module {
|
||||||
|
|
||||||
/// Create `Module` from `parity_wasm::elements::Module`.
|
/// Create `Module` from `parity_wasm::elements::Module`.
|
||||||
///
|
///
|
||||||
/// This function will load, validate and prepare a `parity_wasm`'s `Module`.
|
/// This function will load, validate and prepare a `parity_wasm`'s `Module`.
|
||||||
|
@ -421,16 +418,76 @@ impl Module {
|
||||||
pub fn from_parity_wasm_module(module: parity_wasm::elements::Module) -> Result<Module, Error> {
|
pub fn from_parity_wasm_module(module: parity_wasm::elements::Module) -> Result<Module, Error> {
|
||||||
use validation::{validate_module, ValidatedModule};
|
use validation::{validate_module, ValidatedModule};
|
||||||
let ValidatedModule {
|
let ValidatedModule {
|
||||||
labels,
|
code_map,
|
||||||
module,
|
module,
|
||||||
} = validate_module(module)?;
|
} = validate_module(module)?;
|
||||||
|
|
||||||
Ok(Module {
|
Ok(Module {
|
||||||
labels,
|
code_map,
|
||||||
module,
|
module,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fail if the module contains any floating-point operations
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns `Err` if provided `Module` is not valid.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate wasmi;
|
||||||
|
/// # extern crate wabt;
|
||||||
|
///
|
||||||
|
/// let wasm_binary: Vec<u8> =
|
||||||
|
/// wabt::wat2wasm(
|
||||||
|
/// r#"
|
||||||
|
/// (module
|
||||||
|
/// (func $add (param $lhs i32) (param $rhs i32) (result i32)
|
||||||
|
/// get_local $lhs
|
||||||
|
/// get_local $rhs
|
||||||
|
/// i32.add))
|
||||||
|
/// "#,
|
||||||
|
/// )
|
||||||
|
/// .expect("failed to parse wat");
|
||||||
|
///
|
||||||
|
/// // Load wasm binary and prepare it for instantiation.
|
||||||
|
/// let module = wasmi::Module::from_buffer(&wasm_binary).expect("Parsing failed");
|
||||||
|
/// assert!(module.deny_floating_point().is_ok());
|
||||||
|
///
|
||||||
|
/// let wasm_binary: Vec<u8> =
|
||||||
|
/// wabt::wat2wasm(
|
||||||
|
/// r#"
|
||||||
|
/// (module
|
||||||
|
/// (func $add (param $lhs f32) (param $rhs f32) (result f32)
|
||||||
|
/// get_local $lhs
|
||||||
|
/// get_local $rhs
|
||||||
|
/// f32.add))
|
||||||
|
/// "#,
|
||||||
|
/// )
|
||||||
|
/// .expect("failed to parse wat");
|
||||||
|
///
|
||||||
|
/// let module = wasmi::Module::from_buffer(&wasm_binary).expect("Parsing failed");
|
||||||
|
/// assert!(module.deny_floating_point().is_err());
|
||||||
|
///
|
||||||
|
/// let wasm_binary: Vec<u8> =
|
||||||
|
/// wabt::wat2wasm(
|
||||||
|
/// r#"
|
||||||
|
/// (module
|
||||||
|
/// (func $add (param $lhs f32) (param $rhs f32) (result f32)
|
||||||
|
/// get_local $lhs))
|
||||||
|
/// "#,
|
||||||
|
/// )
|
||||||
|
/// .expect("failed to parse wat");
|
||||||
|
///
|
||||||
|
/// let module = wasmi::Module::from_buffer(&wasm_binary).expect("Parsing failed");
|
||||||
|
/// 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)
|
||||||
|
}
|
||||||
|
|
||||||
/// Create `Module` from a given buffer.
|
/// Create `Module` from a given buffer.
|
||||||
///
|
///
|
||||||
/// This function will deserialize wasm module from a given module,
|
/// This function will deserialize wasm module from a given module,
|
||||||
|
@ -467,7 +524,7 @@ impl Module {
|
||||||
&self.module
|
&self.module
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn labels(&self) -> &HashMap<usize, HashMap<usize, usize>> {
|
pub(crate) fn code(&self) -> &Vec<isa::Instructions> {
|
||||||
&self.labels
|
&self.code_map
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ impl ::std::ops::Deref for MemoryRef {
|
||||||
///
|
///
|
||||||
/// [`LINEAR_MEMORY_PAGE_SIZE`]: constant.LINEAR_MEMORY_PAGE_SIZE.html
|
/// [`LINEAR_MEMORY_PAGE_SIZE`]: constant.LINEAR_MEMORY_PAGE_SIZE.html
|
||||||
pub struct MemoryInstance {
|
pub struct MemoryInstance {
|
||||||
/// Memofy limits.
|
/// Memory limits.
|
||||||
limits: ResizableLimits,
|
limits: ResizableLimits,
|
||||||
/// Linear memory buffer.
|
/// Linear memory buffer.
|
||||||
buffer: RefCell<Vec<u8>>,
|
buffer: RefCell<Vec<u8>>,
|
||||||
|
@ -315,7 +315,7 @@ impl MemoryInstance {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fill memory region with a specified value.
|
/// Fill the memory region with the specified value.
|
||||||
///
|
///
|
||||||
/// Semantically equivalent to `memset`.
|
/// Semantically equivalent to `memset`.
|
||||||
///
|
///
|
||||||
|
@ -330,7 +330,7 @@ impl MemoryInstance {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fill specified memory region with zeroes.
|
/// Fill the specified memory region with zeroes.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
|
@ -338,6 +338,35 @@ impl MemoryInstance {
|
||||||
pub fn zero(&self, offset: usize, len: usize) -> Result<(), Error> {
|
pub fn zero(&self, offset: usize, len: usize) -> Result<(), Error> {
|
||||||
self.clear(offset, 0, len)
|
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> {
|
pub fn validate_memory(initial: Pages, maximum: Option<Pages>) -> Result<(), String> {
|
||||||
|
@ -369,6 +398,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn alloc() {
|
fn alloc() {
|
||||||
|
#[cfg(target_pointer_width = "64")]
|
||||||
let fixtures = &[
|
let fixtures = &[
|
||||||
(0, None, true),
|
(0, None, true),
|
||||||
(0, Some(0), true),
|
(0, Some(0), true),
|
||||||
|
@ -381,6 +411,17 @@ mod tests {
|
||||||
(65536, Some(0), false),
|
(65536, Some(0), false),
|
||||||
(65536, None, true),
|
(65536, None, true),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
#[cfg(target_pointer_width = "32")]
|
||||||
|
let fixtures = &[
|
||||||
|
(0, None, true),
|
||||||
|
(0, Some(0), true),
|
||||||
|
(1, None, true),
|
||||||
|
(1, Some(1), true),
|
||||||
|
(0, Some(1), true),
|
||||||
|
(1, Some(0), false),
|
||||||
|
];
|
||||||
|
|
||||||
for (index, &(initial, maybe_max, expected_ok)) in fixtures.iter().enumerate() {
|
for (index, &(initial, maybe_max, expected_ok)) in fixtures.iter().enumerate() {
|
||||||
let initial: Pages = Pages(initial);
|
let initial: Pages = Pages(initial);
|
||||||
let maximum: Option<Pages> = maybe_max.map(|m| Pages(m));
|
let maximum: Option<Pages> = maybe_max.map(|m| Pages(m));
|
||||||
|
@ -472,4 +513,27 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(data, [17, 129]);
|
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]);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -291,7 +291,7 @@ impl ModuleInstance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
||||||
&[],
|
&[],
|
||||||
|
@ -308,13 +308,12 @@ impl ModuleInstance {
|
||||||
let signature = instance.signature_by_index(ty.type_ref()).expect(
|
let signature = instance.signature_by_index(ty.type_ref()).expect(
|
||||||
"Due to validation type should exists",
|
"Due to validation type should exists",
|
||||||
);
|
);
|
||||||
let labels = labels.get(&index).expect(
|
let code = code.get(index).expect(
|
||||||
"At func validation time labels are collected; Collected labels are added by index; qed",
|
"At func validation time labels are collected; Collected labels are added by index; qed",
|
||||||
).clone();
|
).clone();
|
||||||
let func_body = FuncBody {
|
let func_body = FuncBody {
|
||||||
locals: body.locals().to_vec(),
|
locals: body.locals().to_vec(),
|
||||||
opcodes: body.code().clone(),
|
code: code,
|
||||||
labels: labels,
|
|
||||||
};
|
};
|
||||||
let func_instance =
|
let func_instance =
|
||||||
FuncInstance::alloc_internal(Rc::downgrade(&instance.0), signature, func_body);
|
FuncInstance::alloc_internal(Rc::downgrade(&instance.0), signature, func_body);
|
||||||
|
@ -420,7 +419,7 @@ impl ModuleInstance {
|
||||||
|
|
||||||
// This check is not only for bailing out early, but also to check the case when
|
// This check is not only for bailing out early, but also to check the case when
|
||||||
// segment consist of 0 members.
|
// segment consist of 0 members.
|
||||||
if offset_val as usize + element_segment.members().len() > table_inst.current_size() as usize {
|
if offset_val as u64 + element_segment.members().len() as u64 > table_inst.current_size() as u64 {
|
||||||
return Err(
|
return Err(
|
||||||
Error::Instantiation("elements segment does not fit".to_string())
|
Error::Instantiation("elements segment does not fit".to_string())
|
||||||
);
|
);
|
||||||
|
|
1228
src/runner.rs
1228
src/runner.rs
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
use std::error;
|
use std::error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::HashSet;
|
||||||
use parity_wasm::elements::{
|
use parity_wasm::elements::{
|
||||||
BlockType, External, GlobalEntry, GlobalType, Internal, MemoryType, Module, Opcode,
|
BlockType, External, GlobalEntry, GlobalType, Internal, MemoryType, Module, Opcode,
|
||||||
ResizableLimits, TableType, ValueType, InitExpr, Type
|
ResizableLimits, TableType, ValueType, InitExpr, Type
|
||||||
|
@ -9,9 +9,11 @@ use common::stack;
|
||||||
use self::context::ModuleContextBuilder;
|
use self::context::ModuleContextBuilder;
|
||||||
use self::func::Validator;
|
use self::func::Validator;
|
||||||
use memory_units::Pages;
|
use memory_units::Pages;
|
||||||
|
use isa;
|
||||||
|
|
||||||
mod context;
|
mod context;
|
||||||
mod func;
|
mod func;
|
||||||
|
mod util;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
@ -39,7 +41,7 @@ impl From<stack::Error> for Error {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ValidatedModule {
|
pub struct ValidatedModule {
|
||||||
pub labels: HashMap<usize, HashMap<usize, usize>>,
|
pub code_map: Vec<isa::Instructions>,
|
||||||
pub module: Module,
|
pub module: Module,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,10 +52,123 @@ impl ::std::ops::Deref for ValidatedModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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> {
|
pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
|
||||||
let mut context_builder = ModuleContextBuilder::new();
|
let mut context_builder = ModuleContextBuilder::new();
|
||||||
let mut imported_globals = Vec::new();
|
let mut imported_globals = Vec::new();
|
||||||
let mut labels = HashMap::new();
|
let mut code_map = Vec::new();
|
||||||
|
|
||||||
// Copy types from module as is.
|
// Copy types from module as is.
|
||||||
context_builder.set_types(
|
context_builder.set_types(
|
||||||
|
@ -143,12 +258,12 @@ pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
|
||||||
index
|
index
|
||||||
)),
|
)),
|
||||||
)?;
|
)?;
|
||||||
let func_labels = Validator::validate_function(&context, function, function_body)
|
let code = Validator::validate_function(&context, function, function_body)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
let Error(ref msg) = e;
|
let Error(ref msg) = e;
|
||||||
Error(format!("Function #{} validation error: {}", index, msg))
|
Error(format!("Function #{} validation error: {}", index, msg))
|
||||||
})?;
|
})?;
|
||||||
labels.insert(index, func_labels);
|
code_map.push(code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,7 +375,7 @@ pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
|
||||||
|
|
||||||
Ok(ValidatedModule {
|
Ok(ValidatedModule {
|
||||||
module,
|
module,
|
||||||
labels
|
code_map,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
use super::validate_module;
|
use super::{validate_module, ValidatedModule};
|
||||||
use parity_wasm::builder::module;
|
use parity_wasm::builder::module;
|
||||||
use parity_wasm::elements::{
|
use parity_wasm::elements::{
|
||||||
External, GlobalEntry, GlobalType, ImportEntry, InitExpr, MemoryType,
|
External, GlobalEntry, GlobalType, ImportEntry, InitExpr, MemoryType,
|
||||||
Opcode, Opcodes, TableType, ValueType, BlockType
|
Opcode, Opcodes, TableType, ValueType, BlockType, deserialize_buffer,
|
||||||
|
Module,
|
||||||
};
|
};
|
||||||
|
use isa;
|
||||||
|
use wabt;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_is_valid() {
|
fn empty_is_valid() {
|
||||||
|
@ -299,3 +302,584 @@ fn if_else_with_return_type_validation() {
|
||||||
.build();
|
.build();
|
||||||
validate_module(m).unwrap();
|
validate_module(m).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate(wat: &str) -> ValidatedModule {
|
||||||
|
let wasm = wabt::wat2wasm(wat).unwrap();
|
||||||
|
let module = deserialize_buffer::<Module>(&wasm).unwrap();
|
||||||
|
let validated_module = validate_module(module).unwrap();
|
||||||
|
validated_module
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile(wat: &str) -> Vec<isa::Instruction> {
|
||||||
|
let validated_module = validate(wat);
|
||||||
|
let code = &validated_module.code_map[0];
|
||||||
|
code.code.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn implicit_return_no_value() {
|
||||||
|
let code = compile(r#"
|
||||||
|
(module
|
||||||
|
(func (export "call")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn implicit_return_with_value() {
|
||||||
|
let code = compile(r#"
|
||||||
|
(module
|
||||||
|
(func (export "call") (result i32)
|
||||||
|
i32.const 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::I32Const(0),
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 0,
|
||||||
|
keep: 1,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn implicit_return_param() {
|
||||||
|
let code = compile(r#"
|
||||||
|
(module
|
||||||
|
(func (export "call") (param i32)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 1,
|
||||||
|
keep: 0,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_local() {
|
||||||
|
let code = compile(r#"
|
||||||
|
(module
|
||||||
|
(func (export "call") (param i32) (result i32)
|
||||||
|
get_local 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::GetLocal(1),
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 1,
|
||||||
|
keep: 1,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn explicit_return() {
|
||||||
|
let code = compile(r#"
|
||||||
|
(module
|
||||||
|
(func (export "call") (param i32) (result i32)
|
||||||
|
get_local 0
|
||||||
|
return
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::GetLocal(1),
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 1,
|
||||||
|
keep: 1,
|
||||||
|
},
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 1,
|
||||||
|
keep: 1,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_params() {
|
||||||
|
let code = compile(r#"
|
||||||
|
(module
|
||||||
|
(func (export "call") (param i32) (param i32) (result i32)
|
||||||
|
get_local 0
|
||||||
|
get_local 1
|
||||||
|
i32.add
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
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 {
|
||||||
|
drop: 2,
|
||||||
|
keep: 1,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn drop_locals() {
|
||||||
|
let code = compile(r#"
|
||||||
|
(module
|
||||||
|
(func (export "call") (param i32)
|
||||||
|
(local i32)
|
||||||
|
get_local 0
|
||||||
|
set_local 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::GetLocal(2),
|
||||||
|
isa::Instruction::SetLocal(1),
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 2,
|
||||||
|
keep: 0,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn if_without_else() {
|
||||||
|
let code = compile(r#"
|
||||||
|
(module
|
||||||
|
(func (export "call") (param i32) (result i32)
|
||||||
|
i32.const 1
|
||||||
|
if
|
||||||
|
i32.const 2
|
||||||
|
return
|
||||||
|
end
|
||||||
|
i32.const 3
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::I32Const(1),
|
||||||
|
isa::Instruction::BrIfEqz(isa::Target {
|
||||||
|
dst_pc: 4,
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
}),
|
||||||
|
isa::Instruction::I32Const(2),
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 1, // 1 param
|
||||||
|
keep: 1, // 1 result
|
||||||
|
},
|
||||||
|
isa::Instruction::I32Const(3),
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 1,
|
||||||
|
keep: 1,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn if_else() {
|
||||||
|
let code = compile(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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::I32Const(1),
|
||||||
|
isa::Instruction::BrIfEqz(isa::Target {
|
||||||
|
dst_pc: 5,
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
}),
|
||||||
|
isa::Instruction::I32Const(2),
|
||||||
|
isa::Instruction::SetLocal(1),
|
||||||
|
isa::Instruction::Br(isa::Target {
|
||||||
|
dst_pc: 7,
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
}),
|
||||||
|
isa::Instruction::I32Const(3),
|
||||||
|
isa::Instruction::SetLocal(1),
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 1,
|
||||||
|
keep: 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn if_else_returns_result() {
|
||||||
|
let code = compile(r#"
|
||||||
|
(module
|
||||||
|
(func (export "call")
|
||||||
|
i32.const 1
|
||||||
|
if (result i32)
|
||||||
|
i32.const 2
|
||||||
|
else
|
||||||
|
i32.const 3
|
||||||
|
end
|
||||||
|
drop
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::I32Const(1),
|
||||||
|
isa::Instruction::BrIfEqz(isa::Target {
|
||||||
|
dst_pc: 4,
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
}),
|
||||||
|
isa::Instruction::I32Const(2),
|
||||||
|
isa::Instruction::Br(isa::Target {
|
||||||
|
dst_pc: 5,
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
}),
|
||||||
|
isa::Instruction::I32Const(3),
|
||||||
|
isa::Instruction::Drop,
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn if_else_branch_from_true_branch() {
|
||||||
|
let code = compile(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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::I32Const(1),
|
||||||
|
isa::Instruction::BrIfEqz(isa::Target {
|
||||||
|
dst_pc: 8,
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
}),
|
||||||
|
isa::Instruction::I32Const(1),
|
||||||
|
isa::Instruction::I32Const(1),
|
||||||
|
isa::Instruction::BrIfNez(isa::Target {
|
||||||
|
dst_pc: 9,
|
||||||
|
drop: 0,
|
||||||
|
keep: 1,
|
||||||
|
}),
|
||||||
|
isa::Instruction::Drop,
|
||||||
|
isa::Instruction::I32Const(2),
|
||||||
|
isa::Instruction::Br(isa::Target {
|
||||||
|
dst_pc: 9,
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
}),
|
||||||
|
isa::Instruction::I32Const(3),
|
||||||
|
isa::Instruction::Drop,
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn if_else_branch_from_false_branch() {
|
||||||
|
let code = compile(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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::I32Const(1),
|
||||||
|
isa::Instruction::BrIfEqz(isa::Target {
|
||||||
|
dst_pc: 4,
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
}),
|
||||||
|
isa::Instruction::I32Const(1),
|
||||||
|
isa::Instruction::Br(isa::Target {
|
||||||
|
dst_pc: 9,
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
}),
|
||||||
|
isa::Instruction::I32Const(2),
|
||||||
|
isa::Instruction::I32Const(1),
|
||||||
|
isa::Instruction::BrIfNez(isa::Target {
|
||||||
|
dst_pc: 9,
|
||||||
|
drop: 0,
|
||||||
|
keep: 1,
|
||||||
|
}),
|
||||||
|
isa::Instruction::Drop,
|
||||||
|
isa::Instruction::I32Const(3),
|
||||||
|
isa::Instruction::Drop,
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn loop_() {
|
||||||
|
let code = compile(r#"
|
||||||
|
(module
|
||||||
|
(func (export "call")
|
||||||
|
loop (result i32)
|
||||||
|
i32.const 1
|
||||||
|
br_if 0
|
||||||
|
i32.const 2
|
||||||
|
end
|
||||||
|
drop
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::I32Const(1),
|
||||||
|
isa::Instruction::BrIfNez(isa::Target {
|
||||||
|
dst_pc: 0,
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
}),
|
||||||
|
isa::Instruction::I32Const(2),
|
||||||
|
isa::Instruction::Drop,
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn loop_empty() {
|
||||||
|
let code = compile(r#"
|
||||||
|
(module
|
||||||
|
(func (export "call")
|
||||||
|
loop
|
||||||
|
end
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn brtable() {
|
||||||
|
let code = compile(r#"
|
||||||
|
(module
|
||||||
|
(func (export "call")
|
||||||
|
block $1
|
||||||
|
loop $2
|
||||||
|
i32.const 0
|
||||||
|
br_table $2 $1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::I32Const(0),
|
||||||
|
isa::Instruction::BrTable(
|
||||||
|
vec![
|
||||||
|
isa::Target {
|
||||||
|
dst_pc: 0,
|
||||||
|
keep: 0,
|
||||||
|
drop: 0,
|
||||||
|
},
|
||||||
|
isa::Target {
|
||||||
|
dst_pc: 2,
|
||||||
|
keep: 0,
|
||||||
|
drop: 0,
|
||||||
|
},
|
||||||
|
].into_boxed_slice()
|
||||||
|
),
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn brtable_returns_result() {
|
||||||
|
let code = compile(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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::I32Const(0),
|
||||||
|
isa::Instruction::I32Const(1),
|
||||||
|
isa::Instruction::BrTable(
|
||||||
|
vec![
|
||||||
|
isa::Target {
|
||||||
|
dst_pc: 3,
|
||||||
|
keep: 1,
|
||||||
|
drop: 0,
|
||||||
|
},
|
||||||
|
isa::Target {
|
||||||
|
dst_pc: 4,
|
||||||
|
keep: 1,
|
||||||
|
drop: 0,
|
||||||
|
},
|
||||||
|
].into_boxed_slice()
|
||||||
|
),
|
||||||
|
isa::Instruction::Unreachable,
|
||||||
|
isa::Instruction::Drop,
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 0,
|
||||||
|
keep: 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wabt_example() {
|
||||||
|
let code = compile(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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
assert_eq!(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
isa::Instruction::GetLocal(1),
|
||||||
|
isa::Instruction::BrIfNez(isa::Target {
|
||||||
|
dst_pc: 4,
|
||||||
|
keep: 0,
|
||||||
|
drop: 0,
|
||||||
|
}),
|
||||||
|
isa::Instruction::I32Const(1),
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 1, // 1 parameter
|
||||||
|
keep: 1, // return value
|
||||||
|
},
|
||||||
|
isa::Instruction::I32Const(2),
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 1,
|
||||||
|
keep: 1,
|
||||||
|
},
|
||||||
|
isa::Instruction::Return {
|
||||||
|
drop: 1,
|
||||||
|
keep: 1,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
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 parameter count.
|
||||||
|
pub fn param_count(&self) -> u32 {
|
||||||
|
self.params.len() as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns total count of all declared locals and paramaterers.
|
||||||
|
///
|
||||||
|
/// Returns `Err` if count overflows 32-bit value.
|
||||||
|
pub fn count(&self) -> Result<u32, Error> {
|
||||||
|
let mut acc = self.param_count();
|
||||||
|
for locals_group in self.local_groups {
|
||||||
|
acc = acc
|
||||||
|
.checked_add(locals_group.count())
|
||||||
|
.ok_or_else(||
|
||||||
|
Error(String::from("Locals range no in 32-bit range"))
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Ok(acc)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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);
|
||||||
|
|
||||||
|
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(_));
|
||||||
|
}
|
||||||
|
}
|
200
src/value.rs
200
src/value.rs
|
@ -1,6 +1,7 @@
|
||||||
use std::{i32, i64, u32, u64, f32};
|
|
||||||
use std::io;
|
|
||||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
use nan_preserving_float::{F32, F64};
|
||||||
|
use std::io;
|
||||||
|
use std::{f32, i32, i64, u32, u64};
|
||||||
use TrapKind;
|
use TrapKind;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -22,9 +23,9 @@ pub enum RuntimeValue {
|
||||||
/// Value of 64-bit signed or unsigned integer.
|
/// Value of 64-bit signed or unsigned integer.
|
||||||
I64(i64),
|
I64(i64),
|
||||||
/// Value of 32-bit IEEE 754-2008 floating point number.
|
/// Value of 32-bit IEEE 754-2008 floating point number.
|
||||||
F32(f32),
|
F32(F32),
|
||||||
/// Value of 64-bit IEEE 754-2008 floating point number.
|
/// Value of 64-bit IEEE 754-2008 floating point number.
|
||||||
F64(f64),
|
F64(F64),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait for creating value from a [`RuntimeValue`].
|
/// Trait for creating value from a [`RuntimeValue`].
|
||||||
|
@ -136,19 +137,19 @@ impl RuntimeValue {
|
||||||
match value_type {
|
match value_type {
|
||||||
::types::ValueType::I32 => RuntimeValue::I32(0),
|
::types::ValueType::I32 => RuntimeValue::I32(0),
|
||||||
::types::ValueType::I64 => RuntimeValue::I64(0),
|
::types::ValueType::I64 => RuntimeValue::I64(0),
|
||||||
::types::ValueType::F32 => RuntimeValue::F32(0f32),
|
::types::ValueType::F32 => RuntimeValue::F32(0f32.into()),
|
||||||
::types::ValueType::F64 => RuntimeValue::F64(0f64),
|
::types::ValueType::F64 => RuntimeValue::F64(0f64.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates new value by interpreting passed u32 as f32.
|
/// Creates new value by interpreting passed u32 as f32.
|
||||||
pub fn decode_f32(val: u32) -> Self {
|
pub fn decode_f32(val: u32) -> Self {
|
||||||
RuntimeValue::F32(f32::from_bits(val))
|
RuntimeValue::F32(F32::from_bits(val))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates new value by interpreting passed u64 as f64.
|
/// Creates new value by interpreting passed u64 as f64.
|
||||||
pub fn decode_f64(val: u64) -> Self {
|
pub fn decode_f64(val: u64) -> Self {
|
||||||
RuntimeValue::F64(f64::from_bits(val))
|
RuntimeValue::F64(F64::from_bits(val))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get variable type for this value.
|
/// Get variable type for this value.
|
||||||
|
@ -197,14 +198,14 @@ impl From<u64> for RuntimeValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<f32> for RuntimeValue {
|
impl From<F32> for RuntimeValue {
|
||||||
fn from(val: f32) -> Self {
|
fn from(val: F32) -> Self {
|
||||||
RuntimeValue::F32(val)
|
RuntimeValue::F32(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<f64> for RuntimeValue {
|
impl From<F64> for RuntimeValue {
|
||||||
fn from(val: f64) -> Self {
|
fn from(val: F64) -> Self {
|
||||||
RuntimeValue::F64(val)
|
RuntimeValue::F64(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,7 +215,7 @@ macro_rules! impl_from_runtime_value {
|
||||||
impl FromRuntimeValue for $into {
|
impl FromRuntimeValue for $into {
|
||||||
fn from_runtime_value(val: RuntimeValue) -> Option<Self> {
|
fn from_runtime_value(val: RuntimeValue) -> Option<Self> {
|
||||||
match val {
|
match val {
|
||||||
RuntimeValue::$expected_rt_ty(val) => Some(val as $into),
|
RuntimeValue::$expected_rt_ty(val) => Some(val.transmute_into()),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -237,19 +238,26 @@ impl FromRuntimeValue for bool {
|
||||||
|
|
||||||
impl_from_runtime_value!(I32, i32);
|
impl_from_runtime_value!(I32, i32);
|
||||||
impl_from_runtime_value!(I64, i64);
|
impl_from_runtime_value!(I64, i64);
|
||||||
impl_from_runtime_value!(F32, f32);
|
impl_from_runtime_value!(F32, F32);
|
||||||
impl_from_runtime_value!(F64, f64);
|
impl_from_runtime_value!(F64, F64);
|
||||||
impl_from_runtime_value!(I32, u32);
|
impl_from_runtime_value!(I32, u32);
|
||||||
impl_from_runtime_value!(I64, u64);
|
impl_from_runtime_value!(I64, u64);
|
||||||
|
|
||||||
macro_rules! impl_wrap_into {
|
macro_rules! impl_wrap_into {
|
||||||
($from: ident, $into: ident) => {
|
($from:ident, $into:ident) => {
|
||||||
impl WrapInto<$into> for $from {
|
impl WrapInto<$into> for $from {
|
||||||
fn wrap_into(self) -> $into {
|
fn wrap_into(self) -> $into {
|
||||||
self as $into
|
self as $into
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
($from:ident, $intermediate:ident, $into:ident) => {
|
||||||
|
impl WrapInto<$into> for $from {
|
||||||
|
fn wrap_into(self) -> $into {
|
||||||
|
$into::from(self as $intermediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_wrap_into!(i32, i8);
|
impl_wrap_into!(i32, i8);
|
||||||
|
@ -257,13 +265,19 @@ impl_wrap_into!(i32, i16);
|
||||||
impl_wrap_into!(i64, i8);
|
impl_wrap_into!(i64, i8);
|
||||||
impl_wrap_into!(i64, i16);
|
impl_wrap_into!(i64, i16);
|
||||||
impl_wrap_into!(i64, i32);
|
impl_wrap_into!(i64, i32);
|
||||||
impl_wrap_into!(i64, f32);
|
impl_wrap_into!(i64, f32, F32);
|
||||||
impl_wrap_into!(u64, f32);
|
impl_wrap_into!(u64, f32, F32);
|
||||||
// Casting from an f64 to an f32 will produce the closest possible value (rounding strategy unspecified)
|
// Casting from an f64 to an f32 will produce the closest possible value (rounding strategy unspecified)
|
||||||
// NOTE: currently this will cause Undefined Behavior if the value is finite but larger or smaller than the
|
// NOTE: currently this will cause Undefined Behavior if the value is finite but larger or smaller than the
|
||||||
// largest or smallest finite value representable by f32. This is a bug and will be fixed.
|
// largest or smallest finite value representable by f32. This is a bug and will be fixed.
|
||||||
impl_wrap_into!(f64, f32);
|
impl_wrap_into!(f64, f32);
|
||||||
|
|
||||||
|
impl WrapInto<F32> for F64 {
|
||||||
|
fn wrap_into(self) -> F32 {
|
||||||
|
(f64::from(self) as f32).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! impl_try_truncate_into {
|
macro_rules! impl_try_truncate_into {
|
||||||
($from: ident, $into: ident) => {
|
($from: ident, $into: ident) => {
|
||||||
impl TryTruncateInto<$into, TrapKind> for $from {
|
impl TryTruncateInto<$into, TrapKind> for $from {
|
||||||
|
@ -284,7 +298,14 @@ macro_rules! impl_try_truncate_into {
|
||||||
Ok(self as $into)
|
Ok(self as $into)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
($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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_try_truncate_into!(f32, i32);
|
impl_try_truncate_into!(f32, i32);
|
||||||
|
@ -295,15 +316,30 @@ impl_try_truncate_into!(f32, u32);
|
||||||
impl_try_truncate_into!(f32, u64);
|
impl_try_truncate_into!(f32, u64);
|
||||||
impl_try_truncate_into!(f64, u32);
|
impl_try_truncate_into!(f64, u32);
|
||||||
impl_try_truncate_into!(f64, u64);
|
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);
|
||||||
|
|
||||||
macro_rules! impl_extend_into {
|
macro_rules! impl_extend_into {
|
||||||
($from: ident, $into: ident) => {
|
($from:ident, $into:ident) => {
|
||||||
impl ExtendInto<$into> for $from {
|
impl ExtendInto<$into> for $from {
|
||||||
fn extend_into(self) -> $into {
|
fn extend_into(self) -> $into {
|
||||||
self as $into
|
self as $into
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
($from:ident, $intermediate:ident, $into:ident) => {
|
||||||
|
impl ExtendInto<$into> for $from {
|
||||||
|
fn extend_into(self) -> $into {
|
||||||
|
$into::from(self as $intermediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_extend_into!(i8, i32);
|
impl_extend_into!(i8, i32);
|
||||||
|
@ -325,6 +361,20 @@ impl_extend_into!(i64, f64);
|
||||||
impl_extend_into!(u64, f64);
|
impl_extend_into!(u64, f64);
|
||||||
impl_extend_into!(f32, f64);
|
impl_extend_into!(f32, f64);
|
||||||
|
|
||||||
|
impl_extend_into!(i32, f32, F32);
|
||||||
|
impl_extend_into!(i32, f64, F64);
|
||||||
|
impl_extend_into!(u32, f32, F32);
|
||||||
|
impl_extend_into!(u32, f64, F64);
|
||||||
|
impl_extend_into!(i64, f64, F64);
|
||||||
|
impl_extend_into!(u64, f64, F64);
|
||||||
|
impl_extend_into!(f32, f64, F64);
|
||||||
|
|
||||||
|
impl ExtendInto<F64> for F32 {
|
||||||
|
fn extend_into(self) -> F64 {
|
||||||
|
(f32::from(self) as f64).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! impl_transmute_into_self {
|
macro_rules! impl_transmute_into_self {
|
||||||
($type: ident) => {
|
($type: ident) => {
|
||||||
impl TransmuteInto<$type> for $type {
|
impl TransmuteInto<$type> for $type {
|
||||||
|
@ -339,6 +389,8 @@ impl_transmute_into_self!(i32);
|
||||||
impl_transmute_into_self!(i64);
|
impl_transmute_into_self!(i64);
|
||||||
impl_transmute_into_self!(f32);
|
impl_transmute_into_self!(f32);
|
||||||
impl_transmute_into_self!(f64);
|
impl_transmute_into_self!(f64);
|
||||||
|
impl_transmute_into_self!(F32);
|
||||||
|
impl_transmute_into_self!(F64);
|
||||||
|
|
||||||
macro_rules! impl_transmute_into_as {
|
macro_rules! impl_transmute_into_as {
|
||||||
($from: ident, $into: ident) => {
|
($from: ident, $into: ident) => {
|
||||||
|
@ -357,6 +409,49 @@ impl_transmute_into_as!(u32, i32);
|
||||||
impl_transmute_into_as!(i64, u64);
|
impl_transmute_into_as!(i64, u64);
|
||||||
impl_transmute_into_as!(u64, i64);
|
impl_transmute_into_as!(u64, i64);
|
||||||
|
|
||||||
|
macro_rules! impl_transmute_into_npf {
|
||||||
|
($npf:ident, $float:ident, $signed:ident, $unsigned:ident) => {
|
||||||
|
impl TransmuteInto<$float> for $npf {
|
||||||
|
fn transmute_into(self) -> $float {
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransmuteInto<$npf> for $float {
|
||||||
|
fn transmute_into(self) -> $npf {
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransmuteInto<$signed> for $npf {
|
||||||
|
fn transmute_into(self) -> $signed {
|
||||||
|
self.to_bits() as _
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransmuteInto<$unsigned> for $npf {
|
||||||
|
fn transmute_into(self) -> $unsigned {
|
||||||
|
self.to_bits()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransmuteInto<$npf> for $signed {
|
||||||
|
fn transmute_into(self) -> $npf {
|
||||||
|
$npf::from_bits(self as _)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransmuteInto<$npf> for $unsigned {
|
||||||
|
fn transmute_into(self) -> $npf {
|
||||||
|
$npf::from_bits(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_transmute_into_npf!(F32, f32, i32, u32);
|
||||||
|
impl_transmute_into_npf!(F64, f64, i64, u64);
|
||||||
|
|
||||||
impl TransmuteInto<i32> for f32 {
|
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 }
|
||||||
}
|
}
|
||||||
|
@ -497,6 +592,26 @@ impl LittleEndianConvert for f64 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LittleEndianConvert for F32 {
|
||||||
|
fn into_little_endian(self) -> Vec<u8> {
|
||||||
|
(self.to_bits() as i32).into_little_endian()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
||||||
|
i32::from_little_endian(buffer).map(|val| Self::from_bits(val as _))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LittleEndianConvert for F64 {
|
||||||
|
fn into_little_endian(self) -> Vec<u8> {
|
||||||
|
(self.to_bits() as i64).into_little_endian()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_little_endian(buffer: &[u8]) -> Result<Self, Error> {
|
||||||
|
i64::from_little_endian(buffer).map(|val| Self::from_bits(val as _))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! impl_integer_arithmetic_ops {
|
macro_rules! impl_integer_arithmetic_ops {
|
||||||
($type: ident) => {
|
($type: ident) => {
|
||||||
impl ArithmeticOps<$type> for $type {
|
impl ArithmeticOps<$type> for $type {
|
||||||
|
@ -538,6 +653,8 @@ macro_rules! impl_float_arithmetic_ops {
|
||||||
|
|
||||||
impl_float_arithmetic_ops!(f32);
|
impl_float_arithmetic_ops!(f32);
|
||||||
impl_float_arithmetic_ops!(f64);
|
impl_float_arithmetic_ops!(f64);
|
||||||
|
impl_float_arithmetic_ops!(F32);
|
||||||
|
impl_float_arithmetic_ops!(F64);
|
||||||
|
|
||||||
macro_rules! impl_integer {
|
macro_rules! impl_integer {
|
||||||
($type: ident) => {
|
($type: ident) => {
|
||||||
|
@ -561,13 +678,26 @@ impl_integer!(i64);
|
||||||
impl_integer!(u64);
|
impl_integer!(u64);
|
||||||
|
|
||||||
macro_rules! impl_float {
|
macro_rules! impl_float {
|
||||||
($type: ident, $int_type: ident) => {
|
($type:ident, $int_type:ident) => {
|
||||||
|
impl_float!($type, $type, $int_type);
|
||||||
|
};
|
||||||
|
($type:ident, $intermediate:ident, $int_type:ident) => {
|
||||||
impl Float<$type> for $type {
|
impl Float<$type> for $type {
|
||||||
fn abs(self) -> $type { self.abs() }
|
fn abs(self) -> $type {
|
||||||
fn floor(self) -> $type { self.floor() }
|
$intermediate::abs(self.into()).into()
|
||||||
fn ceil(self) -> $type { self.ceil() }
|
}
|
||||||
fn trunc(self) -> $type { self.trunc() }
|
fn floor(self) -> $type {
|
||||||
fn round(self) -> $type { self.round() }
|
$intermediate::floor(self.into()).into()
|
||||||
|
}
|
||||||
|
fn ceil(self) -> $type {
|
||||||
|
$intermediate::ceil(self.into()).into()
|
||||||
|
}
|
||||||
|
fn trunc(self) -> $type {
|
||||||
|
$intermediate::trunc(self.into()).into()
|
||||||
|
}
|
||||||
|
fn round(self) -> $type {
|
||||||
|
$intermediate::round(self.into()).into()
|
||||||
|
}
|
||||||
fn nearest(self) -> $type {
|
fn nearest(self) -> $type {
|
||||||
let round = self.round();
|
let round = self.round();
|
||||||
if self.fract().abs() != 0.5 {
|
if self.fract().abs() != 0.5 {
|
||||||
|
@ -583,12 +713,13 @@ macro_rules! impl_float {
|
||||||
round
|
round
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn sqrt(self) -> $type { self.sqrt() }
|
fn sqrt(self) -> $type {
|
||||||
|
$intermediate::sqrt(self.into()).into()
|
||||||
|
}
|
||||||
// This instruction corresponds to what is sometimes called "minNaN" in other languages.
|
// This instruction corresponds to what is sometimes called "minNaN" in other languages.
|
||||||
fn min(self, other: $type) -> $type {
|
fn min(self, other: $type) -> $type {
|
||||||
if self.is_nan() || other.is_nan() {
|
if self.is_nan() || other.is_nan() {
|
||||||
use std::$type;
|
return ::std::$intermediate::NAN.into();
|
||||||
return $type::NAN;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.min(other)
|
self.min(other)
|
||||||
|
@ -596,8 +727,7 @@ macro_rules! impl_float {
|
||||||
// This instruction corresponds to what is sometimes called "maxNaN" in other languages.
|
// This instruction corresponds to what is sometimes called "maxNaN" in other languages.
|
||||||
fn max(self, other: $type) -> $type {
|
fn max(self, other: $type) -> $type {
|
||||||
if self.is_nan() || other.is_nan() {
|
if self.is_nan() || other.is_nan() {
|
||||||
use std::$type;
|
return ::std::$intermediate::NAN.into();
|
||||||
return $type::NAN;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.max(other)
|
self.max(other)
|
||||||
|
@ -623,8 +753,10 @@ macro_rules! impl_float {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_float!(f32, i32);
|
impl_float!(f32, i32);
|
||||||
impl_float!(f64, i64);
|
impl_float!(f64, i64);
|
||||||
|
impl_float!(F32, f32, i32);
|
||||||
|
impl_float!(F64, f64, i64);
|
||||||
|
|
|
@ -2,22 +2,20 @@
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
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::memory_units::Pages;
|
|
||||||
use wabt::script::{self, Action, Command, CommandKind, ScriptParser, Value};
|
use wabt::script::{self, Action, Command, CommandKind, ScriptParser, Value};
|
||||||
|
use wasmi::memory_units::Pages;
|
||||||
|
use wasmi::{Error as InterpreterError, Externals, FuncInstance, FuncRef, GlobalDescriptor,
|
||||||
|
GlobalInstance, GlobalRef, ImportResolver, ImportsBuilder, MemoryDescriptor,
|
||||||
|
MemoryInstance, MemoryRef, Module, ModuleImportResolver, ModuleInstance, ModuleRef,
|
||||||
|
RuntimeArgs, RuntimeValue, Signature, TableDescriptor, TableInstance, TableRef, Trap};
|
||||||
|
|
||||||
fn spec_to_runtime_value(value: Value) -> RuntimeValue {
|
fn spec_to_runtime_value(val: Value<u32, u64>) -> RuntimeValue {
|
||||||
match value {
|
match val {
|
||||||
Value::I32(v) => RuntimeValue::I32(v),
|
Value::I32(v) => RuntimeValue::I32(v),
|
||||||
Value::I64(v) => RuntimeValue::I64(v),
|
Value::I64(v) => RuntimeValue::I64(v),
|
||||||
Value::F32(v) => RuntimeValue::F32(v),
|
Value::F32(v) => RuntimeValue::F32(v.into()),
|
||||||
Value::F64(v) => RuntimeValue::F64(v),
|
Value::F64(v) => RuntimeValue::F64(v.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -49,15 +47,15 @@ struct SpecModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpecModule {
|
impl SpecModule {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
SpecModule {
|
SpecModule {
|
||||||
table: TableInstance::alloc(10, Some(20)).unwrap(),
|
table: TableInstance::alloc(10, Some(20)).unwrap(),
|
||||||
memory: MemoryInstance::alloc(Pages(1), Some(Pages(2))).unwrap(),
|
memory: MemoryInstance::alloc(Pages(1), Some(Pages(2))).unwrap(),
|
||||||
global_i32: GlobalInstance::alloc(RuntimeValue::I32(666), false),
|
global_i32: GlobalInstance::alloc(RuntimeValue::I32(666), false),
|
||||||
global_f32: GlobalInstance::alloc(RuntimeValue::F32(666.0), false),
|
global_f32: GlobalInstance::alloc(RuntimeValue::F32(666.0.into()), false),
|
||||||
global_f64: GlobalInstance::alloc(RuntimeValue::F64(666.0), false),
|
global_f64: GlobalInstance::alloc(RuntimeValue::F64(666.0.into()), false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const PRINT_FUNC_INDEX: usize = 0;
|
const PRINT_FUNC_INDEX: usize = 0;
|
||||||
|
@ -121,341 +119,367 @@ impl ModuleImportResolver for SpecModule {
|
||||||
_ => Err(InterpreterError::Instantiation(format!(
|
_ => Err(InterpreterError::Instantiation(format!(
|
||||||
"Unknown host global import {}",
|
"Unknown host global import {}",
|
||||||
field_name
|
field_name
|
||||||
)))
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_memory(
|
fn resolve_memory(
|
||||||
&self,
|
&self,
|
||||||
field_name: &str,
|
field_name: &str,
|
||||||
_memory_type: &MemoryDescriptor,
|
_memory_type: &MemoryDescriptor,
|
||||||
) -> Result<MemoryRef, InterpreterError> {
|
) -> Result<MemoryRef, InterpreterError> {
|
||||||
if field_name == "memory" {
|
if field_name == "memory" {
|
||||||
return Ok(self.memory.clone());
|
return Ok(self.memory.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(InterpreterError::Instantiation(format!(
|
Err(InterpreterError::Instantiation(format!(
|
||||||
"Unknown host memory import {}",
|
"Unknown host memory import {}",
|
||||||
field_name
|
field_name
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_table(
|
fn resolve_table(
|
||||||
&self,
|
&self,
|
||||||
field_name: &str,
|
field_name: &str,
|
||||||
_table_type: &TableDescriptor,
|
_table_type: &TableDescriptor,
|
||||||
) -> Result<TableRef, InterpreterError> {
|
) -> Result<TableRef, InterpreterError> {
|
||||||
if field_name == "table" {
|
if field_name == "table" {
|
||||||
return Ok(self.table.clone());
|
return Ok(self.table.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(InterpreterError::Instantiation(format!(
|
Err(InterpreterError::Instantiation(format!(
|
||||||
"Unknown host table import {}",
|
"Unknown host table import {}",
|
||||||
field_name
|
field_name
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SpecDriver {
|
struct SpecDriver {
|
||||||
spec_module: SpecModule,
|
spec_module: SpecModule,
|
||||||
instances: HashMap<String, ModuleRef>,
|
instances: HashMap<String, ModuleRef>,
|
||||||
last_module: Option<ModuleRef>,
|
last_module: Option<ModuleRef>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpecDriver {
|
impl SpecDriver {
|
||||||
fn new() -> SpecDriver {
|
fn new() -> SpecDriver {
|
||||||
SpecDriver {
|
SpecDriver {
|
||||||
spec_module: SpecModule::new(),
|
spec_module: SpecModule::new(),
|
||||||
instances: HashMap::new(),
|
instances: HashMap::new(),
|
||||||
last_module: None,
|
last_module: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spec_module(&mut self) -> &mut SpecModule {
|
fn spec_module(&mut self) -> &mut SpecModule {
|
||||||
&mut self.spec_module
|
&mut self.spec_module
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_module(&mut self, name: Option<String>, module: ModuleRef) {
|
fn add_module(&mut self, name: Option<String>, module: ModuleRef) {
|
||||||
self.last_module = Some(module.clone());
|
self.last_module = Some(module.clone());
|
||||||
if let Some(name) = name {
|
if let Some(name) = name {
|
||||||
self.instances.insert(name, module);
|
self.instances.insert(name, module);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn module(&self, name: &str) -> Result<ModuleRef, InterpreterError> {
|
fn module(&self, name: &str) -> Result<ModuleRef, InterpreterError> {
|
||||||
self.instances.get(name).cloned().ok_or_else(|| {
|
self.instances.get(name).cloned().ok_or_else(|| {
|
||||||
InterpreterError::Instantiation(format!("Module not registered {}", name))
|
InterpreterError::Instantiation(format!("Module not registered {}", name))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn module_or_last(&self, name: Option<&str>) -> Result<ModuleRef, InterpreterError> {
|
fn module_or_last(&self, name: Option<&str>) -> Result<ModuleRef, InterpreterError> {
|
||||||
match name {
|
match name {
|
||||||
Some(name) => self.module(name),
|
Some(name) => self.module(name),
|
||||||
None => self.last_module
|
None => self.last_module
|
||||||
.clone()
|
.clone()
|
||||||
.ok_or_else(|| InterpreterError::Instantiation("No modules registered".into())),
|
.ok_or_else(|| InterpreterError::Instantiation("No modules registered".into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImportResolver for SpecDriver {
|
impl ImportResolver for SpecDriver {
|
||||||
fn resolve_func(
|
fn resolve_func(
|
||||||
&self,
|
&self,
|
||||||
module_name: &str,
|
module_name: &str,
|
||||||
field_name: &str,
|
field_name: &str,
|
||||||
func_type: &Signature,
|
func_type: &Signature,
|
||||||
) -> Result<FuncRef, InterpreterError> {
|
) -> Result<FuncRef, InterpreterError> {
|
||||||
if module_name == "spectest" {
|
if module_name == "spectest" {
|
||||||
self.spec_module.resolve_func(field_name, func_type)
|
self.spec_module.resolve_func(field_name, func_type)
|
||||||
} else {
|
} else {
|
||||||
self.module(module_name)?
|
self.module(module_name)?
|
||||||
.resolve_func(field_name, func_type)
|
.resolve_func(field_name, func_type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_global(
|
fn resolve_global(
|
||||||
&self,
|
&self,
|
||||||
module_name: &str,
|
module_name: &str,
|
||||||
field_name: &str,
|
field_name: &str,
|
||||||
global_type: &GlobalDescriptor,
|
global_type: &GlobalDescriptor,
|
||||||
) -> Result<GlobalRef, InterpreterError> {
|
) -> Result<GlobalRef, InterpreterError> {
|
||||||
if module_name == "spectest" {
|
if module_name == "spectest" {
|
||||||
self.spec_module.resolve_global(field_name, global_type)
|
self.spec_module.resolve_global(field_name, global_type)
|
||||||
} else {
|
} else {
|
||||||
self.module(module_name)?
|
self.module(module_name)?
|
||||||
.resolve_global(field_name, global_type)
|
.resolve_global(field_name, global_type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_memory(
|
fn resolve_memory(
|
||||||
&self,
|
&self,
|
||||||
module_name: &str,
|
module_name: &str,
|
||||||
field_name: &str,
|
field_name: &str,
|
||||||
memory_type: &MemoryDescriptor,
|
memory_type: &MemoryDescriptor,
|
||||||
) -> Result<MemoryRef, InterpreterError> {
|
) -> Result<MemoryRef, InterpreterError> {
|
||||||
if module_name == "spectest" {
|
if module_name == "spectest" {
|
||||||
self.spec_module.resolve_memory(field_name, memory_type)
|
self.spec_module.resolve_memory(field_name, memory_type)
|
||||||
} else {
|
} else {
|
||||||
self.module(module_name)?
|
self.module(module_name)?
|
||||||
.resolve_memory(field_name, memory_type)
|
.resolve_memory(field_name, memory_type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_table(
|
fn resolve_table(
|
||||||
&self,
|
&self,
|
||||||
module_name: &str,
|
module_name: &str,
|
||||||
field_name: &str,
|
field_name: &str,
|
||||||
table_type: &TableDescriptor,
|
table_type: &TableDescriptor,
|
||||||
) -> Result<TableRef, InterpreterError> {
|
) -> Result<TableRef, InterpreterError> {
|
||||||
if module_name == "spectest" {
|
if module_name == "spectest" {
|
||||||
self.spec_module.resolve_table(field_name, table_type)
|
self.spec_module.resolve_table(field_name, table_type)
|
||||||
} else {
|
} else {
|
||||||
self.module(module_name)?
|
self.module(module_name)?
|
||||||
.resolve_table(field_name, table_type)
|
.resolve_table(field_name, table_type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_load_module(wasm: &[u8]) -> Result<Module, Error> {
|
fn try_load_module(wasm: &[u8]) -> Result<Module, Error> {
|
||||||
Module::from_buffer(wasm).map_err(|e| Error::Load(e.to_string()))
|
Module::from_buffer(wasm).map_err(|e| Error::Load(e.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_load(wasm: &[u8], spec_driver: &mut SpecDriver) -> Result<(), Error> {
|
fn try_load(wasm: &[u8], spec_driver: &mut SpecDriver) -> Result<(), Error> {
|
||||||
let module = try_load_module(wasm)?;
|
let module = try_load_module(wasm)?;
|
||||||
let instance = ModuleInstance::new(&module, &ImportsBuilder::default())?;
|
let instance = ModuleInstance::new(&module, &ImportsBuilder::default())?;
|
||||||
instance
|
instance
|
||||||
.run_start(spec_driver.spec_module())
|
.run_start(spec_driver.spec_module())
|
||||||
.map_err(|trap| Error::Start(trap))?;
|
.map_err(|trap| Error::Start(trap))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_module(wasm: &[u8], name: &Option<String>, spec_driver: &mut SpecDriver) -> Result<ModuleRef, Error> {
|
fn load_module(
|
||||||
let module = try_load_module(wasm)?;
|
wasm: &[u8],
|
||||||
let instance = ModuleInstance::new(&module, spec_driver)
|
name: &Option<String>,
|
||||||
.map_err(|e| Error::Load(e.to_string()))?
|
spec_driver: &mut SpecDriver,
|
||||||
.run_start(spec_driver.spec_module())
|
) -> Result<ModuleRef, Error> {
|
||||||
.map_err(|trap| Error::Start(trap))?;
|
let module = try_load_module(wasm)?;
|
||||||
|
let instance = ModuleInstance::new(&module, spec_driver)
|
||||||
|
.map_err(|e| Error::Load(e.to_string()))?
|
||||||
|
.run_start(spec_driver.spec_module())
|
||||||
|
.map_err(|trap| Error::Start(trap))?;
|
||||||
|
|
||||||
let module_name = name.clone();
|
let module_name = name.clone();
|
||||||
spec_driver.add_module(module_name, instance.clone());
|
spec_driver.add_module(module_name, instance.clone());
|
||||||
|
|
||||||
Ok(instance)
|
Ok(instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_action(
|
fn run_action(
|
||||||
program: &mut SpecDriver,
|
program: &mut SpecDriver,
|
||||||
action: &Action,
|
action: &Action<u32, u64>,
|
||||||
) -> Result<Option<RuntimeValue>, InterpreterError> {
|
) -> Result<Option<RuntimeValue>, InterpreterError> {
|
||||||
match *action {
|
match *action {
|
||||||
Action::Invoke {
|
Action::Invoke {
|
||||||
ref module,
|
ref module,
|
||||||
ref field,
|
ref field,
|
||||||
ref args,
|
ref args,
|
||||||
} => {
|
} => {
|
||||||
let module = program
|
let module = program
|
||||||
.module_or_last(module.as_ref().map(|x| x.as_ref()))
|
.module_or_last(module.as_ref().map(|x| x.as_ref()))
|
||||||
.expect(&format!(
|
.expect(&format!(
|
||||||
"Expected program to have loaded module {:?}",
|
"Expected program to have loaded module {:?}",
|
||||||
module
|
module
|
||||||
));
|
));
|
||||||
module.invoke_export(
|
let vec_args = args.iter()
|
||||||
field,
|
.cloned()
|
||||||
&args.iter()
|
.map(spec_to_runtime_value)
|
||||||
.cloned()
|
.collect::<Vec<_>>();
|
||||||
.map(spec_to_runtime_value)
|
module.invoke_export(field, &vec_args, program.spec_module())
|
||||||
.collect::<Vec<_>>(),
|
}
|
||||||
program.spec_module(),
|
Action::Get {
|
||||||
)
|
ref module,
|
||||||
}
|
ref field,
|
||||||
Action::Get {
|
..
|
||||||
ref module,
|
} => {
|
||||||
ref field,
|
let module = program
|
||||||
..
|
.module_or_last(module.as_ref().map(|x| x.as_ref()))
|
||||||
} => {
|
.expect(&format!(
|
||||||
let module = program
|
"Expected program to have loaded module {:?}",
|
||||||
.module_or_last(module.as_ref().map(|x| x.as_ref()))
|
module
|
||||||
.expect(&format!(
|
));
|
||||||
"Expected program to have loaded module {:?}",
|
let global = module
|
||||||
module
|
.export_by_name(&field)
|
||||||
));
|
.ok_or_else(|| {
|
||||||
let global = module
|
InterpreterError::Global(format!("Expected to have export with name {}", field))
|
||||||
.export_by_name(&field)
|
})?
|
||||||
.ok_or_else(|| {
|
.as_global()
|
||||||
InterpreterError::Global(format!("Expected to have export with name {}", field))
|
.cloned()
|
||||||
})?
|
.ok_or_else(|| {
|
||||||
.as_global()
|
InterpreterError::Global(format!("Expected export {} to be a global", field))
|
||||||
.cloned()
|
})?;
|
||||||
.ok_or_else(|| {
|
Ok(Some(global.get()))
|
||||||
InterpreterError::Global(format!("Expected export {} to be a global", field))
|
}
|
||||||
})?;
|
}
|
||||||
Ok(Some(global.get()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spec(name: &str) {
|
pub fn spec(name: &str) {
|
||||||
println!("running test: {}", name);
|
println!("running test: {}", name);
|
||||||
try_spec(name).expect("Failed to run spec");
|
try_spec(name).expect("Failed to run spec");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_spec(name: &str) -> Result<(), Error> {
|
fn try_spec(name: &str) -> Result<(), Error> {
|
||||||
let mut spec_driver = SpecDriver::new();
|
let mut spec_driver = SpecDriver::new();
|
||||||
let spec_script_path = format!("tests/spec/testsuite/{}.wast", name);
|
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");
|
let mut parser = ScriptParser::from_file(spec_script_path).expect("Can't read spec script");
|
||||||
while let Some(Command { kind, line }) = parser.next()? {
|
let mut errors = vec![];
|
||||||
println!("Line {}:", line);
|
|
||||||
match kind {
|
|
||||||
CommandKind::Module { name, module, .. } => {
|
|
||||||
load_module(&module.into_vec()?, &name, &mut spec_driver).expect("Failed to load module");
|
|
||||||
}
|
|
||||||
CommandKind::AssertReturn { action, expected } => {
|
|
||||||
let result = run_action(&mut spec_driver, &action);
|
|
||||||
match result {
|
|
||||||
Ok(result) => {
|
|
||||||
let spec_expected = expected
|
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.map(spec_to_runtime_value)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let actual_result = result.into_iter().collect::<Vec<RuntimeValue>>();
|
|
||||||
for (actual_result, spec_expected) in
|
|
||||||
actual_result.iter().zip(spec_expected.iter())
|
|
||||||
{
|
|
||||||
assert_eq!(actual_result.value_type(), spec_expected.value_type());
|
|
||||||
// f32::NAN != f32::NAN
|
|
||||||
match spec_expected {
|
|
||||||
&RuntimeValue::F32(val) if val.is_nan() => match actual_result {
|
|
||||||
&RuntimeValue::F32(val) => assert!(val.is_nan()),
|
|
||||||
_ => unreachable!(), // checked above that types are same
|
|
||||||
},
|
|
||||||
&RuntimeValue::F64(val) if val.is_nan() => match actual_result {
|
|
||||||
&RuntimeValue::F64(val) => assert!(val.is_nan()),
|
|
||||||
_ => unreachable!(), // checked above that types are same
|
|
||||||
},
|
|
||||||
spec_expected @ _ => assert_eq!(actual_result, spec_expected),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!("assert_return at line {} - success", line);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
panic!("Expected action to return value, got error: {:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CommandKind::AssertReturnCanonicalNan { action }
|
|
||||||
| CommandKind::AssertReturnArithmeticNan { action } => {
|
|
||||||
let result = run_action(&mut spec_driver, &action);
|
|
||||||
match result {
|
|
||||||
Ok(result) => {
|
|
||||||
for actual_result in result.into_iter().collect::<Vec<RuntimeValue>>() {
|
|
||||||
match actual_result {
|
|
||||||
RuntimeValue::F32(val) => if !val.is_nan() {
|
|
||||||
panic!("Expected nan value, got {:?}", val)
|
|
||||||
},
|
|
||||||
RuntimeValue::F64(val) => if !val.is_nan() {
|
|
||||||
panic!("Expected nan value, got {:?}", val)
|
|
||||||
},
|
|
||||||
val @ _ => {
|
|
||||||
panic!("Expected action to return float value, got {:?}", val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!("assert_return_nan at line {} - success", line);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
panic!("Expected action to return value, got error: {:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CommandKind::AssertExhaustion { action, .. } => {
|
|
||||||
let result = run_action(&mut spec_driver, &action);
|
|
||||||
match result {
|
|
||||||
Ok(result) => panic!("Expected exhaustion, got result: {:?}", result),
|
|
||||||
Err(e) => println!("assert_exhaustion at line {} - success ({:?})", line, e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CommandKind::AssertTrap { action, .. } => {
|
|
||||||
let result = run_action(&mut spec_driver, &action);
|
|
||||||
match result {
|
|
||||||
Ok(result) => {
|
|
||||||
panic!(
|
|
||||||
"Expected action to result in a trap, got result: {:?}",
|
|
||||||
result
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
println!("assert_trap at line {} - success ({:?})", line, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CommandKind::AssertInvalid { module, .. }
|
|
||||||
| CommandKind::AssertMalformed { module, .. }
|
|
||||||
| CommandKind::AssertUnlinkable { module, .. } => {
|
|
||||||
let module_load = try_load(&module.into_vec()?, &mut spec_driver);
|
|
||||||
match module_load {
|
|
||||||
Ok(_) => panic!("Expected invalid module definition, got some module!"),
|
|
||||||
Err(e) => println!("assert_invalid at line {} - success ({:?})", line, e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CommandKind::AssertUninstantiable { module, .. } => {
|
|
||||||
match try_load(&module.into_vec()?, &mut spec_driver) {
|
|
||||||
Ok(_) => panic!("Expected error running start function at line {}", line),
|
|
||||||
Err(e) => println!("assert_uninstantiable - success ({:?})", e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CommandKind::Register { name, as_name, .. } => {
|
|
||||||
let module = match spec_driver.module_or_last(name.as_ref().map(|x| x.as_ref())) {
|
|
||||||
Ok(module) => module,
|
|
||||||
Err(e) => panic!("No such module, at line {} - ({:?})", e, line),
|
|
||||||
};
|
|
||||||
spec_driver.add_module(Some(as_name.clone()), module);
|
|
||||||
}
|
|
||||||
CommandKind::PerformAction(action) => match run_action(&mut spec_driver, &action) {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => panic!("Failed to invoke action at line {}: {:?}", line, e),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
while let Some(Command { kind, line }) = parser.next()? {
|
||||||
|
macro_rules! assert_eq {
|
||||||
|
($a:expr, $b:expr) => {{
|
||||||
|
let (a, b) = ($a, $b);
|
||||||
|
|
||||||
|
if a != b {
|
||||||
|
errors.push(format!(
|
||||||
|
r#"ERROR (line {}):
|
||||||
|
expected: {:?}
|
||||||
|
got: {:?}
|
||||||
|
"#,
|
||||||
|
line, b, a,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Running spec cmd {}: {:?}", line, kind);
|
||||||
|
|
||||||
|
match kind {
|
||||||
|
CommandKind::Module { name, module, .. } => {
|
||||||
|
load_module(&module.into_vec()?, &name, &mut spec_driver)
|
||||||
|
.expect("Failed to load module");
|
||||||
|
}
|
||||||
|
CommandKind::AssertReturn { action, expected } => {
|
||||||
|
let result = run_action(&mut spec_driver, &action);
|
||||||
|
match result {
|
||||||
|
Ok(result) => {
|
||||||
|
let spec_expected = expected
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(spec_to_runtime_value)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let actual_result = result.into_iter().collect::<Vec<RuntimeValue>>();
|
||||||
|
for (actual_result, spec_expected) in
|
||||||
|
actual_result.iter().zip(spec_expected.iter())
|
||||||
|
{
|
||||||
|
assert_eq!(actual_result.value_type(), spec_expected.value_type());
|
||||||
|
// f32::NAN != f32::NAN
|
||||||
|
match spec_expected {
|
||||||
|
&RuntimeValue::F32(val) if val.is_nan() => match actual_result {
|
||||||
|
&RuntimeValue::F32(val) => assert!(val.is_nan()),
|
||||||
|
_ => unreachable!(), // checked above that types are same
|
||||||
|
},
|
||||||
|
&RuntimeValue::F64(val) if val.is_nan() => match actual_result {
|
||||||
|
&RuntimeValue::F64(val) => assert!(val.is_nan()),
|
||||||
|
_ => unreachable!(), // checked above that types are same
|
||||||
|
},
|
||||||
|
spec_expected @ _ => assert_eq!(actual_result, spec_expected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
panic!("Expected action to return value, got error: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CommandKind::AssertReturnCanonicalNan { action }
|
||||||
|
| CommandKind::AssertReturnArithmeticNan { action } => {
|
||||||
|
let result = run_action(&mut spec_driver, &action);
|
||||||
|
match result {
|
||||||
|
Ok(result) => {
|
||||||
|
for actual_result in result.into_iter().collect::<Vec<RuntimeValue>>() {
|
||||||
|
match actual_result {
|
||||||
|
RuntimeValue::F32(val) => if !val.is_nan() {
|
||||||
|
panic!("Expected nan value, got {:?}", val)
|
||||||
|
},
|
||||||
|
RuntimeValue::F64(val) => if !val.is_nan() {
|
||||||
|
panic!("Expected nan value, got {:?}", val)
|
||||||
|
},
|
||||||
|
val @ _ => {
|
||||||
|
panic!("Expected action to return float value, got {:?}", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
panic!("Expected action to return value, got error: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CommandKind::AssertExhaustion { action, .. } => {
|
||||||
|
let result = run_action(&mut spec_driver, &action);
|
||||||
|
match result {
|
||||||
|
Ok(result) => panic!("Expected exhaustion, got result: {:?}", result),
|
||||||
|
Err(_e) => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CommandKind::AssertTrap { action, .. } => {
|
||||||
|
let result = run_action(&mut spec_driver, &action);
|
||||||
|
match result {
|
||||||
|
Ok(result) => {
|
||||||
|
panic!(
|
||||||
|
"Expected action to result in a trap, got result: {:?}",
|
||||||
|
result
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(_e) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CommandKind::AssertInvalid { module, .. }
|
||||||
|
| CommandKind::AssertMalformed { module, .. }
|
||||||
|
| CommandKind::AssertUnlinkable { module, .. } => {
|
||||||
|
let module_load = try_load(&module.into_vec()?, &mut spec_driver);
|
||||||
|
match module_load {
|
||||||
|
Ok(_) => panic!("Expected invalid module definition, got some module!"),
|
||||||
|
Err(_e) => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CommandKind::AssertUninstantiable { module, .. } => {
|
||||||
|
match try_load(&module.into_vec()?, &mut spec_driver) {
|
||||||
|
Ok(_) => panic!("Expected error running start function at line {}", line),
|
||||||
|
Err(_e) => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CommandKind::Register { name, as_name, .. } => {
|
||||||
|
let module = match spec_driver.module_or_last(name.as_ref().map(|x| x.as_ref())) {
|
||||||
|
Ok(module) => module,
|
||||||
|
Err(e) => panic!("No such module, at line {} - ({:?})", e, line),
|
||||||
|
};
|
||||||
|
spec_driver.add_module(Some(as_name.clone()), module);
|
||||||
|
}
|
||||||
|
CommandKind::PerformAction(action) => match run_action(&mut spec_driver, &action) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => panic!("Failed to invoke action at line {}: {:?}", line, e),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.is_empty() {
|
||||||
|
use std::fmt::Write;
|
||||||
|
let mut out = "\n".to_owned();
|
||||||
|
for err in errors {
|
||||||
|
write!(out, "{}", err).expect("Error formatting errors");
|
||||||
|
}
|
||||||
|
panic!(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue