diff --git a/Cargo.lock b/Cargo.lock index 7564a72a01e01..7c798893bc552 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", @@ -1852,6 +1853,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" @@ -2593,6 +2604,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" @@ -5939,11 +5956,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", @@ -5953,9 +5965,7 @@ dependencies = [ "sp-runtime-interface", "sp-wasm-interface", "wasmi", - "wasmtime-environ", - "wasmtime-jit", - "wasmtime-runtime", + "wasmtime", ] [[package]] @@ -7696,9 +7706,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" @@ -8270,7 +8280,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]] @@ -8633,35 +8643,61 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.39.3" +version = "0.48.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "073da89bf1c84db000dd68ce660c1b4a08e3a2d28fd1e3394ab9e7abdde4a0f8" + +[[package]] +name = "wasmparser" +version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c702914acda5feeeffbc29e4d953e5b9ce79d8b98da4dbf18a77086e116c5470" +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", @@ -8671,37 +8707,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", @@ -8710,16 +8746,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", @@ -8729,6 +8763,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" diff --git a/client/executor/common/src/wasm_runtime.rs b/client/executor/common/src/wasm_runtime.rs index 0733350f4cd6a..9b0ad6b780d83 100644 --- a/client/executor/common/src/wasm_runtime.rs +++ b/client/executor/common/src/wasm_runtime.rs @@ -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]; diff --git a/client/executor/src/integration_tests/mod.rs b/client/executor/src/integration_tests/mod.rs index 3f3d9f69e13dc..8c48ec7fcb298 100644 --- a/client/executor/src/integration_tests/mod.rs +++ b/client/executor/src/integration_tests/mod.rs @@ -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\")" ), } } diff --git a/client/executor/src/wasm_runtime.rs b/client/executor/src/wasm_runtime.rs index b8966c3af2739..9d54246ee0763 100644 --- a/client/executor/src/wasm_runtime.rs +++ b/client/executor/src/wasm_runtime.rs @@ -42,6 +42,8 @@ pub enum WasmExecutionMethod { /// A Wasm runtime object along with its cached runtime version. struct VersionedRuntime { runtime: Box, + /// 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( Ok(VersionedRuntime { runtime, version, + heap_pages, }) } diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index 6fbfdbc1cced4..7965c7f95e0fa 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -73,8 +73,7 @@ impl<'a> sandbox::SandboxCapabilities for FunctionExecutor<'a> { invoke_args_len: WordSize, state: u32, func_idx: sandbox::SupervisorFuncIndex, - ) -> Result - { + ) -> Result { let result = wasmi::FuncInstance::invoke( dispatch_thunk, &[ @@ -536,7 +535,6 @@ struct StateSnapshot { data_segments: Vec<(u32, Vec)>, /// The list of all global mutable variables of the module in their sequential order. global_mut_values: Vec, - heap_pages: u64, } impl StateSnapshot { @@ -544,7 +542,6 @@ impl StateSnapshot { fn take( module_instance: &ModuleRef, data_segments: Vec, - heap_pages: u64, ) -> Option { 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; diff --git a/client/executor/wasmtime/Cargo.toml b/client/executor/wasmtime/Cargo.toml index 30d3a5dc87f0e..eb41adb2714e0 100644 --- a/client/executor/wasmtime/Cargo.toml +++ b/client/executor/wasmtime/Cargo.toml @@ -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" diff --git a/client/executor/wasmtime/src/function_executor.rs b/client/executor/wasmtime/src/function_executor.rs deleted file mode 100644 index b4971f8b8a65e..0000000000000 --- a/client/executor/wasmtime/src/function_executor.rs +++ /dev/null @@ -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 . - -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 `::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, - 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, - 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 - { - 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, - invoke_args_len: WordSize, - state: u32, - func_idx: SupervisorFuncIndex, - ) -> Result - { - 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::(); - 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, dest: &mut [u8]) -> WResult<()> { - read_memory_into(self.memory, address, dest).map_err(|e| e.to_string()) - } - - fn write_memory(&mut self, address: Pointer, data: &[u8]) -> WResult<()> { - write_memory_from(self.memory, address, data).map_err(|e| e.to_string()) - } - - fn allocate_memory(&mut self, size: WordSize) -> WResult> { - self.heap.allocate(self.memory, size).map_err(|e| e.to_string()) - } - - fn deallocate_memory(&mut self, ptr: Pointer) -> 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, - buf_len: WordSize, - ) -> WResult - { - 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, - val_len: WordSize, - ) -> WResult - { - 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 { - 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, - return_val_len: u32, - state: u32, - ) -> WResult { - trace!(target: "sp-sandbox", "invoke, instance_idx={}", instance_id); - - // Deserialize arguments and convert them into wasmi types. - let args = Vec::::decode(&mut &args[..]) - .map_err(|_| "Can't decode serialized arguments for the invocation")? - .into_iter() - .map(Into::into) - .collect::>(); - - 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 - { - // 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> { - 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, - frontend_config: TargetFrontendConfig, -) -> (ir::Signature, Vec) -{ - // This code is based on the wasmtime_jit::Context::invoke. - - let param_types = args.iter() - .map(|arg| arg.value_type()) - .collect::>(); - 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) -} - diff --git a/client/executor/wasmtime/src/host.rs b/client/executor/wasmtime/src/host.rs new file mode 100644 index 0000000000000..e0cc6ecc9ae7a --- /dev/null +++ b/client/executor/wasmtime/src/host.rs @@ -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 . + +//! 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 `::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>, + allocator: RefCell, + 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, + invoke_args_len: WordSize, + state: u32, + func_idx: SupervisorFuncIndex, + ) -> Result { + 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, + 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, 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> { + self.instance + .allocate(&mut *self.allocator.borrow_mut(), size) + .map_err(|e| e.to_string()) + } + + fn deallocate_memory(&mut self, ptr: Pointer) -> 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, + buf_len: WordSize, + ) -> sp_wasm_interface::Result { + 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, + val_len: WordSize, + ) -> sp_wasm_interface::Result { + 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 { + 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, + return_val_len: u32, + state: u32, + ) -> sp_wasm_interface::Result { + trace!(target: "sp-sandbox", "invoke, instance_idx={}", instance_id); + + // Deserialize arguments and convert them into wasmi types. + let args = Vec::::decode(&mut &args[..]) + .map_err(|_| "Can't decode serialized arguments for the invocation")? + .into_iter() + .map(Into::into) + .collect::>(); + + 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")?; + } + ::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 { + // 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> { + self.sandbox_store + .borrow() + .instance(instance_idx) + .map(|i| i.get_global_val(name)) + .map_err(|e| e.to_string()) + } +} diff --git a/client/executor/wasmtime/src/imports.rs b/client/executor/wasmtime/src/imports.rs new file mode 100644 index 0000000000000..349f84a0d74d2 --- /dev/null +++ b/client/executor/wasmtime/src/imports.rs @@ -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 . + +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, + pub externs: Vec, +} + +/// 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 { + 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 { + 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 { + 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::>() + .into_boxed_slice(); + let results = func + .signature() + .return_value + .iter() + .cloned() + .map(into_wasmtime_val_type) + .collect::>() + .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) -> String { + match payload.downcast::<&'static str>() { + Ok(msg) => msg.to_string(), + Err(payload) => match payload.downcast::() { + Ok(msg) => *msg, + // At least we tried... + Err(_) => "Box".to_string(), + }, + } +} diff --git a/client/executor/wasmtime/src/instance_wrapper.rs b/client/executor/wasmtime/src/instance_wrapper.rs new file mode 100644 index 0000000000000..8f722f6490a91 --- /dev/null +++ b/client/executor/wasmtime/src/instance_wrapper.rs @@ -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 . + +//! 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, + // 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 { + 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 { + // 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 { + 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 { + 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
{ + 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, 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, 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> { + 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, + ) -> 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) + } + } +} diff --git a/client/executor/wasmtime/src/lib.rs b/client/executor/wasmtime/src/lib.rs index 244fca8f842a3..8f4801e6da1d0 100644 --- a/client/executor/wasmtime/src/lib.rs +++ b/client/executor/wasmtime/src/lib.rs @@ -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; - diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index abf860667f818..106a398dfc770 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -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> { 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 { - let heap_pages = u32::try_from(heap_pages) - .map_err(|e| - WasmError::Other(format!("Heap pages can not be converted into `u32`: {:?}", e)) - )?; - - let (compiled_module, context, memory_index) = create_compiled_unit( - code, + // 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 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>, -} - -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 { - 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> { - 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 mut instance = module.instantiate().map_err(|e| Error::Other(e.to_string()))?; - - if !is_imported_memory { - grow_memory(&mut instance, heap_pages)?; - } - - // 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))?; + 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); - // 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), - } - ActionOutcome::Trapped { message } => return Err(trap_error.unwrap_or_else( - || format!("Wasm execution trapped: {}", message).into() - )), - }; - - // 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) + perform_call(data, state_holder, instance_wrapper, entrypoint, allocator) } -/// The implementation is based on wasmtime_wasi::instantiate_wasi. -fn instantiate_env_module( - global_exports: Rc>>>, - compiler: Compiler, - host_functions: &[&'static dyn Function], - heap_pages: u32, - missing_functions_stubs: Vec, - add_memory: bool, -) -> std::result::Result<(InstanceHandle, Option), 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 = >::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, 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 { - let isa = target_isa()?; - Ok(Compiler::new(isa, strategy)) -} - -fn clear_globals(global_exports: &mut HashMap>, 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), +fn perform_call( + data: &[u8], + state_holder: &StateHolder, + instance_wrapper: InstanceWrapper, + entrypoint: wasmtime::Func, + mut allocator: FreeingBumpHeapAllocator, +) -> Result> { + let (data_ptr, data_len) = inject_input_data(&instance_wrapper, &mut allocator, data)?; + + 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() + ))); + } } - }; - instance.memory_grow(memory_index, pages) - .map(|_| ()) - .ok_or_else(|| "requested heap_pages would exceed maximum memory size".into()) -} + }); + let (output_ptr, output_len) = ret?; -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::() - .ok_or_else(|| "cannot get \"env\" module host state".into()) -} + let instance = host_state.into_instance(); + let output = extract_output_data(&instance, output_ptr, output_len)?; -fn reset_env_state_and_take_trap( - context: &mut Context, - executor_state: Option, -) -> Result> -{ - let env_state = get_env_state(context)?; - env_state.executor_state = executor_state; - Ok(env_state.take_trap()) + Ok(output) } fn inject_input_data( - context: &mut Context, - instance: &mut InstanceHandle, + instance: &InstanceWrapper, + allocator: &mut FreeingBumpHeapAllocator, data: &[u8], - memory_index: MemoryIndex, ) -> Result<(Pointer, 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 { - // 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> { + let mut output = vec![0; output_len as usize]; + instance.read_memory_into(Pointer::new(output_ptr), &mut output)?; + Ok(output) } diff --git a/client/executor/wasmtime/src/state_holder.rs b/client/executor/wasmtime/src/state_holder.rs new file mode 100644 index 0000000000000..57564ed3ec414 --- /dev/null +++ b/client/executor/wasmtime/src/state_holder.rs @@ -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 . + +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>>, +} + +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(&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(&self, f: F) -> R + where + F: FnOnce(Option) -> R, + { + let state = self.state.borrow(); + match *state { + Some(ref state) => f(Some(state.materialize())), + None => f(None), + } + } +} diff --git a/client/executor/wasmtime/src/trampoline.rs b/client/executor/wasmtime/src/trampoline.rs deleted file mode 100644 index 8a2147760921b..0000000000000 --- a/client/executor/wasmtime/src/trampoline.rs +++ /dev/null @@ -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 . - -//! 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, - /// 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, -} - -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 { - 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::() { - 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::() { - 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 = 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)), - } -} diff --git a/client/executor/wasmtime/src/util.rs b/client/executor/wasmtime/src/util.rs index 77fb8af3867a4..d2de95d4cc715 100644 --- a/client/executor/wasmtime/src/util.rs +++ b/client/executor/wasmtime/src/util.rs @@ -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 . -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, 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, 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 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::>(), - 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(_)) - ) - } -}