diff --git a/Cargo.toml b/Cargo.toml index 2f53b26..3cd355f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "parity-wasm" -version = "0.12.1" +version = "0.12.2" authors = ["Nikolay Volf ", "Svyatoslav Nikolsky "] 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] diff --git a/benches/interpreter.rs b/benches/interpreter.rs new file mode 100644 index 0000000..e1711f5 --- /dev/null +++ b/benches/interpreter.rs @@ -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) +} diff --git a/examples/info.rs b/examples/info.rs index c5e8cc9..58c5927 100644 --- a/examples/info.rs +++ b/examples/info.rs @@ -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()); }, diff --git a/examples/invoke.rs b/examples/invoke.rs new file mode 100644 index 0000000..22a47e1 --- /dev/null +++ b/examples/invoke.rs @@ -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: {} [...]", 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 = function_type.params().iter().enumerate().map(|(i, value)| match value { + &ValueType::I32 => RuntimeValue::I32(program_args[i].parse::().expect(&format!("Can't parse arg #{} as i32", program_args[i]))), + &ValueType::I64 => RuntimeValue::I64(program_args[i].parse::().expect(&format!("Can't parse arg #{} as i64", program_args[i]))), + &ValueType::F32 => RuntimeValue::F32(program_args[i].parse::().expect(&format!("Can't parse arg #{} as f32", program_args[i]))), + &ValueType::F64 => RuntimeValue::F64(program_args[i].parse::().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("")); +} diff --git a/src/builder/code.rs b/src/builder/code.rs index b76d941..b3cf39f 100644 --- a/src/builder/code.rs +++ b/src/builder/code.rs @@ -346,7 +346,7 @@ mod tests { .build() .bind(); - assert_eq!(result.len(), 1); + assert_eq!(result.len(), 1); } #[test] diff --git a/src/builder/module.rs b/src/builder/module.rs index c63b5a1..742b05c 100644 --- a/src/builder/module.rs +++ b/src/builder/module.rs @@ -301,6 +301,20 @@ impl ModuleBuilder where F: Invoke { } /// 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 { import::ImportBuilder::with_callback(self) } @@ -318,11 +332,43 @@ impl ModuleBuilder where F: Invoke { } /// 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 { 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 { global::GlobalBuilder::with_callback(self) } @@ -442,6 +488,22 @@ impl Invoke for ModuleBuilder } /// 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() } diff --git a/src/elements/primitives.rs b/src/elements/primitives.rs index 305f597..57f638b 100644 --- a/src/elements/primitives.rs +++ b/src/elements/primitives.rs @@ -559,7 +559,7 @@ impl, T: IntoIterator> 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) { 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) { + 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, 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, val: u64) { + varuint64_de_test(dt.clone(), val); + varuint64_ser_test(val, dt); + } + + fn varint64_ser_test(val: i64, expected: Vec) { + 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, 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, 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![ diff --git a/src/interpreter/env.rs b/src/interpreter/env.rs index ce4849c..8a92365 100644 --- a/src/interpreter/env.rs +++ b/src/interpreter/env.rs @@ -85,7 +85,8 @@ pub struct EnvModuleInstance { impl EnvModuleInstance { pub fn new(params: EnvParams, module: Module) -> Result { - 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 { .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))) diff --git a/src/interpreter/imports.rs b/src/interpreter/imports.rs index a2df35b..1f7912b 100644 --- a/src/interpreter/imports.rs +++ b/src/interpreter/imports.rs @@ -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. diff --git a/src/interpreter/memory.rs b/src/interpreter/memory.rs index 171c3ab..c6a9a0d 100644 --- a/src/interpreter/memory.rs +++ b/src/interpreter/memory.rs @@ -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)) } diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 3e0892f..1d59c06 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -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; diff --git a/src/interpreter/module.rs b/src/interpreter/module.rs index 29b5613..f490710 100644 --- a/src/interpreter/module.rs +++ b/src/interpreter/module.rs @@ -98,6 +98,8 @@ pub struct ModuleInstance { functions_labels: HashMap>, /// Module imports. imports: ModuleImports, + /// Module exports. + exports: HashMap>, /// Tables. tables: Vec>, /// 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, 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 { - 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 { 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(), diff --git a/src/interpreter/tests/basics.rs b/src/interpreter/tests/basics.rs index ee54797..87b0fb2 100644 --- a/src/interpreter/tests/basics.rs +++ b/src/interpreter/tests/basics.rs @@ -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();