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