This repository was archived by the owner on Nov 15, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Integrate Wasmtime for runtime execution #3869
Merged
Merged
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
87bf2d4
executor: Use non wasmi-specific execution in tests.
jimpo f37b9d8
executor: Move all runtime execution tests into tests file.
jimpo 36bdc5c
executor: Use test_case macro to easily execute tests with different
jimpo 727ce43
executor: Convert errors to strings with Display, not Debug.
jimpo 1bcb472
node-executor: Rewrite benchmarks with criterion.
jimpo 3908f76
executor: Begin implementation of Wasm runtime.
jimpo 447c31a
executor: Define and implement basic FunctionExecutor.
jimpo 828fbf0
executor: Implement host function trampoline generation.
jimpo 81535c5
executor: Instantiate and link runtime module to env module.
jimpo a3d2b35
executor: Provide input data during wasmtime execution.
jimpo a7bb759
executor: Implement SandboxCapabilites::invoke for wasmtime executor.
jimpo 9076ca0
executor: Integrate and test wasmtime execution method.
jimpo 5a903a8
executor: Improve FunctionExecution error messages.
jimpo a77e9bd
Scope the unsafe blocks to be smaller.
jimpo c964753
Rename TrampolineState to EnvState.
jimpo 5e0d828
Let EnvState own its own compiler instead of unsafe lifetime cast.
jimpo 7623f12
Refactor out some common wasmi/wasmtime logic.
jimpo 7467e35
Typos and cosmetic changes.
jimpo b73465c
More trampoline comments.
jimpo d5b72c6
Cargo.lock update.
jimpo 779d4b4
cli: CLI option for running Substrate with compiled Wasm execution.
jimpo 5ffda81
executor: Switch dependency from fork to official wasmtime repo.
jimpo 70896c0
Quiet down cranelift logs.
jimpo 4b0cdc8
Explicitly catch panics during host calls.
jimpo 28f7c11
Additional checks and clarifications in make_trampoline.
jimpo f4016dd
Merge branch 'master' into wasmtime-real
jimpo b76f733
Fixes after merge from master and panic safety for wasmtime
jimpo 0ec6caf
Merge branch 'master' into wasmtime-real
jimpo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
executor: Define and implement basic FunctionExecutor.
The SandboxCapabilities::invoke is still left unimplemented.
- Loading branch information
commit 447c31ad7f65e48d93071ba1ac94a95ea68f215a
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,300 @@ | ||
| // Copyright 2019 Parity Technologies (UK) Ltd. | ||
| // This file is part of Substrate. | ||
|
|
||
| // Substrate is free software: you can redistribute it and/or modify | ||
| // it under the terms of the GNU General Public License as published by | ||
| // the Free Software Foundation, either version 3 of the License, or | ||
| // (at your option) any later version. | ||
|
|
||
| // Substrate is distributed in the hope that it will be useful, | ||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| // GNU General Public License for more details. | ||
|
|
||
| // You should have received a copy of the GNU General Public License | ||
| // along with Substrate. If not, see <http://www.gnu.org/licenses/>. | ||
|
|
||
| use crate::allocator::FreeingBumpHeapAllocator; | ||
| use crate::error::{Error, Result}; | ||
| use crate::sandbox::{self, SandboxCapabilities, SupervisorFuncIndex}; | ||
| use crate::wasmtime::util::{checked_range, read_memory_into, write_memory_from}; | ||
|
|
||
| use codec::{Decode, Encode}; | ||
| use log::trace; | ||
| use primitives::sandbox as sandbox_primitives; | ||
| use wasmtime_jit::Compiler; | ||
| use wasmtime_runtime::{Export, VMCallerCheckedAnyfunc, VMContext}; | ||
| use wasm_interface::{ | ||
| FunctionContext, MemoryId, Pointer, Result as WResult, Sandbox, WordSize, | ||
| }; | ||
|
|
||
| /// Wrapper type for pointer to a Wasm table entry. | ||
| /// | ||
| /// The wrapper type is used to ensure that the function reference is valid as it must be unsafely | ||
| /// dereferenced from within the safe method `<FunctionExecutor as SandboxCapabilities>::invoke`. | ||
| #[derive(Clone, Copy)] | ||
| pub struct SupervisorFuncRef(*const VMCallerCheckedAnyfunc); | ||
|
|
||
| /// The state required to construct a FunctionExecutor context. The context only lasts for one host | ||
| /// call, whereas the state is maintained for the duration of a Wasm runtime call, which may make | ||
| /// many different host calls that must share state. | ||
| /// | ||
| /// This is stored as part of the host state of the "env" Wasmtime instance. | ||
| pub struct FunctionExecutorState { | ||
| // The lifetime of the reference is not actually static, but is unsafely cast to static since | ||
| // the compiler is owned by the wasmtime-jit `Context` and must be accessed during host calls. | ||
| compiler: &'static mut Compiler, | ||
| sandbox_store: sandbox::Store<SupervisorFuncRef>, | ||
| heap: FreeingBumpHeapAllocator, | ||
| } | ||
|
|
||
| impl FunctionExecutorState { | ||
| /// Constructs a new `FunctionExecutorState`. | ||
| pub fn new(compiler: &'static mut Compiler, heap_base: u32) -> Self { | ||
| FunctionExecutorState { | ||
| compiler, | ||
| sandbox_store: sandbox::Store::new(), | ||
| heap: FreeingBumpHeapAllocator::new(heap_base), | ||
| } | ||
| } | ||
|
|
||
| /// Returns a mutable reference to the heap allocator. | ||
| pub fn heap(&mut self) -> &mut FreeingBumpHeapAllocator { | ||
| &mut self.heap | ||
| } | ||
| } | ||
|
|
||
| /// A `FunctionExecutor` implements `FunctionContext` for making host calls from a Wasmtime | ||
| /// runtime. The `FunctionExecutor` exists only for the lifetime of the call and borrows state from | ||
| /// a longer-living `FunctionExecutorState`. | ||
| pub struct FunctionExecutor<'a> { | ||
| compiler: &'a mut Compiler, | ||
| sandbox_store: &'a mut sandbox::Store<SupervisorFuncRef>, | ||
| heap: &'a mut FreeingBumpHeapAllocator, | ||
| memory: &'a mut [u8], | ||
| table: Option<&'a [VMCallerCheckedAnyfunc]>, | ||
| } | ||
|
|
||
| impl<'a> FunctionExecutor<'a> { | ||
| /// Construct a new `FunctionExecutor`. | ||
| /// | ||
| /// The vmctx MUST come from a call to a function in the "env" module. | ||
| /// The state MUST be looked up from the host state of the "env" module. | ||
| pub unsafe fn new(vmctx: *mut VMContext, state: &'a mut FunctionExecutorState) | ||
| -> Result<Self> | ||
| { | ||
| let memory = match (*vmctx).lookup_global_export("memory") { | ||
| Some(Export::Memory { definition, vmctx: _, memory: _ }) => | ||
| std::slice::from_raw_parts_mut( | ||
| (*definition).base, | ||
| (*definition).current_length, | ||
| ), | ||
| _ => return Err(Error::InvalidMemoryReference), | ||
| }; | ||
| let table = match (*vmctx).lookup_global_export("__indirect_function_table") { | ||
| Some(Export::Table { definition, vmctx: _, table: _ }) => | ||
| Some(std::slice::from_raw_parts( | ||
| (*definition).base as *const VMCallerCheckedAnyfunc, | ||
| (*definition).current_elements as usize, | ||
| )), | ||
| _ => None, | ||
| }; | ||
| Ok(FunctionExecutor { | ||
| compiler: &mut state.compiler, | ||
| sandbox_store: &mut state.sandbox_store, | ||
| heap: &mut state.heap, | ||
| memory, | ||
| table, | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| impl<'a> SandboxCapabilities for FunctionExecutor<'a> { | ||
| type SupervisorFuncRef = SupervisorFuncRef; | ||
|
|
||
| fn store(&self) -> &sandbox::Store<Self::SupervisorFuncRef> { | ||
| &self.sandbox_store | ||
| } | ||
|
|
||
| fn store_mut(&mut self) -> &mut sandbox::Store<Self::SupervisorFuncRef> { | ||
| &mut self.sandbox_store | ||
| } | ||
|
|
||
| fn allocate(&mut self, len: WordSize) -> Result<Pointer<u8>> { | ||
| self.heap.allocate(self.memory, len) | ||
| } | ||
|
|
||
| fn deallocate(&mut self, ptr: Pointer<u8>) -> Result<()> { | ||
| self.heap.deallocate(self.memory, ptr) | ||
| } | ||
|
|
||
| fn write_memory(&mut self, ptr: Pointer<u8>, data: &[u8]) -> Result<()> { | ||
| write_memory_from(self.memory, ptr, data) | ||
| } | ||
|
|
||
| fn read_memory(&self, ptr: Pointer<u8>, len: WordSize) -> Result<Vec<u8>> { | ||
| let mut output = vec![0; len as usize]; | ||
| read_memory_into(self.memory, ptr, output.as_mut())?; | ||
| Ok(output) | ||
| } | ||
|
|
||
| fn invoke( | ||
| &mut self, | ||
| _dispatch_thunk: &Self::SupervisorFuncRef, | ||
| _invoke_args_ptr: Pointer<u8>, | ||
| _invoke_args_len: WordSize, | ||
| _state: u32, | ||
| _func_idx: SupervisorFuncIndex, | ||
| ) -> Result<i64> | ||
| { | ||
| unimplemented!() | ||
| } | ||
| } | ||
|
|
||
| impl<'a> FunctionContext for FunctionExecutor<'a> { | ||
| fn read_memory_into(&self, address: Pointer<u8>, dest: &mut [u8]) -> WResult<()> { | ||
| read_memory_into(self.memory, address, dest).map_err(|e| e.to_string()) | ||
| } | ||
|
|
||
| fn write_memory(&mut self, address: Pointer<u8>, data: &[u8]) -> WResult<()> { | ||
| write_memory_from(self.memory, address, data).map_err(|e| e.to_string()) | ||
| } | ||
|
|
||
| fn allocate_memory(&mut self, size: WordSize) -> WResult<Pointer<u8>> { | ||
| self.heap.allocate(self.memory, size).map_err(|e| e.to_string()) | ||
| } | ||
|
|
||
| fn deallocate_memory(&mut self, ptr: Pointer<u8>) -> WResult<()> { | ||
| self.heap.deallocate(self.memory, ptr).map_err(|e| e.to_string()) | ||
| } | ||
|
|
||
| fn sandbox(&mut self) -> &mut dyn Sandbox { | ||
| self | ||
| } | ||
| } | ||
|
|
||
| impl<'a> Sandbox for FunctionExecutor<'a> { | ||
| fn memory_get( | ||
| &mut self, | ||
| memory_id: MemoryId, | ||
| offset: WordSize, | ||
| buf_ptr: Pointer<u8>, | ||
| buf_len: WordSize, | ||
| ) -> WResult<u32> | ||
| { | ||
| let sandboxed_memory = self.sandbox_store.memory(memory_id) | ||
| .map_err(|e| e.to_string())?; | ||
| sandboxed_memory.with_direct_access(|memory| { | ||
| let len = buf_len as usize; | ||
| let src_range = match checked_range(offset as usize, len, memory.len()) { | ||
| Some(range) => range, | ||
| None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), | ||
| }; | ||
| let dst_range = match checked_range(buf_ptr.into(), len, self.memory.len()) { | ||
| Some(range) => range, | ||
| None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), | ||
| }; | ||
| &mut self.memory[dst_range].copy_from_slice(&memory[src_range]); | ||
| Ok(sandbox_primitives::ERR_OK) | ||
| }) | ||
| } | ||
|
|
||
| fn memory_set( | ||
| &mut self, | ||
| memory_id: MemoryId, | ||
| offset: WordSize, | ||
| val_ptr: Pointer<u8>, | ||
| val_len: WordSize, | ||
| ) -> WResult<u32> | ||
| { | ||
| let sandboxed_memory = self.sandbox_store.memory(memory_id) | ||
| .map_err(|e| e.to_string())?; | ||
| sandboxed_memory.with_direct_access_mut(|memory| { | ||
| let len = val_len as usize; | ||
| let src_range = match checked_range(val_ptr.into(), len, self.memory.len()) { | ||
| Some(range) => range, | ||
| None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), | ||
| }; | ||
| let dst_range = match checked_range(offset as usize, len, memory.len()) { | ||
| Some(range) => range, | ||
| None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), | ||
| }; | ||
| &mut memory[dst_range].copy_from_slice(&self.memory[src_range]); | ||
| Ok(sandbox_primitives::ERR_OK) | ||
| }) | ||
| } | ||
|
|
||
| fn memory_teardown(&mut self, memory_id: MemoryId) | ||
| -> WResult<()> | ||
| { | ||
| self.sandbox_store.memory_teardown(memory_id).map_err(|e| e.to_string()) | ||
| } | ||
|
|
||
| fn memory_new(&mut self, initial: u32, maximum: MemoryId) -> WResult<u32> { | ||
| self.sandbox_store.new_memory(initial, maximum).map_err(|e| e.to_string()) | ||
| } | ||
|
|
||
| fn invoke( | ||
| &mut self, | ||
| instance_id: u32, | ||
| export_name: &str, | ||
| args: &[u8], | ||
| return_val: Pointer<u8>, | ||
| return_val_len: u32, | ||
| state: u32, | ||
| ) -> WResult<u32> { | ||
| trace!(target: "sr-sandbox", "invoke, instance_idx={}", instance_id); | ||
|
|
||
| // Deserialize arguments and convert them into wasmi types. | ||
| let args = Vec::<sandbox_primitives::TypedValue>::decode(&mut &args[..]) | ||
| .map_err(|_| "Can't decode serialized arguments for the invocation")? | ||
| .into_iter() | ||
| .map(Into::into) | ||
| .collect::<Vec<_>>(); | ||
|
|
||
| let instance = self.sandbox_store.instance(instance_id).map_err(|e| e.to_string())?; | ||
| let result = instance.invoke(export_name, &args, self, state); | ||
|
|
||
| match result { | ||
| Ok(None) => Ok(sandbox_primitives::ERR_OK), | ||
| Ok(Some(val)) => { | ||
| // Serialize return value and write it back into the memory. | ||
| sandbox_primitives::ReturnValue::Value(val.into()).using_encoded(|val| { | ||
| if val.len() > return_val_len as usize { | ||
| Err("Return value buffer is too small")?; | ||
| } | ||
| FunctionContext::write_memory(self, return_val, val)?; | ||
| Ok(sandbox_primitives::ERR_OK) | ||
| }) | ||
| } | ||
| Err(_) => Ok(sandbox_primitives::ERR_EXECUTION), | ||
| } | ||
| } | ||
|
|
||
| fn instance_teardown(&mut self, instance_id: u32) -> WResult<()> { | ||
| self.sandbox_store.instance_teardown(instance_id).map_err(|e| e.to_string()) | ||
| } | ||
|
|
||
| fn instance_new(&mut self, dispatch_thunk_id: u32, wasm: &[u8], raw_env_def: &[u8], state: u32) | ||
| -> WResult<u32> | ||
| { | ||
| // Extract a dispatch thunk from instance's table by the specified index. | ||
| let dispatch_thunk = { | ||
| let table = self.table.as_ref() | ||
| .ok_or_else(|| "Runtime doesn't have a table; sandbox is unavailable")?; | ||
| let func_ref = table.get(dispatch_thunk_id as usize) | ||
| .ok_or_else(|| "dispatch_thunk_idx is out of the table bounds")?; | ||
| SupervisorFuncRef(func_ref) | ||
| }; | ||
|
|
||
| let instance_idx_or_err_code = | ||
| match sandbox::instantiate(self, dispatch_thunk, wasm, raw_env_def, state) { | ||
| 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) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.