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