wasmer_cli/commands/run/
mod.rs

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/// The unstable `wasmer run` subcommand.
82#[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    /// Set the default stack size (default is 1048576)
91    #[clap(long = "stack-size")]
92    stack_size: Option<usize>,
93    /// The entrypoint module for webc packages.
94    #[clap(short, long, aliases = &["command", "command-name"])]
95    entrypoint: Option<String>,
96    /// The function to invoke.
97    #[clap(short, long)]
98    invoke: Option<String>,
99    /// Generate a coredump at this path if a WebAssembly trap occurs
100    #[clap(name = "COREDUMP_PATH", long)]
101    coredump_on_trap: Option<PathBuf>,
102    /// Enable experimental N-API imports for modules that require them
103    #[clap(long = "experimental-napi")]
104    experimental_napi: bool,
105    /// The file, URL, or package to run.
106    #[clap(value_parser = CliPackageSource::infer)]
107    input: CliPackageSource,
108    /// Command-line arguments passed to the package
109    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        // Check for the preferred webc version.
236        // Default to v3.
237        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        // Get the input file path
249        let mut wasm_bytes: Option<Vec<u8>> = None;
250
251        // Try to detect WebAssembly features before selecting a backend
252        tracing::info!("Input source: {:?}", self.input);
253        if let CliPackageSource::File(path) = &self.input {
254            tracing::info!("Input file path: {}", path.display());
255
256            // Try to read and detect any file that exists, regardless of extension
257            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        // Get engine with feature-based backend selection if possible
296        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                // No WebAssembly file available for analysis, check if we have a webc package
305                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        // This is a slow operation, so let's temporarily wrap the runtime with
337        // something that displays progress
338        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        // push the TTY state so we can restore it after the program finishes
357        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                    // Check if we should update the engine based on the WebC package features
368                    if let Some(cmd) = pkg.get_entrypoint_command()
369                        && let Some(features) = cmd.wasm_features()
370                    {
371                        // Get the right engine for these features
372                        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                            // Get a new engine that's compatible with the required features
389                            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                                // Create a new runtime with the updated engine
399                                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        // restore the TTY state as the execution may have changed it
430        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        // Assume webcs are always WASIX
532        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        /// The rest of the execution happens in the main thread, so we can create the
541        /// store here.
542        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
806/// Exit the current process, using the WASI exit code if the error contains
807/// one.
808fn 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                    // Something else happened
817                    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}