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