diff --git a/Cargo.lock b/Cargo.lock index 1205f903867..4a90b020de8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4197,7 +4197,7 @@ dependencies = [ "thiserror", "wasmer", "wasmer-cache", - "wasmi 0.13.2", + "wasmi 0.30.0", ] [[package]] @@ -5169,6 +5169,12 @@ dependencies = [ "webrtc-util", ] +[[package]] +name = "intx" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f38a50a899dc47a6d0ed5508e7f601a2e34c3a85303514b5d137f3c10a0c75" + [[package]] name = "io-lifetimes" version = "1.0.11" @@ -13891,6 +13897,19 @@ dependencies = [ "wasmparser-nostd 0.83.0", ] +[[package]] +name = "wasmi" +version = "0.30.0" +source = "git+https://github.com/gear-tech/wasmi?branch=al-modify-globals#9a89568836dd345e3ede789c68a3e15ec244150e" +dependencies = [ + "intx", + "smallvec", + "spin 0.9.8", + "wasmi_arena", + "wasmi_core 0.12.0", + "wasmparser-nostd 0.100.1", +] + [[package]] name = "wasmi-validation" version = "0.5.0" @@ -13899,6 +13918,11 @@ dependencies = [ "parity-wasm 0.45.0", ] +[[package]] +name = "wasmi_arena" +version = "0.4.0" +source = "git+https://github.com/gear-tech/wasmi?branch=al-modify-globals#9a89568836dd345e3ede789c68a3e15ec244150e" + [[package]] name = "wasmi_core" version = "0.2.1" @@ -13926,6 +13950,18 @@ dependencies = [ "region", ] +[[package]] +name = "wasmi_core" +version = "0.12.0" +source = "git+https://github.com/gear-tech/wasmi?branch=al-modify-globals#9a89568836dd345e3ede789c68a3e15ec244150e" +dependencies = [ + "downcast-rs", + "libm 0.2.7", + "num-traits", + "paste", + "region", +] + [[package]] name = "wasmparser" version = "0.83.0" diff --git a/gsdk/tests/rpc.rs b/gsdk/tests/rpc.rs index a20c30644ba..6f857061794 100644 --- a/gsdk/tests/rpc.rs +++ b/gsdk/tests/rpc.rs @@ -122,7 +122,7 @@ async fn test_calculate_handle_gas() -> Result<()> { ) .await?; - assert!(signer.api().gprog(pid).await.is_ok()); + signer.api().gprog(pid).await.unwrap(); // 2. calculate handle gas and send message. let gas_info = signer diff --git a/sandbox/host/Cargo.toml b/sandbox/host/Cargo.toml index 854f36e26a0..8af2306a612 100644 --- a/sandbox/host/Cargo.toml +++ b/sandbox/host/Cargo.toml @@ -18,7 +18,7 @@ environmental = "1.1.3" thiserror.workspace = true log = { workspace = true, features = ["std"] } wasmer = { version = "2.2", features = ["singlepass"] } -wasmi = { git = "https://github.com/gear-tech/wasmi", branch = "v0.13.2-sign-ext", features = ["virtual_memory"] } +wasmi = { git = "https://github.com/gear-tech/wasmi", branch = "al-modify-globals", features = ["virtual_memory"] } sp-allocator = { workspace = true, features = ["std"] } sp-wasm-interface = { workspace = true, features = ["std"] } gear-sandbox-env = { workspace = true, features = ["std"] } diff --git a/sandbox/host/src/error.rs b/sandbox/host/src/error.rs index a7de27f77a6..791f64d70f9 100644 --- a/sandbox/host/src/error.rs +++ b/sandbox/host/src/error.rs @@ -109,8 +109,6 @@ pub enum Error { AbortedDueToTrap(MessageWithBacktrace), } -impl wasmi::HostError for Error {} - impl From<&'static str> for Error { fn from(err: &'static str) -> Error { Error::Other(err.into()) diff --git a/sandbox/host/src/sandbox.rs b/sandbox/host/src/sandbox.rs index 4cafa6502a6..53983aceb67 100644 --- a/sandbox/host/src/sandbox.rs +++ b/sandbox/host/src/sandbox.rs @@ -23,7 +23,12 @@ mod wasmer_backend; mod wasmi_backend; -use std::{collections::HashMap, pin::Pin, rc::Rc}; +use std::{ + cell::RefCell, + collections::{BTreeMap, HashMap}, + pin::Pin, + rc::Rc, +}; use codec::Decode; use gear_sandbox_env as sandbox_env; @@ -42,7 +47,7 @@ use self::{ }, wasmi_backend::{ get_global as wasmi_get_global, instantiate as wasmi_instantiate, invoke as wasmi_invoke, - new_memory as wasmi_new_memory, set_global as wasmi_set_global, + new_memory as wasmi_new_memory, set_global as wasmi_set_global, Backend as WasmiBackend, MemoryWrapper as WasmiMemoryWrapper, }, }; @@ -171,19 +176,15 @@ pub trait SandboxContext { fn deallocate_memory(&mut self, ptr: Pointer) -> sp_wasm_interface::Result<()>; } -/// Implementation of [`Externals`] that allows execution of guest module with -/// [externals][`Externals`] that might refer functions defined by supervisor. -/// -/// [`Externals`]: ../wasmi/trait.Externals.html -pub struct GuestExternals<'a> { - /// Instance of sandboxed module to be dispatched - sandbox_instance: &'a SandboxInstance, -} - /// Module instance in terms of selected backend enum BackendInstance { /// Wasmi module instance - Wasmi(wasmi::ModuleRef), + Wasmi { + instance: wasmi::Instance, + store: Rc>>, + exports: BTreeMap, + globals: wasmi::Globals, + }, /// Wasmer module instance Wasmer(wasmer::Instance), @@ -205,7 +206,6 @@ enum BackendInstance { /// [`invoke`]: #method.invoke pub struct SandboxInstance { backend_instance: BackendInstance, - guest_to_supervisor_mapping: GuestToSupervisorFunctionMapping, } impl SandboxInstance { @@ -220,9 +220,9 @@ impl SandboxInstance { sandbox_context: &mut dyn SandboxContext, ) -> std::result::Result, error::Error> { match &self.backend_instance { - BackendInstance::Wasmi(wasmi_instance) => { - wasmi_invoke(self, wasmi_instance, export_name, args, sandbox_context) - } + BackendInstance::Wasmi { + instance, store, .. + } => wasmi_invoke(instance, store.clone(), export_name, args, sandbox_context), BackendInstance::Wasmer(wasmer_instance) => { wasmer_invoke(wasmer_instance, export_name, args, sandbox_context) @@ -235,7 +235,9 @@ impl SandboxInstance { /// Returns `Some(_)` if the global could be found. pub fn get_global_val(&self, name: &str) -> Option { match &self.backend_instance { - BackendInstance::Wasmi(wasmi_instance) => wasmi_get_global(wasmi_instance, name), + BackendInstance::Wasmi { + exports, globals, .. + } => wasmi_get_global(exports, globals, name), BackendInstance::Wasmer(wasmer_instance) => wasmer_get_global(wasmer_instance, name), } @@ -250,7 +252,9 @@ impl SandboxInstance { value: sp_wasm_interface::Value, ) -> std::result::Result, error::Error> { match &self.backend_instance { - BackendInstance::Wasmi(wasmi_instance) => wasmi_set_global(wasmi_instance, name, value), + BackendInstance::Wasmi { + exports, globals, .. + } => wasmi_set_global(exports, globals, name, value), BackendInstance::Wasmer(wasmer_instance) => { wasmer_set_global(wasmer_instance, name, value) @@ -452,7 +456,7 @@ impl util::MemoryTransfer for Memory { /// Information specific to a particular execution backend enum BackendContext { /// Wasmi specific context - Wasmi, + Wasmi(WasmiBackend), /// Wasmer specific context Wasmer(WasmerBackend), @@ -461,7 +465,7 @@ enum BackendContext { impl BackendContext { pub fn new(backend: SandboxBackend) -> BackendContext { match backend { - SandboxBackend::Wasmi => BackendContext::Wasmi, + SandboxBackend::Wasmi => BackendContext::Wasmi(WasmiBackend::new()), SandboxBackend::Wasmer => BackendContext::Wasmer(WasmerBackend::new()), } @@ -504,11 +508,9 @@ impl Store
{ ); self.memories.clear(); - match self.backend_context { - BackendContext::Wasmi => (), - BackendContext::Wasmer(_) => { - self.backend_context = BackendContext::Wasmer(WasmerBackend::new()); - } + self.backend_context = match self.backend_context { + BackendContext::Wasmi(_) => BackendContext::Wasmi(WasmiBackend::new()), + BackendContext::Wasmer(_) => BackendContext::Wasmer(WasmerBackend::new()), } } @@ -520,15 +522,15 @@ impl Store
{ /// Typically happens if `initial` is more than `maximum`. pub fn new_memory(&mut self, initial: u32, maximum: u32) -> Result { let memories = &mut self.memories; - let backend_context = &self.backend_context; + let backend_context = &mut self.backend_context; let maximum = match maximum { sandbox_env::MEM_UNLIMITED => None, specified_limit => Some(specified_limit), }; - let memory = match &backend_context { - BackendContext::Wasmi => wasmi_new_memory(initial, maximum)?, + let memory = match backend_context { + BackendContext::Wasmi(context) => wasmi_new_memory(context, initial, maximum)?, BackendContext::Wasmer(context) => wasmer_new_memory(context, initial, maximum)?, }; @@ -633,10 +635,12 @@ impl Store
{ guest_env: GuestEnvironment, sandbox_context: &mut dyn SandboxContext, ) -> std::result::Result { - let sandbox_instance = match self.backend_context { - BackendContext::Wasmi => wasmi_instantiate(wasm, guest_env, sandbox_context)?, + let sandbox_instance = match &mut self.backend_context { + BackendContext::Wasmi(context) => { + wasmi_instantiate(context, wasm, guest_env, sandbox_context)? + } - BackendContext::Wasmer(ref context) => { + BackendContext::Wasmer(context) => { wasmer_instantiate(context, wasm, guest_env, sandbox_context)? } }; diff --git a/sandbox/host/src/sandbox/wasmer_backend.rs b/sandbox/host/src/sandbox/wasmer_backend.rs index 27c92864aef..836c3ec2777 100644 --- a/sandbox/host/src/sandbox/wasmer_backend.rs +++ b/sandbox/host/src/sandbox/wasmer_backend.rs @@ -269,7 +269,6 @@ pub fn instantiate( Ok(SandboxInstance { backend_instance: BackendInstance::Wasmer(instance), - guest_to_supervisor_mapping: guest_env.guest_to_supervisor_mapping, }) } diff --git a/sandbox/host/src/sandbox/wasmi_backend.rs b/sandbox/host/src/sandbox/wasmi_backend.rs index e430093caac..25c5fdabe30 100644 --- a/sandbox/host/src/sandbox/wasmi_backend.rs +++ b/sandbox/host/src/sandbox/wasmi_backend.rs @@ -18,118 +18,135 @@ //! Wasmi specific impls for sandbox -use std::fmt; +use std::{cell::RefCell, collections::BTreeMap, fmt, rc::Rc}; use codec::{Decode, Encode}; use gear_sandbox_env::HostError; use sp_wasm_interface::{util, Pointer, ReturnValue, Value, WordSize}; use wasmi::{ - memory_units::Pages, ImportResolver, MemoryInstance, Module, ModuleInstance, RuntimeArgs, - RuntimeValue, Trap, + core::{Pages, Trap}, + AsContext, AsContextMut, Engine, Extern, ExternType, Func, Globals, Linker, Module, Store, }; use crate::{ error::{self, Error}, sandbox::{ - BackendInstance, GuestEnvironment, GuestExternals, GuestFuncIndex, Imports, - InstantiationError, Memory, SandboxContext, SandboxInstance, + BackendInstance, GuestEnvironment, InstantiationError, Memory, SandboxContext, + SandboxInstance, SupervisorFuncIndex, }, util::MemoryTransfer, }; environmental::environmental!(SandboxContextStore: trait SandboxContext); -#[derive(Debug)] -struct CustomHostError(String); +thread_local! { + static WASMI_CALLER: RefCell>> = RefCell::new(None); +} -impl fmt::Display for CustomHostError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "HostError: {}", self.0) +#[must_use] +struct WasmiCallerSetter(()); + +impl WasmiCallerSetter { + fn new(caller: wasmi::Caller<'_, ()>) -> Self { + unsafe { + WASMI_CALLER.with(|ref_| { + let static_caller = std::mem::transmute::< + wasmi::Caller<'_, ()>, + wasmi::Caller<'static, ()>, + >(caller); + + let old_caller = ref_.borrow_mut().replace(static_caller); + assert!(old_caller.is_none()); + }); + } + + Self(()) } } -impl wasmi::HostError for CustomHostError {} +impl Drop for WasmiCallerSetter { + fn drop(&mut self) { + unsafe { + WASMI_CALLER.with(|caller| { + let caller = caller + .borrow_mut() + .take() + .expect("caller set in `WasmiCallerSetter::new`"); + + let _caller = std::mem::transmute::< + wasmi::Caller<'static, ()>, + wasmi::Caller<'_, ()>, + >(caller); + }); + } + } +} -/// Construct trap error from specified message -fn trap(msg: &'static str) -> Trap { - Trap::host(CustomHostError(msg.into())) +#[derive(Clone)] +struct WasmiContext { + store: Rc>>, } -impl ImportResolver for Imports { - fn resolve_func( - &self, - module_name: &str, - field_name: &str, - signature: &wasmi::Signature, - ) -> std::result::Result { - let idx = self.func_by_name(module_name, field_name).ok_or_else(|| { - wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name)) - })?; - - Ok(wasmi::FuncInstance::alloc_host(signature.clone(), idx.0)) +impl WasmiContext { + fn with(&self, f: F) -> R + where + F: FnOnce(&dyn AsContext) -> R, + { + WASMI_CALLER.with(|caller| match caller.borrow().as_ref() { + Some(store) => f(store), + None => { + let store = self.store.borrow(); + f(&*store) + } + }) } - fn resolve_memory( - &self, - module_name: &str, - field_name: &str, - _memory_type: &wasmi::MemoryDescriptor, - ) -> std::result::Result { - let mem = self - .memory_by_name(module_name, field_name) - .ok_or_else(|| { - wasmi::Error::Instantiation(format!( - "Export {}:{} not found", - module_name, field_name - )) - })?; - - let wrapper = mem.as_wasmi().ok_or_else(|| { - wasmi::Error::Instantiation(format!( - "Unsupported non-wasmi export {}:{}", - module_name, field_name - )) - })?; - - // Here we use inner memory reference only to resolve the imports - // without accessing the memory contents. All subsequent memory accesses - // should happen through the wrapper, that enforces the memory access protocol. - let mem = wrapper.0; - - Ok(mem) + fn with_mut(&self, f: F) -> R + where + F: FnOnce(&mut dyn AsContextMut) -> R, + { + WASMI_CALLER.with(|caller| match caller.borrow_mut().as_mut() { + Some(store) => f(store), + None => { + let mut store = self.store.borrow_mut(); + f(&mut *store) + } + }) } +} - fn resolve_global( - &self, - module_name: &str, - field_name: &str, - _global_type: &wasmi::GlobalDescriptor, - ) -> std::result::Result { - Err(wasmi::Error::Instantiation(format!( - "Export {}:{} not found", - module_name, field_name - ))) +/// Wasmi specific context +pub struct Backend { + engine: Engine, + store: Rc>>, +} + +impl Backend { + pub fn new() -> Self { + let engine = Engine::default(); + let store = Store::new(&engine, ()); + let store = Rc::new(RefCell::new(store)); + Backend { engine, store } } - fn resolve_table( - &self, - module_name: &str, - field_name: &str, - _table_type: &wasmi::TableDescriptor, - ) -> std::result::Result { - Err(wasmi::Error::Instantiation(format!( - "Export {}:{} not found", - module_name, field_name - ))) + fn create_context(&self) -> WasmiContext { + WasmiContext { + store: self.store.clone(), + } } } /// Allocate new memory region -pub fn new_memory(initial: u32, maximum: Option) -> crate::error::Result { - let memory = Memory::Wasmi(MemoryWrapper::new( - MemoryInstance::alloc(Pages(initial as usize), maximum.map(|m| Pages(m as usize))) - .map_err(|error| Error::Sandbox(error.to_string()))?, - )); +pub fn new_memory( + context: &mut Backend, + initial: u32, + maximum: Option, +) -> crate::error::Result { + let memory_type = + wasmi::MemoryType::new(initial, maximum).map_err(|err| Error::Sandbox(err.to_string()))?; + let memory = wasmi::Memory::new(&mut *context.store.borrow_mut(), memory_type) + .map_err(|err| Error::Sandbox(err.to_string()))?; + let memory = Memory::Wasmi(MemoryWrapper::new(context, memory)); Ok(memory) } @@ -137,19 +154,35 @@ pub fn new_memory(initial: u32, maximum: Option) -> crate::error::Result fmt::Result { + f.debug_struct("MemoryWrapper") + .field("memory", &self.memory) + .finish() + } +} impl MemoryWrapper { /// Take ownership of the memory region and return a wrapper object - fn new(memory: wasmi::MemoryRef) -> Self { - Self(memory) + fn new(context: &Backend, memory: wasmi::Memory) -> Self { + Self { + memory, + context: context.create_context(), + } } } impl MemoryTransfer for MemoryWrapper { fn read(&self, source_addr: Pointer, size: usize) -> error::Result> { - self.0.with_direct_access(|source| { + self.context.with(|context| { + let source = self.memory.data(context.as_context()); + let range = util::checked_range(source_addr.into(), size, source.len()) .ok_or_else(|| error::Error::Other("memory read is out of bounds".into()))?; @@ -158,7 +191,8 @@ impl MemoryTransfer for MemoryWrapper { } fn read_into(&self, source_addr: Pointer, destination: &mut [u8]) -> error::Result<()> { - self.0.with_direct_access(|source| { + self.context.with(|context| { + let source = self.memory.data(context.as_context()); let range = util::checked_range(source_addr.into(), destination.len(), source.len()) .ok_or_else(|| error::Error::Other("memory read is out of bounds".into()))?; @@ -168,7 +202,8 @@ impl MemoryTransfer for MemoryWrapper { } fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> error::Result<()> { - self.0.with_direct_access_mut(|destination| { + self.context.with_mut(|context| { + let destination = self.memory.data_mut(context.as_context_mut()); let range = util::checked_range(dest_addr.into(), source.len(), destination.len()) .ok_or_else(|| error::Error::Other("memory write is out of bounds".into()))?; @@ -178,209 +213,287 @@ impl MemoryTransfer for MemoryWrapper { } fn memory_grow(&mut self, pages: u32) -> error::Result { - self.0 - .grow(Pages(pages as usize)) - .map_err(|e| { - Error::Sandbox(format!( - "Cannot grow memory in masmi sandbox executor: {}", - e - )) - }) - .map(|p| p.0 as u32) + self.context.with_mut(|context| { + self.memory + .grow(context.as_context_mut(), Pages::from(pages as u16)) + .map_err(|e| { + Error::Sandbox(format!( + "Cannot grow memory in masmi sandbox executor: {}", + e + )) + }) + .map(Into::into) + }) } fn memory_size(&mut self) -> u32 { - self.0.current_size().0 as u32 + self.context + .with(|context| self.memory.current_pages(context.as_context()).into()) } fn get_buff(&mut self) -> *mut u8 { - self.0.direct_access_mut().as_mut().as_mut_ptr() + self.context + .with_mut(|context| self.memory.data_mut(context.as_context_mut()).as_mut_ptr()) } } -impl<'a> wasmi::Externals for GuestExternals<'a> { - fn invoke_index( - &mut self, - index: usize, - args: RuntimeArgs, - ) -> std::result::Result, Trap> { - SandboxContextStore::with(|sandbox_context| { - // Make `index` typesafe again. - let index = GuestFuncIndex(index); - - // Convert function index from guest to supervisor space - let func_idx = self.sandbox_instance - .guest_to_supervisor_mapping - .func_by_guest_index(index) - .expect( - "`invoke_index` is called with indexes registered via `FuncInstance::alloc_host`; - `FuncInstance::alloc_host` is called with indexes that were obtained from `guest_to_supervisor_mapping`; - `func_by_guest_index` called with `index` can't return `None`; - qed" - ); - - // Serialize arguments into a byte vector. - let invoke_args_data: Vec = args - .as_ref() - .iter() - .cloned() - .map(Value::from) - .collect::>() - .encode(); - - // Move serialized arguments inside the memory, invoke dispatch thunk and - // then free allocated memory. - let invoke_args_len = invoke_args_data.len() as WordSize; - let invoke_args_ptr = sandbox_context - .allocate_memory(invoke_args_len) - .map_err(|_| trap("Can't allocate memory in supervisor for the arguments"))?; - - let deallocate = |sandbox_context: &mut dyn SandboxContext, ptr, fail_msg| { - sandbox_context.deallocate_memory(ptr).map_err(|_| trap(fail_msg)) - }; - - if sandbox_context - .write_memory(invoke_args_ptr, &invoke_args_data) - .is_err() - { - deallocate( - sandbox_context, - invoke_args_ptr, - "Failed dealloction after failed write of invoke arguments", - )?; - return Err(trap("Can't write invoke args into memory")) - } - - let result = sandbox_context.invoke( - invoke_args_ptr, - invoke_args_len, - func_idx, - ); - - deallocate( - sandbox_context, - invoke_args_ptr, - "Can't deallocate memory for dispatch thunk's invoke arguments", - )?; - let result = result?; - - // dispatch_thunk returns pointer to serialized arguments. - // Unpack pointer and len of the serialized result data. - let (serialized_result_val_ptr, serialized_result_val_len) = { - // Cast to u64 to use zero-extension. - let v = result as u64; - let ptr = (v >> 32) as u32; - let len = (v & 0xFFFFFFFF) as u32; - (Pointer::new(ptr), len) - }; - - let serialized_result_val = sandbox_context - .read_memory(serialized_result_val_ptr, serialized_result_val_len) - .map_err(|_| trap("Can't read the serialized result from dispatch thunk")); - - deallocate( - sandbox_context, - serialized_result_val_ptr, - "Can't deallocate memory for dispatch thunk's result", - ) - .and(serialized_result_val) - .and_then(|serialized_result_val| { - let result_val = std::result::Result::::decode(&mut serialized_result_val.as_slice()) - .map_err(|_| trap("Decoding Result failed!"))?; - - match result_val { - Ok(return_value) => Ok(match return_value { - ReturnValue::Unit => None, - ReturnValue::Value(typed_value) => Some(From::from(typed_value)), - }), - Err(HostError) => Err(trap("Supervisor function returned sandbox::HostError")), - } - }) - }).expect("SandboxContextStore is set when invoking sandboxed functions; qed") - } -} - -fn with_guest_externals(sandbox_instance: &SandboxInstance, f: F) -> R -where - F: FnOnce(&mut GuestExternals) -> R, -{ - f(&mut GuestExternals { sandbox_instance }) -} - /// Instantiate a module within a sandbox context pub fn instantiate( + backend: &mut Backend, wasm: &[u8], guest_env: GuestEnvironment, sandbox_context: &mut dyn SandboxContext, ) -> std::result::Result { - let wasmi_module = Module::from_buffer(wasm).map_err(|_| InstantiationError::ModuleDecoding)?; - let wasmi_instance = ModuleInstance::new(&wasmi_module, &guest_env.imports) - .map_err(|_| InstantiationError::Instantiation)?; - - let sandbox_instance = SandboxInstance { - // In general, it's not a very good idea to use `.not_started_instance()` for - // anything but for extracting memory and tables. But in this particular case, we - // are extracting for the purpose of running `start` function which should be ok. - backend_instance: BackendInstance::Wasmi(wasmi_instance.not_started_instance().clone()), - guest_to_supervisor_mapping: guest_env.guest_to_supervisor_mapping, - }; + let store = &mut backend.store; + let module = + Module::new(&backend.engine, wasm).map_err(|_| InstantiationError::ModuleDecoding)?; + + let mut linker = Linker::new(&backend.engine); + for import in module.imports() { + match import.ty() { + ExternType::Func(func_type) => { + let Some(guest_func_index) = guest_env + .imports + .func_by_name(import.module(), import.name()) else { + continue + }; + + let supervisor_func_index = guest_env + .guest_to_supervisor_mapping + .func_by_guest_index(guest_func_index) + .ok_or(InstantiationError::ModuleDecoding)?; + + let func = dispatch_function( + supervisor_func_index, + &mut store.borrow_mut(), + func_type.clone(), + ); + linker + .define(import.module(), import.name(), func) + .map_err(|_| InstantiationError::Instantiation)?; + } + ExternType::Memory(_) => { + let memory = guest_env + .imports + .memory_by_name(import.module(), import.name()) + .ok_or(InstantiationError::ModuleDecoding)?; + + let memory = memory.as_wasmi().expect( + "memory is created by wasmi; \ + exported by the same module and backend; \ + thus the operation can't fail; \ + qed", + ); + + linker + .define(import.module(), import.name(), memory.memory) + .map_err(|_| InstantiationError::Instantiation)?; + } + _ => continue, + } + } - with_guest_externals(&sandbox_instance, |guest_externals| { - SandboxContextStore::using(sandbox_context, || { - wasmi_instance - .run_start(guest_externals) - .map_err(|_| InstantiationError::StartTrapped) - }) + let instance = SandboxContextStore::using(sandbox_context, || { + let instance_pre = linker + .instantiate(&mut *store.borrow_mut(), &module) + .map_err(|_| InstantiationError::Instantiation)?; + instance_pre + .start(&mut *store.borrow_mut()) + .map_err(|_| InstantiationError::StartTrapped) })?; - Ok(sandbox_instance) + let exports = instance + .exports(&*store.borrow()) + .map(|export| (export.name().to_string(), export.into_extern())) + .collect(); + let globals = store.borrow_mut().globals(); + let store = store.clone(); + + Ok(SandboxInstance { + backend_instance: BackendInstance::Wasmi { + instance, + store, + exports, + globals, + }, + }) +} + +fn dispatch_function( + supervisor_func_index: SupervisorFuncIndex, + store: &mut Store<()>, + func_ty: wasmi::FuncType, +) -> Func { + Func::new(store, func_ty, move |caller, params, results| { + let _wasmi_caller_guard = WasmiCallerSetter::new(caller); + SandboxContextStore::with(|sandbox_context| { + // Serialize arguments into a byte vector. + let invoke_args_data = params + .iter() + .cloned() + .map(|val| wasmi_to_ri(val).map_err(Trap::new)) + .collect::, _>>()? + .encode(); + + // Move serialized arguments inside the memory, invoke dispatch thunk and + // then free allocated memory. + let invoke_args_len = invoke_args_data.len() as WordSize; + let invoke_args_ptr = sandbox_context + .allocate_memory(invoke_args_len) + .map_err(|_| Trap::new("Can't allocate memory in supervisor for the arguments"))?; + + let deallocate = |fe: &mut dyn SandboxContext, ptr, fail_msg| { + fe.deallocate_memory(ptr).map_err(|_| Trap::new(fail_msg)) + }; + + if sandbox_context + .write_memory(invoke_args_ptr, &invoke_args_data) + .is_err() + { + deallocate( + sandbox_context, + invoke_args_ptr, + "Failed dealloction after failed write of invoke arguments", + )?; + + return Err(Trap::new("Can't write invoke args into memory")); + } + + // Perform the actuall call + let serialized_result = sandbox_context + .invoke(invoke_args_ptr, invoke_args_len, supervisor_func_index) + .map_err(|e| Trap::new(e.to_string())); + + deallocate( + sandbox_context, + invoke_args_ptr, + "Failed dealloction after invoke", + )?; + + let serialized_result = serialized_result?; + + // dispatch_thunk returns pointer to serialized arguments. + // Unpack pointer and len of the serialized result data. + let (serialized_result_val_ptr, serialized_result_val_len) = { + // Cast to u64 to use zero-extension. + let v = serialized_result as u64; + let ptr = (v >> 32) as u32; + let len = (v & 0xFFFFFFFF) as u32; + (Pointer::new(ptr), len) + }; + + let serialized_result_val = sandbox_context + .read_memory(serialized_result_val_ptr, serialized_result_val_len) + .map_err(|_| Trap::new("Can't read the serialized result from dispatch thunk")); + + deallocate( + sandbox_context, + serialized_result_val_ptr, + "Can't deallocate memory for dispatch thunk's result", + )?; + + let serialized_result_val = serialized_result_val?; + + let deserialized_result = std::result::Result::::decode( + &mut serialized_result_val.as_slice(), + ) + .map_err(|_| Trap::new("Decoding Result failed!"))? + .map_err(|_| Trap::new("Supervisor function returned sandbox::HostError"))?; + + match deserialized_result { + ReturnValue::Value(value) => { + results[0] = ri_to_wasmi(value); + } + ReturnValue::Unit => {} + }; + + Ok(()) + }) + .expect("SandboxContextStore is set when invoking sandboxed functions; qed") + }) } /// Invoke a function within a sandboxed module pub fn invoke( - instance: &SandboxInstance, - module: &wasmi::ModuleRef, + instance: &wasmi::Instance, + store: Rc>>, export_name: &str, args: &[Value], sandbox_context: &mut dyn SandboxContext, ) -> std::result::Result, error::Error> { - with_guest_externals(instance, |guest_externals| { - SandboxContextStore::using(sandbox_context, || { - let args = args.iter().cloned().map(From::from).collect::>(); - - module - .invoke_export(export_name, &args, guest_externals) - .map(|result| result.map(Into::into)) - .map_err(|error| error::Error::Sandbox(error.to_string())) - }) - }) + let function = instance + .get_func(&*store.borrow(), export_name) + .ok_or_else(|| Error::MethodNotFound(export_name.to_string()))?; + + let args: Vec = args.iter().cloned().map(ri_to_wasmi).collect(); + + let results = function.ty(&*store.borrow()).results().len(); + let mut results = vec![wasmi::Value::I32(0); results]; + SandboxContextStore::using(sandbox_context, || { + function + .call(&mut *store.borrow_mut(), &args, &mut results) + .map_err(|error| Error::Sandbox(error.to_string())) + })?; + + let results: &[wasmi::Value] = results.as_ref(); + match results { + [] => Ok(None), + + [wasm_value] => wasmi_to_ri(wasm_value.clone()) + .map(Some) + .map_err(Error::Sandbox), + + _ => Err(Error::Sandbox( + "multiple return types are not supported yet".into(), + )), + } } /// Get global value by name -pub fn get_global(instance: &wasmi::ModuleRef, name: &str) -> Option { - Some(Into::into( - instance.export_by_name(name)?.as_global()?.get(), - )) +pub fn get_global( + exports: &BTreeMap, + globals: &Globals, + name: &str, +) -> Option { + let global = exports.get(name).copied()?.into_global()?; + let value = globals.resolve(&global).get(); + wasmi_to_ri(value).ok() } /// Set global value by name pub fn set_global( - instance: &wasmi::ModuleRef, + exports: &BTreeMap, + globals: &Globals, name: &str, value: Value, ) -> std::result::Result, error::Error> { - let export = match instance.export_by_name(name) { - Some(e) => e, - None => return Ok(None), + let Some(Extern::Global(global)) = exports.get(name) else { + return Ok(None); }; - let global = match export.as_global() { - Some(g) => g, - None => return Ok(None), - }; + let value = ri_to_wasmi(value); + globals + .resolve_mut_with(&global, |entity| entity.set(value)) + .map_err(|err| Error::Sandbox(err.to_string()))?; - global - .set(From::from(value)) - .map(|_| Some(())) - .map_err(error::Error::Wasmi) + Ok(Some(())) +} + +fn wasmi_to_ri(val: wasmi::Value) -> Result { + match val { + wasmi::Value::I32(val) => Ok(Value::I32(val)), + wasmi::Value::I64(val) => Ok(Value::I64(val)), + wasmi::Value::F32(val) => Ok(Value::F32(val.to_bits())), + wasmi::Value::F64(val) => Ok(Value::F64(val.to_bits())), + _ => Err(format!("Unsupported function argument: {:?}", val)), + } +} + +fn ri_to_wasmi(val: Value) -> wasmi::Value { + match val { + Value::I32(val) => wasmi::Value::I32(val), + Value::I64(val) => wasmi::Value::I64(val), + Value::F32(val) => wasmi::Value::F32(wasmi::core::F32::from_bits(val)), + Value::F64(val) => wasmi::Value::F64(wasmi::core::F64::from_bits(val)), + } }