Skip to main content

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