Initial commit
This commit is contained in:
commit
49347a63ee
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
/target/
|
||||||
|
**/*.rs.bk
|
||||||
|
Cargo.lock
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "parity-wasm-interp"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Sergey Pepyakin <s.pepyakin@gmail.com>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# parity-wasm = "0.20"
|
||||||
|
parity-wasm = { path = "/Users/pepyakin/dev/parity/parity-wasm" }
|
||||||
|
wabt = "0.1"
|
||||||
|
byteorder = "1.0"
|
Binary file not shown.
|
@ -0,0 +1,94 @@
|
||||||
|
;; /// @file accumulate_u8.cpp
|
||||||
|
;; #include <emscripten.h> // macro EMSCRIPTEN_KEEPALIVE
|
||||||
|
;; #include <stdint.h>
|
||||||
|
;; #include <vector>
|
||||||
|
;; #include <numeric>
|
||||||
|
;; extern "C" {
|
||||||
|
;; int32_t EMSCRIPTEN_KEEPALIVE accumulate_u8(const int32_t arlen, const uint8_t *ar) {
|
||||||
|
;; int32_t arsum = 0;
|
||||||
|
;; for (int32_t i=0; i<arlen; ++i)
|
||||||
|
;; arsum += (int32_t) ar[i];
|
||||||
|
;; return arsum;
|
||||||
|
;; }
|
||||||
|
;; } // extern "C"
|
||||||
|
(module
|
||||||
|
(type $0 (func (param i32 i32) (result i32)))
|
||||||
|
(type $1 (func))
|
||||||
|
(import "env" "memoryBase" (global $import$0 i32))
|
||||||
|
(import "env" "memory" (memory $0 256))
|
||||||
|
(import "env" "table" (table 0 anyfunc))
|
||||||
|
(import "env" "tableBase" (global $import$3 i32))
|
||||||
|
(global $global$0 (mut i32) (i32.const 0))
|
||||||
|
(global $global$1 (mut i32) (i32.const 0))
|
||||||
|
(export "__post_instantiate" (func $2))
|
||||||
|
(export "_accumulate_u8" (func $0))
|
||||||
|
(func $0 (type $0) (param $var$0 i32) (param $var$1 i32) (result i32)
|
||||||
|
(local $var$2 i32)
|
||||||
|
(local $var$3 i32)
|
||||||
|
(block $label$0 (result i32)
|
||||||
|
(if
|
||||||
|
(i32.gt_s
|
||||||
|
(get_local $var$0)
|
||||||
|
(i32.const 0)
|
||||||
|
)
|
||||||
|
(block $label$1
|
||||||
|
(set_local $var$2
|
||||||
|
(i32.const 0)
|
||||||
|
)
|
||||||
|
(set_local $var$3
|
||||||
|
(i32.const 0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(block $label$2
|
||||||
|
(return
|
||||||
|
(i32.const 0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(loop $label$3
|
||||||
|
(set_local $var$3
|
||||||
|
(i32.add
|
||||||
|
(i32.load8_u
|
||||||
|
(i32.add
|
||||||
|
(get_local $var$1)
|
||||||
|
(get_local $var$2)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(get_local $var$3)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(br_if $label$3
|
||||||
|
(i32.ne
|
||||||
|
(tee_local $var$2
|
||||||
|
(i32.add
|
||||||
|
(get_local $var$2)
|
||||||
|
(i32.const 1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(get_local $var$0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(get_local $var$3)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(func $1 (type $1)
|
||||||
|
(nop)
|
||||||
|
)
|
||||||
|
(func $2 (type $1)
|
||||||
|
(block $label$0
|
||||||
|
(set_global $global$0
|
||||||
|
(get_global $import$0)
|
||||||
|
)
|
||||||
|
(set_global $global$1
|
||||||
|
(i32.add
|
||||||
|
(get_global $global$0)
|
||||||
|
(i32.const 5242880)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(call $1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
;; custom section "dylink", size 5
|
||||||
|
)
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,24 @@
|
||||||
|
(module
|
||||||
|
(type (;0;) (func (result i32)))
|
||||||
|
(func (;0;) (type 0) (result i32)
|
||||||
|
i64.const 9223372036854775807
|
||||||
|
i64.const -9223372036854775808
|
||||||
|
i64.const -1152894205662152753
|
||||||
|
i64.const -8192
|
||||||
|
i32.const 1024
|
||||||
|
i32.const 2048
|
||||||
|
i32.const 4096
|
||||||
|
i32.const 8192
|
||||||
|
i32.const 16384
|
||||||
|
i32.const 32767
|
||||||
|
i32.const -1024
|
||||||
|
i32.const -2048
|
||||||
|
i32.const -4096
|
||||||
|
i32.const -8192
|
||||||
|
i32.const -16384
|
||||||
|
i32.const -32768
|
||||||
|
i32.const -2147483648
|
||||||
|
i32.const 2147483647
|
||||||
|
return
|
||||||
|
)
|
||||||
|
)
|
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
|
@ -0,0 +1,16 @@
|
||||||
|
(module
|
||||||
|
(type (;0;) (func (result i32)))
|
||||||
|
(func (;0;) (type 0) (result i32)
|
||||||
|
(local i32)
|
||||||
|
i32.const 0
|
||||||
|
set_local 0
|
||||||
|
i32.const 0
|
||||||
|
if i32
|
||||||
|
i32.const 5
|
||||||
|
else
|
||||||
|
i32.const 7
|
||||||
|
end
|
||||||
|
set_local 0
|
||||||
|
get_local 0
|
||||||
|
return)
|
||||||
|
)
|
Binary file not shown.
|
@ -0,0 +1,45 @@
|
||||||
|
;; /// @file inc_i32.cpp
|
||||||
|
;; #include <emscripten.h> // macro EMSCRIPTEN_KEEPALIVE
|
||||||
|
;; #include <stdint.h>
|
||||||
|
;; extern "C" {
|
||||||
|
;; uint32_t EMSCRIPTEN_KEEPALIVE inc_i32(uint32_t param) {
|
||||||
|
;; return ++param;
|
||||||
|
;; }
|
||||||
|
;; } // extern "C"
|
||||||
|
(module
|
||||||
|
(type $0 (func (param i32) (result i32)))
|
||||||
|
(type $1 (func))
|
||||||
|
(import "env" "memoryBase" (global $import$0 i32))
|
||||||
|
(import "env" "memory" (memory $0 256))
|
||||||
|
(import "env" "table" (table 0 anyfunc))
|
||||||
|
(import "env" "tableBase" (global $import$3 i32))
|
||||||
|
(global $global$0 (mut i32) (i32.const 0))
|
||||||
|
(global $global$1 (mut i32) (i32.const 0))
|
||||||
|
(export "_inc_i32" (func $0))
|
||||||
|
(export "__post_instantiate" (func $2))
|
||||||
|
(func $0 (type $0) (param $var$0 i32) (result i32)
|
||||||
|
(i32.add
|
||||||
|
(get_local $var$0)
|
||||||
|
(i32.const 1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(func $1 (type $1)
|
||||||
|
(nop)
|
||||||
|
)
|
||||||
|
(func $2 (type $1)
|
||||||
|
(block $label$0
|
||||||
|
(set_global $global$0
|
||||||
|
(get_global $import$0)
|
||||||
|
)
|
||||||
|
(set_global $global$1
|
||||||
|
(i32.add
|
||||||
|
(get_global $global$0)
|
||||||
|
(i32.const 5242880)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(call $1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
;; custom section "dylink", size 5
|
||||||
|
)
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,11 @@
|
||||||
|
(module
|
||||||
|
(import "env" "memory" (memory (;0;) 256 256))
|
||||||
|
(type (;0;) (func (result i32)))
|
||||||
|
(func (;0;) (type 0) (result i32)
|
||||||
|
(local i32)
|
||||||
|
get_local 0
|
||||||
|
i64.const 72340172838076673
|
||||||
|
i64.store offset=32 align=1
|
||||||
|
i32.const 1
|
||||||
|
)
|
||||||
|
)
|
Binary file not shown.
|
@ -0,0 +1,20 @@
|
||||||
|
(module
|
||||||
|
(type (;0;) (func (param i32 i32) (result i32)))
|
||||||
|
(type (;1;) (func (param i32)))
|
||||||
|
(type (;2;) (func (param i32) (result i64)))
|
||||||
|
(type (;3;) (func (param i32 i32 i32)))
|
||||||
|
(type (;4;) (func (param i32 i32 i32 i32)))
|
||||||
|
(type (;5;) (func (result i32)))
|
||||||
|
(type (;6;) (func))
|
||||||
|
(type (;7;) (func (param i32 i32 i32) (result i32)))
|
||||||
|
(type (;8;) (func (param i32 i32)))
|
||||||
|
(type (;9;) (func (param i32) (result i32)))
|
||||||
|
(type (;10;) (func (param i32 i32 i32 i32) (result i32)))
|
||||||
|
(type (;11;) (func (param i32 i32 i32 i32 i32)))
|
||||||
|
(type (;12;) (func (param i32 i32 i64 i32 i32) (result i32)))
|
||||||
|
(type (;13;) (func (param i32 i32 i32 i32 i32 i32) (result i32)))
|
||||||
|
(type (;14;) (func (param i32 i32 i32 i32 i32 i32 i32)))
|
||||||
|
(type (;15;) (func (param i32 i32 i32 i32 i32 i32) (result i32)))
|
||||||
|
(type (;16;) (func (param i32 i32 i32 i32 i32) (result i32)))
|
||||||
|
(type (;17;) (func (param i32 i32) (result i64)))
|
||||||
|
)
|
Binary file not shown.
|
@ -0,0 +1,5 @@
|
||||||
|
(module
|
||||||
|
(type (;0;) (func (param i32 i32) (result i32)))
|
||||||
|
(type (;1;) (func (param i32)))
|
||||||
|
(import "env" "abort" (func (;0;) (type 1)))
|
||||||
|
)
|
Binary file not shown.
|
@ -0,0 +1,21 @@
|
||||||
|
(module
|
||||||
|
(type (;0;) (func (param i32) (result i32)))
|
||||||
|
(global (;0;) (mut i32) (i32.const 0))
|
||||||
|
(func (;55;) (type 0) (param i32) (result i32)
|
||||||
|
(local i32)
|
||||||
|
block i32 ;; label = @1
|
||||||
|
get_global 0
|
||||||
|
set_local 1
|
||||||
|
get_global 0
|
||||||
|
get_local 0
|
||||||
|
i32.add
|
||||||
|
set_global 0
|
||||||
|
get_global 0
|
||||||
|
i32.const 15
|
||||||
|
i32.add
|
||||||
|
i32.const -16
|
||||||
|
i32.and
|
||||||
|
set_global 0
|
||||||
|
get_local 1
|
||||||
|
end)
|
||||||
|
)
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,26 @@
|
||||||
|
#![feature(link_args)]
|
||||||
|
#![feature(lang_items)]
|
||||||
|
#![feature(start)]
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
#[lang="panic_fmt"]
|
||||||
|
extern fn panic_fmt(_: ::core::fmt::Arguments, _: &'static str, _: u32) -> ! {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[lang = "eh_personality"]
|
||||||
|
extern fn eh_personality() {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[link_args = "-s EXPORTED_FUNCTIONS=['_hello_world']"]
|
||||||
|
extern {}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub fn hello_world() -> isize {
|
||||||
|
45 + 99
|
||||||
|
}
|
||||||
|
|
||||||
|
#[start]
|
||||||
|
fn main(argc: isize, argv: *const *const u8) -> isize {
|
||||||
|
/* Intentionally left blank */
|
||||||
|
0
|
||||||
|
}
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,21 @@
|
||||||
|
#![feature(link_args)]
|
||||||
|
#![feature(lang_items)]
|
||||||
|
#![feature(start)]
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[lang="panic_fmt"]
|
||||||
|
extern fn panic_fmt(_: ::core::fmt::Arguments, _: &'static str, _: u32) {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[lang = "eh_personality"]
|
||||||
|
extern fn eh_personality() {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[link_args = "-s WASM=1 -s NO_EXIT_RUNTIME=1 -s NO_FILESYSTEM=1"]
|
||||||
|
extern {}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub fn hello_world() -> isize {
|
||||||
|
45 + 99
|
||||||
|
}
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,44 @@
|
||||||
|
use parity_wasm::elements::BlockType;
|
||||||
|
|
||||||
|
pub mod stack;
|
||||||
|
|
||||||
|
/// Index of default linear memory.
|
||||||
|
pub const DEFAULT_MEMORY_INDEX: u32 = 0;
|
||||||
|
/// Index of default table.
|
||||||
|
pub const DEFAULT_TABLE_INDEX: u32 = 0;
|
||||||
|
/// Maximum number of entries in value stack.
|
||||||
|
pub const DEFAULT_VALUE_STACK_LIMIT: usize = 16384;
|
||||||
|
/// Maximum number of entries in frame stack.
|
||||||
|
pub const DEFAULT_FRAME_STACK_LIMIT: usize = 1024;
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::error;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Error(String);
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for Error {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stack with limit.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct StackWithLimit<T> where T: Clone {
|
||||||
|
/// Stack values.
|
||||||
|
values: VecDeque<T>,
|
||||||
|
/// Stack limit (maximal stack len).
|
||||||
|
limit: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> StackWithLimit<T> where T: Clone {
|
||||||
|
pub fn with_data<D: IntoIterator<Item=T>>(data: D, limit: usize) -> Self {
|
||||||
|
StackWithLimit {
|
||||||
|
values: data.into_iter().collect(),
|
||||||
|
limit: limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_limit(limit: usize) -> Self {
|
||||||
|
StackWithLimit {
|
||||||
|
values: VecDeque::new(),
|
||||||
|
limit: limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.values.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.values.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn limit(&self) -> usize {
|
||||||
|
self.limit
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn top(&self) -> Result<&T, Error> {
|
||||||
|
self.values
|
||||||
|
.back()
|
||||||
|
.ok_or(Error("non-empty stack expected".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, index: usize) -> Result<&T, Error> {
|
||||||
|
if index >= self.values.len() {
|
||||||
|
return Err(Error(format!("trying to get value at position {} on stack of size {}", index, self.values.len())));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self.values.get(self.values.len() - 1 - index).expect("checked couple of lines above"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&mut self, value: T) -> Result<(), Error> {
|
||||||
|
if self.values.len() >= self.limit {
|
||||||
|
return Err(Error(format!("exceeded stack limit {}", self.limit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.values.push_back(value);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop(&mut self) -> Result<T, Error> {
|
||||||
|
self.values
|
||||||
|
.pop_back()
|
||||||
|
.ok_or(Error("non-empty stack expected".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resize(&mut self, new_size: usize, dummy: T) {
|
||||||
|
debug_assert!(new_size <= self.values.len());
|
||||||
|
self.values.resize(new_size, dummy);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,139 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::fmt;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use parity_wasm::elements::{FunctionType, Local, Opcodes};
|
||||||
|
use Error;
|
||||||
|
use host::Externals;
|
||||||
|
use runner::{prepare_function_args, FunctionContext, Interpreter};
|
||||||
|
use value::RuntimeValue;
|
||||||
|
use module::ModuleRef;
|
||||||
|
use common::stack::StackWithLimit;
|
||||||
|
use common::{DEFAULT_FRAME_STACK_LIMIT, DEFAULT_VALUE_STACK_LIMIT};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct FuncRef(Rc<FuncInstance>);
|
||||||
|
|
||||||
|
impl ::std::ops::Deref for FuncRef {
|
||||||
|
type Target = FuncInstance;
|
||||||
|
fn deref(&self) -> &FuncInstance {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum FuncInstance {
|
||||||
|
Internal {
|
||||||
|
func_type: Rc<FunctionType>,
|
||||||
|
module: ModuleRef,
|
||||||
|
body: Rc<FuncBody>,
|
||||||
|
},
|
||||||
|
Host {
|
||||||
|
func_type: FunctionType,
|
||||||
|
host_func_index: usize,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for FuncInstance {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
&FuncInstance::Internal {
|
||||||
|
ref func_type,
|
||||||
|
ref module,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Internal {{ type={:?}, module={:?} }}",
|
||||||
|
func_type,
|
||||||
|
module
|
||||||
|
)
|
||||||
|
}
|
||||||
|
&FuncInstance::Host { ref func_type, .. } => {
|
||||||
|
write!(f, "Host {{ type={:?} }}", func_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FuncInstance {
|
||||||
|
pub(crate) fn alloc_internal(
|
||||||
|
module: ModuleRef,
|
||||||
|
func_type: Rc<FunctionType>,
|
||||||
|
body: FuncBody,
|
||||||
|
) -> FuncRef {
|
||||||
|
let func = FuncInstance::Internal {
|
||||||
|
func_type,
|
||||||
|
module: module,
|
||||||
|
body: Rc::new(body),
|
||||||
|
};
|
||||||
|
FuncRef(Rc::new(func))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn alloc_host(func_type: FunctionType, host_func_index: usize) -> FuncRef {
|
||||||
|
let func = FuncInstance::Host {
|
||||||
|
func_type,
|
||||||
|
host_func_index,
|
||||||
|
};
|
||||||
|
FuncRef(Rc::new(func))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn func_type(&self) -> &FunctionType {
|
||||||
|
match *self {
|
||||||
|
FuncInstance::Internal { ref func_type, .. } => func_type,
|
||||||
|
FuncInstance::Host { ref func_type, .. } => func_type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn body(&self) -> Option<Rc<FuncBody>> {
|
||||||
|
match *self {
|
||||||
|
FuncInstance::Internal { ref body, .. } => Some(Rc::clone(body)),
|
||||||
|
FuncInstance::Host { .. } => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn invoke<E: Externals>(
|
||||||
|
func: FuncRef,
|
||||||
|
args: Cow<[RuntimeValue]>,
|
||||||
|
externals: &mut E,
|
||||||
|
) -> Result<Option<RuntimeValue>, Error> {
|
||||||
|
enum InvokeKind<'a> {
|
||||||
|
Internal(FunctionContext),
|
||||||
|
Host(usize, &'a [RuntimeValue]),
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = match *func {
|
||||||
|
FuncInstance::Internal { ref func_type, .. } => {
|
||||||
|
let mut stack =
|
||||||
|
StackWithLimit::with_data(args.into_iter().cloned(), DEFAULT_VALUE_STACK_LIMIT);
|
||||||
|
let args = prepare_function_args(func_type, &mut stack)?;
|
||||||
|
let context = FunctionContext::new(
|
||||||
|
func.clone(),
|
||||||
|
DEFAULT_VALUE_STACK_LIMIT,
|
||||||
|
DEFAULT_FRAME_STACK_LIMIT,
|
||||||
|
func_type,
|
||||||
|
args,
|
||||||
|
);
|
||||||
|
InvokeKind::Internal(context)
|
||||||
|
}
|
||||||
|
FuncInstance::Host { ref host_func_index, .. } => {
|
||||||
|
InvokeKind::Host(*host_func_index, &*args)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match result {
|
||||||
|
InvokeKind::Internal(ctx) => {
|
||||||
|
let mut interpreter = Interpreter::new(externals);
|
||||||
|
interpreter.run_function(ctx)
|
||||||
|
}
|
||||||
|
InvokeKind::Host(host_func, args) => externals.invoke_index(host_func, args),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct FuncBody {
|
||||||
|
pub locals: Vec<Local>,
|
||||||
|
pub opcodes: Opcodes,
|
||||||
|
pub labels: HashMap<usize, usize>,
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::cell::Cell;
|
||||||
|
use parity_wasm::elements::ValueType;
|
||||||
|
use value::RuntimeValue;
|
||||||
|
use Error;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct GlobalRef(Rc<GlobalInstance>);
|
||||||
|
|
||||||
|
impl ::std::ops::Deref for GlobalRef {
|
||||||
|
type Target = GlobalInstance;
|
||||||
|
fn deref(&self) -> &GlobalInstance {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GlobalInstance {
|
||||||
|
val: Cell<RuntimeValue>,
|
||||||
|
mutable: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GlobalInstance {
|
||||||
|
|
||||||
|
pub fn alloc(val: RuntimeValue, mutable: bool) -> GlobalRef {
|
||||||
|
let global = GlobalInstance::new(val, mutable);
|
||||||
|
GlobalRef(Rc::new(global))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(val: RuntimeValue, mutable: bool) -> GlobalInstance {
|
||||||
|
GlobalInstance {
|
||||||
|
val: Cell::new(val),
|
||||||
|
mutable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&self, val: RuntimeValue) -> Result<(), Error> {
|
||||||
|
if !self.mutable {
|
||||||
|
return Err(Error::Global("Attempt to change an immutable variable".into()));
|
||||||
|
}
|
||||||
|
if self.value_type() != val.value_type() {
|
||||||
|
return Err(Error::Global("Attempt to change variable type".into()));
|
||||||
|
}
|
||||||
|
self.val.set(val);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self) -> RuntimeValue {
|
||||||
|
self.val.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_mutable(&self) -> bool {
|
||||||
|
self.mutable
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn value_type(&self) -> ValueType {
|
||||||
|
self.val.get().value_type()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
use std::any::TypeId;
|
||||||
|
use value::RuntimeValue;
|
||||||
|
use Error;
|
||||||
|
|
||||||
|
/// Custom user error.
|
||||||
|
pub trait HostError: 'static + ::std::fmt::Display + ::std::fmt::Debug {
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn __private_get_type_id__(&self) -> TypeId {
|
||||||
|
TypeId::of::<Self>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HostError {
|
||||||
|
/// Attempt to downcast this `HostError` to a concrete type by reference.
|
||||||
|
pub fn downcast_ref<T: HostError>(&self) -> Option<&T> {
|
||||||
|
if self.__private_get_type_id__() == TypeId::of::<T>() {
|
||||||
|
unsafe { Some(&*(self as *const HostError as *const T)) }
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to downcast this `HostError` to a concrete type by mutable
|
||||||
|
/// reference.
|
||||||
|
pub fn downcast_mut<T: HostError>(&mut self) -> Option<&mut T> {
|
||||||
|
if self.__private_get_type_id__() == TypeId::of::<T>() {
|
||||||
|
unsafe { Some(&mut *(self as *mut HostError as *mut T)) }
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Externals {
|
||||||
|
fn invoke_index(
|
||||||
|
&mut self,
|
||||||
|
index: usize,
|
||||||
|
args: &[RuntimeValue],
|
||||||
|
) -> Result<Option<RuntimeValue>, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NopExternals;
|
||||||
|
|
||||||
|
impl Externals for NopExternals {
|
||||||
|
fn invoke_index(
|
||||||
|
&mut self,
|
||||||
|
_index: usize,
|
||||||
|
_args: &[RuntimeValue],
|
||||||
|
) -> Result<Option<RuntimeValue>, Error> {
|
||||||
|
Err(Error::Trap("invoke index on no-op externals".into()))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,221 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use parity_wasm::elements::{FunctionType, GlobalType, MemoryType, TableType};
|
||||||
|
use global::GlobalRef;
|
||||||
|
use memory::MemoryRef;
|
||||||
|
use func::FuncRef;
|
||||||
|
use table::TableRef;
|
||||||
|
use module::ModuleRef;
|
||||||
|
use Error;
|
||||||
|
|
||||||
|
pub trait ImportResolver {
|
||||||
|
fn resolve_func(
|
||||||
|
&self,
|
||||||
|
module_name: &str,
|
||||||
|
field_name: &str,
|
||||||
|
func_type: &FunctionType,
|
||||||
|
) -> Result<FuncRef, Error>;
|
||||||
|
|
||||||
|
fn resolve_global(
|
||||||
|
&self,
|
||||||
|
module_name: &str,
|
||||||
|
field_name: &str,
|
||||||
|
global_type: &GlobalType,
|
||||||
|
) -> Result<GlobalRef, Error>;
|
||||||
|
|
||||||
|
fn resolve_memory(
|
||||||
|
&self,
|
||||||
|
module_name: &str,
|
||||||
|
field_name: &str,
|
||||||
|
memory_type: &MemoryType,
|
||||||
|
) -> Result<MemoryRef, Error>;
|
||||||
|
|
||||||
|
fn resolve_table(
|
||||||
|
&self,
|
||||||
|
module_name: &str,
|
||||||
|
field_name: &str,
|
||||||
|
table_type: &TableType,
|
||||||
|
) -> Result<TableRef, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ImportsBuilder<'a> {
|
||||||
|
modules: HashMap<String, &'a ModuleImportResolver>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Default for ImportsBuilder<'a> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ImportsBuilder<'a> {
|
||||||
|
pub fn new() -> ImportsBuilder<'a> {
|
||||||
|
ImportsBuilder { modules: HashMap::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_resolver<N: Into<String>>(
|
||||||
|
mut self,
|
||||||
|
name: N,
|
||||||
|
resolver: &'a ModuleImportResolver,
|
||||||
|
) -> Self {
|
||||||
|
self.modules.insert(name.into(), resolver);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_resolver<N: Into<String>>(&mut self, name: N, resolver: &'a ModuleImportResolver) {
|
||||||
|
self.modules.insert(name.into(), resolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolver(&self, name: &str) -> Option<&ModuleImportResolver> {
|
||||||
|
self.modules.get(name).cloned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ImportResolver for ImportsBuilder<'a> {
|
||||||
|
fn resolve_func(
|
||||||
|
&self,
|
||||||
|
module_name: &str,
|
||||||
|
field_name: &str,
|
||||||
|
func_type: &FunctionType,
|
||||||
|
) -> Result<FuncRef, Error> {
|
||||||
|
self.resolver(module_name).ok_or_else(||
|
||||||
|
Error::Instantiation(format!("Module {} not found", module_name))
|
||||||
|
)?.resolve_func(field_name, func_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_global(
|
||||||
|
&self,
|
||||||
|
module_name: &str,
|
||||||
|
field_name: &str,
|
||||||
|
global_type: &GlobalType,
|
||||||
|
) -> Result<GlobalRef, Error> {
|
||||||
|
self.resolver(module_name).ok_or_else(||
|
||||||
|
Error::Instantiation(format!("Module {} not found", module_name))
|
||||||
|
)?.resolve_global(field_name, global_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_memory(
|
||||||
|
&self,
|
||||||
|
module_name: &str,
|
||||||
|
field_name: &str,
|
||||||
|
memory_type: &MemoryType,
|
||||||
|
) -> Result<MemoryRef, Error> {
|
||||||
|
self.resolver(module_name).ok_or_else(||
|
||||||
|
Error::Instantiation(format!("Module {} not found", module_name))
|
||||||
|
)?.resolve_memory(field_name, memory_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_table(
|
||||||
|
&self,
|
||||||
|
module_name: &str,
|
||||||
|
field_name: &str,
|
||||||
|
table_type: &TableType,
|
||||||
|
) -> Result<TableRef, Error> {
|
||||||
|
self.resolver(module_name).ok_or_else(||
|
||||||
|
Error::Instantiation(format!("Module {} not found", module_name))
|
||||||
|
)?.resolve_table(field_name, table_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ModuleImportResolver {
|
||||||
|
fn resolve_func(
|
||||||
|
&self,
|
||||||
|
field_name: &str,
|
||||||
|
_func_type: &FunctionType,
|
||||||
|
) -> Result<FuncRef, Error> {
|
||||||
|
Err(Error::Instantiation(
|
||||||
|
format!("Export {} not found", field_name),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_global(
|
||||||
|
&self,
|
||||||
|
field_name: &str,
|
||||||
|
_global_type: &GlobalType,
|
||||||
|
) -> Result<GlobalRef, Error> {
|
||||||
|
Err(Error::Instantiation(
|
||||||
|
format!("Export {} not found", field_name),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_memory(
|
||||||
|
&self,
|
||||||
|
field_name: &str,
|
||||||
|
_memory_type: &MemoryType,
|
||||||
|
) -> Result<MemoryRef, Error> {
|
||||||
|
Err(Error::Instantiation(
|
||||||
|
format!("Export {} not found", field_name),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_table(
|
||||||
|
&self,
|
||||||
|
field_name: &str,
|
||||||
|
_table_type: &TableType,
|
||||||
|
) -> Result<TableRef, Error> {
|
||||||
|
Err(Error::Instantiation(
|
||||||
|
format!("Export {} not found", field_name),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModuleImportResolver for ModuleRef {
|
||||||
|
fn resolve_func(
|
||||||
|
&self,
|
||||||
|
field_name: &str,
|
||||||
|
_func_type: &FunctionType,
|
||||||
|
) -> Result<FuncRef, Error> {
|
||||||
|
Ok(self.export_by_name(field_name)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::Instantiation(format!("Export {} not found", field_name))
|
||||||
|
})?
|
||||||
|
.as_func()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::Instantiation(format!("Export {} is not a function", field_name))
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_global(
|
||||||
|
&self,
|
||||||
|
field_name: &str,
|
||||||
|
_global_type: &GlobalType,
|
||||||
|
) -> Result<GlobalRef, Error> {
|
||||||
|
Ok(self.export_by_name(field_name)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::Instantiation(format!("Export {} not found", field_name))
|
||||||
|
})?
|
||||||
|
.as_global()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::Instantiation(format!("Export {} is not a global", field_name))
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_memory(
|
||||||
|
&self,
|
||||||
|
field_name: &str,
|
||||||
|
_memory_type: &MemoryType,
|
||||||
|
) -> Result<MemoryRef, Error> {
|
||||||
|
Ok(self.export_by_name(field_name)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::Instantiation(format!("Export {} not found", field_name))
|
||||||
|
})?
|
||||||
|
.as_memory()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::Instantiation(format!("Export {} is not a memory", field_name))
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_table(
|
||||||
|
&self,
|
||||||
|
field_name: &str,
|
||||||
|
_table_type: &TableType,
|
||||||
|
) -> Result<TableRef, Error> {
|
||||||
|
Ok(self.export_by_name(field_name)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::Instantiation(format!("Export {} not found", field_name))
|
||||||
|
})?
|
||||||
|
.as_table()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::Instantiation(format!("Export {} is not a table", field_name))
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
//! WebAssembly interpreter module.
|
||||||
|
|
||||||
|
// TODO(pepyakin): Fix these asap
|
||||||
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
extern crate wabt;
|
||||||
|
extern crate parity_wasm;
|
||||||
|
extern crate byteorder;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
use std::error;
|
||||||
|
|
||||||
|
/// Internal interpreter error.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
/// Error while instantiating a module. Might occur when provided
|
||||||
|
/// with incorrect exports (i.e. linkage failure).
|
||||||
|
Instantiation(String),
|
||||||
|
/// Function-level error.
|
||||||
|
Function(String),
|
||||||
|
/// Table-level error.
|
||||||
|
Table(String),
|
||||||
|
/// Memory-level error.
|
||||||
|
Memory(String),
|
||||||
|
/// Global-level error.
|
||||||
|
Global(String),
|
||||||
|
/// Stack-level error.
|
||||||
|
Stack(String),
|
||||||
|
/// Value-level error.
|
||||||
|
Value(String),
|
||||||
|
/// Trap.
|
||||||
|
Trap(String),
|
||||||
|
/// Custom embedder error.
|
||||||
|
Host(Box<host::HostError>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<String> for Error {
|
||||||
|
fn into(self) -> String {
|
||||||
|
match self {
|
||||||
|
Error::Instantiation(s) => s,
|
||||||
|
Error::Function(s) => s,
|
||||||
|
Error::Table(s) => s,
|
||||||
|
Error::Memory(s) => s,
|
||||||
|
Error::Global(s) => s,
|
||||||
|
Error::Stack(s) => s,
|
||||||
|
Error::Value(s) => s,
|
||||||
|
Error::Trap(s) => format!("trap: {}", s),
|
||||||
|
Error::Host(e) => format!("user: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
Error::Instantiation(ref s) => write!(f, "Instantiation: {}", s),
|
||||||
|
Error::Function(ref s) => write!(f, "Function: {}", s),
|
||||||
|
Error::Table(ref s) => write!(f, "Table: {}", s),
|
||||||
|
Error::Memory(ref s) => write!(f, "Memory: {}", s),
|
||||||
|
Error::Global(ref s) => write!(f, "Global: {}", s),
|
||||||
|
Error::Stack(ref s) => write!(f, "Stack: {}", s),
|
||||||
|
Error::Value(ref s) => write!(f, "Value: {}", s),
|
||||||
|
Error::Trap(ref s) => write!(f, "Trap: {}", s),
|
||||||
|
Error::Host(ref e) => write!(f, "User: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
impl error::Error for Error {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
match *self {
|
||||||
|
Error::Instantiation(ref s) => s,
|
||||||
|
Error::Function(ref s) => s,
|
||||||
|
Error::Table(ref s) => s,
|
||||||
|
Error::Memory(ref s) => s,
|
||||||
|
Error::Global(ref s) => s,
|
||||||
|
Error::Stack(ref s) => s,
|
||||||
|
Error::Value(ref s) => s,
|
||||||
|
Error::Trap(ref s) => s,
|
||||||
|
Error::Host(_) => "Host error",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<U> From<U> for Error where U: host::HostError + Sized {
|
||||||
|
fn from(e: U) -> Self {
|
||||||
|
Error::Host(Box::new(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<::common::stack::Error> for Error {
|
||||||
|
fn from(e: ::common::stack::Error) -> Self {
|
||||||
|
Error::Stack(e.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod validation;
|
||||||
|
mod common;
|
||||||
|
mod memory;
|
||||||
|
mod module;
|
||||||
|
mod runner;
|
||||||
|
mod table;
|
||||||
|
mod value;
|
||||||
|
mod host;
|
||||||
|
mod imports;
|
||||||
|
mod global;
|
||||||
|
mod func;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
pub use self::memory::{MemoryInstance, MemoryRef};
|
||||||
|
pub use self::table::{TableInstance, TableRef};
|
||||||
|
pub use self::value::{RuntimeValue, TryInto};
|
||||||
|
pub use self::host::{Externals, NopExternals, HostError};
|
||||||
|
pub use self::imports::{ModuleImportResolver, ImportResolver, ImportsBuilder};
|
||||||
|
pub use self::module::{ModuleInstance, ModuleRef, ExternVal, NotStartedModuleRef};
|
||||||
|
pub use self::global::{GlobalInstance, GlobalRef};
|
||||||
|
pub use self::func::{FuncInstance, FuncRef};
|
|
@ -0,0 +1,320 @@
|
||||||
|
use std::u32;
|
||||||
|
use std::ops::Range;
|
||||||
|
use std::cmp;
|
||||||
|
use std::fmt;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use parity_wasm::elements::ResizableLimits;
|
||||||
|
use Error;
|
||||||
|
use module::check_limits;
|
||||||
|
|
||||||
|
/// Linear memory page size.
|
||||||
|
pub const LINEAR_MEMORY_PAGE_SIZE: u32 = 65536;
|
||||||
|
/// Maximal number of pages.
|
||||||
|
const LINEAR_MEMORY_MAX_PAGES: u32 = 65536;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct MemoryRef(Rc<MemoryInstance>);
|
||||||
|
|
||||||
|
impl ::std::ops::Deref for MemoryRef {
|
||||||
|
type Target = MemoryInstance;
|
||||||
|
fn deref(&self) -> &MemoryInstance {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Linear memory instance.
|
||||||
|
pub struct MemoryInstance {
|
||||||
|
/// Memofy limits.
|
||||||
|
limits: ResizableLimits,
|
||||||
|
/// Linear memory buffer.
|
||||||
|
buffer: RefCell<Vec<u8>>,
|
||||||
|
/// Maximum buffer size.
|
||||||
|
maximum_size: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for MemoryInstance {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_struct("MemoryInstance")
|
||||||
|
.field("limits", &self.limits)
|
||||||
|
.field("buffer.len", &self.buffer.borrow().len())
|
||||||
|
.field("maximum_size", &self.maximum_size)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CheckedRegion<'a, B: 'a> where B: ::std::ops::Deref<Target=Vec<u8>> {
|
||||||
|
buffer: &'a B,
|
||||||
|
offset: usize,
|
||||||
|
size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, B: 'a> CheckedRegion<'a, B> where B: ::std::ops::Deref<Target=Vec<u8>> {
|
||||||
|
fn range(&self) -> Range<usize> {
|
||||||
|
self.offset..self.offset+self.size
|
||||||
|
}
|
||||||
|
|
||||||
|
fn slice(&self) -> &[u8] {
|
||||||
|
&self.buffer[self.range()]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersects(&self, other: &Self) -> bool {
|
||||||
|
let low = cmp::max(self.offset, other.offset);
|
||||||
|
let high = cmp::min(self.offset + self.size, other.offset + other.size);
|
||||||
|
|
||||||
|
low < high
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MemoryInstance {
|
||||||
|
|
||||||
|
pub fn alloc(initial_pages: u32, maximum_pages: Option<u32>) -> Result<MemoryRef, Error> {
|
||||||
|
let memory = MemoryInstance::new(ResizableLimits::new(initial_pages, maximum_pages))?;
|
||||||
|
Ok(MemoryRef(Rc::new(memory)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create new linear memory instance.
|
||||||
|
fn new(limits: ResizableLimits) -> Result<Self, Error> {
|
||||||
|
check_limits(&limits)?;
|
||||||
|
|
||||||
|
let maximum_size = match limits.maximum() {
|
||||||
|
Some(maximum_pages) if maximum_pages > LINEAR_MEMORY_MAX_PAGES =>
|
||||||
|
return Err(Error::Memory(format!("maximum memory size must be at most {} pages", LINEAR_MEMORY_MAX_PAGES))),
|
||||||
|
Some(maximum_pages) => maximum_pages.saturating_mul(LINEAR_MEMORY_PAGE_SIZE),
|
||||||
|
None => u32::MAX,
|
||||||
|
};
|
||||||
|
let initial_size = calculate_memory_size(0, limits.initial(), maximum_size)
|
||||||
|
.ok_or(Error::Memory(format!("initial memory size must be at most {} pages", LINEAR_MEMORY_MAX_PAGES)))?;
|
||||||
|
|
||||||
|
let memory = MemoryInstance {
|
||||||
|
limits: limits,
|
||||||
|
buffer: RefCell::new(vec![0; initial_size as usize]),
|
||||||
|
maximum_size: maximum_size,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(memory)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return linear memory limits.
|
||||||
|
pub(crate) fn limits(&self) -> &ResizableLimits {
|
||||||
|
&self.limits
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initial_pages(&self) -> u32 {
|
||||||
|
self.limits.initial()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn maximum_pages(&self) -> Option<u32> {
|
||||||
|
self.limits.maximum()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return linear memory size (in pages).
|
||||||
|
pub fn size(&self) -> u32 {
|
||||||
|
self.buffer.borrow().len() as u32 / LINEAR_MEMORY_PAGE_SIZE
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get data at given offset.
|
||||||
|
pub fn get(&self, offset: u32, size: usize) -> Result<Vec<u8>, Error> {
|
||||||
|
let buffer = self.buffer.borrow();
|
||||||
|
let region = self.checked_region(&buffer, offset as usize, size)?;
|
||||||
|
|
||||||
|
Ok(region.slice().to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write memory slice into another slice
|
||||||
|
pub fn get_into(&self, offset: u32, target: &mut [u8]) -> Result<(), Error> {
|
||||||
|
let buffer = self.buffer.borrow();
|
||||||
|
let region = self.checked_region(&buffer, offset as usize, target.len())?;
|
||||||
|
|
||||||
|
target.copy_from_slice(region.slice());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set data at given offset.
|
||||||
|
pub fn set(&self, offset: u32, value: &[u8]) -> Result<(), Error> {
|
||||||
|
let mut buffer = self.buffer.borrow_mut();
|
||||||
|
let range = self.checked_region(&buffer, offset as usize, value.len())?.range();
|
||||||
|
|
||||||
|
buffer[range].copy_from_slice(value);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Increases the size of the linear memory by given number of pages.
|
||||||
|
/// Returns previous memory size (in pages) if succeeds.
|
||||||
|
pub fn grow(&self, pages: u32) -> Result<u32, Error> {
|
||||||
|
let mut buffer = self.buffer.borrow_mut();
|
||||||
|
let old_size = buffer.len() as u32;
|
||||||
|
match calculate_memory_size(old_size, pages, self.maximum_size) {
|
||||||
|
None => Err(Error::Memory(
|
||||||
|
format!(
|
||||||
|
"Trying to grow memory by {} pages when already have {}",
|
||||||
|
pages,
|
||||||
|
old_size / LINEAR_MEMORY_PAGE_SIZE,
|
||||||
|
)
|
||||||
|
)),
|
||||||
|
Some(new_size) => {
|
||||||
|
buffer.resize(new_size as usize, 0);
|
||||||
|
Ok(old_size / LINEAR_MEMORY_PAGE_SIZE)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checked_region<'a, B>(&self, buffer: &'a B, offset: usize, size: usize) -> Result<CheckedRegion<'a, B>, Error>
|
||||||
|
where B: ::std::ops::Deref<Target=Vec<u8>>
|
||||||
|
{
|
||||||
|
let end = offset.checked_add(size)
|
||||||
|
.ok_or(Error::Memory(format!("trying to access memory block of size {} from offset {}", size, offset)))?;
|
||||||
|
|
||||||
|
if end > buffer.len() {
|
||||||
|
return Err(Error::Memory(format!("trying to access region [{}..{}] in memory [0..{}]", offset, end, buffer.len())));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(CheckedRegion {
|
||||||
|
buffer: buffer,
|
||||||
|
offset: offset,
|
||||||
|
size: size,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy memory region. Semantically equivalent to `memmove`.
|
||||||
|
pub fn copy(&self, src_offset: usize, dst_offset: usize, len: usize) -> Result<(), Error> {
|
||||||
|
let buffer = self.buffer.borrow_mut();
|
||||||
|
|
||||||
|
let read_region = self.checked_region(&buffer, src_offset, len)?;
|
||||||
|
let write_region = self.checked_region(&buffer, dst_offset, len)?;
|
||||||
|
|
||||||
|
unsafe { ::std::ptr::copy(
|
||||||
|
buffer[read_region.range()].as_ptr(),
|
||||||
|
buffer[write_region.range()].as_ptr() as *mut _,
|
||||||
|
len,
|
||||||
|
)}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy memory region, non-overlapping version. Semantically equivalent to `memcpy`,
|
||||||
|
/// but returns Error if source overlaping with destination.
|
||||||
|
pub fn copy_nonoverlapping(&self, src_offset: usize, dst_offset: usize, len: usize) -> Result<(), Error> {
|
||||||
|
let buffer = self.buffer.borrow_mut();
|
||||||
|
|
||||||
|
let read_region = self.checked_region(&buffer, src_offset, len)?;
|
||||||
|
let write_region = self.checked_region(&buffer, dst_offset, len)?;
|
||||||
|
|
||||||
|
if read_region.intersects(&write_region) {
|
||||||
|
return Err(Error::Memory(format!("non-overlapping copy is used for overlapping regions")))
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe { ::std::ptr::copy_nonoverlapping(
|
||||||
|
buffer[read_region.range()].as_ptr(),
|
||||||
|
buffer[write_region.range()].as_ptr() as *mut _,
|
||||||
|
len,
|
||||||
|
)}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear memory region with a specified value. Semantically equivalent to `memset`.
|
||||||
|
pub fn clear(&self, offset: usize, new_val: u8, len: usize) -> Result<(), Error> {
|
||||||
|
let mut buffer = self.buffer.borrow_mut();
|
||||||
|
|
||||||
|
let range = self.checked_region(&buffer, offset, len)?.range();
|
||||||
|
for val in &mut buffer[range] { *val = new_val }
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Zero memory region
|
||||||
|
pub fn zero(&self, offset: usize, len: usize) -> Result<(), Error> {
|
||||||
|
self.clear(offset, 0, len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_memory_size(old_size: u32, additional_pages: u32, maximum_size: u32) -> Option<u32> {
|
||||||
|
additional_pages
|
||||||
|
.checked_mul(LINEAR_MEMORY_PAGE_SIZE)
|
||||||
|
.and_then(|size| size.checked_add(old_size))
|
||||||
|
.and_then(|size| if size > maximum_size {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(size)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use super::MemoryInstance;
|
||||||
|
use Error;
|
||||||
|
use parity_wasm::elements::ResizableLimits;
|
||||||
|
|
||||||
|
fn create_memory(initial_content: &[u8]) -> MemoryInstance {
|
||||||
|
let mem = MemoryInstance::new(ResizableLimits::new(1, Some(1)))
|
||||||
|
.expect("MemoryInstance created successfuly");
|
||||||
|
mem.set(0, initial_content).expect("Successful initialize the memory");
|
||||||
|
mem
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn copy_overlaps_1() {
|
||||||
|
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||||
|
mem.copy(0, 4, 6).expect("Successfully copy the elements");
|
||||||
|
let result = mem.get(0, 10).expect("Successfully retrieve the result");
|
||||||
|
assert_eq!(result, &[0, 1, 2, 3, 0, 1, 2, 3, 4, 5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn copy_overlaps_2() {
|
||||||
|
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||||
|
mem.copy(4, 0, 6).expect("Successfully copy the elements");
|
||||||
|
let result = mem.get(0, 10).expect("Successfully retrieve the result");
|
||||||
|
assert_eq!(result, &[4, 5, 6, 7, 8, 9, 6, 7, 8, 9]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn copy_nonoverlapping() {
|
||||||
|
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||||
|
mem.copy_nonoverlapping(0, 10, 10).expect("Successfully copy the elements");
|
||||||
|
let result = mem.get(10, 10).expect("Successfully retrieve the result");
|
||||||
|
assert_eq!(result, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn copy_nonoverlapping_overlaps_1() {
|
||||||
|
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||||
|
let result = mem.copy_nonoverlapping(0, 4, 6);
|
||||||
|
match result {
|
||||||
|
Err(Error::Memory(_)) => {},
|
||||||
|
_ => panic!("Expected Error::Memory(_) result, but got {:?}", result),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn copy_nonoverlapping_overlaps_2() {
|
||||||
|
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||||
|
let result = mem.copy_nonoverlapping(4, 0, 6);
|
||||||
|
match result {
|
||||||
|
Err(Error::Memory(_)) => {},
|
||||||
|
_ => panic!("Expected Error::Memory(_), but got {:?}", result),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn clear() {
|
||||||
|
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||||
|
mem.clear(0, 0x4A, 10).expect("To successfully clear the memory");
|
||||||
|
let result = mem.get(0, 10).expect("To successfully retrieve the result");
|
||||||
|
assert_eq!(result, &[0x4A; 10]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_into() {
|
||||||
|
let mem = MemoryInstance::new(ResizableLimits::new(1, None)).expect("memory instance creation should not fail");
|
||||||
|
mem.set(6, &[13, 17, 129]).expect("memory set should not fail");
|
||||||
|
|
||||||
|
let mut data = [0u8; 2];
|
||||||
|
mem.get_into(7, &mut data[..]).expect("get_into should not fail");
|
||||||
|
|
||||||
|
assert_eq!(data, [17, 129]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,542 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::fmt;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use parity_wasm::elements::{External, FunctionType, InitExpr, Internal, Opcode, ResizableLimits, Type};
|
||||||
|
use {Error, MemoryInstance, RuntimeValue, TableInstance};
|
||||||
|
use imports::ImportResolver;
|
||||||
|
use global::{GlobalInstance, GlobalRef};
|
||||||
|
use func::{FuncRef, FuncBody, FuncInstance};
|
||||||
|
use table::TableRef;
|
||||||
|
use memory::MemoryRef;
|
||||||
|
use host::Externals;
|
||||||
|
use validation::ValidatedModule;
|
||||||
|
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ModuleRef(Rc<ModuleInstance>);
|
||||||
|
|
||||||
|
impl ::std::ops::Deref for ModuleRef {
|
||||||
|
type Target = ModuleInstance;
|
||||||
|
fn deref(&self) -> &ModuleInstance {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ExternVal {
|
||||||
|
Func(FuncRef),
|
||||||
|
Table(TableRef),
|
||||||
|
Memory(MemoryRef),
|
||||||
|
Global(GlobalRef),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for ExternVal {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
match *self {
|
||||||
|
ExternVal::Func(ref func) => ExternVal::Func(func.clone()),
|
||||||
|
ExternVal::Table(ref table) => ExternVal::Table(table.clone()),
|
||||||
|
ExternVal::Memory(ref memory) => ExternVal::Memory(memory.clone()),
|
||||||
|
ExternVal::Global(ref global) => ExternVal::Global(global.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for ExternVal {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"ExternVal {{ {} }}",
|
||||||
|
match *self {
|
||||||
|
ExternVal::Func(_) => "Func",
|
||||||
|
ExternVal::Table(_) => "Table",
|
||||||
|
ExternVal::Memory(_) => "Memory",
|
||||||
|
ExternVal::Global(_) => "Global",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExternVal {
|
||||||
|
pub fn as_func(&self) -> Option<FuncRef> {
|
||||||
|
match *self {
|
||||||
|
ExternVal::Func(ref func) => Some(func.clone()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_table(&self) -> Option<TableRef> {
|
||||||
|
match *self {
|
||||||
|
ExternVal::Table(ref table) => Some(table.clone()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_memory(&self) -> Option<MemoryRef> {
|
||||||
|
match *self {
|
||||||
|
ExternVal::Memory(ref memory) => Some(memory.clone()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_global(&self) -> Option<GlobalRef> {
|
||||||
|
match *self {
|
||||||
|
ExternVal::Global(ref global) => Some(global.clone()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ModuleInstance {
|
||||||
|
types: RefCell<Vec<Rc<FunctionType>>>,
|
||||||
|
tables: RefCell<Vec<TableRef>>,
|
||||||
|
funcs: RefCell<Vec<FuncRef>>,
|
||||||
|
memories: RefCell<Vec<MemoryRef>>,
|
||||||
|
globals: RefCell<Vec<GlobalRef>>,
|
||||||
|
exports: RefCell<HashMap<String, ExternVal>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModuleInstance {
|
||||||
|
fn default() -> Self {
|
||||||
|
ModuleInstance {
|
||||||
|
funcs: RefCell::new(Vec::new()),
|
||||||
|
types: RefCell::new(Vec::new()),
|
||||||
|
tables: RefCell::new(Vec::new()),
|
||||||
|
memories: RefCell::new(Vec::new()),
|
||||||
|
globals: RefCell::new(Vec::new()),
|
||||||
|
exports: RefCell::new(HashMap::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn memory_by_index(&self, idx: u32) -> Option<MemoryRef> {
|
||||||
|
self.memories.borrow_mut().get(idx as usize).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn table_by_index(&self, idx: u32) -> Option<TableRef> {
|
||||||
|
self.tables.borrow_mut().get(idx as usize).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn global_by_index(&self, idx: u32) -> Option<GlobalRef> {
|
||||||
|
self.globals.borrow_mut().get(idx as usize).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn func_by_index(&self, idx: u32) -> Option<FuncRef> {
|
||||||
|
self.funcs.borrow().get(idx as usize).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn export_by_name(&self, name: &str) -> Option<ExternVal> {
|
||||||
|
self.exports.borrow().get(name).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn type_by_index(&self, idx: u32) -> Option<Rc<FunctionType>> {
|
||||||
|
self.types.borrow().get(idx as usize).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_func(&self, func: FuncRef) {
|
||||||
|
self.funcs.borrow_mut().push(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_type(&self, func_type: Rc<FunctionType>) {
|
||||||
|
self.types.borrow_mut().push(func_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_memory(&self, memory: MemoryRef) {
|
||||||
|
self.memories.borrow_mut().push(memory)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_table(&self, table: TableRef) {
|
||||||
|
self.tables.borrow_mut().push(table)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_global(&self, global: GlobalRef) {
|
||||||
|
self.globals.borrow_mut().push(global)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_export<N: Into<String>>(&self, name: N, extern_val: ExternVal) {
|
||||||
|
self.exports.borrow_mut().insert(name.into(), extern_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc_module(
|
||||||
|
validated_module: &ValidatedModule,
|
||||||
|
extern_vals: &[ExternVal]
|
||||||
|
) -> Result<ModuleRef, Error> {
|
||||||
|
let module = validated_module.module();
|
||||||
|
let instance = ModuleRef(Rc::new(ModuleInstance::default()));
|
||||||
|
|
||||||
|
for &Type::Function(ref ty) in module.type_section().map(|ts| ts.types()).unwrap_or(&[]) {
|
||||||
|
let type_id = alloc_func_type(ty.clone());
|
||||||
|
instance.push_type(type_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let imports = module.import_section().map(|is| is.entries()).unwrap_or(
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
if imports.len() != extern_vals.len() {
|
||||||
|
return Err(Error::Instantiation(format!(
|
||||||
|
"extern_vals length is not equal to import section entries"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (import, extern_val) in
|
||||||
|
Iterator::zip(imports.into_iter(), extern_vals.into_iter())
|
||||||
|
{
|
||||||
|
match (import.external(), extern_val) {
|
||||||
|
(&External::Function(fn_type_idx), &ExternVal::Func(ref func)) => {
|
||||||
|
let expected_fn_type = instance.type_by_index(fn_type_idx).expect(
|
||||||
|
"Due to validation function type should exists",
|
||||||
|
);
|
||||||
|
let actual_fn_type = func.func_type();
|
||||||
|
if &*expected_fn_type != actual_fn_type {
|
||||||
|
return Err(Error::Instantiation(format!(
|
||||||
|
"Expected function with type {:?}, but actual type is {:?} for entry {}",
|
||||||
|
expected_fn_type,
|
||||||
|
actual_fn_type,
|
||||||
|
import.field(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
instance.push_func(func.clone())
|
||||||
|
}
|
||||||
|
(&External::Table(ref tt), &ExternVal::Table(ref table)) => {
|
||||||
|
match_limits(table.limits(), tt.limits())?;
|
||||||
|
instance.push_table(table.clone());
|
||||||
|
}
|
||||||
|
(&External::Memory(ref mt), &ExternVal::Memory(ref memory)) => {
|
||||||
|
match_limits(memory.limits(), mt.limits())?;
|
||||||
|
instance.push_memory(memory.clone());
|
||||||
|
}
|
||||||
|
(&External::Global(ref gl), &ExternVal::Global(ref global)) => {
|
||||||
|
if gl.content_type() != global.value_type() {
|
||||||
|
return Err(Error::Instantiation(format!(
|
||||||
|
"Expect global with {:?} type, but provided global with {:?} type",
|
||||||
|
gl.content_type(),
|
||||||
|
global.value_type(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
instance.push_global(global.clone());
|
||||||
|
}
|
||||||
|
(expected_import, actual_extern_val) => {
|
||||||
|
return Err(Error::Instantiation(format!(
|
||||||
|
"Expected {:?} type, but provided {:?} extern_val",
|
||||||
|
expected_import,
|
||||||
|
actual_extern_val
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let labels = validated_module.labels();
|
||||||
|
{
|
||||||
|
let funcs = module.function_section().map(|fs| fs.entries()).unwrap_or(
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
let bodies = module.code_section().map(|cs| cs.bodies()).unwrap_or(&[]);
|
||||||
|
debug_assert!(
|
||||||
|
funcs.len() == bodies.len(),
|
||||||
|
"Due to validation func and body counts must match"
|
||||||
|
);
|
||||||
|
|
||||||
|
for (index, (ty, body)) in
|
||||||
|
Iterator::zip(funcs.into_iter(), bodies.into_iter()).enumerate()
|
||||||
|
{
|
||||||
|
let func_type = instance.type_by_index(ty.type_ref()).expect(
|
||||||
|
"Due to validation type should exists",
|
||||||
|
);
|
||||||
|
let labels = labels.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,
|
||||||
|
};
|
||||||
|
let func_instance =
|
||||||
|
FuncInstance::alloc_internal(instance.clone(), func_type, func_body);
|
||||||
|
instance.push_func(func_instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for table_type in module.table_section().map(|ts| ts.entries()).unwrap_or(&[]) {
|
||||||
|
let table = TableInstance::alloc(
|
||||||
|
table_type.limits().initial(),
|
||||||
|
table_type.limits().maximum(),
|
||||||
|
)?;
|
||||||
|
instance.push_table(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
for memory_type in module.memory_section().map(|ms| ms.entries()).unwrap_or(
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
{
|
||||||
|
let memory = MemoryInstance::alloc(
|
||||||
|
memory_type.limits().initial(),
|
||||||
|
memory_type.limits().maximum()
|
||||||
|
)?;
|
||||||
|
instance.push_memory(memory);
|
||||||
|
}
|
||||||
|
|
||||||
|
for global_entry in module.global_section().map(|gs| gs.entries()).unwrap_or(
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
{
|
||||||
|
let init_val = eval_init_expr(global_entry.init_expr(), &*instance);
|
||||||
|
let global = GlobalInstance::alloc(
|
||||||
|
init_val,
|
||||||
|
global_entry.global_type().is_mutable(),
|
||||||
|
);
|
||||||
|
instance.push_global(global);
|
||||||
|
}
|
||||||
|
|
||||||
|
for export in module.export_section().map(|es| es.entries()).unwrap_or(
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
{
|
||||||
|
let field = export.field();
|
||||||
|
let extern_val: ExternVal = match *export.internal() {
|
||||||
|
Internal::Function(idx) => {
|
||||||
|
let func = instance.func_by_index(idx).expect(
|
||||||
|
"Due to validation func should exists",
|
||||||
|
);
|
||||||
|
ExternVal::Func(func)
|
||||||
|
}
|
||||||
|
Internal::Global(idx) => {
|
||||||
|
let global = instance.global_by_index(idx).expect(
|
||||||
|
"Due to validation global should exists",
|
||||||
|
);
|
||||||
|
ExternVal::Global(global)
|
||||||
|
}
|
||||||
|
Internal::Memory(idx) => {
|
||||||
|
let memory = instance.memory_by_index(idx).expect(
|
||||||
|
"Due to validation memory should exists",
|
||||||
|
);
|
||||||
|
ExternVal::Memory(memory)
|
||||||
|
}
|
||||||
|
Internal::Table(idx) => {
|
||||||
|
let table = instance.table_by_index(idx).expect(
|
||||||
|
"Due to validation table should exists",
|
||||||
|
);
|
||||||
|
ExternVal::Table(table)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
instance.insert_export(field, extern_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instantiate_with_externvals(
|
||||||
|
validated_module: &ValidatedModule,
|
||||||
|
extern_vals: &[ExternVal],
|
||||||
|
) -> Result<ModuleRef, Error> {
|
||||||
|
let module = validated_module.module();
|
||||||
|
|
||||||
|
let module_ref = ModuleInstance::alloc_module(validated_module, extern_vals)?;
|
||||||
|
|
||||||
|
for element_segment in module.elements_section().map(|es| es.entries()).unwrap_or(
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
{
|
||||||
|
let offset_val = match eval_init_expr(element_segment.offset(), &module_ref) {
|
||||||
|
RuntimeValue::I32(v) => v as u32,
|
||||||
|
_ => panic!("Due to validation elem segment offset should evaluate to i32"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let table_inst = module_ref.table_by_index(DEFAULT_TABLE_INDEX).expect(
|
||||||
|
"Due to validation default table should exists",
|
||||||
|
);
|
||||||
|
for (j, func_idx) in element_segment.members().into_iter().enumerate() {
|
||||||
|
let func = module_ref.func_by_index(*func_idx).expect(
|
||||||
|
"Due to validation funcs from element segments should exists",
|
||||||
|
);
|
||||||
|
|
||||||
|
table_inst.set(offset_val + j as u32, Some(func))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for data_segment in module.data_section().map(|ds| ds.entries()).unwrap_or(&[]) {
|
||||||
|
let offset_val = match eval_init_expr(data_segment.offset(), &module_ref) {
|
||||||
|
RuntimeValue::I32(v) => v as u32,
|
||||||
|
_ => panic!("Due to validation data segment offset should evaluate to i32"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let memory_inst = module_ref.memory_by_index(DEFAULT_MEMORY_INDEX).expect(
|
||||||
|
"Due to validation default memory should exists",
|
||||||
|
);
|
||||||
|
memory_inst.set(offset_val, data_segment.value())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(module_ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new<'m, I: ImportResolver>(
|
||||||
|
validated_module: &'m ValidatedModule,
|
||||||
|
imports: &I,
|
||||||
|
) -> Result<NotStartedModuleRef<'m>, Error> {
|
||||||
|
let module = validated_module.module();
|
||||||
|
|
||||||
|
let mut extern_vals = Vec::new();
|
||||||
|
for import_entry in module.import_section().map(|s| s.entries()).unwrap_or(&[]) {
|
||||||
|
let module_name = import_entry.module();
|
||||||
|
let field_name = import_entry.field();
|
||||||
|
let extern_val = match *import_entry.external() {
|
||||||
|
External::Function(fn_ty_idx) => {
|
||||||
|
let types = module.type_section().map(|s| s.types()).unwrap_or(&[]);
|
||||||
|
let &Type::Function(ref func_type) = types
|
||||||
|
.get(fn_ty_idx as usize)
|
||||||
|
.expect("Due to validation functions should have valid types");
|
||||||
|
let func = imports.resolve_func(module_name, field_name, func_type)?;
|
||||||
|
ExternVal::Func(func)
|
||||||
|
}
|
||||||
|
External::Table(ref table_type) => {
|
||||||
|
let table = imports.resolve_table(module_name, field_name, table_type)?;
|
||||||
|
ExternVal::Table(table)
|
||||||
|
}
|
||||||
|
External::Memory(ref memory_type) => {
|
||||||
|
let memory = imports.resolve_memory(module_name, field_name, memory_type)?;
|
||||||
|
ExternVal::Memory(memory)
|
||||||
|
}
|
||||||
|
External::Global(ref global_type) => {
|
||||||
|
let global = imports.resolve_global(module_name, field_name, global_type)?;
|
||||||
|
ExternVal::Global(global)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
extern_vals.push(extern_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
let instance = Self::instantiate_with_externvals(validated_module, &extern_vals)?;
|
||||||
|
Ok(NotStartedModuleRef {
|
||||||
|
validated_module,
|
||||||
|
instance,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn invoke_index<E: Externals>(
|
||||||
|
&self,
|
||||||
|
func_idx: u32,
|
||||||
|
args: &[RuntimeValue],
|
||||||
|
externals: &mut E,
|
||||||
|
) -> Result<Option<RuntimeValue>, Error> {
|
||||||
|
let func_instance = self.func_by_index(func_idx).ok_or_else(|| {
|
||||||
|
Error::Function(format!(
|
||||||
|
"Module doesn't contain function at index {}",
|
||||||
|
func_idx
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
FuncInstance::invoke(func_instance, Cow::Borrowed(args), externals)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn invoke_export<E: Externals>(
|
||||||
|
&self,
|
||||||
|
func_name: &str,
|
||||||
|
args: &[RuntimeValue],
|
||||||
|
externals: &mut E,
|
||||||
|
) -> Result<Option<RuntimeValue>, Error> {
|
||||||
|
let extern_val = self.export_by_name(func_name).ok_or_else(|| {
|
||||||
|
Error::Function(format!("Module doesn't have export {}", func_name))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let func_instance = match extern_val {
|
||||||
|
ExternVal::Func(func_instance) => func_instance,
|
||||||
|
unexpected => {
|
||||||
|
return Err(Error::Function(format!(
|
||||||
|
"Export {} is not a function, but {:?}",
|
||||||
|
func_name,
|
||||||
|
unexpected
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FuncInstance::invoke(func_instance.clone(), Cow::Borrowed(args), externals)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NotStartedModuleRef<'a> {
|
||||||
|
validated_module: &'a ValidatedModule,
|
||||||
|
instance: ModuleRef,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> NotStartedModuleRef<'a> {
|
||||||
|
pub fn not_started_instance(&self) -> &ModuleRef {
|
||||||
|
&self.instance
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_start<'b, E: Externals>(self, state: &'b mut E) -> Result<ModuleRef, Error> {
|
||||||
|
if let Some(start_fn_idx) = self.validated_module.module().start_section() {
|
||||||
|
let start_func = self.instance.func_by_index(start_fn_idx).expect(
|
||||||
|
"Due to validation start function should exists",
|
||||||
|
);
|
||||||
|
FuncInstance::invoke(start_func, Cow::Borrowed(&[]), state)?;
|
||||||
|
}
|
||||||
|
Ok(self.instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn assert_no_start(self) -> ModuleRef {
|
||||||
|
assert!(self.validated_module.module().start_section().is_none());
|
||||||
|
self.instance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc_func_type(func_type: FunctionType) -> Rc<FunctionType> {
|
||||||
|
Rc::new(func_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_init_expr(init_expr: &InitExpr, module: &ModuleInstance) -> RuntimeValue {
|
||||||
|
let code = init_expr.code();
|
||||||
|
debug_assert!(
|
||||||
|
code.len() == 2,
|
||||||
|
"Due to validation `code`.len() should be 2"
|
||||||
|
);
|
||||||
|
match code[0] {
|
||||||
|
Opcode::I32Const(v) => v.into(),
|
||||||
|
Opcode::I64Const(v) => v.into(),
|
||||||
|
Opcode::F32Const(v) => RuntimeValue::decode_f32(v),
|
||||||
|
Opcode::F64Const(v) => RuntimeValue::decode_f64(v),
|
||||||
|
Opcode::GetGlobal(idx) => {
|
||||||
|
let global = module.global_by_index(idx).expect(
|
||||||
|
"Due to validation global should exists in module",
|
||||||
|
);
|
||||||
|
global.get()
|
||||||
|
}
|
||||||
|
_ => panic!("Due to validation init should be a const expr"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn match_limits(l1: &ResizableLimits, l2: &ResizableLimits) -> Result<(), Error> {
|
||||||
|
if l1.initial() < l2.initial() {
|
||||||
|
return Err(Error::Instantiation(format!(
|
||||||
|
"trying to import with limits l1.initial={} and l2.initial={}",
|
||||||
|
l1.initial(),
|
||||||
|
l2.initial()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
match (l1.maximum(), l2.maximum()) {
|
||||||
|
(_, None) => (),
|
||||||
|
(Some(m1), Some(m2)) if m1 <= m2 => (),
|
||||||
|
_ => {
|
||||||
|
return Err(Error::Instantiation(format!(
|
||||||
|
"trying to import with limits l1.max={:?} and l2.max={:?}",
|
||||||
|
l1.maximum(),
|
||||||
|
l2.maximum()
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_limits(limits: &ResizableLimits) -> Result<(), Error> {
|
||||||
|
if let Some(maximum) = limits.maximum() {
|
||||||
|
if maximum < limits.initial() {
|
||||||
|
return Err(Error::Instantiation(format!(
|
||||||
|
"maximum limit {} is less than minimum {}",
|
||||||
|
maximum,
|
||||||
|
limits.initial()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,122 @@
|
||||||
|
use std::u32;
|
||||||
|
use std::fmt;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use parity_wasm::elements::ResizableLimits;
|
||||||
|
use Error;
|
||||||
|
use func::FuncRef;
|
||||||
|
use module::check_limits;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct TableRef(Rc<TableInstance>);
|
||||||
|
|
||||||
|
impl ::std::ops::Deref for TableRef {
|
||||||
|
type Target = TableInstance;
|
||||||
|
fn deref(&self) -> &TableInstance {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Table instance.
|
||||||
|
pub struct TableInstance {
|
||||||
|
/// Table limits.
|
||||||
|
limits: ResizableLimits,
|
||||||
|
/// Table memory buffer.
|
||||||
|
buffer: RefCell<Vec<Option<FuncRef>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for TableInstance {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_struct("TableInstance")
|
||||||
|
.field("limits", &self.limits)
|
||||||
|
.field("buffer.len", &self.buffer.borrow().len())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableInstance {
|
||||||
|
|
||||||
|
pub fn alloc(initial_size: u32, maximum_size: Option<u32>) -> Result<TableRef, Error> {
|
||||||
|
let table = TableInstance::new(ResizableLimits::new(initial_size, maximum_size))?;
|
||||||
|
Ok(TableRef(Rc::new(table)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(limits: ResizableLimits) -> Result<TableInstance, Error> {
|
||||||
|
check_limits(&limits)?;
|
||||||
|
Ok(TableInstance {
|
||||||
|
buffer: RefCell::new(vec![None; limits.initial() as usize]),
|
||||||
|
limits: limits,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return table limits.
|
||||||
|
pub(crate) fn limits(&self) -> &ResizableLimits {
|
||||||
|
&self.limits
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initial_size(&self) -> u32 {
|
||||||
|
self.limits.initial()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn maximum_size(&self) -> Option<u32> {
|
||||||
|
self.limits.maximum()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_size(&self) -> u32 {
|
||||||
|
self.buffer.borrow().len() as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn grow(&self, by: u32) -> Result<(), Error> {
|
||||||
|
let mut buffer = self.buffer.borrow_mut();
|
||||||
|
let maximum_size = self.maximum_size().unwrap_or(u32::MAX);
|
||||||
|
let new_size = self.current_size().checked_add(by)
|
||||||
|
.and_then(|new_size| {
|
||||||
|
if maximum_size < new_size {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(new_size)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok_or_else(||
|
||||||
|
Error::Table(format!(
|
||||||
|
"Trying to grow table by {} items when there are already {} items",
|
||||||
|
by,
|
||||||
|
self.current_size(),
|
||||||
|
))
|
||||||
|
)?;
|
||||||
|
buffer.resize(new_size as usize, None);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the specific value in the table
|
||||||
|
pub fn get(&self, offset: u32) -> Result<FuncRef, Error> {
|
||||||
|
let buffer = self.buffer.borrow();
|
||||||
|
let buffer_len = buffer.len();
|
||||||
|
let table_elem = buffer.get(offset as usize).cloned().ok_or_else(||
|
||||||
|
Error::Table(format!(
|
||||||
|
"trying to read table item with index {} when there are only {} items",
|
||||||
|
offset,
|
||||||
|
buffer_len
|
||||||
|
)),
|
||||||
|
)?;
|
||||||
|
Ok(table_elem.ok_or(Error::Table(format!(
|
||||||
|
"trying to read uninitialized element on index {}",
|
||||||
|
offset
|
||||||
|
)))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the table element to the specified function.
|
||||||
|
pub fn set(&self, offset: u32, value: Option<FuncRef>) -> Result<(), Error> {
|
||||||
|
let mut buffer = self.buffer.borrow_mut();
|
||||||
|
let buffer_len = buffer.len();
|
||||||
|
let table_elem = buffer.get_mut(offset as usize).ok_or_else(||
|
||||||
|
Error::Table(format!(
|
||||||
|
"trying to update table item with index {} when there are only {} items",
|
||||||
|
offset,
|
||||||
|
buffer_len
|
||||||
|
))
|
||||||
|
)?;
|
||||||
|
*table_elem = value;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,749 @@
|
||||||
|
|
||||||
|
use parity_wasm::elements::{deserialize_buffer, FunctionType, MemoryType, TableType, ValueType};
|
||||||
|
use validation::{validate_module, ValidatedModule};
|
||||||
|
use {
|
||||||
|
Error, Externals, FuncInstance, FuncRef, HostError, ImportsBuilder,
|
||||||
|
MemoryInstance, MemoryRef, TableInstance, TableRef, ModuleImportResolver, ModuleInstance, ModuleRef,
|
||||||
|
RuntimeValue, TryInto,
|
||||||
|
};
|
||||||
|
use wabt::wat2wasm;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
struct HostErrorWithCode {
|
||||||
|
error_code: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::std::fmt::Display for HostErrorWithCode {
|
||||||
|
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> {
|
||||||
|
write!(f, "{}", self.error_code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HostError for HostErrorWithCode {}
|
||||||
|
|
||||||
|
/// Host state for the test environment.
|
||||||
|
///
|
||||||
|
/// This struct can be used as an external function executor and
|
||||||
|
/// as imports provider. This has a drawback: this struct
|
||||||
|
/// should be provided upon an instantiation of the module.
|
||||||
|
///
|
||||||
|
/// However, this limitation can be lifted by implementing `Externals`
|
||||||
|
/// and `ModuleImportResolver` traits for different structures.
|
||||||
|
/// See `defer_providing_externals` test for details.
|
||||||
|
struct TestHost {
|
||||||
|
memory: Option<MemoryRef>,
|
||||||
|
instance: Option<ModuleRef>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestHost {
|
||||||
|
fn new() -> TestHost {
|
||||||
|
TestHost {
|
||||||
|
memory: Some(MemoryInstance::alloc(1, Some(1)).unwrap()),
|
||||||
|
instance: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// sub(a: i32, b: i32) -> i32
|
||||||
|
///
|
||||||
|
/// This function just substracts one integer from another,
|
||||||
|
/// returning the subtraction result.
|
||||||
|
const SUB_FUNC_INDEX: usize = 0;
|
||||||
|
|
||||||
|
/// err(error_code: i32) -> !
|
||||||
|
///
|
||||||
|
/// This function traps upon a call.
|
||||||
|
/// The trap have a special type - HostErrorWithCode.
|
||||||
|
const ERR_FUNC_INDEX: usize = 1;
|
||||||
|
|
||||||
|
/// inc_mem(ptr: *mut u8)
|
||||||
|
///
|
||||||
|
/// Increments value at the given address in memory. This function
|
||||||
|
/// requires attached memory.
|
||||||
|
const INC_MEM_FUNC_INDEX: usize = 2;
|
||||||
|
|
||||||
|
/// get_mem(ptr: *mut u8) -> u8
|
||||||
|
///
|
||||||
|
/// Returns value at the given address in memory. This function
|
||||||
|
/// requires attached memory.
|
||||||
|
const GET_MEM_FUNC_INDEX: usize = 3;
|
||||||
|
|
||||||
|
/// recurse<T>(val: T) -> T
|
||||||
|
///
|
||||||
|
/// If called, resolves exported function named 'recursive' from the attached
|
||||||
|
/// module instance and then calls into it with the provided argument.
|
||||||
|
/// Note that this function is polymorphic over type T.
|
||||||
|
/// This function requires attached module instance.
|
||||||
|
const RECURSE_FUNC_INDEX: usize = 4;
|
||||||
|
|
||||||
|
impl Externals for TestHost {
|
||||||
|
fn invoke_index(
|
||||||
|
&mut self,
|
||||||
|
index: usize,
|
||||||
|
args: &[RuntimeValue],
|
||||||
|
) -> Result<Option<RuntimeValue>, Error> {
|
||||||
|
let mut args = args.iter().cloned();
|
||||||
|
match index {
|
||||||
|
SUB_FUNC_INDEX => {
|
||||||
|
let a: i32 = args.next().unwrap().try_into().unwrap();
|
||||||
|
let b: i32 = args.next().unwrap().try_into().unwrap();
|
||||||
|
|
||||||
|
let result: RuntimeValue = (a - b).into();
|
||||||
|
|
||||||
|
Ok(Some(result))
|
||||||
|
}
|
||||||
|
ERR_FUNC_INDEX => {
|
||||||
|
let error_code: u32 = args.next().unwrap().try_into().unwrap();
|
||||||
|
let error = HostErrorWithCode { error_code };
|
||||||
|
Err(Error::Host(Box::new(error)))
|
||||||
|
}
|
||||||
|
INC_MEM_FUNC_INDEX => {
|
||||||
|
let ptr: u32 = args.next().unwrap().try_into().unwrap();
|
||||||
|
|
||||||
|
let memory = self.memory.as_ref().expect(
|
||||||
|
"Function 'inc_mem' expects attached memory",
|
||||||
|
);
|
||||||
|
let mut buf = [0u8; 1];
|
||||||
|
memory.get_into(ptr, &mut buf).unwrap();
|
||||||
|
buf[0] += 1;
|
||||||
|
memory.set(ptr, &buf).unwrap();
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
GET_MEM_FUNC_INDEX => {
|
||||||
|
let ptr: u32 = args.next().unwrap().try_into().unwrap();
|
||||||
|
|
||||||
|
let memory = self.memory.as_ref().expect(
|
||||||
|
"Function 'get_mem' expects attached memory",
|
||||||
|
);
|
||||||
|
let mut buf = [0u8; 1];
|
||||||
|
memory.get_into(ptr, &mut buf).unwrap();
|
||||||
|
|
||||||
|
Ok(Some(RuntimeValue::I32(buf[0] as i32)))
|
||||||
|
}
|
||||||
|
RECURSE_FUNC_INDEX => {
|
||||||
|
let val: RuntimeValue = args.next().unwrap();
|
||||||
|
|
||||||
|
let instance = self.instance
|
||||||
|
.as_ref()
|
||||||
|
.expect("Function 'recurse' expects attached module instance")
|
||||||
|
.clone();
|
||||||
|
let result = instance
|
||||||
|
.invoke_export("recursive", &[val.into()], self)
|
||||||
|
.expect("Failed to call 'recursive'")
|
||||||
|
.expect("expected to be Some");
|
||||||
|
|
||||||
|
if val.value_type() != result.value_type() {
|
||||||
|
return Err(Error::Host(Box::new(HostErrorWithCode { error_code: 123 })));
|
||||||
|
}
|
||||||
|
Ok(Some(result))
|
||||||
|
}
|
||||||
|
_ => panic!("env doesn't provide function at index {}", index),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestHost {
|
||||||
|
fn check_signature(&self, index: usize, func_type: &FunctionType) -> bool {
|
||||||
|
if index == RECURSE_FUNC_INDEX {
|
||||||
|
// This function requires special handling because it is polymorphic.
|
||||||
|
if func_type.params().len() != 1 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let param_type = func_type.params()[0];
|
||||||
|
return func_type.return_type() == Some(param_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (params, ret_ty): (&[ValueType], Option<ValueType>) = match index {
|
||||||
|
SUB_FUNC_INDEX => (&[ValueType::I32, ValueType::I32], Some(ValueType::I32)),
|
||||||
|
ERR_FUNC_INDEX => (&[ValueType::I32], None),
|
||||||
|
INC_MEM_FUNC_INDEX => (&[ValueType::I32], None),
|
||||||
|
GET_MEM_FUNC_INDEX => (&[ValueType::I32], Some(ValueType::I32)),
|
||||||
|
_ => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
func_type.params() == params && func_type.return_type() == ret_ty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModuleImportResolver for TestHost {
|
||||||
|
fn resolve_func(&self, field_name: &str, func_type: &FunctionType) -> Result<FuncRef, Error> {
|
||||||
|
let index = match field_name {
|
||||||
|
"sub" => SUB_FUNC_INDEX,
|
||||||
|
"err" => ERR_FUNC_INDEX,
|
||||||
|
"inc_mem" => INC_MEM_FUNC_INDEX,
|
||||||
|
"get_mem" => GET_MEM_FUNC_INDEX,
|
||||||
|
"recurse" => RECURSE_FUNC_INDEX,
|
||||||
|
_ => {
|
||||||
|
return Err(Error::Instantiation(
|
||||||
|
format!("Export {} not found", field_name),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !self.check_signature(index, func_type) {
|
||||||
|
return Err(Error::Instantiation(format!(
|
||||||
|
"Export `{}` doesnt match expected type {:?}",
|
||||||
|
field_name,
|
||||||
|
func_type
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(FuncInstance::alloc_host(func_type.clone(), index))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_memory(
|
||||||
|
&self,
|
||||||
|
field_name: &str,
|
||||||
|
_memory_type: &MemoryType,
|
||||||
|
) -> Result<MemoryRef, Error> {
|
||||||
|
Err(Error::Instantiation(
|
||||||
|
format!("Export {} not found", field_name),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_wat(source: &str) -> ValidatedModule {
|
||||||
|
let wasm_binary = wat2wasm(source).expect("Failed to parse wat source");
|
||||||
|
let module = deserialize_buffer(&wasm_binary).expect("Failed to deserialize module");
|
||||||
|
let validated_module = validate_module(module).expect("Failed to validate module");
|
||||||
|
validated_module
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn call_host_func() {
|
||||||
|
let module = parse_wat(
|
||||||
|
r#"
|
||||||
|
(module
|
||||||
|
(import "env" "sub" (func $sub (param i32 i32) (result i32)))
|
||||||
|
|
||||||
|
(func (export "test") (result i32)
|
||||||
|
(call $sub
|
||||||
|
(i32.const 5)
|
||||||
|
(i32.const 7)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut env = TestHost::new();
|
||||||
|
|
||||||
|
let instance = ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &env))
|
||||||
|
.expect("Failed to instantiate module")
|
||||||
|
.assert_no_start();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
instance.invoke_export("test", &[], &mut env).expect(
|
||||||
|
"Failed to invoke 'test' function",
|
||||||
|
),
|
||||||
|
Some(RuntimeValue::I32(-2))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn host_err() {
|
||||||
|
let module = parse_wat(
|
||||||
|
r#"
|
||||||
|
(module
|
||||||
|
(import "env" "err" (func $err (param i32)))
|
||||||
|
|
||||||
|
(func (export "test")
|
||||||
|
(call $err
|
||||||
|
(i32.const 228)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut env = TestHost::new();
|
||||||
|
|
||||||
|
let instance = ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &env))
|
||||||
|
.expect("Failed to instantiate module")
|
||||||
|
.assert_no_start();
|
||||||
|
|
||||||
|
let error = instance.invoke_export("test", &[], &mut env).expect_err(
|
||||||
|
"`test` expected to return error",
|
||||||
|
);
|
||||||
|
|
||||||
|
let host_error: Box<HostError> = match error {
|
||||||
|
Error::Host(err) => err,
|
||||||
|
err => panic!("Unexpected error {:?}", err),
|
||||||
|
};
|
||||||
|
|
||||||
|
let error_with_code = host_error.downcast_ref::<HostErrorWithCode>().expect(
|
||||||
|
"Failed to downcast to expected error type",
|
||||||
|
);
|
||||||
|
assert_eq!(error_with_code.error_code, 228);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn modify_mem_with_host_funcs() {
|
||||||
|
let module = parse_wat(
|
||||||
|
r#"
|
||||||
|
(module
|
||||||
|
(import "env" "inc_mem" (func $inc_mem (param i32)))
|
||||||
|
;; (import "env" "get_mem" (func $get_mem (param i32) (result i32)))
|
||||||
|
|
||||||
|
(func (export "modify_mem")
|
||||||
|
;; inc memory at address 12 for 4 times.
|
||||||
|
(call $inc_mem (i32.const 12))
|
||||||
|
(call $inc_mem (i32.const 12))
|
||||||
|
(call $inc_mem (i32.const 12))
|
||||||
|
(call $inc_mem (i32.const 12))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut env = TestHost::new();
|
||||||
|
|
||||||
|
let instance = ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &env))
|
||||||
|
.expect("Failed to instantiate module")
|
||||||
|
.assert_no_start();
|
||||||
|
|
||||||
|
instance.invoke_export("modify_mem", &[], &mut env).expect(
|
||||||
|
"Failed to invoke 'test' function",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check contents of memory at address 12.
|
||||||
|
let mut buf = [0u8; 1];
|
||||||
|
env.memory.unwrap().get_into(12, &mut buf).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(&buf, &[4]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pull_internal_mem_from_module() {
|
||||||
|
let module = parse_wat(
|
||||||
|
r#"
|
||||||
|
(module
|
||||||
|
(import "env" "inc_mem" (func $inc_mem (param i32)))
|
||||||
|
(import "env" "get_mem" (func $get_mem (param i32) (result i32)))
|
||||||
|
|
||||||
|
;; declare internal memory and export it under name "mem"
|
||||||
|
(memory (export "mem") 1 1)
|
||||||
|
|
||||||
|
(func (export "test") (result i32)
|
||||||
|
;; Increment value at address 1337
|
||||||
|
(call $inc_mem (i32.const 1337))
|
||||||
|
|
||||||
|
;; Return value at address 1337
|
||||||
|
(call $get_mem (i32.const 1337))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut env = TestHost {
|
||||||
|
memory: None,
|
||||||
|
instance: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let instance = ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &env))
|
||||||
|
.expect("Failed to instantiate module")
|
||||||
|
.assert_no_start();
|
||||||
|
|
||||||
|
// Get memory instance exported by name 'mem' from the module instance.
|
||||||
|
let internal_mem = instance
|
||||||
|
.export_by_name("mem")
|
||||||
|
.expect("Module expected to have 'mem' export")
|
||||||
|
.as_memory()
|
||||||
|
.expect("'mem' export should be a memory");
|
||||||
|
|
||||||
|
env.memory = Some(internal_mem);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
instance.invoke_export("test", &[], &mut env).unwrap(),
|
||||||
|
Some(RuntimeValue::I32(1))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn recursion() {
|
||||||
|
let module = parse_wat(
|
||||||
|
r#"
|
||||||
|
(module
|
||||||
|
;; Import 'recurse' function. Upon a call it will call back inside
|
||||||
|
;; this module, namely to function 'recursive' defined below.
|
||||||
|
(import "env" "recurse" (func $recurse (param i64) (result i64)))
|
||||||
|
|
||||||
|
;; Note that we import same function but with different type signature
|
||||||
|
;; this is possible since 'recurse' is a host function and it is defined
|
||||||
|
;; to be polymorphic.
|
||||||
|
(import "env" "recurse" (func (param f32) (result f32)))
|
||||||
|
|
||||||
|
(func (export "recursive") (param i64) (result i64)
|
||||||
|
;; return arg_0 + 42;
|
||||||
|
(i64.add
|
||||||
|
(get_local 0)
|
||||||
|
(i64.const 42)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(func (export "test") (result i64)
|
||||||
|
(call $recurse (i64.const 321))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut env = TestHost::new();
|
||||||
|
|
||||||
|
let instance = ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &env))
|
||||||
|
.expect("Failed to instantiate module")
|
||||||
|
.assert_no_start();
|
||||||
|
|
||||||
|
// Put instance into the env, because $recurse function expects
|
||||||
|
// attached module instance.
|
||||||
|
env.instance = Some(instance.clone());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
instance.invoke_export("test", &[], &mut env).expect(
|
||||||
|
"Failed to invoke 'test' function",
|
||||||
|
),
|
||||||
|
// 363 = 321 + 42
|
||||||
|
Some(RuntimeValue::I64(363))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn defer_providing_externals() {
|
||||||
|
const INC_FUNC_INDEX: usize = 0;
|
||||||
|
|
||||||
|
/// `HostImportResolver` will be passed at instantiation time.
|
||||||
|
///
|
||||||
|
/// Main purpose of this struct is to statsify imports of
|
||||||
|
/// the module being instantiated.
|
||||||
|
struct HostImportResolver {
|
||||||
|
mem: MemoryRef,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModuleImportResolver for HostImportResolver {
|
||||||
|
fn resolve_func(
|
||||||
|
&self,
|
||||||
|
field_name: &str,
|
||||||
|
func_type: &FunctionType,
|
||||||
|
) -> Result<FuncRef, Error> {
|
||||||
|
if field_name != "inc" {
|
||||||
|
return Err(Error::Instantiation(
|
||||||
|
format!("Export {} not found", field_name),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if func_type.params() != &[ValueType::I32] || func_type.return_type() != None {
|
||||||
|
return Err(Error::Instantiation(format!(
|
||||||
|
"Export `{}` doesnt match expected type {:?}",
|
||||||
|
field_name,
|
||||||
|
func_type
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(FuncInstance::alloc_host(func_type.clone(), INC_FUNC_INDEX))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_memory(
|
||||||
|
&self,
|
||||||
|
field_name: &str,
|
||||||
|
_memory_type: &MemoryType,
|
||||||
|
) -> Result<MemoryRef, Error> {
|
||||||
|
if field_name == "mem" {
|
||||||
|
Ok(self.mem.clone())
|
||||||
|
} else {
|
||||||
|
Err(Error::Instantiation(
|
||||||
|
format!("Export {} not found", field_name),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This struct implements external functions that can be called
|
||||||
|
/// by wasm module.
|
||||||
|
struct HostExternals<'a> {
|
||||||
|
acc: &'a mut u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Externals for HostExternals<'a> {
|
||||||
|
fn invoke_index(
|
||||||
|
&mut self,
|
||||||
|
index: usize,
|
||||||
|
args: &[RuntimeValue],
|
||||||
|
) -> Result<Option<RuntimeValue>, Error> {
|
||||||
|
match index {
|
||||||
|
INC_FUNC_INDEX => {
|
||||||
|
let mut args = args.iter().cloned();
|
||||||
|
let a: u32 = args.next().unwrap().try_into().unwrap();
|
||||||
|
*self.acc += a;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
_ => panic!("env module doesn't provide function at index {}", index),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let module = parse_wat(
|
||||||
|
r#"
|
||||||
|
(module
|
||||||
|
;; Just to require 'mem' from 'host'.
|
||||||
|
(import "host" "mem" (memory 1))
|
||||||
|
(import "host" "inc" (func $inc (param i32)))
|
||||||
|
|
||||||
|
(func (export "test")
|
||||||
|
(call $inc (i32.const 1))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create HostImportResolver with some initialized memory instance.
|
||||||
|
// This memory instance will be provided as 'mem' export.
|
||||||
|
let host_import_resolver =
|
||||||
|
HostImportResolver { mem: MemoryInstance::alloc(1, Some(1)).unwrap() };
|
||||||
|
|
||||||
|
// Instantiate module with `host_import_resolver` as import resolver for "host" module.
|
||||||
|
let instance = ModuleInstance::new(
|
||||||
|
&module,
|
||||||
|
&ImportsBuilder::new().with_resolver("host", &host_import_resolver),
|
||||||
|
).expect("Failed to instantiate module")
|
||||||
|
.assert_no_start();
|
||||||
|
|
||||||
|
let mut acc = 89;
|
||||||
|
{
|
||||||
|
let mut host_externals = HostExternals { acc: &mut acc };
|
||||||
|
|
||||||
|
instance
|
||||||
|
.invoke_export("test", &[], &mut host_externals)
|
||||||
|
.unwrap(); // acc += 1;
|
||||||
|
instance
|
||||||
|
.invoke_export("test", &[], &mut host_externals)
|
||||||
|
.unwrap(); // acc += 1;
|
||||||
|
}
|
||||||
|
assert_eq!(acc, 91);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn two_envs_one_externals() {
|
||||||
|
const PRIVILEGED_FUNC_INDEX: usize = 0;
|
||||||
|
const ORDINARY_FUNC_INDEX: usize = 1;
|
||||||
|
|
||||||
|
struct HostExternals;
|
||||||
|
|
||||||
|
impl Externals for HostExternals {
|
||||||
|
fn invoke_index(
|
||||||
|
&mut self,
|
||||||
|
index: usize,
|
||||||
|
_args: &[RuntimeValue],
|
||||||
|
) -> Result<Option<RuntimeValue>, Error> {
|
||||||
|
match index {
|
||||||
|
PRIVILEGED_FUNC_INDEX => {
|
||||||
|
println!("privileged!");
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
ORDINARY_FUNC_INDEX => Ok(None),
|
||||||
|
_ => panic!("env module doesn't provide function at index {}", index),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PrivilegedResolver;
|
||||||
|
struct OrdinaryResolver;
|
||||||
|
|
||||||
|
impl ModuleImportResolver for PrivilegedResolver {
|
||||||
|
fn resolve_func(
|
||||||
|
&self,
|
||||||
|
field_name: &str,
|
||||||
|
func_type: &FunctionType,
|
||||||
|
) -> Result<FuncRef, Error> {
|
||||||
|
let index = match field_name {
|
||||||
|
"ordinary" => ORDINARY_FUNC_INDEX,
|
||||||
|
"privileged" => PRIVILEGED_FUNC_INDEX,
|
||||||
|
_ => {
|
||||||
|
return Err(Error::Instantiation(
|
||||||
|
format!("Export {} not found", field_name),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(FuncInstance::alloc_host(func_type.clone(), index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModuleImportResolver for OrdinaryResolver {
|
||||||
|
fn resolve_func(
|
||||||
|
&self,
|
||||||
|
field_name: &str,
|
||||||
|
func_type: &FunctionType,
|
||||||
|
) -> Result<FuncRef, Error> {
|
||||||
|
let index = match field_name {
|
||||||
|
"ordinary" => ORDINARY_FUNC_INDEX,
|
||||||
|
"privileged" => {
|
||||||
|
return Err(Error::Instantiation(
|
||||||
|
"'priveleged' can be imported only in privileged context".into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(Error::Instantiation(
|
||||||
|
format!("Export {} not found", field_name),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(FuncInstance::alloc_host(func_type.clone(), index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let trusted_module = parse_wat(
|
||||||
|
r#"
|
||||||
|
(module
|
||||||
|
;; Trusted module can import both ordinary and privileged functions.
|
||||||
|
(import "env" "ordinary" (func $ordinary))
|
||||||
|
(import "env" "privileged" (func $privileged))
|
||||||
|
(func (export "do_trusted_things")
|
||||||
|
(call $ordinary)
|
||||||
|
(call $privileged)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let untrusted_module = parse_wat(
|
||||||
|
r#"
|
||||||
|
(module
|
||||||
|
;; Untrusted module can import only ordinary functions.
|
||||||
|
(import "env" "ordinary" (func $ordinary))
|
||||||
|
(import "trusted" "do_trusted_things" (func $do_trusted_things))
|
||||||
|
(func (export "test")
|
||||||
|
(call $ordinary)
|
||||||
|
(call $do_trusted_things)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let trusted_instance = ModuleInstance::new(
|
||||||
|
&trusted_module,
|
||||||
|
&ImportsBuilder::new().with_resolver("env", &PrivilegedResolver),
|
||||||
|
).expect("Failed to instantiate module")
|
||||||
|
.assert_no_start();
|
||||||
|
|
||||||
|
let untrusted_instance = ModuleInstance::new(
|
||||||
|
&untrusted_module,
|
||||||
|
&ImportsBuilder::new()
|
||||||
|
.with_resolver("env", &OrdinaryResolver)
|
||||||
|
.with_resolver("trusted", &trusted_instance),
|
||||||
|
).expect("Failed to instantiate module")
|
||||||
|
.assert_no_start();
|
||||||
|
|
||||||
|
untrusted_instance
|
||||||
|
.invoke_export("test", &[], &mut HostExternals)
|
||||||
|
.expect("Failed to invoke 'test' function");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dynamically_add_host_func() {
|
||||||
|
const ADD_FUNC_FUNC_INDEX: usize = 0;
|
||||||
|
|
||||||
|
struct HostExternals {
|
||||||
|
table: TableRef,
|
||||||
|
added_funcs: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Externals for HostExternals {
|
||||||
|
fn invoke_index(
|
||||||
|
&mut self,
|
||||||
|
index: usize,
|
||||||
|
_args: &[RuntimeValue],
|
||||||
|
) -> Result<Option<RuntimeValue>, Error> {
|
||||||
|
match index {
|
||||||
|
ADD_FUNC_FUNC_INDEX => {
|
||||||
|
// Allocate indicies for the new function.
|
||||||
|
// host_func_index is in host index space, and first index is occupied by ADD_FUNC_FUNC_INDEX.
|
||||||
|
let table_index = self.added_funcs;
|
||||||
|
let host_func_index = table_index + 1;
|
||||||
|
self.added_funcs += 1;
|
||||||
|
|
||||||
|
let added_func = FuncInstance::alloc_host(
|
||||||
|
FunctionType::new(vec![], Some(ValueType::I32)),
|
||||||
|
host_func_index as usize,
|
||||||
|
);
|
||||||
|
self.table.set(table_index, Some(added_func))?;
|
||||||
|
|
||||||
|
Ok(Some(RuntimeValue::I32(table_index as i32)))
|
||||||
|
}
|
||||||
|
index if index as u32 <= self.added_funcs => {
|
||||||
|
Ok(Some(RuntimeValue::I32(index as i32)))
|
||||||
|
}
|
||||||
|
_ => panic!("'env' module doesn't provide function at index {}", index),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModuleImportResolver for HostExternals {
|
||||||
|
fn resolve_func(
|
||||||
|
&self,
|
||||||
|
field_name: &str,
|
||||||
|
func_type: &FunctionType,
|
||||||
|
) -> Result<FuncRef, Error> {
|
||||||
|
let index = match field_name {
|
||||||
|
"add_func" => ADD_FUNC_FUNC_INDEX,
|
||||||
|
_ => {
|
||||||
|
return Err(Error::Instantiation(
|
||||||
|
format!("Export {} not found", field_name),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(FuncInstance::alloc_host(func_type.clone(), index))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_table(
|
||||||
|
&self,
|
||||||
|
field_name: &str,
|
||||||
|
_table_type: &TableType,
|
||||||
|
) -> Result<TableRef, Error> {
|
||||||
|
if field_name == "table" {
|
||||||
|
Ok(self.table.clone())
|
||||||
|
} else {
|
||||||
|
Err(Error::Instantiation(
|
||||||
|
format!("Export {} not found", field_name),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut host_externals = HostExternals {
|
||||||
|
table: TableInstance::alloc(10, None).unwrap(),
|
||||||
|
added_funcs: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let module = parse_wat(
|
||||||
|
r#"
|
||||||
|
(module
|
||||||
|
(type $t0 (func (result i32)))
|
||||||
|
(import "env" "add_func" (func $add_func (result i32)))
|
||||||
|
(import "env" "table" (table 10 anyfunc))
|
||||||
|
(func (export "test") (result i32)
|
||||||
|
;; Call add_func but discard the result
|
||||||
|
call $add_func
|
||||||
|
drop
|
||||||
|
|
||||||
|
;; Call add_func and then make an indirect call with the returned index
|
||||||
|
call $add_func
|
||||||
|
call_indirect (type $t0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let instance = ModuleInstance::new(
|
||||||
|
&module,
|
||||||
|
&ImportsBuilder::new().with_resolver("env", &host_externals),
|
||||||
|
).expect("Failed to instantiate module")
|
||||||
|
.assert_no_start();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
instance
|
||||||
|
.invoke_export("test", &[], &mut host_externals)
|
||||||
|
.expect("Failed to invoke 'test' function"),
|
||||||
|
Some(RuntimeValue::I32(2))
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
mod host;
|
||||||
|
mod wasm;
|
|
@ -0,0 +1,142 @@
|
||||||
|
use parity_wasm::elements::deserialize_file;
|
||||||
|
use parity_wasm::elements::{FunctionType, GlobalType, MemoryType, Module, TableType};
|
||||||
|
use {Error, FuncRef, GlobalInstance, GlobalRef, ImportsBuilder, MemoryInstance,
|
||||||
|
MemoryRef, ModuleImportResolver, ModuleInstance, NopExternals, RuntimeValue,
|
||||||
|
TableInstance, TableRef};
|
||||||
|
use validation::validate_module;
|
||||||
|
|
||||||
|
struct Env {
|
||||||
|
table_base: GlobalRef,
|
||||||
|
memory_base: GlobalRef,
|
||||||
|
memory: MemoryRef,
|
||||||
|
table: TableRef,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Env {
|
||||||
|
fn new() -> Env {
|
||||||
|
Env {
|
||||||
|
table_base: GlobalInstance::alloc(RuntimeValue::I32(0), false),
|
||||||
|
memory_base: GlobalInstance::alloc(RuntimeValue::I32(0), false),
|
||||||
|
memory: MemoryInstance::alloc(256, None).unwrap(),
|
||||||
|
table: TableInstance::alloc(64, None).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModuleImportResolver for Env {
|
||||||
|
fn resolve_func(&self, _field_name: &str, _func_type: &FunctionType) -> Result<FuncRef, Error> {
|
||||||
|
Err(Error::Instantiation(
|
||||||
|
"env module doesn't provide any functions".into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_global(
|
||||||
|
&self,
|
||||||
|
field_name: &str,
|
||||||
|
_global_type: &GlobalType,
|
||||||
|
) -> Result<GlobalRef, Error> {
|
||||||
|
match field_name {
|
||||||
|
"tableBase" => Ok(self.table_base.clone()),
|
||||||
|
"memoryBase" => Ok(self.memory_base.clone()),
|
||||||
|
_ => Err(Error::Instantiation(format!(
|
||||||
|
"env module doesn't provide global '{}'",
|
||||||
|
field_name
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_memory(
|
||||||
|
&self,
|
||||||
|
field_name: &str,
|
||||||
|
_memory_type: &MemoryType,
|
||||||
|
) -> Result<MemoryRef, Error> {
|
||||||
|
match field_name {
|
||||||
|
"memory" => Ok(self.memory.clone()),
|
||||||
|
_ => Err(Error::Instantiation(format!(
|
||||||
|
"env module doesn't provide memory '{}'",
|
||||||
|
field_name
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_table(&self, field_name: &str, _table_type: &TableType) -> Result<TableRef, Error> {
|
||||||
|
match field_name {
|
||||||
|
"table" => Ok(self.table.clone()),
|
||||||
|
_ => Err(Error::Instantiation(
|
||||||
|
format!("env module doesn't provide table '{}'", field_name),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn interpreter_inc_i32() {
|
||||||
|
// Name of function contained in WASM file (note the leading underline)
|
||||||
|
const FUNCTION_NAME: &'static str = "_inc_i32";
|
||||||
|
// The WASM file containing the module and function
|
||||||
|
const WASM_FILE: &str = &"res/cases/v1/inc_i32.wasm";
|
||||||
|
|
||||||
|
let module: Module =
|
||||||
|
deserialize_file(WASM_FILE).expect("Failed to deserialize module from buffer");
|
||||||
|
let validated_module = validate_module(module).expect("Failed to validate module");
|
||||||
|
|
||||||
|
let env = Env::new();
|
||||||
|
|
||||||
|
let instance = ModuleInstance::new(
|
||||||
|
&validated_module,
|
||||||
|
&ImportsBuilder::new().with_resolver("env", &env),
|
||||||
|
).expect("Failed to instantiate module")
|
||||||
|
.assert_no_start();
|
||||||
|
|
||||||
|
let i32_val = 42;
|
||||||
|
// the functions expects a single i32 parameter
|
||||||
|
let args = &[RuntimeValue::I32(i32_val)];
|
||||||
|
let exp_retval = Some(RuntimeValue::I32(i32_val + 1));
|
||||||
|
|
||||||
|
let retval = instance
|
||||||
|
.invoke_export(FUNCTION_NAME, args, &mut NopExternals)
|
||||||
|
.expect("");
|
||||||
|
assert_eq!(exp_retval, retval);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn interpreter_accumulate_u8() {
|
||||||
|
// Name of function contained in WASM file (note the leading underline)
|
||||||
|
const FUNCTION_NAME: &'static str = "_accumulate_u8";
|
||||||
|
// The WASM file containing the module and function
|
||||||
|
const WASM_FILE: &str = &"res/cases/v1/accumulate_u8.wasm";
|
||||||
|
// The octet sequence being accumulated
|
||||||
|
const BUF: &[u8] = &[9,8,7,6,5,4,3,2,1];
|
||||||
|
|
||||||
|
|
||||||
|
// Load the module-structure from wasm-file and add to program
|
||||||
|
let module: Module =
|
||||||
|
deserialize_file(WASM_FILE).expect("Failed to deserialize module from buffer");
|
||||||
|
let validated_module = validate_module(module).expect("Failed to validate module");
|
||||||
|
|
||||||
|
let env = Env::new();
|
||||||
|
let instance = ModuleInstance::new(
|
||||||
|
&validated_module,
|
||||||
|
&ImportsBuilder::new().with_resolver("env", &env),
|
||||||
|
).expect("Failed to instantiate module")
|
||||||
|
.assert_no_start();
|
||||||
|
|
||||||
|
let env_memory = env.memory.clone();
|
||||||
|
|
||||||
|
// Place the octet-sequence at index 0 in linear memory
|
||||||
|
let offset: u32 = 0;
|
||||||
|
let _ = env_memory.set(offset, BUF);
|
||||||
|
|
||||||
|
// Set up the function argument list and invoke the function
|
||||||
|
let args = &[RuntimeValue::I32(BUF.len() as i32), RuntimeValue::I32(offset as i32)];
|
||||||
|
let retval = instance
|
||||||
|
.invoke_export(FUNCTION_NAME, args, &mut NopExternals)
|
||||||
|
.expect("Failed to execute function");
|
||||||
|
|
||||||
|
// For verification, repeat accumulation using native code
|
||||||
|
let accu = BUF.into_iter().fold(0 as i32, |a, b| a + *b as i32);
|
||||||
|
let exp_retval: Option<RuntimeValue> = Some(RuntimeValue::I32(accu));
|
||||||
|
|
||||||
|
// Verify calculation from WebAssembly runtime is identical to expected result
|
||||||
|
assert_eq!(exp_retval, retval);
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
use parity_wasm::elements::{MemoryType, TableType, GlobalType, BlockType, ValueType, FunctionType};
|
||||||
|
use validation::Error;
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct ModuleContext {
|
||||||
|
pub memories: Vec<MemoryType>,
|
||||||
|
pub tables: Vec<TableType>,
|
||||||
|
pub globals: Vec<GlobalType>,
|
||||||
|
pub types: Vec<FunctionType>,
|
||||||
|
pub func_type_indexes: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModuleContext {
|
||||||
|
pub fn memories(&self) -> &[MemoryType] {
|
||||||
|
&self.memories
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tables(&self) -> &[TableType] {
|
||||||
|
&self.tables
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn globals(&self) -> &[GlobalType] {
|
||||||
|
&self.globals
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn types(&self) -> &[FunctionType] {
|
||||||
|
&self.types
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn func_type_indexes(&self) -> &[u32] {
|
||||||
|
&self.func_type_indexes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn require_memory(&self, idx: u32) -> Result<(), Error> {
|
||||||
|
if self.memories().get(idx as usize).is_none() {
|
||||||
|
return Err(Error(format!("Memory at index {} doesn't exists", idx)));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn require_table(&self, idx: u32) -> Result<&TableType, Error> {
|
||||||
|
self.tables()
|
||||||
|
.get(idx as usize)
|
||||||
|
.ok_or_else(|| Error(format!("Table at index {} doesn't exists", idx)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn require_function(&self, idx: u32) -> Result<(&[ValueType], BlockType), Error> {
|
||||||
|
let ty_idx = self.func_type_indexes()
|
||||||
|
.get(idx as usize)
|
||||||
|
.ok_or_else(|| Error(format!("Function at index {} doesn't exists", idx)))?;
|
||||||
|
self.require_function_type(*ty_idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn require_function_type(&self, idx: u32) -> Result<(&[ValueType], BlockType), Error> {
|
||||||
|
let ty = self.types()
|
||||||
|
.get(idx as usize)
|
||||||
|
.ok_or_else(|| Error(format!("Type at index {} doesn't exists", idx)))?;
|
||||||
|
|
||||||
|
let params = ty.params();
|
||||||
|
let return_ty = ty.return_type()
|
||||||
|
.map(BlockType::Value)
|
||||||
|
.unwrap_or(BlockType::NoResult);
|
||||||
|
Ok((params, return_ty))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn require_global(&self, idx: u32, mutability: Option<bool>) -> Result<&GlobalType, Error> {
|
||||||
|
let global = self.globals()
|
||||||
|
.get(idx as usize)
|
||||||
|
.ok_or_else(|| Error(format!("Global at index {} doesn't exists", idx)))?;
|
||||||
|
|
||||||
|
if let Some(expected_mutable) = mutability {
|
||||||
|
if expected_mutable && !global.is_mutable() {
|
||||||
|
return Err(Error(format!("Expected global {} to be mutable", idx)));
|
||||||
|
}
|
||||||
|
if !expected_mutable && global.is_mutable() {
|
||||||
|
return Err(Error(format!("Expected global {} to be immutable", idx)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(global)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ModuleContextBuilder {
|
||||||
|
memories: Vec<MemoryType>,
|
||||||
|
tables: Vec<TableType>,
|
||||||
|
globals: Vec<GlobalType>,
|
||||||
|
types: Vec<FunctionType>,
|
||||||
|
func_type_indexes: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModuleContextBuilder {
|
||||||
|
pub fn new() -> ModuleContextBuilder {
|
||||||
|
ModuleContextBuilder::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_memory(&mut self, memory: MemoryType) {
|
||||||
|
self.memories.push(memory);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_table(&mut self, table: TableType) {
|
||||||
|
self.tables.push(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_global(&mut self, global: GlobalType) {
|
||||||
|
self.globals.push(global);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_types(&mut self, types: Vec<FunctionType>) {
|
||||||
|
self.types = types;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_func_type_index(&mut self, func_type_index: u32) {
|
||||||
|
self.func_type_indexes.push(func_type_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self) -> ModuleContext {
|
||||||
|
let ModuleContextBuilder {
|
||||||
|
memories,
|
||||||
|
tables,
|
||||||
|
globals,
|
||||||
|
types,
|
||||||
|
func_type_indexes,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
ModuleContext {
|
||||||
|
memories,
|
||||||
|
tables,
|
||||||
|
globals,
|
||||||
|
types,
|
||||||
|
func_type_indexes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,777 @@
|
||||||
|
use std::u32;
|
||||||
|
use std::iter::repeat;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use parity_wasm::elements::{Opcode, BlockType, ValueType, TableElementType, Func, FuncBody};
|
||||||
|
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
|
||||||
|
use validation::context::ModuleContext;
|
||||||
|
|
||||||
|
use validation::Error;
|
||||||
|
|
||||||
|
use common::stack::StackWithLimit;
|
||||||
|
use common::{BlockFrame, BlockFrameType};
|
||||||
|
|
||||||
|
/// Constant from wabt' validator.cc to skip alignment validation (not a part of spec).
|
||||||
|
const NATURAL_ALIGNMENT: u32 = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
/// Maximum number of entries in value stack.
|
||||||
|
const DEFAULT_VALUE_STACK_LIMIT: usize = 16384;
|
||||||
|
/// Maximum number of entries in frame stack.
|
||||||
|
const DEFAULT_FRAME_STACK_LIMIT: usize = 1024;
|
||||||
|
|
||||||
|
/// Function validation context.
|
||||||
|
struct FunctionValidationContext<'a> {
|
||||||
|
/// Wasm module
|
||||||
|
module: &'a ModuleContext,
|
||||||
|
/// Current instruction position.
|
||||||
|
position: usize,
|
||||||
|
/// Local variables.
|
||||||
|
locals: &'a [ValueType],
|
||||||
|
/// Value stack.
|
||||||
|
value_stack: StackWithLimit<StackValueType>,
|
||||||
|
/// Frame stack.
|
||||||
|
frame_stack: StackWithLimit<BlockFrame>,
|
||||||
|
/// Function return type. None if validating expression.
|
||||||
|
return_type: Option<BlockType>,
|
||||||
|
/// Labels positions.
|
||||||
|
labels: HashMap<usize, usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Value type on the stack.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
enum StackValueType {
|
||||||
|
/// Any value type.
|
||||||
|
Any,
|
||||||
|
/// Any number of any values of any type.
|
||||||
|
AnyUnlimited,
|
||||||
|
/// Concrete value type.
|
||||||
|
Specific(ValueType),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Function validator.
|
||||||
|
pub struct Validator;
|
||||||
|
|
||||||
|
/// Instruction outcome.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum InstructionOutcome {
|
||||||
|
/// Continue with next instruction.
|
||||||
|
ValidateNextInstruction,
|
||||||
|
/// Unreachable instruction reached.
|
||||||
|
Unreachable,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Validator {
|
||||||
|
pub fn validate_function(
|
||||||
|
module: &ModuleContext,
|
||||||
|
func: &Func,
|
||||||
|
body: &FuncBody,
|
||||||
|
) -> Result<HashMap<usize, usize>, Error> {
|
||||||
|
let (params, result_ty) = module.require_function_type(func.type_ref())?;
|
||||||
|
|
||||||
|
// locals = (params + vars)
|
||||||
|
let mut locals = params.to_vec();
|
||||||
|
locals.extend(
|
||||||
|
body.locals()
|
||||||
|
.iter()
|
||||||
|
.flat_map(|l| repeat(l.value_type())
|
||||||
|
.take(l.count() as usize)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut context = FunctionValidationContext::new(
|
||||||
|
&module,
|
||||||
|
&locals,
|
||||||
|
DEFAULT_VALUE_STACK_LIMIT,
|
||||||
|
DEFAULT_FRAME_STACK_LIMIT,
|
||||||
|
result_ty,
|
||||||
|
);
|
||||||
|
|
||||||
|
context.push_label(BlockFrameType::Function, result_ty)?;
|
||||||
|
Validator::validate_function_block(&mut context, body.code().elements())?;
|
||||||
|
while !context.frame_stack.is_empty() {
|
||||||
|
context.pop_label()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(context.into_labels())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_function_block(context: &mut FunctionValidationContext, body: &[Opcode]) -> Result<(), Error> {
|
||||||
|
let body_len = body.len();
|
||||||
|
if body_len == 0 {
|
||||||
|
return Err(Error("Non-empty function body expected".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let opcode = &body[context.position];
|
||||||
|
match Validator::validate_instruction(context, opcode)? {
|
||||||
|
InstructionOutcome::ValidateNextInstruction => (),
|
||||||
|
InstructionOutcome::Unreachable => context.unreachable()?,
|
||||||
|
}
|
||||||
|
|
||||||
|
context.position += 1;
|
||||||
|
if context.position == body_len {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_instruction(context: &mut FunctionValidationContext, opcode: &Opcode) -> Result<InstructionOutcome, Error> {
|
||||||
|
use self::Opcode::*;
|
||||||
|
match *opcode {
|
||||||
|
Unreachable => Ok(InstructionOutcome::Unreachable),
|
||||||
|
Nop => Ok(InstructionOutcome::ValidateNextInstruction),
|
||||||
|
Block(block_type) => Validator::validate_block(context, block_type),
|
||||||
|
Loop(block_type) => Validator::validate_loop(context, block_type),
|
||||||
|
If(block_type) => Validator::validate_if(context, block_type),
|
||||||
|
Else => Validator::validate_else(context),
|
||||||
|
End => Validator::validate_end(context),
|
||||||
|
Br(idx) => Validator::validate_br(context, idx),
|
||||||
|
BrIf(idx) => Validator::validate_br_if(context, idx),
|
||||||
|
BrTable(ref table, default) => Validator::validate_br_table(context, table, default),
|
||||||
|
Return => Validator::validate_return(context),
|
||||||
|
|
||||||
|
Call(index) => Validator::validate_call(context, index),
|
||||||
|
CallIndirect(index, _reserved) => Validator::validate_call_indirect(context, index),
|
||||||
|
|
||||||
|
Drop => Validator::validate_drop(context),
|
||||||
|
Select => Validator::validate_select(context),
|
||||||
|
|
||||||
|
GetLocal(index) => Validator::validate_get_local(context, index),
|
||||||
|
SetLocal(index) => Validator::validate_set_local(context, index),
|
||||||
|
TeeLocal(index) => Validator::validate_tee_local(context, index),
|
||||||
|
GetGlobal(index) => Validator::validate_get_global(context, index),
|
||||||
|
SetGlobal(index) => Validator::validate_set_global(context, index),
|
||||||
|
|
||||||
|
I32Load(align, _) => Validator::validate_load(context, align, 4, ValueType::I32),
|
||||||
|
I64Load(align, _) => Validator::validate_load(context, align, 8, ValueType::I64),
|
||||||
|
F32Load(align, _) => Validator::validate_load(context, align, 4, ValueType::F32),
|
||||||
|
F64Load(align, _) => Validator::validate_load(context, align, 8, ValueType::F64),
|
||||||
|
I32Load8S(align, _) => Validator::validate_load(context, align, 1, ValueType::I32),
|
||||||
|
I32Load8U(align, _) => Validator::validate_load(context, align, 1, ValueType::I32),
|
||||||
|
I32Load16S(align, _) => Validator::validate_load(context, align, 2, ValueType::I32),
|
||||||
|
I32Load16U(align, _) => Validator::validate_load(context, align, 2, ValueType::I32),
|
||||||
|
I64Load8S(align, _) => Validator::validate_load(context, align, 1, ValueType::I64),
|
||||||
|
I64Load8U(align, _) => Validator::validate_load(context, align, 1, ValueType::I64),
|
||||||
|
I64Load16S(align, _) => Validator::validate_load(context, align, 2, ValueType::I64),
|
||||||
|
I64Load16U(align, _) => Validator::validate_load(context, align, 2, ValueType::I64),
|
||||||
|
I64Load32S(align, _) => Validator::validate_load(context, align, 4, ValueType::I64),
|
||||||
|
I64Load32U(align, _) => Validator::validate_load(context, align, 4, ValueType::I64),
|
||||||
|
|
||||||
|
I32Store(align, _) => Validator::validate_store(context, align, 4, ValueType::I32),
|
||||||
|
I64Store(align, _) => Validator::validate_store(context, align, 8, ValueType::I64),
|
||||||
|
F32Store(align, _) => Validator::validate_store(context, align, 4, ValueType::F32),
|
||||||
|
F64Store(align, _) => Validator::validate_store(context, align, 8, ValueType::F64),
|
||||||
|
I32Store8(align, _) => Validator::validate_store(context, align, 1, ValueType::I32),
|
||||||
|
I32Store16(align, _) => Validator::validate_store(context, align, 2, ValueType::I32),
|
||||||
|
I64Store8(align, _) => Validator::validate_store(context, align, 1, ValueType::I64),
|
||||||
|
I64Store16(align, _) => Validator::validate_store(context, align, 2, ValueType::I64),
|
||||||
|
I64Store32(align, _) => Validator::validate_store(context, align, 4, ValueType::I64),
|
||||||
|
|
||||||
|
CurrentMemory(_) => Validator::validate_current_memory(context),
|
||||||
|
GrowMemory(_) => Validator::validate_grow_memory(context),
|
||||||
|
|
||||||
|
I32Const(_) => Validator::validate_const(context, ValueType::I32),
|
||||||
|
I64Const(_) => Validator::validate_const(context, ValueType::I64),
|
||||||
|
F32Const(_) => Validator::validate_const(context, ValueType::F32),
|
||||||
|
F64Const(_) => Validator::validate_const(context, ValueType::F64),
|
||||||
|
|
||||||
|
I32Eqz => Validator::validate_testop(context, ValueType::I32),
|
||||||
|
I32Eq => Validator::validate_relop(context, ValueType::I32),
|
||||||
|
I32Ne => Validator::validate_relop(context, ValueType::I32),
|
||||||
|
I32LtS => Validator::validate_relop(context, ValueType::I32),
|
||||||
|
I32LtU => Validator::validate_relop(context, ValueType::I32),
|
||||||
|
I32GtS => Validator::validate_relop(context, ValueType::I32),
|
||||||
|
I32GtU => Validator::validate_relop(context, ValueType::I32),
|
||||||
|
I32LeS => Validator::validate_relop(context, ValueType::I32),
|
||||||
|
I32LeU => Validator::validate_relop(context, ValueType::I32),
|
||||||
|
I32GeS => Validator::validate_relop(context, ValueType::I32),
|
||||||
|
I32GeU => Validator::validate_relop(context, ValueType::I32),
|
||||||
|
|
||||||
|
I64Eqz => Validator::validate_testop(context, ValueType::I64),
|
||||||
|
I64Eq => Validator::validate_relop(context, ValueType::I64),
|
||||||
|
I64Ne => Validator::validate_relop(context, ValueType::I64),
|
||||||
|
I64LtS => Validator::validate_relop(context, ValueType::I64),
|
||||||
|
I64LtU => Validator::validate_relop(context, ValueType::I64),
|
||||||
|
I64GtS => Validator::validate_relop(context, ValueType::I64),
|
||||||
|
I64GtU => Validator::validate_relop(context, ValueType::I64),
|
||||||
|
I64LeS => Validator::validate_relop(context, ValueType::I64),
|
||||||
|
I64LeU => Validator::validate_relop(context, ValueType::I64),
|
||||||
|
I64GeS => Validator::validate_relop(context, ValueType::I64),
|
||||||
|
I64GeU => Validator::validate_relop(context, ValueType::I64),
|
||||||
|
|
||||||
|
F32Eq => Validator::validate_relop(context, ValueType::F32),
|
||||||
|
F32Ne => Validator::validate_relop(context, ValueType::F32),
|
||||||
|
F32Lt => Validator::validate_relop(context, ValueType::F32),
|
||||||
|
F32Gt => Validator::validate_relop(context, ValueType::F32),
|
||||||
|
F32Le => Validator::validate_relop(context, ValueType::F32),
|
||||||
|
F32Ge => Validator::validate_relop(context, ValueType::F32),
|
||||||
|
|
||||||
|
F64Eq => Validator::validate_relop(context, ValueType::F64),
|
||||||
|
F64Ne => Validator::validate_relop(context, ValueType::F64),
|
||||||
|
F64Lt => Validator::validate_relop(context, ValueType::F64),
|
||||||
|
F64Gt => Validator::validate_relop(context, ValueType::F64),
|
||||||
|
F64Le => Validator::validate_relop(context, ValueType::F64),
|
||||||
|
F64Ge => Validator::validate_relop(context, ValueType::F64),
|
||||||
|
|
||||||
|
I32Clz => Validator::validate_unop(context, ValueType::I32),
|
||||||
|
I32Ctz => Validator::validate_unop(context, ValueType::I32),
|
||||||
|
I32Popcnt => Validator::validate_unop(context, ValueType::I32),
|
||||||
|
I32Add => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32Sub => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32Mul => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32DivS => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32DivU => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32RemS => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32RemU => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32And => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32Or => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32Xor => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32Shl => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32ShrS => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32ShrU => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32Rotl => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32Rotr => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
|
||||||
|
I64Clz => Validator::validate_unop(context, ValueType::I64),
|
||||||
|
I64Ctz => Validator::validate_unop(context, ValueType::I64),
|
||||||
|
I64Popcnt => Validator::validate_unop(context, ValueType::I64),
|
||||||
|
I64Add => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64Sub => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64Mul => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64DivS => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64DivU => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64RemS => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64RemU => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64And => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64Or => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64Xor => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64Shl => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64ShrS => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64ShrU => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64Rotl => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64Rotr => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
|
||||||
|
F32Abs => Validator::validate_unop(context, ValueType::F32),
|
||||||
|
F32Neg => Validator::validate_unop(context, ValueType::F32),
|
||||||
|
F32Ceil => Validator::validate_unop(context, ValueType::F32),
|
||||||
|
F32Floor => Validator::validate_unop(context, ValueType::F32),
|
||||||
|
F32Trunc => Validator::validate_unop(context, ValueType::F32),
|
||||||
|
F32Nearest => Validator::validate_unop(context, ValueType::F32),
|
||||||
|
F32Sqrt => Validator::validate_unop(context, ValueType::F32),
|
||||||
|
F32Add => Validator::validate_binop(context, ValueType::F32),
|
||||||
|
F32Sub => Validator::validate_binop(context, ValueType::F32),
|
||||||
|
F32Mul => Validator::validate_binop(context, ValueType::F32),
|
||||||
|
F32Div => Validator::validate_binop(context, ValueType::F32),
|
||||||
|
F32Min => Validator::validate_binop(context, ValueType::F32),
|
||||||
|
F32Max => Validator::validate_binop(context, ValueType::F32),
|
||||||
|
F32Copysign => Validator::validate_binop(context, ValueType::F32),
|
||||||
|
|
||||||
|
F64Abs => Validator::validate_unop(context, ValueType::F64),
|
||||||
|
F64Neg => Validator::validate_unop(context, ValueType::F64),
|
||||||
|
F64Ceil => Validator::validate_unop(context, ValueType::F64),
|
||||||
|
F64Floor => Validator::validate_unop(context, ValueType::F64),
|
||||||
|
F64Trunc => Validator::validate_unop(context, ValueType::F64),
|
||||||
|
F64Nearest => Validator::validate_unop(context, ValueType::F64),
|
||||||
|
F64Sqrt => Validator::validate_unop(context, ValueType::F64),
|
||||||
|
F64Add => Validator::validate_binop(context, ValueType::F64),
|
||||||
|
F64Sub => Validator::validate_binop(context, ValueType::F64),
|
||||||
|
F64Mul => Validator::validate_binop(context, ValueType::F64),
|
||||||
|
F64Div => Validator::validate_binop(context, ValueType::F64),
|
||||||
|
F64Min => Validator::validate_binop(context, ValueType::F64),
|
||||||
|
F64Max => Validator::validate_binop(context, ValueType::F64),
|
||||||
|
F64Copysign => Validator::validate_binop(context, ValueType::F64),
|
||||||
|
|
||||||
|
I32WrapI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::I32),
|
||||||
|
I32TruncSF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I32),
|
||||||
|
I32TruncUF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I32),
|
||||||
|
I32TruncSF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I32),
|
||||||
|
I32TruncUF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I32),
|
||||||
|
I64ExtendSI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::I64),
|
||||||
|
I64ExtendUI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::I64),
|
||||||
|
I64TruncSF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I64),
|
||||||
|
I64TruncUF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I64),
|
||||||
|
I64TruncSF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I64),
|
||||||
|
I64TruncUF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I64),
|
||||||
|
F32ConvertSI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F32),
|
||||||
|
F32ConvertUI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F32),
|
||||||
|
F32ConvertSI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F32),
|
||||||
|
F32ConvertUI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F32),
|
||||||
|
F32DemoteF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::F32),
|
||||||
|
F64ConvertSI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F64),
|
||||||
|
F64ConvertUI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F64),
|
||||||
|
F64ConvertSI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F64),
|
||||||
|
F64ConvertUI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F64),
|
||||||
|
F64PromoteF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::F64),
|
||||||
|
|
||||||
|
I32ReinterpretF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I32),
|
||||||
|
I64ReinterpretF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I64),
|
||||||
|
F32ReinterpretI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F32),
|
||||||
|
F64ReinterpretI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F64),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_const(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.push_value(value_type.into())?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_unop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.pop_value(value_type.into())?;
|
||||||
|
context.push_value(value_type.into())?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_binop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.pop_value(value_type.into())?;
|
||||||
|
context.pop_value(value_type.into())?;
|
||||||
|
context.push_value(value_type.into())?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_testop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.pop_value(value_type.into())?;
|
||||||
|
context.push_value(ValueType::I32.into())?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_relop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.pop_value(value_type.into())?;
|
||||||
|
context.pop_value(value_type.into())?;
|
||||||
|
context.push_value(ValueType::I32.into())?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_cvtop(context: &mut FunctionValidationContext, value_type1: ValueType, value_type2: ValueType) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.pop_value(value_type1.into())?;
|
||||||
|
context.push_value(value_type2.into())?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_drop(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.pop_any_value().map(|_| ())?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_select(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.pop_value(ValueType::I32.into())?;
|
||||||
|
let select_type = context.pop_any_value()?;
|
||||||
|
context.pop_value(select_type)?;
|
||||||
|
context.push_value(select_type)?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_get_local(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
||||||
|
let local_type = context.require_local(index)?;
|
||||||
|
context.push_value(local_type)?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_set_local(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
||||||
|
let local_type = context.require_local(index)?;
|
||||||
|
let value_type = context.pop_any_value()?;
|
||||||
|
if local_type != value_type {
|
||||||
|
return Err(Error(format!("Trying to update local {} of type {:?} with value of type {:?}", index, local_type, value_type)));
|
||||||
|
}
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_tee_local(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
||||||
|
let local_type = context.require_local(index)?;
|
||||||
|
let value_type = context.tee_any_value()?;
|
||||||
|
if local_type != value_type {
|
||||||
|
return Err(Error(format!("Trying to update local {} of type {:?} with value of type {:?}", index, local_type, value_type)));
|
||||||
|
}
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_get_global(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
||||||
|
let global_type: StackValueType = {
|
||||||
|
let global = context.module.require_global(index, None)?;
|
||||||
|
global.content_type().into()
|
||||||
|
};
|
||||||
|
context.push_value(global_type)?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_set_global(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
||||||
|
let global_type: StackValueType = {
|
||||||
|
let global = context.module.require_global(index, Some(true))?;
|
||||||
|
global.content_type().into()
|
||||||
|
};
|
||||||
|
let value_type = context.pop_any_value()?;
|
||||||
|
if global_type != value_type {
|
||||||
|
return Err(Error(format!("Trying to update global {} of type {:?} with value of type {:?}", index, global_type, value_type)));
|
||||||
|
}
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_load(context: &mut FunctionValidationContext, align: u32, max_align: u32, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
||||||
|
if align != NATURAL_ALIGNMENT {
|
||||||
|
if 1u32.checked_shl(align).unwrap_or(u32::MAX) > max_align {
|
||||||
|
return Err(Error(format!("Too large memory alignment 2^{} (expected at most {})", align, max_align)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.pop_value(ValueType::I32.into())?;
|
||||||
|
context.module.require_memory(DEFAULT_MEMORY_INDEX)?;
|
||||||
|
context.push_value(value_type.into())?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_store(context: &mut FunctionValidationContext, align: u32, max_align: u32, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
||||||
|
if align != NATURAL_ALIGNMENT {
|
||||||
|
if 1u32.checked_shl(align).unwrap_or(u32::MAX) > max_align {
|
||||||
|
return Err(Error(format!("Too large memory alignment 2^{} (expected at most {})", align, max_align)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.module.require_memory(DEFAULT_MEMORY_INDEX)?;
|
||||||
|
context.pop_value(value_type.into())?;
|
||||||
|
context.pop_value(ValueType::I32.into())?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_block(context: &mut FunctionValidationContext, block_type: BlockType) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.push_label(BlockFrameType::Block, block_type).map(|_| InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_loop(context: &mut FunctionValidationContext, block_type: BlockType) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.push_label(BlockFrameType::Loop, block_type).map(|_| InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_if(context: &mut FunctionValidationContext, block_type: BlockType) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.pop_value(ValueType::I32.into())?;
|
||||||
|
context.push_label(BlockFrameType::IfTrue, block_type).map(|_| InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_else(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||||
|
let block_type = {
|
||||||
|
let top_frame = context.top_label()?;
|
||||||
|
if top_frame.frame_type != BlockFrameType::IfTrue {
|
||||||
|
return Err(Error("Misplaced else instruction".into()));
|
||||||
|
}
|
||||||
|
top_frame.block_type
|
||||||
|
};
|
||||||
|
context.pop_label()?;
|
||||||
|
|
||||||
|
if let BlockType::Value(value_type) = block_type {
|
||||||
|
context.pop_value(value_type.into())?;
|
||||||
|
}
|
||||||
|
context.push_label(BlockFrameType::IfFalse, block_type).map(|_| InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_end(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||||
|
{
|
||||||
|
let top_frame = context.top_label()?;
|
||||||
|
if top_frame.frame_type == BlockFrameType::IfTrue {
|
||||||
|
if top_frame.block_type != BlockType::NoResult {
|
||||||
|
return Err(Error(format!("If block without else required to have NoResult block type. But it have {:?} type", top_frame.block_type)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.pop_label().map(|_| InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_br(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
|
||||||
|
let (frame_type, frame_block_type) = {
|
||||||
|
let frame = context.require_label(idx)?;
|
||||||
|
(frame.frame_type, frame.block_type)
|
||||||
|
};
|
||||||
|
if frame_type != BlockFrameType::Loop {
|
||||||
|
if let BlockType::Value(value_type) = frame_block_type {
|
||||||
|
context.tee_value(value_type.into())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(InstructionOutcome::Unreachable)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_br_if(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.pop_value(ValueType::I32.into())?;
|
||||||
|
|
||||||
|
let (frame_type, frame_block_type) = {
|
||||||
|
let frame = context.require_label(idx)?;
|
||||||
|
(frame.frame_type, frame.block_type)
|
||||||
|
};
|
||||||
|
if frame_type != BlockFrameType::Loop {
|
||||||
|
if let BlockType::Value(value_type) = frame_block_type {
|
||||||
|
context.tee_value(value_type.into())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_br_table(context: &mut FunctionValidationContext, table: &Vec<u32>, default: u32) -> Result<InstructionOutcome, Error> {
|
||||||
|
let mut required_block_type = None;
|
||||||
|
|
||||||
|
{
|
||||||
|
let default_block = context.require_label(default)?;
|
||||||
|
if default_block.frame_type != BlockFrameType::Loop {
|
||||||
|
required_block_type = Some(default_block.block_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
for label in table {
|
||||||
|
let label_block = context.require_label(*label)?;
|
||||||
|
if label_block.frame_type != BlockFrameType::Loop {
|
||||||
|
if let Some(required_block_type) = required_block_type {
|
||||||
|
if required_block_type != label_block.block_type {
|
||||||
|
return Err(Error(format!("Labels in br_table points to block of different types: {:?} and {:?}", required_block_type, label_block.block_type)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
required_block_type = Some(label_block.block_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.pop_value(ValueType::I32.into())?;
|
||||||
|
if let Some(required_block_type) = required_block_type {
|
||||||
|
if let BlockType::Value(value_type) = required_block_type {
|
||||||
|
context.tee_value(value_type.into())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(InstructionOutcome::Unreachable)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_return(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||||
|
if let BlockType::Value(value_type) = context.return_type()? {
|
||||||
|
context.tee_value(value_type.into())?;
|
||||||
|
}
|
||||||
|
Ok(InstructionOutcome::Unreachable)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_call(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
|
||||||
|
let (argument_types, return_type) = context.module.require_function(idx)?;
|
||||||
|
for argument_type in argument_types.iter().rev() {
|
||||||
|
context.pop_value((*argument_type).into())?;
|
||||||
|
}
|
||||||
|
if let BlockType::Value(value_type) = return_type {
|
||||||
|
context.push_value(value_type.into())?;
|
||||||
|
}
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_call_indirect(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
|
||||||
|
{
|
||||||
|
let table = context.module.require_table(DEFAULT_TABLE_INDEX)?;
|
||||||
|
if table.elem_type() != TableElementType::AnyFunc {
|
||||||
|
return Err(Error(format!(
|
||||||
|
"Table {} has element type {:?} while `anyfunc` expected",
|
||||||
|
idx,
|
||||||
|
table.elem_type()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.pop_value(ValueType::I32.into())?;
|
||||||
|
let (argument_types, return_type) = context.module.require_function_type(idx)?;
|
||||||
|
for argument_type in argument_types.iter().rev() {
|
||||||
|
context.pop_value((*argument_type).into())?;
|
||||||
|
}
|
||||||
|
if let BlockType::Value(value_type) = return_type {
|
||||||
|
context.push_value(value_type.into())?;
|
||||||
|
}
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_current_memory(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.module.require_memory(DEFAULT_MEMORY_INDEX)?;
|
||||||
|
context.push_value(ValueType::I32.into())?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_grow_memory(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.module.require_memory(DEFAULT_MEMORY_INDEX)?;
|
||||||
|
context.pop_value(ValueType::I32.into())?;
|
||||||
|
context.push_value(ValueType::I32.into())?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FunctionValidationContext<'a> {
|
||||||
|
fn new(
|
||||||
|
module: &'a ModuleContext,
|
||||||
|
locals: &'a [ValueType],
|
||||||
|
value_stack_limit: usize,
|
||||||
|
frame_stack_limit: usize,
|
||||||
|
return_type: BlockType,
|
||||||
|
) -> Self {
|
||||||
|
FunctionValidationContext {
|
||||||
|
module: module,
|
||||||
|
position: 0,
|
||||||
|
locals: locals,
|
||||||
|
value_stack: StackWithLimit::with_limit(value_stack_limit),
|
||||||
|
frame_stack: StackWithLimit::with_limit(frame_stack_limit),
|
||||||
|
return_type: Some(return_type),
|
||||||
|
labels: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_value(&mut self, value_type: StackValueType) -> Result<(), Error> {
|
||||||
|
Ok(self.value_stack.push(value_type.into())?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_value(&mut self, value_type: StackValueType) -> Result<(), Error> {
|
||||||
|
self.check_stack_access()?;
|
||||||
|
match self.value_stack.pop()? {
|
||||||
|
StackValueType::Specific(stack_value_type) if stack_value_type == value_type => Ok(()),
|
||||||
|
StackValueType::Any => Ok(()),
|
||||||
|
StackValueType::AnyUnlimited => {
|
||||||
|
self.value_stack.push(StackValueType::AnyUnlimited)?;
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
stack_value_type @ _ => Err(Error(format!("Expected value of type {:?} on top of stack. Got {:?}", value_type, stack_value_type))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tee_value(&mut self, value_type: StackValueType) -> Result<(), Error> {
|
||||||
|
self.check_stack_access()?;
|
||||||
|
match *self.value_stack.top()? {
|
||||||
|
StackValueType::Specific(stack_value_type) if stack_value_type == value_type => Ok(()),
|
||||||
|
StackValueType::Any | StackValueType::AnyUnlimited => Ok(()),
|
||||||
|
stack_value_type @ _ => Err(Error(format!("Expected value of type {:?} on top of stack. Got {:?}", value_type, stack_value_type))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_any_value(&mut self) -> Result<StackValueType, Error> {
|
||||||
|
self.check_stack_access()?;
|
||||||
|
match self.value_stack.pop()? {
|
||||||
|
StackValueType::Specific(stack_value_type) => Ok(StackValueType::Specific(stack_value_type)),
|
||||||
|
StackValueType::Any => Ok(StackValueType::Any),
|
||||||
|
StackValueType::AnyUnlimited => {
|
||||||
|
self.value_stack.push(StackValueType::AnyUnlimited)?;
|
||||||
|
Ok(StackValueType::Any)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tee_any_value(&mut self) -> Result<StackValueType, Error> {
|
||||||
|
self.check_stack_access()?;
|
||||||
|
Ok(self.value_stack.top().map(Clone::clone)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unreachable(&mut self) -> Result<(), Error> {
|
||||||
|
Ok(self.value_stack.push(StackValueType::AnyUnlimited)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn top_label(&self) -> Result<&BlockFrame, Error> {
|
||||||
|
Ok(self.frame_stack.top()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_label(&mut self, frame_type: BlockFrameType, block_type: BlockType) -> Result<(), Error> {
|
||||||
|
Ok(self.frame_stack.push(BlockFrame {
|
||||||
|
frame_type: frame_type,
|
||||||
|
block_type: block_type,
|
||||||
|
begin_position: self.position,
|
||||||
|
branch_position: self.position,
|
||||||
|
end_position: self.position,
|
||||||
|
value_stack_len: self.value_stack.len(),
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_label(&mut self) -> Result<InstructionOutcome, Error> {
|
||||||
|
let frame = self.frame_stack.pop()?;
|
||||||
|
let actual_value_type = if self.value_stack.len() > frame.value_stack_len {
|
||||||
|
Some(self.value_stack.pop()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
self.value_stack.resize(frame.value_stack_len, StackValueType::Any);
|
||||||
|
|
||||||
|
match frame.block_type {
|
||||||
|
BlockType::NoResult if actual_value_type.map(|vt| vt.is_any_unlimited()).unwrap_or(true) => (),
|
||||||
|
BlockType::Value(required_value_type) if actual_value_type.map(|vt| vt == required_value_type).unwrap_or(false) => (),
|
||||||
|
_ => return Err(Error(format!("Expected block to return {:?} while it has returned {:?}", frame.block_type, actual_value_type))),
|
||||||
|
}
|
||||||
|
if !self.frame_stack.is_empty() {
|
||||||
|
self.labels.insert(frame.begin_position, self.position);
|
||||||
|
}
|
||||||
|
if let BlockType::Value(value_type) = frame.block_type {
|
||||||
|
self.push_value(value_type.into())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn require_label(&self, idx: u32) -> Result<&BlockFrame, Error> {
|
||||||
|
Ok(self.frame_stack.get(idx as usize)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn return_type(&self) -> Result<BlockType, Error> {
|
||||||
|
self.return_type.ok_or(Error("Trying to return from expression".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn require_local(&self, idx: u32) -> Result<StackValueType, Error> {
|
||||||
|
self.locals.get(idx as usize)
|
||||||
|
.cloned()
|
||||||
|
.map(Into::into)
|
||||||
|
.ok_or(Error(format!("Trying to access local with index {} when there are only {} locals", idx, self.locals.len())))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_stack_access(&self) -> Result<(), Error> {
|
||||||
|
let value_stack_min = self.frame_stack.top().expect("at least 1 topmost block").value_stack_len;
|
||||||
|
if self.value_stack.len() > value_stack_min {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error("Trying to access parent frame stack values.".into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_labels(self) -> HashMap<usize, usize> {
|
||||||
|
self.labels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StackValueType {
|
||||||
|
fn is_any(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
&StackValueType::Any => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_any_unlimited(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
&StackValueType::AnyUnlimited => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value_type(&self) -> ValueType {
|
||||||
|
match self {
|
||||||
|
&StackValueType::Any | &StackValueType::AnyUnlimited => unreachable!("must be checked by caller"),
|
||||||
|
&StackValueType::Specific(value_type) => value_type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ValueType> for StackValueType {
|
||||||
|
fn from(value_type: ValueType) -> Self {
|
||||||
|
StackValueType::Specific(value_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<StackValueType> for StackValueType {
|
||||||
|
fn eq(&self, other: &StackValueType) -> bool {
|
||||||
|
if self.is_any() || other.is_any() || self.is_any_unlimited() || other.is_any_unlimited() {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
self.value_type() == other.value_type()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<ValueType> for StackValueType {
|
||||||
|
fn eq(&self, other: &ValueType) -> bool {
|
||||||
|
if self.is_any() || self.is_any_unlimited() {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
self.value_type() == *other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<StackValueType> for ValueType {
|
||||||
|
fn eq(&self, other: &StackValueType) -> bool {
|
||||||
|
other == self
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,340 @@
|
||||||
|
use std::error;
|
||||||
|
use std::fmt;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use parity_wasm::elements::{
|
||||||
|
BlockType, External, GlobalEntry, GlobalType, Internal, MemoryType, Module, Opcode,
|
||||||
|
ResizableLimits, TableType, ValueType, InitExpr, Type
|
||||||
|
};
|
||||||
|
use common::stack;
|
||||||
|
use self::context::ModuleContextBuilder;
|
||||||
|
use self::func::Validator;
|
||||||
|
|
||||||
|
mod context;
|
||||||
|
mod func;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Error(String);
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for Error {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<stack::Error> for Error {
|
||||||
|
fn from(e: stack::Error) -> Error {
|
||||||
|
Error(format!("Stack: {}", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ValidatedModule {
|
||||||
|
labels: HashMap<usize, HashMap<usize, usize>>,
|
||||||
|
module: Module,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValidatedModule {
|
||||||
|
pub fn module(&self) -> &Module {
|
||||||
|
&self.module
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_module(self) -> Module {
|
||||||
|
self.module
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn labels(&self) -> &HashMap<usize, HashMap<usize, usize>> {
|
||||||
|
&self.labels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::std::ops::Deref for ValidatedModule {
|
||||||
|
type Target = Module;
|
||||||
|
fn deref(&self) -> &Module {
|
||||||
|
&self.module
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
|
||||||
|
let mut context_builder = ModuleContextBuilder::new();
|
||||||
|
let mut imported_globals = Vec::new();
|
||||||
|
let mut labels = HashMap::new();
|
||||||
|
|
||||||
|
// Copy types from module as is.
|
||||||
|
context_builder.set_types(
|
||||||
|
module
|
||||||
|
.type_section()
|
||||||
|
.map(|ts| {
|
||||||
|
ts.types()
|
||||||
|
.into_iter()
|
||||||
|
.map(|&Type::Function(ref ty)| ty)
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fill elements with imported values.
|
||||||
|
for import_entry in module
|
||||||
|
.import_section()
|
||||||
|
.map(|i| i.entries())
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
match *import_entry.external() {
|
||||||
|
External::Function(idx) => context_builder.push_func_type_index(idx),
|
||||||
|
External::Table(ref table) => context_builder.push_table(table.clone()),
|
||||||
|
External::Memory(ref memory) => context_builder.push_memory(memory.clone()),
|
||||||
|
External::Global(ref global) => {
|
||||||
|
context_builder.push_global(global.clone());
|
||||||
|
imported_globals.push(global.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concatenate elements with defined in the module.
|
||||||
|
if let Some(function_section) = module.function_section() {
|
||||||
|
for func_entry in function_section.entries() {
|
||||||
|
context_builder.push_func_type_index(func_entry.type_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(table_section) = module.table_section() {
|
||||||
|
for table_entry in table_section.entries() {
|
||||||
|
validate_table_type(table_entry)?;
|
||||||
|
context_builder.push_table(table_entry.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(mem_section) = module.memory_section() {
|
||||||
|
for mem_entry in mem_section.entries() {
|
||||||
|
validate_memory_type(mem_entry)?;
|
||||||
|
context_builder.push_memory(mem_entry.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(global_section) = module.global_section() {
|
||||||
|
for global_entry in global_section.entries() {
|
||||||
|
validate_global_entry(global_entry, &imported_globals)?;
|
||||||
|
context_builder.push_global(global_entry.global_type().clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let context = context_builder.build();
|
||||||
|
|
||||||
|
let function_section_len = module
|
||||||
|
.function_section()
|
||||||
|
.map(|s| s.entries().len())
|
||||||
|
.unwrap_or(0);
|
||||||
|
let code_section_len = module.code_section().map(|s| s.bodies().len()).unwrap_or(0);
|
||||||
|
if function_section_len != code_section_len {
|
||||||
|
return Err(Error(format!(
|
||||||
|
"length of function section is {}, while len of code section is {}",
|
||||||
|
function_section_len,
|
||||||
|
code_section_len
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate every function body in user modules
|
||||||
|
if function_section_len != 0 {
|
||||||
|
// tests use invalid code
|
||||||
|
let function_section = module.function_section().expect(
|
||||||
|
"function_section_len != 0; qed",
|
||||||
|
);
|
||||||
|
let code_section = module.code_section().expect(
|
||||||
|
"function_section_len != 0; function_section_len == code_section_len; qed",
|
||||||
|
);
|
||||||
|
// check every function body
|
||||||
|
for (index, function) in function_section.entries().iter().enumerate() {
|
||||||
|
let function_body = code_section.bodies().get(index as usize).ok_or(
|
||||||
|
Error(format!(
|
||||||
|
"Missing body for function {}",
|
||||||
|
index
|
||||||
|
)),
|
||||||
|
)?;
|
||||||
|
let func_labels = Validator::validate_function(&context, function, function_body)
|
||||||
|
.map_err(|e| {
|
||||||
|
let Error(ref msg) = e;
|
||||||
|
Error(format!("Function #{} validation error: {}", index, msg))
|
||||||
|
})?;
|
||||||
|
labels.insert(index, func_labels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate start section
|
||||||
|
if let Some(start_fn_idx) = module.start_section() {
|
||||||
|
let (params, return_ty) = context.require_function(start_fn_idx)?;
|
||||||
|
if return_ty != BlockType::NoResult || params.len() != 0 {
|
||||||
|
return Err(Error(
|
||||||
|
"start function expected to have type [] -> []".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate export section
|
||||||
|
if let Some(export_section) = module.export_section() {
|
||||||
|
for export in export_section.entries() {
|
||||||
|
match *export.internal() {
|
||||||
|
Internal::Function(function_index) => {
|
||||||
|
context.require_function(function_index)?;
|
||||||
|
}
|
||||||
|
Internal::Global(global_index) => {
|
||||||
|
context.require_global(global_index, Some(false))?;
|
||||||
|
}
|
||||||
|
Internal::Memory(memory_index) => {
|
||||||
|
context.require_memory(memory_index)?;
|
||||||
|
}
|
||||||
|
Internal::Table(table_index) => {
|
||||||
|
context.require_table(table_index)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate import section
|
||||||
|
if let Some(import_section) = module.import_section() {
|
||||||
|
for import in import_section.entries() {
|
||||||
|
match *import.external() {
|
||||||
|
External::Function(function_type_index) => {
|
||||||
|
context.require_function(function_type_index)?;
|
||||||
|
}
|
||||||
|
External::Global(ref global_type) => {
|
||||||
|
if global_type.is_mutable() {
|
||||||
|
return Err(Error(format!(
|
||||||
|
"trying to import mutable global {}",
|
||||||
|
import.field()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
External::Memory(ref memory_type) => {
|
||||||
|
validate_memory_type(memory_type)?;
|
||||||
|
}
|
||||||
|
External::Table(ref table_type) => {
|
||||||
|
validate_table_type(table_type)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// there must be no greater than 1 table in tables index space
|
||||||
|
if context.tables().len() > 1 {
|
||||||
|
return Err(Error(format!(
|
||||||
|
"too many tables in index space: {}",
|
||||||
|
context.tables().len()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// there must be no greater than 1 linear memory in memory index space
|
||||||
|
if context.memories().len() > 1 {
|
||||||
|
return Err(Error(format!(
|
||||||
|
"too many memory regions in index space: {}",
|
||||||
|
context.memories().len()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// use data section to initialize linear memory regions
|
||||||
|
if let Some(data_section) = module.data_section() {
|
||||||
|
for data_segment in data_section.entries() {
|
||||||
|
context.require_memory(data_segment.index())?;
|
||||||
|
let init_ty = expr_const_type(data_segment.offset(), context.globals())?;
|
||||||
|
if init_ty != ValueType::I32 {
|
||||||
|
return Err(Error("segment offset should return I32".into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// use element section to fill tables
|
||||||
|
if let Some(element_section) = module.elements_section() {
|
||||||
|
for element_segment in element_section.entries() {
|
||||||
|
context.require_table(element_segment.index())?;
|
||||||
|
|
||||||
|
let init_ty = expr_const_type(element_segment.offset(), context.globals())?;
|
||||||
|
if init_ty != ValueType::I32 {
|
||||||
|
return Err(Error("segment offset should return I32".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
for function_index in element_segment.members() {
|
||||||
|
context.require_function(*function_index)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ValidatedModule {
|
||||||
|
module,
|
||||||
|
labels
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_limits(limits: &ResizableLimits) -> Result<(), Error> {
|
||||||
|
if let Some(maximum) = limits.maximum() {
|
||||||
|
if limits.initial() > maximum {
|
||||||
|
return Err(Error(format!(
|
||||||
|
"maximum limit {} is lesser than minimum {}",
|
||||||
|
maximum,
|
||||||
|
limits.initial()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_memory_type(memory_type: &MemoryType) -> Result<(), Error> {
|
||||||
|
validate_limits(memory_type.limits())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_table_type(table_type: &TableType) -> Result<(), Error> {
|
||||||
|
validate_limits(table_type.limits())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_global_entry(global_entry: &GlobalEntry, globals: &[GlobalType]) -> Result<(), Error> {
|
||||||
|
let init = global_entry.init_expr();
|
||||||
|
let init_expr_ty = expr_const_type(init, globals)?;
|
||||||
|
if init_expr_ty != global_entry.global_type().content_type() {
|
||||||
|
return Err(Error(format!(
|
||||||
|
"Trying to initialize variable of type {:?} with value of type {:?}",
|
||||||
|
global_entry.global_type().content_type(),
|
||||||
|
init_expr_ty
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns type of this constant expression.
|
||||||
|
fn expr_const_type(init_expr: &InitExpr, globals: &[GlobalType]) -> Result<ValueType, Error> {
|
||||||
|
let code = init_expr.code();
|
||||||
|
if code.len() != 2 {
|
||||||
|
return Err(Error(
|
||||||
|
"Init expression should always be with length 2".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let expr_ty: ValueType = match code[0] {
|
||||||
|
Opcode::I32Const(_) => ValueType::I32,
|
||||||
|
Opcode::I64Const(_) => ValueType::I64,
|
||||||
|
Opcode::F32Const(_) => ValueType::F32,
|
||||||
|
Opcode::F64Const(_) => ValueType::F64,
|
||||||
|
Opcode::GetGlobal(idx) => {
|
||||||
|
match globals.get(idx as usize) {
|
||||||
|
Some(target_global) => {
|
||||||
|
if target_global.is_mutable() {
|
||||||
|
return Err(Error(format!("Global {} is mutable", idx)));
|
||||||
|
}
|
||||||
|
target_global.content_type()
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(Error(
|
||||||
|
format!("Global {} doesn't exists or not yet defined", idx),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return Err(Error("Non constant opcode in init expr".into())),
|
||||||
|
};
|
||||||
|
if code[1] != Opcode::End {
|
||||||
|
return Err(Error("Expression doesn't ends with `end` opcode".into()));
|
||||||
|
}
|
||||||
|
Ok(expr_ty)
|
||||||
|
}
|
|
@ -0,0 +1,301 @@
|
||||||
|
use super::validate_module;
|
||||||
|
use parity_wasm::builder::module;
|
||||||
|
use parity_wasm::elements::{
|
||||||
|
External, GlobalEntry, GlobalType, ImportEntry, InitExpr, MemoryType,
|
||||||
|
Opcode, Opcodes, TableType, ValueType, BlockType
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_is_valid() {
|
||||||
|
let module = module().build();
|
||||||
|
assert!(validate_module(module).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn limits() {
|
||||||
|
let test_cases = vec![
|
||||||
|
// min > max
|
||||||
|
(10, Some(9), false),
|
||||||
|
// min = max
|
||||||
|
(10, Some(10), true),
|
||||||
|
// table/memory is always valid without max
|
||||||
|
(10, None, true),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (min, max, is_valid) in test_cases {
|
||||||
|
// defined table
|
||||||
|
let m = module()
|
||||||
|
.table()
|
||||||
|
.with_min(min)
|
||||||
|
.with_max(max)
|
||||||
|
.build()
|
||||||
|
.build();
|
||||||
|
assert_eq!(validate_module(m).is_ok(), is_valid);
|
||||||
|
|
||||||
|
// imported table
|
||||||
|
let m = module()
|
||||||
|
.with_import(
|
||||||
|
ImportEntry::new(
|
||||||
|
"core".into(),
|
||||||
|
"table".into(),
|
||||||
|
External::Table(TableType::new(min, max))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert_eq!(validate_module(m).is_ok(), is_valid);
|
||||||
|
|
||||||
|
// defined memory
|
||||||
|
let m = module()
|
||||||
|
.memory()
|
||||||
|
.with_min(min)
|
||||||
|
.with_max(max)
|
||||||
|
.build()
|
||||||
|
.build();
|
||||||
|
assert_eq!(validate_module(m).is_ok(), is_valid);
|
||||||
|
|
||||||
|
// imported table
|
||||||
|
let m = module()
|
||||||
|
.with_import(
|
||||||
|
ImportEntry::new(
|
||||||
|
"core".into(),
|
||||||
|
"memory".into(),
|
||||||
|
External::Memory(MemoryType::new(min, max))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert_eq!(validate_module(m).is_ok(), is_valid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn global_init_const() {
|
||||||
|
let m = module()
|
||||||
|
.with_global(
|
||||||
|
GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, true),
|
||||||
|
InitExpr::new(
|
||||||
|
vec![Opcode::I32Const(42), Opcode::End]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(m).is_ok());
|
||||||
|
|
||||||
|
// init expr type differs from declared global type
|
||||||
|
let m = module()
|
||||||
|
.with_global(
|
||||||
|
GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I64, true),
|
||||||
|
InitExpr::new(vec![Opcode::I32Const(42), Opcode::End])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(m).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn global_init_global() {
|
||||||
|
let m = module()
|
||||||
|
.with_import(
|
||||||
|
ImportEntry::new(
|
||||||
|
"env".into(),
|
||||||
|
"ext_global".into(),
|
||||||
|
External::Global(GlobalType::new(ValueType::I32, false))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.with_global(
|
||||||
|
GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, true),
|
||||||
|
InitExpr::new(vec![Opcode::GetGlobal(0), Opcode::End])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(m).is_ok());
|
||||||
|
|
||||||
|
// get_global can reference only previously defined globals
|
||||||
|
let m = module()
|
||||||
|
.with_global(
|
||||||
|
GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, true),
|
||||||
|
InitExpr::new(vec![Opcode::GetGlobal(0), Opcode::End])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(m).is_err());
|
||||||
|
|
||||||
|
// get_global can reference only const globals
|
||||||
|
let m = module()
|
||||||
|
.with_import(
|
||||||
|
ImportEntry::new(
|
||||||
|
"env".into(),
|
||||||
|
"ext_global".into(),
|
||||||
|
External::Global(GlobalType::new(ValueType::I32, true))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.with_global(
|
||||||
|
GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, true),
|
||||||
|
InitExpr::new(vec![Opcode::GetGlobal(0), Opcode::End])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(m).is_err());
|
||||||
|
|
||||||
|
// get_global in init_expr can only refer to imported globals.
|
||||||
|
let m = module()
|
||||||
|
.with_global(
|
||||||
|
GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, false),
|
||||||
|
InitExpr::new(vec![Opcode::I32Const(0), Opcode::End])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.with_global(
|
||||||
|
GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, true),
|
||||||
|
InitExpr::new(vec![Opcode::GetGlobal(0), Opcode::End])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(m).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn global_init_misc() {
|
||||||
|
// without delimiting End opcode
|
||||||
|
let m = module()
|
||||||
|
.with_global(
|
||||||
|
GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, true),
|
||||||
|
InitExpr::new(vec![Opcode::I32Const(42)])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(m).is_err());
|
||||||
|
|
||||||
|
// empty init expr
|
||||||
|
let m = module()
|
||||||
|
.with_global(
|
||||||
|
GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, true),
|
||||||
|
InitExpr::new(vec![Opcode::End])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(m).is_err());
|
||||||
|
|
||||||
|
// not an constant opcode used
|
||||||
|
let m = module()
|
||||||
|
.with_global(
|
||||||
|
GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, true),
|
||||||
|
InitExpr::new(vec![Opcode::Unreachable, Opcode::End])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(m).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_limits_validity() {
|
||||||
|
// module cannot contain more than 1 memory atm.
|
||||||
|
let m = module()
|
||||||
|
.with_import(
|
||||||
|
ImportEntry::new(
|
||||||
|
"core".into(),
|
||||||
|
"memory".into(),
|
||||||
|
External::Memory(MemoryType::new(10, None))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.memory()
|
||||||
|
.with_min(10)
|
||||||
|
.build()
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(m).is_err());
|
||||||
|
|
||||||
|
// module cannot contain more than 1 table atm.
|
||||||
|
let m = module()
|
||||||
|
.with_import(
|
||||||
|
ImportEntry::new(
|
||||||
|
"core".into(),
|
||||||
|
"table".into(),
|
||||||
|
External::Table(TableType::new(10, None))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.table()
|
||||||
|
.with_min(10)
|
||||||
|
.build()
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(m).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn funcs() {
|
||||||
|
// recursive function calls is legal.
|
||||||
|
let m = module()
|
||||||
|
.function()
|
||||||
|
.signature().return_type().i32().build()
|
||||||
|
.body().with_opcodes(Opcodes::new(vec![
|
||||||
|
Opcode::Call(1),
|
||||||
|
Opcode::End,
|
||||||
|
])).build()
|
||||||
|
.build()
|
||||||
|
.function()
|
||||||
|
.signature().return_type().i32().build()
|
||||||
|
.body().with_opcodes(Opcodes::new(vec![
|
||||||
|
Opcode::Call(0),
|
||||||
|
Opcode::End,
|
||||||
|
])).build()
|
||||||
|
.build()
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(m).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn globals() {
|
||||||
|
// import immutable global is legal.
|
||||||
|
let m = module()
|
||||||
|
.with_import(
|
||||||
|
ImportEntry::new(
|
||||||
|
"env".into(),
|
||||||
|
"ext_global".into(),
|
||||||
|
External::Global(GlobalType::new(ValueType::I32, false))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(m).is_ok());
|
||||||
|
|
||||||
|
// import mutable global is invalid.
|
||||||
|
let m = module()
|
||||||
|
.with_import(
|
||||||
|
ImportEntry::new(
|
||||||
|
"env".into(),
|
||||||
|
"ext_global".into(),
|
||||||
|
External::Global(GlobalType::new(ValueType::I32, true))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(m).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn if_else_with_return_type_validation() {
|
||||||
|
let m = module()
|
||||||
|
.function()
|
||||||
|
.signature().build()
|
||||||
|
.body().with_opcodes(Opcodes::new(vec![
|
||||||
|
Opcode::I32Const(1),
|
||||||
|
Opcode::If(BlockType::NoResult),
|
||||||
|
Opcode::I32Const(1),
|
||||||
|
Opcode::If(BlockType::Value(ValueType::I32)),
|
||||||
|
Opcode::I32Const(1),
|
||||||
|
Opcode::Else,
|
||||||
|
Opcode::I32Const(2),
|
||||||
|
Opcode::End,
|
||||||
|
Opcode::Drop,
|
||||||
|
Opcode::End,
|
||||||
|
Opcode::End,
|
||||||
|
])).build()
|
||||||
|
.build()
|
||||||
|
.build();
|
||||||
|
validate_module(m).unwrap();
|
||||||
|
}
|
|
@ -0,0 +1,662 @@
|
||||||
|
use std::{i32, i64, u32, u64, f32};
|
||||||
|
use std::io;
|
||||||
|
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
use Error;
|
||||||
|
use parity_wasm::elements::ValueType;
|
||||||
|
|
||||||
|
|
||||||
|
/// Runtime value.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
pub enum RuntimeValue {
|
||||||
|
/// 32b-length signed/unsigned int.
|
||||||
|
I32(i32),
|
||||||
|
/// 64b-length signed/unsigned int.
|
||||||
|
I64(i64),
|
||||||
|
/// 32b-length float.
|
||||||
|
F32(f32),
|
||||||
|
/// 64b-length float.
|
||||||
|
F64(f64),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to convert into trait.
|
||||||
|
pub trait TryInto<T, E> {
|
||||||
|
/// Try to convert self into other value.
|
||||||
|
fn try_into(self) -> Result<T, E>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert one type to another by wrapping.
|
||||||
|
pub trait WrapInto<T> {
|
||||||
|
/// Convert one type to another by wrapping.
|
||||||
|
fn wrap_into(self) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert one type to another by rounding to the nearest integer towards zero.
|
||||||
|
pub trait TryTruncateInto<T, E> {
|
||||||
|
/// Convert one type to another by rounding to the nearest integer towards zero.
|
||||||
|
fn try_truncate_into(self) -> Result<T, E>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert one type to another by extending with leading zeroes.
|
||||||
|
pub trait ExtendInto<T> {
|
||||||
|
/// Convert one type to another by extending with leading zeroes.
|
||||||
|
fn extend_into(self) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reinterprets the bits of a value of one type as another type.
|
||||||
|
pub trait TransmuteInto<T> {
|
||||||
|
/// Reinterprets the bits of a value of one type as another type.
|
||||||
|
fn transmute_into(self) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert from and to little endian.
|
||||||
|
pub trait LittleEndianConvert where Self: Sized {
|
||||||
|
/// Convert to little endian buffer.
|
||||||
|
fn into_little_endian(self) -> Vec<u8>;
|
||||||
|
/// Convert from little endian buffer.
|
||||||
|
fn from_little_endian(buffer: Vec<u8>) -> Result<Self, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Arithmetic operations.
|
||||||
|
pub trait ArithmeticOps<T> {
|
||||||
|
/// Add two values.
|
||||||
|
fn add(self, other: T) -> T;
|
||||||
|
/// Subtract two values.
|
||||||
|
fn sub(self, other: T) -> T;
|
||||||
|
/// Multiply two values.
|
||||||
|
fn mul(self, other: T) -> T;
|
||||||
|
/// Divide two values.
|
||||||
|
fn div(self, other: T) -> Result<T, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Integer value.
|
||||||
|
pub trait Integer<T>: ArithmeticOps<T> {
|
||||||
|
/// Counts leading zeros in the bitwise representation of the value.
|
||||||
|
fn leading_zeros(self) -> T;
|
||||||
|
/// Counts trailing zeros in the bitwise representation of the value.
|
||||||
|
fn trailing_zeros(self) -> T;
|
||||||
|
/// Counts 1-bits in the bitwise representation of the value.
|
||||||
|
fn count_ones(self) -> T;
|
||||||
|
/// Get left bit rotation result.
|
||||||
|
fn rotl(self, other: T) -> T;
|
||||||
|
/// Get right bit rotation result.
|
||||||
|
fn rotr(self, other: T) -> T;
|
||||||
|
/// Get division remainder.
|
||||||
|
fn rem(self, other: T) -> Result<T, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Float-point value.
|
||||||
|
pub trait Float<T>: ArithmeticOps<T> {
|
||||||
|
/// Get absolute value.
|
||||||
|
fn abs(self) -> T;
|
||||||
|
/// Returns the largest integer less than or equal to a number.
|
||||||
|
fn floor(self) -> T;
|
||||||
|
/// Returns the smallest integer greater than or equal to a number.
|
||||||
|
fn ceil(self) -> T;
|
||||||
|
/// Returns the integer part of a number.
|
||||||
|
fn trunc(self) -> T;
|
||||||
|
/// Returns the nearest integer to a number. Round half-way cases away from 0.0.
|
||||||
|
fn round(self) -> T;
|
||||||
|
/// Returns the nearest integer to a number. Ties are round to even number.
|
||||||
|
fn nearest(self) -> T;
|
||||||
|
/// Takes the square root of a number.
|
||||||
|
fn sqrt(self) -> T;
|
||||||
|
/// Returns the minimum of the two numbers.
|
||||||
|
fn min(self, other: T) -> T;
|
||||||
|
/// Returns the maximum of the two numbers.
|
||||||
|
fn max(self, other: T) -> T;
|
||||||
|
/// Sets sign of this value to the sign of other value.
|
||||||
|
fn copysign(self, other: T) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RuntimeValue {
|
||||||
|
/// Creates new default value of given type.
|
||||||
|
pub fn default(value_type: ValueType) -> Self {
|
||||||
|
match value_type {
|
||||||
|
ValueType::I32 => RuntimeValue::I32(0),
|
||||||
|
ValueType::I64 => RuntimeValue::I64(0),
|
||||||
|
ValueType::F32 => RuntimeValue::F32(0f32),
|
||||||
|
ValueType::F64 => RuntimeValue::F64(0f64),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates new value by interpreting passed u32 as f32.
|
||||||
|
pub fn decode_f32(val: u32) -> Self {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get variable type for this value.
|
||||||
|
pub fn value_type(&self) -> ValueType {
|
||||||
|
match *self {
|
||||||
|
RuntimeValue::I32(_) => ValueType::I32,
|
||||||
|
RuntimeValue::I64(_) => ValueType::I64,
|
||||||
|
RuntimeValue::F32(_) => ValueType::F32,
|
||||||
|
RuntimeValue::F64(_) => ValueType::F64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<i32> for RuntimeValue {
|
||||||
|
fn from(val: i32) -> Self {
|
||||||
|
RuntimeValue::I32(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<i64> for RuntimeValue {
|
||||||
|
fn from(val: i64) -> Self {
|
||||||
|
RuntimeValue::I64(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u32> for RuntimeValue {
|
||||||
|
fn from(val: u32) -> Self {
|
||||||
|
RuntimeValue::I32(val as i32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u64> for RuntimeValue {
|
||||||
|
fn from(val: u64) -> Self {
|
||||||
|
RuntimeValue::I64(val as i64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<f32> for RuntimeValue {
|
||||||
|
fn from(val: f32) -> Self {
|
||||||
|
RuntimeValue::F32(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<f64> for RuntimeValue {
|
||||||
|
fn from(val: f64) -> Self {
|
||||||
|
RuntimeValue::F64(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<bool, Error> for RuntimeValue {
|
||||||
|
fn try_into(self) -> Result<bool, Error> {
|
||||||
|
match self {
|
||||||
|
RuntimeValue::I32(val) => Ok(val != 0),
|
||||||
|
_ => Err(Error::Value(format!("32-bit int value expected"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<i32, Error> for RuntimeValue {
|
||||||
|
fn try_into(self) -> Result<i32, Error> {
|
||||||
|
match self {
|
||||||
|
RuntimeValue::I32(val) => Ok(val),
|
||||||
|
_ => Err(Error::Value(format!("32-bit int value expected"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<i64, Error> for RuntimeValue {
|
||||||
|
fn try_into(self) -> Result<i64, Error> {
|
||||||
|
match self {
|
||||||
|
RuntimeValue::I64(val) => Ok(val),
|
||||||
|
_ => Err(Error::Value(format!("64-bit int value expected"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<f32, Error> for RuntimeValue {
|
||||||
|
fn try_into(self) -> Result<f32, Error> {
|
||||||
|
match self {
|
||||||
|
RuntimeValue::F32(val) => Ok(val),
|
||||||
|
_ => Err(Error::Value(format!("32-bit float value expected"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<f64, Error> for RuntimeValue {
|
||||||
|
fn try_into(self) -> Result<f64, Error> {
|
||||||
|
match self {
|
||||||
|
RuntimeValue::F64(val) => Ok(val),
|
||||||
|
_ => Err(Error::Value(format!("64-bit float value expected"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<u32, Error> for RuntimeValue {
|
||||||
|
fn try_into(self) -> Result<u32, Error> {
|
||||||
|
match self {
|
||||||
|
RuntimeValue::I32(val) => Ok(val as u32),
|
||||||
|
_ => Err(Error::Value(format!("32-bit int value expected"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<u64, Error> for RuntimeValue {
|
||||||
|
fn try_into(self) -> Result<u64, Error> {
|
||||||
|
match self {
|
||||||
|
RuntimeValue::I64(val) => Ok(val as u64),
|
||||||
|
_ => Err(Error::Value(format!("64-bit int value expected"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_wrap_into {
|
||||||
|
($from: ident, $into: ident) => {
|
||||||
|
impl WrapInto<$into> for $from {
|
||||||
|
fn wrap_into(self) -> $into {
|
||||||
|
self as $into
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_wrap_into!(i32, i8);
|
||||||
|
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);
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
macro_rules! impl_try_truncate_into {
|
||||||
|
($from: ident, $into: ident) => {
|
||||||
|
impl TryTruncateInto<$into, Error> for $from {
|
||||||
|
fn try_truncate_into(self) -> Result<$into, Error> {
|
||||||
|
// Casting from a float to an integer will round the float towards zero
|
||||||
|
// NOTE: currently this will cause Undefined Behavior if the rounded value cannot be represented by the
|
||||||
|
// target integer type. This includes Inf and NaN. This is a bug and will be fixed.
|
||||||
|
if self.is_nan() || self.is_infinite() {
|
||||||
|
return Err(Error::Value("invalid float value for this operation".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// range check
|
||||||
|
let result = self as $into;
|
||||||
|
if result as $from != self.trunc() {
|
||||||
|
return Err(Error::Value("invalid float value for this operation".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self as $into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_try_truncate_into!(f32, i32);
|
||||||
|
impl_try_truncate_into!(f32, i64);
|
||||||
|
impl_try_truncate_into!(f64, i32);
|
||||||
|
impl_try_truncate_into!(f64, i64);
|
||||||
|
impl_try_truncate_into!(f32, u32);
|
||||||
|
impl_try_truncate_into!(f32, u64);
|
||||||
|
impl_try_truncate_into!(f64, u32);
|
||||||
|
impl_try_truncate_into!(f64, u64);
|
||||||
|
|
||||||
|
macro_rules! impl_extend_into {
|
||||||
|
($from: ident, $into: ident) => {
|
||||||
|
impl ExtendInto<$into> for $from {
|
||||||
|
fn extend_into(self) -> $into {
|
||||||
|
self as $into
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_extend_into!(i8, i32);
|
||||||
|
impl_extend_into!(u8, i32);
|
||||||
|
impl_extend_into!(i16, i32);
|
||||||
|
impl_extend_into!(u16, i32);
|
||||||
|
impl_extend_into!(i8, i64);
|
||||||
|
impl_extend_into!(u8, i64);
|
||||||
|
impl_extend_into!(i16, i64);
|
||||||
|
impl_extend_into!(u16, i64);
|
||||||
|
impl_extend_into!(i32, i64);
|
||||||
|
impl_extend_into!(u32, i64);
|
||||||
|
impl_extend_into!(u32, u64);
|
||||||
|
impl_extend_into!(i32, f32);
|
||||||
|
impl_extend_into!(i32, f64);
|
||||||
|
impl_extend_into!(u32, f32);
|
||||||
|
impl_extend_into!(u32, f64);
|
||||||
|
impl_extend_into!(i64, f64);
|
||||||
|
impl_extend_into!(u64, f64);
|
||||||
|
impl_extend_into!(f32, f64);
|
||||||
|
|
||||||
|
macro_rules! impl_transmute_into_self {
|
||||||
|
($type: ident) => {
|
||||||
|
impl TransmuteInto<$type> for $type {
|
||||||
|
fn transmute_into(self) -> $type {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_transmute_into_self!(i32);
|
||||||
|
impl_transmute_into_self!(i64);
|
||||||
|
impl_transmute_into_self!(f32);
|
||||||
|
impl_transmute_into_self!(f64);
|
||||||
|
|
||||||
|
macro_rules! impl_transmute_into_as {
|
||||||
|
($from: ident, $into: ident) => {
|
||||||
|
impl TransmuteInto<$into> for $from {
|
||||||
|
fn transmute_into(self) -> $into {
|
||||||
|
self as $into
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_transmute_into_as!(i8, u8);
|
||||||
|
impl_transmute_into_as!(u8, i8);
|
||||||
|
impl_transmute_into_as!(i32, u32);
|
||||||
|
impl_transmute_into_as!(u32, i32);
|
||||||
|
impl_transmute_into_as!(i64, u64);
|
||||||
|
impl_transmute_into_as!(u64, i64);
|
||||||
|
|
||||||
|
// TODO: rewrite these safely when `f32/f32::to_bits/from_bits` stabilized.
|
||||||
|
impl TransmuteInto<i32> for f32 {
|
||||||
|
fn transmute_into(self) -> i32 { unsafe { ::std::mem::transmute(self) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransmuteInto<i64> for f64 {
|
||||||
|
fn transmute_into(self) -> i64 { unsafe { ::std::mem::transmute(self) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransmuteInto<f32> for i32 {
|
||||||
|
fn transmute_into(self) -> f32 { f32_from_bits(self as _) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransmuteInto<f64> for i64 {
|
||||||
|
fn transmute_into(self) -> f64 { f64_from_bits(self as _) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LittleEndianConvert for i8 {
|
||||||
|
fn into_little_endian(self) -> Vec<u8> {
|
||||||
|
vec![self as u8]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_little_endian(buffer: Vec<u8>) -> Result<Self, Error> {
|
||||||
|
buffer.get(0)
|
||||||
|
.map(|v| *v as i8)
|
||||||
|
.ok_or(Error::Value("invalid little endian buffer".into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LittleEndianConvert for u8 {
|
||||||
|
fn into_little_endian(self) -> Vec<u8> {
|
||||||
|
vec![self]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_little_endian(buffer: Vec<u8>) -> Result<Self, Error> {
|
||||||
|
buffer.get(0)
|
||||||
|
.cloned()
|
||||||
|
.ok_or(Error::Value("invalid little endian buffer".into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LittleEndianConvert for i16 {
|
||||||
|
fn into_little_endian(self) -> Vec<u8> {
|
||||||
|
let mut vec = Vec::with_capacity(2);
|
||||||
|
vec.write_i16::<LittleEndian>(self)
|
||||||
|
.expect("i16 is written without any errors");
|
||||||
|
vec
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_little_endian(buffer: Vec<u8>) -> Result<Self, Error> {
|
||||||
|
io::Cursor::new(buffer).read_i16::<LittleEndian>()
|
||||||
|
.map_err(|e| Error::Value(e.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LittleEndianConvert for u16 {
|
||||||
|
fn into_little_endian(self) -> Vec<u8> {
|
||||||
|
let mut vec = Vec::with_capacity(2);
|
||||||
|
vec.write_u16::<LittleEndian>(self)
|
||||||
|
.expect("u16 is written without any errors");
|
||||||
|
vec
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_little_endian(buffer: Vec<u8>) -> Result<Self, Error> {
|
||||||
|
io::Cursor::new(buffer).read_u16::<LittleEndian>()
|
||||||
|
.map_err(|e| Error::Value(e.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LittleEndianConvert for i32 {
|
||||||
|
fn into_little_endian(self) -> Vec<u8> {
|
||||||
|
let mut vec = Vec::with_capacity(4);
|
||||||
|
vec.write_i32::<LittleEndian>(self)
|
||||||
|
.expect("i32 is written without any errors");
|
||||||
|
vec
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_little_endian(buffer: Vec<u8>) -> Result<Self, Error> {
|
||||||
|
io::Cursor::new(buffer).read_i32::<LittleEndian>()
|
||||||
|
.map_err(|e| Error::Value(e.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LittleEndianConvert for u32 {
|
||||||
|
fn into_little_endian(self) -> Vec<u8> {
|
||||||
|
let mut vec = Vec::with_capacity(4);
|
||||||
|
vec.write_u32::<LittleEndian>(self)
|
||||||
|
.expect("u32 is written without any errors");
|
||||||
|
vec
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_little_endian(buffer: Vec<u8>) -> Result<Self, Error> {
|
||||||
|
io::Cursor::new(buffer).read_u32::<LittleEndian>()
|
||||||
|
.map_err(|e| Error::Value(e.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LittleEndianConvert for i64 {
|
||||||
|
fn into_little_endian(self) -> Vec<u8> {
|
||||||
|
let mut vec = Vec::with_capacity(8);
|
||||||
|
vec.write_i64::<LittleEndian>(self)
|
||||||
|
.expect("i64 is written without any errors");
|
||||||
|
vec
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_little_endian(buffer: Vec<u8>) -> Result<Self, Error> {
|
||||||
|
io::Cursor::new(buffer).read_i64::<LittleEndian>()
|
||||||
|
.map_err(|e| Error::Value(e.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LittleEndianConvert for f32 {
|
||||||
|
fn into_little_endian(self) -> Vec<u8> {
|
||||||
|
let mut vec = Vec::with_capacity(4);
|
||||||
|
vec.write_f32::<LittleEndian>(self)
|
||||||
|
.expect("f32 is written without any errors");
|
||||||
|
vec
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_little_endian(buffer: Vec<u8>) -> Result<Self, Error> {
|
||||||
|
io::Cursor::new(buffer).read_u32::<LittleEndian>()
|
||||||
|
.map(f32_from_bits)
|
||||||
|
.map_err(|e| Error::Value(e.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LittleEndianConvert for f64 {
|
||||||
|
fn into_little_endian(self) -> Vec<u8> {
|
||||||
|
let mut vec = Vec::with_capacity(8);
|
||||||
|
vec.write_f64::<LittleEndian>(self)
|
||||||
|
.expect("i64 is written without any errors");
|
||||||
|
vec
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_little_endian(buffer: Vec<u8>) -> Result<Self, Error> {
|
||||||
|
io::Cursor::new(buffer).read_u64::<LittleEndian>()
|
||||||
|
.map(f64_from_bits)
|
||||||
|
.map_err(|e| Error::Value(e.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert u32 to f32 safely, masking out sNAN
|
||||||
|
fn f32_from_bits(mut v: u32) -> f32 {
|
||||||
|
const EXP_MASK: u32 = 0x7F800000;
|
||||||
|
const QNAN_MASK: u32 = 0x00400000;
|
||||||
|
const FRACT_MASK: u32 = 0x007FFFFF;
|
||||||
|
|
||||||
|
if v & EXP_MASK == EXP_MASK && v & FRACT_MASK != 0 {
|
||||||
|
// If we have a NaN value, we
|
||||||
|
// convert signaling NaN values to quiet NaN
|
||||||
|
// by setting the the highest bit of the fraction
|
||||||
|
// TODO: remove when https://github.com/BurntSushi/byteorder/issues/71 closed.
|
||||||
|
// or `f32::from_bits` stabilized.
|
||||||
|
v |= QNAN_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe { ::std::mem::transmute(v) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert u64 to f64 safely, masking out sNAN
|
||||||
|
fn f64_from_bits(mut v: u64) -> f64 {
|
||||||
|
const EXP_MASK: u64 = 0x7FF0000000000000;
|
||||||
|
const QNAN_MASK: u64 = 0x0001000000000000;
|
||||||
|
const FRACT_MASK: u64 = 0x000FFFFFFFFFFFFF;
|
||||||
|
|
||||||
|
if v & EXP_MASK == EXP_MASK && v & FRACT_MASK != 0 {
|
||||||
|
// If we have a NaN value, we
|
||||||
|
// convert signaling NaN values to quiet NaN
|
||||||
|
// by setting the the highest bit of the fraction
|
||||||
|
// TODO: remove when https://github.com/BurntSushi/byteorder/issues/71 closed.
|
||||||
|
// or `f64::from_bits` stabilized.
|
||||||
|
v |= QNAN_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe { ::std::mem::transmute(v) }
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_integer_arithmetic_ops {
|
||||||
|
($type: ident) => {
|
||||||
|
impl ArithmeticOps<$type> for $type {
|
||||||
|
fn add(self, other: $type) -> $type { self.wrapping_add(other) }
|
||||||
|
fn sub(self, other: $type) -> $type { self.wrapping_sub(other) }
|
||||||
|
fn mul(self, other: $type) -> $type { self.wrapping_mul(other) }
|
||||||
|
fn div(self, other: $type) -> Result<$type, Error> {
|
||||||
|
if other == 0 { Err(Error::Value("Division by zero".to_owned())) }
|
||||||
|
else {
|
||||||
|
let (result, overflow) = self.overflowing_div(other);
|
||||||
|
if overflow {
|
||||||
|
return Err(Error::Value("Attempt to divide with overflow".to_owned()));
|
||||||
|
} else {
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_integer_arithmetic_ops!(i32);
|
||||||
|
impl_integer_arithmetic_ops!(u32);
|
||||||
|
impl_integer_arithmetic_ops!(i64);
|
||||||
|
impl_integer_arithmetic_ops!(u64);
|
||||||
|
|
||||||
|
macro_rules! impl_float_arithmetic_ops {
|
||||||
|
($type: ident) => {
|
||||||
|
impl ArithmeticOps<$type> for $type {
|
||||||
|
fn add(self, other: $type) -> $type { self + other }
|
||||||
|
fn sub(self, other: $type) -> $type { self - other }
|
||||||
|
fn mul(self, other: $type) -> $type { self * other }
|
||||||
|
fn div(self, other: $type) -> Result<$type, Error> { Ok(self / other) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_float_arithmetic_ops!(f32);
|
||||||
|
impl_float_arithmetic_ops!(f64);
|
||||||
|
|
||||||
|
macro_rules! impl_integer {
|
||||||
|
($type: ident) => {
|
||||||
|
impl Integer<$type> for $type {
|
||||||
|
fn leading_zeros(self) -> $type { self.leading_zeros() as $type }
|
||||||
|
fn trailing_zeros(self) -> $type { self.trailing_zeros() as $type }
|
||||||
|
fn count_ones(self) -> $type { self.count_ones() as $type }
|
||||||
|
fn rotl(self, other: $type) -> $type { self.rotate_left(other as u32) }
|
||||||
|
fn rotr(self, other: $type) -> $type { self.rotate_right(other as u32) }
|
||||||
|
fn rem(self, other: $type) -> Result<$type, Error> {
|
||||||
|
if other == 0 { Err(Error::Value("Division by zero".to_owned())) }
|
||||||
|
else { Ok(self.wrapping_rem(other)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_integer!(i32);
|
||||||
|
impl_integer!(u32);
|
||||||
|
impl_integer!(i64);
|
||||||
|
impl_integer!(u64);
|
||||||
|
|
||||||
|
macro_rules! impl_float {
|
||||||
|
($type: 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 nearest(self) -> $type {
|
||||||
|
let round = self.round();
|
||||||
|
if self.fract().abs() != 0.5 {
|
||||||
|
return round;
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::ops::Rem;
|
||||||
|
if round.rem(2.0) == 1.0 {
|
||||||
|
self.floor()
|
||||||
|
} else if round.rem(2.0) == -1.0 {
|
||||||
|
self.ceil()
|
||||||
|
} else {
|
||||||
|
round
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn sqrt(self) -> $type { self.sqrt() }
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.min(other)
|
||||||
|
}
|
||||||
|
// This instruction corresponds to what is sometimes called "maxNaN" in other languages.
|
||||||
|
fn max(self, other: $type) -> $type {
|
||||||
|
if self.is_nan() || other.is_nan() {
|
||||||
|
use std::$type;
|
||||||
|
return $type::NAN;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.max(other)
|
||||||
|
}
|
||||||
|
fn copysign(self, other: $type) -> $type {
|
||||||
|
use std::mem::size_of;
|
||||||
|
|
||||||
|
if self.is_nan() {
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sign_mask: $int_type = 1 << ((size_of::<$int_type>() << 3) - 1);
|
||||||
|
let self_int: $int_type = self.transmute_into();
|
||||||
|
let other_int: $int_type = other.transmute_into();
|
||||||
|
let is_self_sign_set = (self_int & sign_mask) != 0;
|
||||||
|
let is_other_sign_set = (other_int & sign_mask) != 0;
|
||||||
|
if is_self_sign_set == is_other_sign_set {
|
||||||
|
self
|
||||||
|
} else if is_other_sign_set {
|
||||||
|
(self_int | sign_mask).transmute_into()
|
||||||
|
} else {
|
||||||
|
(self_int & !sign_mask).transmute_into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_float!(f32, i32);
|
||||||
|
impl_float!(f64, i64);
|
Loading…
Reference in New Issue