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
393 store.data_mut().limits = self.run.store_limits();
394 store.limiter(|t| &mut t.limits);
395
396 if let Some(fuel) = self.run.common.wasm.fuel {
399 store.set_fuel(fuel)?;
400 }
401
402 Ok((store, linker))
403 }
404
405 #[cfg(feature = "debug")]
406 pub(crate) fn add_debugger_api(
407 &mut self,
408 linker: &mut wasmtime::component::Linker<Host>,
409 ) -> Result<()> {
410 wasmtime_debugger::add_to_linker(linker, |x| x.ctx().table)?;
411 Ok(())
412 }
413
414 pub async fn instantiate_and_run(
420 &self,
421 engine: &Engine,
422 linker: &mut CliLinker,
423 main: &RunTarget,
424 store: &mut Store<Host>,
425 ) -> Result<CliInstance> {
426 let dur = self
427 .run
428 .common
429 .wasm
430 .timeout
431 .unwrap_or(std::time::Duration::MAX);
432 let result = tokio::time::timeout(dur, async {
433 let mut profiled_modules: Vec<(String, Module)> = Vec::new();
434 if let RunTarget::Core(m) = &main {
435 profiled_modules.push(("".to_string(), m.clone()));
436 }
437
438 for (name, path) in self.preloads.modules.iter() {
440 let preload_target = self.run.load_module(&engine, path, None)?;
442 let preload_module = match preload_target {
443 RunTarget::Core(m) => m,
444 #[cfg(feature = "component-model")]
445 RunTarget::Component(_) => {
446 bail!("components cannot be loaded with `--preload`")
447 }
448 };
449 profiled_modules.push((name.to_string(), preload_module.clone()));
450
451 match linker {
453 #[cfg(feature = "cranelift")]
454 CliLinker::Core(linker) => {
455 linker
456 .module_async(&mut *store, name, &preload_module)
457 .await
458 .with_context(|| {
459 format!(
460 "failed to process preload `{}` at `{}`",
461 name,
462 path.display()
463 )
464 })?;
465 }
466 #[cfg(not(feature = "cranelift"))]
467 CliLinker::Core(_) => {
468 bail!("support for --preload disabled at compile time");
469 }
470 #[cfg(feature = "component-model")]
471 CliLinker::Component(_) => {
472 bail!("--preload cannot be used with components");
473 }
474 }
475 }
476
477 self.load_main_module(store, linker, &main, profiled_modules)
478 .await
479 .with_context(|| {
480 format!(
481 "failed to run main module `{}`",
482 self.module_and_args[0].to_string_lossy()
483 )
484 })
485 })
486 .await;
487
488 let instance = match result.unwrap_or_else(|elapsed| {
490 Err(wasmtime::Error::from(wasmtime::Trap::Interrupt))
491 .with_context(|| format!("timed out after {elapsed}"))
492 }) {
493 Ok(instance) => instance,
494 Err(e) => {
495 if store.data().wasip1_ctx.is_some() {
499 if let Some(exit) = e.downcast_ref::<wasmtime_wasi::I32Exit>() {
500 std::process::exit(exit.0);
501 }
502 }
503 if e.is::<wasmtime::Trap>() {
504 eprintln!("Error: {e:?}");
505 cfg_if::cfg_if! {
506 if #[cfg(unix)] {
507 std::process::exit(rustix::process::EXIT_SIGNALED_SIGABRT);
508 } else if #[cfg(windows)] {
509 std::process::exit(3);
511 }
512 }
513 }
514 return Err(e);
515 }
516 };
517
518 Ok(instance)
519 }
520
521 pub(crate) fn compute_argv(&self) -> Result<Vec<String>> {
522 let mut result = Vec::new();
523
524 for (i, arg) in self.module_and_args.iter().enumerate() {
525 let arg = if i == 0 {
528 match &self.argv0 {
529 Some(s) => s.as_ref(),
530 None => Path::new(arg).components().next_back().unwrap().as_os_str(),
531 }
532 } else {
533 arg.as_ref()
534 };
535 result.push(
536 arg.to_str()
537 .ok_or_else(|| format_err!("failed to convert {arg:?} to utf-8"))?
538 .to_string(),
539 );
540 }
541
542 Ok(result)
543 }
544
545 fn setup_epoch_handler(
546 &self,
547 store: &mut Store<Host>,
548 main_target: &RunTarget,
549 profiled_modules: Vec<(String, Module)>,
550 ) -> Result<Box<dyn FnOnce(&mut Store<Host>) + Send>> {
551 if self.run.common.debug.debugger.is_some() {
557 if self.run.profile.is_some() {
558 bail!("Cannot set profile options together with debugging; they are incompatible");
559 }
560 if self.run.common.wasm.timeout.is_some() {
561 bail!("Cannot set timeout options together with debugging; they are incompatible");
562 }
563 store.epoch_deadline_async_yield_and_update(1);
564 } else {
565 if let Some(Profile::Guest { path, interval }) = &self.run.profile {
566 #[cfg(feature = "profiling")]
567 return Ok(self.setup_guest_profiler(
568 store,
569 main_target,
570 profiled_modules,
571 path,
572 *interval,
573 )?);
574 #[cfg(not(feature = "profiling"))]
575 {
576 let _ = (profiled_modules, path, interval, main_target);
577 bail!("support for profiling disabled at compile time");
578 }
579 }
580
581 if let Some(timeout) = self.run.common.wasm.timeout {
582 store.set_epoch_deadline(1);
583 let engine = store.engine().clone();
584 thread::spawn(move || {
585 thread::sleep(timeout);
586 engine.increment_epoch();
587 });
588 }
589 }
590
591 Ok(Box::new(|_store| {}))
592 }
593
594 #[cfg(feature = "profiling")]
595 fn setup_guest_profiler(
596 &self,
597 store: &mut Store<Host>,
598 main_target: &RunTarget,
599 profiled_modules: Vec<(String, Module)>,
600 path: &str,
601 interval: std::time::Duration,
602 ) -> Result<Box<dyn FnOnce(&mut Store<Host>) + Send>> {
603 use wasmtime::{AsContext, GuestProfiler, StoreContext, StoreContextMut, UpdateDeadline};
604
605 let module_name = self.module_and_args[0].to_str().unwrap_or("<main module>");
606 store.data_mut().guest_profiler = match main_target {
607 RunTarget::Core(_m) => Some(GuestProfiler::new(
608 store.engine(),
609 module_name,
610 interval,
611 profiled_modules,
612 )?),
613 RunTarget::Component(component) => Some(GuestProfiler::new_component(
614 store.engine(),
615 module_name,
616 interval,
617 component.clone(),
618 profiled_modules,
619 )?),
620 };
621
622 fn sample(
623 mut store: StoreContextMut<Host>,
624 f: impl FnOnce(&mut GuestProfiler, StoreContext<Host>),
625 ) {
626 let mut profiler = store.data_mut().guest_profiler.take().unwrap();
627 f(&mut profiler, store.as_context());
628 store.data_mut().guest_profiler = Some(profiler);
629 }
630
631 store.call_hook(|store, kind| {
632 sample(store, |profiler, store| profiler.call_hook(store, kind));
633 Ok(())
634 });
635
636 if let Some(timeout) = self.run.common.wasm.timeout {
637 let mut timeout = (timeout.as_secs_f64() / interval.as_secs_f64()).ceil() as u64;
638 assert!(timeout > 0);
639 store.epoch_deadline_callback(move |store| {
640 sample(store, |profiler, store| {
641 profiler.sample(store, std::time::Duration::ZERO)
642 });
643 timeout -= 1;
644 if timeout == 0 {
645 bail!("timeout exceeded");
646 }
647 Ok(UpdateDeadline::Continue(1))
648 });
649 } else {
650 store.epoch_deadline_callback(move |store| {
651 sample(store, |profiler, store| {
652 profiler.sample(store, std::time::Duration::ZERO)
653 });
654 Ok(UpdateDeadline::Continue(1))
655 });
656 }
657
658 store.set_epoch_deadline(1);
659 let engine = store.engine().clone();
660 thread::spawn(move || {
661 loop {
662 thread::sleep(interval);
663 engine.increment_epoch();
664 }
665 });
666
667 let path = path.to_string();
668 Ok(Box::new(move |store| {
669 let profiler = store.data_mut().guest_profiler.take().unwrap();
670 if let Err(e) = std::fs::File::create(&path)
671 .map_err(wasmtime::Error::new)
672 .and_then(|output| profiler.finish(std::io::BufWriter::new(output)))
673 {
674 eprintln!("failed writing profile at {path}: {e:#}");
675 } else {
676 eprintln!();
677 eprintln!("Profile written to: {path}");
678 eprintln!("View this profile at https://profiler.firefox.com/.");
679 }
680 }))
681 }
682
683 async fn load_main_module(
684 &self,
685 store: &mut Store<Host>,
686 linker: &mut CliLinker,
687 main_target: &RunTarget,
688 profiled_modules: Vec<(String, Module)>,
689 ) -> Result<CliInstance> {
690 if self.run.common.wasm.unknown_imports_trap == Some(true) {
693 match linker {
694 CliLinker::Core(linker) => {
695 linker.define_unknown_imports_as_traps(main_target.unwrap_core())?;
696 }
697 #[cfg(feature = "component-model")]
698 CliLinker::Component(linker) => {
699 linker.define_unknown_imports_as_traps(main_target.unwrap_component())?;
700 }
701 }
702 }
703
704 if self.run.common.wasm.unknown_imports_default == Some(true) {
706 match linker {
707 CliLinker::Core(linker) => {
708 linker.define_unknown_imports_as_default_values(
709 &mut *store,
710 main_target.unwrap_core(),
711 )?;
712 }
713 _ => bail!("cannot use `--default-values-unknown-imports` with components"),
714 }
715 }
716
717 let finish_epoch_handler =
718 self.setup_epoch_handler(store, main_target, profiled_modules)?;
719
720 let result = match linker {
721 CliLinker::Core(linker) => {
722 let module = main_target.unwrap_core();
723 let instance = linker
724 .instantiate_async(&mut *store, &module)
725 .await
726 .with_context(|| {
727 format!("failed to instantiate {:?}", self.module_and_args[0])
728 })?;
729
730 if let Some(func) = instance.get_func(&mut *store, "_initialize") {
733 func.typed::<(), ()>(&store)?
734 .call_async(&mut *store, ())
735 .await?;
736 }
737
738 let func = if let Some(name) = &self.invoke {
741 Some(
742 instance
743 .get_func(&mut *store, name)
744 .ok_or_else(|| format_err!("no func export named `{name}` found"))?,
745 )
746 } else {
747 instance
748 .get_func(&mut *store, "")
749 .or_else(|| instance.get_func(&mut *store, "_start"))
750 };
751
752 if let Some(func) = func {
753 self.invoke_func(store, func).await?;
754 }
755 Ok(CliInstance::Core(instance))
756 }
757 #[cfg(feature = "component-model")]
758 CliLinker::Component(linker) => {
759 let component = main_target.unwrap_component();
760 let result = if self.invoke.is_some() {
761 self.invoke_component(&mut *store, component, linker).await
762 } else {
763 self.run_command_component(&mut *store, component, linker)
764 .await
765 };
766 result
767 .map(CliInstance::Component)
768 .map_err(|e| self.handle_core_dump(&mut *store, e))
769 }
770 };
771 finish_epoch_handler(store);
772
773 result
774 }
775
776 #[cfg(feature = "component-model")]
777 async fn invoke_component(
778 &self,
779 store: &mut Store<Host>,
780 component: &wasmtime::component::Component,
781 linker: &mut wasmtime::component::Linker<Host>,
782 ) -> Result<wasmtime::component::Instance> {
783 use wasmtime::component::{
784 Val,
785 wasm_wave::{
786 untyped::UntypedFuncCall,
787 wasm::{DisplayFuncResults, WasmFunc},
788 },
789 };
790
791 let invoke: &String = self.invoke.as_ref().unwrap();
793
794 let untyped_call = UntypedFuncCall::parse(invoke).with_context(|| {
795 format!(
796 "Failed to parse invoke '{invoke}': See https://docs.wasmtime.dev/cli-options.html#run for syntax",
797 )
798 })?;
799
800 let name = untyped_call.item_name().map_err(|e| {
801 wasmtime::Error::from_anyhow(e).context(format!(
802 "parsing `{}` as a wit item name",
803 untyped_call.name()
804 ))
805 })?;
806
807 let (export, func_type) = Self::search_component_funcs(store, &component, &name)?;
808
809 let param_types = WasmFunc::params(&func_type).collect::<Vec<_>>();
810 let params = untyped_call
811 .to_wasm_params(¶m_types)
812 .with_context(|| format!("while interpreting parameters in invoke \"{invoke}\""))?;
813
814 let instance = linker.instantiate_async(&mut *store, component).await?;
815
816 let func = instance
817 .get_func(&mut *store, export)
818 .expect("found export index");
819
820 let mut results = vec![Val::Bool(false); func_type.results().len()];
821 self.call_component_func(store, ¶ms, func, &mut results)
822 .await?;
823
824 println!("{}", DisplayFuncResults(&results));
825 Ok(instance)
826 }
827
828 #[cfg(feature = "component-model")]
829 async fn call_component_func(
830 &self,
831 store: &mut Store<Host>,
832 params: &[wasmtime::component::Val],
833 func: wasmtime::component::Func,
834 results: &mut Vec<wasmtime::component::Val>,
835 ) -> Result<(), Error> {
836 #[cfg(feature = "component-model-async")]
837 if self.run.common.wasm.concurrency_support.unwrap_or(true) {
838 store
839 .run_concurrent(async |store| func.call_concurrent(store, params, results).await)
840 .await??;
841 return Ok(());
842 }
843
844 func.call_async(&mut *store, ¶ms, results).await?;
845 Ok(())
846 }
847
848 #[cfg(feature = "component-model")]
851 async fn run_command_component(
852 &self,
853 store: &mut Store<Host>,
854 component: &wasmtime::component::Component,
855 linker: &wasmtime::component::Linker<Host>,
856 ) -> Result<wasmtime::component::Instance> {
857 let instance = linker.instantiate_async(&mut *store, component).await?;
858
859 let mut result = None;
860 let _ = &mut result;
861
862 #[cfg(feature = "component-model-async")]
865 if self.run.common.wasi.p3.unwrap_or(crate::common::P3_DEFAULT) {
866 if let Ok(command) = wasmtime_wasi::p3::bindings::Command::new(&mut *store, &instance) {
867 result = Some(
868 store
869 .run_concurrent(async |store| command.wasi_cli_run().call_run(store).await)
870 .await?,
871 );
872 }
873 }
874
875 let result = match result {
876 Some(result) => result,
877 None => {
880 wasmtime_wasi::p2::bindings::Command::new(&mut *store, &instance)?
881 .wasi_cli_run()
882 .call_run(&mut *store)
883 .await
884 }
885 };
886 let wasm_result = result.context("failed to invoke `run` function")?;
887
888 match wasm_result {
891 Ok(()) => Ok(instance),
892 Err(()) => Err(wasmtime_wasi::I32Exit(1).into()),
893 }
894 }
895
896 #[cfg(feature = "debug")]
901 pub(crate) async fn invoke_debugger<
902 T: Send + 'static,
903 F: for<'a> FnOnce(
904 &'a mut Store<T>,
905 ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>
906 + Send
907 + 'static,
908 >(
909 &self,
910 store: &mut Store<Host>,
911 component: &wasmtime::component::Component,
912 linker: &mut wasmtime::component::Linker<Host>,
913 debuggee_host: Store<T>,
914 body: F,
915 ) -> Result<()> {
916 let instance = linker.instantiate_async(&mut *store, component).await?;
917 let command = wasmtime_debugger::DebuggerComponent::new(&mut *store, &instance)?;
918 let debuggee = wasmtime_debugger::Debuggee::new(debuggee_host, body);
919 let debuggee = wasmtime_debugger::add_debuggee(store.data_mut().ctx().table, debuggee)?;
920 {
921 let borrowed = wasmtime::component::Resource::new_borrow(debuggee.rep());
926 let args = self.compute_argv()?;
927 command
928 .bytecodealliance_wasmtime_debugger()
929 .call_debug(&mut *store, borrowed, &args)
930 .await?;
931 }
932 let mut debuggee = store.data_mut().ctx().table.delete(debuggee)?;
933 debuggee.finish().await?;
934 Ok(())
935 }
936
937 #[cfg(feature = "component-model")]
938 fn search_component_funcs(
939 store: &mut Store<Host>,
940 component: &wasmtime::component::Component,
941 item_name: &wasmtime::component::wit_parser::ItemName,
942 ) -> Result<(
943 wasmtime::component::ComponentExportIndex,
944 wasmtime::component::types::ComponentFunc,
945 )> {
946 use wasmtime::component::types::ComponentItem as CItem;
947 match component.get_export(None, item_name) {
952 Some((CItem::ComponentFunc(func), index)) => return Ok((index, func.clone())),
953 _ => {}
954 }
955 if item_name.interface.is_some() || item_name.package.is_some() {
956 bail!("No exported func named `{item_name}` in component.")
961 }
962 let needle = item_name.to_string();
966 let mut search = component
967 .component_type()
968 .exports(store.engine())
969 .filter_map(|(instname, item)| match item.ty {
970 CItem::ComponentInstance(inst) => {
971 inst.exports(store.engine())
972 .find_map(|(leafname, item)| match item.ty {
973 CItem::ComponentFunc(func) => {
974 if leafname == needle {
975 let (_item, inst_index) = component
979 .get_export(None, instname)
980 .expect("found exported component instance");
981 let (_item, index) = component
982 .get_export(Some(&inst_index), leafname)
983 .expect("found func");
984 Some((index, func.clone(), instname.to_string()))
985 } else {
986 None
987 }
988 }
989 _ => None,
990 })
991 }
992
993 _ => None,
994 })
995 .collect::<Vec<_>>();
996
997 match search.len() {
998 0 => bail!("No exported func named `{needle}` in component."),
999 1 => {
1000 let (index, func, _instname) = search.pop().unwrap();
1001 Ok((index, func))
1002 }
1003 _ => {
1004 let candidates = search
1005 .into_iter()
1006 .map(|(_index, _func, instname)| {
1007 let mut itemname: wasmtime::component::wit_parser::ItemName =
1010 instname.parse().unwrap();
1011 itemname.interface = Some(itemname.name.clone());
1013 itemname.name = needle.to_string();
1014 format!("`{itemname}`")
1015 })
1016 .collect::<Vec<_>>();
1017 bail!(
1018 "Multiple instances contained funcs named `{needle}`, retry with a more specific name: {}",
1019 candidates.join(", ")
1020 )
1021 }
1022 }
1023 }
1024
1025 async fn invoke_func(&self, store: &mut Store<Host>, func: Func) -> Result<()> {
1026 let ty = func.ty(&store);
1027 if ty.params().len() > 0 {
1028 eprintln!(
1029 "warning: using `--invoke` with a function that takes arguments \
1030 is experimental and may break in the future"
1031 );
1032 }
1033 let mut args = self.module_and_args.iter().skip(1);
1034 let mut values = Vec::new();
1035 for ty in ty.params() {
1036 let val = match args.next() {
1037 Some(s) => s,
1038 None => {
1039 if let Some(name) = &self.invoke {
1040 bail!("not enough arguments for `{name}`")
1041 } else {
1042 bail!("not enough arguments for command default")
1043 }
1044 }
1045 };
1046 let val = val
1047 .to_str()
1048 .ok_or_else(|| format_err!("argument is not valid utf-8: {val:?}"))?;
1049 values.push(match ty {
1050 ValType::I32 => Val::I32(if val.starts_with("0x") || val.starts_with("0X") {
1052 i32::from_str_radix(&val[2..], 16)?
1053 } else {
1054 val.parse::<i32>()?
1055 }),
1056 ValType::I64 => Val::I64(if val.starts_with("0x") || val.starts_with("0X") {
1057 i64::from_str_radix(&val[2..], 16)?
1058 } else {
1059 val.parse::<i64>()?
1060 }),
1061 ValType::F32 => Val::F32(val.parse::<f32>()?.to_bits()),
1062 ValType::F64 => Val::F64(val.parse::<f64>()?.to_bits()),
1063 t => bail!("unsupported argument type {t:?}"),
1064 });
1065 }
1066
1067 let mut results = vec![Val::null_func_ref(); ty.results().len()];
1070 let invoke_res = func
1071 .call_async(&mut *store, &values, &mut results)
1072 .await
1073 .with_context(|| {
1074 if let Some(name) = &self.invoke {
1075 format!("failed to invoke `{name}`")
1076 } else {
1077 format!("failed to invoke command default")
1078 }
1079 });
1080
1081 if let Err(err) = invoke_res {
1082 return Err(self.handle_core_dump(&mut *store, err));
1083 }
1084
1085 if !results.is_empty() {
1086 eprintln!(
1087 "warning: using `--invoke` with a function that returns values \
1088 is experimental and may break in the future"
1089 );
1090 }
1091
1092 for result in results {
1093 match result {
1094 Val::I32(i) => println!("{i}"),
1095 Val::I64(i) => println!("{i}"),
1096 Val::F32(f) => println!("{}", f32::from_bits(f)),
1097 Val::F64(f) => println!("{}", f64::from_bits(f)),
1098 Val::V128(i) => println!("{}", i.as_u128()),
1099 Val::ExternRef(None) => println!("<null externref>"),
1100 Val::ExternRef(Some(_)) => println!("<externref>"),
1101 Val::FuncRef(None) => println!("<null funcref>"),
1102 Val::FuncRef(Some(_)) => println!("<funcref>"),
1103 Val::AnyRef(None) => println!("<null anyref>"),
1104 Val::AnyRef(Some(_)) => println!("<anyref>"),
1105 Val::ExnRef(None) => println!("<null exnref>"),
1106 Val::ExnRef(Some(_)) => println!("<exnref>"),
1107 Val::ContRef(None) => println!("<null contref>"),
1108 Val::ContRef(Some(_)) => println!("<contref>"),
1109 }
1110 }
1111
1112 Ok(())
1113 }
1114
1115 #[cfg(feature = "coredump")]
1116 fn handle_core_dump(&self, store: &mut Store<Host>, err: Error) -> Error {
1117 let coredump_path = match &self.run.common.debug.coredump {
1118 Some(path) => path,
1119 None => return err,
1120 };
1121 if !err.is::<wasmtime::Trap>() {
1122 return err;
1123 }
1124 let source_name = self.module_and_args[0]
1125 .to_str()
1126 .unwrap_or_else(|| "unknown");
1127
1128 if let Err(coredump_err) = write_core_dump(store, &err, &source_name, coredump_path) {
1129 eprintln!("warning: coredump failed to generate: {coredump_err}");
1130 err
1131 } else {
1132 err.context(format!("core dumped at {coredump_path}"))
1133 }
1134 }
1135
1136 #[cfg(not(feature = "coredump"))]
1137 fn handle_core_dump(&self, _store: &mut Store<Host>, err: Error) -> Error {
1138 err
1139 }
1140
1141 fn populate_with_wasi(&self, linker: &mut CliLinker, store: &mut Store<Host>) -> Result<()> {
1143 self.run.validate_p3_option()?;
1144 let cli = self.run.validate_cli_enabled()?;
1145
1146 if cli != Some(false) {
1147 match linker {
1148 CliLinker::Core(linker) => {
1149 match (self.run.common.wasi.preview2, self.run.common.wasi.threads) {
1150 (Some(false), _) | (None, Some(true)) => {
1151 let flag = if self.run.common.wasi.preview2 == Some(false) {
1152 "-Spreview2=n"
1153 } else {
1154 "-Sthreads"
1155 };
1156 bail!("the `{flag}` flag is no longer supported")
1157 }
1158 (Some(true), _) | (None, Some(false) | None) => {
1165 if self.run.common.wasi.preview0 != Some(false) {
1166 wasmtime_wasi::p0::add_to_linker_async(linker, |t| t.wasip1_ctx())?;
1167 }
1168 wasmtime_wasi::p1::add_to_linker_async(linker, |t| t.wasip1_ctx())?;
1169 self.set_wasi_ctx(store)?;
1170 }
1171 }
1172 }
1173 #[cfg(feature = "component-model")]
1174 CliLinker::Component(linker) => {
1175 self.run.add_wasmtime_wasi_to_linker(linker)?;
1176 self.set_wasi_ctx(store)?;
1177 }
1178 }
1179 }
1180
1181 if self.run.common.wasi.nn == Some(true) {
1182 #[cfg(not(feature = "wasi-nn"))]
1183 {
1184 bail!("Cannot enable wasi-nn when the binary is not compiled with this feature.");
1185 }
1186 #[cfg(all(feature = "wasi-nn", feature = "component-model"))]
1187 {
1188 let (backends, registry) = self.collect_preloaded_nn_graphs()?;
1189 match linker {
1190 CliLinker::Core(linker) => {
1191 wasmtime_wasi_nn::witx::add_to_linker(linker, |host| {
1192 host.wasi_nn_witx.as_mut().unwrap()
1193 })?;
1194 store.data_mut().wasi_nn_witx =
1195 Some(wasmtime_wasi_nn::witx::WasiNnCtx::new(backends, registry));
1196 }
1197 #[cfg(feature = "component-model")]
1198 CliLinker::Component(linker) => {
1199 wasmtime_wasi_nn::wit::add_to_linker(linker, |h: &mut Host| {
1200 let ctx = h.wasip1_ctx.as_mut().expect("wasi is not configured");
1201 let nn_ctx = h.wasi_nn_wit.as_mut().unwrap();
1202 WasiNnView::new(ctx.ctx().table, nn_ctx)
1203 })?;
1204 store.data_mut().wasi_nn_wit =
1205 Some(wasmtime_wasi_nn::wit::WasiNnCtx::new(backends, registry));
1206 }
1207 }
1208 }
1209 }
1210
1211 if self.run.common.wasi.config == Some(true) {
1212 #[cfg(not(feature = "wasi-config"))]
1213 {
1214 bail!(
1215 "Cannot enable wasi-config when the binary is not compiled with this feature."
1216 );
1217 }
1218 #[cfg(all(feature = "wasi-config", feature = "component-model"))]
1219 {
1220 match linker {
1221 CliLinker::Core(_) => {
1222 bail!("Cannot enable wasi-config for core wasm modules");
1223 }
1224 CliLinker::Component(linker) => {
1225 let vars = WasiConfigVariables::from_iter(
1226 self.run
1227 .common
1228 .wasi
1229 .config_var
1230 .iter()
1231 .map(|v| (v.key.clone(), v.value.clone())),
1232 );
1233
1234 wasmtime_wasi_config::add_to_linker(linker, |h| {
1235 WasiConfig::new(h.wasi_config.as_mut().unwrap())
1236 })?;
1237 store.data_mut().wasi_config = Some(vars);
1238 }
1239 }
1240 }
1241 }
1242
1243 if self.run.common.wasi.keyvalue == Some(true) {
1244 #[cfg(not(feature = "wasi-keyvalue"))]
1245 {
1246 bail!(
1247 "Cannot enable wasi-keyvalue when the binary is not compiled with this feature."
1248 );
1249 }
1250 #[cfg(all(feature = "wasi-keyvalue", feature = "component-model"))]
1251 {
1252 match linker {
1253 CliLinker::Core(_) => {
1254 bail!("Cannot enable wasi-keyvalue for core wasm modules");
1255 }
1256 CliLinker::Component(linker) => {
1257 let ctx = WasiKeyValueCtxBuilder::new()
1258 .in_memory_data(
1259 self.run
1260 .common
1261 .wasi
1262 .keyvalue_in_memory_data
1263 .iter()
1264 .map(|v| (v.key.clone(), v.value.clone())),
1265 )
1266 .build();
1267
1268 wasmtime_wasi_keyvalue::add_to_linker(linker, |h| {
1269 let ctx = h.wasip1_ctx.as_mut().expect("wasip2 is not configured");
1270 WasiKeyValue::new(h.wasi_keyvalue.as_mut().unwrap(), ctx.ctx().table)
1271 })?;
1272 store.data_mut().wasi_keyvalue = Some(ctx);
1273 }
1274 }
1275 }
1276 }
1277
1278 if self.run.common.wasi.threads == Some(true) {
1279 bail!("support for wasi-threads has been removed from Wasmtime");
1280 }
1281
1282 if self.run.common.wasi.http == Some(true) {
1283 #[cfg(not(all(feature = "wasi-http", feature = "component-model")))]
1284 {
1285 bail!("Cannot enable wasi-http when the binary is not compiled with this feature.");
1286 }
1287 #[cfg(all(feature = "wasi-http", feature = "component-model"))]
1288 {
1289 match linker {
1290 CliLinker::Core(_) => {
1291 bail!("Cannot enable wasi-http for core wasm modules");
1292 }
1293 CliLinker::Component(linker) => {
1294 wasmtime_wasi_http::p2::add_only_http_to_linker_async(linker)?;
1295 #[cfg(feature = "component-model-async")]
1296 if self.run.common.wasi.p3.unwrap_or(crate::common::P3_DEFAULT) {
1297 wasmtime_wasi_http::p3::add_to_linker(linker)?;
1298 }
1299 }
1300 }
1301 let http = self.run.wasi_http_ctx()?;
1302 store.data_mut().wasi_http = Some(http);
1303 }
1304 }
1305
1306 if self.run.common.wasi.tls == Some(true) {
1307 #[cfg(all(not(all(feature = "wasi-tls", feature = "component-model"))))]
1308 {
1309 bail!("Cannot enable wasi-tls when the binary is not compiled with this feature.");
1310 }
1311 #[cfg(all(feature = "wasi-tls", feature = "component-model",))]
1312 {
1313 match linker {
1314 CliLinker::Core(_) => {
1315 bail!("Cannot enable wasi-tls for core wasm modules");
1316 }
1317 CliLinker::Component(linker) => {
1318 let mut opts = wasmtime_wasi_tls::p2::LinkOptions::default();
1319 opts.tls(true);
1320 wasmtime_wasi_tls::p2::add_to_linker(linker, &opts)?;
1321
1322 #[cfg(feature = "component-model-async")]
1323 if self.run.common.wasi.p3.unwrap_or(crate::common::P3_DEFAULT) {
1324 wasmtime_wasi_tls::p3::add_to_linker(linker)?;
1325 }
1326
1327 let ctx = wasmtime_wasi_tls::WasiTlsCtxBuilder::new().build();
1328 store.data_mut().wasi_tls = Some(ctx);
1329 }
1330 }
1331 }
1332 }
1333
1334 Ok(())
1335 }
1336
1337 fn set_wasi_ctx(&self, store: &mut Store<Host>) -> Result<()> {
1339 let mut builder = wasmtime_wasi::WasiCtxBuilder::new();
1340 builder.args(&self.compute_argv()?);
1341 if self.run.common.wasi.inherit_stdin.unwrap_or(true) {
1342 builder.inherit_stdin();
1343 }
1344 if self.run.common.wasi.inherit_stdout.unwrap_or(true) {
1345 builder.inherit_stdout();
1346 }
1347 if self.run.common.wasi.inherit_stderr.unwrap_or(true) {
1348 builder.inherit_stderr();
1349 }
1350 self.run.configure_wasip2(&mut builder)?;
1351 let mut ctx = builder.build_p1();
1352 if let Some(max) = self.run.common.wasi.max_resources {
1353 ctx.ctx().table.set_max_capacity(max);
1354 #[cfg(feature = "component-model-async")]
1355 if let Some(table) = store.concurrent_resource_table() {
1356 table.set_max_capacity(max);
1357 }
1358 }
1359 if let Some(fuel) = self.run.common.wasi.hostcall_fuel {
1360 store.set_hostcall_fuel(fuel);
1361 }
1362 store.data_mut().wasip1_ctx = Some(ctx);
1363 Ok(())
1364 }
1365
1366 #[cfg(feature = "wasi-nn")]
1367 fn collect_preloaded_nn_graphs(
1368 &self,
1369 ) -> Result<(Vec<wasmtime_wasi_nn::Backend>, wasmtime_wasi_nn::Registry)> {
1370 let graphs = self
1371 .run
1372 .common
1373 .wasi
1374 .nn_graph
1375 .iter()
1376 .map(|g| (g.format.clone(), g.dir.clone()))
1377 .collect::<Vec<_>>();
1378 wasmtime_wasi_nn::preload(&graphs)
1379 }
1380}
1381
1382#[derive(Default)]
1388pub struct Host {
1389 limits: StoreLimits,
1390 #[cfg(feature = "profiling")]
1391 guest_profiler: Option<wasmtime::GuestProfiler>,
1392
1393 wasip1_ctx: Option<wasmtime_wasi::p1::WasiP1Ctx>,
1397
1398 #[cfg(feature = "wasi-nn")]
1399 wasi_nn_wit: Option<wasmtime_wasi_nn::wit::WasiNnCtx>,
1400 #[cfg(feature = "wasi-nn")]
1401 wasi_nn_witx: Option<wasmtime_wasi_nn::witx::WasiNnCtx>,
1402
1403 #[cfg(feature = "wasi-http")]
1404 wasi_http: Option<WasiHttpCtx>,
1405 #[cfg(feature = "wasi-http")]
1406 wasi_http_hooks: crate::common::HttpHooks,
1407
1408 #[cfg(feature = "wasi-config")]
1409 wasi_config: Option<WasiConfigVariables>,
1410 #[cfg(feature = "wasi-keyvalue")]
1411 wasi_keyvalue: Option<WasiKeyValueCtx>,
1412 #[cfg(feature = "wasi-tls")]
1413 wasi_tls: Option<wasmtime_wasi_tls::WasiTlsCtx>,
1414}
1415
1416impl Host {
1417 pub(crate) fn wasip1_ctx(&mut self) -> &mut wasmtime_wasi::p1::WasiP1Ctx {
1418 self.wasip1_ctx.as_mut().unwrap()
1419 }
1420}
1421
1422impl WasiView for Host {
1423 fn ctx(&mut self) -> WasiCtxView<'_> {
1424 WasiView::ctx(self.wasip1_ctx())
1425 }
1426}
1427
1428#[cfg(feature = "wasi-http")]
1429impl wasmtime_wasi_http::p2::WasiHttpView for Host {
1430 fn http(&mut self) -> wasmtime_wasi_http::p2::WasiHttpCtxView<'_> {
1431 let ctx = self.wasi_http.as_mut().unwrap();
1432 wasmtime_wasi_http::p2::WasiHttpCtxView {
1433 table: WasiView::ctx(self.wasip1_ctx.as_mut().unwrap()).table,
1434 ctx,
1435 hooks: &mut self.wasi_http_hooks,
1436 }
1437 }
1438}
1439
1440#[cfg(all(feature = "wasi-http", feature = "component-model-async"))]
1441impl wasmtime_wasi_http::p3::WasiHttpView for Host {
1442 fn http(&mut self) -> wasmtime_wasi_http::p3::WasiHttpCtxView<'_> {
1443 let ctx = self.wasi_http.as_mut().unwrap();
1444 wasmtime_wasi_http::p3::WasiHttpCtxView {
1445 table: WasiView::ctx(self.wasip1_ctx.as_mut().unwrap()).table,
1446 ctx,
1447 hooks: &mut self.wasi_http_hooks,
1448 }
1449 }
1450}
1451
1452#[cfg(all(feature = "wasi-tls"))]
1453impl wasmtime_wasi_tls::WasiTlsView for Host {
1454 fn tls(&mut self) -> wasmtime_wasi_tls::WasiTlsCtxView<'_> {
1455 wasmtime_wasi_tls::WasiTlsCtxView {
1456 table: WasiView::ctx(self.wasip1_ctx.as_mut().unwrap()).table,
1457 ctx: self.wasi_tls.as_mut().unwrap(),
1458 }
1459 }
1460}
1461
1462#[cfg(feature = "coredump")]
1463fn write_core_dump(
1464 store: &mut Store<Host>,
1465 err: &wasmtime::Error,
1466 name: &str,
1467 path: &str,
1468) -> Result<()> {
1469 use std::fs::File;
1470 use std::io::Write;
1471
1472 let core_dump = err
1473 .downcast_ref::<wasmtime::WasmCoreDump>()
1474 .expect("should have been configured to capture core dumps");
1475
1476 let core_dump = core_dump.serialize(store, name);
1477
1478 let mut core_dump_file =
1479 File::create(path).with_context(|| format!("failed to create file at `{path}`"))?;
1480 core_dump_file
1481 .write_all(&core_dump)
1482 .with_context(|| format!("failed to write core dump file at `{path}`"))?;
1483 Ok(())
1484}