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