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:
|
||||
- rust
|
||||
- cpp
|
||||
rust:
|
||||
- nightly
|
||||
- stable
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
|
@ -12,18 +15,16 @@ addons:
|
|||
- g++-6
|
||||
- cmake
|
||||
env:
|
||||
- NIGHTLY_TOOLCHAIN=nightly-2018-02-05
|
||||
- CC=/usr/bin/gcc-6 CXX=/usr/bin/g++-6
|
||||
|
||||
install:
|
||||
# Install `cargo-deadlinks` unless it is currently installed.
|
||||
- command -v cargo-deadlinks &> /dev/null || cargo install cargo-deadlinks
|
||||
# Install nightly toolchain.
|
||||
- rustup toolchain install $NIGHTLY_TOOLCHAIN
|
||||
- if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then rustup target add wasm32-unknown-unknown; fi
|
||||
script:
|
||||
- export CC=/usr/bin/gcc-6
|
||||
- export CXX=/usr/bin/g++-6
|
||||
# Make sure fuzz targets are not broken.
|
||||
- rustup run $NIGHTLY_TOOLCHAIN cargo check --tests --manifest-path=fuzz/Cargo.toml
|
||||
# Make sure nightly targets are not broken.
|
||||
- if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo check --tests --manifest-path=fuzz/Cargo.toml; fi
|
||||
- if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo check --benches --manifest-path=benches/Cargo.toml; fi
|
||||
- ./test.sh
|
||||
- ./doc.sh
|
||||
after_success: |
|
||||
|
|
14
Cargo.toml
14
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
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>"]
|
||||
license = "MIT/Apache-2.0"
|
||||
readme = "README.md"
|
||||
|
@ -8,19 +8,15 @@ repository = "https://github.com/paritytech/wasmi"
|
|||
documentation = "https://paritytech.github.io/wasmi/"
|
||||
description = "WebAssembly interpreter"
|
||||
keywords = ["wasm", "webassembly", "bytecode", "interpreter"]
|
||||
exclude = [ "/res/*", "/tests/*", "/fuzz/*" ]
|
||||
exclude = [ "/res/*", "/tests/*", "/fuzz/*", "/benches/*" ]
|
||||
|
||||
[dependencies]
|
||||
# parity-wasm = "0.27"
|
||||
parity-wasm = { git = "https://github.com/paritytech/parity-wasm.git", rev = "0a61083238d8d9d8d9f6451a5d0da17674b11c21" }
|
||||
byteorder = "1.0"
|
||||
memory_units = "0.3.0"
|
||||
nan-preserving-float = "0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
wabt = "~0.2.2"
|
||||
|
||||
[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 = []
|
||||
assert_matches = "1.1"
|
||||
wabt = "0.3"
|
||||
|
|
|
@ -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)
|
||||
|
||||
if [ -s NIGHTLY_TOOLCHAIN ]; then
|
||||
rustup run $NIGHTLY_TOOLCHAIN cargo doc
|
||||
else
|
||||
cargo doc
|
||||
fi;
|
||||
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.
|
||||
|
|
|
@ -64,8 +64,8 @@ fn main() {
|
|||
function_type.params().iter().enumerate().map(|(i, value)| match value {
|
||||
&ValueType::I32 => RuntimeValue::I32(program_args[i].parse::<i32>().expect(&format!("Can't parse arg #{} as i32", program_args[i]))),
|
||||
&ValueType::I64 => RuntimeValue::I64(program_args[i].parse::<i64>().expect(&format!("Can't parse arg #{} as i64", program_args[i]))),
|
||||
&ValueType::F32 => RuntimeValue::F32(program_args[i].parse::<f32>().expect(&format!("Can't parse arg #{} as f32", program_args[i]))),
|
||||
&ValueType::F64 => RuntimeValue::F64(program_args[i].parse::<f64>().expect(&format!("Can't parse arg #{} as f64", program_args[i]))),
|
||||
&ValueType::F32 => RuntimeValue::F32(program_args[i].parse::<f32>().expect(&format!("Can't parse arg #{} as f32", program_args[i])).into()),
|
||||
&ValueType::F64 => RuntimeValue::F64(program_args[i].parse::<f64>().expect(&format!("Can't parse arg #{} as f64", program_args[i])).into()),
|
||||
}).collect::<Vec<RuntimeValue>>()
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use parity_wasm::elements::BlockType;
|
||||
|
||||
pub mod stack;
|
||||
|
||||
|
@ -7,38 +6,4 @@ pub const DEFAULT_MEMORY_INDEX: u32 = 0;
|
|||
/// Index of default table.
|
||||
pub const DEFAULT_TABLE_INDEX: u32 = 0;
|
||||
|
||||
/// Control stack frame.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BlockFrame {
|
||||
/// Frame type.
|
||||
pub frame_type: BlockFrameType,
|
||||
/// A signature, which is a block signature type indicating the number and types of result values of the region.
|
||||
pub block_type: BlockType,
|
||||
/// A label for reference to block instruction.
|
||||
pub begin_position: usize,
|
||||
/// A label for reference from branch instructions.
|
||||
pub branch_position: usize,
|
||||
/// A label for reference from end instructions.
|
||||
pub end_position: usize,
|
||||
/// A limit integer value, which is an index into the value stack indicating where to reset it to on a branch to that label.
|
||||
pub value_stack_len: usize,
|
||||
/// Boolean which signals whether value stack became polymorphic. Value stack starts in non-polymorphic state and
|
||||
/// becomes polymorphic only after an instruction that never passes control further is executed,
|
||||
/// i.e. `unreachable`, `br` (but not `br_if`!), etc.
|
||||
pub polymorphic_stack: bool,
|
||||
}
|
||||
|
||||
/// Type of block frame.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum BlockFrameType {
|
||||
/// Function frame.
|
||||
Function,
|
||||
/// Usual block frame.
|
||||
Block,
|
||||
/// Loop frame (branching to the beginning of block).
|
||||
Loop,
|
||||
/// True-subblock of if expression.
|
||||
IfTrue,
|
||||
/// False-subblock of if expression.
|
||||
IfFalse,
|
||||
}
|
||||
// TODO: Move BlockFrame under validation.
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
|
||||
use std::collections::VecDeque;
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
|
||||
|
@ -22,7 +21,7 @@ impl error::Error for Error {
|
|||
#[derive(Debug)]
|
||||
pub struct StackWithLimit<T> where T: Clone {
|
||||
/// Stack values.
|
||||
values: VecDeque<T>,
|
||||
values: Vec<T>,
|
||||
/// Stack limit (maximal stack len).
|
||||
limit: usize,
|
||||
}
|
||||
|
@ -30,7 +29,7 @@ pub struct StackWithLimit<T> where T: Clone {
|
|||
impl<T> StackWithLimit<T> where T: Clone {
|
||||
pub fn with_limit(limit: usize) -> Self {
|
||||
StackWithLimit {
|
||||
values: VecDeque::new(),
|
||||
values: Vec::new(),
|
||||
limit: limit
|
||||
}
|
||||
}
|
||||
|
@ -43,19 +42,17 @@ impl<T> StackWithLimit<T> where T: Clone {
|
|||
self.values.len()
|
||||
}
|
||||
|
||||
pub fn limit(&self) -> usize {
|
||||
self.limit
|
||||
}
|
||||
|
||||
pub fn top(&self) -> Result<&T, Error> {
|
||||
let len = self.values.len();
|
||||
self.values
|
||||
.back()
|
||||
.get(len - 1)
|
||||
.ok_or_else(|| Error("non-empty stack expected".into()))
|
||||
}
|
||||
|
||||
pub fn top_mut(&mut self) -> Result<&mut T, Error> {
|
||||
let len = self.values.len();
|
||||
self.values
|
||||
.back_mut()
|
||||
.get_mut(len - 1)
|
||||
.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)));
|
||||
}
|
||||
|
||||
self.values.push_back(value);
|
||||
self.values.push(value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) -> Result<T, Error> {
|
||||
self.values
|
||||
.pop_back()
|
||||
.pop()
|
||||
.ok_or_else(|| Error("non-empty stack expected".into()))
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use std::rc::{Rc, Weak};
|
||||
use std::fmt;
|
||||
use std::collections::HashMap;
|
||||
use parity_wasm::elements::{Local, Opcodes};
|
||||
use parity_wasm::elements::Local;
|
||||
use {Trap, TrapKind, Signature};
|
||||
use host::Externals;
|
||||
use runner::{check_function_args, Interpreter};
|
||||
use value::RuntimeValue;
|
||||
use module::ModuleInstance;
|
||||
use isa;
|
||||
|
||||
/// Reference to a function (See [`FuncInstance`] for details).
|
||||
///
|
||||
|
@ -158,6 +158,5 @@ impl FuncInstance {
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct FuncBody {
|
||||
pub locals: Vec<Local>,
|
||||
pub opcodes: Opcodes,
|
||||
pub labels: HashMap<usize, usize>,
|
||||
pub code: isa::Instructions,
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
extern crate wabt;
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate assert_matches;
|
||||
|
||||
extern crate parity_wasm;
|
||||
extern crate byteorder;
|
||||
extern crate memory_units as memory_units_crate;
|
||||
|
||||
#[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"
|
||||
}
|
||||
extern crate nan_preserving_float;
|
||||
|
||||
use std::fmt;
|
||||
use std::error;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Error type which can thrown by wasm code or by host environment.
|
||||
///
|
||||
|
@ -358,6 +355,7 @@ mod imports;
|
|||
mod global;
|
||||
mod func;
|
||||
mod types;
|
||||
mod isa;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
@ -380,12 +378,11 @@ pub mod memory_units {
|
|||
|
||||
/// Deserialized module prepared for instantiation.
|
||||
pub struct Module {
|
||||
labels: HashMap<usize, HashMap<usize, usize>>,
|
||||
code_map: Vec<isa::Instructions>,
|
||||
module: parity_wasm::elements::Module,
|
||||
}
|
||||
|
||||
impl Module {
|
||||
|
||||
/// Create `Module` from `parity_wasm::elements::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> {
|
||||
use validation::{validate_module, ValidatedModule};
|
||||
let ValidatedModule {
|
||||
labels,
|
||||
code_map,
|
||||
module,
|
||||
} = validate_module(module)?;
|
||||
|
||||
Ok(Module {
|
||||
labels,
|
||||
code_map,
|
||||
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.
|
||||
///
|
||||
/// This function will deserialize wasm module from a given module,
|
||||
|
@ -467,7 +524,7 @@ impl Module {
|
|||
&self.module
|
||||
}
|
||||
|
||||
pub(crate) fn labels(&self) -> &HashMap<usize, HashMap<usize, usize>> {
|
||||
&self.labels
|
||||
pub(crate) fn code(&self) -> &Vec<isa::Instructions> {
|
||||
&self.code_map
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ impl ::std::ops::Deref for MemoryRef {
|
|||
///
|
||||
/// [`LINEAR_MEMORY_PAGE_SIZE`]: constant.LINEAR_MEMORY_PAGE_SIZE.html
|
||||
pub struct MemoryInstance {
|
||||
/// Memofy limits.
|
||||
/// Memory limits.
|
||||
limits: ResizableLimits,
|
||||
/// Linear memory buffer.
|
||||
buffer: RefCell<Vec<u8>>,
|
||||
|
@ -315,7 +315,7 @@ impl MemoryInstance {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Fill memory region with a specified value.
|
||||
/// Fill the memory region with the specified value.
|
||||
///
|
||||
/// Semantically equivalent to `memset`.
|
||||
///
|
||||
|
@ -330,7 +330,7 @@ impl MemoryInstance {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Fill specified memory region with zeroes.
|
||||
/// Fill the specified memory region with zeroes.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
|
@ -338,6 +338,35 @@ impl MemoryInstance {
|
|||
pub fn zero(&self, offset: usize, len: usize) -> Result<(), Error> {
|
||||
self.clear(offset, 0, len)
|
||||
}
|
||||
|
||||
/// Provides direct access to the underlying memory buffer.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Any call that requires write access to memory (such as [`set`], [`clear`], etc) made within
|
||||
/// the closure will panic. Proceed with caution.
|
||||
///
|
||||
/// [`set`]: #method.get
|
||||
/// [`clear`]: #method.set
|
||||
pub fn with_direct_access<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
|
||||
let buf = self.buffer.borrow();
|
||||
f(&*buf)
|
||||
}
|
||||
|
||||
/// Provides direct mutable access to the underlying memory buffer.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Any calls that requires either read or write access to memory (such as [`get`], [`set`], [`copy`], etc) made
|
||||
/// within the closure will panic. Proceed with caution.
|
||||
///
|
||||
/// [`get`]: #method.get
|
||||
/// [`set`]: #method.set
|
||||
/// [`copy`]: #method.copy
|
||||
pub fn with_direct_access_mut<R, F: FnOnce(&mut [u8]) -> R>(&self, f: F) -> R {
|
||||
let mut buf = self.buffer.borrow_mut();
|
||||
f(&mut *buf)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate_memory(initial: Pages, maximum: Option<Pages>) -> Result<(), String> {
|
||||
|
@ -369,6 +398,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn alloc() {
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
let fixtures = &[
|
||||
(0, None, true),
|
||||
(0, Some(0), true),
|
||||
|
@ -381,6 +411,17 @@ mod tests {
|
|||
(65536, Some(0), false),
|
||||
(65536, None, true),
|
||||
];
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
let fixtures = &[
|
||||
(0, None, true),
|
||||
(0, Some(0), true),
|
||||
(1, None, true),
|
||||
(1, Some(1), true),
|
||||
(0, Some(1), true),
|
||||
(1, Some(0), false),
|
||||
];
|
||||
|
||||
for (index, &(initial, maybe_max, expected_ok)) in fixtures.iter().enumerate() {
|
||||
let initial: Pages = Pages(initial);
|
||||
let maximum: Option<Pages> = maybe_max.map(|m| Pages(m));
|
||||
|
@ -472,4 +513,27 @@ mod tests {
|
|||
|
||||
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(
|
||||
&[],
|
||||
|
@ -308,13 +308,12 @@ impl ModuleInstance {
|
|||
let signature = instance.signature_by_index(ty.type_ref()).expect(
|
||||
"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",
|
||||
).clone();
|
||||
let func_body = FuncBody {
|
||||
locals: body.locals().to_vec(),
|
||||
opcodes: body.code().clone(),
|
||||
labels: labels,
|
||||
code: code,
|
||||
};
|
||||
let func_instance =
|
||||
FuncInstance::alloc_internal(Rc::downgrade(&instance.0), signature, func_body);
|
||||
|
@ -420,7 +419,7 @@ impl ModuleInstance {
|
|||
|
||||
// This check is not only for bailing out early, but also to check the case when
|
||||
// segment consist of 0 members.
|
||||
if offset_val as 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(
|
||||
Error::Instantiation("elements segment does not fit".to_string())
|
||||
);
|
||||
|
|
1230
src/runner.rs
1230
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::fmt;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::HashSet;
|
||||
use parity_wasm::elements::{
|
||||
BlockType, External, GlobalEntry, GlobalType, Internal, MemoryType, Module, Opcode,
|
||||
ResizableLimits, TableType, ValueType, InitExpr, Type
|
||||
|
@ -9,9 +9,11 @@ use common::stack;
|
|||
use self::context::ModuleContextBuilder;
|
||||
use self::func::Validator;
|
||||
use memory_units::Pages;
|
||||
use isa;
|
||||
|
||||
mod context;
|
||||
mod func;
|
||||
mod util;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
@ -39,7 +41,7 @@ impl From<stack::Error> for Error {
|
|||
|
||||
#[derive(Clone)]
|
||||
pub struct ValidatedModule {
|
||||
pub labels: HashMap<usize, HashMap<usize, usize>>,
|
||||
pub code_map: Vec<isa::Instructions>,
|
||||
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> {
|
||||
let mut context_builder = ModuleContextBuilder::new();
|
||||
let mut imported_globals = Vec::new();
|
||||
let mut labels = HashMap::new();
|
||||
let mut code_map = Vec::new();
|
||||
|
||||
// Copy types from module as is.
|
||||
context_builder.set_types(
|
||||
|
@ -143,12 +258,12 @@ pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
|
|||
index
|
||||
)),
|
||||
)?;
|
||||
let func_labels = Validator::validate_function(&context, function, function_body)
|
||||
let code = Validator::validate_function(&context, function, function_body)
|
||||
.map_err(|e| {
|
||||
let Error(ref msg) = e;
|
||||
Error(format!("Function #{} validation error: {}", index, msg))
|
||||
})?;
|
||||
labels.insert(index, func_labels);
|
||||
code_map.push(code);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -260,7 +375,7 @@ pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
|
|||
|
||||
Ok(ValidatedModule {
|
||||
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::elements::{
|
||||
External, GlobalEntry, GlobalType, ImportEntry, InitExpr, MemoryType,
|
||||
Opcode, Opcodes, TableType, ValueType, BlockType
|
||||
Opcode, Opcodes, TableType, ValueType, BlockType, deserialize_buffer,
|
||||
Module,
|
||||
};
|
||||
use isa;
|
||||
use wabt;
|
||||
|
||||
#[test]
|
||||
fn empty_is_valid() {
|
||||
|
@ -299,3 +302,584 @@ fn if_else_with_return_type_validation() {
|
|||
.build();
|
||||
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(_));
|
||||
}
|
||||
}
|
194
src/value.rs
194
src/value.rs
|
@ -1,6 +1,7 @@
|
|||
use std::{i32, i64, u32, u64, f32};
|
||||
use std::io;
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
use nan_preserving_float::{F32, F64};
|
||||
use std::io;
|
||||
use std::{f32, i32, i64, u32, u64};
|
||||
use TrapKind;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -22,9 +23,9 @@ pub enum RuntimeValue {
|
|||
/// Value of 64-bit signed or unsigned integer.
|
||||
I64(i64),
|
||||
/// Value of 32-bit IEEE 754-2008 floating point number.
|
||||
F32(f32),
|
||||
F32(F32),
|
||||
/// Value of 64-bit IEEE 754-2008 floating point number.
|
||||
F64(f64),
|
||||
F64(F64),
|
||||
}
|
||||
|
||||
/// Trait for creating value from a [`RuntimeValue`].
|
||||
|
@ -136,19 +137,19 @@ impl RuntimeValue {
|
|||
match value_type {
|
||||
::types::ValueType::I32 => RuntimeValue::I32(0),
|
||||
::types::ValueType::I64 => RuntimeValue::I64(0),
|
||||
::types::ValueType::F32 => RuntimeValue::F32(0f32),
|
||||
::types::ValueType::F64 => RuntimeValue::F64(0f64),
|
||||
::types::ValueType::F32 => RuntimeValue::F32(0f32.into()),
|
||||
::types::ValueType::F64 => RuntimeValue::F64(0f64.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates new value by interpreting passed u32 as f32.
|
||||
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.
|
||||
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.
|
||||
|
@ -197,14 +198,14 @@ impl From<u64> for RuntimeValue {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<f32> for RuntimeValue {
|
||||
fn from(val: f32) -> Self {
|
||||
impl From<F32> for RuntimeValue {
|
||||
fn from(val: F32) -> Self {
|
||||
RuntimeValue::F32(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for RuntimeValue {
|
||||
fn from(val: f64) -> Self {
|
||||
impl From<F64> for RuntimeValue {
|
||||
fn from(val: F64) -> Self {
|
||||
RuntimeValue::F64(val)
|
||||
}
|
||||
}
|
||||
|
@ -214,7 +215,7 @@ macro_rules! impl_from_runtime_value {
|
|||
impl FromRuntimeValue for $into {
|
||||
fn from_runtime_value(val: RuntimeValue) -> Option<Self> {
|
||||
match val {
|
||||
RuntimeValue::$expected_rt_ty(val) => Some(val as $into),
|
||||
RuntimeValue::$expected_rt_ty(val) => Some(val.transmute_into()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -237,19 +238,26 @@ impl FromRuntimeValue for bool {
|
|||
|
||||
impl_from_runtime_value!(I32, i32);
|
||||
impl_from_runtime_value!(I64, i64);
|
||||
impl_from_runtime_value!(F32, f32);
|
||||
impl_from_runtime_value!(F64, f64);
|
||||
impl_from_runtime_value!(F32, F32);
|
||||
impl_from_runtime_value!(F64, F64);
|
||||
impl_from_runtime_value!(I32, u32);
|
||||
impl_from_runtime_value!(I64, u64);
|
||||
|
||||
macro_rules! impl_wrap_into {
|
||||
($from: ident, $into: ident) => {
|
||||
($from:ident, $into:ident) => {
|
||||
impl WrapInto<$into> for $from {
|
||||
fn wrap_into(self) -> $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);
|
||||
|
@ -257,13 +265,19 @@ impl_wrap_into!(i32, i16);
|
|||
impl_wrap_into!(i64, i8);
|
||||
impl_wrap_into!(i64, i16);
|
||||
impl_wrap_into!(i64, i32);
|
||||
impl_wrap_into!(i64, f32);
|
||||
impl_wrap_into!(u64, f32);
|
||||
impl_wrap_into!(i64, f32, F32);
|
||||
impl_wrap_into!(u64, f32, F32);
|
||||
// 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
|
||||
// largest or smallest finite value representable by f32. This is a bug and will be fixed.
|
||||
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 {
|
||||
($from: ident, $into: ident) => {
|
||||
impl TryTruncateInto<$into, TrapKind> for $from {
|
||||
|
@ -284,7 +298,14 @@ macro_rules! impl_try_truncate_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);
|
||||
|
@ -295,15 +316,30 @@ impl_try_truncate_into!(f32, u32);
|
|||
impl_try_truncate_into!(f32, u64);
|
||||
impl_try_truncate_into!(f64, u32);
|
||||
impl_try_truncate_into!(f64, u64);
|
||||
impl_try_truncate_into!(F32, f32, i32);
|
||||
impl_try_truncate_into!(F32, f32, i64);
|
||||
impl_try_truncate_into!(F64, f64, i32);
|
||||
impl_try_truncate_into!(F64, f64, i64);
|
||||
impl_try_truncate_into!(F32, f32, u32);
|
||||
impl_try_truncate_into!(F32, f32, u64);
|
||||
impl_try_truncate_into!(F64, f64, u32);
|
||||
impl_try_truncate_into!(F64, f64, u64);
|
||||
|
||||
macro_rules! impl_extend_into {
|
||||
($from: ident, $into: ident) => {
|
||||
($from:ident, $into:ident) => {
|
||||
impl ExtendInto<$into> for $from {
|
||||
fn extend_into(self) -> $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);
|
||||
|
@ -325,6 +361,20 @@ impl_extend_into!(i64, f64);
|
|||
impl_extend_into!(u64, 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 {
|
||||
($type: ident) => {
|
||||
impl TransmuteInto<$type> for $type {
|
||||
|
@ -339,6 +389,8 @@ impl_transmute_into_self!(i32);
|
|||
impl_transmute_into_self!(i64);
|
||||
impl_transmute_into_self!(f32);
|
||||
impl_transmute_into_self!(f64);
|
||||
impl_transmute_into_self!(F32);
|
||||
impl_transmute_into_self!(F64);
|
||||
|
||||
macro_rules! impl_transmute_into_as {
|
||||
($from: ident, $into: ident) => {
|
||||
|
@ -357,6 +409,49 @@ impl_transmute_into_as!(u32, i32);
|
|||
impl_transmute_into_as!(i64, u64);
|
||||
impl_transmute_into_as!(u64, i64);
|
||||
|
||||
macro_rules! impl_transmute_into_npf {
|
||||
($npf:ident, $float:ident, $signed:ident, $unsigned:ident) => {
|
||||
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 {
|
||||
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 {
|
||||
($type: ident) => {
|
||||
impl ArithmeticOps<$type> for $type {
|
||||
|
@ -538,6 +653,8 @@ macro_rules! impl_float_arithmetic_ops {
|
|||
|
||||
impl_float_arithmetic_ops!(f32);
|
||||
impl_float_arithmetic_ops!(f64);
|
||||
impl_float_arithmetic_ops!(F32);
|
||||
impl_float_arithmetic_ops!(F64);
|
||||
|
||||
macro_rules! impl_integer {
|
||||
($type: ident) => {
|
||||
|
@ -561,13 +678,26 @@ impl_integer!(i64);
|
|||
impl_integer!(u64);
|
||||
|
||||
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 {
|
||||
fn abs(self) -> $type { self.abs() }
|
||||
fn floor(self) -> $type { self.floor() }
|
||||
fn ceil(self) -> $type { self.ceil() }
|
||||
fn trunc(self) -> $type { self.trunc() }
|
||||
fn round(self) -> $type { self.round() }
|
||||
fn abs(self) -> $type {
|
||||
$intermediate::abs(self.into()).into()
|
||||
}
|
||||
fn floor(self) -> $type {
|
||||
$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 {
|
||||
let round = self.round();
|
||||
if self.fract().abs() != 0.5 {
|
||||
|
@ -583,12 +713,13 @@ macro_rules! impl_float {
|
|||
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.
|
||||
fn min(self, other: $type) -> $type {
|
||||
if self.is_nan() || other.is_nan() {
|
||||
use std::$type;
|
||||
return $type::NAN;
|
||||
return ::std::$intermediate::NAN.into();
|
||||
}
|
||||
|
||||
self.min(other)
|
||||
|
@ -596,8 +727,7 @@ macro_rules! impl_float {
|
|||
// This instruction corresponds to what is sometimes called "maxNaN" in other languages.
|
||||
fn max(self, other: $type) -> $type {
|
||||
if self.is_nan() || other.is_nan() {
|
||||
use std::$type;
|
||||
return $type::NAN;
|
||||
return ::std::$intermediate::NAN.into();
|
||||
}
|
||||
|
||||
self.max(other)
|
||||
|
@ -623,8 +753,10 @@ macro_rules! impl_float {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_float!(f32, i32);
|
||||
impl_float!(f64, i64);
|
||||
impl_float!(F32, f32, i32);
|
||||
impl_float!(F64, f64, i64);
|
||||
|
|
|
@ -2,21 +2,19 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use wasmi::{
|
||||
Error as InterpreterError, Externals, FuncInstance, FuncRef, GlobalDescriptor,
|
||||
use wabt::script::{self, Action, Command, CommandKind, ScriptParser, Value};
|
||||
use wasmi::memory_units::Pages;
|
||||
use wasmi::{Error as InterpreterError, Externals, FuncInstance, FuncRef, GlobalDescriptor,
|
||||
GlobalInstance, GlobalRef, ImportResolver, ImportsBuilder, MemoryDescriptor,
|
||||
MemoryInstance, MemoryRef, Module, ModuleImportResolver, ModuleInstance, ModuleRef,
|
||||
RuntimeArgs, RuntimeValue, Signature, TableDescriptor, TableInstance, TableRef, Trap,
|
||||
};
|
||||
use wasmi::memory_units::Pages;
|
||||
use wabt::script::{self, Action, Command, CommandKind, ScriptParser, Value};
|
||||
RuntimeArgs, RuntimeValue, Signature, TableDescriptor, TableInstance, TableRef, Trap};
|
||||
|
||||
fn spec_to_runtime_value(value: Value) -> RuntimeValue {
|
||||
match value {
|
||||
fn spec_to_runtime_value(val: Value<u32, u64>) -> RuntimeValue {
|
||||
match val {
|
||||
Value::I32(v) => RuntimeValue::I32(v),
|
||||
Value::I64(v) => RuntimeValue::I64(v),
|
||||
Value::F32(v) => RuntimeValue::F32(v),
|
||||
Value::F64(v) => RuntimeValue::F64(v),
|
||||
Value::F32(v) => RuntimeValue::F32(v.into()),
|
||||
Value::F64(v) => RuntimeValue::F64(v.into()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,8 +52,8 @@ impl SpecModule {
|
|||
table: TableInstance::alloc(10, Some(20)).unwrap(),
|
||||
memory: MemoryInstance::alloc(Pages(1), Some(Pages(2))).unwrap(),
|
||||
global_i32: GlobalInstance::alloc(RuntimeValue::I32(666), false),
|
||||
global_f32: GlobalInstance::alloc(RuntimeValue::F32(666.0), false),
|
||||
global_f64: GlobalInstance::alloc(RuntimeValue::F64(666.0), false),
|
||||
global_f32: GlobalInstance::alloc(RuntimeValue::F32(666.0.into()), false),
|
||||
global_f64: GlobalInstance::alloc(RuntimeValue::F64(666.0.into()), false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -121,7 +119,7 @@ impl ModuleImportResolver for SpecModule {
|
|||
_ => Err(InterpreterError::Instantiation(format!(
|
||||
"Unknown host global import {}",
|
||||
field_name
|
||||
)))
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -269,7 +267,11 @@ fn try_load(wasm: &[u8], spec_driver: &mut SpecDriver) -> Result<(), Error> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn load_module(wasm: &[u8], name: &Option<String>, spec_driver: &mut SpecDriver) -> Result<ModuleRef, Error> {
|
||||
fn load_module(
|
||||
wasm: &[u8],
|
||||
name: &Option<String>,
|
||||
spec_driver: &mut SpecDriver,
|
||||
) -> Result<ModuleRef, Error> {
|
||||
let module = try_load_module(wasm)?;
|
||||
let instance = ModuleInstance::new(&module, spec_driver)
|
||||
.map_err(|e| Error::Load(e.to_string()))?
|
||||
|
@ -284,7 +286,7 @@ fn load_module(wasm: &[u8], name: &Option<String>, spec_driver: &mut SpecDriver)
|
|||
|
||||
fn run_action(
|
||||
program: &mut SpecDriver,
|
||||
action: &Action,
|
||||
action: &Action<u32, u64>,
|
||||
) -> Result<Option<RuntimeValue>, InterpreterError> {
|
||||
match *action {
|
||||
Action::Invoke {
|
||||
|
@ -298,14 +300,11 @@ fn run_action(
|
|||
"Expected program to have loaded module {:?}",
|
||||
module
|
||||
));
|
||||
module.invoke_export(
|
||||
field,
|
||||
&args.iter()
|
||||
let vec_args = args.iter()
|
||||
.cloned()
|
||||
.map(spec_to_runtime_value)
|
||||
.collect::<Vec<_>>(),
|
||||
program.spec_module(),
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
module.invoke_export(field, &vec_args, program.spec_module())
|
||||
}
|
||||
Action::Get {
|
||||
ref module,
|
||||
|
@ -342,11 +341,31 @@ fn try_spec(name: &str) -> Result<(), Error> {
|
|||
let mut spec_driver = SpecDriver::new();
|
||||
let spec_script_path = format!("tests/spec/testsuite/{}.wast", name);
|
||||
let mut parser = ScriptParser::from_file(spec_script_path).expect("Can't read spec script");
|
||||
let mut errors = vec![];
|
||||
|
||||
while let Some(Command { kind, line }) = parser.next()? {
|
||||
println!("Line {}:", line);
|
||||
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");
|
||||
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);
|
||||
|
@ -375,7 +394,6 @@ fn try_spec(name: &str) -> Result<(), Error> {
|
|||
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);
|
||||
|
@ -400,7 +418,6 @@ fn try_spec(name: &str) -> Result<(), Error> {
|
|||
}
|
||||
}
|
||||
}
|
||||
println!("assert_return_nan at line {} - success", line);
|
||||
}
|
||||
Err(e) => {
|
||||
panic!("Expected action to return value, got error: {:?}", e);
|
||||
|
@ -411,7 +428,7 @@ fn try_spec(name: &str) -> Result<(), Error> {
|
|||
let result = run_action(&mut spec_driver, &action);
|
||||
match result {
|
||||
Ok(result) => panic!("Expected exhaustion, got result: {:?}", result),
|
||||
Err(e) => println!("assert_exhaustion at line {} - success ({:?})", line, e),
|
||||
Err(_e) => {},
|
||||
}
|
||||
}
|
||||
CommandKind::AssertTrap { action, .. } => {
|
||||
|
@ -423,9 +440,7 @@ fn try_spec(name: &str) -> Result<(), Error> {
|
|||
result
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("assert_trap at line {} - success ({:?})", line, e);
|
||||
}
|
||||
Err(_e) => {}
|
||||
}
|
||||
}
|
||||
CommandKind::AssertInvalid { module, .. }
|
||||
|
@ -434,13 +449,13 @@ fn try_spec(name: &str) -> Result<(), Error> {
|
|||
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),
|
||||
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) => println!("assert_uninstantiable - success ({:?})", e),
|
||||
Err(_e) => {},
|
||||
}
|
||||
}
|
||||
CommandKind::Register { name, as_name, .. } => {
|
||||
|
@ -457,5 +472,14 @@ fn try_spec(name: &str) -> Result<(), Error> {
|
|||
}
|
||||
}
|
||||
|
||||
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