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