1pub mod module_cache;
2pub mod package_loader;
3pub mod resolver;
4pub mod task_manager;
5
6use self::module_cache::CacheError;
7pub use self::task_manager::{SpawnType, VirtualTaskManager};
8use module_cache::HashedModuleData;
9use wasmer_types::{CompilationProgressCallback, ModuleHash};
10
11use std::{
12 borrow::Cow,
13 fmt,
14 ops::Deref,
15 sync::{Arc, Mutex},
16};
17
18use anyhow::Context as _;
19use futures::future::BoxFuture;
20use virtual_mio::block_on;
21use virtual_net::{DynVirtualNetworking, VirtualNetworking};
22use wasmer::{Engine, Module, RuntimeError};
23use wasmer_wasix_types::wasi::ExitCode;
24
25#[cfg(feature = "journal")]
26use crate::journal::{DynJournal, DynReadableJournal};
27use crate::{
28 SpawnError, WasiTtyState,
29 bin_factory::BinaryPackageCommand,
30 http::{DynHttpClient, HttpClient},
31 os::TtyBridge,
32 runtime::{
33 module_cache::{
34 ModuleCache, ThreadLocalCache,
35 progress::{ModuleLoadProgress, ModuleLoadProgressReporter},
36 },
37 package_loader::{PackageLoader, UnsupportedPackageLoader},
38 resolver::{BackendSource, MultiSource, Source},
39 },
40};
41
42#[derive(Default)]
56pub struct InstantiationState {
57 state: Option<Box<dyn std::any::Any + Send>>,
58}
59
60impl InstantiationState {
61 pub fn empty() -> Self {
64 Self { state: None }
65 }
66
67 pub fn new<T: std::any::Any + Send>(state: T) -> Self {
69 Self {
70 state: Some(Box::new(state)),
71 }
72 }
73
74 pub fn is_empty(&self) -> bool {
76 self.state.is_none()
77 }
78
79 pub fn take<T: std::any::Any + Send>(self) -> anyhow::Result<T> {
84 let state = self
85 .state
86 .context("missing instantiation state from the import phase")?;
87 state
88 .downcast::<T>()
89 .map(|state| *state)
90 .map_err(|_| anyhow::anyhow!("instantiation state does not belong to this hook"))
91 }
92}
93
94impl fmt::Debug for InstantiationState {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 if self.is_empty() {
97 f.write_str("InstantiationState::empty()")
98 } else {
99 f.write_str("InstantiationState(..)")
100 }
101 }
102}
103
104pub trait InstantiationHook: fmt::Debug + Send + Sync + 'static {
117 fn additional_imports(
124 &self,
125 module: &wasmer::Module,
126 store: &mut wasmer::StoreMut,
127 ) -> anyhow::Result<(wasmer::Imports, InstantiationState)> {
128 let _ = (module, store);
129 Ok((wasmer::Imports::new(), InstantiationState::empty()))
130 }
131
132 fn prepare_imports(
144 &self,
145 module: &wasmer::Module,
146 store: &mut wasmer::StoreMut,
147 imports: &mut wasmer::Imports,
148 ) -> anyhow::Result<InstantiationState> {
149 let (additional_imports, state) = self.additional_imports(module, store)?;
150 merge_missing_imports(imports, &additional_imports);
151 Ok(state)
152 }
153
154 fn configure_new_instance(
160 &self,
161 module: &wasmer::Module,
162 store: &mut wasmer::StoreMut,
163 instance: &wasmer::Instance,
164 imported_memory: Option<&wasmer::Memory>,
165 state: InstantiationState,
166 ) -> anyhow::Result<()> {
167 let _ = (module, store, instance, imported_memory, state);
168 Ok(())
169 }
170}
171
172impl<H: InstantiationHook + ?Sized> InstantiationHook for Arc<H> {
173 fn additional_imports(
174 &self,
175 module: &wasmer::Module,
176 store: &mut wasmer::StoreMut,
177 ) -> anyhow::Result<(wasmer::Imports, InstantiationState)> {
178 (**self).additional_imports(module, store)
179 }
180
181 fn prepare_imports(
182 &self,
183 module: &wasmer::Module,
184 store: &mut wasmer::StoreMut,
185 imports: &mut wasmer::Imports,
186 ) -> anyhow::Result<InstantiationState> {
187 (**self).prepare_imports(module, store, imports)
188 }
189
190 fn configure_new_instance(
191 &self,
192 module: &wasmer::Module,
193 store: &mut wasmer::StoreMut,
194 instance: &wasmer::Instance,
195 imported_memory: Option<&wasmer::Memory>,
196 state: InstantiationState,
197 ) -> anyhow::Result<()> {
198 (**self).configure_new_instance(module, store, instance, imported_memory, state)
199 }
200}
201
202struct ImportsOnlyHook<F>(F);
204
205impl<F> fmt::Debug for ImportsOnlyHook<F> {
206 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207 f.write_str("ImportsOnlyHook(..)")
208 }
209}
210
211impl<F> InstantiationHook for ImportsOnlyHook<F>
212where
213 F: Fn(&wasmer::Module, &mut wasmer::StoreMut) -> anyhow::Result<wasmer::Imports>
214 + Send
215 + Sync
216 + 'static,
217{
218 fn additional_imports(
219 &self,
220 module: &wasmer::Module,
221 store: &mut wasmer::StoreMut,
222 ) -> anyhow::Result<(wasmer::Imports, InstantiationState)> {
223 Ok(((self.0)(module, store)?, InstantiationState::empty()))
224 }
225}
226
227struct InstanceSetupHook<F>(F);
229
230impl<F> fmt::Debug for InstanceSetupHook<F> {
231 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232 f.write_str("InstanceSetupHook(..)")
233 }
234}
235
236impl<F> InstantiationHook for InstanceSetupHook<F>
237where
238 F: Fn(
239 &wasmer::Module,
240 &mut wasmer::StoreMut,
241 &wasmer::Instance,
242 Option<&wasmer::Memory>,
243 ) -> anyhow::Result<()>
244 + Send
245 + Sync
246 + 'static,
247{
248 fn configure_new_instance(
249 &self,
250 module: &wasmer::Module,
251 store: &mut wasmer::StoreMut,
252 instance: &wasmer::Instance,
253 imported_memory: Option<&wasmer::Memory>,
254 _state: InstantiationState,
255 ) -> anyhow::Result<()> {
256 (self.0)(module, store, instance, imported_memory)
257 }
258}
259
260#[derive(Clone)]
261pub enum TaintReason {
262 UnknownWasiVersion,
263 NonZeroExitCode(ExitCode),
264 RuntimeError(RuntimeError),
265 DlSymbolResolutionFailed(String),
266}
267
268#[allow(clippy::large_enum_variant)]
275pub enum ModuleInput<'a> {
276 Bytes(Cow<'a, [u8]>),
278 Hashed(Cow<'a, HashedModuleData>),
280 Command(Cow<'a, BinaryPackageCommand>),
282}
283
284impl<'a> ModuleInput<'a> {
285 pub fn to_owned(&'a self) -> ModuleInput<'static> {
287 match self {
289 Self::Bytes(Cow::Borrowed(b)) => {
290 let v: Vec<u8> = (*b).to_owned();
291 let c: Cow<'static, [u8]> = Cow::from(v);
292 ModuleInput::Bytes(c)
293 }
294 Self::Bytes(Cow::Owned(b)) => ModuleInput::Bytes(Cow::Owned((*b).clone())),
295 Self::Hashed(Cow::Borrowed(h)) => ModuleInput::Hashed(Cow::Owned((*h).clone())),
296 Self::Hashed(Cow::Owned(h)) => ModuleInput::Hashed(Cow::Owned(h.clone())),
297 Self::Command(Cow::Borrowed(c)) => ModuleInput::Command(Cow::Owned((*c).clone())),
298 Self::Command(Cow::Owned(c)) => ModuleInput::Command(Cow::Owned(c.clone())),
299 }
300 }
301
302 pub fn hash(&self) -> ModuleHash {
306 match self {
307 Self::Bytes(b) => {
308 ModuleHash::new(b)
310 }
311 Self::Hashed(hashed) => *hashed.hash(),
312 Self::Command(cmd) => *cmd.hash(),
313 }
314 }
315
316 pub fn wasm(&self) -> &[u8] {
318 match self {
319 Self::Bytes(b) => b,
320 Self::Hashed(hashed) => hashed.wasm().as_ref(),
321 Self::Command(cmd) => cmd.atom_ref().as_ref(),
322 }
323 }
324
325 pub fn to_hashed(&self) -> HashedModuleData {
329 match self {
330 Self::Bytes(b) => HashedModuleData::new(b.as_ref()),
331 Self::Hashed(hashed) => hashed.as_ref().clone(),
332 Self::Command(cmd) => HashedModuleData::from_command(cmd),
333 }
334 }
335}
336
337#[allow(unused_variables)]
341pub trait Runtime
342where
343 Self: fmt::Debug,
344{
345 fn networking(&self) -> &DynVirtualNetworking;
347
348 fn task_manager(&self) -> &Arc<dyn VirtualTaskManager>;
350
351 fn package_loader(&self) -> Arc<dyn PackageLoader + Send + Sync> {
353 Arc::new(UnsupportedPackageLoader)
354 }
355
356 fn module_cache(&self) -> Arc<dyn ModuleCache + Send + Sync> {
358 Arc::new(ThreadLocalCache::default())
365 }
366
367 fn source(&self) -> Arc<dyn Source + Send + Sync>;
369
370 fn engine(&self) -> Engine {
372 Engine::default()
373 }
374
375 fn new_store(&self) -> wasmer::Store {
377 cfg_select! {
378 feature = "sys" => {
379 wasmer::Store::new(self.engine())
380 }
381 _ => {
382 wasmer::Store::default()
383 }
384 }
385 }
386
387 fn additional_imports(
398 &self,
399 _module: &wasmer::Module,
400 _store: &mut wasmer::StoreMut,
401 ) -> anyhow::Result<(wasmer::Imports, InstantiationState)> {
402 Ok((wasmer::Imports::new(), InstantiationState::empty()))
403 }
404
405 fn prepare_imports(
412 &self,
413 module: &wasmer::Module,
414 store: &mut wasmer::StoreMut,
415 imports: &mut wasmer::Imports,
416 ) -> anyhow::Result<InstantiationState> {
417 let (additional_imports, state) = self.additional_imports(module, store)?;
418 merge_missing_imports(imports, &additional_imports);
419 Ok(state)
420 }
421
422 fn configure_new_instance(
428 &self,
429 _module: &wasmer::Module,
430 _store: &mut wasmer::StoreMut,
431 _instance: &wasmer::Instance,
432 _imported_memory: Option<&wasmer::Memory>,
433 _state: InstantiationState,
434 ) -> anyhow::Result<()> {
435 Ok(())
436 }
437
438 fn http_client(&self) -> Option<&DynHttpClient> {
440 None
441 }
442
443 fn tty(&self) -> Option<&(dyn TtyBridge + Send + Sync)> {
445 None
446 }
447
448 fn resolve_module<'a>(
455 &'a self,
456 input: ModuleInput<'a>,
457 engine: Option<&Engine>,
458 on_progress: Option<ModuleLoadProgressReporter>,
459 ) -> BoxFuture<'a, Result<Module, SpawnError>> {
460 let data = input.to_hashed();
461
462 let engine = if let Some(e) = engine {
463 e.clone()
464 } else {
465 match &input {
466 ModuleInput::Bytes(_) => self.engine(),
467 ModuleInput::Hashed(_) => self.engine(),
468 ModuleInput::Command(cmd) => self.engine(),
469 }
470 };
471
472 let module_cache = self.module_cache();
473
474 let task = async move { load_module(&engine, &module_cache, input, on_progress).await };
475 Box::pin(task)
476 }
477
478 fn resolve_module_sync(
480 &self,
481 input: ModuleInput<'_>,
482 engine: Option<&Engine>,
483 on_progress: Option<ModuleLoadProgressReporter>,
484 ) -> Result<Module, SpawnError> {
485 block_on(self.resolve_module(input, engine, on_progress))
486 }
487
488 #[deprecated(since = "0.601.0", note = "Use `resolve_module` instead")]
495 fn load_command_module(
496 &self,
497 cmd: &BinaryPackageCommand,
498 ) -> BoxFuture<'_, Result<Module, SpawnError>> {
499 self.resolve_module(ModuleInput::Command(Cow::Owned(cmd.clone())), None, None)
500 }
501
502 #[deprecated(since = "0.601.0", note = "Use `resolve_module_sync` instead")]
504 fn load_command_module_sync(&self, cmd: &BinaryPackageCommand) -> Result<Module, SpawnError> {
505 block_on(self.resolve_module(ModuleInput::Command(Cow::Borrowed(cmd)), None, None))
506 }
507
508 #[deprecated(since = "0.601.0", note = "Use `resolve_module` instead")]
512 fn load_module<'a>(&'a self, wasm: &'a [u8]) -> BoxFuture<'a, Result<Module, SpawnError>> {
513 self.resolve_module(ModuleInput::Bytes(Cow::Borrowed(wasm)), None, None)
514 }
515
516 #[deprecated(
518 since = "0.601.0",
519 note = "Use `load_command_module` or `load_hashed_module` instead - this method can have high overhead"
520 )]
521 fn load_module_sync(&self, wasm: &[u8]) -> Result<Module, SpawnError> {
522 block_on(self.resolve_module(ModuleInput::Bytes(Cow::Borrowed(wasm)), None, None))
523 }
524
525 fn load_hashed_module(
529 &self,
530 module: HashedModuleData,
531 engine: Option<&Engine>,
532 ) -> BoxFuture<'_, Result<Module, SpawnError>> {
533 self.resolve_module(ModuleInput::Hashed(Cow::Owned(module)), engine, None)
534 }
535
536 fn load_hashed_module_sync(
538 &self,
539 wasm: HashedModuleData,
540 engine: Option<&Engine>,
541 ) -> Result<Module, SpawnError> {
542 block_on(self.resolve_module(ModuleInput::Hashed(Cow::Owned(wasm)), engine, None))
543 }
544
545 fn on_taint(&self, _reason: TaintReason) {}
548
549 #[cfg(feature = "journal")]
552 fn read_only_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynReadableJournal>> + 'a> {
553 Box::new(std::iter::empty())
554 }
555
556 #[cfg(feature = "journal")]
558 fn writable_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynJournal>> + 'a> {
559 Box::new(std::iter::empty())
560 }
561
562 #[cfg(feature = "journal")]
565 fn active_journal(&self) -> Option<&'_ DynJournal> {
566 None
567 }
568}
569
570pub type DynRuntime = dyn Runtime + Send + Sync;
571
572#[tracing::instrument(level = "debug", skip_all)]
577pub async fn load_module(
578 engine: &Engine,
579 module_cache: &(dyn ModuleCache + Send + Sync),
580 input: ModuleInput<'_>,
581 on_progress: Option<ModuleLoadProgressReporter>,
582) -> Result<Module, crate::SpawnError> {
583 let wasm_hash = input.hash();
584
585 let result = if let Some(on_progress) = &on_progress {
586 module_cache
587 .load_with_progress(wasm_hash, engine, on_progress.clone())
588 .await
589 } else {
590 module_cache.load(wasm_hash, engine).await
591 };
592
593 match result {
594 Ok(module) => return Ok(module),
595 Err(CacheError::NotFound) => {}
596 Err(other) => {
597 tracing::warn!(
598 %wasm_hash,
599 error=&other as &dyn std::error::Error,
600 "Unable to load the cached module",
601 );
602 }
603 }
604
605 let res = if let Some(progress) = on_progress {
606 #[allow(unused_variables)]
607 let p = CompilationProgressCallback::new(move |p| {
608 progress.notify(ModuleLoadProgress::CompilingModule(p))
609 });
610 #[cfg(feature = "sys")]
611 {
612 if engine.is_sys() {
613 use wasmer::sys::NativeEngineExt;
614 engine.new_module_with_progress(input.wasm(), p)
615 } else {
616 Module::new(&engine, input.wasm())
617 }
618 }
619 #[cfg(not(feature = "sys"))]
620 {
621 Module::new(&engine, input.wasm())
622 }
623 } else {
624 Module::new(&engine, input.wasm())
625 };
626
627 let module = res.map_err(|err| crate::SpawnError::CompileError {
628 module_hash: wasm_hash,
629 error: err,
630 })?;
631
632 if let Err(e) = module_cache.save(wasm_hash, engine, &module).await {
634 tracing::warn!(
635 %wasm_hash,
636 error=&e as &dyn std::error::Error,
637 "Unable to cache the compiled module",
638 );
639 }
640
641 Ok(module)
642}
643
644#[derive(Debug, Default)]
645pub struct DefaultTty {
646 state: Mutex<WasiTtyState>,
647}
648
649impl TtyBridge for DefaultTty {
650 fn reset(&self) {
651 let mut state = self.state.lock().unwrap();
652 state.echo = false;
653 state.line_buffered = false;
654 state.line_feeds = false
655 }
656
657 fn tty_get(&self) -> WasiTtyState {
658 let state = self.state.lock().unwrap();
659 state.clone()
660 }
661
662 fn tty_set(&self, tty_state: WasiTtyState) {
663 let mut state = self.state.lock().unwrap();
664 *state = tty_state;
665 }
666}
667
668#[derive(Debug, Clone)]
669pub struct PluggableRuntime {
670 pub rt: Arc<dyn VirtualTaskManager>,
671 pub networking: DynVirtualNetworking,
672 pub http_client: Option<DynHttpClient>,
673 pub package_loader: Arc<dyn PackageLoader + Send + Sync>,
674 pub source: Arc<dyn Source + Send + Sync>,
675 pub engine: Engine,
676 pub module_cache: Arc<dyn ModuleCache + Send + Sync>,
677 pub tty: Option<Arc<dyn TtyBridge + Send + Sync>>,
678 #[cfg(feature = "journal")]
679 pub read_only_journals: Vec<Arc<DynReadableJournal>>,
680 #[cfg(feature = "journal")]
681 pub writable_journals: Vec<Arc<DynJournal>>,
682 pub instantiation_hooks: Vec<Arc<dyn InstantiationHook>>,
683}
684
685impl PluggableRuntime {
686 pub fn new(rt: Arc<dyn VirtualTaskManager>) -> Self {
687 cfg_select! {
689 feature = "host-vnet" => {
690 let networking = Arc::new(virtual_net::host::LocalNetworking::default());
691 }
692 _ => {
693 let networking = Arc::new(virtual_net::UnsupportedVirtualNetworking::default());
694 }
695 }
696 let http_client =
697 crate::http::default_http_client().map(|client| Arc::new(client) as DynHttpClient);
698
699 let loader = UnsupportedPackageLoader;
700
701 let mut source = MultiSource::default();
702 if let Some(client) = &http_client {
703 source.add_source(BackendSource::new(
704 BackendSource::WASMER_PROD_ENDPOINT.parse().unwrap(),
705 client.clone(),
706 ));
707 }
708
709 Self {
710 rt,
711 networking,
712 http_client,
713 engine: Default::default(),
714 tty: None,
715 source: Arc::new(source),
716 package_loader: Arc::new(loader),
717 module_cache: Arc::new(module_cache::in_memory()),
718 #[cfg(feature = "journal")]
719 read_only_journals: Vec::new(),
720 #[cfg(feature = "journal")]
721 writable_journals: Vec::new(),
722 instantiation_hooks: Vec::new(),
723 }
724 }
725
726 pub fn set_networking_implementation<I>(&mut self, net: I) -> &mut Self
727 where
728 I: VirtualNetworking + Sync,
729 {
730 self.networking = Arc::new(net);
731 self
732 }
733
734 pub fn set_engine(&mut self, engine: Engine) -> &mut Self {
735 self.engine = engine;
736 self
737 }
738
739 pub fn set_tty(&mut self, tty: Arc<dyn TtyBridge + Send + Sync>) -> &mut Self {
740 self.tty = Some(tty);
741 self
742 }
743
744 pub fn set_module_cache(
745 &mut self,
746 module_cache: impl ModuleCache + Send + Sync + 'static,
747 ) -> &mut Self {
748 self.module_cache = Arc::new(module_cache);
749 self
750 }
751
752 pub fn set_source(&mut self, source: impl Source + Send + 'static) -> &mut Self {
753 self.source = Arc::new(source);
754 self
755 }
756
757 pub fn set_package_loader(
758 &mut self,
759 package_loader: impl PackageLoader + 'static,
760 ) -> &mut Self {
761 self.package_loader = Arc::new(package_loader);
762 self
763 }
764
765 pub fn set_http_client(
766 &mut self,
767 client: impl HttpClient + Send + Sync + 'static,
768 ) -> &mut Self {
769 self.http_client = Some(Arc::new(client));
770 self
771 }
772
773 #[cfg(feature = "journal")]
774 pub fn add_read_only_journal(&mut self, journal: Arc<DynReadableJournal>) -> &mut Self {
775 self.read_only_journals.push(journal);
776 self
777 }
778
779 #[cfg(feature = "journal")]
780 pub fn add_writable_journal(&mut self, journal: Arc<DynJournal>) -> &mut Self {
781 self.writable_journals.push(journal);
782 self
783 }
784
785 pub fn with_additional_imports(
787 &mut self,
788 imports: impl Fn(&wasmer::Module, &mut wasmer::StoreMut) -> anyhow::Result<wasmer::Imports>
789 + Send
790 + Sync
791 + 'static,
792 ) -> &mut Self {
793 self.with_instantiation_hook(ImportsOnlyHook(imports))
794 }
795
796 pub fn with_instance_setup(
798 &mut self,
799 callback: impl Fn(
800 &wasmer::Module,
801 &mut wasmer::StoreMut,
802 &wasmer::Instance,
803 Option<&wasmer::Memory>,
804 ) -> anyhow::Result<()>
805 + Send
806 + Sync
807 + 'static,
808 ) -> &mut Self {
809 self.with_instantiation_hook(InstanceSetupHook(callback))
810 }
811
812 pub fn with_instantiation_hook(&mut self, hook: impl InstantiationHook) -> &mut Self {
815 self.instantiation_hooks.push(Arc::new(hook));
816 self
817 }
818}
819
820fn merge_missing_imports(imports: &mut wasmer::Imports, additional_imports: &wasmer::Imports) {
821 for (namespace, name, value) in additional_imports.iter() {
822 if imports.exists(namespace, name) {
823 tracing::warn!(
824 "Skipping duplicate additional import {}.{}",
825 namespace,
826 name
827 );
828 } else {
829 imports.define(namespace, name, value.clone());
830 }
831 }
832}
833
834fn run_import_hooks(
837 hooks: &[Arc<dyn InstantiationHook>],
838 module: &wasmer::Module,
839 store: &mut wasmer::StoreMut,
840 imports: &mut wasmer::Imports,
841) -> anyhow::Result<Vec<InstantiationState>> {
842 let mut states = Vec::with_capacity(hooks.len());
843 for hook in hooks {
844 let state = hook.prepare_imports(module, store, imports)?;
845 states.push(state);
846 }
847 Ok(states)
848}
849
850struct OverriddenInstantiationState {
853 inner: InstantiationState,
854 own: Vec<InstantiationState>,
855}
856
857fn run_setup_hooks(
860 hooks: &[Arc<dyn InstantiationHook>],
861 states: Vec<InstantiationState>,
862 module: &wasmer::Module,
863 store: &mut wasmer::StoreMut,
864 instance: &wasmer::Instance,
865 imported_memory: Option<&wasmer::Memory>,
866) -> anyhow::Result<()> {
867 anyhow::ensure!(
868 states.len() == hooks.len(),
869 "instance setup state does not match the registered instantiation hooks \
870 (got {} states for {} hooks)",
871 states.len(),
872 hooks.len(),
873 );
874 for (hook, state) in hooks.iter().zip(states) {
875 hook.configure_new_instance(module, store, instance, imported_memory, state)?;
876 }
877 Ok(())
878}
879
880impl Runtime for PluggableRuntime {
881 fn networking(&self) -> &DynVirtualNetworking {
882 &self.networking
883 }
884
885 fn http_client(&self) -> Option<&DynHttpClient> {
886 self.http_client.as_ref()
887 }
888
889 fn package_loader(&self) -> Arc<dyn PackageLoader + Send + Sync> {
890 Arc::clone(&self.package_loader)
891 }
892
893 fn source(&self) -> Arc<dyn Source + Send + Sync> {
894 Arc::clone(&self.source)
895 }
896
897 fn engine(&self) -> Engine {
898 self.engine.clone()
899 }
900
901 fn new_store(&self) -> wasmer::Store {
902 wasmer::Store::new(self.engine.clone())
903 }
904
905 fn task_manager(&self) -> &Arc<dyn VirtualTaskManager> {
906 &self.rt
907 }
908
909 fn tty(&self) -> Option<&(dyn TtyBridge + Send + Sync)> {
910 self.tty.as_deref()
911 }
912
913 fn module_cache(&self) -> Arc<dyn ModuleCache + Send + Sync> {
914 self.module_cache.clone()
915 }
916
917 fn additional_imports(
918 &self,
919 module: &wasmer::Module,
920 store: &mut wasmer::StoreMut,
921 ) -> anyhow::Result<(wasmer::Imports, InstantiationState)> {
922 if self.instantiation_hooks.is_empty() {
923 return Ok((wasmer::Imports::new(), InstantiationState::empty()));
924 }
925 let mut imports = wasmer::Imports::new();
926 let states = run_import_hooks(&self.instantiation_hooks, module, store, &mut imports)?;
927 Ok((imports, InstantiationState::new(states)))
928 }
929
930 fn prepare_imports(
931 &self,
932 module: &wasmer::Module,
933 store: &mut wasmer::StoreMut,
934 imports: &mut wasmer::Imports,
935 ) -> anyhow::Result<InstantiationState> {
936 if self.instantiation_hooks.is_empty() {
937 return Ok(InstantiationState::empty());
938 }
939 let states = run_import_hooks(&self.instantiation_hooks, module, store, imports)?;
940 Ok(InstantiationState::new(states))
941 }
942
943 fn configure_new_instance(
944 &self,
945 module: &wasmer::Module,
946 store: &mut wasmer::StoreMut,
947 instance: &wasmer::Instance,
948 imported_memory: Option<&wasmer::Memory>,
949 state: InstantiationState,
950 ) -> anyhow::Result<()> {
951 if self.instantiation_hooks.is_empty() {
952 return Ok(());
953 }
954 let states = state
955 .take::<Vec<InstantiationState>>()
956 .context("invalid instance setup state from import preparation")?;
957 run_setup_hooks(
958 &self.instantiation_hooks,
959 states,
960 module,
961 store,
962 instance,
963 imported_memory,
964 )
965 }
966
967 #[cfg(feature = "journal")]
968 fn read_only_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynReadableJournal>> + 'a> {
969 Box::new(self.read_only_journals.iter().cloned())
970 }
971
972 #[cfg(feature = "journal")]
973 fn writable_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynJournal>> + 'a> {
974 Box::new(self.writable_journals.iter().cloned())
975 }
976
977 #[cfg(feature = "journal")]
978 fn active_journal(&self) -> Option<&DynJournal> {
979 self.writable_journals.iter().last().map(|a| a.as_ref())
980 }
981}
982
983#[derive(Clone, Debug)]
986pub struct OverriddenRuntime {
987 inner: Arc<DynRuntime>,
988 task_manager: Option<Arc<dyn VirtualTaskManager>>,
989 networking: Option<DynVirtualNetworking>,
990 http_client: Option<DynHttpClient>,
991 package_loader: Option<Arc<dyn PackageLoader + Send + Sync>>,
992 source: Option<Arc<dyn Source + Send + Sync>>,
993 engine: Option<Engine>,
994 module_cache: Option<Arc<dyn ModuleCache + Send + Sync>>,
995 tty: Option<Arc<dyn TtyBridge + Send + Sync>>,
996 instantiation_hooks: Vec<Arc<dyn InstantiationHook>>,
997 #[cfg(feature = "journal")]
998 pub read_only_journals: Option<Vec<Arc<DynReadableJournal>>>,
999 #[cfg(feature = "journal")]
1000 pub writable_journals: Option<Vec<Arc<DynJournal>>>,
1001}
1002
1003impl OverriddenRuntime {
1004 pub fn new(inner: Arc<DynRuntime>) -> Self {
1005 Self {
1006 inner,
1007 task_manager: None,
1008 networking: None,
1009 http_client: None,
1010 package_loader: None,
1011 source: None,
1012 engine: None,
1013 module_cache: None,
1014 tty: None,
1015 instantiation_hooks: Vec::new(),
1016 #[cfg(feature = "journal")]
1017 read_only_journals: None,
1018 #[cfg(feature = "journal")]
1019 writable_journals: None,
1020 }
1021 }
1022
1023 pub fn with_task_manager(mut self, task_manager: Arc<dyn VirtualTaskManager>) -> Self {
1024 self.task_manager.replace(task_manager);
1025 self
1026 }
1027
1028 pub fn with_networking(mut self, networking: DynVirtualNetworking) -> Self {
1029 self.networking.replace(networking);
1030 self
1031 }
1032
1033 pub fn with_http_client(mut self, http_client: DynHttpClient) -> Self {
1034 self.http_client.replace(http_client);
1035 self
1036 }
1037
1038 pub fn with_package_loader(
1039 mut self,
1040 package_loader: Arc<dyn PackageLoader + Send + Sync>,
1041 ) -> Self {
1042 self.package_loader.replace(package_loader);
1043 self
1044 }
1045
1046 pub fn with_source(mut self, source: Arc<dyn Source + Send + Sync>) -> Self {
1047 self.source.replace(source);
1048 self
1049 }
1050
1051 pub fn with_engine(mut self, engine: Engine) -> Self {
1052 self.engine.replace(engine);
1053 self
1054 }
1055
1056 pub fn with_module_cache(mut self, module_cache: Arc<dyn ModuleCache + Send + Sync>) -> Self {
1057 self.module_cache.replace(module_cache);
1058 self
1059 }
1060
1061 pub fn with_tty(mut self, tty: Arc<dyn TtyBridge + Send + Sync>) -> Self {
1062 self.tty.replace(tty);
1063 self
1064 }
1065
1066 pub fn with_additional_imports(
1068 self,
1069 imports: impl Fn(&wasmer::Module, &mut wasmer::StoreMut) -> anyhow::Result<wasmer::Imports>
1070 + Send
1071 + Sync
1072 + 'static,
1073 ) -> Self {
1074 self.with_instantiation_hook(ImportsOnlyHook(imports))
1075 }
1076
1077 pub fn with_instance_setup(
1079 self,
1080 callback: impl Fn(
1081 &wasmer::Module,
1082 &mut wasmer::StoreMut,
1083 &wasmer::Instance,
1084 Option<&wasmer::Memory>,
1085 ) -> anyhow::Result<()>
1086 + Send
1087 + Sync
1088 + 'static,
1089 ) -> Self {
1090 self.with_instantiation_hook(InstanceSetupHook(callback))
1091 }
1092
1093 pub fn with_instantiation_hook(mut self, hook: impl InstantiationHook) -> Self {
1096 self.instantiation_hooks.push(Arc::new(hook));
1097 self
1098 }
1099
1100 #[cfg(feature = "journal")]
1101 pub fn with_read_only_journals(mut self, journals: Vec<Arc<DynReadableJournal>>) -> Self {
1102 self.read_only_journals.replace(journals);
1103 self
1104 }
1105
1106 #[cfg(feature = "journal")]
1107 pub fn with_writable_journals(mut self, journals: Vec<Arc<DynJournal>>) -> Self {
1108 self.writable_journals.replace(journals);
1109 self
1110 }
1111}
1112
1113impl Runtime for OverriddenRuntime {
1114 fn networking(&self) -> &DynVirtualNetworking {
1115 if let Some(net) = self.networking.as_ref() {
1116 net
1117 } else {
1118 self.inner.networking()
1119 }
1120 }
1121
1122 fn task_manager(&self) -> &Arc<dyn VirtualTaskManager> {
1123 if let Some(rt) = self.task_manager.as_ref() {
1124 rt
1125 } else {
1126 self.inner.task_manager()
1127 }
1128 }
1129
1130 fn source(&self) -> Arc<dyn Source + Send + Sync> {
1131 if let Some(source) = self.source.clone() {
1132 source
1133 } else {
1134 self.inner.source()
1135 }
1136 }
1137
1138 fn package_loader(&self) -> Arc<dyn PackageLoader + Send + Sync> {
1139 if let Some(loader) = self.package_loader.clone() {
1140 loader
1141 } else {
1142 self.inner.package_loader()
1143 }
1144 }
1145
1146 fn module_cache(&self) -> Arc<dyn ModuleCache + Send + Sync> {
1147 if let Some(cache) = self.module_cache.clone() {
1148 cache
1149 } else {
1150 self.inner.module_cache()
1151 }
1152 }
1153
1154 fn engine(&self) -> Engine {
1155 if let Some(engine) = self.engine.clone() {
1156 engine
1157 } else {
1158 self.inner.engine()
1159 }
1160 }
1161
1162 fn new_store(&self) -> wasmer::Store {
1163 if let Some(engine) = self.engine.clone() {
1164 wasmer::Store::new(engine)
1165 } else {
1166 self.inner.new_store()
1167 }
1168 }
1169
1170 fn additional_imports(
1171 &self,
1172 module: &wasmer::Module,
1173 store: &mut wasmer::StoreMut,
1174 ) -> anyhow::Result<(wasmer::Imports, InstantiationState)> {
1175 let (mut imports, inner_state) = self.inner.additional_imports(module, store)?;
1176 if self.instantiation_hooks.is_empty() && inner_state.is_empty() {
1177 return Ok((imports, InstantiationState::empty()));
1178 }
1179 let own_states = run_import_hooks(&self.instantiation_hooks, module, store, &mut imports)?;
1180 Ok((
1181 imports,
1182 InstantiationState::new(OverriddenInstantiationState {
1183 inner: inner_state,
1184 own: own_states,
1185 }),
1186 ))
1187 }
1188
1189 fn prepare_imports(
1190 &self,
1191 module: &wasmer::Module,
1192 store: &mut wasmer::StoreMut,
1193 imports: &mut wasmer::Imports,
1194 ) -> anyhow::Result<InstantiationState> {
1195 let inner_state = self.inner.prepare_imports(module, store, imports)?;
1196 if self.instantiation_hooks.is_empty() && inner_state.is_empty() {
1197 return Ok(InstantiationState::empty());
1198 }
1199 let own_states = run_import_hooks(&self.instantiation_hooks, module, store, imports)?;
1200 Ok(InstantiationState::new(OverriddenInstantiationState {
1201 inner: inner_state,
1202 own: own_states,
1203 }))
1204 }
1205
1206 fn configure_new_instance(
1207 &self,
1208 module: &wasmer::Module,
1209 store: &mut wasmer::StoreMut,
1210 instance: &wasmer::Instance,
1211 imported_memory: Option<&wasmer::Memory>,
1212 state: InstantiationState,
1213 ) -> anyhow::Result<()> {
1214 let state = if state.is_empty() {
1215 anyhow::ensure!(
1216 self.instantiation_hooks.is_empty(),
1217 "missing instance setup state from import preparation"
1218 );
1219 OverriddenInstantiationState {
1220 inner: InstantiationState::empty(),
1221 own: Vec::new(),
1222 }
1223 } else {
1224 state
1225 .take::<OverriddenInstantiationState>()
1226 .context("invalid instance setup state from import preparation")?
1227 };
1228 self.inner
1229 .configure_new_instance(module, store, instance, imported_memory, state.inner)?;
1230 run_setup_hooks(
1231 &self.instantiation_hooks,
1232 state.own,
1233 module,
1234 store,
1235 instance,
1236 imported_memory,
1237 )
1238 }
1239
1240 fn http_client(&self) -> Option<&DynHttpClient> {
1241 if let Some(client) = self.http_client.as_ref() {
1242 Some(client)
1243 } else {
1244 self.inner.http_client()
1245 }
1246 }
1247
1248 fn tty(&self) -> Option<&(dyn TtyBridge + Send + Sync)> {
1249 if let Some(tty) = self.tty.as_ref() {
1250 Some(tty.deref())
1251 } else {
1252 self.inner.tty()
1253 }
1254 }
1255
1256 #[cfg(feature = "journal")]
1257 fn read_only_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynReadableJournal>> + 'a> {
1258 if let Some(journals) = self.read_only_journals.as_ref() {
1259 Box::new(journals.iter().cloned())
1260 } else {
1261 self.inner.read_only_journals()
1262 }
1263 }
1264
1265 #[cfg(feature = "journal")]
1266 fn writable_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynJournal>> + 'a> {
1267 if let Some(journals) = self.writable_journals.as_ref() {
1268 Box::new(journals.iter().cloned())
1269 } else {
1270 self.inner.writable_journals()
1271 }
1272 }
1273
1274 #[cfg(feature = "journal")]
1275 fn active_journal(&self) -> Option<&'_ DynJournal> {
1276 if let Some(journals) = self.writable_journals.as_ref() {
1277 journals.iter().last().map(|a| a.as_ref())
1278 } else {
1279 self.inner.active_journal()
1280 }
1281 }
1282}
1283
1284#[cfg(test)]
1285mod tests {
1286 use super::{InstantiationHook, InstantiationState};
1287 use wasmer::{AsStoreMut, Extern, Global, Imports, Module, Store, Value};
1288
1289 #[derive(Debug)]
1290 struct LegacyImportsHook;
1291
1292 impl InstantiationHook for LegacyImportsHook {
1293 fn additional_imports(
1294 &self,
1295 _module: &Module,
1296 store: &mut wasmer::StoreMut,
1297 ) -> anyhow::Result<(Imports, InstantiationState)> {
1298 let mut imports = Imports::new();
1299 imports.define("host", "existing", Global::new(store, Value::I32(2)));
1300 imports.define("host", "added", Global::new(store, Value::I32(3)));
1301 Ok((imports, InstantiationState::empty()))
1302 }
1303 }
1304
1305 #[test]
1306 fn prepare_imports_preserves_the_legacy_additional_imports_contract() {
1307 let mut store = Store::default();
1308 let module = Module::new(&store, "(module)").unwrap();
1309 let mut imports = Imports::new();
1310 imports.define("host", "existing", Global::new(&mut store, Value::I32(1)));
1311
1312 LegacyImportsHook
1313 .prepare_imports(&module, &mut store.as_store_mut(), &mut imports)
1314 .unwrap();
1315
1316 let Some(Extern::Global(existing)) = imports.get_export("host", "existing") else {
1317 panic!("existing import is not a global");
1318 };
1319 let Some(Extern::Global(added)) = imports.get_export("host", "added") else {
1320 panic!("added import is not a global");
1321 };
1322 assert_eq!(existing.get(&mut store), Value::I32(1));
1323 assert_eq!(added.get(&mut store), Value::I32(3));
1324 }
1325
1326 #[test]
1327 fn instantiation_state_round_trips_the_hook_data() {
1328 let state = InstantiationState::new(42u32);
1329 assert!(!state.is_empty());
1330 assert_eq!(state.take::<u32>().unwrap(), 42);
1331 }
1332
1333 #[test]
1334 fn empty_instantiation_state_carries_nothing() {
1335 let state = InstantiationState::empty();
1336 assert!(state.is_empty());
1337 let err = state.take::<u32>().unwrap_err();
1338 assert!(err.to_string().contains("missing instantiation state"));
1339 }
1340
1341 #[test]
1342 fn instantiation_state_from_another_hook_is_rejected() {
1343 let state = InstantiationState::new("some other hook's state");
1346 let err = state.take::<u32>().unwrap_err();
1347 assert!(err.to_string().contains("does not belong to this hook"));
1348 }
1349}