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 configure_new_instance(
138 &self,
139 module: &wasmer::Module,
140 store: &mut wasmer::StoreMut,
141 instance: &wasmer::Instance,
142 imported_memory: Option<&wasmer::Memory>,
143 state: InstantiationState,
144 ) -> anyhow::Result<()> {
145 let _ = (module, store, instance, imported_memory, state);
146 Ok(())
147 }
148}
149
150impl<H: InstantiationHook + ?Sized> InstantiationHook for Arc<H> {
151 fn additional_imports(
152 &self,
153 module: &wasmer::Module,
154 store: &mut wasmer::StoreMut,
155 ) -> anyhow::Result<(wasmer::Imports, InstantiationState)> {
156 (**self).additional_imports(module, store)
157 }
158
159 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 (**self).configure_new_instance(module, store, instance, imported_memory, state)
168 }
169}
170
171struct ImportsOnlyHook<F>(F);
173
174impl<F> fmt::Debug for ImportsOnlyHook<F> {
175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176 f.write_str("ImportsOnlyHook(..)")
177 }
178}
179
180impl<F> InstantiationHook for ImportsOnlyHook<F>
181where
182 F: Fn(&wasmer::Module, &mut wasmer::StoreMut) -> anyhow::Result<wasmer::Imports>
183 + Send
184 + Sync
185 + 'static,
186{
187 fn additional_imports(
188 &self,
189 module: &wasmer::Module,
190 store: &mut wasmer::StoreMut,
191 ) -> anyhow::Result<(wasmer::Imports, InstantiationState)> {
192 Ok(((self.0)(module, store)?, InstantiationState::empty()))
193 }
194}
195
196struct InstanceSetupHook<F>(F);
198
199impl<F> fmt::Debug for InstanceSetupHook<F> {
200 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201 f.write_str("InstanceSetupHook(..)")
202 }
203}
204
205impl<F> InstantiationHook for InstanceSetupHook<F>
206where
207 F: Fn(
208 &wasmer::Module,
209 &mut wasmer::StoreMut,
210 &wasmer::Instance,
211 Option<&wasmer::Memory>,
212 ) -> anyhow::Result<()>
213 + Send
214 + Sync
215 + 'static,
216{
217 fn configure_new_instance(
218 &self,
219 module: &wasmer::Module,
220 store: &mut wasmer::StoreMut,
221 instance: &wasmer::Instance,
222 imported_memory: Option<&wasmer::Memory>,
223 _state: InstantiationState,
224 ) -> anyhow::Result<()> {
225 (self.0)(module, store, instance, imported_memory)
226 }
227}
228
229#[derive(Clone)]
230pub enum TaintReason {
231 UnknownWasiVersion,
232 NonZeroExitCode(ExitCode),
233 RuntimeError(RuntimeError),
234 DlSymbolResolutionFailed(String),
235}
236
237#[allow(clippy::large_enum_variant)]
244pub enum ModuleInput<'a> {
245 Bytes(Cow<'a, [u8]>),
247 Hashed(Cow<'a, HashedModuleData>),
249 Command(Cow<'a, BinaryPackageCommand>),
251}
252
253impl<'a> ModuleInput<'a> {
254 pub fn to_owned(&'a self) -> ModuleInput<'static> {
256 match self {
258 Self::Bytes(Cow::Borrowed(b)) => {
259 let v: Vec<u8> = (*b).to_owned();
260 let c: Cow<'static, [u8]> = Cow::from(v);
261 ModuleInput::Bytes(c)
262 }
263 Self::Bytes(Cow::Owned(b)) => ModuleInput::Bytes(Cow::Owned((*b).clone())),
264 Self::Hashed(Cow::Borrowed(h)) => ModuleInput::Hashed(Cow::Owned((*h).clone())),
265 Self::Hashed(Cow::Owned(h)) => ModuleInput::Hashed(Cow::Owned(h.clone())),
266 Self::Command(Cow::Borrowed(c)) => ModuleInput::Command(Cow::Owned((*c).clone())),
267 Self::Command(Cow::Owned(c)) => ModuleInput::Command(Cow::Owned(c.clone())),
268 }
269 }
270
271 pub fn hash(&self) -> ModuleHash {
275 match self {
276 Self::Bytes(b) => {
277 ModuleHash::new(b)
279 }
280 Self::Hashed(hashed) => *hashed.hash(),
281 Self::Command(cmd) => *cmd.hash(),
282 }
283 }
284
285 pub fn wasm(&self) -> &[u8] {
287 match self {
288 Self::Bytes(b) => b,
289 Self::Hashed(hashed) => hashed.wasm().as_ref(),
290 Self::Command(cmd) => cmd.atom_ref().as_ref(),
291 }
292 }
293
294 pub fn to_hashed(&self) -> HashedModuleData {
298 match self {
299 Self::Bytes(b) => HashedModuleData::new(b.as_ref()),
300 Self::Hashed(hashed) => hashed.as_ref().clone(),
301 Self::Command(cmd) => HashedModuleData::from_command(cmd),
302 }
303 }
304}
305
306#[allow(unused_variables)]
310pub trait Runtime
311where
312 Self: fmt::Debug,
313{
314 fn networking(&self) -> &DynVirtualNetworking;
316
317 fn task_manager(&self) -> &Arc<dyn VirtualTaskManager>;
319
320 fn package_loader(&self) -> Arc<dyn PackageLoader + Send + Sync> {
322 Arc::new(UnsupportedPackageLoader)
323 }
324
325 fn module_cache(&self) -> Arc<dyn ModuleCache + Send + Sync> {
327 Arc::new(ThreadLocalCache::default())
334 }
335
336 fn source(&self) -> Arc<dyn Source + Send + Sync>;
338
339 fn engine(&self) -> Engine {
341 Engine::default()
342 }
343
344 fn new_store(&self) -> wasmer::Store {
346 cfg_if::cfg_if! {
347 if #[cfg(feature = "sys")] {
348 wasmer::Store::new(self.engine())
349 } else {
350 wasmer::Store::default()
351 }
352 }
353 }
354
355 fn additional_imports(
366 &self,
367 _module: &wasmer::Module,
368 _store: &mut wasmer::StoreMut,
369 ) -> anyhow::Result<(wasmer::Imports, InstantiationState)> {
370 Ok((wasmer::Imports::new(), InstantiationState::empty()))
371 }
372
373 fn configure_new_instance(
379 &self,
380 _module: &wasmer::Module,
381 _store: &mut wasmer::StoreMut,
382 _instance: &wasmer::Instance,
383 _imported_memory: Option<&wasmer::Memory>,
384 _state: InstantiationState,
385 ) -> anyhow::Result<()> {
386 Ok(())
387 }
388
389 fn http_client(&self) -> Option<&DynHttpClient> {
391 None
392 }
393
394 fn tty(&self) -> Option<&(dyn TtyBridge + Send + Sync)> {
396 None
397 }
398
399 fn resolve_module<'a>(
406 &'a self,
407 input: ModuleInput<'a>,
408 engine: Option<&Engine>,
409 on_progress: Option<ModuleLoadProgressReporter>,
410 ) -> BoxFuture<'a, Result<Module, SpawnError>> {
411 let data = input.to_hashed();
412
413 let engine = if let Some(e) = engine {
414 e.clone()
415 } else {
416 match &input {
417 ModuleInput::Bytes(_) => self.engine(),
418 ModuleInput::Hashed(_) => self.engine(),
419 ModuleInput::Command(cmd) => self.engine(),
420 }
421 };
422
423 let module_cache = self.module_cache();
424
425 let task = async move { load_module(&engine, &module_cache, input, on_progress).await };
426 Box::pin(task)
427 }
428
429 fn resolve_module_sync(
431 &self,
432 input: ModuleInput<'_>,
433 engine: Option<&Engine>,
434 on_progress: Option<ModuleLoadProgressReporter>,
435 ) -> Result<Module, SpawnError> {
436 block_on(self.resolve_module(input, engine, on_progress))
437 }
438
439 #[deprecated(since = "0.601.0", note = "Use `resolve_module` instead")]
446 fn load_command_module(
447 &self,
448 cmd: &BinaryPackageCommand,
449 ) -> BoxFuture<'_, Result<Module, SpawnError>> {
450 self.resolve_module(ModuleInput::Command(Cow::Owned(cmd.clone())), None, None)
451 }
452
453 #[deprecated(since = "0.601.0", note = "Use `resolve_module_sync` instead")]
455 fn load_command_module_sync(&self, cmd: &BinaryPackageCommand) -> Result<Module, SpawnError> {
456 block_on(self.resolve_module(ModuleInput::Command(Cow::Borrowed(cmd)), None, None))
457 }
458
459 #[deprecated(since = "0.601.0", note = "Use `resolve_module` instead")]
463 fn load_module<'a>(&'a self, wasm: &'a [u8]) -> BoxFuture<'a, Result<Module, SpawnError>> {
464 self.resolve_module(ModuleInput::Bytes(Cow::Borrowed(wasm)), None, None)
465 }
466
467 #[deprecated(
469 since = "0.601.0",
470 note = "Use `load_command_module` or `load_hashed_module` instead - this method can have high overhead"
471 )]
472 fn load_module_sync(&self, wasm: &[u8]) -> Result<Module, SpawnError> {
473 block_on(self.resolve_module(ModuleInput::Bytes(Cow::Borrowed(wasm)), None, None))
474 }
475
476 fn load_hashed_module(
480 &self,
481 module: HashedModuleData,
482 engine: Option<&Engine>,
483 ) -> BoxFuture<'_, Result<Module, SpawnError>> {
484 self.resolve_module(ModuleInput::Hashed(Cow::Owned(module)), engine, None)
485 }
486
487 fn load_hashed_module_sync(
489 &self,
490 wasm: HashedModuleData,
491 engine: Option<&Engine>,
492 ) -> Result<Module, SpawnError> {
493 block_on(self.resolve_module(ModuleInput::Hashed(Cow::Owned(wasm)), engine, None))
494 }
495
496 fn on_taint(&self, _reason: TaintReason) {}
499
500 #[cfg(feature = "journal")]
503 fn read_only_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynReadableJournal>> + 'a> {
504 Box::new(std::iter::empty())
505 }
506
507 #[cfg(feature = "journal")]
509 fn writable_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynJournal>> + 'a> {
510 Box::new(std::iter::empty())
511 }
512
513 #[cfg(feature = "journal")]
516 fn active_journal(&self) -> Option<&'_ DynJournal> {
517 None
518 }
519}
520
521pub type DynRuntime = dyn Runtime + Send + Sync;
522
523#[tracing::instrument(level = "debug", skip_all)]
528pub async fn load_module(
529 engine: &Engine,
530 module_cache: &(dyn ModuleCache + Send + Sync),
531 input: ModuleInput<'_>,
532 on_progress: Option<ModuleLoadProgressReporter>,
533) -> Result<Module, crate::SpawnError> {
534 let wasm_hash = input.hash();
535
536 let result = if let Some(on_progress) = &on_progress {
537 module_cache
538 .load_with_progress(wasm_hash, engine, on_progress.clone())
539 .await
540 } else {
541 module_cache.load(wasm_hash, engine).await
542 };
543
544 match result {
545 Ok(module) => return Ok(module),
546 Err(CacheError::NotFound) => {}
547 Err(other) => {
548 tracing::warn!(
549 %wasm_hash,
550 error=&other as &dyn std::error::Error,
551 "Unable to load the cached module",
552 );
553 }
554 }
555
556 let res = if let Some(progress) = on_progress {
557 #[allow(unused_variables)]
558 let p = CompilationProgressCallback::new(move |p| {
559 progress.notify(ModuleLoadProgress::CompilingModule(p))
560 });
561 #[cfg(feature = "sys")]
562 {
563 if engine.is_sys() {
564 use wasmer::sys::NativeEngineExt;
565 engine.new_module_with_progress(input.wasm(), p)
566 } else {
567 Module::new(&engine, input.wasm())
568 }
569 }
570 #[cfg(not(feature = "sys"))]
571 {
572 Module::new(&engine, input.wasm())
573 }
574 } else {
575 Module::new(&engine, input.wasm())
576 };
577
578 let module = res.map_err(|err| crate::SpawnError::CompileError {
579 module_hash: wasm_hash,
580 error: err,
581 })?;
582
583 if let Err(e) = module_cache.save(wasm_hash, engine, &module).await {
585 tracing::warn!(
586 %wasm_hash,
587 error=&e as &dyn std::error::Error,
588 "Unable to cache the compiled module",
589 );
590 }
591
592 Ok(module)
593}
594
595#[derive(Debug, Default)]
596pub struct DefaultTty {
597 state: Mutex<WasiTtyState>,
598}
599
600impl TtyBridge for DefaultTty {
601 fn reset(&self) {
602 let mut state = self.state.lock().unwrap();
603 state.echo = false;
604 state.line_buffered = false;
605 state.line_feeds = false
606 }
607
608 fn tty_get(&self) -> WasiTtyState {
609 let state = self.state.lock().unwrap();
610 state.clone()
611 }
612
613 fn tty_set(&self, tty_state: WasiTtyState) {
614 let mut state = self.state.lock().unwrap();
615 *state = tty_state;
616 }
617}
618
619#[derive(Debug, Clone)]
620pub struct PluggableRuntime {
621 pub rt: Arc<dyn VirtualTaskManager>,
622 pub networking: DynVirtualNetworking,
623 pub http_client: Option<DynHttpClient>,
624 pub package_loader: Arc<dyn PackageLoader + Send + Sync>,
625 pub source: Arc<dyn Source + Send + Sync>,
626 pub engine: Engine,
627 pub module_cache: Arc<dyn ModuleCache + Send + Sync>,
628 pub tty: Option<Arc<dyn TtyBridge + Send + Sync>>,
629 #[cfg(feature = "journal")]
630 pub read_only_journals: Vec<Arc<DynReadableJournal>>,
631 #[cfg(feature = "journal")]
632 pub writable_journals: Vec<Arc<DynJournal>>,
633 pub instantiation_hooks: Vec<Arc<dyn InstantiationHook>>,
634}
635
636impl PluggableRuntime {
637 pub fn new(rt: Arc<dyn VirtualTaskManager>) -> Self {
638 cfg_if::cfg_if! {
640 if #[cfg(feature = "host-vnet")] {
641 let networking = Arc::new(virtual_net::host::LocalNetworking::default());
642 } else {
643 let networking = Arc::new(virtual_net::UnsupportedVirtualNetworking::default());
644 }
645 }
646 let http_client =
647 crate::http::default_http_client().map(|client| Arc::new(client) as DynHttpClient);
648
649 let loader = UnsupportedPackageLoader;
650
651 let mut source = MultiSource::default();
652 if let Some(client) = &http_client {
653 source.add_source(BackendSource::new(
654 BackendSource::WASMER_PROD_ENDPOINT.parse().unwrap(),
655 client.clone(),
656 ));
657 }
658
659 Self {
660 rt,
661 networking,
662 http_client,
663 engine: Default::default(),
664 tty: None,
665 source: Arc::new(source),
666 package_loader: Arc::new(loader),
667 module_cache: Arc::new(module_cache::in_memory()),
668 #[cfg(feature = "journal")]
669 read_only_journals: Vec::new(),
670 #[cfg(feature = "journal")]
671 writable_journals: Vec::new(),
672 instantiation_hooks: Vec::new(),
673 }
674 }
675
676 pub fn set_networking_implementation<I>(&mut self, net: I) -> &mut Self
677 where
678 I: VirtualNetworking + Sync,
679 {
680 self.networking = Arc::new(net);
681 self
682 }
683
684 pub fn set_engine(&mut self, engine: Engine) -> &mut Self {
685 self.engine = engine;
686 self
687 }
688
689 pub fn set_tty(&mut self, tty: Arc<dyn TtyBridge + Send + Sync>) -> &mut Self {
690 self.tty = Some(tty);
691 self
692 }
693
694 pub fn set_module_cache(
695 &mut self,
696 module_cache: impl ModuleCache + Send + Sync + 'static,
697 ) -> &mut Self {
698 self.module_cache = Arc::new(module_cache);
699 self
700 }
701
702 pub fn set_source(&mut self, source: impl Source + Send + 'static) -> &mut Self {
703 self.source = Arc::new(source);
704 self
705 }
706
707 pub fn set_package_loader(
708 &mut self,
709 package_loader: impl PackageLoader + 'static,
710 ) -> &mut Self {
711 self.package_loader = Arc::new(package_loader);
712 self
713 }
714
715 pub fn set_http_client(
716 &mut self,
717 client: impl HttpClient + Send + Sync + 'static,
718 ) -> &mut Self {
719 self.http_client = Some(Arc::new(client));
720 self
721 }
722
723 #[cfg(feature = "journal")]
724 pub fn add_read_only_journal(&mut self, journal: Arc<DynReadableJournal>) -> &mut Self {
725 self.read_only_journals.push(journal);
726 self
727 }
728
729 #[cfg(feature = "journal")]
730 pub fn add_writable_journal(&mut self, journal: Arc<DynJournal>) -> &mut Self {
731 self.writable_journals.push(journal);
732 self
733 }
734
735 pub fn with_additional_imports(
737 &mut self,
738 imports: impl Fn(&wasmer::Module, &mut wasmer::StoreMut) -> anyhow::Result<wasmer::Imports>
739 + Send
740 + Sync
741 + 'static,
742 ) -> &mut Self {
743 self.with_instantiation_hook(ImportsOnlyHook(imports))
744 }
745
746 pub fn with_instance_setup(
748 &mut self,
749 callback: impl Fn(
750 &wasmer::Module,
751 &mut wasmer::StoreMut,
752 &wasmer::Instance,
753 Option<&wasmer::Memory>,
754 ) -> anyhow::Result<()>
755 + Send
756 + Sync
757 + 'static,
758 ) -> &mut Self {
759 self.with_instantiation_hook(InstanceSetupHook(callback))
760 }
761
762 pub fn with_instantiation_hook(&mut self, hook: impl InstantiationHook) -> &mut Self {
765 self.instantiation_hooks.push(Arc::new(hook));
766 self
767 }
768}
769
770fn run_import_hooks(
773 hooks: &[Arc<dyn InstantiationHook>],
774 module: &wasmer::Module,
775 store: &mut wasmer::StoreMut,
776) -> anyhow::Result<(wasmer::Imports, Vec<InstantiationState>)> {
777 let mut imports = wasmer::Imports::new();
778 let mut states = Vec::with_capacity(hooks.len());
779 for hook in hooks {
780 let (hook_imports, state) = hook.additional_imports(module, store)?;
781 imports.extend(&hook_imports);
782 states.push(state);
783 }
784 Ok((imports, states))
785}
786
787struct OverriddenInstantiationState {
790 inner: InstantiationState,
791 own: Vec<InstantiationState>,
792}
793
794fn run_setup_hooks(
797 hooks: &[Arc<dyn InstantiationHook>],
798 states: Vec<InstantiationState>,
799 module: &wasmer::Module,
800 store: &mut wasmer::StoreMut,
801 instance: &wasmer::Instance,
802 imported_memory: Option<&wasmer::Memory>,
803) -> anyhow::Result<()> {
804 anyhow::ensure!(
805 states.len() == hooks.len(),
806 "instance setup state does not match the registered instantiation hooks \
807 (got {} states for {} hooks)",
808 states.len(),
809 hooks.len(),
810 );
811 for (hook, state) in hooks.iter().zip(states) {
812 hook.configure_new_instance(module, store, instance, imported_memory, state)?;
813 }
814 Ok(())
815}
816
817impl Runtime for PluggableRuntime {
818 fn networking(&self) -> &DynVirtualNetworking {
819 &self.networking
820 }
821
822 fn http_client(&self) -> Option<&DynHttpClient> {
823 self.http_client.as_ref()
824 }
825
826 fn package_loader(&self) -> Arc<dyn PackageLoader + Send + Sync> {
827 Arc::clone(&self.package_loader)
828 }
829
830 fn source(&self) -> Arc<dyn Source + Send + Sync> {
831 Arc::clone(&self.source)
832 }
833
834 fn engine(&self) -> Engine {
835 self.engine.clone()
836 }
837
838 fn new_store(&self) -> wasmer::Store {
839 wasmer::Store::new(self.engine.clone())
840 }
841
842 fn task_manager(&self) -> &Arc<dyn VirtualTaskManager> {
843 &self.rt
844 }
845
846 fn tty(&self) -> Option<&(dyn TtyBridge + Send + Sync)> {
847 self.tty.as_deref()
848 }
849
850 fn module_cache(&self) -> Arc<dyn ModuleCache + Send + Sync> {
851 self.module_cache.clone()
852 }
853
854 fn additional_imports(
855 &self,
856 module: &wasmer::Module,
857 store: &mut wasmer::StoreMut,
858 ) -> anyhow::Result<(wasmer::Imports, InstantiationState)> {
859 if self.instantiation_hooks.is_empty() {
860 return Ok((wasmer::Imports::new(), InstantiationState::empty()));
861 }
862 let (imports, states) = run_import_hooks(&self.instantiation_hooks, module, store)?;
863 Ok((imports, InstantiationState::new(states)))
864 }
865
866 fn configure_new_instance(
867 &self,
868 module: &wasmer::Module,
869 store: &mut wasmer::StoreMut,
870 instance: &wasmer::Instance,
871 imported_memory: Option<&wasmer::Memory>,
872 state: InstantiationState,
873 ) -> anyhow::Result<()> {
874 if self.instantiation_hooks.is_empty() {
875 return Ok(());
876 }
877 let states = state
878 .take::<Vec<InstantiationState>>()
879 .context("invalid instance setup state from additional_imports")?;
880 run_setup_hooks(
881 &self.instantiation_hooks,
882 states,
883 module,
884 store,
885 instance,
886 imported_memory,
887 )
888 }
889
890 #[cfg(feature = "journal")]
891 fn read_only_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynReadableJournal>> + 'a> {
892 Box::new(self.read_only_journals.iter().cloned())
893 }
894
895 #[cfg(feature = "journal")]
896 fn writable_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynJournal>> + 'a> {
897 Box::new(self.writable_journals.iter().cloned())
898 }
899
900 #[cfg(feature = "journal")]
901 fn active_journal(&self) -> Option<&DynJournal> {
902 self.writable_journals.iter().last().map(|a| a.as_ref())
903 }
904}
905
906#[derive(Clone, Debug)]
909pub struct OverriddenRuntime {
910 inner: Arc<DynRuntime>,
911 task_manager: Option<Arc<dyn VirtualTaskManager>>,
912 networking: Option<DynVirtualNetworking>,
913 http_client: Option<DynHttpClient>,
914 package_loader: Option<Arc<dyn PackageLoader + Send + Sync>>,
915 source: Option<Arc<dyn Source + Send + Sync>>,
916 engine: Option<Engine>,
917 module_cache: Option<Arc<dyn ModuleCache + Send + Sync>>,
918 tty: Option<Arc<dyn TtyBridge + Send + Sync>>,
919 instantiation_hooks: Vec<Arc<dyn InstantiationHook>>,
920 #[cfg(feature = "journal")]
921 pub read_only_journals: Option<Vec<Arc<DynReadableJournal>>>,
922 #[cfg(feature = "journal")]
923 pub writable_journals: Option<Vec<Arc<DynJournal>>>,
924}
925
926impl OverriddenRuntime {
927 pub fn new(inner: Arc<DynRuntime>) -> Self {
928 Self {
929 inner,
930 task_manager: None,
931 networking: None,
932 http_client: None,
933 package_loader: None,
934 source: None,
935 engine: None,
936 module_cache: None,
937 tty: None,
938 instantiation_hooks: Vec::new(),
939 #[cfg(feature = "journal")]
940 read_only_journals: None,
941 #[cfg(feature = "journal")]
942 writable_journals: None,
943 }
944 }
945
946 pub fn with_task_manager(mut self, task_manager: Arc<dyn VirtualTaskManager>) -> Self {
947 self.task_manager.replace(task_manager);
948 self
949 }
950
951 pub fn with_networking(mut self, networking: DynVirtualNetworking) -> Self {
952 self.networking.replace(networking);
953 self
954 }
955
956 pub fn with_http_client(mut self, http_client: DynHttpClient) -> Self {
957 self.http_client.replace(http_client);
958 self
959 }
960
961 pub fn with_package_loader(
962 mut self,
963 package_loader: Arc<dyn PackageLoader + Send + Sync>,
964 ) -> Self {
965 self.package_loader.replace(package_loader);
966 self
967 }
968
969 pub fn with_source(mut self, source: Arc<dyn Source + Send + Sync>) -> Self {
970 self.source.replace(source);
971 self
972 }
973
974 pub fn with_engine(mut self, engine: Engine) -> Self {
975 self.engine.replace(engine);
976 self
977 }
978
979 pub fn with_module_cache(mut self, module_cache: Arc<dyn ModuleCache + Send + Sync>) -> Self {
980 self.module_cache.replace(module_cache);
981 self
982 }
983
984 pub fn with_tty(mut self, tty: Arc<dyn TtyBridge + Send + Sync>) -> Self {
985 self.tty.replace(tty);
986 self
987 }
988
989 pub fn with_additional_imports(
991 self,
992 imports: impl Fn(&wasmer::Module, &mut wasmer::StoreMut) -> anyhow::Result<wasmer::Imports>
993 + Send
994 + Sync
995 + 'static,
996 ) -> Self {
997 self.with_instantiation_hook(ImportsOnlyHook(imports))
998 }
999
1000 pub fn with_instance_setup(
1002 self,
1003 callback: impl Fn(
1004 &wasmer::Module,
1005 &mut wasmer::StoreMut,
1006 &wasmer::Instance,
1007 Option<&wasmer::Memory>,
1008 ) -> anyhow::Result<()>
1009 + Send
1010 + Sync
1011 + 'static,
1012 ) -> Self {
1013 self.with_instantiation_hook(InstanceSetupHook(callback))
1014 }
1015
1016 pub fn with_instantiation_hook(mut self, hook: impl InstantiationHook) -> Self {
1019 self.instantiation_hooks.push(Arc::new(hook));
1020 self
1021 }
1022
1023 #[cfg(feature = "journal")]
1024 pub fn with_read_only_journals(mut self, journals: Vec<Arc<DynReadableJournal>>) -> Self {
1025 self.read_only_journals.replace(journals);
1026 self
1027 }
1028
1029 #[cfg(feature = "journal")]
1030 pub fn with_writable_journals(mut self, journals: Vec<Arc<DynJournal>>) -> Self {
1031 self.writable_journals.replace(journals);
1032 self
1033 }
1034}
1035
1036impl Runtime for OverriddenRuntime {
1037 fn networking(&self) -> &DynVirtualNetworking {
1038 if let Some(net) = self.networking.as_ref() {
1039 net
1040 } else {
1041 self.inner.networking()
1042 }
1043 }
1044
1045 fn task_manager(&self) -> &Arc<dyn VirtualTaskManager> {
1046 if let Some(rt) = self.task_manager.as_ref() {
1047 rt
1048 } else {
1049 self.inner.task_manager()
1050 }
1051 }
1052
1053 fn source(&self) -> Arc<dyn Source + Send + Sync> {
1054 if let Some(source) = self.source.clone() {
1055 source
1056 } else {
1057 self.inner.source()
1058 }
1059 }
1060
1061 fn package_loader(&self) -> Arc<dyn PackageLoader + Send + Sync> {
1062 if let Some(loader) = self.package_loader.clone() {
1063 loader
1064 } else {
1065 self.inner.package_loader()
1066 }
1067 }
1068
1069 fn module_cache(&self) -> Arc<dyn ModuleCache + Send + Sync> {
1070 if let Some(cache) = self.module_cache.clone() {
1071 cache
1072 } else {
1073 self.inner.module_cache()
1074 }
1075 }
1076
1077 fn engine(&self) -> Engine {
1078 if let Some(engine) = self.engine.clone() {
1079 engine
1080 } else {
1081 self.inner.engine()
1082 }
1083 }
1084
1085 fn new_store(&self) -> wasmer::Store {
1086 if let Some(engine) = self.engine.clone() {
1087 wasmer::Store::new(engine)
1088 } else {
1089 self.inner.new_store()
1090 }
1091 }
1092
1093 fn additional_imports(
1094 &self,
1095 module: &wasmer::Module,
1096 store: &mut wasmer::StoreMut,
1097 ) -> anyhow::Result<(wasmer::Imports, InstantiationState)> {
1098 let (mut imports, inner_state) = self.inner.additional_imports(module, store)?;
1099 if self.instantiation_hooks.is_empty() && inner_state.is_empty() {
1100 return Ok((imports, InstantiationState::empty()));
1101 }
1102 let (own_imports, own_states) = run_import_hooks(&self.instantiation_hooks, module, store)?;
1103 imports.extend(&own_imports);
1104 Ok((
1105 imports,
1106 InstantiationState::new(OverriddenInstantiationState {
1107 inner: inner_state,
1108 own: own_states,
1109 }),
1110 ))
1111 }
1112
1113 fn configure_new_instance(
1114 &self,
1115 module: &wasmer::Module,
1116 store: &mut wasmer::StoreMut,
1117 instance: &wasmer::Instance,
1118 imported_memory: Option<&wasmer::Memory>,
1119 state: InstantiationState,
1120 ) -> anyhow::Result<()> {
1121 let state = if state.is_empty() {
1122 anyhow::ensure!(
1123 self.instantiation_hooks.is_empty(),
1124 "missing instance setup state from additional_imports"
1125 );
1126 OverriddenInstantiationState {
1127 inner: InstantiationState::empty(),
1128 own: Vec::new(),
1129 }
1130 } else {
1131 state
1132 .take::<OverriddenInstantiationState>()
1133 .context("invalid instance setup state from additional_imports")?
1134 };
1135 self.inner
1136 .configure_new_instance(module, store, instance, imported_memory, state.inner)?;
1137 run_setup_hooks(
1138 &self.instantiation_hooks,
1139 state.own,
1140 module,
1141 store,
1142 instance,
1143 imported_memory,
1144 )
1145 }
1146
1147 fn http_client(&self) -> Option<&DynHttpClient> {
1148 if let Some(client) = self.http_client.as_ref() {
1149 Some(client)
1150 } else {
1151 self.inner.http_client()
1152 }
1153 }
1154
1155 fn tty(&self) -> Option<&(dyn TtyBridge + Send + Sync)> {
1156 if let Some(tty) = self.tty.as_ref() {
1157 Some(tty.deref())
1158 } else {
1159 self.inner.tty()
1160 }
1161 }
1162
1163 #[cfg(feature = "journal")]
1164 fn read_only_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynReadableJournal>> + 'a> {
1165 if let Some(journals) = self.read_only_journals.as_ref() {
1166 Box::new(journals.iter().cloned())
1167 } else {
1168 self.inner.read_only_journals()
1169 }
1170 }
1171
1172 #[cfg(feature = "journal")]
1173 fn writable_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynJournal>> + 'a> {
1174 if let Some(journals) = self.writable_journals.as_ref() {
1175 Box::new(journals.iter().cloned())
1176 } else {
1177 self.inner.writable_journals()
1178 }
1179 }
1180
1181 #[cfg(feature = "journal")]
1182 fn active_journal(&self) -> Option<&'_ DynJournal> {
1183 if let Some(journals) = self.writable_journals.as_ref() {
1184 journals.iter().last().map(|a| a.as_ref())
1185 } else {
1186 self.inner.active_journal()
1187 }
1188 }
1189}
1190
1191#[cfg(test)]
1192mod tests {
1193 use super::InstantiationState;
1194
1195 #[test]
1196 fn instantiation_state_round_trips_the_hook_data() {
1197 let state = InstantiationState::new(42u32);
1198 assert!(!state.is_empty());
1199 assert_eq!(state.take::<u32>().unwrap(), 42);
1200 }
1201
1202 #[test]
1203 fn empty_instantiation_state_carries_nothing() {
1204 let state = InstantiationState::empty();
1205 assert!(state.is_empty());
1206 let err = state.take::<u32>().unwrap_err();
1207 assert!(err.to_string().contains("missing instantiation state"));
1208 }
1209
1210 #[test]
1211 fn instantiation_state_from_another_hook_is_rejected() {
1212 let state = InstantiationState::new("some other hook's state");
1215 let err = state.take::<u32>().unwrap_err();
1216 assert!(err.to_string().contains("does not belong to this hook"));
1217 }
1218}