From bc89a20b967ce5b8a93c00fba5b50ff3d2bac3a9 Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Thu, 25 Jan 2018 18:10:39 +0300 Subject: [PATCH] Third iteration on documenation. --- spec/src/run.rs | 1 + src/func.rs | 6 +- src/imports.rs | 8 +++ src/lib.rs | 97 ++++++++++++++++++++++++++- src/memory.rs | 2 + src/module.rs | 162 ++++++++++++++++++++++++++++++++++++++++++---- src/tests/host.rs | 1 + 7 files changed, 260 insertions(+), 17 deletions(-) diff --git a/spec/src/run.rs b/spec/src/run.rs index a74b526..73f2d67 100644 --- a/spec/src/run.rs +++ b/spec/src/run.rs @@ -339,6 +339,7 @@ fn run_action( InterpreterError::Global(format!("Expected to have export with name {}", field)) })? .as_global() + .cloned() .ok_or_else(|| { InterpreterError::Global(format!("Expected export {} to be a global", field)) })?; diff --git a/src/func.rs b/src/func.rs index 6264cfe..ce413ba 100644 --- a/src/func.rs +++ b/src/func.rs @@ -15,7 +15,6 @@ use common::{DEFAULT_FRAME_STACK_LIMIT, DEFAULT_VALUE_STACK_LIMIT}; /// This reference has a reference-counting semantics. /// /// [`FuncInstance`]: struct.FuncInstance.html -/// #[derive(Clone, Debug)] pub struct FuncRef(Rc); @@ -30,8 +29,8 @@ impl ::std::ops::Deref for FuncRef { /// /// Functions are the unit of orgianization of code in WebAssembly. Each function takes a sequence of values /// as parameters and either optionally return a value or trap. -/// Functions can call other function (including itself, i.e recursively) and imported functions -/// (i.e defined in another module). +/// Functions can call other function including itself (i.e recursive calls are allowed) and imported functions +/// (i.e functions defined in another module or by the host environment). /// /// Functions can be defined either: /// @@ -40,7 +39,6 @@ impl ::std::ops::Deref for FuncRef { /// See more in [`Externals`]. /// /// [`Externals`]: trait.Externals.html -/// pub struct FuncInstance(FuncInstanceInternal); #[derive(Clone)] diff --git a/src/imports.rs b/src/imports.rs index 419eccf..0252967 100644 --- a/src/imports.rs +++ b/src/imports.rs @@ -15,6 +15,10 @@ use {Error, Signature}; /// /// The job of implementations of this trait is to provide on each /// import a corresponding concrete reference. +/// +/// For simple use-cases you can use [`ImportsBuilder`]. +/// +/// [`ImportsBuilder`]: struct.ImportsBuilder.html pub trait ImportResolver { /// Resolve a function. @@ -254,6 +258,7 @@ impl ModuleImportResolver for ModuleRef { Error::Instantiation(format!("Export {} not found", field_name)) })? .as_func() + .cloned() .ok_or_else(|| { Error::Instantiation(format!("Export {} is not a function", field_name)) })?) @@ -269,6 +274,7 @@ impl ModuleImportResolver for ModuleRef { Error::Instantiation(format!("Export {} not found", field_name)) })? .as_global() + .cloned() .ok_or_else(|| { Error::Instantiation(format!("Export {} is not a global", field_name)) })?) @@ -284,6 +290,7 @@ impl ModuleImportResolver for ModuleRef { Error::Instantiation(format!("Export {} not found", field_name)) })? .as_memory() + .cloned() .ok_or_else(|| { Error::Instantiation(format!("Export {} is not a memory", field_name)) })?) @@ -299,6 +306,7 @@ impl ModuleImportResolver for ModuleRef { Error::Instantiation(format!("Export {} not found", field_name)) })? .as_table() + .cloned() .ok_or_else(|| { Error::Instantiation(format!("Export {} is not a table", field_name)) })?) diff --git a/src/lib.rs b/src/lib.rs index d763009..93d7fbc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,99 @@ -//! WebAssembly interpreter module. +//! # wasmi +//! +//! This library allows to load WebAssembly modules in binary format and invoke functions on them. +//! +//! # Introduction +//! +//! WebAssembly (wasm) is a safe, portable, compact format that designed for efficient execution. +//! +//! Wasm code is distributed in a form of modules, that contains definitions of: +//! +//! - functions, +//! - global variables, +//! - linear memories, +//! - tables. +//! +//! and this definitions can be imported. Also, each definition can be exported. +//! +//! In addition to definitions, modules can define initialization data for their memories or tables that takes the +//! form of segments copied to given offsets. They can also define a `start` function that is automatically executed. +//! +//! ## Loading and Validation +//! +//! Before execution a module should be validated. This process checks that module is well-formed +//! and makes only allowed operations. +//! +//! Valid modules can't access memory out of it's sandbox, can't cause stack underflow +//! and can call functions only with correct signatures. +//! +//! ## Instantiatiation +//! +//! In order to execute code in wasm module it should be instatiated. +//! Instantiation includes the following steps: +//! +//! 1. Create an empty module instance, +//! 2. Resolve definition instances for each declared import in the module, +//! 3. Instantiate definitions declared in the module (e.g. allocate global variables, allocate linear memory, etc), +//! 4. Initialize memory and table contents by copiying segments into them, +//! 5. Execute `start` function, if any. +//! +//! After these steps, module instance are ready to execute functions. +//! +//! ## Execution +//! +//! It is allowed to only execute functions which are exported by a module. +//! Functions can either return a result or trap (e.g. there can't be linking-error at the middle of execution). +//! This property is ensured by the validation process. +//! +//! # Examples +//! +//! ```rust +//! extern crate wasmi; +//! extern crate wabt; +//! +//! use wasmi::{ModuleInstance, ImportsBuilder, NopExternals, RuntimeValue}; +//! +//! fn main() { +//! // Parse WAT (WebAssembly Text format) into wasm bytecode. +//! let wasm_binary: Vec = +//! wabt::wat2wasm( +//! r#" +//! (module +//! (func (export "test") (result i32) +//! i32.const 1337 +//! ) +//! ) +//! "#, +//! ) +//! .expect("failed to parse wat"); +//! +//! // Load wasm binary and prepare it for instantiation. +//! let module = wasmi::load_from_buffer(&wasm_binary) +//! .expect("failed to load wasm"); +//! +//! // Instantiate a module with empty imports and +//! // asserting that there is no `start` function. +//! let instance = +//! ModuleInstance::new( +//! &module, +//! &ImportsBuilder::default() +//! ) +//! .expect("failed to instantiate wasm module") +//! .assert_no_start(); +//! +//! // Finally, invoke exported function "test" with no parameters +//! // and empty external function executor. +//! assert_eq!( +//! instance.invoke_export( +//! "test", +//! &[], +//! &mut NopExternals, +//! ).expect("failed to execute export"), +//! Some(RuntimeValue::I32(1337)), +//! ); +//! } +//! ``` +//! // TODO(pepyakin): Fix this asap https://github.com/pepyakin/wasmi/issues/3 #![allow(missing_docs)] diff --git a/src/memory.rs b/src/memory.rs index ae548bb..cf25233 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -38,6 +38,8 @@ impl ::std::ops::Deref for MemoryRef { /// A memory is created with an initial size but can be grown dynamically. /// The growth can be limited by specifying maximum size. /// The size of a memory is always a integer multiple of a page size - 64KiB. +/// +/// At the moment, wasm doesn't provide any way to shrink the memory. pub struct MemoryInstance { /// Memofy limits. limits: ResizableLimits, diff --git a/src/module.rs b/src/module.rs index 2885123..1dc1641 100644 --- a/src/module.rs +++ b/src/module.rs @@ -23,6 +23,7 @@ impl ::std::ops::Deref for ModuleRef { } } +/// An external value is the runtime representation of an entity that can be imported or exported. pub enum ExternVal { Func(FuncRef), Table(TableRef), @@ -57,35 +58,64 @@ impl fmt::Debug for ExternVal { } impl ExternVal { - pub fn as_func(&self) -> Option { + /// Get underlying function reference if this `ExternVal` contains + /// a function, or `None` if it is some other kind. + pub fn as_func(&self) -> Option<&FuncRef> { match *self { - ExternVal::Func(ref func) => Some(func.clone()), + ExternVal::Func(ref func) => Some(func), _ => None, } } - pub fn as_table(&self) -> Option { + /// Get underlying table reference if this `ExternVal` contains + /// a table, or `None` if it is some other kind. + pub fn as_table(&self) -> Option<&TableRef> { match *self { - ExternVal::Table(ref table) => Some(table.clone()), + ExternVal::Table(ref table) => Some(table), _ => None, } } - pub fn as_memory(&self) -> Option { + /// Get underlying memory reference if this `ExternVal` contains + /// a memory, or `None` if it is some other kind. + pub fn as_memory(&self) -> Option<&MemoryRef> { match *self { - ExternVal::Memory(ref memory) => Some(memory.clone()), + ExternVal::Memory(ref memory) => Some(memory), _ => None, } } - pub fn as_global(&self) -> Option { + /// Get underlying global variable reference if this `ExternVal` contains + /// a global, or `None` if it is some other kind. + pub fn as_global(&self) -> Option<&GlobalRef> { match *self { - ExternVal::Global(ref global) => Some(global.clone()), + ExternVal::Global(ref global) => Some(global), _ => None, } } } +/// A module instance is the runtime representation of a [module][`LoadedModule`]. +/// +/// It is created by instantiating a [module][`LoadedModule`], and collects runtime representations +/// of all entities that are imported or defined by the module, namely: +/// +/// - [functions][`FuncInstance`], +/// - [memories][`MemoryInstance`], +/// - [tables][`TableInstance`], +/// - [globals][`GlobalInstance`], +/// +/// In order to instantiate a module you need to provide entities to satisfy +/// every module's imports (i.e. wasm modules don't have optional imports). +/// +/// After module is instantiated you can start invoking it's exported functions with [`invoke_export`]. +/// +/// [`LoadedModule`]: struct.LoadedModule.html +/// [`FuncInstance`]: struct.FuncInstance.html +/// [`MemoryInstance`]: struct.MemoryInstance.html +/// [`TableInstance`]: struct.TableInstance.html +/// [`GlobalInstance`]: struct.GlobalInstance.html +/// [`invoke_export`]: #method.invoke_export #[derive(Debug)] pub struct ModuleInstance { signatures: RefCell>>, @@ -124,10 +154,6 @@ impl ModuleInstance { self.funcs.borrow().get(idx as usize).cloned() } - pub fn export_by_name(&self, name: &str) -> Option { - self.exports.borrow().get(name).cloned() - } - pub(crate) fn signature_by_index(&self, idx: u32) -> Option> { self.signatures.borrow().get(idx as usize).cloned() } @@ -369,6 +395,63 @@ impl ModuleInstance { Ok(module_ref) } + /// Instantiate a [module][`LoadedModule`]. + /// + /// Note that in case of successful instantiation this function returns a reference to + /// a module which `start` function is not called. + /// In order to complete instantiatiation `start` function must be called. However, there are + /// situations where you might need to do additional setup before calling `start` function. + /// For such sitations this separation might be useful. + /// + /// # Errors + /// + /// Returns `Err` if the module cannot be instantiated. + /// + /// This can happen if one of the imports can't + /// be satisfied (e.g module isn't registered in `imports` [resolver][`ImportResolver`]) or + /// there is a mismatch between requested import and provided (e.g. module requested memory with no + /// maximum size limit, however, was provided memory with the maximum size limit). + /// + /// # Examples + /// + /// ```rust + /// use wasmi::{load_from_buffer, ModuleInstance, ImportsBuilder, NopExternals}; + /// # fn func() -> Result<(), ::wasmi::Error> { + /// # let module = load_from_buffer(&[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]).unwrap(); + /// + /// // ModuleInstance::new returns instance which `start` function isn't called. + /// let not_started = ModuleInstance::new( + /// &module, + /// &ImportsBuilder::default() + /// )?; + /// // Call `start` function if any. + /// let instance = not_started.run_start(&mut NopExternals)?; + /// + /// # Ok(()) + /// # } + /// ``` + /// + /// If you sure that the module doesn't have `start` function you can use [`assert_no_start`] to get + /// instantiated module without calling `start` function. + /// + /// ```rust + /// use wasmi::{load_from_buffer, ModuleInstance, ImportsBuilder, NopExternals}; + /// # fn func() -> Result<(), ::wasmi::Error> { + /// # let module = load_from_buffer(&[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]).unwrap(); + /// + /// // This will panic if the module actually contain `start` function. + /// let not_started = ModuleInstance::new( + /// &module, + /// &ImportsBuilder::default() + /// )?.assert_no_start(); + /// + /// # Ok(()) + /// # } + /// ``` + /// + /// [`LoadedModule`]: struct.LoadedModule.html + /// [`ImportResolver`]: trait.ImportResolver.html + /// [`assert_no_start`]: struct.NotStartedModuleRef.html#method.assert_no_start pub fn new<'m, I: ImportResolver>( loaded_module: &'m LoadedModule, imports: &I, @@ -415,6 +498,54 @@ impl ModuleInstance { }) } + /// Invoke exported function by a name. + /// + /// This function finds exported function by a name, and calls it with provided arguments and + /// external state. + /// + /// # Errors + /// + /// Returns `Err` if: + /// + /// - there are no export with a given name or this export is not a function, + /// - given arguments doesn't match to function signature, + /// - trap occured at the execution time, + /// + /// # Examples + /// + /// Invoke a function that takes two numbers and returns sum of them. + /// + /// ```rust + /// # extern crate wasmi; + /// # extern crate wabt; + /// # use wasmi::{ModuleInstance, ImportsBuilder, NopExternals, RuntimeValue}; + /// # fn main() { + /// # let wasm_binary: Vec = wabt::wat2wasm( + /// # r#" + /// # (module + /// # (func (export "add") (param i32 i32) (result i32) + /// # get_local 0 + /// # get_local 1 + /// # i32.add + /// # ) + /// # ) + /// # "#, + /// # ).expect("failed to parse wat"); + /// # let module = wasmi::load_from_buffer(&wasm_binary).expect("failed to load wasm"); + /// # let instance = ModuleInstance::new( + /// # &module, + /// # &ImportsBuilder::default() + /// # ).expect("failed to instantiate wasm module").assert_no_start(); + /// assert_eq!( + /// instance.invoke_export( + /// "add", + /// &[RuntimeValue::I32(5), RuntimeValue::I32(3)], + /// &mut NopExternals, + /// ).expect("failed to execute export"), + /// Some(RuntimeValue::I32(8)), + /// ); + /// # } + /// ``` pub fn invoke_export( &self, func_name: &str, @@ -438,6 +569,13 @@ impl ModuleInstance { FuncInstance::invoke(&func_instance, args, externals) } + + /// Find export by a name. + /// + /// Returns `None` if there is no export with such name. + pub fn export_by_name(&self, name: &str) -> Option { + self.exports.borrow().get(name).cloned() + } } pub struct NotStartedModuleRef<'a> { diff --git a/src/tests/host.rs b/src/tests/host.rs index 39b655c..2dc9ed5 100644 --- a/src/tests/host.rs +++ b/src/tests/host.rs @@ -345,6 +345,7 @@ fn pull_internal_mem_from_module() { .export_by_name("mem") .expect("Module expected to have 'mem' export") .as_memory() + .cloned() .expect("'mem' export should be a memory"); env.memory = Some(internal_mem);