1#![allow(missing_docs, unused)]
2
3mod capabilities;
4mod package_source;
5mod runtime;
6mod target;
7mod wasi;
8
9use std::{
10 borrow::Cow,
11 collections::{BTreeMap, hash_map::DefaultHasher},
12 fmt::{Binary, Display},
13 fs::File,
14 hash::{BuildHasherDefault, Hash, Hasher},
15 io::{ErrorKind, LineWriter, Read, Write},
16 net::SocketAddr,
17 path::{Path, PathBuf},
18 str::FromStr,
19 sync::{Arc, Mutex},
20 time::{Duration, SystemTime, UNIX_EPOCH},
21};
22
23use anyhow::{Context, Error, anyhow, bail};
24use clap::{Parser, ValueEnum};
25use colored::Colorize;
26use futures::future::BoxFuture;
27use indicatif::{MultiProgress, ProgressBar};
28use once_cell::sync::Lazy;
29use tempfile::NamedTempFile;
30use url::Url;
31#[cfg(feature = "sys")]
32use wasmer::sys::NativeEngineExt;
33use wasmer::{
34 AsStoreMut, DeserializeError, Engine, Function, Imports, Instance, Module, RuntimeError, Store,
35 Type, TypedFunction, Value, wat2wasm,
36};
37
38use wasmer_types::{Features, target::Target};
39
40#[cfg(feature = "compiler")]
41use wasmer_compiler::ArtifactBuild;
42use wasmer_config::package::PackageSource;
43use wasmer_package::utils::from_disk;
44use wasmer_types::ModuleHash;
45
46#[cfg(feature = "journal")]
47use wasmer_wasix::journal::{LogFileJournal, SnapshotTrigger};
48use wasmer_wasix::{
49 Runtime, SpawnError, WasiError,
50 bin_factory::{BinaryPackage, BinaryPackageCommand},
51 journal::CompactingLogFileJournal,
52 runners::{
53 MappedCommand, MappedDirectory, Runner,
54 wasi::{RuntimeOrEngine, WasiRunner},
55 },
56 runtime::{
57 ModuleInput, OverriddenRuntime,
58 module_cache::{CacheError, HashedModuleData},
59 package_loader::PackageLoader,
60 resolver::QueryError,
61 task_manager::VirtualTaskManagerExt,
62 },
63};
64use webc::Container;
65use webc::metadata::Manifest;
66
67use crate::{
68 backend::RuntimeOptions,
69 commands::run::{target::TargetOnDisk, wasi::Wasi},
70 config::WasmerEnv,
71 error::PrettyError,
72 logging::Output,
73};
74
75use self::{
76 package_source::CliPackageSource, runtime::MonitoringRuntime, target::ExecutableTarget,
77};
78
79const TICK: Duration = Duration::from_millis(250);
80
81#[derive(Debug, Parser)]
83pub struct Run {
84 #[clap(flatten)]
85 env: WasmerEnv,
86 #[clap(flatten)]
87 rt: RuntimeOptions,
88 #[clap(flatten)]
89 wasi: crate::commands::run::Wasi,
90 #[clap(long = "stack-size")]
92 stack_size: Option<usize>,
93 #[clap(short, long, aliases = &["command", "command-name"])]
95 entrypoint: Option<String>,
96 #[clap(short, long)]
98 invoke: Option<String>,
99 #[clap(name = "COREDUMP_PATH", long)]
101 coredump_on_trap: Option<PathBuf>,
102 #[clap(long = "experimental-napi")]
104 experimental_napi: bool,
105 #[clap(value_parser = CliPackageSource::infer)]
107 input: CliPackageSource,
108 args: Vec<String>,
110}
111
112impl Run {
113 #[cfg(feature = "napi-v8")]
114 fn module_needs_napi(module: &Module) -> bool {
115 let (napi_version, napi_extension_version) = wasmer_napi::module_needs_napi(module);
116 napi_version.is_some() || napi_extension_version.is_some()
117 }
118
119 #[cfg(feature = "napi-v8")]
120 fn maybe_wrap_runtime_with_napi(
121 &self,
122 module: &Module,
123 runtime: Arc<dyn Runtime + Send + Sync>,
124 ) -> Result<Arc<dyn Runtime + Send + Sync>, Error> {
125 use anyhow::ensure;
126
127 if !Self::module_needs_napi(module) {
128 return Ok(runtime);
129 }
130 ensure!(
131 self.experimental_napi,
132 "This module imports N-API. Re-run with '--experimental-napi' to enable the experimental N-API runtime."
133 );
134
135 let hooks = wasmer_napi::NapiCtx::default().runtime_hooks();
136 Ok(Arc::new(
137 OverriddenRuntime::new(runtime)
138 .with_additional_imports({
139 let hooks = hooks.clone();
140 move |module, store| hooks.additional_imports(module, store)
141 })
142 .with_instance_setup(move |module, store, instance, imported_memory| {
143 hooks.configure_instance(module, store, instance, imported_memory)
144 }),
145 ))
146 }
147
148 #[cfg(feature = "napi-v8")]
149 fn configure_wasi_runner_for_napi(&self, module: &Module, runner: &mut WasiRunner) {
150 if Self::module_needs_napi(module) {
151 runner
152 .capabilities_mut()
153 .threading
154 .enable_asynchronous_threading = false;
155 }
156 }
157
158 #[cfg(not(feature = "napi-v8"))]
159 fn maybe_wrap_runtime_with_napi(
160 &self,
161 _module: &Module,
162 runtime: Arc<dyn Runtime + Send + Sync>,
163 ) -> Result<Arc<dyn Runtime + Send + Sync>, Error> {
164 Ok(runtime)
165 }
166
167 #[cfg(not(feature = "napi-v8"))]
168 fn configure_wasi_runner_for_napi(&self, _module: &Module, _runner: &mut WasiRunner) {}
169
170 #[cfg(feature = "wasm-c-api")]
171 fn module_uses_wasm_c_api(module: &Module) -> bool {
172 wasmer_c_api_imports::module_wasm_c_api_version_used(module).is_some()
173 }
174
175 #[cfg(feature = "wasm-c-api")]
176 fn maybe_wrap_runtime_with_wasm_c_api(
177 &self,
178 module: &Module,
179 runtime: Arc<dyn Runtime + Send + Sync>,
180 ) -> Result<Arc<dyn Runtime + Send + Sync>, Error> {
181 maybe_wrap_runtime_with_wasm_c_api(module, runtime)
182 }
183
184 #[cfg(not(feature = "wasm-c-api"))]
185 fn maybe_wrap_runtime_with_wasm_c_api(
186 &self,
187 _module: &Module,
188 runtime: Arc<dyn Runtime + Send + Sync>,
189 ) -> Result<Arc<dyn Runtime + Send + Sync>, Error> {
190 Ok(runtime)
191 }
192
193 fn maybe_wrap_runtime_for_module(
194 &self,
195 module: &Module,
196 runtime: Arc<dyn Runtime + Send + Sync>,
197 ) -> Result<Arc<dyn Runtime + Send + Sync>, Error> {
198 let runtime = self.maybe_wrap_runtime_with_napi(module, runtime)?;
199 self.maybe_wrap_runtime_with_wasm_c_api(module, runtime)
200 }
201
202 #[cfg(any(feature = "napi-v8", feature = "wasm-c-api"))]
203 fn resolve_wasi_command_module(
204 &self,
205 command_name: &str,
206 pkg: &BinaryPackage,
207 runtime: &Arc<dyn Runtime + Send + Sync>,
208 ) -> Result<Module, Error> {
209 let cmd = pkg.get_command(command_name).with_context(|| {
210 format!("Unable to get metadata for the \"{command_name}\" command")
211 })?;
212 Ok(runtime.resolve_module_sync(ModuleInput::Command(Cow::Borrowed(cmd)), None, None)?)
213 }
214
215 pub fn execute(self, output: Output) -> ! {
216 let result = self.execute_inner(output);
217 exit_with_wasi_exit_code(result);
218 }
219
220 #[tracing::instrument(level = "debug", name = "wasmer_run", skip_all)]
221 fn execute_inner(mut self, output: Output) -> Result<(), Error> {
222 self.print_option_warnings();
223
224 let pb = ProgressBar::new_spinner();
225 pb.set_draw_target(output.draw_target());
226 pb.enable_steady_tick(TICK);
227
228 pb.set_message("Initializing the WebAssembly VM");
229
230 let runtime = tokio::runtime::Builder::new_multi_thread()
231 .enable_all()
232 .build()?;
233 let handle = runtime.handle().clone();
234
235 let webc_version_var = std::env::var("WASMER_WEBC_VERSION");
238 let preferred_webc_version = match webc_version_var.as_deref() {
239 Ok("2") => webc::Version::V2,
240 Ok("3") | Err(_) => webc::Version::V3,
241 Ok(other) => {
242 bail!("unknown webc version: '{other}'");
243 }
244 };
245
246 let _guard = handle.enter();
247
248 let mut wasm_bytes: Option<Vec<u8>> = None;
250
251 tracing::info!("Input source: {:?}", self.input);
253 if let CliPackageSource::File(path) = &self.input {
254 tracing::info!("Input file path: {}", path.display());
255
256 let target = TargetOnDisk::from_file(path);
258 if let Ok(target) = target {
259 match target {
260 TargetOnDisk::WebAssemblyBinary => {
261 if let Ok(data) = std::fs::read(path) {
262 wasm_bytes = Some(data);
263 } else {
264 tracing::info!("Failed to read file: {}", path.display());
265 }
266 }
267 TargetOnDisk::Wat => match std::fs::read(path) {
268 Ok(data) => match wat2wasm(&data) {
269 Ok(wasm) => {
270 wasm_bytes = Some(wasm.to_vec());
271 }
272 Err(e) => {
273 tracing::info!(
274 "Failed to convert WAT to Wasm for {}: {e}",
275 path.display()
276 );
277 }
278 },
279 Err(e) => {
280 tracing::info!("Failed to read WAT file {}: {e}", path.display());
281 }
282 },
283 _ => {}
284 }
285 } else {
286 tracing::info!(
287 "Failed to read file for feature detection: {}",
288 path.display()
289 );
290 }
291 } else {
292 tracing::info!("Input is not a file, skipping WebAssembly feature detection");
293 }
294
295 let mut engine = match &wasm_bytes {
297 Some(wasm_bytes) => {
298 tracing::info!("Attempting to detect WebAssembly features from binary");
299
300 self.rt
301 .get_engine_for_module(wasm_bytes, &Target::default())?
302 }
303 None => {
304 if let CliPackageSource::Package(pkg_source) = &self.input {
306 tracing::info!("Checking package for WebAssembly features: {}", pkg_source);
307 self.rt.get_engine(&Target::default())?
308 } else {
309 tracing::info!("No feature detection possible, using default engine");
310 self.rt.get_engine(&Target::default())?
311 }
312 }
313 };
314
315 let engine_kind = engine.deterministic_id();
316 tracing::info!("Executing on backend {engine_kind:?}");
317
318 #[cfg(feature = "sys")]
319 if engine.is_sys()
320 && let Some(stack_size) = self.stack_size
321 {
322 wasmer_vm::set_stack_size(stack_size);
323 }
324
325 let engine = engine.clone();
326
327 let runtime = self.wasi.prepare_runtime(
328 engine,
329 &self.env,
330 &capabilities::get_capability_cache_path(&self.env, &self.input)?,
331 runtime,
332 preferred_webc_version,
333 self.rt.compiler_debug_dir.is_some(),
334 )?;
335
336 let monitoring_runtime = Arc::new(MonitoringRuntime::new(
339 runtime,
340 pb.clone(),
341 output.is_quiet_or_no_tty(),
342 ));
343 let runtime: Arc<dyn Runtime + Send + Sync> = monitoring_runtime.runtime.clone();
344 let monitoring_runtime: Arc<dyn Runtime + Send + Sync> = monitoring_runtime;
345
346 let target = self.input.resolve_target(&monitoring_runtime, &pb)?;
347
348 if let ExecutableTarget::Package(ref pkg) = target {
349 self.wasi
350 .volumes
351 .extend(pkg.additional_host_mapped_directories.clone());
352 }
353
354 pb.finish_and_clear();
355
356 let tty = runtime.tty().map(|tty| tty.tty_get());
358
359 let result = {
360 match target {
361 ExecutableTarget::WebAssembly {
362 module,
363 module_hash,
364 path,
365 } => self.execute_wasm(&path, module, module_hash, runtime.clone()),
366 ExecutableTarget::Package(pkg) => {
367 if let Some(cmd) = pkg.get_entrypoint_command()
369 && let Some(features) = cmd.wasm_features()
370 {
371 let backends = self.rt.get_available_backends()?;
373 let available_engines = backends
374 .iter()
375 .map(|b| b.to_string())
376 .collect::<Vec<_>>()
377 .join(", ");
378
379 let filtered_backends = RuntimeOptions::filter_backends_by_features(
380 backends.clone(),
381 &features,
382 &Target::default(),
383 );
384
385 if let Some(backend) = filtered_backends.first() {
386 let engine_id = backend.to_string();
387
388 if let Ok(new_engine) = backend.get_engine(&Target::default(), &self.rt)
390 {
391 tracing::info!(
392 "The command '{}' requires to run the Wasm module with the features {:?}. The backends available are {}. Choosing {}.",
393 cmd.name(),
394 features,
395 available_engines,
396 engine_id
397 );
398 let capability_cache_path =
400 capabilities::get_capability_cache_path(
401 &self.env,
402 &self.input,
403 )?;
404 let new_runtime = self.wasi.prepare_runtime(
405 new_engine,
406 &self.env,
407 &capability_cache_path,
408 tokio::runtime::Builder::new_multi_thread()
409 .enable_all()
410 .build()?,
411 preferred_webc_version,
412 self.rt.compiler_debug_dir.is_some(),
413 )?;
414
415 let new_runtime = Arc::new(MonitoringRuntime::new(
416 new_runtime,
417 pb.clone(),
418 output.is_quiet_or_no_tty(),
419 ));
420 return self.execute_webc(&pkg, new_runtime);
421 }
422 }
423 }
424 self.execute_webc(&pkg, monitoring_runtime)
425 }
426 }
427 };
428
429 if let Some(state) = tty
431 && let Some(tty) = runtime.tty()
432 {
433 tty.tty_set(state);
434 }
435
436 if let Err(e) = &result {
437 self.maybe_save_coredump(e);
438 }
439
440 result
441 }
442
443 #[tracing::instrument(skip_all)]
444 fn execute_wasm(
445 &self,
446 path: &Path,
447 module: Module,
448 module_hash: ModuleHash,
449 runtime: Arc<dyn Runtime + Send + Sync>,
450 ) -> Result<(), Error> {
451 if wasmer_wasix::is_wasi_module(&module) || wasmer_wasix::is_wasix_module(&module) {
452 self.execute_wasi_module(path, module, module_hash, runtime)
453 } else {
454 self.execute_pure_wasm_module(&module)
455 }
456 }
457
458 #[tracing::instrument(skip_all)]
459 fn execute_webc(
460 &self,
461 pkg: &BinaryPackage,
462 runtime: Arc<dyn Runtime + Send + Sync>,
463 ) -> Result<(), Error> {
464 let id = match self.entrypoint.as_deref() {
465 Some(cmd) => cmd,
466 None => pkg.infer_entrypoint()?,
467 };
468 let cmd = pkg
469 .get_command(id)
470 .with_context(|| format!("Unable to get metadata for the \"{id}\" command"))?;
471
472 let uses = self.load_injected_packages(&runtime)?;
473
474 if WasiRunner::can_run_command(cmd.metadata())? {
475 self.run_wasi(id, pkg, uses, runtime)
476 } else {
477 bail!(
478 "Unable to find a runner that supports \"{}\"",
479 cmd.metadata().runner
480 );
481 }
482 }
483
484 #[tracing::instrument(level = "debug", skip_all)]
485 fn load_injected_packages(
486 &self,
487 runtime: &Arc<dyn Runtime + Send + Sync>,
488 ) -> Result<Vec<BinaryPackage>, Error> {
489 let mut dependencies = Vec::new();
490
491 for name in &self.wasi.uses {
492 let specifier = name
493 .parse::<PackageSource>()
494 .with_context(|| format!("Unable to parse \"{name}\" as a package specifier"))?;
495 let pkg = {
496 let specifier = specifier.clone();
497 let inner_runtime = runtime.clone();
498 runtime
499 .task_manager()
500 .spawn_and_block_on(async move {
501 BinaryPackage::from_registry(&specifier, inner_runtime.as_ref()).await
502 })
503 .with_context(|| format!("Unable to load \"{name}\""))??
504 };
505 dependencies.push(pkg);
506 }
507
508 Ok(dependencies)
509 }
510
511 fn run_wasi(
512 &self,
513 command_name: &str,
514 pkg: &BinaryPackage,
515 uses: Vec<BinaryPackage>,
516 runtime: Arc<dyn Runtime + Send + Sync>,
517 ) -> Result<(), Error> {
518 #[cfg(feature = "napi-v8")]
519 let (module, runtime) = {
520 let module = self.resolve_wasi_command_module(command_name, pkg, &runtime)?;
521 let runtime = self.maybe_wrap_runtime_for_module(&module, runtime)?;
522 (module, runtime)
523 };
524
525 #[cfg(all(not(feature = "napi-v8"), feature = "wasm-c-api"))]
526 let runtime = {
527 let module = self.resolve_wasi_command_module(command_name, pkg, &runtime)?;
528 self.maybe_wrap_runtime_for_module(&module, runtime)?
529 };
530
531 let mut runner = self.build_wasi_runner(&runtime, true)?;
533 #[cfg(feature = "napi-v8")]
534 self.configure_wasi_runner_for_napi(&module, &mut runner);
535 Runner::run_command(&mut runner, command_name, pkg, runtime)
536 }
537
538 #[tracing::instrument(skip_all)]
539 fn execute_pure_wasm_module(&self, module: &Module) -> Result<(), Error> {
540 let mut store = self.rt.get_store()?;
543 let imports = Imports::default();
544 let instance = Instance::new(&mut store, module, &imports)
545 .context("Unable to instantiate the WebAssembly module")?;
546
547 let entry_function = match &self.invoke {
548 Some(entry) => {
549 instance.exports
550 .get_function(entry)
551 .with_context(|| format!("The module doesn't export a function named \"{entry}\""))?
552 },
553 None => {
554 instance.exports.get_function("_start")
555 .context("The module doesn't export a \"_start\" function. Either implement it or specify an entry function with --invoke")?
556 }
557 };
558
559 let result = invoke_function(&instance, &mut store, entry_function, &self.args)?;
560
561 match result {
562 Ok(return_values) => {
563 println!(
564 "{}",
565 return_values
566 .iter()
567 .map(|val| val.to_string())
568 .collect::<Vec<String>>()
569 .join(" ")
570 );
571 Ok(())
572 }
573 Err(err) => {
574 bail!("{}", err.display(&mut store));
575 }
576 }
577 }
578
579 fn build_wasi_runner(
580 &self,
581 runtime: &Arc<dyn Runtime + Send + Sync>,
582 is_wasix: bool,
583 ) -> Result<WasiRunner, anyhow::Error> {
584 let packages = self.load_injected_packages(runtime)?;
585
586 let mut runner = WasiRunner::new();
587
588 let (is_home_mapped, mapped_directories) = self.wasi.build_mapped_directories(is_wasix)?;
589
590 runner
591 .with_args(&self.args)
592 .with_injected_packages(packages)
593 .with_envs(self.wasi.env_vars.clone())
594 .with_mapped_host_commands(self.wasi.build_mapped_commands()?)
595 .with_mapped_directories(mapped_directories)
596 .with_home_mapped(is_home_mapped)
597 .with_forward_host_env(self.wasi.forward_host_env)
598 .with_capabilities(self.wasi.capabilities());
599
600 if let Some(cwd) = self.wasi.cwd.as_ref() {
601 if !cwd.starts_with("/") {
602 bail!("The argument to --cwd must be an absolute path");
603 }
604 runner.with_current_dir(cwd.clone());
605 }
606
607 if let Some(ref entry_function) = self.invoke {
608 runner.with_entry_function(entry_function);
609 }
610
611 #[cfg(feature = "journal")]
612 {
613 for trigger in self.wasi.snapshot_on.iter().cloned() {
614 runner.with_snapshot_trigger(trigger);
615 }
616 if self.wasi.snapshot_on.is_empty() && !self.wasi.writable_journals.is_empty() {
617 runner.with_default_snapshot_triggers();
618 }
619 if let Some(period) = self.wasi.snapshot_interval {
620 if self.wasi.writable_journals.is_empty() {
621 return Err(anyhow::format_err!(
622 "If you specify a snapshot interval then you must also specify a writable journal file"
623 ));
624 }
625 runner.with_snapshot_interval(Duration::from_millis(period));
626 }
627 if self.wasi.stop_after_snapshot {
628 runner.with_stop_running_after_snapshot(true);
629 }
630 let (r, w) = self.wasi.build_journals()?;
631 for journal in r {
632 runner.with_read_only_journal(journal);
633 }
634 for journal in w {
635 runner.with_writable_journal(journal);
636 }
637 runner.with_skip_stdio_during_bootstrap(self.wasi.skip_stdio_during_bootstrap);
638 }
639
640 Ok(runner)
641 }
642
643 #[tracing::instrument(skip_all)]
644 fn execute_wasi_module(
645 &self,
646 wasm_path: &Path,
647 module: Module,
648 module_hash: ModuleHash,
649 runtime: Arc<dyn Runtime + Send + Sync>,
650 ) -> Result<(), Error> {
651 let program_name = wasm_path.display().to_string();
652 let runtime = self.maybe_wrap_runtime_for_module(&module, runtime)?;
653
654 let mut runner =
655 self.build_wasi_runner(&runtime, wasmer_wasix::is_wasix_module(&module))?;
656 self.configure_wasi_runner_for_napi(&module, &mut runner);
657 runner.run_wasm(
658 RuntimeOrEngine::Runtime(runtime),
659 &program_name,
660 module,
661 module_hash,
662 )
663 }
664
665 #[allow(unused_variables)]
666 fn maybe_save_coredump(&self, e: &Error) {
667 #[cfg(feature = "coredump")]
668 if let Some(coredump) = &self.coredump_on_trap
669 && let Err(e) = generate_coredump(e, self.input.to_string(), coredump)
670 {
671 tracing::warn!(
672 error = &*e as &dyn std::error::Error,
673 coredump_path=%coredump.display(),
674 "Unable to generate a coredump",
675 );
676 }
677 }
678
679 fn print_option_warnings(&self) {
680 if !self.wasi.mapped_dirs.is_empty() {
681 eprintln!(
682 "{}The `{}` option is deprecated and will be removed in the next major release. Please use `{}` instead.",
683 "warning: ".yellow(),
684 "--mapdir".yellow(),
685 "--volume".green()
686 );
687 }
688 if !self.wasi.pre_opened_directories.is_empty() {
689 eprintln!(
690 "{}The `{}` option is deprecated and will be removed in the next major release. Please use `{}` instead.",
691 "warning: ".yellow(),
692 "--dir".yellow(),
693 "--volume".green()
694 );
695 }
696 }
697}
698
699#[cfg(feature = "wasm-c-api")]
700fn maybe_wrap_runtime_with_wasm_c_api(
701 module: &Module,
702 runtime: Arc<dyn Runtime + Send + Sync>,
703) -> Result<Arc<dyn Runtime + Send + Sync>, Error> {
704 if !Run::module_uses_wasm_c_api(module) {
705 return Ok(runtime);
706 }
707
708 let runtime_for_resolver = runtime.clone();
709 let hooks =
710 wasmer_c_api_imports::WasmCapiRuntimeHooks::new().with_resolve_module_sync(move |bytes| {
711 runtime_for_resolver
712 .resolve_module_sync(ModuleInput::Bytes(Cow::Owned(bytes)), None, None)
713 .context("failed to resolve Wasm C API module")
714 });
715 Ok(Arc::new(
716 OverriddenRuntime::new(runtime)
717 .with_additional_imports({
718 let hooks = hooks.clone();
719 move |module, store| hooks.additional_imports(module, store)
720 })
721 .with_instance_setup(move |module, store, instance, imported_memory| {
722 hooks.configure_instance(module, store, instance, imported_memory)
723 }),
724 ))
725}
726
727fn invoke_function(
728 instance: &Instance,
729 store: &mut Store,
730 func: &Function,
731 args: &[String],
732) -> anyhow::Result<Result<Box<[Value]>, RuntimeError>> {
733 let func_ty = func.ty(store);
734 let required_arguments = func_ty.params().len();
735 let provided_arguments = args.len();
736
737 anyhow::ensure!(
738 required_arguments == provided_arguments,
739 "Function expected {required_arguments} arguments, but received {provided_arguments}"
740 );
741
742 let invoke_args = args
743 .iter()
744 .zip(func_ty.params().iter())
745 .map(|(arg, param_type)| {
746 parse_value(arg, *param_type)
747 .with_context(|| format!("Unable to convert {arg:?} to {param_type:?}"))
748 })
749 .collect::<Result<Vec<_>, _>>()?;
750
751 Ok(func.call(store, &invoke_args))
752}
753
754fn parse_value(s: &str, ty: wasmer_types::Type) -> Result<Value, Error> {
755 let value = match ty {
756 Type::I32 => Value::I32(s.parse()?),
757 Type::I64 => Value::I64(s.parse()?),
758 Type::F32 => Value::F32(s.parse()?),
759 Type::F64 => Value::F64(s.parse()?),
760 Type::V128 => Value::V128(s.parse()?),
761 _ => bail!("There is no known conversion from {s:?} to {ty:?}"),
762 };
763 Ok(value)
764}
765
766#[cfg(feature = "coredump")]
767fn generate_coredump(err: &Error, source_name: String, coredump_path: &Path) -> Result<(), Error> {
768 let err: &wasmer::RuntimeError = match err.downcast_ref() {
769 Some(e) => e,
770 None => {
771 log::warn!("no runtime error found to generate coredump with");
772 return Ok(());
773 }
774 };
775
776 let mut coredump_builder =
777 wasm_coredump_builder::CoredumpBuilder::new().executable_name(&source_name);
778
779 let mut thread_builder = wasm_coredump_builder::ThreadBuilder::new().thread_name("main");
780
781 for frame in err.trace() {
782 let coredump_frame = wasm_coredump_builder::FrameBuilder::new()
783 .codeoffset(frame.func_offset() as u32)
784 .funcidx(frame.func_index())
785 .build();
786 thread_builder.add_frame(coredump_frame);
787 }
788
789 coredump_builder.add_thread(thread_builder.build());
790
791 let coredump = coredump_builder
792 .serialize()
793 .map_err(Error::msg)
794 .context("Coredump serializing failed")?;
795
796 std::fs::write(coredump_path, &coredump).with_context(|| {
797 format!(
798 "Unable to save the coredump to \"{}\"",
799 coredump_path.display()
800 )
801 })?;
802
803 Ok(())
804}
805
806fn exit_with_wasi_exit_code(result: Result<(), Error>) -> ! {
809 let exit_code = match result {
810 Ok(_) => 0,
811 Err(error) => {
812 match error.chain().find_map(get_exit_code) {
813 Some(exit_code) => exit_code.raw(),
814 None => {
815 eprintln!("{:?}", PrettyError::new(error));
816 1
818 }
819 }
820 }
821 };
822
823 std::io::stdout().flush().ok();
824 std::io::stderr().flush().ok();
825
826 std::process::exit(exit_code);
827}
828
829fn get_exit_code(
830 error: &(dyn std::error::Error + 'static),
831) -> Option<wasmer_wasix::types::wasi::ExitCode> {
832 if let Some(WasiError::Exit(exit_code)) = error.downcast_ref() {
833 return Some(*exit_code);
834 }
835 if let Some(error) = error.downcast_ref::<wasmer_wasix::WasiRuntimeError>() {
836 return error.as_exit_code();
837 }
838
839 None
840}
841
842#[cfg(all(test, feature = "wasm-c-api"))]
843mod tests {
844 use std::sync::Arc;
845
846 use super::*;
847 use wasmer_wasix::{
848 PluggableRuntime, WasiError, runtime::task_manager::tokio::TokioTaskManager,
849 };
850
851 fn wasm_c_api_guest() -> Vec<u8> {
852 wat2wasm(
853 br#"(module
854 (import "wasi_snapshot_preview1" "proc_exit" (func $proc_exit (param i32)))
855 (import "wasm_c_api_v0" "wasm_engine_new" (func $wasm_engine_new (result i32)))
856 (import "wasm_c_api_v0" "wasm_store_new" (func $wasm_store_new (param i32) (result i32)))
857 (import "wasm_c_api_v0" "wasm_module_validate" (func $wasm_module_validate (param i32 i32) (result i32)))
858 (import "wasm_c_api_v0" "wasm_module_new" (func $wasm_module_new (param i32 i32) (result i32)))
859
860 (memory (export "memory") 1)
861 (data (i32.const 16) "\08\00\00\00\20\00\00\00")
862 (data (i32.const 32) "\00asm\01\00\00\00")
863
864 (func (export "_start")
865 (local $engine i32)
866 (local $store i32)
867 (local $module i32)
868
869 (local.set $engine (call $wasm_engine_new))
870 (if (i32.eqz (local.get $engine))
871 (then (call $proc_exit (i32.const 10))))
872
873 (local.set $store (call $wasm_store_new (local.get $engine)))
874 (if (i32.eqz (local.get $store))
875 (then (call $proc_exit (i32.const 11))))
876
877 (if (i32.eqz (call $wasm_module_validate (local.get $store) (i32.const 16)))
878 (then (call $proc_exit (i32.const 12))))
879
880 (local.set $module (call $wasm_module_new (local.get $store) (i32.const 16)))
881 (if (i32.eqz (local.get $module))
882 (then (call $proc_exit (i32.const 13))))
883 )
884 )"#,
885 )
886 .expect("guest wat parses")
887 .into_owned()
888 }
889
890 #[test]
891 fn cli_wasm_c_api_runtime_wrapper_runs_wasix_guest() {
892 let wasm = wasm_c_api_guest();
893 let store = Store::default();
894 let module = Module::new(&store, &wasm).expect("guest module compiles");
895 assert!(Run::module_uses_wasm_c_api(&module));
896
897 let tokio_runtime = tokio::runtime::Builder::new_multi_thread()
898 .enable_all()
899 .build()
900 .expect("tokio runtime starts");
901 let _guard = tokio_runtime.enter();
902 let mut base_runtime = PluggableRuntime::new(Arc::new(TokioTaskManager::new(
903 tokio_runtime.handle().clone(),
904 )));
905 base_runtime.set_engine(store.engine().clone());
906
907 let runtime = maybe_wrap_runtime_with_wasm_c_api(&module, Arc::new(base_runtime))
908 .expect("runtime wrapper is installed");
909 let mut import_store = runtime.new_store();
910 let mut import_store_mut = import_store.as_store_mut();
911 let imports = runtime
912 .additional_imports(&module, &mut import_store_mut)
913 .expect("wasm c api imports are created");
914 assert!(imports.exists("wasm_c_api_v0", "wasm_engine_new"));
915
916 let result = WasiRunner::new().run_wasm(
917 RuntimeOrEngine::Runtime(runtime),
918 "wasm-c-api-cli-smoke",
919 module,
920 ModuleHash::new(&wasm),
921 );
922
923 match result {
924 Ok(()) => {}
925 Err(err) => {
926 if let Some(WasiError::Exit(code)) = err.downcast_ref::<WasiError>() {
927 panic!("guest exited with status {code}");
928 }
929 panic!("guest failed: {err:?}");
930 }
931 }
932 }
933}