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};
12#[cfg(feature = "debug")]
13use std::pin::Pin;
14use std::thread;
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
30fn parse_preloads(s: &str) -> Result<(String, PathBuf)> {
31 let parts: Vec<&str> = s.splitn(2, '=').collect();
32 if parts.len() != 2 {
33 bail!("must contain exactly one equals character ('=')");
34 }
35 Ok((parts[0].into(), parts[1].into()))
36}
37
38#[derive(Parser)]
40pub struct RunCommand {
41 #[command(flatten)]
42 #[expect(missing_docs, reason = "don't want to mess with clap doc-strings")]
43 pub run: RunCommon,
44
45 #[arg(long, value_name = "FUNCTION")]
57 pub invoke: Option<String>,
58
59 #[command(flatten)]
60 #[expect(missing_docs, reason = "don't want to mess with clap doc-strings")]
61 pub preloads: Preloads,
62
63 #[arg(long)]
70 pub argv0: Option<String>,
71
72 #[arg(skip)]
78 pub module_bytes: Option<&'static [u8]>,
79
80 #[arg(value_name = "WASM", trailing_var_arg = true, required = true)]
86 pub module_and_args: Vec<OsString>,
87}
88
89impl RunCommand {
90 #[cfg(feature = "debug")]
99 pub(crate) fn debugger_run(&mut self) -> Result<Option<RunCommand>> {
100 fn set_implicit_option(
101 place: &str,
102 name: &str,
103 setting: &mut Option<bool>,
104 value: bool,
105 ) -> Result<()> {
106 if *setting == Some(!value) {
107 bail!(
108 "Explicitly-set option on {place} {name}={} is not compatible with debugging-implied setting {value}",
109 setting.unwrap()
110 );
111 }
112 *setting = Some(value);
113 Ok(())
114 }
115
116 #[cfg(feature = "gdbstub")]
119 let override_bytes = if let Some(addr) = self.run.gdbstub.as_deref() {
120 if self.run.common.debug.debugger.is_some() {
121 bail!("-g/--gdb cannot be combined with -Ddebugger=");
122 }
123 let addr = if addr.parse::<u16>().is_ok() {
125 format!("127.0.0.1:{addr}")
126 } else {
127 use std::net::SocketAddr;
128 addr.parse::<SocketAddr>()
129 .with_context(|| format!("invalid gdbstub address: `{addr}`"))?;
130 addr.to_string()
131 };
132 self.run.common.debug.debugger = Some("<built-in gdbstub>".into());
133 self.run.common.debug.arg.push(addr);
134 Some(gdbstub_component_artifact::GDBSTUB_COMPONENT)
135 } else {
136 None
137 };
138 #[cfg(not(feature = "gdbstub"))]
139 let override_bytes = None;
140
141 if let Some(debugger_component_path) = self.run.common.debug.debugger.as_ref() {
142 set_implicit_option(
143 "debuggee",
144 "guest_debug",
145 &mut self.run.common.debug.guest_debug,
146 true,
147 )?;
148 set_implicit_option(
149 "debuggee",
150 "epoch_interruption",
151 &mut self.run.common.wasm.epoch_interruption,
152 true,
153 )?;
154
155 let mut debugger_run = RunCommand::try_parse_from(
156 ["run".into(), debugger_component_path.into()]
157 .into_iter()
158 .chain(self.run.common.debug.arg.iter().map(OsString::from)),
159 )?;
160 debugger_run.module_bytes = override_bytes;
161
162 debugger_run.run.common.wasi.tcp.get_or_insert(true);
165 debugger_run
166 .run
167 .common
168 .wasi
169 .inherit_network
170 .get_or_insert(true);
171
172 set_implicit_option(
180 "debugger",
181 "inherit_stdin",
182 &mut debugger_run.run.common.wasi.inherit_stdin,
183 self.run.common.debug.inherit_stdin.unwrap_or(false),
184 )?;
185 set_implicit_option(
186 "debugger",
187 "inherit_stdout",
188 &mut debugger_run.run.common.wasi.inherit_stdout,
189 self.run.common.debug.inherit_stdout.unwrap_or(false),
190 )?;
191 set_implicit_option(
192 "debugger",
193 "inherit_stderr",
194 &mut debugger_run.run.common.wasi.inherit_stderr,
195 self.run.common.debug.inherit_stderr.unwrap_or(false),
196 )?;
197 Ok(Some(debugger_run))
198 } else {
199 Ok(None)
200 }
201 }
202}
203
204#[expect(missing_docs, reason = "don't want to mess with clap doc-strings")]
205#[derive(Parser, Default, Clone)]
206pub struct Preloads {
207 #[arg(
209 long = "preload",
210 number_of_values = 1,
211 value_name = "NAME=MODULE_PATH",
212 value_parser = parse_preloads,
213 )]
214 modules: Vec<(String, PathBuf)>,
215}
216
217#[expect(missing_docs, reason = "self-explanatory")]
219pub enum CliLinker {
220 Core(wasmtime::Linker<Host>),
221 #[cfg(feature = "component-model")]
222 Component(wasmtime::component::Linker<Host>),
223}
224
225#[expect(missing_docs, reason = "self-explanatory")]
227pub enum CliInstance {
228 Core(wasmtime::Instance),
229 #[cfg(feature = "component-model")]
230 Component(wasmtime::component::Instance),
231}
232
233impl RunCommand {
234 #[cfg(feature = "run")]
236 pub fn execute(mut self) -> Result<()> {
237 let runtime = tokio::runtime::Builder::new_multi_thread()
238 .enable_time()
239 .enable_io()
240 .build()?;
241
242 runtime.block_on(async {
243 self.run.common.init_logging()?;
244
245 #[cfg(feature = "debug")]
246 let debug_run = self.debugger_run()?;
247
248 let engine = self.new_engine()?;
249 let main = self.run.load_module(
250 &engine,
251 self.module_and_args[0].as_ref(),
252 self.module_bytes.as_ref().map(|v| &v[..]),
253 )?;
254 let (mut store, mut linker) = self.new_store_and_linker(&engine, &main)?;
255
256 #[cfg(feature = "debug")]
257 if let Some(mut debug_run) = debug_run {
258 let debug_engine = debug_run.new_engine()?;
259 let debug_main = debug_run.run.load_module(
260 &debug_engine,
261 debug_run.module_and_args[0].as_ref(),
262 debug_run.module_bytes.as_ref().map(|v| &v[..]),
263 )?;
264 let (mut debug_store, debug_linker) =
265 debug_run.new_store_and_linker(&debug_engine, &debug_main)?;
266
267 let debug_component = match debug_main {
268 RunTarget::Core(_) => wasmtime::bail!(
269 "Debugger component is a core module; only components are supported"
270 ),
271 RunTarget::Component(c) => c,
272 };
273 let mut debug_linker = match debug_linker {
274 CliLinker::Core(_) => unreachable!(),
275 CliLinker::Component(l) => l,
276 };
277 debug_run.add_debugger_api(&mut debug_linker)?;
278
279 match &main {
284 RunTarget::Core(m) => {
285 store.debug_register_module(m)?;
286 }
287 #[cfg(feature = "component-model")]
288 RunTarget::Component(c) => {
289 store.debug_register_component(c)?;
290 }
291 }
292
293 debug_run
294 .invoke_debugger(
295 &mut debug_store,
296 &debug_component,
297 &mut debug_linker,
298 store,
299 move |store| {
300 Box::pin(async move {
301 let engine_clone = store.engine().clone();
302 let cancel =
303 std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
304 let cancel_clone = cancel.clone();
305 let epoch_thread = thread::spawn(move || {
306 while !cancel_clone.load(std::sync::atomic::Ordering::Relaxed) {
307 thread::sleep(std::time::Duration::from_millis(1));
308 engine_clone.increment_epoch();
309 }
310 });
311 self.instantiate_and_run(&engine, &mut linker, &main, store)
312 .await?;
313 cancel.store(true, std::sync::atomic::Ordering::Relaxed);
314 epoch_thread
315 .join()
316 .map_err(|_| wasmtime::Error::msg("epoch thread panicked"))?;
317 Ok(())
318 })
319 },
320 )
321 .await?;
322 return Ok(());
323 }
324
325 self.instantiate_and_run(&engine, &mut linker, &main, &mut store)
326 .await?;
327 Ok(())
328 })
329 }
330
331 pub fn new_engine(&mut self) -> Result<Engine> {
333 let mut config = self.run.common.config(None)?;
334
335 if self.run.common.wasm.timeout.is_some() {
336 config.epoch_interruption(true);
337 }
338 match self.run.profile {
339 Some(Profile::Native(s)) => {
340 config.profiler(s);
341 }
342 Some(Profile::Guest { .. }) => {
343 config.epoch_interruption(true);
345 }
346 None => {}
347 }
348
349 Engine::new(&config)
350 }
351
352 pub fn new_store_and_linker(
358 &mut self,
359 engine: &Engine,
360 main: &RunTarget,
361 ) -> Result<(Store<Host>, CliLinker)> {
362 if let Some(path) = &self.run.common.debug.coredump {
364 if path.contains("%") {
365 bail!("the coredump-on-trap path does not support patterns yet.")
366 }
367 }
368
369 let mut linker = match &main {
370 RunTarget::Core(_) => CliLinker::Core(wasmtime::Linker::new(&engine)),
371 #[cfg(feature = "component-model")]
372 RunTarget::Component(_) => {
373 CliLinker::Component(wasmtime::component::Linker::new(&engine))
374 }
375 };
376 if let Some(enable) = self.run.common.wasm.unknown_exports_allow {
377 match &mut linker {
378 CliLinker::Core(l) => {
379 l.allow_unknown_exports(enable);
380 }
381 #[cfg(feature = "component-model")]
382 CliLinker::Component(_) => {
383 bail!("--allow-unknown-exports not supported with components");
384 }
385 }
386 }
387
388 let host = Host::default();
389
390 let mut store = Store::new(&engine, host);
391 self.populate_with_wasi(&mut linker, &mut store)?;
392 self.run.configure_store(&mut store, |t| &mut t.limits)?;
393
394 Ok((store, linker))
395 }
396
397 #[cfg(feature = "debug")]
398 pub(crate) fn add_debugger_api(
399 &mut self,
400 linker: &mut wasmtime::component::Linker<Host>,
401 ) -> Result<()> {
402 wasmtime_debugger::add_to_linker(linker, |x| x.ctx().table)?;
403 Ok(())
404 }
405
406 pub async fn instantiate_and_run(
412 &self,
413 engine: &Engine,
414 linker: &mut CliLinker,
415 main: &RunTarget,
416 store: &mut Store<Host>,
417 ) -> Result<CliInstance> {
418 let dur = self
419 .run
420 .common
421 .wasm
422 .timeout
423 .unwrap_or(std::time::Duration::MAX);
424 let result = tokio::time::timeout(dur, async {
425 let mut profiled_modules: Vec<(String, Module)> = Vec::new();
426 if let RunTarget::Core(m) = &main {
427 profiled_modules.push(("".to_string(), m.clone()));
428 }
429
430 for (name, path) in self.preloads.modules.iter() {
432 let preload_target = self.run.load_module(&engine, path, None)?;
434 let preload_module = match preload_target {
435 RunTarget::Core(m) => m,
436 #[cfg(feature = "component-model")]
437 RunTarget::Component(_) => {
438 bail!("components cannot be loaded with `--preload`")
439 }
440 };
441 profiled_modules.push((name.to_string(), preload_module.clone()));
442
443 match linker {
445 #[cfg(feature = "cranelift")]
446 CliLinker::Core(linker) => {
447 linker
448 .module_async(&mut *store, name, &preload_module)
449 .await
450 .with_context(|| {
451 format!(
452 "failed to process preload `{}` at `{}`",
453 name,
454 path.display()
455 )
456 })?;
457 }
458 #[cfg(not(feature = "cranelift"))]
459 CliLinker::Core(_) => {
460 bail!("support for --preload disabled at compile time");
461 }
462 #[cfg(feature = "component-model")]
463 CliLinker::Component(_) => {
464 bail!("--preload cannot be used with components");
465 }
466 }
467 }
468
469 self.load_main_module(store, linker, &main, profiled_modules)
470 .await
471 .with_context(|| {
472 format!(
473 "failed to run main module `{}`",
474 self.module_and_args[0].to_string_lossy()
475 )
476 })
477 })
478 .await;
479
480 let instance = match result.unwrap_or_else(|elapsed| {
482 Err(wasmtime::Error::from(wasmtime::Trap::Interrupt))
483 .with_context(|| format!("timed out after {elapsed}"))
484 }) {
485 Ok(instance) => instance,
486 Err(e) => {
487 if store.data().wasip1_ctx.is_some() {
491 if let Some(exit) = e.downcast_ref::<wasmtime_wasi::I32Exit>() {
492 std::process::exit(exit.0);
493 }
494 }
495 if e.is::<wasmtime::Trap>() {
496 eprintln!("Error: {e:?}");
497 cfg_select! {
498 unix => {
499 std::process::exit(rustix::process::EXIT_SIGNALED_SIGABRT);
500 }
501 windows => {
502 std::process::exit(3);
504 }
505 }
506 }
507 return Err(e);
508 }
509 };
510
511 Ok(instance)
512 }
513
514 pub(crate) fn compute_argv(&self) -> Result<Vec<String>> {
515 let mut result = Vec::new();
516
517 for (i, arg) in self.module_and_args.iter().enumerate() {
518 let arg = if i == 0 {
521 match &self.argv0 {
522 Some(s) => s.as_ref(),
523 None => Path::new(arg).components().next_back().unwrap().as_os_str(),
524 }
525 } else {
526 arg.as_ref()
527 };
528 result.push(
529 arg.to_str()
530 .ok_or_else(|| format_err!("failed to convert {arg:?} to utf-8"))?
531 .to_string(),
532 );
533 }
534
535 Ok(result)
536 }
537
538 fn setup_epoch_handler(
539 &self,
540 store: &mut Store<Host>,
541 main_target: &RunTarget,
542 profiled_modules: Vec<(String, Module)>,
543 ) -> Result<Box<dyn FnOnce(&mut Store<Host>) + Send>> {
544 if self.run.common.debug.debugger.is_some() {
550 if self.run.profile.is_some() {
551 bail!("Cannot set profile options together with debugging; they are incompatible");
552 }
553 if self.run.common.wasm.timeout.is_some() {
554 bail!("Cannot set timeout options together with debugging; they are incompatible");
555 }
556 store.epoch_deadline_async_yield_and_update(1);
557 } else {
558 if let Some(Profile::Guest { path, interval }) = &self.run.profile {
559 #[cfg(feature = "profiling")]
560 return Ok(self.setup_guest_profiler(
561 store,
562 main_target,
563 profiled_modules,
564 path,
565 *interval,
566 )?);
567 #[cfg(not(feature = "profiling"))]
568 {
569 let _ = (profiled_modules, path, interval, main_target);
570 bail!("support for profiling disabled at compile time");
571 }
572 }
573
574 if let Some(timeout) = self.run.common.wasm.timeout {
575 store.set_epoch_deadline(1);
576 let engine = store.engine().clone();
577 thread::spawn(move || {
578 thread::sleep(timeout);
579 engine.increment_epoch();
580 });
581 }
582 }
583
584 Ok(Box::new(|_store| {}))
585 }
586
587 #[cfg(feature = "profiling")]
588 fn setup_guest_profiler(
589 &self,
590 store: &mut Store<Host>,
591 main_target: &RunTarget,
592 profiled_modules: Vec<(String, Module)>,
593 path: &str,
594 interval: std::time::Duration,
595 ) -> Result<Box<dyn FnOnce(&mut Store<Host>) + Send>> {
596 use wasmtime::{AsContext, GuestProfiler, StoreContext, StoreContextMut, UpdateDeadline};
597
598 let module_name = self.module_and_args[0].to_str().unwrap_or("<main module>");
599 store.data_mut().guest_profiler = match main_target {
600 RunTarget::Core(_m) => Some(GuestProfiler::new(
601 store.engine(),
602 module_name,
603 interval,
604 profiled_modules,
605 )?),
606 RunTarget::Component(component) => Some(GuestProfiler::new_component(
607 store.engine(),
608 module_name,
609 interval,
610 component.clone(),
611 profiled_modules,
612 )?),
613 };
614
615 fn sample(
616 mut store: StoreContextMut<Host>,
617 f: impl FnOnce(&mut GuestProfiler, StoreContext<Host>),
618 ) {
619 let mut profiler = store.data_mut().guest_profiler.take().unwrap();
620 f(&mut profiler, store.as_context());
621 store.data_mut().guest_profiler = Some(profiler);
622 }
623
624 store.call_hook(|store, kind| {
625 sample(store, |profiler, store| profiler.call_hook(store, kind));
626 Ok(())
627 });
628
629 if let Some(timeout) = self.run.common.wasm.timeout {
630 let mut timeout = (timeout.as_secs_f64() / interval.as_secs_f64()).ceil() as u64;
631 assert!(timeout > 0);
632 store.epoch_deadline_callback(move |store| {
633 sample(store, |profiler, store| {
634 profiler.sample(store, std::time::Duration::ZERO)
635 });
636 timeout -= 1;
637 if timeout == 0 {
638 bail!("timeout exceeded");
639 }
640 Ok(UpdateDeadline::Continue(1))
641 });
642 } else {
643 store.epoch_deadline_callback(move |store| {
644 sample(store, |profiler, store| {
645 profiler.sample(store, std::time::Duration::ZERO)
646 });
647 Ok(UpdateDeadline::Continue(1))
648 });
649 }
650
651 store.set_epoch_deadline(1);
652 let engine = store.engine().clone();
653 thread::spawn(move || {
654 loop {
655 thread::sleep(interval);
656 engine.increment_epoch();
657 }
658 });
659
660 let path = path.to_string();
661 Ok(Box::new(move |store| {
662 let profiler = store.data_mut().guest_profiler.take().unwrap();
663 if let Err(e) = std::fs::File::create(&path)
664 .map_err(wasmtime::Error::new)
665 .and_then(|output| profiler.finish(std::io::BufWriter::new(output)))
666 {
667 eprintln!("failed writing profile at {path}: {e:#}");
668 } else {
669 eprintln!();
670 eprintln!("Profile written to: {path}");
671 eprintln!("View this profile at https://profiler.firefox.com/.");
672 }
673 }))
674 }
675
676 async fn load_main_module(
677 &self,
678 store: &mut Store<Host>,
679 linker: &mut CliLinker,
680 main_target: &RunTarget,
681 profiled_modules: Vec<(String, Module)>,
682 ) -> Result<CliInstance> {
683 if self.run.common.wasm.unknown_imports_trap == Some(true) {
686 match linker {
687 CliLinker::Core(linker) => {
688 linker.define_unknown_imports_as_traps(main_target.unwrap_core())?;
689 }
690 #[cfg(feature = "component-model")]
691 CliLinker::Component(linker) => {
692 linker.define_unknown_imports_as_traps(main_target.unwrap_component())?;
693 }
694 }
695 }
696
697 if self.run.common.wasm.unknown_imports_default == Some(true) {
699 match linker {
700 CliLinker::Core(linker) => {
701 linker.define_unknown_imports_as_default_values(
702 &mut *store,
703 main_target.unwrap_core(),
704 )?;
705 }
706 _ => bail!("cannot use `--default-values-unknown-imports` with components"),
707 }
708 }
709
710 let finish_epoch_handler =
711 self.setup_epoch_handler(store, main_target, profiled_modules)?;
712
713 let result = match linker {
714 CliLinker::Core(linker) => {
715 let module = main_target.unwrap_core();
716 let instance = linker
717 .instantiate_async(&mut *store, &module)
718 .await
719 .with_context(|| {
720 format!("failed to instantiate {:?}", self.module_and_args[0])
721 })?;
722
723 if let Some(func) = instance.get_func(&mut *store, "_initialize") {
726 func.typed::<(), ()>(&store)?
727 .call_async(&mut *store, ())
728 .await?;
729 }
730
731 let func = if let Some(name) = &self.invoke {
734 Some(
735 instance
736 .get_func(&mut *store, name)
737 .ok_or_else(|| format_err!("no func export named `{name}` found"))?,
738 )
739 } else {
740 instance
741 .get_func(&mut *store, "")
742 .or_else(|| instance.get_func(&mut *store, "_start"))
743 };
744
745 if let Some(func) = func {
746 self.invoke_func(store, func).await?;
747 }
748 Ok(CliInstance::Core(instance))
749 }
750 #[cfg(feature = "component-model")]
751 CliLinker::Component(linker) => {
752 let component = main_target.unwrap_component();
753 let result = if self.invoke.is_some() {
754 self.invoke_component(&mut *store, component, linker).await
755 } else {
756 self.run_command_component(&mut *store, component, linker)
757 .await
758 };
759 result
760 .map(CliInstance::Component)
761 .map_err(|e| self.handle_core_dump(&mut *store, e))
762 }
763 };
764 finish_epoch_handler(store);
765
766 result
767 }
768
769 #[cfg(feature = "component-model")]
770 async fn invoke_component(
771 &self,
772 store: &mut Store<Host>,
773 component: &wasmtime::component::Component,
774 linker: &mut wasmtime::component::Linker<Host>,
775 ) -> Result<wasmtime::component::Instance> {
776 use wasmtime::component::{
777 Val,
778 wasm_wave::{
779 untyped::UntypedFuncCall,
780 wasm::{DisplayFuncResults, WasmFunc},
781 },
782 };
783
784 let invoke: &String = self.invoke.as_ref().unwrap();
786
787 let untyped_call = UntypedFuncCall::parse(invoke).with_context(|| {
788 format!(
789 "Failed to parse invoke '{invoke}': See https://docs.wasmtime.dev/cli-options.html#run for syntax",
790 )
791 })?;
792
793 let name = untyped_call.item_name().map_err(|e| {
794 wasmtime::Error::from_anyhow(e).context(format!(
795 "parsing `{}` as a wit item name",
796 untyped_call.name()
797 ))
798 })?;
799
800 let (export, func_type) = Self::search_component_funcs(store, &component, &name)?;
801
802 let param_types = WasmFunc::params(&func_type).collect::<Vec<_>>();
803 let params = untyped_call
804 .to_wasm_params(¶m_types)
805 .with_context(|| format!("while interpreting parameters in invoke \"{invoke}\""))?;
806
807 let instance = linker.instantiate_async(&mut *store, component).await?;
808
809 let func = instance
810 .get_func(&mut *store, export)
811 .expect("found export index");
812
813 let mut results = vec![Val::Bool(false); func_type.results().len()];
814 self.call_component_func(store, ¶ms, func, &mut results)
815 .await?;
816
817 println!("{}", DisplayFuncResults(&results));
818 Ok(instance)
819 }
820
821 #[cfg(feature = "component-model")]
822 async fn call_component_func(
823 &self,
824 store: &mut Store<Host>,
825 params: &[wasmtime::component::Val],
826 func: wasmtime::component::Func,
827 results: &mut Vec<wasmtime::component::Val>,
828 ) -> Result<(), Error> {
829 #[cfg(feature = "component-model-async")]
830 if self.run.common.wasm.concurrency_support.unwrap_or(true) {
831 store
832 .run_concurrent(async |store| func.call_concurrent(store, params, results).await)
833 .await??;
834 return Ok(());
835 }
836
837 func.call_async(&mut *store, ¶ms, results).await?;
838 Ok(())
839 }
840
841 #[cfg(feature = "component-model")]
844 async fn run_command_component(
845 &self,
846 store: &mut Store<Host>,
847 component: &wasmtime::component::Component,
848 linker: &wasmtime::component::Linker<Host>,
849 ) -> Result<wasmtime::component::Instance> {
850 let instance = linker.instantiate_async(&mut *store, component).await?;
851
852 let mut result = None;
853 let _ = &mut result;
854
855 #[cfg(feature = "component-model-async")]
858 if self.run.common.wasi.p3.unwrap_or(crate::common::P3_DEFAULT) {
859 if let Ok(command) = wasmtime_wasi::p3::bindings::Command::new(&mut *store, &instance) {
860 result = Some(
861 store
862 .run_concurrent(async |store| command.wasi_cli_run().call_run(store).await)
863 .await?,
864 );
865 }
866 }
867
868 let result = match result {
869 Some(result) => result,
870 None => {
873 wasmtime_wasi::p2::bindings::Command::new(&mut *store, &instance)?
874 .wasi_cli_run()
875 .call_run(&mut *store)
876 .await
877 }
878 };
879 let wasm_result = result.context("failed to invoke `run` function")?;
880
881 match wasm_result {
884 Ok(()) => Ok(instance),
885 Err(()) => Err(wasmtime_wasi::I32Exit(1).into()),
886 }
887 }
888
889 #[cfg(feature = "debug")]
894 pub(crate) async fn invoke_debugger<
895 T: Send + 'static,
896 F: FnOnce(&mut Store<T>) -> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>>
897 + Send
898 + 'static,
899 >(
900 &self,
901 store: &mut Store<Host>,
902 component: &wasmtime::component::Component,
903 linker: &mut wasmtime::component::Linker<Host>,
904 debuggee_host: Store<T>,
905 body: F,
906 ) -> Result<()> {
907 let instance = linker.instantiate_async(&mut *store, component).await?;
908 let command = wasmtime_debugger::DebuggerComponent::new(&mut *store, &instance)?;
909 let debuggee = wasmtime_debugger::Debuggee::new(debuggee_host, body);
910 let debuggee = wasmtime_debugger::add_debuggee(store.data_mut().ctx().table, debuggee)?;
911 {
912 let borrowed = wasmtime::component::Resource::new_borrow(debuggee.rep());
917 let args = self.compute_argv()?;
918 command
919 .bytecodealliance_wasmtime_debugger()
920 .call_debug(&mut *store, borrowed, &args)
921 .await?;
922 }
923 let mut debuggee = store.data_mut().ctx().table.delete(debuggee)?;
924 debuggee.finish().await?;
925 Ok(())
926 }
927
928 #[cfg(feature = "component-model")]
929 fn search_component_funcs(
930 store: &mut Store<Host>,
931 component: &wasmtime::component::Component,
932 item_name: &wasmtime::component::wit_parser::ItemName,
933 ) -> Result<(
934 wasmtime::component::ComponentExportIndex,
935 wasmtime::component::types::ComponentFunc,
936 )> {
937 use wasmtime::component::types::ComponentItem as CItem;
938 match component.get_export(None, item_name) {
943 Some((CItem::ComponentFunc(func), index)) => return Ok((index, func.clone())),
944 _ => {}
945 }
946 if item_name.interface.is_some() || item_name.package.is_some() {
947 bail!("No exported func named `{item_name}` in component.")
952 }
953 let needle = item_name.to_string();
957 let mut search = component
958 .component_type()
959 .exports(store.engine())
960 .filter_map(|(instname, item)| match item.ty {
961 CItem::ComponentInstance(inst) => {
962 inst.exports(store.engine())
963 .find_map(|(leafname, item)| match item.ty {
964 CItem::ComponentFunc(func) => {
965 if leafname == needle {
966 let (_item, inst_index) = component
970 .get_export(None, instname)
971 .expect("found exported component instance");
972 let (_item, index) = component
973 .get_export(Some(&inst_index), leafname)
974 .expect("found func");
975 Some((index, func.clone(), instname.to_string()))
976 } else {
977 None
978 }
979 }
980 _ => None,
981 })
982 }
983
984 _ => None,
985 })
986 .collect::<Vec<_>>();
987
988 match search.len() {
989 0 => bail!("No exported func named `{needle}` in component."),
990 1 => {
991 let (index, func, _instname) = search.pop().unwrap();
992 Ok((index, func))
993 }
994 _ => {
995 let candidates = search
996 .into_iter()
997 .map(|(_index, _func, instname)| {
998 let mut itemname: wasmtime::component::wit_parser::ItemName =
1001 instname.parse().unwrap();
1002 itemname.interface = Some(itemname.name.clone());
1004 itemname.name = needle.to_string();
1005 format!("`{itemname}`")
1006 })
1007 .collect::<Vec<_>>();
1008 bail!(
1009 "Multiple instances contained funcs named `{needle}`, retry with a more specific name: {}",
1010 candidates.join(", ")
1011 )
1012 }
1013 }
1014 }
1015
1016 async fn invoke_func(&self, store: &mut Store<Host>, func: Func) -> Result<()> {
1017 let ty = func.ty(&store);
1018 if ty.params().len() > 0 {
1019 eprintln!(
1020 "warning: using `--invoke` with a function that takes arguments \
1021 is experimental and may break in the future"
1022 );
1023 }
1024 let mut args = self.module_and_args.iter().skip(1);
1025 let mut values = Vec::new();
1026 for ty in ty.params() {
1027 let val = match args.next() {
1028 Some(s) => s,
1029 None => {
1030 if let Some(name) = &self.invoke {
1031 bail!("not enough arguments for `{name}`")
1032 } else {
1033 bail!("not enough arguments for command default")
1034 }
1035 }
1036 };
1037 let val = val
1038 .to_str()
1039 .ok_or_else(|| format_err!("argument is not valid utf-8: {val:?}"))?;
1040 values.push(match ty {
1041 ValType::I32 => Val::I32(if val.starts_with("0x") || val.starts_with("0X") {
1043 i32::from_str_radix(&val[2..], 16)?
1044 } else {
1045 val.parse::<i32>()?
1046 }),
1047 ValType::I64 => Val::I64(if val.starts_with("0x") || val.starts_with("0X") {
1048 i64::from_str_radix(&val[2..], 16)?
1049 } else {
1050 val.parse::<i64>()?
1051 }),
1052 ValType::F32 => Val::F32(val.parse::<f32>()?.to_bits()),
1053 ValType::F64 => Val::F64(val.parse::<f64>()?.to_bits()),
1054 t => bail!("unsupported argument type {t:?}"),
1055 });
1056 }
1057
1058 let mut results = vec![Val::null_func_ref(); ty.results().len()];
1061 let invoke_res = func
1062 .call_async(&mut *store, &values, &mut results)
1063 .await
1064 .with_context(|| {
1065 if let Some(name) = &self.invoke {
1066 format!("failed to invoke `{name}`")
1067 } else {
1068 format!("failed to invoke command default")
1069 }
1070 });
1071
1072 if let Err(err) = invoke_res {
1073 return Err(self.handle_core_dump(&mut *store, err));
1074 }
1075
1076 if !results.is_empty() {
1077 eprintln!(
1078 "warning: using `--invoke` with a function that returns values \
1079 is experimental and may break in the future"
1080 );
1081 }
1082
1083 for result in results {
1084 match result {
1085 Val::I32(i) => println!("{i}"),
1086 Val::I64(i) => println!("{i}"),
1087 Val::F32(f) => println!("{}", f32::from_bits(f)),
1088 Val::F64(f) => println!("{}", f64::from_bits(f)),
1089 Val::V128(i) => println!("{}", i.as_u128()),
1090 Val::ExternRef(None) => println!("<null externref>"),
1091 Val::ExternRef(Some(_)) => println!("<externref>"),
1092 Val::FuncRef(None) => println!("<null funcref>"),
1093 Val::FuncRef(Some(_)) => println!("<funcref>"),
1094 Val::AnyRef(None) => println!("<null anyref>"),
1095 Val::AnyRef(Some(_)) => println!("<anyref>"),
1096 Val::ExnRef(None) => println!("<null exnref>"),
1097 Val::ExnRef(Some(_)) => println!("<exnref>"),
1098 Val::ContRef(None) => println!("<null contref>"),
1099 Val::ContRef(Some(_)) => println!("<contref>"),
1100 }
1101 }
1102
1103 Ok(())
1104 }
1105
1106 #[cfg(feature = "coredump")]
1107 fn handle_core_dump(&self, store: &mut Store<Host>, err: Error) -> Error {
1108 let coredump_path = match &self.run.common.debug.coredump {
1109 Some(path) => path,
1110 None => return err,
1111 };
1112 if !err.is::<wasmtime::Trap>() {
1113 return err;
1114 }
1115 let source_name = self.module_and_args[0]
1116 .to_str()
1117 .unwrap_or_else(|| "unknown");
1118
1119 if let Err(coredump_err) = write_core_dump(store, &err, &source_name, coredump_path) {
1120 eprintln!("warning: coredump failed to generate: {coredump_err}");
1121 err
1122 } else {
1123 err.context(format!("core dumped at {coredump_path}"))
1124 }
1125 }
1126
1127 #[cfg(not(feature = "coredump"))]
1128 fn handle_core_dump(&self, _store: &mut Store<Host>, err: Error) -> Error {
1129 err
1130 }
1131
1132 fn populate_with_wasi(&self, linker: &mut CliLinker, store: &mut Store<Host>) -> Result<()> {
1134 self.run.validate_p3_option()?;
1135 let cli = self.run.validate_cli_enabled()?;
1136
1137 if cli != Some(false) {
1138 match linker {
1139 CliLinker::Core(linker) => {
1140 match (self.run.common.wasi.preview2, self.run.common.wasi.threads) {
1141 (Some(false), _) | (None, Some(true)) => {
1142 let flag = if self.run.common.wasi.preview2 == Some(false) {
1143 "-Spreview2=n"
1144 } else {
1145 "-Sthreads"
1146 };
1147 bail!("the `{flag}` flag is no longer supported")
1148 }
1149 (Some(true), _) | (None, Some(false) | None) => {
1156 if self.run.common.wasi.preview0 != Some(false) {
1157 wasmtime_wasi::p0::add_to_linker_async(linker, |t| t.wasip1_ctx())?;
1158 }
1159 wasmtime_wasi::p1::add_to_linker_async(linker, |t| t.wasip1_ctx())?;
1160 self.set_wasi_ctx(store)?;
1161 }
1162 }
1163 }
1164 #[cfg(feature = "component-model")]
1165 CliLinker::Component(linker) => {
1166 self.run.add_wasmtime_wasi_to_linker(linker)?;
1167 self.set_wasi_ctx(store)?;
1168 }
1169 }
1170 }
1171
1172 if self.run.common.wasi.nn == Some(true) {
1173 #[cfg(not(feature = "wasi-nn"))]
1174 {
1175 bail!("Cannot enable wasi-nn when the binary is not compiled with this feature.");
1176 }
1177 #[cfg(all(feature = "wasi-nn", feature = "component-model"))]
1178 {
1179 let (backends, registry) = self.collect_preloaded_nn_graphs()?;
1180 match linker {
1181 CliLinker::Core(linker) => {
1182 wasmtime_wasi_nn::witx::add_to_linker(linker, |host| {
1183 host.wasi_nn_witx.as_mut().unwrap()
1184 })?;
1185 store.data_mut().wasi_nn_witx =
1186 Some(wasmtime_wasi_nn::witx::WasiNnCtx::new(backends, registry));
1187 }
1188 #[cfg(feature = "component-model")]
1189 CliLinker::Component(linker) => {
1190 wasmtime_wasi_nn::wit::add_to_linker(linker, |h: &mut Host| {
1191 let ctx = h.wasip1_ctx.as_mut().expect("wasi is not configured");
1192 let nn_ctx = h.wasi_nn_wit.as_mut().unwrap();
1193 WasiNnView::new(ctx.ctx().table, nn_ctx)
1194 })?;
1195 store.data_mut().wasi_nn_wit =
1196 Some(wasmtime_wasi_nn::wit::WasiNnCtx::new(backends, registry));
1197 }
1198 }
1199 }
1200 }
1201
1202 if self.run.common.wasi.config == Some(true) {
1203 #[cfg(not(feature = "wasi-config"))]
1204 {
1205 bail!(
1206 "Cannot enable wasi-config when the binary is not compiled with this feature."
1207 );
1208 }
1209 #[cfg(all(feature = "wasi-config", feature = "component-model"))]
1210 {
1211 match linker {
1212 CliLinker::Core(_) => {
1213 bail!("Cannot enable wasi-config for core wasm modules");
1214 }
1215 CliLinker::Component(linker) => {
1216 let vars = WasiConfigVariables::from_iter(
1217 self.run
1218 .common
1219 .wasi
1220 .config_var
1221 .iter()
1222 .map(|v| (v.key.clone(), v.value.clone())),
1223 );
1224
1225 wasmtime_wasi_config::add_to_linker(linker, |h| {
1226 WasiConfig::new(h.wasi_config.as_mut().unwrap())
1227 })?;
1228 store.data_mut().wasi_config = Some(vars);
1229 }
1230 }
1231 }
1232 }
1233
1234 if self.run.common.wasi.keyvalue == Some(true) {
1235 #[cfg(not(feature = "wasi-keyvalue"))]
1236 {
1237 bail!(
1238 "Cannot enable wasi-keyvalue when the binary is not compiled with this feature."
1239 );
1240 }
1241 #[cfg(all(feature = "wasi-keyvalue", feature = "component-model"))]
1242 {
1243 match linker {
1244 CliLinker::Core(_) => {
1245 bail!("Cannot enable wasi-keyvalue for core wasm modules");
1246 }
1247 CliLinker::Component(linker) => {
1248 let ctx = WasiKeyValueCtxBuilder::new()
1249 .in_memory_data(
1250 self.run
1251 .common
1252 .wasi
1253 .keyvalue_in_memory_data
1254 .iter()
1255 .map(|v| (v.key.clone(), v.value.clone())),
1256 )
1257 .build();
1258
1259 wasmtime_wasi_keyvalue::add_to_linker(linker, |h| {
1260 let ctx = h.wasip1_ctx.as_mut().expect("wasip2 is not configured");
1261 WasiKeyValue::new(h.wasi_keyvalue.as_mut().unwrap(), ctx.ctx().table)
1262 })?;
1263 store.data_mut().wasi_keyvalue = Some(ctx);
1264 }
1265 }
1266 }
1267 }
1268
1269 if self.run.common.wasi.threads == Some(true) {
1270 bail!("support for wasi-threads has been removed from Wasmtime");
1271 }
1272
1273 if self.run.common.wasi.http == Some(true) {
1274 #[cfg(not(all(feature = "wasi-http", feature = "component-model")))]
1275 {
1276 bail!("Cannot enable wasi-http when the binary is not compiled with this feature.");
1277 }
1278 #[cfg(all(feature = "wasi-http", feature = "component-model"))]
1279 {
1280 match linker {
1281 CliLinker::Core(_) => {
1282 bail!("Cannot enable wasi-http for core wasm modules");
1283 }
1284 CliLinker::Component(linker) => {
1285 wasmtime_wasi_http::p2::add_only_http_to_linker_async(linker)?;
1286 #[cfg(feature = "component-model-async")]
1287 if self.run.common.wasi.p3.unwrap_or(crate::common::P3_DEFAULT) {
1288 wasmtime_wasi_http::p3::add_to_linker(linker)?;
1289 }
1290 }
1291 }
1292 let http = self.run.wasi_http_ctx()?;
1293 store.data_mut().wasi_http = Some(http);
1294 }
1295 }
1296
1297 if self.run.common.wasi.tls == Some(true) {
1298 #[cfg(all(not(all(feature = "wasi-tls", feature = "component-model"))))]
1299 {
1300 bail!("Cannot enable wasi-tls when the binary is not compiled with this feature.");
1301 }
1302 #[cfg(all(feature = "wasi-tls", feature = "component-model",))]
1303 {
1304 match linker {
1305 CliLinker::Core(_) => {
1306 bail!("Cannot enable wasi-tls for core wasm modules");
1307 }
1308 CliLinker::Component(linker) => {
1309 let mut opts = wasmtime_wasi_tls::p2::LinkOptions::default();
1310 opts.tls(true);
1311 wasmtime_wasi_tls::p2::add_to_linker(linker, &opts)?;
1312
1313 #[cfg(feature = "component-model-async")]
1314 if self.run.common.wasi.p3.unwrap_or(crate::common::P3_DEFAULT) {
1315 wasmtime_wasi_tls::p3::add_to_linker(linker)?;
1316 }
1317
1318 let ctx = wasmtime_wasi_tls::WasiTlsCtxBuilder::new().build();
1319 store.data_mut().wasi_tls = Some(ctx);
1320 }
1321 }
1322 }
1323 }
1324
1325 Ok(())
1326 }
1327
1328 fn set_wasi_ctx(&self, store: &mut Store<Host>) -> Result<()> {
1330 let mut builder = wasmtime_wasi::WasiCtxBuilder::new();
1331 builder.args(&self.compute_argv()?);
1332 if self.run.common.wasi.inherit_stdin.unwrap_or(true) {
1333 builder.inherit_stdin();
1334 }
1335 if self.run.common.wasi.inherit_stdout.unwrap_or(true) {
1336 builder.inherit_stdout();
1337 }
1338 if self.run.common.wasi.inherit_stderr.unwrap_or(true) {
1339 builder.inherit_stderr();
1340 }
1341 self.run.configure_wasip2(&mut builder)?;
1342 store.data_mut().wasip1_ctx = Some(builder.build_p1());
1343 Ok(())
1344 }
1345
1346 #[cfg(feature = "wasi-nn")]
1347 fn collect_preloaded_nn_graphs(
1348 &self,
1349 ) -> Result<(Vec<wasmtime_wasi_nn::Backend>, wasmtime_wasi_nn::Registry)> {
1350 let graphs = self
1351 .run
1352 .common
1353 .wasi
1354 .nn_graph
1355 .iter()
1356 .map(|g| (g.format.clone(), g.dir.clone()))
1357 .collect::<Vec<_>>();
1358 wasmtime_wasi_nn::preload(&graphs)
1359 }
1360}
1361
1362#[derive(Default)]
1368pub struct Host {
1369 limits: StoreLimits,
1370 #[cfg(feature = "profiling")]
1371 guest_profiler: Option<wasmtime::GuestProfiler>,
1372
1373 wasip1_ctx: Option<wasmtime_wasi::p1::WasiP1Ctx>,
1377
1378 #[cfg(feature = "wasi-nn")]
1379 wasi_nn_wit: Option<wasmtime_wasi_nn::wit::WasiNnCtx>,
1380 #[cfg(feature = "wasi-nn")]
1381 wasi_nn_witx: Option<wasmtime_wasi_nn::witx::WasiNnCtx>,
1382
1383 #[cfg(feature = "wasi-http")]
1384 wasi_http: Option<WasiHttpCtx>,
1385 #[cfg(feature = "wasi-http")]
1386 wasi_http_hooks: crate::common::HttpHooks,
1387
1388 #[cfg(feature = "wasi-config")]
1389 wasi_config: Option<WasiConfigVariables>,
1390 #[cfg(feature = "wasi-keyvalue")]
1391 wasi_keyvalue: Option<WasiKeyValueCtx>,
1392 #[cfg(feature = "wasi-tls")]
1393 wasi_tls: Option<wasmtime_wasi_tls::WasiTlsCtx>,
1394}
1395
1396impl Host {
1397 pub(crate) fn wasip1_ctx(&mut self) -> &mut wasmtime_wasi::p1::WasiP1Ctx {
1398 self.wasip1_ctx.as_mut().unwrap()
1399 }
1400}
1401
1402impl WasiView for Host {
1403 fn ctx(&mut self) -> WasiCtxView<'_> {
1404 WasiView::ctx(self.wasip1_ctx())
1405 }
1406}
1407
1408#[cfg(feature = "wasi-http")]
1409impl wasmtime_wasi_http::WasiHttpView for Host {
1410 fn http(&mut self) -> wasmtime_wasi_http::WasiHttpCtxView<'_> {
1411 let ctx = self.wasi_http.as_mut().unwrap();
1412 wasmtime_wasi_http::WasiHttpCtxView {
1413 table: WasiView::ctx(self.wasip1_ctx.as_mut().unwrap()).table,
1414 ctx,
1415 hooks: &mut self.wasi_http_hooks,
1416 }
1417 }
1418}
1419
1420#[cfg(all(feature = "wasi-tls"))]
1421impl wasmtime_wasi_tls::WasiTlsView for Host {
1422 fn tls(&mut self) -> wasmtime_wasi_tls::WasiTlsCtxView<'_> {
1423 wasmtime_wasi_tls::WasiTlsCtxView {
1424 table: WasiView::ctx(self.wasip1_ctx.as_mut().unwrap()).table,
1425 ctx: self.wasi_tls.as_mut().unwrap(),
1426 }
1427 }
1428}
1429
1430#[cfg(feature = "coredump")]
1431fn write_core_dump(
1432 store: &mut Store<Host>,
1433 err: &wasmtime::Error,
1434 name: &str,
1435 path: &str,
1436) -> Result<()> {
1437 use std::fs::File;
1438 use std::io::Write;
1439
1440 let core_dump = err
1441 .downcast_ref::<wasmtime::WasmCoreDump>()
1442 .expect("should have been configured to capture core dumps");
1443
1444 let core_dump = core_dump.serialize(store, name);
1445
1446 let mut core_dump_file =
1447 File::create(path).with_context(|| format!("failed to create file at `{path}`"))?;
1448 core_dump_file
1449 .write_all(&core_dump)
1450 .with_context(|| format!("failed to write core dump file at `{path}`"))?;
1451 Ok(())
1452}