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::{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    WasiHttpCtx, DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS, DEFAULT_OUTGOING_BODY_CHUNK_SIZE,
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 || loop {
399            thread::sleep(interval);
400            engine.increment_epoch();
401        });
402
403        let path = path.to_string();
404        return Box::new(move |store| {
405            let profiler = Arc::try_unwrap(store.data_mut().guest_profiler.take().unwrap())
406                .expect("profiling doesn't support threads yet");
407            if let Err(e) = std::fs::File::create(&path)
408                .map_err(anyhow::Error::new)
409                .and_then(|output| profiler.finish(std::io::BufWriter::new(output)))
410            {
411                eprintln!("failed writing profile at {path}: {e:#}");
412            } else {
413                eprintln!();
414                eprintln!("Profile written to: {path}");
415                eprintln!("View this profile at https://profiler.firefox.com/.");
416            }
417        });
418    }
419
420    async fn load_main_module(
421        &self,
422        store: &mut Store<Host>,
423        linker: &mut CliLinker,
424        main_target: &RunTarget,
425        profiled_modules: Vec<(String, Module)>,
426    ) -> Result<()> {
427        // The main module might be allowed to have unknown imports, which
428        // should be defined as traps:
429        if self.run.common.wasm.unknown_imports_trap == Some(true) {
430            match linker {
431                CliLinker::Core(linker) => {
432                    linker.define_unknown_imports_as_traps(main_target.unwrap_core())?;
433                }
434                #[cfg(feature = "component-model")]
435                CliLinker::Component(linker) => {
436                    linker.define_unknown_imports_as_traps(main_target.unwrap_component())?;
437                }
438            }
439        }
440
441        // ...or as default values.
442        if self.run.common.wasm.unknown_imports_default == Some(true) {
443            match linker {
444                CliLinker::Core(linker) => {
445                    linker.define_unknown_imports_as_default_values(
446                        store,
447                        main_target.unwrap_core(),
448                    )?;
449                }
450                _ => bail!("cannot use `--default-values-unknown-imports` with components"),
451            }
452        }
453
454        let finish_epoch_handler =
455            self.setup_epoch_handler(store, main_target, profiled_modules)?;
456
457        let result = match linker {
458            CliLinker::Core(linker) => {
459                let module = main_target.unwrap_core();
460                let instance = linker
461                    .instantiate_async(&mut *store, &module)
462                    .await
463                    .context(format!(
464                        "failed to instantiate {:?}",
465                        self.module_and_args[0]
466                    ))?;
467
468                // If `_initialize` is present, meaning a reactor, then invoke
469                // the function.
470                if let Some(func) = instance.get_func(&mut *store, "_initialize") {
471                    func.typed::<(), ()>(&store)?
472                        .call_async(&mut *store, ())
473                        .await?;
474                }
475
476                // Look for the specific function provided or otherwise look for
477                // "" or "_start" exports to run as a "main" function.
478                let func = if let Some(name) = &self.invoke {
479                    Some(
480                        instance
481                            .get_func(&mut *store, name)
482                            .ok_or_else(|| anyhow!("no func export named `{}` found", name))?,
483                    )
484                } else {
485                    instance
486                        .get_func(&mut *store, "")
487                        .or_else(|| instance.get_func(&mut *store, "_start"))
488                };
489
490                match func {
491                    Some(func) => self.invoke_func(store, func).await,
492                    None => Ok(()),
493                }
494            }
495            #[cfg(feature = "component-model")]
496            CliLinker::Component(linker) => {
497                if self.invoke.is_some() {
498                    bail!("using `--invoke` with components is not supported");
499                }
500
501                let component = main_target.unwrap_component();
502
503                let command = wasmtime_wasi::bindings::Command::instantiate_async(
504                    &mut *store,
505                    component,
506                    linker,
507                )
508                .await?;
509                let result = command
510                    .wasi_cli_run()
511                    .call_run(&mut *store)
512                    .await
513                    .context("failed to invoke `run` function")
514                    .map_err(|e| self.handle_core_dump(&mut *store, e));
515
516                // Translate the `Result<(),()>` produced by wasm into a feigned
517                // explicit exit here with status 1 if `Err(())` is returned.
518                result.and_then(|wasm_result| match wasm_result {
519                    Ok(()) => Ok(()),
520                    Err(()) => Err(wasmtime_wasi::I32Exit(1).into()),
521                })
522            }
523        };
524        finish_epoch_handler(store);
525
526        result
527    }
528
529    async fn invoke_func(&self, store: &mut Store<Host>, func: Func) -> Result<()> {
530        let ty = func.ty(&store);
531        if ty.params().len() > 0 {
532            eprintln!(
533                "warning: using `--invoke` with a function that takes arguments \
534                 is experimental and may break in the future"
535            );
536        }
537        let mut args = self.module_and_args.iter().skip(1);
538        let mut values = Vec::new();
539        for ty in ty.params() {
540            let val = match args.next() {
541                Some(s) => s,
542                None => {
543                    if let Some(name) = &self.invoke {
544                        bail!("not enough arguments for `{}`", name)
545                    } else {
546                        bail!("not enough arguments for command default")
547                    }
548                }
549            };
550            let val = val
551                .to_str()
552                .ok_or_else(|| anyhow!("argument is not valid utf-8: {val:?}"))?;
553            values.push(match ty {
554                // Supports both decimal and hexadecimal notation (with 0x prefix)
555                ValType::I32 => Val::I32(if val.starts_with("0x") || val.starts_with("0X") {
556                    i32::from_str_radix(&val[2..], 16)?
557                } else {
558                    val.parse::<i32>()?
559                }),
560                ValType::I64 => Val::I64(if val.starts_with("0x") || val.starts_with("0X") {
561                    i64::from_str_radix(&val[2..], 16)?
562                } else {
563                    val.parse::<i64>()?
564                }),
565                ValType::F32 => Val::F32(val.parse::<f32>()?.to_bits()),
566                ValType::F64 => Val::F64(val.parse::<f64>()?.to_bits()),
567                t => bail!("unsupported argument type {:?}", t),
568            });
569        }
570
571        // Invoke the function and then afterwards print all the results that came
572        // out, if there are any.
573        let mut results = vec![Val::null_func_ref(); ty.results().len()];
574        let invoke_res = func
575            .call_async(&mut *store, &values, &mut results)
576            .await
577            .with_context(|| {
578                if let Some(name) = &self.invoke {
579                    format!("failed to invoke `{name}`")
580                } else {
581                    format!("failed to invoke command default")
582                }
583            });
584
585        if let Err(err) = invoke_res {
586            return Err(self.handle_core_dump(&mut *store, err));
587        }
588
589        if !results.is_empty() {
590            eprintln!(
591                "warning: using `--invoke` with a function that returns values \
592                 is experimental and may break in the future"
593            );
594        }
595
596        for result in results {
597            match result {
598                Val::I32(i) => println!("{i}"),
599                Val::I64(i) => println!("{i}"),
600                Val::F32(f) => println!("{}", f32::from_bits(f)),
601                Val::F64(f) => println!("{}", f64::from_bits(f)),
602                Val::V128(i) => println!("{}", i.as_u128()),
603                Val::ExternRef(None) => println!("<null externref>"),
604                Val::ExternRef(Some(_)) => println!("<externref>"),
605                Val::FuncRef(None) => println!("<null funcref>"),
606                Val::FuncRef(Some(_)) => println!("<funcref>"),
607                Val::AnyRef(None) => println!("<null anyref>"),
608                Val::AnyRef(Some(_)) => println!("<anyref>"),
609            }
610        }
611
612        Ok(())
613    }
614
615    #[cfg(feature = "coredump")]
616    fn handle_core_dump(&self, store: &mut Store<Host>, err: Error) -> Error {
617        let coredump_path = match &self.run.common.debug.coredump {
618            Some(path) => path,
619            None => return err,
620        };
621        if !err.is::<wasmtime::Trap>() {
622            return err;
623        }
624        let source_name = self.module_and_args[0]
625            .to_str()
626            .unwrap_or_else(|| "unknown");
627
628        if let Err(coredump_err) = write_core_dump(store, &err, &source_name, coredump_path) {
629            eprintln!("warning: coredump failed to generate: {coredump_err}");
630            err
631        } else {
632            err.context(format!("core dumped at {coredump_path}"))
633        }
634    }
635
636    #[cfg(not(feature = "coredump"))]
637    fn handle_core_dump(&self, _store: &mut Store<Host>, err: Error) -> Error {
638        err
639    }
640
641    /// Populates the given `Linker` with WASI APIs.
642    fn populate_with_wasi(
643        &self,
644        linker: &mut CliLinker,
645        store: &mut Store<Host>,
646        module: &RunTarget,
647    ) -> Result<()> {
648        let mut cli = self.run.common.wasi.cli;
649
650        // Accept -Scommon as a deprecated alias for -Scli.
651        if let Some(common) = self.run.common.wasi.common {
652            if cli.is_some() {
653                bail!(
654                    "The -Scommon option should not be use with -Scli as it is a deprecated alias"
655                );
656            } else {
657                // In the future, we may add a warning here to tell users to use
658                // `-S cli` instead of `-S common`.
659                cli = Some(common);
660            }
661        }
662
663        if cli != Some(false) {
664            match linker {
665                CliLinker::Core(linker) => {
666                    match (self.run.common.wasi.preview2, self.run.common.wasi.threads) {
667                        // If preview2 is explicitly disabled, or if threads
668                        // are enabled, then use the historical preview1
669                        // implementation.
670                        (Some(false), _) | (None, Some(true)) => {
671                            wasi_common::tokio::add_to_linker(linker, |host| {
672                                host.preview1_ctx.as_mut().unwrap()
673                            })?;
674                            self.set_preview1_ctx(store)?;
675                        }
676                        // If preview2 was explicitly requested, always use it.
677                        // Otherwise use it so long as threads are disabled.
678                        //
679                        // Note that for now `preview0` is currently
680                        // default-enabled but this may turn into
681                        // default-disabled in the future.
682                        (Some(true), _) | (None, Some(false) | None) => {
683                            if self.run.common.wasi.preview0 != Some(false) {
684                                wasmtime_wasi::preview0::add_to_linker_async(linker, |t| {
685                                    t.preview2_ctx()
686                                })?;
687                            }
688                            wasmtime_wasi::preview1::add_to_linker_async(linker, |t| {
689                                t.preview2_ctx()
690                            })?;
691                            self.set_preview2_ctx(store)?;
692                        }
693                    }
694                }
695                #[cfg(feature = "component-model")]
696                CliLinker::Component(linker) => {
697                    let link_options = self.run.compute_wasi_features();
698                    wasmtime_wasi::add_to_linker_with_options_async(linker, &link_options)?;
699                    self.set_preview2_ctx(store)?;
700                }
701            }
702        }
703
704        if self.run.common.wasi.nn == Some(true) {
705            #[cfg(not(feature = "wasi-nn"))]
706            {
707                bail!("Cannot enable wasi-nn when the binary is not compiled with this feature.");
708            }
709            #[cfg(all(feature = "wasi-nn", feature = "component-model"))]
710            {
711                let (backends, registry) = self.collect_preloaded_nn_graphs()?;
712                match linker {
713                    CliLinker::Core(linker) => {
714                        wasmtime_wasi_nn::witx::add_to_linker(linker, |host| {
715                            Arc::get_mut(host.wasi_nn_witx.as_mut().unwrap())
716                                .expect("wasi-nn is not implemented with multi-threading support")
717                        })?;
718                        store.data_mut().wasi_nn_witx = Some(Arc::new(
719                            wasmtime_wasi_nn::witx::WasiNnCtx::new(backends, registry),
720                        ));
721                    }
722                    #[cfg(feature = "component-model")]
723                    CliLinker::Component(linker) => {
724                        wasmtime_wasi_nn::wit::add_to_linker(linker, |h: &mut Host| {
725                            let preview2_ctx =
726                                h.preview2_ctx.as_mut().expect("wasip2 is not configured");
727                            let preview2_ctx = Arc::get_mut(preview2_ctx)
728                                .expect("wasmtime_wasi is not compatible with threads")
729                                .get_mut()
730                                .unwrap();
731                            let nn_ctx = Arc::get_mut(h.wasi_nn_wit.as_mut().unwrap())
732                                .expect("wasi-nn is not implemented with multi-threading support");
733                            WasiNnView::new(preview2_ctx.table(), nn_ctx)
734                        })?;
735                        store.data_mut().wasi_nn_wit = Some(Arc::new(
736                            wasmtime_wasi_nn::wit::WasiNnCtx::new(backends, registry),
737                        ));
738                    }
739                }
740            }
741        }
742
743        if self.run.common.wasi.config == Some(true) {
744            #[cfg(not(feature = "wasi-config"))]
745            {
746                bail!(
747                    "Cannot enable wasi-config when the binary is not compiled with this feature."
748                );
749            }
750            #[cfg(all(feature = "wasi-config", feature = "component-model"))]
751            {
752                match linker {
753                    CliLinker::Core(_) => {
754                        bail!("Cannot enable wasi-config for core wasm modules");
755                    }
756                    CliLinker::Component(linker) => {
757                        let vars = WasiConfigVariables::from_iter(
758                            self.run
759                                .common
760                                .wasi
761                                .config_var
762                                .iter()
763                                .map(|v| (v.key.clone(), v.value.clone())),
764                        );
765
766                        wasmtime_wasi_config::add_to_linker(linker, |h| {
767                            WasiConfig::new(Arc::get_mut(h.wasi_config.as_mut().unwrap()).unwrap())
768                        })?;
769                        store.data_mut().wasi_config = Some(Arc::new(vars));
770                    }
771                }
772            }
773        }
774
775        if self.run.common.wasi.keyvalue == Some(true) {
776            #[cfg(not(feature = "wasi-keyvalue"))]
777            {
778                bail!("Cannot enable wasi-keyvalue when the binary is not compiled with this feature.");
779            }
780            #[cfg(all(feature = "wasi-keyvalue", feature = "component-model"))]
781            {
782                match linker {
783                    CliLinker::Core(_) => {
784                        bail!("Cannot enable wasi-keyvalue for core wasm modules");
785                    }
786                    CliLinker::Component(linker) => {
787                        let ctx = WasiKeyValueCtxBuilder::new()
788                            .in_memory_data(
789                                self.run
790                                    .common
791                                    .wasi
792                                    .keyvalue_in_memory_data
793                                    .iter()
794                                    .map(|v| (v.key.clone(), v.value.clone())),
795                            )
796                            .build();
797
798                        wasmtime_wasi_keyvalue::add_to_linker(linker, |h| {
799                            let preview2_ctx =
800                                h.preview2_ctx.as_mut().expect("wasip2 is not configured");
801                            let preview2_ctx =
802                                Arc::get_mut(preview2_ctx).unwrap().get_mut().unwrap();
803                            WasiKeyValue::new(
804                                Arc::get_mut(h.wasi_keyvalue.as_mut().unwrap()).unwrap(),
805                                preview2_ctx.table(),
806                            )
807                        })?;
808                        store.data_mut().wasi_keyvalue = Some(Arc::new(ctx));
809                    }
810                }
811            }
812        }
813
814        if self.run.common.wasi.threads == Some(true) {
815            #[cfg(not(feature = "wasi-threads"))]
816            {
817                // Silence the unused warning for `module` as it is only used in the
818                // conditionally-compiled wasi-threads.
819                let _ = &module;
820
821                bail!(
822                    "Cannot enable wasi-threads when the binary is not compiled with this feature."
823                );
824            }
825            #[cfg(feature = "wasi-threads")]
826            {
827                let linker = match linker {
828                    CliLinker::Core(linker) => linker,
829                    _ => bail!("wasi-threads does not support components yet"),
830                };
831                let module = module.unwrap_core();
832                wasmtime_wasi_threads::add_to_linker(linker, store, &module, |host| {
833                    host.wasi_threads.as_ref().unwrap()
834                })?;
835                store.data_mut().wasi_threads = Some(Arc::new(WasiThreadsCtx::new(
836                    module.clone(),
837                    Arc::new(linker.clone()),
838                )?));
839            }
840        }
841
842        if self.run.common.wasi.http == Some(true) {
843            #[cfg(not(all(feature = "wasi-http", feature = "component-model")))]
844            {
845                bail!("Cannot enable wasi-http when the binary is not compiled with this feature.");
846            }
847            #[cfg(all(feature = "wasi-http", feature = "component-model"))]
848            {
849                match linker {
850                    CliLinker::Core(_) => {
851                        bail!("Cannot enable wasi-http for core wasm modules");
852                    }
853                    CliLinker::Component(linker) => {
854                        wasmtime_wasi_http::add_only_http_to_linker_sync(linker)?;
855                    }
856                }
857
858                store.data_mut().wasi_http = Some(Arc::new(WasiHttpCtx::new()));
859            }
860        }
861
862        if self.run.common.wasi.tls == Some(true) {
863            #[cfg(all(not(all(feature = "wasi-tls", feature = "component-model"))))]
864            {
865                bail!("Cannot enable wasi-tls when the binary is not compiled with this feature.");
866            }
867            #[cfg(all(feature = "wasi-tls", feature = "component-model",))]
868            {
869                match linker {
870                    CliLinker::Core(_) => {
871                        bail!("Cannot enable wasi-tls for core wasm modules");
872                    }
873                    CliLinker::Component(linker) => {
874                        let mut opts = wasmtime_wasi_tls::LinkOptions::default();
875                        opts.tls(true);
876                        wasmtime_wasi_tls::add_to_linker(linker, &mut opts, |h| {
877                            let preview2_ctx =
878                                h.preview2_ctx.as_mut().expect("wasip2 is not configured");
879                            let preview2_ctx =
880                                Arc::get_mut(preview2_ctx).unwrap().get_mut().unwrap();
881                            WasiTlsCtx::new(preview2_ctx.table())
882                        })?;
883                    }
884                }
885            }
886        }
887
888        Ok(())
889    }
890
891    fn set_preview1_ctx(&self, store: &mut Store<Host>) -> Result<()> {
892        let mut builder = WasiCtxBuilder::new();
893        builder.inherit_stdio().args(&self.compute_argv()?)?;
894
895        if self.run.common.wasi.inherit_env == Some(true) {
896            for (k, v) in std::env::vars() {
897                builder.env(&k, &v)?;
898            }
899        }
900        for (key, value) in self.run.vars.iter() {
901            let value = match value {
902                Some(value) => value.clone(),
903                None => match std::env::var_os(key) {
904                    Some(val) => val
905                        .into_string()
906                        .map_err(|_| anyhow!("environment variable `{key}` not valid utf-8"))?,
907                    None => {
908                        // leave the env var un-set in the guest
909                        continue;
910                    }
911                },
912            };
913            builder.env(key, &value)?;
914        }
915
916        let mut num_fd: usize = 3;
917
918        if self.run.common.wasi.listenfd == Some(true) {
919            num_fd = ctx_set_listenfd(num_fd, &mut builder)?;
920        }
921
922        for listener in self.run.compute_preopen_sockets()? {
923            let listener = TcpListener::from_std(listener);
924            builder.preopened_socket(num_fd as _, listener)?;
925            num_fd += 1;
926        }
927
928        for (host, guest) in self.run.dirs.iter() {
929            let dir = Dir::open_ambient_dir(host, ambient_authority())
930                .with_context(|| format!("failed to open directory '{host}'"))?;
931            builder.preopened_dir(dir, guest)?;
932        }
933
934        store.data_mut().preview1_ctx = Some(builder.build());
935        Ok(())
936    }
937
938    fn set_preview2_ctx(&self, store: &mut Store<Host>) -> Result<()> {
939        let mut builder = wasmtime_wasi::WasiCtxBuilder::new();
940        builder.inherit_stdio().args(&self.compute_argv()?);
941        self.run.configure_wasip2(&mut builder)?;
942        let ctx = builder.build_p1();
943        store.data_mut().preview2_ctx = Some(Arc::new(Mutex::new(ctx)));
944        Ok(())
945    }
946
947    #[cfg(feature = "wasi-nn")]
948    fn collect_preloaded_nn_graphs(
949        &self,
950    ) -> Result<(Vec<wasmtime_wasi_nn::Backend>, wasmtime_wasi_nn::Registry)> {
951        let graphs = self
952            .run
953            .common
954            .wasi
955            .nn_graph
956            .iter()
957            .map(|g| (g.format.clone(), g.dir.clone()))
958            .collect::<Vec<_>>();
959        wasmtime_wasi_nn::preload(&graphs)
960    }
961}
962
963#[derive(Default, Clone)]
964struct Host {
965    preview1_ctx: Option<wasi_common::WasiCtx>,
966
967    // The Mutex is only needed to satisfy the Sync constraint but we never
968    // actually perform any locking on it as we use Mutex::get_mut for every
969    // access.
970    preview2_ctx: Option<Arc<Mutex<wasmtime_wasi::preview1::WasiP1Ctx>>>,
971
972    #[cfg(feature = "wasi-nn")]
973    wasi_nn_wit: Option<Arc<wasmtime_wasi_nn::wit::WasiNnCtx>>,
974    #[cfg(feature = "wasi-nn")]
975    wasi_nn_witx: Option<Arc<wasmtime_wasi_nn::witx::WasiNnCtx>>,
976
977    #[cfg(feature = "wasi-threads")]
978    wasi_threads: Option<Arc<WasiThreadsCtx<Host>>>,
979    #[cfg(feature = "wasi-http")]
980    wasi_http: Option<Arc<WasiHttpCtx>>,
981    #[cfg(feature = "wasi-http")]
982    wasi_http_outgoing_body_buffer_chunks: Option<usize>,
983    #[cfg(feature = "wasi-http")]
984    wasi_http_outgoing_body_chunk_size: Option<usize>,
985    limits: StoreLimits,
986    #[cfg(feature = "profiling")]
987    guest_profiler: Option<Arc<wasmtime::GuestProfiler>>,
988
989    #[cfg(feature = "wasi-config")]
990    wasi_config: Option<Arc<WasiConfigVariables>>,
991    #[cfg(feature = "wasi-keyvalue")]
992    wasi_keyvalue: Option<Arc<WasiKeyValueCtx>>,
993}
994
995impl Host {
996    fn preview2_ctx(&mut self) -> &mut wasmtime_wasi::preview1::WasiP1Ctx {
997        let ctx = self
998            .preview2_ctx
999            .as_mut()
1000            .expect("wasip2 is not configured");
1001        Arc::get_mut(ctx)
1002            .expect("wasmtime_wasi is not compatible with threads")
1003            .get_mut()
1004            .unwrap()
1005    }
1006}
1007
1008impl IoView for Host {
1009    fn table(&mut self) -> &mut wasmtime::component::ResourceTable {
1010        self.preview2_ctx().table()
1011    }
1012}
1013impl WasiView for Host {
1014    fn ctx(&mut self) -> &mut wasmtime_wasi::WasiCtx {
1015        self.preview2_ctx().ctx()
1016    }
1017}
1018
1019#[cfg(feature = "wasi-http")]
1020impl wasmtime_wasi_http::types::WasiHttpView for Host {
1021    fn ctx(&mut self) -> &mut WasiHttpCtx {
1022        let ctx = self.wasi_http.as_mut().unwrap();
1023        Arc::get_mut(ctx).expect("wasmtime_wasi is not compatible with threads")
1024    }
1025
1026    fn outgoing_body_buffer_chunks(&mut self) -> usize {
1027        self.wasi_http_outgoing_body_buffer_chunks
1028            .unwrap_or_else(|| DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS)
1029    }
1030
1031    fn outgoing_body_chunk_size(&mut self) -> usize {
1032        self.wasi_http_outgoing_body_chunk_size
1033            .unwrap_or_else(|| DEFAULT_OUTGOING_BODY_CHUNK_SIZE)
1034    }
1035}
1036
1037#[cfg(not(unix))]
1038fn ctx_set_listenfd(num_fd: usize, _builder: &mut WasiCtxBuilder) -> Result<usize> {
1039    Ok(num_fd)
1040}
1041
1042#[cfg(unix)]
1043fn ctx_set_listenfd(mut num_fd: usize, builder: &mut WasiCtxBuilder) -> Result<usize> {
1044    use listenfd::ListenFd;
1045
1046    for env in ["LISTEN_FDS", "LISTEN_FDNAMES"] {
1047        if let Ok(val) = std::env::var(env) {
1048            builder.env(env, &val)?;
1049        }
1050    }
1051
1052    let mut listenfd = ListenFd::from_env();
1053
1054    for i in 0..listenfd.len() {
1055        if let Some(stdlistener) = listenfd.take_tcp_listener(i)? {
1056            let _ = stdlistener.set_nonblocking(true)?;
1057            let listener = TcpListener::from_std(stdlistener);
1058            builder.preopened_socket((3 + i) as _, listener)?;
1059            num_fd = 3 + i;
1060        }
1061    }
1062
1063    Ok(num_fd)
1064}
1065
1066#[cfg(feature = "coredump")]
1067fn write_core_dump(
1068    store: &mut Store<Host>,
1069    err: &anyhow::Error,
1070    name: &str,
1071    path: &str,
1072) -> Result<()> {
1073    use std::fs::File;
1074    use std::io::Write;
1075
1076    let core_dump = err
1077        .downcast_ref::<wasmtime::WasmCoreDump>()
1078        .expect("should have been configured to capture core dumps");
1079
1080    let core_dump = core_dump.serialize(store, name);
1081
1082    let mut core_dump_file =
1083        File::create(path).context(format!("failed to create file at `{path}`"))?;
1084    core_dump_file
1085        .write_all(&core_dump)
1086        .with_context(|| format!("failed to write core dump file at `{path}`"))?;
1087    Ok(())
1088}