Initial commit

This commit is contained in:
Sergey Pepyakin 2018-01-17 18:32:33 +03:00
commit 49347a63ee
48 changed files with 73337 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/target/
**/*.rs.bk
Cargo.lock

10
Cargo.toml Normal file
View File

@ -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.

View File

@ -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
)

BIN
res/cases/v1/const.wasm Normal file

Binary file not shown.

24
res/cases/v1/const.wast Normal file
View File

@ -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
)
)

BIN
res/cases/v1/hello.wasm Normal file

Binary file not shown.

39560
res/cases/v1/hello.wast Normal file

File diff suppressed because one or more lines are too long

BIN
res/cases/v1/ifelse.wasm Normal file

Binary file not shown.

16
res/cases/v1/ifelse.wast Normal file
View File

@ -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)
)

BIN
res/cases/v1/inc_i32.wasm Normal file

Binary file not shown.

45
res/cases/v1/inc_i32.wast Normal file
View File

@ -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
)

BIN
res/cases/v1/offset.wasm Normal file

Binary file not shown.

11
res/cases/v1/offset.wast Normal file
View File

@ -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
)
)

BIN
res/cases/v1/test.wasm Normal file

Binary file not shown.

20
res/cases/v1/test.wast Normal file
View File

@ -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)))
)

BIN
res/cases/v1/test2.wasm Normal file

Binary file not shown.

5
res/cases/v1/test2.wast Normal file
View File

@ -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)))
)

BIN
res/cases/v1/test3.wasm Normal file

Binary file not shown.

21
res/cases/v1/test3.wast Normal file
View File

@ -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)
)

BIN
res/cases/v1/test4.wasm Normal file

Binary file not shown.

9964
res/cases/v1/test4.wast Normal file

File diff suppressed because it is too large Load Diff

26
res/cases/v1/test5.rs Normal file
View File

@ -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
}

BIN
res/cases/v1/test5.wasm Normal file

Binary file not shown.

9841
res/cases/v1/test5.wast Normal file

File diff suppressed because it is too large Load Diff

2448
res/cases/v1/test6.js Normal file

File diff suppressed because it is too large Load Diff

21
res/cases/v1/test6.rs Normal file
View File

@ -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
}

BIN
res/cases/v1/test6.wasm Normal file

Binary file not shown.

5217
res/cases/v1/test6.wast Normal file

File diff suppressed because it is too large Load Diff

44
src/common/mod.rs Normal file
View File

@ -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,
}

90
src/common/stack.rs Normal file
View File

@ -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);
}
}

139
src/func.rs Normal file
View File

@ -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>,
}

59
src/global.rs Normal file
View File

@ -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()
}
}

52
src/host.rs Normal file
View File

@ -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()))
}
}

221
src/imports.rs Normal file
View File

@ -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))
})?)
}
}

122
src/lib.rs Normal file
View File

@ -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};

320
src/memory.rs Normal file
View File

@ -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]);
}
}

542
src/module.rs Normal file
View File

@ -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(())
}

1192
src/runner.rs Normal file

File diff suppressed because it is too large Load Diff

122
src/table.rs Normal file
View File

@ -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(())
}
}

749
src/tests/host.rs Normal file
View File

@ -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))
);
}

2
src/tests/mod.rs Normal file
View File

@ -0,0 +1,2 @@
mod host;
mod wasm;

142
src/tests/wasm.rs Normal file
View File

@ -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);
}

134
src/validation/context.rs Normal file
View File

@ -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,
}
}
}

777
src/validation/func.rs Normal file
View File

@ -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
}
}

340
src/validation/mod.rs Normal file
View File

@ -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)
}

301
src/validation/tests.rs Normal file
View File

@ -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();
}

662
src/value.rs Normal file
View File

@ -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);