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 ) -> Result<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 store.engine(),
393 module_name,
394 interval,
395 profiled_modules,
396 )?)),
397 RunTarget::Component(component) => Some(Arc::new(GuestProfiler::new_component(
398 store.engine(),
399 module_name,
400 interval,
401 component.clone(),
402 profiled_modules,
403 )?)),
404 };
405
406 fn sample(
407 mut store: StoreContextMut<Host>,
408 f: impl FnOnce(&mut GuestProfiler, StoreContext<Host>),
409 ) {
410 let mut profiler = store.data_mut().guest_profiler.take().unwrap();
411 f(
412 Arc::get_mut(&mut profiler).expect("profiling doesn't support threads yet"),
413 store.as_context(),
414 );
415 store.data_mut().guest_profiler = Some(profiler);
416 }
417
418 store.call_hook(|store, kind| {
419 sample(store, |profiler, store| profiler.call_hook(store, kind));
420 Ok(())
421 });
422
423 if let Some(timeout) = self.run.common.wasm.timeout {
424 let mut timeout = (timeout.as_secs_f64() / interval.as_secs_f64()).ceil() as u64;
425 assert!(timeout > 0);
426 store.epoch_deadline_callback(move |store| {
427 sample(store, |profiler, store| {
428 profiler.sample(store, std::time::Duration::ZERO)
429 });
430 timeout -= 1;
431 if timeout == 0 {
432 bail!("timeout exceeded");
433 }
434 Ok(UpdateDeadline::Continue(1))
435 });
436 } else {
437 store.epoch_deadline_callback(move |store| {
438 sample(store, |profiler, store| {
439 profiler.sample(store, std::time::Duration::ZERO)
440 });
441 Ok(UpdateDeadline::Continue(1))
442 });
443 }
444
445 store.set_epoch_deadline(1);
446 let engine = store.engine().clone();
447 thread::spawn(move || {
448 loop {
449 thread::sleep(interval);
450 engine.increment_epoch();
451 }
452 });
453
454 let path = path.to_string();
455 Ok(Box::new(move |store| {
456 let profiler = Arc::try_unwrap(store.data_mut().guest_profiler.take().unwrap())
457 .expect("profiling doesn't support threads yet");
458 if let Err(e) = std::fs::File::create(&path)
459 .map_err(anyhow::Error::new)
460 .and_then(|output| profiler.finish(std::io::BufWriter::new(output)))
461 {
462 eprintln!("failed writing profile at {path}: {e:#}");
463 } else {
464 eprintln!();
465 eprintln!("Profile written to: {path}");
466 eprintln!("View this profile at https://profiler.firefox.com/.");
467 }
468 }))
469 }
470
471 async fn load_main_module(
472 &self,
473 store: &mut Store<Host>,
474 linker: &mut CliLinker,
475 main_target: &RunTarget,
476 profiled_modules: Vec<(String, Module)>,
477 ) -> Result<CliInstance> {
478 if self.run.common.wasm.unknown_imports_trap == Some(true) {
481 match linker {
482 CliLinker::Core(linker) => {
483 linker.define_unknown_imports_as_traps(main_target.unwrap_core())?;
484 }
485 #[cfg(feature = "component-model")]
486 CliLinker::Component(linker) => {
487 linker.define_unknown_imports_as_traps(main_target.unwrap_component())?;
488 }
489 }
490 }
491
492 if self.run.common.wasm.unknown_imports_default == Some(true) {
494 match linker {
495 CliLinker::Core(linker) => {
496 linker.define_unknown_imports_as_default_values(
497 &mut *store,
498 main_target.unwrap_core(),
499 )?;
500 }
501 _ => bail!("cannot use `--default-values-unknown-imports` with components"),
502 }
503 }
504
505 let finish_epoch_handler =
506 self.setup_epoch_handler(store, main_target, profiled_modules)?;
507
508 let result = match linker {
509 CliLinker::Core(linker) => {
510 let module = main_target.unwrap_core();
511 let instance = linker
512 .instantiate_async(&mut *store, &module)
513 .await
514 .context(format!(
515 "failed to instantiate {:?}",
516 self.module_and_args[0]
517 ))?;
518
519 if let Some(func) = instance.get_func(&mut *store, "_initialize") {
522 func.typed::<(), ()>(&store)?
523 .call_async(&mut *store, ())
524 .await?;
525 }
526
527 let func = if let Some(name) = &self.invoke {
530 Some(
531 instance
532 .get_func(&mut *store, name)
533 .ok_or_else(|| anyhow!("no func export named `{name}` found"))?,
534 )
535 } else {
536 instance
537 .get_func(&mut *store, "")
538 .or_else(|| instance.get_func(&mut *store, "_start"))
539 };
540
541 if let Some(func) = func {
542 self.invoke_func(store, func).await?;
543 }
544 Ok(CliInstance::Core(instance))
545 }
546 #[cfg(feature = "component-model")]
547 CliLinker::Component(linker) => {
548 let component = main_target.unwrap_component();
549 let result = if self.invoke.is_some() {
550 self.invoke_component(&mut *store, component, linker).await
551 } else {
552 self.run_command_component(&mut *store, component, linker)
553 .await
554 };
555 result
556 .map(CliInstance::Component)
557 .map_err(|e| self.handle_core_dump(&mut *store, e))
558 }
559 };
560 finish_epoch_handler(store);
561
562 result
563 }
564
565 #[cfg(feature = "component-model")]
566 async fn invoke_component(
567 &self,
568 store: &mut Store<Host>,
569 component: &wasmtime::component::Component,
570 linker: &mut wasmtime::component::Linker<Host>,
571 ) -> Result<wasmtime::component::Instance> {
572 use wasmtime::component::{
573 Val,
574 wasm_wave::{
575 untyped::UntypedFuncCall,
576 wasm::{DisplayFuncResults, WasmFunc},
577 },
578 };
579
580 let invoke: &String = self.invoke.as_ref().unwrap();
582
583 let untyped_call = UntypedFuncCall::parse(invoke).with_context(|| {
584 format!(
585 "Failed to parse invoke '{invoke}': See https://docs.wasmtime.dev/cli-options.html#run for syntax",
586 )
587 })?;
588
589 let name = untyped_call.name();
590 let matches =
591 Self::search_component_funcs(store.engine(), component.component_type(), name);
592 let (names, func_type) = match matches.len() {
593 0 => bail!("No exported func named `{name}` in component."),
594 1 => &matches[0],
595 _ => bail!(
596 "Multiple exports named `{name}`: {matches:?}. FIXME: support some way to disambiguate names"
597 ),
598 };
599
600 let param_types = WasmFunc::params(func_type).collect::<Vec<_>>();
601 let params = untyped_call
602 .to_wasm_params(¶m_types)
603 .with_context(|| format!("while interpreting parameters in invoke \"{invoke}\""))?;
604
605 let export = names
606 .iter()
607 .fold(None, |instance, name| {
608 component.get_export_index(instance.as_ref(), name)
609 })
610 .expect("export has at least one name");
611
612 let instance = linker.instantiate_async(&mut *store, component).await?;
613
614 let func = instance
615 .get_func(&mut *store, export)
616 .expect("found export index");
617
618 let mut results = vec![Val::Bool(false); func_type.results().len()];
619 func.call_async(&mut *store, ¶ms, &mut results).await?;
620 func.post_return_async(&mut *store).await?;
621
622 println!("{}", DisplayFuncResults(&results));
623
624 Ok(instance)
625 }
626
627 #[cfg(feature = "component-model")]
630 async fn run_command_component(
631 &self,
632 store: &mut Store<Host>,
633 component: &wasmtime::component::Component,
634 linker: &wasmtime::component::Linker<Host>,
635 ) -> Result<wasmtime::component::Instance> {
636 let instance = linker.instantiate_async(&mut *store, component).await?;
637
638 let mut result = None;
639 let _ = &mut result;
640
641 #[cfg(feature = "component-model-async")]
644 if self.run.common.wasi.p3.unwrap_or(crate::common::P3_DEFAULT) {
645 if let Ok(command) = wasmtime_wasi::p3::bindings::Command::new(&mut *store, &instance) {
646 result = Some(
647 store
648 .run_concurrent(async |store| command.wasi_cli_run().call_run(store).await)
649 .await?,
650 );
651 }
652 }
653
654 let result = match result {
655 Some(result) => result,
656 None => {
659 wasmtime_wasi::p2::bindings::Command::new(&mut *store, &instance)?
660 .wasi_cli_run()
661 .call_run(&mut *store)
662 .await
663 }
664 };
665 let wasm_result = result.context("failed to invoke `run` function")?;
666
667 match wasm_result {
670 Ok(()) => Ok(instance),
671 Err(()) => Err(wasmtime_wasi::I32Exit(1).into()),
672 }
673 }
674
675 #[cfg(feature = "component-model")]
676 fn search_component_funcs(
677 engine: &Engine,
678 component: wasmtime::component::types::Component,
679 name: &str,
680 ) -> Vec<(Vec<String>, wasmtime::component::types::ComponentFunc)> {
681 use wasmtime::component::types::ComponentItem as CItem;
682 fn collect_exports(
683 engine: &Engine,
684 item: CItem,
685 basename: Vec<String>,
686 ) -> Vec<(Vec<String>, CItem)> {
687 match item {
688 CItem::Component(c) => c
689 .exports(engine)
690 .flat_map(move |(name, item)| {
691 let mut names = basename.clone();
692 names.push(name.to_string());
693 collect_exports(engine, item, names)
694 })
695 .collect::<Vec<_>>(),
696 CItem::ComponentInstance(c) => c
697 .exports(engine)
698 .flat_map(move |(name, item)| {
699 let mut names = basename.clone();
700 names.push(name.to_string());
701 collect_exports(engine, item, names)
702 })
703 .collect::<Vec<_>>(),
704 _ => vec![(basename, item)],
705 }
706 }
707
708 collect_exports(engine, CItem::Component(component), Vec::new())
709 .into_iter()
710 .filter_map(|(names, item)| {
711 let CItem::ComponentFunc(func) = item else {
712 return None;
713 };
714 let func_name = names.last().expect("at least one name");
715 (func_name == name).then_some((names, func))
716 })
717 .collect()
718 }
719
720 async fn invoke_func(&self, store: &mut Store<Host>, func: Func) -> Result<()> {
721 let ty = func.ty(&store);
722 if ty.params().len() > 0 {
723 eprintln!(
724 "warning: using `--invoke` with a function that takes arguments \
725 is experimental and may break in the future"
726 );
727 }
728 let mut args = self.module_and_args.iter().skip(1);
729 let mut values = Vec::new();
730 for ty in ty.params() {
731 let val = match args.next() {
732 Some(s) => s,
733 None => {
734 if let Some(name) = &self.invoke {
735 bail!("not enough arguments for `{name}`")
736 } else {
737 bail!("not enough arguments for command default")
738 }
739 }
740 };
741 let val = val
742 .to_str()
743 .ok_or_else(|| anyhow!("argument is not valid utf-8: {val:?}"))?;
744 values.push(match ty {
745 ValType::I32 => Val::I32(if val.starts_with("0x") || val.starts_with("0X") {
747 i32::from_str_radix(&val[2..], 16)?
748 } else {
749 val.parse::<i32>()?
750 }),
751 ValType::I64 => Val::I64(if val.starts_with("0x") || val.starts_with("0X") {
752 i64::from_str_radix(&val[2..], 16)?
753 } else {
754 val.parse::<i64>()?
755 }),
756 ValType::F32 => Val::F32(val.parse::<f32>()?.to_bits()),
757 ValType::F64 => Val::F64(val.parse::<f64>()?.to_bits()),
758 t => bail!("unsupported argument type {t:?}"),
759 });
760 }
761
762 let mut results = vec![Val::null_func_ref(); ty.results().len()];
765 let invoke_res = func
766 .call_async(&mut *store, &values, &mut results)
767 .await
768 .with_context(|| {
769 if let Some(name) = &self.invoke {
770 format!("failed to invoke `{name}`")
771 } else {
772 format!("failed to invoke command default")
773 }
774 });
775
776 if let Err(err) = invoke_res {
777 return Err(self.handle_core_dump(&mut *store, err));
778 }
779
780 if !results.is_empty() {
781 eprintln!(
782 "warning: using `--invoke` with a function that returns values \
783 is experimental and may break in the future"
784 );
785 }
786
787 for result in results {
788 match result {
789 Val::I32(i) => println!("{i}"),
790 Val::I64(i) => println!("{i}"),
791 Val::F32(f) => println!("{}", f32::from_bits(f)),
792 Val::F64(f) => println!("{}", f64::from_bits(f)),
793 Val::V128(i) => println!("{}", i.as_u128()),
794 Val::ExternRef(None) => println!("<null externref>"),
795 Val::ExternRef(Some(_)) => println!("<externref>"),
796 Val::FuncRef(None) => println!("<null funcref>"),
797 Val::FuncRef(Some(_)) => println!("<funcref>"),
798 Val::AnyRef(None) => println!("<null anyref>"),
799 Val::AnyRef(Some(_)) => println!("<anyref>"),
800 Val::ExnRef(None) => println!("<null exnref>"),
801 Val::ExnRef(Some(_)) => println!("<exnref>"),
802 Val::ContRef(None) => println!("<null contref>"),
803 Val::ContRef(Some(_)) => println!("<contref>"),
804 }
805 }
806
807 Ok(())
808 }
809
810 #[cfg(feature = "coredump")]
811 fn handle_core_dump(&self, store: &mut Store<Host>, err: Error) -> Error {
812 let coredump_path = match &self.run.common.debug.coredump {
813 Some(path) => path,
814 None => return err,
815 };
816 if !err.is::<wasmtime::Trap>() {
817 return err;
818 }
819 let source_name = self.module_and_args[0]
820 .to_str()
821 .unwrap_or_else(|| "unknown");
822
823 if let Err(coredump_err) = write_core_dump(store, &err, &source_name, coredump_path) {
824 eprintln!("warning: coredump failed to generate: {coredump_err}");
825 err
826 } else {
827 err.context(format!("core dumped at {coredump_path}"))
828 }
829 }
830
831 #[cfg(not(feature = "coredump"))]
832 fn handle_core_dump(&self, _store: &mut Store<Host>, err: Error) -> Error {
833 err
834 }
835
836 fn populate_with_wasi(
838 &self,
839 linker: &mut CliLinker,
840 store: &mut Store<Host>,
841 module: &RunTarget,
842 ) -> Result<()> {
843 self.run.validate_p3_option()?;
844 let cli = self.run.validate_cli_enabled()?;
845
846 if cli != Some(false) {
847 match linker {
848 CliLinker::Core(linker) => {
849 match (self.run.common.wasi.preview2, self.run.common.wasi.threads) {
850 (Some(false), _) | (None, Some(true)) => {
854 wasi_common::tokio::add_to_linker(linker, |host| {
855 host.legacy_p1_ctx.as_mut().unwrap()
856 })?;
857 self.set_legacy_p1_ctx(store)?;
858 }
859 (Some(true), _) | (None, Some(false) | None) => {
866 if self.run.common.wasi.preview0 != Some(false) {
867 wasmtime_wasi::p0::add_to_linker_async(linker, |t| t.wasip1_ctx())?;
868 }
869 wasmtime_wasi::p1::add_to_linker_async(linker, |t| t.wasip1_ctx())?;
870 self.set_wasi_ctx(store)?;
871 }
872 }
873 }
874 #[cfg(feature = "component-model")]
875 CliLinker::Component(linker) => {
876 self.run.add_wasmtime_wasi_to_linker(linker)?;
877 self.set_wasi_ctx(store)?;
878 }
879 }
880 }
881
882 if self.run.common.wasi.nn == Some(true) {
883 #[cfg(not(feature = "wasi-nn"))]
884 {
885 bail!("Cannot enable wasi-nn when the binary is not compiled with this feature.");
886 }
887 #[cfg(all(feature = "wasi-nn", feature = "component-model"))]
888 {
889 let (backends, registry) = self.collect_preloaded_nn_graphs()?;
890 match linker {
891 CliLinker::Core(linker) => {
892 wasmtime_wasi_nn::witx::add_to_linker(linker, |host| {
893 Arc::get_mut(host.wasi_nn_witx.as_mut().unwrap())
894 .expect("wasi-nn is not implemented with multi-threading support")
895 })?;
896 store.data_mut().wasi_nn_witx = Some(Arc::new(
897 wasmtime_wasi_nn::witx::WasiNnCtx::new(backends, registry),
898 ));
899 }
900 #[cfg(feature = "component-model")]
901 CliLinker::Component(linker) => {
902 wasmtime_wasi_nn::wit::add_to_linker(linker, |h: &mut Host| {
903 let ctx = h.wasip1_ctx.as_mut().expect("wasi is not configured");
904 let ctx = Arc::get_mut(ctx)
905 .expect("wasmtime_wasi is not compatible with threads")
906 .get_mut()
907 .unwrap();
908 let nn_ctx = Arc::get_mut(h.wasi_nn_wit.as_mut().unwrap())
909 .expect("wasi-nn is not implemented with multi-threading support");
910 WasiNnView::new(ctx.ctx().table, nn_ctx)
911 })?;
912 store.data_mut().wasi_nn_wit = Some(Arc::new(
913 wasmtime_wasi_nn::wit::WasiNnCtx::new(backends, registry),
914 ));
915 }
916 }
917 }
918 }
919
920 if self.run.common.wasi.config == Some(true) {
921 #[cfg(not(feature = "wasi-config"))]
922 {
923 bail!(
924 "Cannot enable wasi-config when the binary is not compiled with this feature."
925 );
926 }
927 #[cfg(all(feature = "wasi-config", feature = "component-model"))]
928 {
929 match linker {
930 CliLinker::Core(_) => {
931 bail!("Cannot enable wasi-config for core wasm modules");
932 }
933 CliLinker::Component(linker) => {
934 let vars = WasiConfigVariables::from_iter(
935 self.run
936 .common
937 .wasi
938 .config_var
939 .iter()
940 .map(|v| (v.key.clone(), v.value.clone())),
941 );
942
943 wasmtime_wasi_config::add_to_linker(linker, |h| {
944 WasiConfig::new(Arc::get_mut(h.wasi_config.as_mut().unwrap()).unwrap())
945 })?;
946 store.data_mut().wasi_config = Some(Arc::new(vars));
947 }
948 }
949 }
950 }
951
952 if self.run.common.wasi.keyvalue == Some(true) {
953 #[cfg(not(feature = "wasi-keyvalue"))]
954 {
955 bail!(
956 "Cannot enable wasi-keyvalue when the binary is not compiled with this feature."
957 );
958 }
959 #[cfg(all(feature = "wasi-keyvalue", feature = "component-model"))]
960 {
961 match linker {
962 CliLinker::Core(_) => {
963 bail!("Cannot enable wasi-keyvalue for core wasm modules");
964 }
965 CliLinker::Component(linker) => {
966 let ctx = WasiKeyValueCtxBuilder::new()
967 .in_memory_data(
968 self.run
969 .common
970 .wasi
971 .keyvalue_in_memory_data
972 .iter()
973 .map(|v| (v.key.clone(), v.value.clone())),
974 )
975 .build();
976
977 wasmtime_wasi_keyvalue::add_to_linker(linker, |h| {
978 let ctx = h.wasip1_ctx.as_mut().expect("wasip2 is not configured");
979 let ctx = Arc::get_mut(ctx).unwrap().get_mut().unwrap();
980 WasiKeyValue::new(
981 Arc::get_mut(h.wasi_keyvalue.as_mut().unwrap()).unwrap(),
982 ctx.ctx().table,
983 )
984 })?;
985 store.data_mut().wasi_keyvalue = Some(Arc::new(ctx));
986 }
987 }
988 }
989 }
990
991 if self.run.common.wasi.threads == Some(true) {
992 #[cfg(not(feature = "wasi-threads"))]
993 {
994 let _ = &module;
997
998 bail!(
999 "Cannot enable wasi-threads when the binary is not compiled with this feature."
1000 );
1001 }
1002 #[cfg(feature = "wasi-threads")]
1003 {
1004 let linker = match linker {
1005 CliLinker::Core(linker) => linker,
1006 _ => bail!("wasi-threads does not support components yet"),
1007 };
1008 let module = module.unwrap_core();
1009 wasmtime_wasi_threads::add_to_linker(linker, store, &module, |host| {
1010 host.wasi_threads.as_ref().unwrap()
1011 })?;
1012 store.data_mut().wasi_threads = Some(Arc::new(WasiThreadsCtx::new(
1013 module.clone(),
1014 Arc::new(linker.clone()),
1015 )?));
1016 }
1017 }
1018
1019 if self.run.common.wasi.http == Some(true) {
1020 #[cfg(not(all(feature = "wasi-http", feature = "component-model")))]
1021 {
1022 bail!("Cannot enable wasi-http when the binary is not compiled with this feature.");
1023 }
1024 #[cfg(all(feature = "wasi-http", feature = "component-model"))]
1025 {
1026 match linker {
1027 CliLinker::Core(_) => {
1028 bail!("Cannot enable wasi-http for core wasm modules");
1029 }
1030 CliLinker::Component(linker) => {
1031 wasmtime_wasi_http::add_only_http_to_linker_sync(linker)?;
1032 #[cfg(feature = "component-model-async")]
1033 if self.run.common.wasi.p3.unwrap_or(crate::common::P3_DEFAULT) {
1034 wasmtime_wasi_http::p3::add_to_linker(linker)?;
1035 }
1036 }
1037 }
1038
1039 store.data_mut().wasi_http = Some(Arc::new(WasiHttpCtx::new()));
1040 }
1041 }
1042
1043 if self.run.common.wasi.tls == Some(true) {
1044 #[cfg(all(not(all(feature = "wasi-tls", feature = "component-model"))))]
1045 {
1046 bail!("Cannot enable wasi-tls when the binary is not compiled with this feature.");
1047 }
1048 #[cfg(all(feature = "wasi-tls", feature = "component-model",))]
1049 {
1050 match linker {
1051 CliLinker::Core(_) => {
1052 bail!("Cannot enable wasi-tls for core wasm modules");
1053 }
1054 CliLinker::Component(linker) => {
1055 let mut opts = wasmtime_wasi_tls::LinkOptions::default();
1056 opts.tls(true);
1057 wasmtime_wasi_tls::add_to_linker(linker, &mut opts, |h| {
1058 let ctx = h.wasip1_ctx.as_mut().expect("wasi is not configured");
1059 let ctx = Arc::get_mut(ctx).unwrap().get_mut().unwrap();
1060 WasiTls::new(
1061 Arc::get_mut(h.wasi_tls.as_mut().unwrap()).unwrap(),
1062 ctx.ctx().table,
1063 )
1064 })?;
1065
1066 let ctx = wasmtime_wasi_tls::WasiTlsCtxBuilder::new().build();
1067 store.data_mut().wasi_tls = Some(Arc::new(ctx));
1068 }
1069 }
1070 }
1071 }
1072
1073 Ok(())
1074 }
1075
1076 fn set_legacy_p1_ctx(&self, store: &mut Store<Host>) -> Result<()> {
1077 let mut builder = WasiCtxBuilder::new();
1078 builder.inherit_stdio().args(&self.compute_argv()?)?;
1079
1080 if self.run.common.wasi.inherit_env == Some(true) {
1081 for (k, v) in std::env::vars() {
1082 builder.env(&k, &v)?;
1083 }
1084 }
1085 for (key, value) in self.run.vars.iter() {
1086 let value = match value {
1087 Some(value) => value.clone(),
1088 None => match std::env::var_os(key) {
1089 Some(val) => val
1090 .into_string()
1091 .map_err(|_| anyhow!("environment variable `{key}` not valid utf-8"))?,
1092 None => {
1093 continue;
1095 }
1096 },
1097 };
1098 builder.env(key, &value)?;
1099 }
1100
1101 let mut num_fd: usize = 3;
1102
1103 if self.run.common.wasi.listenfd == Some(true) {
1104 num_fd = ctx_set_listenfd(num_fd, &mut builder)?;
1105 }
1106
1107 for listener in self.run.compute_preopen_sockets()? {
1108 let listener = TcpListener::from_std(listener);
1109 builder.preopened_socket(num_fd as _, listener)?;
1110 num_fd += 1;
1111 }
1112
1113 for (host, guest) in self.run.dirs.iter() {
1114 let dir = Dir::open_ambient_dir(host, ambient_authority())
1115 .with_context(|| format!("failed to open directory '{host}'"))?;
1116 builder.preopened_dir(dir, guest)?;
1117 }
1118
1119 store.data_mut().legacy_p1_ctx = Some(builder.build());
1120 Ok(())
1121 }
1122
1123 fn set_wasi_ctx(&self, store: &mut Store<Host>) -> Result<()> {
1131 let mut builder = wasmtime_wasi::WasiCtxBuilder::new();
1132 builder.inherit_stdio().args(&self.compute_argv()?);
1133 self.run.configure_wasip2(&mut builder)?;
1134 let ctx = builder.build_p1();
1135 store.data_mut().wasip1_ctx = Some(Arc::new(Mutex::new(ctx)));
1136 Ok(())
1137 }
1138
1139 #[cfg(feature = "wasi-nn")]
1140 fn collect_preloaded_nn_graphs(
1141 &self,
1142 ) -> Result<(Vec<wasmtime_wasi_nn::Backend>, wasmtime_wasi_nn::Registry)> {
1143 let graphs = self
1144 .run
1145 .common
1146 .wasi
1147 .nn_graph
1148 .iter()
1149 .map(|g| (g.format.clone(), g.dir.clone()))
1150 .collect::<Vec<_>>();
1151 wasmtime_wasi_nn::preload(&graphs)
1152 }
1153}
1154
1155#[derive(Default, Clone)]
1171pub struct Host {
1172 legacy_p1_ctx: Option<wasi_common::WasiCtx>,
1175
1176 wasip1_ctx: Option<Arc<Mutex<wasmtime_wasi::p1::WasiP1Ctx>>>,
1184
1185 #[cfg(feature = "wasi-nn")]
1186 wasi_nn_wit: Option<Arc<wasmtime_wasi_nn::wit::WasiNnCtx>>,
1187 #[cfg(feature = "wasi-nn")]
1188 wasi_nn_witx: Option<Arc<wasmtime_wasi_nn::witx::WasiNnCtx>>,
1189
1190 #[cfg(feature = "wasi-threads")]
1191 wasi_threads: Option<Arc<WasiThreadsCtx<Host>>>,
1192 #[cfg(feature = "wasi-http")]
1193 wasi_http: Option<Arc<WasiHttpCtx>>,
1194 #[cfg(feature = "wasi-http")]
1195 wasi_http_outgoing_body_buffer_chunks: Option<usize>,
1196 #[cfg(feature = "wasi-http")]
1197 wasi_http_outgoing_body_chunk_size: Option<usize>,
1198 #[cfg(all(feature = "wasi-http", feature = "component-model-async"))]
1199 p3_http: crate::common::DefaultP3Ctx,
1200 limits: StoreLimits,
1201 #[cfg(feature = "profiling")]
1202 guest_profiler: Option<Arc<wasmtime::GuestProfiler>>,
1203
1204 #[cfg(feature = "wasi-config")]
1205 wasi_config: Option<Arc<WasiConfigVariables>>,
1206 #[cfg(feature = "wasi-keyvalue")]
1207 wasi_keyvalue: Option<Arc<WasiKeyValueCtx>>,
1208 #[cfg(feature = "wasi-tls")]
1209 wasi_tls: Option<Arc<WasiTlsCtx>>,
1210}
1211
1212impl Host {
1213 fn wasip1_ctx(&mut self) -> &mut wasmtime_wasi::p1::WasiP1Ctx {
1214 unwrap_singlethread_context(&mut self.wasip1_ctx)
1215 }
1216}
1217
1218fn unwrap_singlethread_context<T>(ctx: &mut Option<Arc<Mutex<T>>>) -> &mut T {
1219 let ctx = ctx.as_mut().expect("context not configured");
1220 Arc::get_mut(ctx)
1221 .expect("context is not compatible with threads")
1222 .get_mut()
1223 .unwrap()
1224}
1225
1226impl WasiView for Host {
1227 fn ctx(&mut self) -> WasiCtxView<'_> {
1228 WasiView::ctx(self.wasip1_ctx())
1229 }
1230}
1231
1232#[cfg(feature = "wasi-http")]
1233impl wasmtime_wasi_http::types::WasiHttpView for Host {
1234 fn ctx(&mut self) -> &mut WasiHttpCtx {
1235 let ctx = self.wasi_http.as_mut().unwrap();
1236 Arc::get_mut(ctx).expect("wasmtime_wasi is not compatible with threads")
1237 }
1238
1239 fn table(&mut self) -> &mut wasmtime::component::ResourceTable {
1240 WasiView::ctx(self).table
1241 }
1242
1243 fn outgoing_body_buffer_chunks(&mut self) -> usize {
1244 self.wasi_http_outgoing_body_buffer_chunks
1245 .unwrap_or_else(|| DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS)
1246 }
1247
1248 fn outgoing_body_chunk_size(&mut self) -> usize {
1249 self.wasi_http_outgoing_body_chunk_size
1250 .unwrap_or_else(|| DEFAULT_OUTGOING_BODY_CHUNK_SIZE)
1251 }
1252}
1253
1254#[cfg(all(feature = "wasi-http", feature = "component-model-async"))]
1255impl wasmtime_wasi_http::p3::WasiHttpView for Host {
1256 fn http(&mut self) -> wasmtime_wasi_http::p3::WasiHttpCtxView<'_> {
1257 wasmtime_wasi_http::p3::WasiHttpCtxView {
1258 table: WasiView::ctx(unwrap_singlethread_context(&mut self.wasip1_ctx)).table,
1259 ctx: &mut self.p3_http,
1260 }
1261 }
1262}
1263
1264fn ctx_set_listenfd(mut num_fd: usize, builder: &mut WasiCtxBuilder) -> Result<usize> {
1265 let _ = &mut num_fd;
1266 let _ = &mut *builder;
1267
1268 #[cfg(all(unix, feature = "run"))]
1269 {
1270 use listenfd::ListenFd;
1271
1272 for env in ["LISTEN_FDS", "LISTEN_FDNAMES"] {
1273 if let Ok(val) = std::env::var(env) {
1274 builder.env(env, &val)?;
1275 }
1276 }
1277
1278 let mut listenfd = ListenFd::from_env();
1279
1280 for i in 0..listenfd.len() {
1281 if let Some(stdlistener) = listenfd.take_tcp_listener(i)? {
1282 let _ = stdlistener.set_nonblocking(true)?;
1283 let listener = TcpListener::from_std(stdlistener);
1284 builder.preopened_socket((3 + i) as _, listener)?;
1285 num_fd = 3 + i;
1286 }
1287 }
1288 }
1289
1290 Ok(num_fd)
1291}
1292
1293#[cfg(feature = "coredump")]
1294fn write_core_dump(
1295 store: &mut Store<Host>,
1296 err: &anyhow::Error,
1297 name: &str,
1298 path: &str,
1299) -> Result<()> {
1300 use std::fs::File;
1301 use std::io::Write;
1302
1303 let core_dump = err
1304 .downcast_ref::<wasmtime::WasmCoreDump>()
1305 .expect("should have been configured to capture core dumps");
1306
1307 let core_dump = core_dump.serialize(store, name);
1308
1309 let mut core_dump_file =
1310 File::create(path).context(format!("failed to create file at `{path}`"))?;
1311 core_dump_file
1312 .write_all(&core_dump)
1313 .with_context(|| format!("failed to write core dump file at `{path}`"))?;
1314 Ok(())
1315}