mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 04:41:02 +00:00
Statically register host WASM functions (#10394)
* Statically register host WASM functions * Fix `substrate-test-client` compilation * Move `ExtendedHostFunctions` to `sp-wasm-interface` * Fix `sp-runtime-interface` tests' compilation * Fix `sc-executor-wasmtime` tests' compilation * Use `runtime_interface` macro in `test-runner` * Fix `sc-executor` tests' compilation * Reformatting/`rustfmt` * Add an extra comment regarding the `H` generic arg in `create_runtime` * Even more `rustfmt` * Depend on `wasmtime` without default features in `sp-wasm-interface` * Bump version of `sp-wasm-interface` to 4.0.1 * Bump `sp-wasm-interface` in `Cargo.lock` too * Bump all of the `sp-wasm-interface` requirements to 4.0.1 Maybe this will appease cargo-unleash? * Revert "Bump all of the `sp-wasm-interface` requirements to 4.0.1" This reverts commit 0f7ccf8e0f371542861121b145ab87af6541ac30. * Make `cargo-unleash` happy (maybe) * Use `cargo-unleash` to bump the crates' versions * Align to review comments
This commit is contained in:
Generated
+4
-2
@@ -9685,7 +9685,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sp-runtime-interface"
|
||||
version = "4.0.0"
|
||||
version = "4.1.0-dev"
|
||||
dependencies = [
|
||||
"impl-trait-for-tuples",
|
||||
"parity-scale-codec",
|
||||
@@ -9962,12 +9962,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sp-wasm-interface"
|
||||
version = "4.0.0"
|
||||
version = "4.1.0-dev"
|
||||
dependencies = [
|
||||
"impl-trait-for-tuples",
|
||||
"log 0.4.14",
|
||||
"parity-scale-codec",
|
||||
"sp-std",
|
||||
"wasmi",
|
||||
"wasmtime",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -15,6 +15,6 @@ targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
sp-core = { version = "4.1.0-dev", path = "../../primitives/core" }
|
||||
sp-wasm-interface = { version = "4.0.0", path = "../../primitives/wasm-interface" }
|
||||
sp-wasm-interface = { version = "4.1.0-dev", path = "../../primitives/wasm-interface" }
|
||||
log = "0.4.11"
|
||||
thiserror = "1.0.30"
|
||||
|
||||
@@ -24,8 +24,8 @@ sp-panic-handler = { version = "4.0.0-dev", path = "../../primitives/panic-handl
|
||||
wasmi = "0.9.1"
|
||||
lazy_static = "1.4.0"
|
||||
sp-api = { version = "4.0.0-dev", path = "../../primitives/api" }
|
||||
sp-wasm-interface = { version = "4.0.0", path = "../../primitives/wasm-interface" }
|
||||
sp-runtime-interface = { version = "4.0.0", path = "../../primitives/runtime-interface" }
|
||||
sp-wasm-interface = { version = "4.1.0-dev", path = "../../primitives/wasm-interface" }
|
||||
sp-runtime-interface = { version = "4.1.0-dev", path = "../../primitives/runtime-interface" }
|
||||
sp-externalities = { version = "0.10.0", path = "../../primitives/externalities" }
|
||||
sc-executor-common = { version = "0.10.0-dev", path = "common" }
|
||||
sc-executor-wasmi = { version = "0.10.0-dev", path = "wasmi" }
|
||||
|
||||
@@ -20,7 +20,7 @@ codec = { package = "parity-scale-codec", version = "2.0.0" }
|
||||
wasmi = "0.9.1"
|
||||
sp-core = { version = "4.1.0-dev", path = "../../../primitives/core" }
|
||||
sc-allocator = { version = "4.1.0-dev", path = "../../allocator" }
|
||||
sp-wasm-interface = { version = "4.0.0", path = "../../../primitives/wasm-interface" }
|
||||
sp-wasm-interface = { version = "4.1.0-dev", path = "../../../primitives/wasm-interface" }
|
||||
sp-maybe-compressed-blob = { version = "4.1.0-dev", path = "../../../primitives/maybe-compressed-blob" }
|
||||
sp-serializer = { version = "4.0.0-dev", path = "../../../primitives/serializer" }
|
||||
thiserror = "1.0.30"
|
||||
|
||||
@@ -332,6 +332,14 @@ sp_core::wasm_export_functions! {
|
||||
fn test_panic_in_spawned() {
|
||||
sp_tasks::spawn(tasks::panicker, vec![]).join();
|
||||
}
|
||||
|
||||
fn test_return_i8() -> i8 {
|
||||
-66
|
||||
}
|
||||
|
||||
fn test_take_i8(value: i8) {
|
||||
assert_eq!(value, -66);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
|
||||
@@ -34,7 +34,6 @@ use sp_core::{
|
||||
use sp_runtime::traits::BlakeTwo256;
|
||||
use sp_state_machine::TestExternalities as CoreTestExternalities;
|
||||
use sp_trie::{trie_types::Layout, TrieConfiguration};
|
||||
use sp_wasm_interface::HostFunctions as _;
|
||||
use std::sync::Arc;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
|
||||
@@ -124,14 +123,8 @@ fn call_in_wasm<E: Externalities>(
|
||||
execution_method: WasmExecutionMethod,
|
||||
ext: &mut E,
|
||||
) -> Result<Vec<u8>, String> {
|
||||
let executor = crate::WasmExecutor::new(
|
||||
execution_method,
|
||||
Some(1024),
|
||||
HostFunctions::host_functions(),
|
||||
8,
|
||||
None,
|
||||
2,
|
||||
);
|
||||
let executor =
|
||||
crate::WasmExecutor::<HostFunctions>::new(execution_method, Some(1024), 8, None, 2);
|
||||
executor.uncached_call(
|
||||
RuntimeBlob::uncompress_if_needed(&wasm_binary_unwrap()[..]).unwrap(),
|
||||
ext,
|
||||
@@ -475,10 +468,9 @@ test_wasm_execution!(should_trap_when_heap_exhausted);
|
||||
fn should_trap_when_heap_exhausted(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
|
||||
let executor = crate::WasmExecutor::new(
|
||||
let executor = crate::WasmExecutor::<HostFunctions>::new(
|
||||
wasm_method,
|
||||
Some(17), // `17` is the initial number of pages compiled into the binary.
|
||||
HostFunctions::host_functions(),
|
||||
8,
|
||||
None,
|
||||
2,
|
||||
@@ -501,11 +493,10 @@ fn mk_test_runtime(wasm_method: WasmExecutionMethod, pages: u64) -> Arc<dyn Wasm
|
||||
let blob = RuntimeBlob::uncompress_if_needed(&wasm_binary_unwrap()[..])
|
||||
.expect("failed to create a runtime blob out of test runtime");
|
||||
|
||||
crate::wasm_runtime::create_wasm_runtime_with_code(
|
||||
crate::wasm_runtime::create_wasm_runtime_with_code::<HostFunctions>(
|
||||
wasm_method,
|
||||
pages,
|
||||
blob,
|
||||
HostFunctions::host_functions(),
|
||||
true,
|
||||
None,
|
||||
)
|
||||
@@ -589,10 +580,9 @@ fn heap_is_reset_between_calls(wasm_method: WasmExecutionMethod) {
|
||||
|
||||
test_wasm_execution!(parallel_execution);
|
||||
fn parallel_execution(wasm_method: WasmExecutionMethod) {
|
||||
let executor = std::sync::Arc::new(crate::WasmExecutor::new(
|
||||
let executor = std::sync::Arc::new(crate::WasmExecutor::<HostFunctions>::new(
|
||||
wasm_method,
|
||||
Some(1024),
|
||||
HostFunctions::host_functions(),
|
||||
8,
|
||||
None,
|
||||
2,
|
||||
@@ -763,11 +753,10 @@ fn memory_is_cleared_between_invocations(wasm_method: WasmExecutionMethod) {
|
||||
)
|
||||
)"#).unwrap();
|
||||
|
||||
let runtime = crate::wasm_runtime::create_wasm_runtime_with_code(
|
||||
let runtime = crate::wasm_runtime::create_wasm_runtime_with_code::<HostFunctions>(
|
||||
wasm_method,
|
||||
1024,
|
||||
RuntimeBlob::uncompress_if_needed(&binary[..]).unwrap(),
|
||||
HostFunctions::host_functions(),
|
||||
true,
|
||||
None,
|
||||
)
|
||||
@@ -780,3 +769,22 @@ fn memory_is_cleared_between_invocations(wasm_method: WasmExecutionMethod) {
|
||||
let res = instance.call_export("returns_no_bss_mutable_static", &[0]).unwrap();
|
||||
assert_eq!(1, u64::decode(&mut &res[..]).unwrap());
|
||||
}
|
||||
|
||||
test_wasm_execution!(return_i8);
|
||||
fn return_i8(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
assert_eq!(
|
||||
call_in_wasm("test_return_i8", &[], wasm_method, &mut ext).unwrap(),
|
||||
(-66_i8).encode()
|
||||
);
|
||||
}
|
||||
|
||||
test_wasm_execution!(take_i8);
|
||||
fn take_i8(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
call_in_wasm("test_take_i8", &(-66_i8).encode(), wasm_method, &mut ext).unwrap();
|
||||
}
|
||||
|
||||
@@ -67,17 +67,15 @@ mod tests {
|
||||
use sc_executor_common::runtime_blob::RuntimeBlob;
|
||||
use sc_runtime_test::wasm_binary_unwrap;
|
||||
use sp_io::TestExternalities;
|
||||
use sp_wasm_interface::HostFunctions;
|
||||
|
||||
#[test]
|
||||
fn call_in_interpreted_wasm_works() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
let executor = WasmExecutor::new(
|
||||
let executor = WasmExecutor::<sp_io::SubstrateHostFunctions>::new(
|
||||
WasmExecutionMethod::Interpreted,
|
||||
Some(8),
|
||||
sp_io::SubstrateHostFunctions::host_functions(),
|
||||
8,
|
||||
None,
|
||||
2,
|
||||
|
||||
@@ -24,6 +24,7 @@ use crate::{
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
marker::PhantomData,
|
||||
panic::{AssertUnwindSafe, UnwindSafe},
|
||||
path::PathBuf,
|
||||
result,
|
||||
@@ -46,7 +47,7 @@ use sp_core::{
|
||||
use sp_externalities::ExternalitiesExt as _;
|
||||
use sp_tasks::new_async_externalities;
|
||||
use sp_version::{GetNativeVersion, NativeVersion, RuntimeVersion};
|
||||
use sp_wasm_interface::{Function, HostFunctions};
|
||||
use sp_wasm_interface::{ExtendedHostFunctions, HostFunctions};
|
||||
|
||||
/// Default num of pages for the heap
|
||||
const DEFAULT_HEAP_PAGES: u64 = 2048;
|
||||
@@ -91,22 +92,36 @@ pub trait NativeExecutionDispatch: Send + Sync {
|
||||
|
||||
/// An abstraction over Wasm code executor. Supports selecting execution backend and
|
||||
/// manages runtime cache.
|
||||
#[derive(Clone)]
|
||||
pub struct WasmExecutor {
|
||||
pub struct WasmExecutor<H> {
|
||||
/// Method used to execute fallback Wasm code.
|
||||
method: WasmExecutionMethod,
|
||||
/// The number of 64KB pages to allocate for Wasm execution.
|
||||
default_heap_pages: u64,
|
||||
/// The host functions registered with this instance.
|
||||
host_functions: Arc<Vec<&'static dyn Function>>,
|
||||
/// WASM runtime cache.
|
||||
cache: Arc<RuntimeCache>,
|
||||
/// The path to a directory which the executor can leverage for a file cache, e.g. put there
|
||||
/// compiled artifacts.
|
||||
cache_path: Option<PathBuf>,
|
||||
|
||||
phantom: PhantomData<H>,
|
||||
}
|
||||
|
||||
impl WasmExecutor {
|
||||
impl<H> Clone for WasmExecutor<H> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
method: self.method,
|
||||
default_heap_pages: self.default_heap_pages,
|
||||
cache: self.cache.clone(),
|
||||
cache_path: self.cache_path.clone(),
|
||||
phantom: self.phantom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H> WasmExecutor<H>
|
||||
where
|
||||
H: HostFunctions,
|
||||
{
|
||||
/// Create new instance.
|
||||
///
|
||||
/// # Parameters
|
||||
@@ -127,7 +142,6 @@ impl WasmExecutor {
|
||||
pub fn new(
|
||||
method: WasmExecutionMethod,
|
||||
default_heap_pages: Option<u64>,
|
||||
host_functions: Vec<&'static dyn Function>,
|
||||
max_runtime_instances: usize,
|
||||
cache_path: Option<PathBuf>,
|
||||
runtime_cache_size: u8,
|
||||
@@ -135,13 +149,13 @@ impl WasmExecutor {
|
||||
WasmExecutor {
|
||||
method,
|
||||
default_heap_pages: default_heap_pages.unwrap_or(DEFAULT_HEAP_PAGES),
|
||||
host_functions: Arc::new(host_functions),
|
||||
cache: Arc::new(RuntimeCache::new(
|
||||
max_runtime_instances,
|
||||
cache_path.clone(),
|
||||
runtime_cache_size,
|
||||
)),
|
||||
cache_path,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,12 +187,11 @@ impl WasmExecutor {
|
||||
AssertUnwindSafe<&mut dyn Externalities>,
|
||||
) -> Result<Result<R>>,
|
||||
{
|
||||
match self.cache.with_instance(
|
||||
match self.cache.with_instance::<H, _, _>(
|
||||
runtime_code,
|
||||
ext,
|
||||
self.method,
|
||||
self.default_heap_pages,
|
||||
&*self.host_functions,
|
||||
allow_missing_host_functions,
|
||||
|module, instance, version, ext| {
|
||||
let module = AssertUnwindSafe(module);
|
||||
@@ -208,11 +221,10 @@ impl WasmExecutor {
|
||||
export_name: &str,
|
||||
call_data: &[u8],
|
||||
) -> std::result::Result<Vec<u8>, String> {
|
||||
let module = crate::wasm_runtime::create_wasm_runtime_with_code(
|
||||
let module = crate::wasm_runtime::create_wasm_runtime_with_code::<H>(
|
||||
self.method,
|
||||
self.default_heap_pages,
|
||||
runtime_blob,
|
||||
self.host_functions.to_vec(),
|
||||
allow_missing_host_functions,
|
||||
self.cache_path.as_deref(),
|
||||
)
|
||||
@@ -235,7 +247,10 @@ impl WasmExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
impl sp_core::traits::ReadRuntimeVersion for WasmExecutor {
|
||||
impl<H> sp_core::traits::ReadRuntimeVersion for WasmExecutor<H>
|
||||
where
|
||||
H: HostFunctions,
|
||||
{
|
||||
fn read_runtime_version(
|
||||
&self,
|
||||
wasm_code: &[u8],
|
||||
@@ -269,7 +284,10 @@ impl sp_core::traits::ReadRuntimeVersion for WasmExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
impl CodeExecutor for WasmExecutor {
|
||||
impl<H> CodeExecutor for WasmExecutor<H>
|
||||
where
|
||||
H: HostFunctions,
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
fn call<
|
||||
@@ -299,7 +317,10 @@ impl CodeExecutor for WasmExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
impl RuntimeVersionOf for WasmExecutor {
|
||||
impl<H> RuntimeVersionOf for WasmExecutor<H>
|
||||
where
|
||||
H: HostFunctions,
|
||||
{
|
||||
fn runtime_version(
|
||||
&self,
|
||||
ext: &mut dyn Externalities,
|
||||
@@ -313,13 +334,17 @@ impl RuntimeVersionOf for WasmExecutor {
|
||||
|
||||
/// A generic `CodeExecutor` implementation that uses a delegate to determine wasm code equivalence
|
||||
/// and dispatch to native code when possible, falling back on `WasmExecutor` when not.
|
||||
pub struct NativeElseWasmExecutor<D> {
|
||||
pub struct NativeElseWasmExecutor<D>
|
||||
where
|
||||
D: NativeExecutionDispatch,
|
||||
{
|
||||
/// Dummy field to avoid the compiler complaining about us not using `D`.
|
||||
_dummy: std::marker::PhantomData<D>,
|
||||
/// Native runtime version info.
|
||||
native_version: NativeVersion,
|
||||
/// Fallback wasm executor.
|
||||
wasm: WasmExecutor,
|
||||
wasm:
|
||||
WasmExecutor<ExtendedHostFunctions<sp_io::SubstrateHostFunctions, D::ExtendHostFunctions>>,
|
||||
}
|
||||
|
||||
impl<D: NativeExecutionDispatch> NativeElseWasmExecutor<D> {
|
||||
@@ -337,24 +362,9 @@ impl<D: NativeExecutionDispatch> NativeElseWasmExecutor<D> {
|
||||
max_runtime_instances: usize,
|
||||
runtime_cache_size: u8,
|
||||
) -> Self {
|
||||
let extended = D::ExtendHostFunctions::host_functions();
|
||||
let mut host_functions = sp_io::SubstrateHostFunctions::host_functions()
|
||||
.into_iter()
|
||||
// filter out any host function overrides provided.
|
||||
.filter(|host_fn| {
|
||||
extended
|
||||
.iter()
|
||||
.find(|ext_host_fn| host_fn.name() == ext_host_fn.name())
|
||||
.is_none()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Add the custom host functions provided by the user.
|
||||
host_functions.extend(extended);
|
||||
let wasm_executor = WasmExecutor::new(
|
||||
fallback_method,
|
||||
default_heap_pages,
|
||||
host_functions,
|
||||
max_runtime_instances,
|
||||
None,
|
||||
runtime_cache_size,
|
||||
@@ -645,8 +655,21 @@ mod tests {
|
||||
8,
|
||||
2,
|
||||
);
|
||||
|
||||
fn extract_host_functions<H>(
|
||||
_: &WasmExecutor<H>,
|
||||
) -> Vec<&'static dyn sp_wasm_interface::Function>
|
||||
where
|
||||
H: HostFunctions,
|
||||
{
|
||||
H::host_functions()
|
||||
}
|
||||
|
||||
my_interface::HostFunctions::host_functions().iter().for_each(|function| {
|
||||
assert_eq!(executor.wasm.host_functions.iter().filter(|f| f == &function).count(), 2);
|
||||
assert_eq!(
|
||||
extract_host_functions(&executor.wasm).iter().filter(|f| f == &function).count(),
|
||||
2
|
||||
);
|
||||
});
|
||||
|
||||
my_interface::say_hello_world("hey");
|
||||
|
||||
@@ -37,7 +37,7 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use sp_wasm_interface::Function;
|
||||
use sp_wasm_interface::HostFunctions;
|
||||
|
||||
/// Specification of different methods of executing the runtime Wasm code.
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
|
||||
@@ -199,14 +199,14 @@ impl RuntimeCache {
|
||||
///
|
||||
/// `wasm_method` - Type of WASM backend to use.
|
||||
///
|
||||
/// `host_functions` - The host functions that should be registered for the Wasm runtime.
|
||||
///
|
||||
/// `allow_missing_func_imports` - Ignore missing function imports.
|
||||
///
|
||||
/// `max_runtime_instances` - The size of the instances cache.
|
||||
///
|
||||
/// `f` - Function to execute.
|
||||
///
|
||||
/// `H` - A compile-time list of host functions to expose to the runtime.
|
||||
///
|
||||
/// # Returns result of `f` wrapped in an additional result.
|
||||
/// In case of failure one of two errors can be returned:
|
||||
///
|
||||
@@ -214,17 +214,17 @@ impl RuntimeCache {
|
||||
///
|
||||
/// `Error::InvalidMemoryReference` is returned if no memory export with the
|
||||
/// identifier `memory` can be found in the runtime.
|
||||
pub fn with_instance<'c, R, F>(
|
||||
pub fn with_instance<'c, H, R, F>(
|
||||
&self,
|
||||
runtime_code: &'c RuntimeCode<'c>,
|
||||
ext: &mut dyn Externalities,
|
||||
wasm_method: WasmExecutionMethod,
|
||||
default_heap_pages: u64,
|
||||
host_functions: &[&'static dyn Function],
|
||||
allow_missing_func_imports: bool,
|
||||
f: F,
|
||||
) -> Result<Result<R, Error>, Error>
|
||||
where
|
||||
H: HostFunctions,
|
||||
F: FnOnce(
|
||||
&Arc<dyn WasmModule>,
|
||||
&mut dyn WasmInstance,
|
||||
@@ -247,12 +247,11 @@ impl RuntimeCache {
|
||||
|
||||
let time = std::time::Instant::now();
|
||||
|
||||
let result = create_versioned_wasm_runtime(
|
||||
let result = create_versioned_wasm_runtime::<H>(
|
||||
&code,
|
||||
ext,
|
||||
wasm_method,
|
||||
heap_pages,
|
||||
host_functions.into(),
|
||||
allow_missing_func_imports,
|
||||
self.max_runtime_instances,
|
||||
self.cache_path.as_deref(),
|
||||
@@ -288,14 +287,16 @@ impl RuntimeCache {
|
||||
}
|
||||
|
||||
/// Create a wasm runtime with the given `code`.
|
||||
pub fn create_wasm_runtime_with_code(
|
||||
pub fn create_wasm_runtime_with_code<H>(
|
||||
wasm_method: WasmExecutionMethod,
|
||||
heap_pages: u64,
|
||||
blob: RuntimeBlob,
|
||||
host_functions: Vec<&'static dyn Function>,
|
||||
allow_missing_func_imports: bool,
|
||||
cache_path: Option<&Path>,
|
||||
) -> Result<Arc<dyn WasmModule>, WasmError> {
|
||||
) -> Result<Arc<dyn WasmModule>, WasmError>
|
||||
where
|
||||
H: HostFunctions,
|
||||
{
|
||||
match wasm_method {
|
||||
WasmExecutionMethod::Interpreted => {
|
||||
// Wasmi doesn't have any need in a cache directory.
|
||||
@@ -307,13 +308,13 @@ pub fn create_wasm_runtime_with_code(
|
||||
sc_executor_wasmi::create_runtime(
|
||||
blob,
|
||||
heap_pages,
|
||||
host_functions,
|
||||
H::host_functions(),
|
||||
allow_missing_func_imports,
|
||||
)
|
||||
.map(|runtime| -> Arc<dyn WasmModule> { Arc::new(runtime) })
|
||||
},
|
||||
#[cfg(feature = "wasmtime")]
|
||||
WasmExecutionMethod::Compiled => sc_executor_wasmtime::create_runtime(
|
||||
WasmExecutionMethod::Compiled => sc_executor_wasmtime::create_runtime::<H>(
|
||||
blob,
|
||||
sc_executor_wasmtime::Config {
|
||||
heap_pages,
|
||||
@@ -327,7 +328,6 @@ pub fn create_wasm_runtime_with_code(
|
||||
parallel_compilation: true,
|
||||
},
|
||||
},
|
||||
host_functions,
|
||||
)
|
||||
.map(|runtime| -> Arc<dyn WasmModule> { Arc::new(runtime) }),
|
||||
}
|
||||
@@ -392,16 +392,18 @@ pub fn read_embedded_version(blob: &RuntimeBlob) -> Result<Option<RuntimeVersion
|
||||
}
|
||||
}
|
||||
|
||||
fn create_versioned_wasm_runtime(
|
||||
fn create_versioned_wasm_runtime<H>(
|
||||
code: &[u8],
|
||||
ext: &mut dyn Externalities,
|
||||
wasm_method: WasmExecutionMethod,
|
||||
heap_pages: u64,
|
||||
host_functions: Vec<&'static dyn Function>,
|
||||
allow_missing_func_imports: bool,
|
||||
max_instances: usize,
|
||||
cache_path: Option<&Path>,
|
||||
) -> Result<VersionedRuntime, WasmError> {
|
||||
) -> Result<VersionedRuntime, WasmError>
|
||||
where
|
||||
H: HostFunctions,
|
||||
{
|
||||
// The incoming code may be actually compressed. We decompress it here and then work with
|
||||
// the uncompressed code from now on.
|
||||
let blob = sc_executor_common::runtime_blob::RuntimeBlob::uncompress_if_needed(&code)?;
|
||||
@@ -411,11 +413,10 @@ fn create_versioned_wasm_runtime(
|
||||
// runtime.
|
||||
let mut version: Option<_> = read_embedded_version(&blob)?;
|
||||
|
||||
let runtime = create_wasm_runtime_with_code(
|
||||
let runtime = create_wasm_runtime_with_code::<H>(
|
||||
wasm_method,
|
||||
heap_pages,
|
||||
blob,
|
||||
host_functions,
|
||||
allow_missing_func_imports,
|
||||
cache_path,
|
||||
)?;
|
||||
|
||||
@@ -19,7 +19,7 @@ wasmi = "0.9.1"
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0" }
|
||||
sc-executor-common = { version = "0.10.0-dev", path = "../common" }
|
||||
sc-allocator = { version = "4.1.0-dev", path = "../../allocator" }
|
||||
sp-wasm-interface = { version = "4.0.0", path = "../../../primitives/wasm-interface" }
|
||||
sp-runtime-interface = { version = "4.0.0", path = "../../../primitives/runtime-interface" }
|
||||
sp-wasm-interface = { version = "4.1.0-dev", path = "../../../primitives/wasm-interface" }
|
||||
sp-runtime-interface = { version = "4.1.0-dev", path = "../../../primitives/runtime-interface" }
|
||||
sp-core = { version = "4.1.0-dev", path = "../../../primitives/core" }
|
||||
scoped-tls = "1.0"
|
||||
|
||||
@@ -19,8 +19,8 @@ log = "0.4.8"
|
||||
parity-wasm = "0.42.0"
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0" }
|
||||
sc-executor-common = { version = "0.10.0-dev", path = "../common" }
|
||||
sp-wasm-interface = { version = "4.0.0", path = "../../../primitives/wasm-interface" }
|
||||
sp-runtime-interface = { version = "4.0.0", path = "../../../primitives/runtime-interface" }
|
||||
sp-wasm-interface = { version = "4.1.0-dev", path = "../../../primitives/wasm-interface", features = ["wasmtime"] }
|
||||
sp-runtime-interface = { version = "4.1.0-dev", path = "../../../primitives/runtime-interface" }
|
||||
sp-core = { version = "4.1.0-dev", path = "../../../primitives/core" }
|
||||
sc-allocator = { version = "4.1.0-dev", path = "../../allocator" }
|
||||
wasmtime = { version = "0.31.0", default-features = false, features = [
|
||||
|
||||
@@ -62,11 +62,11 @@ impl HostState {
|
||||
/// A `HostContext` implements `FunctionContext` for making host calls from a Wasmtime
|
||||
/// runtime. The `HostContext` exists only for the lifetime of the call and borrows state from
|
||||
/// a longer-living `HostState`.
|
||||
pub(crate) struct HostContext<'a, 'b> {
|
||||
pub(crate) caller: &'a mut Caller<'b, StoreData>,
|
||||
pub(crate) struct HostContext<'a> {
|
||||
pub(crate) caller: Caller<'a, StoreData>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> HostContext<'a, 'b> {
|
||||
impl<'a> HostContext<'a> {
|
||||
fn host_state(&self) -> &HostState {
|
||||
self.caller
|
||||
.data()
|
||||
@@ -98,7 +98,7 @@ impl<'a, 'b> HostContext<'a, 'b> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> sp_wasm_interface::FunctionContext for HostContext<'a, 'b> {
|
||||
impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> {
|
||||
fn read_memory_into(
|
||||
&self,
|
||||
address: Pointer<u8>,
|
||||
@@ -136,7 +136,7 @@ impl<'a, 'b> sp_wasm_interface::FunctionContext for HostContext<'a, 'b> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Sandbox for HostContext<'a, 'b> {
|
||||
impl<'a> Sandbox for HostContext<'a> {
|
||||
fn memory_get(
|
||||
&mut self,
|
||||
memory_id: MemoryId,
|
||||
@@ -320,12 +320,12 @@ impl<'a, 'b> Sandbox for HostContext<'a, 'b> {
|
||||
}
|
||||
}
|
||||
|
||||
struct SandboxContext<'a, 'b, 'c> {
|
||||
host_context: &'a mut HostContext<'b, 'c>,
|
||||
struct SandboxContext<'a, 'b> {
|
||||
host_context: &'a mut HostContext<'b>,
|
||||
dispatch_thunk: Func,
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c> sandbox::SandboxContext for SandboxContext<'a, 'b, 'c> {
|
||||
impl<'a, 'b> sandbox::SandboxContext for SandboxContext<'a, 'b> {
|
||||
fn invoke(
|
||||
&mut self,
|
||||
invoke_args_ptr: Pointer<u8>,
|
||||
|
||||
@@ -19,14 +19,11 @@
|
||||
use crate::{
|
||||
host::HostContext,
|
||||
runtime::{Store, StoreData},
|
||||
util,
|
||||
};
|
||||
use sc_executor_common::error::WasmError;
|
||||
use sp_wasm_interface::{Function, ValueType};
|
||||
use std::{any::Any, convert::TryInto};
|
||||
use wasmtime::{
|
||||
Caller, Extern, ExternType, Func, FuncType, ImportType, Memory, MemoryType, Module, Trap, Val,
|
||||
};
|
||||
use sp_wasm_interface::{FunctionContext, HostFunctions};
|
||||
use std::{collections::HashMap, convert::TryInto};
|
||||
use wasmtime::{Extern, ExternType, Func, FuncType, ImportType, Memory, MemoryType, Module, Trap};
|
||||
|
||||
pub struct Imports {
|
||||
/// Contains the index into `externs` where the memory import is stored if any. `None` if there
|
||||
@@ -37,16 +34,19 @@ pub struct Imports {
|
||||
|
||||
/// Goes over all imports of a module and prepares a vector of `Extern`s that can be used for
|
||||
/// instantiation of the module. Returns an error if there are imports that cannot be satisfied.
|
||||
pub(crate) fn resolve_imports(
|
||||
pub(crate) fn resolve_imports<H>(
|
||||
store: &mut Store,
|
||||
module: &Module,
|
||||
host_functions: &[&'static dyn Function],
|
||||
heap_pages: u64,
|
||||
allow_missing_func_imports: bool,
|
||||
) -> Result<Imports, WasmError> {
|
||||
) -> Result<Imports, WasmError>
|
||||
where
|
||||
H: HostFunctions,
|
||||
{
|
||||
let mut externs = vec![];
|
||||
let mut memory_import_index = None;
|
||||
for import_ty in module.imports() {
|
||||
let mut pending_func_imports = HashMap::new();
|
||||
for (index, import_ty) in module.imports().enumerate() {
|
||||
let name = import_name(&import_ty)?;
|
||||
|
||||
if import_ty.module() != "env" {
|
||||
@@ -57,19 +57,89 @@ pub(crate) fn resolve_imports(
|
||||
)))
|
||||
}
|
||||
|
||||
let resolved = match name {
|
||||
"memory" => {
|
||||
memory_import_index = Some(externs.len());
|
||||
resolve_memory_import(store, &import_ty, heap_pages)?
|
||||
if name == "memory" {
|
||||
memory_import_index = Some(index);
|
||||
externs.push((index, resolve_memory_import(store, &import_ty, heap_pages)?));
|
||||
continue
|
||||
}
|
||||
|
||||
match import_ty.ty() {
|
||||
ExternType::Func(func_ty) => {
|
||||
pending_func_imports.insert(name.to_owned(), (index, import_ty, func_ty));
|
||||
},
|
||||
_ =>
|
||||
resolve_func_import(store, &import_ty, host_functions, allow_missing_func_imports)?,
|
||||
return Err(WasmError::Other(format!(
|
||||
"host doesn't provide any non function imports besides 'memory': {}:{}",
|
||||
import_ty.module(),
|
||||
name,
|
||||
))),
|
||||
};
|
||||
externs.push(resolved);
|
||||
}
|
||||
|
||||
let mut registry = Registry { store, externs, pending_func_imports };
|
||||
|
||||
H::register_static(&mut registry)?;
|
||||
let mut externs = registry.externs;
|
||||
|
||||
if !registry.pending_func_imports.is_empty() {
|
||||
if allow_missing_func_imports {
|
||||
for (_, (index, import_ty, func_ty)) in registry.pending_func_imports {
|
||||
externs.push((
|
||||
index,
|
||||
MissingHostFuncHandler::new(&import_ty)?.into_extern(store, &func_ty),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
let mut names = Vec::new();
|
||||
for (name, (_, import_ty, _)) in registry.pending_func_imports {
|
||||
names.push(format!("'{}:{}'", import_ty.module(), name));
|
||||
}
|
||||
let names = names.join(", ");
|
||||
return Err(WasmError::Other(format!(
|
||||
"runtime requires function imports which are not present on the host: {}",
|
||||
names
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
externs.sort_unstable_by_key(|&(index, _)| index);
|
||||
let externs = externs.into_iter().map(|(_, ext)| ext).collect();
|
||||
|
||||
Ok(Imports { memory_import_index, externs })
|
||||
}
|
||||
|
||||
struct Registry<'a, 'b> {
|
||||
store: &'a mut Store,
|
||||
externs: Vec<(usize, Extern)>,
|
||||
pending_func_imports: HashMap<String, (usize, ImportType<'b>, FuncType)>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> sp_wasm_interface::HostFunctionRegistry for Registry<'a, 'b> {
|
||||
type State = StoreData;
|
||||
type Error = WasmError;
|
||||
type FunctionContext = HostContext<'a>;
|
||||
|
||||
fn with_function_context<R>(
|
||||
caller: wasmtime::Caller<Self::State>,
|
||||
callback: impl FnOnce(&mut dyn FunctionContext) -> R,
|
||||
) -> R {
|
||||
callback(&mut HostContext { caller })
|
||||
}
|
||||
|
||||
fn register_static<Params, Results>(
|
||||
&mut self,
|
||||
fn_name: &str,
|
||||
func: impl wasmtime::IntoFunc<Self::State, Params, Results>,
|
||||
) -> Result<(), Self::Error> {
|
||||
if let Some((index, _, _)) = self.pending_func_imports.remove(fn_name) {
|
||||
let func = Func::wrap(&mut *self.store, func);
|
||||
self.externs.push((index, Extern::Func(func)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// When the module linking proposal is supported the import's name can be `None`.
|
||||
/// Because we are not using this proposal we could safely unwrap the name.
|
||||
/// However, we opt for an error in order to avoid panics at all costs.
|
||||
@@ -139,115 +209,6 @@ fn resolve_memory_import(
|
||||
Ok(Extern::Memory(memory))
|
||||
}
|
||||
|
||||
fn resolve_func_import(
|
||||
store: &mut Store,
|
||||
import_ty: &ImportType,
|
||||
host_functions: &[&'static dyn Function],
|
||||
allow_missing_func_imports: bool,
|
||||
) -> Result<Extern, WasmError> {
|
||||
let name = import_name(&import_ty)?;
|
||||
|
||||
let func_ty = match import_ty.ty() {
|
||||
ExternType::Func(func_ty) => func_ty,
|
||||
_ =>
|
||||
return Err(WasmError::Other(format!(
|
||||
"host doesn't provide any non function imports besides 'memory': {}:{}",
|
||||
import_ty.module(),
|
||||
name,
|
||||
))),
|
||||
};
|
||||
|
||||
let host_func = match host_functions.iter().find(|host_func| host_func.name() == name) {
|
||||
Some(host_func) => host_func,
|
||||
None if allow_missing_func_imports =>
|
||||
return Ok(MissingHostFuncHandler::new(import_ty)?.into_extern(store, &func_ty)),
|
||||
None =>
|
||||
return Err(WasmError::Other(format!(
|
||||
"host doesn't provide such function: {}:{}",
|
||||
import_ty.module(),
|
||||
name,
|
||||
))),
|
||||
};
|
||||
if &func_ty != &wasmtime_func_sig(*host_func) {
|
||||
return Err(WasmError::Other(format!(
|
||||
"signature mismatch for: {}:{}",
|
||||
import_ty.module(),
|
||||
name,
|
||||
)))
|
||||
}
|
||||
|
||||
Ok(HostFuncHandler::new(*host_func).into_extern(store))
|
||||
}
|
||||
|
||||
/// This structure implements `Callable` and acts as a bridge between wasmtime and
|
||||
/// substrate host functions.
|
||||
struct HostFuncHandler {
|
||||
host_func: &'static dyn Function,
|
||||
}
|
||||
|
||||
fn call_static<'a>(
|
||||
static_func: &'static dyn Function,
|
||||
wasmtime_params: &[Val],
|
||||
wasmtime_results: &mut [Val],
|
||||
mut caller: Caller<'a, StoreData>,
|
||||
) -> Result<(), wasmtime::Trap> {
|
||||
let unwind_result = {
|
||||
let mut host_ctx = HostContext { caller: &mut caller };
|
||||
|
||||
// `from_wasmtime_val` panics if it encounters a value that doesn't fit into the values
|
||||
// available in substrate.
|
||||
//
|
||||
// This, however, cannot happen since the signature of this function is created from
|
||||
// a `dyn Function` signature of which cannot have a non substrate value by definition.
|
||||
let mut params = wasmtime_params.iter().cloned().map(util::from_wasmtime_val);
|
||||
|
||||
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
static_func.execute(&mut host_ctx, &mut params)
|
||||
}))
|
||||
};
|
||||
|
||||
let execution_result = match unwind_result {
|
||||
Ok(execution_result) => execution_result,
|
||||
Err(err) => return Err(Trap::new(stringify_panic_payload(err))),
|
||||
};
|
||||
|
||||
match execution_result {
|
||||
Ok(Some(ret_val)) => {
|
||||
debug_assert!(
|
||||
wasmtime_results.len() == 1,
|
||||
"wasmtime function signature, therefore the number of results, should always \
|
||||
correspond to the number of results returned by the host function",
|
||||
);
|
||||
wasmtime_results[0] = util::into_wasmtime_val(ret_val);
|
||||
Ok(())
|
||||
},
|
||||
Ok(None) => {
|
||||
debug_assert!(
|
||||
wasmtime_results.len() == 0,
|
||||
"wasmtime function signature, therefore the number of results, should always \
|
||||
correspond to the number of results returned by the host function",
|
||||
);
|
||||
Ok(())
|
||||
},
|
||||
Err(msg) => Err(Trap::new(msg)),
|
||||
}
|
||||
}
|
||||
|
||||
impl HostFuncHandler {
|
||||
fn new(host_func: &'static dyn Function) -> Self {
|
||||
Self { host_func }
|
||||
}
|
||||
|
||||
fn into_extern(self, store: &mut Store) -> Extern {
|
||||
let host_func = self.host_func;
|
||||
let func_ty = wasmtime_func_sig(self.host_func);
|
||||
let func = Func::new(store, func_ty, move |caller, params, result| {
|
||||
call_static(host_func, params, result, caller)
|
||||
});
|
||||
Extern::Func(func)
|
||||
}
|
||||
}
|
||||
|
||||
/// A `Callable` handler for missing functions.
|
||||
struct MissingHostFuncHandler {
|
||||
module: String,
|
||||
@@ -270,33 +231,3 @@ impl MissingHostFuncHandler {
|
||||
Extern::Func(func)
|
||||
}
|
||||
}
|
||||
|
||||
fn wasmtime_func_sig(func: &dyn Function) -> wasmtime::FuncType {
|
||||
let signature = func.signature();
|
||||
let params = signature.args.iter().cloned().map(into_wasmtime_val_type);
|
||||
|
||||
let results = signature.return_value.iter().cloned().map(into_wasmtime_val_type);
|
||||
|
||||
wasmtime::FuncType::new(params, results)
|
||||
}
|
||||
|
||||
fn into_wasmtime_val_type(val_ty: ValueType) -> wasmtime::ValType {
|
||||
match val_ty {
|
||||
ValueType::I32 => wasmtime::ValType::I32,
|
||||
ValueType::I64 => wasmtime::ValType::I64,
|
||||
ValueType::F32 => wasmtime::ValType::F32,
|
||||
ValueType::F64 => wasmtime::ValType::F64,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to convert a opaque panic payload to a string.
|
||||
fn stringify_panic_payload(payload: Box<dyn Any + Send + 'static>) -> String {
|
||||
match payload.downcast::<&'static str>() {
|
||||
Ok(msg) => msg.to_string(),
|
||||
Err(payload) => match payload.downcast::<String>() {
|
||||
Ok(msg) => *msg,
|
||||
// At least we tried...
|
||||
Err(_) => "Box<Any>".to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ use sc_executor_common::{
|
||||
error::{Error, Result},
|
||||
wasm_runtime::InvokeMethod,
|
||||
};
|
||||
use sp_wasm_interface::{Function, Pointer, Value, WordSize};
|
||||
use sp_wasm_interface::{HostFunctions, Pointer, Value, WordSize};
|
||||
use wasmtime::{
|
||||
AsContext, AsContextMut, Extern, Func, Global, Instance, Memory, Module, Table, Val,
|
||||
};
|
||||
@@ -138,13 +138,15 @@ fn extern_func(extern_: &Extern) -> Option<&Func> {
|
||||
|
||||
impl InstanceWrapper {
|
||||
/// Create a new instance wrapper from the given wasm module.
|
||||
pub fn new(
|
||||
pub fn new<H>(
|
||||
module: &Module,
|
||||
host_functions: &[&'static dyn Function],
|
||||
heap_pages: u64,
|
||||
allow_missing_func_imports: bool,
|
||||
max_memory_size: Option<usize>,
|
||||
) -> Result<Self> {
|
||||
) -> Result<Self>
|
||||
where
|
||||
H: HostFunctions,
|
||||
{
|
||||
let limits = if let Some(max_memory_size) = max_memory_size {
|
||||
wasmtime::StoreLimitsBuilder::new().memory_size(max_memory_size).build()
|
||||
} else {
|
||||
@@ -161,10 +163,9 @@ impl InstanceWrapper {
|
||||
|
||||
// Scan all imports, find the matching host functions, and create stubs that adapt arguments
|
||||
// and results.
|
||||
let imports = crate::imports::resolve_imports(
|
||||
let imports = crate::imports::resolve_imports::<H>(
|
||||
&mut store,
|
||||
module,
|
||||
host_functions,
|
||||
heap_pages,
|
||||
allow_missing_func_imports,
|
||||
)?;
|
||||
|
||||
@@ -23,6 +23,7 @@ use crate::{
|
||||
instance_wrapper::{EntryPoint, InstanceWrapper},
|
||||
util,
|
||||
};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use sc_allocator::FreeingBumpHeapAllocator;
|
||||
use sc_executor_common::{
|
||||
@@ -33,7 +34,7 @@ use sc_executor_common::{
|
||||
wasm_runtime::{InvokeMethod, WasmInstance, WasmModule},
|
||||
};
|
||||
use sp_runtime_interface::unpack_ptr_and_len;
|
||||
use sp_wasm_interface::{Function, Pointer, Value, WordSize};
|
||||
use sp_wasm_interface::{HostFunctions, Pointer, Value, WordSize};
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::{
|
||||
@@ -79,29 +80,31 @@ impl StoreData {
|
||||
|
||||
pub(crate) type Store = wasmtime::Store<StoreData>;
|
||||
|
||||
enum Strategy {
|
||||
enum Strategy<H> {
|
||||
FastInstanceReuse {
|
||||
instance_wrapper: InstanceWrapper,
|
||||
globals_snapshot: GlobalsSnapshot<wasmtime::Global>,
|
||||
data_segments_snapshot: Arc<DataSegmentsSnapshot>,
|
||||
heap_base: u32,
|
||||
},
|
||||
RecreateInstance(InstanceCreator),
|
||||
RecreateInstance(InstanceCreator<H>),
|
||||
}
|
||||
|
||||
struct InstanceCreator {
|
||||
struct InstanceCreator<H> {
|
||||
module: Arc<wasmtime::Module>,
|
||||
host_functions: Vec<&'static dyn Function>,
|
||||
heap_pages: u64,
|
||||
allow_missing_func_imports: bool,
|
||||
max_memory_size: Option<usize>,
|
||||
phantom: PhantomData<H>,
|
||||
}
|
||||
|
||||
impl InstanceCreator {
|
||||
impl<H> InstanceCreator<H>
|
||||
where
|
||||
H: HostFunctions,
|
||||
{
|
||||
fn instantiate(&mut self) -> Result<InstanceWrapper> {
|
||||
InstanceWrapper::new(
|
||||
InstanceWrapper::new::<H>(
|
||||
&*self.module,
|
||||
&self.host_functions,
|
||||
self.heap_pages,
|
||||
self.allow_missing_func_imports,
|
||||
self.max_memory_size,
|
||||
@@ -141,19 +144,21 @@ struct InstanceSnapshotData {
|
||||
|
||||
/// A `WasmModule` implementation using wasmtime to compile the runtime module to machine code
|
||||
/// and execute the compiled code.
|
||||
pub struct WasmtimeRuntime {
|
||||
pub struct WasmtimeRuntime<H> {
|
||||
module: Arc<wasmtime::Module>,
|
||||
snapshot_data: Option<InstanceSnapshotData>,
|
||||
config: Config,
|
||||
host_functions: Vec<&'static dyn Function>,
|
||||
phantom: PhantomData<H>,
|
||||
}
|
||||
|
||||
impl WasmModule for WasmtimeRuntime {
|
||||
impl<H> WasmModule for WasmtimeRuntime<H>
|
||||
where
|
||||
H: HostFunctions,
|
||||
{
|
||||
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(
|
||||
let mut instance_wrapper = InstanceWrapper::new::<H>(
|
||||
&self.module,
|
||||
&self.host_functions,
|
||||
self.config.heap_pages,
|
||||
self.config.allow_missing_func_imports,
|
||||
self.config.max_memory_size,
|
||||
@@ -169,19 +174,19 @@ impl WasmModule for WasmtimeRuntime {
|
||||
&mut InstanceGlobals { instance: &mut instance_wrapper },
|
||||
);
|
||||
|
||||
Strategy::FastInstanceReuse {
|
||||
Strategy::<H>::FastInstanceReuse {
|
||||
instance_wrapper,
|
||||
globals_snapshot,
|
||||
data_segments_snapshot: snapshot_data.data_segments_snapshot.clone(),
|
||||
heap_base,
|
||||
}
|
||||
} else {
|
||||
Strategy::RecreateInstance(InstanceCreator {
|
||||
Strategy::<H>::RecreateInstance(InstanceCreator {
|
||||
module: self.module.clone(),
|
||||
host_functions: self.host_functions.clone(),
|
||||
heap_pages: self.config.heap_pages,
|
||||
allow_missing_func_imports: self.config.allow_missing_func_imports,
|
||||
max_memory_size: self.config.max_memory_size,
|
||||
phantom: PhantomData,
|
||||
})
|
||||
};
|
||||
|
||||
@@ -191,11 +196,14 @@ impl WasmModule for WasmtimeRuntime {
|
||||
|
||||
/// A `WasmInstance` implementation that reuses compiled module and spawns instances
|
||||
/// to execute the compiled code.
|
||||
pub struct WasmtimeInstance {
|
||||
strategy: Strategy,
|
||||
pub struct WasmtimeInstance<H> {
|
||||
strategy: Strategy<H>,
|
||||
}
|
||||
|
||||
impl WasmInstance for WasmtimeInstance {
|
||||
impl<H> WasmInstance for WasmtimeInstance<H>
|
||||
where
|
||||
H: HostFunctions,
|
||||
{
|
||||
fn call(&mut self, method: InvokeMethod, data: &[u8]) -> Result<Vec<u8>> {
|
||||
match &mut self.strategy {
|
||||
Strategy::FastInstanceReuse {
|
||||
@@ -483,13 +491,18 @@ enum CodeSupplyMode<'a> {
|
||||
|
||||
/// Create a new `WasmtimeRuntime` given the code. This function performs translation from Wasm to
|
||||
/// machine code, which can be computationally heavy.
|
||||
pub fn create_runtime(
|
||||
///
|
||||
/// The `H` generic parameter is used to statically pass a set of host functions which are exposed
|
||||
/// to the runtime.
|
||||
pub fn create_runtime<H>(
|
||||
blob: RuntimeBlob,
|
||||
config: Config,
|
||||
host_functions: Vec<&'static dyn Function>,
|
||||
) -> std::result::Result<WasmtimeRuntime, WasmError> {
|
||||
) -> std::result::Result<WasmtimeRuntime<H>, WasmError>
|
||||
where
|
||||
H: HostFunctions,
|
||||
{
|
||||
// SAFETY: this is safe because it doesn't use `CodeSupplyMode::Artifact`.
|
||||
unsafe { do_create_runtime(CodeSupplyMode::Verbatim { blob }, config, host_functions) }
|
||||
unsafe { do_create_runtime::<H>(CodeSupplyMode::Verbatim { blob }, config) }
|
||||
}
|
||||
|
||||
/// The same as [`create_runtime`] but takes a precompiled artifact, which makes this function
|
||||
@@ -503,23 +516,27 @@ pub fn create_runtime(
|
||||
///
|
||||
/// 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(
|
||||
pub unsafe fn create_runtime_from_artifact<H>(
|
||||
compiled_artifact: &[u8],
|
||||
config: Config,
|
||||
host_functions: Vec<&'static dyn Function>,
|
||||
) -> std::result::Result<WasmtimeRuntime, WasmError> {
|
||||
do_create_runtime(CodeSupplyMode::Artifact { compiled_artifact }, config, host_functions)
|
||||
) -> std::result::Result<WasmtimeRuntime<H>, WasmError>
|
||||
where
|
||||
H: HostFunctions,
|
||||
{
|
||||
do_create_runtime::<H>(CodeSupplyMode::Artifact { compiled_artifact }, config)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// This is only unsafe if called with [`CodeSupplyMode::Artifact`]. See
|
||||
/// [`create_runtime_from_artifact`] to get more details.
|
||||
unsafe fn do_create_runtime(
|
||||
unsafe fn do_create_runtime<H>(
|
||||
code_supply_mode: CodeSupplyMode<'_>,
|
||||
config: Config,
|
||||
host_functions: Vec<&'static dyn Function>,
|
||||
) -> std::result::Result<WasmtimeRuntime, WasmError> {
|
||||
) -> std::result::Result<WasmtimeRuntime<H>, WasmError>
|
||||
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 {
|
||||
@@ -566,7 +583,7 @@ unsafe fn do_create_runtime(
|
||||
},
|
||||
};
|
||||
|
||||
Ok(WasmtimeRuntime { module: Arc::new(module), snapshot_data, config, host_functions })
|
||||
Ok(WasmtimeRuntime { module: Arc::new(module), snapshot_data, config, phantom: PhantomData })
|
||||
}
|
||||
|
||||
fn instrument(
|
||||
|
||||
@@ -78,7 +78,7 @@ impl RuntimeBuilder {
|
||||
.expect("failed to create a runtime blob out of test runtime")
|
||||
};
|
||||
|
||||
let rt = crate::create_runtime(
|
||||
let rt = crate::create_runtime::<HostFunctions>(
|
||||
blob,
|
||||
crate::Config {
|
||||
heap_pages: self.heap_pages,
|
||||
@@ -98,10 +98,6 @@ impl RuntimeBuilder {
|
||||
parallel_compilation: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
use sp_wasm_interface::HostFunctions as _;
|
||||
HostFunctions::host_functions()
|
||||
},
|
||||
)
|
||||
.expect("cannot create runtime");
|
||||
|
||||
@@ -316,9 +312,7 @@ fn test_max_memory_pages() {
|
||||
#[cfg_attr(build_type = "debug", ignore)]
|
||||
#[test]
|
||||
fn test_instances_without_reuse_are_not_leaked() {
|
||||
use sp_wasm_interface::HostFunctions;
|
||||
|
||||
let runtime = crate::create_runtime(
|
||||
let runtime = crate::create_runtime::<HostFunctions>(
|
||||
RuntimeBlob::uncompress_if_needed(&wasm_binary_unwrap()[..]).unwrap(),
|
||||
crate::Config {
|
||||
heap_pages: 2048,
|
||||
@@ -332,7 +326,6 @@ fn test_instances_without_reuse_are_not_leaked() {
|
||||
parallel_compilation: true,
|
||||
},
|
||||
},
|
||||
sp_io::SubstrateHostFunctions::host_functions(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ paste = "1.0"
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false }
|
||||
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
|
||||
sp-api = { version = "4.0.0-dev", path = "../../primitives/api", default-features = false }
|
||||
sp-runtime-interface = { version = "4.0.0", path = "../../primitives/runtime-interface", default-features = false }
|
||||
sp-runtime-interface = { version = "4.1.0-dev", path = "../../primitives/runtime-interface", default-features = false }
|
||||
sp-runtime = { version = "4.0.0-dev", path = "../../primitives/runtime", default-features = false }
|
||||
sp-std = { version = "4.0.0", path = "../../primitives/std", default-features = false }
|
||||
sp-io = { version = "4.0.0-dev", path = "../../primitives/io", default-features = false }
|
||||
|
||||
@@ -69,7 +69,7 @@ libsecp256k1 = { version = "0.7", default-features = false, features = ["hmac",
|
||||
merlin = { version = "2.0", default-features = false, optional = true }
|
||||
ss58-registry = { version = "1.5.0", default-features = false }
|
||||
sp-core-hashing = { version = "4.0.0", path = "./hashing", default-features = false, optional = true }
|
||||
sp-runtime-interface = { version = "4.0.0", default-features = false, path = "../runtime-interface" }
|
||||
sp-runtime-interface = { version = "4.1.0-dev", default-features = false, path = "../runtime-interface" }
|
||||
|
||||
[dev-dependencies]
|
||||
sp-serializer = { version = "4.0.0-dev", path = "../serializer" }
|
||||
|
||||
@@ -22,8 +22,8 @@ sp-keystore = { version = "0.10.0-dev", default-features = false, optional = tru
|
||||
sp-std = { version = "4.0.0", default-features = false, path = "../std" }
|
||||
libsecp256k1 = { version = "0.7", optional = true }
|
||||
sp-state-machine = { version = "0.10.0-dev", optional = true, path = "../state-machine" }
|
||||
sp-wasm-interface = { version = "4.0.0", path = "../wasm-interface", default-features = false }
|
||||
sp-runtime-interface = { version = "4.0.0", default-features = false, path = "../runtime-interface" }
|
||||
sp-wasm-interface = { version = "4.1.0-dev", path = "../wasm-interface", default-features = false }
|
||||
sp-runtime-interface = { version = "4.1.0-dev", default-features = false, path = "../runtime-interface" }
|
||||
sp-trie = { version = "4.0.0-dev", optional = true, path = "../trie" }
|
||||
sp-externalities = { version = "0.10.0", optional = true, path = "../externalities" }
|
||||
sp-tracing = { version = "4.0.0", default-features = false, path = "../tracing" }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "sp-runtime-interface"
|
||||
version = "4.0.0"
|
||||
version = "4.1.0-dev"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
@@ -14,7 +14,7 @@ readme = "README.md"
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
sp-wasm-interface = { version = "4.0.0", path = "../wasm-interface", default-features = false }
|
||||
sp-wasm-interface = { version = "4.1.0-dev", path = "../wasm-interface", default-features = false }
|
||||
sp-std = { version = "4.0.0", default-features = false, path = "../std" }
|
||||
sp-tracing = { version = "4.0.0", default-features = false, path = "../tracing" }
|
||||
sp-runtime-interface-proc-macro = { version = "4.0.0", path = "proc-macro" }
|
||||
|
||||
+189
-165
@@ -35,11 +35,11 @@ use syn::{
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
|
||||
use quote::{quote, ToTokens};
|
||||
use quote::quote;
|
||||
|
||||
use inflector::Inflector;
|
||||
|
||||
use std::iter::{self, Iterator};
|
||||
use std::iter::Iterator;
|
||||
|
||||
/// Generate the extern host functions for wasm and the `HostFunctions` struct that provides the
|
||||
/// implementations for the host functions on the host.
|
||||
@@ -163,14 +163,20 @@ fn generate_host_functions_struct(
|
||||
) -> Result<TokenStream> {
|
||||
let crate_ = generate_crate_access();
|
||||
|
||||
let host_functions = get_runtime_interface(trait_def)?
|
||||
.all_versions()
|
||||
.map(|(version, method)| {
|
||||
generate_host_function_implementation(&trait_def.ident, method, version, is_wasm_only)
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
let mut host_function_impls = Vec::new();
|
||||
let mut host_function_names = Vec::new();
|
||||
let mut register_bodies = Vec::new();
|
||||
for (version, method) in get_runtime_interface(trait_def)?.all_versions() {
|
||||
let (implementation, name, register_body) =
|
||||
generate_host_function_implementation(&trait_def.ident, method, version, is_wasm_only)?;
|
||||
host_function_impls.push(implementation);
|
||||
host_function_names.push(name);
|
||||
register_bodies.push(register_body);
|
||||
}
|
||||
|
||||
Ok(quote! {
|
||||
#(#host_function_impls)*
|
||||
|
||||
/// Provides implementations for the extern host functions.
|
||||
#[cfg(feature = "std")]
|
||||
pub struct HostFunctions;
|
||||
@@ -178,7 +184,16 @@ fn generate_host_functions_struct(
|
||||
#[cfg(feature = "std")]
|
||||
impl #crate_::sp_wasm_interface::HostFunctions for HostFunctions {
|
||||
fn host_functions() -> Vec<&'static dyn #crate_::sp_wasm_interface::Function> {
|
||||
vec![ #( #host_functions ),* ]
|
||||
vec![ #( &#host_function_names as &dyn #crate_::sp_wasm_interface::Function ),* ]
|
||||
}
|
||||
|
||||
#crate_::sp_wasm_interface::if_wasmtime_is_enabled! {
|
||||
fn register_static<T>(registry: &mut T) -> core::result::Result<(), T::Error>
|
||||
where T: #crate_::sp_wasm_interface::HostFunctionRegistry
|
||||
{
|
||||
#(#register_bodies)*
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -194,47 +209,182 @@ fn generate_host_function_implementation(
|
||||
method: &TraitItemMethod,
|
||||
version: u32,
|
||||
is_wasm_only: bool,
|
||||
) -> Result<TokenStream> {
|
||||
) -> Result<(TokenStream, Ident, TokenStream)> {
|
||||
let name = create_host_function_ident(&method.sig.ident, version, trait_name).to_string();
|
||||
let struct_name = Ident::new(&name.to_pascal_case(), Span::call_site());
|
||||
let crate_ = generate_crate_access();
|
||||
let signature = generate_wasm_interface_signature_for_host_function(&method.sig)?;
|
||||
let wasm_to_ffi_values =
|
||||
generate_wasm_to_ffi_values(&method.sig, trait_name).collect::<Result<Vec<_>>>()?;
|
||||
let ffi_to_host_values = generate_ffi_to_host_value(&method.sig).collect::<Result<Vec<_>>>()?;
|
||||
let host_function_call = generate_host_function_call(&method.sig, version, is_wasm_only);
|
||||
let into_preallocated_ffi_value = generate_into_preallocated_ffi_value(&method.sig)?;
|
||||
let convert_return_value = generate_return_value_into_wasm_value(&method.sig);
|
||||
|
||||
Ok(quote! {
|
||||
{
|
||||
struct #struct_name;
|
||||
let fn_name = create_function_ident_with_version(&method.sig.ident, version);
|
||||
let ref_and_mut = get_function_argument_types_ref_and_mut(&method.sig);
|
||||
|
||||
impl #crate_::sp_wasm_interface::Function for #struct_name {
|
||||
fn name(&self) -> &str {
|
||||
#name
|
||||
}
|
||||
// List of variable names containing WASM FFI-compatible arguments.
|
||||
let mut ffi_names = Vec::new();
|
||||
|
||||
fn signature(&self) -> #crate_::sp_wasm_interface::Signature {
|
||||
#signature
|
||||
}
|
||||
// List of `$name: $ty` tokens containing WASM FFI-compatible arguments.
|
||||
let mut ffi_args_prototype = Vec::new();
|
||||
|
||||
fn execute(
|
||||
&self,
|
||||
__function_context__: &mut dyn #crate_::sp_wasm_interface::FunctionContext,
|
||||
args: &mut dyn Iterator<Item = #crate_::sp_wasm_interface::Value>,
|
||||
) -> std::result::Result<Option<#crate_::sp_wasm_interface::Value>, String> {
|
||||
#( #wasm_to_ffi_values )*
|
||||
#( #ffi_to_host_values )*
|
||||
#host_function_call
|
||||
#into_preallocated_ffi_value
|
||||
#convert_return_value
|
||||
}
|
||||
// List of variable names containing arguments already converted into native Rust types.
|
||||
// Also includes the preceding `&` or `&mut`. To be used to call the actual implementation of
|
||||
// the host function.
|
||||
let mut host_names_with_ref = Vec::new();
|
||||
|
||||
// List of code snippets to copy over the results returned from a host function through
|
||||
// any `&mut` arguments back into WASM's linear memory.
|
||||
let mut copy_data_into_ref_mut_args = Vec::new();
|
||||
|
||||
// List of code snippets to convert dynamic FFI args (`Value` enum) into concrete static FFI
|
||||
// types (`u32`, etc.).
|
||||
let mut convert_args_dynamic_ffi_to_static_ffi = Vec::new();
|
||||
|
||||
// List of code snippets to convert static FFI args (`u32`, etc.) into native Rust types.
|
||||
let mut convert_args_static_ffi_to_host = Vec::new();
|
||||
|
||||
for ((host_name, host_ty), ref_and_mut) in
|
||||
get_function_argument_names_and_types_without_ref(&method.sig).zip(ref_and_mut)
|
||||
{
|
||||
let ffi_name = generate_ffi_value_var_name(&host_name)?;
|
||||
let host_name_ident = match *host_name {
|
||||
Pat::Ident(ref pat_ident) => pat_ident.ident.clone(),
|
||||
_ => unreachable!("`generate_ffi_value_var_name` above would return an error on `Pat` != `Ident`; qed"),
|
||||
};
|
||||
|
||||
let ffi_ty = quote! { <#host_ty as #crate_::RIType>::FFIType };
|
||||
ffi_args_prototype.push(quote! { #ffi_name: #ffi_ty });
|
||||
ffi_names.push(quote! { #ffi_name });
|
||||
|
||||
let convert_arg_error = format!(
|
||||
"could not marshal the '{}' argument through the WASM FFI boundary while executing '{}' from interface '{}'",
|
||||
host_name_ident,
|
||||
method.sig.ident,
|
||||
trait_name
|
||||
);
|
||||
convert_args_static_ffi_to_host.push(quote! {
|
||||
let mut #host_name = <#host_ty as #crate_::host::FromFFIValue>::from_ffi_value(__function_context__, #ffi_name)
|
||||
.map_err(|err| format!("{}: {}", err, #convert_arg_error))?;
|
||||
});
|
||||
|
||||
let ref_and_mut_tokens =
|
||||
ref_and_mut.map(|(token_ref, token_mut)| quote!(#token_ref #token_mut));
|
||||
|
||||
host_names_with_ref.push(quote! { #ref_and_mut_tokens #host_name });
|
||||
|
||||
if ref_and_mut.map(|(_, token_mut)| token_mut.is_some()).unwrap_or(false) {
|
||||
copy_data_into_ref_mut_args.push(quote! {
|
||||
<#host_ty as #crate_::host::IntoPreallocatedFFIValue>::into_preallocated_ffi_value(
|
||||
#host_name,
|
||||
__function_context__,
|
||||
#ffi_name,
|
||||
)?;
|
||||
});
|
||||
}
|
||||
|
||||
let arg_count_mismatch_error = format!(
|
||||
"missing argument '{}': number of arguments given to '{}' from interface '{}' does not match the expected number of arguments",
|
||||
host_name_ident,
|
||||
method.sig.ident,
|
||||
trait_name
|
||||
);
|
||||
convert_args_dynamic_ffi_to_static_ffi.push(quote! {
|
||||
let #ffi_name = args.next().ok_or_else(|| #arg_count_mismatch_error.to_owned())?;
|
||||
let #ffi_name: #ffi_ty = #crate_::sp_wasm_interface::TryFromValue::try_from_value(#ffi_name)
|
||||
.ok_or_else(|| #convert_arg_error.to_owned())?;
|
||||
});
|
||||
}
|
||||
|
||||
let ffi_return_ty = match &method.sig.output {
|
||||
ReturnType::Type(_, ty) => quote! { <#ty as #crate_::RIType>::FFIType },
|
||||
ReturnType::Default => quote! { () },
|
||||
};
|
||||
|
||||
let convert_return_value_host_to_static_ffi = match &method.sig.output {
|
||||
ReturnType::Type(_, ty) => quote! {
|
||||
let __result__ = <#ty as #crate_::host::IntoFFIValue>::into_ffi_value(
|
||||
__result__,
|
||||
__function_context__
|
||||
);
|
||||
},
|
||||
ReturnType::Default => quote! {
|
||||
let __result__ = Ok(__result__);
|
||||
},
|
||||
};
|
||||
|
||||
let convert_return_value_static_ffi_to_dynamic_ffi = match &method.sig.output {
|
||||
ReturnType::Type(_, _) => quote! {
|
||||
let __result__ = Ok(Some(#crate_::sp_wasm_interface::IntoValue::into_value(__result__)));
|
||||
},
|
||||
ReturnType::Default => quote! {
|
||||
let __result__ = Ok(None);
|
||||
},
|
||||
};
|
||||
|
||||
if is_wasm_only {
|
||||
host_names_with_ref.push(quote! {
|
||||
__function_context__
|
||||
});
|
||||
}
|
||||
|
||||
let implementation = quote! {
|
||||
#[cfg(feature = "std")]
|
||||
struct #struct_name;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl #struct_name {
|
||||
fn call(
|
||||
__function_context__: &mut dyn #crate_::sp_wasm_interface::FunctionContext,
|
||||
#(#ffi_args_prototype),*
|
||||
) -> std::result::Result<#ffi_return_ty, String> {
|
||||
#(#convert_args_static_ffi_to_host)*
|
||||
let __result__ = #fn_name(#(#host_names_with_ref),*);
|
||||
#(#copy_data_into_ref_mut_args)*
|
||||
#convert_return_value_host_to_static_ffi
|
||||
__result__
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl #crate_::sp_wasm_interface::Function for #struct_name {
|
||||
fn name(&self) -> &str {
|
||||
#name
|
||||
}
|
||||
|
||||
&#struct_name as &dyn #crate_::sp_wasm_interface::Function
|
||||
fn signature(&self) -> #crate_::sp_wasm_interface::Signature {
|
||||
#signature
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&self,
|
||||
__function_context__: &mut dyn #crate_::sp_wasm_interface::FunctionContext,
|
||||
args: &mut dyn Iterator<Item = #crate_::sp_wasm_interface::Value>,
|
||||
) -> std::result::Result<Option<#crate_::sp_wasm_interface::Value>, String> {
|
||||
#(#convert_args_dynamic_ffi_to_static_ffi)*
|
||||
let __result__ = Self::call(
|
||||
__function_context__,
|
||||
#(#ffi_names),*
|
||||
)?;
|
||||
#convert_return_value_static_ffi_to_dynamic_ffi
|
||||
__result__
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let register_body = quote! {
|
||||
registry.register_static(
|
||||
#crate_::sp_wasm_interface::Function::name(&#struct_name),
|
||||
|mut caller: #crate_::sp_wasm_interface::wasmtime::Caller<T::State>, #(#ffi_args_prototype),*|
|
||||
-> std::result::Result<#ffi_return_ty, #crate_::sp_wasm_interface::wasmtime::Trap>
|
||||
{
|
||||
T::with_function_context(caller, move |__function_context__| {
|
||||
#struct_name::call(
|
||||
__function_context__,
|
||||
#(#ffi_names,)*
|
||||
)
|
||||
}).map_err(#crate_::sp_wasm_interface::wasmtime::Trap::new)
|
||||
}
|
||||
)?;
|
||||
};
|
||||
|
||||
Ok((implementation, struct_name, register_body))
|
||||
}
|
||||
|
||||
/// Generate the `wasm_interface::Signature` for the given host function `sig`.
|
||||
@@ -260,86 +410,6 @@ fn generate_wasm_interface_signature_for_host_function(sig: &Signature) -> Resul
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate the code that converts the wasm values given to `HostFunctions::execute` into the FFI
|
||||
/// values.
|
||||
fn generate_wasm_to_ffi_values<'a>(
|
||||
sig: &'a Signature,
|
||||
trait_name: &'a Ident,
|
||||
) -> impl Iterator<Item = Result<TokenStream>> + 'a {
|
||||
let crate_ = generate_crate_access();
|
||||
let function_name = &sig.ident;
|
||||
let error_message = format!(
|
||||
"Number of arguments given to `{}` does not match the expected number of arguments!",
|
||||
function_name,
|
||||
);
|
||||
|
||||
get_function_argument_names_and_types_without_ref(sig).map(move |(name, ty)| {
|
||||
let try_from_error = format!(
|
||||
"Could not instantiate `{}` from wasm value while executing `{}` from interface `{}`!",
|
||||
name.to_token_stream(),
|
||||
function_name,
|
||||
trait_name,
|
||||
);
|
||||
|
||||
let var_name = generate_ffi_value_var_name(&name)?;
|
||||
|
||||
Ok(quote! {
|
||||
let val = args.next().ok_or_else(|| #error_message)?;
|
||||
let #var_name = <
|
||||
<#ty as #crate_::RIType>::FFIType as #crate_::sp_wasm_interface::TryFromValue
|
||||
>::try_from_value(val).ok_or_else(|| #try_from_error)?;
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate the code to convert the ffi values on the host to the host values using `FromFFIValue`.
|
||||
fn generate_ffi_to_host_value<'a>(
|
||||
sig: &'a Signature,
|
||||
) -> impl Iterator<Item = Result<TokenStream>> + 'a {
|
||||
let mut_access = get_function_argument_types_ref_and_mut(sig);
|
||||
let crate_ = generate_crate_access();
|
||||
|
||||
get_function_argument_names_and_types_without_ref(sig)
|
||||
.zip(mut_access.map(|v| v.and_then(|m| m.1)))
|
||||
.map(move |((name, ty), mut_access)| {
|
||||
let ffi_value_var_name = generate_ffi_value_var_name(&name)?;
|
||||
|
||||
Ok(quote! {
|
||||
let #mut_access #name = <#ty as #crate_::host::FromFFIValue>::from_ffi_value(
|
||||
__function_context__,
|
||||
#ffi_value_var_name,
|
||||
)?;
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate the code to call the host function and the ident that stores the result.
|
||||
fn generate_host_function_call(sig: &Signature, version: u32, is_wasm_only: bool) -> TokenStream {
|
||||
let host_function_name = create_function_ident_with_version(&sig.ident, version);
|
||||
let result_var_name = generate_host_function_result_var_name(&sig.ident);
|
||||
let ref_and_mut =
|
||||
get_function_argument_types_ref_and_mut(sig).map(|ram| ram.map(|(vr, vm)| quote!(#vr #vm)));
|
||||
let names = get_function_argument_names(sig);
|
||||
|
||||
let var_access = names
|
||||
.zip(ref_and_mut)
|
||||
.map(|(n, ref_and_mut)| quote!( #ref_and_mut #n ))
|
||||
// If this is a wasm only interface, we add the function context as last parameter.
|
||||
.chain(
|
||||
iter::from_fn(|| if is_wasm_only { Some(quote!(__function_context__)) } else { None })
|
||||
.take(1),
|
||||
);
|
||||
|
||||
quote! {
|
||||
let #result_var_name = #host_function_name ( #( #var_access ),* );
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate the variable name that stores the result of the host function.
|
||||
fn generate_host_function_result_var_name(name: &Ident) -> Ident {
|
||||
Ident::new(&format!("{}_result", name), Span::call_site())
|
||||
}
|
||||
|
||||
/// Generate the variable name that stores the FFI value.
|
||||
fn generate_ffi_value_var_name(pat: &Pat) -> Result<Ident> {
|
||||
match pat {
|
||||
@@ -354,49 +424,3 @@ fn generate_ffi_value_var_name(pat: &Pat) -> Result<Ident> {
|
||||
_ => Err(Error::new(pat.span(), "Not supported as variable name!")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate code that copies data from the host back to preallocated wasm memory.
|
||||
///
|
||||
/// Any argument that is given as `&mut` is interpreted as preallocated memory and it is expected
|
||||
/// that the type implements `IntoPreAllocatedFFIValue`.
|
||||
fn generate_into_preallocated_ffi_value(sig: &Signature) -> Result<TokenStream> {
|
||||
let crate_ = generate_crate_access();
|
||||
let ref_and_mut = get_function_argument_types_ref_and_mut(sig)
|
||||
.map(|ram| ram.and_then(|(vr, vm)| vm.map(|v| (vr, v))));
|
||||
let names_and_types = get_function_argument_names_and_types_without_ref(sig);
|
||||
|
||||
ref_and_mut
|
||||
.zip(names_and_types)
|
||||
.filter_map(|(ram, (name, ty))| ram.map(|_| (name, ty)))
|
||||
.map(|(name, ty)| {
|
||||
let ffi_var_name = generate_ffi_value_var_name(&name)?;
|
||||
|
||||
Ok(quote! {
|
||||
<#ty as #crate_::host::IntoPreallocatedFFIValue>::into_preallocated_ffi_value(
|
||||
#name,
|
||||
__function_context__,
|
||||
#ffi_var_name,
|
||||
)?;
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Generate the code that converts the return value into the appropriate wasm value.
|
||||
fn generate_return_value_into_wasm_value(sig: &Signature) -> TokenStream {
|
||||
let crate_ = generate_crate_access();
|
||||
|
||||
match &sig.output {
|
||||
ReturnType::Default => quote!(Ok(None)),
|
||||
ReturnType::Type(_, ty) => {
|
||||
let result_var_name = generate_host_function_result_var_name(&sig.ident);
|
||||
|
||||
quote! {
|
||||
<#ty as #crate_::host::IntoFFIValue>::into_ffi_value(
|
||||
#result_var_name,
|
||||
__function_context__,
|
||||
).map(#crate_::sp_wasm_interface::IntoValue::into_value).map(Some)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,36 +95,36 @@ macro_rules! impl_traits_for_primitives {
|
||||
}
|
||||
|
||||
impl_traits_for_primitives! {
|
||||
u8, u8,
|
||||
u16, u16,
|
||||
u8, u32,
|
||||
u16, u32,
|
||||
u32, u32,
|
||||
u64, u64,
|
||||
i8, i8,
|
||||
i16, i16,
|
||||
i8, i32,
|
||||
i16, i32,
|
||||
i32, i32,
|
||||
i64, i64,
|
||||
}
|
||||
|
||||
/// `bool` is passed as `u8`.
|
||||
/// `bool` is passed as `u32`.
|
||||
///
|
||||
/// - `1`: true
|
||||
/// - `0`: false
|
||||
impl RIType for bool {
|
||||
type FFIType = u8;
|
||||
type FFIType = u32;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
impl IntoFFIValue for bool {
|
||||
type Owned = ();
|
||||
|
||||
fn into_ffi_value(&self) -> WrappedFFIValue<u8> {
|
||||
fn into_ffi_value(&self) -> WrappedFFIValue<u32> {
|
||||
if *self { 1 } else { 0 }.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
impl FromFFIValue for bool {
|
||||
fn from_ffi_value(arg: u8) -> bool {
|
||||
fn from_ffi_value(arg: u32) -> bool {
|
||||
arg == 1
|
||||
}
|
||||
}
|
||||
@@ -133,14 +133,14 @@ impl FromFFIValue for bool {
|
||||
impl FromFFIValue for bool {
|
||||
type SelfInstance = bool;
|
||||
|
||||
fn from_ffi_value(_: &mut dyn FunctionContext, arg: u8) -> Result<bool> {
|
||||
fn from_ffi_value(_: &mut dyn FunctionContext, arg: u32) -> Result<bool> {
|
||||
Ok(arg == 1)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl IntoFFIValue for bool {
|
||||
fn into_ffi_value(self, _: &mut dyn FunctionContext) -> Result<u8> {
|
||||
fn into_ffi_value(self, _: &mut dyn FunctionContext) -> Result<u32> {
|
||||
Ok(if self { 1 } else { 0 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,17 +80,17 @@
|
||||
//!
|
||||
//! | Type | FFI type | Conversion |
|
||||
//! |----|----|----|
|
||||
//! | `u8` | `u8` | `Identity` |
|
||||
//! | `u16` | `u16` | `Identity` |
|
||||
//! | `u8` | `u32` | zero-extended to 32-bits |
|
||||
//! | `u16` | `u32` | zero-extended to 32-bits |
|
||||
//! | `u32` | `u32` | `Identity` |
|
||||
//! | `u64` | `u64` | `Identity` |
|
||||
//! | `i128` | `u32` | `v.as_ptr()` (pointer to a 16 byte array) |
|
||||
//! | `i8` | `i8` | `Identity` |
|
||||
//! | `i16` | `i16` | `Identity` |
|
||||
//! | `i8` | `i32` | sign-extended to 32-bits |
|
||||
//! | `i16` | `i32` | sign-extended to 32-bits |
|
||||
//! | `i32` | `i32` | `Identity` |
|
||||
//! | `i64` | `i64` | `Identity` |
|
||||
//! | `u128` | `u32` | `v.as_ptr()` (pointer to a 16 byte array) |
|
||||
//! | `bool` | `u8` | `if v { 1 } else { 0 }` |
|
||||
//! | `bool` | `u32` | `if v { 1 } else { 0 }` |
|
||||
//! | `&str` | `u64` | <code>v.len() 32bit << 32 | v.as_ptr() 32bit</code> |
|
||||
//! | `&[u8]` | `u64` | <code>v.len() 32bit << 32 | v.as_ptr() 32bit</code> |
|
||||
//! | `Vec<u8>` | `u64` | <code>v.len() 32bit << 32 | v.as_ptr() 32bit</code> |
|
||||
@@ -325,7 +325,9 @@ pub use util::{pack_ptr_and_len, unpack_ptr_and_len};
|
||||
pub trait RIType {
|
||||
/// The ffi type that is used to represent `Self`.
|
||||
#[cfg(feature = "std")]
|
||||
type FFIType: sp_wasm_interface::IntoValue + sp_wasm_interface::TryFromValue;
|
||||
type FFIType: sp_wasm_interface::IntoValue
|
||||
+ sp_wasm_interface::TryFromValue
|
||||
+ sp_wasm_interface::WasmTy;
|
||||
#[cfg(not(feature = "std"))]
|
||||
type FFIType;
|
||||
}
|
||||
|
||||
@@ -403,11 +403,11 @@ pub struct Enum<T: Copy + Into<u8> + TryFrom<u8>>(PhantomData<T>);
|
||||
#[cfg(feature = "std")]
|
||||
impl<T: Copy + Into<u8> + TryFrom<u8>> PassByImpl<T> for Enum<T> {
|
||||
fn into_ffi_value(instance: T, _: &mut dyn FunctionContext) -> Result<Self::FFIType> {
|
||||
Ok(instance.into())
|
||||
Ok(instance.into() as u32)
|
||||
}
|
||||
|
||||
fn from_ffi_value(_: &mut dyn FunctionContext, arg: Self::FFIType) -> Result<T> {
|
||||
T::try_from(arg).map_err(|_| format!("Invalid enum discriminant: {}", arg))
|
||||
T::try_from(arg as u8).map_err(|_| format!("Invalid enum discriminant: {}", arg))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,17 +417,17 @@ impl<T: Copy + Into<u8> + TryFrom<u8, Error = ()>> PassByImpl<T> for Enum<T> {
|
||||
|
||||
fn into_ffi_value(instance: &T) -> WrappedFFIValue<Self::FFIType, Self::Owned> {
|
||||
let value: u8 = (*instance).into();
|
||||
value.into()
|
||||
(value as u32).into()
|
||||
}
|
||||
|
||||
fn from_ffi_value(arg: Self::FFIType) -> T {
|
||||
T::try_from(arg).expect("Host to wasm provides a valid enum discriminant; qed")
|
||||
T::try_from(arg as u8).expect("Host to wasm provides a valid enum discriminant; qed")
|
||||
}
|
||||
}
|
||||
|
||||
/// The type is passed as `u8`.
|
||||
/// The type is passed as `u32`.
|
||||
///
|
||||
/// The value is corresponds to the discriminant of the variant.
|
||||
impl<T: Copy + Into<u8> + TryFrom<u8>> RIType for Enum<T> {
|
||||
type FFIType = u8;
|
||||
type FFIType = u32;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ publish = false
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
sp-runtime-interface = { version = "4.0.0", default-features = false, path = "../" }
|
||||
sp-runtime-interface = { version = "4.1.0-dev", default-features = false, path = "../" }
|
||||
sp-std = { version = "4.0.0", default-features = false, path = "../../std" }
|
||||
sp-io = { version = "4.0.0-dev", default-features = false, path = "../../io" }
|
||||
sp-core = { version = "4.1.0-dev", default-features = false, path = "../../core" }
|
||||
|
||||
@@ -13,7 +13,7 @@ publish = false
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
sp-runtime-interface = { version = "4.0.0", default-features = false, path = "../" }
|
||||
sp-runtime-interface = { version = "4.1.0-dev", default-features = false, path = "../" }
|
||||
sp-std = { version = "4.0.0", default-features = false, path = "../../std" }
|
||||
sp-io = { version = "4.0.0-dev", default-features = false, path = "../../io" }
|
||||
sp-core = { version = "4.1.0-dev", default-features = false, path = "../../core" }
|
||||
|
||||
@@ -12,7 +12,7 @@ repository = "https://github.com/paritytech/substrate/"
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
sp-runtime-interface = { version = "4.0.0", path = "../" }
|
||||
sp-runtime-interface = { version = "4.1.0-dev", path = "../" }
|
||||
sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" }
|
||||
sc-executor-common = { version = "0.10.0-dev", path = "../../../client/executor/common" }
|
||||
sp-runtime-interface-test-wasm = { version = "2.0.0", path = "../test-wasm" }
|
||||
|
||||
@@ -24,7 +24,7 @@ use sp_runtime_interface_test_wasm::{test_api::HostFunctions, wasm_binary_unwrap
|
||||
use sp_runtime_interface_test_wasm_deprecated::wasm_binary_unwrap as wasm_binary_deprecated_unwrap;
|
||||
|
||||
use sc_executor_common::runtime_blob::RuntimeBlob;
|
||||
use sp_wasm_interface::HostFunctions as HostFunctionsT;
|
||||
use sp_wasm_interface::{ExtendedHostFunctions, HostFunctions as HostFunctionsT};
|
||||
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
@@ -39,17 +39,11 @@ fn call_wasm_method_with_result<HF: HostFunctionsT>(
|
||||
) -> Result<TestExternalities, String> {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext_ext = ext.ext();
|
||||
let mut host_functions = HF::host_functions();
|
||||
host_functions.extend(sp_io::SubstrateHostFunctions::host_functions());
|
||||
|
||||
let executor = sc_executor::WasmExecutor::new(
|
||||
sc_executor::WasmExecutionMethod::Interpreted,
|
||||
Some(8),
|
||||
host_functions,
|
||||
8,
|
||||
None,
|
||||
2,
|
||||
);
|
||||
let executor = sc_executor::WasmExecutor::<
|
||||
ExtendedHostFunctions<sp_io::SubstrateHostFunctions, HF>,
|
||||
>::new(sc_executor::WasmExecutionMethod::Interpreted, Some(8), 8, None, 2);
|
||||
|
||||
executor
|
||||
.uncached_call(
|
||||
RuntimeBlob::uncompress_if_needed(binary).expect("Failed to parse binary"),
|
||||
|
||||
@@ -23,7 +23,7 @@ wasmi = { version = "0.9.0", optional = true }
|
||||
sp-core = { version = "4.1.0-dev", default-features = false, path = "../core" }
|
||||
sp-std = { version = "4.0.0", default-features = false, path = "../std" }
|
||||
sp-io = { version = "4.0.0-dev", default-features = false, path = "../io" }
|
||||
sp-wasm-interface = { version = "4.0.0", default-features = false, path = "../wasm-interface" }
|
||||
sp-wasm-interface = { version = "4.1.0-dev", default-features = false, path = "../wasm-interface" }
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false }
|
||||
log = { version = "0.4", default-features = false }
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ log = { version = "0.4.8", optional = true }
|
||||
sp-core = { version = "4.1.0-dev", default-features = false, path = "../core" }
|
||||
sp-externalities = { version = "0.10.0", optional = true, path = "../externalities" }
|
||||
sp-io = { version = "4.0.0-dev", default-features = false, path = "../io" }
|
||||
sp-runtime-interface = { version = "4.0.0", default-features = false, path = "../runtime-interface" }
|
||||
sp-runtime-interface = { version = "4.1.0-dev", default-features = false, path = "../runtime-interface" }
|
||||
sp-std = { version = "4.0.0", default-features = false, path = "../std" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "sp-wasm-interface"
|
||||
version = "4.0.0"
|
||||
version = "4.1.0-dev"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
@@ -15,10 +15,12 @@ targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
wasmi = { version = "0.9.1", optional = true }
|
||||
wasmtime = { version = "0.31.0", optional = true, default-features = false }
|
||||
log = { version = "0.4.14", optional = true }
|
||||
impl-trait-for-tuples = "0.2.1"
|
||||
sp-std = { version = "4.0.0", path = "../std", default-features = false }
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] }
|
||||
|
||||
[features]
|
||||
default = [ "std" ]
|
||||
std = [ "wasmi", "sp-std/std", "codec/std" ]
|
||||
std = [ "wasmi", "sp-std/std", "codec/std", "log" ]
|
||||
|
||||
@@ -24,6 +24,25 @@ use sp_std::{borrow::Cow, iter::Iterator, marker::PhantomData, mem, result, vec,
|
||||
#[cfg(feature = "std")]
|
||||
mod wasmi_impl;
|
||||
|
||||
#[cfg(not(all(feature = "std", feature = "wasmtime")))]
|
||||
#[macro_export]
|
||||
macro_rules! if_wasmtime_is_enabled {
|
||||
($($token:tt)*) => {};
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "std", feature = "wasmtime"))]
|
||||
#[macro_export]
|
||||
macro_rules! if_wasmtime_is_enabled {
|
||||
($($token:tt)*) => {
|
||||
$($token)*
|
||||
}
|
||||
}
|
||||
|
||||
if_wasmtime_is_enabled! {
|
||||
// Reexport wasmtime so that its types are accessible from the procedural macro.
|
||||
pub use wasmtime;
|
||||
}
|
||||
|
||||
/// Result type used by traits in this crate.
|
||||
#[cfg(feature = "std")]
|
||||
pub type Result<T> = result::Result<T, String>;
|
||||
@@ -105,7 +124,8 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides `Sealed` trait to prevent implementing trait `PointerType` outside of this crate.
|
||||
/// Provides `Sealed` trait to prevent implementing trait `PointerType` and `WasmTy` outside of this
|
||||
/// crate.
|
||||
mod private {
|
||||
pub trait Sealed {}
|
||||
|
||||
@@ -113,6 +133,9 @@ mod private {
|
||||
impl Sealed for u16 {}
|
||||
impl Sealed for u32 {}
|
||||
impl Sealed for u64 {}
|
||||
|
||||
impl Sealed for i32 {}
|
||||
impl Sealed for i64 {}
|
||||
}
|
||||
|
||||
/// Something that can be wrapped in a wasm `Pointer`.
|
||||
@@ -338,10 +361,48 @@ pub trait Sandbox {
|
||||
fn get_global_val(&self, instance_idx: u32, name: &str) -> Result<Option<Value>>;
|
||||
}
|
||||
|
||||
if_wasmtime_is_enabled! {
|
||||
/// A trait used to statically register host callbacks with the WASM executor,
|
||||
/// so that they call be called from within the runtime with minimal overhead.
|
||||
///
|
||||
/// This is used internally to interface the wasmtime-based executor with the
|
||||
/// host functions' definitions generated through the runtime interface macro,
|
||||
/// and is not meant to be used directly.
|
||||
pub trait HostFunctionRegistry {
|
||||
type State;
|
||||
type Error;
|
||||
type FunctionContext: FunctionContext;
|
||||
|
||||
/// Wraps the given `caller` in a type which implements `FunctionContext`
|
||||
/// and calls the given `callback`.
|
||||
fn with_function_context<R>(
|
||||
caller: wasmtime::Caller<Self::State>,
|
||||
callback: impl FnOnce(&mut dyn FunctionContext) -> R,
|
||||
) -> R;
|
||||
|
||||
/// Registers a given host function with the WASM executor.
|
||||
///
|
||||
/// The function has to be statically callable, and all of its arguments
|
||||
/// and its return value have to be compatible with WASM FFI.
|
||||
fn register_static<Params, Results>(
|
||||
&mut self,
|
||||
fn_name: &str,
|
||||
func: impl wasmtime::IntoFunc<Self::State, Params, Results> + 'static,
|
||||
) -> core::result::Result<(), Self::Error>;
|
||||
}
|
||||
}
|
||||
|
||||
/// Something that provides implementations for host functions.
|
||||
pub trait HostFunctions: 'static {
|
||||
pub trait HostFunctions: 'static + Send + Sync {
|
||||
/// Returns the host functions `Self` provides.
|
||||
fn host_functions() -> Vec<&'static dyn Function>;
|
||||
|
||||
if_wasmtime_is_enabled! {
|
||||
/// Statically registers the host functions.
|
||||
fn register_static<T>(registry: &mut T) -> core::result::Result<(), T::Error>
|
||||
where
|
||||
T: HostFunctionRegistry;
|
||||
}
|
||||
}
|
||||
|
||||
#[impl_trait_for_tuples::impl_for_tuples(30)]
|
||||
@@ -353,8 +414,146 @@ impl HostFunctions for Tuple {
|
||||
|
||||
host_functions
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "std", feature = "wasmtime"))]
|
||||
fn register_static<T>(registry: &mut T) -> core::result::Result<(), T::Error>
|
||||
where
|
||||
T: HostFunctionRegistry,
|
||||
{
|
||||
for_tuples!(
|
||||
#( Tuple::register_static(registry)?; )*
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper which merges two sets of host functions, and allows the second set to override
|
||||
/// the host functions from the first set.
|
||||
pub struct ExtendedHostFunctions<Base, Overlay> {
|
||||
phantom: PhantomData<(Base, Overlay)>,
|
||||
}
|
||||
|
||||
impl<Base, Overlay> HostFunctions for ExtendedHostFunctions<Base, Overlay>
|
||||
where
|
||||
Base: HostFunctions,
|
||||
Overlay: HostFunctions,
|
||||
{
|
||||
fn host_functions() -> Vec<&'static dyn Function> {
|
||||
let mut base = Base::host_functions();
|
||||
let overlay = Overlay::host_functions();
|
||||
base.retain(|host_fn| {
|
||||
!overlay.iter().any(|ext_host_fn| host_fn.name() == ext_host_fn.name())
|
||||
});
|
||||
base.extend(overlay);
|
||||
base
|
||||
}
|
||||
|
||||
if_wasmtime_is_enabled! {
|
||||
fn register_static<T>(registry: &mut T) -> core::result::Result<(), T::Error>
|
||||
where
|
||||
T: HostFunctionRegistry,
|
||||
{
|
||||
struct Proxy<'a, T> {
|
||||
registry: &'a mut T,
|
||||
seen_overlay: std::collections::HashSet<String>,
|
||||
seen_base: std::collections::HashSet<String>,
|
||||
overlay_registered: bool,
|
||||
}
|
||||
|
||||
impl<'a, T> HostFunctionRegistry for Proxy<'a, T>
|
||||
where
|
||||
T: HostFunctionRegistry,
|
||||
{
|
||||
type State = T::State;
|
||||
type Error = T::Error;
|
||||
type FunctionContext = T::FunctionContext;
|
||||
|
||||
fn with_function_context<R>(
|
||||
caller: wasmtime::Caller<Self::State>,
|
||||
callback: impl FnOnce(&mut dyn FunctionContext) -> R,
|
||||
) -> R {
|
||||
T::with_function_context(caller, callback)
|
||||
}
|
||||
|
||||
fn register_static<Params, Results>(
|
||||
&mut self,
|
||||
fn_name: &str,
|
||||
func: impl wasmtime::IntoFunc<Self::State, Params, Results> + 'static,
|
||||
) -> core::result::Result<(), Self::Error> {
|
||||
if self.overlay_registered {
|
||||
if !self.seen_base.insert(fn_name.to_owned()) {
|
||||
log::warn!(
|
||||
target: "extended_host_functions",
|
||||
"Duplicate base host function: '{}'",
|
||||
fn_name,
|
||||
);
|
||||
|
||||
// TODO: Return an error here?
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
if self.seen_overlay.contains(fn_name) {
|
||||
// Was already registered when we went through the overlay, so just ignore it.
|
||||
log::debug!(
|
||||
target: "extended_host_functions",
|
||||
"Overriding base host function: '{}'",
|
||||
fn_name,
|
||||
);
|
||||
|
||||
return Ok(())
|
||||
}
|
||||
} else if !self.seen_overlay.insert(fn_name.to_owned()) {
|
||||
log::warn!(
|
||||
target: "extended_host_functions",
|
||||
"Duplicate overlay host function: '{}'",
|
||||
fn_name,
|
||||
);
|
||||
|
||||
// TODO: Return an error here?
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
self.registry.register_static(fn_name, func)
|
||||
}
|
||||
}
|
||||
|
||||
let mut proxy = Proxy {
|
||||
registry,
|
||||
seen_overlay: Default::default(),
|
||||
seen_base: Default::default(),
|
||||
overlay_registered: false,
|
||||
};
|
||||
|
||||
// The functions from the `Overlay` can override those from the `Base`,
|
||||
// so `Overlay` is registered first, and then we skip those functions
|
||||
// in `Base` if they were already registered from the `Overlay`.
|
||||
Overlay::register_static(&mut proxy)?;
|
||||
proxy.overlay_registered = true;
|
||||
Base::register_static(&mut proxy)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for types directly usable at the WASM FFI boundary without any conversion at all.
|
||||
///
|
||||
/// This trait is sealed and should not be implemented downstream.
|
||||
#[cfg(all(feature = "std", feature = "wasmtime"))]
|
||||
pub trait WasmTy: wasmtime::WasmTy + private::Sealed {}
|
||||
|
||||
/// A trait for types directly usable at the WASM FFI boundary without any conversion at all.
|
||||
///
|
||||
/// This trait is sealed and should not be implemented downstream.
|
||||
#[cfg(not(all(feature = "std", feature = "wasmtime")))]
|
||||
pub trait WasmTy: private::Sealed {}
|
||||
|
||||
impl WasmTy for i32 {}
|
||||
impl WasmTy for u32 {}
|
||||
impl WasmTy for i64 {}
|
||||
impl WasmTy for u64 {}
|
||||
|
||||
/// Something that can be converted into a wasm compatible `Value`.
|
||||
pub trait IntoValue {
|
||||
/// The type of the value in wasm.
|
||||
|
||||
@@ -262,7 +262,8 @@ impl<Block: BlockT, D, Backend, G: GenesisInit>
|
||||
client::LocalCallExecutor<Block, Backend, NativeElseWasmExecutor<D>>,
|
||||
Backend,
|
||||
G,
|
||||
>
|
||||
> where
|
||||
D: sc_executor::NativeExecutionDispatch,
|
||||
{
|
||||
/// Build the test client with the given native executor.
|
||||
pub fn build_with_native_executor<RuntimeApi, I>(
|
||||
|
||||
@@ -25,7 +25,7 @@ memory-db = { version = "0.27.0", default-features = false }
|
||||
sp-offchain = { version = "4.0.0-dev", default-features = false, path = "../../primitives/offchain" }
|
||||
sp-core = { version = "4.1.0-dev", default-features = false, path = "../../primitives/core" }
|
||||
sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" }
|
||||
sp-runtime-interface = { version = "4.0.0", default-features = false, path = "../../primitives/runtime-interface" }
|
||||
sp-runtime-interface = { version = "4.1.0-dev", default-features = false, path = "../../primitives/runtime-interface" }
|
||||
sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" }
|
||||
frame-support = { version = "4.0.0-dev", default-features = false, path = "../../frame/support" }
|
||||
sp-version = { version = "4.0.0-dev", default-features = false, path = "../../primitives/version" }
|
||||
|
||||
@@ -52,3 +52,8 @@ tokio = { version = "1.13", features = ["signal"] }
|
||||
# Calling RPC
|
||||
jsonrpc-core = "18.0"
|
||||
num-traits = "0.2.14"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
# This is here so that we can use the `runtime_interface` procedural macro
|
||||
std = []
|
||||
|
||||
@@ -16,75 +16,38 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/// Use this to override host functions.
|
||||
/// eg
|
||||
/// ```rust
|
||||
/// use test_runner::override_host_functions;
|
||||
/// pub struct SignatureVerificationOverride;
|
||||
///
|
||||
/// impl sp_wasm_interface::HostFunctions for SignatureVerificationOverride {
|
||||
/// fn host_functions() -> Vec<&'static dyn sp_wasm_interface::Function> {
|
||||
/// override_host_functions!(
|
||||
/// "ext_crypto_ecdsa_verify_version_1", EcdsaVerify,
|
||||
/// )
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! override_host_functions {
|
||||
($($fn_name:expr, $name:ident,)*) => {{
|
||||
let mut host_functions = vec![];
|
||||
$(
|
||||
struct $name;
|
||||
impl sp_wasm_interface::Function for $name {
|
||||
fn name(&self) -> &str {
|
||||
&$fn_name
|
||||
}
|
||||
use sp_core::{ecdsa, ed25519, sr25519};
|
||||
use sp_runtime_interface::runtime_interface;
|
||||
|
||||
fn signature(&self) -> sp_wasm_interface::Signature {
|
||||
sp_wasm_interface::Signature {
|
||||
args: std::borrow::Cow::Owned(vec![
|
||||
sp_wasm_interface::ValueType::I32,
|
||||
sp_wasm_interface::ValueType::I64,
|
||||
sp_wasm_interface::ValueType::I32,
|
||||
]),
|
||||
return_value: Some(sp_wasm_interface::ValueType::I32),
|
||||
}
|
||||
}
|
||||
#[runtime_interface]
|
||||
trait Crypto {
|
||||
fn ecdsa_verify(_sig: &ecdsa::Signature, _msg: &[u8], _pub_key: &ecdsa::Public) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&self,
|
||||
context: &mut dyn sp_wasm_interface::FunctionContext,
|
||||
_args: &mut dyn Iterator<Item = sp_wasm_interface::Value>,
|
||||
) -> Result<Option<sp_wasm_interface::Value>, String> {
|
||||
<bool as sp_runtime_interface::host::IntoFFIValue>::into_ffi_value(true, context)
|
||||
.map(sp_wasm_interface::IntoValue::into_value)
|
||||
.map(Some)
|
||||
}
|
||||
}
|
||||
host_functions.push(&$name as &'static dyn sp_wasm_interface::Function);
|
||||
)*
|
||||
host_functions
|
||||
}};
|
||||
#[version(2)]
|
||||
fn ecdsa_verify(_sig: &ecdsa::Signature, _msg: &[u8], _pub_key: &ecdsa::Public) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn ed25519_verify(_sig: &ed25519::Signature, _msg: &[u8], _pub_key: &ed25519::Public) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn sr25519_verify(_sig: &sr25519::Signature, _msg: &[u8], _pub_key: &sr25519::Public) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[version(2)]
|
||||
fn sr25519_verify(_sig: &sr25519::Signature, _msg: &[u8], _pub_key: &sr25519::Public) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides host functions that overrides runtime signature verification
|
||||
/// to always return true.
|
||||
pub struct SignatureVerificationOverride;
|
||||
pub type SignatureVerificationOverride = crypto::HostFunctions;
|
||||
|
||||
impl sp_wasm_interface::HostFunctions for SignatureVerificationOverride {
|
||||
fn host_functions() -> Vec<&'static dyn sp_wasm_interface::Function> {
|
||||
override_host_functions!(
|
||||
"ext_crypto_ecdsa_verify_version_1",
|
||||
EcdsaVerify,
|
||||
"ext_crypto_ecdsa_verify_version_2",
|
||||
EcdsaVerifyV2,
|
||||
"ext_crypto_ed25519_verify_version_1",
|
||||
Ed25519Verify,
|
||||
"ext_crypto_sr25519_verify_version_1",
|
||||
Sr25519Verify,
|
||||
"ext_crypto_sr25519_verify_version_2",
|
||||
Sr25519VerifyV2,
|
||||
)
|
||||
}
|
||||
}
|
||||
// This is here to get rid of the warnings.
|
||||
#[allow(unused_imports, dead_code)]
|
||||
use self::crypto::{ecdsa_verify, ed25519_verify, sr25519_verify};
|
||||
|
||||
Reference in New Issue
Block a user