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