diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index 4bed902878..b576a12ea6 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -9685,7 +9685,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface" -version = "4.0.0" +version = "4.1.0-dev" dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", @@ -9962,12 +9962,14 @@ dependencies = [ [[package]] name = "sp-wasm-interface" -version = "4.0.0" +version = "4.1.0-dev" dependencies = [ "impl-trait-for-tuples", + "log 0.4.14", "parity-scale-codec", "sp-std", "wasmi", + "wasmtime", ] [[package]] diff --git a/substrate/client/allocator/Cargo.toml b/substrate/client/allocator/Cargo.toml index ea0ce3c0bb..eb8d298e1e 100644 --- a/substrate/client/allocator/Cargo.toml +++ b/substrate/client/allocator/Cargo.toml @@ -15,6 +15,6 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] sp-core = { version = "4.1.0-dev", path = "../../primitives/core" } -sp-wasm-interface = { version = "4.0.0", path = "../../primitives/wasm-interface" } +sp-wasm-interface = { version = "4.1.0-dev", path = "../../primitives/wasm-interface" } log = "0.4.11" thiserror = "1.0.30" diff --git a/substrate/client/executor/Cargo.toml b/substrate/client/executor/Cargo.toml index 58d7ec34b2..377623512f 100644 --- a/substrate/client/executor/Cargo.toml +++ b/substrate/client/executor/Cargo.toml @@ -24,8 +24,8 @@ sp-panic-handler = { version = "4.0.0-dev", path = "../../primitives/panic-handl wasmi = "0.9.1" lazy_static = "1.4.0" sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } -sp-wasm-interface = { version = "4.0.0", path = "../../primitives/wasm-interface" } -sp-runtime-interface = { version = "4.0.0", path = "../../primitives/runtime-interface" } +sp-wasm-interface = { version = "4.1.0-dev", path = "../../primitives/wasm-interface" } +sp-runtime-interface = { version = "4.1.0-dev", path = "../../primitives/runtime-interface" } sp-externalities = { version = "0.10.0", path = "../../primitives/externalities" } sc-executor-common = { version = "0.10.0-dev", path = "common" } sc-executor-wasmi = { version = "0.10.0-dev", path = "wasmi" } diff --git a/substrate/client/executor/common/Cargo.toml b/substrate/client/executor/common/Cargo.toml index abaee76073..5edf179c05 100644 --- a/substrate/client/executor/common/Cargo.toml +++ b/substrate/client/executor/common/Cargo.toml @@ -20,7 +20,7 @@ codec = { package = "parity-scale-codec", version = "2.0.0" } wasmi = "0.9.1" sp-core = { version = "4.1.0-dev", path = "../../../primitives/core" } sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } -sp-wasm-interface = { version = "4.0.0", path = "../../../primitives/wasm-interface" } +sp-wasm-interface = { version = "4.1.0-dev", path = "../../../primitives/wasm-interface" } sp-maybe-compressed-blob = { version = "4.1.0-dev", path = "../../../primitives/maybe-compressed-blob" } sp-serializer = { version = "4.0.0-dev", path = "../../../primitives/serializer" } thiserror = "1.0.30" diff --git a/substrate/client/executor/runtime-test/src/lib.rs b/substrate/client/executor/runtime-test/src/lib.rs index 3ea6e2d7ed..01e46ab946 100644 --- a/substrate/client/executor/runtime-test/src/lib.rs +++ b/substrate/client/executor/runtime-test/src/lib.rs @@ -332,6 +332,14 @@ sp_core::wasm_export_functions! { fn test_panic_in_spawned() { sp_tasks::spawn(tasks::panicker, vec![]).join(); } + + fn test_return_i8() -> i8 { + -66 + } + + fn test_take_i8(value: i8) { + assert_eq!(value, -66); + } } #[cfg(not(feature = "std"))] diff --git a/substrate/client/executor/src/integration_tests/mod.rs b/substrate/client/executor/src/integration_tests/mod.rs index 3ff0ecebd9..b2b3167992 100644 --- a/substrate/client/executor/src/integration_tests/mod.rs +++ b/substrate/client/executor/src/integration_tests/mod.rs @@ -34,7 +34,6 @@ use sp_core::{ use sp_runtime::traits::BlakeTwo256; use sp_state_machine::TestExternalities as CoreTestExternalities; use sp_trie::{trie_types::Layout, TrieConfiguration}; -use sp_wasm_interface::HostFunctions as _; use std::sync::Arc; use tracing_subscriber::layer::SubscriberExt; @@ -124,14 +123,8 @@ fn call_in_wasm( execution_method: WasmExecutionMethod, ext: &mut E, ) -> Result, String> { - let executor = crate::WasmExecutor::new( - execution_method, - Some(1024), - HostFunctions::host_functions(), - 8, - None, - 2, - ); + let executor = + crate::WasmExecutor::::new(execution_method, Some(1024), 8, None, 2); executor.uncached_call( RuntimeBlob::uncompress_if_needed(&wasm_binary_unwrap()[..]).unwrap(), ext, @@ -475,10 +468,9 @@ test_wasm_execution!(should_trap_when_heap_exhausted); fn should_trap_when_heap_exhausted(wasm_method: WasmExecutionMethod) { let mut ext = TestExternalities::default(); - let executor = crate::WasmExecutor::new( + let executor = crate::WasmExecutor::::new( wasm_method, Some(17), // `17` is the initial number of pages compiled into the binary. - HostFunctions::host_functions(), 8, None, 2, @@ -501,11 +493,10 @@ fn mk_test_runtime(wasm_method: WasmExecutionMethod, pages: u64) -> Arc( wasm_method, pages, blob, - HostFunctions::host_functions(), true, None, ) @@ -589,10 +580,9 @@ fn heap_is_reset_between_calls(wasm_method: WasmExecutionMethod) { test_wasm_execution!(parallel_execution); fn parallel_execution(wasm_method: WasmExecutionMethod) { - let executor = std::sync::Arc::new(crate::WasmExecutor::new( + let executor = std::sync::Arc::new(crate::WasmExecutor::::new( wasm_method, Some(1024), - HostFunctions::host_functions(), 8, None, 2, @@ -763,11 +753,10 @@ fn memory_is_cleared_between_invocations(wasm_method: WasmExecutionMethod) { ) )"#).unwrap(); - let runtime = crate::wasm_runtime::create_wasm_runtime_with_code( + let runtime = crate::wasm_runtime::create_wasm_runtime_with_code::( wasm_method, 1024, RuntimeBlob::uncompress_if_needed(&binary[..]).unwrap(), - HostFunctions::host_functions(), true, None, ) @@ -780,3 +769,22 @@ fn memory_is_cleared_between_invocations(wasm_method: WasmExecutionMethod) { let res = instance.call_export("returns_no_bss_mutable_static", &[0]).unwrap(); assert_eq!(1, u64::decode(&mut &res[..]).unwrap()); } + +test_wasm_execution!(return_i8); +fn return_i8(wasm_method: WasmExecutionMethod) { + let mut ext = TestExternalities::default(); + let mut ext = ext.ext(); + + assert_eq!( + call_in_wasm("test_return_i8", &[], wasm_method, &mut ext).unwrap(), + (-66_i8).encode() + ); +} + +test_wasm_execution!(take_i8); +fn take_i8(wasm_method: WasmExecutionMethod) { + let mut ext = TestExternalities::default(); + let mut ext = ext.ext(); + + call_in_wasm("test_take_i8", &(-66_i8).encode(), wasm_method, &mut ext).unwrap(); +} diff --git a/substrate/client/executor/src/lib.rs b/substrate/client/executor/src/lib.rs index 9a4d8e3cf0..8bac541afc 100644 --- a/substrate/client/executor/src/lib.rs +++ b/substrate/client/executor/src/lib.rs @@ -67,17 +67,15 @@ mod tests { use sc_executor_common::runtime_blob::RuntimeBlob; use sc_runtime_test::wasm_binary_unwrap; use sp_io::TestExternalities; - use sp_wasm_interface::HostFunctions; #[test] fn call_in_interpreted_wasm_works() { let mut ext = TestExternalities::default(); let mut ext = ext.ext(); - let executor = WasmExecutor::new( + let executor = WasmExecutor::::new( WasmExecutionMethod::Interpreted, Some(8), - sp_io::SubstrateHostFunctions::host_functions(), 8, None, 2, diff --git a/substrate/client/executor/src/native_executor.rs b/substrate/client/executor/src/native_executor.rs index 1c01520ac9..60e661d10d 100644 --- a/substrate/client/executor/src/native_executor.rs +++ b/substrate/client/executor/src/native_executor.rs @@ -24,6 +24,7 @@ use crate::{ use std::{ collections::HashMap, + marker::PhantomData, panic::{AssertUnwindSafe, UnwindSafe}, path::PathBuf, result, @@ -46,7 +47,7 @@ use sp_core::{ use sp_externalities::ExternalitiesExt as _; use sp_tasks::new_async_externalities; use sp_version::{GetNativeVersion, NativeVersion, RuntimeVersion}; -use sp_wasm_interface::{Function, HostFunctions}; +use sp_wasm_interface::{ExtendedHostFunctions, HostFunctions}; /// Default num of pages for the heap const DEFAULT_HEAP_PAGES: u64 = 2048; @@ -91,22 +92,36 @@ pub trait NativeExecutionDispatch: Send + Sync { /// An abstraction over Wasm code executor. Supports selecting execution backend and /// manages runtime cache. -#[derive(Clone)] -pub struct WasmExecutor { +pub struct WasmExecutor { /// Method used to execute fallback Wasm code. method: WasmExecutionMethod, /// The number of 64KB pages to allocate for Wasm execution. default_heap_pages: u64, - /// The host functions registered with this instance. - host_functions: Arc>, /// WASM runtime cache. cache: Arc, /// The path to a directory which the executor can leverage for a file cache, e.g. put there /// compiled artifacts. cache_path: Option, + + phantom: PhantomData, } -impl WasmExecutor { +impl Clone for WasmExecutor { + fn clone(&self) -> Self { + Self { + method: self.method, + default_heap_pages: self.default_heap_pages, + cache: self.cache.clone(), + cache_path: self.cache_path.clone(), + phantom: self.phantom, + } + } +} + +impl WasmExecutor +where + H: HostFunctions, +{ /// Create new instance. /// /// # Parameters @@ -127,7 +142,6 @@ impl WasmExecutor { pub fn new( method: WasmExecutionMethod, default_heap_pages: Option, - host_functions: Vec<&'static dyn Function>, max_runtime_instances: usize, cache_path: Option, runtime_cache_size: u8, @@ -135,13 +149,13 @@ impl WasmExecutor { WasmExecutor { method, default_heap_pages: default_heap_pages.unwrap_or(DEFAULT_HEAP_PAGES), - host_functions: Arc::new(host_functions), cache: Arc::new(RuntimeCache::new( max_runtime_instances, cache_path.clone(), runtime_cache_size, )), cache_path, + phantom: PhantomData, } } @@ -173,12 +187,11 @@ impl WasmExecutor { AssertUnwindSafe<&mut dyn Externalities>, ) -> Result>, { - match self.cache.with_instance( + match self.cache.with_instance::( runtime_code, ext, self.method, self.default_heap_pages, - &*self.host_functions, allow_missing_host_functions, |module, instance, version, ext| { let module = AssertUnwindSafe(module); @@ -208,11 +221,10 @@ impl WasmExecutor { export_name: &str, call_data: &[u8], ) -> std::result::Result, String> { - let module = crate::wasm_runtime::create_wasm_runtime_with_code( + let module = crate::wasm_runtime::create_wasm_runtime_with_code::( self.method, self.default_heap_pages, runtime_blob, - self.host_functions.to_vec(), allow_missing_host_functions, self.cache_path.as_deref(), ) @@ -235,7 +247,10 @@ impl WasmExecutor { } } -impl sp_core::traits::ReadRuntimeVersion for WasmExecutor { +impl sp_core::traits::ReadRuntimeVersion for WasmExecutor +where + H: HostFunctions, +{ fn read_runtime_version( &self, wasm_code: &[u8], @@ -269,7 +284,10 @@ impl sp_core::traits::ReadRuntimeVersion for WasmExecutor { } } -impl CodeExecutor for WasmExecutor { +impl CodeExecutor for WasmExecutor +where + H: HostFunctions, +{ type Error = Error; fn call< @@ -299,7 +317,10 @@ impl CodeExecutor for WasmExecutor { } } -impl RuntimeVersionOf for WasmExecutor { +impl RuntimeVersionOf for WasmExecutor +where + H: HostFunctions, +{ fn runtime_version( &self, ext: &mut dyn Externalities, @@ -313,13 +334,17 @@ impl RuntimeVersionOf for WasmExecutor { /// A generic `CodeExecutor` implementation that uses a delegate to determine wasm code equivalence /// and dispatch to native code when possible, falling back on `WasmExecutor` when not. -pub struct NativeElseWasmExecutor { +pub struct NativeElseWasmExecutor +where + D: NativeExecutionDispatch, +{ /// Dummy field to avoid the compiler complaining about us not using `D`. _dummy: std::marker::PhantomData, /// Native runtime version info. native_version: NativeVersion, /// Fallback wasm executor. - wasm: WasmExecutor, + wasm: + WasmExecutor>, } impl NativeElseWasmExecutor { @@ -337,24 +362,9 @@ impl NativeElseWasmExecutor { max_runtime_instances: usize, runtime_cache_size: u8, ) -> Self { - let extended = D::ExtendHostFunctions::host_functions(); - let mut host_functions = sp_io::SubstrateHostFunctions::host_functions() - .into_iter() - // filter out any host function overrides provided. - .filter(|host_fn| { - extended - .iter() - .find(|ext_host_fn| host_fn.name() == ext_host_fn.name()) - .is_none() - }) - .collect::>(); - - // Add the custom host functions provided by the user. - host_functions.extend(extended); let wasm_executor = WasmExecutor::new( fallback_method, default_heap_pages, - host_functions, max_runtime_instances, None, runtime_cache_size, @@ -645,8 +655,21 @@ mod tests { 8, 2, ); + + fn extract_host_functions( + _: &WasmExecutor, + ) -> Vec<&'static dyn sp_wasm_interface::Function> + where + H: HostFunctions, + { + H::host_functions() + } + my_interface::HostFunctions::host_functions().iter().for_each(|function| { - assert_eq!(executor.wasm.host_functions.iter().filter(|f| f == &function).count(), 2); + assert_eq!( + extract_host_functions(&executor.wasm).iter().filter(|f| f == &function).count(), + 2 + ); }); my_interface::say_hello_world("hey"); diff --git a/substrate/client/executor/src/wasm_runtime.rs b/substrate/client/executor/src/wasm_runtime.rs index 44ed6eb323..cebfc7b01b 100644 --- a/substrate/client/executor/src/wasm_runtime.rs +++ b/substrate/client/executor/src/wasm_runtime.rs @@ -37,7 +37,7 @@ use std::{ sync::Arc, }; -use sp_wasm_interface::Function; +use sp_wasm_interface::HostFunctions; /// Specification of different methods of executing the runtime Wasm code. #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] @@ -199,14 +199,14 @@ impl RuntimeCache { /// /// `wasm_method` - Type of WASM backend to use. /// - /// `host_functions` - The host functions that should be registered for the Wasm runtime. - /// /// `allow_missing_func_imports` - Ignore missing function imports. /// /// `max_runtime_instances` - The size of the instances cache. /// /// `f` - Function to execute. /// + /// `H` - A compile-time list of host functions to expose to the runtime. + /// /// # Returns result of `f` wrapped in an additional result. /// In case of failure one of two errors can be returned: /// @@ -214,17 +214,17 @@ impl RuntimeCache { /// /// `Error::InvalidMemoryReference` is returned if no memory export with the /// identifier `memory` can be found in the runtime. - pub fn with_instance<'c, R, F>( + pub fn with_instance<'c, H, R, F>( &self, runtime_code: &'c RuntimeCode<'c>, ext: &mut dyn Externalities, wasm_method: WasmExecutionMethod, default_heap_pages: u64, - host_functions: &[&'static dyn Function], allow_missing_func_imports: bool, f: F, ) -> Result, Error> where + H: HostFunctions, F: FnOnce( &Arc, &mut dyn WasmInstance, @@ -247,12 +247,11 @@ impl RuntimeCache { let time = std::time::Instant::now(); - let result = create_versioned_wasm_runtime( + let result = create_versioned_wasm_runtime::( &code, ext, wasm_method, heap_pages, - host_functions.into(), allow_missing_func_imports, self.max_runtime_instances, self.cache_path.as_deref(), @@ -288,14 +287,16 @@ impl RuntimeCache { } /// Create a wasm runtime with the given `code`. -pub fn create_wasm_runtime_with_code( +pub fn create_wasm_runtime_with_code( wasm_method: WasmExecutionMethod, heap_pages: u64, blob: RuntimeBlob, - host_functions: Vec<&'static dyn Function>, allow_missing_func_imports: bool, cache_path: Option<&Path>, -) -> Result, WasmError> { +) -> Result, WasmError> +where + H: HostFunctions, +{ match wasm_method { WasmExecutionMethod::Interpreted => { // Wasmi doesn't have any need in a cache directory. @@ -307,13 +308,13 @@ pub fn create_wasm_runtime_with_code( sc_executor_wasmi::create_runtime( blob, heap_pages, - host_functions, + H::host_functions(), allow_missing_func_imports, ) .map(|runtime| -> Arc { Arc::new(runtime) }) }, #[cfg(feature = "wasmtime")] - WasmExecutionMethod::Compiled => sc_executor_wasmtime::create_runtime( + WasmExecutionMethod::Compiled => sc_executor_wasmtime::create_runtime::( blob, sc_executor_wasmtime::Config { heap_pages, @@ -327,7 +328,6 @@ pub fn create_wasm_runtime_with_code( parallel_compilation: true, }, }, - host_functions, ) .map(|runtime| -> Arc { Arc::new(runtime) }), } @@ -392,16 +392,18 @@ pub fn read_embedded_version(blob: &RuntimeBlob) -> Result( code: &[u8], ext: &mut dyn Externalities, wasm_method: WasmExecutionMethod, heap_pages: u64, - host_functions: Vec<&'static dyn Function>, allow_missing_func_imports: bool, max_instances: usize, cache_path: Option<&Path>, -) -> Result { +) -> Result +where + H: HostFunctions, +{ // The incoming code may be actually compressed. We decompress it here and then work with // the uncompressed code from now on. let blob = sc_executor_common::runtime_blob::RuntimeBlob::uncompress_if_needed(&code)?; @@ -411,11 +413,10 @@ fn create_versioned_wasm_runtime( // runtime. let mut version: Option<_> = read_embedded_version(&blob)?; - let runtime = create_wasm_runtime_with_code( + let runtime = create_wasm_runtime_with_code::( wasm_method, heap_pages, blob, - host_functions, allow_missing_func_imports, cache_path, )?; diff --git a/substrate/client/executor/wasmi/Cargo.toml b/substrate/client/executor/wasmi/Cargo.toml index d50237edfe..9dc6374716 100644 --- a/substrate/client/executor/wasmi/Cargo.toml +++ b/substrate/client/executor/wasmi/Cargo.toml @@ -19,7 +19,7 @@ wasmi = "0.9.1" codec = { package = "parity-scale-codec", version = "2.0.0" } sc-executor-common = { version = "0.10.0-dev", path = "../common" } sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } -sp-wasm-interface = { version = "4.0.0", path = "../../../primitives/wasm-interface" } -sp-runtime-interface = { version = "4.0.0", path = "../../../primitives/runtime-interface" } +sp-wasm-interface = { version = "4.1.0-dev", path = "../../../primitives/wasm-interface" } +sp-runtime-interface = { version = "4.1.0-dev", path = "../../../primitives/runtime-interface" } sp-core = { version = "4.1.0-dev", path = "../../../primitives/core" } scoped-tls = "1.0" diff --git a/substrate/client/executor/wasmtime/Cargo.toml b/substrate/client/executor/wasmtime/Cargo.toml index 49e44766b9..06f668ef67 100644 --- a/substrate/client/executor/wasmtime/Cargo.toml +++ b/substrate/client/executor/wasmtime/Cargo.toml @@ -19,8 +19,8 @@ log = "0.4.8" parity-wasm = "0.42.0" codec = { package = "parity-scale-codec", version = "2.0.0" } sc-executor-common = { version = "0.10.0-dev", path = "../common" } -sp-wasm-interface = { version = "4.0.0", path = "../../../primitives/wasm-interface" } -sp-runtime-interface = { version = "4.0.0", path = "../../../primitives/runtime-interface" } +sp-wasm-interface = { version = "4.1.0-dev", path = "../../../primitives/wasm-interface", features = ["wasmtime"] } +sp-runtime-interface = { version = "4.1.0-dev", path = "../../../primitives/runtime-interface" } sp-core = { version = "4.1.0-dev", path = "../../../primitives/core" } sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } wasmtime = { version = "0.31.0", default-features = false, features = [ diff --git a/substrate/client/executor/wasmtime/src/host.rs b/substrate/client/executor/wasmtime/src/host.rs index 39ee9ced80..ab8b4cf8b7 100644 --- a/substrate/client/executor/wasmtime/src/host.rs +++ b/substrate/client/executor/wasmtime/src/host.rs @@ -62,11 +62,11 @@ impl HostState { /// A `HostContext` implements `FunctionContext` for making host calls from a Wasmtime /// runtime. The `HostContext` exists only for the lifetime of the call and borrows state from /// a longer-living `HostState`. -pub(crate) struct HostContext<'a, 'b> { - pub(crate) caller: &'a mut Caller<'b, StoreData>, +pub(crate) struct HostContext<'a> { + pub(crate) caller: Caller<'a, StoreData>, } -impl<'a, 'b> HostContext<'a, 'b> { +impl<'a> HostContext<'a> { fn host_state(&self) -> &HostState { self.caller .data() @@ -98,7 +98,7 @@ impl<'a, 'b> HostContext<'a, 'b> { } } -impl<'a, 'b> sp_wasm_interface::FunctionContext for HostContext<'a, 'b> { +impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> { fn read_memory_into( &self, address: Pointer, @@ -136,7 +136,7 @@ impl<'a, 'b> sp_wasm_interface::FunctionContext for HostContext<'a, 'b> { } } -impl<'a, 'b> Sandbox for HostContext<'a, 'b> { +impl<'a> Sandbox for HostContext<'a> { fn memory_get( &mut self, memory_id: MemoryId, @@ -320,12 +320,12 @@ impl<'a, 'b> Sandbox for HostContext<'a, 'b> { } } -struct SandboxContext<'a, 'b, 'c> { - host_context: &'a mut HostContext<'b, 'c>, +struct SandboxContext<'a, 'b> { + host_context: &'a mut HostContext<'b>, dispatch_thunk: Func, } -impl<'a, 'b, 'c> sandbox::SandboxContext for SandboxContext<'a, 'b, 'c> { +impl<'a, 'b> sandbox::SandboxContext for SandboxContext<'a, 'b> { fn invoke( &mut self, invoke_args_ptr: Pointer, diff --git a/substrate/client/executor/wasmtime/src/imports.rs b/substrate/client/executor/wasmtime/src/imports.rs index 57ce48f537..c18cf58ac7 100644 --- a/substrate/client/executor/wasmtime/src/imports.rs +++ b/substrate/client/executor/wasmtime/src/imports.rs @@ -19,14 +19,11 @@ use crate::{ host::HostContext, runtime::{Store, StoreData}, - util, }; use sc_executor_common::error::WasmError; -use sp_wasm_interface::{Function, ValueType}; -use std::{any::Any, convert::TryInto}; -use wasmtime::{ - Caller, Extern, ExternType, Func, FuncType, ImportType, Memory, MemoryType, Module, Trap, Val, -}; +use sp_wasm_interface::{FunctionContext, HostFunctions}; +use std::{collections::HashMap, convert::TryInto}; +use wasmtime::{Extern, ExternType, Func, FuncType, ImportType, Memory, MemoryType, Module, Trap}; pub struct Imports { /// Contains the index into `externs` where the memory import is stored if any. `None` if there @@ -37,16 +34,19 @@ pub struct Imports { /// Goes over all imports of a module and prepares a vector of `Extern`s that can be used for /// instantiation of the module. Returns an error if there are imports that cannot be satisfied. -pub(crate) fn resolve_imports( +pub(crate) fn resolve_imports( store: &mut Store, module: &Module, - host_functions: &[&'static dyn Function], heap_pages: u64, allow_missing_func_imports: bool, -) -> Result { +) -> Result +where + H: HostFunctions, +{ let mut externs = vec![]; let mut memory_import_index = None; - for import_ty in module.imports() { + let mut pending_func_imports = HashMap::new(); + for (index, import_ty) in module.imports().enumerate() { let name = import_name(&import_ty)?; if import_ty.module() != "env" { @@ -57,19 +57,89 @@ pub(crate) fn resolve_imports( ))) } - let resolved = match name { - "memory" => { - memory_import_index = Some(externs.len()); - resolve_memory_import(store, &import_ty, heap_pages)? + if name == "memory" { + memory_import_index = Some(index); + externs.push((index, resolve_memory_import(store, &import_ty, heap_pages)?)); + continue + } + + match import_ty.ty() { + ExternType::Func(func_ty) => { + pending_func_imports.insert(name.to_owned(), (index, import_ty, func_ty)); }, _ => - resolve_func_import(store, &import_ty, host_functions, allow_missing_func_imports)?, + return Err(WasmError::Other(format!( + "host doesn't provide any non function imports besides 'memory': {}:{}", + import_ty.module(), + name, + ))), }; - externs.push(resolved); } + + let mut registry = Registry { store, externs, pending_func_imports }; + + H::register_static(&mut registry)?; + let mut externs = registry.externs; + + if !registry.pending_func_imports.is_empty() { + if allow_missing_func_imports { + for (_, (index, import_ty, func_ty)) in registry.pending_func_imports { + externs.push(( + index, + MissingHostFuncHandler::new(&import_ty)?.into_extern(store, &func_ty), + )); + } + } else { + let mut names = Vec::new(); + for (name, (_, import_ty, _)) in registry.pending_func_imports { + names.push(format!("'{}:{}'", import_ty.module(), name)); + } + let names = names.join(", "); + return Err(WasmError::Other(format!( + "runtime requires function imports which are not present on the host: {}", + names + ))) + } + } + + externs.sort_unstable_by_key(|&(index, _)| index); + let externs = externs.into_iter().map(|(_, ext)| ext).collect(); + Ok(Imports { memory_import_index, externs }) } +struct Registry<'a, 'b> { + store: &'a mut Store, + externs: Vec<(usize, Extern)>, + pending_func_imports: HashMap, FuncType)>, +} + +impl<'a, 'b> sp_wasm_interface::HostFunctionRegistry for Registry<'a, 'b> { + type State = StoreData; + type Error = WasmError; + type FunctionContext = HostContext<'a>; + + fn with_function_context( + caller: wasmtime::Caller, + callback: impl FnOnce(&mut dyn FunctionContext) -> R, + ) -> R { + callback(&mut HostContext { caller }) + } + + fn register_static( + &mut self, + fn_name: &str, + func: impl wasmtime::IntoFunc, + ) -> Result<(), Self::Error> { + if let Some((index, _, _)) = self.pending_func_imports.remove(fn_name) { + let func = Func::wrap(&mut *self.store, func); + self.externs.push((index, Extern::Func(func))); + } + + Ok(()) + } +} + /// When the module linking proposal is supported the import's name can be `None`. /// Because we are not using this proposal we could safely unwrap the name. /// However, we opt for an error in order to avoid panics at all costs. @@ -139,115 +209,6 @@ fn resolve_memory_import( Ok(Extern::Memory(memory)) } -fn resolve_func_import( - store: &mut Store, - import_ty: &ImportType, - host_functions: &[&'static dyn Function], - allow_missing_func_imports: bool, -) -> Result { - let name = import_name(&import_ty)?; - - let func_ty = match import_ty.ty() { - ExternType::Func(func_ty) => func_ty, - _ => - return Err(WasmError::Other(format!( - "host doesn't provide any non function imports besides 'memory': {}:{}", - import_ty.module(), - name, - ))), - }; - - let host_func = match host_functions.iter().find(|host_func| host_func.name() == name) { - Some(host_func) => host_func, - None if allow_missing_func_imports => - return Ok(MissingHostFuncHandler::new(import_ty)?.into_extern(store, &func_ty)), - None => - return Err(WasmError::Other(format!( - "host doesn't provide such function: {}:{}", - import_ty.module(), - name, - ))), - }; - if &func_ty != &wasmtime_func_sig(*host_func) { - return Err(WasmError::Other(format!( - "signature mismatch for: {}:{}", - import_ty.module(), - name, - ))) - } - - Ok(HostFuncHandler::new(*host_func).into_extern(store)) -} - -/// This structure implements `Callable` and acts as a bridge between wasmtime and -/// substrate host functions. -struct HostFuncHandler { - host_func: &'static dyn Function, -} - -fn call_static<'a>( - static_func: &'static dyn Function, - wasmtime_params: &[Val], - wasmtime_results: &mut [Val], - mut caller: Caller<'a, StoreData>, -) -> Result<(), wasmtime::Trap> { - let unwind_result = { - let mut host_ctx = HostContext { caller: &mut caller }; - - // `from_wasmtime_val` panics if it encounters a value that doesn't fit into the values - // available in substrate. - // - // This, however, cannot happen since the signature of this function is created from - // a `dyn Function` signature of which cannot have a non substrate value by definition. - let mut params = wasmtime_params.iter().cloned().map(util::from_wasmtime_val); - - std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - static_func.execute(&mut host_ctx, &mut params) - })) - }; - - let execution_result = match unwind_result { - Ok(execution_result) => execution_result, - Err(err) => return Err(Trap::new(stringify_panic_payload(err))), - }; - - match execution_result { - Ok(Some(ret_val)) => { - debug_assert!( - wasmtime_results.len() == 1, - "wasmtime function signature, therefore the number of results, should always \ - correspond to the number of results returned by the host function", - ); - wasmtime_results[0] = util::into_wasmtime_val(ret_val); - Ok(()) - }, - Ok(None) => { - debug_assert!( - wasmtime_results.len() == 0, - "wasmtime function signature, therefore the number of results, should always \ - correspond to the number of results returned by the host function", - ); - Ok(()) - }, - Err(msg) => Err(Trap::new(msg)), - } -} - -impl HostFuncHandler { - fn new(host_func: &'static dyn Function) -> Self { - Self { host_func } - } - - fn into_extern(self, store: &mut Store) -> Extern { - let host_func = self.host_func; - let func_ty = wasmtime_func_sig(self.host_func); - let func = Func::new(store, func_ty, move |caller, params, result| { - call_static(host_func, params, result, caller) - }); - Extern::Func(func) - } -} - /// A `Callable` handler for missing functions. struct MissingHostFuncHandler { module: String, @@ -270,33 +231,3 @@ impl MissingHostFuncHandler { Extern::Func(func) } } - -fn wasmtime_func_sig(func: &dyn Function) -> wasmtime::FuncType { - let signature = func.signature(); - let params = signature.args.iter().cloned().map(into_wasmtime_val_type); - - let results = signature.return_value.iter().cloned().map(into_wasmtime_val_type); - - wasmtime::FuncType::new(params, results) -} - -fn into_wasmtime_val_type(val_ty: ValueType) -> wasmtime::ValType { - match val_ty { - ValueType::I32 => wasmtime::ValType::I32, - ValueType::I64 => wasmtime::ValType::I64, - ValueType::F32 => wasmtime::ValType::F32, - ValueType::F64 => wasmtime::ValType::F64, - } -} - -/// Attempt to convert a opaque panic payload to a string. -fn stringify_panic_payload(payload: Box) -> String { - match payload.downcast::<&'static str>() { - Ok(msg) => msg.to_string(), - Err(payload) => match payload.downcast::() { - Ok(msg) => *msg, - // At least we tried... - Err(_) => "Box".to_string(), - }, - } -} diff --git a/substrate/client/executor/wasmtime/src/instance_wrapper.rs b/substrate/client/executor/wasmtime/src/instance_wrapper.rs index e9b18d8c2b..58f96eb263 100644 --- a/substrate/client/executor/wasmtime/src/instance_wrapper.rs +++ b/substrate/client/executor/wasmtime/src/instance_wrapper.rs @@ -24,7 +24,7 @@ use sc_executor_common::{ error::{Error, Result}, wasm_runtime::InvokeMethod, }; -use sp_wasm_interface::{Function, Pointer, Value, WordSize}; +use sp_wasm_interface::{HostFunctions, Pointer, Value, WordSize}; use wasmtime::{ AsContext, AsContextMut, Extern, Func, Global, Instance, Memory, Module, Table, Val, }; @@ -138,13 +138,15 @@ fn extern_func(extern_: &Extern) -> Option<&Func> { impl InstanceWrapper { /// Create a new instance wrapper from the given wasm module. - pub fn new( + pub fn new( module: &Module, - host_functions: &[&'static dyn Function], heap_pages: u64, allow_missing_func_imports: bool, max_memory_size: Option, - ) -> Result { + ) -> Result + where + H: HostFunctions, + { let limits = if let Some(max_memory_size) = max_memory_size { wasmtime::StoreLimitsBuilder::new().memory_size(max_memory_size).build() } else { @@ -161,10 +163,9 @@ impl InstanceWrapper { // Scan all imports, find the matching host functions, and create stubs that adapt arguments // and results. - let imports = crate::imports::resolve_imports( + let imports = crate::imports::resolve_imports::( &mut store, module, - host_functions, heap_pages, allow_missing_func_imports, )?; diff --git a/substrate/client/executor/wasmtime/src/runtime.rs b/substrate/client/executor/wasmtime/src/runtime.rs index 606401132e..b870405f92 100644 --- a/substrate/client/executor/wasmtime/src/runtime.rs +++ b/substrate/client/executor/wasmtime/src/runtime.rs @@ -23,6 +23,7 @@ use crate::{ instance_wrapper::{EntryPoint, InstanceWrapper}, util, }; +use core::marker::PhantomData; use sc_allocator::FreeingBumpHeapAllocator; use sc_executor_common::{ @@ -33,7 +34,7 @@ use sc_executor_common::{ wasm_runtime::{InvokeMethod, WasmInstance, WasmModule}, }; use sp_runtime_interface::unpack_ptr_and_len; -use sp_wasm_interface::{Function, Pointer, Value, WordSize}; +use sp_wasm_interface::{HostFunctions, Pointer, Value, WordSize}; use std::{ path::{Path, PathBuf}, sync::{ @@ -79,29 +80,31 @@ impl StoreData { pub(crate) type Store = wasmtime::Store; -enum Strategy { +enum Strategy { FastInstanceReuse { instance_wrapper: InstanceWrapper, globals_snapshot: GlobalsSnapshot, data_segments_snapshot: Arc, heap_base: u32, }, - RecreateInstance(InstanceCreator), + RecreateInstance(InstanceCreator), } -struct InstanceCreator { +struct InstanceCreator { module: Arc, - host_functions: Vec<&'static dyn Function>, heap_pages: u64, allow_missing_func_imports: bool, max_memory_size: Option, + phantom: PhantomData, } -impl InstanceCreator { +impl InstanceCreator +where + H: HostFunctions, +{ fn instantiate(&mut self) -> Result { - InstanceWrapper::new( + InstanceWrapper::new::( &*self.module, - &self.host_functions, self.heap_pages, self.allow_missing_func_imports, self.max_memory_size, @@ -141,19 +144,21 @@ struct InstanceSnapshotData { /// A `WasmModule` implementation using wasmtime to compile the runtime module to machine code /// and execute the compiled code. -pub struct WasmtimeRuntime { +pub struct WasmtimeRuntime { module: Arc, snapshot_data: Option, config: Config, - host_functions: Vec<&'static dyn Function>, + phantom: PhantomData, } -impl WasmModule for WasmtimeRuntime { +impl WasmModule for WasmtimeRuntime +where + H: HostFunctions, +{ fn new_instance(&self) -> Result> { let strategy = if let Some(ref snapshot_data) = self.snapshot_data { - let mut instance_wrapper = InstanceWrapper::new( + let mut instance_wrapper = InstanceWrapper::new::( &self.module, - &self.host_functions, self.config.heap_pages, self.config.allow_missing_func_imports, self.config.max_memory_size, @@ -169,19 +174,19 @@ impl WasmModule for WasmtimeRuntime { &mut InstanceGlobals { instance: &mut instance_wrapper }, ); - Strategy::FastInstanceReuse { + Strategy::::FastInstanceReuse { instance_wrapper, globals_snapshot, data_segments_snapshot: snapshot_data.data_segments_snapshot.clone(), heap_base, } } else { - Strategy::RecreateInstance(InstanceCreator { + Strategy::::RecreateInstance(InstanceCreator { module: self.module.clone(), - host_functions: self.host_functions.clone(), heap_pages: self.config.heap_pages, allow_missing_func_imports: self.config.allow_missing_func_imports, max_memory_size: self.config.max_memory_size, + phantom: PhantomData, }) }; @@ -191,11 +196,14 @@ impl WasmModule for WasmtimeRuntime { /// A `WasmInstance` implementation that reuses compiled module and spawns instances /// to execute the compiled code. -pub struct WasmtimeInstance { - strategy: Strategy, +pub struct WasmtimeInstance { + strategy: Strategy, } -impl WasmInstance for WasmtimeInstance { +impl WasmInstance for WasmtimeInstance +where + H: HostFunctions, +{ fn call(&mut self, method: InvokeMethod, data: &[u8]) -> Result> { match &mut self.strategy { Strategy::FastInstanceReuse { @@ -483,13 +491,18 @@ enum CodeSupplyMode<'a> { /// Create a new `WasmtimeRuntime` given the code. This function performs translation from Wasm to /// machine code, which can be computationally heavy. -pub fn create_runtime( +/// +/// The `H` generic parameter is used to statically pass a set of host functions which are exposed +/// to the runtime. +pub fn create_runtime( blob: RuntimeBlob, config: Config, - host_functions: Vec<&'static dyn Function>, -) -> std::result::Result { +) -> std::result::Result, WasmError> +where + H: HostFunctions, +{ // SAFETY: this is safe because it doesn't use `CodeSupplyMode::Artifact`. - unsafe { do_create_runtime(CodeSupplyMode::Verbatim { blob }, config, host_functions) } + unsafe { do_create_runtime::(CodeSupplyMode::Verbatim { blob }, config) } } /// The same as [`create_runtime`] but takes a precompiled artifact, which makes this function @@ -503,23 +516,27 @@ pub fn create_runtime( /// /// It is ok though if the `compiled_artifact` was created by code of another version or with /// different configuration flags. In such case the caller will receive an `Err` deterministically. -pub unsafe fn create_runtime_from_artifact( +pub unsafe fn create_runtime_from_artifact( compiled_artifact: &[u8], config: Config, - host_functions: Vec<&'static dyn Function>, -) -> std::result::Result { - do_create_runtime(CodeSupplyMode::Artifact { compiled_artifact }, config, host_functions) +) -> std::result::Result, WasmError> +where + H: HostFunctions, +{ + do_create_runtime::(CodeSupplyMode::Artifact { compiled_artifact }, config) } /// # Safety /// /// This is only unsafe if called with [`CodeSupplyMode::Artifact`]. See /// [`create_runtime_from_artifact`] to get more details. -unsafe fn do_create_runtime( +unsafe fn do_create_runtime( code_supply_mode: CodeSupplyMode<'_>, config: Config, - host_functions: Vec<&'static dyn Function>, -) -> std::result::Result { +) -> std::result::Result, WasmError> +where + H: HostFunctions, +{ // Create the engine, store and finally the module from the given code. let mut wasmtime_config = common_config(&config.semantics)?; if let Some(ref cache_path) = config.cache_path { @@ -566,7 +583,7 @@ unsafe fn do_create_runtime( }, }; - Ok(WasmtimeRuntime { module: Arc::new(module), snapshot_data, config, host_functions }) + Ok(WasmtimeRuntime { module: Arc::new(module), snapshot_data, config, phantom: PhantomData }) } fn instrument( diff --git a/substrate/client/executor/wasmtime/src/tests.rs b/substrate/client/executor/wasmtime/src/tests.rs index c34cbfad11..31fb5d3da1 100644 --- a/substrate/client/executor/wasmtime/src/tests.rs +++ b/substrate/client/executor/wasmtime/src/tests.rs @@ -78,7 +78,7 @@ impl RuntimeBuilder { .expect("failed to create a runtime blob out of test runtime") }; - let rt = crate::create_runtime( + let rt = crate::create_runtime::( blob, crate::Config { heap_pages: self.heap_pages, @@ -98,10 +98,6 @@ impl RuntimeBuilder { parallel_compilation: true, }, }, - { - use sp_wasm_interface::HostFunctions as _; - HostFunctions::host_functions() - }, ) .expect("cannot create runtime"); @@ -316,9 +312,7 @@ fn test_max_memory_pages() { #[cfg_attr(build_type = "debug", ignore)] #[test] fn test_instances_without_reuse_are_not_leaked() { - use sp_wasm_interface::HostFunctions; - - let runtime = crate::create_runtime( + let runtime = crate::create_runtime::( RuntimeBlob::uncompress_if_needed(&wasm_binary_unwrap()[..]).unwrap(), crate::Config { heap_pages: 2048, @@ -332,7 +326,6 @@ fn test_instances_without_reuse_are_not_leaked() { parallel_compilation: true, }, }, - sp_io::SubstrateHostFunctions::host_functions(), ) .unwrap(); diff --git a/substrate/frame/benchmarking/Cargo.toml b/substrate/frame/benchmarking/Cargo.toml index d907cf7194..1e89c39be1 100644 --- a/substrate/frame/benchmarking/Cargo.toml +++ b/substrate/frame/benchmarking/Cargo.toml @@ -18,7 +18,7 @@ paste = "1.0" codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } scale-info = { version = "1.0", default-features = false, features = ["derive"] } sp-api = { version = "4.0.0-dev", path = "../../primitives/api", default-features = false } -sp-runtime-interface = { version = "4.0.0", path = "../../primitives/runtime-interface", default-features = false } +sp-runtime-interface = { version = "4.1.0-dev", path = "../../primitives/runtime-interface", default-features = false } sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime", default-features = false } sp-std = { version = "4.0.0", path = "../../primitives/std", default-features = false } sp-io = { version = "4.0.0-dev", path = "../../primitives/io", default-features = false } diff --git a/substrate/primitives/core/Cargo.toml b/substrate/primitives/core/Cargo.toml index a078cc09aa..330dd57e81 100644 --- a/substrate/primitives/core/Cargo.toml +++ b/substrate/primitives/core/Cargo.toml @@ -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" } diff --git a/substrate/primitives/io/Cargo.toml b/substrate/primitives/io/Cargo.toml index 4485a8a7f4..4364202618 100644 --- a/substrate/primitives/io/Cargo.toml +++ b/substrate/primitives/io/Cargo.toml @@ -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" } diff --git a/substrate/primitives/runtime-interface/Cargo.toml b/substrate/primitives/runtime-interface/Cargo.toml index 6b120ab50c..9ff17b1dff 100644 --- a/substrate/primitives/runtime-interface/Cargo.toml +++ b/substrate/primitives/runtime-interface/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-runtime-interface" -version = "4.0.0" +version = "4.1.0-dev" authors = ["Parity Technologies "] 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" } diff --git a/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs b/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs index 75498c09c1..c79886d45b 100644 --- a/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs +++ b/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs @@ -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 { 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::>>()?; + 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(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 { +) -> 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::>>()?; - let ffi_to_host_values = generate_ffi_to_host_value(&method.sig).collect::>>()?; - 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, - ) -> std::result::Result, 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, + ) -> std::result::Result, 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, #(#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> + '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> + '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 { match pat { @@ -354,49 +424,3 @@ fn generate_ffi_value_var_name(pat: &Pat) -> Result { _ => 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 { - 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) - } - }, - } -} diff --git a/substrate/primitives/runtime-interface/src/impls.rs b/substrate/primitives/runtime-interface/src/impls.rs index 0b9cdc26f4..35badd27fa 100644 --- a/substrate/primitives/runtime-interface/src/impls.rs +++ b/substrate/primitives/runtime-interface/src/impls.rs @@ -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 { + fn into_ffi_value(&self) -> WrappedFFIValue { 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 { + fn from_ffi_value(_: &mut dyn FunctionContext, arg: u32) -> Result { Ok(arg == 1) } } #[cfg(feature = "std")] impl IntoFFIValue for bool { - fn into_ffi_value(self, _: &mut dyn FunctionContext) -> Result { + fn into_ffi_value(self, _: &mut dyn FunctionContext) -> Result { Ok(if self { 1 } else { 0 }) } } diff --git a/substrate/primitives/runtime-interface/src/lib.rs b/substrate/primitives/runtime-interface/src/lib.rs index 27c4422ed9..e8e29329a9 100644 --- a/substrate/primitives/runtime-interface/src/lib.rs +++ b/substrate/primitives/runtime-interface/src/lib.rs @@ -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` | v.len() 32bit << 32 | v.as_ptr() 32bit | //! | `&[u8]` | `u64` | v.len() 32bit << 32 | v.as_ptr() 32bit | //! | `Vec` | `u64` | v.len() 32bit << 32 | v.as_ptr() 32bit | @@ -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; } diff --git a/substrate/primitives/runtime-interface/src/pass_by.rs b/substrate/primitives/runtime-interface/src/pass_by.rs index 7324e93638..02cbe502f0 100644 --- a/substrate/primitives/runtime-interface/src/pass_by.rs +++ b/substrate/primitives/runtime-interface/src/pass_by.rs @@ -403,11 +403,11 @@ pub struct Enum + TryFrom>(PhantomData); #[cfg(feature = "std")] impl + TryFrom> PassByImpl for Enum { fn into_ffi_value(instance: T, _: &mut dyn FunctionContext) -> Result { - Ok(instance.into()) + Ok(instance.into() as u32) } fn from_ffi_value(_: &mut dyn FunctionContext, arg: Self::FFIType) -> Result { - 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 + TryFrom> PassByImpl for Enum { fn into_ffi_value(instance: &T) -> WrappedFFIValue { 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 + TryFrom> RIType for Enum { - type FFIType = u8; + type FFIType = u32; } diff --git a/substrate/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml b/substrate/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml index 02fb75d108..032de1d215 100644 --- a/substrate/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml +++ b/substrate/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml @@ -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" } diff --git a/substrate/primitives/runtime-interface/test-wasm/Cargo.toml b/substrate/primitives/runtime-interface/test-wasm/Cargo.toml index 24fdb88429..80ad44569e 100644 --- a/substrate/primitives/runtime-interface/test-wasm/Cargo.toml +++ b/substrate/primitives/runtime-interface/test-wasm/Cargo.toml @@ -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" } diff --git a/substrate/primitives/runtime-interface/test/Cargo.toml b/substrate/primitives/runtime-interface/test/Cargo.toml index 5e4483fb38..b197f2ab8b 100644 --- a/substrate/primitives/runtime-interface/test/Cargo.toml +++ b/substrate/primitives/runtime-interface/test/Cargo.toml @@ -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" } diff --git a/substrate/primitives/runtime-interface/test/src/lib.rs b/substrate/primitives/runtime-interface/test/src/lib.rs index f64b743364..bf82f3b4b5 100644 --- a/substrate/primitives/runtime-interface/test/src/lib.rs +++ b/substrate/primitives/runtime-interface/test/src/lib.rs @@ -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( ) -> Result { 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, + >::new(sc_executor::WasmExecutionMethod::Interpreted, Some(8), 8, None, 2); + executor .uncached_call( RuntimeBlob::uncompress_if_needed(binary).expect("Failed to parse binary"), diff --git a/substrate/primitives/sandbox/Cargo.toml b/substrate/primitives/sandbox/Cargo.toml index a94ba0f375..26a81caea8 100644 --- a/substrate/primitives/sandbox/Cargo.toml +++ b/substrate/primitives/sandbox/Cargo.toml @@ -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 } diff --git a/substrate/primitives/tasks/Cargo.toml b/substrate/primitives/tasks/Cargo.toml index c154d1c92d..c57eb50edd 100644 --- a/substrate/primitives/tasks/Cargo.toml +++ b/substrate/primitives/tasks/Cargo.toml @@ -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] diff --git a/substrate/primitives/wasm-interface/Cargo.toml b/substrate/primitives/wasm-interface/Cargo.toml index da0d0161c0..cffc05e4d8 100644 --- a/substrate/primitives/wasm-interface/Cargo.toml +++ b/substrate/primitives/wasm-interface/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-wasm-interface" -version = "4.0.0" +version = "4.1.0-dev" authors = ["Parity Technologies "] 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" ] diff --git a/substrate/primitives/wasm-interface/src/lib.rs b/substrate/primitives/wasm-interface/src/lib.rs index e1903ef425..44afaf82ed 100644 --- a/substrate/primitives/wasm-interface/src/lib.rs +++ b/substrate/primitives/wasm-interface/src/lib.rs @@ -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 = result::Result; @@ -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>; } +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 { +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)] @@ -353,8 +414,146 @@ impl HostFunctions for Tuple { 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(()) + } + } +} + +/// 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. diff --git a/substrate/test-utils/client/src/lib.rs b/substrate/test-utils/client/src/lib.rs index 96f3c98983..86231bb34c 100644 --- a/substrate/test-utils/client/src/lib.rs +++ b/substrate/test-utils/client/src/lib.rs @@ -262,7 +262,8 @@ impl client::LocalCallExecutor>, Backend, G, - > + > where + D: sc_executor::NativeExecutionDispatch, { /// Build the test client with the given native executor. pub fn build_with_native_executor( diff --git a/substrate/test-utils/runtime/Cargo.toml b/substrate/test-utils/runtime/Cargo.toml index 3be0e40b0b..ae4be0d13a 100644 --- a/substrate/test-utils/runtime/Cargo.toml +++ b/substrate/test-utils/runtime/Cargo.toml @@ -25,7 +25,7 @@ memory-db = { version = "0.27.0", default-features = false } sp-offchain = { version = "4.0.0-dev", default-features = false, path = "../../primitives/offchain" } sp-core = { version = "4.1.0-dev", default-features = false, path = "../../primitives/core" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-runtime-interface = { version = "4.0.0", default-features = false, path = "../../primitives/runtime-interface" } +sp-runtime-interface = { version = "4.1.0-dev", default-features = false, path = "../../primitives/runtime-interface" } sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../frame/support" } sp-version = { version = "4.0.0-dev", default-features = false, path = "../../primitives/version" } diff --git a/substrate/test-utils/test-runner/Cargo.toml b/substrate/test-utils/test-runner/Cargo.toml index 7066fc778c..eb565fd7b5 100644 --- a/substrate/test-utils/test-runner/Cargo.toml +++ b/substrate/test-utils/test-runner/Cargo.toml @@ -52,3 +52,8 @@ tokio = { version = "1.13", features = ["signal"] } # Calling RPC jsonrpc-core = "18.0" num-traits = "0.2.14" + +[features] +default = ["std"] +# This is here so that we can use the `runtime_interface` procedural macro +std = [] diff --git a/substrate/test-utils/test-runner/src/host_functions.rs b/substrate/test-utils/test-runner/src/host_functions.rs index 731abfbb9d..6d9fa3534b 100644 --- a/substrate/test-utils/test-runner/src/host_functions.rs +++ b/substrate/test-utils/test-runner/src/host_functions.rs @@ -16,75 +16,38 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -/// Use this to override host functions. -/// eg -/// ```rust -/// use test_runner::override_host_functions; -/// pub struct SignatureVerificationOverride; -/// -/// impl sp_wasm_interface::HostFunctions for SignatureVerificationOverride { -/// fn host_functions() -> Vec<&'static dyn sp_wasm_interface::Function> { -/// override_host_functions!( -/// "ext_crypto_ecdsa_verify_version_1", EcdsaVerify, -/// ) -/// } -/// } -/// ``` -#[macro_export] -macro_rules! override_host_functions { - ($($fn_name:expr, $name:ident,)*) => {{ - let mut host_functions = vec![]; - $( - struct $name; - impl sp_wasm_interface::Function for $name { - fn name(&self) -> &str { - &$fn_name - } +use sp_core::{ecdsa, ed25519, sr25519}; +use sp_runtime_interface::runtime_interface; - fn signature(&self) -> sp_wasm_interface::Signature { - sp_wasm_interface::Signature { - args: std::borrow::Cow::Owned(vec![ - sp_wasm_interface::ValueType::I32, - sp_wasm_interface::ValueType::I64, - sp_wasm_interface::ValueType::I32, - ]), - return_value: Some(sp_wasm_interface::ValueType::I32), - } - } +#[runtime_interface] +trait Crypto { + fn ecdsa_verify(_sig: &ecdsa::Signature, _msg: &[u8], _pub_key: &ecdsa::Public) -> bool { + true + } - fn execute( - &self, - context: &mut dyn sp_wasm_interface::FunctionContext, - _args: &mut dyn Iterator, - ) -> Result, String> { - ::into_ffi_value(true, context) - .map(sp_wasm_interface::IntoValue::into_value) - .map(Some) - } - } - host_functions.push(&$name as &'static dyn sp_wasm_interface::Function); - )* - host_functions - }}; + #[version(2)] + fn ecdsa_verify(_sig: &ecdsa::Signature, _msg: &[u8], _pub_key: &ecdsa::Public) -> bool { + true + } + + fn ed25519_verify(_sig: &ed25519::Signature, _msg: &[u8], _pub_key: &ed25519::Public) -> bool { + true + } + + fn sr25519_verify(_sig: &sr25519::Signature, _msg: &[u8], _pub_key: &sr25519::Public) -> bool { + true + } + + #[version(2)] + fn sr25519_verify(_sig: &sr25519::Signature, _msg: &[u8], _pub_key: &sr25519::Public) -> bool { + true + } } /// Provides host functions that overrides runtime signature verification /// to always return true. -pub struct SignatureVerificationOverride; +pub type SignatureVerificationOverride = crypto::HostFunctions; -impl sp_wasm_interface::HostFunctions for SignatureVerificationOverride { - fn host_functions() -> Vec<&'static dyn sp_wasm_interface::Function> { - override_host_functions!( - "ext_crypto_ecdsa_verify_version_1", - EcdsaVerify, - "ext_crypto_ecdsa_verify_version_2", - EcdsaVerifyV2, - "ext_crypto_ed25519_verify_version_1", - Ed25519Verify, - "ext_crypto_sr25519_verify_version_1", - Sr25519Verify, - "ext_crypto_sr25519_verify_version_2", - Sr25519VerifyV2, - ) - } -} +// This is here to get rid of the warnings. +#[allow(unused_imports, dead_code)] +use self::crypto::{ecdsa_verify, ed25519_verify, sr25519_verify};