1 //! The module that implements the `wasmtime run` command. 2 3 #![cfg_attr( 4 not(feature = "component-model"), 5 allow(irrefutable_let_patterns, unreachable_patterns) 6 )] 7 8 use crate::common::{Profile, RunCommon, RunTarget}; 9 10 use anyhow::{Context as _, Error, Result, anyhow, bail}; 11 use clap::Parser; 12 use std::ffi::OsString; 13 use std::path::{Path, PathBuf}; 14 use std::sync::{Arc, Mutex}; 15 use std::thread; 16 use wasi_common::sync::{Dir, TcpListener, WasiCtxBuilder, ambient_authority}; 17 use wasmtime::{Engine, Func, Module, Store, StoreLimits, Val, ValType}; 18 use wasmtime_wasi::p2::{IoView, WasiView}; 19 20 #[cfg(feature = "wasi-nn")] 21 use wasmtime_wasi_nn::wit::WasiNnView; 22 23 #[cfg(feature = "wasi-threads")] 24 use wasmtime_wasi_threads::WasiThreadsCtx; 25 26 #[cfg(feature = "wasi-config")] 27 use wasmtime_wasi_config::{WasiConfig, WasiConfigVariables}; 28 #[cfg(feature = "wasi-http")] 29 use wasmtime_wasi_http::{ 30 DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS, DEFAULT_OUTGOING_BODY_CHUNK_SIZE, WasiHttpCtx, 31 }; 32 #[cfg(feature = "wasi-keyvalue")] 33 use wasmtime_wasi_keyvalue::{WasiKeyValue, WasiKeyValueCtx, WasiKeyValueCtxBuilder}; 34 35 #[cfg(feature = "wasi-tls")] 36 use wasmtime_wasi_tls::{WasiTls, WasiTlsCtx}; 37 38 fn 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 /// Runs a WebAssembly module 47 #[derive(Parser)] 48 pub 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 /// The name of the function to run 54 #[arg(long, value_name = "FUNCTION")] 55 pub invoke: Option<String>, 56 57 /// Load the given WebAssembly module before the main module 58 #[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 /// Override the value of `argv[0]`, typically the name of the executable of 67 /// the application being run. 68 /// 69 /// This can be useful to pass in situations where a CLI tool is being 70 /// executed that dispatches its functionality on the value of `argv[0]` 71 /// without needing to rename the original wasm binary. 72 #[arg(long)] 73 pub argv0: Option<String>, 74 75 /// The WebAssembly module to run and arguments to pass to it. 76 /// 77 /// Arguments passed to the wasm module will be configured as WASI CLI 78 /// arguments unless the `--invoke` CLI argument is passed in which case 79 /// arguments will be interpreted as arguments to the function specified. 80 #[arg(value_name = "WASM", trailing_var_arg = true, required = true)] 81 pub module_and_args: Vec<OsString>, 82 } 83 84 enum CliLinker { 85 Core(wasmtime::Linker<Host>), 86 #[cfg(feature = "component-model")] 87 Component(wasmtime::component::Linker<Host>), 88 } 89 90 impl RunCommand { 91 /// Executes the command. 92 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 // Further configured down below as well. 107 config.epoch_interruption(true); 108 } 109 None => {} 110 } 111 112 let engine = Engine::new(&config)?; 113 114 // Read the wasm module binary either as `*.wat` or a raw binary. 115 let main = self 116 .run 117 .load_module(&engine, self.module_and_args[0].as_ref())?; 118 119 // Validate coredump-on-trap argument 120 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 fuel has been configured, we want to add the configured 164 // fuel amount to this store. 165 if let Some(fuel) = self.run.common.wasm.fuel { 166 store.set_fuel(fuel)?; 167 } 168 169 // Always run the module asynchronously to ensure that the module can be 170 // interrupted, even if it is blocking on I/O or a timeout or something. 171 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 // Load the preload wasm modules. 190 for (name, path) in self.preloads.iter() { 191 // Read the wasm module binary either as `*.wat` or a raw binary 192 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 // Add the module's functions to the linker. 203 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 // Load the main wasm module. 239 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 // Exit the process if Wasmtime understands the error; 246 // otherwise, fall back on Rust's default error printing/return 247 // code. 248 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 // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/abort?view=vs-2019 262 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 // For argv[0], which is the program name. Only include the base 278 // name of the main wasm module, to avoid leaking path information. 279 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 // The main module might be allowed to have unknown imports, which 430 // should be defined as traps: 431 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 // ...or as default values. 444 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 `_initialize` is present, meaning a reactor, then invoke 471 // the function. 472 if let Some(func) = instance.get_func(&mut *store, "_initialize") { 473 func.typed::<(), ()>(&store)? 474 .call_async(&mut *store, ()) 475 .await?; 476 } 477 478 // Look for the specific function provided or otherwise look for 479 // "" or "_start" exports to run as a "main" function. 480 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 // Translate the `Result<(),()>` produced by wasm into a feigned 518 // explicit exit here with status 1 if `Err(())` is returned. 519 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 // Check if the invoke string is present 548 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 // Supports both decimal and hexadecimal notation (with 0x prefix) 668 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 // Invoke the function and then afterwards print all the results that came 685 // out, if there are any. 686 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 /// Populates the given `Linker` with WASI APIs. 755 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 // Accept -Scommon as a deprecated alias for -Scli. 764 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 // In the future, we may add a warning here to tell users to use 771 // `-S cli` instead of `-S common`. 772 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 // If preview2 is explicitly disabled, or if threads 781 // are enabled, then use the historical preview1 782 // implementation. 783 (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 // If preview2 was explicitly requested, always use it. 790 // Otherwise use it so long as threads are disabled. 791 // 792 // Note that for now `preview0` is currently 793 // default-enabled but this may turn into 794 // default-disabled in the future. 795 (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 // Silence the unused warning for `module` as it is only used in the 933 // conditionally-compiled wasi-threads. 934 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 WasiTls::new( 997 Arc::get_mut(h.wasi_tls.as_mut().unwrap()).unwrap(), 998 preview2_ctx.table(), 999 ) 1000 })?; 1001 1002 let ctx = wasmtime_wasi_tls::WasiTlsCtxBuilder::new().build(); 1003 store.data_mut().wasi_tls = Some(Arc::new(ctx)); 1004 } 1005 } 1006 } 1007 } 1008 1009 Ok(()) 1010 } 1011 1012 fn set_preview1_ctx(&self, store: &mut Store<Host>) -> Result<()> { 1013 let mut builder = WasiCtxBuilder::new(); 1014 builder.inherit_stdio().args(&self.compute_argv()?)?; 1015 1016 if self.run.common.wasi.inherit_env == Some(true) { 1017 for (k, v) in std::env::vars() { 1018 builder.env(&k, &v)?; 1019 } 1020 } 1021 for (key, value) in self.run.vars.iter() { 1022 let value = match value { 1023 Some(value) => value.clone(), 1024 None => match std::env::var_os(key) { 1025 Some(val) => val 1026 .into_string() 1027 .map_err(|_| anyhow!("environment variable `{key}` not valid utf-8"))?, 1028 None => { 1029 // leave the env var un-set in the guest 1030 continue; 1031 } 1032 }, 1033 }; 1034 builder.env(key, &value)?; 1035 } 1036 1037 let mut num_fd: usize = 3; 1038 1039 if self.run.common.wasi.listenfd == Some(true) { 1040 num_fd = ctx_set_listenfd(num_fd, &mut builder)?; 1041 } 1042 1043 for listener in self.run.compute_preopen_sockets()? { 1044 let listener = TcpListener::from_std(listener); 1045 builder.preopened_socket(num_fd as _, listener)?; 1046 num_fd += 1; 1047 } 1048 1049 for (host, guest) in self.run.dirs.iter() { 1050 let dir = Dir::open_ambient_dir(host, ambient_authority()) 1051 .with_context(|| format!("failed to open directory '{host}'"))?; 1052 builder.preopened_dir(dir, guest)?; 1053 } 1054 1055 store.data_mut().preview1_ctx = Some(builder.build()); 1056 Ok(()) 1057 } 1058 1059 fn set_preview2_ctx(&self, store: &mut Store<Host>) -> Result<()> { 1060 let mut builder = wasmtime_wasi::p2::WasiCtxBuilder::new(); 1061 builder.inherit_stdio().args(&self.compute_argv()?); 1062 self.run.configure_wasip2(&mut builder)?; 1063 let ctx = builder.build_p1(); 1064 store.data_mut().preview2_ctx = Some(Arc::new(Mutex::new(ctx))); 1065 Ok(()) 1066 } 1067 1068 #[cfg(feature = "wasi-nn")] 1069 fn collect_preloaded_nn_graphs( 1070 &self, 1071 ) -> Result<(Vec<wasmtime_wasi_nn::Backend>, wasmtime_wasi_nn::Registry)> { 1072 let graphs = self 1073 .run 1074 .common 1075 .wasi 1076 .nn_graph 1077 .iter() 1078 .map(|g| (g.format.clone(), g.dir.clone())) 1079 .collect::<Vec<_>>(); 1080 wasmtime_wasi_nn::preload(&graphs) 1081 } 1082 } 1083 1084 #[derive(Default, Clone)] 1085 struct Host { 1086 preview1_ctx: Option<wasi_common::WasiCtx>, 1087 1088 // The Mutex is only needed to satisfy the Sync constraint but we never 1089 // actually perform any locking on it as we use Mutex::get_mut for every 1090 // access. 1091 preview2_ctx: Option<Arc<Mutex<wasmtime_wasi::preview1::WasiP1Ctx>>>, 1092 1093 #[cfg(feature = "wasi-nn")] 1094 wasi_nn_wit: Option<Arc<wasmtime_wasi_nn::wit::WasiNnCtx>>, 1095 #[cfg(feature = "wasi-nn")] 1096 wasi_nn_witx: Option<Arc<wasmtime_wasi_nn::witx::WasiNnCtx>>, 1097 1098 #[cfg(feature = "wasi-threads")] 1099 wasi_threads: Option<Arc<WasiThreadsCtx<Host>>>, 1100 #[cfg(feature = "wasi-http")] 1101 wasi_http: Option<Arc<WasiHttpCtx>>, 1102 #[cfg(feature = "wasi-http")] 1103 wasi_http_outgoing_body_buffer_chunks: Option<usize>, 1104 #[cfg(feature = "wasi-http")] 1105 wasi_http_outgoing_body_chunk_size: Option<usize>, 1106 limits: StoreLimits, 1107 #[cfg(feature = "profiling")] 1108 guest_profiler: Option<Arc<wasmtime::GuestProfiler>>, 1109 1110 #[cfg(feature = "wasi-config")] 1111 wasi_config: Option<Arc<WasiConfigVariables>>, 1112 #[cfg(feature = "wasi-keyvalue")] 1113 wasi_keyvalue: Option<Arc<WasiKeyValueCtx>>, 1114 #[cfg(feature = "wasi-tls")] 1115 wasi_tls: Option<Arc<WasiTlsCtx>>, 1116 } 1117 1118 impl Host { 1119 fn preview2_ctx(&mut self) -> &mut wasmtime_wasi::preview1::WasiP1Ctx { 1120 let ctx = self 1121 .preview2_ctx 1122 .as_mut() 1123 .expect("wasip2 is not configured"); 1124 Arc::get_mut(ctx) 1125 .expect("wasmtime_wasi is not compatible with threads") 1126 .get_mut() 1127 .unwrap() 1128 } 1129 } 1130 1131 impl IoView for Host { 1132 fn table(&mut self) -> &mut wasmtime::component::ResourceTable { 1133 self.preview2_ctx().table() 1134 } 1135 } 1136 impl WasiView for Host { 1137 fn ctx(&mut self) -> &mut wasmtime_wasi::p2::WasiCtx { 1138 self.preview2_ctx().ctx() 1139 } 1140 } 1141 1142 #[cfg(feature = "wasi-http")] 1143 impl wasmtime_wasi_http::types::WasiHttpView for Host { 1144 fn ctx(&mut self) -> &mut WasiHttpCtx { 1145 let ctx = self.wasi_http.as_mut().unwrap(); 1146 Arc::get_mut(ctx).expect("wasmtime_wasi is not compatible with threads") 1147 } 1148 1149 fn outgoing_body_buffer_chunks(&mut self) -> usize { 1150 self.wasi_http_outgoing_body_buffer_chunks 1151 .unwrap_or_else(|| DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS) 1152 } 1153 1154 fn outgoing_body_chunk_size(&mut self) -> usize { 1155 self.wasi_http_outgoing_body_chunk_size 1156 .unwrap_or_else(|| DEFAULT_OUTGOING_BODY_CHUNK_SIZE) 1157 } 1158 } 1159 1160 #[cfg(not(unix))] 1161 fn ctx_set_listenfd(num_fd: usize, _builder: &mut WasiCtxBuilder) -> Result<usize> { 1162 Ok(num_fd) 1163 } 1164 1165 #[cfg(unix)] 1166 fn ctx_set_listenfd(mut num_fd: usize, builder: &mut WasiCtxBuilder) -> Result<usize> { 1167 use listenfd::ListenFd; 1168 1169 for env in ["LISTEN_FDS", "LISTEN_FDNAMES"] { 1170 if let Ok(val) = std::env::var(env) { 1171 builder.env(env, &val)?; 1172 } 1173 } 1174 1175 let mut listenfd = ListenFd::from_env(); 1176 1177 for i in 0..listenfd.len() { 1178 if let Some(stdlistener) = listenfd.take_tcp_listener(i)? { 1179 let _ = stdlistener.set_nonblocking(true)?; 1180 let listener = TcpListener::from_std(stdlistener); 1181 builder.preopened_socket((3 + i) as _, listener)?; 1182 num_fd = 3 + i; 1183 } 1184 } 1185 1186 Ok(num_fd) 1187 } 1188 1189 #[cfg(feature = "coredump")] 1190 fn write_core_dump( 1191 store: &mut Store<Host>, 1192 err: &anyhow::Error, 1193 name: &str, 1194 path: &str, 1195 ) -> Result<()> { 1196 use std::fs::File; 1197 use std::io::Write; 1198 1199 let core_dump = err 1200 .downcast_ref::<wasmtime::WasmCoreDump>() 1201 .expect("should have been configured to capture core dumps"); 1202 1203 let core_dump = core_dump.serialize(store, name); 1204 1205 let mut core_dump_file = 1206 File::create(path).context(format!("failed to create file at `{path}`"))?; 1207 core_dump_file 1208 .write_all(&core_dump) 1209 .with_context(|| format!("failed to write core dump file at `{path}`"))?; 1210 Ok(()) 1211 } 1212