diff --git a/Cargo.lock b/Cargo.lock index 0b3692e48..1f60d5b09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1217,6 +1217,7 @@ dependencies = [ "Inflector", "anyhow", "expander", + "impl-trait-for-tuples", "log", "parity-scale-codec", "proc-macro-crate 3.2.0", diff --git a/node/runtime-interface-macro/cere-wasm-interface/Cargo.toml b/node/runtime-interface-macro/cere-wasm-interface/Cargo.toml index e864af2ce..3f73b3dc9 100644 --- a/node/runtime-interface-macro/cere-wasm-interface/Cargo.toml +++ b/node/runtime-interface-macro/cere-wasm-interface/Cargo.toml @@ -30,6 +30,7 @@ sp-wasm-interface = { workspace = true } anyhow = { optional = true, workspace = true } log = { optional = true, workspace = true, default-features = true } wasmtime = { optional = true, workspace = true } +impl-trait-for-tuples = { workspace = true } [features] default = ["std"] diff --git a/node/runtime-interface-macro/cere-wasm-interface/src/lib.rs b/node/runtime-interface-macro/cere-wasm-interface/src/lib.rs index a92e2b8c6..0f8ad6063 100644 --- a/node/runtime-interface-macro/cere-wasm-interface/src/lib.rs +++ b/node/runtime-interface-macro/cere-wasm-interface/src/lib.rs @@ -4,7 +4,7 @@ extern crate alloc; use alloc::{borrow::Cow, vec, vec::Vec}; use core::{iter::Iterator, marker::PhantomData, mem, result}; -use sp_wasm_interface::{Function, Result as WResult}; +use sp_wasm_interface::{Result as WResult}; #[cfg(not(all(feature = "std", feature = "wasmtime")))] @@ -220,6 +220,61 @@ impl TryFromValue for Pointer { /// The word size used in wasm. Normally known as `usize` in Rust. pub type WordSize = u32; +#[derive(Eq, PartialEq, Debug, Clone)] +pub struct Signature { + /// The arguments of a function. + pub args: Cow<'static, [ValueType]>, + /// The optional return value of a function. + pub return_value: Option, +} + +impl Signature { + /// Create a new instance of `Signature`. + pub fn new>>( + args: T, + return_value: Option, + ) -> Self { + Self { args: args.into(), return_value } + } + + /// Create a new instance of `Signature` with the given `args` and without any return value. + pub fn new_with_args>>(args: T) -> Self { + Self { args: args.into(), return_value: None } + } +} + +/// A trait that requires `RefUnwindSafe` when `feature = std`. +#[cfg(feature = "std")] +pub trait MaybeRefUnwindSafe: std::panic::RefUnwindSafe {} +#[cfg(feature = "std")] +impl MaybeRefUnwindSafe for T {} + +/// A trait that requires `RefUnwindSafe` when `feature = std`. +#[cfg(not(feature = "std"))] +pub trait MaybeRefUnwindSafe {} +#[cfg(not(feature = "std"))] +impl MaybeRefUnwindSafe for T {} + +/// Something that provides a function implementation on the host for a wasm function. +pub trait Function: MaybeRefUnwindSafe + Send + Sync { + /// Returns the name of this function. + fn name(&self) -> &str; + /// Returns the signature of this function. + fn signature(&self) -> Signature; + /// Execute this function with the given arguments. + fn execute( + &self, + context: &mut dyn FunctionContext, + args: &mut dyn Iterator, + ) -> Result>; +} + +impl PartialEq for dyn Function { + fn eq(&self, other: &Self) -> bool { + other.name() == self.name() && other.signature() == self.signature() + } +} + /// Something that can be converted into a wasm compatible `Value`. pub trait IntoValue { /// The type of the value in wasm. @@ -235,7 +290,6 @@ pub trait TryFromValue: Sized { fn try_from_value(val: Value) -> Option; } - pub trait FunctionContext { /// Read memory from `address` into a vector. fn read_memory(&self, address: Pointer, size: WordSize) -> Result> { @@ -336,3 +390,242 @@ pub trait Sandbox { state_ptr: Pointer, ) -> u32; } + + +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( + caller: wasmtime::Caller, + 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( + &mut self, + fn_name: &str, + func: impl wasmtime::IntoFunc + 'static, + ) -> core::result::Result<(), Self::Error>; + } +} + +/// Something that provides implementations for host functions. +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(registry: &mut T) -> core::result::Result<(), T::Error> + where + T: HostFunctionRegistry; + } +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl HostFunctions for Tuple { + fn host_functions() -> Vec<&'static dyn Function> { + let mut host_functions = Vec::new(); + + for_tuples!( #( host_functions.extend(Tuple::host_functions()); )* ); + + host_functions + } + + #[cfg(all(feature = "std", feature = "wasmtime"))] + fn register_static(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 { + phantom: PhantomData<(Base, Overlay)>, +} + +impl HostFunctions for ExtendedHostFunctions + 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(registry: &mut T) -> core::result::Result<(), T::Error> + where + T: HostFunctionRegistry, + { + struct Proxy<'a, T> { + registry: &'a mut T, + seen_overlay: std::collections::HashSet, + seen_base: std::collections::HashSet, + 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( + caller: wasmtime::Caller, + callback: impl FnOnce(&mut dyn FunctionContext) -> R, + ) -> R { + T::with_function_context(caller, callback) + } + + fn register_static( + &mut self, + fn_name: &str, + func: impl wasmtime::IntoFunc + '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(()) + } + } +} + +macro_rules! impl_into_and_from_value { + ( + $( + $type:ty, $( < $gen:ident >, )? $value_variant:ident, + )* + ) => { + $( + impl $( <$gen> )? IntoValue for $type { + const VALUE_TYPE: ValueType = ValueType::$value_variant; + fn into_value(self) -> Value { Value::$value_variant(self as _) } + } + + impl $( <$gen> )? TryFromValue for $type { + fn try_from_value(val: Value) -> Option { + match val { + Value::$value_variant(val) => Some(val as _), + _ => None, + } + } + } + )* + } +} + +impl_into_and_from_value! { + u8, I32, + u16, I32, + u32, I32, + u64, I64, + i8, I32, + i16, I32, + i32, I32, + i64, I64, +} + +/// Typed value that can be returned from a function. +/// +/// Basically a `TypedValue` plus `Unit`, for functions which return nothing. +#[derive(Clone, Copy, PartialEq, codec::Encode, codec::Decode, Debug)] +pub enum ReturnValue { + /// For returning nothing. + Unit, + /// For returning some concrete value. + Value(Value), +} + +impl From for ReturnValue { + fn from(v: Value) -> ReturnValue { + ReturnValue::Value(v) + } +} + +impl ReturnValue { + /// Maximum number of bytes `ReturnValue` might occupy when serialized with `SCALE`. + /// + /// Breakdown: + /// 1 byte for encoding unit/value variant + /// 1 byte for encoding value type + /// 8 bytes for encoding the biggest value types available in wasm: f64, i64. + pub const ENCODED_MAX_SIZE: usize = 10; +} diff --git a/node/runtime-interface-macro/proc-macro/src/runtime_interface/host_function_interface.rs b/node/runtime-interface-macro/proc-macro/src/runtime_interface/host_function_interface.rs index 3848ce401..b420b73c5 100644 --- a/node/runtime-interface-macro/proc-macro/src/runtime_interface/host_function_interface.rs +++ b/node/runtime-interface-macro/proc-macro/src/runtime_interface/host_function_interface.rs @@ -186,16 +186,16 @@ fn generate_host_functions_struct( pub struct HostFunctions; #[cfg(feature = "std")] - impl #crate_::sp_wasm_interface::HostFunctions for HostFunctions { - fn host_functions() -> Vec<&'static dyn #crate_::sp_wasm_interface::Function> { + impl #crate_::cere_wasm_interface::HostFunctions for HostFunctions { + fn host_functions() -> Vec<&'static dyn #crate_::cere_wasm_interface::Function> { let mut host_functions_list = Vec::new(); #(#append_hf_bodies)* host_functions_list } - #crate_::sp_wasm_interface::if_wasmtime_is_enabled! { + #crate_::cere_wasm_interface::if_wasmtime_is_enabled! { fn register_static(registry: &mut T) -> core::result::Result<(), T::Error> - where T: #crate_::sp_wasm_interface::HostFunctionRegistry + where T: #crate_::cere_wasm_interface::HostFunctionRegistry { #(#register_bodies)* Ok(()) @@ -293,7 +293,7 @@ fn generate_host_function_implementation( ); 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) + let #ffi_name: #ffi_ty = #crate_::cere_wasm_interface::TryFromValue::try_from_value(#ffi_name) .ok_or_else(|| #convert_arg_error.to_owned())?; }); } @@ -317,7 +317,7 @@ fn generate_host_function_implementation( 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__))); + let __result__ = Ok(Some(#crate_::cere_wasm_interface::IntoValue::into_value(__result__))); }, ReturnType::Default => quote! { let __result__ = Ok(None); @@ -361,20 +361,20 @@ fn generate_host_function_implementation( #(#cfg_attrs)* #[cfg(feature = "std")] - impl #crate_::sp_wasm_interface::Function for #struct_name { + impl #crate_::cere_wasm_interface::Function for #struct_name { fn name(&self) -> &str { #name } - fn signature(&self) -> #crate_::sp_wasm_interface::Signature { + fn signature(&self) -> #crate_::cere_wasm_interface::Signature { #signature } fn execute( &self, __function_context__: &mut dyn #crate_::cere_wasm_interface::FunctionContext, - args: &mut dyn Iterator, - ) -> std::result::Result, String> { + args: &mut dyn Iterator, + ) -> std::result::Result, String> { #(#convert_args_dynamic_ffi_to_static_ffi)* let __result__ = Self::call( __function_context__, @@ -389,16 +389,16 @@ fn generate_host_function_implementation( let register_body = quote! { #(#cfg_attrs)* registry.register_static( - #crate_::sp_wasm_interface::Function::name(&#struct_name), - |mut caller: #crate_::sp_wasm_interface::wasmtime::Caller, #(#ffi_args_prototype),*| - -> std::result::Result<#ffi_return_ty, #crate_::sp_wasm_interface::anyhow::Error> + #crate_::cere_wasm_interface::Function::name(&#struct_name), + |mut caller: #crate_::cere_wasm_interface::wasmtime::Caller, #(#ffi_args_prototype),*| + -> std::result::Result<#ffi_return_ty, #crate_::cere_wasm_interface::anyhow::Error> { T::with_function_context(caller, move |__function_context__| { let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { #struct_name::call( __function_context__, #(#ffi_names,)* - ).map_err(#crate_::sp_wasm_interface::anyhow::Error::msg) + ).map_err(#crate_::cere_wasm_interface::anyhow::Error::msg) })); match result { Ok(result) => result, @@ -411,7 +411,7 @@ fn generate_host_function_implementation( } else { "host code panicked while being called by the runtime".to_owned() }; - return Err(#crate_::sp_wasm_interface::anyhow::Error::msg(message)); + return Err(#crate_::cere_wasm_interface::anyhow::Error::msg(message)); } } }) @@ -421,7 +421,7 @@ fn generate_host_function_implementation( let append_hf_body = quote! { #(#cfg_attrs)* - host_functions_list.push(&#struct_name as &dyn #crate_::sp_wasm_interface::Function); + host_functions_list.push(&#struct_name as &dyn #crate_::cere_wasm_interface::Function); }; Ok((implementation, register_body, append_hf_body)) @@ -432,18 +432,18 @@ fn generate_wasm_interface_signature_for_host_function(sig: &Signature) -> Resul let crate_ = generate_crate_access(); let return_value = match &sig.output { ReturnType::Type(_, ty) => quote! { - Some( <<#ty as #crate_::RIType>::FFIType as #crate_::sp_wasm_interface::IntoValue>::VALUE_TYPE ) + Some( <<#ty as #crate_::RIType>::FFIType as #crate_::cere_wasm_interface::IntoValue>::VALUE_TYPE ) }, ReturnType::Default => quote!(None), }; let arg_types = get_function_argument_types_without_ref(sig).map(|ty| { quote! { - <<#ty as #crate_::RIType>::FFIType as #crate_::sp_wasm_interface::IntoValue>::VALUE_TYPE + <<#ty as #crate_::RIType>::FFIType as #crate_::cere_wasm_interface::IntoValue>::VALUE_TYPE } }); Ok(quote! { - #crate_::sp_wasm_interface::Signature { + #crate_::cere_wasm_interface::Signature { args: std::borrow::Cow::Borrowed(&[ #( #arg_types ),* ][..]), return_value: #return_value, } diff --git a/node/runtime-interface-macro/src/impls.rs b/node/runtime-interface-macro/src/impls.rs index 675b09971..fb2bf1b22 100644 --- a/node/runtime-interface-macro/src/impls.rs +++ b/node/runtime-interface-macro/src/impls.rs @@ -518,12 +518,12 @@ macro_rules! for_u128_i128 { for_u128_i128!(u128); for_u128_i128!(i128); -impl PassBy for sp_wasm_interface::ValueType { - type PassBy = Enum; +impl PassBy for cere_wasm_interface::ValueType { + type PassBy = Enum; } -impl PassBy for sp_wasm_interface::Value { - type PassBy = Codec; +impl PassBy for cere_wasm_interface::Value { + type PassBy = Codec; } impl PassBy for sp_storage::TrackedStorageKey { diff --git a/node/runtime-interface-macro/src/lib.rs b/node/runtime-interface-macro/src/lib.rs index 683ff0e14..d2d550451 100644 --- a/node/runtime-interface-macro/src/lib.rs +++ b/node/runtime-interface-macro/src/lib.rs @@ -401,8 +401,8 @@ 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: cere_wasm_interface::IntoValue + + cere_wasm_interface::TryFromValue + sp_wasm_interface::WasmTy; #[cfg(not(feature = "std"))] type FFIType; diff --git a/node/runtime-interfaces/src/freeing_bump.rs b/node/runtime-interfaces/src/freeing_bump.rs index 07f10d4d1..2a346a5eb 100644 --- a/node/runtime-interfaces/src/freeing_bump.rs +++ b/node/runtime-interfaces/src/freeing_bump.rs @@ -68,8 +68,7 @@ //! sizes. pub use sp_core::MAX_POSSIBLE_ALLOCATION; -use sp_wasm_interface::{WordSize}; -use cere_wasm_interface::{Pointer}; +use cere_wasm_interface::{Pointer, WordSize}; use std::{ cmp::{max, min}, mem, diff --git a/node/runtime-interfaces/src/lib.rs b/node/runtime-interfaces/src/lib.rs index e252f065d..85011581e 100644 --- a/node/runtime-interfaces/src/lib.rs +++ b/node/runtime-interfaces/src/lib.rs @@ -1,5 +1,5 @@ use sp_runtime_interface_macro::runtime_interface; -use sp_wasm_interface::{Result as SandboxResult, Value, WordSize}; +use sp_wasm_interface::{Result as SandboxResult }; use cere_wasm_interface::Pointer; pub type MemoryId = u32; use std::{cell::RefCell, rc::Rc, str, sync::Arc}; @@ -10,7 +10,7 @@ mod util; mod sandbox_interface; mod wasmi_backend; use crate::sandbox_util::Store; -use sp_wasm_interface::Function; +use cere_wasm_interface::{Function, Value, WordSize}; use wasmi::TableRef; use wasmi::MemoryRef; use crate::freeing_bump::FreeingBumpHeapAllocator; @@ -99,7 +99,7 @@ pub trait Sandbox { &mut self, instance_idx: u32, name: &str, - ) -> Option { + ) -> Option { // self.sandbox() // .get_global_val(instance_idx, name) // .expect("Failed to get global from sandbox") diff --git a/node/runtime-interfaces/src/sandbox_interface.rs b/node/runtime-interfaces/src/sandbox_interface.rs index 9a3ffb40f..0b2ea15a0 100644 --- a/node/runtime-interfaces/src/sandbox_interface.rs +++ b/node/runtime-interfaces/src/sandbox_interface.rs @@ -1,6 +1,6 @@ use std::{cell::RefCell, rc::Rc, str, sync::Arc, result}; -use sp_wasm_interface::{Function, Result as WResult, Value, WordSize}; -use cere_wasm_interface::{FunctionContext, Pointer}; +use sp_wasm_interface::{Result as WResult}; +use cere_wasm_interface::{FunctionContext, Pointer, Function, Value, WordSize}; use crate::FunctionExecutor; use crate::env as sandbox_env; use log::{debug, error, trace}; @@ -262,7 +262,7 @@ impl Sandbox for FunctionExecutor { trace!(target: "sp-sandbox", "invoke, instance_idx={}", instance_id); // Deserialize arguments and convert them into wasmi types. - let args = Vec::::decode(&mut args) + let args = Vec::::decode(&mut args) .map_err(|_| "Can't decode serialized arguments for the invocation")? .into_iter() .collect::>(); @@ -285,7 +285,7 @@ impl Sandbox for FunctionExecutor { Ok(None) => Ok(sandbox_env::ERR_OK), Ok(Some(val)) => { // Serialize return value and write it back into the memory. - sp_wasm_interface::ReturnValue::Value(val).using_encoded(|val| { + cere_wasm_interface::ReturnValue::Value(val).using_encoded(|val| { if val.len() > return_val_len as usize { return Err("Return value buffer is too small".into()) } @@ -351,7 +351,7 @@ impl Sandbox for FunctionExecutor { &self, instance_idx: u32, name: &str, - ) -> WResult> { + ) -> WResult> { self.sandbox_store .borrow() .instance(instance_idx) diff --git a/node/runtime-interfaces/src/sandbox_util.rs b/node/runtime-interfaces/src/sandbox_util.rs index d88b6c5ef..5c654fc0d 100644 --- a/node/runtime-interfaces/src/sandbox_util.rs +++ b/node/runtime-interfaces/src/sandbox_util.rs @@ -27,8 +27,7 @@ use std::{collections::HashMap, rc::Rc}; use codec::Decode; use crate::env as sandbox_env; -use sp_wasm_interface::{ WordSize }; -use cere_wasm_interface::{FunctionContext, Pointer}; +use cere_wasm_interface::{FunctionContext, Pointer, WordSize}; use crate::{ util,wasmi_backend, @@ -196,10 +195,10 @@ impl SandboxInstance { pub fn invoke( &self, export_name: &str, - args: &[sp_wasm_interface::Value], + args: &[cere_wasm_interface::Value], state: u32, sandbox_context: &mut dyn SandboxContext, - ) -> std::result::Result, Error> { + ) -> std::result::Result, Error> { match &self.backend_instance { BackendInstance::Wasmi(wasmi_instance) => wasmi_invoke(self, wasmi_instance, export_name, args, state, sandbox_context), @@ -213,7 +212,7 @@ impl SandboxInstance { /// Get the value from a global with the given `name`. /// /// Returns `Some(_)` if the global could be found. - pub fn get_global_val(&self, name: &str) -> Option { + pub fn get_global_val(&self, name: &str) -> Option { match &self.backend_instance { BackendInstance::Wasmi(wasmi_instance) => wasmi_get_global(wasmi_instance, name), diff --git a/node/runtime-interfaces/src/wasmi_backend.rs b/node/runtime-interfaces/src/wasmi_backend.rs index 9c26cf74e..b79ce3b41 100644 --- a/node/runtime-interfaces/src/wasmi_backend.rs +++ b/node/runtime-interfaces/src/wasmi_backend.rs @@ -21,7 +21,7 @@ use std::{fmt, rc::Rc}; use codec::{Decode, Encode}; -use sp_wasm_interface::{ReturnValue, Value, WordSize}; +use cere_wasm_interface::{ReturnValue, Value, WordSize}; use cere_wasm_interface::{FunctionContext, Pointer}; use wasmi::{ memory_units::Pages, ImportResolver, MemoryInstance, Module, ModuleInstance, RuntimeArgs,