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_config::package::SuggestedCompilerOptimizations;
10use wasmer_types::{
11 CompilationProgressCallback, ModuleHash,
12 target::UserCompilerOptimizations as WasmerSuggestedCompilerOptimizations,
13};
14
15use std::{
16 borrow::Cow,
17 fmt,
18 ops::Deref,
19 sync::{Arc, Mutex},
20};
21
22use futures::future::BoxFuture;
23use virtual_mio::block_on;
24use virtual_net::{DynVirtualNetworking, VirtualNetworking};
25use wasmer::{CompileError, Engine, Module, RuntimeError};
26use wasmer_wasix_types::wasi::ExitCode;
27
28#[cfg(feature = "journal")]
29use crate::journal::{DynJournal, DynReadableJournal};
30use crate::{
31 SpawnError, WasiTtyState,
32 bin_factory::BinaryPackageCommand,
33 http::{DynHttpClient, HttpClient},
34 os::TtyBridge,
35 runtime::{
36 module_cache::{
37 ModuleCache, ThreadLocalCache,
38 progress::{ModuleLoadProgress, ModuleLoadProgressReporter},
39 },
40 package_loader::{PackageLoader, UnsupportedPackageLoader},
41 resolver::{BackendSource, MultiSource, Source},
42 },
43};
44
45#[derive(Clone)]
46pub enum TaintReason {
47 UnknownWasiVersion,
48 NonZeroExitCode(ExitCode),
49 RuntimeError(RuntimeError),
50 DlSymbolResolutionFailed(String),
51}
52
53pub enum ModuleInput<'a> {
60 Bytes(Cow<'a, [u8]>),
62 Hashed(Cow<'a, HashedModuleData>),
64 Command(Cow<'a, BinaryPackageCommand>),
66}
67
68impl<'a> ModuleInput<'a> {
69 pub fn to_owned(&'a self) -> ModuleInput<'static> {
71 match self {
73 Self::Bytes(Cow::Borrowed(b)) => {
74 let v: Vec<u8> = (*b).to_owned();
75 let c: Cow<'static, [u8]> = Cow::from(v);
76 ModuleInput::Bytes(c)
77 }
78 Self::Bytes(Cow::Owned(b)) => ModuleInput::Bytes(Cow::Owned((*b).clone())),
79 Self::Hashed(Cow::Borrowed(h)) => ModuleInput::Hashed(Cow::Owned((*h).clone())),
80 Self::Hashed(Cow::Owned(h)) => ModuleInput::Hashed(Cow::Owned(h.clone())),
81 Self::Command(Cow::Borrowed(c)) => ModuleInput::Command(Cow::Owned((*c).clone())),
82 Self::Command(Cow::Owned(c)) => ModuleInput::Command(Cow::Owned(c.clone())),
83 }
84 }
85
86 pub fn hash(&self) -> ModuleHash {
90 match self {
91 Self::Bytes(b) => {
92 ModuleHash::new(b)
94 }
95 Self::Hashed(hashed) => *hashed.hash(),
96 Self::Command(cmd) => *cmd.hash(),
97 }
98 }
99
100 pub fn wasm(&self) -> &[u8] {
102 match self {
103 Self::Bytes(b) => b,
104 Self::Hashed(hashed) => hashed.wasm().as_ref(),
105 Self::Command(cmd) => cmd.atom_ref().as_ref(),
106 }
107 }
108
109 pub fn to_hashed(&self) -> HashedModuleData {
113 match self {
114 Self::Bytes(b) => HashedModuleData::new(b.as_ref()),
115 Self::Hashed(hashed) => hashed.as_ref().clone(),
116 Self::Command(cmd) => HashedModuleData::from_command(cmd),
117 }
118 }
119}
120
121#[allow(unused_variables)]
125pub trait Runtime
126where
127 Self: fmt::Debug,
128{
129 fn networking(&self) -> &DynVirtualNetworking;
131
132 fn task_manager(&self) -> &Arc<dyn VirtualTaskManager>;
134
135 fn package_loader(&self) -> Arc<dyn PackageLoader + Send + Sync> {
137 Arc::new(UnsupportedPackageLoader)
138 }
139
140 fn module_cache(&self) -> Arc<dyn ModuleCache + Send + Sync> {
142 Arc::new(ThreadLocalCache::default())
149 }
150
151 fn source(&self) -> Arc<dyn Source + Send + Sync>;
153
154 fn engine(&self) -> Engine {
156 Engine::default()
157 }
158
159 fn engine_with_suggested_opts(
160 &self,
161 suggested_opts: &SuggestedCompilerOptimizations,
162 ) -> Result<Engine, CompileError> {
163 let mut engine = self.engine();
164 engine.with_opts(&WasmerSuggestedCompilerOptimizations {
165 pass_params: suggested_opts.pass_params,
166 })?;
167 Ok(engine)
168 }
169
170 fn new_store(&self) -> wasmer::Store {
172 cfg_if::cfg_if! {
173 if #[cfg(feature = "sys")] {
174 wasmer::Store::new(self.engine())
175 } else {
176 wasmer::Store::default()
177 }
178 }
179 }
180
181 fn http_client(&self) -> Option<&DynHttpClient> {
183 None
184 }
185
186 fn tty(&self) -> Option<&(dyn TtyBridge + Send + Sync)> {
188 None
189 }
190
191 fn resolve_module<'a>(
198 &'a self,
199 input: ModuleInput<'a>,
200 engine: Option<&Engine>,
201 on_progress: Option<ModuleLoadProgressReporter>,
202 ) -> BoxFuture<'a, Result<Module, SpawnError>> {
203 let data = input.to_hashed();
204
205 let engine = if let Some(e) = engine {
206 e.clone()
207 } else {
208 match &input {
209 ModuleInput::Bytes(_) => self.engine(),
210 ModuleInput::Hashed(_) => self.engine(),
211 ModuleInput::Command(cmd) => {
212 match self
213 .engine_with_suggested_opts(&cmd.as_ref().suggested_compiler_optimizations)
214 {
215 Ok(engine) => engine,
216 Err(error) => {
217 return Box::pin(async move {
218 Err(SpawnError::CompileError {
219 module_hash: *data.hash(),
220 error,
221 })
222 });
223 }
224 }
225 }
226 }
227 };
228
229 let module_cache = self.module_cache();
230
231 let task = async move { load_module(&engine, &module_cache, input, on_progress).await };
232 Box::pin(task)
233 }
234
235 fn resolve_module_sync(
237 &self,
238 input: ModuleInput<'_>,
239 engine: Option<&Engine>,
240 on_progress: Option<ModuleLoadProgressReporter>,
241 ) -> Result<Module, SpawnError> {
242 block_on(self.resolve_module(input, engine, on_progress))
243 }
244
245 #[deprecated(since = "0.601.0", note = "Use `resolve_module` instead")]
252 fn load_command_module(
253 &self,
254 cmd: &BinaryPackageCommand,
255 ) -> BoxFuture<'_, Result<Module, SpawnError>> {
256 self.resolve_module(ModuleInput::Command(Cow::Owned(cmd.clone())), None, None)
257 }
258
259 #[deprecated(since = "0.601.0", note = "Use `resolve_module_sync` instead")]
261 fn load_command_module_sync(&self, cmd: &BinaryPackageCommand) -> Result<Module, SpawnError> {
262 block_on(self.resolve_module(ModuleInput::Command(Cow::Borrowed(cmd)), None, None))
263 }
264
265 #[deprecated(since = "0.601.0", note = "Use `resolve_module` instead")]
269 fn load_module<'a>(&'a self, wasm: &'a [u8]) -> BoxFuture<'a, Result<Module, SpawnError>> {
270 self.resolve_module(ModuleInput::Bytes(Cow::Borrowed(wasm)), None, None)
271 }
272
273 #[deprecated(
275 since = "0.601.0",
276 note = "Use `load_command_module` or `load_hashed_module` instead - this method can have high overhead"
277 )]
278 fn load_module_sync(&self, wasm: &[u8]) -> Result<Module, SpawnError> {
279 block_on(self.resolve_module(ModuleInput::Bytes(Cow::Borrowed(wasm)), None, None))
280 }
281
282 fn load_hashed_module(
286 &self,
287 module: HashedModuleData,
288 engine: Option<&Engine>,
289 ) -> BoxFuture<'_, Result<Module, SpawnError>> {
290 self.resolve_module(ModuleInput::Hashed(Cow::Owned(module)), engine, None)
291 }
292
293 fn load_hashed_module_sync(
295 &self,
296 wasm: HashedModuleData,
297 engine: Option<&Engine>,
298 ) -> Result<Module, SpawnError> {
299 block_on(self.resolve_module(ModuleInput::Hashed(Cow::Owned(wasm)), engine, None))
300 }
301
302 fn on_taint(&self, _reason: TaintReason) {}
305
306 #[cfg(feature = "journal")]
309 fn read_only_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynReadableJournal>> + 'a> {
310 Box::new(std::iter::empty())
311 }
312
313 #[cfg(feature = "journal")]
315 fn writable_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynJournal>> + 'a> {
316 Box::new(std::iter::empty())
317 }
318
319 #[cfg(feature = "journal")]
322 fn active_journal(&self) -> Option<&'_ DynJournal> {
323 None
324 }
325}
326
327pub type DynRuntime = dyn Runtime + Send + Sync;
328
329#[tracing::instrument(level = "debug", skip_all)]
334pub async fn load_module(
335 engine: &Engine,
336 module_cache: &(dyn ModuleCache + Send + Sync),
337 input: ModuleInput<'_>,
338 on_progress: Option<ModuleLoadProgressReporter>,
339) -> Result<Module, crate::SpawnError> {
340 let wasm_hash = input.hash();
341
342 let result = if let Some(on_progress) = &on_progress {
343 module_cache
344 .load_with_progress(wasm_hash, engine, on_progress.clone())
345 .await
346 } else {
347 module_cache.load(wasm_hash, engine).await
348 };
349
350 match result {
351 Ok(module) => return Ok(module),
352 Err(CacheError::NotFound) => {}
353 Err(other) => {
354 tracing::warn!(
355 %wasm_hash,
356 error=&other as &dyn std::error::Error,
357 "Unable to load the cached module",
358 );
359 }
360 }
361
362 let res = if let Some(progress) = on_progress {
363 #[allow(unused_variables)]
364 let p = CompilationProgressCallback::new(move |p| {
365 progress.notify(ModuleLoadProgress::CompilingModule(p))
366 });
367 #[cfg(feature = "sys-default")]
368 {
369 if engine.is_sys() {
370 use wasmer::sys::NativeEngineExt;
371 engine.new_module_with_progress(input.wasm(), p)
372 } else {
373 Module::new(&engine, input.wasm())
374 }
375 }
376 #[cfg(not(feature = "sys-default"))]
377 {
378 Module::new(&engine, input.wasm())
379 }
380 } else {
381 Module::new(&engine, input.wasm())
382 };
383
384 let module = res.map_err(|err| crate::SpawnError::CompileError {
385 module_hash: wasm_hash,
386 error: err,
387 })?;
388
389 if let Err(e) = module_cache.save(wasm_hash, engine, &module).await {
391 tracing::warn!(
392 %wasm_hash,
393 error=&e as &dyn std::error::Error,
394 "Unable to cache the compiled module",
395 );
396 }
397
398 Ok(module)
399}
400
401#[derive(Debug, Default)]
402pub struct DefaultTty {
403 state: Mutex<WasiTtyState>,
404}
405
406impl TtyBridge for DefaultTty {
407 fn reset(&self) {
408 let mut state = self.state.lock().unwrap();
409 state.echo = false;
410 state.line_buffered = false;
411 state.line_feeds = false
412 }
413
414 fn tty_get(&self) -> WasiTtyState {
415 let state = self.state.lock().unwrap();
416 state.clone()
417 }
418
419 fn tty_set(&self, tty_state: WasiTtyState) {
420 let mut state = self.state.lock().unwrap();
421 *state = tty_state;
422 }
423}
424
425#[derive(Debug, Clone)]
426pub struct PluggableRuntime {
427 pub rt: Arc<dyn VirtualTaskManager>,
428 pub networking: DynVirtualNetworking,
429 pub http_client: Option<DynHttpClient>,
430 pub package_loader: Arc<dyn PackageLoader + Send + Sync>,
431 pub source: Arc<dyn Source + Send + Sync>,
432 pub engine: Engine,
433 pub module_cache: Arc<dyn ModuleCache + Send + Sync>,
434 pub tty: Option<Arc<dyn TtyBridge + Send + Sync>>,
435 #[cfg(feature = "journal")]
436 pub read_only_journals: Vec<Arc<DynReadableJournal>>,
437 #[cfg(feature = "journal")]
438 pub writable_journals: Vec<Arc<DynJournal>>,
439}
440
441impl PluggableRuntime {
442 pub fn new(rt: Arc<dyn VirtualTaskManager>) -> Self {
443 cfg_if::cfg_if! {
445 if #[cfg(feature = "host-vnet")] {
446 let networking = Arc::new(virtual_net::host::LocalNetworking::default());
447 } else {
448 let networking = Arc::new(virtual_net::UnsupportedVirtualNetworking::default());
449 }
450 }
451 let http_client =
452 crate::http::default_http_client().map(|client| Arc::new(client) as DynHttpClient);
453
454 let loader = UnsupportedPackageLoader;
455
456 let mut source = MultiSource::default();
457 if let Some(client) = &http_client {
458 source.add_source(BackendSource::new(
459 BackendSource::WASMER_PROD_ENDPOINT.parse().unwrap(),
460 client.clone(),
461 ));
462 }
463
464 Self {
465 rt,
466 networking,
467 http_client,
468 engine: Default::default(),
469 tty: None,
470 source: Arc::new(source),
471 package_loader: Arc::new(loader),
472 module_cache: Arc::new(module_cache::in_memory()),
473 #[cfg(feature = "journal")]
474 read_only_journals: Vec::new(),
475 #[cfg(feature = "journal")]
476 writable_journals: Vec::new(),
477 }
478 }
479
480 pub fn set_networking_implementation<I>(&mut self, net: I) -> &mut Self
481 where
482 I: VirtualNetworking + Sync,
483 {
484 self.networking = Arc::new(net);
485 self
486 }
487
488 pub fn set_engine(&mut self, engine: Engine) -> &mut Self {
489 self.engine = engine;
490 self
491 }
492
493 pub fn set_tty(&mut self, tty: Arc<dyn TtyBridge + Send + Sync>) -> &mut Self {
494 self.tty = Some(tty);
495 self
496 }
497
498 pub fn set_module_cache(
499 &mut self,
500 module_cache: impl ModuleCache + Send + Sync + 'static,
501 ) -> &mut Self {
502 self.module_cache = Arc::new(module_cache);
503 self
504 }
505
506 pub fn set_source(&mut self, source: impl Source + Send + 'static) -> &mut Self {
507 self.source = Arc::new(source);
508 self
509 }
510
511 pub fn set_package_loader(
512 &mut self,
513 package_loader: impl PackageLoader + 'static,
514 ) -> &mut Self {
515 self.package_loader = Arc::new(package_loader);
516 self
517 }
518
519 pub fn set_http_client(
520 &mut self,
521 client: impl HttpClient + Send + Sync + 'static,
522 ) -> &mut Self {
523 self.http_client = Some(Arc::new(client));
524 self
525 }
526
527 #[cfg(feature = "journal")]
528 pub fn add_read_only_journal(&mut self, journal: Arc<DynReadableJournal>) -> &mut Self {
529 self.read_only_journals.push(journal);
530 self
531 }
532
533 #[cfg(feature = "journal")]
534 pub fn add_writable_journal(&mut self, journal: Arc<DynJournal>) -> &mut Self {
535 self.writable_journals.push(journal);
536 self
537 }
538}
539
540impl Runtime for PluggableRuntime {
541 fn networking(&self) -> &DynVirtualNetworking {
542 &self.networking
543 }
544
545 fn http_client(&self) -> Option<&DynHttpClient> {
546 self.http_client.as_ref()
547 }
548
549 fn package_loader(&self) -> Arc<dyn PackageLoader + Send + Sync> {
550 Arc::clone(&self.package_loader)
551 }
552
553 fn source(&self) -> Arc<dyn Source + Send + Sync> {
554 Arc::clone(&self.source)
555 }
556
557 fn engine(&self) -> Engine {
558 self.engine.clone()
559 }
560
561 fn new_store(&self) -> wasmer::Store {
562 wasmer::Store::new(self.engine.clone())
563 }
564
565 fn task_manager(&self) -> &Arc<dyn VirtualTaskManager> {
566 &self.rt
567 }
568
569 fn tty(&self) -> Option<&(dyn TtyBridge + Send + Sync)> {
570 self.tty.as_deref()
571 }
572
573 fn module_cache(&self) -> Arc<dyn ModuleCache + Send + Sync> {
574 self.module_cache.clone()
575 }
576
577 #[cfg(feature = "journal")]
578 fn read_only_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynReadableJournal>> + 'a> {
579 Box::new(self.read_only_journals.iter().cloned())
580 }
581
582 #[cfg(feature = "journal")]
583 fn writable_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynJournal>> + 'a> {
584 Box::new(self.writable_journals.iter().cloned())
585 }
586
587 #[cfg(feature = "journal")]
588 fn active_journal(&self) -> Option<&DynJournal> {
589 self.writable_journals.iter().last().map(|a| a.as_ref())
590 }
591}
592
593#[derive(Clone, Debug)]
596pub struct OverriddenRuntime {
597 inner: Arc<DynRuntime>,
598 task_manager: Option<Arc<dyn VirtualTaskManager>>,
599 networking: Option<DynVirtualNetworking>,
600 http_client: Option<DynHttpClient>,
601 package_loader: Option<Arc<dyn PackageLoader + Send + Sync>>,
602 source: Option<Arc<dyn Source + Send + Sync>>,
603 engine: Option<Engine>,
604 module_cache: Option<Arc<dyn ModuleCache + Send + Sync>>,
605 tty: Option<Arc<dyn TtyBridge + Send + Sync>>,
606 #[cfg(feature = "journal")]
607 pub read_only_journals: Option<Vec<Arc<DynReadableJournal>>>,
608 #[cfg(feature = "journal")]
609 pub writable_journals: Option<Vec<Arc<DynJournal>>>,
610}
611
612impl OverriddenRuntime {
613 pub fn new(inner: Arc<DynRuntime>) -> Self {
614 Self {
615 inner,
616 task_manager: None,
617 networking: None,
618 http_client: None,
619 package_loader: None,
620 source: None,
621 engine: None,
622 module_cache: None,
623 tty: None,
624 #[cfg(feature = "journal")]
625 read_only_journals: None,
626 #[cfg(feature = "journal")]
627 writable_journals: None,
628 }
629 }
630
631 pub fn with_task_manager(mut self, task_manager: Arc<dyn VirtualTaskManager>) -> Self {
632 self.task_manager.replace(task_manager);
633 self
634 }
635
636 pub fn with_networking(mut self, networking: DynVirtualNetworking) -> Self {
637 self.networking.replace(networking);
638 self
639 }
640
641 pub fn with_http_client(mut self, http_client: DynHttpClient) -> Self {
642 self.http_client.replace(http_client);
643 self
644 }
645
646 pub fn with_package_loader(
647 mut self,
648 package_loader: Arc<dyn PackageLoader + Send + Sync>,
649 ) -> Self {
650 self.package_loader.replace(package_loader);
651 self
652 }
653
654 pub fn with_source(mut self, source: Arc<dyn Source + Send + Sync>) -> Self {
655 self.source.replace(source);
656 self
657 }
658
659 pub fn with_engine(mut self, engine: Engine) -> Self {
660 self.engine.replace(engine);
661 self
662 }
663
664 pub fn with_module_cache(mut self, module_cache: Arc<dyn ModuleCache + Send + Sync>) -> Self {
665 self.module_cache.replace(module_cache);
666 self
667 }
668
669 pub fn with_tty(mut self, tty: Arc<dyn TtyBridge + Send + Sync>) -> Self {
670 self.tty.replace(tty);
671 self
672 }
673
674 #[cfg(feature = "journal")]
675 pub fn with_read_only_journals(mut self, journals: Vec<Arc<DynReadableJournal>>) -> Self {
676 self.read_only_journals.replace(journals);
677 self
678 }
679
680 #[cfg(feature = "journal")]
681 pub fn with_writable_journals(mut self, journals: Vec<Arc<DynJournal>>) -> Self {
682 self.writable_journals.replace(journals);
683 self
684 }
685}
686
687impl Runtime for OverriddenRuntime {
688 fn networking(&self) -> &DynVirtualNetworking {
689 if let Some(net) = self.networking.as_ref() {
690 net
691 } else {
692 self.inner.networking()
693 }
694 }
695
696 fn task_manager(&self) -> &Arc<dyn VirtualTaskManager> {
697 if let Some(rt) = self.task_manager.as_ref() {
698 rt
699 } else {
700 self.inner.task_manager()
701 }
702 }
703
704 fn source(&self) -> Arc<dyn Source + Send + Sync> {
705 if let Some(source) = self.source.clone() {
706 source
707 } else {
708 self.inner.source()
709 }
710 }
711
712 fn package_loader(&self) -> Arc<dyn PackageLoader + Send + Sync> {
713 if let Some(loader) = self.package_loader.clone() {
714 loader
715 } else {
716 self.inner.package_loader()
717 }
718 }
719
720 fn module_cache(&self) -> Arc<dyn ModuleCache + Send + Sync> {
721 if let Some(cache) = self.module_cache.clone() {
722 cache
723 } else {
724 self.inner.module_cache()
725 }
726 }
727
728 fn engine(&self) -> Engine {
729 if let Some(engine) = self.engine.clone() {
730 engine
731 } else {
732 self.inner.engine()
733 }
734 }
735
736 fn new_store(&self) -> wasmer::Store {
737 if let Some(engine) = self.engine.clone() {
738 wasmer::Store::new(engine)
739 } else {
740 self.inner.new_store()
741 }
742 }
743
744 fn http_client(&self) -> Option<&DynHttpClient> {
745 if let Some(client) = self.http_client.as_ref() {
746 Some(client)
747 } else {
748 self.inner.http_client()
749 }
750 }
751
752 fn tty(&self) -> Option<&(dyn TtyBridge + Send + Sync)> {
753 if let Some(tty) = self.tty.as_ref() {
754 Some(tty.deref())
755 } else {
756 self.inner.tty()
757 }
758 }
759
760 #[cfg(feature = "journal")]
761 fn read_only_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynReadableJournal>> + 'a> {
762 if let Some(journals) = self.read_only_journals.as_ref() {
763 Box::new(journals.iter().cloned())
764 } else {
765 self.inner.read_only_journals()
766 }
767 }
768
769 #[cfg(feature = "journal")]
770 fn writable_journals<'a>(&'a self) -> Box<dyn Iterator<Item = Arc<DynJournal>> + 'a> {
771 if let Some(journals) = self.writable_journals.as_ref() {
772 Box::new(journals.iter().cloned())
773 } else {
774 self.inner.writable_journals()
775 }
776 }
777
778 #[cfg(feature = "journal")]
779 fn active_journal(&self) -> Option<&'_ DynJournal> {
780 if let Some(journals) = self.writable_journals.as_ref() {
781 journals.iter().last().map(|a| a.as_ref())
782 } else {
783 self.inner.active_journal()
784 }
785 }
786}