mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-09 18:47:59 +00:00
Remove wasmi backend from sc-executor (#13800)
* refactor: use builder api for all executors * improve a lot * remove unused args * cleanup deps * fix inconsistency about heap alloc * add `heap_pages` back to try-runtime * fix * chore: reduce duplicated code for sc-service-test * cleanup code * fmt * improve test executor * improve * use #[deprecated] * set runtime_cache_size: 4 * wip * fix and improve * remove sc-executor-wasmi deps * clean up bench and tests * delete "client/executor/wasmi" * cleanup * refactor builder * fix * fix bench * fix tests * fix warnings * fix warnings * fix * fix * remove wasmi and fix tests * unused imports * improve by suggestions * Update client/cli/src/arg_enums.rs --------- Co-authored-by: Bastian Köcher <git@kchr.de> Co-authored-by: parity-processbot <>
This commit is contained in:
Generated
+3
-31
@@ -7840,14 +7840,14 @@ version = "0.3.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e30165d31df606f5726b090ec7592c308a0eaf61721ff64c9a3018e344a8753e"
|
||||
dependencies = [
|
||||
"portable-atomic 1.3.1",
|
||||
"portable-atomic 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.3.1"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bbda379e6e462c97ea6afe9f6233619b202bbc4968d7caa6917788d2070a044"
|
||||
checksum = "dc59d1bcc64fc5d021d67521f818db868368028108d37f0e98d74e33f68297b5"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
@@ -8413,18 +8413,6 @@ version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
|
||||
|
||||
[[package]]
|
||||
name = "region"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76e189c2369884dce920945e2ddf79b3dff49e071a167dd1817fa9c4c00d512e"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
"mach",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "resolv-conf"
|
||||
version = "0.7.0"
|
||||
@@ -9316,7 +9304,6 @@ dependencies = [
|
||||
"paste",
|
||||
"regex",
|
||||
"sc-executor-common",
|
||||
"sc-executor-wasmi",
|
||||
"sc-executor-wasmtime",
|
||||
"sc-runtime-test",
|
||||
"sc-tracing",
|
||||
@@ -9337,7 +9324,6 @@ dependencies = [
|
||||
"tempfile",
|
||||
"tracing",
|
||||
"tracing-subscriber 0.2.25",
|
||||
"wasmi 0.13.2",
|
||||
"wat",
|
||||
]
|
||||
|
||||
@@ -9350,19 +9336,6 @@ dependencies = [
|
||||
"sp-wasm-interface",
|
||||
"thiserror",
|
||||
"wasm-instrument 0.3.0",
|
||||
"wasmi 0.13.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sc-executor-wasmi"
|
||||
version = "0.10.0-dev"
|
||||
dependencies = [
|
||||
"log",
|
||||
"sc-allocator",
|
||||
"sc-executor-common",
|
||||
"sp-runtime-interface",
|
||||
"sp-wasm-interface",
|
||||
"wasmi 0.13.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -13092,7 +13065,6 @@ dependencies = [
|
||||
"memory_units",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
"region",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -38,7 +38,6 @@ members = [
|
||||
"client/executor",
|
||||
"client/executor/common",
|
||||
"client/executor/runtime-test",
|
||||
"client/executor/wasmi",
|
||||
"client/executor/wasmtime",
|
||||
"client/informant",
|
||||
"client/keystore",
|
||||
|
||||
@@ -69,7 +69,6 @@ pub fn new_partial(
|
||||
.transpose()?;
|
||||
|
||||
let executor = sc_service::new_native_or_wasm_executor(&config);
|
||||
|
||||
let (client, backend, keystore_container, task_manager) =
|
||||
sc_service::new_full_parts::<Block, RuntimeApi, _>(
|
||||
config,
|
||||
|
||||
@@ -182,7 +182,6 @@ fn bench_execute_block(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("execute blocks");
|
||||
let execution_methods = vec![
|
||||
ExecutionMethod::Native,
|
||||
ExecutionMethod::Wasm(WasmExecutionMethod::Interpreted),
|
||||
ExecutionMethod::Wasm(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite,
|
||||
}),
|
||||
|
||||
@@ -54,7 +54,7 @@ pub const DEFAULT_WASMTIME_INSTANTIATION_STRATEGY: WasmtimeInstantiationStrategy
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
#[value(rename_all = "kebab-case")]
|
||||
pub enum WasmExecutionMethod {
|
||||
/// Uses an interpreter.
|
||||
/// Uses an interpreter which now is deprecated.
|
||||
#[clap(name = "interpreted-i-know-what-i-do")]
|
||||
Interpreted,
|
||||
/// Uses a compiled runtime.
|
||||
@@ -76,21 +76,24 @@ 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,
|
||||
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,
|
||||
},
|
||||
if let WasmExecutionMethod::Interpreted = execution_method {
|
||||
log::warn!(
|
||||
"`interpreted-i-know-what-i-do` is deprecated and will be removed in the future. Defaults to `compiled` execution mode."
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,9 @@ targets = ["x86_64-unknown-linux-gnu"]
|
||||
lru = "0.8.1"
|
||||
parking_lot = "0.12.1"
|
||||
tracing = "0.1.29"
|
||||
wasmi = "0.13.2"
|
||||
|
||||
codec = { package = "parity-scale-codec", version = "3.2.2" }
|
||||
sc-executor-common = { version = "0.10.0-dev", path = "common" }
|
||||
sc-executor-wasmi = { version = "0.10.0-dev", path = "wasmi" }
|
||||
sc-executor-wasmtime = { version = "0.10.0-dev", path = "wasmtime" }
|
||||
sp-api = { version = "4.0.0-dev", path = "../../primitives/api" }
|
||||
sp-core = { version = "7.0.0", path = "../../primitives/core" }
|
||||
|
||||
@@ -33,7 +33,6 @@ use std::sync::{
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Method {
|
||||
Interpreted,
|
||||
Compiled { instantiation_strategy: InstantiationStrategy, precompile: bool },
|
||||
}
|
||||
|
||||
@@ -50,17 +49,10 @@ fn initialize(
|
||||
method: Method,
|
||||
) -> Box<dyn WasmModule> {
|
||||
let blob = RuntimeBlob::uncompress_if_needed(runtime).unwrap();
|
||||
let host_functions = sp_io::SubstrateHostFunctions::host_functions();
|
||||
|
||||
let allow_missing_func_imports = true;
|
||||
|
||||
match method {
|
||||
Method::Interpreted => sc_executor_wasmi::create_runtime(
|
||||
blob,
|
||||
DEFAULT_HEAP_ALLOC_STRATEGY,
|
||||
host_functions,
|
||||
allow_missing_func_imports,
|
||||
)
|
||||
.map(|runtime| -> Box<dyn WasmModule> { Box::new(runtime) }),
|
||||
Method::Compiled { instantiation_strategy, precompile } => {
|
||||
let config = sc_executor_wasmtime::Config {
|
||||
allow_missing_func_imports,
|
||||
@@ -215,7 +207,6 @@ fn bench_call_instance(c: &mut Criterion) {
|
||||
precompile: true,
|
||||
},
|
||||
),
|
||||
("interpreted", Method::Interpreted),
|
||||
];
|
||||
|
||||
let runtimes = [("kusama_runtime", kusama_runtime()), ("test_runtime", test_runtime())];
|
||||
|
||||
@@ -16,7 +16,6 @@ targets = ["x86_64-unknown-linux-gnu"]
|
||||
[dependencies]
|
||||
thiserror = "1.0.30"
|
||||
wasm-instrument = "0.3"
|
||||
wasmi = "0.13.2"
|
||||
sc-allocator = { version = "4.1.0-dev", path = "../../allocator" }
|
||||
sp-maybe-compressed-blob = { version = "4.1.0-dev", path = "../../../primitives/maybe-compressed-blob" }
|
||||
sp-wasm-interface = { version = "7.0.0", path = "../../../primitives/wasm-interface" }
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
|
||||
//! Rust executor possible errors.
|
||||
|
||||
use wasmi;
|
||||
|
||||
/// Result type alias.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
@@ -27,9 +25,6 @@ pub type Result<T> = std::result::Result<T, Error>;
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
Wasmi(#[from] wasmi::Error),
|
||||
|
||||
#[error("Error calling api function: {0}")]
|
||||
ApiError(Box<dyn std::error::Error + Send + Sync>),
|
||||
|
||||
@@ -48,9 +43,6 @@ pub enum Error {
|
||||
#[error("Invalid type returned (should be u64)")]
|
||||
InvalidReturn,
|
||||
|
||||
#[error("Runtime error")]
|
||||
Runtime,
|
||||
|
||||
#[error("Runtime panicked: {0}")]
|
||||
RuntimePanicked(String),
|
||||
|
||||
@@ -109,8 +101,6 @@ pub enum Error {
|
||||
OutputExceedsBounds,
|
||||
}
|
||||
|
||||
impl wasmi::HostError for Error {}
|
||||
|
||||
impl From<&'static str> for Error {
|
||||
fn from(err: &'static str) -> Error {
|
||||
Error::Other(err.into())
|
||||
|
||||
@@ -26,9 +26,6 @@
|
||||
//!
|
||||
//! To give you some examples:
|
||||
//!
|
||||
//! - wasmi allows reaching to non-exported mutable globals so that we could reset them. Wasmtime
|
||||
//! doesn’t support that.
|
||||
//!
|
||||
//! We need to reset the globals because when we
|
||||
//! execute the Substrate Runtime, we do not drop and create the instance anew, instead
|
||||
//! we restore some selected parts of the state.
|
||||
|
||||
@@ -18,34 +18,15 @@
|
||||
|
||||
//! Tests that are only relevant for Linux.
|
||||
|
||||
mod smaps;
|
||||
|
||||
use super::mk_test_runtime;
|
||||
use crate::WasmExecutionMethod;
|
||||
use codec::Encode as _;
|
||||
use sc_executor_common::wasm_runtime::DEFAULT_HEAP_ALLOC_STRATEGY;
|
||||
|
||||
mod smaps;
|
||||
|
||||
use self::smaps::Smaps;
|
||||
|
||||
#[test]
|
||||
fn memory_consumption_interpreted() {
|
||||
let _ = sp_tracing::try_init_simple();
|
||||
|
||||
if std::env::var("RUN_TEST").is_ok() {
|
||||
memory_consumption(WasmExecutionMethod::Interpreted);
|
||||
} else {
|
||||
// We need to run the test in isolation, to not getting interfered by the other tests.
|
||||
let executable = std::env::current_exe().unwrap();
|
||||
let output = std::process::Command::new(executable)
|
||||
.env("RUN_TEST", "1")
|
||||
.args(&["--nocapture", "memory_consumption_interpreted"])
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
assert!(output.status.success());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memory_consumption_compiled() {
|
||||
let _ = sp_tracing::try_init_simple();
|
||||
|
||||
@@ -24,7 +24,7 @@ use codec::{Decode, Encode};
|
||||
use sc_executor_common::{
|
||||
error::Error,
|
||||
runtime_blob::RuntimeBlob,
|
||||
wasm_runtime::{HeapAllocStrategy, WasmModule, DEFAULT_HEAP_ALLOC_STRATEGY},
|
||||
wasm_runtime::{HeapAllocStrategy, WasmModule},
|
||||
};
|
||||
use sc_runtime_test::wasm_binary_unwrap;
|
||||
use sp_core::{
|
||||
@@ -50,12 +50,6 @@ type HostFunctions = sp_io::SubstrateHostFunctions;
|
||||
macro_rules! test_wasm_execution {
|
||||
($method_name:ident) => {
|
||||
paste::item! {
|
||||
#[test]
|
||||
fn [<$method_name _interpreted>]() {
|
||||
let _ = sp_tracing::try_init_simple();
|
||||
$method_name(WasmExecutionMethod::Interpreted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn [<$method_name _compiled_recreate_instance_cow>]() {
|
||||
let _ = sp_tracing::try_init_simple();
|
||||
@@ -97,15 +91,6 @@ macro_rules! test_wasm_execution {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(interpreted_only $method_name:ident) => {
|
||||
paste::item! {
|
||||
#[test]
|
||||
fn [<$method_name _interpreted>]() {
|
||||
$method_name(WasmExecutionMethod::Interpreted);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn call_in_wasm<E: Externalities>(
|
||||
@@ -144,8 +129,8 @@ fn call_not_existing_function(wasm_method: WasmExecutionMethod) {
|
||||
match call_in_wasm("test_calling_missing_external", &[], wasm_method, &mut ext).unwrap_err() {
|
||||
Error::AbortedDueToTrap(error) => {
|
||||
let expected = match wasm_method {
|
||||
WasmExecutionMethod::Interpreted => "Other: Function `missing_external` is only a stub. Calling a stub is not allowed.",
|
||||
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);
|
||||
},
|
||||
@@ -163,8 +148,8 @@ fn call_yet_another_not_existing_function(wasm_method: WasmExecutionMethod) {
|
||||
{
|
||||
Error::AbortedDueToTrap(error) => {
|
||||
let expected = match wasm_method {
|
||||
WasmExecutionMethod::Interpreted => "Other: Function `yet_another_missing_external` is only a stub. Calling a stub is not allowed.",
|
||||
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,9 +458,6 @@ fn should_trap_when_heap_exhausted(wasm_method: WasmExecutionMethod) {
|
||||
r#"host code panicked while being called by the runtime: Failed to allocate memory: "Allocator ran out of space""#
|
||||
);
|
||||
},
|
||||
Error::RuntimePanicked(error) if wasm_method == WasmExecutionMethod::Interpreted => {
|
||||
assert_eq!(error, r#"Failed to allocate memory: "Allocator ran out of space""#);
|
||||
},
|
||||
error => panic!("unexpected error: {:?}", error),
|
||||
}
|
||||
}
|
||||
@@ -558,25 +540,6 @@ fn restoration_of_globals(wasm_method: WasmExecutionMethod) {
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
||||
test_wasm_execution!(interpreted_only heap_is_reset_between_calls);
|
||||
fn heap_is_reset_between_calls(wasm_method: WasmExecutionMethod) {
|
||||
let runtime = mk_test_runtime(wasm_method, DEFAULT_HEAP_ALLOC_STRATEGY);
|
||||
let mut instance = runtime.new_instance().unwrap();
|
||||
|
||||
let heap_base = instance
|
||||
.get_global_const("__heap_base")
|
||||
.expect("`__heap_base` is valid")
|
||||
.expect("`__heap_base` exists")
|
||||
.as_i32()
|
||||
.expect("`__heap_base` is an `i32`");
|
||||
|
||||
let params = (heap_base as u32, 512u32 * 64 * 1024).encode();
|
||||
instance.call_export("check_and_set_in_heap", ¶ms).unwrap();
|
||||
|
||||
// Cal it a second time to check that the heap was freed.
|
||||
instance.call_export("check_and_set_in_heap", ¶ms).unwrap();
|
||||
}
|
||||
|
||||
test_wasm_execution!(parallel_execution);
|
||||
fn parallel_execution(wasm_method: WasmExecutionMethod) {
|
||||
let executor = Arc::new(
|
||||
@@ -787,7 +750,6 @@ fn unreachable_intrinsic(wasm_method: WasmExecutionMethod) {
|
||||
match call_in_wasm("test_unreachable_intrinsic", &[], wasm_method, &mut ext).unwrap_err() {
|
||||
Error::AbortedDueToTrap(error) => {
|
||||
let expected = match wasm_method {
|
||||
WasmExecutionMethod::Interpreted => "unreachable",
|
||||
WasmExecutionMethod::Compiled { .. } =>
|
||||
"wasm trap: wasm `unreachable` instruction executed",
|
||||
};
|
||||
@@ -814,9 +776,6 @@ fn return_huge_len(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = ext.ext();
|
||||
|
||||
match call_in_wasm("test_return_huge_len", &[], wasm_method, &mut ext).unwrap_err() {
|
||||
Error::Runtime => {
|
||||
assert_matches!(wasm_method, WasmExecutionMethod::Interpreted);
|
||||
},
|
||||
Error::OutputExceedsBounds => {
|
||||
assert_matches!(wasm_method, WasmExecutionMethod::Compiled { .. });
|
||||
},
|
||||
@@ -843,9 +802,6 @@ fn return_max_memory_offset_plus_one(wasm_method: WasmExecutionMethod) {
|
||||
match call_in_wasm("test_return_max_memory_offset_plus_one", &[], wasm_method, &mut ext)
|
||||
.unwrap_err()
|
||||
{
|
||||
Error::Runtime => {
|
||||
assert_matches!(wasm_method, WasmExecutionMethod::Interpreted);
|
||||
},
|
||||
Error::OutputExceedsBounds => {
|
||||
assert_matches!(wasm_method, WasmExecutionMethod::Compiled { .. });
|
||||
},
|
||||
@@ -859,9 +815,6 @@ fn return_overflow(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = ext.ext();
|
||||
|
||||
match call_in_wasm("test_return_overflow", &[], wasm_method, &mut ext).unwrap_err() {
|
||||
Error::Runtime => {
|
||||
assert_matches!(wasm_method, WasmExecutionMethod::Interpreted);
|
||||
},
|
||||
Error::OutputExceedsBounds => {
|
||||
assert_matches!(wasm_method, WasmExecutionMethod::Compiled { .. });
|
||||
},
|
||||
|
||||
@@ -49,7 +49,6 @@ pub use sp_core::traits::Externalities;
|
||||
pub use sp_version::{NativeVersion, RuntimeVersion};
|
||||
#[doc(hidden)]
|
||||
pub use sp_wasm_interface;
|
||||
pub use wasmi;
|
||||
|
||||
pub use sc_executor_common::{
|
||||
error,
|
||||
|
||||
@@ -43,8 +43,6 @@ use sp_wasm_interface::HostFunctions;
|
||||
/// Specification of different methods of executing the runtime Wasm code.
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
|
||||
pub enum WasmExecutionMethod {
|
||||
/// Uses the Wasmi interpreter.
|
||||
Interpreted,
|
||||
/// Uses the Wasmtime compiled runtime.
|
||||
Compiled {
|
||||
/// The instantiation strategy to use.
|
||||
@@ -53,8 +51,10 @@ pub enum WasmExecutionMethod {
|
||||
}
|
||||
|
||||
impl Default for WasmExecutionMethod {
|
||||
fn default() -> WasmExecutionMethod {
|
||||
WasmExecutionMethod::Interpreted
|
||||
fn default() -> Self {
|
||||
Self::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::PoolingCopyOnWrite,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,21 +299,6 @@ where
|
||||
H: HostFunctions,
|
||||
{
|
||||
match wasm_method {
|
||||
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.
|
||||
let _ = cache_path;
|
||||
|
||||
sc_executor_wasmi::create_runtime(
|
||||
blob,
|
||||
heap_alloc_strategy,
|
||||
H::host_functions(),
|
||||
allow_missing_func_imports,
|
||||
)
|
||||
.map(|runtime| -> Box<dyn WasmModule> { Box::new(runtime) })
|
||||
},
|
||||
WasmExecutionMethod::Compiled { instantiation_strategy } =>
|
||||
sc_executor_wasmtime::create_runtime::<H>(
|
||||
blob,
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
[package]
|
||||
name = "sc-executor-wasmi"
|
||||
version = "0.10.0-dev"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
homepage = "https://substrate.io"
|
||||
repository = "https://github.com/paritytech/substrate/"
|
||||
description = "This crate provides an implementation of `WasmRuntime` that is baked by wasmi."
|
||||
documentation = "https://docs.rs/sc-executor-wasmi"
|
||||
readme = "README.md"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.17"
|
||||
wasmi = { version = "0.13.2", features = [ "virtual_memory" ] }
|
||||
sc-allocator = { version = "4.1.0-dev", path = "../../allocator" }
|
||||
sc-executor-common = { version = "0.10.0-dev", path = "../common" }
|
||||
sp-runtime-interface = { version = "7.0.0", path = "../../../primitives/runtime-interface" }
|
||||
sp-wasm-interface = { version = "7.0.0", path = "../../../primitives/wasm-interface" }
|
||||
@@ -1,3 +0,0 @@
|
||||
This crate provides an implementation of `WasmModule` that is baked by wasmi.
|
||||
|
||||
License: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
@@ -1,599 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! This crate provides an implementation of `WasmModule` that is baked by wasmi.
|
||||
|
||||
use std::{cell::RefCell, str, sync::Arc};
|
||||
|
||||
use log::{error, trace};
|
||||
use wasmi::{
|
||||
memory_units::Pages,
|
||||
FuncInstance, ImportsBuilder, MemoryRef, Module, ModuleInstance, ModuleRef,
|
||||
RuntimeValue::{self, I32, I64},
|
||||
TableRef,
|
||||
};
|
||||
|
||||
use sc_allocator::{AllocationStats, FreeingBumpHeapAllocator};
|
||||
use sc_executor_common::{
|
||||
error::{Error, MessageWithBacktrace, WasmError},
|
||||
runtime_blob::{DataSegmentsSnapshot, RuntimeBlob},
|
||||
wasm_runtime::{HeapAllocStrategy, InvokeMethod, WasmInstance, WasmModule},
|
||||
};
|
||||
use sp_runtime_interface::unpack_ptr_and_len;
|
||||
use sp_wasm_interface::{Function, FunctionContext, Pointer, Result as WResult, WordSize};
|
||||
|
||||
/// Wrapper around [`MemorRef`] that implements [`sc_allocator::Memory`].
|
||||
struct MemoryWrapper<'a>(&'a MemoryRef);
|
||||
|
||||
impl sc_allocator::Memory for MemoryWrapper<'_> {
|
||||
fn with_access_mut<R>(&mut self, run: impl FnOnce(&mut [u8]) -> R) -> R {
|
||||
self.0.with_direct_access_mut(run)
|
||||
}
|
||||
|
||||
fn with_access<R>(&self, run: impl FnOnce(&[u8]) -> R) -> R {
|
||||
self.0.with_direct_access(run)
|
||||
}
|
||||
|
||||
fn pages(&self) -> u32 {
|
||||
self.0.current_size().0 as _
|
||||
}
|
||||
|
||||
fn max_pages(&self) -> Option<u32> {
|
||||
self.0.maximum().map(|p| p.0 as _)
|
||||
}
|
||||
|
||||
fn grow(&mut self, additional: u32) -> Result<(), ()> {
|
||||
self.0
|
||||
.grow(Pages(additional as _))
|
||||
.map_err(|e| {
|
||||
log::error!(
|
||||
target: "wasm-executor",
|
||||
"Failed to grow memory by {} pages: {}",
|
||||
additional,
|
||||
e,
|
||||
)
|
||||
})
|
||||
.map(drop)
|
||||
}
|
||||
}
|
||||
|
||||
struct FunctionExecutor {
|
||||
heap: RefCell<sc_allocator::FreeingBumpHeapAllocator>,
|
||||
memory: MemoryRef,
|
||||
host_functions: Arc<Vec<&'static dyn Function>>,
|
||||
allow_missing_func_imports: bool,
|
||||
missing_functions: Arc<Vec<String>>,
|
||||
panic_message: Option<String>,
|
||||
}
|
||||
|
||||
impl FunctionExecutor {
|
||||
fn new(
|
||||
m: MemoryRef,
|
||||
heap_base: u32,
|
||||
host_functions: Arc<Vec<&'static dyn Function>>,
|
||||
allow_missing_func_imports: bool,
|
||||
missing_functions: Arc<Vec<String>>,
|
||||
) -> Result<Self, Error> {
|
||||
Ok(FunctionExecutor {
|
||||
heap: RefCell::new(FreeingBumpHeapAllocator::new(heap_base)),
|
||||
memory: m,
|
||||
host_functions,
|
||||
allow_missing_func_imports,
|
||||
missing_functions,
|
||||
panic_message: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionContext for FunctionExecutor {
|
||||
fn read_memory_into(&self, address: Pointer<u8>, dest: &mut [u8]) -> WResult<()> {
|
||||
self.memory.get_into(address.into(), dest).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn write_memory(&mut self, address: Pointer<u8>, data: &[u8]) -> WResult<()> {
|
||||
self.memory.set(address.into(), data).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn allocate_memory(&mut self, size: WordSize) -> WResult<Pointer<u8>> {
|
||||
self.heap
|
||||
.borrow_mut()
|
||||
.allocate(&mut MemoryWrapper(&self.memory), size)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn deallocate_memory(&mut self, ptr: Pointer<u8>) -> WResult<()> {
|
||||
self.heap
|
||||
.borrow_mut()
|
||||
.deallocate(&mut MemoryWrapper(&self.memory), ptr)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn register_panic_error_message(&mut self, message: &str) {
|
||||
self.panic_message = Some(message.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
/// Will be used on initialization of a module to resolve function and memory imports.
|
||||
struct Resolver<'a> {
|
||||
/// All the hot functions that we export for the WASM blob.
|
||||
host_functions: &'a [&'static dyn Function],
|
||||
/// Should we allow missing function imports?
|
||||
///
|
||||
/// If `true`, we return a stub that will return an error when being called.
|
||||
allow_missing_func_imports: bool,
|
||||
/// All the names of functions for that we did not provide a host function.
|
||||
missing_functions: RefCell<Vec<String>>,
|
||||
}
|
||||
|
||||
impl<'a> Resolver<'a> {
|
||||
fn new(
|
||||
host_functions: &'a [&'static dyn Function],
|
||||
allow_missing_func_imports: bool,
|
||||
) -> Resolver<'a> {
|
||||
Resolver {
|
||||
host_functions,
|
||||
allow_missing_func_imports,
|
||||
missing_functions: RefCell::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> wasmi::ModuleImportResolver for Resolver<'a> {
|
||||
fn resolve_func(
|
||||
&self,
|
||||
name: &str,
|
||||
signature: &wasmi::Signature,
|
||||
) -> std::result::Result<wasmi::FuncRef, wasmi::Error> {
|
||||
let signature = sp_wasm_interface::Signature::from(signature);
|
||||
for (function_index, function) in self.host_functions.iter().enumerate() {
|
||||
if name == function.name() {
|
||||
if signature == function.signature() {
|
||||
return Ok(wasmi::FuncInstance::alloc_host(signature.into(), function_index))
|
||||
} else {
|
||||
return Err(wasmi::Error::Instantiation(format!(
|
||||
"Invalid signature for function `{}` expected `{:?}`, got `{:?}`",
|
||||
function.name(),
|
||||
signature,
|
||||
function.signature(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.allow_missing_func_imports {
|
||||
trace!(
|
||||
target: "wasm-executor",
|
||||
"Could not find function `{}`, a stub will be provided instead.",
|
||||
name,
|
||||
);
|
||||
let id = self.missing_functions.borrow().len() + self.host_functions.len();
|
||||
self.missing_functions.borrow_mut().push(name.to_string());
|
||||
|
||||
Ok(wasmi::FuncInstance::alloc_host(signature.into(), id))
|
||||
} else {
|
||||
Err(wasmi::Error::Instantiation(format!("Export {} not found", name)))
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_memory(
|
||||
&self,
|
||||
_: &str,
|
||||
_: &wasmi::MemoryDescriptor,
|
||||
) -> Result<MemoryRef, wasmi::Error> {
|
||||
Err(wasmi::Error::Instantiation(
|
||||
"Internal error, wasmi expects that the wasm blob exports memory.".into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl wasmi::Externals for FunctionExecutor {
|
||||
fn invoke_index(
|
||||
&mut self,
|
||||
index: usize,
|
||||
args: wasmi::RuntimeArgs,
|
||||
) -> Result<Option<wasmi::RuntimeValue>, wasmi::Trap> {
|
||||
let mut args = args.as_ref().iter().copied().map(Into::into);
|
||||
|
||||
if let Some(function) = self.host_functions.clone().get(index) {
|
||||
function
|
||||
.execute(self, &mut args)
|
||||
.map_err(|msg| Error::FunctionExecution(function.name().to_string(), msg))
|
||||
.map_err(wasmi::Trap::from)
|
||||
.map(|v| v.map(Into::into))
|
||||
} else if self.allow_missing_func_imports &&
|
||||
index >= self.host_functions.len() &&
|
||||
index < self.host_functions.len() + self.missing_functions.len()
|
||||
{
|
||||
Err(Error::from(format!(
|
||||
"Function `{}` is only a stub. Calling a stub is not allowed.",
|
||||
self.missing_functions[index - self.host_functions.len()],
|
||||
))
|
||||
.into())
|
||||
} else {
|
||||
Err(Error::from(format!("Could not find host function with index: {}", index)).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mem_instance(module: &ModuleRef) -> Result<MemoryRef, Error> {
|
||||
Ok(module
|
||||
.export_by_name("memory")
|
||||
.ok_or(Error::InvalidMemoryReference)?
|
||||
.as_memory()
|
||||
.ok_or(Error::InvalidMemoryReference)?
|
||||
.clone())
|
||||
}
|
||||
|
||||
/// Find the global named `__heap_base` in the given wasm module instance and
|
||||
/// tries to get its value.
|
||||
fn get_heap_base(module: &ModuleRef) -> Result<u32, Error> {
|
||||
let heap_base_val = module
|
||||
.export_by_name("__heap_base")
|
||||
.ok_or(Error::HeapBaseNotFoundOrInvalid)?
|
||||
.as_global()
|
||||
.ok_or(Error::HeapBaseNotFoundOrInvalid)?
|
||||
.get();
|
||||
|
||||
match heap_base_val {
|
||||
wasmi::RuntimeValue::I32(v) => Ok(v as u32),
|
||||
_ => Err(Error::HeapBaseNotFoundOrInvalid),
|
||||
}
|
||||
}
|
||||
|
||||
/// Call a given method in the given wasm-module runtime.
|
||||
fn call_in_wasm_module(
|
||||
module_instance: &ModuleRef,
|
||||
memory: &MemoryRef,
|
||||
method: InvokeMethod,
|
||||
data: &[u8],
|
||||
host_functions: Arc<Vec<&'static dyn Function>>,
|
||||
allow_missing_func_imports: bool,
|
||||
missing_functions: Arc<Vec<String>>,
|
||||
allocation_stats: &mut Option<AllocationStats>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
// Initialize FunctionExecutor.
|
||||
let table: Option<TableRef> = module_instance
|
||||
.export_by_name("__indirect_function_table")
|
||||
.and_then(|e| e.as_table().cloned());
|
||||
let heap_base = get_heap_base(module_instance)?;
|
||||
|
||||
let mut function_executor = FunctionExecutor::new(
|
||||
memory.clone(),
|
||||
heap_base,
|
||||
host_functions,
|
||||
allow_missing_func_imports,
|
||||
missing_functions,
|
||||
)?;
|
||||
|
||||
// Write the call data
|
||||
let offset = function_executor.allocate_memory(data.len() as u32)?;
|
||||
function_executor.write_memory(offset, data)?;
|
||||
|
||||
fn convert_trap(executor: &mut FunctionExecutor, trap: wasmi::Trap) -> Error {
|
||||
if let Some(message) = executor.panic_message.take() {
|
||||
Error::AbortedDueToPanic(MessageWithBacktrace { message, backtrace: None })
|
||||
} else {
|
||||
Error::AbortedDueToTrap(MessageWithBacktrace {
|
||||
message: trap.to_string(),
|
||||
backtrace: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let result = match method {
|
||||
InvokeMethod::Export(method) => module_instance
|
||||
.invoke_export(
|
||||
method,
|
||||
&[I32(u32::from(offset) as i32), I32(data.len() as i32)],
|
||||
&mut function_executor,
|
||||
)
|
||||
.map_err(|error| {
|
||||
if let wasmi::Error::Trap(trap) = error {
|
||||
convert_trap(&mut function_executor, trap)
|
||||
} else {
|
||||
error.into()
|
||||
}
|
||||
}),
|
||||
InvokeMethod::Table(func_ref) => {
|
||||
let func = table
|
||||
.ok_or(Error::NoTable)?
|
||||
.get(func_ref)?
|
||||
.ok_or(Error::NoTableEntryWithIndex(func_ref))?;
|
||||
FuncInstance::invoke(
|
||||
&func,
|
||||
&[I32(u32::from(offset) as i32), I32(data.len() as i32)],
|
||||
&mut function_executor,
|
||||
)
|
||||
.map_err(|trap| convert_trap(&mut function_executor, trap))
|
||||
},
|
||||
InvokeMethod::TableWithWrapper { dispatcher_ref, func } => {
|
||||
let dispatcher = table
|
||||
.ok_or(Error::NoTable)?
|
||||
.get(dispatcher_ref)?
|
||||
.ok_or(Error::NoTableEntryWithIndex(dispatcher_ref))?;
|
||||
|
||||
FuncInstance::invoke(
|
||||
&dispatcher,
|
||||
&[I32(func as _), I32(u32::from(offset) as i32), I32(data.len() as i32)],
|
||||
&mut function_executor,
|
||||
)
|
||||
.map_err(|trap| convert_trap(&mut function_executor, trap))
|
||||
},
|
||||
};
|
||||
|
||||
*allocation_stats = Some(function_executor.heap.borrow().stats());
|
||||
|
||||
match result {
|
||||
Ok(Some(I64(r))) => {
|
||||
let (ptr, length) = unpack_ptr_and_len(r as u64);
|
||||
#[allow(deprecated)]
|
||||
memory.get(ptr, length as usize).map_err(|_| Error::Runtime)
|
||||
},
|
||||
Err(e) => {
|
||||
trace!(
|
||||
target: "wasm-executor",
|
||||
"Failed to execute code with {} pages",
|
||||
memory.current_size().0,
|
||||
);
|
||||
Err(e)
|
||||
},
|
||||
_ => Err(Error::InvalidReturn),
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare module instance
|
||||
fn instantiate_module(
|
||||
module: &Module,
|
||||
host_functions: &[&'static dyn Function],
|
||||
allow_missing_func_imports: bool,
|
||||
) -> Result<(ModuleRef, Vec<String>, MemoryRef), Error> {
|
||||
let resolver = Resolver::new(host_functions, allow_missing_func_imports);
|
||||
// start module instantiation. Don't run 'start' function yet.
|
||||
let intermediate_instance =
|
||||
ModuleInstance::new(module, &ImportsBuilder::new().with_resolver("env", &resolver))?;
|
||||
|
||||
// Verify that the module has the heap base global variable.
|
||||
let _ = get_heap_base(intermediate_instance.not_started_instance())?;
|
||||
|
||||
// The `module` should export the memory with the correct properties (min, max).
|
||||
//
|
||||
// This is ensured by modifying the `RuntimeBlob` before initializing the `Module`.
|
||||
let memory = get_mem_instance(intermediate_instance.not_started_instance())?;
|
||||
|
||||
if intermediate_instance.has_start() {
|
||||
// Runtime is not allowed to have the `start` function.
|
||||
Err(Error::RuntimeHasStartFn)
|
||||
} else {
|
||||
Ok((
|
||||
intermediate_instance.assert_no_start(),
|
||||
resolver.missing_functions.into_inner(),
|
||||
memory,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// A state snapshot of an instance taken just after instantiation.
|
||||
///
|
||||
/// It is used for restoring the state of the module after execution.
|
||||
#[derive(Clone)]
|
||||
struct GlobalValsSnapshot {
|
||||
/// The list of all global mutable variables of the module in their sequential order.
|
||||
global_mut_values: Vec<RuntimeValue>,
|
||||
}
|
||||
|
||||
impl GlobalValsSnapshot {
|
||||
// Returns `None` if instance is not valid.
|
||||
fn take(module_instance: &ModuleRef) -> Self {
|
||||
// Collect all values of mutable globals.
|
||||
let global_mut_values = module_instance
|
||||
.globals()
|
||||
.iter()
|
||||
.filter(|g| g.is_mutable())
|
||||
.map(|g| g.get())
|
||||
.collect();
|
||||
Self { global_mut_values }
|
||||
}
|
||||
|
||||
/// Reset the runtime instance to the initial version by restoring
|
||||
/// the preserved memory and globals.
|
||||
///
|
||||
/// Returns `Err` if applying the snapshot is failed.
|
||||
fn apply(&self, instance: &ModuleRef) -> Result<(), WasmError> {
|
||||
for (global_ref, global_val) in instance
|
||||
.globals()
|
||||
.iter()
|
||||
.filter(|g| g.is_mutable())
|
||||
.zip(self.global_mut_values.iter())
|
||||
{
|
||||
// the instance should be the same as used for preserving and
|
||||
// we iterate the same way it as we do it for preserving values that means that the
|
||||
// types should be the same and all the values are mutable. So no error is expected/
|
||||
global_ref.set(*global_val).map_err(|_| WasmError::ApplySnapshotFailed)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A runtime along with initial copy of data segments.
|
||||
pub struct WasmiRuntime {
|
||||
/// A wasm module.
|
||||
module: Module,
|
||||
/// The host functions registered for this instance.
|
||||
host_functions: Arc<Vec<&'static dyn Function>>,
|
||||
/// Enable stub generation for functions that are not available in `host_functions`.
|
||||
/// These stubs will error when the wasm blob tries to call them.
|
||||
allow_missing_func_imports: bool,
|
||||
|
||||
global_vals_snapshot: GlobalValsSnapshot,
|
||||
data_segments_snapshot: DataSegmentsSnapshot,
|
||||
}
|
||||
|
||||
impl WasmModule for WasmiRuntime {
|
||||
fn new_instance(&self) -> Result<Box<dyn WasmInstance>, Error> {
|
||||
// Instantiate this module.
|
||||
let (instance, missing_functions, memory) =
|
||||
instantiate_module(&self.module, &self.host_functions, self.allow_missing_func_imports)
|
||||
.map_err(|e| WasmError::Instantiation(e.to_string()))?;
|
||||
|
||||
Ok(Box::new(WasmiInstance {
|
||||
instance,
|
||||
memory,
|
||||
global_vals_snapshot: self.global_vals_snapshot.clone(),
|
||||
data_segments_snapshot: self.data_segments_snapshot.clone(),
|
||||
host_functions: self.host_functions.clone(),
|
||||
allow_missing_func_imports: self.allow_missing_func_imports,
|
||||
missing_functions: Arc::new(missing_functions),
|
||||
memory_zeroed: true,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `WasmiRuntime` given the code. This function loads the module and
|
||||
/// stores it in the instance.
|
||||
pub fn create_runtime(
|
||||
mut blob: RuntimeBlob,
|
||||
heap_alloc_strategy: HeapAllocStrategy,
|
||||
host_functions: Vec<&'static dyn Function>,
|
||||
allow_missing_func_imports: bool,
|
||||
) -> Result<WasmiRuntime, WasmError> {
|
||||
let data_segments_snapshot =
|
||||
DataSegmentsSnapshot::take(&blob).map_err(|e| WasmError::Other(e.to_string()))?;
|
||||
|
||||
// Make sure we only have exported memory to simplify the code of the wasmi executor.
|
||||
blob.convert_memory_import_into_export()?;
|
||||
// Ensure that the memory uses the correct heap pages.
|
||||
blob.setup_memory_according_to_heap_alloc_strategy(heap_alloc_strategy)?;
|
||||
|
||||
let module =
|
||||
Module::from_parity_wasm_module(blob.into_inner()).map_err(|_| WasmError::InvalidModule)?;
|
||||
|
||||
let global_vals_snapshot = {
|
||||
let (instance, _, _) =
|
||||
instantiate_module(&module, &host_functions, allow_missing_func_imports)
|
||||
.map_err(|e| WasmError::Instantiation(e.to_string()))?;
|
||||
GlobalValsSnapshot::take(&instance)
|
||||
};
|
||||
|
||||
Ok(WasmiRuntime {
|
||||
module,
|
||||
data_segments_snapshot,
|
||||
global_vals_snapshot,
|
||||
host_functions: Arc::new(host_functions),
|
||||
allow_missing_func_imports,
|
||||
})
|
||||
}
|
||||
|
||||
/// Wasmi instance wrapper along with the state snapshot.
|
||||
pub struct WasmiInstance {
|
||||
/// A wasm module instance.
|
||||
instance: ModuleRef,
|
||||
/// The memory instance of used by the wasm module.
|
||||
memory: MemoryRef,
|
||||
/// Is the memory zeroed?
|
||||
memory_zeroed: bool,
|
||||
/// The snapshot of global variable values just after instantiation.
|
||||
global_vals_snapshot: GlobalValsSnapshot,
|
||||
/// The snapshot of data segments.
|
||||
data_segments_snapshot: DataSegmentsSnapshot,
|
||||
/// The host functions registered for this instance.
|
||||
host_functions: Arc<Vec<&'static dyn Function>>,
|
||||
/// Enable stub generation for functions that are not available in `host_functions`.
|
||||
/// These stubs will error when the wasm blob trie to call them.
|
||||
allow_missing_func_imports: bool,
|
||||
/// List of missing functions detected during function resolution
|
||||
missing_functions: Arc<Vec<String>>,
|
||||
}
|
||||
|
||||
// This is safe because `WasmiInstance` does not leak any references to `self.memory` and
|
||||
// `self.instance`
|
||||
unsafe impl Send for WasmiInstance {}
|
||||
|
||||
impl WasmiInstance {
|
||||
fn call_impl(
|
||||
&mut self,
|
||||
method: InvokeMethod,
|
||||
data: &[u8],
|
||||
allocation_stats: &mut Option<AllocationStats>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
// We reuse a single wasm instance for multiple calls and a previous call (if any)
|
||||
// altered the state. Therefore, we need to restore the instance to original state.
|
||||
|
||||
if !self.memory_zeroed {
|
||||
// First, zero initialize the linear memory.
|
||||
self.memory.erase().map_err(|e| {
|
||||
// Snapshot restoration failed. This is pretty unexpected since this can happen
|
||||
// if some invariant is broken or if the system is under extreme memory pressure
|
||||
// (so erasing fails).
|
||||
error!(target: "wasm-executor", "snapshot restoration failed: {}", e);
|
||||
WasmError::ErasingFailed(e.to_string())
|
||||
})?;
|
||||
}
|
||||
|
||||
// Second, reapply data segments into the linear memory.
|
||||
self.data_segments_snapshot
|
||||
.apply(|offset, contents| self.memory.set(offset, contents))?;
|
||||
|
||||
// Third, restore the global variables to their initial values.
|
||||
self.global_vals_snapshot.apply(&self.instance)?;
|
||||
|
||||
let res = call_in_wasm_module(
|
||||
&self.instance,
|
||||
&self.memory,
|
||||
method,
|
||||
data,
|
||||
self.host_functions.clone(),
|
||||
self.allow_missing_func_imports,
|
||||
self.missing_functions.clone(),
|
||||
allocation_stats,
|
||||
);
|
||||
|
||||
// If we couldn't unmap it, erase the memory.
|
||||
self.memory_zeroed = self.memory.erase().is_ok();
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl WasmInstance for WasmiInstance {
|
||||
fn call_with_allocation_stats(
|
||||
&mut self,
|
||||
method: InvokeMethod,
|
||||
data: &[u8],
|
||||
) -> (Result<Vec<u8>, Error>, Option<AllocationStats>) {
|
||||
let mut allocation_stats = None;
|
||||
let result = self.call_impl(method, data, &mut allocation_stats);
|
||||
(result, allocation_stats)
|
||||
}
|
||||
|
||||
fn get_global_const(&mut self, name: &str) -> Result<Option<sp_wasm_interface::Value>, Error> {
|
||||
match self.instance.export_by_name(name) {
|
||||
Some(global) => Ok(Some(
|
||||
global
|
||||
.as_global()
|
||||
.ok_or_else(|| format!("`{}` is not a global", name))?
|
||||
.get()
|
||||
.into(),
|
||||
)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn linear_memory_base_ptr(&self) -> Option<*const u8> {
|
||||
Some(self.memory.direct_access().as_ref().as_ptr())
|
||||
}
|
||||
}
|
||||
@@ -109,8 +109,8 @@ fn host_function_not_found() {
|
||||
.0
|
||||
.unwrap_err();
|
||||
|
||||
assert!(err.contains("Instantiation: Export "));
|
||||
assert!(err.contains(" not found"));
|
||||
assert!(err.contains("test_return_data"));
|
||||
assert!(err.contains(" Failed to create module"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user