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