mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 03:01:07 +00:00
Switch to pooling copy-on-write instantiation strategy for WASM (#11232)
* Switch to pooling copy-on-write instantiation strategy for WASM * Fix benchmark compilation * Fix `cargo fmt` * Fix compilation of another benchmark I've missed * Cleanups according to review comments * Move `max_memory_size` to `Semantics` * Set `memory_guaranteed_dense_image_size` to `max_memory_size` * Rename `wasm_instantiation_strategy` to `wasmtime_instantiation_strategy` * Update the doc-comments regarding the instantiation strategy * Extend the integration tests to test every instantiation strategy * Don't drop the temporary directory until the runtime is dropped in benchmarks * Don't drop the temporary directory until the runtime is dropped in tests
This commit is contained in:
Generated
+13
@@ -4173,6 +4173,15 @@ version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||
|
||||
[[package]]
|
||||
name = "memfd"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6627dc657574b49d6ad27105ed671822be56e0d2547d413bfbf3e8d8fa92e7a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memmap"
|
||||
version = "0.7.0"
|
||||
@@ -8197,6 +8206,7 @@ dependencies = [
|
||||
"hex-literal",
|
||||
"lazy_static",
|
||||
"lru",
|
||||
"num_cpus",
|
||||
"parity-scale-codec",
|
||||
"parking_lot 0.12.0",
|
||||
"paste 1.0.6",
|
||||
@@ -8221,6 +8231,7 @@ dependencies = [
|
||||
"sp-version",
|
||||
"sp-wasm-interface",
|
||||
"substrate-test-runtime",
|
||||
"tempfile",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"wasmi",
|
||||
@@ -8274,6 +8285,7 @@ dependencies = [
|
||||
"sp-runtime-interface",
|
||||
"sp-sandbox",
|
||||
"sp-wasm-interface",
|
||||
"tempfile",
|
||||
"wasmtime",
|
||||
"wat",
|
||||
]
|
||||
@@ -11838,6 +11850,7 @@ dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"mach",
|
||||
"memfd",
|
||||
"memoffset",
|
||||
"more-asserts",
|
||||
"rand 0.8.4",
|
||||
|
||||
@@ -29,7 +29,7 @@ use sc_consensus::{
|
||||
use sc_service::{
|
||||
config::{
|
||||
DatabaseSource, KeepBlocks, KeystoreConfig, NetworkConfiguration, OffchainWorkerConfig,
|
||||
PruningMode, WasmExecutionMethod,
|
||||
PruningMode, WasmExecutionMethod, WasmtimeInstantiationStrategy,
|
||||
},
|
||||
BasePath, Configuration, Role,
|
||||
};
|
||||
@@ -77,7 +77,9 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase {
|
||||
state_pruning: Some(PruningMode::ArchiveAll),
|
||||
keep_blocks: KeepBlocks::All,
|
||||
chain_spec: spec,
|
||||
wasm_method: WasmExecutionMethod::Compiled,
|
||||
wasm_method: WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite,
|
||||
},
|
||||
execution_strategies: ExecutionStrategies {
|
||||
syncing: execution_strategy,
|
||||
importing: execution_strategy,
|
||||
|
||||
@@ -25,6 +25,8 @@ use node_runtime::{
|
||||
UncheckedExtrinsic,
|
||||
};
|
||||
use node_testing::keyring::*;
|
||||
#[cfg(feature = "wasmtime")]
|
||||
use sc_executor::WasmtimeInstantiationStrategy;
|
||||
use sc_executor::{Externalities, NativeElseWasmExecutor, RuntimeVersionOf, WasmExecutionMethod};
|
||||
use sp_core::{
|
||||
storage::well_known_keys,
|
||||
@@ -183,7 +185,9 @@ fn bench_execute_block(c: &mut Criterion) {
|
||||
ExecutionMethod::Native,
|
||||
ExecutionMethod::Wasm(WasmExecutionMethod::Interpreted),
|
||||
#[cfg(feature = "wasmtime")]
|
||||
ExecutionMethod::Wasm(WasmExecutionMethod::Compiled),
|
||||
ExecutionMethod::Wasm(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite,
|
||||
}),
|
||||
];
|
||||
|
||||
for strategy in execution_methods {
|
||||
|
||||
@@ -46,7 +46,7 @@ use sc_client_api::{
|
||||
};
|
||||
use sc_client_db::PruningMode;
|
||||
use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy, ImportResult, ImportedAux};
|
||||
use sc_executor::{NativeElseWasmExecutor, WasmExecutionMethod};
|
||||
use sc_executor::{NativeElseWasmExecutor, WasmExecutionMethod, WasmtimeInstantiationStrategy};
|
||||
use sp_api::ProvideRuntimeApi;
|
||||
use sp_block_builder::BlockBuilder;
|
||||
use sp_consensus::BlockOrigin;
|
||||
@@ -398,7 +398,14 @@ impl BenchDb {
|
||||
let backend = sc_service::new_db_backend(db_config).expect("Should not fail");
|
||||
let client = sc_service::new_client(
|
||||
backend.clone(),
|
||||
NativeElseWasmExecutor::new(WasmExecutionMethod::Compiled, None, 8, 2),
|
||||
NativeElseWasmExecutor::new(
|
||||
WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite,
|
||||
},
|
||||
None,
|
||||
8,
|
||||
2,
|
||||
),
|
||||
&keyring.generate_genesis(),
|
||||
None,
|
||||
None,
|
||||
|
||||
@@ -20,6 +20,36 @@
|
||||
|
||||
use clap::ArgEnum;
|
||||
|
||||
/// The instantiation strategy to use in compiled mode.
|
||||
#[derive(Debug, Clone, Copy, ArgEnum)]
|
||||
#[clap(rename_all = "kebab-case")]
|
||||
pub enum WasmtimeInstantiationStrategy {
|
||||
/// Pool the instances to avoid initializing everything from scratch
|
||||
/// on each instantiation. Use copy-on-write memory when possible.
|
||||
PoolingCopyOnWrite,
|
||||
|
||||
/// Recreate the instance from scratch on every instantiation.
|
||||
/// Use copy-on-write memory when possible.
|
||||
RecreateInstanceCopyOnWrite,
|
||||
|
||||
/// Pool the instances to avoid initializing everything from scratch
|
||||
/// on each instantiation.
|
||||
Pooling,
|
||||
|
||||
/// Recreate the instance from scratch on every instantiation. Very slow.
|
||||
RecreateInstance,
|
||||
|
||||
/// Legacy instance reuse mechanism. DEPRECATED. Will be removed in the future.
|
||||
///
|
||||
/// Should only be used in case of encountering any issues with the new default
|
||||
/// instantiation strategy.
|
||||
LegacyInstanceReuse,
|
||||
}
|
||||
|
||||
/// The default [`WasmtimeInstantiationStrategy`].
|
||||
pub const DEFAULT_WASMTIME_INSTANTIATION_STRATEGY: WasmtimeInstantiationStrategy =
|
||||
WasmtimeInstantiationStrategy::PoolingCopyOnWrite;
|
||||
|
||||
/// How to execute Wasm runtime code.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum WasmExecutionMethod {
|
||||
@@ -71,18 +101,33 @@ impl WasmExecutionMethod {
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<sc_service::config::WasmExecutionMethod> for WasmExecutionMethod {
|
||||
fn into(self) -> sc_service::config::WasmExecutionMethod {
|
||||
match self {
|
||||
WasmExecutionMethod::Interpreted =>
|
||||
sc_service::config::WasmExecutionMethod::Interpreted,
|
||||
#[cfg(feature = "wasmtime")]
|
||||
WasmExecutionMethod::Compiled => sc_service::config::WasmExecutionMethod::Compiled,
|
||||
#[cfg(not(feature = "wasmtime"))]
|
||||
WasmExecutionMethod::Compiled => panic!(
|
||||
"Substrate must be compiled with \"wasmtime\" feature for compiled Wasm execution"
|
||||
),
|
||||
}
|
||||
/// Converts the execution method and instantiation strategy command line arguments
|
||||
/// into an execution method which can be used internally.
|
||||
pub fn execution_method_from_cli(
|
||||
execution_method: WasmExecutionMethod,
|
||||
_instantiation_strategy: WasmtimeInstantiationStrategy,
|
||||
) -> sc_service::config::WasmExecutionMethod {
|
||||
match execution_method {
|
||||
WasmExecutionMethod::Interpreted => sc_service::config::WasmExecutionMethod::Interpreted,
|
||||
#[cfg(feature = "wasmtime")]
|
||||
WasmExecutionMethod::Compiled => sc_service::config::WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: match _instantiation_strategy {
|
||||
WasmtimeInstantiationStrategy::PoolingCopyOnWrite =>
|
||||
sc_service::config::WasmtimeInstantiationStrategy::PoolingCopyOnWrite,
|
||||
WasmtimeInstantiationStrategy::RecreateInstanceCopyOnWrite =>
|
||||
sc_service::config::WasmtimeInstantiationStrategy::RecreateInstanceCopyOnWrite,
|
||||
WasmtimeInstantiationStrategy::Pooling =>
|
||||
sc_service::config::WasmtimeInstantiationStrategy::Pooling,
|
||||
WasmtimeInstantiationStrategy::RecreateInstance =>
|
||||
sc_service::config::WasmtimeInstantiationStrategy::RecreateInstance,
|
||||
WasmtimeInstantiationStrategy::LegacyInstanceReuse =>
|
||||
sc_service::config::WasmtimeInstantiationStrategy::LegacyInstanceReuse,
|
||||
},
|
||||
},
|
||||
#[cfg(not(feature = "wasmtime"))]
|
||||
WasmExecutionMethod::Compiled => panic!(
|
||||
"Substrate must be compiled with \"wasmtime\" feature for compiled Wasm execution"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,11 @@
|
||||
|
||||
use crate::{
|
||||
arg_enums::{
|
||||
ExecutionStrategy, WasmExecutionMethod, DEFAULT_EXECUTION_BLOCK_CONSTRUCTION,
|
||||
DEFAULT_EXECUTION_IMPORT_BLOCK, DEFAULT_EXECUTION_IMPORT_BLOCK_VALIDATOR,
|
||||
DEFAULT_EXECUTION_OFFCHAIN_WORKER, DEFAULT_EXECUTION_OTHER, DEFAULT_EXECUTION_SYNCING,
|
||||
DEFAULT_WASM_EXECUTION_METHOD,
|
||||
ExecutionStrategy, WasmExecutionMethod, WasmtimeInstantiationStrategy,
|
||||
DEFAULT_EXECUTION_BLOCK_CONSTRUCTION, DEFAULT_EXECUTION_IMPORT_BLOCK,
|
||||
DEFAULT_EXECUTION_IMPORT_BLOCK_VALIDATOR, DEFAULT_EXECUTION_OFFCHAIN_WORKER,
|
||||
DEFAULT_EXECUTION_OTHER, DEFAULT_EXECUTION_SYNCING,
|
||||
DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, DEFAULT_WASM_EXECUTION_METHOD,
|
||||
},
|
||||
params::{DatabaseParams, PruningParams},
|
||||
};
|
||||
@@ -62,6 +63,27 @@ pub struct ImportParams {
|
||||
)]
|
||||
pub wasm_method: WasmExecutionMethod,
|
||||
|
||||
/// The WASM instantiation method to use.
|
||||
///
|
||||
/// Only has an effect when `wasm-execution` is set to `compiled`.
|
||||
///
|
||||
/// The copy-on-write strategies are only supported on Linux.
|
||||
/// If the copy-on-write variant of a strategy is unsupported
|
||||
/// the executor will fall back to the non-CoW equivalent.
|
||||
///
|
||||
/// The fastest (and the default) strategy available is `pooling-copy-on-write`.
|
||||
///
|
||||
/// The `legacy-instance-reuse` strategy is deprecated and will
|
||||
/// be removed in the future. It should only be used in case of
|
||||
/// issues with the default instantiation strategy.
|
||||
#[clap(
|
||||
long,
|
||||
value_name = "STRATEGY",
|
||||
default_value_t = DEFAULT_WASMTIME_INSTANTIATION_STRATEGY,
|
||||
arg_enum,
|
||||
)]
|
||||
pub wasmtime_instantiation_strategy: WasmtimeInstantiationStrategy,
|
||||
|
||||
/// Specify the path where local WASM runtimes are stored.
|
||||
///
|
||||
/// These runtimes will override on-chain runtimes when the version matches.
|
||||
@@ -85,7 +107,7 @@ impl ImportParams {
|
||||
|
||||
/// Get the WASM execution method from the parameters
|
||||
pub fn wasm_method(&self) -> sc_service::config::WasmExecutionMethod {
|
||||
self.wasm_method.into()
|
||||
crate::execution_method_from_cli(self.wasm_method, self.wasmtime_instantiation_strategy)
|
||||
}
|
||||
|
||||
/// Enable overriding on-chain WASM with locally-stored WASM
|
||||
|
||||
@@ -50,6 +50,8 @@ paste = "1.0"
|
||||
regex = "1.5.5"
|
||||
criterion = "0.3"
|
||||
env_logger = "0.9"
|
||||
num_cpus = "1.13.1"
|
||||
tempfile = "3.3.0"
|
||||
|
||||
[[bench]]
|
||||
name = "bench"
|
||||
|
||||
@@ -17,26 +17,43 @@
|
||||
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
use sc_executor_common::{runtime_blob::RuntimeBlob, wasm_runtime::WasmModule};
|
||||
use codec::Encode;
|
||||
|
||||
use sc_executor_common::{
|
||||
runtime_blob::RuntimeBlob,
|
||||
wasm_runtime::{WasmInstance, WasmModule},
|
||||
};
|
||||
#[cfg(feature = "wasmtime")]
|
||||
use sc_executor_wasmtime::InstantiationStrategy;
|
||||
use sc_runtime_test::wasm_binary_unwrap as test_runtime;
|
||||
use sp_wasm_interface::HostFunctions as _;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Method {
|
||||
Interpreted,
|
||||
#[cfg(feature = "wasmtime")]
|
||||
Compiled {
|
||||
fast_instance_reuse: bool,
|
||||
instantiation_strategy: InstantiationStrategy,
|
||||
precompile: bool,
|
||||
},
|
||||
}
|
||||
|
||||
// This is just a bog-standard Kusama runtime with the extra `test_empty_return`
|
||||
// function copy-pasted from the test runtime.
|
||||
// This is just a bog-standard Kusama runtime with an extra
|
||||
// `test_empty_return` and `test_dirty_plenty_memory` functions
|
||||
// copy-pasted from the test runtime.
|
||||
fn kusama_runtime() -> &'static [u8] {
|
||||
include_bytes!("kusama_runtime.wasm")
|
||||
}
|
||||
|
||||
fn initialize(runtime: &[u8], method: Method) -> Arc<dyn WasmModule> {
|
||||
fn initialize(
|
||||
_tmpdir: &mut Option<tempfile::TempDir>,
|
||||
runtime: &[u8],
|
||||
method: Method,
|
||||
) -> Arc<dyn WasmModule> {
|
||||
let blob = RuntimeBlob::uncompress_if_needed(runtime).unwrap();
|
||||
let host_functions = sp_io::SubstrateHostFunctions::host_functions();
|
||||
let heap_pages = 2048;
|
||||
@@ -51,80 +68,200 @@ fn initialize(runtime: &[u8], method: Method) -> Arc<dyn WasmModule> {
|
||||
)
|
||||
.map(|runtime| -> Arc<dyn WasmModule> { Arc::new(runtime) }),
|
||||
#[cfg(feature = "wasmtime")]
|
||||
Method::Compiled { fast_instance_reuse } =>
|
||||
sc_executor_wasmtime::create_runtime::<sp_io::SubstrateHostFunctions>(
|
||||
blob,
|
||||
sc_executor_wasmtime::Config {
|
||||
Method::Compiled { instantiation_strategy, precompile } => {
|
||||
let config = sc_executor_wasmtime::Config {
|
||||
allow_missing_func_imports,
|
||||
cache_path: None,
|
||||
semantics: sc_executor_wasmtime::Semantics {
|
||||
extra_heap_pages: heap_pages,
|
||||
instantiation_strategy,
|
||||
deterministic_stack_limit: None,
|
||||
canonicalize_nans: false,
|
||||
parallel_compilation: true,
|
||||
max_memory_size: None,
|
||||
allow_missing_func_imports,
|
||||
cache_path: None,
|
||||
semantics: sc_executor_wasmtime::Semantics {
|
||||
extra_heap_pages: heap_pages,
|
||||
fast_instance_reuse,
|
||||
deterministic_stack_limit: None,
|
||||
canonicalize_nans: false,
|
||||
parallel_compilation: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
.map(|runtime| -> Arc<dyn WasmModule> { Arc::new(runtime) }),
|
||||
};
|
||||
|
||||
if precompile {
|
||||
let precompiled_blob =
|
||||
sc_executor_wasmtime::prepare_runtime_artifact(blob, &config.semantics)
|
||||
.unwrap();
|
||||
|
||||
// Create a fresh temporary directory to make absolutely sure
|
||||
// we'll use the right module.
|
||||
*_tmpdir = Some(tempfile::tempdir().unwrap());
|
||||
let tmpdir = _tmpdir.as_ref().unwrap();
|
||||
|
||||
let path = tmpdir.path().join("module.bin");
|
||||
std::fs::write(&path, &precompiled_blob).unwrap();
|
||||
unsafe {
|
||||
sc_executor_wasmtime::create_runtime_from_artifact::<
|
||||
sp_io::SubstrateHostFunctions,
|
||||
>(&path, config)
|
||||
}
|
||||
} else {
|
||||
sc_executor_wasmtime::create_runtime::<sp_io::SubstrateHostFunctions>(blob, config)
|
||||
}
|
||||
.map(|runtime| -> Arc<dyn WasmModule> { Arc::new(runtime) })
|
||||
},
|
||||
}
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn run_benchmark(
|
||||
c: &mut Criterion,
|
||||
benchmark_name: &str,
|
||||
thread_count: usize,
|
||||
runtime: &dyn WasmModule,
|
||||
testcase: impl Fn(&mut Box<dyn WasmInstance>) + Copy + Send + 'static,
|
||||
) {
|
||||
c.bench_function(benchmark_name, |b| {
|
||||
// Here we deliberately start a bunch of extra threads which will just
|
||||
// keep on independently instantiating the runtime over and over again.
|
||||
//
|
||||
// We don't really have to measure how much time those take since the
|
||||
// work done is essentially the same on each thread, and what we're
|
||||
// interested in here is only how those extra threads affect the execution
|
||||
// on the current thread.
|
||||
//
|
||||
// In an ideal case assuming we have enough CPU cores those extra threads
|
||||
// shouldn't affect the main thread's runtime at all, however in practice
|
||||
// they're not completely independent. There might be per-process
|
||||
// locks in the kernel which are briefly held during instantiation, etc.,
|
||||
// and how much those affect the execution here is what we want to measure.
|
||||
let is_benchmark_running = Arc::new(AtomicBool::new(true));
|
||||
let threads_running = Arc::new(AtomicUsize::new(0));
|
||||
let aux_threads: Vec<_> = (0..thread_count - 1)
|
||||
.map(|_| {
|
||||
let mut instance = runtime.new_instance().unwrap();
|
||||
let is_benchmark_running = is_benchmark_running.clone();
|
||||
let threads_running = threads_running.clone();
|
||||
std::thread::spawn(move || {
|
||||
threads_running.fetch_add(1, Ordering::SeqCst);
|
||||
while is_benchmark_running.load(Ordering::Relaxed) {
|
||||
testcase(&mut instance);
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
while threads_running.load(Ordering::SeqCst) != (thread_count - 1) {
|
||||
std::thread::yield_now();
|
||||
}
|
||||
|
||||
let mut instance = runtime.new_instance().unwrap();
|
||||
b.iter(|| testcase(&mut instance));
|
||||
|
||||
is_benchmark_running.store(false, Ordering::SeqCst);
|
||||
for thread in aux_threads {
|
||||
thread.join().unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_call_instance(c: &mut Criterion) {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
#[cfg(feature = "wasmtime")]
|
||||
{
|
||||
let runtime = initialize(test_runtime(), Method::Compiled { fast_instance_reuse: true });
|
||||
c.bench_function("call_instance_test_runtime_with_fast_instance_reuse", |b| {
|
||||
let mut instance = runtime.new_instance().unwrap();
|
||||
b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap())
|
||||
});
|
||||
let strategies = [
|
||||
#[cfg(feature = "wasmtime")]
|
||||
(
|
||||
"legacy_instance_reuse",
|
||||
Method::Compiled {
|
||||
instantiation_strategy: InstantiationStrategy::LegacyInstanceReuse,
|
||||
precompile: false,
|
||||
},
|
||||
),
|
||||
#[cfg(feature = "wasmtime")]
|
||||
(
|
||||
"recreate_instance_vanilla",
|
||||
Method::Compiled {
|
||||
instantiation_strategy: InstantiationStrategy::RecreateInstance,
|
||||
precompile: false,
|
||||
},
|
||||
),
|
||||
#[cfg(feature = "wasmtime")]
|
||||
(
|
||||
"recreate_instance_cow_fresh",
|
||||
Method::Compiled {
|
||||
instantiation_strategy: InstantiationStrategy::RecreateInstanceCopyOnWrite,
|
||||
precompile: false,
|
||||
},
|
||||
),
|
||||
#[cfg(feature = "wasmtime")]
|
||||
(
|
||||
"recreate_instance_cow_precompiled",
|
||||
Method::Compiled {
|
||||
instantiation_strategy: InstantiationStrategy::RecreateInstanceCopyOnWrite,
|
||||
precompile: true,
|
||||
},
|
||||
),
|
||||
#[cfg(feature = "wasmtime")]
|
||||
(
|
||||
"pooling_vanilla",
|
||||
Method::Compiled {
|
||||
instantiation_strategy: InstantiationStrategy::Pooling,
|
||||
precompile: false,
|
||||
},
|
||||
),
|
||||
#[cfg(feature = "wasmtime")]
|
||||
(
|
||||
"pooling_cow_fresh",
|
||||
Method::Compiled {
|
||||
instantiation_strategy: InstantiationStrategy::PoolingCopyOnWrite,
|
||||
precompile: false,
|
||||
},
|
||||
),
|
||||
#[cfg(feature = "wasmtime")]
|
||||
(
|
||||
"pooling_cow_precompiled",
|
||||
Method::Compiled {
|
||||
instantiation_strategy: InstantiationStrategy::PoolingCopyOnWrite,
|
||||
precompile: true,
|
||||
},
|
||||
),
|
||||
("interpreted", Method::Interpreted),
|
||||
];
|
||||
|
||||
let runtimes = [("kusama_runtime", kusama_runtime()), ("test_runtime", test_runtime())];
|
||||
|
||||
let thread_counts = [1, 2, 4, 8, 16];
|
||||
|
||||
fn test_call_empty_function(instance: &mut Box<dyn WasmInstance>) {
|
||||
instance.call_export("test_empty_return", &[0]).unwrap();
|
||||
}
|
||||
|
||||
#[cfg(feature = "wasmtime")]
|
||||
{
|
||||
let runtime = initialize(test_runtime(), Method::Compiled { fast_instance_reuse: false });
|
||||
c.bench_function("call_instance_test_runtime_without_fast_instance_reuse", |b| {
|
||||
let mut instance = runtime.new_instance().unwrap();
|
||||
b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap());
|
||||
});
|
||||
fn test_dirty_1mb_of_memory(instance: &mut Box<dyn WasmInstance>) {
|
||||
instance.call_export("test_dirty_plenty_memory", &(0, 16).encode()).unwrap();
|
||||
}
|
||||
|
||||
#[cfg(feature = "wasmtime")]
|
||||
{
|
||||
let runtime = initialize(kusama_runtime(), Method::Compiled { fast_instance_reuse: true });
|
||||
c.bench_function("call_instance_kusama_runtime_with_fast_instance_reuse", |b| {
|
||||
let mut instance = runtime.new_instance().unwrap();
|
||||
b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap())
|
||||
});
|
||||
}
|
||||
let testcases = [
|
||||
("call_empty_function", test_call_empty_function as fn(&mut Box<dyn WasmInstance>)),
|
||||
("dirty_1mb_of_memory", test_dirty_1mb_of_memory),
|
||||
];
|
||||
|
||||
#[cfg(feature = "wasmtime")]
|
||||
{
|
||||
let runtime = initialize(kusama_runtime(), Method::Compiled { fast_instance_reuse: false });
|
||||
c.bench_function("call_instance_kusama_runtime_without_fast_instance_reuse", |b| {
|
||||
let mut instance = runtime.new_instance().unwrap();
|
||||
b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap());
|
||||
});
|
||||
}
|
||||
let num_cpus = num_cpus::get_physical();
|
||||
let mut tmpdir = None;
|
||||
|
||||
{
|
||||
let runtime = initialize(test_runtime(), Method::Interpreted);
|
||||
c.bench_function("call_instance_test_runtime_interpreted", |b| {
|
||||
let mut instance = runtime.new_instance().unwrap();
|
||||
b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap())
|
||||
});
|
||||
}
|
||||
for (strategy_name, strategy) in strategies {
|
||||
for (runtime_name, runtime) in runtimes {
|
||||
let runtime = initialize(&mut tmpdir, runtime, strategy.clone());
|
||||
|
||||
{
|
||||
let runtime = initialize(kusama_runtime(), Method::Interpreted);
|
||||
c.bench_function("call_instance_kusama_runtime_interpreted", |b| {
|
||||
let mut instance = runtime.new_instance().unwrap();
|
||||
b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap())
|
||||
});
|
||||
for (testcase_name, testcase) in testcases {
|
||||
for thread_count in thread_counts {
|
||||
if thread_count > num_cpus {
|
||||
// If there are not enough cores available the benchmark is pointless.
|
||||
continue
|
||||
}
|
||||
|
||||
let benchmark_name = format!(
|
||||
"{}_from_{}_with_{}_on_{}_threads",
|
||||
testcase_name, runtime_name, strategy_name, thread_count
|
||||
);
|
||||
|
||||
run_benchmark(c, &benchmark_name, thread_count, &*runtime, testcase);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -38,7 +38,13 @@ fn memory_consumption_compiled() {
|
||||
// For that we make a series of runtime calls, probing the RSS for the VMA matching the linear
|
||||
// memory. After the call we expect RSS to be equal to 0.
|
||||
|
||||
let runtime = mk_test_runtime(WasmExecutionMethod::Compiled, 1024);
|
||||
let runtime = mk_test_runtime(
|
||||
WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy:
|
||||
sc_executor_wasmtime::InstantiationStrategy::LegacyInstanceReuse,
|
||||
},
|
||||
1024,
|
||||
);
|
||||
|
||||
let mut instance = runtime.new_instance().unwrap();
|
||||
let heap_base = instance
|
||||
|
||||
@@ -54,8 +54,42 @@ macro_rules! test_wasm_execution {
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "wasmtime")]
|
||||
fn [<$method_name _compiled>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled);
|
||||
fn [<$method_name _compiled_recreate_instance_cow>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstanceCopyOnWrite
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "wasmtime")]
|
||||
fn [<$method_name _compiled_recreate_instance_vanilla>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstance
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "wasmtime")]
|
||||
fn [<$method_name _compiled_pooling_cow>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::PoolingCopyOnWrite
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "wasmtime")]
|
||||
fn [<$method_name _compiled_pooling_vanilla>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::Pooling
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "wasmtime")]
|
||||
fn [<$method_name _compiled_legacy_instance_reuse>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::LegacyInstanceReuse
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -88,14 +122,82 @@ macro_rules! test_wasm_execution_sandbox {
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "wasmtime")]
|
||||
fn [<$method_name _compiled_host_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled, "_host");
|
||||
fn [<$method_name _compiled_pooling_cow_host_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::PoolingCopyOnWrite
|
||||
}, "_host");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "wasmtime")]
|
||||
fn [<$method_name _compiled_embedded_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled, "_embedded");
|
||||
fn [<$method_name _compiled_pooling_cow_embedded_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::PoolingCopyOnWrite
|
||||
}, "_embedded");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "wasmtime")]
|
||||
fn [<$method_name _compiled_pooling_vanilla_host_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::Pooling
|
||||
}, "_host");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "wasmtime")]
|
||||
fn [<$method_name _compiled_pooling_vanilla_embedded_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::Pooling
|
||||
}, "_embedded");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "wasmtime")]
|
||||
fn [<$method_name _compiled_recreate_instance_cow_host_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstanceCopyOnWrite
|
||||
}, "_host");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "wasmtime")]
|
||||
fn [<$method_name _compiled_recreate_instance_cow_embedded_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstanceCopyOnWrite
|
||||
}, "_embedded");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "wasmtime")]
|
||||
fn [<$method_name _compiled_recreate_instance_vanilla_host_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstance
|
||||
}, "_host");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "wasmtime")]
|
||||
fn [<$method_name _compiled_recreate_instance_vanilla_embedded_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstance
|
||||
}, "_embedded");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "wasmtime")]
|
||||
fn [<$method_name _compiled_legacy_instance_reuse_host_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::LegacyInstanceReuse
|
||||
}, "_host");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "wasmtime")]
|
||||
fn [<$method_name _compiled_legacy_instance_reuse_embedded_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::LegacyInstanceReuse
|
||||
}, "_embedded");
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -153,7 +255,7 @@ fn call_not_existing_function(wasm_method: WasmExecutionMethod) {
|
||||
let expected = match wasm_method {
|
||||
WasmExecutionMethod::Interpreted => "Trap: Host(Other(\"Function `missing_external` is only a stub. Calling a stub is not allowed.\"))",
|
||||
#[cfg(feature = "wasmtime")]
|
||||
WasmExecutionMethod::Compiled => "call to a missing function env:missing_external"
|
||||
WasmExecutionMethod::Compiled { .. } => "call to a missing function env:missing_external"
|
||||
};
|
||||
assert_eq!(error.message, expected);
|
||||
},
|
||||
@@ -173,7 +275,7 @@ fn call_yet_another_not_existing_function(wasm_method: WasmExecutionMethod) {
|
||||
let expected = match wasm_method {
|
||||
WasmExecutionMethod::Interpreted => "Trap: Host(Other(\"Function `yet_another_missing_external` is only a stub. Calling a stub is not allowed.\"))",
|
||||
#[cfg(feature = "wasmtime")]
|
||||
WasmExecutionMethod::Compiled => "call to a missing function env:yet_another_missing_external"
|
||||
WasmExecutionMethod::Compiled { .. } => "call to a missing function env:yet_another_missing_external"
|
||||
};
|
||||
assert_eq!(error.message, expected);
|
||||
},
|
||||
@@ -473,7 +575,9 @@ fn should_trap_when_heap_exhausted(wasm_method: WasmExecutionMethod) {
|
||||
|
||||
match err {
|
||||
#[cfg(feature = "wasmtime")]
|
||||
Error::AbortedDueToTrap(error) if wasm_method == WasmExecutionMethod::Compiled => {
|
||||
Error::AbortedDueToTrap(error)
|
||||
if matches!(wasm_method, WasmExecutionMethod::Compiled { .. }) =>
|
||||
{
|
||||
assert_eq!(
|
||||
error.message,
|
||||
r#"host code panicked while being called by the runtime: Failed to allocate memory: "Allocator ran out of space""#
|
||||
@@ -807,7 +911,7 @@ fn unreachable_intrinsic(wasm_method: WasmExecutionMethod) {
|
||||
let expected = match wasm_method {
|
||||
WasmExecutionMethod::Interpreted => "Trap: Unreachable",
|
||||
#[cfg(feature = "wasmtime")]
|
||||
WasmExecutionMethod::Compiled => "wasm trap: wasm `unreachable` instruction executed",
|
||||
WasmExecutionMethod::Compiled { .. } => "wasm trap: wasm `unreachable` instruction executed",
|
||||
};
|
||||
assert_eq!(error.message, expected);
|
||||
},
|
||||
|
||||
@@ -51,6 +51,9 @@ pub use wasmi;
|
||||
|
||||
pub use sc_executor_common::{error, sandbox};
|
||||
|
||||
#[cfg(feature = "wasmtime")]
|
||||
pub use sc_executor_wasmtime::InstantiationStrategy as WasmtimeInstantiationStrategy;
|
||||
|
||||
/// Extracts the runtime version of a given runtime code.
|
||||
pub trait RuntimeVersionOf {
|
||||
/// Extract [`RuntimeVersion`](sp_version::RuntimeVersion) of the given `runtime_code`.
|
||||
|
||||
@@ -46,7 +46,10 @@ pub enum WasmExecutionMethod {
|
||||
Interpreted,
|
||||
/// Uses the Wasmtime compiled runtime.
|
||||
#[cfg(feature = "wasmtime")]
|
||||
Compiled,
|
||||
Compiled {
|
||||
/// The instantiation strategy to use.
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy,
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for WasmExecutionMethod {
|
||||
@@ -71,6 +74,9 @@ struct VersionedRuntime {
|
||||
module: Arc<dyn WasmModule>,
|
||||
/// Runtime version according to `Core_version` if any.
|
||||
version: Option<RuntimeVersion>,
|
||||
|
||||
// TODO: Remove this once the legacy instance reuse instantiation strategy
|
||||
// for `wasmtime` is gone, as this only makes sense with that particular strategy.
|
||||
/// Cached instance pool.
|
||||
instances: Arc<Vec<Mutex<Option<Box<dyn WasmInstance>>>>>,
|
||||
}
|
||||
@@ -310,22 +316,23 @@ where
|
||||
.map(|runtime| -> Arc<dyn WasmModule> { Arc::new(runtime) })
|
||||
},
|
||||
#[cfg(feature = "wasmtime")]
|
||||
WasmExecutionMethod::Compiled => sc_executor_wasmtime::create_runtime::<H>(
|
||||
blob,
|
||||
sc_executor_wasmtime::Config {
|
||||
max_memory_size: None,
|
||||
allow_missing_func_imports,
|
||||
cache_path: cache_path.map(ToOwned::to_owned),
|
||||
semantics: sc_executor_wasmtime::Semantics {
|
||||
extra_heap_pages: heap_pages,
|
||||
fast_instance_reuse: true,
|
||||
deterministic_stack_limit: None,
|
||||
canonicalize_nans: false,
|
||||
parallel_compilation: true,
|
||||
WasmExecutionMethod::Compiled { instantiation_strategy } =>
|
||||
sc_executor_wasmtime::create_runtime::<H>(
|
||||
blob,
|
||||
sc_executor_wasmtime::Config {
|
||||
allow_missing_func_imports,
|
||||
cache_path: cache_path.map(ToOwned::to_owned),
|
||||
semantics: sc_executor_wasmtime::Semantics {
|
||||
extra_heap_pages: heap_pages,
|
||||
instantiation_strategy,
|
||||
deterministic_stack_limit: None,
|
||||
canonicalize_nans: false,
|
||||
parallel_compilation: true,
|
||||
max_memory_size: None,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
.map(|runtime| -> Arc<dyn WasmModule> { Arc::new(runtime) }),
|
||||
)
|
||||
.map(|runtime| -> Arc<dyn WasmModule> { Arc::new(runtime) }),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ wasmtime = { version = "0.35.3", default-features = false, features = [
|
||||
"cranelift",
|
||||
"jitdump",
|
||||
"parallel-compilation",
|
||||
"memory-init-cow",
|
||||
"pooling-allocator",
|
||||
] }
|
||||
sc-allocator = { version = "4.1.0-dev", path = "../../allocator" }
|
||||
sc-executor-common = { version = "0.10.0-dev", path = "../common" }
|
||||
@@ -34,3 +36,4 @@ sp-wasm-interface = { version = "6.0.0", features = ["wasmtime"], path = "../../
|
||||
wat = "1.0"
|
||||
sc-runtime-test = { version = "2.0.0", path = "../runtime-test" }
|
||||
sp-io = { version = "6.0.0", path = "../../../primitives/io" }
|
||||
tempfile = "3.3.0"
|
||||
|
||||
@@ -38,5 +38,5 @@ mod tests;
|
||||
|
||||
pub use runtime::{
|
||||
create_runtime, create_runtime_from_artifact, prepare_runtime_artifact, Config,
|
||||
DeterministicStackLimit, Semantics,
|
||||
DeterministicStackLimit, InstantiationStrategy, Semantics,
|
||||
};
|
||||
|
||||
@@ -80,7 +80,7 @@ impl StoreData {
|
||||
pub(crate) type Store = wasmtime::Store<StoreData>;
|
||||
|
||||
enum Strategy {
|
||||
FastInstanceReuse {
|
||||
LegacyInstanceReuse {
|
||||
instance_wrapper: InstanceWrapper,
|
||||
globals_snapshot: GlobalsSnapshot<wasmtime::Global>,
|
||||
data_segments_snapshot: Arc<DataSegmentsSnapshot>,
|
||||
@@ -136,41 +136,42 @@ struct InstanceSnapshotData {
|
||||
pub struct WasmtimeRuntime {
|
||||
engine: wasmtime::Engine,
|
||||
instance_pre: Arc<wasmtime::InstancePre<StoreData>>,
|
||||
snapshot_data: Option<InstanceSnapshotData>,
|
||||
instantiation_strategy: InternalInstantiationStrategy,
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl WasmModule for WasmtimeRuntime {
|
||||
fn new_instance(&self) -> Result<Box<dyn WasmInstance>> {
|
||||
let strategy = if let Some(ref snapshot_data) = self.snapshot_data {
|
||||
let mut instance_wrapper = InstanceWrapper::new(
|
||||
&self.engine,
|
||||
&self.instance_pre,
|
||||
self.config.max_memory_size,
|
||||
)?;
|
||||
let heap_base = instance_wrapper.extract_heap_base()?;
|
||||
let strategy = match self.instantiation_strategy {
|
||||
InternalInstantiationStrategy::LegacyInstanceReuse(ref snapshot_data) => {
|
||||
let mut instance_wrapper = InstanceWrapper::new(
|
||||
&self.engine,
|
||||
&self.instance_pre,
|
||||
self.config.semantics.max_memory_size,
|
||||
)?;
|
||||
let heap_base = instance_wrapper.extract_heap_base()?;
|
||||
|
||||
// This function panics if the instance was created from a runtime blob different from
|
||||
// which the mutable globals were collected. Here, it is easy to see that there is only
|
||||
// a single runtime blob and thus it's the same that was used for both creating the
|
||||
// instance and collecting the mutable globals.
|
||||
let globals_snapshot = GlobalsSnapshot::take(
|
||||
&snapshot_data.mutable_globals,
|
||||
&mut InstanceGlobals { instance: &mut instance_wrapper },
|
||||
);
|
||||
// This function panics if the instance was created from a runtime blob different
|
||||
// from which the mutable globals were collected. Here, it is easy to see that there
|
||||
// is only a single runtime blob and thus it's the same that was used for both
|
||||
// creating the instance and collecting the mutable globals.
|
||||
let globals_snapshot = GlobalsSnapshot::take(
|
||||
&snapshot_data.mutable_globals,
|
||||
&mut InstanceGlobals { instance: &mut instance_wrapper },
|
||||
);
|
||||
|
||||
Strategy::FastInstanceReuse {
|
||||
instance_wrapper,
|
||||
globals_snapshot,
|
||||
data_segments_snapshot: snapshot_data.data_segments_snapshot.clone(),
|
||||
heap_base,
|
||||
}
|
||||
} else {
|
||||
Strategy::RecreateInstance(InstanceCreator {
|
||||
Strategy::LegacyInstanceReuse {
|
||||
instance_wrapper,
|
||||
globals_snapshot,
|
||||
data_segments_snapshot: snapshot_data.data_segments_snapshot.clone(),
|
||||
heap_base,
|
||||
}
|
||||
},
|
||||
InternalInstantiationStrategy::Builtin => Strategy::RecreateInstance(InstanceCreator {
|
||||
engine: self.engine.clone(),
|
||||
instance_pre: self.instance_pre.clone(),
|
||||
max_memory_size: self.config.max_memory_size,
|
||||
})
|
||||
max_memory_size: self.config.semantics.max_memory_size,
|
||||
}),
|
||||
};
|
||||
|
||||
Ok(Box::new(WasmtimeInstance { strategy }))
|
||||
@@ -186,7 +187,7 @@ pub struct WasmtimeInstance {
|
||||
impl WasmInstance for WasmtimeInstance {
|
||||
fn call(&mut self, method: InvokeMethod, data: &[u8]) -> Result<Vec<u8>> {
|
||||
match &mut self.strategy {
|
||||
Strategy::FastInstanceReuse {
|
||||
Strategy::LegacyInstanceReuse {
|
||||
ref mut instance_wrapper,
|
||||
globals_snapshot,
|
||||
data_segments_snapshot,
|
||||
@@ -225,7 +226,7 @@ impl WasmInstance for WasmtimeInstance {
|
||||
|
||||
fn get_global_const(&mut self, name: &str) -> Result<Option<Value>> {
|
||||
match &mut self.strategy {
|
||||
Strategy::FastInstanceReuse { instance_wrapper, .. } =>
|
||||
Strategy::LegacyInstanceReuse { instance_wrapper, .. } =>
|
||||
instance_wrapper.get_global_val(name),
|
||||
Strategy::RecreateInstance(ref mut instance_creator) =>
|
||||
instance_creator.instantiate()?.get_global_val(name),
|
||||
@@ -239,7 +240,7 @@ impl WasmInstance for WasmtimeInstance {
|
||||
// associated with it.
|
||||
None
|
||||
},
|
||||
Strategy::FastInstanceReuse { instance_wrapper, .. } =>
|
||||
Strategy::LegacyInstanceReuse { instance_wrapper, .. } =>
|
||||
Some(instance_wrapper.base_ptr()),
|
||||
}
|
||||
}
|
||||
@@ -326,6 +327,48 @@ fn common_config(semantics: &Semantics) -> std::result::Result<wasmtime::Config,
|
||||
config.wasm_threads(false);
|
||||
config.wasm_memory64(false);
|
||||
|
||||
let (use_pooling, use_cow) = match semantics.instantiation_strategy {
|
||||
InstantiationStrategy::PoolingCopyOnWrite => (true, true),
|
||||
InstantiationStrategy::Pooling => (true, false),
|
||||
InstantiationStrategy::RecreateInstanceCopyOnWrite => (false, true),
|
||||
InstantiationStrategy::RecreateInstance => (false, false),
|
||||
InstantiationStrategy::LegacyInstanceReuse => (false, false),
|
||||
};
|
||||
|
||||
config.memory_init_cow(use_cow);
|
||||
config.memory_guaranteed_dense_image_size(
|
||||
semantics.max_memory_size.map(|max| max as u64).unwrap_or(u64::MAX),
|
||||
);
|
||||
|
||||
if use_pooling {
|
||||
config.allocation_strategy(wasmtime::InstanceAllocationStrategy::Pooling {
|
||||
strategy: wasmtime::PoolingAllocationStrategy::ReuseAffinity,
|
||||
|
||||
// Pooling needs a bunch of hard limits to be set; if we go over
|
||||
// any of these then the instantiation will fail.
|
||||
instance_limits: wasmtime::InstanceLimits {
|
||||
// Current minimum values for kusama (as of 2022-04-14):
|
||||
// size: 32384
|
||||
// table_elements: 1249
|
||||
// memory_pages: 2070
|
||||
size: 64 * 1024,
|
||||
table_elements: 2048,
|
||||
memory_pages: 4096,
|
||||
|
||||
// We can only have a single of those.
|
||||
tables: 1,
|
||||
memories: 1,
|
||||
|
||||
// This determines how many instances of the module can be
|
||||
// instantiated in parallel from the same `Module`.
|
||||
//
|
||||
// This includes nested instances spawned with `sp_tasks::spawn`
|
||||
// from *within* the runtime.
|
||||
count: 32,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
@@ -373,18 +416,47 @@ pub struct DeterministicStackLimit {
|
||||
pub native_stack_max: u32,
|
||||
}
|
||||
|
||||
/// The instantiation strategy to use for the WASM executor.
|
||||
///
|
||||
/// All of the CoW strategies (with `CopyOnWrite` suffix) are only supported when either:
|
||||
/// a) we're running on Linux,
|
||||
/// b) we're running on an Unix-like system and we're precompiling
|
||||
/// our module beforehand.
|
||||
///
|
||||
/// If the CoW variant of a strategy is unsupported the executor will
|
||||
/// fall back to the non-CoW equivalent.
|
||||
#[non_exhaustive]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum InstantiationStrategy {
|
||||
/// Pool the instances to avoid initializing everything from scratch
|
||||
/// on each instantiation. Use copy-on-write memory when possible.
|
||||
///
|
||||
/// This is the fastest instantiation strategy.
|
||||
PoolingCopyOnWrite,
|
||||
|
||||
/// Recreate the instance from scratch on every instantiation.
|
||||
/// Use copy-on-write memory when possible.
|
||||
RecreateInstanceCopyOnWrite,
|
||||
|
||||
/// Pool the instances to avoid initializing everything from scratch
|
||||
/// on each instantiation.
|
||||
Pooling,
|
||||
|
||||
/// Recreate the instance from scratch on every instantiation. Very slow.
|
||||
RecreateInstance,
|
||||
|
||||
/// Legacy instance reuse mechanism. DEPRECATED. Will be removed. Do not use.
|
||||
LegacyInstanceReuse,
|
||||
}
|
||||
|
||||
enum InternalInstantiationStrategy {
|
||||
LegacyInstanceReuse(InstanceSnapshotData),
|
||||
Builtin,
|
||||
}
|
||||
|
||||
pub struct Semantics {
|
||||
/// Enabling this will lead to some optimization shenanigans that make calling [`WasmInstance`]
|
||||
/// extremely fast.
|
||||
///
|
||||
/// Primarily this is achieved by not recreating the instance for each call and performing a
|
||||
/// bare minimum clean up: reapplying the data segments and restoring the values for global
|
||||
/// variables.
|
||||
///
|
||||
/// Since this feature depends on instrumentation, it can be set only if runtime is
|
||||
/// instantiated using the runtime blob, e.g. using [`create_runtime`].
|
||||
// I.e. if [`CodeSupplyMode::Verbatim`] is used.
|
||||
pub fast_instance_reuse: bool,
|
||||
/// The instantiation strategy to use.
|
||||
pub instantiation_strategy: InstantiationStrategy,
|
||||
|
||||
/// Specifying `Some` will enable deterministic stack height. That is, all executor
|
||||
/// invocations will reach stack overflow at the exactly same point across different wasmtime
|
||||
@@ -418,9 +490,7 @@ pub struct Semantics {
|
||||
/// The number of extra WASM pages which will be allocated
|
||||
/// on top of what is requested by the WASM blob itself.
|
||||
pub extra_heap_pages: u64,
|
||||
}
|
||||
|
||||
pub struct Config {
|
||||
/// The total amount of memory in bytes an instance can request.
|
||||
///
|
||||
/// If specified, the runtime will be able to allocate only that much of wasm memory.
|
||||
@@ -436,7 +506,9 @@ pub struct Config {
|
||||
///
|
||||
/// The default is `None`.
|
||||
pub max_memory_size: Option<usize>,
|
||||
}
|
||||
|
||||
pub struct Config {
|
||||
/// The WebAssembly standard requires all imports of an instantiated module to be resolved,
|
||||
/// otherwise, the instantiation fails. If this option is set to `true`, then this behavior is
|
||||
/// overriden and imports that are requested by the module and not provided by the host
|
||||
@@ -452,24 +524,16 @@ pub struct Config {
|
||||
|
||||
enum CodeSupplyMode<'a> {
|
||||
/// The runtime is instantiated using the given runtime blob.
|
||||
Verbatim {
|
||||
// Rationale to take the `RuntimeBlob` here is so that the client will be able to reuse
|
||||
// the blob e.g. if they did a prevalidation. If they didn't they can pass a `RuntimeBlob`
|
||||
// instance and it will be used anyway in most cases, because we are going to do at least
|
||||
// some instrumentations for both anticipated paths: substrate execution and PVF execution.
|
||||
//
|
||||
// Should there raise a need in performing no instrumentation and the client doesn't need
|
||||
// to do any checks, then we can provide a `Cow` like semantics here: if we need the blob
|
||||
// and the user got `RuntimeBlob` then extract it, or otherwise create it from the given
|
||||
// bytecode.
|
||||
blob: RuntimeBlob,
|
||||
},
|
||||
Fresh(RuntimeBlob),
|
||||
|
||||
/// The code is supplied in a form of a compiled artifact.
|
||||
/// The runtime is instantiated using a precompiled module.
|
||||
///
|
||||
/// This assumes that the code is already prepared for execution and the same `Config` was
|
||||
/// used.
|
||||
Artifact { compiled_artifact: &'a [u8] },
|
||||
///
|
||||
/// We use a `Path` here instead of simply passing a byte slice to allow `wasmtime` to
|
||||
/// map the runtime's linear memory on supported platforms in a copy-on-write fashion.
|
||||
Precompiled(&'a Path),
|
||||
}
|
||||
|
||||
/// Create a new `WasmtimeRuntime` given the code. This function performs translation from Wasm to
|
||||
@@ -484,29 +548,34 @@ pub fn create_runtime<H>(
|
||||
where
|
||||
H: HostFunctions,
|
||||
{
|
||||
// SAFETY: this is safe because it doesn't use `CodeSupplyMode::Artifact`.
|
||||
unsafe { do_create_runtime::<H>(CodeSupplyMode::Verbatim { blob }, config) }
|
||||
// SAFETY: this is safe because it doesn't use `CodeSupplyMode::Precompiled`.
|
||||
unsafe { do_create_runtime::<H>(CodeSupplyMode::Fresh(blob), config) }
|
||||
}
|
||||
|
||||
/// The same as [`create_runtime`] but takes a precompiled artifact, which makes this function
|
||||
/// considerably faster than [`create_runtime`].
|
||||
/// The same as [`create_runtime`] but takes a path to a precompiled artifact,
|
||||
/// which makes this function considerably faster than [`create_runtime`].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that the compiled artifact passed here was produced by
|
||||
/// [`prepare_runtime_artifact`]. Otherwise, there is a risk of arbitrary code execution with all
|
||||
/// implications.
|
||||
/// The caller must ensure that the compiled artifact passed here was:
|
||||
/// 1) produced by [`prepare_runtime_artifact`],
|
||||
/// 2) written to the disk as a file,
|
||||
/// 3) was not modified,
|
||||
/// 4) will not be modified while any runtime using this artifact is alive, or is being
|
||||
/// instantiated.
|
||||
///
|
||||
/// It is ok though if the `compiled_artifact` was created by code of another version or with
|
||||
/// Failure to adhere to these requirements might lead to crashes and arbitrary code execution.
|
||||
///
|
||||
/// 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<H>(
|
||||
compiled_artifact: &[u8],
|
||||
compiled_artifact_path: &Path,
|
||||
config: Config,
|
||||
) -> std::result::Result<WasmtimeRuntime, WasmError>
|
||||
where
|
||||
H: HostFunctions,
|
||||
{
|
||||
do_create_runtime::<H>(CodeSupplyMode::Artifact { compiled_artifact }, config)
|
||||
do_create_runtime::<H>(CodeSupplyMode::Precompiled(compiled_artifact_path), config)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
@@ -520,7 +589,6 @@ unsafe fn do_create_runtime<H>(
|
||||
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 {
|
||||
if let Err(reason) = setup_wasmtime_caching(cache_path, &mut wasmtime_config) {
|
||||
@@ -534,45 +602,71 @@ where
|
||||
let engine = Engine::new(&wasmtime_config)
|
||||
.map_err(|e| WasmError::Other(format!("cannot create the wasmtime engine: {}", e)))?;
|
||||
|
||||
let (module, snapshot_data) = match code_supply_mode {
|
||||
CodeSupplyMode::Verbatim { blob } => {
|
||||
let (module, instantiation_strategy) = match code_supply_mode {
|
||||
CodeSupplyMode::Fresh(blob) => {
|
||||
let blob = prepare_blob_for_compilation(blob, &config.semantics)?;
|
||||
let serialized_blob = blob.clone().serialize();
|
||||
|
||||
let module = wasmtime::Module::new(&engine, &serialized_blob)
|
||||
.map_err(|e| WasmError::Other(format!("cannot create module: {}", e)))?;
|
||||
|
||||
if config.semantics.fast_instance_reuse {
|
||||
let data_segments_snapshot = DataSegmentsSnapshot::take(&blob).map_err(|e| {
|
||||
WasmError::Other(format!("cannot take data segments snapshot: {}", e))
|
||||
})?;
|
||||
let data_segments_snapshot = Arc::new(data_segments_snapshot);
|
||||
let mutable_globals = ExposedMutableGlobalsSet::collect(&blob);
|
||||
match config.semantics.instantiation_strategy {
|
||||
InstantiationStrategy::LegacyInstanceReuse => {
|
||||
let data_segments_snapshot =
|
||||
DataSegmentsSnapshot::take(&blob).map_err(|e| {
|
||||
WasmError::Other(format!("cannot take data segments snapshot: {}", e))
|
||||
})?;
|
||||
let data_segments_snapshot = Arc::new(data_segments_snapshot);
|
||||
let mutable_globals = ExposedMutableGlobalsSet::collect(&blob);
|
||||
|
||||
(module, Some(InstanceSnapshotData { data_segments_snapshot, mutable_globals }))
|
||||
} else {
|
||||
(module, None)
|
||||
(
|
||||
module,
|
||||
InternalInstantiationStrategy::LegacyInstanceReuse(InstanceSnapshotData {
|
||||
data_segments_snapshot,
|
||||
mutable_globals,
|
||||
}),
|
||||
)
|
||||
},
|
||||
InstantiationStrategy::Pooling |
|
||||
InstantiationStrategy::PoolingCopyOnWrite |
|
||||
InstantiationStrategy::RecreateInstance |
|
||||
InstantiationStrategy::RecreateInstanceCopyOnWrite =>
|
||||
(module, InternalInstantiationStrategy::Builtin),
|
||||
}
|
||||
},
|
||||
CodeSupplyMode::Artifact { compiled_artifact } => {
|
||||
// SAFETY: The unsafity of `deserialize` is covered by this function. The
|
||||
CodeSupplyMode::Precompiled(compiled_artifact_path) => {
|
||||
if let InstantiationStrategy::LegacyInstanceReuse =
|
||||
config.semantics.instantiation_strategy
|
||||
{
|
||||
return Err(WasmError::Other("the legacy instance reuse instantiation strategy is incompatible with precompiled modules".into()));
|
||||
}
|
||||
|
||||
// SAFETY: The unsafety of `deserialize_file` is covered by this function. The
|
||||
// responsibilities to maintain the invariants are passed to the caller.
|
||||
let module = wasmtime::Module::deserialize(&engine, compiled_artifact)
|
||||
//
|
||||
// See [`create_runtime_from_artifact`] for more details.
|
||||
let module = wasmtime::Module::deserialize_file(&engine, compiled_artifact_path)
|
||||
.map_err(|e| WasmError::Other(format!("cannot deserialize module: {}", e)))?;
|
||||
|
||||
(module, None)
|
||||
(module, InternalInstantiationStrategy::Builtin)
|
||||
},
|
||||
};
|
||||
|
||||
let mut linker = wasmtime::Linker::new(&engine);
|
||||
crate::imports::prepare_imports::<H>(&mut linker, &module, config.allow_missing_func_imports)?;
|
||||
|
||||
let mut store = crate::instance_wrapper::create_store(module.engine(), config.max_memory_size);
|
||||
let mut store =
|
||||
crate::instance_wrapper::create_store(module.engine(), config.semantics.max_memory_size);
|
||||
let instance_pre = linker
|
||||
.instantiate_pre(&mut store, &module)
|
||||
.map_err(|e| WasmError::Other(format!("cannot preinstantiate module: {}", e)))?;
|
||||
|
||||
Ok(WasmtimeRuntime { engine, instance_pre: Arc::new(instance_pre), snapshot_data, config })
|
||||
Ok(WasmtimeRuntime {
|
||||
engine,
|
||||
instance_pre: Arc::new(instance_pre),
|
||||
instantiation_strategy,
|
||||
config,
|
||||
})
|
||||
}
|
||||
|
||||
fn prepare_blob_for_compilation(
|
||||
@@ -583,16 +677,17 @@ fn prepare_blob_for_compilation(
|
||||
blob = blob.inject_stack_depth_metering(logical_max)?;
|
||||
}
|
||||
|
||||
// If enabled, this should happen after all other passes that may introduce global variables.
|
||||
if semantics.fast_instance_reuse {
|
||||
if let InstantiationStrategy::LegacyInstanceReuse = semantics.instantiation_strategy {
|
||||
// When this strategy is used this must be called after all other passes which may introduce
|
||||
// new global variables, otherwise they will not be reset when we call into the runtime
|
||||
// again.
|
||||
blob.expose_mutable_globals();
|
||||
}
|
||||
|
||||
// We don't actually need the memory to be imported so we can just convert any memory
|
||||
// import into an export with impunity. This simplifies our code since `wasmtime` will
|
||||
// now automatically take care of creating the memory for us, and it also allows us
|
||||
// to potentially enable `wasmtime`'s instance pooling at a later date. (Imported
|
||||
// memories are ineligible for pooling.)
|
||||
// now automatically take care of creating the memory for us, and it is also necessary
|
||||
// to enable `wasmtime`'s instance pooling. (Imported memories are ineligible for pooling.)
|
||||
blob.convert_memory_import_into_export()?;
|
||||
blob.add_extra_heap_pages_to_memory_section(
|
||||
semantics
|
||||
|
||||
@@ -19,18 +19,20 @@
|
||||
use codec::{Decode as _, Encode as _};
|
||||
use sc_executor_common::{error::Error, runtime_blob::RuntimeBlob, wasm_runtime::WasmModule};
|
||||
use sc_runtime_test::wasm_binary_unwrap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::InstantiationStrategy;
|
||||
|
||||
type HostFunctions = sp_io::SubstrateHostFunctions;
|
||||
|
||||
struct RuntimeBuilder {
|
||||
code: Option<String>,
|
||||
fast_instance_reuse: bool,
|
||||
instantiation_strategy: InstantiationStrategy,
|
||||
canonicalize_nans: bool,
|
||||
deterministic_stack: bool,
|
||||
extra_heap_pages: u64,
|
||||
max_memory_size: Option<usize>,
|
||||
precompile_runtime: bool,
|
||||
tmpdir: Option<tempfile::TempDir>,
|
||||
}
|
||||
|
||||
impl RuntimeBuilder {
|
||||
@@ -39,41 +41,42 @@ impl RuntimeBuilder {
|
||||
fn new_on_demand() -> Self {
|
||||
Self {
|
||||
code: None,
|
||||
fast_instance_reuse: false,
|
||||
instantiation_strategy: InstantiationStrategy::RecreateInstance,
|
||||
canonicalize_nans: false,
|
||||
deterministic_stack: false,
|
||||
extra_heap_pages: 1024,
|
||||
max_memory_size: None,
|
||||
precompile_runtime: false,
|
||||
tmpdir: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn use_wat(&mut self, code: String) -> &mut Self {
|
||||
fn use_wat(mut self, code: String) -> Self {
|
||||
self.code = Some(code);
|
||||
self
|
||||
}
|
||||
|
||||
fn canonicalize_nans(&mut self, canonicalize_nans: bool) -> &mut Self {
|
||||
fn canonicalize_nans(mut self, canonicalize_nans: bool) -> Self {
|
||||
self.canonicalize_nans = canonicalize_nans;
|
||||
self
|
||||
}
|
||||
|
||||
fn deterministic_stack(&mut self, deterministic_stack: bool) -> &mut Self {
|
||||
fn deterministic_stack(mut self, deterministic_stack: bool) -> Self {
|
||||
self.deterministic_stack = deterministic_stack;
|
||||
self
|
||||
}
|
||||
|
||||
fn precompile_runtime(&mut self, precompile_runtime: bool) -> &mut Self {
|
||||
fn precompile_runtime(mut self, precompile_runtime: bool) -> Self {
|
||||
self.precompile_runtime = precompile_runtime;
|
||||
self
|
||||
}
|
||||
|
||||
fn max_memory_size(&mut self, max_memory_size: Option<usize>) -> &mut Self {
|
||||
fn max_memory_size(mut self, max_memory_size: Option<usize>) -> Self {
|
||||
self.max_memory_size = max_memory_size;
|
||||
self
|
||||
}
|
||||
|
||||
fn build(&mut self) -> Arc<dyn WasmModule> {
|
||||
fn build<'a>(&'a mut self) -> impl WasmModule + 'a {
|
||||
let blob = {
|
||||
let wasm: Vec<u8>;
|
||||
|
||||
@@ -90,11 +93,10 @@ impl RuntimeBuilder {
|
||||
};
|
||||
|
||||
let config = crate::Config {
|
||||
max_memory_size: self.max_memory_size,
|
||||
allow_missing_func_imports: true,
|
||||
cache_path: None,
|
||||
semantics: crate::Semantics {
|
||||
fast_instance_reuse: self.fast_instance_reuse,
|
||||
instantiation_strategy: self.instantiation_strategy,
|
||||
deterministic_stack_limit: match self.deterministic_stack {
|
||||
true => Some(crate::DeterministicStackLimit {
|
||||
logical_max: 65536,
|
||||
@@ -105,24 +107,31 @@ impl RuntimeBuilder {
|
||||
canonicalize_nans: self.canonicalize_nans,
|
||||
parallel_compilation: true,
|
||||
extra_heap_pages: self.extra_heap_pages,
|
||||
max_memory_size: self.max_memory_size,
|
||||
},
|
||||
};
|
||||
|
||||
let rt = if self.precompile_runtime {
|
||||
if self.precompile_runtime {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = dir.path().join("runtime.bin");
|
||||
|
||||
// Delay the removal of the temporary directory until we're dropped.
|
||||
self.tmpdir = Some(dir);
|
||||
|
||||
let artifact = crate::prepare_runtime_artifact(blob, &config.semantics).unwrap();
|
||||
unsafe { crate::create_runtime_from_artifact::<HostFunctions>(&artifact, config) }
|
||||
std::fs::write(&path, artifact).unwrap();
|
||||
unsafe { crate::create_runtime_from_artifact::<HostFunctions>(&path, config) }
|
||||
} else {
|
||||
crate::create_runtime::<HostFunctions>(blob, config)
|
||||
}
|
||||
.expect("cannot create runtime");
|
||||
|
||||
Arc::new(rt) as Arc<dyn WasmModule>
|
||||
.expect("cannot create runtime")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nan_canonicalization() {
|
||||
let runtime = RuntimeBuilder::new_on_demand().canonicalize_nans(true).build();
|
||||
let mut builder = RuntimeBuilder::new_on_demand().canonicalize_nans(true);
|
||||
let runtime = builder.build();
|
||||
|
||||
let mut instance = runtime.new_instance().expect("failed to instantiate a runtime");
|
||||
|
||||
@@ -161,10 +170,11 @@ fn test_nan_canonicalization() {
|
||||
fn test_stack_depth_reaching() {
|
||||
const TEST_GUARD_PAGE_SKIP: &str = include_str!("test-guard-page-skip.wat");
|
||||
|
||||
let runtime = RuntimeBuilder::new_on_demand()
|
||||
let mut builder = RuntimeBuilder::new_on_demand()
|
||||
.use_wat(TEST_GUARD_PAGE_SKIP.to_string())
|
||||
.deterministic_stack(true)
|
||||
.build();
|
||||
.deterministic_stack(true);
|
||||
|
||||
let runtime = builder.build();
|
||||
let mut instance = runtime.new_instance().expect("failed to instantiate a runtime");
|
||||
|
||||
match instance.call_export("test-many-locals", &[]).unwrap_err() {
|
||||
@@ -202,11 +212,12 @@ fn test_max_memory_pages(import_memory: bool, precompile_runtime: bool) {
|
||||
wat: String,
|
||||
precompile_runtime: bool,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let runtime = RuntimeBuilder::new_on_demand()
|
||||
let mut builder = RuntimeBuilder::new_on_demand()
|
||||
.use_wat(wat)
|
||||
.max_memory_size(max_memory_size)
|
||||
.precompile_runtime(precompile_runtime)
|
||||
.build();
|
||||
.precompile_runtime(precompile_runtime);
|
||||
|
||||
let runtime = builder.build();
|
||||
let mut instance = runtime.new_instance()?;
|
||||
let _ = instance.call_export("main", &[])?;
|
||||
Ok(())
|
||||
@@ -377,15 +388,15 @@ fn test_instances_without_reuse_are_not_leaked() {
|
||||
let runtime = crate::create_runtime::<HostFunctions>(
|
||||
RuntimeBlob::uncompress_if_needed(wasm_binary_unwrap()).unwrap(),
|
||||
crate::Config {
|
||||
max_memory_size: None,
|
||||
allow_missing_func_imports: true,
|
||||
cache_path: None,
|
||||
semantics: crate::Semantics {
|
||||
fast_instance_reuse: false,
|
||||
instantiation_strategy: InstantiationStrategy::RecreateInstance,
|
||||
deterministic_stack_limit: None,
|
||||
canonicalize_nans: false,
|
||||
parallel_compilation: true,
|
||||
extra_heap_pages: 2048,
|
||||
max_memory_size: None,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
pub use sc_client_api::execution_extensions::{ExecutionStrategies, ExecutionStrategy};
|
||||
pub use sc_client_db::{Database, DatabaseSource, KeepBlocks, PruningMode};
|
||||
pub use sc_executor::WasmExecutionMethod;
|
||||
#[cfg(feature = "wasmtime")]
|
||||
pub use sc_executor::WasmtimeInstantiationStrategy;
|
||||
pub use sc_network::{
|
||||
config::{
|
||||
MultiaddrWithPeerId, NetworkConfiguration, NodeKeyConfig, NonDefaultSetConfig, Role,
|
||||
|
||||
@@ -23,7 +23,9 @@ use frame_benchmarking::{
|
||||
};
|
||||
use frame_support::traits::StorageInfo;
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use sc_cli::{CliConfiguration, ExecutionStrategy, Result, SharedParams};
|
||||
use sc_cli::{
|
||||
execution_method_from_cli, CliConfiguration, ExecutionStrategy, Result, SharedParams,
|
||||
};
|
||||
use sc_client_db::BenchmarkingState;
|
||||
use sc_executor::NativeElseWasmExecutor;
|
||||
use sc_service::{Configuration, NativeExecutionDispatch};
|
||||
@@ -121,7 +123,6 @@ impl PalletCmd {
|
||||
}
|
||||
|
||||
let spec = config.chain_spec;
|
||||
let wasm_method = self.wasm_method.into();
|
||||
let strategy = self.execution.unwrap_or(ExecutionStrategy::Native);
|
||||
let pallet = self.pallet.clone().unwrap_or_default();
|
||||
let pallet = pallet.as_bytes();
|
||||
@@ -141,7 +142,7 @@ impl PalletCmd {
|
||||
let state_without_tracking =
|
||||
BenchmarkingState::<BB>::new(genesis_storage, cache_size, self.record_proof, false)?;
|
||||
let executor = NativeElseWasmExecutor::<ExecDispatch>::new(
|
||||
wasm_method,
|
||||
execution_method_from_cli(self.wasm_method, self.wasmtime_instantiation_strategy),
|
||||
self.heap_pages,
|
||||
2, // The runtime instances cache size.
|
||||
2, // The runtime cache size
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
mod command;
|
||||
mod writer;
|
||||
|
||||
use sc_cli::{ExecutionStrategy, WasmExecutionMethod, DEFAULT_WASM_EXECUTION_METHOD};
|
||||
use sc_cli::{
|
||||
ExecutionStrategy, WasmExecutionMethod, WasmtimeInstantiationStrategy,
|
||||
DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, DEFAULT_WASM_EXECUTION_METHOD,
|
||||
};
|
||||
use std::{fmt::Debug, path::PathBuf};
|
||||
|
||||
// Add a more relaxed parsing for pallet names by allowing pallet directory names with `-` to be
|
||||
@@ -131,6 +134,17 @@ pub struct PalletCmd {
|
||||
)]
|
||||
pub wasm_method: WasmExecutionMethod,
|
||||
|
||||
/// The WASM instantiation method to use.
|
||||
///
|
||||
/// Only has an effect when `wasm-execution` is set to `compiled`.
|
||||
#[clap(
|
||||
long = "wasm-instantiation-strategy",
|
||||
value_name = "STRATEGY",
|
||||
default_value_t = DEFAULT_WASMTIME_INSTANTIATION_STRATEGY,
|
||||
arg_enum,
|
||||
)]
|
||||
pub wasmtime_instantiation_strategy: WasmtimeInstantiationStrategy,
|
||||
|
||||
/// Limit the memory the database cache can use.
|
||||
#[clap(long = "db-cache", value_name = "MiB", default_value = "1024")]
|
||||
pub database_cache_size: u32,
|
||||
|
||||
@@ -271,7 +271,9 @@ use remote_externalities::{
|
||||
};
|
||||
use sc_chain_spec::ChainSpec;
|
||||
use sc_cli::{
|
||||
CliConfiguration, ExecutionStrategy, WasmExecutionMethod, DEFAULT_WASM_EXECUTION_METHOD,
|
||||
execution_method_from_cli, CliConfiguration, ExecutionStrategy, WasmExecutionMethod,
|
||||
WasmtimeInstantiationStrategy, DEFAULT_WASMTIME_INSTANTIATION_STRATEGY,
|
||||
DEFAULT_WASM_EXECUTION_METHOD,
|
||||
};
|
||||
use sc_executor::NativeElseWasmExecutor;
|
||||
use sc_service::{Configuration, NativeExecutionDispatch};
|
||||
@@ -400,6 +402,17 @@ pub struct SharedParams {
|
||||
)]
|
||||
pub wasm_method: WasmExecutionMethod,
|
||||
|
||||
/// The WASM instantiation method to use.
|
||||
///
|
||||
/// Only has an effect when `wasm-execution` is set to `compiled`.
|
||||
#[clap(
|
||||
long = "wasm-instantiation-strategy",
|
||||
value_name = "STRATEGY",
|
||||
default_value_t = DEFAULT_WASMTIME_INSTANTIATION_STRATEGY,
|
||||
arg_enum,
|
||||
)]
|
||||
pub wasmtime_instantiation_strategy: WasmtimeInstantiationStrategy,
|
||||
|
||||
/// The number of 64KB pages to allocate for Wasm execution. Defaults to
|
||||
/// [`sc_service::Configuration.default_heap_pages`].
|
||||
#[clap(long)]
|
||||
@@ -675,13 +688,12 @@ pub(crate) fn build_executor<D: NativeExecutionDispatch + 'static>(
|
||||
shared: &SharedParams,
|
||||
config: &sc_service::Configuration,
|
||||
) -> NativeElseWasmExecutor<D> {
|
||||
let wasm_method = shared.wasm_method;
|
||||
let heap_pages = shared.heap_pages.or(config.default_heap_pages);
|
||||
let max_runtime_instances = config.max_runtime_instances;
|
||||
let runtime_cache_size = config.runtime_cache_size;
|
||||
|
||||
NativeElseWasmExecutor::<D>::new(
|
||||
wasm_method.into(),
|
||||
execution_method_from_cli(shared.wasm_method, shared.wasmtime_instantiation_strategy),
|
||||
heap_pages,
|
||||
max_runtime_instances,
|
||||
runtime_cache_size,
|
||||
|
||||
Reference in New Issue
Block a user