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().legacy_p1_ctx.is_some() {
245 return Err(wasi_common::maybe_exit_on_error(e));
246 } else if store.data().wasip1_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 let result = if self.invoke.is_some() {
497 self.invoke_component(&mut *store, component, linker).await
498 } else {
499 self.run_command_component(&mut *store, component, linker)
500 .await
501 };
502 result.map_err(|e| self.handle_core_dump(&mut *store, e))
503 }
504 };
505 finish_epoch_handler(store);
506
507 result
508 }
509
510 #[cfg(feature = "component-model")]
511 async fn invoke_component(
512 &self,
513 store: &mut Store<Host>,
514 component: &wasmtime::component::Component,
515 linker: &mut wasmtime::component::Linker<Host>,
516 ) -> Result<()> {
517 use wasmtime::component::{
518 Val,
519 wasm_wave::{
520 untyped::UntypedFuncCall,
521 wasm::{DisplayFuncResults, WasmFunc},
522 },
523 };
524
525 let invoke: &String = self.invoke.as_ref().unwrap();
527
528 let untyped_call = UntypedFuncCall::parse(invoke).with_context(|| {
529 format!(
530 "Failed to parse invoke '{invoke}': See https://docs.wasmtime.dev/cli-options.html#run for syntax",
531 )
532 })?;
533
534 let name = untyped_call.name();
535 let matches =
536 Self::search_component_funcs(store.engine(), component.component_type(), name);
537 let (names, func_type) = match matches.len() {
538 0 => bail!("No exported func named `{name}` in component."),
539 1 => &matches[0],
540 _ => bail!(
541 "Multiple exports named `{name}`: {matches:?}. FIXME: support some way to disambiguate names"
542 ),
543 };
544
545 let param_types = WasmFunc::params(func_type).collect::<Vec<_>>();
546 let params = untyped_call
547 .to_wasm_params(¶m_types)
548 .with_context(|| format!("while interpreting parameters in invoke \"{invoke}\""))?;
549
550 let export = names
551 .iter()
552 .fold(None, |instance, name| {
553 component.get_export_index(instance.as_ref(), name)
554 })
555 .expect("export has at least one name");
556
557 let instance = linker.instantiate_async(&mut *store, component).await?;
558
559 let func = instance
560 .get_func(&mut *store, export)
561 .expect("found export index");
562
563 let mut results = vec![Val::Bool(false); func_type.results().len()];
564 func.call_async(&mut *store, ¶ms, &mut results).await?;
565
566 println!("{}", DisplayFuncResults(&results));
567
568 Ok(())
569 }
570
571 #[cfg(feature = "component-model")]
574 async fn run_command_component(
575 &self,
576 store: &mut Store<Host>,
577 component: &wasmtime::component::Component,
578 linker: &wasmtime::component::Linker<Host>,
579 ) -> Result<()> {
580 let instance = linker.instantiate_async(&mut *store, component).await?;
581
582 let mut result = None;
583 let _ = &mut result;
584
585 #[cfg(feature = "component-model-async")]
588 if self.run.common.wasi.p3.unwrap_or(crate::common::P3_DEFAULT) {
589 if let Ok(command) = wasmtime_wasi::p3::bindings::Command::new(&mut *store, &instance) {
590 result = Some(
591 instance
592 .run_concurrent(&mut *store, async |store| {
593 command.wasi_cli_run().call_run(store).await
594 })
595 .await?,
596 );
597 }
598 }
599
600 let result = match result {
601 Some(result) => result,
602 None => {
605 wasmtime_wasi::p2::bindings::Command::new(&mut *store, &instance)?
606 .wasi_cli_run()
607 .call_run(&mut *store)
608 .await
609 }
610 };
611 let wasm_result = result.context("failed to invoke `run` function")?;
612
613 match wasm_result {
616 Ok(()) => Ok(()),
617 Err(()) => Err(wasmtime_wasi::I32Exit(1).into()),
618 }
619 }
620
621 #[cfg(feature = "component-model")]
622 fn search_component_funcs(
623 engine: &Engine,
624 component: wasmtime::component::types::Component,
625 name: &str,
626 ) -> Vec<(Vec<String>, wasmtime::component::types::ComponentFunc)> {
627 use wasmtime::component::types::ComponentItem as CItem;
628 fn collect_exports(
629 engine: &Engine,
630 item: CItem,
631 basename: Vec<String>,
632 ) -> Vec<(Vec<String>, CItem)> {
633 match item {
634 CItem::Component(c) => c
635 .exports(engine)
636 .flat_map(move |(name, item)| {
637 let mut names = basename.clone();
638 names.push(name.to_string());
639 collect_exports(engine, item, names)
640 })
641 .collect::<Vec<_>>(),
642 CItem::ComponentInstance(c) => c
643 .exports(engine)
644 .flat_map(move |(name, item)| {
645 let mut names = basename.clone();
646 names.push(name.to_string());
647 collect_exports(engine, item, names)
648 })
649 .collect::<Vec<_>>(),
650 _ => vec![(basename, item)],
651 }
652 }
653
654 collect_exports(engine, CItem::Component(component), Vec::new())
655 .into_iter()
656 .filter_map(|(names, item)| {
657 let CItem::ComponentFunc(func) = item else {
658 return None;
659 };
660 let func_name = names.last().expect("at least one name");
661 let base_func_name = func_name.strip_prefix("[async]").unwrap_or(func_name);
662 (base_func_name == name).then_some((names, func))
663 })
664 .collect()
665 }
666
667 async fn invoke_func(&self, store: &mut Store<Host>, func: Func) -> Result<()> {
668 let ty = func.ty(&store);
669 if ty.params().len() > 0 {
670 eprintln!(
671 "warning: using `--invoke` with a function that takes arguments \
672 is experimental and may break in the future"
673 );
674 }
675 let mut args = self.module_and_args.iter().skip(1);
676 let mut values = Vec::new();
677 for ty in ty.params() {
678 let val = match args.next() {
679 Some(s) => s,
680 None => {
681 if let Some(name) = &self.invoke {
682 bail!("not enough arguments for `{}`", name)
683 } else {
684 bail!("not enough arguments for command default")
685 }
686 }
687 };
688 let val = val
689 .to_str()
690 .ok_or_else(|| anyhow!("argument is not valid utf-8: {val:?}"))?;
691 values.push(match ty {
692 ValType::I32 => Val::I32(if val.starts_with("0x") || val.starts_with("0X") {
694 i32::from_str_radix(&val[2..], 16)?
695 } else {
696 val.parse::<i32>()?
697 }),
698 ValType::I64 => Val::I64(if val.starts_with("0x") || val.starts_with("0X") {
699 i64::from_str_radix(&val[2..], 16)?
700 } else {
701 val.parse::<i64>()?
702 }),
703 ValType::F32 => Val::F32(val.parse::<f32>()?.to_bits()),
704 ValType::F64 => Val::F64(val.parse::<f64>()?.to_bits()),
705 t => bail!("unsupported argument type {:?}", t),
706 });
707 }
708
709 let mut results = vec![Val::null_func_ref(); ty.results().len()];
712 let invoke_res = func
713 .call_async(&mut *store, &values, &mut results)
714 .await
715 .with_context(|| {
716 if let Some(name) = &self.invoke {
717 format!("failed to invoke `{name}`")
718 } else {
719 format!("failed to invoke command default")
720 }
721 });
722
723 if let Err(err) = invoke_res {
724 return Err(self.handle_core_dump(&mut *store, err));
725 }
726
727 if !results.is_empty() {
728 eprintln!(
729 "warning: using `--invoke` with a function that returns values \
730 is experimental and may break in the future"
731 );
732 }
733
734 for result in results {
735 match result {
736 Val::I32(i) => println!("{i}"),
737 Val::I64(i) => println!("{i}"),
738 Val::F32(f) => println!("{}", f32::from_bits(f)),
739 Val::F64(f) => println!("{}", f64::from_bits(f)),
740 Val::V128(i) => println!("{}", i.as_u128()),
741 Val::ExternRef(None) => println!("<null externref>"),
742 Val::ExternRef(Some(_)) => println!("<externref>"),
743 Val::FuncRef(None) => println!("<null funcref>"),
744 Val::FuncRef(Some(_)) => println!("<funcref>"),
745 Val::AnyRef(None) => println!("<null anyref>"),
746 Val::AnyRef(Some(_)) => println!("<anyref>"),
747 Val::ExnRef(None) => println!("<null exnref>"),
748 Val::ExnRef(Some(_)) => println!("<exnref>"),
749 Val::ContRef(None) => println!("<null contref>"),
750 Val::ContRef(Some(_)) => println!("<contref>"),
751 }
752 }
753
754 Ok(())
755 }
756
757 #[cfg(feature = "coredump")]
758 fn handle_core_dump(&self, store: &mut Store<Host>, err: Error) -> Error {
759 let coredump_path = match &self.run.common.debug.coredump {
760 Some(path) => path,
761 None => return err,
762 };
763 if !err.is::<wasmtime::Trap>() {
764 return err;
765 }
766 let source_name = self.module_and_args[0]
767 .to_str()
768 .unwrap_or_else(|| "unknown");
769
770 if let Err(coredump_err) = write_core_dump(store, &err, &source_name, coredump_path) {
771 eprintln!("warning: coredump failed to generate: {coredump_err}");
772 err
773 } else {
774 err.context(format!("core dumped at {coredump_path}"))
775 }
776 }
777
778 #[cfg(not(feature = "coredump"))]
779 fn handle_core_dump(&self, _store: &mut Store<Host>, err: Error) -> Error {
780 err
781 }
782
783 fn populate_with_wasi(
785 &self,
786 linker: &mut CliLinker,
787 store: &mut Store<Host>,
788 module: &RunTarget,
789 ) -> Result<()> {
790 self.run.validate_p3_option()?;
791 let cli = self.run.validate_cli_enabled()?;
792
793 if cli != Some(false) {
794 match linker {
795 CliLinker::Core(linker) => {
796 match (self.run.common.wasi.preview2, self.run.common.wasi.threads) {
797 (Some(false), _) | (None, Some(true)) => {
801 wasi_common::tokio::add_to_linker(linker, |host| {
802 host.legacy_p1_ctx.as_mut().unwrap()
803 })?;
804 self.set_legacy_p1_ctx(store)?;
805 }
806 (Some(true), _) | (None, Some(false) | None) => {
813 if self.run.common.wasi.preview0 != Some(false) {
814 wasmtime_wasi::p0::add_to_linker_async(linker, |t| t.wasip1_ctx())?;
815 }
816 wasmtime_wasi::p1::add_to_linker_async(linker, |t| t.wasip1_ctx())?;
817 self.set_wasi_ctx(store)?;
818 }
819 }
820 }
821 #[cfg(feature = "component-model")]
822 CliLinker::Component(linker) => {
823 self.run.add_wasmtime_wasi_to_linker(linker)?;
824 self.set_wasi_ctx(store)?;
825 }
826 }
827 }
828
829 if self.run.common.wasi.nn == Some(true) {
830 #[cfg(not(feature = "wasi-nn"))]
831 {
832 bail!("Cannot enable wasi-nn when the binary is not compiled with this feature.");
833 }
834 #[cfg(all(feature = "wasi-nn", feature = "component-model"))]
835 {
836 let (backends, registry) = self.collect_preloaded_nn_graphs()?;
837 match linker {
838 CliLinker::Core(linker) => {
839 wasmtime_wasi_nn::witx::add_to_linker(linker, |host| {
840 Arc::get_mut(host.wasi_nn_witx.as_mut().unwrap())
841 .expect("wasi-nn is not implemented with multi-threading support")
842 })?;
843 store.data_mut().wasi_nn_witx = Some(Arc::new(
844 wasmtime_wasi_nn::witx::WasiNnCtx::new(backends, registry),
845 ));
846 }
847 #[cfg(feature = "component-model")]
848 CliLinker::Component(linker) => {
849 wasmtime_wasi_nn::wit::add_to_linker(linker, |h: &mut Host| {
850 let ctx = h.wasip1_ctx.as_mut().expect("wasi is not configured");
851 let ctx = Arc::get_mut(ctx)
852 .expect("wasmtime_wasi is not compatible with threads")
853 .get_mut()
854 .unwrap();
855 let nn_ctx = Arc::get_mut(h.wasi_nn_wit.as_mut().unwrap())
856 .expect("wasi-nn is not implemented with multi-threading support");
857 WasiNnView::new(ctx.ctx().table, nn_ctx)
858 })?;
859 store.data_mut().wasi_nn_wit = Some(Arc::new(
860 wasmtime_wasi_nn::wit::WasiNnCtx::new(backends, registry),
861 ));
862 }
863 }
864 }
865 }
866
867 if self.run.common.wasi.config == Some(true) {
868 #[cfg(not(feature = "wasi-config"))]
869 {
870 bail!(
871 "Cannot enable wasi-config when the binary is not compiled with this feature."
872 );
873 }
874 #[cfg(all(feature = "wasi-config", feature = "component-model"))]
875 {
876 match linker {
877 CliLinker::Core(_) => {
878 bail!("Cannot enable wasi-config for core wasm modules");
879 }
880 CliLinker::Component(linker) => {
881 let vars = WasiConfigVariables::from_iter(
882 self.run
883 .common
884 .wasi
885 .config_var
886 .iter()
887 .map(|v| (v.key.clone(), v.value.clone())),
888 );
889
890 wasmtime_wasi_config::add_to_linker(linker, |h| {
891 WasiConfig::new(Arc::get_mut(h.wasi_config.as_mut().unwrap()).unwrap())
892 })?;
893 store.data_mut().wasi_config = Some(Arc::new(vars));
894 }
895 }
896 }
897 }
898
899 if self.run.common.wasi.keyvalue == Some(true) {
900 #[cfg(not(feature = "wasi-keyvalue"))]
901 {
902 bail!(
903 "Cannot enable wasi-keyvalue when the binary is not compiled with this feature."
904 );
905 }
906 #[cfg(all(feature = "wasi-keyvalue", feature = "component-model"))]
907 {
908 match linker {
909 CliLinker::Core(_) => {
910 bail!("Cannot enable wasi-keyvalue for core wasm modules");
911 }
912 CliLinker::Component(linker) => {
913 let ctx = WasiKeyValueCtxBuilder::new()
914 .in_memory_data(
915 self.run
916 .common
917 .wasi
918 .keyvalue_in_memory_data
919 .iter()
920 .map(|v| (v.key.clone(), v.value.clone())),
921 )
922 .build();
923
924 wasmtime_wasi_keyvalue::add_to_linker(linker, |h| {
925 let ctx = h.wasip1_ctx.as_mut().expect("wasip2 is not configured");
926 let ctx = Arc::get_mut(ctx).unwrap().get_mut().unwrap();
927 WasiKeyValue::new(
928 Arc::get_mut(h.wasi_keyvalue.as_mut().unwrap()).unwrap(),
929 ctx.ctx().table,
930 )
931 })?;
932 store.data_mut().wasi_keyvalue = Some(Arc::new(ctx));
933 }
934 }
935 }
936 }
937
938 if self.run.common.wasi.threads == Some(true) {
939 #[cfg(not(feature = "wasi-threads"))]
940 {
941 let _ = &module;
944
945 bail!(
946 "Cannot enable wasi-threads when the binary is not compiled with this feature."
947 );
948 }
949 #[cfg(feature = "wasi-threads")]
950 {
951 let linker = match linker {
952 CliLinker::Core(linker) => linker,
953 _ => bail!("wasi-threads does not support components yet"),
954 };
955 let module = module.unwrap_core();
956 wasmtime_wasi_threads::add_to_linker(linker, store, &module, |host| {
957 host.wasi_threads.as_ref().unwrap()
958 })?;
959 store.data_mut().wasi_threads = Some(Arc::new(WasiThreadsCtx::new(
960 module.clone(),
961 Arc::new(linker.clone()),
962 )?));
963 }
964 }
965
966 if self.run.common.wasi.http == Some(true) {
967 #[cfg(not(all(feature = "wasi-http", feature = "component-model")))]
968 {
969 bail!("Cannot enable wasi-http when the binary is not compiled with this feature.");
970 }
971 #[cfg(all(feature = "wasi-http", feature = "component-model"))]
972 {
973 match linker {
974 CliLinker::Core(_) => {
975 bail!("Cannot enable wasi-http for core wasm modules");
976 }
977 CliLinker::Component(linker) => {
978 wasmtime_wasi_http::add_only_http_to_linker_sync(linker)?;
979 }
980 }
981
982 store.data_mut().wasi_http = Some(Arc::new(WasiHttpCtx::new()));
983 }
984 }
985
986 if self.run.common.wasi.tls == Some(true) {
987 #[cfg(all(not(all(feature = "wasi-tls", feature = "component-model"))))]
988 {
989 bail!("Cannot enable wasi-tls when the binary is not compiled with this feature.");
990 }
991 #[cfg(all(feature = "wasi-tls", feature = "component-model",))]
992 {
993 match linker {
994 CliLinker::Core(_) => {
995 bail!("Cannot enable wasi-tls for core wasm modules");
996 }
997 CliLinker::Component(linker) => {
998 let mut opts = wasmtime_wasi_tls::LinkOptions::default();
999 opts.tls(true);
1000 wasmtime_wasi_tls::add_to_linker(linker, &mut opts, |h| {
1001 let ctx = h.wasip1_ctx.as_mut().expect("wasi is not configured");
1002 let ctx = Arc::get_mut(ctx).unwrap().get_mut().unwrap();
1003 WasiTls::new(
1004 Arc::get_mut(h.wasi_tls.as_mut().unwrap()).unwrap(),
1005 ctx.ctx().table,
1006 )
1007 })?;
1008
1009 let ctx = wasmtime_wasi_tls::WasiTlsCtxBuilder::new().build();
1010 store.data_mut().wasi_tls = Some(Arc::new(ctx));
1011 }
1012 }
1013 }
1014 }
1015
1016 Ok(())
1017 }
1018
1019 fn set_legacy_p1_ctx(&self, store: &mut Store<Host>) -> Result<()> {
1020 let mut builder = WasiCtxBuilder::new();
1021 builder.inherit_stdio().args(&self.compute_argv()?)?;
1022
1023 if self.run.common.wasi.inherit_env == Some(true) {
1024 for (k, v) in std::env::vars() {
1025 builder.env(&k, &v)?;
1026 }
1027 }
1028 for (key, value) in self.run.vars.iter() {
1029 let value = match value {
1030 Some(value) => value.clone(),
1031 None => match std::env::var_os(key) {
1032 Some(val) => val
1033 .into_string()
1034 .map_err(|_| anyhow!("environment variable `{key}` not valid utf-8"))?,
1035 None => {
1036 continue;
1038 }
1039 },
1040 };
1041 builder.env(key, &value)?;
1042 }
1043
1044 let mut num_fd: usize = 3;
1045
1046 if self.run.common.wasi.listenfd == Some(true) {
1047 num_fd = ctx_set_listenfd(num_fd, &mut builder)?;
1048 }
1049
1050 for listener in self.run.compute_preopen_sockets()? {
1051 let listener = TcpListener::from_std(listener);
1052 builder.preopened_socket(num_fd as _, listener)?;
1053 num_fd += 1;
1054 }
1055
1056 for (host, guest) in self.run.dirs.iter() {
1057 let dir = Dir::open_ambient_dir(host, ambient_authority())
1058 .with_context(|| format!("failed to open directory '{host}'"))?;
1059 builder.preopened_dir(dir, guest)?;
1060 }
1061
1062 store.data_mut().legacy_p1_ctx = Some(builder.build());
1063 Ok(())
1064 }
1065
1066 fn set_wasi_ctx(&self, store: &mut Store<Host>) -> Result<()> {
1074 let mut builder = wasmtime_wasi::WasiCtxBuilder::new();
1075 builder.inherit_stdio().args(&self.compute_argv()?);
1076 self.run.configure_wasip2(&mut builder)?;
1077 let ctx = builder.build_p1();
1078 store.data_mut().wasip1_ctx = Some(Arc::new(Mutex::new(ctx)));
1079 Ok(())
1080 }
1081
1082 #[cfg(feature = "wasi-nn")]
1083 fn collect_preloaded_nn_graphs(
1084 &self,
1085 ) -> Result<(Vec<wasmtime_wasi_nn::Backend>, wasmtime_wasi_nn::Registry)> {
1086 let graphs = self
1087 .run
1088 .common
1089 .wasi
1090 .nn_graph
1091 .iter()
1092 .map(|g| (g.format.clone(), g.dir.clone()))
1093 .collect::<Vec<_>>();
1094 wasmtime_wasi_nn::preload(&graphs)
1095 }
1096}
1097
1098#[derive(Default, Clone)]
1114struct Host {
1115 legacy_p1_ctx: Option<wasi_common::WasiCtx>,
1118
1119 wasip1_ctx: Option<Arc<Mutex<wasmtime_wasi::p1::WasiP1Ctx>>>,
1127
1128 #[cfg(feature = "wasi-nn")]
1129 wasi_nn_wit: Option<Arc<wasmtime_wasi_nn::wit::WasiNnCtx>>,
1130 #[cfg(feature = "wasi-nn")]
1131 wasi_nn_witx: Option<Arc<wasmtime_wasi_nn::witx::WasiNnCtx>>,
1132
1133 #[cfg(feature = "wasi-threads")]
1134 wasi_threads: Option<Arc<WasiThreadsCtx<Host>>>,
1135 #[cfg(feature = "wasi-http")]
1136 wasi_http: Option<Arc<WasiHttpCtx>>,
1137 #[cfg(feature = "wasi-http")]
1138 wasi_http_outgoing_body_buffer_chunks: Option<usize>,
1139 #[cfg(feature = "wasi-http")]
1140 wasi_http_outgoing_body_chunk_size: Option<usize>,
1141 limits: StoreLimits,
1142 #[cfg(feature = "profiling")]
1143 guest_profiler: Option<Arc<wasmtime::GuestProfiler>>,
1144
1145 #[cfg(feature = "wasi-config")]
1146 wasi_config: Option<Arc<WasiConfigVariables>>,
1147 #[cfg(feature = "wasi-keyvalue")]
1148 wasi_keyvalue: Option<Arc<WasiKeyValueCtx>>,
1149 #[cfg(feature = "wasi-tls")]
1150 wasi_tls: Option<Arc<WasiTlsCtx>>,
1151}
1152
1153impl Host {
1154 fn wasip1_ctx(&mut self) -> &mut wasmtime_wasi::p1::WasiP1Ctx {
1155 let ctx = self.wasip1_ctx.as_mut().expect("wasi is not configured");
1156 Arc::get_mut(ctx)
1157 .expect("wasmtime_wasi is not compatible with threads")
1158 .get_mut()
1159 .unwrap()
1160 }
1161}
1162
1163impl WasiView for Host {
1164 fn ctx(&mut self) -> WasiCtxView<'_> {
1165 WasiView::ctx(self.wasip1_ctx())
1166 }
1167}
1168
1169#[cfg(feature = "wasi-http")]
1170impl wasmtime_wasi_http::types::WasiHttpView for Host {
1171 fn ctx(&mut self) -> &mut WasiHttpCtx {
1172 let ctx = self.wasi_http.as_mut().unwrap();
1173 Arc::get_mut(ctx).expect("wasmtime_wasi is not compatible with threads")
1174 }
1175
1176 fn table(&mut self) -> &mut wasmtime::component::ResourceTable {
1177 WasiView::ctx(self).table
1178 }
1179
1180 fn outgoing_body_buffer_chunks(&mut self) -> usize {
1181 self.wasi_http_outgoing_body_buffer_chunks
1182 .unwrap_or_else(|| DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS)
1183 }
1184
1185 fn outgoing_body_chunk_size(&mut self) -> usize {
1186 self.wasi_http_outgoing_body_chunk_size
1187 .unwrap_or_else(|| DEFAULT_OUTGOING_BODY_CHUNK_SIZE)
1188 }
1189}
1190
1191#[cfg(not(unix))]
1192fn ctx_set_listenfd(num_fd: usize, _builder: &mut WasiCtxBuilder) -> Result<usize> {
1193 Ok(num_fd)
1194}
1195
1196#[cfg(unix)]
1197fn ctx_set_listenfd(mut num_fd: usize, builder: &mut WasiCtxBuilder) -> Result<usize> {
1198 use listenfd::ListenFd;
1199
1200 for env in ["LISTEN_FDS", "LISTEN_FDNAMES"] {
1201 if let Ok(val) = std::env::var(env) {
1202 builder.env(env, &val)?;
1203 }
1204 }
1205
1206 let mut listenfd = ListenFd::from_env();
1207
1208 for i in 0..listenfd.len() {
1209 if let Some(stdlistener) = listenfd.take_tcp_listener(i)? {
1210 let _ = stdlistener.set_nonblocking(true)?;
1211 let listener = TcpListener::from_std(stdlistener);
1212 builder.preopened_socket((3 + i) as _, listener)?;
1213 num_fd = 3 + i;
1214 }
1215 }
1216
1217 Ok(num_fd)
1218}
1219
1220#[cfg(feature = "coredump")]
1221fn write_core_dump(
1222 store: &mut Store<Host>,
1223 err: &anyhow::Error,
1224 name: &str,
1225 path: &str,
1226) -> Result<()> {
1227 use std::fs::File;
1228 use std::io::Write;
1229
1230 let core_dump = err
1231 .downcast_ref::<wasmtime::WasmCoreDump>()
1232 .expect("should have been configured to capture core dumps");
1233
1234 let core_dump = core_dump.serialize(store, name);
1235
1236 let mut core_dump_file =
1237 File::create(path).context(format!("failed to create file at `{path}`"))?;
1238 core_dump_file
1239 .write_all(&core_dump)
1240 .with_context(|| format!("failed to write core dump file at `{path}`"))?;
1241 Ok(())
1242}