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