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

WasmExecutor takes a cache directory #8057

Merged
1 commit merged into from
Feb 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean caching is disabled when running substrate node?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I deliberately didn't put it here.

The thing is that this change is dictated by needs of polkadot. I believe it should mitigate the stalls we are seeing there right now. Therefore, it is critical to land it quickly. I figured that we should move the question about the cache out of the way.

);

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)
Copy link
Contributor

@kianenigma kianenigma Feb 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read in the docs that this needs a config feature enabled on wasmtime, but I can't see it anywhere. Am I missing something?

In 0.19 it is not feature gated, in latest (0.21) it is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I assume you meant the cache feature), it is enabled by default as well.

.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