From 230abc6a912306b653a230c1f003b54ef19f89dc Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Wed, 24 Jan 2018 18:49:42 +0300 Subject: [PATCH] Second iteration on documenation. --- src/host.rs | 121 +++++++++++++++++++++++++++++++++++++++++++++++-- src/imports.rs | 97 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 209 insertions(+), 9 deletions(-) diff --git a/src/host.rs b/src/host.rs index e3a4c49..590f7bc 100644 --- a/src/host.rs +++ b/src/host.rs @@ -13,7 +13,6 @@ impl<'a> From<&'a [RuntimeValue]> for RuntimeArgs<'a> { } impl<'a> RuntimeArgs<'a> { - /// Extract argument by index `idx` returning error if cast is invalid or not enough arguments pub fn nth(&self, idx: usize) -> Result where RuntimeValue: TryInto { Ok(self.nth_value(idx)?.try_into().map_err(|_| Error::Value("Invalid argument cast".to_owned()))?) @@ -33,7 +32,44 @@ impl<'a> RuntimeArgs<'a> { } } -/// Custom user error. +/// Trait that allows the host to return custom error. +/// +/// It should be useful for representing custom traps, +/// troubles at instantiation time or other host specific conditions. +/// +/// # Examples +/// +/// ```rust +/// use std::fmt; +/// use wasmi::{Error, HostError}; +/// +/// #[derive(Debug)] +/// struct MyError { +/// code: u32, +/// } +/// +/// impl fmt::Display for MyError { +/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +/// write!(f, "MyError, code={}", self.code) +/// } +/// } +/// +/// impl HostError for MyError { } +/// +/// fn failable_fn() -> Result<(), Error> { +/// let my_error = MyError { code: 1312 }; +/// Err(Error::Host(Box::new(my_error))) +/// } +/// +/// match failable_fn() { +/// Err(Error::Host(host_error)) => { +/// let my_error = host_error.downcast_ref::().unwrap(); +/// assert_eq!(my_error.code, 1312); +/// } +/// _ => panic!(), +/// } +/// ``` +/// pub trait HostError: 'static + ::std::fmt::Display + ::std::fmt::Debug + Send + Sync { #[doc(hidden)] fn __private_get_type_id__(&self) -> TypeId { @@ -62,6 +98,77 @@ impl HostError { } } +/// Trait that allows to implement host functions. +/// +/// # Examples +/// +/// ```rust +/// use wasmi::{ +/// Externals, RuntimeValue, RuntimeArgs, Error, ModuleImportResolver, +/// FuncRef, ValueType, Signature, FuncInstance +/// }; +/// +/// struct HostExternals { +/// counter: usize, +/// } +/// +/// const ADD_FUNC_INDEX: usize = 0; +/// +/// impl Externals for HostExternals { +/// fn invoke_index( +/// &mut self, +/// index: usize, +/// args: RuntimeArgs, +/// ) -> Result, Error> { +/// match index { +/// ADD_FUNC_INDEX => { +/// let a: u32 = args.nth(0).unwrap(); +/// let b: u32 = args.nth(1).unwrap(); +/// let result = a + b; +/// +/// Ok(Some(RuntimeValue::I32(result as i32))) +/// } +/// _ => panic!("Unimplemented function at {}", index), +/// } +/// } +/// } +/// +/// impl HostExternals { +/// fn check_signature( +/// &self, +/// index: usize, +/// signature: &Signature +/// ) -> bool { +/// let (params, ret_ty): (&[ValueType], Option) = match index { +/// ADD_FUNC_INDEX => (&[ValueType::I32, ValueType::I32], Some(ValueType::I32)), +/// _ => return false, +/// }; +/// signature.params() == params && signature.return_type() == ret_ty +/// } +/// } +/// +/// impl ModuleImportResolver for HostExternals { +/// fn resolve_func( +/// &self, +/// field_name: &str, +/// signature: &Signature +/// ) -> Result { +/// let index = match field_name { +/// "add" => ADD_FUNC_INDEX, +/// _ => { +/// return Err(Error::Instantiation( +/// format!("Export {} not found", field_name), +/// )) +/// } +/// }; +/// +/// Ok(FuncInstance::alloc_host( +/// Signature::new(&[ValueType::I32, ValueType::I32][..], Some(ValueType::I32)), +/// ADD_FUNC_INDEX, +/// )) +/// } +/// } +/// ``` pub trait Externals { fn invoke_index( &mut self, @@ -70,6 +177,10 @@ pub trait Externals { ) -> Result, Error>; } +/// Implementation of [`Externals`] that just traps on [`invoke_index`]. +/// +/// [`Externals`]: trait.Externals.html +/// [`invoke_index`]: trait.Externals.html#tymethod.invoke_index pub struct NopExternals; impl Externals for NopExternals { @@ -86,7 +197,7 @@ impl Externals for NopExternals { mod tests { use value::RuntimeValue; - use super::RuntimeArgs; + use super::{RuntimeArgs, HostError}; #[test] fn i32_runtime_args() { @@ -100,4 +211,8 @@ mod tests { let args: RuntimeArgs = (&[RuntimeValue::I64(90534534545322)][..]).into(); assert!(args.nth::(0).is_err()); } + + // Tests that `HostError` trait is object safe. + fn _host_error_is_object_safe(_: &HostError) { + } } diff --git a/src/imports.rs b/src/imports.rs index ef6a0e9..419eccf 100644 --- a/src/imports.rs +++ b/src/imports.rs @@ -7,36 +7,95 @@ use module::ModuleRef; use types::{GlobalDescriptor, TableDescriptor, MemoryDescriptor}; use {Error, Signature}; + +/// Resolver of a module's dependencies. +/// +/// A module have dependencies in a form of a list of imports (i.e. +/// tuple of a (`module_name`, `field_name`, `descriptor`)). +/// +/// The job of implementations of this trait is to provide on each +/// import a corresponding concrete reference. pub trait ImportResolver { + + /// Resolve a function. + /// + /// Returned function should match given `signature`, i.e. all parameter types and return value should have exact match. + /// Otherwise, link-time error will occur. fn resolve_func( &self, - module_name: &str, + _module_name: &str, field_name: &str, - func_type: &Signature, + _signature: &Signature, ) -> Result; + /// Resolve a global variable. + /// + /// Returned global should match given `descriptor`, i.e. type and mutability + /// should match. Otherwise, link-time error will occur. fn resolve_global( &self, module_name: &str, field_name: &str, - global_type: &GlobalDescriptor, + descriptor: &GlobalDescriptor, ) -> Result; + /// Resolve a memory. + /// + /// Returned memory should match requested memory (described by the `descriptor`), + /// i.e. initial size of a returned memory should be equal or larger than requested memory. + /// Furthermore, if requested memory have maximum size, returned memory either should have + /// equal or larger maximum size or have no maximum size at all. + /// If returned memory doesn't match the requested then link-time error will occur. fn resolve_memory( &self, module_name: &str, field_name: &str, - memory_type: &MemoryDescriptor, + descriptor: &MemoryDescriptor, ) -> Result; + /// Resolve a table. + /// + /// Returned table should match requested table (described by the `descriptor`), + /// i.e. initial size of a returned table should be equal or larger than requested table. + /// Furthermore, if requested memory have maximum size, returned memory either should have + /// equal or larger maximum size or have no maximum size at all. + /// If returned table doesn't match the requested then link-time error will occur. fn resolve_table( &self, module_name: &str, field_name: &str, - table_type: &TableDescriptor, + descriptor: &TableDescriptor, ) -> Result; } +/// Convenience builder of [`ImportResolver`]. +/// +/// With help of this builder, you can easily create [`ImportResolver`], just by +/// adding needed [resolvers][`ModuleImportResolver`] by names. +/// +/// # Examples +/// +/// ```rust +/// use wasmi::{load_from_buffer, ModuleInstance, ImportsBuilder}; +/// # +/// # struct EnvModuleResolver; +/// # impl ::wasmi::ModuleImportResolver for EnvModuleResolver { } +/// # fn func() -> Result<(), ::wasmi::Error> { +/// # let module = load_from_buffer(&[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]).unwrap(); +/// # let other_instance = ModuleInstance::new(&module, &ImportsBuilder::default())?.assert_no_start(); +/// +/// let imports = ImportsBuilder::new() +/// .with_resolver("env", &EnvModuleResolver) +/// // Note, that ModuleInstance can be a resolver too. +/// .with_resolver("other_instance", &other_instance); +/// let instance = ModuleInstance::new(&module, &imports)?.assert_no_start(); +/// +/// # Ok(()) +/// # } +/// ``` +/// +/// [`ImportResolver`]: trait.ImportResolver.html +/// [`ModuleImportResolver`]: trait.ModuleImportResolver.html pub struct ImportsBuilder<'a> { modules: HashMap, } @@ -48,10 +107,12 @@ impl<'a> Default for ImportsBuilder<'a> { } impl<'a> ImportsBuilder<'a> { + /// Create an empty `ImportsBuilder`. pub fn new() -> ImportsBuilder<'a> { ImportsBuilder { modules: HashMap::new() } } + /// Register an resolver by a name. pub fn with_resolver>( mut self, name: N, @@ -61,11 +122,12 @@ impl<'a> ImportsBuilder<'a> { self } + /// Register an resolver by a name. pub fn push_resolver>(&mut self, name: N, resolver: &'a ModuleImportResolver) { self.modules.insert(name.into(), resolver); } - pub fn resolver(&self, name: &str) -> Option<&ModuleImportResolver> { + fn resolver(&self, name: &str) -> Option<&ModuleImportResolver> { self.modules.get(name).cloned() } } @@ -116,7 +178,15 @@ impl<'a> ImportResolver for ImportsBuilder<'a> { } } +/// Version of [`ImportResolver`] specialized for a single module. +/// +/// [`ImportResolver`]: trait.ImportResolver.html pub trait ModuleImportResolver { + /// Resolve a function. + /// + /// See [`ImportResolver::resolve_func`] for details. + /// + /// [`ImportResolver::resolve_func`]: trait.ImportResolver.html#tymethod.resolve_func fn resolve_func( &self, field_name: &str, @@ -127,6 +197,11 @@ pub trait ModuleImportResolver { )) } + /// Resolve a global variable. + /// + /// See [`ImportResolver::resolve_global`] for details. + /// + /// [`ImportResolver::resolve_global`]: trait.ImportResolver.html#tymethod.resolve_global fn resolve_global( &self, field_name: &str, @@ -137,6 +212,11 @@ pub trait ModuleImportResolver { )) } + /// Resolve a memory. + /// + /// See [`ImportResolver::resolve_memory`] for details. + /// + /// [`ImportResolver::resolve_memory`]: trait.ImportResolver.html#tymethod.resolve_memory fn resolve_memory( &self, field_name: &str, @@ -147,6 +227,11 @@ pub trait ModuleImportResolver { )) } + /// Resolve a table. + /// + /// See [`ImportResolver::resolve_table`] for details. + /// + /// [`ImportResolver::resolve_table`]: trait.ImportResolver.html#tymethod.resolve_table fn resolve_table( &self, field_name: &str,