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