Merge remote-tracking branch 'origin/master' into fix-membase

This commit is contained in:
NikVolf 2017-07-26 21:57:11 +03:00
commit 8bb17db6d9
13 changed files with 325 additions and 52 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "parity-wasm"
version = "0.12.1"
version = "0.12.2"
authors = ["Nikolay Volf <nikvolf@gmail.com>", "Svyatoslav Nikolsky <svyatonik@yandex.ru>"]
license = "MIT/Apache-2.0"
readme = "README.md"
@ -8,7 +8,7 @@ repository = "https://github.com/nikvolf/parity-wasm"
homepage = "https://github.com/nikvolf/parity-wasm"
documentation = "https://nikvolf.github.io/parity-wasm/parity_wasm/"
description = "WebAssembly binary format serialization/deserialization/interpreter"
keywords = ["wasm", "webassembly", "bytecode", "serde", "interpreter"]
keywords = ["wasm", "webassembly", "bytecode", "serde", "interpreter"]
exclude = [ "res/*", "spec/*" ]
[dependencies]

54
benches/interpreter.rs Normal file
View File

@ -0,0 +1,54 @@
#![feature(test)]
extern crate test;
extern crate parity_wasm;
use test::Bencher;
use parity_wasm::builder::module;
use parity_wasm::elements::{ExportEntry, Internal, ImportEntry, External, Opcodes, Opcode};
use parity_wasm::interpreter::{ProgramInstance, ModuleInstanceInterface, RuntimeValue};
#[bench]
fn export_entry_performance(b: &mut Bencher) {
// create module with 1000 functions
const NUM_FUNCTIONS: u32 = 1000;
let mut callee_module = module();
for i in 0..NUM_FUNCTIONS {
callee_module = callee_module
.with_export(ExportEntry::new(format!("func{}", i), Internal::Function(i)))
.function()
.signature().return_type().i32().build()
.body().with_opcodes(Opcodes::new(vec![
Opcode::I32Const(i as i32),
Opcode::End,
])).build()
.build();
}
let callee_module = callee_module.build();
// create module which call one of 1000 functions
let caller_module = module()
.with_import(ImportEntry::new("callee_module".into(), "func500".into(), External::Function(0)))
.function()
.signature().return_type().i32().build()
.body().with_opcodes(Opcodes::new(vec![
Opcode::Call(0),
Opcode::I32Const(1000),
Opcode::I32Add,
Opcode::End,
])).build()
.build()
.build();
// add both modules to program
let program = ProgramInstance::new().unwrap();
program.add_module("callee_module", callee_module, None).unwrap();
let caller_module = program.add_module("caller_module", caller_module, None).unwrap();
// run bench
b.iter(||
assert_eq!(caller_module.execute_index(1, vec![].into()).unwrap(), Some(RuntimeValue::I32(1500)))
);
// test export_entry_performance ... bench: 3,497 ns/iter (+/- 200)
}

View File

@ -15,20 +15,25 @@ fn main() {
println!("Module sections: {}", module.sections().len());
for section in module.sections() {
match section {
&Section::Import(ref import_section) => {
match *section {
Section::Import(ref import_section) => {
println!(" Imports: {}", import_section.entries().len());
import_section.entries().iter().map(|e| println!(" {}.{}", e.module(), e.field())).count();
},
&Section::Export(ref exports_section) => {
Section::Export(ref exports_section) => {
println!(" Exports: {}", exports_section.entries().len());
exports_section.entries().iter().map(|e| println!(" {}", e.field())).count();
},
&Section::Function(ref function_section) => {
Section::Function(ref function_section) => {
println!(" Functions: {}", function_section.entries().len());
},
&Section::Global(ref globals_section) => {
Section::Type(ref type_section) => {
println!(" Types: {}", type_section.types().len());
},
Section::Global(ref globals_section) => {
println!(" Globals: {}", globals_section.entries().len());
},
&Section::Data(ref data_section) if data_section.entries().len() > 0 => {
Section::Data(ref data_section) if data_section.entries().len() > 0 => {
let data = &data_section.entries()[0];
println!(" Data size: {}", data.value().len());
},

68
examples/invoke.rs Normal file
View File

@ -0,0 +1,68 @@
extern crate parity_wasm;
use std::env::args;
use parity_wasm::{interpreter, ModuleInstanceInterface, RuntimeValue};
use parity_wasm::elements::{Internal, External, Type, FunctionType, ValueType};
fn main() {
let args: Vec<_> = args().collect();
if args.len() < 3 {
println!("Usage: {} <wasm file> <exported func> [<arg>...]", args[0]);
return;
}
let func_name = &args[2];
let (_, program_args) = args.split_at(3);
let program = parity_wasm::ProgramInstance::with_env_params(
interpreter::EnvParams {
total_stack: 128*1024,
total_memory: 2*1024*1024,
allow_memory_growth: false,
}
).expect("Program instance to load");
let module = parity_wasm::deserialize_file(&args[1]).expect("File to be deserialized");
let execution_params = {
let export_section = module.export_section().expect("No export section found");
let function_section = module.function_section().expect("No function section found");
let type_section = module.type_section().expect("No type section found");
let found_entry = export_section.entries().iter()
.find(|entry| func_name == entry.field()).expect(&format!("No export with name {} found", func_name));
// Function index with imported functions
let function_index: usize = match found_entry.internal() {
&Internal::Function(index) => index as usize,
_ => panic!("Founded export is not a function"),
};
let import_section_len: usize = match module.import_section() {
Some(import) =>
import.entries().iter().filter(|entry| match entry.external() {
&External::Function(_) => true,
_ => false,
}).count(),
None => 0,
};
let function_index_in_section = function_index - import_section_len;
let func_type_ref: usize = function_section.entries()[function_index_in_section].type_ref() as usize;
let function_type: &FunctionType = match &type_section.types()[func_type_ref] {
&Type::Function(ref func_type) => func_type,
};
let args: Vec<RuntimeValue> = function_type.params().iter().enumerate().map(|(i, value)| match value {
&ValueType::I32 => RuntimeValue::I32(program_args[i].parse::<i32>().expect(&format!("Can't parse arg #{} as i32", program_args[i]))),
&ValueType::I64 => RuntimeValue::I64(program_args[i].parse::<i64>().expect(&format!("Can't parse arg #{} as i64", program_args[i]))),
&ValueType::F32 => RuntimeValue::F32(program_args[i].parse::<f32>().expect(&format!("Can't parse arg #{} as f32", program_args[i]))),
&ValueType::F64 => RuntimeValue::F64(program_args[i].parse::<f64>().expect(&format!("Can't parse arg #{} as f64", program_args[i]))),
}).collect();
interpreter::ExecutionParams::from(args)
};
let module = program.add_module("main", module, None).expect("Failed to initialize module");
println!("Result: {:?}", module.execute_export(func_name, execution_params).expect(""));
}

View File

@ -346,7 +346,7 @@ mod tests {
.build()
.bind();
assert_eq!(result.len(), 1);
assert_eq!(result.len(), 1);
}
#[test]

View File

@ -301,6 +301,20 @@ impl<F> ModuleBuilder<F> where F: Invoke<elements::Module> {
}
/// Import entry builder
/// # Examples
/// ```
/// use parity_wasm::builder::module;
///
/// let module = module()
/// .import()
/// .module("env")
/// .field("memory")
/// .external().memory(256, Some(256))
/// .build()
/// .build();
///
/// assert_eq!(module.import_section().expect("import section to exist").entries().len(), 1);
/// ```
pub fn import(self) -> import::ImportBuilder<Self> {
import::ImportBuilder::with_callback(self)
}
@ -318,11 +332,43 @@ impl<F> ModuleBuilder<F> where F: Invoke<elements::Module> {
}
/// Export entry builder
/// # Examples
/// ```
/// use parity_wasm::builder::module;
/// use parity_wasm::elements::Opcode::*;
///
/// let module = module()
/// .global()
/// .value_type().i32()
/// .init_expr(I32Const(0))
/// .build()
/// .export()
/// .field("_zero")
/// .internal().global(0)
/// .build()
/// .build();
///
/// assert_eq!(module.export_section().expect("export section to exist").entries().len(), 1);
/// ```
pub fn export(self) -> export::ExportBuilder<Self> {
export::ExportBuilder::with_callback(self)
}
/// Glboal entry builder
/// # Examples
/// ```
/// use parity_wasm::builder::module;
/// use parity_wasm::elements::Opcode::*;
///
/// let module = module()
/// .global()
/// .value_type().i32()
/// .init_expr(I32Const(0))
/// .build()
/// .build();
///
/// assert_eq!(module.global_section().expect("global section to exist").entries().len(), 1);
/// ```
pub fn global(self) -> global::GlobalBuilder<Self> {
global::GlobalBuilder::with_callback(self)
}
@ -442,6 +488,22 @@ impl<F> Invoke<elements::DataSegment> for ModuleBuilder<F>
}
/// Start new module builder
/// # Examples
///
/// ```
/// use parity_wasm::builder;
///
/// let module = builder::module()
/// .function()
/// .signature().param().i32().build()
/// .body().build()
/// .build()
/// .build();
///
/// assert_eq!(module.type_section().expect("type section to exist").types().len(), 1);
/// assert_eq!(module.function_section().expect("function section to exist").entries().len(), 1);
/// assert_eq!(module.code_section().expect("code section to exist").bodies().len(), 1);
/// ```
pub fn module() -> ModuleBuilder {
ModuleBuilder::new()
}

View File

@ -559,7 +559,7 @@ impl<I: Serialize<Error=::elements::Error>, T: IntoIterator<Item=I>> Serialize f
mod tests {
use super::super::{deserialize_buffer, Serialize};
use super::{CountedList, VarInt7, VarUint32, VarInt32};
use super::{CountedList, VarInt7, VarUint32, VarInt32, VarInt64, VarUint64};
fn varuint32_ser_test(val: u32, expected: Vec<u8>) {
let mut buf = Vec::new();
@ -594,6 +594,40 @@ mod tests {
varint32_de_test(dt.clone(), val);
varint32_ser_test(val, dt);
}
fn varuint64_ser_test(val: u64, expected: Vec<u8>) {
let mut buf = Vec::new();
let v1: VarUint64 = val.into();
v1.serialize(&mut buf).expect("to be serialized ok");
assert_eq!(expected, buf);
}
fn varuint64_de_test(dt: Vec<u8>, expected: u64) {
let val: VarUint64 = super::super::deserialize_buffer(dt).expect("buf to be serialized");
assert_eq!(expected, val.into());
}
fn varuint64_serde_test(dt: Vec<u8>, val: u64) {
varuint64_de_test(dt.clone(), val);
varuint64_ser_test(val, dt);
}
fn varint64_ser_test(val: i64, expected: Vec<u8>) {
let mut buf = Vec::new();
let v1: VarInt64 = val.into();
v1.serialize(&mut buf).expect("to be serialized ok");
assert_eq!(expected, buf);
}
fn varint64_de_test(dt: Vec<u8>, expected: i64) {
let val: VarInt64 = super::super::deserialize_buffer(dt).expect("buf to be serialized");
assert_eq!(expected, val.into());
}
fn varint64_serde_test(dt: Vec<u8>, val: i64) {
varint64_de_test(dt.clone(), val);
varint64_ser_test(val, dt);
}
#[test]
fn varuint32_0() {
@ -625,6 +659,36 @@ mod tests {
varint32_serde_test(vec![0x80, 0x40], -8192);
}
#[test]
fn varuint64_0() {
varuint64_serde_test(vec![0u8; 1], 0);
}
#[test]
fn varuint64_1() {
varuint64_serde_test(vec![1u8; 1], 1);
}
#[test]
fn varuint64_135() {
varuint64_serde_test(vec![135u8, 0x01], 135);
}
#[test]
fn varuint64_8192() {
varuint64_serde_test(vec![0x80, 0x40], 8192);
}
#[test]
fn varint64_8192() {
varint64_serde_test(vec![0x80, 0xc0, 0x00], 8192);
}
#[test]
fn varint64_neg_8192() {
varint64_serde_test(vec![0x80, 0x40], -8192);
}
#[test]
fn counted_list() {
let payload = vec![

View File

@ -85,7 +85,8 @@ pub struct EnvModuleInstance {
impl EnvModuleInstance {
pub fn new(params: EnvParams, module: Module) -> Result<Self, Error> {
let instance = ModuleInstance::new(Weak::default(), "env".into(), module)?;
let mut instance = ModuleInstance::new(Weak::default(), "env".into(), module)?;
instance.instantiate(false, None)?;
Ok(EnvModuleInstance {
_params: params,
@ -190,7 +191,7 @@ pub fn env_module(params: EnvParams) -> Result<EnvModuleInstance, Error> {
.with_export(ExportEntry::new("DYNAMIC_BASE".into(), Internal::Global(INDEX_GLOBAL_DYNAMIC_BASE)))
.with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, true), InitExpr::new(vec![Opcode::I32Const((DEFAULT_STACK_BASE + params.total_stack) as i32)])))
.with_export(ExportEntry::new("DYNAMICTOP_PTR".into(), Internal::Global(INDEX_GLOBAL_DYNAMICTOP_PTR)))
.with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, params.allow_memory_growth), InitExpr::new(vec![Opcode::I32Const(params.total_memory as i32)])))
.with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, params.allow_memory_growth), InitExpr::new(vec![Opcode::I32Const(params.total_memory as i32)])))
.with_export(ExportEntry::new("TOTAL_MEMORY".into(), Internal::Global(INDEX_GLOBAL_TOTAL_MEMORY)))
.with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, true), InitExpr::new(vec![Opcode::I32Const(0)])))
.with_export(ExportEntry::new("ABORT".into(), Internal::Global(INDEX_GLOBAL_ABORT)))

View File

@ -8,7 +8,6 @@ use interpreter::program::ProgramInstanceEssence;
use interpreter::table::TableInstance;
use interpreter::variable::{VariableInstance, VariableType};
// TODO: cache Internal-s to fasten access
/// Module imports.
pub struct ModuleImports {
/// Program instance.

View File

@ -49,12 +49,9 @@ impl MemoryInstance {
.ok_or(Error::Memory(format!("initial memory size must be at most {} pages", LINEAR_MEMORY_MAX_PAGES)))?;
let memory = MemoryInstance {
buffer: RwLock::new(Vec::with_capacity(initial_size as usize)),
buffer: RwLock::new(vec![0; initial_size as usize]),
maximum_size: maximum_size,
};
if memory.grow(memory_type.limits().initial())? == u32::MAX {
return Err(Error::Memory(format!("error initializing {}-bytes linear memory region", initial_size)));
}
Ok(Arc::new(memory))
}

View File

@ -74,7 +74,7 @@ mod variable;
mod tests;
pub use self::memory::MemoryInstance;
pub use self::module::{ModuleInstance, ModuleInstanceInterface, ItemIndex, ExportEntryType, CallerContext, ExecutionParams};
pub use self::module::{ModuleInstance, ModuleInstanceInterface, ItemIndex, ExportEntryType, CallerContext, ExecutionParams, FunctionSignature};
pub use self::table::TableInstance;
pub use self::program::ProgramInstance;
pub use self::value::RuntimeValue;

View File

@ -98,6 +98,8 @@ pub struct ModuleInstance {
functions_labels: HashMap<u32, HashMap<usize, usize>>,
/// Module imports.
imports: ModuleImports,
/// Module exports.
exports: HashMap<String, Vec<Internal>>,
/// Tables.
tables: Vec<Arc<TableInstance>>,
/// Linear memory regions.
@ -211,6 +213,7 @@ impl ModuleInstance {
name: name,
module: module,
imports: imports,
exports: HashMap::new(),
functions_labels: HashMap::new(),
memory: memory,
tables: tables,
@ -232,24 +235,30 @@ impl ModuleInstance {
}
// validate export section
if is_user_module { // TODO: env module exports STACKTOP global, which is mutable => check is failed
if let Some(export_section) = self.module.export_section() {
for export in export_section.entries() {
match export.internal() {
&Internal::Function(function_index) =>
self.require_function(ItemIndex::IndexSpace(function_index)).map(|_| ())?,
&Internal::Global(global_index) =>
self.global(ItemIndex::IndexSpace(global_index), None)
.and_then(|g| if g.is_mutable() {
Err(Error::Validation(format!("trying to export mutable global {}", export.field())))
} else {
Ok(())
})?,
&Internal::Memory(memory_index) =>
self.memory(ItemIndex::IndexSpace(memory_index)).map(|_| ())?,
&Internal::Table(table_index) =>
self.table(ItemIndex::IndexSpace(table_index)).map(|_| ())?,
}
if let Some(export_section) = self.module.export_section() {
for export in export_section.entries() {
match export.internal() {
&Internal::Function(function_index) => {
self.require_function(ItemIndex::IndexSpace(function_index)).map(|_| ())?;
self.exports.entry(export.field().into()).or_insert_with(Default::default).push(Internal::Function(function_index));
},
&Internal::Global(global_index) => {
self.global(ItemIndex::IndexSpace(global_index), None)
.and_then(|g| if g.is_mutable() && is_user_module {
Err(Error::Validation(format!("trying to export mutable global {}", export.field())))
} else {
Ok(())
})?;
self.exports.entry(export.field().into()).or_insert_with(Default::default).push(Internal::Global(global_index));
},
&Internal::Memory(memory_index) => {
self.memory(ItemIndex::IndexSpace(memory_index)).map(|_| ())?;
self.exports.entry(export.field().into()).or_insert_with(Default::default).push(Internal::Memory(memory_index));
},
&Internal::Table(table_index) => {
self.table(ItemIndex::IndexSpace(table_index)).map(|_| ())?;
self.exports.entry(export.field().into()).or_insert_with(Default::default).push(Internal::Table(table_index));
},
}
}
}
@ -422,15 +431,15 @@ impl ModuleInstanceInterface for ModuleInstance {
}
fn execute_export(&self, name: &str, params: ExecutionParams) -> Result<Option<RuntimeValue>, Error> {
let index = self.module.export_section()
.ok_or(Error::Function("missing export section".into()))
.and_then(|s| s.entries().iter()
.find(|e| e.field() == name && match e.internal() {
&Internal::Function(_) => true,
let index = self.exports.get(name)
.ok_or(Error::Function(format!("missing exports with name {}", name)))
.and_then(|l| l.iter()
.find(|i| match i {
&&Internal::Function(_) => true,
_ => false,
})
.ok_or(Error::Function(format!("missing export section exported function with name {}", name)))
.map(|e| match e.internal() {
.ok_or(Error::Function(format!("missing exported function with name {}", name)))
.map(|i| match i {
&Internal::Function(index) => index,
_ => unreachable!(), // checked couple of lines above
})
@ -439,24 +448,24 @@ impl ModuleInstanceInterface for ModuleInstance {
}
fn export_entry<'a>(&self, name: &str, required_type: &ExportEntryType) -> Result<Internal, Error> {
self.module.export_section()
.ok_or(Error::Program(format!("trying to import {} from module without export section", name)))
.and_then(|s| s.entries().iter()
.find(|e| e.field() == name && match required_type {
self.exports.get(name)
.ok_or(Error::Function(format!("missing exports with name {}", name)))
.and_then(|l| l.iter()
.find(|i| match required_type {
&ExportEntryType::Any => true,
&ExportEntryType::Global(global_type) => match e.internal() {
&Internal::Global(global_index) => self.global(ItemIndex::IndexSpace(global_index), Some(global_type)).map(|_| true).unwrap_or(false),
&ExportEntryType::Global(global_type) => match i {
&&Internal::Global(global_index) => self.global(ItemIndex::IndexSpace(global_index), Some(global_type)).map(|_| true).unwrap_or(false),
_ => false,
},
&ExportEntryType::Function(ref required_type) => match e.internal() {
&Internal::Function(function_index) =>
&ExportEntryType::Function(ref required_type) => match i {
&&Internal::Function(function_index) =>
self.function_type(ItemIndex::IndexSpace(function_index))
.map(|ft| ft == *required_type)
.unwrap_or(false),
_ => false,
},
})
.map(|e| *e.internal())
.map(|i| *i)
.ok_or(Error::Program(format!("unresolved import {}", name))))
}
@ -653,6 +662,7 @@ fn get_initializer(expr: &InitExpr, module: &Module, imports: &ModuleImports, ex
}
impl<'a> FunctionSignature<'a> {
/// Get return type of this function.
pub fn return_type(&self) -> Option<ValueType> {
match self {
&FunctionSignature::Module(ft) => ft.return_type(),
@ -660,6 +670,7 @@ impl<'a> FunctionSignature<'a> {
}
}
/// Get parameters of this function.
pub fn params(&self) -> &[ValueType] {
match self {
&FunctionSignature::Module(ft) => ft.params(),

View File

@ -97,6 +97,7 @@ fn global_get_set() {
let program = ProgramInstance::new().unwrap();
let module = program.add_module("main", module, None).unwrap();
assert_eq!(module.execute_index(0, vec![].into()).unwrap().unwrap(), RuntimeValue::I32(50));
assert_eq!(module.execute_index(0, vec![].into()).unwrap().unwrap(), RuntimeValue::I32(58));
}
const SIGNATURE_I32_I32: &'static [ValueType] = &[ValueType::I32, ValueType::I32];
@ -209,6 +210,17 @@ fn single_program_different_modules() {
assert_eq!(executor.values, vec![7, 57, 42]);
}
#[test]
fn import_env_mutable_global() {
let program = ProgramInstance::new().unwrap();
let module = module()
.with_import(ImportEntry::new("env".into(), "STACKTOP".into(), External::Global(GlobalType::new(ValueType::I32, false))))
.build();
program.add_module("main", module, None).unwrap();
}
#[test]
fn env_native_export_entry_type_check() {
let program = ProgramInstance::new().unwrap();