diff --git a/substrate/client/executor/src/integration_tests/mod.rs b/substrate/client/executor/src/integration_tests/mod.rs index 1f14678c7a..b28e3ca243 100644 --- a/substrate/client/executor/src/integration_tests/mod.rs +++ b/substrate/client/executor/src/integration_tests/mod.rs @@ -75,6 +75,7 @@ fn call_in_wasm( Some(1024), HostFunctions::host_functions(), 8, + None, ); executor.call_in_wasm( &wasm_binary_unwrap()[..], @@ -536,6 +537,7 @@ fn should_trap_when_heap_exhausted(wasm_method: WasmExecutionMethod) { Some(17), // `17` is the initial number of pages compiled into the binary. HostFunctions::host_functions(), 8, + None, ); let err = executor.call_in_wasm( @@ -558,6 +560,7 @@ fn returns_mutable_static(wasm_method: WasmExecutionMethod) { &wasm_binary_unwrap()[..], HostFunctions::host_functions(), true, + None, ).expect("Creates runtime"); let instance = runtime.new_instance().unwrap(); @@ -591,6 +594,7 @@ fn restoration_of_globals(wasm_method: WasmExecutionMethod) { &wasm_binary_unwrap()[..], HostFunctions::host_functions(), true, + None, ).expect("Creates runtime"); let instance = runtime.new_instance().unwrap(); @@ -611,6 +615,7 @@ fn heap_is_reset_between_calls(wasm_method: WasmExecutionMethod) { &wasm_binary_unwrap()[..], HostFunctions::host_functions(), true, + None, ).expect("Creates runtime"); let instance = runtime.new_instance().unwrap(); @@ -634,6 +639,7 @@ fn parallel_execution(wasm_method: WasmExecutionMethod) { Some(1024), HostFunctions::host_functions(), 8, + None, )); let code_hash = blake2_256(wasm_binary_unwrap()).to_vec(); let threads: Vec<_> = (0..8).map(|_| diff --git a/substrate/client/executor/src/lib.rs b/substrate/client/executor/src/lib.rs index ccb7aa1b44..c30015a86b 100644 --- a/substrate/client/executor/src/lib.rs +++ b/substrate/client/executor/src/lib.rs @@ -80,6 +80,7 @@ mod tests { Some(8), sp_io::SubstrateHostFunctions::host_functions(), 8, + None, ); let res = executor.call_in_wasm( &wasm_binary_unwrap()[..], diff --git a/substrate/client/executor/src/native_executor.rs b/substrate/client/executor/src/native_executor.rs index 766dada331..cdfe349eda 100644 --- a/substrate/client/executor/src/native_executor.rs +++ b/substrate/client/executor/src/native_executor.rs @@ -26,6 +26,7 @@ use std::{ panic::{UnwindSafe, AssertUnwindSafe}, result, sync::{Arc, atomic::{AtomicU64, Ordering}, mpsc}, + path::PathBuf, }; use sp_version::{NativeVersion, RuntimeVersion}; @@ -102,6 +103,9 @@ pub struct WasmExecutor { cache: Arc, /// The size of the instances cache. max_runtime_instances: usize, + /// The path to a directory which the executor can leverage for a file cache, e.g. put there + /// compiled artifacts. + cache_path: Option, } impl WasmExecutor { @@ -112,19 +116,30 @@ impl WasmExecutor { /// `method` - Method used to execute Wasm code. /// /// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution. - /// Defaults to `DEFAULT_HEAP_PAGES` if `None` is provided. + /// Defaults to `DEFAULT_HEAP_PAGES` if `None` is provided. + /// + /// `host_functions` - The set of host functions to be available for import provided by this + /// executor. + /// + /// `max_runtime_instances` - The number of runtime instances to keep in memory ready for reuse. + /// + /// `cache_path` - A path to a directory where the executor can place its files for purposes of + /// caching. This may be important in cases when there are many different modules with the + /// compiled execution method is used. pub fn new( method: WasmExecutionMethod, default_heap_pages: Option, host_functions: Vec<&'static dyn Function>, max_runtime_instances: usize, + cache_path: Option, ) -> Self { 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: Arc::new(RuntimeCache::new(max_runtime_instances, cache_path.clone())), max_runtime_instances, + cache_path, } } @@ -210,6 +225,7 @@ impl sp_core::traits::CallInWasm for WasmExecutor { &wasm_code, self.host_functions.to_vec(), allow_missing_host_functions, + self.cache_path.as_deref(), ) .map_err(|e| format!("Failed to create module: {:?}", e))?; @@ -267,6 +283,7 @@ impl NativeExecutor { default_heap_pages, host_functions, max_runtime_instances, + None, ); NativeExecutor { diff --git a/substrate/client/executor/src/wasm_runtime.rs b/substrate/client/executor/src/wasm_runtime.rs index a7d8b0ce23..4772471049 100644 --- a/substrate/client/executor/src/wasm_runtime.rs +++ b/substrate/client/executor/src/wasm_runtime.rs @@ -28,6 +28,7 @@ use codec::Decode; use sp_core::traits::{Externalities, RuntimeCode, FetchRuntimeCode}; use sp_version::RuntimeVersion; use std::panic::AssertUnwindSafe; +use std::path::{Path, PathBuf}; use sc_executor_common::wasm_runtime::{WasmModule, WasmInstance}; use sp_wasm_interface::Function; @@ -152,14 +153,22 @@ pub struct RuntimeCache { runtimes: Mutex<[Option>; MAX_RUNTIMES]>, /// The size of the instances cache for each runtime. max_runtime_instances: usize, + cache_path: Option, } impl RuntimeCache { /// Creates a new instance of a runtimes cache. - pub fn new(max_runtime_instances: usize) -> RuntimeCache { + /// + /// `max_runtime_instances` specifies the number of runtime instances preserved in an in-memory + /// cache. + /// + /// `cache_path` allows to specify an optional directory where the executor can store files + /// for caching. + pub fn new(max_runtime_instances: usize, cache_path: Option) -> RuntimeCache { RuntimeCache { runtimes: Default::default(), max_runtime_instances, + cache_path, } } @@ -235,6 +244,7 @@ impl RuntimeCache { host_functions.into(), allow_missing_func_imports, self.max_runtime_instances, + self.cache_path.as_deref(), ); if let Err(ref err) = result { log::warn!(target: "wasm-runtime", "Cannot create a runtime: {:?}", err); @@ -271,22 +281,32 @@ pub fn create_wasm_runtime_with_code( code: &[u8], host_functions: Vec<&'static dyn Function>, allow_missing_func_imports: bool, + cache_path: Option<&Path>, ) -> Result, WasmError> { match wasm_method { - WasmExecutionMethod::Interpreted => + WasmExecutionMethod::Interpreted => { + // Wasmi doesn't have any need in a cache directory. + // + // We drop the cache_path here to silence warnings that cache_path is not used if compiling + // without the `wasmtime` flag. + drop(cache_path); + sc_executor_wasmi::create_runtime( code, heap_pages, host_functions, - allow_missing_func_imports - ).map(|runtime| -> Arc { Arc::new(runtime) }), + allow_missing_func_imports, + ) + .map(|runtime| -> Arc { Arc::new(runtime) }) + } #[cfg(feature = "wasmtime")] WasmExecutionMethod::Compiled => sc_executor_wasmtime::create_runtime( code, heap_pages, host_functions, - allow_missing_func_imports + allow_missing_func_imports, + cache_path, ).map(|runtime| -> Arc { Arc::new(runtime) }), } } @@ -319,6 +339,7 @@ fn create_versioned_wasm_runtime( host_functions: Vec<&'static dyn Function>, allow_missing_func_imports: bool, max_instances: usize, + cache_path: Option<&Path>, ) -> Result { #[cfg(not(target_os = "unknown"))] let time = std::time::Instant::now(); @@ -328,6 +349,7 @@ fn create_versioned_wasm_runtime( &code, host_functions, allow_missing_func_imports, + cache_path, )?; // Call to determine runtime version. diff --git a/substrate/client/executor/wasmtime/src/runtime.rs b/substrate/client/executor/wasmtime/src/runtime.rs index a17a034918..64ad5a1f4e 100644 --- a/substrate/client/executor/wasmtime/src/runtime.rs +++ b/substrate/client/executor/wasmtime/src/runtime.rs @@ -25,6 +25,7 @@ use crate::state_holder; use std::rc::Rc; use std::sync::Arc; +use std::path::Path; use sc_executor_common::{ error::{Result, WasmError}, wasm_runtime::{WasmModule, WasmInstance, InvokeMethod}, @@ -119,20 +120,68 @@ impl WasmInstance for WasmtimeInstance { } } +/// Prepare a directory structure and a config file to enable wasmtime caching. +/// +/// In case of an error the caching will not be enabled. +fn setup_wasmtime_caching( + cache_path: &Path, + config: &mut Config, +) -> std::result::Result<(), String> { + use std::fs; + + let wasmtime_cache_root = cache_path.join("wasmtime"); + fs::create_dir_all(&wasmtime_cache_root) + .map_err(|err| format!("cannot create the dirs to cache: {:?}", err))?; + + // Canonicalize the path after creating the directories. + let wasmtime_cache_root = wasmtime_cache_root + .canonicalize() + .map_err(|err| format!("failed to canonicalize the path: {:?}", err))?; + + // Write the cache config file + let cache_config_path = wasmtime_cache_root.join("cache-config.toml"); + let config_content = format!( + "\ +[cache] +enabled = true +directory = \"{cache_dir}\" +", + cache_dir = wasmtime_cache_root.display() + ); + fs::write(&cache_config_path, config_content) + .map_err(|err| format!("cannot write the cache config: {:?}", err))?; + + config + .cache_config_load(cache_config_path) + .map_err(|err| format!("failed to parse the config: {:?}", err))?; + + Ok(()) +} + /// Create a new `WasmtimeRuntime` given the code. This function performs translation from Wasm to /// machine code, which can be computationally heavy. +/// +/// The `cache_path` designates where this executor implementation can put compiled artifacts. pub fn create_runtime( code: &[u8], heap_pages: u64, host_functions: Vec<&'static dyn Function>, allow_missing_func_imports: bool, + cache_path: Option<&Path>, ) -> std::result::Result { // Create the engine, store and finally the module from the given code. let mut config = Config::new(); config.cranelift_opt_level(wasmtime::OptLevel::SpeedAndSize); + if let Some(cache_path) = cache_path { + if let Err(reason) = setup_wasmtime_caching(cache_path, &mut config) { + log::warn!( + "failed to setup wasmtime cache. Performance may degrade significantly: {}.", + reason, + ); + } + } let engine = Engine::new(&config); - let module_wrapper = ModuleWrapper::new(&engine, code) .map_err(|e| WasmError::Other(format!("cannot create module: {}", e)))?; diff --git a/substrate/primitives/runtime-interface/test/src/lib.rs b/substrate/primitives/runtime-interface/test/src/lib.rs index 75aebf1cae..4426997663 100644 --- a/substrate/primitives/runtime-interface/test/src/lib.rs +++ b/substrate/primitives/runtime-interface/test/src/lib.rs @@ -44,6 +44,7 @@ fn call_wasm_method_with_result( Some(8), host_functions, 8, + None, ); executor.call_in_wasm( binary,