Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/src/commands/run.rs
1690 views
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
use anyhow::{Context as _, Error, Result, anyhow, bail};
10
use clap::Parser;
11
use std::ffi::OsString;
12
use std::path::{Path, PathBuf};
13
use std::sync::{Arc, Mutex};
14
use std::thread;
15
use wasi_common::sync::{Dir, TcpListener, WasiCtxBuilder, ambient_authority};
16
use wasmtime::{Engine, Func, Module, Store, StoreLimits, Val, ValType};
17
use wasmtime_wasi::{WasiCtxView, WasiView};
18
19
#[cfg(feature = "wasi-config")]
20
use wasmtime_wasi_config::{WasiConfig, WasiConfigVariables};
21
#[cfg(feature = "wasi-http")]
22
use wasmtime_wasi_http::{
23
DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS, DEFAULT_OUTGOING_BODY_CHUNK_SIZE, WasiHttpCtx,
24
};
25
#[cfg(feature = "wasi-keyvalue")]
26
use wasmtime_wasi_keyvalue::{WasiKeyValue, WasiKeyValueCtx, WasiKeyValueCtxBuilder};
27
#[cfg(feature = "wasi-nn")]
28
use wasmtime_wasi_nn::wit::WasiNnView;
29
#[cfg(feature = "wasi-threads")]
30
use wasmtime_wasi_threads::WasiThreadsCtx;
31
#[cfg(feature = "wasi-tls")]
32
use wasmtime_wasi_tls::{WasiTls, WasiTlsCtx};
33
34
fn 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
/// Runs a WebAssembly module
43
#[derive(Parser)]
44
pub 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
/// The name of the function to run
50
#[arg(long, value_name = "FUNCTION")]
51
pub invoke: Option<String>,
52
53
/// Load the given WebAssembly module before the main module
54
#[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
/// Override the value of `argv[0]`, typically the name of the executable of
63
/// the application being run.
64
///
65
/// This can be useful to pass in situations where a CLI tool is being
66
/// executed that dispatches its functionality on the value of `argv[0]`
67
/// without needing to rename the original wasm binary.
68
#[arg(long)]
69
pub argv0: Option<String>,
70
71
/// The WebAssembly module to run and arguments to pass to it.
72
///
73
/// Arguments passed to the wasm module will be configured as WASI CLI
74
/// arguments unless the `--invoke` CLI argument is passed in which case
75
/// arguments will be interpreted as arguments to the function specified.
76
#[arg(value_name = "WASM", trailing_var_arg = true, required = true)]
77
pub module_and_args: Vec<OsString>,
78
}
79
80
enum CliLinker {
81
Core(wasmtime::Linker<Host>),
82
#[cfg(feature = "component-model")]
83
Component(wasmtime::component::Linker<Host>),
84
}
85
86
impl RunCommand {
87
/// Executes the command.
88
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
// Further configured down below as well.
103
config.epoch_interruption(true);
104
}
105
None => {}
106
}
107
108
let engine = Engine::new(&config)?;
109
110
// Read the wasm module binary either as `*.wat` or a raw binary.
111
let main = self
112
.run
113
.load_module(&engine, self.module_and_args[0].as_ref())?;
114
115
// Validate coredump-on-trap argument
116
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 fuel has been configured, we want to add the configured
160
// fuel amount to this store.
161
if let Some(fuel) = self.run.common.wasm.fuel {
162
store.set_fuel(fuel)?;
163
}
164
165
// Always run the module asynchronously to ensure that the module can be
166
// interrupted, even if it is blocking on I/O or a timeout or something.
167
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
// Load the preload wasm modules.
186
for (name, path) in self.preloads.iter() {
187
// Read the wasm module binary either as `*.wat` or a raw binary
188
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
// Add the module's functions to the linker.
199
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
// Load the main wasm module.
235
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
// Exit the process if Wasmtime understands the error;
242
// otherwise, fall back on Rust's default error printing/return
243
// code.
244
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
// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/abort?view=vs-2019
258
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
// For argv[0], which is the program name. Only include the base
274
// name of the main wasm module, to avoid leaking path information.
275
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
// The main module might be allowed to have unknown imports, which
426
// should be defined as traps:
427
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
// ...or as default values.
440
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 `_initialize` is present, meaning a reactor, then invoke
467
// the function.
468
if let Some(func) = instance.get_func(&mut *store, "_initialize") {
469
func.typed::<(), ()>(&store)?
470
.call_async(&mut *store, ())
471
.await?;
472
}
473
474
// Look for the specific function provided or otherwise look for
475
// "" or "_start" exports to run as a "main" function.
476
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
// Check if the invoke string is present
526
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(&param_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, &params, &mut results).await?;
565
566
println!("{}", DisplayFuncResults(&results));
567
568
Ok(())
569
}
570
571
/// Execute the default behavior for components on the CLI, looking for
572
/// `wasi:cli`-based commands and running their exported `run` function.
573
#[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
// If WASIp3 is enabled at compile time, enabled at runtime, and found
586
// in this component then use that to generate the result.
587
#[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
// If WASIp3 wasn't found then fall back to requiring WASIp2 and
603
// this'll report an error if the right export doesn't exist.
604
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
// Translate the `Result<(),()>` produced by wasm into a feigned
614
// explicit exit here with status 1 if `Err(())` is returned.
615
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
// Supports both decimal and hexadecimal notation (with 0x prefix)
693
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
// Invoke the function and then afterwards print all the results that came
710
// out, if there are any.
711
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
/// Populates the given `Linker` with WASI APIs.
784
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
// If preview2 is explicitly disabled, or if threads
798
// are enabled, then use the historical preview1
799
// implementation.
800
(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
// If preview2 was explicitly requested, always use it.
807
// Otherwise use it so long as threads are disabled.
808
//
809
// Note that for now `p0` is currently
810
// default-enabled but this may turn into
811
// default-disabled in the future.
812
(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
// Silence the unused warning for `module` as it is only used in the
942
// conditionally-compiled wasi-threads.
943
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
// leave the env var un-set in the guest
1037
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
/// Note the naming here is subtle, but this is effectively setting up a
1067
/// `wasmtime_wasi::WasiCtx` structure.
1068
///
1069
/// This is stored in `Host` as `WasiP1Ctx` which internally contains the
1070
/// `WasiCtx` and `ResourceTable` used for WASI implementations. Exactly
1071
/// which "p" for WASIpN is more a reference to
1072
/// `wasmtime-wasi`-vs-`wasi-common` here more than anything else.
1073
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
/// The `T` in `Store<T>` for what the CLI is running.
1099
///
1100
/// This structures has a number of contexts used for various WASI proposals.
1101
/// Note that all of them are optional meaning that they're `None` by default
1102
/// and enabled with various CLI flags (some CLI flags are on-by-default). Note
1103
/// additionally that this structure is `Clone` to implement the `wasi-threads`
1104
/// proposal. Many WASI proposals are not compatible with `wasi-threads` so to
1105
/// model this `Arc` and `Arc<Mutex<T>>` is used for many configurations. If a
1106
/// WASI proposal is inherently threadsafe it's protected with just an `Arc` to
1107
/// share its configuration across many threads.
1108
///
1109
/// If mutation is required then `Mutex` is used. Note though that the mutex is
1110
/// not actually locked as access always goes through `Arc::get_mut` which
1111
/// effectively asserts that there's only one thread. In short much of this is
1112
/// not compatible with `wasi-threads`.
1113
#[derive(Default, Clone)]
1114
struct Host {
1115
// Legacy wasip1 context using `wasi_common`, not set unless opted-in-to
1116
// with the CLI.
1117
legacy_p1_ctx: Option<wasi_common::WasiCtx>,
1118
1119
// Context for both WASIp1 and WASIp2 (and beyond) for the `wasmtime_wasi`
1120
// crate. This has both `wasmtime_wasi::WasiCtx` as well as a
1121
// `ResourceTable` internally to be used.
1122
//
1123
// The Mutex is only needed to satisfy the Sync constraint but we never
1124
// actually perform any locking on it as we use Mutex::get_mut for every
1125
// access.
1126
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
1153
impl 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
1163
impl WasiView for Host {
1164
fn ctx(&mut self) -> WasiCtxView<'_> {
1165
WasiView::ctx(self.wasip1_ctx())
1166
}
1167
}
1168
1169
#[cfg(feature = "wasi-http")]
1170
impl 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))]
1192
fn ctx_set_listenfd(num_fd: usize, _builder: &mut WasiCtxBuilder) -> Result<usize> {
1193
Ok(num_fd)
1194
}
1195
1196
#[cfg(unix)]
1197
fn 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")]
1221
fn 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
}
1243
1244