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