Statically register host WASM functions (#10394)

* Statically register host WASM functions

* Fix `substrate-test-client` compilation

* Move `ExtendedHostFunctions` to `sp-wasm-interface`

* Fix `sp-runtime-interface` tests' compilation

* Fix `sc-executor-wasmtime` tests' compilation

* Use `runtime_interface` macro in `test-runner`

* Fix `sc-executor` tests' compilation

* Reformatting/`rustfmt`

* Add an extra comment regarding the `H` generic arg in `create_runtime`

* Even more `rustfmt`

* Depend on `wasmtime` without default features in `sp-wasm-interface`

* Bump version of `sp-wasm-interface` to 4.0.1

* Bump `sp-wasm-interface` in `Cargo.lock` too

* Bump all of the `sp-wasm-interface` requirements to 4.0.1

Maybe this will appease cargo-unleash?

* Revert "Bump all of the `sp-wasm-interface` requirements to 4.0.1"

This reverts commit 0f7ccf8e0f371542861121b145ab87af6541ac30.

* Make `cargo-unleash` happy (maybe)

* Use `cargo-unleash` to bump the crates' versions

* Align to review comments
This commit is contained in:
Koute
2021-12-14 17:26:40 +09:00
committed by GitHub
parent 23c5b6755b
commit 7711f5266e
36 changed files with 742 additions and 570 deletions
+1 -1
View File
@@ -69,7 +69,7 @@ libsecp256k1 = { version = "0.7", default-features = false, features = ["hmac",
merlin = { version = "2.0", default-features = false, optional = true }
ss58-registry = { version = "1.5.0", default-features = false }
sp-core-hashing = { version = "4.0.0", path = "./hashing", default-features = false, optional = true }
sp-runtime-interface = { version = "4.0.0", default-features = false, path = "../runtime-interface" }
sp-runtime-interface = { version = "4.1.0-dev", default-features = false, path = "../runtime-interface" }
[dev-dependencies]
sp-serializer = { version = "4.0.0-dev", path = "../serializer" }
+2 -2
View File
@@ -22,8 +22,8 @@ sp-keystore = { version = "0.10.0-dev", default-features = false, optional = tru
sp-std = { version = "4.0.0", default-features = false, path = "../std" }
libsecp256k1 = { version = "0.7", optional = true }
sp-state-machine = { version = "0.10.0-dev", optional = true, path = "../state-machine" }
sp-wasm-interface = { version = "4.0.0", path = "../wasm-interface", default-features = false }
sp-runtime-interface = { version = "4.0.0", default-features = false, path = "../runtime-interface" }
sp-wasm-interface = { version = "4.1.0-dev", path = "../wasm-interface", default-features = false }
sp-runtime-interface = { version = "4.1.0-dev", default-features = false, path = "../runtime-interface" }
sp-trie = { version = "4.0.0-dev", optional = true, path = "../trie" }
sp-externalities = { version = "0.10.0", optional = true, path = "../externalities" }
sp-tracing = { version = "4.0.0", default-features = false, path = "../tracing" }
@@ -1,6 +1,6 @@
[package]
name = "sp-runtime-interface"
version = "4.0.0"
version = "4.1.0-dev"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
license = "Apache-2.0"
@@ -14,7 +14,7 @@ readme = "README.md"
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
sp-wasm-interface = { version = "4.0.0", path = "../wasm-interface", default-features = false }
sp-wasm-interface = { version = "4.1.0-dev", path = "../wasm-interface", default-features = false }
sp-std = { version = "4.0.0", default-features = false, path = "../std" }
sp-tracing = { version = "4.0.0", default-features = false, path = "../tracing" }
sp-runtime-interface-proc-macro = { version = "4.0.0", path = "proc-macro" }
@@ -35,11 +35,11 @@ use syn::{
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use quote::quote;
use inflector::Inflector;
use std::iter::{self, Iterator};
use std::iter::Iterator;
/// Generate the extern host functions for wasm and the `HostFunctions` struct that provides the
/// implementations for the host functions on the host.
@@ -163,14 +163,20 @@ fn generate_host_functions_struct(
) -> Result<TokenStream> {
let crate_ = generate_crate_access();
let host_functions = get_runtime_interface(trait_def)?
.all_versions()
.map(|(version, method)| {
generate_host_function_implementation(&trait_def.ident, method, version, is_wasm_only)
})
.collect::<Result<Vec<_>>>()?;
let mut host_function_impls = Vec::new();
let mut host_function_names = Vec::new();
let mut register_bodies = Vec::new();
for (version, method) in get_runtime_interface(trait_def)?.all_versions() {
let (implementation, name, register_body) =
generate_host_function_implementation(&trait_def.ident, method, version, is_wasm_only)?;
host_function_impls.push(implementation);
host_function_names.push(name);
register_bodies.push(register_body);
}
Ok(quote! {
#(#host_function_impls)*
/// Provides implementations for the extern host functions.
#[cfg(feature = "std")]
pub struct HostFunctions;
@@ -178,7 +184,16 @@ fn generate_host_functions_struct(
#[cfg(feature = "std")]
impl #crate_::sp_wasm_interface::HostFunctions for HostFunctions {
fn host_functions() -> Vec<&'static dyn #crate_::sp_wasm_interface::Function> {
vec![ #( #host_functions ),* ]
vec![ #( &#host_function_names as &dyn #crate_::sp_wasm_interface::Function ),* ]
}
#crate_::sp_wasm_interface::if_wasmtime_is_enabled! {
fn register_static<T>(registry: &mut T) -> core::result::Result<(), T::Error>
where T: #crate_::sp_wasm_interface::HostFunctionRegistry
{
#(#register_bodies)*
Ok(())
}
}
}
})
@@ -194,47 +209,182 @@ fn generate_host_function_implementation(
method: &TraitItemMethod,
version: u32,
is_wasm_only: bool,
) -> Result<TokenStream> {
) -> Result<(TokenStream, Ident, TokenStream)> {
let name = create_host_function_ident(&method.sig.ident, version, trait_name).to_string();
let struct_name = Ident::new(&name.to_pascal_case(), Span::call_site());
let crate_ = generate_crate_access();
let signature = generate_wasm_interface_signature_for_host_function(&method.sig)?;
let wasm_to_ffi_values =
generate_wasm_to_ffi_values(&method.sig, trait_name).collect::<Result<Vec<_>>>()?;
let ffi_to_host_values = generate_ffi_to_host_value(&method.sig).collect::<Result<Vec<_>>>()?;
let host_function_call = generate_host_function_call(&method.sig, version, is_wasm_only);
let into_preallocated_ffi_value = generate_into_preallocated_ffi_value(&method.sig)?;
let convert_return_value = generate_return_value_into_wasm_value(&method.sig);
Ok(quote! {
{
struct #struct_name;
let fn_name = create_function_ident_with_version(&method.sig.ident, version);
let ref_and_mut = get_function_argument_types_ref_and_mut(&method.sig);
impl #crate_::sp_wasm_interface::Function for #struct_name {
fn name(&self) -> &str {
#name
}
// List of variable names containing WASM FFI-compatible arguments.
let mut ffi_names = Vec::new();
fn signature(&self) -> #crate_::sp_wasm_interface::Signature {
#signature
}
// List of `$name: $ty` tokens containing WASM FFI-compatible arguments.
let mut ffi_args_prototype = Vec::new();
fn execute(
&self,
__function_context__: &mut dyn #crate_::sp_wasm_interface::FunctionContext,
args: &mut dyn Iterator<Item = #crate_::sp_wasm_interface::Value>,
) -> std::result::Result<Option<#crate_::sp_wasm_interface::Value>, String> {
#( #wasm_to_ffi_values )*
#( #ffi_to_host_values )*
#host_function_call
#into_preallocated_ffi_value
#convert_return_value
}
// List of variable names containing arguments already converted into native Rust types.
// Also includes the preceding `&` or `&mut`. To be used to call the actual implementation of
// the host function.
let mut host_names_with_ref = Vec::new();
// List of code snippets to copy over the results returned from a host function through
// any `&mut` arguments back into WASM's linear memory.
let mut copy_data_into_ref_mut_args = Vec::new();
// List of code snippets to convert dynamic FFI args (`Value` enum) into concrete static FFI
// types (`u32`, etc.).
let mut convert_args_dynamic_ffi_to_static_ffi = Vec::new();
// List of code snippets to convert static FFI args (`u32`, etc.) into native Rust types.
let mut convert_args_static_ffi_to_host = Vec::new();
for ((host_name, host_ty), ref_and_mut) in
get_function_argument_names_and_types_without_ref(&method.sig).zip(ref_and_mut)
{
let ffi_name = generate_ffi_value_var_name(&host_name)?;
let host_name_ident = match *host_name {
Pat::Ident(ref pat_ident) => pat_ident.ident.clone(),
_ => unreachable!("`generate_ffi_value_var_name` above would return an error on `Pat` != `Ident`; qed"),
};
let ffi_ty = quote! { <#host_ty as #crate_::RIType>::FFIType };
ffi_args_prototype.push(quote! { #ffi_name: #ffi_ty });
ffi_names.push(quote! { #ffi_name });
let convert_arg_error = format!(
"could not marshal the '{}' argument through the WASM FFI boundary while executing '{}' from interface '{}'",
host_name_ident,
method.sig.ident,
trait_name
);
convert_args_static_ffi_to_host.push(quote! {
let mut #host_name = <#host_ty as #crate_::host::FromFFIValue>::from_ffi_value(__function_context__, #ffi_name)
.map_err(|err| format!("{}: {}", err, #convert_arg_error))?;
});
let ref_and_mut_tokens =
ref_and_mut.map(|(token_ref, token_mut)| quote!(#token_ref #token_mut));
host_names_with_ref.push(quote! { #ref_and_mut_tokens #host_name });
if ref_and_mut.map(|(_, token_mut)| token_mut.is_some()).unwrap_or(false) {
copy_data_into_ref_mut_args.push(quote! {
<#host_ty as #crate_::host::IntoPreallocatedFFIValue>::into_preallocated_ffi_value(
#host_name,
__function_context__,
#ffi_name,
)?;
});
}
let arg_count_mismatch_error = format!(
"missing argument '{}': number of arguments given to '{}' from interface '{}' does not match the expected number of arguments",
host_name_ident,
method.sig.ident,
trait_name
);
convert_args_dynamic_ffi_to_static_ffi.push(quote! {
let #ffi_name = args.next().ok_or_else(|| #arg_count_mismatch_error.to_owned())?;
let #ffi_name: #ffi_ty = #crate_::sp_wasm_interface::TryFromValue::try_from_value(#ffi_name)
.ok_or_else(|| #convert_arg_error.to_owned())?;
});
}
let ffi_return_ty = match &method.sig.output {
ReturnType::Type(_, ty) => quote! { <#ty as #crate_::RIType>::FFIType },
ReturnType::Default => quote! { () },
};
let convert_return_value_host_to_static_ffi = match &method.sig.output {
ReturnType::Type(_, ty) => quote! {
let __result__ = <#ty as #crate_::host::IntoFFIValue>::into_ffi_value(
__result__,
__function_context__
);
},
ReturnType::Default => quote! {
let __result__ = Ok(__result__);
},
};
let convert_return_value_static_ffi_to_dynamic_ffi = match &method.sig.output {
ReturnType::Type(_, _) => quote! {
let __result__ = Ok(Some(#crate_::sp_wasm_interface::IntoValue::into_value(__result__)));
},
ReturnType::Default => quote! {
let __result__ = Ok(None);
},
};
if is_wasm_only {
host_names_with_ref.push(quote! {
__function_context__
});
}
let implementation = quote! {
#[cfg(feature = "std")]
struct #struct_name;
#[cfg(feature = "std")]
impl #struct_name {
fn call(
__function_context__: &mut dyn #crate_::sp_wasm_interface::FunctionContext,
#(#ffi_args_prototype),*
) -> std::result::Result<#ffi_return_ty, String> {
#(#convert_args_static_ffi_to_host)*
let __result__ = #fn_name(#(#host_names_with_ref),*);
#(#copy_data_into_ref_mut_args)*
#convert_return_value_host_to_static_ffi
__result__
}
}
#[cfg(feature = "std")]
impl #crate_::sp_wasm_interface::Function for #struct_name {
fn name(&self) -> &str {
#name
}
&#struct_name as &dyn #crate_::sp_wasm_interface::Function
fn signature(&self) -> #crate_::sp_wasm_interface::Signature {
#signature
}
fn execute(
&self,
__function_context__: &mut dyn #crate_::sp_wasm_interface::FunctionContext,
args: &mut dyn Iterator<Item = #crate_::sp_wasm_interface::Value>,
) -> std::result::Result<Option<#crate_::sp_wasm_interface::Value>, String> {
#(#convert_args_dynamic_ffi_to_static_ffi)*
let __result__ = Self::call(
__function_context__,
#(#ffi_names),*
)?;
#convert_return_value_static_ffi_to_dynamic_ffi
__result__
}
}
})
};
let register_body = quote! {
registry.register_static(
#crate_::sp_wasm_interface::Function::name(&#struct_name),
|mut caller: #crate_::sp_wasm_interface::wasmtime::Caller<T::State>, #(#ffi_args_prototype),*|
-> std::result::Result<#ffi_return_ty, #crate_::sp_wasm_interface::wasmtime::Trap>
{
T::with_function_context(caller, move |__function_context__| {
#struct_name::call(
__function_context__,
#(#ffi_names,)*
)
}).map_err(#crate_::sp_wasm_interface::wasmtime::Trap::new)
}
)?;
};
Ok((implementation, struct_name, register_body))
}
/// Generate the `wasm_interface::Signature` for the given host function `sig`.
@@ -260,86 +410,6 @@ fn generate_wasm_interface_signature_for_host_function(sig: &Signature) -> Resul
})
}
/// Generate the code that converts the wasm values given to `HostFunctions::execute` into the FFI
/// values.
fn generate_wasm_to_ffi_values<'a>(
sig: &'a Signature,
trait_name: &'a Ident,
) -> impl Iterator<Item = Result<TokenStream>> + 'a {
let crate_ = generate_crate_access();
let function_name = &sig.ident;
let error_message = format!(
"Number of arguments given to `{}` does not match the expected number of arguments!",
function_name,
);
get_function_argument_names_and_types_without_ref(sig).map(move |(name, ty)| {
let try_from_error = format!(
"Could not instantiate `{}` from wasm value while executing `{}` from interface `{}`!",
name.to_token_stream(),
function_name,
trait_name,
);
let var_name = generate_ffi_value_var_name(&name)?;
Ok(quote! {
let val = args.next().ok_or_else(|| #error_message)?;
let #var_name = <
<#ty as #crate_::RIType>::FFIType as #crate_::sp_wasm_interface::TryFromValue
>::try_from_value(val).ok_or_else(|| #try_from_error)?;
})
})
}
/// Generate the code to convert the ffi values on the host to the host values using `FromFFIValue`.
fn generate_ffi_to_host_value<'a>(
sig: &'a Signature,
) -> impl Iterator<Item = Result<TokenStream>> + 'a {
let mut_access = get_function_argument_types_ref_and_mut(sig);
let crate_ = generate_crate_access();
get_function_argument_names_and_types_without_ref(sig)
.zip(mut_access.map(|v| v.and_then(|m| m.1)))
.map(move |((name, ty), mut_access)| {
let ffi_value_var_name = generate_ffi_value_var_name(&name)?;
Ok(quote! {
let #mut_access #name = <#ty as #crate_::host::FromFFIValue>::from_ffi_value(
__function_context__,
#ffi_value_var_name,
)?;
})
})
}
/// Generate the code to call the host function and the ident that stores the result.
fn generate_host_function_call(sig: &Signature, version: u32, is_wasm_only: bool) -> TokenStream {
let host_function_name = create_function_ident_with_version(&sig.ident, version);
let result_var_name = generate_host_function_result_var_name(&sig.ident);
let ref_and_mut =
get_function_argument_types_ref_and_mut(sig).map(|ram| ram.map(|(vr, vm)| quote!(#vr #vm)));
let names = get_function_argument_names(sig);
let var_access = names
.zip(ref_and_mut)
.map(|(n, ref_and_mut)| quote!( #ref_and_mut #n ))
// If this is a wasm only interface, we add the function context as last parameter.
.chain(
iter::from_fn(|| if is_wasm_only { Some(quote!(__function_context__)) } else { None })
.take(1),
);
quote! {
let #result_var_name = #host_function_name ( #( #var_access ),* );
}
}
/// Generate the variable name that stores the result of the host function.
fn generate_host_function_result_var_name(name: &Ident) -> Ident {
Ident::new(&format!("{}_result", name), Span::call_site())
}
/// Generate the variable name that stores the FFI value.
fn generate_ffi_value_var_name(pat: &Pat) -> Result<Ident> {
match pat {
@@ -354,49 +424,3 @@ fn generate_ffi_value_var_name(pat: &Pat) -> Result<Ident> {
_ => Err(Error::new(pat.span(), "Not supported as variable name!")),
}
}
/// Generate code that copies data from the host back to preallocated wasm memory.
///
/// Any argument that is given as `&mut` is interpreted as preallocated memory and it is expected
/// that the type implements `IntoPreAllocatedFFIValue`.
fn generate_into_preallocated_ffi_value(sig: &Signature) -> Result<TokenStream> {
let crate_ = generate_crate_access();
let ref_and_mut = get_function_argument_types_ref_and_mut(sig)
.map(|ram| ram.and_then(|(vr, vm)| vm.map(|v| (vr, v))));
let names_and_types = get_function_argument_names_and_types_without_ref(sig);
ref_and_mut
.zip(names_and_types)
.filter_map(|(ram, (name, ty))| ram.map(|_| (name, ty)))
.map(|(name, ty)| {
let ffi_var_name = generate_ffi_value_var_name(&name)?;
Ok(quote! {
<#ty as #crate_::host::IntoPreallocatedFFIValue>::into_preallocated_ffi_value(
#name,
__function_context__,
#ffi_var_name,
)?;
})
})
.collect()
}
/// Generate the code that converts the return value into the appropriate wasm value.
fn generate_return_value_into_wasm_value(sig: &Signature) -> TokenStream {
let crate_ = generate_crate_access();
match &sig.output {
ReturnType::Default => quote!(Ok(None)),
ReturnType::Type(_, ty) => {
let result_var_name = generate_host_function_result_var_name(&sig.ident);
quote! {
<#ty as #crate_::host::IntoFFIValue>::into_ffi_value(
#result_var_name,
__function_context__,
).map(#crate_::sp_wasm_interface::IntoValue::into_value).map(Some)
}
},
}
}
@@ -95,36 +95,36 @@ macro_rules! impl_traits_for_primitives {
}
impl_traits_for_primitives! {
u8, u8,
u16, u16,
u8, u32,
u16, u32,
u32, u32,
u64, u64,
i8, i8,
i16, i16,
i8, i32,
i16, i32,
i32, i32,
i64, i64,
}
/// `bool` is passed as `u8`.
/// `bool` is passed as `u32`.
///
/// - `1`: true
/// - `0`: false
impl RIType for bool {
type FFIType = u8;
type FFIType = u32;
}
#[cfg(not(feature = "std"))]
impl IntoFFIValue for bool {
type Owned = ();
fn into_ffi_value(&self) -> WrappedFFIValue<u8> {
fn into_ffi_value(&self) -> WrappedFFIValue<u32> {
if *self { 1 } else { 0 }.into()
}
}
#[cfg(not(feature = "std"))]
impl FromFFIValue for bool {
fn from_ffi_value(arg: u8) -> bool {
fn from_ffi_value(arg: u32) -> bool {
arg == 1
}
}
@@ -133,14 +133,14 @@ impl FromFFIValue for bool {
impl FromFFIValue for bool {
type SelfInstance = bool;
fn from_ffi_value(_: &mut dyn FunctionContext, arg: u8) -> Result<bool> {
fn from_ffi_value(_: &mut dyn FunctionContext, arg: u32) -> Result<bool> {
Ok(arg == 1)
}
}
#[cfg(feature = "std")]
impl IntoFFIValue for bool {
fn into_ffi_value(self, _: &mut dyn FunctionContext) -> Result<u8> {
fn into_ffi_value(self, _: &mut dyn FunctionContext) -> Result<u32> {
Ok(if self { 1 } else { 0 })
}
}
@@ -80,17 +80,17 @@
//!
//! | Type | FFI type | Conversion |
//! |----|----|----|
//! | `u8` | `u8` | `Identity` |
//! | `u16` | `u16` | `Identity` |
//! | `u8` | `u32` | zero-extended to 32-bits |
//! | `u16` | `u32` | zero-extended to 32-bits |
//! | `u32` | `u32` | `Identity` |
//! | `u64` | `u64` | `Identity` |
//! | `i128` | `u32` | `v.as_ptr()` (pointer to a 16 byte array) |
//! | `i8` | `i8` | `Identity` |
//! | `i16` | `i16` | `Identity` |
//! | `i8` | `i32` | sign-extended to 32-bits |
//! | `i16` | `i32` | sign-extended to 32-bits |
//! | `i32` | `i32` | `Identity` |
//! | `i64` | `i64` | `Identity` |
//! | `u128` | `u32` | `v.as_ptr()` (pointer to a 16 byte array) |
//! | `bool` | `u8` | `if v { 1 } else { 0 }` |
//! | `bool` | `u32` | `if v { 1 } else { 0 }` |
//! | `&str` | `u64` | <code>v.len() 32bit << 32 &#124; v.as_ptr() 32bit</code> |
//! | `&[u8]` | `u64` | <code>v.len() 32bit << 32 &#124; v.as_ptr() 32bit</code> |
//! | `Vec<u8>` | `u64` | <code>v.len() 32bit << 32 &#124; v.as_ptr() 32bit</code> |
@@ -325,7 +325,9 @@ pub use util::{pack_ptr_and_len, unpack_ptr_and_len};
pub trait RIType {
/// The ffi type that is used to represent `Self`.
#[cfg(feature = "std")]
type FFIType: sp_wasm_interface::IntoValue + sp_wasm_interface::TryFromValue;
type FFIType: sp_wasm_interface::IntoValue
+ sp_wasm_interface::TryFromValue
+ sp_wasm_interface::WasmTy;
#[cfg(not(feature = "std"))]
type FFIType;
}
@@ -403,11 +403,11 @@ pub struct Enum<T: Copy + Into<u8> + TryFrom<u8>>(PhantomData<T>);
#[cfg(feature = "std")]
impl<T: Copy + Into<u8> + TryFrom<u8>> PassByImpl<T> for Enum<T> {
fn into_ffi_value(instance: T, _: &mut dyn FunctionContext) -> Result<Self::FFIType> {
Ok(instance.into())
Ok(instance.into() as u32)
}
fn from_ffi_value(_: &mut dyn FunctionContext, arg: Self::FFIType) -> Result<T> {
T::try_from(arg).map_err(|_| format!("Invalid enum discriminant: {}", arg))
T::try_from(arg as u8).map_err(|_| format!("Invalid enum discriminant: {}", arg))
}
}
@@ -417,17 +417,17 @@ impl<T: Copy + Into<u8> + TryFrom<u8, Error = ()>> PassByImpl<T> for Enum<T> {
fn into_ffi_value(instance: &T) -> WrappedFFIValue<Self::FFIType, Self::Owned> {
let value: u8 = (*instance).into();
value.into()
(value as u32).into()
}
fn from_ffi_value(arg: Self::FFIType) -> T {
T::try_from(arg).expect("Host to wasm provides a valid enum discriminant; qed")
T::try_from(arg as u8).expect("Host to wasm provides a valid enum discriminant; qed")
}
}
/// The type is passed as `u8`.
/// The type is passed as `u32`.
///
/// The value is corresponds to the discriminant of the variant.
impl<T: Copy + Into<u8> + TryFrom<u8>> RIType for Enum<T> {
type FFIType = u8;
type FFIType = u32;
}
@@ -13,7 +13,7 @@ publish = false
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
sp-runtime-interface = { version = "4.0.0", default-features = false, path = "../" }
sp-runtime-interface = { version = "4.1.0-dev", default-features = false, path = "../" }
sp-std = { version = "4.0.0", default-features = false, path = "../../std" }
sp-io = { version = "4.0.0-dev", default-features = false, path = "../../io" }
sp-core = { version = "4.1.0-dev", default-features = false, path = "../../core" }
@@ -13,7 +13,7 @@ publish = false
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
sp-runtime-interface = { version = "4.0.0", default-features = false, path = "../" }
sp-runtime-interface = { version = "4.1.0-dev", default-features = false, path = "../" }
sp-std = { version = "4.0.0", default-features = false, path = "../../std" }
sp-io = { version = "4.0.0-dev", default-features = false, path = "../../io" }
sp-core = { version = "4.1.0-dev", default-features = false, path = "../../core" }
@@ -12,7 +12,7 @@ repository = "https://github.com/paritytech/substrate/"
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
sp-runtime-interface = { version = "4.0.0", path = "../" }
sp-runtime-interface = { version = "4.1.0-dev", path = "../" }
sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" }
sc-executor-common = { version = "0.10.0-dev", path = "../../../client/executor/common" }
sp-runtime-interface-test-wasm = { version = "2.0.0", path = "../test-wasm" }
@@ -24,7 +24,7 @@ use sp_runtime_interface_test_wasm::{test_api::HostFunctions, wasm_binary_unwrap
use sp_runtime_interface_test_wasm_deprecated::wasm_binary_unwrap as wasm_binary_deprecated_unwrap;
use sc_executor_common::runtime_blob::RuntimeBlob;
use sp_wasm_interface::HostFunctions as HostFunctionsT;
use sp_wasm_interface::{ExtendedHostFunctions, HostFunctions as HostFunctionsT};
use std::{
collections::HashSet,
@@ -39,17 +39,11 @@ fn call_wasm_method_with_result<HF: HostFunctionsT>(
) -> Result<TestExternalities, String> {
let mut ext = TestExternalities::default();
let mut ext_ext = ext.ext();
let mut host_functions = HF::host_functions();
host_functions.extend(sp_io::SubstrateHostFunctions::host_functions());
let executor = sc_executor::WasmExecutor::new(
sc_executor::WasmExecutionMethod::Interpreted,
Some(8),
host_functions,
8,
None,
2,
);
let executor = sc_executor::WasmExecutor::<
ExtendedHostFunctions<sp_io::SubstrateHostFunctions, HF>,
>::new(sc_executor::WasmExecutionMethod::Interpreted, Some(8), 8, None, 2);
executor
.uncached_call(
RuntimeBlob::uncompress_if_needed(binary).expect("Failed to parse binary"),
+1 -1
View File
@@ -23,7 +23,7 @@ wasmi = { version = "0.9.0", optional = true }
sp-core = { version = "4.1.0-dev", default-features = false, path = "../core" }
sp-std = { version = "4.0.0", default-features = false, path = "../std" }
sp-io = { version = "4.0.0-dev", default-features = false, path = "../io" }
sp-wasm-interface = { version = "4.0.0", default-features = false, path = "../wasm-interface" }
sp-wasm-interface = { version = "4.1.0-dev", default-features = false, path = "../wasm-interface" }
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false }
log = { version = "0.4", default-features = false }
+1 -1
View File
@@ -18,7 +18,7 @@ log = { version = "0.4.8", optional = true }
sp-core = { version = "4.1.0-dev", default-features = false, path = "../core" }
sp-externalities = { version = "0.10.0", optional = true, path = "../externalities" }
sp-io = { version = "4.0.0-dev", default-features = false, path = "../io" }
sp-runtime-interface = { version = "4.0.0", default-features = false, path = "../runtime-interface" }
sp-runtime-interface = { version = "4.1.0-dev", default-features = false, path = "../runtime-interface" }
sp-std = { version = "4.0.0", default-features = false, path = "../std" }
[dev-dependencies]
@@ -1,6 +1,6 @@
[package]
name = "sp-wasm-interface"
version = "4.0.0"
version = "4.1.0-dev"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
license = "Apache-2.0"
@@ -15,10 +15,12 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
wasmi = { version = "0.9.1", optional = true }
wasmtime = { version = "0.31.0", optional = true, default-features = false }
log = { version = "0.4.14", optional = true }
impl-trait-for-tuples = "0.2.1"
sp-std = { version = "4.0.0", path = "../std", default-features = false }
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] }
[features]
default = [ "std" ]
std = [ "wasmi", "sp-std/std", "codec/std" ]
std = [ "wasmi", "sp-std/std", "codec/std", "log" ]
+201 -2
View File
@@ -24,6 +24,25 @@ use sp_std::{borrow::Cow, iter::Iterator, marker::PhantomData, mem, result, vec,
#[cfg(feature = "std")]
mod wasmi_impl;
#[cfg(not(all(feature = "std", feature = "wasmtime")))]
#[macro_export]
macro_rules! if_wasmtime_is_enabled {
($($token:tt)*) => {};
}
#[cfg(all(feature = "std", feature = "wasmtime"))]
#[macro_export]
macro_rules! if_wasmtime_is_enabled {
($($token:tt)*) => {
$($token)*
}
}
if_wasmtime_is_enabled! {
// Reexport wasmtime so that its types are accessible from the procedural macro.
pub use wasmtime;
}
/// Result type used by traits in this crate.
#[cfg(feature = "std")]
pub type Result<T> = result::Result<T, String>;
@@ -105,7 +124,8 @@ impl Value {
}
}
/// Provides `Sealed` trait to prevent implementing trait `PointerType` outside of this crate.
/// Provides `Sealed` trait to prevent implementing trait `PointerType` and `WasmTy` outside of this
/// crate.
mod private {
pub trait Sealed {}
@@ -113,6 +133,9 @@ mod private {
impl Sealed for u16 {}
impl Sealed for u32 {}
impl Sealed for u64 {}
impl Sealed for i32 {}
impl Sealed for i64 {}
}
/// Something that can be wrapped in a wasm `Pointer`.
@@ -338,10 +361,48 @@ pub trait Sandbox {
fn get_global_val(&self, instance_idx: u32, name: &str) -> Result<Option<Value>>;
}
if_wasmtime_is_enabled! {
/// A trait used to statically register host callbacks with the WASM executor,
/// so that they call be called from within the runtime with minimal overhead.
///
/// This is used internally to interface the wasmtime-based executor with the
/// host functions' definitions generated through the runtime interface macro,
/// and is not meant to be used directly.
pub trait HostFunctionRegistry {
type State;
type Error;
type FunctionContext: FunctionContext;
/// Wraps the given `caller` in a type which implements `FunctionContext`
/// and calls the given `callback`.
fn with_function_context<R>(
caller: wasmtime::Caller<Self::State>,
callback: impl FnOnce(&mut dyn FunctionContext) -> R,
) -> R;
/// Registers a given host function with the WASM executor.
///
/// The function has to be statically callable, and all of its arguments
/// and its return value have to be compatible with WASM FFI.
fn register_static<Params, Results>(
&mut self,
fn_name: &str,
func: impl wasmtime::IntoFunc<Self::State, Params, Results> + 'static,
) -> core::result::Result<(), Self::Error>;
}
}
/// Something that provides implementations for host functions.
pub trait HostFunctions: 'static {
pub trait HostFunctions: 'static + Send + Sync {
/// Returns the host functions `Self` provides.
fn host_functions() -> Vec<&'static dyn Function>;
if_wasmtime_is_enabled! {
/// Statically registers the host functions.
fn register_static<T>(registry: &mut T) -> core::result::Result<(), T::Error>
where
T: HostFunctionRegistry;
}
}
#[impl_trait_for_tuples::impl_for_tuples(30)]
@@ -353,8 +414,146 @@ impl HostFunctions for Tuple {
host_functions
}
#[cfg(all(feature = "std", feature = "wasmtime"))]
fn register_static<T>(registry: &mut T) -> core::result::Result<(), T::Error>
where
T: HostFunctionRegistry,
{
for_tuples!(
#( Tuple::register_static(registry)?; )*
);
Ok(())
}
}
/// A wrapper which merges two sets of host functions, and allows the second set to override
/// the host functions from the first set.
pub struct ExtendedHostFunctions<Base, Overlay> {
phantom: PhantomData<(Base, Overlay)>,
}
impl<Base, Overlay> HostFunctions for ExtendedHostFunctions<Base, Overlay>
where
Base: HostFunctions,
Overlay: HostFunctions,
{
fn host_functions() -> Vec<&'static dyn Function> {
let mut base = Base::host_functions();
let overlay = Overlay::host_functions();
base.retain(|host_fn| {
!overlay.iter().any(|ext_host_fn| host_fn.name() == ext_host_fn.name())
});
base.extend(overlay);
base
}
if_wasmtime_is_enabled! {
fn register_static<T>(registry: &mut T) -> core::result::Result<(), T::Error>
where
T: HostFunctionRegistry,
{
struct Proxy<'a, T> {
registry: &'a mut T,
seen_overlay: std::collections::HashSet<String>,
seen_base: std::collections::HashSet<String>,
overlay_registered: bool,
}
impl<'a, T> HostFunctionRegistry for Proxy<'a, T>
where
T: HostFunctionRegistry,
{
type State = T::State;
type Error = T::Error;
type FunctionContext = T::FunctionContext;
fn with_function_context<R>(
caller: wasmtime::Caller<Self::State>,
callback: impl FnOnce(&mut dyn FunctionContext) -> R,
) -> R {
T::with_function_context(caller, callback)
}
fn register_static<Params, Results>(
&mut self,
fn_name: &str,
func: impl wasmtime::IntoFunc<Self::State, Params, Results> + 'static,
) -> core::result::Result<(), Self::Error> {
if self.overlay_registered {
if !self.seen_base.insert(fn_name.to_owned()) {
log::warn!(
target: "extended_host_functions",
"Duplicate base host function: '{}'",
fn_name,
);
// TODO: Return an error here?
return Ok(())
}
if self.seen_overlay.contains(fn_name) {
// Was already registered when we went through the overlay, so just ignore it.
log::debug!(
target: "extended_host_functions",
"Overriding base host function: '{}'",
fn_name,
);
return Ok(())
}
} else if !self.seen_overlay.insert(fn_name.to_owned()) {
log::warn!(
target: "extended_host_functions",
"Duplicate overlay host function: '{}'",
fn_name,
);
// TODO: Return an error here?
return Ok(())
}
self.registry.register_static(fn_name, func)
}
}
let mut proxy = Proxy {
registry,
seen_overlay: Default::default(),
seen_base: Default::default(),
overlay_registered: false,
};
// The functions from the `Overlay` can override those from the `Base`,
// so `Overlay` is registered first, and then we skip those functions
// in `Base` if they were already registered from the `Overlay`.
Overlay::register_static(&mut proxy)?;
proxy.overlay_registered = true;
Base::register_static(&mut proxy)?;
Ok(())
}
}
}
/// A trait for types directly usable at the WASM FFI boundary without any conversion at all.
///
/// This trait is sealed and should not be implemented downstream.
#[cfg(all(feature = "std", feature = "wasmtime"))]
pub trait WasmTy: wasmtime::WasmTy + private::Sealed {}
/// A trait for types directly usable at the WASM FFI boundary without any conversion at all.
///
/// This trait is sealed and should not be implemented downstream.
#[cfg(not(all(feature = "std", feature = "wasmtime")))]
pub trait WasmTy: private::Sealed {}
impl WasmTy for i32 {}
impl WasmTy for u32 {}
impl WasmTy for i64 {}
impl WasmTy for u64 {}
/// Something that can be converted into a wasm compatible `Value`.
pub trait IntoValue {
/// The type of the value in wasm.