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