diff --git a/client/executor/wasmtime/build.rs b/client/executor/wasmtime/build.rs new file mode 100644 index 0000000000000..6ab581c9c2685 --- /dev/null +++ b/client/executor/wasmtime/build.rs @@ -0,0 +1,25 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use std::env; + +fn main() { + if let Ok(profile) = env::var("PROFILE") { + println!("cargo:rustc-cfg=build_type=\"{}\"", profile); + } +} diff --git a/client/executor/wasmtime/src/host.rs b/client/executor/wasmtime/src/host.rs index fcb4c4cae3b8a..39ee9ced80af7 100644 --- a/client/executor/wasmtime/src/host.rs +++ b/client/executor/wasmtime/src/host.rs @@ -19,7 +19,7 @@ //! This module defines `HostState` and `HostContext` structs which provide logic and state //! required for execution of host. -use crate::{instance_wrapper::InstanceWrapper, runtime::StoreData}; +use crate::{runtime::StoreData, util}; use codec::{Decode, Encode}; use log::trace; use sc_allocator::FreeingBumpHeapAllocator; @@ -30,101 +30,104 @@ use sc_executor_common::{ }; use sp_core::sandbox as sandbox_primitives; use sp_wasm_interface::{FunctionContext, MemoryId, Pointer, Sandbox, WordSize}; -use std::{cell::RefCell, rc::Rc}; use wasmtime::{Caller, Func, Val}; +// The sandbox store is inside of a Option>> so that we can temporarily borrow it. +struct SandboxStore(Option>>); + +// There are a bunch of `Rc`s within the sandbox store, however we only manipulate +// those within one thread so this should be safe. +unsafe impl Send for SandboxStore {} + /// 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: Rc>>, - allocator: RefCell, - instance: Rc, + sandbox_store: SandboxStore, + allocator: FreeingBumpHeapAllocator, } impl HostState { /// Constructs a new `HostState`. - pub fn new(allocator: FreeingBumpHeapAllocator, instance: Rc) -> Self { + pub fn new(allocator: FreeingBumpHeapAllocator) -> Self { HostState { - sandbox_store: Rc::new(RefCell::new(sandbox::Store::new( + sandbox_store: SandboxStore(Some(Box::new(sandbox::Store::new( sandbox::SandboxBackend::TryWasmer, - ))), - allocator: RefCell::new(allocator), - instance, + )))), + allocator, } } - - /// Materialize `HostContext` that can be used to invoke a substrate host `dyn Function`. - pub(crate) fn materialize<'a, 'b, 'c>( - &'a self, - caller: &'b mut Caller<'c, StoreData>, - ) -> HostContext<'a, 'b, 'c> { - HostContext { host_state: self, caller } - } } /// A `HostContext` implements `FunctionContext` for making host calls from a Wasmtime /// runtime. The `HostContext` exists only for the lifetime of the call and borrows state from /// a longer-living `HostState`. -pub(crate) struct HostContext<'a, 'b, 'c> { - host_state: &'a HostState, - caller: &'b mut Caller<'c, StoreData>, +pub(crate) struct HostContext<'a, 'b> { + pub(crate) caller: &'a mut Caller<'b, StoreData>, } -impl<'a, 'b, 'c> std::ops::Deref for HostContext<'a, 'b, 'c> { - type Target = HostState; - fn deref(&self) -> &HostState { - self.host_state +impl<'a, 'b> HostContext<'a, 'b> { + fn host_state(&self) -> &HostState { + self.caller + .data() + .host_state() + .expect("host state is not empty when calling a function in wasm; qed") + } + + fn host_state_mut(&mut self) -> &mut HostState { + self.caller + .data_mut() + .host_state_mut() + .expect("host state is not empty when calling a function in wasm; qed") + } + + fn sandbox_store(&self) -> &sandbox::Store { + self.host_state() + .sandbox_store + .0 + .as_ref() + .expect("sandbox store is only empty when temporarily borrowed") + } + + fn sandbox_store_mut(&mut self) -> &mut sandbox::Store { + self.host_state_mut() + .sandbox_store + .0 + .as_mut() + .expect("sandbox store is only empty when temporarily borrowed") } } -impl<'a, 'b, 'c> sp_wasm_interface::FunctionContext for HostContext<'a, 'b, 'c> { +impl<'a, 'b> sp_wasm_interface::FunctionContext for HostContext<'a, 'b> { fn read_memory_into( &self, address: Pointer, dest: &mut [u8], ) -> sp_wasm_interface::Result<()> { - let ctx = &self.caller; - self.host_state - .instance - .read_memory_into(ctx, address, dest) - .map_err(|e| e.to_string()) + util::read_memory_into(&self.caller, address, dest).map_err(|e| e.to_string()) } fn write_memory(&mut self, address: Pointer, data: &[u8]) -> sp_wasm_interface::Result<()> { - let ctx = &mut self.caller; - self.host_state - .instance - .write_memory_from(ctx, address, data) - .map_err(|e| e.to_string()) + util::write_memory_from(&mut self.caller, address, data).map_err(|e| e.to_string()) } fn allocate_memory(&mut self, size: WordSize) -> sp_wasm_interface::Result> { - let ctx = &mut self.caller; - let allocator = &self.host_state.allocator; - - self.host_state - .instance - .allocate(ctx, &mut *allocator.borrow_mut(), size) + let memory = self.caller.data().memory(); + let (memory, data) = memory.data_and_store_mut(&mut self.caller); + data.host_state_mut() + .expect("host state is not empty when calling a function in wasm; qed") + .allocator + .allocate(memory, size) .map_err(|e| e.to_string()) } fn deallocate_memory(&mut self, ptr: Pointer) -> sp_wasm_interface::Result<()> { - let ctx = &mut self.caller; - let allocator = &self.host_state.allocator; - - self.host_state - .instance - .deallocate(ctx, &mut *allocator.borrow_mut(), ptr) + let memory = self.caller.data().memory(); + let (memory, data) = memory.data_and_store_mut(&mut self.caller); + data.host_state_mut() + .expect("host state is not empty when calling a function in wasm; qed") + .allocator + .deallocate(memory, ptr) .map_err(|e| e.to_string()) } @@ -133,7 +136,7 @@ impl<'a, 'b, 'c> sp_wasm_interface::FunctionContext for HostContext<'a, 'b, 'c> } } -impl<'a, 'b, 'c> Sandbox for HostContext<'a, 'b, 'c> { +impl<'a, 'b> Sandbox for HostContext<'a, 'b> { fn memory_get( &mut self, memory_id: MemoryId, @@ -141,8 +144,7 @@ impl<'a, 'b, 'c> Sandbox for HostContext<'a, 'b, 'c> { 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())?; + let sandboxed_memory = self.sandbox_store().memory(memory_id).map_err(|e| e.to_string())?; let len = buf_len as usize; @@ -151,8 +153,7 @@ impl<'a, 'b, 'c> Sandbox for HostContext<'a, 'b, 'c> { Ok(buffer) => buffer, }; - let instance = self.instance.clone(); - if let Err(_) = instance.write_memory_from(&mut self.caller, buf_ptr, &buffer) { + if util::write_memory_from(&mut self.caller, buf_ptr, &buffer).is_err() { return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS) } @@ -166,17 +167,16 @@ impl<'a, 'b, 'c> Sandbox for HostContext<'a, 'b, 'c> { 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())?; + let sandboxed_memory = self.sandbox_store().memory(memory_id).map_err(|e| e.to_string())?; let len = val_len as usize; - let buffer = match self.instance.read_memory(&self.caller, val_ptr, len) { + let buffer = match util::read_memory(&self.caller, val_ptr, len) { Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), Ok(buffer) => buffer, }; - if let Err(_) = sandboxed_memory.write_from(Pointer::new(offset as u32), &buffer) { + if sandboxed_memory.write_from(Pointer::new(offset as u32), &buffer).is_err() { return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS) } @@ -184,17 +184,11 @@ impl<'a, 'b, 'c> Sandbox for HostContext<'a, 'b, 'c> { } 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()) + self.sandbox_store_mut().memory_teardown(memory_id).map_err(|e| e.to_string()) } fn memory_new(&mut self, initial: u32, maximum: u32) -> sp_wasm_interface::Result { - self.sandbox_store - .borrow_mut() - .new_memory(initial, maximum) - .map_err(|e| e.to_string()) + self.sandbox_store_mut().new_memory(initial, maximum).map_err(|e| e.to_string()) } fn invoke( @@ -215,14 +209,10 @@ impl<'a, 'b, 'c> Sandbox for HostContext<'a, 'b, 'c> { .map(Into::into) .collect::>(); - let instance = - self.sandbox_store.borrow().instance(instance_id).map_err(|e| e.to_string())?; + let instance = self.sandbox_store().instance(instance_id).map_err(|e| e.to_string())?; - let dispatch_thunk = self - .sandbox_store - .borrow() - .dispatch_thunk(instance_id) - .map_err(|e| e.to_string())?; + let dispatch_thunk = + self.sandbox_store().dispatch_thunk(instance_id).map_err(|e| e.to_string())?; let result = instance.invoke( export_name, @@ -249,8 +239,7 @@ impl<'a, 'b, 'c> Sandbox for HostContext<'a, 'b, 'c> { } fn instance_teardown(&mut self, instance_id: u32) -> sp_wasm_interface::Result<()> { - self.sandbox_store - .borrow_mut() + self.sandbox_store_mut() .instance_teardown(instance_id) .map_err(|e| e.to_string()) } @@ -264,14 +253,12 @@ impl<'a, 'b, 'c> Sandbox for HostContext<'a, 'b, 'c> { ) -> sp_wasm_interface::Result { // Extract a dispatch thunk from the instance's table by the specified index. let dispatch_thunk = { - let ctx = &mut self.caller; - let table_item = self - .host_state - .instance + let table = self + .caller + .data() .table() - .as_ref() - .ok_or_else(|| "Runtime doesn't have a table; sandbox is unavailable")? - .get(ctx, dispatch_thunk_id); + .ok_or_else(|| "Runtime doesn't have a table; sandbox is unavailable")?; + let table_item = table.get(&mut self.caller, dispatch_thunk_id); table_item .ok_or_else(|| "dispatch_thunk_id is out of bounds")? @@ -281,25 +268,39 @@ impl<'a, 'b, 'c> Sandbox for HostContext<'a, 'b, 'c> { .clone() }; - 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 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 store = self.sandbox_store.clone(); - let store = &mut store.borrow_mut(); - let result = store - .instantiate( + let mut store = self + .host_state_mut() + .sandbox_store + .0 + .take() + .expect("sandbox store is only empty when borrowed"); + + // Catch any potential panics so that we can properly restore the sandbox store + // which we've destructively borrowed. + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + store.instantiate( wasm, guest_env, state, &mut SandboxContext { host_context: self, dispatch_thunk: dispatch_thunk.clone() }, ) - .map(|i| i.register(store, dispatch_thunk)); + })); + + self.host_state_mut().sandbox_store.0 = Some(store); + + let result = match result { + Ok(result) => result, + Err(error) => std::panic::resume_unwind(error), + }; let instance_idx_or_err_code = match result { - Ok(instance_idx) => instance_idx, + Ok(instance) => instance.register(&mut self.sandbox_store_mut(), dispatch_thunk), Err(sandbox::InstantiationError::StartTrapped) => sandbox_primitives::ERR_EXECUTION, Err(_) => sandbox_primitives::ERR_MODULE, }; @@ -312,20 +313,19 @@ impl<'a, 'b, 'c> Sandbox for HostContext<'a, 'b, 'c> { instance_idx: u32, name: &str, ) -> sp_wasm_interface::Result> { - self.sandbox_store - .borrow() + self.sandbox_store() .instance(instance_idx) .map(|i| i.get_global_val(name)) .map_err(|e| e.to_string()) } } -struct SandboxContext<'a, 'b, 'c, 'd> { - host_context: &'a mut HostContext<'b, 'c, 'd>, +struct SandboxContext<'a, 'b, 'c> { + host_context: &'a mut HostContext<'b, 'c>, dispatch_thunk: Func, } -impl<'a, 'b, 'c, 'd> sandbox::SandboxContext for SandboxContext<'a, 'b, 'c, 'd> { +impl<'a, 'b, 'c> sandbox::SandboxContext for SandboxContext<'a, 'b, 'c> { fn invoke( &mut self, invoke_args_ptr: Pointer, diff --git a/client/executor/wasmtime/src/imports.rs b/client/executor/wasmtime/src/imports.rs index a00ab14263e7f..57ce48f537e94 100644 --- a/client/executor/wasmtime/src/imports.rs +++ b/client/executor/wasmtime/src/imports.rs @@ -17,6 +17,7 @@ // along with this program. If not, see . use crate::{ + host::HostContext, runtime::{Store, StoreData}, util, }; @@ -191,19 +192,7 @@ fn call_static<'a>( mut caller: Caller<'a, StoreData>, ) -> Result<(), wasmtime::Trap> { let unwind_result = { - let host_state = caller - .data() - .host_state() - .expect( - "host functions can be called only from wasm instance; - wasm instance is always called initializing context; - therefore host_ctx cannot be None; - qed - ", - ) - .clone(); - - let mut host_ctx = host_state.materialize(&mut caller); + let mut host_ctx = HostContext { caller: &mut caller }; // `from_wasmtime_val` panics if it encounters a value that doesn't fit into the values // available in substrate. diff --git a/client/executor/wasmtime/src/instance_wrapper.rs b/client/executor/wasmtime/src/instance_wrapper.rs index 1d40563d0a9ff..e9b18d8c2b89d 100644 --- a/client/executor/wasmtime/src/instance_wrapper.rs +++ b/client/executor/wasmtime/src/instance_wrapper.rs @@ -19,15 +19,12 @@ //! Defines data and logic needed for interaction with an WebAssembly instance of a substrate //! runtime module. -use crate::imports::Imports; - +use crate::runtime::{Store, StoreData}; use sc_executor_common::{ error::{Error, Result}, - util::checked_range, wasm_runtime::InvokeMethod, }; -use sp_wasm_interface::{Pointer, Value, WordSize}; -use std::marker; +use sp_wasm_interface::{Function, Pointer, Value, WordSize}; use wasmtime::{ AsContext, AsContextMut, Extern, Func, Global, Instance, Memory, Module, Table, Val, }; @@ -107,18 +104,8 @@ impl EntryPoint { /// routines. pub struct InstanceWrapper { instance: Instance, - - // The memory instance of the `instance`. - // - // 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, - - /// Indirect functions table of the module - table: Option, - - // Make this struct explicitly !Send & !Sync. - _not_send_nor_sync: marker::PhantomData<*const ()>, + store: Store, } fn extern_memory(extern_: &Extern) -> Option<&Memory> { @@ -153,11 +140,36 @@ impl InstanceWrapper { /// Create a new instance wrapper from the given wasm module. pub fn new( module: &Module, - imports: &Imports, + host_functions: &[&'static dyn Function], heap_pages: u64, - mut ctx: impl AsContextMut, + allow_missing_func_imports: bool, + max_memory_size: Option, ) -> Result { - let instance = Instance::new(&mut ctx, module, &imports.externs) + let limits = if let Some(max_memory_size) = max_memory_size { + wasmtime::StoreLimitsBuilder::new().memory_size(max_memory_size).build() + } else { + Default::default() + }; + + let mut store = Store::new( + module.engine(), + StoreData { limits, host_state: None, memory: None, table: None }, + ); + if max_memory_size.is_some() { + store.limiter(|s| &mut s.limits); + } + + // Scan all imports, find the matching host functions, and create stubs that adapt arguments + // and results. + let imports = crate::imports::resolve_imports( + &mut store, + module, + host_functions, + heap_pages, + allow_missing_func_imports, + )?; + + let instance = Instance::new(&mut store, module, &imports.externs) .map_err(|e| Error::from(format!("cannot instantiate: {}", e)))?; let memory = match imports.memory_import_index { @@ -165,55 +177,56 @@ impl InstanceWrapper { .expect("only memory can be at the `memory_idx`; qed") .clone(), None => { - let memory = get_linear_memory(&instance, &mut ctx)?; - if !memory.grow(&mut ctx, heap_pages).is_ok() { + let memory = get_linear_memory(&instance, &mut store)?; + if !memory.grow(&mut store, heap_pages).is_ok() { return Err("failed top increase the linear memory size".into()) } memory }, }; - let table = get_table(&instance, ctx); + let table = get_table(&instance, &mut store); + + store.data_mut().memory = Some(memory); + store.data_mut().table = table; - Ok(Self { table, instance, memory, _not_send_nor_sync: marker::PhantomData }) + Ok(Self { instance, memory, store }) } /// 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, - method: InvokeMethod, - mut ctx: impl AsContextMut, - ) -> Result { + pub fn resolve_entrypoint(&mut self, method: InvokeMethod) -> Result { Ok(match method { InvokeMethod::Export(method) => { // Resolve the requested method and verify that it has a proper signature. - let export = self.instance.get_export(&mut ctx, method).ok_or_else(|| { - Error::from(format!("Exported method {} is not found", method)) - })?; + let export = + self.instance.get_export(&mut self.store, method).ok_or_else(|| { + Error::from(format!("Exported method {} is not found", method)) + })?; let func = extern_func(&export) .ok_or_else(|| Error::from(format!("Export {} is not a function", method)))? .clone(); - EntryPoint::direct(func, ctx).map_err(|_| { + EntryPoint::direct(func, &self.store).map_err(|_| { Error::from(format!("Exported function '{}' has invalid signature.", method)) })? }, InvokeMethod::Table(func_ref) => { let table = self .instance - .get_table(&mut ctx, "__indirect_function_table") + .get_table(&mut self.store, "__indirect_function_table") .ok_or(Error::NoTable)?; - let val = - table.get(&mut ctx, func_ref).ok_or(Error::NoTableEntryWithIndex(func_ref))?; + let val = table + .get(&mut self.store, func_ref) + .ok_or(Error::NoTableEntryWithIndex(func_ref))?; let func = val .funcref() .ok_or(Error::TableElementIsNotAFunction(func_ref))? .ok_or(Error::FunctionRefIsNull(func_ref))? .clone(); - EntryPoint::direct(func, ctx).map_err(|_| { + EntryPoint::direct(func, &self.store).map_err(|_| { Error::from(format!( "Function @{} in exported table has invalid signature for direct call.", func_ref, @@ -223,10 +236,10 @@ impl InstanceWrapper { InvokeMethod::TableWithWrapper { dispatcher_ref, func } => { let table = self .instance - .get_table(&mut ctx, "__indirect_function_table") + .get_table(&mut self.store, "__indirect_function_table") .ok_or(Error::NoTable)?; let val = table - .get(&mut ctx, dispatcher_ref) + .get(&mut self.store, dispatcher_ref) .ok_or(Error::NoTableEntryWithIndex(dispatcher_ref))?; let dispatcher = val .funcref() @@ -234,7 +247,7 @@ impl InstanceWrapper { .ok_or(Error::FunctionRefIsNull(dispatcher_ref))? .clone(); - EntryPoint::wrapped(dispatcher, func, ctx).map_err(|_| { + EntryPoint::wrapped(dispatcher, func, &self.store).map_err(|_| { Error::from(format!( "Function @{} in exported table has invalid signature for wrapped call.", dispatcher_ref, @@ -244,25 +257,20 @@ impl InstanceWrapper { }) } - /// Returns an indirect function table of this instance. - pub fn table(&self) -> Option<&Table> { - self.table.as_ref() - } - /// 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, mut ctx: impl AsContextMut) -> Result { + pub fn extract_heap_base(&mut self) -> Result { let heap_base_export = self .instance - .get_export(&mut ctx, "__heap_base") + .get_export(&mut self.store, "__heap_base") .ok_or_else(|| Error::from("__heap_base is not found"))?; let heap_base_global = extern_global(&heap_base_export) .ok_or_else(|| Error::from("__heap_base is not a global"))?; let heap_base = heap_base_global - .get(&mut ctx) + .get(&mut self.store) .i32() .ok_or_else(|| Error::from("__heap_base is not a i32"))?; @@ -270,15 +278,15 @@ impl InstanceWrapper { } /// Get the value from a global with the given `name`. - pub fn get_global_val(&self, mut ctx: impl AsContextMut, name: &str) -> Result> { - let global = match self.instance.get_export(&mut ctx, name) { + pub fn get_global_val(&mut self, name: &str) -> Result> { + let global = match self.instance.get_export(&mut self.store, name) { Some(global) => global, None => return Ok(None), }; let global = extern_global(&global).ok_or_else(|| format!("`{}` is not a global", name))?; - match global.get(ctx) { + match global.get(&mut self.store) { Val::I32(val) => Ok(Some(Value::I32(val))), Val::I64(val) => Ok(Some(Value::I64(val))), Val::F32(val) => Ok(Some(Value::F32(val))), @@ -288,8 +296,8 @@ impl InstanceWrapper { } /// Get a global with the given `name`. - pub fn get_global(&self, ctx: impl AsContextMut, name: &str) -> Option { - self.instance.get_global(ctx, name) + pub fn get_global(&mut self, name: &str) -> Option { + self.instance.get_global(&mut self.store, name) } } @@ -307,7 +315,7 @@ fn get_linear_memory(instance: &Instance, ctx: impl AsContextMut) -> Result Option
{ +fn get_table(instance: &Instance, ctx: &mut Store) -> Option
{ instance .get_export(ctx, "__indirect_function_table") .as_ref() @@ -317,97 +325,16 @@ fn get_table(instance: &Instance, ctx: impl AsContextMut) -> Option
{ /// Functions related to memory. impl InstanceWrapper { - /// Read data from a slice of memory into a newly allocated buffer. - /// - /// Returns an error if the read would go out of the memory bounds. - pub fn read_memory( - &self, - ctx: impl AsContext, - source_addr: Pointer, - size: usize, - ) -> Result> { - let range = checked_range(source_addr.into(), size, self.memory.data_size(&ctx)) - .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; - - let mut buffer = vec![0; range.len()]; - self.read_memory_into(ctx, source_addr, &mut buffer)?; - - Ok(buffer) - } - - /// Read data from the instance memory into a slice. - /// - /// Returns an error if the read would go out of the memory bounds. - pub fn read_memory_into( - &self, - ctx: impl AsContext, - address: Pointer, - dest: &mut [u8], - ) -> Result<()> { - let memory = self.memory.data(ctx.as_context()); - - 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 the instance memory from a slice. - /// - /// Returns an error if the write would go out of the memory bounds. - pub fn write_memory_from( - &self, - mut ctx: impl AsContextMut, - address: Pointer, - data: &[u8], - ) -> Result<()> { - let memory = self.memory.data_mut(ctx.as_context_mut()); - - let range = checked_range(address.into(), data.len(), memory.len()) - .ok_or_else(|| Error::Other("memory write is out of bounds".into()))?; - 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, - mut ctx: impl AsContextMut, - allocator: &mut sc_allocator::FreeingBumpHeapAllocator, - size: WordSize, - ) -> Result> { - let memory = self.memory.data_mut(ctx.as_context_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, - mut ctx: impl AsContextMut, - allocator: &mut sc_allocator::FreeingBumpHeapAllocator, - ptr: Pointer, - ) -> Result<()> { - let memory = self.memory.data_mut(ctx.as_context_mut()); - - allocator.deallocate(memory, ptr).map_err(Into::into) - } - /// Returns the pointer to the first byte of the linear memory for this instance. - pub fn base_ptr(&self, ctx: impl AsContext) -> *const u8 { - self.memory.data_ptr(ctx) + pub fn base_ptr(&self) -> *const u8 { + self.memory.data_ptr(&self.store) } /// If possible removes physical backing from the allocated linear memory which /// leads to returning the memory back to the system; this also zeroes the memory /// as a side-effect. - pub fn decommit(&self, mut ctx: impl AsContextMut) { - if self.memory.data_size(&ctx) == 0 { + pub fn decommit(&mut self) { + if self.memory.data_size(&self.store) == 0 { return } @@ -416,8 +343,8 @@ impl InstanceWrapper { use std::sync::Once; unsafe { - let ptr = self.memory.data_ptr(&ctx); - let len = self.memory.data_size(&ctx); + let ptr = self.memory.data_ptr(&self.store); + let len = self.memory.data_size(&self.store); // Linux handles MADV_DONTNEED reliably. The result is that the given area // is unmapped and will be zeroed on the next pagefault. @@ -438,6 +365,14 @@ impl InstanceWrapper { // If we're on an unsupported OS or the memory couldn't have been // decommited for some reason then just manually zero it out. - self.memory.data_mut(ctx.as_context_mut()).fill(0); + self.memory.data_mut(self.store.as_context_mut()).fill(0); + } + + pub(crate) fn store(&self) -> &Store { + &self.store + } + + pub(crate) fn store_mut(&mut self) -> &mut Store { + &mut self.store } } diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index 7808ac7ce547d..606401132e9e9 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -20,7 +20,6 @@ use crate::{ host::HostState, - imports::{resolve_imports, Imports}, instance_wrapper::{EntryPoint, InstanceWrapper}, util, }; @@ -37,75 +36,98 @@ use sp_runtime_interface::unpack_ptr_and_len; use sp_wasm_interface::{Function, Pointer, Value, WordSize}; use std::{ path::{Path, PathBuf}, - rc::Rc, sync::{ atomic::{AtomicBool, Ordering}, Arc, }, }; -use wasmtime::{AsContext, AsContextMut, Engine, StoreLimits}; +use wasmtime::{Engine, Memory, StoreLimits, Table}; pub(crate) struct StoreData { - /// The limits we aply to the store. We need to store it here to return a reference to this + /// The limits we apply to the store. We need to store it here to return a reference to this /// object when we have the limits enabled. - limits: StoreLimits, + pub(crate) limits: StoreLimits, /// This will only be set when we call into the runtime. - host_state: Option>, + pub(crate) host_state: Option, + /// This will be always set once the store is initialized. + pub(crate) memory: Option, + /// This will be set only if the runtime actually contains a table. + pub(crate) table: Option
, } impl StoreData { /// Returns a reference to the host state. - pub fn host_state(&self) -> Option<&Rc> { + pub fn host_state(&self) -> Option<&HostState> { self.host_state.as_ref() } + + /// Returns a mutable reference to the host state. + pub fn host_state_mut(&mut self) -> Option<&mut HostState> { + self.host_state.as_mut() + } + + /// Returns the host memory. + pub fn memory(&self) -> Memory { + self.memory.expect("memory is always set; qed") + } + + /// Returns the host table. + pub fn table(&self) -> Option
{ + self.table + } } pub(crate) type Store = wasmtime::Store; enum Strategy { FastInstanceReuse { - instance_wrapper: Rc, + instance_wrapper: InstanceWrapper, globals_snapshot: GlobalsSnapshot, data_segments_snapshot: Arc, heap_base: u32, - store: Store, }, RecreateInstance(InstanceCreator), } struct InstanceCreator { - store: Store, module: Arc, - imports: Arc, + host_functions: Vec<&'static dyn Function>, heap_pages: u64, + allow_missing_func_imports: bool, + max_memory_size: Option, } impl InstanceCreator { fn instantiate(&mut self) -> Result { - InstanceWrapper::new(&*self.module, &*self.imports, self.heap_pages, &mut self.store) + InstanceWrapper::new( + &*self.module, + &self.host_functions, + self.heap_pages, + self.allow_missing_func_imports, + self.max_memory_size, + ) } } -struct InstanceGlobals<'a, C> { - ctx: &'a mut C, - instance: &'a InstanceWrapper, +struct InstanceGlobals<'a> { + instance: &'a mut InstanceWrapper, } -impl<'a, C: AsContextMut> runtime_blob::InstanceGlobals for InstanceGlobals<'a, C> { +impl<'a> runtime_blob::InstanceGlobals for InstanceGlobals<'a> { type Global = wasmtime::Global; fn get_global(&mut self, export_name: &str) -> Self::Global { self.instance - .get_global(&mut self.ctx, export_name) + .get_global(export_name) .expect("get_global is guaranteed to be called with an export name of a global; qed") } fn get_global_value(&mut self, global: &Self::Global) -> Value { - util::from_wasmtime_val(global.get(&mut self.ctx)) + util::from_wasmtime_val(global.get(&mut self.instance.store_mut())) } fn set_global_value(&mut self, global: &Self::Global, value: Value) { - global.set(&mut self.ctx, util::into_wasmtime_val(value)).expect( + global.set(&mut self.instance.store_mut(), util::into_wasmtime_val(value)).expect( "the value is guaranteed to be of the same value; the global is guaranteed to be mutable; qed", ); } @@ -124,50 +146,19 @@ pub struct WasmtimeRuntime { snapshot_data: Option, config: Config, host_functions: Vec<&'static dyn Function>, - engine: Engine, -} - -impl WasmtimeRuntime { - /// Creates the store respecting the set limits. - fn new_store(&self) -> Store { - let limits = if let Some(max_memory_size) = self.config.max_memory_size { - wasmtime::StoreLimitsBuilder::new().memory_size(max_memory_size).build() - } else { - Default::default() - }; - - let mut store = Store::new(&self.engine, StoreData { limits, host_state: None }); - - if self.config.max_memory_size.is_some() { - store.limiter(|s| &mut s.limits); - } - - store - } } impl WasmModule for WasmtimeRuntime { fn new_instance(&self) -> Result> { - let mut store = self.new_store(); - - // Scan all imports, find the matching host functions, and create stubs that adapt arguments - // and results. - // - // NOTE: Attentive reader may notice that this could've been moved in `WasmModule` creation. - // However, I am not sure if that's a good idea since it would be pushing our luck - // further by assuming that `Store` not only `Send` but also `Sync`. - let imports = resolve_imports( - &mut store, - &self.module, - &self.host_functions, - self.config.heap_pages, - self.config.allow_missing_func_imports, - )?; - let strategy = if let Some(ref snapshot_data) = self.snapshot_data { - let instance_wrapper = - InstanceWrapper::new(&self.module, &imports, self.config.heap_pages, &mut store)?; - let heap_base = instance_wrapper.extract_heap_base(&mut store)?; + let mut instance_wrapper = InstanceWrapper::new( + &self.module, + &self.host_functions, + self.config.heap_pages, + self.config.allow_missing_func_imports, + self.config.max_memory_size, + )?; + let heap_base = instance_wrapper.extract_heap_base()?; // This function panics if the instance was created from a runtime blob different from // which the mutable globals were collected. Here, it is easy to see that there is only @@ -175,22 +166,22 @@ impl WasmModule for WasmtimeRuntime { // instance and collecting the mutable globals. let globals_snapshot = GlobalsSnapshot::take( &snapshot_data.mutable_globals, - &mut InstanceGlobals { ctx: &mut store, instance: &instance_wrapper }, + &mut InstanceGlobals { instance: &mut instance_wrapper }, ); Strategy::FastInstanceReuse { - instance_wrapper: Rc::new(instance_wrapper), + instance_wrapper, globals_snapshot, data_segments_snapshot: snapshot_data.data_segments_snapshot.clone(), heap_base, - store, } } else { Strategy::RecreateInstance(InstanceCreator { - imports: Arc::new(imports), module: self.module.clone(), - store, + host_functions: self.host_functions.clone(), heap_pages: self.config.heap_pages, + allow_missing_func_imports: self.config.allow_missing_func_imports, + max_memory_size: self.config.max_memory_size, }) }; @@ -204,68 +195,52 @@ pub struct WasmtimeInstance { strategy: Strategy, } -// This is safe because `WasmtimeInstance` does not leak reference to `self.imports` -// and all imports don't reference anything, other than host functions and memory -unsafe impl Send for WasmtimeInstance {} - impl WasmInstance for WasmtimeInstance { fn call(&mut self, method: InvokeMethod, data: &[u8]) -> Result> { match &mut self.strategy { Strategy::FastInstanceReuse { - instance_wrapper, + ref mut instance_wrapper, globals_snapshot, data_segments_snapshot, heap_base, - ref mut store, } => { - let entrypoint = instance_wrapper.resolve_entrypoint(method, &mut *store)?; + let entrypoint = instance_wrapper.resolve_entrypoint(method)?; data_segments_snapshot.apply(|offset, contents| { - instance_wrapper.write_memory_from(&mut *store, Pointer::new(offset), contents) + util::write_memory_from( + instance_wrapper.store_mut(), + Pointer::new(offset), + contents, + ) })?; - globals_snapshot - .apply(&mut InstanceGlobals { ctx: &mut *store, instance: &*instance_wrapper }); + globals_snapshot.apply(&mut InstanceGlobals { instance: instance_wrapper }); let allocator = FreeingBumpHeapAllocator::new(*heap_base); - let result = perform_call( - &mut *store, - data, - instance_wrapper.clone(), - entrypoint, - allocator, - ); + let result = perform_call(data, instance_wrapper, entrypoint, allocator); // Signal to the OS that we are done with the linear memory and that it can be // reclaimed. - instance_wrapper.decommit(store); + instance_wrapper.decommit(); result }, Strategy::RecreateInstance(ref mut instance_creator) => { - let instance_wrapper = instance_creator.instantiate()?; - let heap_base = instance_wrapper.extract_heap_base(&mut instance_creator.store)?; - let entrypoint = - instance_wrapper.resolve_entrypoint(method, &mut instance_creator.store)?; + let mut instance_wrapper = instance_creator.instantiate()?; + let heap_base = instance_wrapper.extract_heap_base()?; + let entrypoint = instance_wrapper.resolve_entrypoint(method)?; let allocator = FreeingBumpHeapAllocator::new(heap_base); - perform_call( - &mut instance_creator.store, - data, - Rc::new(instance_wrapper), - entrypoint, - allocator, - ) + perform_call(data, &mut instance_wrapper, entrypoint, allocator) }, } } fn get_global_const(&mut self, name: &str) -> Result> { match &mut self.strategy { - Strategy::FastInstanceReuse { instance_wrapper, ref mut store, .. } => - instance_wrapper.get_global_val(&mut *store, name), - Strategy::RecreateInstance(ref mut instance_creator) => instance_creator - .instantiate()? - .get_global_val(&mut instance_creator.store, name), + Strategy::FastInstanceReuse { instance_wrapper, .. } => + instance_wrapper.get_global_val(name), + Strategy::RecreateInstance(ref mut instance_creator) => + instance_creator.instantiate()?.get_global_val(name), } } @@ -276,8 +251,8 @@ impl WasmInstance for WasmtimeInstance { // associated with it. None }, - Strategy::FastInstanceReuse { instance_wrapper, store, .. } => - Some(instance_wrapper.base_ptr(&store)), + Strategy::FastInstanceReuse { instance_wrapper, .. } => + Some(instance_wrapper.base_ptr()), } } } @@ -591,7 +566,7 @@ unsafe fn do_create_runtime( }, }; - Ok(WasmtimeRuntime { module: Arc::new(module), snapshot_data, config, host_functions, engine }) + Ok(WasmtimeRuntime { module: Arc::new(module), snapshot_data, config, host_functions }) } fn instrument( @@ -627,50 +602,51 @@ pub fn prepare_runtime_artifact( } fn perform_call( - mut ctx: impl AsContextMut, data: &[u8], - instance_wrapper: Rc, + instance_wrapper: &mut InstanceWrapper, entrypoint: EntryPoint, mut allocator: FreeingBumpHeapAllocator, ) -> Result> { - let (data_ptr, data_len) = - inject_input_data(&mut ctx, &instance_wrapper, &mut allocator, data)?; + let (data_ptr, data_len) = inject_input_data(instance_wrapper, &mut allocator, data)?; - let host_state = HostState::new(allocator, instance_wrapper.clone()); + let host_state = HostState::new(allocator); // Set the host state before calling into wasm. - ctx.as_context_mut().data_mut().host_state = Some(Rc::new(host_state)); + instance_wrapper.store_mut().data_mut().host_state = Some(host_state); - let ret = entrypoint.call(&mut ctx, data_ptr, data_len).map(unpack_ptr_and_len); + let ret = entrypoint + .call(instance_wrapper.store_mut(), data_ptr, data_len) + .map(unpack_ptr_and_len); // Reset the host state - ctx.as_context_mut().data_mut().host_state = None; + instance_wrapper.store_mut().data_mut().host_state = None; let (output_ptr, output_len) = ret?; - let output = extract_output_data(ctx, &instance_wrapper, output_ptr, output_len)?; + let output = extract_output_data(instance_wrapper, output_ptr, output_len)?; Ok(output) } fn inject_input_data( - mut ctx: impl AsContextMut, - instance: &InstanceWrapper, + instance: &mut InstanceWrapper, allocator: &mut FreeingBumpHeapAllocator, data: &[u8], ) -> Result<(Pointer, WordSize)> { + let mut ctx = instance.store_mut(); + let memory = ctx.data().memory(); + let memory = memory.data_mut(&mut ctx); let data_len = data.len() as WordSize; - let data_ptr = instance.allocate(&mut ctx, allocator, data_len)?; - instance.write_memory_from(ctx, data_ptr, data)?; + let data_ptr = allocator.allocate(memory, data_len)?; + util::write_memory_from(instance.store_mut(), data_ptr, data)?; Ok((data_ptr, data_len)) } fn extract_output_data( - ctx: impl AsContext, instance: &InstanceWrapper, output_ptr: u32, output_len: u32, ) -> Result> { let mut output = vec![0; output_len as usize]; - instance.read_memory_into(ctx, Pointer::new(output_ptr), &mut output)?; + util::read_memory_into(instance.store(), Pointer::new(output_ptr), &mut output)?; Ok(output) } diff --git a/client/executor/wasmtime/src/tests.rs b/client/executor/wasmtime/src/tests.rs index 261afba0c6bc9..c34cbfad11138 100644 --- a/client/executor/wasmtime/src/tests.rs +++ b/client/executor/wasmtime/src/tests.rs @@ -310,3 +310,39 @@ fn test_max_memory_pages() { ) .unwrap(); } + +// This test takes quite a while to execute in a debug build (over 6 minutes on a TR 3970x) +// so it's ignored by default unless it was compiled with `--release`. +#[cfg_attr(build_type = "debug", ignore)] +#[test] +fn test_instances_without_reuse_are_not_leaked() { + use sp_wasm_interface::HostFunctions; + + let runtime = crate::create_runtime( + RuntimeBlob::uncompress_if_needed(&wasm_binary_unwrap()[..]).unwrap(), + crate::Config { + heap_pages: 2048, + max_memory_size: None, + allow_missing_func_imports: true, + cache_path: None, + semantics: crate::Semantics { + fast_instance_reuse: false, + deterministic_stack_limit: None, + canonicalize_nans: false, + parallel_compilation: true, + }, + }, + sp_io::SubstrateHostFunctions::host_functions(), + ) + .unwrap(); + + // As long as the `wasmtime`'s `Store` lives the instances spawned through it + // will live indefinitely. Currently it has a maximum limit of 10k instances, + // so let's spawn 10k + 1 of them to make sure our code doesn't keep the `Store` + // alive longer than it is necessary. (And since we disabled instance reuse + // a new instance will be spawned on each call.) + let mut instance = runtime.new_instance().unwrap(); + for _ in 0..10001 { + instance.call_export("test_empty_return", &[0]).unwrap(); + } +} diff --git a/client/executor/wasmtime/src/util.rs b/client/executor/wasmtime/src/util.rs index 2c135fe7a343b..2c9379e9ce812 100644 --- a/client/executor/wasmtime/src/util.rs +++ b/client/executor/wasmtime/src/util.rs @@ -16,7 +16,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use sp_wasm_interface::Value; +use crate::runtime::StoreData; +use sc_executor_common::{ + error::{Error, Result}, + util::checked_range, +}; +use sp_wasm_interface::{Pointer, Value}; +use wasmtime::{AsContext, AsContextMut}; /// Converts a [`wasmtime::Val`] into a substrate runtime interface [`Value`]. /// @@ -41,3 +47,54 @@ pub fn into_wasmtime_val(value: Value) -> wasmtime::Val { Value::F64(f_bits) => wasmtime::Val::F64(f_bits), } } + +/// Read data from a slice of memory into a newly allocated buffer. +/// +/// Returns an error if the read would go out of the memory bounds. +pub(crate) fn read_memory( + ctx: impl AsContext, + source_addr: Pointer, + size: usize, +) -> Result> { + let range = + checked_range(source_addr.into(), size, ctx.as_context().data().memory().data_size(&ctx)) + .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; + + let mut buffer = vec![0; range.len()]; + read_memory_into(ctx, source_addr, &mut buffer)?; + + Ok(buffer) +} + +/// Read data from the instance memory into a slice. +/// +/// Returns an error if the read would go out of the memory bounds. +pub(crate) fn read_memory_into( + ctx: impl AsContext, + address: Pointer, + dest: &mut [u8], +) -> Result<()> { + let memory = ctx.as_context().data().memory().data(&ctx); + + 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 the instance memory from a slice. +/// +/// Returns an error if the write would go out of the memory bounds. +pub(crate) fn write_memory_from( + mut ctx: impl AsContextMut, + address: Pointer, + data: &[u8], +) -> Result<()> { + let memory = ctx.as_context().data().memory(); + let memory = memory.data_mut(&mut ctx); + + let range = checked_range(address.into(), data.len(), memory.len()) + .ok_or_else(|| Error::Other("memory write is out of bounds".into()))?; + memory[range].copy_from_slice(data); + Ok(()) +}