wasmtime_cli/commands/
run.rs

1//! The module that implements the `wasmtime run` command.
2
3#![cfg_attr(
4    not(feature = "component-model"),
5    allow(irrefutable_let_patterns, unreachable_patterns)
6)]
7
8use crate::common::{Profile, RunCommon, RunTarget};
9use anyhow::{Context as _, Error, Result, anyhow, bail};
10use clap::Parser;
11use std::ffi::OsString;
12use std::path::{Path, PathBuf};
13use std::sync::{Arc, Mutex};
14use std::thread;
15use wasi_common::sync::{Dir, TcpListener, WasiCtxBuilder, ambient_authority};
16use wasmtime::{Engine, Func, Module, Store, StoreLimits, Val, ValType};
17use wasmtime_wasi::{WasiCtxView, WasiView};
18
19#[cfg(feature = "wasi-config")]
20use wasmtime_wasi_config::{WasiConfig, WasiConfigVariables};
21#[cfg(feature = "wasi-http")]
22use wasmtime_wasi_http::{
23    DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS, DEFAULT_OUTGOING_BODY_CHUNK_SIZE, WasiHttpCtx,
24};
25#[cfg(feature = "wasi-keyvalue")]
26use wasmtime_wasi_keyvalue::{WasiKeyValue, WasiKeyValueCtx, WasiKeyValueCtxBuilder};
27#[cfg(feature = "wasi-nn")]
28use wasmtime_wasi_nn::wit::WasiNnView;
29#[cfg(feature = "wasi-threads")]
30use wasmtime_wasi_threads::WasiThreadsCtx;
31#[cfg(feature = "wasi-tls")]
32use wasmtime_wasi_tls::{WasiTls, WasiTlsCtx};
33
34fn parse_preloads(s: &str) -> Result<(String, PathBuf)> {
35    let parts: Vec<&str> = s.splitn(2, '=').collect();
36    if parts.len() != 2 {
37        bail!("must contain exactly one equals character ('=')");
38    }
39    Ok((parts[0].into(), parts[1].into()))
40}
41
42/// Runs a WebAssembly module
43#[derive(Parser)]
44pub struct RunCommand {
45    #[command(flatten)]
46    #[expect(missing_docs, reason = "don't want to mess with clap doc-strings")]
47    pub run: RunCommon,
48
49    /// The name of the function to run
50    #[arg(long, value_name = "FUNCTION")]
51    pub invoke: Option<String>,
52
53    /// Load the given WebAssembly module before the main module
54    #[arg(
55        long = "preload",
56        number_of_values = 1,
57        value_name = "NAME=MODULE_PATH",
58        value_parser = parse_preloads,
59    )]
60    pub preloads: Vec<(String, PathBuf)>,
61
62    /// Override the value of `argv[0]`, typically the name of the executable of
63    /// the application being run.
64    ///
65    /// This can be useful to pass in situations where a CLI tool is being
66    /// executed that dispatches its functionality on the value of `argv[0]`
67    /// without needing to rename the original wasm binary.
68    #[arg(long)]
69    pub argv0: Option<String>,
70
71    /// The WebAssembly module to run and arguments to pass to it.
72    ///
73    /// Arguments passed to the wasm module will be configured as WASI CLI
74    /// arguments unless the `--invoke` CLI argument is passed in which case
75    /// arguments will be interpreted as arguments to the function specified.
76    #[arg(value_name = "WASM", trailing_var_arg = true, required = true)]
77    pub module_and_args: Vec<OsString>,
78}
79
80enum CliLinker {
81    Core(wasmtime::Linker<Host>),
82    #[cfg(feature = "component-model")]
83    Component(wasmtime::component::Linker<Host>),
84}
85
86impl RunCommand {
87    /// Executes the command.
88    pub fn execute(mut self) -> Result<()> {
89        self.run.common.init_logging()?;
90
91        let mut config = self.run.common.config(None)?;
92        config.async_support(true);
93
94        if self.run.common.wasm.timeout.is_some() {
95            config.epoch_interruption(true);
96        }
97        match self.run.profile {
98            Some(Profile::Native(s)) => {
99                config.profiler(s);
100            }
101            Some(Profile::Guest { .. }) => {
102                // Further configured down below as well.
103                config.epoch_interruption(true);
104            }
105            None => {}
106        }
107
108        let engine = Engine::new(&config)?;
109
110        // Read the wasm module binary either as `*.wat` or a raw binary.
111        let main = self
112            .run
113            .load_module(&engine, self.module_and_args[0].as_ref())?;
114
115        // Validate coredump-on-trap argument
116        if let Some(path) = &self.run.common.debug.coredump {
117            if path.contains("%") {
118                bail!("the coredump-on-trap path does not support patterns yet.")
119            }
120        }
121
122        let mut linker = match &main {
123            RunTarget::Core(_) => CliLinker::Core(wasmtime::Linker::new(&engine)),
124            #[cfg(feature = "component-model")]
125            RunTarget::Component(_) => {
126                CliLinker::Component(wasmtime::component::Linker::new(&engine))
127            }
128        };
129        if let Some(enable) = self.run.common.wasm.unknown_exports_allow {
130            match &mut linker {
131                CliLinker::Core(l) => {
132                    l.allow_unknown_exports(enable);
133                }
134                #[cfg(feature = "component-model")]
135                CliLinker::Component(_) => {
136                    bail!("--allow-unknown-exports not supported with components");
137                }
138            }
139        }
140
141        let host = Host {
142            #[cfg(feature = "wasi-http")]
143            wasi_http_outgoing_body_buffer_chunks: self
144                .run
145                .common
146                .wasi
147                .http_outgoing_body_buffer_chunks,
148            #[cfg(feature = "wasi-http")]
149            wasi_http_outgoing_body_chunk_size: self.run.common.wasi.http_outgoing_body_chunk_size,
150            ..Default::default()
151        };
152
153        let mut store = Store::new(&engine, host);
154        self.populate_with_wasi(&mut linker, &mut store, &main)?;
155
156        store.data_mut().limits = self.run.store_limits();
157        store.limiter(|t| &mut t.limits);
158
159        // If fuel has been configured, we want to add the configured
160        // fuel amount to this store.
161        if let Some(fuel) = self.run.common.wasm.fuel {
162            store.set_fuel(fuel)?;
163        }
164
165        // Always run the module asynchronously to ensure that the module can be
166        // interrupted, even if it is blocking on I/O or a timeout or something.
167        let runtime = tokio::runtime::Builder::new_multi_thread()
168            .enable_time()
169            .enable_io()
170            .build()?;
171
172        let dur = self
173            .run
174            .common
175            .wasm
176            .timeout
177            .unwrap_or(std::time::Duration::MAX);
178        let result = runtime.block_on(async {
179            tokio::time::timeout(dur, async {
180                let mut profiled_modules: Vec<(String, Module)> = Vec::new();
181                if let RunTarget::Core(m) = &main {
182                    profiled_modules.push(("".to_string(), m.clone()));
183                }
184
185                // Load the preload wasm modules.
186                for (name, path) in self.preloads.iter() {
187                    // Read the wasm module binary either as `*.wat` or a raw binary
188                    let preload_target = self.run.load_module(&engine, path)?;
189                    let preload_module = match preload_target {
190                        RunTarget::Core(m) => m,
191                        #[cfg(feature = "component-model")]
192                        RunTarget::Component(_) => {
193                            bail!("components cannot be loaded with `--preload`")
194                        }
195                    };
196                    profiled_modules.push((name.to_string(), preload_module.clone()));
197
198                    // Add the module's functions to the linker.
199                    match &mut linker {
200                        #[cfg(feature = "cranelift")]
201                        CliLinker::Core(linker) => {
202                            linker
203                                .module_async(&mut store, name, &preload_module)
204                                .await
205                                .context(format!(
206                                    "failed to process preload `{}` at `{}`",
207                                    name,
208                                    path.display()
209                                ))?;
210                        }
211                        #[cfg(not(feature = "cranelift"))]
212                        CliLinker::Core(_) => {
213                            bail!("support for --preload disabled at compile time");
214                        }
215                        #[cfg(feature = "component-model")]
216                        CliLinker::Component(_) => {
217                            bail!("--preload cannot be used with components");
218                        }
219                    }
220                }
221
222                self.load_main_module(&mut store, &mut linker, &main, profiled_modules)
223                    .await
224                    .with_context(|| {
225                        format!(
226                            "failed to run main module `{}`",
227                            self.module_and_args[0].to_string_lossy()
228                        )
229                    })
230            })
231            .await
232        });
233
234        // Load the main wasm module.
235        match result.unwrap_or_else(|elapsed| {
236            Err(anyhow::Error::from(wasmtime::Trap::Interrupt))
237                .with_context(|| format!("timed out after {elapsed}"))
238        }) {
239            Ok(()) => (),
240            Err(e) => {
241                // Exit the process if Wasmtime understands the error;
242                // otherwise, fall back on Rust's default error printing/return
243                // code.
244                if store.data().legacy_p1_ctx.is_some() {
245                    return Err(wasi_common::maybe_exit_on_error(e));
246                } else if store.data().wasip1_ctx.is_some() {
247                    if let Some(exit) = e.downcast_ref::<wasmtime_wasi::I32Exit>() {
248                        std::process::exit(exit.0);
249                    }
250                }
251                if e.is::<wasmtime::Trap>() {
252                    eprintln!("Error: {e:?}");
253                    cfg_if::cfg_if! {
254                        if #[cfg(unix)] {
255                            std::process::exit(rustix::process::EXIT_SIGNALED_SIGABRT);
256                        } else if #[cfg(windows)] {
257                            // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/abort?view=vs-2019
258                            std::process::exit(3);
259                        }
260                    }
261                }
262                return Err(e);
263            }
264        }
265
266        Ok(())
267    }
268
269    fn compute_argv(&self) -> Result<Vec<String>> {
270        let mut result = Vec::new();
271
272        for (i, arg) in self.module_and_args.iter().enumerate() {
273            // For argv[0], which is the program name. Only include the base
274            // name of the main wasm module, to avoid leaking path information.
275            let arg = if i == 0 {
276                match &self.argv0 {
277                    Some(s) => s.as_ref(),
278                    None => Path::new(arg).components().next_back().unwrap().as_os_str(),
279                }
280            } else {
281                arg.as_ref()
282            };
283            result.push(
284                arg.to_str()
285                    .ok_or_else(|| anyhow!("failed to convert {arg:?} to utf-8"))?
286                    .to_string(),
287            );
288        }
289
290        Ok(result)
291    }
292
293    fn setup_epoch_handler(
294        &self,
295        store: &mut Store<Host>,
296        main_target: &RunTarget,
297        profiled_modules: Vec<(String, Module)>,
298    ) -> Result<Box<dyn FnOnce(&mut Store<Host>)>> {
299        if let Some(Profile::Guest { path, interval }) = &self.run.profile {
300            #[cfg(feature = "profiling")]
301            return Ok(self.setup_guest_profiler(
302                store,
303                main_target,
304                profiled_modules,
305                path,
306                *interval,
307            ));
308            #[cfg(not(feature = "profiling"))]
309            {
310                let _ = (profiled_modules, path, interval, main_target);
311                bail!("support for profiling disabled at compile time");
312            }
313        }
314
315        if let Some(timeout) = self.run.common.wasm.timeout {
316            store.set_epoch_deadline(1);
317            let engine = store.engine().clone();
318            thread::spawn(move || {
319                thread::sleep(timeout);
320                engine.increment_epoch();
321            });
322        }
323
324        Ok(Box::new(|_store| {}))
325    }
326
327    #[cfg(feature = "profiling")]
328    fn setup_guest_profiler(
329        &self,
330        store: &mut Store<Host>,
331        main_target: &RunTarget,
332        profiled_modules: Vec<(String, Module)>,
333        path: &str,
334        interval: std::time::Duration,
335    ) -> Box<dyn FnOnce(&mut Store<Host>)> {
336        use wasmtime::{AsContext, GuestProfiler, StoreContext, StoreContextMut, UpdateDeadline};
337
338        let module_name = self.module_and_args[0].to_str().unwrap_or("<main module>");
339        store.data_mut().guest_profiler = match main_target {
340            RunTarget::Core(_m) => Some(Arc::new(GuestProfiler::new(
341                module_name,
342                interval,
343                profiled_modules,
344            ))),
345            RunTarget::Component(component) => Some(Arc::new(GuestProfiler::new_component(
346                module_name,
347                interval,
348                component.clone(),
349                profiled_modules,
350            ))),
351        };
352
353        fn sample(
354            mut store: StoreContextMut<Host>,
355            f: impl FnOnce(&mut GuestProfiler, StoreContext<Host>),
356        ) {
357            let mut profiler = store.data_mut().guest_profiler.take().unwrap();
358            f(
359                Arc::get_mut(&mut profiler).expect("profiling doesn't support threads yet"),
360                store.as_context(),
361            );
362            store.data_mut().guest_profiler = Some(profiler);
363        }
364
365        store.call_hook(|store, kind| {
366            sample(store, |profiler, store| profiler.call_hook(store, kind));
367            Ok(())
368        });
369
370        if let Some(timeout) = self.run.common.wasm.timeout {
371            let mut timeout = (timeout.as_secs_f64() / interval.as_secs_f64()).ceil() as u64;
372            assert!(timeout > 0);
373            store.epoch_deadline_callback(move |store| {
374                sample(store, |profiler, store| {
375                    profiler.sample(store, std::time::Duration::ZERO)
376                });
377                timeout -= 1;
378                if timeout == 0 {
379                    bail!("timeout exceeded");
380                }
381                Ok(UpdateDeadline::Continue(1))
382            });
383        } else {
384            store.epoch_deadline_callback(move |store| {
385                sample(store, |profiler, store| {
386                    profiler.sample(store, std::time::Duration::ZERO)
387                });
388                Ok(UpdateDeadline::Continue(1))
389            });
390        }
391
392        store.set_epoch_deadline(1);
393        let engine = store.engine().clone();
394        thread::spawn(move || {
395            loop {
396                thread::sleep(interval);
397                engine.increment_epoch();
398            }
399        });
400
401        let path = path.to_string();
402        return Box::new(move |store| {
403            let profiler = Arc::try_unwrap(store.data_mut().guest_profiler.take().unwrap())
404                .expect("profiling doesn't support threads yet");
405            if let Err(e) = std::fs::File::create(&path)
406                .map_err(anyhow::Error::new)
407                .and_then(|output| profiler.finish(std::io::BufWriter::new(output)))
408            {
409                eprintln!("failed writing profile at {path}: {e:#}");
410            } else {
411                eprintln!();
412                eprintln!("Profile written to: {path}");
413                eprintln!("View this profile at https://profiler.firefox.com/.");
414            }
415        });
416    }
417
418    async fn load_main_module(
419        &self,
420        store: &mut Store<Host>,
421        linker: &mut CliLinker,
422        main_target: &RunTarget,
423        profiled_modules: Vec<(String, Module)>,
424    ) -> Result<()> {
425        // The main module might be allowed to have unknown imports, which
426        // should be defined as traps:
427        if self.run.common.wasm.unknown_imports_trap == Some(true) {
428            match linker {
429                CliLinker::Core(linker) => {
430                    linker.define_unknown_imports_as_traps(main_target.unwrap_core())?;
431                }
432                #[cfg(feature = "component-model")]
433                CliLinker::Component(linker) => {
434                    linker.define_unknown_imports_as_traps(main_target.unwrap_component())?;
435                }
436            }
437        }
438
439        // ...or as default values.
440        if self.run.common.wasm.unknown_imports_default == Some(true) {
441            match linker {
442                CliLinker::Core(linker) => {
443                    linker.define_unknown_imports_as_default_values(
444                        store,
445                        main_target.unwrap_core(),
446                    )?;
447                }
448                _ => bail!("cannot use `--default-values-unknown-imports` with components"),
449            }
450        }
451
452        let finish_epoch_handler =
453            self.setup_epoch_handler(store, main_target, profiled_modules)?;
454
455        let result = match linker {
456            CliLinker::Core(linker) => {
457                let module = main_target.unwrap_core();
458                let instance = linker
459                    .instantiate_async(&mut *store, &module)
460                    .await
461                    .context(format!(
462                        "failed to instantiate {:?}",
463                        self.module_and_args[0]
464                    ))?;
465
466                // If `_initialize` is present, meaning a reactor, then invoke
467                // the function.
468                if let Some(func) = instance.get_func(&mut *store, "_initialize") {
469                    func.typed::<(), ()>(&store)?
470                        .call_async(&mut *store, ())
471                        .await?;
472                }
473
474                // Look for the specific function provided or otherwise look for
475                // "" or "_start" exports to run as a "main" function.
476                let func = if let Some(name) = &self.invoke {
477                    Some(
478                        instance
479                            .get_func(&mut *store, name)
480                            .ok_or_else(|| anyhow!("no func export named `{}` found", name))?,
481                    )
482                } else {
483                    instance
484                        .get_func(&mut *store, "")
485                        .or_else(|| instance.get_func(&mut *store, "_start"))
486                };
487
488                match func {
489                    Some(func) => self.invoke_func(store, func).await,
490                    None => Ok(()),
491                }
492            }
493            #[cfg(feature = "component-model")]
494            CliLinker::Component(linker) => {
495                let component = main_target.unwrap_component();
496                let result = if self.invoke.is_some() {
497                    self.invoke_component(&mut *store, component, linker).await
498                } else {
499                    self.run_command_component(&mut *store, component, linker)
500                        .await
501                };
502                result.map_err(|e| self.handle_core_dump(&mut *store, e))
503            }
504        };
505        finish_epoch_handler(store);
506
507        result
508    }
509
510    #[cfg(feature = "component-model")]
511    async fn invoke_component(
512        &self,
513        store: &mut Store<Host>,
514        component: &wasmtime::component::Component,
515        linker: &mut wasmtime::component::Linker<Host>,
516    ) -> Result<()> {
517        use wasmtime::component::{
518            Val,
519            wasm_wave::{
520                untyped::UntypedFuncCall,
521                wasm::{DisplayFuncResults, WasmFunc},
522            },
523        };
524
525        // Check if the invoke string is present
526        let invoke: &String = self.invoke.as_ref().unwrap();
527
528        let untyped_call = UntypedFuncCall::parse(invoke).with_context(|| {
529                format!(
530                    "Failed to parse invoke '{invoke}': See https://docs.wasmtime.dev/cli-options.html#run for syntax",
531                )
532        })?;
533
534        let name = untyped_call.name();
535        let matches =
536            Self::search_component_funcs(store.engine(), component.component_type(), name);
537        let (names, func_type) = match matches.len() {
538            0 => bail!("No exported func named `{name}` in component."),
539            1 => &matches[0],
540            _ => bail!(
541                "Multiple exports named `{name}`: {matches:?}. FIXME: support some way to disambiguate names"
542            ),
543        };
544
545        let param_types = WasmFunc::params(func_type).collect::<Vec<_>>();
546        let params = untyped_call
547            .to_wasm_params(&param_types)
548            .with_context(|| format!("while interpreting parameters in invoke \"{invoke}\""))?;
549
550        let export = names
551            .iter()
552            .fold(None, |instance, name| {
553                component.get_export_index(instance.as_ref(), name)
554            })
555            .expect("export has at least one name");
556
557        let instance = linker.instantiate_async(&mut *store, component).await?;
558
559        let func = instance
560            .get_func(&mut *store, export)
561            .expect("found export index");
562
563        let mut results = vec![Val::Bool(false); func_type.results().len()];
564        func.call_async(&mut *store, &params, &mut results).await?;
565
566        println!("{}", DisplayFuncResults(&results));
567
568        Ok(())
569    }
570
571    /// Execute the default behavior for components on the CLI, looking for
572    /// `wasi:cli`-based commands and running their exported `run` function.
573    #[cfg(feature = "component-model")]
574    async fn run_command_component(
575        &self,
576        store: &mut Store<Host>,
577        component: &wasmtime::component::Component,
578        linker: &wasmtime::component::Linker<Host>,
579    ) -> Result<()> {
580        let instance = linker.instantiate_async(&mut *store, component).await?;
581
582        let mut result = None;
583        let _ = &mut result;
584
585        // If WASIp3 is enabled at compile time, enabled at runtime, and found
586        // in this component then use that to generate the result.
587        #[cfg(feature = "component-model-async")]
588        if self.run.common.wasi.p3.unwrap_or(crate::common::P3_DEFAULT) {
589            if let Ok(command) = wasmtime_wasi::p3::bindings::Command::new(&mut *store, &instance) {
590                result = Some(
591                    instance
592                        .run_concurrent(&mut *store, async |store| {
593                            command.wasi_cli_run().call_run(store).await
594                        })
595                        .await?,
596                );
597            }
598        }
599
600        let result = match result {
601            Some(result) => result,
602            // If WASIp3 wasn't found then fall back to requiring WASIp2 and
603            // this'll report an error if the right export doesn't exist.
604            None => {
605                wasmtime_wasi::p2::bindings::Command::new(&mut *store, &instance)?
606                    .wasi_cli_run()
607                    .call_run(&mut *store)
608                    .await
609            }
610        };
611        let wasm_result = result.context("failed to invoke `run` function")?;
612
613        // Translate the `Result<(),()>` produced by wasm into a feigned
614        // explicit exit here with status 1 if `Err(())` is returned.
615        match wasm_result {
616            Ok(()) => Ok(()),
617            Err(()) => Err(wasmtime_wasi::I32Exit(1).into()),
618        }
619    }
620
621    #[cfg(feature = "component-model")]
622    fn search_component_funcs(
623        engine: &Engine,
624        component: wasmtime::component::types::Component,
625        name: &str,
626    ) -> Vec<(Vec<String>, wasmtime::component::types::ComponentFunc)> {
627        use wasmtime::component::types::ComponentItem as CItem;
628        fn collect_exports(
629            engine: &Engine,
630            item: CItem,
631            basename: Vec<String>,
632        ) -> Vec<(Vec<String>, CItem)> {
633            match item {
634                CItem::Component(c) => c
635                    .exports(engine)
636                    .flat_map(move |(name, item)| {
637                        let mut names = basename.clone();
638                        names.push(name.to_string());
639                        collect_exports(engine, item, names)
640                    })
641                    .collect::<Vec<_>>(),
642                CItem::ComponentInstance(c) => c
643                    .exports(engine)
644                    .flat_map(move |(name, item)| {
645                        let mut names = basename.clone();
646                        names.push(name.to_string());
647                        collect_exports(engine, item, names)
648                    })
649                    .collect::<Vec<_>>(),
650                _ => vec![(basename, item)],
651            }
652        }
653
654        collect_exports(engine, CItem::Component(component), Vec::new())
655            .into_iter()
656            .filter_map(|(names, item)| {
657                let CItem::ComponentFunc(func) = item else {
658                    return None;
659                };
660                let func_name = names.last().expect("at least one name");
661                let base_func_name = func_name.strip_prefix("[async]").unwrap_or(func_name);
662                (base_func_name == name).then_some((names, func))
663            })
664            .collect()
665    }
666
667    async fn invoke_func(&self, store: &mut Store<Host>, func: Func) -> Result<()> {
668        let ty = func.ty(&store);
669        if ty.params().len() > 0 {
670            eprintln!(
671                "warning: using `--invoke` with a function that takes arguments \
672                 is experimental and may break in the future"
673            );
674        }
675        let mut args = self.module_and_args.iter().skip(1);
676        let mut values = Vec::new();
677        for ty in ty.params() {
678            let val = match args.next() {
679                Some(s) => s,
680                None => {
681                    if let Some(name) = &self.invoke {
682                        bail!("not enough arguments for `{}`", name)
683                    } else {
684                        bail!("not enough arguments for command default")
685                    }
686                }
687            };
688            let val = val
689                .to_str()
690                .ok_or_else(|| anyhow!("argument is not valid utf-8: {val:?}"))?;
691            values.push(match ty {
692                // Supports both decimal and hexadecimal notation (with 0x prefix)
693                ValType::I32 => Val::I32(if val.starts_with("0x") || val.starts_with("0X") {
694                    i32::from_str_radix(&val[2..], 16)?
695                } else {
696                    val.parse::<i32>()?
697                }),
698                ValType::I64 => Val::I64(if val.starts_with("0x") || val.starts_with("0X") {
699                    i64::from_str_radix(&val[2..], 16)?
700                } else {
701                    val.parse::<i64>()?
702                }),
703                ValType::F32 => Val::F32(val.parse::<f32>()?.to_bits()),
704                ValType::F64 => Val::F64(val.parse::<f64>()?.to_bits()),
705                t => bail!("unsupported argument type {:?}", t),
706            });
707        }
708
709        // Invoke the function and then afterwards print all the results that came
710        // out, if there are any.
711        let mut results = vec![Val::null_func_ref(); ty.results().len()];
712        let invoke_res = func
713            .call_async(&mut *store, &values, &mut results)
714            .await
715            .with_context(|| {
716                if let Some(name) = &self.invoke {
717                    format!("failed to invoke `{name}`")
718                } else {
719                    format!("failed to invoke command default")
720                }
721            });
722
723        if let Err(err) = invoke_res {
724            return Err(self.handle_core_dump(&mut *store, err));
725        }
726
727        if !results.is_empty() {
728            eprintln!(
729                "warning: using `--invoke` with a function that returns values \
730                 is experimental and may break in the future"
731            );
732        }
733
734        for result in results {
735            match result {
736                Val::I32(i) => println!("{i}"),
737                Val::I64(i) => println!("{i}"),
738                Val::F32(f) => println!("{}", f32::from_bits(f)),
739                Val::F64(f) => println!("{}", f64::from_bits(f)),
740                Val::V128(i) => println!("{}", i.as_u128()),
741                Val::ExternRef(None) => println!("<null externref>"),
742                Val::ExternRef(Some(_)) => println!("<externref>"),
743                Val::FuncRef(None) => println!("<null funcref>"),
744                Val::FuncRef(Some(_)) => println!("<funcref>"),
745                Val::AnyRef(None) => println!("<null anyref>"),
746                Val::AnyRef(Some(_)) => println!("<anyref>"),
747                Val::ExnRef(None) => println!("<null exnref>"),
748                Val::ExnRef(Some(_)) => println!("<exnref>"),
749                Val::ContRef(None) => println!("<null contref>"),
750                Val::ContRef(Some(_)) => println!("<contref>"),
751            }
752        }
753
754        Ok(())
755    }
756
757    #[cfg(feature = "coredump")]
758    fn handle_core_dump(&self, store: &mut Store<Host>, err: Error) -> Error {
759        let coredump_path = match &self.run.common.debug.coredump {
760            Some(path) => path,
761            None => return err,
762        };
763        if !err.is::<wasmtime::Trap>() {
764            return err;
765        }
766        let source_name = self.module_and_args[0]
767            .to_str()
768            .unwrap_or_else(|| "unknown");
769
770        if let Err(coredump_err) = write_core_dump(store, &err, &source_name, coredump_path) {
771            eprintln!("warning: coredump failed to generate: {coredump_err}");
772            err
773        } else {
774            err.context(format!("core dumped at {coredump_path}"))
775        }
776    }
777
778    #[cfg(not(feature = "coredump"))]
779    fn handle_core_dump(&self, _store: &mut Store<Host>, err: Error) -> Error {
780        err
781    }
782
783    /// Populates the given `Linker` with WASI APIs.
784    fn populate_with_wasi(
785        &self,
786        linker: &mut CliLinker,
787        store: &mut Store<Host>,
788        module: &RunTarget,
789    ) -> Result<()> {
790        self.run.validate_p3_option()?;
791        let cli = self.run.validate_cli_enabled()?;
792
793        if cli != Some(false) {
794            match linker {
795                CliLinker::Core(linker) => {
796                    match (self.run.common.wasi.preview2, self.run.common.wasi.threads) {
797                        // If preview2 is explicitly disabled, or if threads
798                        // are enabled, then use the historical preview1
799                        // implementation.
800                        (Some(false), _) | (None, Some(true)) => {
801                            wasi_common::tokio::add_to_linker(linker, |host| {
802                                host.legacy_p1_ctx.as_mut().unwrap()
803                            })?;
804                            self.set_legacy_p1_ctx(store)?;
805                        }
806                        // If preview2 was explicitly requested, always use it.
807                        // Otherwise use it so long as threads are disabled.
808                        //
809                        // Note that for now `p0` is currently
810                        // default-enabled but this may turn into
811                        // default-disabled in the future.
812                        (Some(true), _) | (None, Some(false) | None) => {
813                            if self.run.common.wasi.preview0 != Some(false) {
814                                wasmtime_wasi::p0::add_to_linker_async(linker, |t| t.wasip1_ctx())?;
815                            }
816                            wasmtime_wasi::p1::add_to_linker_async(linker, |t| t.wasip1_ctx())?;
817                            self.set_wasi_ctx(store)?;
818                        }
819                    }
820                }
821                #[cfg(feature = "component-model")]
822                CliLinker::Component(linker) => {
823                    self.run.add_wasmtime_wasi_to_linker(linker)?;
824                    self.set_wasi_ctx(store)?;
825                }
826            }
827        }
828
829        if self.run.common.wasi.nn == Some(true) {
830            #[cfg(not(feature = "wasi-nn"))]
831            {
832                bail!("Cannot enable wasi-nn when the binary is not compiled with this feature.");
833            }
834            #[cfg(all(feature = "wasi-nn", feature = "component-model"))]
835            {
836                let (backends, registry) = self.collect_preloaded_nn_graphs()?;
837                match linker {
838                    CliLinker::Core(linker) => {
839                        wasmtime_wasi_nn::witx::add_to_linker(linker, |host| {
840                            Arc::get_mut(host.wasi_nn_witx.as_mut().unwrap())
841                                .expect("wasi-nn is not implemented with multi-threading support")
842                        })?;
843                        store.data_mut().wasi_nn_witx = Some(Arc::new(
844                            wasmtime_wasi_nn::witx::WasiNnCtx::new(backends, registry),
845                        ));
846                    }
847                    #[cfg(feature = "component-model")]
848                    CliLinker::Component(linker) => {
849                        wasmtime_wasi_nn::wit::add_to_linker(linker, |h: &mut Host| {
850                            let ctx = h.wasip1_ctx.as_mut().expect("wasi is not configured");
851                            let ctx = Arc::get_mut(ctx)
852                                .expect("wasmtime_wasi is not compatible with threads")
853                                .get_mut()
854                                .unwrap();
855                            let nn_ctx = Arc::get_mut(h.wasi_nn_wit.as_mut().unwrap())
856                                .expect("wasi-nn is not implemented with multi-threading support");
857                            WasiNnView::new(ctx.ctx().table, nn_ctx)
858                        })?;
859                        store.data_mut().wasi_nn_wit = Some(Arc::new(
860                            wasmtime_wasi_nn::wit::WasiNnCtx::new(backends, registry),
861                        ));
862                    }
863                }
864            }
865        }
866
867        if self.run.common.wasi.config == Some(true) {
868            #[cfg(not(feature = "wasi-config"))]
869            {
870                bail!(
871                    "Cannot enable wasi-config when the binary is not compiled with this feature."
872                );
873            }
874            #[cfg(all(feature = "wasi-config", feature = "component-model"))]
875            {
876                match linker {
877                    CliLinker::Core(_) => {
878                        bail!("Cannot enable wasi-config for core wasm modules");
879                    }
880                    CliLinker::Component(linker) => {
881                        let vars = WasiConfigVariables::from_iter(
882                            self.run
883                                .common
884                                .wasi
885                                .config_var
886                                .iter()
887                                .map(|v| (v.key.clone(), v.value.clone())),
888                        );
889
890                        wasmtime_wasi_config::add_to_linker(linker, |h| {
891                            WasiConfig::new(Arc::get_mut(h.wasi_config.as_mut().unwrap()).unwrap())
892                        })?;
893                        store.data_mut().wasi_config = Some(Arc::new(vars));
894                    }
895                }
896            }
897        }
898
899        if self.run.common.wasi.keyvalue == Some(true) {
900            #[cfg(not(feature = "wasi-keyvalue"))]
901            {
902                bail!(
903                    "Cannot enable wasi-keyvalue when the binary is not compiled with this feature."
904                );
905            }
906            #[cfg(all(feature = "wasi-keyvalue", feature = "component-model"))]
907            {
908                match linker {
909                    CliLinker::Core(_) => {
910                        bail!("Cannot enable wasi-keyvalue for core wasm modules");
911                    }
912                    CliLinker::Component(linker) => {
913                        let ctx = WasiKeyValueCtxBuilder::new()
914                            .in_memory_data(
915                                self.run
916                                    .common
917                                    .wasi
918                                    .keyvalue_in_memory_data
919                                    .iter()
920                                    .map(|v| (v.key.clone(), v.value.clone())),
921                            )
922                            .build();
923
924                        wasmtime_wasi_keyvalue::add_to_linker(linker, |h| {
925                            let ctx = h.wasip1_ctx.as_mut().expect("wasip2 is not configured");
926                            let ctx = Arc::get_mut(ctx).unwrap().get_mut().unwrap();
927                            WasiKeyValue::new(
928                                Arc::get_mut(h.wasi_keyvalue.as_mut().unwrap()).unwrap(),
929                                ctx.ctx().table,
930                            )
931                        })?;
932                        store.data_mut().wasi_keyvalue = Some(Arc::new(ctx));
933                    }
934                }
935            }
936        }
937
938        if self.run.common.wasi.threads == Some(true) {
939            #[cfg(not(feature = "wasi-threads"))]
940            {
941                // Silence the unused warning for `module` as it is only used in the
942                // conditionally-compiled wasi-threads.
943                let _ = &module;
944
945                bail!(
946                    "Cannot enable wasi-threads when the binary is not compiled with this feature."
947                );
948            }
949            #[cfg(feature = "wasi-threads")]
950            {
951                let linker = match linker {
952                    CliLinker::Core(linker) => linker,
953                    _ => bail!("wasi-threads does not support components yet"),
954                };
955                let module = module.unwrap_core();
956                wasmtime_wasi_threads::add_to_linker(linker, store, &module, |host| {
957                    host.wasi_threads.as_ref().unwrap()
958                })?;
959                store.data_mut().wasi_threads = Some(Arc::new(WasiThreadsCtx::new(
960                    module.clone(),
961                    Arc::new(linker.clone()),
962                )?));
963            }
964        }
965
966        if self.run.common.wasi.http == Some(true) {
967            #[cfg(not(all(feature = "wasi-http", feature = "component-model")))]
968            {
969                bail!("Cannot enable wasi-http when the binary is not compiled with this feature.");
970            }
971            #[cfg(all(feature = "wasi-http", feature = "component-model"))]
972            {
973                match linker {
974                    CliLinker::Core(_) => {
975                        bail!("Cannot enable wasi-http for core wasm modules");
976                    }
977                    CliLinker::Component(linker) => {
978                        wasmtime_wasi_http::add_only_http_to_linker_sync(linker)?;
979                    }
980                }
981
982                store.data_mut().wasi_http = Some(Arc::new(WasiHttpCtx::new()));
983            }
984        }
985
986        if self.run.common.wasi.tls == Some(true) {
987            #[cfg(all(not(all(feature = "wasi-tls", feature = "component-model"))))]
988            {
989                bail!("Cannot enable wasi-tls when the binary is not compiled with this feature.");
990            }
991            #[cfg(all(feature = "wasi-tls", feature = "component-model",))]
992            {
993                match linker {
994                    CliLinker::Core(_) => {
995                        bail!("Cannot enable wasi-tls for core wasm modules");
996                    }
997                    CliLinker::Component(linker) => {
998                        let mut opts = wasmtime_wasi_tls::LinkOptions::default();
999                        opts.tls(true);
1000                        wasmtime_wasi_tls::add_to_linker(linker, &mut opts, |h| {
1001                            let ctx = h.wasip1_ctx.as_mut().expect("wasi is not configured");
1002                            let ctx = Arc::get_mut(ctx).unwrap().get_mut().unwrap();
1003                            WasiTls::new(
1004                                Arc::get_mut(h.wasi_tls.as_mut().unwrap()).unwrap(),
1005                                ctx.ctx().table,
1006                            )
1007                        })?;
1008
1009                        let ctx = wasmtime_wasi_tls::WasiTlsCtxBuilder::new().build();
1010                        store.data_mut().wasi_tls = Some(Arc::new(ctx));
1011                    }
1012                }
1013            }
1014        }
1015
1016        Ok(())
1017    }
1018
1019    fn set_legacy_p1_ctx(&self, store: &mut Store<Host>) -> Result<()> {
1020        let mut builder = WasiCtxBuilder::new();
1021        builder.inherit_stdio().args(&self.compute_argv()?)?;
1022
1023        if self.run.common.wasi.inherit_env == Some(true) {
1024            for (k, v) in std::env::vars() {
1025                builder.env(&k, &v)?;
1026            }
1027        }
1028        for (key, value) in self.run.vars.iter() {
1029            let value = match value {
1030                Some(value) => value.clone(),
1031                None => match std::env::var_os(key) {
1032                    Some(val) => val
1033                        .into_string()
1034                        .map_err(|_| anyhow!("environment variable `{key}` not valid utf-8"))?,
1035                    None => {
1036                        // leave the env var un-set in the guest
1037                        continue;
1038                    }
1039                },
1040            };
1041            builder.env(key, &value)?;
1042        }
1043
1044        let mut num_fd: usize = 3;
1045
1046        if self.run.common.wasi.listenfd == Some(true) {
1047            num_fd = ctx_set_listenfd(num_fd, &mut builder)?;
1048        }
1049
1050        for listener in self.run.compute_preopen_sockets()? {
1051            let listener = TcpListener::from_std(listener);
1052            builder.preopened_socket(num_fd as _, listener)?;
1053            num_fd += 1;
1054        }
1055
1056        for (host, guest) in self.run.dirs.iter() {
1057            let dir = Dir::open_ambient_dir(host, ambient_authority())
1058                .with_context(|| format!("failed to open directory '{host}'"))?;
1059            builder.preopened_dir(dir, guest)?;
1060        }
1061
1062        store.data_mut().legacy_p1_ctx = Some(builder.build());
1063        Ok(())
1064    }
1065
1066    /// Note the naming here is subtle, but this is effectively setting up a
1067    /// `wasmtime_wasi::WasiCtx` structure.
1068    ///
1069    /// This is stored in `Host` as `WasiP1Ctx` which internally contains the
1070    /// `WasiCtx` and `ResourceTable` used for WASI implementations. Exactly
1071    /// which "p" for WASIpN is more a reference to
1072    /// `wasmtime-wasi`-vs-`wasi-common` here more than anything else.
1073    fn set_wasi_ctx(&self, store: &mut Store<Host>) -> Result<()> {
1074        let mut builder = wasmtime_wasi::WasiCtxBuilder::new();
1075        builder.inherit_stdio().args(&self.compute_argv()?);
1076        self.run.configure_wasip2(&mut builder)?;
1077        let ctx = builder.build_p1();
1078        store.data_mut().wasip1_ctx = Some(Arc::new(Mutex::new(ctx)));
1079        Ok(())
1080    }
1081
1082    #[cfg(feature = "wasi-nn")]
1083    fn collect_preloaded_nn_graphs(
1084        &self,
1085    ) -> Result<(Vec<wasmtime_wasi_nn::Backend>, wasmtime_wasi_nn::Registry)> {
1086        let graphs = self
1087            .run
1088            .common
1089            .wasi
1090            .nn_graph
1091            .iter()
1092            .map(|g| (g.format.clone(), g.dir.clone()))
1093            .collect::<Vec<_>>();
1094        wasmtime_wasi_nn::preload(&graphs)
1095    }
1096}
1097
1098/// The `T` in `Store<T>` for what the CLI is running.
1099///
1100/// This structures has a number of contexts used for various WASI proposals.
1101/// Note that all of them are optional meaning that they're `None` by default
1102/// and enabled with various CLI flags (some CLI flags are on-by-default). Note
1103/// additionally that this structure is `Clone` to implement the `wasi-threads`
1104/// proposal. Many WASI proposals are not compatible with `wasi-threads` so to
1105/// model this `Arc` and `Arc<Mutex<T>>` is used for many configurations. If a
1106/// WASI proposal is inherently threadsafe it's protected with just an `Arc` to
1107/// share its configuration across many threads.
1108///
1109/// If mutation is required then `Mutex` is used. Note though that the mutex is
1110/// not actually locked as access always goes through `Arc::get_mut` which
1111/// effectively asserts that there's only one thread. In short much of this is
1112/// not compatible with `wasi-threads`.
1113#[derive(Default, Clone)]
1114struct Host {
1115    // Legacy wasip1 context using `wasi_common`, not set unless opted-in-to
1116    // with the CLI.
1117    legacy_p1_ctx: Option<wasi_common::WasiCtx>,
1118
1119    // Context for both WASIp1 and WASIp2 (and beyond) for the `wasmtime_wasi`
1120    // crate. This has both `wasmtime_wasi::WasiCtx` as well as a
1121    // `ResourceTable` internally to be used.
1122    //
1123    // The Mutex is only needed to satisfy the Sync constraint but we never
1124    // actually perform any locking on it as we use Mutex::get_mut for every
1125    // access.
1126    wasip1_ctx: Option<Arc<Mutex<wasmtime_wasi::p1::WasiP1Ctx>>>,
1127
1128    #[cfg(feature = "wasi-nn")]
1129    wasi_nn_wit: Option<Arc<wasmtime_wasi_nn::wit::WasiNnCtx>>,
1130    #[cfg(feature = "wasi-nn")]
1131    wasi_nn_witx: Option<Arc<wasmtime_wasi_nn::witx::WasiNnCtx>>,
1132
1133    #[cfg(feature = "wasi-threads")]
1134    wasi_threads: Option<Arc<WasiThreadsCtx<Host>>>,
1135    #[cfg(feature = "wasi-http")]
1136    wasi_http: Option<Arc<WasiHttpCtx>>,
1137    #[cfg(feature = "wasi-http")]
1138    wasi_http_outgoing_body_buffer_chunks: Option<usize>,
1139    #[cfg(feature = "wasi-http")]
1140    wasi_http_outgoing_body_chunk_size: Option<usize>,
1141    limits: StoreLimits,
1142    #[cfg(feature = "profiling")]
1143    guest_profiler: Option<Arc<wasmtime::GuestProfiler>>,
1144
1145    #[cfg(feature = "wasi-config")]
1146    wasi_config: Option<Arc<WasiConfigVariables>>,
1147    #[cfg(feature = "wasi-keyvalue")]
1148    wasi_keyvalue: Option<Arc<WasiKeyValueCtx>>,
1149    #[cfg(feature = "wasi-tls")]
1150    wasi_tls: Option<Arc<WasiTlsCtx>>,
1151}
1152
1153impl Host {
1154    fn wasip1_ctx(&mut self) -> &mut wasmtime_wasi::p1::WasiP1Ctx {
1155        let ctx = self.wasip1_ctx.as_mut().expect("wasi is not configured");
1156        Arc::get_mut(ctx)
1157            .expect("wasmtime_wasi is not compatible with threads")
1158            .get_mut()
1159            .unwrap()
1160    }
1161}
1162
1163impl WasiView for Host {
1164    fn ctx(&mut self) -> WasiCtxView<'_> {
1165        WasiView::ctx(self.wasip1_ctx())
1166    }
1167}
1168
1169#[cfg(feature = "wasi-http")]
1170impl wasmtime_wasi_http::types::WasiHttpView for Host {
1171    fn ctx(&mut self) -> &mut WasiHttpCtx {
1172        let ctx = self.wasi_http.as_mut().unwrap();
1173        Arc::get_mut(ctx).expect("wasmtime_wasi is not compatible with threads")
1174    }
1175
1176    fn table(&mut self) -> &mut wasmtime::component::ResourceTable {
1177        WasiView::ctx(self).table
1178    }
1179
1180    fn outgoing_body_buffer_chunks(&mut self) -> usize {
1181        self.wasi_http_outgoing_body_buffer_chunks
1182            .unwrap_or_else(|| DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS)
1183    }
1184
1185    fn outgoing_body_chunk_size(&mut self) -> usize {
1186        self.wasi_http_outgoing_body_chunk_size
1187            .unwrap_or_else(|| DEFAULT_OUTGOING_BODY_CHUNK_SIZE)
1188    }
1189}
1190
1191#[cfg(not(unix))]
1192fn ctx_set_listenfd(num_fd: usize, _builder: &mut WasiCtxBuilder) -> Result<usize> {
1193    Ok(num_fd)
1194}
1195
1196#[cfg(unix)]
1197fn ctx_set_listenfd(mut num_fd: usize, builder: &mut WasiCtxBuilder) -> Result<usize> {
1198    use listenfd::ListenFd;
1199
1200    for env in ["LISTEN_FDS", "LISTEN_FDNAMES"] {
1201        if let Ok(val) = std::env::var(env) {
1202            builder.env(env, &val)?;
1203        }
1204    }
1205
1206    let mut listenfd = ListenFd::from_env();
1207
1208    for i in 0..listenfd.len() {
1209        if let Some(stdlistener) = listenfd.take_tcp_listener(i)? {
1210            let _ = stdlistener.set_nonblocking(true)?;
1211            let listener = TcpListener::from_std(stdlistener);
1212            builder.preopened_socket((3 + i) as _, listener)?;
1213            num_fd = 3 + i;
1214        }
1215    }
1216
1217    Ok(num_fd)
1218}
1219
1220#[cfg(feature = "coredump")]
1221fn write_core_dump(
1222    store: &mut Store<Host>,
1223    err: &anyhow::Error,
1224    name: &str,
1225    path: &str,
1226) -> Result<()> {
1227    use std::fs::File;
1228    use std::io::Write;
1229
1230    let core_dump = err
1231        .downcast_ref::<wasmtime::WasmCoreDump>()
1232        .expect("should have been configured to capture core dumps");
1233
1234    let core_dump = core_dump.serialize(store, name);
1235
1236    let mut core_dump_file =
1237        File::create(path).context(format!("failed to create file at `{path}`"))?;
1238    core_dump_file
1239        .write_all(&core_dump)
1240        .with_context(|| format!("failed to write core dump file at `{path}`"))?;
1241    Ok(())
1242}