1#![cfg_attr(
4 not(feature = "component-model"),
5 allow(irrefutable_let_patterns, unreachable_patterns)
6)]
7
8use crate::common::{Profile, RunCommon, RunTarget};
9
10use anyhow::{anyhow, bail, Context as _, Error, Result};
11use clap::Parser;
12use std::ffi::OsString;
13use std::path::{Path, PathBuf};
14use std::sync::{Arc, Mutex};
15use std::thread;
16use wasi_common::sync::{ambient_authority, Dir, TcpListener, WasiCtxBuilder};
17use wasmtime::{Engine, Func, Module, Store, StoreLimits, Val, ValType};
18use wasmtime_wasi::{IoView, WasiView};
19
20#[cfg(feature = "wasi-nn")]
21use wasmtime_wasi_nn::wit::WasiNnView;
22
23#[cfg(feature = "wasi-threads")]
24use wasmtime_wasi_threads::WasiThreadsCtx;
25
26#[cfg(feature = "wasi-config")]
27use wasmtime_wasi_config::{WasiConfig, WasiConfigVariables};
28#[cfg(feature = "wasi-http")]
29use wasmtime_wasi_http::{
30 WasiHttpCtx, DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS, DEFAULT_OUTGOING_BODY_CHUNK_SIZE,
31};
32#[cfg(feature = "wasi-keyvalue")]
33use wasmtime_wasi_keyvalue::{WasiKeyValue, WasiKeyValueCtx, WasiKeyValueCtxBuilder};
34
35#[cfg(feature = "wasi-tls")]
36use wasmtime_wasi_tls::WasiTlsCtx;
37
38fn parse_preloads(s: &str) -> Result<(String, PathBuf)> {
39 let parts: Vec<&str> = s.splitn(2, '=').collect();
40 if parts.len() != 2 {
41 bail!("must contain exactly one equals character ('=')");
42 }
43 Ok((parts[0].into(), parts[1].into()))
44}
45
46#[derive(Parser)]
48pub struct RunCommand {
49 #[command(flatten)]
50 #[expect(missing_docs, reason = "don't want to mess with clap doc-strings")]
51 pub run: RunCommon,
52
53 #[arg(long, value_name = "FUNCTION")]
55 pub invoke: Option<String>,
56
57 #[arg(
59 long = "preload",
60 number_of_values = 1,
61 value_name = "NAME=MODULE_PATH",
62 value_parser = parse_preloads,
63 )]
64 pub preloads: Vec<(String, PathBuf)>,
65
66 #[arg(long)]
73 pub argv0: Option<String>,
74
75 #[arg(value_name = "WASM", trailing_var_arg = true, required = true)]
81 pub module_and_args: Vec<OsString>,
82}
83
84enum CliLinker {
85 Core(wasmtime::Linker<Host>),
86 #[cfg(feature = "component-model")]
87 Component(wasmtime::component::Linker<Host>),
88}
89
90impl RunCommand {
91 pub fn execute(mut self) -> Result<()> {
93 self.run.common.init_logging()?;
94
95 let mut config = self.run.common.config(None)?;
96 config.async_support(true);
97
98 if self.run.common.wasm.timeout.is_some() {
99 config.epoch_interruption(true);
100 }
101 match self.run.profile {
102 Some(Profile::Native(s)) => {
103 config.profiler(s);
104 }
105 Some(Profile::Guest { .. }) => {
106 config.epoch_interruption(true);
108 }
109 None => {}
110 }
111
112 let engine = Engine::new(&config)?;
113
114 let main = self
116 .run
117 .load_module(&engine, self.module_and_args[0].as_ref())?;
118
119 if let Some(path) = &self.run.common.debug.coredump {
121 if path.contains("%") {
122 bail!("the coredump-on-trap path does not support patterns yet.")
123 }
124 }
125
126 let mut linker = match &main {
127 RunTarget::Core(_) => CliLinker::Core(wasmtime::Linker::new(&engine)),
128 #[cfg(feature = "component-model")]
129 RunTarget::Component(_) => {
130 CliLinker::Component(wasmtime::component::Linker::new(&engine))
131 }
132 };
133 if let Some(enable) = self.run.common.wasm.unknown_exports_allow {
134 match &mut linker {
135 CliLinker::Core(l) => {
136 l.allow_unknown_exports(enable);
137 }
138 #[cfg(feature = "component-model")]
139 CliLinker::Component(_) => {
140 bail!("--allow-unknown-exports not supported with components");
141 }
142 }
143 }
144
145 let host = Host {
146 #[cfg(feature = "wasi-http")]
147 wasi_http_outgoing_body_buffer_chunks: self
148 .run
149 .common
150 .wasi
151 .http_outgoing_body_buffer_chunks,
152 #[cfg(feature = "wasi-http")]
153 wasi_http_outgoing_body_chunk_size: self.run.common.wasi.http_outgoing_body_chunk_size,
154 ..Default::default()
155 };
156
157 let mut store = Store::new(&engine, host);
158 self.populate_with_wasi(&mut linker, &mut store, &main)?;
159
160 store.data_mut().limits = self.run.store_limits();
161 store.limiter(|t| &mut t.limits);
162
163 if let Some(fuel) = self.run.common.wasm.fuel {
166 store.set_fuel(fuel)?;
167 }
168
169 let runtime = tokio::runtime::Builder::new_multi_thread()
172 .enable_time()
173 .enable_io()
174 .build()?;
175
176 let dur = self
177 .run
178 .common
179 .wasm
180 .timeout
181 .unwrap_or(std::time::Duration::MAX);
182 let result = runtime.block_on(async {
183 tokio::time::timeout(dur, async {
184 let mut profiled_modules: Vec<(String, Module)> = Vec::new();
185 if let RunTarget::Core(m) = &main {
186 profiled_modules.push(("".to_string(), m.clone()));
187 }
188
189 for (name, path) in self.preloads.iter() {
191 let preload_target = self.run.load_module(&engine, path)?;
193 let preload_module = match preload_target {
194 RunTarget::Core(m) => m,
195 #[cfg(feature = "component-model")]
196 RunTarget::Component(_) => {
197 bail!("components cannot be loaded with `--preload`")
198 }
199 };
200 profiled_modules.push((name.to_string(), preload_module.clone()));
201
202 match &mut linker {
204 #[cfg(feature = "cranelift")]
205 CliLinker::Core(linker) => {
206 linker
207 .module_async(&mut store, name, &preload_module)
208 .await
209 .context(format!(
210 "failed to process preload `{}` at `{}`",
211 name,
212 path.display()
213 ))?;
214 }
215 #[cfg(not(feature = "cranelift"))]
216 CliLinker::Core(_) => {
217 bail!("support for --preload disabled at compile time");
218 }
219 #[cfg(feature = "component-model")]
220 CliLinker::Component(_) => {
221 bail!("--preload cannot be used with components");
222 }
223 }
224 }
225
226 self.load_main_module(&mut store, &mut linker, &main, profiled_modules)
227 .await
228 .with_context(|| {
229 format!(
230 "failed to run main module `{}`",
231 self.module_and_args[0].to_string_lossy()
232 )
233 })
234 })
235 .await
236 });
237
238 match result.unwrap_or_else(|elapsed| {
240 Err(anyhow::Error::from(wasmtime::Trap::Interrupt))
241 .with_context(|| format!("timed out after {elapsed}"))
242 }) {
243 Ok(()) => (),
244 Err(e) => {
245 if store.data().preview1_ctx.is_some() {
249 return Err(wasi_common::maybe_exit_on_error(e));
250 } else if store.data().preview2_ctx.is_some() {
251 if let Some(exit) = e.downcast_ref::<wasmtime_wasi::I32Exit>() {
252 std::process::exit(exit.0);
253 }
254 }
255 if e.is::<wasmtime::Trap>() {
256 eprintln!("Error: {e:?}");
257 cfg_if::cfg_if! {
258 if #[cfg(unix)] {
259 std::process::exit(rustix::process::EXIT_SIGNALED_SIGABRT);
260 } else if #[cfg(windows)] {
261 std::process::exit(3);
263 }
264 }
265 }
266 return Err(e);
267 }
268 }
269
270 Ok(())
271 }
272
273 fn compute_argv(&self) -> Result<Vec<String>> {
274 let mut result = Vec::new();
275
276 for (i, arg) in self.module_and_args.iter().enumerate() {
277 let arg = if i == 0 {
280 match &self.argv0 {
281 Some(s) => s.as_ref(),
282 None => Path::new(arg).components().next_back().unwrap().as_os_str(),
283 }
284 } else {
285 arg.as_ref()
286 };
287 result.push(
288 arg.to_str()
289 .ok_or_else(|| anyhow!("failed to convert {arg:?} to utf-8"))?
290 .to_string(),
291 );
292 }
293
294 Ok(result)
295 }
296
297 fn setup_epoch_handler(
298 &self,
299 store: &mut Store<Host>,
300 main_target: &RunTarget,
301 profiled_modules: Vec<(String, Module)>,
302 ) -> Result<Box<dyn FnOnce(&mut Store<Host>)>> {
303 if let Some(Profile::Guest { path, interval }) = &self.run.profile {
304 #[cfg(feature = "profiling")]
305 return Ok(self.setup_guest_profiler(
306 store,
307 main_target,
308 profiled_modules,
309 path,
310 *interval,
311 ));
312 #[cfg(not(feature = "profiling"))]
313 {
314 let _ = (profiled_modules, path, interval, main_target);
315 bail!("support for profiling disabled at compile time");
316 }
317 }
318
319 if let Some(timeout) = self.run.common.wasm.timeout {
320 store.set_epoch_deadline(1);
321 let engine = store.engine().clone();
322 thread::spawn(move || {
323 thread::sleep(timeout);
324 engine.increment_epoch();
325 });
326 }
327
328 Ok(Box::new(|_store| {}))
329 }
330
331 #[cfg(feature = "profiling")]
332 fn setup_guest_profiler(
333 &self,
334 store: &mut Store<Host>,
335 main_target: &RunTarget,
336 profiled_modules: Vec<(String, Module)>,
337 path: &str,
338 interval: std::time::Duration,
339 ) -> Box<dyn FnOnce(&mut Store<Host>)> {
340 use wasmtime::{AsContext, GuestProfiler, StoreContext, StoreContextMut, UpdateDeadline};
341
342 let module_name = self.module_and_args[0].to_str().unwrap_or("<main module>");
343 store.data_mut().guest_profiler = match main_target {
344 RunTarget::Core(_m) => Some(Arc::new(GuestProfiler::new(
345 module_name,
346 interval,
347 profiled_modules,
348 ))),
349 RunTarget::Component(component) => Some(Arc::new(GuestProfiler::new_component(
350 module_name,
351 interval,
352 component.clone(),
353 profiled_modules,
354 ))),
355 };
356
357 fn sample(
358 mut store: StoreContextMut<Host>,
359 f: impl FnOnce(&mut GuestProfiler, StoreContext<Host>),
360 ) {
361 let mut profiler = store.data_mut().guest_profiler.take().unwrap();
362 f(
363 Arc::get_mut(&mut profiler).expect("profiling doesn't support threads yet"),
364 store.as_context(),
365 );
366 store.data_mut().guest_profiler = Some(profiler);
367 }
368
369 store.call_hook(|store, kind| {
370 sample(store, |profiler, store| profiler.call_hook(store, kind));
371 Ok(())
372 });
373
374 if let Some(timeout) = self.run.common.wasm.timeout {
375 let mut timeout = (timeout.as_secs_f64() / interval.as_secs_f64()).ceil() as u64;
376 assert!(timeout > 0);
377 store.epoch_deadline_callback(move |store| {
378 sample(store, |profiler, store| {
379 profiler.sample(store, std::time::Duration::ZERO)
380 });
381 timeout -= 1;
382 if timeout == 0 {
383 bail!("timeout exceeded");
384 }
385 Ok(UpdateDeadline::Continue(1))
386 });
387 } else {
388 store.epoch_deadline_callback(move |store| {
389 sample(store, |profiler, store| {
390 profiler.sample(store, std::time::Duration::ZERO)
391 });
392 Ok(UpdateDeadline::Continue(1))
393 });
394 }
395
396 store.set_epoch_deadline(1);
397 let engine = store.engine().clone();
398 thread::spawn(move || loop {
399 thread::sleep(interval);
400 engine.increment_epoch();
401 });
402
403 let path = path.to_string();
404 return Box::new(move |store| {
405 let profiler = Arc::try_unwrap(store.data_mut().guest_profiler.take().unwrap())
406 .expect("profiling doesn't support threads yet");
407 if let Err(e) = std::fs::File::create(&path)
408 .map_err(anyhow::Error::new)
409 .and_then(|output| profiler.finish(std::io::BufWriter::new(output)))
410 {
411 eprintln!("failed writing profile at {path}: {e:#}");
412 } else {
413 eprintln!();
414 eprintln!("Profile written to: {path}");
415 eprintln!("View this profile at https://profiler.firefox.com/.");
416 }
417 });
418 }
419
420 async fn load_main_module(
421 &self,
422 store: &mut Store<Host>,
423 linker: &mut CliLinker,
424 main_target: &RunTarget,
425 profiled_modules: Vec<(String, Module)>,
426 ) -> Result<()> {
427 if self.run.common.wasm.unknown_imports_trap == Some(true) {
430 match linker {
431 CliLinker::Core(linker) => {
432 linker.define_unknown_imports_as_traps(main_target.unwrap_core())?;
433 }
434 #[cfg(feature = "component-model")]
435 CliLinker::Component(linker) => {
436 linker.define_unknown_imports_as_traps(main_target.unwrap_component())?;
437 }
438 }
439 }
440
441 if self.run.common.wasm.unknown_imports_default == Some(true) {
443 match linker {
444 CliLinker::Core(linker) => {
445 linker.define_unknown_imports_as_default_values(
446 store,
447 main_target.unwrap_core(),
448 )?;
449 }
450 _ => bail!("cannot use `--default-values-unknown-imports` with components"),
451 }
452 }
453
454 let finish_epoch_handler =
455 self.setup_epoch_handler(store, main_target, profiled_modules)?;
456
457 let result = match linker {
458 CliLinker::Core(linker) => {
459 let module = main_target.unwrap_core();
460 let instance = linker
461 .instantiate_async(&mut *store, &module)
462 .await
463 .context(format!(
464 "failed to instantiate {:?}",
465 self.module_and_args[0]
466 ))?;
467
468 if let Some(func) = instance.get_func(&mut *store, "_initialize") {
471 func.typed::<(), ()>(&store)?
472 .call_async(&mut *store, ())
473 .await?;
474 }
475
476 let func = if let Some(name) = &self.invoke {
479 Some(
480 instance
481 .get_func(&mut *store, name)
482 .ok_or_else(|| anyhow!("no func export named `{}` found", name))?,
483 )
484 } else {
485 instance
486 .get_func(&mut *store, "")
487 .or_else(|| instance.get_func(&mut *store, "_start"))
488 };
489
490 match func {
491 Some(func) => self.invoke_func(store, func).await,
492 None => Ok(()),
493 }
494 }
495 #[cfg(feature = "component-model")]
496 CliLinker::Component(linker) => {
497 if self.invoke.is_some() {
498 bail!("using `--invoke` with components is not supported");
499 }
500
501 let component = main_target.unwrap_component();
502
503 let command = wasmtime_wasi::bindings::Command::instantiate_async(
504 &mut *store,
505 component,
506 linker,
507 )
508 .await?;
509 let result = command
510 .wasi_cli_run()
511 .call_run(&mut *store)
512 .await
513 .context("failed to invoke `run` function")
514 .map_err(|e| self.handle_core_dump(&mut *store, e));
515
516 result.and_then(|wasm_result| match wasm_result {
519 Ok(()) => Ok(()),
520 Err(()) => Err(wasmtime_wasi::I32Exit(1).into()),
521 })
522 }
523 };
524 finish_epoch_handler(store);
525
526 result
527 }
528
529 async fn invoke_func(&self, store: &mut Store<Host>, func: Func) -> Result<()> {
530 let ty = func.ty(&store);
531 if ty.params().len() > 0 {
532 eprintln!(
533 "warning: using `--invoke` with a function that takes arguments \
534 is experimental and may break in the future"
535 );
536 }
537 let mut args = self.module_and_args.iter().skip(1);
538 let mut values = Vec::new();
539 for ty in ty.params() {
540 let val = match args.next() {
541 Some(s) => s,
542 None => {
543 if let Some(name) = &self.invoke {
544 bail!("not enough arguments for `{}`", name)
545 } else {
546 bail!("not enough arguments for command default")
547 }
548 }
549 };
550 let val = val
551 .to_str()
552 .ok_or_else(|| anyhow!("argument is not valid utf-8: {val:?}"))?;
553 values.push(match ty {
554 ValType::I32 => Val::I32(if val.starts_with("0x") || val.starts_with("0X") {
556 i32::from_str_radix(&val[2..], 16)?
557 } else {
558 val.parse::<i32>()?
559 }),
560 ValType::I64 => Val::I64(if val.starts_with("0x") || val.starts_with("0X") {
561 i64::from_str_radix(&val[2..], 16)?
562 } else {
563 val.parse::<i64>()?
564 }),
565 ValType::F32 => Val::F32(val.parse::<f32>()?.to_bits()),
566 ValType::F64 => Val::F64(val.parse::<f64>()?.to_bits()),
567 t => bail!("unsupported argument type {:?}", t),
568 });
569 }
570
571 let mut results = vec![Val::null_func_ref(); ty.results().len()];
574 let invoke_res = func
575 .call_async(&mut *store, &values, &mut results)
576 .await
577 .with_context(|| {
578 if let Some(name) = &self.invoke {
579 format!("failed to invoke `{name}`")
580 } else {
581 format!("failed to invoke command default")
582 }
583 });
584
585 if let Err(err) = invoke_res {
586 return Err(self.handle_core_dump(&mut *store, err));
587 }
588
589 if !results.is_empty() {
590 eprintln!(
591 "warning: using `--invoke` with a function that returns values \
592 is experimental and may break in the future"
593 );
594 }
595
596 for result in results {
597 match result {
598 Val::I32(i) => println!("{i}"),
599 Val::I64(i) => println!("{i}"),
600 Val::F32(f) => println!("{}", f32::from_bits(f)),
601 Val::F64(f) => println!("{}", f64::from_bits(f)),
602 Val::V128(i) => println!("{}", i.as_u128()),
603 Val::ExternRef(None) => println!("<null externref>"),
604 Val::ExternRef(Some(_)) => println!("<externref>"),
605 Val::FuncRef(None) => println!("<null funcref>"),
606 Val::FuncRef(Some(_)) => println!("<funcref>"),
607 Val::AnyRef(None) => println!("<null anyref>"),
608 Val::AnyRef(Some(_)) => println!("<anyref>"),
609 }
610 }
611
612 Ok(())
613 }
614
615 #[cfg(feature = "coredump")]
616 fn handle_core_dump(&self, store: &mut Store<Host>, err: Error) -> Error {
617 let coredump_path = match &self.run.common.debug.coredump {
618 Some(path) => path,
619 None => return err,
620 };
621 if !err.is::<wasmtime::Trap>() {
622 return err;
623 }
624 let source_name = self.module_and_args[0]
625 .to_str()
626 .unwrap_or_else(|| "unknown");
627
628 if let Err(coredump_err) = write_core_dump(store, &err, &source_name, coredump_path) {
629 eprintln!("warning: coredump failed to generate: {coredump_err}");
630 err
631 } else {
632 err.context(format!("core dumped at {coredump_path}"))
633 }
634 }
635
636 #[cfg(not(feature = "coredump"))]
637 fn handle_core_dump(&self, _store: &mut Store<Host>, err: Error) -> Error {
638 err
639 }
640
641 fn populate_with_wasi(
643 &self,
644 linker: &mut CliLinker,
645 store: &mut Store<Host>,
646 module: &RunTarget,
647 ) -> Result<()> {
648 let mut cli = self.run.common.wasi.cli;
649
650 if let Some(common) = self.run.common.wasi.common {
652 if cli.is_some() {
653 bail!(
654 "The -Scommon option should not be use with -Scli as it is a deprecated alias"
655 );
656 } else {
657 cli = Some(common);
660 }
661 }
662
663 if cli != Some(false) {
664 match linker {
665 CliLinker::Core(linker) => {
666 match (self.run.common.wasi.preview2, self.run.common.wasi.threads) {
667 (Some(false), _) | (None, Some(true)) => {
671 wasi_common::tokio::add_to_linker(linker, |host| {
672 host.preview1_ctx.as_mut().unwrap()
673 })?;
674 self.set_preview1_ctx(store)?;
675 }
676 (Some(true), _) | (None, Some(false) | None) => {
683 if self.run.common.wasi.preview0 != Some(false) {
684 wasmtime_wasi::preview0::add_to_linker_async(linker, |t| {
685 t.preview2_ctx()
686 })?;
687 }
688 wasmtime_wasi::preview1::add_to_linker_async(linker, |t| {
689 t.preview2_ctx()
690 })?;
691 self.set_preview2_ctx(store)?;
692 }
693 }
694 }
695 #[cfg(feature = "component-model")]
696 CliLinker::Component(linker) => {
697 let link_options = self.run.compute_wasi_features();
698 wasmtime_wasi::add_to_linker_with_options_async(linker, &link_options)?;
699 self.set_preview2_ctx(store)?;
700 }
701 }
702 }
703
704 if self.run.common.wasi.nn == Some(true) {
705 #[cfg(not(feature = "wasi-nn"))]
706 {
707 bail!("Cannot enable wasi-nn when the binary is not compiled with this feature.");
708 }
709 #[cfg(all(feature = "wasi-nn", feature = "component-model"))]
710 {
711 let (backends, registry) = self.collect_preloaded_nn_graphs()?;
712 match linker {
713 CliLinker::Core(linker) => {
714 wasmtime_wasi_nn::witx::add_to_linker(linker, |host| {
715 Arc::get_mut(host.wasi_nn_witx.as_mut().unwrap())
716 .expect("wasi-nn is not implemented with multi-threading support")
717 })?;
718 store.data_mut().wasi_nn_witx = Some(Arc::new(
719 wasmtime_wasi_nn::witx::WasiNnCtx::new(backends, registry),
720 ));
721 }
722 #[cfg(feature = "component-model")]
723 CliLinker::Component(linker) => {
724 wasmtime_wasi_nn::wit::add_to_linker(linker, |h: &mut Host| {
725 let preview2_ctx =
726 h.preview2_ctx.as_mut().expect("wasip2 is not configured");
727 let preview2_ctx = Arc::get_mut(preview2_ctx)
728 .expect("wasmtime_wasi is not compatible with threads")
729 .get_mut()
730 .unwrap();
731 let nn_ctx = Arc::get_mut(h.wasi_nn_wit.as_mut().unwrap())
732 .expect("wasi-nn is not implemented with multi-threading support");
733 WasiNnView::new(preview2_ctx.table(), nn_ctx)
734 })?;
735 store.data_mut().wasi_nn_wit = Some(Arc::new(
736 wasmtime_wasi_nn::wit::WasiNnCtx::new(backends, registry),
737 ));
738 }
739 }
740 }
741 }
742
743 if self.run.common.wasi.config == Some(true) {
744 #[cfg(not(feature = "wasi-config"))]
745 {
746 bail!(
747 "Cannot enable wasi-config when the binary is not compiled with this feature."
748 );
749 }
750 #[cfg(all(feature = "wasi-config", feature = "component-model"))]
751 {
752 match linker {
753 CliLinker::Core(_) => {
754 bail!("Cannot enable wasi-config for core wasm modules");
755 }
756 CliLinker::Component(linker) => {
757 let vars = WasiConfigVariables::from_iter(
758 self.run
759 .common
760 .wasi
761 .config_var
762 .iter()
763 .map(|v| (v.key.clone(), v.value.clone())),
764 );
765
766 wasmtime_wasi_config::add_to_linker(linker, |h| {
767 WasiConfig::new(Arc::get_mut(h.wasi_config.as_mut().unwrap()).unwrap())
768 })?;
769 store.data_mut().wasi_config = Some(Arc::new(vars));
770 }
771 }
772 }
773 }
774
775 if self.run.common.wasi.keyvalue == Some(true) {
776 #[cfg(not(feature = "wasi-keyvalue"))]
777 {
778 bail!("Cannot enable wasi-keyvalue when the binary is not compiled with this feature.");
779 }
780 #[cfg(all(feature = "wasi-keyvalue", feature = "component-model"))]
781 {
782 match linker {
783 CliLinker::Core(_) => {
784 bail!("Cannot enable wasi-keyvalue for core wasm modules");
785 }
786 CliLinker::Component(linker) => {
787 let ctx = WasiKeyValueCtxBuilder::new()
788 .in_memory_data(
789 self.run
790 .common
791 .wasi
792 .keyvalue_in_memory_data
793 .iter()
794 .map(|v| (v.key.clone(), v.value.clone())),
795 )
796 .build();
797
798 wasmtime_wasi_keyvalue::add_to_linker(linker, |h| {
799 let preview2_ctx =
800 h.preview2_ctx.as_mut().expect("wasip2 is not configured");
801 let preview2_ctx =
802 Arc::get_mut(preview2_ctx).unwrap().get_mut().unwrap();
803 WasiKeyValue::new(
804 Arc::get_mut(h.wasi_keyvalue.as_mut().unwrap()).unwrap(),
805 preview2_ctx.table(),
806 )
807 })?;
808 store.data_mut().wasi_keyvalue = Some(Arc::new(ctx));
809 }
810 }
811 }
812 }
813
814 if self.run.common.wasi.threads == Some(true) {
815 #[cfg(not(feature = "wasi-threads"))]
816 {
817 let _ = &module;
820
821 bail!(
822 "Cannot enable wasi-threads when the binary is not compiled with this feature."
823 );
824 }
825 #[cfg(feature = "wasi-threads")]
826 {
827 let linker = match linker {
828 CliLinker::Core(linker) => linker,
829 _ => bail!("wasi-threads does not support components yet"),
830 };
831 let module = module.unwrap_core();
832 wasmtime_wasi_threads::add_to_linker(linker, store, &module, |host| {
833 host.wasi_threads.as_ref().unwrap()
834 })?;
835 store.data_mut().wasi_threads = Some(Arc::new(WasiThreadsCtx::new(
836 module.clone(),
837 Arc::new(linker.clone()),
838 )?));
839 }
840 }
841
842 if self.run.common.wasi.http == Some(true) {
843 #[cfg(not(all(feature = "wasi-http", feature = "component-model")))]
844 {
845 bail!("Cannot enable wasi-http when the binary is not compiled with this feature.");
846 }
847 #[cfg(all(feature = "wasi-http", feature = "component-model"))]
848 {
849 match linker {
850 CliLinker::Core(_) => {
851 bail!("Cannot enable wasi-http for core wasm modules");
852 }
853 CliLinker::Component(linker) => {
854 wasmtime_wasi_http::add_only_http_to_linker_sync(linker)?;
855 }
856 }
857
858 store.data_mut().wasi_http = Some(Arc::new(WasiHttpCtx::new()));
859 }
860 }
861
862 if self.run.common.wasi.tls == Some(true) {
863 #[cfg(all(not(all(feature = "wasi-tls", feature = "component-model"))))]
864 {
865 bail!("Cannot enable wasi-tls when the binary is not compiled with this feature.");
866 }
867 #[cfg(all(feature = "wasi-tls", feature = "component-model",))]
868 {
869 match linker {
870 CliLinker::Core(_) => {
871 bail!("Cannot enable wasi-tls for core wasm modules");
872 }
873 CliLinker::Component(linker) => {
874 let mut opts = wasmtime_wasi_tls::LinkOptions::default();
875 opts.tls(true);
876 wasmtime_wasi_tls::add_to_linker(linker, &mut opts, |h| {
877 let preview2_ctx =
878 h.preview2_ctx.as_mut().expect("wasip2 is not configured");
879 let preview2_ctx =
880 Arc::get_mut(preview2_ctx).unwrap().get_mut().unwrap();
881 WasiTlsCtx::new(preview2_ctx.table())
882 })?;
883 }
884 }
885 }
886 }
887
888 Ok(())
889 }
890
891 fn set_preview1_ctx(&self, store: &mut Store<Host>) -> Result<()> {
892 let mut builder = WasiCtxBuilder::new();
893 builder.inherit_stdio().args(&self.compute_argv()?)?;
894
895 if self.run.common.wasi.inherit_env == Some(true) {
896 for (k, v) in std::env::vars() {
897 builder.env(&k, &v)?;
898 }
899 }
900 for (key, value) in self.run.vars.iter() {
901 let value = match value {
902 Some(value) => value.clone(),
903 None => match std::env::var_os(key) {
904 Some(val) => val
905 .into_string()
906 .map_err(|_| anyhow!("environment variable `{key}` not valid utf-8"))?,
907 None => {
908 continue;
910 }
911 },
912 };
913 builder.env(key, &value)?;
914 }
915
916 let mut num_fd: usize = 3;
917
918 if self.run.common.wasi.listenfd == Some(true) {
919 num_fd = ctx_set_listenfd(num_fd, &mut builder)?;
920 }
921
922 for listener in self.run.compute_preopen_sockets()? {
923 let listener = TcpListener::from_std(listener);
924 builder.preopened_socket(num_fd as _, listener)?;
925 num_fd += 1;
926 }
927
928 for (host, guest) in self.run.dirs.iter() {
929 let dir = Dir::open_ambient_dir(host, ambient_authority())
930 .with_context(|| format!("failed to open directory '{host}'"))?;
931 builder.preopened_dir(dir, guest)?;
932 }
933
934 store.data_mut().preview1_ctx = Some(builder.build());
935 Ok(())
936 }
937
938 fn set_preview2_ctx(&self, store: &mut Store<Host>) -> Result<()> {
939 let mut builder = wasmtime_wasi::WasiCtxBuilder::new();
940 builder.inherit_stdio().args(&self.compute_argv()?);
941 self.run.configure_wasip2(&mut builder)?;
942 let ctx = builder.build_p1();
943 store.data_mut().preview2_ctx = Some(Arc::new(Mutex::new(ctx)));
944 Ok(())
945 }
946
947 #[cfg(feature = "wasi-nn")]
948 fn collect_preloaded_nn_graphs(
949 &self,
950 ) -> Result<(Vec<wasmtime_wasi_nn::Backend>, wasmtime_wasi_nn::Registry)> {
951 let graphs = self
952 .run
953 .common
954 .wasi
955 .nn_graph
956 .iter()
957 .map(|g| (g.format.clone(), g.dir.clone()))
958 .collect::<Vec<_>>();
959 wasmtime_wasi_nn::preload(&graphs)
960 }
961}
962
963#[derive(Default, Clone)]
964struct Host {
965 preview1_ctx: Option<wasi_common::WasiCtx>,
966
967 preview2_ctx: Option<Arc<Mutex<wasmtime_wasi::preview1::WasiP1Ctx>>>,
971
972 #[cfg(feature = "wasi-nn")]
973 wasi_nn_wit: Option<Arc<wasmtime_wasi_nn::wit::WasiNnCtx>>,
974 #[cfg(feature = "wasi-nn")]
975 wasi_nn_witx: Option<Arc<wasmtime_wasi_nn::witx::WasiNnCtx>>,
976
977 #[cfg(feature = "wasi-threads")]
978 wasi_threads: Option<Arc<WasiThreadsCtx<Host>>>,
979 #[cfg(feature = "wasi-http")]
980 wasi_http: Option<Arc<WasiHttpCtx>>,
981 #[cfg(feature = "wasi-http")]
982 wasi_http_outgoing_body_buffer_chunks: Option<usize>,
983 #[cfg(feature = "wasi-http")]
984 wasi_http_outgoing_body_chunk_size: Option<usize>,
985 limits: StoreLimits,
986 #[cfg(feature = "profiling")]
987 guest_profiler: Option<Arc<wasmtime::GuestProfiler>>,
988
989 #[cfg(feature = "wasi-config")]
990 wasi_config: Option<Arc<WasiConfigVariables>>,
991 #[cfg(feature = "wasi-keyvalue")]
992 wasi_keyvalue: Option<Arc<WasiKeyValueCtx>>,
993}
994
995impl Host {
996 fn preview2_ctx(&mut self) -> &mut wasmtime_wasi::preview1::WasiP1Ctx {
997 let ctx = self
998 .preview2_ctx
999 .as_mut()
1000 .expect("wasip2 is not configured");
1001 Arc::get_mut(ctx)
1002 .expect("wasmtime_wasi is not compatible with threads")
1003 .get_mut()
1004 .unwrap()
1005 }
1006}
1007
1008impl IoView for Host {
1009 fn table(&mut self) -> &mut wasmtime::component::ResourceTable {
1010 self.preview2_ctx().table()
1011 }
1012}
1013impl WasiView for Host {
1014 fn ctx(&mut self) -> &mut wasmtime_wasi::WasiCtx {
1015 self.preview2_ctx().ctx()
1016 }
1017}
1018
1019#[cfg(feature = "wasi-http")]
1020impl wasmtime_wasi_http::types::WasiHttpView for Host {
1021 fn ctx(&mut self) -> &mut WasiHttpCtx {
1022 let ctx = self.wasi_http.as_mut().unwrap();
1023 Arc::get_mut(ctx).expect("wasmtime_wasi is not compatible with threads")
1024 }
1025
1026 fn outgoing_body_buffer_chunks(&mut self) -> usize {
1027 self.wasi_http_outgoing_body_buffer_chunks
1028 .unwrap_or_else(|| DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS)
1029 }
1030
1031 fn outgoing_body_chunk_size(&mut self) -> usize {
1032 self.wasi_http_outgoing_body_chunk_size
1033 .unwrap_or_else(|| DEFAULT_OUTGOING_BODY_CHUNK_SIZE)
1034 }
1035}
1036
1037#[cfg(not(unix))]
1038fn ctx_set_listenfd(num_fd: usize, _builder: &mut WasiCtxBuilder) -> Result<usize> {
1039 Ok(num_fd)
1040}
1041
1042#[cfg(unix)]
1043fn ctx_set_listenfd(mut num_fd: usize, builder: &mut WasiCtxBuilder) -> Result<usize> {
1044 use listenfd::ListenFd;
1045
1046 for env in ["LISTEN_FDS", "LISTEN_FDNAMES"] {
1047 if let Ok(val) = std::env::var(env) {
1048 builder.env(env, &val)?;
1049 }
1050 }
1051
1052 let mut listenfd = ListenFd::from_env();
1053
1054 for i in 0..listenfd.len() {
1055 if let Some(stdlistener) = listenfd.take_tcp_listener(i)? {
1056 let _ = stdlistener.set_nonblocking(true)?;
1057 let listener = TcpListener::from_std(stdlistener);
1058 builder.preopened_socket((3 + i) as _, listener)?;
1059 num_fd = 3 + i;
1060 }
1061 }
1062
1063 Ok(num_fd)
1064}
1065
1066#[cfg(feature = "coredump")]
1067fn write_core_dump(
1068 store: &mut Store<Host>,
1069 err: &anyhow::Error,
1070 name: &str,
1071 path: &str,
1072) -> Result<()> {
1073 use std::fs::File;
1074 use std::io::Write;
1075
1076 let core_dump = err
1077 .downcast_ref::<wasmtime::WasmCoreDump>()
1078 .expect("should have been configured to capture core dumps");
1079
1080 let core_dump = core_dump.serialize(store, name);
1081
1082 let mut core_dump_file =
1083 File::create(path).context(format!("failed to create file at `{path}`"))?;
1084 core_dump_file
1085 .write_all(&core_dump)
1086 .with_context(|| format!("failed to write core dump file at `{path}`"))?;
1087 Ok(())
1088}