Skip to main content

wasmtime_cli/commands/
run.rs

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