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