First step for generating host externals out of the function definition in sr-io (#3567)

* Adds new wrapper traits for wasm executor

* Add new crate `substrate-wasm-interface`

Thew new crate holds types and traits for the communicating between the
wasm runtime and the host.

* Rewrite externals with new macro etc

* Fix vec initialization

* Make executor tests working

* Remove unused code + warnings

* Introduce `Pointer` and `WordSize` for working with wasm

* Fix tests and compilation

* Fix compilation

* Apply suggestions from code review

Co-Authored-By: Sergei Pepyakin <sergei@parity.io>

* Review feedback

* Remove unused conversions

* Make each host function its own struct

`HostFunctions` now just returns these function structs. Each function
can be executed by using one of the function structs. The inherent host
functions are now moved to the "normal" host functions.

* Remove byteorder

* Add floating point types

* Make pointer interface more safe

* Add type alias for wasm-interface Result

* More review comments
This commit is contained in:
Bastian Köcher
2019-09-10 16:07:25 +02:00
committed by GitHub
parent 86b3f2e1a7
commit 1450719acc
20 changed files with 1815 additions and 1462 deletions
+31 -31
View File
@@ -19,8 +19,8 @@
use crate::error::{Error, Result};
use log::trace;
use wasmi::MemoryRef;
use wasmi::memory_units::Bytes;
use wasmi::{MemoryRef, memory_units::Bytes};
use wasm_interface::{Pointer, WordSize};
// The pointers need to be aligned to 8 bytes.
const ALIGNMENT: u32 = 8;
@@ -79,7 +79,7 @@ impl FreeingBumpHeapAllocator {
/// Gets requested number of bytes to allocate and returns a pointer.
/// The maximum size which can be allocated at once is 16 MiB.
pub fn allocate(&mut self, size: u32) -> Result<u32> {
pub fn allocate(&mut self, size: WordSize) -> Result<Pointer<u8>> {
if size > MAX_POSSIBLE_ALLOCATION {
return Err(Error::RequestedAllocationTooLarge);
}
@@ -95,7 +95,7 @@ impl FreeingBumpHeapAllocator {
// Something from the free list
let item = self.heads[list_index];
let four_bytes = self.get_heap_4bytes(item)?;
self.heads[list_index] = FreeingBumpHeapAllocator::le_bytes_to_u32(four_bytes);
self.heads[list_index] = Self::le_bytes_to_u32(four_bytes);
item + 8
} else {
// Nothing to be freed. Bump.
@@ -109,12 +109,12 @@ impl FreeingBumpHeapAllocator {
self.total_size = self.total_size + item_size + 8;
trace!(target: "wasm-heap", "Heap size is {} bytes after allocation", self.total_size);
Ok(self.ptr_offset + ptr)
Ok(Pointer::new(self.ptr_offset + ptr))
}
/// Deallocates the space which was allocated for a pointer.
pub fn deallocate(&mut self, ptr: u32) -> Result<()> {
let ptr = ptr - self.ptr_offset;
pub fn deallocate(&mut self, ptr: Pointer<u8>) -> Result<()> {
let ptr = u32::from(ptr) - self.ptr_offset;
if ptr < 8 {
return Err(error("Invalid pointer for deallocation"));
}
@@ -125,10 +125,10 @@ impl FreeingBumpHeapAllocator {
self.heads[list_index] = ptr - 8;
let mut slice = self.get_heap_4bytes(ptr - 8)?;
FreeingBumpHeapAllocator::write_u32_into_le_bytes(tail, &mut slice);
Self::write_u32_into_le_bytes(tail, &mut slice);
self.set_heap_4bytes(ptr - 8, slice)?;
let item_size = FreeingBumpHeapAllocator::get_item_size_from_index(list_index);
let item_size = Self::get_item_size_from_index(list_index);
self.total_size = self.total_size.checked_sub(item_size as u32 + 8)
.ok_or_else(|| error("Unable to subtract from total heap size without overflow"))?;
trace!(target: "wasm-heap", "Heap size is {} bytes after deallocation", self.total_size);
@@ -147,8 +147,7 @@ impl FreeingBumpHeapAllocator {
}
fn write_u32_into_le_bytes(bytes: u32, slice: &mut [u8]) {
let bytes: [u8; 4] = unsafe { std::mem::transmute::<u32, [u8; 4]>(bytes.to_le()) };
for i in 0..4 { slice[i] = bytes[i]; }
slice[..4].copy_from_slice(&bytes.to_le_bytes());
}
fn get_item_size_from_index(index: usize) -> usize {
@@ -175,7 +174,6 @@ impl FreeingBumpHeapAllocator {
fn set_heap_4bytes(&mut self, ptr: u32, value: [u8; 4]) -> Result<()> {
self.heap.set(self.ptr_offset + ptr, &value).map_err(Into::into)
}
}
#[cfg(test)]
@@ -186,6 +184,11 @@ mod tests {
const PAGE_SIZE: u32 = 65536;
/// Makes a pointer out of the given address.
fn to_pointer(address: u32) -> Pointer<u8> {
Pointer::new(address)
}
#[test]
fn should_allocate_properly() {
// given
@@ -196,7 +199,7 @@ mod tests {
let ptr = heap.allocate(1).unwrap();
// then
assert_eq!(ptr, 8);
assert_eq!(ptr, to_pointer(8));
}
#[test]
@@ -211,7 +214,7 @@ mod tests {
// then
// the pointer must start at the next multiple of 8 from 13
// + the prefix of 8 bytes.
assert_eq!(ptr, 24);
assert_eq!(ptr, to_pointer(24));
}
#[test]
@@ -227,14 +230,14 @@ mod tests {
// then
// a prefix of 8 bytes is prepended to each pointer
assert_eq!(ptr1, 8);
assert_eq!(ptr1, to_pointer(8));
// the prefix of 8 bytes + the content of ptr1 padded to the lowest possible
// item size of 8 bytes + the prefix of ptr1
assert_eq!(ptr2, 24);
assert_eq!(ptr2, to_pointer(24));
// ptr2 + its content of 16 bytes + the prefix of 8 bytes
assert_eq!(ptr3, 24 + 16 + 8);
assert_eq!(ptr3, to_pointer(24 + 16 + 8));
}
#[test]
@@ -244,11 +247,11 @@ mod tests {
let mut heap = FreeingBumpHeapAllocator::new(mem, 0);
let ptr1 = heap.allocate(1).unwrap();
// the prefix of 8 bytes is prepended to the pointer
assert_eq!(ptr1, 8);
assert_eq!(ptr1, to_pointer(8));
let ptr2 = heap.allocate(1).unwrap();
// the prefix of 8 bytes + the content of ptr 1 is prepended to the pointer
assert_eq!(ptr2, 24);
assert_eq!(ptr2, to_pointer(24));
// when
heap.deallocate(ptr2).unwrap();
@@ -256,7 +259,7 @@ mod tests {
// then
// then the heads table should contain a pointer to the
// prefix of ptr2 in the leftmost entry
assert_eq!(heap.heads[0], ptr2 - 8);
assert_eq!(heap.heads[0], u32::from(ptr2) - 8);
}
#[test]
@@ -268,13 +271,13 @@ mod tests {
let ptr1 = heap.allocate(1).unwrap();
// the prefix of 8 bytes is prepended to the pointer
assert_eq!(ptr1, padded_offset + 8);
assert_eq!(ptr1, to_pointer(padded_offset + 8));
let ptr2 = heap.allocate(9).unwrap();
// the padded_offset + the previously allocated ptr (8 bytes prefix +
// 8 bytes content) + the prefix of 8 bytes which is prepended to the
// current pointer
assert_eq!(ptr2, padded_offset + 16 + 8);
assert_eq!(ptr2, to_pointer(padded_offset + 16 + 8));
// when
heap.deallocate(ptr2).unwrap();
@@ -282,7 +285,7 @@ mod tests {
// then
// should have re-allocated
assert_eq!(ptr3, padded_offset + 16 + 8);
assert_eq!(ptr3, to_pointer(padded_offset + 16 + 8));
assert_eq!(heap.heads, [0; N]);
}
@@ -302,15 +305,12 @@ mod tests {
heap.deallocate(ptr3).unwrap();
// then
let mut expected = [0; N];
expected[0] = ptr3 - 8;
assert_eq!(heap.heads, expected);
assert_eq!(heap.heads[0], u32::from(ptr3) - 8);
let ptr4 = heap.allocate(8).unwrap();
assert_eq!(ptr4, ptr3);
expected[0] = ptr2 - 8;
assert_eq!(heap.heads, expected);
assert_eq!(heap.heads[0], u32::from(ptr2) - 8);
}
#[test]
@@ -338,7 +338,7 @@ mod tests {
let mem = MemoryInstance::alloc(Pages(1), Some(Pages(1))).unwrap();
let mut heap = FreeingBumpHeapAllocator::new(mem, 0);
let ptr1 = heap.allocate((PAGE_SIZE / 2) - 8).unwrap();
assert_eq!(ptr1, 8);
assert_eq!(ptr1, to_pointer(8));
// when
let ptr2 = heap.allocate(PAGE_SIZE / 2);
@@ -365,7 +365,7 @@ mod tests {
let ptr = heap.allocate(MAX_POSSIBLE_ALLOCATION).unwrap();
// then
assert_eq!(ptr, 8);
assert_eq!(ptr, to_pointer(8));
}
#[test]
@@ -409,7 +409,7 @@ mod tests {
// when
let ptr = heap.allocate(42).unwrap();
assert_eq!(ptr, 16 + 8);
assert_eq!(ptr, to_pointer(16 + 8));
heap.deallocate(ptr).unwrap();
// then
+6 -3
View File
@@ -66,7 +66,7 @@ pub enum Error {
#[display(fmt="The runtime has the `start` function")]
RuntimeHasStartFn,
/// Some other error occurred
Other(&'static str),
Other(String),
/// Some error occurred in the allocator
#[display(fmt="Error in allocator: {}", _0)]
Allocator(&'static str),
@@ -76,6 +76,9 @@ pub enum Error {
/// Someone tried to allocate more memory than the allowed maximum per allocation.
#[display(fmt="Requested allocation size is too large")]
RequestedAllocationTooLarge,
/// Executing the given function failed with the given error.
#[display(fmt="Function execution failed with: {}", _0)]
FunctionExecution(String),
}
impl std::error::Error for Error {
@@ -93,8 +96,8 @@ impl state_machine::Error for Error {}
impl wasmi::HostError for Error {}
impl From<&'static str> for Error {
fn from(err: &'static str) -> Error {
impl From<String> for Error {
fn from(err: String) -> Error {
Error::Other(err)
}
}
+2
View File
@@ -48,6 +48,8 @@ pub use runtime_version::{RuntimeVersion, NativeVersion};
pub use codec::Codec;
#[doc(hidden)]
pub use primitives::Blake2Hasher;
#[doc(hidden)]
pub use wasm_interface;
/// Provides runtime information.
pub trait RuntimeInfo {
+12 -8
View File
@@ -26,6 +26,7 @@ use wasmi::{
Externals, FuncRef, ImportResolver, MemoryInstance, MemoryRef, Module, ModuleInstance,
ModuleRef, RuntimeArgs, RuntimeValue, Trap, TrapKind, memory_units::Pages,
};
use wasm_interface::{Pointer, WordSize};
/// Index of a function inside the supervisor.
///
@@ -150,7 +151,7 @@ pub trait SandboxCapabilities {
/// Returns `Err` if allocation not possible or errors during heap management.
///
/// Returns pointer to the allocated block.
fn allocate(&mut self, len: u32) -> Result<u32>;
fn allocate(&mut self, len: WordSize) -> Result<Pointer<u8>>;
/// Deallocate space specified by the pointer that was previously returned by [`allocate`].
///
@@ -159,21 +160,21 @@ pub trait SandboxCapabilities {
/// Returns `Err` if deallocation not possible or because of errors in heap management.
///
/// [`allocate`]: #tymethod.allocate
fn deallocate(&mut self, ptr: u32) -> Result<()>;
fn deallocate(&mut self, ptr: Pointer<u8>) -> Result<()>;
/// Write `data` into the supervisor memory at offset specified by `ptr`.
///
/// # Errors
///
/// Returns `Err` if `ptr + data.len()` is out of bounds.
fn write_memory(&mut self, ptr: u32, data: &[u8]) -> Result<()>;
fn write_memory(&mut self, ptr: Pointer<u8>, data: &[u8]) -> Result<()>;
/// Read `len` bytes from the supervisor memory.
///
/// # Errors
///
/// Returns `Err` if `ptr + len` is out of bounds.
fn read_memory(&self, ptr: u32, len: u32) -> Result<Vec<u8>>;
fn read_memory(&self, ptr: Pointer<u8>, len: WordSize) -> Result<Vec<u8>>;
}
/// Implementation of [`Externals`] that allows execution of guest module with
@@ -187,7 +188,7 @@ pub struct GuestExternals<'a, FE: SandboxCapabilities + Externals + 'a> {
}
fn trap(msg: &'static str) -> Trap {
TrapKind::Host(Box::new(Error::Other(msg))).into()
TrapKind::Host(Box::new(Error::Other(msg.into()))).into()
}
fn deserialize_result(serialized_result: &[u8]) -> std::result::Result<Option<RuntimeValue>, Trap> {
@@ -243,7 +244,7 @@ impl<'a, FE: SandboxCapabilities + Externals + 'a> Externals for GuestExternals<
let result = ::wasmi::FuncInstance::invoke(
&dispatch_thunk,
&[
RuntimeValue::I32(invoke_args_ptr as i32),
RuntimeValue::I32(u32::from(invoke_args_ptr) as i32),
RuntimeValue::I32(invoke_args_data.len() as i32),
RuntimeValue::I32(state as i32),
RuntimeValue::I32(func_idx.0 as i32),
@@ -260,7 +261,7 @@ impl<'a, FE: SandboxCapabilities + Externals + 'a> Externals for GuestExternals<
let v = v as u64;
let ptr = (v as u64 >> 32) as u32;
let len = (v & 0xFFFFFFFF) as u32;
(ptr, len)
(Pointer::new(ptr), len)
}
Ok(_) => return Err(trap("Supervisor function returned unexpected result!")),
Err(_) => return Err(trap("Supervisor function trapped!")),
@@ -643,7 +644,10 @@ mod tests {
if let Err(err) = res {
assert_eq!(
format!("{}", err),
format!("{}", wasmi::Error::Trap(Error::AllocatorOutOfSpace.into()))
format!(
"{}",
wasmi::Error::Trap(Error::FunctionExecution("AllocatorOutOfSpace".into()).into()),
),
);
}
}
File diff suppressed because it is too large Load Diff
@@ -282,7 +282,11 @@ impl RuntimesCache {
},
Entry::Vacant(v) => {
trace!(target: "runtimes_cache", "no instance found in cache, creating now.");
let result = Self::create_wasm_instance(wasm_executor, ext, heap_pages);
let result = Self::create_wasm_instance(
wasm_executor,
ext,
heap_pages,
);
if let Err(ref err) = result {
warn!(target: "runtimes_cache", "cannot create a runtime: {:?}", err);
}
@@ -305,10 +309,10 @@ impl RuntimesCache {
//
// A return of this error actually indicates that there is a problem in logic, since
// we just loaded and validated the `module` above.
let data_segments = extract_data_segments(&code).ok_or(CacheError::CantDeserializeWasm)?;
let data_segments = extract_data_segments(&code)?;
// Instantiate this module.
let instance = WasmExecutor::instantiate_module::<E>(heap_pages as usize, &module)
let instance = WasmExecutor::instantiate_module(heap_pages as usize, &module)
.map_err(CacheError::Instantiation)?;
// Take state snapshot before executing anything.
@@ -335,12 +339,14 @@ impl RuntimesCache {
/// Extract the data segments from the given wasm code.
///
/// Returns `Err` if the given wasm code cannot be deserialized.
fn extract_data_segments(wasm_code: &[u8]) -> Option<Vec<DataSegment>> {
let raw_module: RawModule = deserialize_buffer(wasm_code).ok()?;
fn extract_data_segments(wasm_code: &[u8]) -> Result<Vec<DataSegment>, CacheError> {
let raw_module: RawModule = deserialize_buffer(wasm_code)
.map_err(|_| CacheError::CantDeserializeWasm)?;
let segments = raw_module
.data_section()
.map(|ds| ds.entries())
.unwrap_or(&[])
.to_vec();
Some(segments)
Ok(segments)
}
+83 -181
View File
@@ -16,129 +16,94 @@
//! Utilities for defining the wasm host environment.
use wasmi::{ValueType, RuntimeValue};
use wasmi::nan_preserving_float::{F32, F64};
pub trait ConvertibleToWasm {
const VALUE_TYPE: ValueType;
type NativeType; fn to_runtime_value(self) -> RuntimeValue;
}
impl ConvertibleToWasm for i32 {
type NativeType = i32;
const VALUE_TYPE: ValueType = ValueType::I32;
fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self) }
}
impl ConvertibleToWasm for u32 {
type NativeType = u32;
const VALUE_TYPE: ValueType = ValueType::I32;
fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as i32) }
}
impl ConvertibleToWasm for i64 {
type NativeType = i64;
const VALUE_TYPE: ValueType = ValueType::I64;
fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I64(self) }
}
impl ConvertibleToWasm for u64 {
type NativeType = u64;
const VALUE_TYPE: ValueType = ValueType::I64;
fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I64(self as i64) }
}
impl ConvertibleToWasm for F32 {
type NativeType = F32;
const VALUE_TYPE: ValueType = ValueType::F32;
fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::F32(self) }
}
impl ConvertibleToWasm for F64 {
type NativeType = F64;
const VALUE_TYPE: ValueType = ValueType::F64;
fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::F64(self) }
}
impl ConvertibleToWasm for isize {
type NativeType = i32;
const VALUE_TYPE: ValueType = ValueType::I32;
fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as i32) }
}
impl ConvertibleToWasm for usize {
type NativeType = u32;
const VALUE_TYPE: ValueType = ValueType::I32;
fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as u32 as i32) }
}
impl<T> ConvertibleToWasm for *const T {
type NativeType = u32;
const VALUE_TYPE: ValueType = ValueType::I32;
fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as isize as i32) }
}
impl<T> ConvertibleToWasm for *mut T {
type NativeType = u32;
const VALUE_TYPE: ValueType = ValueType::I32;
fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as isize as i32) }
}
/// Converts arguments into respective WASM types.
#[macro_export]
macro_rules! convert_args {
() => ([]);
( $( $t:ty ),* ) => ( [ $( { use $crate::wasm_utils::ConvertibleToWasm; <$t>::VALUE_TYPE }, )* ] );
( $( $t:ty ),* ) => ( [ $( <$t as $crate::wasm_interface::IntoValue>::VALUE_TYPE, )* ] );
}
/// Generates a WASM signature for given list of parameters.
#[macro_export]
macro_rules! gen_signature {
( ( $( $params: ty ),* ) ) => (
{
$crate::wasmi::Signature::new(&convert_args!($($params),*)[..], None)
$crate::wasm_interface::Signature {
args: std::borrow::Cow::Borrowed(&convert_args!( $( $params ),* )[..]),
return_value: None,
}
);
( ( $( $params: ty ),* ) -> $returns: ty ) => (
{
$crate::wasmi::Signature::new(&convert_args!($($params),*)[..], Some({
use $crate::wasm_utils::ConvertibleToWasm; <$returns>::VALUE_TYPE
}))
( ( $( $params: ty ),* ) -> $returns:ty ) => (
$crate::wasm_interface::Signature {
args: std::borrow::Cow::Borrowed(&convert_args!( $( $params ),* )[..]),
return_value: Some(<$returns as $crate::wasm_interface::IntoValue>::VALUE_TYPE),
}
);
}
macro_rules! resolve_fn {
(@iter $index:expr, $sig_var:ident, $name_var:ident) => ();
(@iter $index:expr, $sig_var:ident, $name_var:ident $name:ident ( $( $params:ty ),* ) $( -> $returns:ty )* => $($tail:tt)* ) => (
if $name_var == stringify!($name) {
let signature = gen_signature!( ( $( $params ),* ) $( -> $returns )* );
if $sig_var != &signature {
return Err($crate::wasmi::Error::Instantiation(
format!("Export {} has different signature {:?}", $name_var, $sig_var),
));
macro_rules! gen_functions {
(@INTERNAL
{ $( $generated:tt )* }
$context:ident,
) => (
&[ $( $generated )* ]
);
(@INTERNAL
{ $( $generated:tt )* }
$context:ident,
$name:ident ( $( $names:ident: $params:ty ),* ) $( -> $returns:ty )? { $( $body:tt )* }
$( $tail:tt )*
) => (
gen_functions! {
@INTERNAL
{
$( $generated )*
{
struct $name;
#[allow(unused)]
impl $crate::wasm_interface::Function for $name {
fn name(&self) -> &str {
stringify!($name)
}
fn signature(&self) -> $crate::wasm_interface::Signature {
gen_signature!( ( $( $params ),* ) $( -> $returns )? )
}
fn execute(
&self,
context: &mut dyn $crate::wasm_interface::FunctionContext,
args: &mut dyn Iterator<Item=$crate::wasm_interface::Value>,
) -> ::std::result::Result<Option<$crate::wasm_interface::Value>, String> {
let mut $context = context;
marshall! {
args,
( $( $names : $params ),* ) $( -> $returns )? => { $( $body )* }
}
}
}
&$name as &dyn $crate::wasm_interface::Function
},
}
return Ok($crate::wasmi::FuncInstance::alloc_host(signature, $index));
$context,
$( $tail )*
}
resolve_fn!(@iter $index + 1, $sig_var, $name_var $($tail)*)
);
($sig_var:ident, $name_var:ident, $($tail:tt)* ) => (
resolve_fn!(@iter 0, $sig_var, $name_var $($tail)*);
( $context:ident, $( $tail:tt )* ) => (
gen_functions!(@INTERNAL {} $context, $($tail)*);
);
}
/// Converts the list of arguments coming from WASM into their native types.
#[macro_export]
macro_rules! unmarshall_args {
( $body:tt, $objectname:ident, $args_iter:ident, $( $names:ident : $params:ty ),*) => ({
( $body:tt, $args_iter:ident, $( $names:ident : $params:ty ),*) => ({
$(
let $names : <$params as $crate::wasm_utils::ConvertibleToWasm>::NativeType =
let $names : $params =
$args_iter.next()
.and_then(|rt_val| rt_val.try_into())
.and_then(|val| <$params as $crate::wasm_interface::TryFromValue>::try_from_value(val))
.expect(
"`$args_iter` comes from an argument of Externals::invoke_index;
"`$args_iter` comes from an argument of Externals::execute_function;
args to an external call always matches the signature of the external;
external signatures are built with count and types and in order defined by `$params`;
here, we iterating on `$params`;
@@ -160,7 +125,7 @@ macro_rules! unmarshall_args {
#[inline(always)]
pub fn constrain_closure<R, F>(f: F) -> F
where
F: FnOnce() -> Result<R, crate::error::Error>
F: FnOnce() -> Result<R, String>
{
f
}
@@ -168,103 +133,40 @@ where
/// Pass the list of parameters by converting them to respective WASM types.
#[macro_export]
macro_rules! marshall {
( $args_iter:ident, $objectname:ident, ( $( $names:ident : $params:ty ),* ) -> $returns:ty => $body:tt ) => ({
let body = $crate::wasm_utils::constrain_closure::<
<$returns as $crate::wasm_utils::ConvertibleToWasm>::NativeType, _
>(|| {
unmarshall_args!($body, $objectname, $args_iter, $( $names : $params ),*)
( $args_iter:ident, ( $( $names:ident : $params:ty ),* ) -> $returns:ty => $body:tt ) => ({
let body = $crate::wasm_utils::constrain_closure::<$returns, _>(|| {
unmarshall_args!($body, $args_iter, $( $names : $params ),*)
});
let r = body().map_err(wasmi::Trap::from)?;
return Ok(Some({ use $crate::wasm_utils::ConvertibleToWasm; r.to_runtime_value() }))
let r = body()?;
return Ok(Some($crate::wasm_interface::IntoValue::into_value(r)))
});
( $args_iter:ident, $objectname:ident, ( $( $names:ident : $params:ty ),* ) => $body:tt ) => ({
( $args_iter:ident, ( $( $names:ident : $params:ty ),* ) => $body:tt ) => ({
let body = $crate::wasm_utils::constrain_closure::<(), _>(|| {
unmarshall_args!($body, $objectname, $args_iter, $( $names : $params ),*)
unmarshall_args!($body, $args_iter, $( $names : $params ),*)
});
body().map_err(wasmi::Trap::from)?;
body()?;
return Ok(None)
})
}
macro_rules! dispatch_fn {
( @iter $index:expr, $index_ident:ident, $objectname:ident, $args_iter:ident) => {
// `$index` comes from an argument of Externals::invoke_index;
// externals are always invoked with index given by resolve_fn! at resolve time;
// For each next function resolve_fn! gives new index, starting from 0;
// Both dispatch_fn! and resolve_fn! are called with the same list of functions;
// qed;
panic!("fn with index {} is undefined", $index);
};
(@iter
$index:expr,
$index_ident:ident,
$objectname:ident,
$args_iter:ident,
$name:ident ( $( $names:ident : $params:ty ),* ) $( -> $returns:ty )* => $body:tt $($tail:tt)*
) => (
if $index_ident == $index {
{ marshall!($args_iter, $objectname, ( $( $names : $params ),* ) $( -> $returns )* => $body) }
}
dispatch_fn!( @iter $index + 1, $index_ident, $objectname, $args_iter $($tail)*)
);
( $index_ident:ident, $objectname:ident, $args_iter:ident, $($tail:tt)* ) => (
dispatch_fn!( @iter 0, $index_ident, $objectname, $args_iter, $($tail)*);
);
}
/// Implements `wasmi::Externals` trait and `Resolver` for given struct.
/// Implements the wasm host interface for the given type.
#[macro_export]
macro_rules! impl_function_executor {
macro_rules! impl_wasm_host_interface {
(
$objectname:ident : $structname:ty,
$(
$name:ident
( $( $names:ident : $params:ty ),* $(,)? )
$( -> $returns:ty )? => { $( $body:tt )* },
)*
=> $( $pre:tt )+
) => (
impl $( $pre ) + $structname {
#[allow(unused)]
fn resolver() -> &'static dyn $crate::wasmi::ModuleImportResolver {
struct Resolver;
impl $crate::wasmi::ModuleImportResolver for Resolver {
fn resolve_func(
&self,
name: &str,
signature: &$crate::wasmi::Signature
) -> std::result::Result<$crate::wasmi::FuncRef, $crate::wasmi::Error> {
resolve_fn!(
signature,
name,
$( $name( $( $params ),* ) $( -> $returns )? => )*
);
Err($crate::wasmi::Error::Instantiation(
format!("Export {} not found", name),
))
}
}
&Resolver
}
impl $interface_name:ident where $context:ident {
$(
$name:ident($( $names:ident : $params:ty ),* $(,)? ) $( -> $returns:ty )?
{ $( $body:tt )* }
)*
}
impl $( $pre ) + $crate::wasmi::Externals for $structname {
fn invoke_index(
&mut self,
index: usize,
args: $crate::wasmi::RuntimeArgs,
) -> std::result::Result<Option<$crate::wasmi::RuntimeValue>, $crate::wasmi::Trap> {
let $objectname = self;
let mut args = args.as_ref().iter();
dispatch_fn! {
index,
$objectname,
args,
$( $name( $( $names : $params ),* ) $( -> $returns )? => { $( $body )* } ),*
};
) => (
impl $crate::wasm_interface::HostFunctions for $interface_name {
#[allow(non_camel_case_types)]
fn functions() -> &'static [&'static dyn $crate::wasm_interface::Function] {
gen_functions!(
$context,
$( $name( $( $names: $params ),* ) $( -> $returns )? { $( $body )* } )*
)
}
}
);