Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/src/common.rs
1685 views
1
//! Common functionality shared between command implementations.
2
3
use anyhow::{Context, Result, anyhow, bail};
4
use clap::Parser;
5
use std::net::TcpListener;
6
use std::{fs::File, path::Path, time::Duration};
7
use wasmtime::{Engine, Module, Precompiled, StoreLimits, StoreLimitsBuilder};
8
use wasmtime_cli_flags::{CommonOptions, opt::WasmtimeOptionValue};
9
use wasmtime_wasi::WasiCtxBuilder;
10
11
#[cfg(feature = "component-model")]
12
use wasmtime::component::Component;
13
14
/// Whether or not WASIp3 is enabled by default.
15
///
16
/// Currently this is disabled (the `&& false`), but that'll get removed in the
17
/// future.
18
pub const P3_DEFAULT: bool = cfg!(feature = "component-model-async") && false;
19
20
pub enum RunTarget {
21
Core(Module),
22
23
#[cfg(feature = "component-model")]
24
Component(Component),
25
}
26
27
impl RunTarget {
28
pub fn unwrap_core(&self) -> &Module {
29
match self {
30
RunTarget::Core(module) => module,
31
#[cfg(feature = "component-model")]
32
RunTarget::Component(_) => panic!("expected a core wasm module, not a component"),
33
}
34
}
35
36
#[cfg(feature = "component-model")]
37
pub fn unwrap_component(&self) -> &Component {
38
match self {
39
RunTarget::Component(c) => c,
40
RunTarget::Core(_) => panic!("expected a component, not a core wasm module"),
41
}
42
}
43
}
44
45
/// Common command line arguments for run commands.
46
#[derive(Parser)]
47
pub struct RunCommon {
48
#[command(flatten)]
49
pub common: CommonOptions,
50
51
/// Allow executing precompiled WebAssembly modules as `*.cwasm` files.
52
///
53
/// Note that this option is not safe to pass if the module being passed in
54
/// is arbitrary user input. Only `wasmtime`-precompiled modules generated
55
/// via the `wasmtime compile` command or equivalent should be passed as an
56
/// argument with this option specified.
57
#[arg(long = "allow-precompiled")]
58
pub allow_precompiled: bool,
59
60
/// Profiling strategy (valid options are: perfmap, jitdump, vtune, guest)
61
///
62
/// The perfmap, jitdump, and vtune profiling strategies integrate Wasmtime
63
/// with external profilers such as `perf`. The guest profiling strategy
64
/// enables in-process sampling and will write the captured profile to
65
/// `wasmtime-guest-profile.json` by default which can be viewed at
66
/// https://profiler.firefox.com/.
67
///
68
/// The `guest` option can be additionally configured as:
69
///
70
/// --profile=guest[,path[,interval]]
71
///
72
/// where `path` is where to write the profile and `interval` is the
73
/// duration between samples. When used with `--wasm-timeout` the timeout
74
/// will be rounded up to the nearest multiple of this interval.
75
#[arg(
76
long,
77
value_name = "STRATEGY",
78
value_parser = Profile::parse,
79
)]
80
pub profile: Option<Profile>,
81
82
/// Grant access of a host directory to a guest.
83
///
84
/// If specified as just `HOST_DIR` then the same directory name on the
85
/// host is made available within the guest. If specified as `HOST::GUEST`
86
/// then the `HOST` directory is opened and made available as the name
87
/// `GUEST` in the guest.
88
#[arg(long = "dir", value_name = "HOST_DIR[::GUEST_DIR]", value_parser = parse_dirs)]
89
pub dirs: Vec<(String, String)>,
90
91
/// Pass an environment variable to the program.
92
///
93
/// The `--env FOO=BAR` form will set the environment variable named `FOO`
94
/// to the value `BAR` for the guest program using WASI. The `--env FOO`
95
/// form will set the environment variable named `FOO` to the same value it
96
/// has in the calling process for the guest, or in other words it will
97
/// cause the environment variable `FOO` to be inherited.
98
#[arg(long = "env", number_of_values = 1, value_name = "NAME[=VAL]", value_parser = parse_env_var)]
99
pub vars: Vec<(String, Option<String>)>,
100
}
101
102
fn parse_env_var(s: &str) -> Result<(String, Option<String>)> {
103
let mut parts = s.splitn(2, '=');
104
Ok((
105
parts.next().unwrap().to_string(),
106
parts.next().map(|s| s.to_string()),
107
))
108
}
109
110
fn parse_dirs(s: &str) -> Result<(String, String)> {
111
let mut parts = s.split("::");
112
let host = parts.next().unwrap();
113
let guest = match parts.next() {
114
Some(guest) => guest,
115
None => host,
116
};
117
Ok((host.into(), guest.into()))
118
}
119
120
impl RunCommon {
121
pub fn store_limits(&self) -> StoreLimits {
122
let mut limits = StoreLimitsBuilder::new();
123
if let Some(max) = self.common.wasm.max_memory_size {
124
limits = limits.memory_size(max);
125
}
126
if let Some(max) = self.common.wasm.max_table_elements {
127
limits = limits.table_elements(max);
128
}
129
if let Some(max) = self.common.wasm.max_instances {
130
limits = limits.instances(max);
131
}
132
if let Some(max) = self.common.wasm.max_tables {
133
limits = limits.tables(max);
134
}
135
if let Some(max) = self.common.wasm.max_memories {
136
limits = limits.memories(max);
137
}
138
if let Some(enable) = self.common.wasm.trap_on_grow_failure {
139
limits = limits.trap_on_grow_failure(enable);
140
}
141
142
limits.build()
143
}
144
145
pub fn ensure_allow_precompiled(&self) -> Result<()> {
146
if self.allow_precompiled {
147
Ok(())
148
} else {
149
bail!("running a precompiled module requires the `--allow-precompiled` flag")
150
}
151
}
152
153
#[cfg(feature = "component-model")]
154
fn ensure_allow_components(&self) -> Result<()> {
155
if self.common.wasm.component_model == Some(false) {
156
bail!("cannot execute a component without `--wasm component-model`");
157
}
158
159
Ok(())
160
}
161
162
pub fn load_module(&self, engine: &Engine, path: &Path) -> Result<RunTarget> {
163
let path = match path.to_str() {
164
#[cfg(unix)]
165
Some("-") => "/dev/stdin".as_ref(),
166
_ => path,
167
};
168
let file =
169
File::open(path).with_context(|| format!("failed to open wasm module {path:?}"))?;
170
171
// First attempt to load the module as an mmap. If this succeeds then
172
// detection can be done with the contents of the mmap and if a
173
// precompiled module is detected then `deserialize_file` can be used
174
// which is a slightly more optimal version than `deserialize` since we
175
// can leave most of the bytes on disk until they're referenced.
176
//
177
// If the mmap fails, for example if stdin is a pipe, then fall back to
178
// `std::fs::read` to load the contents. At that point precompiled
179
// modules must go through the `deserialize` functions.
180
//
181
// Note that this has the unfortunate side effect for precompiled
182
// modules on disk that they're opened once to detect what they are and
183
// then again internally in Wasmtime as part of the `deserialize_file`
184
// API. Currently there's no way to pass the `MmapVec` here through to
185
// Wasmtime itself (that'd require making `MmapVec` a public type, both
186
// which isn't ready to happen at this time). It's hoped though that
187
// opening a file twice isn't too bad in the grand scheme of things with
188
// respect to the CLI.
189
match wasmtime::_internal::MmapVec::from_file(file) {
190
Ok(map) => self.load_module_contents(
191
engine,
192
path,
193
&map,
194
|| unsafe { Module::deserialize_file(engine, path) },
195
#[cfg(feature = "component-model")]
196
|| unsafe { Component::deserialize_file(engine, path) },
197
),
198
Err(_) => {
199
let bytes = std::fs::read(path)
200
.with_context(|| format!("failed to read file: {}", path.display()))?;
201
self.load_module_contents(
202
engine,
203
path,
204
&bytes,
205
|| unsafe { Module::deserialize(engine, &bytes) },
206
#[cfg(feature = "component-model")]
207
|| unsafe { Component::deserialize(engine, &bytes) },
208
)
209
}
210
}
211
}
212
213
pub fn load_module_contents(
214
&self,
215
engine: &Engine,
216
path: &Path,
217
bytes: &[u8],
218
deserialize_module: impl FnOnce() -> Result<Module>,
219
#[cfg(feature = "component-model")] deserialize_component: impl FnOnce() -> Result<Component>,
220
) -> Result<RunTarget> {
221
Ok(match Engine::detect_precompiled(bytes) {
222
Some(Precompiled::Module) => {
223
self.ensure_allow_precompiled()?;
224
RunTarget::Core(deserialize_module()?)
225
}
226
#[cfg(feature = "component-model")]
227
Some(Precompiled::Component) => {
228
self.ensure_allow_precompiled()?;
229
self.ensure_allow_components()?;
230
RunTarget::Component(deserialize_component()?)
231
}
232
#[cfg(not(feature = "component-model"))]
233
Some(Precompiled::Component) => {
234
bail!("support for components was not enabled at compile time");
235
}
236
#[cfg(any(feature = "cranelift", feature = "winch"))]
237
None => {
238
let mut code = wasmtime::CodeBuilder::new(engine);
239
code.wasm_binary_or_text(bytes, Some(path))?;
240
match code.hint() {
241
Some(wasmtime::CodeHint::Component) => {
242
#[cfg(feature = "component-model")]
243
{
244
self.ensure_allow_components()?;
245
RunTarget::Component(code.compile_component()?)
246
}
247
#[cfg(not(feature = "component-model"))]
248
{
249
bail!("support for components was not enabled at compile time");
250
}
251
}
252
Some(wasmtime::CodeHint::Module) | None => {
253
RunTarget::Core(code.compile_module()?)
254
}
255
}
256
}
257
258
#[cfg(not(any(feature = "cranelift", feature = "winch")))]
259
None => {
260
let _ = (path, engine);
261
bail!("support for compiling modules was disabled at compile time");
262
}
263
})
264
}
265
266
pub fn configure_wasip2(&self, builder: &mut WasiCtxBuilder) -> Result<()> {
267
// It's ok to block the current thread since we're the only thread in
268
// the program as the CLI. This helps improve the performance of some
269
// blocking operations in WASI, for example, by skipping the
270
// back-and-forth between sync and async.
271
//
272
// However, do not set this if a timeout is configured, as that would
273
// cause the timeout to be ignored if the guest does, for example,
274
// something like `sleep(FOREVER)`.
275
builder.allow_blocking_current_thread(self.common.wasm.timeout.is_none());
276
277
if self.common.wasi.inherit_env == Some(true) {
278
for (k, v) in std::env::vars() {
279
builder.env(&k, &v);
280
}
281
}
282
for (key, value) in self.vars.iter() {
283
let value = match value {
284
Some(value) => value.clone(),
285
None => match std::env::var_os(key) {
286
Some(val) => val
287
.into_string()
288
.map_err(|_| anyhow!("environment variable `{key}` not valid utf-8"))?,
289
None => {
290
// leave the env var un-set in the guest
291
continue;
292
}
293
},
294
};
295
builder.env(key, &value);
296
}
297
298
for (host, guest) in self.dirs.iter() {
299
builder.preopened_dir(
300
host,
301
guest,
302
wasmtime_wasi::DirPerms::all(),
303
wasmtime_wasi::FilePerms::all(),
304
)?;
305
}
306
307
if self.common.wasi.listenfd == Some(true) {
308
bail!("components do not support --listenfd");
309
}
310
for _ in self.compute_preopen_sockets()? {
311
bail!("components do not support --tcplisten");
312
}
313
314
if self.common.wasi.inherit_network == Some(true) {
315
builder.inherit_network();
316
}
317
if let Some(enable) = self.common.wasi.allow_ip_name_lookup {
318
builder.allow_ip_name_lookup(enable);
319
}
320
if let Some(enable) = self.common.wasi.tcp {
321
builder.allow_tcp(enable);
322
}
323
if let Some(enable) = self.common.wasi.udp {
324
builder.allow_udp(enable);
325
}
326
327
Ok(())
328
}
329
330
pub fn compute_preopen_sockets(&self) -> Result<Vec<TcpListener>> {
331
let mut listeners = vec![];
332
333
for address in &self.common.wasi.tcplisten {
334
let stdlistener = std::net::TcpListener::bind(address)
335
.with_context(|| format!("failed to bind to address '{address}'"))?;
336
337
let _ = stdlistener.set_nonblocking(true)?;
338
339
listeners.push(stdlistener)
340
}
341
Ok(listeners)
342
}
343
344
pub fn validate_p3_option(&self) -> Result<()> {
345
let p3 = self.common.wasi.p3.unwrap_or(P3_DEFAULT);
346
if p3 && !cfg!(feature = "component-model-async") {
347
bail!("support for WASIp3 disabled at compile time");
348
}
349
Ok(())
350
}
351
352
pub fn validate_cli_enabled(&self) -> Result<Option<bool>> {
353
let mut cli = self.common.wasi.cli;
354
355
// Accept -Scommon as a deprecated alias for -Scli.
356
if let Some(common) = self.common.wasi.common {
357
if cli.is_some() {
358
bail!(
359
"The -Scommon option should not be use with -Scli as it is a deprecated alias"
360
);
361
} else {
362
// In the future, we may add a warning here to tell users to use
363
// `-S cli` instead of `-S common`.
364
cli = Some(common);
365
}
366
}
367
368
Ok(cli)
369
}
370
371
/// Adds `wasmtime-wasi` interfaces (dubbed "-Scli" in the flags to the
372
/// `wasmtime` command) to the `linker` provided.
373
///
374
/// This will handle adding various WASI standard versions to the linker
375
/// internally.
376
#[cfg(feature = "component-model")]
377
pub fn add_wasmtime_wasi_to_linker<T>(
378
&self,
379
linker: &mut wasmtime::component::Linker<T>,
380
) -> Result<()>
381
where
382
T: wasmtime_wasi::WasiView,
383
{
384
let mut p2_options = wasmtime_wasi::p2::bindings::LinkOptions::default();
385
p2_options.cli_exit_with_code(self.common.wasi.cli_exit_with_code.unwrap_or(false));
386
p2_options.network_error_code(self.common.wasi.network_error_code.unwrap_or(false));
387
wasmtime_wasi::p2::add_to_linker_with_options_async(linker, &p2_options)?;
388
389
#[cfg(feature = "component-model-async")]
390
if self.common.wasi.p3.unwrap_or(P3_DEFAULT) {
391
let mut p3_options = wasmtime_wasi::p3::bindings::LinkOptions::default();
392
p3_options.cli_exit_with_code(self.common.wasi.cli_exit_with_code.unwrap_or(false));
393
wasmtime_wasi::p3::add_to_linker_with_options(linker, &p3_options)
394
.context("failed to link `wasi:[email protected]`")?;
395
}
396
397
Ok(())
398
}
399
}
400
401
#[derive(Clone, PartialEq)]
402
pub enum Profile {
403
Native(wasmtime::ProfilingStrategy),
404
Guest { path: String, interval: Duration },
405
}
406
407
impl Profile {
408
/// Parse the `profile` argument to either the `run` or `serve` commands.
409
pub fn parse(s: &str) -> Result<Profile> {
410
let parts = s.split(',').collect::<Vec<_>>();
411
match &parts[..] {
412
["perfmap"] => Ok(Profile::Native(wasmtime::ProfilingStrategy::PerfMap)),
413
["jitdump"] => Ok(Profile::Native(wasmtime::ProfilingStrategy::JitDump)),
414
["vtune"] => Ok(Profile::Native(wasmtime::ProfilingStrategy::VTune)),
415
["pulley"] => Ok(Profile::Native(wasmtime::ProfilingStrategy::Pulley)),
416
["guest"] => Ok(Profile::Guest {
417
path: "wasmtime-guest-profile.json".to_string(),
418
interval: Duration::from_millis(10),
419
}),
420
["guest", path] => Ok(Profile::Guest {
421
path: path.to_string(),
422
interval: Duration::from_millis(10),
423
}),
424
["guest", path, dur] => Ok(Profile::Guest {
425
path: path.to_string(),
426
interval: WasmtimeOptionValue::parse(Some(dur))?,
427
}),
428
_ => bail!("unknown profiling strategy: {s}"),
429
}
430
}
431
}
432
433