xref: /wasmtime-44.0.1/src/commands/run.rs (revision 4c7c01dc)
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 clap::Parser;
10 use std::ffi::OsString;
11 use std::path::{Path, PathBuf};
12 #[cfg(feature = "debug")]
13 use std::pin::Pin;
14 use std::sync::{Arc, Mutex};
15 use std::thread;
16 use wasi_common::sync::{Dir, TcpListener, WasiCtxBuilder, ambient_authority};
17 use wasmtime::{
18     Engine, Error, Func, Module, Result, Store, StoreLimits, Val, ValType, bail,
19     error::Context as _, format_err,
20 };
21 use wasmtime_wasi::{WasiCtxView, WasiView};
22 
23 #[cfg(feature = "wasi-config")]
24 use wasmtime_wasi_config::{WasiConfig, WasiConfigVariables};
25 #[cfg(feature = "wasi-http")]
26 use wasmtime_wasi_http::WasiHttpCtx;
27 #[cfg(feature = "wasi-keyvalue")]
28 use wasmtime_wasi_keyvalue::{WasiKeyValue, WasiKeyValueCtx, WasiKeyValueCtxBuilder};
29 #[cfg(feature = "wasi-nn")]
30 use wasmtime_wasi_nn::wit::WasiNnView;
31 #[cfg(feature = "wasi-threads")]
32 use wasmtime_wasi_threads::WasiThreadsCtx;
33 
parse_preloads(s: &str) -> Result<(String, PathBuf)>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     /// Override the module bytes loaded from disk. When set, the
67     /// first positional argument is ignored for loading purposes and
68     /// these bytes are used instead. This is not a CLI option; it is
69     /// used internally to inject pre-built bytes (e.g. for an
70     /// included debug adapter).
71     #[arg(skip)]
72     pub module_bytes: Option<&'static [u8]>,
73 
74     /// The WebAssembly module to run and arguments to pass to it.
75     ///
76     /// Arguments passed to the wasm module will be configured as WASI CLI
77     /// arguments unless the `--invoke` CLI argument is passed in which case
78     /// arguments will be interpreted as arguments to the function specified.
79     #[arg(value_name = "WASM", trailing_var_arg = true, required = true)]
80     pub module_and_args: Vec<OsString>,
81 }
82 
83 impl RunCommand {
84     /// Split off a sub-command representing the invocation of a
85     /// debugger component side-car to this execution.
86     ///
87     /// This is used to factor out most of the environment bringup for
88     /// the debugger component environment.
89     ///
90     /// This also adjusts the guest options as needed to enable
91     /// debugging (e.g., implicitly set `-D guest-debug=y`).
92     #[cfg(feature = "debug")]
debugger_run(&mut self) -> Result<Option<RunCommand>>93     pub(crate) fn debugger_run(&mut self) -> Result<Option<RunCommand>> {
94         fn set_implicit_option(
95             place: &str,
96             name: &str,
97             setting: &mut Option<bool>,
98             value: bool,
99         ) -> Result<()> {
100             if *setting == Some(!value) {
101                 bail!(
102                     "Explicitly-set option on {place} {name}={} is not compatible with debugging-implied setting {value}",
103                     setting.unwrap()
104                 );
105             }
106             *setting = Some(value);
107             Ok(())
108         }
109 
110         // When -g is specified, set up the debugger path and args from
111         // the built-in gdbstub component.
112         #[cfg(feature = "gdbstub")]
113         let override_bytes = if let Some(addr) = self.run.gdbstub.as_deref() {
114             if self.run.common.debug.debugger.is_some() {
115                 bail!("-g/--gdb cannot be combined with -Ddebugger=");
116             }
117             // Accept either a bare port number or a full address:port.
118             let addr = if addr.parse::<u16>().is_ok() {
119                 format!("127.0.0.1:{addr}")
120             } else {
121                 use std::net::SocketAddr;
122                 addr.parse::<SocketAddr>()
123                     .with_context(|| format!("invalid gdbstub address: `{addr}`"))?;
124                 addr.to_string()
125             };
126             self.run.common.debug.debugger = Some("<built-in gdbstub>".into());
127             self.run.common.debug.arg.push(addr);
128             Some(gdbstub_component_artifact::GDBSTUB_COMPONENT)
129         } else {
130             None
131         };
132         #[cfg(not(feature = "gdbstub"))]
133         let override_bytes = None;
134 
135         if let Some(debugger_component_path) = self.run.common.debug.debugger.as_ref() {
136             set_implicit_option(
137                 "debuggee",
138                 "guest_debug",
139                 &mut self.run.common.debug.guest_debug,
140                 true,
141             )?;
142             set_implicit_option(
143                 "debuggee",
144                 "epoch_interruption",
145                 &mut self.run.common.wasm.epoch_interruption,
146                 true,
147             )?;
148 
149             let mut debugger_run = RunCommand::try_parse_from(
150                 ["run".into(), debugger_component_path.into()]
151                     .into_iter()
152                     .chain(self.run.common.debug.arg.iter().map(OsString::from)),
153             )?;
154             debugger_run.module_bytes = override_bytes;
155 
156             // Explicitly permit TCP sockets for the debugger-main
157             // environment, if not already set.
158             debugger_run.run.common.wasi.tcp.get_or_insert(true);
159             debugger_run
160                 .run
161                 .common
162                 .wasi
163                 .inherit_network
164                 .get_or_insert(true);
165 
166             // Copy over stdin/stdout/stderr inheritance settings,
167             // except default to `false` for the debugger (so it
168             // doesn't interfere with the debuggee's CLI interface, if
169             // any). We expect most debug components will serve an
170             // interface over the network; for those that want a TUI,
171             // their setup instructions can instruct the user to set
172             // these flags as needed.
173             set_implicit_option(
174                 "debugger",
175                 "inherit_stdin",
176                 &mut debugger_run.run.common.wasi.inherit_stdin,
177                 self.run.common.debug.inherit_stdin.unwrap_or(false),
178             )?;
179             set_implicit_option(
180                 "debugger",
181                 "inherit_stdout",
182                 &mut debugger_run.run.common.wasi.inherit_stdout,
183                 self.run.common.debug.inherit_stdout.unwrap_or(false),
184             )?;
185             set_implicit_option(
186                 "debugger",
187                 "inherit_stderr",
188                 &mut debugger_run.run.common.wasi.inherit_stderr,
189                 self.run.common.debug.inherit_stderr.unwrap_or(false),
190             )?;
191             Ok(Some(debugger_run))
192         } else {
193             Ok(None)
194         }
195     }
196 }
197 
198 #[expect(missing_docs, reason = "don't want to mess with clap doc-strings")]
199 #[derive(Parser, Default, Clone)]
200 pub struct Preloads {
201     /// Load the given WebAssembly module before the main module
202     #[arg(
203         long = "preload",
204         number_of_values = 1,
205         value_name = "NAME=MODULE_PATH",
206         value_parser = parse_preloads,
207     )]
208     modules: Vec<(String, PathBuf)>,
209 }
210 
211 /// Dispatch between either a core or component linker.
212 #[expect(missing_docs, reason = "self-explanatory")]
213 pub enum CliLinker {
214     Core(wasmtime::Linker<Host>),
215     #[cfg(feature = "component-model")]
216     Component(wasmtime::component::Linker<Host>),
217 }
218 
219 /// Dispatch between either a core or component instance.
220 #[expect(missing_docs, reason = "self-explanatory")]
221 pub enum CliInstance {
222     Core(wasmtime::Instance),
223     #[cfg(feature = "component-model")]
224     Component(wasmtime::component::Instance),
225 }
226 
227 impl RunCommand {
228     /// Executes the command.
229     #[cfg(feature = "run")]
execute(mut self) -> Result<()>230     pub fn execute(mut self) -> Result<()> {
231         let runtime = tokio::runtime::Builder::new_multi_thread()
232             .enable_time()
233             .enable_io()
234             .build()?;
235 
236         runtime.block_on(async {
237             self.run.common.init_logging()?;
238 
239             #[cfg(feature = "debug")]
240             let debug_run = self.debugger_run()?;
241 
242             let engine = self.new_engine()?;
243             let main = self.run.load_module(
244                 &engine,
245                 self.module_and_args[0].as_ref(),
246                 self.module_bytes.as_ref().map(|v| &v[..]),
247             )?;
248             let (mut store, mut linker) = self.new_store_and_linker(&engine, &main)?;
249 
250             #[cfg(feature = "debug")]
251             if let Some(mut debug_run) = debug_run {
252                 let debug_engine = debug_run.new_engine()?;
253                 let debug_main = debug_run.run.load_module(
254                     &debug_engine,
255                     debug_run.module_and_args[0].as_ref(),
256                     debug_run.module_bytes.as_ref().map(|v| &v[..]),
257                 )?;
258                 let (mut debug_store, debug_linker) =
259                     debug_run.new_store_and_linker(&debug_engine, &debug_main)?;
260 
261                 let debug_component = match debug_main {
262                     RunTarget::Core(_) => wasmtime::bail!(
263                         "Debugger component is a core module; only components are supported"
264                     ),
265                     RunTarget::Component(c) => c,
266                 };
267                 let mut debug_linker = match debug_linker {
268                     CliLinker::Core(_) => unreachable!(),
269                     CliLinker::Component(l) => l,
270                 };
271                 debug_run.add_debugger_api(&mut debug_linker)?;
272 
273                 // Pre-register the main module on the debuggee store
274                 // so that `debug_all_modules()` returns it before any
275                 // Wasm executes. This lets the debugger see modules
276                 // and set breakpoints at the initial stop.
277                 match &main {
278                     RunTarget::Core(m) => {
279                         store.debug_register_module(m)?;
280                     }
281                     #[cfg(feature = "component-model")]
282                     RunTarget::Component(c) => {
283                         store.debug_register_component(c)?;
284                     }
285                 }
286 
287                 debug_run
288                     .invoke_debugger(
289                         &mut debug_store,
290                         &debug_component,
291                         &mut debug_linker,
292                         store,
293                         move |store| {
294                             Box::pin(async move {
295                                 let engine_clone = store.engine().clone();
296                                 let cancel = Arc::new(std::sync::atomic::AtomicBool::new(false));
297                                 let cancel_clone = cancel.clone();
298                                 let epoch_thread = thread::spawn(move || {
299                                     while !cancel_clone.load(std::sync::atomic::Ordering::Relaxed) {
300                                         thread::sleep(std::time::Duration::from_millis(1));
301                                         engine_clone.increment_epoch();
302                                     }
303                                 });
304                                 self.instantiate_and_run(&engine, &mut linker, &main, store)
305                                     .await?;
306                                 cancel.store(true, std::sync::atomic::Ordering::Relaxed);
307                                 epoch_thread
308                                     .join()
309                                     .map_err(|_| wasmtime::Error::msg("epoch thread panicked"))?;
310                                 Ok(())
311                             })
312                         },
313                     )
314                     .await?;
315                 return Ok(());
316             }
317 
318             self.instantiate_and_run(&engine, &mut linker, &main, &mut store)
319                 .await?;
320             Ok(())
321         })
322     }
323 
324     /// Creates a new `Engine` with the configuration for this command.
new_engine(&mut self) -> Result<Engine>325     pub fn new_engine(&mut self) -> Result<Engine> {
326         let mut config = self.run.common.config(None)?;
327 
328         if self.run.common.wasm.timeout.is_some() {
329             config.epoch_interruption(true);
330         }
331         match self.run.profile {
332             Some(Profile::Native(s)) => {
333                 config.profiler(s);
334             }
335             Some(Profile::Guest { .. }) => {
336                 // Further configured down below as well.
337                 config.epoch_interruption(true);
338             }
339             None => {}
340         }
341 
342         Engine::new(&config)
343     }
344 
345     /// Populates a new `Store` and `CliLinker` with the configuration in this
346     /// command.
347     ///
348     /// The `engine` provided is used to for the store/linker and the `main`
349     /// provided is the module/component that is going to be run.
new_store_and_linker( &mut self, engine: &Engine, main: &RunTarget, ) -> Result<(Store<Host>, CliLinker)>350     pub fn new_store_and_linker(
351         &mut self,
352         engine: &Engine,
353         main: &RunTarget,
354     ) -> Result<(Store<Host>, CliLinker)> {
355         // Validate coredump-on-trap argument
356         if let Some(path) = &self.run.common.debug.coredump {
357             if path.contains("%") {
358                 bail!("the coredump-on-trap path does not support patterns yet.")
359             }
360         }
361 
362         let mut linker = match &main {
363             RunTarget::Core(_) => CliLinker::Core(wasmtime::Linker::new(&engine)),
364             #[cfg(feature = "component-model")]
365             RunTarget::Component(_) => {
366                 CliLinker::Component(wasmtime::component::Linker::new(&engine))
367             }
368         };
369         if let Some(enable) = self.run.common.wasm.unknown_exports_allow {
370             match &mut linker {
371                 CliLinker::Core(l) => {
372                     l.allow_unknown_exports(enable);
373                 }
374                 #[cfg(feature = "component-model")]
375                 CliLinker::Component(_) => {
376                     bail!("--allow-unknown-exports not supported with components");
377                 }
378             }
379         }
380 
381         let host = Host::default();
382 
383         let mut store = Store::new(&engine, host);
384         self.populate_with_wasi(&mut linker, &mut store, &main)?;
385 
386         store.data_mut().limits = self.run.store_limits();
387         store.limiter(|t| &mut t.limits);
388 
389         // If fuel has been configured, we want to add the configured
390         // fuel amount to this store.
391         if let Some(fuel) = self.run.common.wasm.fuel {
392             store.set_fuel(fuel)?;
393         }
394 
395         Ok((store, linker))
396     }
397 
398     #[cfg(feature = "debug")]
add_debugger_api( &mut self, linker: &mut wasmtime::component::Linker<Host>, ) -> Result<()>399     pub(crate) fn add_debugger_api(
400         &mut self,
401         linker: &mut wasmtime::component::Linker<Host>,
402     ) -> Result<()> {
403         wasmtime_debugger::add_to_linker(linker, |x| x.ctx().table)?;
404         Ok(())
405     }
406 
407     /// Executes the `main` after instantiating it within `store`.
408     ///
409     /// This applies all configuration within `self`, such as timeouts and
410     /// profiling, and performs the execution. The resulting instance is
411     /// returned.
instantiate_and_run( &self, engine: &Engine, linker: &mut CliLinker, main: &RunTarget, store: &mut Store<Host>, ) -> Result<CliInstance>412     pub async fn instantiate_and_run(
413         &self,
414         engine: &Engine,
415         linker: &mut CliLinker,
416         main: &RunTarget,
417         store: &mut Store<Host>,
418     ) -> Result<CliInstance> {
419         let dur = self
420             .run
421             .common
422             .wasm
423             .timeout
424             .unwrap_or(std::time::Duration::MAX);
425         let result = tokio::time::timeout(dur, async {
426             let mut profiled_modules: Vec<(String, Module)> = Vec::new();
427             if let RunTarget::Core(m) = &main {
428                 profiled_modules.push(("".to_string(), m.clone()));
429             }
430 
431             // Load the preload wasm modules.
432             for (name, path) in self.preloads.modules.iter() {
433                 // Read the wasm module binary either as `*.wat` or a raw binary
434                 let preload_target = self.run.load_module(&engine, path, None)?;
435                 let preload_module = match preload_target {
436                     RunTarget::Core(m) => m,
437                     #[cfg(feature = "component-model")]
438                     RunTarget::Component(_) => {
439                         bail!("components cannot be loaded with `--preload`")
440                     }
441                 };
442                 profiled_modules.push((name.to_string(), preload_module.clone()));
443 
444                 // Add the module's functions to the linker.
445                 match linker {
446                     #[cfg(feature = "cranelift")]
447                     CliLinker::Core(linker) => {
448                         linker
449                             .module_async(&mut *store, name, &preload_module)
450                             .await
451                             .with_context(|| {
452                                 format!(
453                                     "failed to process preload `{}` at `{}`",
454                                     name,
455                                     path.display()
456                                 )
457                             })?;
458                     }
459                     #[cfg(not(feature = "cranelift"))]
460                     CliLinker::Core(_) => {
461                         bail!("support for --preload disabled at compile time");
462                     }
463                     #[cfg(feature = "component-model")]
464                     CliLinker::Component(_) => {
465                         bail!("--preload cannot be used with components");
466                     }
467                 }
468             }
469 
470             self.load_main_module(store, linker, &main, profiled_modules)
471                 .await
472                 .with_context(|| {
473                     format!(
474                         "failed to run main module `{}`",
475                         self.module_and_args[0].to_string_lossy()
476                     )
477                 })
478         })
479         .await;
480 
481         // Load the main wasm module.
482         let instance = match result.unwrap_or_else(|elapsed| {
483             Err(wasmtime::Error::from(wasmtime::Trap::Interrupt))
484                 .with_context(|| format!("timed out after {elapsed}"))
485         }) {
486             Ok(instance) => instance,
487             Err(e) => {
488                 // Exit the process if Wasmtime understands the error;
489                 // otherwise, fall back on Rust's default error printing/return
490                 // code.
491                 if store.data().legacy_p1_ctx.is_some() {
492                     return Err(wasi_common::maybe_exit_on_error(e));
493                 } else if store.data().wasip1_ctx.is_some() {
494                     if let Some(exit) = e.downcast_ref::<wasmtime_wasi::I32Exit>() {
495                         std::process::exit(exit.0);
496                     }
497                 }
498                 if e.is::<wasmtime::Trap>() {
499                     eprintln!("Error: {e:?}");
500                     cfg_if::cfg_if! {
501                         if #[cfg(unix)] {
502                             std::process::exit(rustix::process::EXIT_SIGNALED_SIGABRT);
503                         } else if #[cfg(windows)] {
504                             // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/abort?view=vs-2019
505                             std::process::exit(3);
506                         }
507                     }
508                 }
509                 return Err(e);
510             }
511         };
512 
513         Ok(instance)
514     }
515 
compute_argv(&self) -> Result<Vec<String>>516     pub(crate) fn compute_argv(&self) -> Result<Vec<String>> {
517         let mut result = Vec::new();
518 
519         for (i, arg) in self.module_and_args.iter().enumerate() {
520             // For argv[0], which is the program name. Only include the base
521             // name of the main wasm module, to avoid leaking path information.
522             let arg = if i == 0 {
523                 match &self.argv0 {
524                     Some(s) => s.as_ref(),
525                     None => Path::new(arg).components().next_back().unwrap().as_os_str(),
526                 }
527             } else {
528                 arg.as_ref()
529             };
530             result.push(
531                 arg.to_str()
532                     .ok_or_else(|| format_err!("failed to convert {arg:?} to utf-8"))?
533                     .to_string(),
534             );
535         }
536 
537         Ok(result)
538     }
539 
setup_epoch_handler( &self, store: &mut Store<Host>, main_target: &RunTarget, profiled_modules: Vec<(String, Module)>, ) -> Result<Box<dyn FnOnce(&mut Store<Host>) + Send>>540     fn setup_epoch_handler(
541         &self,
542         store: &mut Store<Host>,
543         main_target: &RunTarget,
544         profiled_modules: Vec<(String, Module)>,
545     ) -> Result<Box<dyn FnOnce(&mut Store<Host>) + Send>> {
546         // If a debugger component is attached, we set up epoch
547         // interruptions in `debugger_run()` above when enabling guest
548         // instrumentation; we need to ensure that epoch interruptions
549         // cause a debug event but no trap here. This overrides other
550         // behavior below.
551         if self.run.common.debug.debugger.is_some() {
552             if self.run.profile.is_some() {
553                 bail!("Cannot set profile options together with debugging; they are incompatible");
554             }
555             if self.run.common.wasm.timeout.is_some() {
556                 bail!("Cannot set timeout options together with debugging; they are incompatible");
557             }
558             store.epoch_deadline_async_yield_and_update(1);
559         } else {
560             if let Some(Profile::Guest { path, interval }) = &self.run.profile {
561                 #[cfg(feature = "profiling")]
562                 return Ok(self.setup_guest_profiler(
563                     store,
564                     main_target,
565                     profiled_modules,
566                     path,
567                     *interval,
568                 )?);
569                 #[cfg(not(feature = "profiling"))]
570                 {
571                     let _ = (profiled_modules, path, interval, main_target);
572                     bail!("support for profiling disabled at compile time");
573                 }
574             }
575 
576             if let Some(timeout) = self.run.common.wasm.timeout {
577                 store.set_epoch_deadline(1);
578                 let engine = store.engine().clone();
579                 thread::spawn(move || {
580                     thread::sleep(timeout);
581                     engine.increment_epoch();
582                 });
583             }
584         }
585 
586         Ok(Box::new(|_store| {}))
587     }
588 
589     #[cfg(feature = "profiling")]
setup_guest_profiler( &self, store: &mut Store<Host>, main_target: &RunTarget, profiled_modules: Vec<(String, Module)>, path: &str, interval: std::time::Duration, ) -> Result<Box<dyn FnOnce(&mut Store<Host>) + Send>>590     fn setup_guest_profiler(
591         &self,
592         store: &mut Store<Host>,
593         main_target: &RunTarget,
594         profiled_modules: Vec<(String, Module)>,
595         path: &str,
596         interval: std::time::Duration,
597     ) -> Result<Box<dyn FnOnce(&mut Store<Host>) + Send>> {
598         use wasmtime::{AsContext, GuestProfiler, StoreContext, StoreContextMut, UpdateDeadline};
599 
600         let module_name = self.module_and_args[0].to_str().unwrap_or("<main module>");
601         store.data_mut().guest_profiler = match main_target {
602             RunTarget::Core(_m) => Some(Arc::new(GuestProfiler::new(
603                 store.engine(),
604                 module_name,
605                 interval,
606                 profiled_modules,
607             )?)),
608             RunTarget::Component(component) => Some(Arc::new(GuestProfiler::new_component(
609                 store.engine(),
610                 module_name,
611                 interval,
612                 component.clone(),
613                 profiled_modules,
614             )?)),
615         };
616 
617         fn sample(
618             mut store: StoreContextMut<Host>,
619             f: impl FnOnce(&mut GuestProfiler, StoreContext<Host>),
620         ) {
621             let mut profiler = store.data_mut().guest_profiler.take().unwrap();
622             f(
623                 Arc::get_mut(&mut profiler).expect("profiling doesn't support threads yet"),
624                 store.as_context(),
625             );
626             store.data_mut().guest_profiler = Some(profiler);
627         }
628 
629         store.call_hook(|store, kind| {
630             sample(store, |profiler, store| profiler.call_hook(store, kind));
631             Ok(())
632         });
633 
634         if let Some(timeout) = self.run.common.wasm.timeout {
635             let mut timeout = (timeout.as_secs_f64() / interval.as_secs_f64()).ceil() as u64;
636             assert!(timeout > 0);
637             store.epoch_deadline_callback(move |store| {
638                 sample(store, |profiler, store| {
639                     profiler.sample(store, std::time::Duration::ZERO)
640                 });
641                 timeout -= 1;
642                 if timeout == 0 {
643                     bail!("timeout exceeded");
644                 }
645                 Ok(UpdateDeadline::Continue(1))
646             });
647         } else {
648             store.epoch_deadline_callback(move |store| {
649                 sample(store, |profiler, store| {
650                     profiler.sample(store, std::time::Duration::ZERO)
651                 });
652                 Ok(UpdateDeadline::Continue(1))
653             });
654         }
655 
656         store.set_epoch_deadline(1);
657         let engine = store.engine().clone();
658         thread::spawn(move || {
659             loop {
660                 thread::sleep(interval);
661                 engine.increment_epoch();
662             }
663         });
664 
665         let path = path.to_string();
666         Ok(Box::new(move |store| {
667             let profiler = Arc::try_unwrap(store.data_mut().guest_profiler.take().unwrap())
668                 .expect("profiling doesn't support threads yet");
669             if let Err(e) = std::fs::File::create(&path)
670                 .map_err(wasmtime::Error::new)
671                 .and_then(|output| profiler.finish(std::io::BufWriter::new(output)))
672             {
673                 eprintln!("failed writing profile at {path}: {e:#}");
674             } else {
675                 eprintln!();
676                 eprintln!("Profile written to: {path}");
677                 eprintln!("View this profile at https://profiler.firefox.com/.");
678             }
679         }))
680     }
681 
load_main_module( &self, store: &mut Store<Host>, linker: &mut CliLinker, main_target: &RunTarget, profiled_modules: Vec<(String, Module)>, ) -> Result<CliInstance>682     async fn load_main_module(
683         &self,
684         store: &mut Store<Host>,
685         linker: &mut CliLinker,
686         main_target: &RunTarget,
687         profiled_modules: Vec<(String, Module)>,
688     ) -> Result<CliInstance> {
689         // The main module might be allowed to have unknown imports, which
690         // should be defined as traps:
691         if self.run.common.wasm.unknown_imports_trap == Some(true) {
692             match linker {
693                 CliLinker::Core(linker) => {
694                     linker.define_unknown_imports_as_traps(main_target.unwrap_core())?;
695                 }
696                 #[cfg(feature = "component-model")]
697                 CliLinker::Component(linker) => {
698                     linker.define_unknown_imports_as_traps(main_target.unwrap_component())?;
699                 }
700             }
701         }
702 
703         // ...or as default values.
704         if self.run.common.wasm.unknown_imports_default == Some(true) {
705             match linker {
706                 CliLinker::Core(linker) => {
707                     linker.define_unknown_imports_as_default_values(
708                         &mut *store,
709                         main_target.unwrap_core(),
710                     )?;
711                 }
712                 _ => bail!("cannot use `--default-values-unknown-imports` with components"),
713             }
714         }
715 
716         let finish_epoch_handler =
717             self.setup_epoch_handler(store, main_target, profiled_modules)?;
718 
719         let result = match linker {
720             CliLinker::Core(linker) => {
721                 let module = main_target.unwrap_core();
722                 let instance = linker
723                     .instantiate_async(&mut *store, &module)
724                     .await
725                     .with_context(|| {
726                         format!("failed to instantiate {:?}", self.module_and_args[0])
727                     })?;
728 
729                 // If `_initialize` is present, meaning a reactor, then invoke
730                 // the function.
731                 if let Some(func) = instance.get_func(&mut *store, "_initialize") {
732                     func.typed::<(), ()>(&store)?
733                         .call_async(&mut *store, ())
734                         .await?;
735                 }
736 
737                 // Look for the specific function provided or otherwise look for
738                 // "" or "_start" exports to run as a "main" function.
739                 let func = if let Some(name) = &self.invoke {
740                     Some(
741                         instance
742                             .get_func(&mut *store, name)
743                             .ok_or_else(|| format_err!("no func export named `{name}` found"))?,
744                     )
745                 } else {
746                     instance
747                         .get_func(&mut *store, "")
748                         .or_else(|| instance.get_func(&mut *store, "_start"))
749                 };
750 
751                 if let Some(func) = func {
752                     self.invoke_func(store, func).await?;
753                 }
754                 Ok(CliInstance::Core(instance))
755             }
756             #[cfg(feature = "component-model")]
757             CliLinker::Component(linker) => {
758                 let component = main_target.unwrap_component();
759                 let result = if self.invoke.is_some() {
760                     self.invoke_component(&mut *store, component, linker).await
761                 } else {
762                     self.run_command_component(&mut *store, component, linker)
763                         .await
764                 };
765                 result
766                     .map(CliInstance::Component)
767                     .map_err(|e| self.handle_core_dump(&mut *store, e))
768             }
769         };
770         finish_epoch_handler(store);
771 
772         result
773     }
774 
775     #[cfg(feature = "component-model")]
invoke_component( &self, store: &mut Store<Host>, component: &wasmtime::component::Component, linker: &mut wasmtime::component::Linker<Host>, ) -> Result<wasmtime::component::Instance>776     async fn invoke_component(
777         &self,
778         store: &mut Store<Host>,
779         component: &wasmtime::component::Component,
780         linker: &mut wasmtime::component::Linker<Host>,
781     ) -> Result<wasmtime::component::Instance> {
782         use wasmtime::component::{
783             Val,
784             wasm_wave::{
785                 untyped::UntypedFuncCall,
786                 wasm::{DisplayFuncResults, WasmFunc},
787             },
788         };
789 
790         // Check if the invoke string is present
791         let invoke: &String = self.invoke.as_ref().unwrap();
792 
793         let untyped_call = UntypedFuncCall::parse(invoke).with_context(|| {
794                 format!(
795                     "Failed to parse invoke '{invoke}': See https://docs.wasmtime.dev/cli-options.html#run for syntax",
796                 )
797         })?;
798 
799         let name = untyped_call.name();
800         let matches =
801             Self::search_component_funcs(store.engine(), component.component_type(), name);
802         let (names, func_type) = match matches.len() {
803             0 => bail!("No exported func named `{name}` in component."),
804             1 => &matches[0],
805             _ => bail!(
806                 "Multiple exports named `{name}`: {matches:?}. FIXME: support some way to disambiguate names"
807             ),
808         };
809 
810         let param_types = WasmFunc::params(func_type).collect::<Vec<_>>();
811         let params = untyped_call
812             .to_wasm_params(&param_types)
813             .with_context(|| format!("while interpreting parameters in invoke \"{invoke}\""))?;
814 
815         let export = names
816             .iter()
817             .fold(None, |instance, name| {
818                 component.get_export_index(instance.as_ref(), name)
819             })
820             .expect("export has at least one name");
821 
822         let instance = linker.instantiate_async(&mut *store, component).await?;
823 
824         let func = instance
825             .get_func(&mut *store, export)
826             .expect("found export index");
827 
828         let mut results = vec![Val::Bool(false); func_type.results().len()];
829         self.call_component_func(store, &params, func, &mut results)
830             .await?;
831 
832         println!("{}", DisplayFuncResults(&results));
833         Ok(instance)
834     }
835 
836     #[cfg(feature = "component-model")]
call_component_func( &self, store: &mut Store<Host>, params: &[wasmtime::component::Val], func: wasmtime::component::Func, results: &mut Vec<wasmtime::component::Val>, ) -> Result<(), Error>837     async fn call_component_func(
838         &self,
839         store: &mut Store<Host>,
840         params: &[wasmtime::component::Val],
841         func: wasmtime::component::Func,
842         results: &mut Vec<wasmtime::component::Val>,
843     ) -> Result<(), Error> {
844         #[cfg(feature = "component-model-async")]
845         if self.run.common.wasm.concurrency_support.unwrap_or(true) {
846             store
847                 .run_concurrent(async |store| func.call_concurrent(store, params, results).await)
848                 .await??;
849             return Ok(());
850         }
851 
852         func.call_async(&mut *store, &params, results).await?;
853         Ok(())
854     }
855 
856     /// Execute the default behavior for components on the CLI, looking for
857     /// `wasi:cli`-based commands and running their exported `run` function.
858     #[cfg(feature = "component-model")]
run_command_component( &self, store: &mut Store<Host>, component: &wasmtime::component::Component, linker: &wasmtime::component::Linker<Host>, ) -> Result<wasmtime::component::Instance>859     async fn run_command_component(
860         &self,
861         store: &mut Store<Host>,
862         component: &wasmtime::component::Component,
863         linker: &wasmtime::component::Linker<Host>,
864     ) -> Result<wasmtime::component::Instance> {
865         let instance = linker.instantiate_async(&mut *store, component).await?;
866 
867         let mut result = None;
868         let _ = &mut result;
869 
870         // If WASIp3 is enabled at compile time, enabled at runtime, and found
871         // in this component then use that to generate the result.
872         #[cfg(feature = "component-model-async")]
873         if self.run.common.wasi.p3.unwrap_or(crate::common::P3_DEFAULT) {
874             if let Ok(command) = wasmtime_wasi::p3::bindings::Command::new(&mut *store, &instance) {
875                 result = Some(
876                     store
877                         .run_concurrent(async |store| command.wasi_cli_run().call_run(store).await)
878                         .await?,
879                 );
880             }
881         }
882 
883         let result = match result {
884             Some(result) => result,
885             // If WASIp3 wasn't found then fall back to requiring WASIp2 and
886             // this'll report an error if the right export doesn't exist.
887             None => {
888                 wasmtime_wasi::p2::bindings::Command::new(&mut *store, &instance)?
889                     .wasi_cli_run()
890                     .call_run(&mut *store)
891                     .await
892             }
893         };
894         let wasm_result = result.context("failed to invoke `run` function")?;
895 
896         // Translate the `Result<(),()>` produced by wasm into a feigned
897         // explicit exit here with status 1 if `Err(())` is returned.
898         match wasm_result {
899             Ok(()) => Ok(instance),
900             Err(()) => Err(wasmtime_wasi::I32Exit(1).into()),
901         }
902     }
903 
904     /// Invoke a debugger component with a debuggee.
905     ///
906     /// The debugger runs in `store` (using run's `Host`), while the
907     /// debuggee wraps an arbitrary store type `T` and body closure.
908     #[cfg(feature = "debug")]
invoke_debugger< T: Send + 'static, F: for<'a> FnOnce( &'a mut Store<T>, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>> + Send + 'static, >( &self, store: &mut Store<Host>, component: &wasmtime::component::Component, linker: &mut wasmtime::component::Linker<Host>, debuggee_host: Store<T>, body: F, ) -> Result<()>909     pub(crate) async fn invoke_debugger<
910         T: Send + 'static,
911         F: for<'a> FnOnce(
912                 &'a mut Store<T>,
913             ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>
914             + Send
915             + 'static,
916     >(
917         &self,
918         store: &mut Store<Host>,
919         component: &wasmtime::component::Component,
920         linker: &mut wasmtime::component::Linker<Host>,
921         debuggee_host: Store<T>,
922         body: F,
923     ) -> Result<()> {
924         let instance = linker.instantiate_async(&mut *store, component).await?;
925         let command = wasmtime_debugger::DebuggerComponent::new(&mut *store, &instance)?;
926         let debuggee = wasmtime_debugger::Debuggee::new(debuggee_host, body);
927         let debuggee = wasmtime_debugger::add_debuggee(store.data_mut().ctx().table, debuggee)?;
928         {
929             // Manually construct a borrow -- wasmtime-wit-bindgen
930             // generates code that consumes the `Resource<T>` for
931             // `call_debug()` below even though the WIT type is a
932             // `borrow<debuggee>`.
933             let borrowed = wasmtime::component::Resource::new_borrow(debuggee.rep());
934             let args = self.compute_argv()?;
935             command
936                 .bytecodealliance_wasmtime_debugger()
937                 .call_debug(&mut *store, borrowed, &args)
938                 .await?;
939         }
940         let mut debuggee = store.data_mut().ctx().table.delete(debuggee)?;
941         debuggee.finish().await?;
942         Ok(())
943     }
944 
945     #[cfg(feature = "component-model")]
search_component_funcs( engine: &Engine, component: wasmtime::component::types::Component, name: &str, ) -> Vec<(Vec<String>, wasmtime::component::types::ComponentFunc)>946     fn search_component_funcs(
947         engine: &Engine,
948         component: wasmtime::component::types::Component,
949         name: &str,
950     ) -> Vec<(Vec<String>, wasmtime::component::types::ComponentFunc)> {
951         use wasmtime::component::types::ComponentItem as CItem;
952         fn collect_exports(
953             engine: &Engine,
954             item: CItem,
955             basename: Vec<String>,
956         ) -> Vec<(Vec<String>, CItem)> {
957             match item {
958                 CItem::Component(c) => c
959                     .exports(engine)
960                     .flat_map(move |(name, item)| {
961                         let mut names = basename.clone();
962                         names.push(name.to_string());
963                         collect_exports(engine, item, names)
964                     })
965                     .collect::<Vec<_>>(),
966                 CItem::ComponentInstance(c) => c
967                     .exports(engine)
968                     .flat_map(move |(name, item)| {
969                         let mut names = basename.clone();
970                         names.push(name.to_string());
971                         collect_exports(engine, item, names)
972                     })
973                     .collect::<Vec<_>>(),
974                 _ => vec![(basename, item)],
975             }
976         }
977 
978         collect_exports(engine, CItem::Component(component), Vec::new())
979             .into_iter()
980             .filter_map(|(names, item)| {
981                 let CItem::ComponentFunc(func) = item else {
982                     return None;
983                 };
984                 let func_name = names.last().expect("at least one name");
985                 (func_name == name).then_some((names, func))
986             })
987             .collect()
988     }
989 
invoke_func(&self, store: &mut Store<Host>, func: Func) -> Result<()>990     async fn invoke_func(&self, store: &mut Store<Host>, func: Func) -> Result<()> {
991         let ty = func.ty(&store);
992         if ty.params().len() > 0 {
993             eprintln!(
994                 "warning: using `--invoke` with a function that takes arguments \
995                  is experimental and may break in the future"
996             );
997         }
998         let mut args = self.module_and_args.iter().skip(1);
999         let mut values = Vec::new();
1000         for ty in ty.params() {
1001             let val = match args.next() {
1002                 Some(s) => s,
1003                 None => {
1004                     if let Some(name) = &self.invoke {
1005                         bail!("not enough arguments for `{name}`")
1006                     } else {
1007                         bail!("not enough arguments for command default")
1008                     }
1009                 }
1010             };
1011             let val = val
1012                 .to_str()
1013                 .ok_or_else(|| format_err!("argument is not valid utf-8: {val:?}"))?;
1014             values.push(match ty {
1015                 // Supports both decimal and hexadecimal notation (with 0x prefix)
1016                 ValType::I32 => Val::I32(if val.starts_with("0x") || val.starts_with("0X") {
1017                     i32::from_str_radix(&val[2..], 16)?
1018                 } else {
1019                     val.parse::<i32>()?
1020                 }),
1021                 ValType::I64 => Val::I64(if val.starts_with("0x") || val.starts_with("0X") {
1022                     i64::from_str_radix(&val[2..], 16)?
1023                 } else {
1024                     val.parse::<i64>()?
1025                 }),
1026                 ValType::F32 => Val::F32(val.parse::<f32>()?.to_bits()),
1027                 ValType::F64 => Val::F64(val.parse::<f64>()?.to_bits()),
1028                 t => bail!("unsupported argument type {t:?}"),
1029             });
1030         }
1031 
1032         // Invoke the function and then afterwards print all the results that came
1033         // out, if there are any.
1034         let mut results = vec![Val::null_func_ref(); ty.results().len()];
1035         let invoke_res = func
1036             .call_async(&mut *store, &values, &mut results)
1037             .await
1038             .with_context(|| {
1039                 if let Some(name) = &self.invoke {
1040                     format!("failed to invoke `{name}`")
1041                 } else {
1042                     format!("failed to invoke command default")
1043                 }
1044             });
1045 
1046         if let Err(err) = invoke_res {
1047             return Err(self.handle_core_dump(&mut *store, err));
1048         }
1049 
1050         if !results.is_empty() {
1051             eprintln!(
1052                 "warning: using `--invoke` with a function that returns values \
1053                  is experimental and may break in the future"
1054             );
1055         }
1056 
1057         for result in results {
1058             match result {
1059                 Val::I32(i) => println!("{i}"),
1060                 Val::I64(i) => println!("{i}"),
1061                 Val::F32(f) => println!("{}", f32::from_bits(f)),
1062                 Val::F64(f) => println!("{}", f64::from_bits(f)),
1063                 Val::V128(i) => println!("{}", i.as_u128()),
1064                 Val::ExternRef(None) => println!("<null externref>"),
1065                 Val::ExternRef(Some(_)) => println!("<externref>"),
1066                 Val::FuncRef(None) => println!("<null funcref>"),
1067                 Val::FuncRef(Some(_)) => println!("<funcref>"),
1068                 Val::AnyRef(None) => println!("<null anyref>"),
1069                 Val::AnyRef(Some(_)) => println!("<anyref>"),
1070                 Val::ExnRef(None) => println!("<null exnref>"),
1071                 Val::ExnRef(Some(_)) => println!("<exnref>"),
1072                 Val::ContRef(None) => println!("<null contref>"),
1073                 Val::ContRef(Some(_)) => println!("<contref>"),
1074             }
1075         }
1076 
1077         Ok(())
1078     }
1079 
1080     #[cfg(feature = "coredump")]
handle_core_dump(&self, store: &mut Store<Host>, err: Error) -> Error1081     fn handle_core_dump(&self, store: &mut Store<Host>, err: Error) -> Error {
1082         let coredump_path = match &self.run.common.debug.coredump {
1083             Some(path) => path,
1084             None => return err,
1085         };
1086         if !err.is::<wasmtime::Trap>() {
1087             return err;
1088         }
1089         let source_name = self.module_and_args[0]
1090             .to_str()
1091             .unwrap_or_else(|| "unknown");
1092 
1093         if let Err(coredump_err) = write_core_dump(store, &err, &source_name, coredump_path) {
1094             eprintln!("warning: coredump failed to generate: {coredump_err}");
1095             err
1096         } else {
1097             err.context(format!("core dumped at {coredump_path}"))
1098         }
1099     }
1100 
1101     #[cfg(not(feature = "coredump"))]
handle_core_dump(&self, _store: &mut Store<Host>, err: Error) -> Error1102     fn handle_core_dump(&self, _store: &mut Store<Host>, err: Error) -> Error {
1103         err
1104     }
1105 
1106     /// Populates the given `Linker` with WASI APIs.
populate_with_wasi( &self, linker: &mut CliLinker, store: &mut Store<Host>, module: &RunTarget, ) -> Result<()>1107     fn populate_with_wasi(
1108         &self,
1109         linker: &mut CliLinker,
1110         store: &mut Store<Host>,
1111         module: &RunTarget,
1112     ) -> Result<()> {
1113         self.run.validate_p3_option()?;
1114         let cli = self.run.validate_cli_enabled()?;
1115 
1116         if cli != Some(false) {
1117             match linker {
1118                 CliLinker::Core(linker) => {
1119                     match (self.run.common.wasi.preview2, self.run.common.wasi.threads) {
1120                         // If preview2 is explicitly disabled, or if threads
1121                         // are enabled, then use the historical preview1
1122                         // implementation.
1123                         (Some(false), _) | (None, Some(true)) => {
1124                             wasi_common::tokio::add_to_linker(linker, |host| {
1125                                 host.legacy_p1_ctx.as_mut().unwrap()
1126                             })?;
1127                             self.set_legacy_p1_ctx(store)?;
1128                         }
1129                         // If preview2 was explicitly requested, always use it.
1130                         // Otherwise use it so long as threads are disabled.
1131                         //
1132                         // Note that for now `p0` is currently
1133                         // default-enabled but this may turn into
1134                         // default-disabled in the future.
1135                         (Some(true), _) | (None, Some(false) | None) => {
1136                             if self.run.common.wasi.preview0 != Some(false) {
1137                                 wasmtime_wasi::p0::add_to_linker_async(linker, |t| t.wasip1_ctx())?;
1138                             }
1139                             wasmtime_wasi::p1::add_to_linker_async(linker, |t| t.wasip1_ctx())?;
1140                             self.set_wasi_ctx(store)?;
1141                         }
1142                     }
1143                 }
1144                 #[cfg(feature = "component-model")]
1145                 CliLinker::Component(linker) => {
1146                     self.run.add_wasmtime_wasi_to_linker(linker)?;
1147                     self.set_wasi_ctx(store)?;
1148                 }
1149             }
1150         }
1151 
1152         if self.run.common.wasi.nn == Some(true) {
1153             #[cfg(not(feature = "wasi-nn"))]
1154             {
1155                 bail!("Cannot enable wasi-nn when the binary is not compiled with this feature.");
1156             }
1157             #[cfg(all(feature = "wasi-nn", feature = "component-model"))]
1158             {
1159                 let (backends, registry) = self.collect_preloaded_nn_graphs()?;
1160                 match linker {
1161                     CliLinker::Core(linker) => {
1162                         wasmtime_wasi_nn::witx::add_to_linker(linker, |host| {
1163                             Arc::get_mut(host.wasi_nn_witx.as_mut().unwrap())
1164                                 .expect("wasi-nn is not implemented with multi-threading support")
1165                         })?;
1166                         store.data_mut().wasi_nn_witx = Some(Arc::new(
1167                             wasmtime_wasi_nn::witx::WasiNnCtx::new(backends, registry),
1168                         ));
1169                     }
1170                     #[cfg(feature = "component-model")]
1171                     CliLinker::Component(linker) => {
1172                         wasmtime_wasi_nn::wit::add_to_linker(linker, |h: &mut Host| {
1173                             let ctx = h.wasip1_ctx.as_mut().expect("wasi is not configured");
1174                             let ctx = Arc::get_mut(ctx)
1175                                 .expect("wasmtime_wasi is not compatible with threads")
1176                                 .get_mut()
1177                                 .unwrap();
1178                             let nn_ctx = Arc::get_mut(h.wasi_nn_wit.as_mut().unwrap())
1179                                 .expect("wasi-nn is not implemented with multi-threading support");
1180                             WasiNnView::new(ctx.ctx().table, nn_ctx)
1181                         })?;
1182                         store.data_mut().wasi_nn_wit = Some(Arc::new(
1183                             wasmtime_wasi_nn::wit::WasiNnCtx::new(backends, registry),
1184                         ));
1185                     }
1186                 }
1187             }
1188         }
1189 
1190         if self.run.common.wasi.config == Some(true) {
1191             #[cfg(not(feature = "wasi-config"))]
1192             {
1193                 bail!(
1194                     "Cannot enable wasi-config when the binary is not compiled with this feature."
1195                 );
1196             }
1197             #[cfg(all(feature = "wasi-config", feature = "component-model"))]
1198             {
1199                 match linker {
1200                     CliLinker::Core(_) => {
1201                         bail!("Cannot enable wasi-config for core wasm modules");
1202                     }
1203                     CliLinker::Component(linker) => {
1204                         let vars = WasiConfigVariables::from_iter(
1205                             self.run
1206                                 .common
1207                                 .wasi
1208                                 .config_var
1209                                 .iter()
1210                                 .map(|v| (v.key.clone(), v.value.clone())),
1211                         );
1212 
1213                         wasmtime_wasi_config::add_to_linker(linker, |h| {
1214                             WasiConfig::new(Arc::get_mut(h.wasi_config.as_mut().unwrap()).unwrap())
1215                         })?;
1216                         store.data_mut().wasi_config = Some(Arc::new(vars));
1217                     }
1218                 }
1219             }
1220         }
1221 
1222         if self.run.common.wasi.keyvalue == Some(true) {
1223             #[cfg(not(feature = "wasi-keyvalue"))]
1224             {
1225                 bail!(
1226                     "Cannot enable wasi-keyvalue when the binary is not compiled with this feature."
1227                 );
1228             }
1229             #[cfg(all(feature = "wasi-keyvalue", feature = "component-model"))]
1230             {
1231                 match linker {
1232                     CliLinker::Core(_) => {
1233                         bail!("Cannot enable wasi-keyvalue for core wasm modules");
1234                     }
1235                     CliLinker::Component(linker) => {
1236                         let ctx = WasiKeyValueCtxBuilder::new()
1237                             .in_memory_data(
1238                                 self.run
1239                                     .common
1240                                     .wasi
1241                                     .keyvalue_in_memory_data
1242                                     .iter()
1243                                     .map(|v| (v.key.clone(), v.value.clone())),
1244                             )
1245                             .build();
1246 
1247                         wasmtime_wasi_keyvalue::add_to_linker(linker, |h| {
1248                             let ctx = h.wasip1_ctx.as_mut().expect("wasip2 is not configured");
1249                             let ctx = Arc::get_mut(ctx).unwrap().get_mut().unwrap();
1250                             WasiKeyValue::new(
1251                                 Arc::get_mut(h.wasi_keyvalue.as_mut().unwrap()).unwrap(),
1252                                 ctx.ctx().table,
1253                             )
1254                         })?;
1255                         store.data_mut().wasi_keyvalue = Some(Arc::new(ctx));
1256                     }
1257                 }
1258             }
1259         }
1260 
1261         if self.run.common.wasi.threads == Some(true) {
1262             #[cfg(not(feature = "wasi-threads"))]
1263             {
1264                 // Silence the unused warning for `module` as it is only used in the
1265                 // conditionally-compiled wasi-threads.
1266                 let _ = &module;
1267 
1268                 bail!(
1269                     "Cannot enable wasi-threads when the binary is not compiled with this feature."
1270                 );
1271             }
1272             #[cfg(feature = "wasi-threads")]
1273             {
1274                 let linker = match linker {
1275                     CliLinker::Core(linker) => linker,
1276                     _ => bail!("wasi-threads does not support components yet"),
1277                 };
1278                 let module = module.unwrap_core();
1279                 wasmtime_wasi_threads::add_to_linker(linker, store, &module, |host| {
1280                     host.wasi_threads.as_ref().unwrap()
1281                 })?;
1282                 store.data_mut().wasi_threads = Some(Arc::new(WasiThreadsCtx::new(
1283                     module.clone(),
1284                     Arc::new(linker.clone()),
1285                     true,
1286                 )?));
1287             }
1288         }
1289 
1290         if self.run.common.wasi.http == Some(true) {
1291             #[cfg(not(all(feature = "wasi-http", feature = "component-model")))]
1292             {
1293                 bail!("Cannot enable wasi-http when the binary is not compiled with this feature.");
1294             }
1295             #[cfg(all(feature = "wasi-http", feature = "component-model"))]
1296             {
1297                 match linker {
1298                     CliLinker::Core(_) => {
1299                         bail!("Cannot enable wasi-http for core wasm modules");
1300                     }
1301                     CliLinker::Component(linker) => {
1302                         wasmtime_wasi_http::p2::add_only_http_to_linker_async(linker)?;
1303                         #[cfg(feature = "component-model-async")]
1304                         if self.run.common.wasi.p3.unwrap_or(crate::common::P3_DEFAULT) {
1305                             wasmtime_wasi_http::p3::add_to_linker(linker)?;
1306                         }
1307                     }
1308                 }
1309                 let http = self.run.wasi_http_ctx()?;
1310                 store.data_mut().wasi_http = Some(Arc::new(http));
1311             }
1312         }
1313 
1314         if self.run.common.wasi.tls == Some(true) {
1315             #[cfg(all(not(all(feature = "wasi-tls", feature = "component-model"))))]
1316             {
1317                 bail!("Cannot enable wasi-tls when the binary is not compiled with this feature.");
1318             }
1319             #[cfg(all(feature = "wasi-tls", feature = "component-model",))]
1320             {
1321                 match linker {
1322                     CliLinker::Core(_) => {
1323                         bail!("Cannot enable wasi-tls for core wasm modules");
1324                     }
1325                     CliLinker::Component(linker) => {
1326                         let mut opts = wasmtime_wasi_tls::p2::LinkOptions::default();
1327                         opts.tls(true);
1328                         wasmtime_wasi_tls::p2::add_to_linker(linker, &opts)?;
1329 
1330                         #[cfg(feature = "component-model-async")]
1331                         if self.run.common.wasi.p3.unwrap_or(crate::common::P3_DEFAULT) {
1332                             wasmtime_wasi_tls::p3::add_to_linker(linker)?;
1333                         }
1334 
1335                         let ctx = wasmtime_wasi_tls::WasiTlsCtxBuilder::new().build();
1336                         store.data_mut().wasi_tls = Some(Arc::new(ctx));
1337                     }
1338                 }
1339             }
1340         }
1341 
1342         Ok(())
1343     }
1344 
set_legacy_p1_ctx(&self, store: &mut Store<Host>) -> Result<()>1345     fn set_legacy_p1_ctx(&self, store: &mut Store<Host>) -> Result<()> {
1346         let mut builder = WasiCtxBuilder::new();
1347         builder.args(&self.compute_argv()?)?;
1348 
1349         if self.run.common.wasi.inherit_stdin.unwrap_or(true) {
1350             builder.inherit_stdin();
1351         }
1352         if self.run.common.wasi.inherit_stdout.unwrap_or(true) {
1353             builder.inherit_stdout();
1354         }
1355         if self.run.common.wasi.inherit_stderr.unwrap_or(true) {
1356             builder.inherit_stderr();
1357         }
1358 
1359         if self.run.common.wasi.inherit_env == Some(true) {
1360             for (k, v) in std::env::vars() {
1361                 builder.env(&k, &v)?;
1362             }
1363         }
1364         for (key, value) in self.run.vars.iter() {
1365             let value = match value {
1366                 Some(value) => value.clone(),
1367                 None => match std::env::var_os(key) {
1368                     Some(val) => val
1369                         .into_string()
1370                         .map_err(|_| format_err!("environment variable `{key}` not valid utf-8"))?,
1371                     None => {
1372                         // leave the env var un-set in the guest
1373                         continue;
1374                     }
1375                 },
1376             };
1377             builder.env(key, &value)?;
1378         }
1379 
1380         let mut num_fd: usize = 3;
1381 
1382         if self.run.common.wasi.listenfd == Some(true) {
1383             num_fd = ctx_set_listenfd(num_fd, &mut builder)?;
1384         }
1385 
1386         for listener in self.run.compute_preopen_sockets()? {
1387             let listener = TcpListener::from_std(listener);
1388             builder.preopened_socket(num_fd as _, listener)?;
1389             num_fd += 1;
1390         }
1391 
1392         for (host, guest) in self.run.dirs.iter() {
1393             let dir = Dir::open_ambient_dir(host, ambient_authority())
1394                 .with_context(|| format!("failed to open directory '{host}'"))?;
1395             builder.preopened_dir(dir, guest)?;
1396         }
1397 
1398         store.data_mut().legacy_p1_ctx = Some(builder.build());
1399         Ok(())
1400     }
1401 
1402     /// Note the naming here is subtle, but this is effectively setting up a
1403     /// `wasmtime_wasi::WasiCtx` structure.
1404     ///
1405     /// This is stored in `Host` as `WasiP1Ctx` which internally contains the
1406     /// `WasiCtx` and `ResourceTable` used for WASI implementations. Exactly
1407     /// which "p" for WASIpN is more a reference to
1408     /// `wasmtime-wasi`-vs-`wasi-common` here more than anything else.
set_wasi_ctx(&self, store: &mut Store<Host>) -> Result<()>1409     fn set_wasi_ctx(&self, store: &mut Store<Host>) -> Result<()> {
1410         let mut builder = wasmtime_wasi::WasiCtxBuilder::new();
1411         builder.args(&self.compute_argv()?);
1412         if self.run.common.wasi.inherit_stdin.unwrap_or(true) {
1413             builder.inherit_stdin();
1414         }
1415         if self.run.common.wasi.inherit_stdout.unwrap_or(true) {
1416             builder.inherit_stdout();
1417         }
1418         if self.run.common.wasi.inherit_stderr.unwrap_or(true) {
1419             builder.inherit_stderr();
1420         }
1421         self.run.configure_wasip2(&mut builder)?;
1422         let mut ctx = builder.build_p1();
1423         if let Some(max) = self.run.common.wasi.max_resources {
1424             ctx.ctx().table.set_max_capacity(max);
1425             #[cfg(feature = "component-model-async")]
1426             if let Some(table) = store.concurrent_resource_table() {
1427                 table.set_max_capacity(max);
1428             }
1429         }
1430         if let Some(fuel) = self.run.common.wasi.hostcall_fuel {
1431             store.set_hostcall_fuel(fuel);
1432         }
1433         store.data_mut().wasip1_ctx = Some(Arc::new(Mutex::new(ctx)));
1434         Ok(())
1435     }
1436 
1437     #[cfg(feature = "wasi-nn")]
collect_preloaded_nn_graphs( &self, ) -> Result<(Vec<wasmtime_wasi_nn::Backend>, wasmtime_wasi_nn::Registry)>1438     fn collect_preloaded_nn_graphs(
1439         &self,
1440     ) -> Result<(Vec<wasmtime_wasi_nn::Backend>, wasmtime_wasi_nn::Registry)> {
1441         let graphs = self
1442             .run
1443             .common
1444             .wasi
1445             .nn_graph
1446             .iter()
1447             .map(|g| (g.format.clone(), g.dir.clone()))
1448             .collect::<Vec<_>>();
1449         wasmtime_wasi_nn::preload(&graphs)
1450     }
1451 }
1452 
1453 /// The `T` in `Store<T>` for what the CLI is running.
1454 ///
1455 /// This structures has a number of contexts used for various WASI proposals.
1456 /// Note that all of them are optional meaning that they're `None` by default
1457 /// and enabled with various CLI flags (some CLI flags are on-by-default). Note
1458 /// additionally that this structure is `Clone` to implement the `wasi-threads`
1459 /// proposal. Many WASI proposals are not compatible with `wasi-threads` so to
1460 /// model this `Arc` and `Arc<Mutex<T>>` is used for many configurations. If a
1461 /// WASI proposal is inherently threadsafe it's protected with just an `Arc` to
1462 /// share its configuration across many threads.
1463 ///
1464 /// If mutation is required then `Mutex` is used. Note though that the mutex is
1465 /// not actually locked as access always goes through `Arc::get_mut` which
1466 /// effectively asserts that there's only one thread. In short much of this is
1467 /// not compatible with `wasi-threads`.
1468 #[derive(Default, Clone)]
1469 pub struct Host {
1470     limits: StoreLimits,
1471     #[cfg(feature = "profiling")]
1472     guest_profiler: Option<Arc<wasmtime::GuestProfiler>>,
1473 
1474     // Legacy wasip1 context using `wasi_common`, not set unless opted-in-to
1475     // with the CLI.
1476     legacy_p1_ctx: Option<wasi_common::WasiCtx>,
1477 
1478     // Context for both WASIp1 and WASIp2 (and beyond) for the `wasmtime_wasi`
1479     // crate. This has both `wasmtime_wasi::WasiCtx` as well as a
1480     // `ResourceTable` internally to be used.
1481     //
1482     // The Mutex is only needed to satisfy the Sync constraint but we never
1483     // actually perform any locking on it as we use Mutex::get_mut for every
1484     // access.
1485     wasip1_ctx: Option<Arc<Mutex<wasmtime_wasi::p1::WasiP1Ctx>>>,
1486 
1487     #[cfg(feature = "wasi-nn")]
1488     wasi_nn_wit: Option<Arc<wasmtime_wasi_nn::wit::WasiNnCtx>>,
1489     #[cfg(feature = "wasi-nn")]
1490     wasi_nn_witx: Option<Arc<wasmtime_wasi_nn::witx::WasiNnCtx>>,
1491 
1492     #[cfg(feature = "wasi-threads")]
1493     wasi_threads: Option<Arc<WasiThreadsCtx<Host>>>,
1494     #[cfg(feature = "wasi-http")]
1495     wasi_http: Option<Arc<WasiHttpCtx>>,
1496     #[cfg(feature = "wasi-http")]
1497     wasi_http_hooks: crate::common::HttpHooks,
1498 
1499     #[cfg(feature = "wasi-config")]
1500     wasi_config: Option<Arc<WasiConfigVariables>>,
1501     #[cfg(feature = "wasi-keyvalue")]
1502     wasi_keyvalue: Option<Arc<WasiKeyValueCtx>>,
1503     #[cfg(feature = "wasi-tls")]
1504     wasi_tls: Option<Arc<wasmtime_wasi_tls::WasiTlsCtx>>,
1505 }
1506 
1507 impl Host {
wasip1_ctx(&mut self) -> &mut wasmtime_wasi::p1::WasiP1Ctx1508     pub(crate) fn wasip1_ctx(&mut self) -> &mut wasmtime_wasi::p1::WasiP1Ctx {
1509         unwrap_singlethread_context(&mut self.wasip1_ctx)
1510     }
1511 }
1512 
unwrap_singlethread_context<T>(ctx: &mut Option<Arc<Mutex<T>>>) -> &mut T1513 fn unwrap_singlethread_context<T>(ctx: &mut Option<Arc<Mutex<T>>>) -> &mut T {
1514     let ctx = ctx.as_mut().expect("context not configured");
1515     Arc::get_mut(ctx)
1516         .expect("context is not compatible with threads")
1517         .get_mut()
1518         .unwrap()
1519 }
1520 
1521 impl WasiView for Host {
ctx(&mut self) -> WasiCtxView<'_>1522     fn ctx(&mut self) -> WasiCtxView<'_> {
1523         WasiView::ctx(self.wasip1_ctx())
1524     }
1525 }
1526 
1527 #[cfg(feature = "wasi-http")]
1528 impl wasmtime_wasi_http::p2::WasiHttpView for Host {
http(&mut self) -> wasmtime_wasi_http::p2::WasiHttpCtxView<'_>1529     fn http(&mut self) -> wasmtime_wasi_http::p2::WasiHttpCtxView<'_> {
1530         let ctx = self.wasi_http.as_mut().unwrap();
1531         let ctx = Arc::get_mut(ctx).expect("wasmtime_wasi_http is not compatible with threads");
1532         wasmtime_wasi_http::p2::WasiHttpCtxView {
1533             table: WasiView::ctx(unwrap_singlethread_context(&mut self.wasip1_ctx)).table,
1534             ctx,
1535             hooks: &mut self.wasi_http_hooks,
1536         }
1537     }
1538 }
1539 
1540 #[cfg(all(feature = "wasi-http", feature = "component-model-async"))]
1541 impl wasmtime_wasi_http::p3::WasiHttpView for Host {
http(&mut self) -> wasmtime_wasi_http::p3::WasiHttpCtxView<'_>1542     fn http(&mut self) -> wasmtime_wasi_http::p3::WasiHttpCtxView<'_> {
1543         let ctx = self.wasi_http.as_mut().unwrap();
1544         let ctx = Arc::get_mut(ctx).expect("wasmtime_wasi_http is not compatible with threads");
1545         wasmtime_wasi_http::p3::WasiHttpCtxView {
1546             table: WasiView::ctx(unwrap_singlethread_context(&mut self.wasip1_ctx)).table,
1547             ctx,
1548             hooks: &mut self.wasi_http_hooks,
1549         }
1550     }
1551 }
1552 
1553 #[cfg(all(feature = "wasi-tls"))]
1554 impl wasmtime_wasi_tls::WasiTlsView for Host {
tls(&mut self) -> wasmtime_wasi_tls::WasiTlsCtxView<'_>1555     fn tls(&mut self) -> wasmtime_wasi_tls::WasiTlsCtxView<'_> {
1556         wasmtime_wasi_tls::WasiTlsCtxView {
1557             table: WasiView::ctx(unwrap_singlethread_context(&mut self.wasip1_ctx)).table,
1558             ctx: Arc::get_mut(self.wasi_tls.as_mut().unwrap()).unwrap(),
1559         }
1560     }
1561 }
1562 
ctx_set_listenfd(mut num_fd: usize, builder: &mut WasiCtxBuilder) -> Result<usize>1563 fn ctx_set_listenfd(mut num_fd: usize, builder: &mut WasiCtxBuilder) -> Result<usize> {
1564     let _ = &mut num_fd;
1565     let _ = &mut *builder;
1566 
1567     #[cfg(all(unix, feature = "run"))]
1568     {
1569         use listenfd::ListenFd;
1570 
1571         for env in ["LISTEN_FDS", "LISTEN_FDNAMES"] {
1572             if let Ok(val) = std::env::var(env) {
1573                 builder.env(env, &val)?;
1574             }
1575         }
1576 
1577         let mut listenfd = ListenFd::from_env();
1578 
1579         for i in 0..listenfd.len() {
1580             if let Some(stdlistener) = listenfd.take_tcp_listener(i)? {
1581                 let _ = stdlistener.set_nonblocking(true)?;
1582                 let listener = TcpListener::from_std(stdlistener);
1583                 builder.preopened_socket((3 + i) as _, listener)?;
1584                 num_fd = 3 + i;
1585             }
1586         }
1587     }
1588 
1589     Ok(num_fd)
1590 }
1591 
1592 #[cfg(feature = "coredump")]
write_core_dump( store: &mut Store<Host>, err: &wasmtime::Error, name: &str, path: &str, ) -> Result<()>1593 fn write_core_dump(
1594     store: &mut Store<Host>,
1595     err: &wasmtime::Error,
1596     name: &str,
1597     path: &str,
1598 ) -> Result<()> {
1599     use std::fs::File;
1600     use std::io::Write;
1601 
1602     let core_dump = err
1603         .downcast_ref::<wasmtime::WasmCoreDump>()
1604         .expect("should have been configured to capture core dumps");
1605 
1606     let core_dump = core_dump.serialize(store, name);
1607 
1608     let mut core_dump_file =
1609         File::create(path).with_context(|| format!("failed to create file at `{path}`"))?;
1610     core_dump_file
1611         .write_all(&core_dump)
1612         .with_context(|| format!("failed to write core dump file at `{path}`"))?;
1613     Ok(())
1614 }
1615