Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Commit

Permalink
WasmExecutor takes a cache directory (#8057)
Browse files Browse the repository at this point in the history
That is useful for executors like wasmtime which produces compiled code
and can actually benefit from caching under some circumstances
  • Loading branch information
pepyakin authored Feb 9, 2021
1 parent 4777aba commit 48e9d49
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 8 deletions.
6 changes: 6 additions & 0 deletions client/executor/src/integration_tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ fn call_in_wasm<E: Externalities>(
Some(1024),
HostFunctions::host_functions(),
8,
None,
);
executor.call_in_wasm(
&wasm_binary_unwrap()[..],
Expand Down Expand Up @@ -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(
Expand All @@ -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();
Expand Down Expand Up @@ -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();

Expand All @@ -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();

Expand All @@ -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(|_|
Expand Down
1 change: 1 addition & 0 deletions client/executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ mod tests {
Some(8),
sp_io::SubstrateHostFunctions::host_functions(),
8,
None,
);
let res = executor.call_in_wasm(
&wasm_binary_unwrap()[..],
Expand Down
21 changes: 19 additions & 2 deletions client/executor/src/native_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use std::{
panic::{UnwindSafe, AssertUnwindSafe},
result,
sync::{Arc, atomic::{AtomicU64, Ordering}, mpsc},
path::PathBuf,
};

use sp_version::{NativeVersion, RuntimeVersion};
Expand Down Expand Up @@ -102,6 +103,9 @@ pub struct WasmExecutor {
cache: Arc<RuntimeCache>,
/// 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<PathBuf>,
}

impl WasmExecutor {
Expand All @@ -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<u64>,
host_functions: Vec<&'static dyn Function>,
max_runtime_instances: usize,
cache_path: Option<PathBuf>,
) -> 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,
}
}

Expand Down Expand Up @@ -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))?;

Expand Down Expand Up @@ -267,6 +283,7 @@ impl<D: NativeExecutionDispatch> NativeExecutor<D> {
default_heap_pages,
host_functions,
max_runtime_instances,
None,
);

NativeExecutor {
Expand Down
32 changes: 27 additions & 5 deletions client/executor/src/wasm_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -152,14 +153,22 @@ pub struct RuntimeCache {
runtimes: Mutex<[Option<Arc<VersionedRuntime>>; MAX_RUNTIMES]>,
/// The size of the instances cache for each runtime.
max_runtime_instances: usize,
cache_path: Option<PathBuf>,
}

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<PathBuf>) -> RuntimeCache {
RuntimeCache {
runtimes: Default::default(),
max_runtime_instances,
cache_path,
}
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<Arc<dyn WasmModule>, 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<dyn WasmModule> { Arc::new(runtime) }),
allow_missing_func_imports,
)
.map(|runtime| -> Arc<dyn WasmModule> { 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<dyn WasmModule> { Arc::new(runtime) }),
}
}
Expand Down Expand Up @@ -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<VersionedRuntime, WasmError> {
#[cfg(not(target_os = "unknown"))]
let time = std::time::Instant::now();
Expand All @@ -328,6 +349,7 @@ fn create_versioned_wasm_runtime(
&code,
host_functions,
allow_missing_func_imports,
cache_path,
)?;

// Call to determine runtime version.
Expand Down
51 changes: 50 additions & 1 deletion client/executor/wasmtime/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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<WasmtimeRuntime, WasmError> {
// 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)))?;

Expand Down
1 change: 1 addition & 0 deletions primitives/runtime-interface/test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ fn call_wasm_method_with_result<HF: HostFunctionsT>(
Some(8),
host_functions,
8,
None,
);
executor.call_in_wasm(
binary,
Expand Down

0 comments on commit 48e9d49

Please sign in to comment.