mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-06 05:38:00 +00:00
executor: Migrate wasmtime backend to a high-level API (#4686)
* Migrate wasmtime backend to wasmtime-api * Port to a newer version of wasmtime * Update to the latest changes. * Rejig the sandbox module a bit * Materialze * Fixes. * executor wasm_runtime fix * Refactor everything * More refactoring * Even more refactorings * More cleaning. * Update to the latest wasmtime * Reformat * Renames * Refactoring and comments. * Docs * Rename FunctionExecutor to host. * Imrpove docs. * fmt * Remove panic * Assert the number of arguments are equal between wasmtime and hostfunc. * Comment a possible panic if there is no corresponding value variant. * Check signature of the entrypoint. * Use git version of wasmtime * Refine and doc the sandbox code. * Comment RefCells. * Update wasmtime to the latest-ish master. This may solve a problem with segfaults. * Apply suggestions from code review Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com> * Use full SHA1 hash of wasmtime commit. * Add a panic message. * Add some documentation * Update wasmtime version to include SIGSEGV fix * Update to crates.io version of wasmtime * Make it work. * Move the creation of memory into `InstanceWrapper::new` * Make `InstanceWrapper` !Send & !Sync * Avoid using `take_mut` * Update client/executor/wasmtime/Cargo.toml Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com> * Limit maximum size of memory. * Rename `init_state` to `with_initialized_state` Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com> Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
This commit is contained in:
Generated
+105
-53
@@ -723,24 +723,25 @@ checksum = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b"
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-bforest"
|
||||
version = "0.50.0"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd05aac8cefcde54ce26178df8f36cb1f518ac691db650e7d2440c2b6b41c4dc"
|
||||
checksum = "fd0f53d59dc9ab1c8ab68c991d8406b52b7a0aab0b15b05a3a6895579c4e5dd9"
|
||||
dependencies = [
|
||||
"cranelift-entity",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-codegen"
|
||||
version = "0.50.0"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c63d9b6ff8a94f98deabab21880d7fd54996e0e16be687b6f80a3b6bdd9c188d"
|
||||
checksum = "0381a794836fb994c47006465d46d46be072483b667f36013d993b9895117fee"
|
||||
dependencies = [
|
||||
"byteorder 1.3.4",
|
||||
"cranelift-bforest",
|
||||
"cranelift-codegen-meta",
|
||||
"cranelift-codegen-shared",
|
||||
"cranelift-entity",
|
||||
"gimli 0.20.0",
|
||||
"log 0.4.8",
|
||||
"serde",
|
||||
"smallvec 1.2.0",
|
||||
@@ -750,9 +751,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-codegen-meta"
|
||||
version = "0.50.0"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cb3df51c2c07d719d02869bfac6cabd8d82ee308d5b29ca62e6528723cc33a4"
|
||||
checksum = "208c3c8d82bfef32a534c5020c6cfc3bc92f41388f1246b7bb98cf543331abaa"
|
||||
dependencies = [
|
||||
"cranelift-codegen-shared",
|
||||
"cranelift-entity",
|
||||
@@ -760,24 +761,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-codegen-shared"
|
||||
version = "0.50.0"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "758f9426b2e22bf83fc1a6b231a9d53cd4830751883c7f0e196ebb3c210467b3"
|
||||
checksum = "ea048c456a517e56fd6df8f0e3947922897e6e6f61fbc5eb557a36c7b8ff6394"
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-entity"
|
||||
version = "0.50.0"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff064733df8b98f453060264a8790393d1e807aca6942706b42f79a4f7aae9ed"
|
||||
checksum = "0c8c7ed50812194c9e9de1fa39c77b39fc9ab48173d5e7ee88b25b6a8953e9b8"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-frontend"
|
||||
version = "0.50.0"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1eaafb5fa623dcbe19a28084a8226d7a1b17184a949c1a1f29a46b479867998d"
|
||||
checksum = "21ceb931d9f919731df1b1ecdc716b5c66384b413a7f95909d1f45441ab9bef5"
|
||||
dependencies = [
|
||||
"cranelift-codegen",
|
||||
"log 0.4.8",
|
||||
@@ -787,9 +788,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-native"
|
||||
version = "0.50.0"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90033dbd7293f6fad4cf9dcd769cd621d60df22b1c5a11799e86359b7447a51d"
|
||||
checksum = "564ee82268bc25b914fcf331edfc2452f2d9ca34f976b187b4ca668beba250c8"
|
||||
dependencies = [
|
||||
"cranelift-codegen",
|
||||
"raw-cpuid",
|
||||
@@ -798,9 +799,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-wasm"
|
||||
version = "0.50.0"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54cb82a1071f88822763a583ec1a8688ffe5e2cda02c111d4483dd4376ed14d8"
|
||||
checksum = "de63e2271b374be5b07f359184e2126a08fb24d24a740cbc178b7e0107ddafa5"
|
||||
dependencies = [
|
||||
"cranelift-codegen",
|
||||
"cranelift-entity",
|
||||
@@ -808,7 +809,7 @@ dependencies = [
|
||||
"log 0.4.8",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"wasmparser",
|
||||
"wasmparser 0.48.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1289,9 +1290,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "faerie"
|
||||
version = "0.13.0"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f902f2af041f6c7177a2a04f805687cdc71e69c7cbef059a2755d8923f4cd7a8"
|
||||
checksum = "74b9ed6159e4a6212c61d9c6a86bee01876b192a64accecf58d5b5ae3b667b52"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"goblin",
|
||||
@@ -1858,6 +1859,16 @@ dependencies = [
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81dd6190aad0f05ddbbf3245c54ed14ca4aa6dd32f22312b70d8f168c3e3e633"
|
||||
dependencies = [
|
||||
"byteorder 1.3.4",
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.2.11"
|
||||
@@ -2599,6 +2610,12 @@ version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f"
|
||||
|
||||
[[package]]
|
||||
name = "leb128"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.66"
|
||||
@@ -5960,11 +5977,6 @@ name = "sc-executor-wasmtime"
|
||||
version = "0.8.0"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"cranelift-codegen",
|
||||
"cranelift-entity",
|
||||
"cranelift-frontend",
|
||||
"cranelift-native",
|
||||
"cranelift-wasm",
|
||||
"log 0.4.8",
|
||||
"parity-scale-codec",
|
||||
"parity-wasm 0.41.0",
|
||||
@@ -5974,9 +5986,7 @@ dependencies = [
|
||||
"sp-runtime-interface",
|
||||
"sp-wasm-interface",
|
||||
"wasmi",
|
||||
"wasmtime-environ",
|
||||
"wasmtime-jit",
|
||||
"wasmtime-runtime",
|
||||
"wasmtime",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7726,9 +7736,9 @@ checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.9.0"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f4c118a7a38378f305a9e111fcb2f7f838c0be324bfb31a77ea04f7f6e684b4"
|
||||
checksum = "ab0e7238dcc7b40a7be719a25365910f6807bd864f4cce6b2e6b873658e2b19d"
|
||||
|
||||
[[package]]
|
||||
name = "target_info"
|
||||
@@ -8310,7 +8320,7 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bfd5b7557925ce778ff9b9ef90e3ade34c524b5ff10e239c69a42d546d2af56"
|
||||
dependencies = [
|
||||
"rand 0.3.23",
|
||||
"rand 0.7.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8673,35 +8683,61 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.39.3"
|
||||
version = "0.48.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c702914acda5feeeffbc29e4d953e5b9ce79d8b98da4dbf18a77086e116c5470"
|
||||
checksum = "073da89bf1c84db000dd68ce660c1b4a08e3a2d28fd1e3394ab9e7abdde4a0f8"
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.51.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e41b27a1677fe28c115de49efca55dabb14f7fece2c32947ffb9b1064fe5bd4"
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5614d964c3e7d07a13b59aca66103c52656bd80430f0d86dc7eeb3af4f03d4a2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"backtrace",
|
||||
"cfg-if",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"region",
|
||||
"rustc-demangle",
|
||||
"target-lexicon",
|
||||
"wasmparser 0.51.1",
|
||||
"wasmtime-environ",
|
||||
"wasmtime-jit",
|
||||
"wasmtime-runtime",
|
||||
"wat",
|
||||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-debug"
|
||||
version = "0.8.0"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5008729ad53f75020f28fa0d682269335d6f0eac0b3ffafe31f185b2f33aca74"
|
||||
checksum = "feb5900275b4ef0b621ce725b9d5660b12825d7f7d79b392b97baf089ffab8c0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cranelift-codegen",
|
||||
"cranelift-entity",
|
||||
"cranelift-wasm",
|
||||
"faerie",
|
||||
"gimli",
|
||||
"gimli 0.19.0",
|
||||
"more-asserts",
|
||||
"target-lexicon",
|
||||
"thiserror",
|
||||
"wasmparser",
|
||||
"wasmparser 0.51.1",
|
||||
"wasmtime-environ",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-environ"
|
||||
version = "0.8.0"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3947662a0b8e05b1418465e64f16de9114f9fec18cc3f56e0ed5aa7737b89d0"
|
||||
checksum = "f04661851e133fb11691c4a0f92a705766b4bbf7afc06811f949e295cc8414fc"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.11.0",
|
||||
"bincode",
|
||||
"cranelift-codegen",
|
||||
@@ -8711,37 +8747,37 @@ dependencies = [
|
||||
"errno",
|
||||
"file-per-thread-logger",
|
||||
"indexmap",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log 0.4.8",
|
||||
"more-asserts",
|
||||
"rayon",
|
||||
"serde",
|
||||
"sha2",
|
||||
"spin",
|
||||
"thiserror",
|
||||
"toml",
|
||||
"wasmparser",
|
||||
"wasmparser 0.51.1",
|
||||
"winapi 0.3.8",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-jit"
|
||||
version = "0.8.0"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ed7922689461a7b5bd0d9c7350cac526c8a520a23b3ffd7f5b446ac51dfc51f"
|
||||
checksum = "d451353764ce55c9bb6a8b260063cfc209b7adadd277a9a872ab4563a69e357c"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cfg-if",
|
||||
"cranelift-codegen",
|
||||
"cranelift-entity",
|
||||
"cranelift-frontend",
|
||||
"cranelift-native",
|
||||
"cranelift-wasm",
|
||||
"more-asserts",
|
||||
"region",
|
||||
"target-lexicon",
|
||||
"thiserror",
|
||||
"wasmparser",
|
||||
"wasmparser 0.51.1",
|
||||
"wasmtime-debug",
|
||||
"wasmtime-environ",
|
||||
"wasmtime-runtime",
|
||||
@@ -8750,16 +8786,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-runtime"
|
||||
version = "0.8.0"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "781d6bb8b346efaa3dc39746386957cd79b8d841e8652ed9b02d77bcf64fb514"
|
||||
checksum = "7dbd4fc114b828cae3e405fed413df4b3814d87a92ea029640cec9ba41f0c162"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"cc",
|
||||
"cranelift-codegen",
|
||||
"cranelift-entity",
|
||||
"cranelift-wasm",
|
||||
"cfg-if",
|
||||
"indexmap",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"memoffset",
|
||||
"more-asserts",
|
||||
@@ -8769,6 +8803,24 @@ dependencies = [
|
||||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wast"
|
||||
version = "7.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12a729d076deb29c8509fa71f2d427729f9394f9496844ed8fcab152f35d163d"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wat"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5795e34a4b39893653dec97e644fac85c31398e0ce1abecc48967aac83d9e8ce"
|
||||
dependencies = [
|
||||
"wast",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.35"
|
||||
|
||||
@@ -23,12 +23,6 @@ use sp_wasm_interface::Function;
|
||||
///
|
||||
/// This can be implemented by an execution engine.
|
||||
pub trait WasmRuntime {
|
||||
/// Attempt to update the number of heap pages available during execution.
|
||||
///
|
||||
/// Returns false if the update cannot be applied. The function is guaranteed to return true if
|
||||
/// the heap pages would not change from its current value.
|
||||
fn update_heap_pages(&mut self, heap_pages: u64) -> bool;
|
||||
|
||||
/// Return the host functions that are registered for this Wasm runtime.
|
||||
fn host_functions(&self) -> &[&'static dyn Function];
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ fn call_not_existing_function(wasm_method: WasmExecutionMethod) {
|
||||
#[cfg(feature = "wasmtime")]
|
||||
WasmExecutionMethod::Compiled => assert_eq!(
|
||||
&format!("{:?}", e),
|
||||
"Other(\"call to undefined external function with index 68\")"
|
||||
"Other(\"Wasm execution trapped: call to a missing function env:missing_external\")"
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -117,7 +117,7 @@ fn call_yet_another_not_existing_function(wasm_method: WasmExecutionMethod) {
|
||||
#[cfg(feature = "wasmtime")]
|
||||
WasmExecutionMethod::Compiled => assert_eq!(
|
||||
&format!("{:?}", e),
|
||||
"Other(\"call to undefined external function with index 69\")"
|
||||
"Other(\"Wasm execution trapped: call to a missing function env:yet_another_missing_external\")"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ pub enum WasmExecutionMethod {
|
||||
/// A Wasm runtime object along with its cached runtime version.
|
||||
struct VersionedRuntime {
|
||||
runtime: Box<dyn WasmRuntime>,
|
||||
/// The number of WebAssembly heap pages this instance was created with.
|
||||
heap_pages: u64,
|
||||
/// Runtime version according to `Core_version`.
|
||||
version: RuntimeVersion,
|
||||
}
|
||||
@@ -122,7 +124,7 @@ impl RuntimesCache {
|
||||
Entry::Occupied(o) => {
|
||||
let result = o.into_mut();
|
||||
if let Ok(ref mut cached_runtime) = result {
|
||||
let heap_pages_changed = !cached_runtime.runtime.update_heap_pages(heap_pages);
|
||||
let heap_pages_changed = cached_runtime.heap_pages != heap_pages;
|
||||
let host_functions_changed = cached_runtime.runtime.host_functions()
|
||||
!= host_functions;
|
||||
if heap_pages_changed || host_functions_changed {
|
||||
@@ -236,6 +238,7 @@ fn create_versioned_wasm_runtime<E: Externalities>(
|
||||
Ok(VersionedRuntime {
|
||||
runtime,
|
||||
version,
|
||||
heap_pages,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -73,8 +73,7 @@ impl<'a> sandbox::SandboxCapabilities for FunctionExecutor<'a> {
|
||||
invoke_args_len: WordSize,
|
||||
state: u32,
|
||||
func_idx: sandbox::SupervisorFuncIndex,
|
||||
) -> Result<i64, Error>
|
||||
{
|
||||
) -> Result<i64, Error> {
|
||||
let result = wasmi::FuncInstance::invoke(
|
||||
dispatch_thunk,
|
||||
&[
|
||||
@@ -536,7 +535,6 @@ struct StateSnapshot {
|
||||
data_segments: Vec<(u32, Vec<u8>)>,
|
||||
/// The list of all global mutable variables of the module in their sequential order.
|
||||
global_mut_values: Vec<RuntimeValue>,
|
||||
heap_pages: u64,
|
||||
}
|
||||
|
||||
impl StateSnapshot {
|
||||
@@ -544,7 +542,6 @@ impl StateSnapshot {
|
||||
fn take(
|
||||
module_instance: &ModuleRef,
|
||||
data_segments: Vec<DataSegment>,
|
||||
heap_pages: u64,
|
||||
) -> Option<Self> {
|
||||
let prepared_segments = data_segments
|
||||
.into_iter()
|
||||
@@ -590,7 +587,6 @@ impl StateSnapshot {
|
||||
Some(Self {
|
||||
data_segments: prepared_segments,
|
||||
global_mut_values,
|
||||
heap_pages,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -646,10 +642,6 @@ pub struct WasmiRuntime {
|
||||
}
|
||||
|
||||
impl WasmRuntime for WasmiRuntime {
|
||||
fn update_heap_pages(&mut self, heap_pages: u64) -> bool {
|
||||
self.state_snapshot.heap_pages == heap_pages
|
||||
}
|
||||
|
||||
fn host_functions(&self) -> &[&'static dyn Function] {
|
||||
&self.host_functions
|
||||
}
|
||||
@@ -702,7 +694,7 @@ pub fn create_instance(
|
||||
).map_err(|e| WasmError::Instantiation(e.to_string()))?;
|
||||
|
||||
// Take state snapshot before executing anything.
|
||||
let state_snapshot = StateSnapshot::take(&instance, data_segments, heap_pages)
|
||||
let state_snapshot = StateSnapshot::take(&instance, data_segments)
|
||||
.expect(
|
||||
"`take` returns `Err` if the module is not valid;
|
||||
we already loaded module above, thus the `Module` is proven to be valid at this point;
|
||||
|
||||
@@ -16,14 +16,7 @@ sp-runtime-interface = { version = "2.0.0", path = "../../../primitives/runtime-
|
||||
sp-core = { version = "2.0.0", path = "../../../primitives/core" }
|
||||
sp-allocator = { version = "2.0.0", path = "../../../primitives/allocator" }
|
||||
|
||||
cranelift-codegen = "0.50"
|
||||
cranelift-entity = "0.50"
|
||||
cranelift-frontend = "0.50"
|
||||
cranelift-native = "0.50"
|
||||
cranelift-wasm = "0.50"
|
||||
wasmtime-environ = "0.8"
|
||||
wasmtime-jit = "0.8"
|
||||
wasmtime-runtime = "0.8"
|
||||
wasmtime = "0.11"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.3.0"
|
||||
|
||||
@@ -1,379 +0,0 @@
|
||||
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use sp_allocator::FreeingBumpHeapAllocator;
|
||||
use sc_executor_common::error::{Error, Result};
|
||||
use sc_executor_common::sandbox::{self, SandboxCapabilities, SupervisorFuncIndex};
|
||||
use crate::util::{
|
||||
checked_range, cranelift_ir_signature, read_memory_into, write_memory_from,
|
||||
};
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use cranelift_codegen::ir;
|
||||
use cranelift_codegen::isa::TargetFrontendConfig;
|
||||
use log::trace;
|
||||
use sp_core::sandbox as sandbox_primitives;
|
||||
use std::{cmp, mem, ptr};
|
||||
use wasmtime_environ::translate_signature;
|
||||
use wasmtime_jit::{ActionError, Compiler};
|
||||
use wasmtime_runtime::{Export, VMCallerCheckedAnyfunc, VMContext, wasmtime_call_trampoline};
|
||||
use sp_wasm_interface::{
|
||||
FunctionContext, MemoryId, Pointer, Result as WResult, Sandbox, Signature, Value, ValueType,
|
||||
WordSize,
|
||||
};
|
||||
|
||||
/// Wrapper type for pointer to a Wasm table entry.
|
||||
///
|
||||
/// The wrapper type is used to ensure that the function reference is valid as it must be unsafely
|
||||
/// dereferenced from within the safe method `<FunctionExecutor as SandboxCapabilities>::invoke`.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SupervisorFuncRef(*const VMCallerCheckedAnyfunc);
|
||||
|
||||
/// The state required to construct a FunctionExecutor context. The context only lasts for one host
|
||||
/// call, whereas the state is maintained for the duration of a Wasm runtime call, which may make
|
||||
/// many different host calls that must share state.
|
||||
///
|
||||
/// This is stored as part of the host state of the "env" Wasmtime instance.
|
||||
pub struct FunctionExecutorState {
|
||||
sandbox_store: sandbox::Store<SupervisorFuncRef>,
|
||||
heap: FreeingBumpHeapAllocator,
|
||||
}
|
||||
|
||||
impl FunctionExecutorState {
|
||||
/// Constructs a new `FunctionExecutorState`.
|
||||
pub fn new(heap_base: u32) -> Self {
|
||||
FunctionExecutorState {
|
||||
sandbox_store: sandbox::Store::new(),
|
||||
heap: FreeingBumpHeapAllocator::new(heap_base),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the heap allocator.
|
||||
pub fn heap(&mut self) -> &mut FreeingBumpHeapAllocator {
|
||||
&mut self.heap
|
||||
}
|
||||
}
|
||||
|
||||
/// A `FunctionExecutor` implements `FunctionContext` for making host calls from a Wasmtime
|
||||
/// runtime. The `FunctionExecutor` exists only for the lifetime of the call and borrows state from
|
||||
/// a longer-living `FunctionExecutorState`.
|
||||
pub struct FunctionExecutor<'a> {
|
||||
compiler: &'a mut Compiler,
|
||||
sandbox_store: &'a mut sandbox::Store<SupervisorFuncRef>,
|
||||
heap: &'a mut FreeingBumpHeapAllocator,
|
||||
memory: &'a mut [u8],
|
||||
table: Option<&'a [VMCallerCheckedAnyfunc]>,
|
||||
}
|
||||
|
||||
impl<'a> FunctionExecutor<'a> {
|
||||
/// Construct a new `FunctionExecutor`.
|
||||
///
|
||||
/// The vmctx MUST come from a call to a function in the "env" module.
|
||||
/// The state MUST be looked up from the host state of the "env" module.
|
||||
pub unsafe fn new(
|
||||
vmctx: *mut VMContext,
|
||||
compiler: &'a mut Compiler,
|
||||
state: &'a mut FunctionExecutorState,
|
||||
) -> Result<Self>
|
||||
{
|
||||
let memory = match (*vmctx).lookup_global_export("memory") {
|
||||
Some(Export::Memory { definition, vmctx: _, memory: _ }) =>
|
||||
std::slice::from_raw_parts_mut(
|
||||
(*definition).base,
|
||||
(*definition).current_length,
|
||||
),
|
||||
_ => return Err(Error::InvalidMemoryReference),
|
||||
};
|
||||
let table = match (*vmctx).lookup_global_export("__indirect_function_table") {
|
||||
Some(Export::Table { definition, vmctx: _, table: _ }) =>
|
||||
Some(std::slice::from_raw_parts(
|
||||
(*definition).base as *const VMCallerCheckedAnyfunc,
|
||||
(*definition).current_elements as usize,
|
||||
)),
|
||||
_ => None,
|
||||
};
|
||||
Ok(FunctionExecutor {
|
||||
compiler,
|
||||
sandbox_store: &mut state.sandbox_store,
|
||||
heap: &mut state.heap,
|
||||
memory,
|
||||
table,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SandboxCapabilities for FunctionExecutor<'a> {
|
||||
type SupervisorFuncRef = SupervisorFuncRef;
|
||||
|
||||
fn invoke(
|
||||
&mut self,
|
||||
dispatch_thunk: &Self::SupervisorFuncRef,
|
||||
invoke_args_ptr: Pointer<u8>,
|
||||
invoke_args_len: WordSize,
|
||||
state: u32,
|
||||
func_idx: SupervisorFuncIndex,
|
||||
) -> Result<i64>
|
||||
{
|
||||
let func_ptr = unsafe { (*dispatch_thunk.0).func_ptr };
|
||||
let vmctx = unsafe { (*dispatch_thunk.0).vmctx };
|
||||
|
||||
// The following code is based on the wasmtime_jit::Context::invoke.
|
||||
let value_size = mem::size_of::<VMInvokeArgument>();
|
||||
let (signature, mut values_vec) = generate_signature_and_args(
|
||||
&[
|
||||
Value::I32(u32::from(invoke_args_ptr) as i32),
|
||||
Value::I32(invoke_args_len as i32),
|
||||
Value::I32(state as i32),
|
||||
Value::I32(usize::from(func_idx) as i32),
|
||||
],
|
||||
Some(ValueType::I64),
|
||||
self.compiler.frontend_config(),
|
||||
);
|
||||
|
||||
// Get the trampoline to call for this function.
|
||||
let exec_code_buf = self.compiler
|
||||
.get_published_trampoline(func_ptr, &signature, value_size)
|
||||
.map_err(ActionError::Setup)
|
||||
.map_err(|e| Error::Other(e.to_string()))?;
|
||||
|
||||
// Call the trampoline.
|
||||
if let Err(message) = unsafe {
|
||||
wasmtime_call_trampoline(
|
||||
vmctx,
|
||||
exec_code_buf,
|
||||
values_vec.as_mut_ptr() as *mut u8,
|
||||
)
|
||||
} {
|
||||
return Err(Error::Other(message));
|
||||
}
|
||||
|
||||
// Load the return value out of `values_vec`.
|
||||
Ok(unsafe { ptr::read(values_vec.as_ptr() as *const i64) })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FunctionContext for FunctionExecutor<'a> {
|
||||
fn read_memory_into(&self, address: Pointer<u8>, dest: &mut [u8]) -> WResult<()> {
|
||||
read_memory_into(self.memory, address, dest).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn write_memory(&mut self, address: Pointer<u8>, data: &[u8]) -> WResult<()> {
|
||||
write_memory_from(self.memory, address, data).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn allocate_memory(&mut self, size: WordSize) -> WResult<Pointer<u8>> {
|
||||
self.heap.allocate(self.memory, size).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn deallocate_memory(&mut self, ptr: Pointer<u8>) -> WResult<()> {
|
||||
self.heap.deallocate(self.memory, ptr).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn sandbox(&mut self) -> &mut dyn Sandbox {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Sandbox for FunctionExecutor<'a> {
|
||||
fn memory_get(
|
||||
&mut self,
|
||||
memory_id: MemoryId,
|
||||
offset: WordSize,
|
||||
buf_ptr: Pointer<u8>,
|
||||
buf_len: WordSize,
|
||||
) -> WResult<u32>
|
||||
{
|
||||
let sandboxed_memory = self.sandbox_store.memory(memory_id)
|
||||
.map_err(|e| e.to_string())?;
|
||||
sandboxed_memory.with_direct_access(|memory| {
|
||||
let len = buf_len as usize;
|
||||
let src_range = match checked_range(offset as usize, len, memory.len()) {
|
||||
Some(range) => range,
|
||||
None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
};
|
||||
let dst_range = match checked_range(buf_ptr.into(), len, self.memory.len()) {
|
||||
Some(range) => range,
|
||||
None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
};
|
||||
&mut self.memory[dst_range].copy_from_slice(&memory[src_range]);
|
||||
Ok(sandbox_primitives::ERR_OK)
|
||||
})
|
||||
}
|
||||
|
||||
fn memory_set(
|
||||
&mut self,
|
||||
memory_id: MemoryId,
|
||||
offset: WordSize,
|
||||
val_ptr: Pointer<u8>,
|
||||
val_len: WordSize,
|
||||
) -> WResult<u32>
|
||||
{
|
||||
let sandboxed_memory = self.sandbox_store.memory(memory_id)
|
||||
.map_err(|e| e.to_string())?;
|
||||
sandboxed_memory.with_direct_access_mut(|memory| {
|
||||
let len = val_len as usize;
|
||||
let src_range = match checked_range(val_ptr.into(), len, self.memory.len()) {
|
||||
Some(range) => range,
|
||||
None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
};
|
||||
let dst_range = match checked_range(offset as usize, len, memory.len()) {
|
||||
Some(range) => range,
|
||||
None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
};
|
||||
&mut memory[dst_range].copy_from_slice(&self.memory[src_range]);
|
||||
Ok(sandbox_primitives::ERR_OK)
|
||||
})
|
||||
}
|
||||
|
||||
fn memory_teardown(&mut self, memory_id: MemoryId)
|
||||
-> WResult<()>
|
||||
{
|
||||
self.sandbox_store.memory_teardown(memory_id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn memory_new(&mut self, initial: u32, maximum: MemoryId) -> WResult<u32> {
|
||||
self.sandbox_store.new_memory(initial, maximum).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn invoke(
|
||||
&mut self,
|
||||
instance_id: u32,
|
||||
export_name: &str,
|
||||
args: &[u8],
|
||||
return_val: Pointer<u8>,
|
||||
return_val_len: u32,
|
||||
state: u32,
|
||||
) -> WResult<u32> {
|
||||
trace!(target: "sp-sandbox", "invoke, instance_idx={}", instance_id);
|
||||
|
||||
// Deserialize arguments and convert them into wasmi types.
|
||||
let args = Vec::<sp_wasm_interface::Value>::decode(&mut &args[..])
|
||||
.map_err(|_| "Can't decode serialized arguments for the invocation")?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let instance = self.sandbox_store.instance(instance_id).map_err(|e| e.to_string())?;
|
||||
let result = instance.invoke(export_name, &args, self, state);
|
||||
|
||||
match result {
|
||||
Ok(None) => Ok(sandbox_primitives::ERR_OK),
|
||||
Ok(Some(val)) => {
|
||||
// Serialize return value and write it back into the memory.
|
||||
sp_wasm_interface::ReturnValue::Value(val.into()).using_encoded(|val| {
|
||||
if val.len() > return_val_len as usize {
|
||||
Err("Return value buffer is too small")?;
|
||||
}
|
||||
FunctionContext::write_memory(self, return_val, val)?;
|
||||
Ok(sandbox_primitives::ERR_OK)
|
||||
})
|
||||
}
|
||||
Err(_) => Ok(sandbox_primitives::ERR_EXECUTION),
|
||||
}
|
||||
}
|
||||
|
||||
fn instance_teardown(&mut self, instance_id: u32) -> WResult<()> {
|
||||
self.sandbox_store.instance_teardown(instance_id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn instance_new(&mut self, dispatch_thunk_id: u32, wasm: &[u8], raw_env_def: &[u8], state: u32)
|
||||
-> WResult<u32>
|
||||
{
|
||||
// Extract a dispatch thunk from instance's table by the specified index.
|
||||
let dispatch_thunk = {
|
||||
let table = self.table.as_ref()
|
||||
.ok_or_else(|| "Runtime doesn't have a table; sandbox is unavailable")?;
|
||||
let func_ref = table.get(dispatch_thunk_id as usize)
|
||||
.ok_or_else(|| "dispatch_thunk_idx is out of the table bounds")?;
|
||||
SupervisorFuncRef(func_ref)
|
||||
};
|
||||
|
||||
let guest_env = match sandbox::GuestEnvironment::decode(&self.sandbox_store, raw_env_def) {
|
||||
Ok(guest_env) => guest_env,
|
||||
Err(_) => return Ok(sandbox_primitives::ERR_MODULE as u32),
|
||||
};
|
||||
|
||||
let instance_idx_or_err_code =
|
||||
match sandbox::instantiate(self, dispatch_thunk, wasm, guest_env, state)
|
||||
.map(|i| i.register(&mut self.sandbox_store))
|
||||
{
|
||||
Ok(instance_idx) => instance_idx,
|
||||
Err(sandbox::InstantiationError::StartTrapped) =>
|
||||
sandbox_primitives::ERR_EXECUTION,
|
||||
Err(_) => sandbox_primitives::ERR_MODULE,
|
||||
};
|
||||
|
||||
Ok(instance_idx_or_err_code as u32)
|
||||
}
|
||||
|
||||
fn get_global_val(
|
||||
&self,
|
||||
instance_idx: u32,
|
||||
name: &str,
|
||||
) -> WResult<Option<sp_wasm_interface::Value>> {
|
||||
self.sandbox_store
|
||||
.instance(instance_idx)
|
||||
.map(|i| i.get_global_val(name))
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
// The storage for a Wasmtime invocation argument.
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
#[repr(C, align(8))]
|
||||
struct VMInvokeArgument([u8; 8]);
|
||||
|
||||
fn generate_signature_and_args(
|
||||
args: &[Value],
|
||||
result_type: Option<ValueType>,
|
||||
frontend_config: TargetFrontendConfig,
|
||||
) -> (ir::Signature, Vec<VMInvokeArgument>)
|
||||
{
|
||||
// This code is based on the wasmtime_jit::Context::invoke.
|
||||
|
||||
let param_types = args.iter()
|
||||
.map(|arg| arg.value_type())
|
||||
.collect::<Vec<_>>();
|
||||
let signature = translate_signature(
|
||||
cranelift_ir_signature(
|
||||
Signature::new(param_types, result_type),
|
||||
&frontend_config.default_call_conv
|
||||
),
|
||||
frontend_config.pointer_type()
|
||||
);
|
||||
|
||||
let mut values_vec = vec![
|
||||
VMInvokeArgument::default();
|
||||
cmp::max(args.len(), result_type.iter().len())
|
||||
];
|
||||
|
||||
// Store the argument values into `values_vec`.
|
||||
for (index, arg) in args.iter().enumerate() {
|
||||
unsafe {
|
||||
let ptr = values_vec.as_mut_ptr().add(index);
|
||||
|
||||
match arg {
|
||||
Value::I32(x) => ptr::write(ptr as *mut i32, *x),
|
||||
Value::I64(x) => ptr::write(ptr as *mut i64, *x),
|
||||
Value::F32(x) => ptr::write(ptr as *mut u32, *x),
|
||||
Value::F64(x) => ptr::write(ptr as *mut u64, *x),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(signature, values_vec)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! This module defines `HostState` and `HostContext` structs which provide logic and state
|
||||
//! required for execution of host.
|
||||
|
||||
use crate::instance_wrapper::InstanceWrapper;
|
||||
use crate::util;
|
||||
use std::cell::RefCell;
|
||||
use log::trace;
|
||||
use codec::{Encode, Decode};
|
||||
use sp_allocator::FreeingBumpHeapAllocator;
|
||||
use sc_executor_common::error::Result;
|
||||
use sc_executor_common::sandbox::{self, SandboxCapabilities, SupervisorFuncIndex};
|
||||
use sp_core::sandbox as sandbox_primitives;
|
||||
use sp_wasm_interface::{FunctionContext, MemoryId, Pointer, Sandbox, WordSize};
|
||||
use wasmtime::{Func, Val};
|
||||
|
||||
/// Wrapper type for pointer to a Wasm table entry.
|
||||
///
|
||||
/// The wrapper type is used to ensure that the function reference is valid as it must be unsafely
|
||||
/// dereferenced from within the safe method `<HostContext as SandboxCapabilities>::invoke`.
|
||||
#[derive(Clone)]
|
||||
pub struct SupervisorFuncRef(Func);
|
||||
|
||||
/// The state required to construct a HostContext context. The context only lasts for one host
|
||||
/// call, whereas the state is maintained for the duration of a Wasm runtime call, which may make
|
||||
/// many different host calls that must share state.
|
||||
pub struct HostState {
|
||||
// We need some interior mutability here since the host state is shared between all host
|
||||
// function handlers and the wasmtime backend's `impl WasmRuntime`.
|
||||
//
|
||||
// Furthermore, because of recursive calls (e.g. runtime can create and call an sandboxed
|
||||
// instance which in turn can call the runtime back) we have to be very careful with borrowing
|
||||
// those.
|
||||
//
|
||||
// Basically, most of the interactions should do temporary borrow immediately releasing the
|
||||
// borrow after performing necessary queries/changes.
|
||||
sandbox_store: RefCell<sandbox::Store<SupervisorFuncRef>>,
|
||||
allocator: RefCell<FreeingBumpHeapAllocator>,
|
||||
instance: InstanceWrapper,
|
||||
}
|
||||
|
||||
impl HostState {
|
||||
/// Constructs a new `HostState`.
|
||||
pub fn new(allocator: FreeingBumpHeapAllocator, instance: InstanceWrapper) -> Self {
|
||||
HostState {
|
||||
sandbox_store: RefCell::new(sandbox::Store::new()),
|
||||
allocator: RefCell::new(allocator),
|
||||
instance,
|
||||
}
|
||||
}
|
||||
|
||||
/// Destruct the host state and extract the `InstanceWrapper` passed at the creation.
|
||||
pub fn into_instance(self) -> InstanceWrapper {
|
||||
self.instance
|
||||
}
|
||||
|
||||
/// Materialize `HostContext` that can be used to invoke a substrate host `dyn Function`.
|
||||
pub fn materialize<'a>(&'a self) -> HostContext<'a> {
|
||||
HostContext(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 struct HostContext<'a>(&'a HostState);
|
||||
|
||||
impl<'a> std::ops::Deref for HostContext<'a> {
|
||||
type Target = HostState;
|
||||
fn deref(&self) -> &HostState {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SandboxCapabilities for HostContext<'a> {
|
||||
type SupervisorFuncRef = SupervisorFuncRef;
|
||||
|
||||
fn invoke(
|
||||
&mut self,
|
||||
dispatch_thunk: &Self::SupervisorFuncRef,
|
||||
invoke_args_ptr: Pointer<u8>,
|
||||
invoke_args_len: WordSize,
|
||||
state: u32,
|
||||
func_idx: SupervisorFuncIndex,
|
||||
) -> Result<i64> {
|
||||
let result = dispatch_thunk.0.call(&[
|
||||
Val::I32(u32::from(invoke_args_ptr) as i32),
|
||||
Val::I32(invoke_args_len as i32),
|
||||
Val::I32(state as i32),
|
||||
Val::I32(usize::from(func_idx) as i32),
|
||||
]);
|
||||
match result {
|
||||
Ok(ret_vals) => {
|
||||
let ret_val = if ret_vals.len() != 1 {
|
||||
return Err(format!(
|
||||
"Supervisor function returned {} results, expected 1",
|
||||
ret_vals.len()
|
||||
)
|
||||
.into());
|
||||
} else {
|
||||
&ret_vals[0]
|
||||
};
|
||||
|
||||
if let Some(ret_val) = ret_val.i64() {
|
||||
Ok(ret_val)
|
||||
} else {
|
||||
return Err("Supervisor function returned unexpected result!".into());
|
||||
}
|
||||
}
|
||||
Err(err) => Err(err.message().to_string().into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> {
|
||||
fn read_memory_into(
|
||||
&self,
|
||||
address: Pointer<u8>,
|
||||
dest: &mut [u8],
|
||||
) -> sp_wasm_interface::Result<()> {
|
||||
self.instance
|
||||
.read_memory_into(address, dest)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn write_memory(&mut self, address: Pointer<u8>, data: &[u8]) -> sp_wasm_interface::Result<()> {
|
||||
self.instance
|
||||
.write_memory_from(address, data)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn allocate_memory(&mut self, size: WordSize) -> sp_wasm_interface::Result<Pointer<u8>> {
|
||||
self.instance
|
||||
.allocate(&mut *self.allocator.borrow_mut(), size)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn deallocate_memory(&mut self, ptr: Pointer<u8>) -> sp_wasm_interface::Result<()> {
|
||||
self.instance
|
||||
.deallocate(&mut *self.allocator.borrow_mut(), ptr)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn sandbox(&mut self) -> &mut dyn Sandbox {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Sandbox for HostContext<'a> {
|
||||
fn memory_get(
|
||||
&mut self,
|
||||
memory_id: MemoryId,
|
||||
offset: WordSize,
|
||||
buf_ptr: Pointer<u8>,
|
||||
buf_len: WordSize,
|
||||
) -> sp_wasm_interface::Result<u32> {
|
||||
let sandboxed_memory = self
|
||||
.sandbox_store
|
||||
.borrow()
|
||||
.memory(memory_id)
|
||||
.map_err(|e| e.to_string())?;
|
||||
sandboxed_memory.with_direct_access(|sandboxed_memory| {
|
||||
let len = buf_len as usize;
|
||||
let src_range = match util::checked_range(offset as usize, len, sandboxed_memory.len())
|
||||
{
|
||||
Some(range) => range,
|
||||
None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
};
|
||||
let supervisor_mem_size = self.instance.memory_size() as usize;
|
||||
let dst_range = match util::checked_range(buf_ptr.into(), len, supervisor_mem_size) {
|
||||
Some(range) => range,
|
||||
None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
};
|
||||
self.instance
|
||||
.write_memory_from(
|
||||
Pointer::new(dst_range.start as u32),
|
||||
&sandboxed_memory[src_range],
|
||||
)
|
||||
.expect("ranges are checked above; write can't fail; qed");
|
||||
Ok(sandbox_primitives::ERR_OK)
|
||||
})
|
||||
}
|
||||
|
||||
fn memory_set(
|
||||
&mut self,
|
||||
memory_id: MemoryId,
|
||||
offset: WordSize,
|
||||
val_ptr: Pointer<u8>,
|
||||
val_len: WordSize,
|
||||
) -> sp_wasm_interface::Result<u32> {
|
||||
let sandboxed_memory = self
|
||||
.sandbox_store
|
||||
.borrow()
|
||||
.memory(memory_id)
|
||||
.map_err(|e| e.to_string())?;
|
||||
sandboxed_memory.with_direct_access_mut(|sandboxed_memory| {
|
||||
let len = val_len as usize;
|
||||
let supervisor_mem_size = self.instance.memory_size() as usize;
|
||||
let src_range = match util::checked_range(val_ptr.into(), len, supervisor_mem_size) {
|
||||
Some(range) => range,
|
||||
None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
};
|
||||
let dst_range = match util::checked_range(offset as usize, len, sandboxed_memory.len())
|
||||
{
|
||||
Some(range) => range,
|
||||
None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
};
|
||||
self.instance
|
||||
.read_memory_into(
|
||||
Pointer::new(src_range.start as u32),
|
||||
&mut sandboxed_memory[dst_range],
|
||||
)
|
||||
.expect("ranges are checked above; read can't fail; qed");
|
||||
Ok(sandbox_primitives::ERR_OK)
|
||||
})
|
||||
}
|
||||
|
||||
fn memory_teardown(&mut self, memory_id: MemoryId) -> sp_wasm_interface::Result<()> {
|
||||
self.sandbox_store
|
||||
.borrow_mut()
|
||||
.memory_teardown(memory_id)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn memory_new(&mut self, initial: u32, maximum: MemoryId) -> sp_wasm_interface::Result<u32> {
|
||||
self.sandbox_store
|
||||
.borrow_mut()
|
||||
.new_memory(initial, maximum)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn invoke(
|
||||
&mut self,
|
||||
instance_id: u32,
|
||||
export_name: &str,
|
||||
args: &[u8],
|
||||
return_val: Pointer<u8>,
|
||||
return_val_len: u32,
|
||||
state: u32,
|
||||
) -> sp_wasm_interface::Result<u32> {
|
||||
trace!(target: "sp-sandbox", "invoke, instance_idx={}", instance_id);
|
||||
|
||||
// Deserialize arguments and convert them into wasmi types.
|
||||
let args = Vec::<sp_wasm_interface::Value>::decode(&mut &args[..])
|
||||
.map_err(|_| "Can't decode serialized arguments for the invocation")?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let instance = self
|
||||
.sandbox_store
|
||||
.borrow()
|
||||
.instance(instance_id)
|
||||
.map_err(|e| e.to_string())?;
|
||||
let result = instance.invoke(export_name, &args, self, state);
|
||||
|
||||
match result {
|
||||
Ok(None) => Ok(sandbox_primitives::ERR_OK),
|
||||
Ok(Some(val)) => {
|
||||
// Serialize return value and write it back into the memory.
|
||||
sp_wasm_interface::ReturnValue::Value(val.into()).using_encoded(|val| {
|
||||
if val.len() > return_val_len as usize {
|
||||
Err("Return value buffer is too small")?;
|
||||
}
|
||||
<HostContext as FunctionContext>::write_memory(self, return_val, val)
|
||||
.map_err(|_| "can't write return value")?;
|
||||
Ok(sandbox_primitives::ERR_OK)
|
||||
})
|
||||
}
|
||||
Err(_) => Ok(sandbox_primitives::ERR_EXECUTION),
|
||||
}
|
||||
}
|
||||
|
||||
fn instance_teardown(&mut self, instance_id: u32) -> sp_wasm_interface::Result<()> {
|
||||
self.sandbox_store
|
||||
.borrow_mut()
|
||||
.instance_teardown(instance_id)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn instance_new(
|
||||
&mut self,
|
||||
dispatch_thunk_id: u32,
|
||||
wasm: &[u8],
|
||||
raw_env_def: &[u8],
|
||||
state: u32,
|
||||
) -> sp_wasm_interface::Result<u32> {
|
||||
// Extract a dispatch thunk from the instance's table by the specified index.
|
||||
let dispatch_thunk = {
|
||||
let table_item = self
|
||||
.instance
|
||||
.table()
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Runtime doesn't have a table; sandbox is unavailable")?
|
||||
.get(dispatch_thunk_id);
|
||||
|
||||
let func_ref = table_item
|
||||
.ok_or_else(|| "dispatch_thunk_id is out of bounds")?
|
||||
.funcref()
|
||||
.ok_or_else(|| "dispatch_thunk_idx should be a funcref")?
|
||||
.clone();
|
||||
SupervisorFuncRef(func_ref)
|
||||
};
|
||||
|
||||
let guest_env =
|
||||
match sandbox::GuestEnvironment::decode(&*self.sandbox_store.borrow(), raw_env_def) {
|
||||
Ok(guest_env) => guest_env,
|
||||
Err(_) => return Ok(sandbox_primitives::ERR_MODULE as u32),
|
||||
};
|
||||
|
||||
let instance_idx_or_err_code =
|
||||
match sandbox::instantiate(self, dispatch_thunk, wasm, guest_env, state)
|
||||
.map(|i| i.register(&mut *self.sandbox_store.borrow_mut()))
|
||||
{
|
||||
Ok(instance_idx) => instance_idx,
|
||||
Err(sandbox::InstantiationError::StartTrapped) => sandbox_primitives::ERR_EXECUTION,
|
||||
Err(_) => sandbox_primitives::ERR_MODULE,
|
||||
};
|
||||
|
||||
Ok(instance_idx_or_err_code as u32)
|
||||
}
|
||||
|
||||
fn get_global_val(
|
||||
&self,
|
||||
instance_idx: u32,
|
||||
name: &str,
|
||||
) -> sp_wasm_interface::Result<Option<sp_wasm_interface::Value>> {
|
||||
self.sandbox_store
|
||||
.borrow()
|
||||
.instance(instance_idx)
|
||||
.map(|i| i.get_global_val(name))
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::state_holder::StateHolder;
|
||||
use sc_executor_common::error::WasmError;
|
||||
use sp_wasm_interface::{Function, Value, ValueType};
|
||||
use std::any::Any;
|
||||
use std::rc::Rc;
|
||||
use wasmtime::{
|
||||
Callable, Extern, ExternType, Func, FuncType, ImportType, Limits, Memory, MemoryType, Module,
|
||||
Trap, Val,
|
||||
};
|
||||
|
||||
pub struct Imports {
|
||||
/// Contains the index into `externs` where the memory import is stored if any. `None` if there
|
||||
/// is none.
|
||||
pub memory_import_index: Option<usize>,
|
||||
pub externs: Vec<Extern>,
|
||||
}
|
||||
|
||||
/// 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 fn resolve_imports(
|
||||
state_holder: &StateHolder,
|
||||
module: &Module,
|
||||
host_functions: &[&'static dyn Function],
|
||||
heap_pages: u32,
|
||||
allow_missing_func_imports: bool,
|
||||
) -> Result<Imports, WasmError> {
|
||||
let mut externs = vec![];
|
||||
let mut memory_import_index = None;
|
||||
for import_ty in module.imports() {
|
||||
if import_ty.module() != "env" {
|
||||
return Err(WasmError::Other(format!(
|
||||
"host doesn't provide any imports from non-env module: {}:{}",
|
||||
import_ty.module(),
|
||||
import_ty.name()
|
||||
)));
|
||||
}
|
||||
|
||||
let resolved = match import_ty.name() {
|
||||
"memory" => {
|
||||
memory_import_index = Some(externs.len());
|
||||
resolve_memory_import(module, import_ty, heap_pages)?
|
||||
}
|
||||
_ => resolve_func_import(
|
||||
module,
|
||||
state_holder,
|
||||
import_ty,
|
||||
host_functions,
|
||||
allow_missing_func_imports,
|
||||
)?,
|
||||
};
|
||||
externs.push(resolved);
|
||||
}
|
||||
Ok(Imports {
|
||||
memory_import_index,
|
||||
externs,
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_memory_import(
|
||||
module: &Module,
|
||||
import_ty: &ImportType,
|
||||
heap_pages: u32,
|
||||
) -> Result<Extern, WasmError> {
|
||||
let requested_memory_ty = match import_ty.ty() {
|
||||
ExternType::Memory(memory_ty) => memory_ty,
|
||||
_ => {
|
||||
return Err(WasmError::Other(format!(
|
||||
"this import must be of memory type: {}:{}",
|
||||
import_ty.module(),
|
||||
import_ty.name()
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
// Increment the min (a.k.a initial) number of pages by `heap_pages` and check if it exceeds the
|
||||
// maximum specified by the import.
|
||||
let initial = requested_memory_ty
|
||||
.limits()
|
||||
.min()
|
||||
.saturating_add(heap_pages);
|
||||
if let Some(max) = requested_memory_ty.limits().max() {
|
||||
if initial > max {
|
||||
return Err(WasmError::Other(format!(
|
||||
"incremented number of pages by heap_pages (total={}) is more than maximum requested\
|
||||
by the runtime wasm module {}",
|
||||
initial,
|
||||
max,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
let memory_ty = MemoryType::new(Limits::new(initial, requested_memory_ty.limits().max()));
|
||||
let memory = Memory::new(module.store(), memory_ty);
|
||||
Ok(Extern::Memory(memory))
|
||||
}
|
||||
|
||||
fn resolve_func_import(
|
||||
module: &Module,
|
||||
state_holder: &StateHolder,
|
||||
import_ty: &ImportType,
|
||||
host_functions: &[&'static dyn Function],
|
||||
allow_missing_func_imports: bool,
|
||||
) -> Result<Extern, WasmError> {
|
||||
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(),
|
||||
import_ty.name()
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let host_func = match host_functions
|
||||
.iter()
|
||||
.find(|host_func| host_func.name() == import_ty.name())
|
||||
{
|
||||
Some(host_func) => host_func,
|
||||
None if allow_missing_func_imports => {
|
||||
return Ok(MissingHostFuncHandler::new(import_ty).into_extern(module, func_ty));
|
||||
}
|
||||
None => {
|
||||
return Err(WasmError::Other(format!(
|
||||
"host doesn't provide such function: {}:{}",
|
||||
import_ty.module(),
|
||||
import_ty.name()
|
||||
)));
|
||||
}
|
||||
};
|
||||
if !signature_matches(&func_ty, &wasmtime_func_sig(*host_func)) {
|
||||
return Err(WasmError::Other(format!(
|
||||
"signature mismatch for: {}:{}",
|
||||
import_ty.module(),
|
||||
import_ty.name()
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(HostFuncHandler::new(&state_holder, *host_func).into_extern(module))
|
||||
}
|
||||
|
||||
/// Returns `true` if `lhs` and `rhs` represent the same signature.
|
||||
fn signature_matches(lhs: &wasmtime::FuncType, rhs: &wasmtime::FuncType) -> bool {
|
||||
lhs.params() == rhs.params() && lhs.results() == rhs.results()
|
||||
}
|
||||
|
||||
/// This structure implements `Callable` and acts as a bridge between wasmtime and
|
||||
/// substrate host functions.
|
||||
struct HostFuncHandler {
|
||||
state_holder: StateHolder,
|
||||
host_func: &'static dyn Function,
|
||||
}
|
||||
|
||||
impl HostFuncHandler {
|
||||
fn new(state_holder: &StateHolder, host_func: &'static dyn Function) -> Self {
|
||||
Self {
|
||||
state_holder: state_holder.clone(),
|
||||
host_func,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_extern(self, module: &Module) -> Extern {
|
||||
let func_ty = wasmtime_func_sig(self.host_func);
|
||||
let func = Func::new(module.store(), func_ty, Rc::new(self));
|
||||
Extern::Func(func)
|
||||
}
|
||||
}
|
||||
|
||||
impl Callable for HostFuncHandler {
|
||||
fn call(
|
||||
&self,
|
||||
wasmtime_params: &[Val],
|
||||
wasmtime_results: &mut [Val],
|
||||
) -> Result<(), wasmtime::Trap> {
|
||||
let unwind_result = self.state_holder.with_context(|host_ctx| {
|
||||
let mut host_ctx = host_ctx.expect(
|
||||
"host functions can be called only from wasm instance;
|
||||
wasm instance is always called initializing context;
|
||||
therefore host_ctx cannot be None;
|
||||
qed
|
||||
",
|
||||
);
|
||||
// `into_value` 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(into_value);
|
||||
|
||||
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
self.host_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] = 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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A `Callable` handler for missing functions.
|
||||
struct MissingHostFuncHandler {
|
||||
module: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl MissingHostFuncHandler {
|
||||
fn new(import_ty: &ImportType) -> Self {
|
||||
Self {
|
||||
module: import_ty.module().to_string(),
|
||||
name: import_ty.name().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_extern(self, module: &Module, func_ty: &FuncType) -> Extern {
|
||||
let func = Func::new(module.store(), func_ty.clone(), Rc::new(self));
|
||||
Extern::Func(func)
|
||||
}
|
||||
}
|
||||
|
||||
impl Callable for MissingHostFuncHandler {
|
||||
fn call(
|
||||
&self,
|
||||
_wasmtime_params: &[Val],
|
||||
_wasmtime_results: &mut [Val],
|
||||
) -> Result<(), wasmtime::Trap> {
|
||||
Err(Trap::new(format!(
|
||||
"call to a missing function {}:{}",
|
||||
self.module, self.name
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
fn wasmtime_func_sig(func: &dyn Function) -> wasmtime::FuncType {
|
||||
let params = func
|
||||
.signature()
|
||||
.args
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(into_wasmtime_val_type)
|
||||
.collect::<Vec<_>>()
|
||||
.into_boxed_slice();
|
||||
let results = func
|
||||
.signature()
|
||||
.return_value
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(into_wasmtime_val_type)
|
||||
.collect::<Vec<_>>()
|
||||
.into_boxed_slice();
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a `Val` into a substrate runtime interface `Value`.
|
||||
///
|
||||
/// Panics if the given value doesn't have a corresponding variant in `Value`.
|
||||
fn into_value(val: Val) -> Value {
|
||||
match val {
|
||||
Val::I32(v) => Value::I32(v),
|
||||
Val::I64(v) => Value::I64(v),
|
||||
Val::F32(f_bits) => Value::F32(f_bits),
|
||||
Val::F64(f_bits) => Value::F64(f_bits),
|
||||
_ => panic!("Given value type is unsupported by substrate"),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_wasmtime_val(value: Value) -> wasmtime::Val {
|
||||
match value {
|
||||
Value::I32(v) => Val::I32(v),
|
||||
Value::I64(v) => Val::I64(v),
|
||||
Value::F32(f_bits) => Val::F32(f_bits),
|
||||
Value::F64(f_bits) => Val::F64(f_bits),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Defines data and logic needed for interaction with an WebAssembly instance of a substrate
|
||||
//! runtime module.
|
||||
|
||||
use crate::util;
|
||||
use crate::imports::Imports;
|
||||
|
||||
use sc_executor_common::error::{Error, Result};
|
||||
use sp_wasm_interface::{Pointer, WordSize};
|
||||
use std::slice;
|
||||
use std::marker;
|
||||
use wasmtime::{Instance, Module, Memory, Table};
|
||||
|
||||
/// Wrap the given WebAssembly Instance of a wasm module with Substrate-runtime.
|
||||
///
|
||||
/// This struct is a handy wrapper around a wasmtime `Instance` that provides substrate specific
|
||||
/// routines.
|
||||
pub struct InstanceWrapper {
|
||||
instance: Instance,
|
||||
// The memory instance of the `intance`.
|
||||
//
|
||||
// It is important to make sure that we don't make any copies of this to make it easier to proof
|
||||
// See `memory_as_slice` and `memory_as_slice_mut`.
|
||||
memory: Memory,
|
||||
table: Option<Table>,
|
||||
// Make this struct explicitly !Send & !Sync.
|
||||
_not_send_nor_sync: marker::PhantomData<*const ()>,
|
||||
}
|
||||
|
||||
impl InstanceWrapper {
|
||||
/// Create a new instance wrapper from the given wasm module.
|
||||
pub fn new(module: &Module, imports: &Imports, heap_pages: u32) -> Result<Self> {
|
||||
let instance = Instance::new(module, &imports.externs)
|
||||
.map_err(|e| Error::from(format!("cannot instantiate: {}", e)))?;
|
||||
|
||||
let memory = match imports.memory_import_index {
|
||||
Some(memory_idx) => {
|
||||
imports.externs[memory_idx]
|
||||
.memory()
|
||||
.expect("only memory can be at the `memory_idx`; qed")
|
||||
.clone()
|
||||
}
|
||||
None => {
|
||||
let memory = get_linear_memory(&instance)?;
|
||||
if !memory.grow(heap_pages).is_ok() {
|
||||
return Err("failed top increase the linear memory size".into());
|
||||
}
|
||||
memory
|
||||
},
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
table: get_table(&instance),
|
||||
memory,
|
||||
instance,
|
||||
_not_send_nor_sync: marker::PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Resolves a substrate entrypoint by the given name.
|
||||
///
|
||||
/// An entrypoint must have a signature `(i32, i32) -> i64`, otherwise this function will return
|
||||
/// an error.
|
||||
pub fn resolve_entrypoint(&self, name: &str) -> Result<wasmtime::Func> {
|
||||
// Resolve the requested method and verify that it has a proper signature.
|
||||
let export = self
|
||||
.instance
|
||||
.get_export(name)
|
||||
.ok_or_else(|| Error::from(format!("Exported method {} is not found", name)))?;
|
||||
let entrypoint = export
|
||||
.func()
|
||||
.ok_or_else(|| Error::from(format!("Export {} is not a function", name)))?;
|
||||
match (entrypoint.ty().params(), entrypoint.ty().results()) {
|
||||
(&[wasmtime::ValType::I32, wasmtime::ValType::I32], &[wasmtime::ValType::I64]) => {}
|
||||
_ => {
|
||||
return Err(Error::from(format!(
|
||||
"method {} have an unsupported signature",
|
||||
name
|
||||
)))
|
||||
}
|
||||
}
|
||||
Ok(entrypoint.clone())
|
||||
}
|
||||
|
||||
/// Returns an indirect function table of this instance.
|
||||
pub fn table(&self) -> Option<&Table> {
|
||||
self.table.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the byte size of the linear memory instance attached to this instance.
|
||||
pub fn memory_size(&self) -> u32 {
|
||||
self.memory.data_size() as u32
|
||||
}
|
||||
|
||||
/// Reads `__heap_base: i32` global variable and returns it.
|
||||
///
|
||||
/// If it doesn't exist, not a global or of not i32 type returns an error.
|
||||
pub fn extract_heap_base(&self) -> Result<u32> {
|
||||
let heap_base_export = self
|
||||
.instance
|
||||
.get_export("__heap_base")
|
||||
.ok_or_else(|| Error::from("__heap_base is not found"))?;
|
||||
|
||||
let heap_base_global = heap_base_export
|
||||
.global()
|
||||
.ok_or_else(|| Error::from("__heap_base is not a global"))?;
|
||||
|
||||
let heap_base = heap_base_global
|
||||
.get()
|
||||
.i32()
|
||||
.ok_or_else(|| Error::from("__heap_base is not a i32"))?;
|
||||
|
||||
Ok(heap_base as u32)
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract linear memory instance from the given instance.
|
||||
fn get_linear_memory(instance: &Instance) -> Result<Memory> {
|
||||
let memory_export = instance
|
||||
.get_export("memory")
|
||||
.ok_or_else(|| Error::from("memory is not exported under `memory` name"))?;
|
||||
|
||||
let memory = memory_export
|
||||
.memory()
|
||||
.ok_or_else(|| Error::from("the `memory` export should have memory type"))?
|
||||
.clone();
|
||||
|
||||
Ok(memory)
|
||||
}
|
||||
|
||||
/// Extract the table from the given instance if any.
|
||||
fn get_table(instance: &Instance) -> Option<Table> {
|
||||
instance
|
||||
.get_export("__indirect_function_table")
|
||||
.and_then(|export| export.table())
|
||||
.cloned()
|
||||
}
|
||||
|
||||
/// Functions realted to memory.
|
||||
impl InstanceWrapper {
|
||||
/// Read data from a slice of memory into a destination buffer.
|
||||
///
|
||||
/// Returns an error if the read would go out of the memory bounds.
|
||||
pub fn read_memory_into(&self, address: Pointer<u8>, dest: &mut [u8]) -> Result<()> {
|
||||
unsafe {
|
||||
// This should be safe since we don't grow up memory while caching this reference and
|
||||
// we give up the reference before returning from this function.
|
||||
let memory = self.memory_as_slice();
|
||||
|
||||
let range = util::checked_range(address.into(), dest.len(), memory.len())
|
||||
.ok_or_else(|| Error::Other("memory read is out of bounds".into()))?;
|
||||
dest.copy_from_slice(&memory[range]);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Write data to a slice of memory.
|
||||
///
|
||||
/// Returns an error if the write would go out of the memory bounds.
|
||||
pub fn write_memory_from(&self, address: Pointer<u8>, data: &[u8]) -> Result<()> {
|
||||
unsafe {
|
||||
// This should be safe since we don't grow up memory while caching this reference and
|
||||
// we give up the reference before returning from this function.
|
||||
let memory = self.memory_as_slice_mut();
|
||||
|
||||
let range = util::checked_range(address.into(), data.len(), memory.len())
|
||||
.ok_or_else(|| Error::Other("memory write is out of bounds".into()))?;
|
||||
&mut memory[range].copy_from_slice(data);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocate some memory of the given size. Returns pointer to the allocated memory region.
|
||||
///
|
||||
/// Returns `Err` in case memory cannot be allocated. Refer to the allocator documentation
|
||||
/// to get more details.
|
||||
pub fn allocate(
|
||||
&self,
|
||||
allocator: &mut sp_allocator::FreeingBumpHeapAllocator,
|
||||
size: WordSize,
|
||||
) -> Result<Pointer<u8>> {
|
||||
unsafe {
|
||||
// This should be safe since we don't grow up memory while caching this reference and
|
||||
// we give up the reference before returning from this function.
|
||||
let memory = self.memory_as_slice_mut();
|
||||
|
||||
allocator.allocate(memory, size).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
/// Deallocate the memory pointed by the given pointer.
|
||||
///
|
||||
/// Returns `Err` in case the given memory region cannot be deallocated.
|
||||
pub fn deallocate(
|
||||
&self,
|
||||
allocator: &mut sp_allocator::FreeingBumpHeapAllocator,
|
||||
ptr: Pointer<u8>,
|
||||
) -> Result<()> {
|
||||
unsafe {
|
||||
// This should be safe since we don't grow up memory while caching this reference and
|
||||
// we give up the reference before returning from this function.
|
||||
let memory = self.memory_as_slice_mut();
|
||||
|
||||
allocator.deallocate(memory, ptr).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns linear memory of the wasm instance as a slice.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Wasmtime doesn't provide comprehensive documentation about the exact behavior of the data
|
||||
/// pointer. If a dynamic style heap is used the base pointer of the heap can change. Since
|
||||
/// growing, we cannot guarantee the lifetime of the returned slice reference.
|
||||
unsafe fn memory_as_slice(&self) -> &[u8] {
|
||||
let ptr = self.memory.data_ptr() as *const _;
|
||||
let len = self.memory.data_size();
|
||||
|
||||
if len == 0 {
|
||||
&[]
|
||||
} else {
|
||||
slice::from_raw_parts(ptr, len)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns linear memory of the wasm instance as a slice.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// See `[memory_as_slice]`. In addition to those requirements, since a mutable reference is
|
||||
/// returned it must be ensured that only one mutable and no shared references to memory exists
|
||||
/// at the same time.
|
||||
unsafe fn memory_as_slice_mut(&self) -> &mut [u8] {
|
||||
let ptr = self.memory.data_ptr();
|
||||
let len = self.memory.data_size();
|
||||
|
||||
if len == 0 {
|
||||
&mut []
|
||||
} else {
|
||||
slice::from_raw_parts_mut(ptr, len)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,11 @@
|
||||
|
||||
///! Defines a `WasmRuntime` that uses the Wasmtime JIT to execute.
|
||||
|
||||
mod function_executor;
|
||||
mod host;
|
||||
mod runtime;
|
||||
mod trampoline;
|
||||
mod state_holder;
|
||||
mod imports;
|
||||
mod instance_wrapper;
|
||||
mod util;
|
||||
|
||||
pub use runtime::create_instance;
|
||||
|
||||
|
||||
@@ -16,67 +16,42 @@
|
||||
|
||||
//! Defines the compiled Wasm runtime that uses Wasmtime internally.
|
||||
|
||||
use crate::function_executor::FunctionExecutorState;
|
||||
use crate::trampoline::{EnvState, make_trampoline};
|
||||
use crate::util::{
|
||||
cranelift_ir_signature,
|
||||
convert_parity_wasm_signature,
|
||||
read_memory_into,
|
||||
write_memory_from
|
||||
};
|
||||
use crate::host::HostState;
|
||||
use crate::imports::{resolve_imports, Imports};
|
||||
use crate::instance_wrapper::InstanceWrapper;
|
||||
use crate::state_holder::StateHolder;
|
||||
|
||||
use sc_executor_common::{
|
||||
error::{Error, Result, WasmError},
|
||||
wasm_runtime::WasmRuntime,
|
||||
};
|
||||
use sp_wasm_interface::{Pointer, WordSize, Function};
|
||||
use sp_allocator::FreeingBumpHeapAllocator;
|
||||
use sp_runtime_interface::unpack_ptr_and_len;
|
||||
use sp_wasm_interface::{Function, Pointer, WordSize};
|
||||
use wasmtime::{Config, Engine, Module, Store};
|
||||
|
||||
use std::{cell::RefCell, collections::HashMap, convert::TryFrom, rc::Rc};
|
||||
|
||||
use cranelift_codegen::ir;
|
||||
use cranelift_codegen::isa::TargetIsa;
|
||||
use cranelift_entity::{EntityRef, PrimaryMap};
|
||||
use cranelift_frontend::FunctionBuilderContext;
|
||||
use cranelift_wasm::{DefinedFuncIndex, MemoryIndex};
|
||||
use wasmtime_environ::{Module, translate_signature};
|
||||
use wasmtime_jit::{
|
||||
ActionOutcome, CodeMemory, CompilationStrategy, CompiledModule, Compiler, Context, RuntimeValue,
|
||||
};
|
||||
use wasmtime_runtime::{Export, Imports, InstanceHandle, VMFunctionBody};
|
||||
|
||||
/// TODO: We should remove this in https://github.com/paritytech/substrate/pull/4686
|
||||
/// Currently there is no way to extract this with wasmtime.
|
||||
const INITIAL_HEAP_PAGES: u32 = 17;
|
||||
|
||||
/// A `WasmRuntime` implementation using the Wasmtime JIT to compile the runtime module to native
|
||||
/// A `WasmRuntime` implementation using wasmtime to compile the runtime module to machine code
|
||||
/// and execute the compiled code.
|
||||
pub struct WasmtimeRuntime {
|
||||
module: CompiledModule,
|
||||
context: Context,
|
||||
module: Module,
|
||||
imports: Imports,
|
||||
state_holder: StateHolder,
|
||||
heap_pages: u32,
|
||||
/// The host functions registered for this instance.
|
||||
host_functions: Vec<&'static dyn Function>,
|
||||
/// The index of the memory in the module.
|
||||
memory_index: MemoryIndex,
|
||||
}
|
||||
|
||||
impl WasmRuntime for WasmtimeRuntime {
|
||||
fn update_heap_pages(&mut self, heap_pages: u64) -> bool {
|
||||
self.heap_pages as u64 == heap_pages
|
||||
}
|
||||
|
||||
fn host_functions(&self) -> &[&'static dyn Function] {
|
||||
&self.host_functions
|
||||
}
|
||||
|
||||
fn call(&mut self, method: &str, data: &[u8]) -> Result<Vec<u8>> {
|
||||
call_method(
|
||||
&mut self.context,
|
||||
&mut self.module,
|
||||
&self.module,
|
||||
&mut self.imports,
|
||||
&self.state_holder,
|
||||
method,
|
||||
data,
|
||||
self.memory_index,
|
||||
self.heap_pages,
|
||||
)
|
||||
}
|
||||
@@ -90,445 +65,105 @@ pub fn create_instance(
|
||||
host_functions: Vec<&'static dyn Function>,
|
||||
allow_missing_func_imports: bool,
|
||||
) -> std::result::Result<WasmtimeRuntime, WasmError> {
|
||||
let heap_pages = u32::try_from(heap_pages)
|
||||
.map_err(|e|
|
||||
WasmError::Other(format!("Heap pages can not be converted into `u32`: {:?}", e))
|
||||
)?;
|
||||
// Create the engine, store and finally the module from the given code.
|
||||
let mut config = Config::new();
|
||||
config.cranelift_opt_level(wasmtime::OptLevel::SpeedAndSize);
|
||||
|
||||
let (compiled_module, context, memory_index) = create_compiled_unit(
|
||||
code,
|
||||
let engine = Engine::new(&config);
|
||||
let store = Store::new(&engine);
|
||||
let module = Module::new(&store, code)
|
||||
.map_err(|e| WasmError::Other(format!("cannot create module: {}", e)))?;
|
||||
|
||||
let state_holder = StateHolder::empty();
|
||||
|
||||
// Scan all imports, find the matching host functions, and create stubs that adapt arguments
|
||||
// and results.
|
||||
let imports = resolve_imports(
|
||||
&state_holder,
|
||||
&module,
|
||||
&host_functions,
|
||||
heap_pages,
|
||||
heap_pages as u32,
|
||||
allow_missing_func_imports,
|
||||
)?;
|
||||
|
||||
let module = compiled_module.module_ref();
|
||||
if !module.is_imported_memory(memory_index) {
|
||||
// Inspect the module for the min and max memory sizes.
|
||||
let (min_memory_size, max_memory_size) = {
|
||||
let memory_plan = module.memory_plans
|
||||
.get(memory_index)
|
||||
.ok_or_else(|| WasmError::InvalidMemory)?;
|
||||
(memory_plan.memory.minimum, memory_plan.memory.maximum)
|
||||
};
|
||||
|
||||
// Check that heap_pages is within the allowed range.
|
||||
let max_heap_pages = max_memory_size.map(|max| max.saturating_sub(min_memory_size));
|
||||
|
||||
if max_heap_pages.map(|m| heap_pages > m).unwrap_or(false) {
|
||||
return Err(WasmError::InvalidHeapPages)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(WasmtimeRuntime {
|
||||
module: compiled_module,
|
||||
context,
|
||||
heap_pages,
|
||||
module,
|
||||
imports,
|
||||
state_holder,
|
||||
heap_pages: heap_pages as u32,
|
||||
host_functions,
|
||||
memory_index,
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MissingFunction {
|
||||
name: String,
|
||||
sig: cranelift_codegen::ir::Signature,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MissingFunctionStubs {
|
||||
stubs: HashMap<String, Vec<MissingFunction>>,
|
||||
}
|
||||
|
||||
impl MissingFunctionStubs {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
stubs: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn insert(&mut self, module: String, name: String, sig: cranelift_codegen::ir::Signature) {
|
||||
self.stubs.entry(module).or_insert_with(Vec::new).push(MissingFunction {
|
||||
name,
|
||||
sig,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn scan_missing_functions(
|
||||
code: &[u8],
|
||||
host_functions: &[&'static dyn Function],
|
||||
) -> std::result::Result<MissingFunctionStubs, WasmError> {
|
||||
let isa = target_isa()?;
|
||||
let call_conv = isa.default_call_conv();
|
||||
|
||||
let module = parity_wasm::elements::Module::from_bytes(code)
|
||||
.map_err(|e| WasmError::Other(format!("cannot deserialize error: {}", e)))?;
|
||||
|
||||
let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]);
|
||||
let import_entries = module
|
||||
.import_section()
|
||||
.map(|is| is.entries())
|
||||
.unwrap_or(&[]);
|
||||
|
||||
let mut missing_functions = MissingFunctionStubs::new();
|
||||
for import_entry in import_entries {
|
||||
let func_ty = match import_entry.external() {
|
||||
parity_wasm::elements::External::Function(func_ty_idx) => {
|
||||
let parity_wasm::elements::Type::Function(ref func_ty) =
|
||||
types.get(*func_ty_idx as usize).ok_or_else(|| {
|
||||
WasmError::Other(format!("corrupted module, type out of bounds"))
|
||||
})?;
|
||||
func_ty
|
||||
}
|
||||
_ => {
|
||||
// We are looking only for missing **functions** here. Any other items, be they
|
||||
// missing or not, will be handled at the resolution stage later.
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let signature = convert_parity_wasm_signature(func_ty);
|
||||
|
||||
if import_entry.module() == "env" {
|
||||
if let Some(hf) = host_functions
|
||||
.iter()
|
||||
.find(|hf| hf.name() == import_entry.field())
|
||||
{
|
||||
if signature == hf.signature() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This function is either not from the env module, or doesn't have a corresponding host
|
||||
// function, or the signature is not matching. Add it to the list.
|
||||
let sig = cranelift_ir_signature(signature, &call_conv);
|
||||
|
||||
missing_functions.insert(
|
||||
import_entry.module().to_string(),
|
||||
import_entry.field().to_string(),
|
||||
sig,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(missing_functions)
|
||||
}
|
||||
|
||||
fn create_compiled_unit(
|
||||
code: &[u8],
|
||||
host_functions: &[&'static dyn Function],
|
||||
heap_pages: u32,
|
||||
allow_missing_func_imports: bool,
|
||||
) -> std::result::Result<(CompiledModule, Context, MemoryIndex), WasmError> {
|
||||
let compilation_strategy = CompilationStrategy::Cranelift;
|
||||
|
||||
let compiler = new_compiler(compilation_strategy)?;
|
||||
let mut context = Context::new(Box::new(compiler));
|
||||
|
||||
// Enable/disable producing of debug info.
|
||||
context.set_debug_info(false);
|
||||
|
||||
// Instantiate and link the env module.
|
||||
let global_exports = context.get_global_exports();
|
||||
let compiler = new_compiler(compilation_strategy)?;
|
||||
|
||||
let mut missing_functions_stubs = if allow_missing_func_imports {
|
||||
scan_missing_functions(code, host_functions)?
|
||||
} else {
|
||||
// If there are in fact missing functions they will be detected at the instantiation time
|
||||
// and the module will be rejected.
|
||||
MissingFunctionStubs::new()
|
||||
};
|
||||
|
||||
let env_missing_functions = missing_functions_stubs.stubs
|
||||
.remove("env")
|
||||
.unwrap_or_else(|| Vec::new());
|
||||
|
||||
let (module, memory_index) = instantiate_env_module(
|
||||
global_exports,
|
||||
compiler,
|
||||
host_functions,
|
||||
heap_pages,
|
||||
env_missing_functions,
|
||||
true,
|
||||
)?;
|
||||
|
||||
context.name_instance("env".to_owned(), module);
|
||||
|
||||
for (module, missing_functions_stubs) in missing_functions_stubs.stubs {
|
||||
let compiler = new_compiler(compilation_strategy)?;
|
||||
let global_exports = context.get_global_exports();
|
||||
let instance = instantiate_env_module(
|
||||
global_exports,
|
||||
compiler,
|
||||
&[],
|
||||
heap_pages,
|
||||
missing_functions_stubs,
|
||||
false,
|
||||
)?.0;
|
||||
context.name_instance(module, instance);
|
||||
}
|
||||
|
||||
// Compile the wasm module.
|
||||
let module = context.compile_module(&code)
|
||||
.map_err(|e| WasmError::Other(format!("module compile error: {}", e)))?;
|
||||
|
||||
Ok((module, context, memory_index.expect("Memory is added on request; qed")))
|
||||
}
|
||||
|
||||
/// Call a function inside a precompiled Wasm module.
|
||||
fn call_method(
|
||||
context: &mut Context,
|
||||
module: &mut CompiledModule,
|
||||
module: &Module,
|
||||
imports: &mut Imports,
|
||||
state_holder: &StateHolder,
|
||||
method: &str,
|
||||
data: &[u8],
|
||||
memory_index: MemoryIndex,
|
||||
heap_pages: u32,
|
||||
) -> Result<Vec<u8>> {
|
||||
let is_imported_memory = module.module().is_imported_memory(memory_index);
|
||||
// Old exports get clobbered in `InstanceHandle::new` if we don't explicitly remove them first.
|
||||
//
|
||||
// The global exports mechanism is temporary in Wasmtime and expected to be removed.
|
||||
// https://github.com/CraneStation/wasmtime/issues/332
|
||||
clear_globals(&mut *context.get_global_exports().borrow_mut(), is_imported_memory);
|
||||
let instance_wrapper = InstanceWrapper::new(module, imports, heap_pages)?;
|
||||
let entrypoint = instance_wrapper.resolve_entrypoint(method)?;
|
||||
let heap_base = instance_wrapper.extract_heap_base()?;
|
||||
let allocator = FreeingBumpHeapAllocator::new(heap_base);
|
||||
|
||||
let mut instance = module.instantiate().map_err(|e| Error::Other(e.to_string()))?;
|
||||
perform_call(data, state_holder, instance_wrapper, entrypoint, allocator)
|
||||
}
|
||||
|
||||
if !is_imported_memory {
|
||||
grow_memory(&mut instance, heap_pages)?;
|
||||
}
|
||||
fn perform_call(
|
||||
data: &[u8],
|
||||
state_holder: &StateHolder,
|
||||
instance_wrapper: InstanceWrapper,
|
||||
entrypoint: wasmtime::Func,
|
||||
mut allocator: FreeingBumpHeapAllocator,
|
||||
) -> Result<Vec<u8>> {
|
||||
let (data_ptr, data_len) = inject_input_data(&instance_wrapper, &mut allocator, data)?;
|
||||
|
||||
// Initialize the function executor state.
|
||||
let heap_base = get_heap_base(&instance)?;
|
||||
let executor_state = FunctionExecutorState::new(heap_base);
|
||||
reset_env_state_and_take_trap(context, Some(executor_state))?;
|
||||
|
||||
// Write the input data into guest memory.
|
||||
let (data_ptr, data_len) = inject_input_data(context, &mut instance, data, memory_index)?;
|
||||
let args = [RuntimeValue::I32(u32::from(data_ptr) as i32), RuntimeValue::I32(data_len as i32)];
|
||||
|
||||
// Invoke the function in the runtime.
|
||||
let outcome = context
|
||||
.invoke(&mut instance, method, &args[..])
|
||||
.map_err(|e| Error::Other(format!("error calling runtime: {}", e)))?;
|
||||
let trap_error = reset_env_state_and_take_trap(context, None)?;
|
||||
let (output_ptr, output_len) = match outcome {
|
||||
ActionOutcome::Returned { values } => match values.as_slice() {
|
||||
[RuntimeValue::I64(retval)] => unpack_ptr_and_len(*retval as u64),
|
||||
_ => return Err(Error::InvalidReturn),
|
||||
let host_state = HostState::new(allocator, instance_wrapper);
|
||||
let (ret, host_state) = state_holder.with_initialized_state(host_state, || {
|
||||
match entrypoint.call(&[
|
||||
wasmtime::Val::I32(u32::from(data_ptr) as i32),
|
||||
wasmtime::Val::I32(u32::from(data_len) as i32),
|
||||
]) {
|
||||
Ok(results) => {
|
||||
let retval = results[0].unwrap_i64() as u64;
|
||||
Ok(unpack_ptr_and_len(retval))
|
||||
}
|
||||
Err(trap) => {
|
||||
return Err(Error::from(format!(
|
||||
"Wasm execution trapped: {}",
|
||||
trap.message()
|
||||
)));
|
||||
}
|
||||
}
|
||||
ActionOutcome::Trapped { message } => return Err(trap_error.unwrap_or_else(
|
||||
|| format!("Wasm execution trapped: {}", message).into()
|
||||
)),
|
||||
};
|
||||
});
|
||||
let (output_ptr, output_len) = ret?;
|
||||
|
||||
let instance = host_state.into_instance();
|
||||
let output = extract_output_data(&instance, output_ptr, output_len)?;
|
||||
|
||||
// Read the output data from guest memory.
|
||||
let mut output = vec![0; output_len as usize];
|
||||
let memory = get_memory_mut(&mut instance, memory_index)?;
|
||||
read_memory_into(memory, Pointer::new(output_ptr), &mut output)?;
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// The implementation is based on wasmtime_wasi::instantiate_wasi.
|
||||
fn instantiate_env_module(
|
||||
global_exports: Rc<RefCell<HashMap<String, Option<Export>>>>,
|
||||
compiler: Compiler,
|
||||
host_functions: &[&'static dyn Function],
|
||||
heap_pages: u32,
|
||||
missing_functions_stubs: Vec<MissingFunction>,
|
||||
add_memory: bool,
|
||||
) -> std::result::Result<(InstanceHandle, Option<MemoryIndex>), WasmError> {
|
||||
let isa = target_isa()?;
|
||||
let pointer_type = isa.pointer_type();
|
||||
let call_conv = isa.default_call_conv();
|
||||
|
||||
let mut fn_builder_ctx = FunctionBuilderContext::new();
|
||||
let mut module = Module::new();
|
||||
let mut finished_functions = <PrimaryMap<DefinedFuncIndex, *const VMFunctionBody>>::new();
|
||||
let mut code_memory = CodeMemory::new();
|
||||
|
||||
for function in host_functions {
|
||||
let sig = translate_signature(
|
||||
cranelift_ir_signature(function.signature(), &call_conv),
|
||||
pointer_type,
|
||||
);
|
||||
let sig_id = module.signatures.push(sig.clone());
|
||||
let func_id = module.functions.push(sig_id);
|
||||
module
|
||||
.exports
|
||||
.insert(function.name().to_string(), wasmtime_environ::Export::Function(func_id));
|
||||
|
||||
let trampoline = make_trampoline(
|
||||
isa.as_ref(),
|
||||
&mut code_memory,
|
||||
&mut fn_builder_ctx,
|
||||
func_id.index() as u32,
|
||||
&sig,
|
||||
)?;
|
||||
finished_functions.push(trampoline);
|
||||
}
|
||||
|
||||
for MissingFunction { name, sig } in missing_functions_stubs {
|
||||
let sig = translate_signature(
|
||||
sig,
|
||||
pointer_type,
|
||||
);
|
||||
let sig_id = module.signatures.push(sig.clone());
|
||||
let func_id = module.functions.push(sig_id);
|
||||
module
|
||||
.exports
|
||||
.insert(name, wasmtime_environ::Export::Function(func_id));
|
||||
let trampoline = make_trampoline(
|
||||
isa.as_ref(),
|
||||
&mut code_memory,
|
||||
&mut fn_builder_ctx,
|
||||
func_id.index() as u32,
|
||||
&sig,
|
||||
)?;
|
||||
finished_functions.push(trampoline);
|
||||
}
|
||||
|
||||
code_memory.publish();
|
||||
|
||||
let memory_id = if add_memory {
|
||||
let memory = cranelift_wasm::Memory {
|
||||
minimum: heap_pages + INITIAL_HEAP_PAGES,
|
||||
maximum: Some(heap_pages + INITIAL_HEAP_PAGES),
|
||||
shared: false,
|
||||
};
|
||||
let memory_plan = wasmtime_environ::MemoryPlan::for_memory(memory, &Default::default());
|
||||
|
||||
let memory_id = module.memory_plans.push(memory_plan);
|
||||
module.exports.insert("memory".into(), wasmtime_environ::Export::Memory(memory_id));
|
||||
|
||||
Some(memory_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let imports = Imports::none();
|
||||
let data_initializers = Vec::new();
|
||||
let signatures = PrimaryMap::new();
|
||||
let env_state = EnvState::new(code_memory, compiler, host_functions);
|
||||
|
||||
let result = InstanceHandle::new(
|
||||
Rc::new(module),
|
||||
global_exports,
|
||||
finished_functions.into_boxed_slice(),
|
||||
imports,
|
||||
&data_initializers,
|
||||
signatures.into_boxed_slice(),
|
||||
None,
|
||||
Box::new(env_state),
|
||||
);
|
||||
|
||||
result
|
||||
.map_err(|e| WasmError::Other(format!("cannot instantiate env: {}", e)))
|
||||
.map(|r| (r, memory_id))
|
||||
}
|
||||
|
||||
/// Build a new TargetIsa for the host machine.
|
||||
fn target_isa() -> std::result::Result<Box<dyn TargetIsa>, WasmError> {
|
||||
let isa_builder = cranelift_native::builder()
|
||||
.map_err(|e| WasmError::Other(format!("missing compiler support: {}", e)))?;
|
||||
let flag_builder = cranelift_codegen::settings::builder();
|
||||
Ok(isa_builder.finish(cranelift_codegen::settings::Flags::new(flag_builder)))
|
||||
}
|
||||
|
||||
fn new_compiler(strategy: CompilationStrategy) -> std::result::Result<Compiler, WasmError> {
|
||||
let isa = target_isa()?;
|
||||
Ok(Compiler::new(isa, strategy))
|
||||
}
|
||||
|
||||
fn clear_globals(global_exports: &mut HashMap<String, Option<Export>>, is_imported_memory: bool) {
|
||||
// When memory is imported, we can not delete the global export.
|
||||
if !is_imported_memory {
|
||||
global_exports.remove("memory");
|
||||
}
|
||||
global_exports.remove("__heap_base");
|
||||
global_exports.remove("__indirect_function_table");
|
||||
}
|
||||
|
||||
fn grow_memory(instance: &mut InstanceHandle, pages: u32) -> Result<()> {
|
||||
// This is safe to wrap in an unsafe block as:
|
||||
// - The result of the `lookup_immutable` call is not mutated
|
||||
// - The definition pointer is returned by a lookup on a valid instance
|
||||
let memory_index = unsafe {
|
||||
match instance.lookup_immutable("memory") {
|
||||
Some(Export::Memory { definition, vmctx: _, memory: _ }) =>
|
||||
instance.memory_index(&*definition),
|
||||
_ => return Err(Error::InvalidMemoryReference),
|
||||
}
|
||||
};
|
||||
instance.memory_grow(memory_index, pages)
|
||||
.map(|_| ())
|
||||
.ok_or_else(|| "requested heap_pages would exceed maximum memory size".into())
|
||||
}
|
||||
|
||||
fn get_env_state(context: &mut Context) -> Result<&mut EnvState> {
|
||||
let env_instance = context.get_instance("env")
|
||||
.map_err(|err| format!("cannot find \"env\" module: {}", err))?;
|
||||
env_instance
|
||||
.host_state()
|
||||
.downcast_mut::<EnvState>()
|
||||
.ok_or_else(|| "cannot get \"env\" module host state".into())
|
||||
}
|
||||
|
||||
fn reset_env_state_and_take_trap(
|
||||
context: &mut Context,
|
||||
executor_state: Option<FunctionExecutorState>,
|
||||
) -> Result<Option<Error>>
|
||||
{
|
||||
let env_state = get_env_state(context)?;
|
||||
env_state.executor_state = executor_state;
|
||||
Ok(env_state.take_trap())
|
||||
}
|
||||
|
||||
fn inject_input_data(
|
||||
context: &mut Context,
|
||||
instance: &mut InstanceHandle,
|
||||
instance: &InstanceWrapper,
|
||||
allocator: &mut FreeingBumpHeapAllocator,
|
||||
data: &[u8],
|
||||
memory_index: MemoryIndex,
|
||||
) -> Result<(Pointer<u8>, WordSize)> {
|
||||
let env_state = get_env_state(context)?;
|
||||
let executor_state = env_state.executor_state
|
||||
.as_mut()
|
||||
.ok_or_else(|| "cannot get \"env\" module executor state")?;
|
||||
|
||||
let memory = get_memory_mut(instance, memory_index)?;
|
||||
|
||||
let data_len = data.len() as WordSize;
|
||||
let data_ptr = executor_state.heap().allocate(memory, data_len)?;
|
||||
write_memory_from(memory, data_ptr, data)?;
|
||||
let data_ptr = instance.allocate(allocator, data_len)?;
|
||||
instance.write_memory_from(data_ptr, data)?;
|
||||
Ok((data_ptr, data_len))
|
||||
}
|
||||
|
||||
fn get_memory_mut(instance: &mut InstanceHandle, memory_index: MemoryIndex) -> Result<&mut [u8]> {
|
||||
match instance.lookup_by_declaration(&wasmtime_environ::Export::Memory(memory_index)) {
|
||||
// This is safe to wrap in an unsafe block as:
|
||||
// - The definition pointer is returned by a lookup on a valid instance and thus points to
|
||||
// a valid memory definition
|
||||
Export::Memory { definition, vmctx: _, memory: _ } => unsafe {
|
||||
Ok(std::slice::from_raw_parts_mut(
|
||||
(*definition).base,
|
||||
(*definition).current_length,
|
||||
))
|
||||
},
|
||||
_ => Err(Error::InvalidMemoryReference),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_heap_base(instance: &InstanceHandle) -> Result<u32> {
|
||||
// This is safe to wrap in an unsafe block as:
|
||||
// - The result of the `lookup_immutable` call is not mutated
|
||||
// - The definition pointer is returned by a lookup on a valid instance
|
||||
// - The defined value is checked to be an I32, which can be read safely as a u32
|
||||
unsafe {
|
||||
match instance.lookup_immutable("__heap_base") {
|
||||
Some(Export::Global { definition, vmctx: _, global })
|
||||
if global.ty == ir::types::I32 =>
|
||||
Ok(*(*definition).as_u32()),
|
||||
_ => return Err(Error::HeapBaseNotFoundOrInvalid),
|
||||
}
|
||||
}
|
||||
fn extract_output_data(
|
||||
instance: &InstanceWrapper,
|
||||
output_ptr: u32,
|
||||
output_len: u32,
|
||||
) -> Result<Vec<u8>> {
|
||||
let mut output = vec![0; output_len as usize];
|
||||
instance.read_memory_into(Pointer::new(output_ptr), &mut output)?;
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::host::{HostContext, HostState};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A common place to store a reference to the `HostState`.
|
||||
///
|
||||
/// This structure is passed into each host function handler and retained in the implementation of
|
||||
/// `WasmRuntime`. Whenever a call into a runtime method is initiated, the host state is populated
|
||||
/// with the state for that runtime method call.
|
||||
///
|
||||
/// During the execution of the runtime method call, wasm can call imported host functions. When
|
||||
/// that happens the host function handler gets a `HostContext` (obtainable through having a
|
||||
/// `HostState` reference).
|
||||
#[derive(Clone)]
|
||||
pub struct StateHolder {
|
||||
// This is `Some` only during a call.
|
||||
state: Rc<RefCell<Option<HostState>>>,
|
||||
}
|
||||
|
||||
impl StateHolder {
|
||||
/// Create a placeholder `StateHolder`.
|
||||
pub fn empty() -> StateHolder {
|
||||
StateHolder {
|
||||
state: Rc::new(RefCell::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide `HostState` for the runtime method call and execute the given function `f`.
|
||||
///
|
||||
/// During the execution of the provided function `with_context` will be callable.
|
||||
pub fn with_initialized_state<R, F>(&self, state: HostState, f: F) -> (R, HostState)
|
||||
where
|
||||
F: FnOnce() -> R,
|
||||
{
|
||||
*self.state.borrow_mut() = Some(state);
|
||||
|
||||
let ret = f();
|
||||
let state = self
|
||||
.state
|
||||
.borrow_mut()
|
||||
.take()
|
||||
.expect("cannot be None since was just assigned; qed");
|
||||
|
||||
(ret, state)
|
||||
}
|
||||
|
||||
/// Create a `HostContext` from the contained `HostState` and execute the given function `f`.
|
||||
///
|
||||
/// This function is only callable within closure passed to `init_state`. Otherwise, the passed
|
||||
/// context will be `None`.
|
||||
pub fn with_context<R, F>(&self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(Option<HostContext>) -> R,
|
||||
{
|
||||
let state = self.state.borrow();
|
||||
match *state {
|
||||
Some(ref state) => f(Some(state.materialize())),
|
||||
None => f(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,361 +0,0 @@
|
||||
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! The trampoline is the dynamically generated entry point to a runtime host call.
|
||||
//!
|
||||
//! This code is based on and large parts are copied from wasmtime's
|
||||
//! wasmtime-api/src/trampoline/func.rs.
|
||||
|
||||
use crate::function_executor::{FunctionExecutorState, FunctionExecutor};
|
||||
use sc_executor_common::error::{Error, WasmError};
|
||||
|
||||
use cranelift_codegen::{Context, binemit, ir, isa};
|
||||
use cranelift_codegen::ir::{InstBuilder, StackSlotData, StackSlotKind, TrapCode};
|
||||
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
|
||||
use cranelift_codegen::print_errors::pretty_error;
|
||||
use wasmtime_jit::{CodeMemory, Compiler};
|
||||
use wasmtime_environ::CompiledFunction;
|
||||
use wasmtime_runtime::{VMContext, VMFunctionBody};
|
||||
use sp_wasm_interface::{Function, Value, ValueType};
|
||||
use std::{cmp, panic::{self, AssertUnwindSafe}, ptr};
|
||||
|
||||
const CALL_SUCCESS: u32 = 0;
|
||||
const CALL_FAILED_WITH_ERROR: u32 = 1;
|
||||
const CALL_WITH_BAD_HOST_STATE: u32 = 2;
|
||||
|
||||
/// A code to trap with that indicates a host call error.
|
||||
const TRAP_USER_CODE: u16 = 0;
|
||||
|
||||
/// The only Wasm types allowed in host function signatures (I32, I64, F32, F64) are all
|
||||
/// represented in at most 8 bytes.
|
||||
const MAX_WASM_TYPE_SIZE: usize = 8;
|
||||
|
||||
/// The top-level host state of the "env" module. This state is used by the trampoline function to
|
||||
/// construct a `FunctionExecutor` which can execute the host call.
|
||||
pub struct EnvState {
|
||||
host_functions: Vec<&'static dyn Function>,
|
||||
compiler: Compiler,
|
||||
// The code memory must be kept around on the state to prevent it from being dropped.
|
||||
#[allow(dead_code)]
|
||||
code_memory: CodeMemory,
|
||||
trap: Option<Error>,
|
||||
/// The executor state stored across host calls during a single Wasm runtime call.
|
||||
/// During a runtime call, this MUST be `Some`.
|
||||
pub executor_state: Option<FunctionExecutorState>,
|
||||
}
|
||||
|
||||
impl EnvState {
|
||||
/// Construct a new `EnvState` which owns the given code memory.
|
||||
pub fn new(
|
||||
code_memory: CodeMemory,
|
||||
compiler: Compiler,
|
||||
host_functions: &[&'static dyn Function],
|
||||
) -> Self {
|
||||
EnvState {
|
||||
trap: None,
|
||||
compiler,
|
||||
code_memory,
|
||||
executor_state: None,
|
||||
host_functions: host_functions.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets the trap error to None and returns the current value.
|
||||
pub fn take_trap(&mut self) -> Option<Error> {
|
||||
self.trap.take()
|
||||
}
|
||||
}
|
||||
|
||||
/// This is called by the dynamically generated trampoline taking the function index and reference
|
||||
/// to the call arguments on the stack as arguments. Returns zero on success and a non-zero value
|
||||
/// on failure.
|
||||
unsafe extern "C" fn stub_fn(vmctx: *mut VMContext, func_index: u32, values_vec: *mut i64) -> u32 {
|
||||
if let Some(state) = (*vmctx).host_state().downcast_mut::<EnvState>() {
|
||||
match stub_fn_inner(
|
||||
vmctx,
|
||||
&state.host_functions,
|
||||
&mut state.compiler,
|
||||
state.executor_state.as_mut(),
|
||||
func_index,
|
||||
values_vec,
|
||||
) {
|
||||
Ok(()) => CALL_SUCCESS,
|
||||
Err(err) => {
|
||||
state.trap = Some(err);
|
||||
CALL_FAILED_WITH_ERROR
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Well, we can't even set a trap message, so we'll just exit without one.
|
||||
CALL_WITH_BAD_HOST_STATE
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements most of the logic in `stub_fn` but returning a `Result` instead of an integer error
|
||||
/// for the sake of readability.
|
||||
unsafe fn stub_fn_inner(
|
||||
vmctx: *mut VMContext,
|
||||
externals: &[&dyn Function],
|
||||
compiler: &mut Compiler,
|
||||
executor_state: Option<&mut FunctionExecutorState>,
|
||||
func_index: u32,
|
||||
values_vec: *mut i64,
|
||||
) -> Result<(), Error> {
|
||||
let func = externals.get(func_index as usize)
|
||||
.ok_or_else(|| format!("call to undefined external function with index {}", func_index))?;
|
||||
let executor_state = executor_state
|
||||
.ok_or_else(|| "executor state is None during call to external function")?;
|
||||
|
||||
// Build the external function context.
|
||||
let mut context = FunctionExecutor::new(vmctx, compiler, executor_state)?;
|
||||
let mut context = AssertUnwindSafe(&mut context);
|
||||
|
||||
// Execute and write output back to the stack.
|
||||
let return_val = panic::catch_unwind(move || {
|
||||
let signature = func.signature();
|
||||
|
||||
// Read the arguments from the stack.
|
||||
let mut args = signature.args.iter()
|
||||
.enumerate()
|
||||
.map(|(i, ¶m_type)| read_value_from(values_vec.offset(i as isize), param_type));
|
||||
|
||||
func.execute(&mut **context, &mut args)
|
||||
});
|
||||
|
||||
match return_val {
|
||||
Ok(ret_val) => {
|
||||
if let Some(val) = ret_val
|
||||
.map_err(|e| Error::FunctionExecution(func.name().to_string(), e))? {
|
||||
write_value_to(values_vec, val);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
Err(e) => {
|
||||
let message = if let Some(err) = e.downcast_ref::<String>() {
|
||||
err.to_string()
|
||||
} else if let Some(err) = e.downcast_ref::<&str>() {
|
||||
err.to_string()
|
||||
} else {
|
||||
"Panicked without any further information!".into()
|
||||
};
|
||||
|
||||
Err(Error::FunctionExecution(func.name().to_string(), message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a trampoline for invoking a host function.
|
||||
///
|
||||
/// The trampoline is a dynamically generated entry point to a runtime host call. The function is
|
||||
/// generated by manually constructing Cranelift IR and using the Cranelift compiler. The
|
||||
/// trampoline embeds the function index as a constant and delegates to a stub function in Rust,
|
||||
/// which takes the function index and a memory reference to the stack arguments and return value
|
||||
/// slots.
|
||||
///
|
||||
/// This code is of modified copy of wasmtime's wasmtime-api/src/trampoline/func.rs.
|
||||
pub fn make_trampoline(
|
||||
isa: &dyn isa::TargetIsa,
|
||||
code_memory: &mut CodeMemory,
|
||||
fn_builder_ctx: &mut FunctionBuilderContext,
|
||||
func_index: u32,
|
||||
signature: &ir::Signature,
|
||||
) -> Result<*const VMFunctionBody, WasmError> {
|
||||
// Mostly reverse copy of the similar method from wasmtime's
|
||||
// wasmtime-jit/src/compiler.rs.
|
||||
let pointer_type = isa.pointer_type();
|
||||
let mut stub_sig = ir::Signature::new(isa.frontend_config().default_call_conv);
|
||||
|
||||
// Ensure that the first parameter of the generated function is the `VMContext` pointer.
|
||||
assert_eq!(
|
||||
signature.params[0],
|
||||
ir::AbiParam::special(pointer_type, ir::ArgumentPurpose::VMContext)
|
||||
);
|
||||
|
||||
// Add the `vmctx` parameter.
|
||||
stub_sig.params.push(ir::AbiParam::special(
|
||||
pointer_type,
|
||||
ir::ArgumentPurpose::VMContext,
|
||||
));
|
||||
|
||||
// Add the `func_index` parameter.
|
||||
stub_sig.params.push(ir::AbiParam::new(ir::types::I32));
|
||||
|
||||
// Add the `values_vec` parameter.
|
||||
stub_sig.params.push(ir::AbiParam::new(pointer_type));
|
||||
|
||||
// Add error/trap return.
|
||||
stub_sig.returns.push(ir::AbiParam::new(ir::types::I32));
|
||||
|
||||
// Each parameter and return value gets a 64-bit (8-byte) wide slot on the stack, as that is
|
||||
// large enough to fit all Wasm primitive types that can be used in host function signatures.
|
||||
// The `VMContext` pointer, which is a parameter of the function signature, is excluded as it
|
||||
// is passed directly to the stub function rather than being looked up on the caller stack from
|
||||
// the `values_vec` pointer.
|
||||
let values_vec_len = cmp::max(signature.params.len() - 1, signature.returns.len());
|
||||
let values_vec_size = (MAX_WASM_TYPE_SIZE * values_vec_len) as u32;
|
||||
|
||||
let mut context = Context::new();
|
||||
context.func =
|
||||
ir::Function::with_name_signature(ir::ExternalName::user(0, 0), signature.clone());
|
||||
|
||||
let ss = context.func.create_stack_slot(StackSlotData::new(
|
||||
StackSlotKind::ExplicitSlot,
|
||||
values_vec_size,
|
||||
));
|
||||
|
||||
{
|
||||
let mut builder = FunctionBuilder::new(&mut context.func, fn_builder_ctx);
|
||||
let block0 = builder.create_ebb();
|
||||
|
||||
builder.append_ebb_params_for_function_params(block0);
|
||||
builder.switch_to_block(block0);
|
||||
builder.seal_block(block0);
|
||||
|
||||
let values_vec_ptr_val = builder.ins().stack_addr(pointer_type, ss, 0);
|
||||
let mflags = ir::MemFlags::trusted();
|
||||
for i in 1..signature.params.len() {
|
||||
let val = builder.func.dfg.ebb_params(block0)[i];
|
||||
builder.ins().store(
|
||||
mflags,
|
||||
val,
|
||||
values_vec_ptr_val,
|
||||
((i - 1) * MAX_WASM_TYPE_SIZE) as i32,
|
||||
);
|
||||
}
|
||||
|
||||
let vmctx_ptr_val = builder.func.dfg.ebb_params(block0)[0];
|
||||
let func_index_val = builder.ins().iconst(ir::types::I32, func_index as i64);
|
||||
|
||||
let callee_args = vec![vmctx_ptr_val, func_index_val, values_vec_ptr_val];
|
||||
|
||||
let new_sig = builder.import_signature(stub_sig.clone());
|
||||
|
||||
let callee_value = builder
|
||||
.ins()
|
||||
.iconst(pointer_type, stub_fn as *const VMFunctionBody as i64);
|
||||
let call = builder
|
||||
.ins()
|
||||
.call_indirect(new_sig, callee_value, &callee_args);
|
||||
|
||||
let call_result = builder.func.dfg.inst_results(call)[0];
|
||||
builder.ins().trapnz(call_result, TrapCode::User(TRAP_USER_CODE));
|
||||
|
||||
let mflags = ir::MemFlags::trusted();
|
||||
let mut results = Vec::new();
|
||||
for (i, r) in signature.returns.iter().enumerate() {
|
||||
let load = builder.ins().load(
|
||||
r.value_type,
|
||||
mflags,
|
||||
values_vec_ptr_val,
|
||||
(i * MAX_WASM_TYPE_SIZE) as i32,
|
||||
);
|
||||
results.push(load);
|
||||
}
|
||||
builder.ins().return_(&results);
|
||||
builder.finalize()
|
||||
}
|
||||
|
||||
let mut code_buf: Vec<u8> = Vec::new();
|
||||
let mut reloc_sink = RelocSink;
|
||||
let mut trap_sink = binemit::NullTrapSink {};
|
||||
let mut stackmap_sink = binemit::NullStackmapSink {};
|
||||
context
|
||||
.compile_and_emit(
|
||||
isa,
|
||||
&mut code_buf,
|
||||
&mut reloc_sink,
|
||||
&mut trap_sink,
|
||||
&mut stackmap_sink,
|
||||
)
|
||||
.map_err(|e| {
|
||||
WasmError::Instantiation(format!(
|
||||
"failed to compile trampoline: {}",
|
||||
pretty_error(&context.func, Some(isa), e)
|
||||
))
|
||||
})?;
|
||||
|
||||
let mut unwind_info = Vec::new();
|
||||
context.emit_unwind_info(isa, &mut unwind_info);
|
||||
|
||||
let func_ref = code_memory
|
||||
.allocate_for_function(&CompiledFunction {
|
||||
body: code_buf,
|
||||
jt_offsets: context.func.jt_offsets,
|
||||
unwind_info,
|
||||
})
|
||||
.map_err(|e| WasmError::Instantiation(format!("failed to allocate code memory: {}", e)))?;
|
||||
|
||||
Ok(func_ref.as_ptr())
|
||||
}
|
||||
|
||||
/// We don't expect trampoline compilation to produce any relocations, so
|
||||
/// this `RelocSink` just asserts that it doesn't recieve any.
|
||||
struct RelocSink;
|
||||
|
||||
impl binemit::RelocSink for RelocSink {
|
||||
fn reloc_ebb(
|
||||
&mut self,
|
||||
_offset: binemit::CodeOffset,
|
||||
_reloc: binemit::Reloc,
|
||||
_ebb_offset: binemit::CodeOffset,
|
||||
) {
|
||||
panic!("trampoline compilation should not produce ebb relocs");
|
||||
}
|
||||
fn reloc_external(
|
||||
&mut self,
|
||||
_offset: binemit::CodeOffset,
|
||||
_reloc: binemit::Reloc,
|
||||
_name: &ir::ExternalName,
|
||||
_addend: binemit::Addend,
|
||||
) {
|
||||
panic!("trampoline compilation should not produce external symbol relocs");
|
||||
}
|
||||
fn reloc_constant(
|
||||
&mut self,
|
||||
_code_offset: binemit::CodeOffset,
|
||||
_reloc: binemit::Reloc,
|
||||
_constant_offset: ir::ConstantOffset,
|
||||
) {
|
||||
panic!("trampoline compilation should not produce constant relocs");
|
||||
}
|
||||
fn reloc_jt(
|
||||
&mut self,
|
||||
_offset: binemit::CodeOffset,
|
||||
_reloc: binemit::Reloc,
|
||||
_jt: ir::JumpTable,
|
||||
) {
|
||||
panic!("trampoline compilation should not produce jump table relocs");
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn write_value_to(p: *mut i64, val: Value) {
|
||||
match val {
|
||||
Value::I32(i) => ptr::write(p as *mut i32, i),
|
||||
Value::I64(i) => ptr::write(p as *mut i64, i),
|
||||
Value::F32(u) => ptr::write(p as *mut u32, u),
|
||||
Value::F64(u) => ptr::write(p as *mut u64, u),
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn read_value_from(p: *const i64, ty: ValueType) -> Value {
|
||||
match ty {
|
||||
ValueType::I32 => Value::I32(ptr::read(p as *const i32)),
|
||||
ValueType::I64 => Value::I64(ptr::read(p as *const i64)),
|
||||
ValueType::F32 => Value::F32(ptr::read(p as *const u32)),
|
||||
ValueType::F64 => Value::F64(ptr::read(p as *const u64)),
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
@@ -14,31 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use sc_executor_common::error::{Error, Result};
|
||||
|
||||
use cranelift_codegen::{ir, isa};
|
||||
use std::ops::Range;
|
||||
use sp_wasm_interface::{Pointer, Signature, ValueType};
|
||||
|
||||
/// Read data from a slice of memory into a destination buffer.
|
||||
///
|
||||
/// Returns an error if the read would go out of the memory bounds.
|
||||
pub fn read_memory_into(memory: &[u8], address: Pointer<u8>, dest: &mut [u8]) -> Result<()> {
|
||||
let range = checked_range(address.into(), dest.len(), memory.len())
|
||||
.ok_or_else(|| Error::Other("memory read is out of bounds".into()))?;
|
||||
dest.copy_from_slice(&memory[range]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write data to a slice of memory.
|
||||
///
|
||||
/// Returns an error if the write would go out of the memory bounds.
|
||||
pub fn write_memory_from(memory: &mut [u8], address: Pointer<u8>, data: &[u8]) -> Result<()> {
|
||||
let range = checked_range(address.into(), data.len(), memory.len())
|
||||
.ok_or_else(|| Error::Other("memory write is out of bounds".into()))?;
|
||||
&mut memory[range].copy_from_slice(data);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Construct a range from an offset to a data length after the offset.
|
||||
/// Returns None if the end of the range would exceed some maximum offset.
|
||||
@@ -50,82 +26,3 @@ pub fn checked_range(offset: usize, len: usize, max: usize) -> Option<Range<usiz
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert from a parity wasm FunctionType to wasm interface's Signature.
|
||||
pub fn convert_parity_wasm_signature(func_ty: &parity_wasm::elements::FunctionType) -> Signature {
|
||||
fn convert_value_type(val_ty: parity_wasm::elements::ValueType) -> ValueType {
|
||||
use parity_wasm::elements::ValueType::*;
|
||||
match val_ty {
|
||||
I32 => ValueType::I32,
|
||||
I64 => ValueType::I64,
|
||||
F32 => ValueType::F32,
|
||||
F64 => ValueType::F64,
|
||||
}
|
||||
}
|
||||
|
||||
Signature::new(
|
||||
func_ty.params().iter().cloned().map(convert_value_type).collect::<Vec<_>>(),
|
||||
func_ty.return_type().map(convert_value_type),
|
||||
)
|
||||
}
|
||||
|
||||
/// Convert a wasm_interface Signature into a cranelift_codegen Signature.
|
||||
pub fn cranelift_ir_signature(signature: Signature, call_conv: &isa::CallConv) -> ir::Signature {
|
||||
ir::Signature {
|
||||
params: signature.args.iter()
|
||||
.map(cranelift_ir_type)
|
||||
.map(ir::AbiParam::new)
|
||||
.collect(),
|
||||
returns: signature.return_value.iter()
|
||||
.map(cranelift_ir_type)
|
||||
.map(ir::AbiParam::new)
|
||||
.collect(),
|
||||
call_conv: call_conv.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a wasm_interface ValueType into a cranelift_codegen Type.
|
||||
pub fn cranelift_ir_type(value_type: &ValueType) -> ir::types::Type {
|
||||
match value_type {
|
||||
ValueType::I32 => ir::types::I32,
|
||||
ValueType::I64 => ir::types::I64,
|
||||
ValueType::F32 => ir::types::F32,
|
||||
ValueType::F64 => ir::types::F64,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use assert_matches::assert_matches;
|
||||
|
||||
#[test]
|
||||
fn test_read_memory_into() {
|
||||
let mut memory = [0; 20];
|
||||
let mut dest = [0; 5];
|
||||
|
||||
&mut memory[15..20].copy_from_slice(b"hello");
|
||||
|
||||
read_memory_into(&memory[..], Pointer::new(15), &mut dest[..]).unwrap();
|
||||
|
||||
// Test that out of bounds read fails.
|
||||
assert_matches!(
|
||||
read_memory_into(&memory[..], Pointer::new(16), &mut dest[..]),
|
||||
Err(Error::Other(_))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_memory_from() {
|
||||
let mut memory = [0; 20];
|
||||
let data = b"hello";
|
||||
|
||||
write_memory_from(&mut memory[..], Pointer::new(15), data).unwrap();
|
||||
|
||||
// Test that out of bounds write fails.
|
||||
assert_matches!(
|
||||
write_memory_from(&mut memory[..], Pointer::new(16), data),
|
||||
Err(Error::Other(_))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user