Provide macro for exporting functions from wasm (#3801)

The macro generates the functions with the signature we expect for wasm
functions. This macro is useful for tests where we need to call into
wasm. Parameter passing is done by SCALE encoding the input and output
parameters.
This commit is contained in:
Bastian Köcher
2019-10-14 20:20:07 +02:00
committed by GitHub
parent b989c6f9ba
commit e3ae647c42
5 changed files with 312 additions and 144 deletions
+4
View File
@@ -39,6 +39,7 @@ use std::borrow::Cow;
use serde::{Serialize, Deserialize};
#[cfg(feature = "std")]
pub use serde;// << for macro
#[doc(hidden)]
pub use codec::{Encode, Decode};// << for macro
#[cfg(feature = "std")]
@@ -82,6 +83,9 @@ pub use self::hasher::blake2::Blake2Hasher;
pub use primitives_storage as storage;
#[doc(hidden)]
pub use rstd;
/// Context for executing a call into the runtime.
pub enum ExecutionContext {
/// Context for general importing (including own blocks).
+115 -1
View File
@@ -129,13 +129,127 @@ impl crate::traits::BareCryptoStore for KeyStore {
}
}
/// Macro for exporting functions from wasm in with the expected signature for using it with the
/// wasm executor. This is useful for tests where you need to call a function in wasm.
///
/// The input parameters are expected to be SCALE encoded and will be automatically decoded for you.
/// The output value is also SCALE encoded when returned back to the host.
///
/// The functions are feature-gated with `#[cfg(not(feature = "std"))]`, so they are only available
/// from within wasm.
///
/// # Example
///
/// ```
/// # use substrate_primitives::wasm_export_functions;
///
/// wasm_export_functions! {
/// fn test_in_wasm(value: bool, another_value: Vec<u8>) -> bool {
/// value && another_value.is_empty()
/// }
///
/// fn without_return_value() {
/// // do something
/// }
/// }
/// ```
#[macro_export]
macro_rules! wasm_export_functions {
(
$(
fn $name:ident (
$( $arg_name:ident: $arg_ty:ty ),* $(,)?
) $( -> $ret_ty:ty )? { $( $fn_impl:tt )* }
)*
) => {
$(
$crate::wasm_export_functions! {
@IMPL
fn $name (
$( $arg_name: $arg_ty ),*
) $( -> $ret_ty )? { $( $fn_impl )* }
}
)*
};
(@IMPL
fn $name:ident (
$( $arg_name:ident: $arg_ty:ty ),*
) { $( $fn_impl:tt )* }
) => {
#[no_mangle]
#[allow(unreachable_code)]
#[cfg(not(feature = "std"))]
pub fn $name(input_data: *mut u8, input_len: usize) -> u64 {
let input: &[u8] = if input_len == 0 {
&[0u8; 0]
} else {
unsafe {
$crate::rstd::slice::from_raw_parts(input_data, input_len)
}
};
{
let ($( $arg_name ),*) : ($( $arg_ty ),*) = $crate::Decode::decode(
&mut &input[..],
).expect("Input data is correctly encoded");
$( $fn_impl )*
}
// We need to return *something*
let output = Vec::<u8>::new();
let res = output.as_ptr() as u64 + ((output.len() as u64) << 32);
// Leak the output vector to avoid it being freed.
// This is fine in a WASM context since the heap
// will be discarded after the call.
$crate::rstd::mem::forget(output);
res
}
};
(@IMPL
fn $name:ident (
$( $arg_name:ident: $arg_ty:ty ),*
) $( -> $ret_ty:ty )? { $( $fn_impl:tt )* }
) => {
#[no_mangle]
#[allow(unreachable_code)]
#[cfg(not(feature = "std"))]
pub fn $name(input_data: *mut u8, input_len: usize) -> u64 {
let input: &[u8] = if input_len == 0 {
&[0u8; 0]
} else {
unsafe {
$crate::rstd::slice::from_raw_parts(input_data, input_len)
}
};
let output $( : $ret_ty )? = {
let ($( $arg_name ),*) : ($( $arg_ty ),*) = $crate::Decode::decode(
&mut &input[..],
).expect("Input data is correctly encoded");
$( $fn_impl )*
};
let output = $crate::Encode::encode(&output);
let res = output.as_ptr() as u64 + ((output.len() as u64) << 32);
// Leak the output vector to avoid it being freed.
// This is fine in a WASM context since the heap
// will be discarded after the call.
$crate::rstd::mem::forget(output);
res
}
};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sr25519;
use crate::testing::{ED25519, SR25519};
#[test]
fn store_key_and_extract() {
let store = KeyStore::new();