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