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::{Context as _, Error, Result, anyhow, bail};
11use clap::Parser;
12use std::ffi::OsString;
13use std::path::{Path, PathBuf};
14use std::sync::{Arc, Mutex};
15use std::thread;
16use wasi_common::sync::{Dir, TcpListener, WasiCtxBuilder, ambient_authority};
17use wasmtime::{Engine, Func, Module, Store, StoreLimits, Val, ValType};
18use wasmtime_wasi::p2::{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 DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS, DEFAULT_OUTGOING_BODY_CHUNK_SIZE, WasiHttpCtx,
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 || {
399 loop {
400 thread::sleep(interval);
401 engine.increment_epoch();
402 }
403 });
404
405 let path = path.to_string();
406 return Box::new(move |store| {
407 let profiler = Arc::try_unwrap(store.data_mut().guest_profiler.take().unwrap())
408 .expect("profiling doesn't support threads yet");
409 if let Err(e) = std::fs::File::create(&path)
410 .map_err(anyhow::Error::new)
411 .and_then(|output| profiler.finish(std::io::BufWriter::new(output)))
412 {
413 eprintln!("failed writing profile at {path}: {e:#}");
414 } else {
415 eprintln!();
416 eprintln!("Profile written to: {path}");
417 eprintln!("View this profile at https://profiler.firefox.com/.");
418 }
419 });
420 }
421
422 async fn load_main_module(
423 &self,
424 store: &mut Store<Host>,
425 linker: &mut CliLinker,
426 main_target: &RunTarget,
427 profiled_modules: Vec<(String, Module)>,
428 ) -> Result<()> {
429 if self.run.common.wasm.unknown_imports_trap == Some(true) {
432 match linker {
433 CliLinker::Core(linker) => {
434 linker.define_unknown_imports_as_traps(main_target.unwrap_core())?;
435 }
436 #[cfg(feature = "component-model")]
437 CliLinker::Component(linker) => {
438 linker.define_unknown_imports_as_traps(main_target.unwrap_component())?;
439 }
440 }
441 }
442
443 if self.run.common.wasm.unknown_imports_default == Some(true) {
445 match linker {
446 CliLinker::Core(linker) => {
447 linker.define_unknown_imports_as_default_values(
448 store,
449 main_target.unwrap_core(),
450 )?;
451 }
452 _ => bail!("cannot use `--default-values-unknown-imports` with components"),
453 }
454 }
455
456 let finish_epoch_handler =
457 self.setup_epoch_handler(store, main_target, profiled_modules)?;
458
459 let result = match linker {
460 CliLinker::Core(linker) => {
461 let module = main_target.unwrap_core();
462 let instance = linker
463 .instantiate_async(&mut *store, &module)
464 .await
465 .context(format!(
466 "failed to instantiate {:?}",
467 self.module_and_args[0]
468 ))?;
469
470 if let Some(func) = instance.get_func(&mut *store, "_initialize") {
473 func.typed::<(), ()>(&store)?
474 .call_async(&mut *store, ())
475 .await?;
476 }
477
478 let func = if let Some(name) = &self.invoke {
481 Some(
482 instance
483 .get_func(&mut *store, name)
484 .ok_or_else(|| anyhow!("no func export named `{}` found", name))?,
485 )
486 } else {
487 instance
488 .get_func(&mut *store, "")
489 .or_else(|| instance.get_func(&mut *store, "_start"))
490 };
491
492 match func {
493 Some(func) => self.invoke_func(store, func).await,
494 None => Ok(()),
495 }
496 }
497 #[cfg(feature = "component-model")]
498 CliLinker::Component(linker) => {
499 let component = main_target.unwrap_component();
500 if self.invoke.is_some() {
501 self.invoke_component(&mut *store, component, linker).await
502 } else {
503 let command = wasmtime_wasi::p2::bindings::Command::instantiate_async(
504 &mut *store,
505 component,
506 linker,
507 )
508 .await?;
509
510 let result = command
511 .wasi_cli_run()
512 .call_run(&mut *store)
513 .await
514 .context("failed to invoke `run` function")
515 .map_err(|e| self.handle_core_dump(&mut *store, e));
516
517 result.and_then(|wasm_result| match wasm_result {
520 Ok(()) => Ok(()),
521 Err(()) => Err(wasmtime_wasi::I32Exit(1).into()),
522 })
523 }
524 }
525 };
526 finish_epoch_handler(store);
527
528 result
529 }
530
531 #[cfg(feature = "component-model")]
532 async fn invoke_component(
533 &self,
534 store: &mut Store<Host>,
535 component: &wasmtime::component::Component,
536 linker: &mut wasmtime::component::Linker<Host>,
537 ) -> Result<()> {
538 use wasmtime::component::{
539 Val,
540 types::ComponentItem,
541 wasm_wave::{
542 untyped::UntypedFuncCall,
543 wasm::{DisplayFuncResults, WasmFunc},
544 },
545 };
546
547 let invoke: &String = self.invoke.as_ref().unwrap();
549
550 let untyped_call = UntypedFuncCall::parse(invoke).with_context(|| {
551 format!(
552 "Failed to parse invoke '{invoke}': See https://docs.wasmtime.dev/cli-options.html#run for syntax",
553 )
554 })?;
555
556 let name = untyped_call.name();
557 let matches = Self::search_component(store.engine(), component.component_type(), name);
558 match matches.len() {
559 0 => bail!("No export named `{name}` in component."),
560 1 => {}
561 _ => bail!(
562 "Multiple exports named `{name}`: {matches:?}. FIXME: support some way to disambiguate names"
563 ),
564 };
565 let (params, result_len, export) = match &matches[0] {
566 (names, ComponentItem::ComponentFunc(func)) => {
567 let param_types = WasmFunc::params(func).collect::<Vec<_>>();
568 let params = untyped_call.to_wasm_params(¶m_types).with_context(|| {
569 format!("while interpreting parameters in invoke \"{invoke}\"")
570 })?;
571 let mut export = None;
572 for name in names {
573 let ix = component
574 .get_export_index(export.as_ref(), name)
575 .expect("export exists");
576 export = Some(ix);
577 }
578 (
579 params,
580 func.results().len(),
581 export.expect("export has at least one name"),
582 )
583 }
584 (names, ty) => {
585 bail!("Cannot invoke export {names:?}: expected ComponentFunc, got type {ty:?}");
586 }
587 };
588
589 let instance = linker.instantiate_async(&mut *store, component).await?;
590
591 let func = instance
592 .get_func(&mut *store, export)
593 .expect("found export index");
594
595 let mut results = vec![Val::Bool(false); result_len];
596 func.call_async(&mut *store, ¶ms, &mut results).await?;
597
598 println!("{}", DisplayFuncResults(&results));
599
600 Ok(())
601 }
602
603 #[cfg(feature = "component-model")]
604 fn search_component(
605 engine: &Engine,
606 component: wasmtime::component::types::Component,
607 name: &str,
608 ) -> Vec<(Vec<String>, wasmtime::component::types::ComponentItem)> {
609 use wasmtime::component::types::ComponentItem as CItem;
610 fn collect_exports(
611 engine: &Engine,
612 item: CItem,
613 basename: Vec<String>,
614 ) -> Vec<(Vec<String>, CItem)> {
615 match item {
616 CItem::Component(c) => c
617 .exports(engine)
618 .flat_map(move |(name, item)| {
619 let mut names = basename.clone();
620 names.push(name.to_string());
621 collect_exports(engine, item, names)
622 })
623 .collect::<Vec<_>>(),
624 CItem::ComponentInstance(c) => c
625 .exports(engine)
626 .flat_map(move |(name, item)| {
627 let mut names = basename.clone();
628 names.push(name.to_string());
629 collect_exports(engine, item, names)
630 })
631 .collect::<Vec<_>>(),
632 _ => vec![(basename, item)],
633 }
634 }
635
636 collect_exports(engine, CItem::Component(component), Vec::new())
637 .into_iter()
638 .filter(|(names, _item)| names.last().expect("at least one name") == name)
639 .collect()
640 }
641
642 async fn invoke_func(&self, store: &mut Store<Host>, func: Func) -> Result<()> {
643 let ty = func.ty(&store);
644 if ty.params().len() > 0 {
645 eprintln!(
646 "warning: using `--invoke` with a function that takes arguments \
647 is experimental and may break in the future"
648 );
649 }
650 let mut args = self.module_and_args.iter().skip(1);
651 let mut values = Vec::new();
652 for ty in ty.params() {
653 let val = match args.next() {
654 Some(s) => s,
655 None => {
656 if let Some(name) = &self.invoke {
657 bail!("not enough arguments for `{}`", name)
658 } else {
659 bail!("not enough arguments for command default")
660 }
661 }
662 };
663 let val = val
664 .to_str()
665 .ok_or_else(|| anyhow!("argument is not valid utf-8: {val:?}"))?;
666 values.push(match ty {
667 ValType::I32 => Val::I32(if val.starts_with("0x") || val.starts_with("0X") {
669 i32::from_str_radix(&val[2..], 16)?
670 } else {
671 val.parse::<i32>()?
672 }),
673 ValType::I64 => Val::I64(if val.starts_with("0x") || val.starts_with("0X") {
674 i64::from_str_radix(&val[2..], 16)?
675 } else {
676 val.parse::<i64>()?
677 }),
678 ValType::F32 => Val::F32(val.parse::<f32>()?.to_bits()),
679 ValType::F64 => Val::F64(val.parse::<f64>()?.to_bits()),
680 t => bail!("unsupported argument type {:?}", t),
681 });
682 }
683
684 let mut results = vec![Val::null_func_ref(); ty.results().len()];
687 let invoke_res = func
688 .call_async(&mut *store, &values, &mut results)
689 .await
690 .with_context(|| {
691 if let Some(name) = &self.invoke {
692 format!("failed to invoke `{name}`")
693 } else {
694 format!("failed to invoke command default")
695 }
696 });
697
698 if let Err(err) = invoke_res {
699 return Err(self.handle_core_dump(&mut *store, err));
700 }
701
702 if !results.is_empty() {
703 eprintln!(
704 "warning: using `--invoke` with a function that returns values \
705 is experimental and may break in the future"
706 );
707 }
708
709 for result in results {
710 match result {
711 Val::I32(i) => println!("{i}"),
712 Val::I64(i) => println!("{i}"),
713 Val::F32(f) => println!("{}", f32::from_bits(f)),
714 Val::F64(f) => println!("{}", f64::from_bits(f)),
715 Val::V128(i) => println!("{}", i.as_u128()),
716 Val::ExternRef(None) => println!("<null externref>"),
717 Val::ExternRef(Some(_)) => println!("<externref>"),
718 Val::FuncRef(None) => println!("<null funcref>"),
719 Val::FuncRef(Some(_)) => println!("<funcref>"),
720 Val::AnyRef(None) => println!("<null anyref>"),
721 Val::AnyRef(Some(_)) => println!("<anyref>"),
722 }
723 }
724
725 Ok(())
726 }
727
728 #[cfg(feature = "coredump")]
729 fn handle_core_dump(&self, store: &mut Store<Host>, err: Error) -> Error {
730 let coredump_path = match &self.run.common.debug.coredump {
731 Some(path) => path,
732 None => return err,
733 };
734 if !err.is::<wasmtime::Trap>() {
735 return err;
736 }
737 let source_name = self.module_and_args[0]
738 .to_str()
739 .unwrap_or_else(|| "unknown");
740
741 if let Err(coredump_err) = write_core_dump(store, &err, &source_name, coredump_path) {
742 eprintln!("warning: coredump failed to generate: {coredump_err}");
743 err
744 } else {
745 err.context(format!("core dumped at {coredump_path}"))
746 }
747 }
748
749 #[cfg(not(feature = "coredump"))]
750 fn handle_core_dump(&self, _store: &mut Store<Host>, err: Error) -> Error {
751 err
752 }
753
754 fn populate_with_wasi(
756 &self,
757 linker: &mut CliLinker,
758 store: &mut Store<Host>,
759 module: &RunTarget,
760 ) -> Result<()> {
761 let mut cli = self.run.common.wasi.cli;
762
763 if let Some(common) = self.run.common.wasi.common {
765 if cli.is_some() {
766 bail!(
767 "The -Scommon option should not be use with -Scli as it is a deprecated alias"
768 );
769 } else {
770 cli = Some(common);
773 }
774 }
775
776 if cli != Some(false) {
777 match linker {
778 CliLinker::Core(linker) => {
779 match (self.run.common.wasi.preview2, self.run.common.wasi.threads) {
780 (Some(false), _) | (None, Some(true)) => {
784 wasi_common::tokio::add_to_linker(linker, |host| {
785 host.preview1_ctx.as_mut().unwrap()
786 })?;
787 self.set_preview1_ctx(store)?;
788 }
789 (Some(true), _) | (None, Some(false) | None) => {
796 if self.run.common.wasi.preview0 != Some(false) {
797 wasmtime_wasi::preview0::add_to_linker_async(linker, |t| {
798 t.preview2_ctx()
799 })?;
800 }
801 wasmtime_wasi::preview1::add_to_linker_async(linker, |t| {
802 t.preview2_ctx()
803 })?;
804 self.set_preview2_ctx(store)?;
805 }
806 }
807 }
808 #[cfg(feature = "component-model")]
809 CliLinker::Component(linker) => {
810 let link_options = self.run.compute_wasi_features();
811 wasmtime_wasi::p2::add_to_linker_with_options_async(linker, &link_options)?;
812 self.set_preview2_ctx(store)?;
813 }
814 }
815 }
816
817 if self.run.common.wasi.nn == Some(true) {
818 #[cfg(not(feature = "wasi-nn"))]
819 {
820 bail!("Cannot enable wasi-nn when the binary is not compiled with this feature.");
821 }
822 #[cfg(all(feature = "wasi-nn", feature = "component-model"))]
823 {
824 let (backends, registry) = self.collect_preloaded_nn_graphs()?;
825 match linker {
826 CliLinker::Core(linker) => {
827 wasmtime_wasi_nn::witx::add_to_linker(linker, |host| {
828 Arc::get_mut(host.wasi_nn_witx.as_mut().unwrap())
829 .expect("wasi-nn is not implemented with multi-threading support")
830 })?;
831 store.data_mut().wasi_nn_witx = Some(Arc::new(
832 wasmtime_wasi_nn::witx::WasiNnCtx::new(backends, registry),
833 ));
834 }
835 #[cfg(feature = "component-model")]
836 CliLinker::Component(linker) => {
837 wasmtime_wasi_nn::wit::add_to_linker(linker, |h: &mut Host| {
838 let preview2_ctx =
839 h.preview2_ctx.as_mut().expect("wasip2 is not configured");
840 let preview2_ctx = Arc::get_mut(preview2_ctx)
841 .expect("wasmtime_wasi is not compatible with threads")
842 .get_mut()
843 .unwrap();
844 let nn_ctx = Arc::get_mut(h.wasi_nn_wit.as_mut().unwrap())
845 .expect("wasi-nn is not implemented with multi-threading support");
846 WasiNnView::new(preview2_ctx.table(), nn_ctx)
847 })?;
848 store.data_mut().wasi_nn_wit = Some(Arc::new(
849 wasmtime_wasi_nn::wit::WasiNnCtx::new(backends, registry),
850 ));
851 }
852 }
853 }
854 }
855
856 if self.run.common.wasi.config == Some(true) {
857 #[cfg(not(feature = "wasi-config"))]
858 {
859 bail!(
860 "Cannot enable wasi-config when the binary is not compiled with this feature."
861 );
862 }
863 #[cfg(all(feature = "wasi-config", feature = "component-model"))]
864 {
865 match linker {
866 CliLinker::Core(_) => {
867 bail!("Cannot enable wasi-config for core wasm modules");
868 }
869 CliLinker::Component(linker) => {
870 let vars = WasiConfigVariables::from_iter(
871 self.run
872 .common
873 .wasi
874 .config_var
875 .iter()
876 .map(|v| (v.key.clone(), v.value.clone())),
877 );
878
879 wasmtime_wasi_config::add_to_linker(linker, |h| {
880 WasiConfig::new(Arc::get_mut(h.wasi_config.as_mut().unwrap()).unwrap())
881 })?;
882 store.data_mut().wasi_config = Some(Arc::new(vars));
883 }
884 }
885 }
886 }
887
888 if self.run.common.wasi.keyvalue == Some(true) {
889 #[cfg(not(feature = "wasi-keyvalue"))]
890 {
891 bail!(
892 "Cannot enable wasi-keyvalue when the binary is not compiled with this feature."
893 );
894 }
895 #[cfg(all(feature = "wasi-keyvalue", feature = "component-model"))]
896 {
897 match linker {
898 CliLinker::Core(_) => {
899 bail!("Cannot enable wasi-keyvalue for core wasm modules");
900 }
901 CliLinker::Component(linker) => {
902 let ctx = WasiKeyValueCtxBuilder::new()
903 .in_memory_data(
904 self.run
905 .common
906 .wasi
907 .keyvalue_in_memory_data
908 .iter()
909 .map(|v| (v.key.clone(), v.value.clone())),
910 )
911 .build();
912
913 wasmtime_wasi_keyvalue::add_to_linker(linker, |h| {
914 let preview2_ctx =
915 h.preview2_ctx.as_mut().expect("wasip2 is not configured");
916 let preview2_ctx =
917 Arc::get_mut(preview2_ctx).unwrap().get_mut().unwrap();
918 WasiKeyValue::new(
919 Arc::get_mut(h.wasi_keyvalue.as_mut().unwrap()).unwrap(),
920 preview2_ctx.table(),
921 )
922 })?;
923 store.data_mut().wasi_keyvalue = Some(Arc::new(ctx));
924 }
925 }
926 }
927 }
928
929 if self.run.common.wasi.threads == Some(true) {
930 #[cfg(not(feature = "wasi-threads"))]
931 {
932 let _ = &module;
935
936 bail!(
937 "Cannot enable wasi-threads when the binary is not compiled with this feature."
938 );
939 }
940 #[cfg(feature = "wasi-threads")]
941 {
942 let linker = match linker {
943 CliLinker::Core(linker) => linker,
944 _ => bail!("wasi-threads does not support components yet"),
945 };
946 let module = module.unwrap_core();
947 wasmtime_wasi_threads::add_to_linker(linker, store, &module, |host| {
948 host.wasi_threads.as_ref().unwrap()
949 })?;
950 store.data_mut().wasi_threads = Some(Arc::new(WasiThreadsCtx::new(
951 module.clone(),
952 Arc::new(linker.clone()),
953 )?));
954 }
955 }
956
957 if self.run.common.wasi.http == Some(true) {
958 #[cfg(not(all(feature = "wasi-http", feature = "component-model")))]
959 {
960 bail!("Cannot enable wasi-http when the binary is not compiled with this feature.");
961 }
962 #[cfg(all(feature = "wasi-http", feature = "component-model"))]
963 {
964 match linker {
965 CliLinker::Core(_) => {
966 bail!("Cannot enable wasi-http for core wasm modules");
967 }
968 CliLinker::Component(linker) => {
969 wasmtime_wasi_http::add_only_http_to_linker_sync(linker)?;
970 }
971 }
972
973 store.data_mut().wasi_http = Some(Arc::new(WasiHttpCtx::new()));
974 }
975 }
976
977 if self.run.common.wasi.tls == Some(true) {
978 #[cfg(all(not(all(feature = "wasi-tls", feature = "component-model"))))]
979 {
980 bail!("Cannot enable wasi-tls when the binary is not compiled with this feature.");
981 }
982 #[cfg(all(feature = "wasi-tls", feature = "component-model",))]
983 {
984 match linker {
985 CliLinker::Core(_) => {
986 bail!("Cannot enable wasi-tls for core wasm modules");
987 }
988 CliLinker::Component(linker) => {
989 let mut opts = wasmtime_wasi_tls::LinkOptions::default();
990 opts.tls(true);
991 wasmtime_wasi_tls::add_to_linker(linker, &mut opts, |h| {
992 let preview2_ctx =
993 h.preview2_ctx.as_mut().expect("wasip2 is not configured");
994 let preview2_ctx =
995 Arc::get_mut(preview2_ctx).unwrap().get_mut().unwrap();
996 WasiTlsCtx::new(preview2_ctx.table())
997 })?;
998 }
999 }
1000 }
1001 }
1002
1003 Ok(())
1004 }
1005
1006 fn set_preview1_ctx(&self, store: &mut Store<Host>) -> Result<()> {
1007 let mut builder = WasiCtxBuilder::new();
1008 builder.inherit_stdio().args(&self.compute_argv()?)?;
1009
1010 if self.run.common.wasi.inherit_env == Some(true) {
1011 for (k, v) in std::env::vars() {
1012 builder.env(&k, &v)?;
1013 }
1014 }
1015 for (key, value) in self.run.vars.iter() {
1016 let value = match value {
1017 Some(value) => value.clone(),
1018 None => match std::env::var_os(key) {
1019 Some(val) => val
1020 .into_string()
1021 .map_err(|_| anyhow!("environment variable `{key}` not valid utf-8"))?,
1022 None => {
1023 continue;
1025 }
1026 },
1027 };
1028 builder.env(key, &value)?;
1029 }
1030
1031 let mut num_fd: usize = 3;
1032
1033 if self.run.common.wasi.listenfd == Some(true) {
1034 num_fd = ctx_set_listenfd(num_fd, &mut builder)?;
1035 }
1036
1037 for listener in self.run.compute_preopen_sockets()? {
1038 let listener = TcpListener::from_std(listener);
1039 builder.preopened_socket(num_fd as _, listener)?;
1040 num_fd += 1;
1041 }
1042
1043 for (host, guest) in self.run.dirs.iter() {
1044 let dir = Dir::open_ambient_dir(host, ambient_authority())
1045 .with_context(|| format!("failed to open directory '{host}'"))?;
1046 builder.preopened_dir(dir, guest)?;
1047 }
1048
1049 store.data_mut().preview1_ctx = Some(builder.build());
1050 Ok(())
1051 }
1052
1053 fn set_preview2_ctx(&self, store: &mut Store<Host>) -> Result<()> {
1054 let mut builder = wasmtime_wasi::p2::WasiCtxBuilder::new();
1055 builder.inherit_stdio().args(&self.compute_argv()?);
1056 self.run.configure_wasip2(&mut builder)?;
1057 let ctx = builder.build_p1();
1058 store.data_mut().preview2_ctx = Some(Arc::new(Mutex::new(ctx)));
1059 Ok(())
1060 }
1061
1062 #[cfg(feature = "wasi-nn")]
1063 fn collect_preloaded_nn_graphs(
1064 &self,
1065 ) -> Result<(Vec<wasmtime_wasi_nn::Backend>, wasmtime_wasi_nn::Registry)> {
1066 let graphs = self
1067 .run
1068 .common
1069 .wasi
1070 .nn_graph
1071 .iter()
1072 .map(|g| (g.format.clone(), g.dir.clone()))
1073 .collect::<Vec<_>>();
1074 wasmtime_wasi_nn::preload(&graphs)
1075 }
1076}
1077
1078#[derive(Default, Clone)]
1079struct Host {
1080 preview1_ctx: Option<wasi_common::WasiCtx>,
1081
1082 preview2_ctx: Option<Arc<Mutex<wasmtime_wasi::preview1::WasiP1Ctx>>>,
1086
1087 #[cfg(feature = "wasi-nn")]
1088 wasi_nn_wit: Option<Arc<wasmtime_wasi_nn::wit::WasiNnCtx>>,
1089 #[cfg(feature = "wasi-nn")]
1090 wasi_nn_witx: Option<Arc<wasmtime_wasi_nn::witx::WasiNnCtx>>,
1091
1092 #[cfg(feature = "wasi-threads")]
1093 wasi_threads: Option<Arc<WasiThreadsCtx<Host>>>,
1094 #[cfg(feature = "wasi-http")]
1095 wasi_http: Option<Arc<WasiHttpCtx>>,
1096 #[cfg(feature = "wasi-http")]
1097 wasi_http_outgoing_body_buffer_chunks: Option<usize>,
1098 #[cfg(feature = "wasi-http")]
1099 wasi_http_outgoing_body_chunk_size: Option<usize>,
1100 limits: StoreLimits,
1101 #[cfg(feature = "profiling")]
1102 guest_profiler: Option<Arc<wasmtime::GuestProfiler>>,
1103
1104 #[cfg(feature = "wasi-config")]
1105 wasi_config: Option<Arc<WasiConfigVariables>>,
1106 #[cfg(feature = "wasi-keyvalue")]
1107 wasi_keyvalue: Option<Arc<WasiKeyValueCtx>>,
1108}
1109
1110impl Host {
1111 fn preview2_ctx(&mut self) -> &mut wasmtime_wasi::preview1::WasiP1Ctx {
1112 let ctx = self
1113 .preview2_ctx
1114 .as_mut()
1115 .expect("wasip2 is not configured");
1116 Arc::get_mut(ctx)
1117 .expect("wasmtime_wasi is not compatible with threads")
1118 .get_mut()
1119 .unwrap()
1120 }
1121}
1122
1123impl IoView for Host {
1124 fn table(&mut self) -> &mut wasmtime::component::ResourceTable {
1125 self.preview2_ctx().table()
1126 }
1127}
1128impl WasiView for Host {
1129 fn ctx(&mut self) -> &mut wasmtime_wasi::p2::WasiCtx {
1130 self.preview2_ctx().ctx()
1131 }
1132}
1133
1134#[cfg(feature = "wasi-http")]
1135impl wasmtime_wasi_http::types::WasiHttpView for Host {
1136 fn ctx(&mut self) -> &mut WasiHttpCtx {
1137 let ctx = self.wasi_http.as_mut().unwrap();
1138 Arc::get_mut(ctx).expect("wasmtime_wasi is not compatible with threads")
1139 }
1140
1141 fn outgoing_body_buffer_chunks(&mut self) -> usize {
1142 self.wasi_http_outgoing_body_buffer_chunks
1143 .unwrap_or_else(|| DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS)
1144 }
1145
1146 fn outgoing_body_chunk_size(&mut self) -> usize {
1147 self.wasi_http_outgoing_body_chunk_size
1148 .unwrap_or_else(|| DEFAULT_OUTGOING_BODY_CHUNK_SIZE)
1149 }
1150}
1151
1152#[cfg(not(unix))]
1153fn ctx_set_listenfd(num_fd: usize, _builder: &mut WasiCtxBuilder) -> Result<usize> {
1154 Ok(num_fd)
1155}
1156
1157#[cfg(unix)]
1158fn ctx_set_listenfd(mut num_fd: usize, builder: &mut WasiCtxBuilder) -> Result<usize> {
1159 use listenfd::ListenFd;
1160
1161 for env in ["LISTEN_FDS", "LISTEN_FDNAMES"] {
1162 if let Ok(val) = std::env::var(env) {
1163 builder.env(env, &val)?;
1164 }
1165 }
1166
1167 let mut listenfd = ListenFd::from_env();
1168
1169 for i in 0..listenfd.len() {
1170 if let Some(stdlistener) = listenfd.take_tcp_listener(i)? {
1171 let _ = stdlistener.set_nonblocking(true)?;
1172 let listener = TcpListener::from_std(stdlistener);
1173 builder.preopened_socket((3 + i) as _, listener)?;
1174 num_fd = 3 + i;
1175 }
1176 }
1177
1178 Ok(num_fd)
1179}
1180
1181#[cfg(feature = "coredump")]
1182fn write_core_dump(
1183 store: &mut Store<Host>,
1184 err: &anyhow::Error,
1185 name: &str,
1186 path: &str,
1187) -> Result<()> {
1188 use std::fs::File;
1189 use std::io::Write;
1190
1191 let core_dump = err
1192 .downcast_ref::<wasmtime::WasmCoreDump>()
1193 .expect("should have been configured to capture core dumps");
1194
1195 let core_dump = core_dump.serialize(store, name);
1196
1197 let mut core_dump_file =
1198 File::create(path).context(format!("failed to create file at `{path}`"))?;
1199 core_dump_file
1200 .write_all(&core_dump)
1201 .with_context(|| format!("failed to write core dump file at `{path}`"))?;
1202 Ok(())
1203}