diff --git a/lib/api/src/imports.rs b/lib/api/src/imports.rs index 344bc24e28f..1f9694849d7 100644 --- a/lib/api/src/imports.rs +++ b/lib/api/src/imports.rs @@ -187,6 +187,15 @@ impl<'a> Iterator for ImportsIterator<'a> { } } +impl IntoIterator for Imports { + type IntoIter = std::collections::hash_map::IntoIter<(String, String), Extern>; + type Item = ((String, String), Extern); + + fn into_iter(self) -> Self::IntoIter { + self.map.into_iter() + } +} + impl IntoIterator for &Imports { type IntoIter = std::collections::hash_map::IntoIter<(String, String), Extern>; type Item = ((String, String), Extern); diff --git a/lib/wasix/src/os/task/thread.rs b/lib/wasix/src/os/task/thread.rs index 6724634940a..dfc4d717e8a 100644 --- a/lib/wasix/src/os/task/thread.rs +++ b/lib/wasix/src/os/task/thread.rs @@ -617,6 +617,8 @@ pub enum WasiThreadError { MemoryCreateFailed(MemoryError), #[error("{0}")] ExportError(ExportError), + #[error("Failed to create additional imports - {0}")] + AdditionalImportCreationFailed(Arc), #[error("Failed to create the instance")] // Note: Boxed so we can keep the error size down InstanceCreateFailed(Box), @@ -634,6 +636,7 @@ impl From for Errno { WasiThreadError::MethodNotFound => Errno::Inval, WasiThreadError::MemoryCreateFailed(_) => Errno::Nomem, WasiThreadError::ExportError(_) => Errno::Noexec, + WasiThreadError::AdditionalImportCreationFailed(_) => Errno::Noexec, WasiThreadError::InstanceCreateFailed(_) => Errno::Noexec, WasiThreadError::InitFailed(_) => Errno::Noexec, WasiThreadError::InvalidWasmContext => Errno::Noexec, diff --git a/lib/wasix/src/runners/wasi.rs b/lib/wasix/src/runners/wasi.rs index f69f7e9ae1d..268de1e48a1 100644 --- a/lib/wasix/src/runners/wasi.rs +++ b/lib/wasix/src/runners/wasi.rs @@ -5,7 +5,7 @@ use std::{path::PathBuf, sync::Arc}; use anyhow::{Context, Error}; use tracing::Instrument; use virtual_fs::{ArcBoxFile, FileSystem, TmpFileSystem, VirtualFile}; -use wasmer::{Extern, Module}; +use wasmer::Module; use webc::metadata::{annotations::Wasi, Command}; use crate::{ @@ -201,33 +201,6 @@ impl WasiRunner { self } - /// Add an item to the list of importable items provided to the instance. - pub fn with_import( - &mut self, - namespace: impl Into, - name: impl Into, - value: impl Into, - ) -> &mut Self { - self.with_imports([((namespace, name), value)]) - } - - /// Add multiple import functions. - /// - /// This method will accept a [`&Imports`][wasmer::Imports] object. - pub fn with_imports(&mut self, imports: I) -> &mut Self - where - I: IntoIterator, - S1: Into, - S2: Into, - E: Into, - { - let imports = imports - .into_iter() - .map(|((ns, n), e)| ((ns.into(), n.into()), e.into())); - self.wasi.additional_imports.extend(imports); - self - } - #[tracing::instrument(level = "debug", skip_all)] pub fn prepare_webc_env( &self, diff --git a/lib/wasix/src/runners/wasi_common.rs b/lib/wasix/src/runners/wasi_common.rs index 82b2135f98a..7d16dab293b 100644 --- a/lib/wasix/src/runners/wasi_common.rs +++ b/lib/wasix/src/runners/wasi_common.rs @@ -9,7 +9,6 @@ use derivative::Derivative; use futures::future::BoxFuture; use tokio::runtime::Handle; use virtual_fs::{FileSystem, FsError, OverlayFileSystem, RootFileSystemBuilder, TmpFileSystem}; -use wasmer::Imports; use webc::metadata::annotations::Wasi as WasiAnnotation; use crate::{ @@ -46,7 +45,6 @@ pub(crate) struct CommonWasiOptions { pub(crate) snapshot_on: Vec, pub(crate) snapshot_interval: Option, pub(crate) current_dir: Option, - pub(crate) additional_imports: Imports, } impl CommonWasiOptions { @@ -88,8 +86,6 @@ impl CommonWasiOptions { *builder.capabilities_mut() = self.capabilities.clone(); - builder.add_imports(&self.additional_imports); - Ok(()) } diff --git a/lib/wasix/src/runtime/mod.rs b/lib/wasix/src/runtime/mod.rs index 85943ed6579..b6b6ae03d0c 100644 --- a/lib/wasix/src/runtime/mod.rs +++ b/lib/wasix/src/runtime/mod.rs @@ -88,6 +88,11 @@ where } } + /// Create additional imports for a new WASIX thread in the provided [wasmer::Store]. + fn additional_imports(&self, store: &mut wasmer::StoreMut) -> anyhow::Result { + Ok(wasmer::Imports::new()) + } + /// Get a custom HTTP client fn http_client(&self) -> Option<&DynHttpClient> { None @@ -205,6 +210,9 @@ impl TtyBridge for DefaultTty { } } +type MakeImportCallback = + dyn (Fn(&mut wasmer::StoreMut) -> anyhow::Result) + Send + Sync + 'static; + #[derive(Clone, Derivative)] #[derivative(Debug)] pub struct PluggableRuntime { @@ -220,6 +228,8 @@ pub struct PluggableRuntime { #[cfg(feature = "journal")] #[derivative(Debug = "ignore")] pub journals: Vec>, + #[derivative(Debug = "ignore")] + pub additional_imports: Vec>, } impl PluggableRuntime { @@ -256,6 +266,7 @@ impl PluggableRuntime { module_cache: Arc::new(module_cache::in_memory()), #[cfg(feature = "journal")] journals: Vec::new(), + additional_imports: Vec::new(), } } @@ -311,6 +322,17 @@ impl PluggableRuntime { self.journals.push(journal); self } + + pub fn with_additional_imports( + &mut self, + imports: impl (Fn(&mut wasmer::StoreMut) -> anyhow::Result) + + Send + + Sync + + 'static, + ) -> &mut Self { + self.additional_imports.push(Arc::new(imports)); + self + } } impl Runtime for PluggableRuntime { @@ -345,6 +367,14 @@ impl Runtime for PluggableRuntime { .unwrap_or_default() } + fn additional_imports(&self, store: &mut wasmer::StoreMut) -> anyhow::Result { + let mut imports = wasmer::Imports::new(); + for cb in &self.additional_imports { + imports.extend((*cb)(store)?); + } + Ok(imports) + } + fn task_manager(&self) -> &Arc { &self.rt } diff --git a/lib/wasix/src/state/builder.rs b/lib/wasix/src/state/builder.rs index b35bf444d16..9d3faf4658d 100644 --- a/lib/wasix/src/state/builder.rs +++ b/lib/wasix/src/state/builder.rs @@ -9,7 +9,7 @@ use std::{ use rand::Rng; use thiserror::Error; use virtual_fs::{ArcFile, FileSystem, FsError, TmpFileSystem, VirtualFile}; -use wasmer::{AsStoreMut, Extern, Imports, Instance, Module, Store}; +use wasmer::{AsStoreMut, Instance, Module, Store}; use wasmer_config::package::PackageId; #[cfg(feature = "journal")] @@ -78,7 +78,6 @@ pub struct WasiEnvBuilder { pub(super) map_commands: HashMap, pub(super) capabilites: Capabilities, - pub(super) additional_imports: Imports, #[cfg(feature = "journal")] pub(super) snapshot_on: Vec, @@ -673,49 +672,30 @@ impl WasiEnvBuilder { self.snapshot_interval.replace(interval); } - /// Add an item to the list of importable items provided to the instance. - pub fn import( - mut self, - namespace: impl Into, - name: impl Into, - value: impl Into, - ) -> Self { - self.add_imports([((namespace, name), value)]); - self - } - - /// Add an item to the list of importable items provided to the instance. - pub fn add_import( - &mut self, - namespace: impl Into, - name: impl Into, - value: impl Into, - ) { - self.add_imports([((namespace, name), value)]); - } + pub fn get_runtime_or_create_default(&mut self) -> Arc { + if self.runtime.is_none() { + self.runtime = Some({ + #[cfg(feature = "sys-thread")] + { + #[allow(unused_mut)] + let mut runtime = crate::runtime::PluggableRuntime::new(Arc::new( + crate::runtime::task_manager::tokio::TokioTaskManager::default(), + )); + #[cfg(feature = "journal")] + for journal in self.journals.clone() { + runtime.add_journal(journal); + } + Arc::new(runtime) + } - pub fn add_imports(&mut self, imports: I) - where - I: IntoIterator, - S1: Into, - S2: Into, - E: Into, - { - let imports = imports - .into_iter() - .map(|((ns, n), e)| ((ns.into(), n.into()), e.into())); - self.additional_imports.extend(imports); - } + #[cfg(not(feature = "sys-thread"))] + { + panic!("this build does not support a default runtime - specify one with WasiEnvBuilder::runtime()"); + } + }); + } - pub fn imports(mut self, imports: I) -> Self - where - I: IntoIterator, - S1: Into, - S2: Into, - E: Into, - { - self.add_imports(imports); - self + self.runtime.as_ref().unwrap().clone() } /// Consumes the [`WasiEnvBuilder`] and produces a [`WasiEnvInit`], which @@ -863,6 +843,8 @@ impl WasiEnvBuilder { wasi_fs.has_unioned.lock().unwrap().insert(id.clone()); } + let runtime = self.get_runtime_or_create_default(); + let state = WasiState { fs: wasi_fs, secret: rand::thread_rng().gen::<[u8; 32]>(), @@ -874,24 +856,6 @@ impl WasiEnvBuilder { envs: std::sync::Mutex::new(conv_env_vars(self.envs)), }; - let runtime = self.runtime.unwrap_or_else(|| { - #[cfg(feature = "sys-thread")] - { - #[allow(unused_mut)] - let mut runtime = crate::runtime::PluggableRuntime::new(Arc::new(crate::runtime::task_manager::tokio::TokioTaskManager::default())); - #[cfg(feature = "journal")] - for journal in self.journals.clone() { - runtime.add_journal(journal); - } - Arc::new(runtime) - } - - #[cfg(not(feature = "sys-thread"))] - { - panic!("this build does not support a default runtime - specify one with WasiEnvBuilder::runtime()"); - } - }); - let uses = self.uses; let map_commands = self.map_commands; @@ -925,7 +889,6 @@ impl WasiEnvBuilder { extra_tracing: true, #[cfg(feature = "journal")] snapshot_on: self.snapshot_on, - additional_imports: self.additional_imports, }; Ok(init) @@ -971,13 +934,17 @@ impl WasiEnvBuilder { #[allow(clippy::result_large_err)] pub fn instantiate_ext( - self, + mut self, module: Module, module_hash: ModuleHash, store: &mut impl AsStoreMut, ) -> Result<(Instance, WasiFunctionEnv), WasiRuntimeError> { + let runtime = self.get_runtime_or_create_default(); + let additional_imports = runtime + .additional_imports(&mut store.as_store_mut()) + .map_err(Arc::new)?; let init = self.build_init()?; - WasiEnv::instantiate(init, module, module_hash, store) + WasiEnv::instantiate(init, module, module_hash, store, additional_imports) } #[allow(clippy::result_large_err)] diff --git a/lib/wasix/src/state/env.rs b/lib/wasix/src/state/env.rs index 452109aa718..6e1d0a75c41 100644 --- a/lib/wasix/src/state/env.rs +++ b/lib/wasix/src/state/env.rs @@ -268,10 +268,6 @@ pub struct WasiEnvInit { /// Indicates if extra tracing should be output pub extra_tracing: bool, - /// Additional functionality provided to the WASIX instance, besides the - /// normal WASIX syscalls. - pub additional_imports: Imports, - /// Indicates triggers that will cause a snapshot to be taken #[cfg(feature = "journal")] pub snapshot_on: Vec, @@ -313,7 +309,6 @@ impl WasiEnvInit { extra_tracing: false, #[cfg(feature = "journal")] snapshot_on: self.snapshot_on.clone(), - additional_imports: self.additional_imports.clone(), } } } @@ -592,6 +587,7 @@ impl WasiEnv { module: Module, module_hash: ModuleHash, store: &mut impl AsStoreMut, + additional_imports: Imports, ) -> Result<(Instance, WasiFunctionEnv), WasiRuntimeError> { let call_initialize = init.call_initialize; let spawn_type = init.memory_ty.take(); @@ -602,8 +598,6 @@ impl WasiEnv { } } - let additional_imports = init.additional_imports.clone(); - let env = Self::from_init(init, module_hash)?; let pid = env.process.pid(); @@ -630,14 +624,6 @@ impl WasiEnv { let (mut import_object, instance_init_callback) = import_object_for_all_wasi_versions(&module, &mut store, &func_env.env); - for ((namespace, name), value) in &additional_imports { - // Note: We don't want to let downstream users override WASIX - // syscalls - if !import_object.exists(&namespace, &name) { - import_object.define(&namespace, &name, value); - } - } - let imported_memory = if let Some(memory) = memory { import_object.define("env", "memory", memory.clone()); Some(memory) @@ -645,6 +631,20 @@ impl WasiEnv { None }; + for ((namespace, name), value) in additional_imports { + // Note: We don't want to let downstream users override WASIX + // syscalls + if import_object.exists(&namespace, &name) { + tracing::warn!( + "Skipping duplicate additional import {}.{}", + namespace, + name + ); + } else { + import_object.define(&namespace, &name, value); + } + } + // Construct the instance. let instance = match Instance::new(&mut store, &module, &import_object) { Ok(a) => a, @@ -932,15 +932,17 @@ impl WasiEnv { /// Providers safe access to the memory /// (it must be initialized before it can be used) - pub(crate) fn try_memory(&self) -> Option> { + pub fn try_memory(&self) -> Option> { self.try_inner().map(|i| i.memory()) } /// Providers safe access to the memory /// (it must be initialized before it can be used) + /// + /// # Safety /// This has been marked as unsafe as it will panic if its executed /// on the wrong thread or before the inner is set - pub(crate) unsafe fn memory(&self) -> WasiInstanceGuardMemory<'_> { + pub unsafe fn memory(&self) -> WasiInstanceGuardMemory<'_> { self.try_memory().expect( "You must initialize the WasiEnv before using it and can not pass it between threads", ) @@ -948,7 +950,7 @@ impl WasiEnv { /// Providers safe access to the memory /// (it must be initialized before it can be used) - pub(crate) fn try_memory_view<'a>( + pub fn try_memory_view<'a>( &self, store: &'a (impl AsStoreRef + ?Sized), ) -> Option> { @@ -957,12 +959,11 @@ impl WasiEnv { /// Providers safe access to the memory /// (it must be initialized before it can be used) + /// + /// # Safety /// This has been marked as unsafe as it will panic if its executed /// on the wrong thread or before the inner is set - pub(crate) unsafe fn memory_view<'a>( - &self, - store: &'a (impl AsStoreRef + ?Sized), - ) -> MemoryView<'a> { + pub unsafe fn memory_view<'a>(&self, store: &'a (impl AsStoreRef + ?Sized)) -> MemoryView<'a> { self.try_memory_view(store).expect( "You must initialize the WasiEnv before using it and can not pass it between threads", ) @@ -971,7 +972,7 @@ impl WasiEnv { /// Copy the lazy reference so that when it's initialized during the /// export phase, all the other references get a copy of it #[allow(dead_code)] - pub(crate) fn try_memory_clone(&self) -> Option { + pub fn try_memory_clone(&self) -> Option { self.try_inner().map(|i| i.memory_clone()) } diff --git a/lib/wasix/src/state/func_env.rs b/lib/wasix/src/state/func_env.rs index 5de4602234d..b6531c92ec1 100644 --- a/lib/wasix/src/state/func_env.rs +++ b/lib/wasix/src/state/func_env.rs @@ -48,6 +48,10 @@ impl WasiFunctionEnv { // Create a new store and put the memory object in it // (but only if it has imported memory) let mut store = env.runtime.new_store(); + let additional_imports = env + .runtime + .additional_imports(&mut store.as_store_mut()) + .map_err(|e| WasiThreadError::AdditionalImportCreationFailed(Arc::new(e)))?; let memory = env .tasks() .build_memory(&mut store.as_store_mut(), spawn_type)?; @@ -56,10 +60,25 @@ impl WasiFunctionEnv { let mut ctx = WasiFunctionEnv::new(&mut store, env); let (mut import_object, init) = import_object_for_all_wasi_versions(&module, &mut store, &ctx.env); + if let Some(memory) = memory.clone() { import_object.define("env", "memory", memory); } + for ((namespace, name), value) in additional_imports { + // Note: We don't want to let downstream users override WASIX + // syscalls + if import_object.exists(&namespace, &name) { + tracing::warn!( + "Skipping duplicate additional import {}.{}", + namespace, + name + ); + } else { + import_object.define(&namespace, &name, value); + } + } + let instance = Instance::new(&mut store, &module, &import_object).map_err(|err| { tracing::warn!("failed to create instance - {}", err); WasiThreadError::InstanceCreateFailed(Box::new(err)) diff --git a/lib/wasix/src/syscalls/wasix/thread_spawn.rs b/lib/wasix/src/syscalls/wasix/thread_spawn.rs index 3f47e16fed2..28fe38804ef 100644 --- a/lib/wasix/src/syscalls/wasix/thread_spawn.rs +++ b/lib/wasix/src/syscalls/wasix/thread_spawn.rs @@ -14,7 +14,7 @@ use crate::{ WasiThreadHandle, }; -use wasmer::Memory; +use wasmer::{ExternType, Memory}; use wasmer_wasix_types::wasi::ThreadStart; /// ### `thread_spawn()` @@ -122,6 +122,15 @@ pub fn thread_spawn_internal_using_layout( let tasks = env.tasks().clone(); let thread_memory = unsafe { env.inner() }.memory_clone(); + let thread_module = unsafe { env.inner() }.module_clone(); + + if !thread_module.imports().any(|i| { + i.module() == "env" && i.name() == "memory" && matches!(i.ty(), ExternType::Memory(_)) + }) { + tracing::warn!("Module does not contain an imported memory by the name env.memory, new thread can't be spawned"); + return Err(Errno::Notcapable); + } + // We capture some local variables let state = env.state.clone(); let mut thread_env = env.clone(); @@ -153,7 +162,6 @@ pub fn thread_spawn_internal_using_layout( warn!("thread failed - the program does not export a `wasi_thread_start` function"); return Err(Errno::Notcapable); } - let thread_module = unsafe { env.inner() }.module_clone(); let globals = capture_store_snapshot(&mut ctx.as_store_mut()); let spawn_type = crate::runtime::SpawnMemoryType::ShareMemory(thread_memory, ctx.as_store_ref());