Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/src/commands/run.rs
3054 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 clap::Parser;
10
use std::ffi::OsString;
11
use std::path::{Path, PathBuf};
12
use std::sync::{Arc, Mutex};
13
use std::thread;
14
use wasi_common::sync::{Dir, TcpListener, WasiCtxBuilder, ambient_authority};
15
use wasmtime::{
16
Engine, Error, Func, Module, Result, Store, StoreLimits, Val, ValType, bail,
17
error::Context as _, format_err,
18
};
19
use wasmtime_wasi::{WasiCtxView, WasiView};
20
21
#[cfg(feature = "wasi-config")]
22
use wasmtime_wasi_config::{WasiConfig, WasiConfigVariables};
23
#[cfg(feature = "wasi-http")]
24
use wasmtime_wasi_http::{
25
DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS, DEFAULT_OUTGOING_BODY_CHUNK_SIZE, WasiHttpCtx,
26
};
27
#[cfg(feature = "wasi-keyvalue")]
28
use wasmtime_wasi_keyvalue::{WasiKeyValue, WasiKeyValueCtx, WasiKeyValueCtxBuilder};
29
#[cfg(feature = "wasi-nn")]
30
use wasmtime_wasi_nn::wit::WasiNnView;
31
#[cfg(feature = "wasi-threads")]
32
use wasmtime_wasi_threads::WasiThreadsCtx;
33
#[cfg(feature = "wasi-tls")]
34
use wasmtime_wasi_tls::{WasiTls, WasiTlsCtx};
35
36
fn parse_preloads(s: &str) -> Result<(String, PathBuf)> {
37
let parts: Vec<&str> = s.splitn(2, '=').collect();
38
if parts.len() != 2 {
39
bail!("must contain exactly one equals character ('=')");
40
}
41
Ok((parts[0].into(), parts[1].into()))
42
}
43
44
/// Runs a WebAssembly module
45
#[derive(Parser)]
46
pub struct RunCommand {
47
#[command(flatten)]
48
#[expect(missing_docs, reason = "don't want to mess with clap doc-strings")]
49
pub run: RunCommon,
50
51
/// The name of the function to run
52
#[arg(long, value_name = "FUNCTION")]
53
pub invoke: Option<String>,
54
55
#[command(flatten)]
56
#[expect(missing_docs, reason = "don't want to mess with clap doc-strings")]
57
pub preloads: Preloads,
58
59
/// Override the value of `argv[0]`, typically the name of the executable of
60
/// the application being run.
61
///
62
/// This can be useful to pass in situations where a CLI tool is being
63
/// executed that dispatches its functionality on the value of `argv[0]`
64
/// without needing to rename the original wasm binary.
65
#[arg(long)]
66
pub argv0: Option<String>,
67
68
/// The WebAssembly module to run and arguments to pass to it.
69
///
70
/// Arguments passed to the wasm module will be configured as WASI CLI
71
/// arguments unless the `--invoke` CLI argument is passed in which case
72
/// arguments will be interpreted as arguments to the function specified.
73
#[arg(value_name = "WASM", trailing_var_arg = true, required = true)]
74
pub module_and_args: Vec<OsString>,
75
}
76
77
#[expect(missing_docs, reason = "don't want to mess with clap doc-strings")]
78
#[derive(Parser, Default, Clone)]
79
pub struct Preloads {
80
/// Load the given WebAssembly module before the main module
81
#[arg(
82
long = "preload",
83
number_of_values = 1,
84
value_name = "NAME=MODULE_PATH",
85
value_parser = parse_preloads,
86
)]
87
modules: Vec<(String, PathBuf)>,
88
}
89
90
/// Dispatch between either a core or component linker.
91
#[expect(missing_docs, reason = "self-explanatory")]
92
pub enum CliLinker {
93
Core(wasmtime::Linker<Host>),
94
#[cfg(feature = "component-model")]
95
Component(wasmtime::component::Linker<Host>),
96
}
97
98
/// Dispatch between either a core or component instance.
99
#[expect(missing_docs, reason = "self-explanatory")]
100
pub enum CliInstance {
101
Core(wasmtime::Instance),
102
#[cfg(feature = "component-model")]
103
Component(wasmtime::component::Instance),
104
}
105
106
impl RunCommand {
107
/// Executes the command.
108
#[cfg(feature = "run")]
109
pub fn execute(mut self) -> Result<()> {
110
let runtime = tokio::runtime::Builder::new_multi_thread()
111
.enable_time()
112
.enable_io()
113
.build()?;
114
115
runtime.block_on(async {
116
self.run.common.init_logging()?;
117
118
let engine = self.new_engine()?;
119
let main = self
120
.run
121
.load_module(&engine, self.module_and_args[0].as_ref())?;
122
let (mut store, mut linker) = self.new_store_and_linker(&engine, &main)?;
123
124
self.instantiate_and_run(&engine, &mut linker, &main, &mut store)
125
.await?;
126
Ok(())
127
})
128
}
129
130
/// Creates a new `Engine` with the configuration for this command.
131
pub fn new_engine(&mut self) -> Result<Engine> {
132
let mut config = self.run.common.config(None)?;
133
134
if self.run.common.wasm.timeout.is_some() {
135
config.epoch_interruption(true);
136
}
137
match self.run.profile {
138
Some(Profile::Native(s)) => {
139
config.profiler(s);
140
}
141
Some(Profile::Guest { .. }) => {
142
// Further configured down below as well.
143
config.epoch_interruption(true);
144
}
145
None => {}
146
}
147
148
Engine::new(&config)
149
}
150
151
/// Populatse a new `Store` and `CliLinker` with the configuration in this
152
/// command.
153
///
154
/// The `engine` provided is used to for the store/linker and the `main`
155
/// provided is the module/component that is going to be run.
156
pub fn new_store_and_linker(
157
&mut self,
158
engine: &Engine,
159
main: &RunTarget,
160
) -> Result<(Store<Host>, CliLinker)> {
161
// Validate coredump-on-trap argument
162
if let Some(path) = &self.run.common.debug.coredump {
163
if path.contains("%") {
164
bail!("the coredump-on-trap path does not support patterns yet.")
165
}
166
}
167
168
let mut linker = match &main {
169
RunTarget::Core(_) => CliLinker::Core(wasmtime::Linker::new(&engine)),
170
#[cfg(feature = "component-model")]
171
RunTarget::Component(_) => {
172
CliLinker::Component(wasmtime::component::Linker::new(&engine))
173
}
174
};
175
if let Some(enable) = self.run.common.wasm.unknown_exports_allow {
176
match &mut linker {
177
CliLinker::Core(l) => {
178
l.allow_unknown_exports(enable);
179
}
180
#[cfg(feature = "component-model")]
181
CliLinker::Component(_) => {
182
bail!("--allow-unknown-exports not supported with components");
183
}
184
}
185
}
186
187
let host = Host {
188
#[cfg(feature = "wasi-http")]
189
wasi_http_outgoing_body_buffer_chunks: self
190
.run
191
.common
192
.wasi
193
.http_outgoing_body_buffer_chunks,
194
#[cfg(feature = "wasi-http")]
195
wasi_http_outgoing_body_chunk_size: self.run.common.wasi.http_outgoing_body_chunk_size,
196
..Default::default()
197
};
198
199
let mut store = Store::new(&engine, host);
200
self.populate_with_wasi(&mut linker, &mut store, &main)?;
201
202
store.data_mut().limits = self.run.store_limits();
203
store.limiter(|t| &mut t.limits);
204
205
// If fuel has been configured, we want to add the configured
206
// fuel amount to this store.
207
if let Some(fuel) = self.run.common.wasm.fuel {
208
store.set_fuel(fuel)?;
209
}
210
211
Ok((store, linker))
212
}
213
214
/// Executes the `main` after instantiating it within `store`.
215
///
216
/// This applies all configuration within `self`, such as timeouts and
217
/// profiling, and performs the execution. The resulting instance is
218
/// returned.
219
pub async fn instantiate_and_run(
220
&self,
221
engine: &Engine,
222
linker: &mut CliLinker,
223
main: &RunTarget,
224
store: &mut Store<Host>,
225
) -> Result<CliInstance> {
226
let dur = self
227
.run
228
.common
229
.wasm
230
.timeout
231
.unwrap_or(std::time::Duration::MAX);
232
let result = tokio::time::timeout(dur, async {
233
let mut profiled_modules: Vec<(String, Module)> = Vec::new();
234
if let RunTarget::Core(m) = &main {
235
profiled_modules.push(("".to_string(), m.clone()));
236
}
237
238
// Load the preload wasm modules.
239
for (name, path) in self.preloads.modules.iter() {
240
// Read the wasm module binary either as `*.wat` or a raw binary
241
let preload_target = self.run.load_module(&engine, path)?;
242
let preload_module = match preload_target {
243
RunTarget::Core(m) => m,
244
#[cfg(feature = "component-model")]
245
RunTarget::Component(_) => {
246
bail!("components cannot be loaded with `--preload`")
247
}
248
};
249
profiled_modules.push((name.to_string(), preload_module.clone()));
250
251
// Add the module's functions to the linker.
252
match linker {
253
#[cfg(feature = "cranelift")]
254
CliLinker::Core(linker) => {
255
linker
256
.module_async(&mut *store, name, &preload_module)
257
.await
258
.with_context(|| {
259
format!(
260
"failed to process preload `{}` at `{}`",
261
name,
262
path.display()
263
)
264
})?;
265
}
266
#[cfg(not(feature = "cranelift"))]
267
CliLinker::Core(_) => {
268
bail!("support for --preload disabled at compile time");
269
}
270
#[cfg(feature = "component-model")]
271
CliLinker::Component(_) => {
272
bail!("--preload cannot be used with components");
273
}
274
}
275
}
276
277
self.load_main_module(store, linker, &main, profiled_modules)
278
.await
279
.with_context(|| {
280
format!(
281
"failed to run main module `{}`",
282
self.module_and_args[0].to_string_lossy()
283
)
284
})
285
})
286
.await;
287
288
// Load the main wasm module.
289
let instance = match result.unwrap_or_else(|elapsed| {
290
Err(wasmtime::Error::from(wasmtime::Trap::Interrupt))
291
.with_context(|| format!("timed out after {elapsed}"))
292
}) {
293
Ok(instance) => instance,
294
Err(e) => {
295
// Exit the process if Wasmtime understands the error;
296
// otherwise, fall back on Rust's default error printing/return
297
// code.
298
if store.data().legacy_p1_ctx.is_some() {
299
return Err(wasi_common::maybe_exit_on_error(e));
300
} else if store.data().wasip1_ctx.is_some() {
301
if let Some(exit) = e.downcast_ref::<wasmtime_wasi::I32Exit>() {
302
std::process::exit(exit.0);
303
}
304
}
305
if e.is::<wasmtime::Trap>() {
306
eprintln!("Error: {e:?}");
307
cfg_if::cfg_if! {
308
if #[cfg(unix)] {
309
std::process::exit(rustix::process::EXIT_SIGNALED_SIGABRT);
310
} else if #[cfg(windows)] {
311
// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/abort?view=vs-2019
312
std::process::exit(3);
313
}
314
}
315
}
316
return Err(e);
317
}
318
};
319
320
Ok(instance)
321
}
322
323
fn compute_argv(&self) -> Result<Vec<String>> {
324
let mut result = Vec::new();
325
326
for (i, arg) in self.module_and_args.iter().enumerate() {
327
// For argv[0], which is the program name. Only include the base
328
// name of the main wasm module, to avoid leaking path information.
329
let arg = if i == 0 {
330
match &self.argv0 {
331
Some(s) => s.as_ref(),
332
None => Path::new(arg).components().next_back().unwrap().as_os_str(),
333
}
334
} else {
335
arg.as_ref()
336
};
337
result.push(
338
arg.to_str()
339
.ok_or_else(|| format_err!("failed to convert {arg:?} to utf-8"))?
340
.to_string(),
341
);
342
}
343
344
Ok(result)
345
}
346
347
fn setup_epoch_handler(
348
&self,
349
store: &mut Store<Host>,
350
main_target: &RunTarget,
351
profiled_modules: Vec<(String, Module)>,
352
) -> Result<Box<dyn FnOnce(&mut Store<Host>)>> {
353
if let Some(Profile::Guest { path, interval }) = &self.run.profile {
354
#[cfg(feature = "profiling")]
355
return Ok(self.setup_guest_profiler(
356
store,
357
main_target,
358
profiled_modules,
359
path,
360
*interval,
361
)?);
362
#[cfg(not(feature = "profiling"))]
363
{
364
let _ = (profiled_modules, path, interval, main_target);
365
bail!("support for profiling disabled at compile time");
366
}
367
}
368
369
if let Some(timeout) = self.run.common.wasm.timeout {
370
store.set_epoch_deadline(1);
371
let engine = store.engine().clone();
372
thread::spawn(move || {
373
thread::sleep(timeout);
374
engine.increment_epoch();
375
});
376
}
377
378
Ok(Box::new(|_store| {}))
379
}
380
381
#[cfg(feature = "profiling")]
382
fn setup_guest_profiler(
383
&self,
384
store: &mut Store<Host>,
385
main_target: &RunTarget,
386
profiled_modules: Vec<(String, Module)>,
387
path: &str,
388
interval: std::time::Duration,
389
) -> Result<Box<dyn FnOnce(&mut Store<Host>)>> {
390
use wasmtime::{AsContext, GuestProfiler, StoreContext, StoreContextMut, UpdateDeadline};
391
392
let module_name = self.module_and_args[0].to_str().unwrap_or("<main module>");
393
store.data_mut().guest_profiler = match main_target {
394
RunTarget::Core(_m) => Some(Arc::new(GuestProfiler::new(
395
store.engine(),
396
module_name,
397
interval,
398
profiled_modules,
399
)?)),
400
RunTarget::Component(component) => Some(Arc::new(GuestProfiler::new_component(
401
store.engine(),
402
module_name,
403
interval,
404
component.clone(),
405
profiled_modules,
406
)?)),
407
};
408
409
fn sample(
410
mut store: StoreContextMut<Host>,
411
f: impl FnOnce(&mut GuestProfiler, StoreContext<Host>),
412
) {
413
let mut profiler = store.data_mut().guest_profiler.take().unwrap();
414
f(
415
Arc::get_mut(&mut profiler).expect("profiling doesn't support threads yet"),
416
store.as_context(),
417
);
418
store.data_mut().guest_profiler = Some(profiler);
419
}
420
421
store.call_hook(|store, kind| {
422
sample(store, |profiler, store| profiler.call_hook(store, kind));
423
Ok(())
424
});
425
426
if let Some(timeout) = self.run.common.wasm.timeout {
427
let mut timeout = (timeout.as_secs_f64() / interval.as_secs_f64()).ceil() as u64;
428
assert!(timeout > 0);
429
store.epoch_deadline_callback(move |store| {
430
sample(store, |profiler, store| {
431
profiler.sample(store, std::time::Duration::ZERO)
432
});
433
timeout -= 1;
434
if timeout == 0 {
435
bail!("timeout exceeded");
436
}
437
Ok(UpdateDeadline::Continue(1))
438
});
439
} else {
440
store.epoch_deadline_callback(move |store| {
441
sample(store, |profiler, store| {
442
profiler.sample(store, std::time::Duration::ZERO)
443
});
444
Ok(UpdateDeadline::Continue(1))
445
});
446
}
447
448
store.set_epoch_deadline(1);
449
let engine = store.engine().clone();
450
thread::spawn(move || {
451
loop {
452
thread::sleep(interval);
453
engine.increment_epoch();
454
}
455
});
456
457
let path = path.to_string();
458
Ok(Box::new(move |store| {
459
let profiler = Arc::try_unwrap(store.data_mut().guest_profiler.take().unwrap())
460
.expect("profiling doesn't support threads yet");
461
if let Err(e) = std::fs::File::create(&path)
462
.map_err(wasmtime::Error::new)
463
.and_then(|output| profiler.finish(std::io::BufWriter::new(output)))
464
{
465
eprintln!("failed writing profile at {path}: {e:#}");
466
} else {
467
eprintln!();
468
eprintln!("Profile written to: {path}");
469
eprintln!("View this profile at https://profiler.firefox.com/.");
470
}
471
}))
472
}
473
474
async fn load_main_module(
475
&self,
476
store: &mut Store<Host>,
477
linker: &mut CliLinker,
478
main_target: &RunTarget,
479
profiled_modules: Vec<(String, Module)>,
480
) -> Result<CliInstance> {
481
// The main module might be allowed to have unknown imports, which
482
// should be defined as traps:
483
if self.run.common.wasm.unknown_imports_trap == Some(true) {
484
match linker {
485
CliLinker::Core(linker) => {
486
linker.define_unknown_imports_as_traps(main_target.unwrap_core())?;
487
}
488
#[cfg(feature = "component-model")]
489
CliLinker::Component(linker) => {
490
linker.define_unknown_imports_as_traps(main_target.unwrap_component())?;
491
}
492
}
493
}
494
495
// ...or as default values.
496
if self.run.common.wasm.unknown_imports_default == Some(true) {
497
match linker {
498
CliLinker::Core(linker) => {
499
linker.define_unknown_imports_as_default_values(
500
&mut *store,
501
main_target.unwrap_core(),
502
)?;
503
}
504
_ => bail!("cannot use `--default-values-unknown-imports` with components"),
505
}
506
}
507
508
let finish_epoch_handler =
509
self.setup_epoch_handler(store, main_target, profiled_modules)?;
510
511
let result = match linker {
512
CliLinker::Core(linker) => {
513
let module = main_target.unwrap_core();
514
let instance = linker
515
.instantiate_async(&mut *store, &module)
516
.await
517
.with_context(|| {
518
format!("failed to instantiate {:?}", self.module_and_args[0])
519
})?;
520
521
// If `_initialize` is present, meaning a reactor, then invoke
522
// the function.
523
if let Some(func) = instance.get_func(&mut *store, "_initialize") {
524
func.typed::<(), ()>(&store)?
525
.call_async(&mut *store, ())
526
.await?;
527
}
528
529
// Look for the specific function provided or otherwise look for
530
// "" or "_start" exports to run as a "main" function.
531
let func = if let Some(name) = &self.invoke {
532
Some(
533
instance
534
.get_func(&mut *store, name)
535
.ok_or_else(|| format_err!("no func export named `{name}` found"))?,
536
)
537
} else {
538
instance
539
.get_func(&mut *store, "")
540
.or_else(|| instance.get_func(&mut *store, "_start"))
541
};
542
543
if let Some(func) = func {
544
self.invoke_func(store, func).await?;
545
}
546
Ok(CliInstance::Core(instance))
547
}
548
#[cfg(feature = "component-model")]
549
CliLinker::Component(linker) => {
550
let component = main_target.unwrap_component();
551
let result = if self.invoke.is_some() {
552
self.invoke_component(&mut *store, component, linker).await
553
} else {
554
self.run_command_component(&mut *store, component, linker)
555
.await
556
};
557
result
558
.map(CliInstance::Component)
559
.map_err(|e| self.handle_core_dump(&mut *store, e))
560
}
561
};
562
finish_epoch_handler(store);
563
564
result
565
}
566
567
#[cfg(feature = "component-model")]
568
async fn invoke_component(
569
&self,
570
store: &mut Store<Host>,
571
component: &wasmtime::component::Component,
572
linker: &mut wasmtime::component::Linker<Host>,
573
) -> Result<wasmtime::component::Instance> {
574
use wasmtime::component::{
575
Val,
576
wasm_wave::{
577
untyped::UntypedFuncCall,
578
wasm::{DisplayFuncResults, WasmFunc},
579
},
580
};
581
582
// Check if the invoke string is present
583
let invoke: &String = self.invoke.as_ref().unwrap();
584
585
let untyped_call = UntypedFuncCall::parse(invoke).with_context(|| {
586
format!(
587
"Failed to parse invoke '{invoke}': See https://docs.wasmtime.dev/cli-options.html#run for syntax",
588
)
589
})?;
590
591
let name = untyped_call.name();
592
let matches =
593
Self::search_component_funcs(store.engine(), component.component_type(), name);
594
let (names, func_type) = match matches.len() {
595
0 => bail!("No exported func named `{name}` in component."),
596
1 => &matches[0],
597
_ => bail!(
598
"Multiple exports named `{name}`: {matches:?}. FIXME: support some way to disambiguate names"
599
),
600
};
601
602
let param_types = WasmFunc::params(func_type).collect::<Vec<_>>();
603
let params = untyped_call
604
.to_wasm_params(&param_types)
605
.with_context(|| format!("while interpreting parameters in invoke \"{invoke}\""))?;
606
607
let export = names
608
.iter()
609
.fold(None, |instance, name| {
610
component.get_export_index(instance.as_ref(), name)
611
})
612
.expect("export has at least one name");
613
614
let instance = linker.instantiate_async(&mut *store, component).await?;
615
616
let func = instance
617
.get_func(&mut *store, export)
618
.expect("found export index");
619
620
let mut results = vec![Val::Bool(false); func_type.results().len()];
621
self.call_component_func(store, &params, func, &mut results)
622
.await?;
623
624
println!("{}", DisplayFuncResults(&results));
625
Ok(instance)
626
}
627
628
#[cfg(feature = "component-model")]
629
async fn call_component_func(
630
&self,
631
store: &mut Store<Host>,
632
params: &[wasmtime::component::Val],
633
func: wasmtime::component::Func,
634
results: &mut Vec<wasmtime::component::Val>,
635
) -> Result<(), Error> {
636
#[cfg(feature = "component-model-async")]
637
if self.run.common.wasm.concurrency_support.unwrap_or(true) {
638
store
639
.run_concurrent(async |store| {
640
let task = func.call_concurrent(store, params, results).await?;
641
task.block(store).await;
642
wasmtime::error::Ok(())
643
})
644
.await??;
645
return Ok(());
646
}
647
648
func.call_async(&mut *store, &params, results).await?;
649
Ok(())
650
}
651
652
/// Execute the default behavior for components on the CLI, looking for
653
/// `wasi:cli`-based commands and running their exported `run` function.
654
#[cfg(feature = "component-model")]
655
async fn run_command_component(
656
&self,
657
store: &mut Store<Host>,
658
component: &wasmtime::component::Component,
659
linker: &wasmtime::component::Linker<Host>,
660
) -> Result<wasmtime::component::Instance> {
661
let instance = linker.instantiate_async(&mut *store, component).await?;
662
663
let mut result = None;
664
let _ = &mut result;
665
666
// If WASIp3 is enabled at compile time, enabled at runtime, and found
667
// in this component then use that to generate the result.
668
#[cfg(feature = "component-model-async")]
669
if self.run.common.wasi.p3.unwrap_or(crate::common::P3_DEFAULT) {
670
if let Ok(command) = wasmtime_wasi::p3::bindings::Command::new(&mut *store, &instance) {
671
result = Some(
672
store
673
.run_concurrent(async |store| {
674
let (result, task) = command.wasi_cli_run().call_run(store).await?;
675
task.block(store).await;
676
Ok(result)
677
})
678
.await?,
679
);
680
}
681
}
682
683
let result = match result {
684
Some(result) => result,
685
// If WASIp3 wasn't found then fall back to requiring WASIp2 and
686
// this'll report an error if the right export doesn't exist.
687
None => {
688
wasmtime_wasi::p2::bindings::Command::new(&mut *store, &instance)?
689
.wasi_cli_run()
690
.call_run(&mut *store)
691
.await
692
}
693
};
694
let wasm_result = result.context("failed to invoke `run` function")?;
695
696
// Translate the `Result<(),()>` produced by wasm into a feigned
697
// explicit exit here with status 1 if `Err(())` is returned.
698
match wasm_result {
699
Ok(()) => Ok(instance),
700
Err(()) => Err(wasmtime_wasi::I32Exit(1).into()),
701
}
702
}
703
704
#[cfg(feature = "component-model")]
705
fn search_component_funcs(
706
engine: &Engine,
707
component: wasmtime::component::types::Component,
708
name: &str,
709
) -> Vec<(Vec<String>, wasmtime::component::types::ComponentFunc)> {
710
use wasmtime::component::types::ComponentItem as CItem;
711
fn collect_exports(
712
engine: &Engine,
713
item: CItem,
714
basename: Vec<String>,
715
) -> Vec<(Vec<String>, CItem)> {
716
match item {
717
CItem::Component(c) => c
718
.exports(engine)
719
.flat_map(move |(name, item)| {
720
let mut names = basename.clone();
721
names.push(name.to_string());
722
collect_exports(engine, item, names)
723
})
724
.collect::<Vec<_>>(),
725
CItem::ComponentInstance(c) => c
726
.exports(engine)
727
.flat_map(move |(name, item)| {
728
let mut names = basename.clone();
729
names.push(name.to_string());
730
collect_exports(engine, item, names)
731
})
732
.collect::<Vec<_>>(),
733
_ => vec![(basename, item)],
734
}
735
}
736
737
collect_exports(engine, CItem::Component(component), Vec::new())
738
.into_iter()
739
.filter_map(|(names, item)| {
740
let CItem::ComponentFunc(func) = item else {
741
return None;
742
};
743
let func_name = names.last().expect("at least one name");
744
(func_name == name).then_some((names, func))
745
})
746
.collect()
747
}
748
749
async fn invoke_func(&self, store: &mut Store<Host>, func: Func) -> Result<()> {
750
let ty = func.ty(&store);
751
if ty.params().len() > 0 {
752
eprintln!(
753
"warning: using `--invoke` with a function that takes arguments \
754
is experimental and may break in the future"
755
);
756
}
757
let mut args = self.module_and_args.iter().skip(1);
758
let mut values = Vec::new();
759
for ty in ty.params() {
760
let val = match args.next() {
761
Some(s) => s,
762
None => {
763
if let Some(name) = &self.invoke {
764
bail!("not enough arguments for `{name}`")
765
} else {
766
bail!("not enough arguments for command default")
767
}
768
}
769
};
770
let val = val
771
.to_str()
772
.ok_or_else(|| format_err!("argument is not valid utf-8: {val:?}"))?;
773
values.push(match ty {
774
// Supports both decimal and hexadecimal notation (with 0x prefix)
775
ValType::I32 => Val::I32(if val.starts_with("0x") || val.starts_with("0X") {
776
i32::from_str_radix(&val[2..], 16)?
777
} else {
778
val.parse::<i32>()?
779
}),
780
ValType::I64 => Val::I64(if val.starts_with("0x") || val.starts_with("0X") {
781
i64::from_str_radix(&val[2..], 16)?
782
} else {
783
val.parse::<i64>()?
784
}),
785
ValType::F32 => Val::F32(val.parse::<f32>()?.to_bits()),
786
ValType::F64 => Val::F64(val.parse::<f64>()?.to_bits()),
787
t => bail!("unsupported argument type {t:?}"),
788
});
789
}
790
791
// Invoke the function and then afterwards print all the results that came
792
// out, if there are any.
793
let mut results = vec![Val::null_func_ref(); ty.results().len()];
794
let invoke_res = func
795
.call_async(&mut *store, &values, &mut results)
796
.await
797
.with_context(|| {
798
if let Some(name) = &self.invoke {
799
format!("failed to invoke `{name}`")
800
} else {
801
format!("failed to invoke command default")
802
}
803
});
804
805
if let Err(err) = invoke_res {
806
return Err(self.handle_core_dump(&mut *store, err));
807
}
808
809
if !results.is_empty() {
810
eprintln!(
811
"warning: using `--invoke` with a function that returns values \
812
is experimental and may break in the future"
813
);
814
}
815
816
for result in results {
817
match result {
818
Val::I32(i) => println!("{i}"),
819
Val::I64(i) => println!("{i}"),
820
Val::F32(f) => println!("{}", f32::from_bits(f)),
821
Val::F64(f) => println!("{}", f64::from_bits(f)),
822
Val::V128(i) => println!("{}", i.as_u128()),
823
Val::ExternRef(None) => println!("<null externref>"),
824
Val::ExternRef(Some(_)) => println!("<externref>"),
825
Val::FuncRef(None) => println!("<null funcref>"),
826
Val::FuncRef(Some(_)) => println!("<funcref>"),
827
Val::AnyRef(None) => println!("<null anyref>"),
828
Val::AnyRef(Some(_)) => println!("<anyref>"),
829
Val::ExnRef(None) => println!("<null exnref>"),
830
Val::ExnRef(Some(_)) => println!("<exnref>"),
831
Val::ContRef(None) => println!("<null contref>"),
832
Val::ContRef(Some(_)) => println!("<contref>"),
833
}
834
}
835
836
Ok(())
837
}
838
839
#[cfg(feature = "coredump")]
840
fn handle_core_dump(&self, store: &mut Store<Host>, err: Error) -> Error {
841
let coredump_path = match &self.run.common.debug.coredump {
842
Some(path) => path,
843
None => return err,
844
};
845
if !err.is::<wasmtime::Trap>() {
846
return err;
847
}
848
let source_name = self.module_and_args[0]
849
.to_str()
850
.unwrap_or_else(|| "unknown");
851
852
if let Err(coredump_err) = write_core_dump(store, &err, &source_name, coredump_path) {
853
eprintln!("warning: coredump failed to generate: {coredump_err}");
854
err
855
} else {
856
err.context(format!("core dumped at {coredump_path}"))
857
}
858
}
859
860
#[cfg(not(feature = "coredump"))]
861
fn handle_core_dump(&self, _store: &mut Store<Host>, err: Error) -> Error {
862
err
863
}
864
865
/// Populates the given `Linker` with WASI APIs.
866
fn populate_with_wasi(
867
&self,
868
linker: &mut CliLinker,
869
store: &mut Store<Host>,
870
module: &RunTarget,
871
) -> Result<()> {
872
self.run.validate_p3_option()?;
873
let cli = self.run.validate_cli_enabled()?;
874
875
if cli != Some(false) {
876
match linker {
877
CliLinker::Core(linker) => {
878
match (self.run.common.wasi.preview2, self.run.common.wasi.threads) {
879
// If preview2 is explicitly disabled, or if threads
880
// are enabled, then use the historical preview1
881
// implementation.
882
(Some(false), _) | (None, Some(true)) => {
883
wasi_common::tokio::add_to_linker(linker, |host| {
884
host.legacy_p1_ctx.as_mut().unwrap()
885
})?;
886
self.set_legacy_p1_ctx(store)?;
887
}
888
// If preview2 was explicitly requested, always use it.
889
// Otherwise use it so long as threads are disabled.
890
//
891
// Note that for now `p0` is currently
892
// default-enabled but this may turn into
893
// default-disabled in the future.
894
(Some(true), _) | (None, Some(false) | None) => {
895
if self.run.common.wasi.preview0 != Some(false) {
896
wasmtime_wasi::p0::add_to_linker_async(linker, |t| t.wasip1_ctx())?;
897
}
898
wasmtime_wasi::p1::add_to_linker_async(linker, |t| t.wasip1_ctx())?;
899
self.set_wasi_ctx(store)?;
900
}
901
}
902
}
903
#[cfg(feature = "component-model")]
904
CliLinker::Component(linker) => {
905
self.run.add_wasmtime_wasi_to_linker(linker)?;
906
self.set_wasi_ctx(store)?;
907
}
908
}
909
}
910
911
if self.run.common.wasi.nn == Some(true) {
912
#[cfg(not(feature = "wasi-nn"))]
913
{
914
bail!("Cannot enable wasi-nn when the binary is not compiled with this feature.");
915
}
916
#[cfg(all(feature = "wasi-nn", feature = "component-model"))]
917
{
918
let (backends, registry) = self.collect_preloaded_nn_graphs()?;
919
match linker {
920
CliLinker::Core(linker) => {
921
wasmtime_wasi_nn::witx::add_to_linker(linker, |host| {
922
Arc::get_mut(host.wasi_nn_witx.as_mut().unwrap())
923
.expect("wasi-nn is not implemented with multi-threading support")
924
})?;
925
store.data_mut().wasi_nn_witx = Some(Arc::new(
926
wasmtime_wasi_nn::witx::WasiNnCtx::new(backends, registry),
927
));
928
}
929
#[cfg(feature = "component-model")]
930
CliLinker::Component(linker) => {
931
wasmtime_wasi_nn::wit::add_to_linker(linker, |h: &mut Host| {
932
let ctx = h.wasip1_ctx.as_mut().expect("wasi is not configured");
933
let ctx = Arc::get_mut(ctx)
934
.expect("wasmtime_wasi is not compatible with threads")
935
.get_mut()
936
.unwrap();
937
let nn_ctx = Arc::get_mut(h.wasi_nn_wit.as_mut().unwrap())
938
.expect("wasi-nn is not implemented with multi-threading support");
939
WasiNnView::new(ctx.ctx().table, nn_ctx)
940
})?;
941
store.data_mut().wasi_nn_wit = Some(Arc::new(
942
wasmtime_wasi_nn::wit::WasiNnCtx::new(backends, registry),
943
));
944
}
945
}
946
}
947
}
948
949
if self.run.common.wasi.config == Some(true) {
950
#[cfg(not(feature = "wasi-config"))]
951
{
952
bail!(
953
"Cannot enable wasi-config when the binary is not compiled with this feature."
954
);
955
}
956
#[cfg(all(feature = "wasi-config", feature = "component-model"))]
957
{
958
match linker {
959
CliLinker::Core(_) => {
960
bail!("Cannot enable wasi-config for core wasm modules");
961
}
962
CliLinker::Component(linker) => {
963
let vars = WasiConfigVariables::from_iter(
964
self.run
965
.common
966
.wasi
967
.config_var
968
.iter()
969
.map(|v| (v.key.clone(), v.value.clone())),
970
);
971
972
wasmtime_wasi_config::add_to_linker(linker, |h| {
973
WasiConfig::new(Arc::get_mut(h.wasi_config.as_mut().unwrap()).unwrap())
974
})?;
975
store.data_mut().wasi_config = Some(Arc::new(vars));
976
}
977
}
978
}
979
}
980
981
if self.run.common.wasi.keyvalue == Some(true) {
982
#[cfg(not(feature = "wasi-keyvalue"))]
983
{
984
bail!(
985
"Cannot enable wasi-keyvalue when the binary is not compiled with this feature."
986
);
987
}
988
#[cfg(all(feature = "wasi-keyvalue", feature = "component-model"))]
989
{
990
match linker {
991
CliLinker::Core(_) => {
992
bail!("Cannot enable wasi-keyvalue for core wasm modules");
993
}
994
CliLinker::Component(linker) => {
995
let ctx = WasiKeyValueCtxBuilder::new()
996
.in_memory_data(
997
self.run
998
.common
999
.wasi
1000
.keyvalue_in_memory_data
1001
.iter()
1002
.map(|v| (v.key.clone(), v.value.clone())),
1003
)
1004
.build();
1005
1006
wasmtime_wasi_keyvalue::add_to_linker(linker, |h| {
1007
let ctx = h.wasip1_ctx.as_mut().expect("wasip2 is not configured");
1008
let ctx = Arc::get_mut(ctx).unwrap().get_mut().unwrap();
1009
WasiKeyValue::new(
1010
Arc::get_mut(h.wasi_keyvalue.as_mut().unwrap()).unwrap(),
1011
ctx.ctx().table,
1012
)
1013
})?;
1014
store.data_mut().wasi_keyvalue = Some(Arc::new(ctx));
1015
}
1016
}
1017
}
1018
}
1019
1020
if self.run.common.wasi.threads == Some(true) {
1021
#[cfg(not(feature = "wasi-threads"))]
1022
{
1023
// Silence the unused warning for `module` as it is only used in the
1024
// conditionally-compiled wasi-threads.
1025
let _ = &module;
1026
1027
bail!(
1028
"Cannot enable wasi-threads when the binary is not compiled with this feature."
1029
);
1030
}
1031
#[cfg(feature = "wasi-threads")]
1032
{
1033
let linker = match linker {
1034
CliLinker::Core(linker) => linker,
1035
_ => bail!("wasi-threads does not support components yet"),
1036
};
1037
let module = module.unwrap_core();
1038
wasmtime_wasi_threads::add_to_linker(linker, store, &module, |host| {
1039
host.wasi_threads.as_ref().unwrap()
1040
})?;
1041
store.data_mut().wasi_threads = Some(Arc::new(WasiThreadsCtx::new(
1042
module.clone(),
1043
Arc::new(linker.clone()),
1044
true,
1045
)?));
1046
}
1047
}
1048
1049
if self.run.common.wasi.http == Some(true) {
1050
#[cfg(not(all(feature = "wasi-http", feature = "component-model")))]
1051
{
1052
bail!("Cannot enable wasi-http when the binary is not compiled with this feature.");
1053
}
1054
#[cfg(all(feature = "wasi-http", feature = "component-model"))]
1055
{
1056
match linker {
1057
CliLinker::Core(_) => {
1058
bail!("Cannot enable wasi-http for core wasm modules");
1059
}
1060
CliLinker::Component(linker) => {
1061
wasmtime_wasi_http::add_only_http_to_linker_sync(linker)?;
1062
#[cfg(feature = "component-model-async")]
1063
if self.run.common.wasi.p3.unwrap_or(crate::common::P3_DEFAULT) {
1064
wasmtime_wasi_http::p3::add_to_linker(linker)?;
1065
}
1066
}
1067
}
1068
1069
store.data_mut().wasi_http = Some(Arc::new(WasiHttpCtx::new()));
1070
}
1071
}
1072
1073
if self.run.common.wasi.tls == Some(true) {
1074
#[cfg(all(not(all(feature = "wasi-tls", feature = "component-model"))))]
1075
{
1076
bail!("Cannot enable wasi-tls when the binary is not compiled with this feature.");
1077
}
1078
#[cfg(all(feature = "wasi-tls", feature = "component-model",))]
1079
{
1080
match linker {
1081
CliLinker::Core(_) => {
1082
bail!("Cannot enable wasi-tls for core wasm modules");
1083
}
1084
CliLinker::Component(linker) => {
1085
let mut opts = wasmtime_wasi_tls::LinkOptions::default();
1086
opts.tls(true);
1087
wasmtime_wasi_tls::add_to_linker(linker, &mut opts, |h| {
1088
let ctx = h.wasip1_ctx.as_mut().expect("wasi is not configured");
1089
let ctx = Arc::get_mut(ctx).unwrap().get_mut().unwrap();
1090
WasiTls::new(
1091
Arc::get_mut(h.wasi_tls.as_mut().unwrap()).unwrap(),
1092
ctx.ctx().table,
1093
)
1094
})?;
1095
1096
let ctx = wasmtime_wasi_tls::WasiTlsCtxBuilder::new().build();
1097
store.data_mut().wasi_tls = Some(Arc::new(ctx));
1098
}
1099
}
1100
}
1101
}
1102
1103
Ok(())
1104
}
1105
1106
fn set_legacy_p1_ctx(&self, store: &mut Store<Host>) -> Result<()> {
1107
let mut builder = WasiCtxBuilder::new();
1108
builder.inherit_stdio().args(&self.compute_argv()?)?;
1109
1110
if self.run.common.wasi.inherit_env == Some(true) {
1111
for (k, v) in std::env::vars() {
1112
builder.env(&k, &v)?;
1113
}
1114
}
1115
for (key, value) in self.run.vars.iter() {
1116
let value = match value {
1117
Some(value) => value.clone(),
1118
None => match std::env::var_os(key) {
1119
Some(val) => val
1120
.into_string()
1121
.map_err(|_| format_err!("environment variable `{key}` not valid utf-8"))?,
1122
None => {
1123
// leave the env var un-set in the guest
1124
continue;
1125
}
1126
},
1127
};
1128
builder.env(key, &value)?;
1129
}
1130
1131
let mut num_fd: usize = 3;
1132
1133
if self.run.common.wasi.listenfd == Some(true) {
1134
num_fd = ctx_set_listenfd(num_fd, &mut builder)?;
1135
}
1136
1137
for listener in self.run.compute_preopen_sockets()? {
1138
let listener = TcpListener::from_std(listener);
1139
builder.preopened_socket(num_fd as _, listener)?;
1140
num_fd += 1;
1141
}
1142
1143
for (host, guest) in self.run.dirs.iter() {
1144
let dir = Dir::open_ambient_dir(host, ambient_authority())
1145
.with_context(|| format!("failed to open directory '{host}'"))?;
1146
builder.preopened_dir(dir, guest)?;
1147
}
1148
1149
store.data_mut().legacy_p1_ctx = Some(builder.build());
1150
Ok(())
1151
}
1152
1153
/// Note the naming here is subtle, but this is effectively setting up a
1154
/// `wasmtime_wasi::WasiCtx` structure.
1155
///
1156
/// This is stored in `Host` as `WasiP1Ctx` which internally contains the
1157
/// `WasiCtx` and `ResourceTable` used for WASI implementations. Exactly
1158
/// which "p" for WASIpN is more a reference to
1159
/// `wasmtime-wasi`-vs-`wasi-common` here more than anything else.
1160
fn set_wasi_ctx(&self, store: &mut Store<Host>) -> Result<()> {
1161
let mut builder = wasmtime_wasi::WasiCtxBuilder::new();
1162
builder.inherit_stdio().args(&self.compute_argv()?);
1163
self.run.configure_wasip2(&mut builder)?;
1164
let ctx = builder.build_p1();
1165
store.data_mut().wasip1_ctx = Some(Arc::new(Mutex::new(ctx)));
1166
Ok(())
1167
}
1168
1169
#[cfg(feature = "wasi-nn")]
1170
fn collect_preloaded_nn_graphs(
1171
&self,
1172
) -> Result<(Vec<wasmtime_wasi_nn::Backend>, wasmtime_wasi_nn::Registry)> {
1173
let graphs = self
1174
.run
1175
.common
1176
.wasi
1177
.nn_graph
1178
.iter()
1179
.map(|g| (g.format.clone(), g.dir.clone()))
1180
.collect::<Vec<_>>();
1181
wasmtime_wasi_nn::preload(&graphs)
1182
}
1183
}
1184
1185
/// The `T` in `Store<T>` for what the CLI is running.
1186
///
1187
/// This structures has a number of contexts used for various WASI proposals.
1188
/// Note that all of them are optional meaning that they're `None` by default
1189
/// and enabled with various CLI flags (some CLI flags are on-by-default). Note
1190
/// additionally that this structure is `Clone` to implement the `wasi-threads`
1191
/// proposal. Many WASI proposals are not compatible with `wasi-threads` so to
1192
/// model this `Arc` and `Arc<Mutex<T>>` is used for many configurations. If a
1193
/// WASI proposal is inherently threadsafe it's protected with just an `Arc` to
1194
/// share its configuration across many threads.
1195
///
1196
/// If mutation is required then `Mutex` is used. Note though that the mutex is
1197
/// not actually locked as access always goes through `Arc::get_mut` which
1198
/// effectively asserts that there's only one thread. In short much of this is
1199
/// not compatible with `wasi-threads`.
1200
#[derive(Default, Clone)]
1201
pub struct Host {
1202
// Legacy wasip1 context using `wasi_common`, not set unless opted-in-to
1203
// with the CLI.
1204
legacy_p1_ctx: Option<wasi_common::WasiCtx>,
1205
1206
// Context for both WASIp1 and WASIp2 (and beyond) for the `wasmtime_wasi`
1207
// crate. This has both `wasmtime_wasi::WasiCtx` as well as a
1208
// `ResourceTable` internally to be used.
1209
//
1210
// The Mutex is only needed to satisfy the Sync constraint but we never
1211
// actually perform any locking on it as we use Mutex::get_mut for every
1212
// access.
1213
wasip1_ctx: Option<Arc<Mutex<wasmtime_wasi::p1::WasiP1Ctx>>>,
1214
1215
#[cfg(feature = "wasi-nn")]
1216
wasi_nn_wit: Option<Arc<wasmtime_wasi_nn::wit::WasiNnCtx>>,
1217
#[cfg(feature = "wasi-nn")]
1218
wasi_nn_witx: Option<Arc<wasmtime_wasi_nn::witx::WasiNnCtx>>,
1219
1220
#[cfg(feature = "wasi-threads")]
1221
wasi_threads: Option<Arc<WasiThreadsCtx<Host>>>,
1222
#[cfg(feature = "wasi-http")]
1223
wasi_http: Option<Arc<WasiHttpCtx>>,
1224
#[cfg(feature = "wasi-http")]
1225
wasi_http_outgoing_body_buffer_chunks: Option<usize>,
1226
#[cfg(feature = "wasi-http")]
1227
wasi_http_outgoing_body_chunk_size: Option<usize>,
1228
#[cfg(all(feature = "wasi-http", feature = "component-model-async"))]
1229
p3_http: crate::common::DefaultP3Ctx,
1230
limits: StoreLimits,
1231
#[cfg(feature = "profiling")]
1232
guest_profiler: Option<Arc<wasmtime::GuestProfiler>>,
1233
1234
#[cfg(feature = "wasi-config")]
1235
wasi_config: Option<Arc<WasiConfigVariables>>,
1236
#[cfg(feature = "wasi-keyvalue")]
1237
wasi_keyvalue: Option<Arc<WasiKeyValueCtx>>,
1238
#[cfg(feature = "wasi-tls")]
1239
wasi_tls: Option<Arc<WasiTlsCtx>>,
1240
}
1241
1242
impl Host {
1243
fn wasip1_ctx(&mut self) -> &mut wasmtime_wasi::p1::WasiP1Ctx {
1244
unwrap_singlethread_context(&mut self.wasip1_ctx)
1245
}
1246
}
1247
1248
fn unwrap_singlethread_context<T>(ctx: &mut Option<Arc<Mutex<T>>>) -> &mut T {
1249
let ctx = ctx.as_mut().expect("context not configured");
1250
Arc::get_mut(ctx)
1251
.expect("context is not compatible with threads")
1252
.get_mut()
1253
.unwrap()
1254
}
1255
1256
impl WasiView for Host {
1257
fn ctx(&mut self) -> WasiCtxView<'_> {
1258
WasiView::ctx(self.wasip1_ctx())
1259
}
1260
}
1261
1262
#[cfg(feature = "wasi-http")]
1263
impl wasmtime_wasi_http::types::WasiHttpView for Host {
1264
fn ctx(&mut self) -> &mut WasiHttpCtx {
1265
let ctx = self.wasi_http.as_mut().unwrap();
1266
Arc::get_mut(ctx).expect("wasmtime_wasi is not compatible with threads")
1267
}
1268
1269
fn table(&mut self) -> &mut wasmtime::component::ResourceTable {
1270
WasiView::ctx(self).table
1271
}
1272
1273
fn outgoing_body_buffer_chunks(&mut self) -> usize {
1274
self.wasi_http_outgoing_body_buffer_chunks
1275
.unwrap_or_else(|| DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS)
1276
}
1277
1278
fn outgoing_body_chunk_size(&mut self) -> usize {
1279
self.wasi_http_outgoing_body_chunk_size
1280
.unwrap_or_else(|| DEFAULT_OUTGOING_BODY_CHUNK_SIZE)
1281
}
1282
}
1283
1284
#[cfg(all(feature = "wasi-http", feature = "component-model-async"))]
1285
impl wasmtime_wasi_http::p3::WasiHttpView for Host {
1286
fn http(&mut self) -> wasmtime_wasi_http::p3::WasiHttpCtxView<'_> {
1287
wasmtime_wasi_http::p3::WasiHttpCtxView {
1288
table: WasiView::ctx(unwrap_singlethread_context(&mut self.wasip1_ctx)).table,
1289
ctx: &mut self.p3_http,
1290
}
1291
}
1292
}
1293
1294
fn ctx_set_listenfd(mut num_fd: usize, builder: &mut WasiCtxBuilder) -> Result<usize> {
1295
let _ = &mut num_fd;
1296
let _ = &mut *builder;
1297
1298
#[cfg(all(unix, feature = "run"))]
1299
{
1300
use listenfd::ListenFd;
1301
1302
for env in ["LISTEN_FDS", "LISTEN_FDNAMES"] {
1303
if let Ok(val) = std::env::var(env) {
1304
builder.env(env, &val)?;
1305
}
1306
}
1307
1308
let mut listenfd = ListenFd::from_env();
1309
1310
for i in 0..listenfd.len() {
1311
if let Some(stdlistener) = listenfd.take_tcp_listener(i)? {
1312
let _ = stdlistener.set_nonblocking(true)?;
1313
let listener = TcpListener::from_std(stdlistener);
1314
builder.preopened_socket((3 + i) as _, listener)?;
1315
num_fd = 3 + i;
1316
}
1317
}
1318
}
1319
1320
Ok(num_fd)
1321
}
1322
1323
#[cfg(feature = "coredump")]
1324
fn write_core_dump(
1325
store: &mut Store<Host>,
1326
err: &wasmtime::Error,
1327
name: &str,
1328
path: &str,
1329
) -> Result<()> {
1330
use std::fs::File;
1331
use std::io::Write;
1332
1333
let core_dump = err
1334
.downcast_ref::<wasmtime::WasmCoreDump>()
1335
.expect("should have been configured to capture core dumps");
1336
1337
let core_dump = core_dump.serialize(store, name);
1338
1339
let mut core_dump_file =
1340
File::create(path).with_context(|| format!("failed to create file at `{path}`"))?;
1341
core_dump_file
1342
.write_all(&core_dump)
1343
.with_context(|| format!("failed to write core dump file at `{path}`"))?;
1344
Ok(())
1345
}
1346
1347