wasmtime_cli/commands/
run.rs

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