bench-api: configure execution with a flags string (#4096)

As discussed previously, we need a way to be able to configure Wasmtime when running it in the Sightglass benchmark infrastructure. The easiest way to do this seemed to be to pass a string from Sightglass to the `bench-api` library and parse this in the same way that Wasmtime parses its CLI flags. The structure that contains these flags is `CommonOptions`, so it has been moved to its own crate to be depended on by both `wasmtime-cli` and `wasmtime-bench-api`. Also, this change adds an externally-visible function for parsing a string into `CommonOptions`, which is used for configuring an engine.
This commit is contained in:
Andrew Brown
2022-05-04 16:30:39 -07:00
committed by GitHub
parent 527b7a9b05
commit 5c3642fcb1
13 changed files with 1093 additions and 744 deletions

124
Cargo.lock generated
View File

@@ -237,9 +237,9 @@ checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]]
name = "cap-fs-ext"
version = "0.24.2"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16812cf8cc096ebfddba83ad6cc768635f4d53eb6bd060f7b80866556493874a"
checksum = "de96a353a1b625fae721c0274552533a75d4961e2f313d5a873076c964e9982e"
dependencies = [
"cap-primitives",
"cap-std",
@@ -249,9 +249,9 @@ dependencies = [
[[package]]
name = "cap-primitives"
version = "0.24.2"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef65294d0067dd0167a84e5d50aaa52d999ab4fc7cfa59ff2ad3906afb033b36"
checksum = "7217718088981caa36e35a01307daeeaad2f6e6e188bf4149d35029dad1c08a2"
dependencies = [
"ambient-authority",
"errno",
@@ -268,9 +268,9 @@ dependencies = [
[[package]]
name = "cap-rand"
version = "0.24.2"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a124986fcbe880abe9ca6aad4189de44b37dbcf6ac0079f8dd9f94b379484b94"
checksum = "575e96a49058d34b2d75caa6ef677d35569add0fcb16cf7866d1a47a35649a87"
dependencies = [
"ambient-authority",
"rand 0.8.5",
@@ -278,9 +278,9 @@ dependencies = [
[[package]]
name = "cap-std"
version = "0.24.2"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3430d1b5ba78fa381eb227525578d2b85d77b42ff49be85d1e72a94f305e603c"
checksum = "5d684df5773e4af5c343c466f47151db7e7a4366daab609b4a6bb7a75aecf732"
dependencies = [
"cap-primitives",
"io-extras",
@@ -291,20 +291,21 @@ dependencies = [
[[package]]
name = "cap-tempfile"
version = "0.24.2"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1857f2acd81cd0dafd729ec386032900863bc0c542188df48f761b6fab5bb41"
checksum = "1def2a81a97ba5f361944b55a96ab0ccf0b3b64bd829c12f20f4bf709ce2ab03"
dependencies = [
"cap-std",
"rand 0.8.5",
"uuid",
"rustix",
"uuid 1.0.0",
]
[[package]]
name = "cap-time-ext"
version = "0.24.2"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ee4390904645f384bd4e03125a4ed4d1167e9f195931c41323bacc8a2328ce"
checksum = "4ed053e759cc9bb1c2cbdb53d029c76c56820787a65619579b9a7147eaaf307b"
dependencies = [
"cap-primitives",
"once_cell",
@@ -403,9 +404,9 @@ dependencies = [
[[package]]
name = "clap"
version = "3.1.12"
version = "3.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c167e37342afc5f33fd87bbc870cedd020d2a6dffa05d45ccd9241fbdd146db"
checksum = "85a35a599b11c089a7f49105658d089b8f2cf0882993c17daf6de15285c2c35d"
dependencies = [
"atty",
"bitflags",
@@ -433,9 +434,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.1.1"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "189ddd3b5d32a70b35e7686054371742a937b0d99128e76dde6340210e966669"
checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213"
dependencies = [
"os_str_bytes",
]
@@ -678,7 +679,7 @@ dependencies = [
name = "cranelift-serde"
version = "0.84.0"
dependencies = [
"clap 3.1.12",
"clap 3.1.15",
"cranelift-codegen",
"cranelift-reader",
"serde_json",
@@ -691,7 +692,7 @@ dependencies = [
"anyhow",
"capstone",
"cfg-if",
"clap 3.1.12",
"clap 3.1.15",
"cranelift",
"cranelift-codegen",
"cranelift-entity",
@@ -1457,7 +1458,7 @@ dependencies = [
name = "islec"
version = "0.1.0"
dependencies = [
"clap 3.1.12",
"clap 3.1.15",
"cranelift-isle",
"env_logger 0.9.0",
"miette",
@@ -1540,9 +1541,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
[[package]]
name = "libc"
version = "0.2.124"
version = "0.2.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50"
checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b"
[[package]]
name = "libfuzzer-sys"
@@ -1584,7 +1585,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e514e2cb8a9624701346ea3e694c1766d76778e343e537d873c1c366e79a7"
dependencies = [
"libc",
"uuid",
"uuid 0.8.2",
"winapi",
]
@@ -1600,9 +1601,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.16"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
@@ -1624,9 +1625,9 @@ checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4"
[[package]]
name = "memchr"
version = "2.4.1"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memfd"
@@ -1770,9 +1771,9 @@ dependencies = [
[[package]]
name = "num-integer"
version = "0.1.44"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg 1.1.0",
"num-traits",
@@ -1803,9 +1804,9 @@ dependencies = [
[[package]]
name = "num-traits"
version = "0.2.14"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg 1.1.0",
"libm",
@@ -2500,15 +2501,15 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "semver"
version = "1.0.7"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d65bd28f48be7196d222d95b9243287f48d27aca604e08497513019ff0502cc4"
checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd"
[[package]]
name = "serde"
version = "1.0.136"
version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
dependencies = [
"serde_derive",
]
@@ -2525,9 +2526,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.136"
version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
dependencies = [
"proc-macro2",
"quote",
@@ -2536,9 +2537,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.79"
version = "1.0.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
checksum = "f972498cf015f7c0746cac89ebe1d6ef10c293b94175a243a2d9442c163d9944"
dependencies = [
"itoa 1.0.1",
"ryu",
@@ -2710,9 +2711,9 @@ checksum = "7c68d531d83ec6c531150584c42a4290911964d5f0d79132b193b67252a23b71"
[[package]]
name = "syn"
version = "1.0.91"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d"
checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52"
dependencies = [
"proc-macro2",
"quote",
@@ -2833,18 +2834,18 @@ checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]]
name = "thiserror"
version = "1.0.30"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.30"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
dependencies = [
"proc-macro2",
"quote",
@@ -2872,9 +2873,9 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.18.0"
version = "1.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f48b6d60512a392e34dbf7fd456249fd2de3c83669ab642e021903f4015185b"
checksum = "dce653fb475565de9f6fb0614b28bca8df2c430c0cf84bcd9c843f15de5414cc"
dependencies = [
"bytes",
"libc",
@@ -2990,9 +2991,9 @@ checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
version = "0.2.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
[[package]]
name = "universal-hash"
@@ -3009,6 +3010,12 @@ name = "uuid"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
[[package]]
name = "uuid"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cfcd319456c4d6ea10087ed423473267e1a071f3bc0aa89f80d60997843c6f0"
dependencies = [
"getrandom 0.2.6",
]
@@ -3348,8 +3355,10 @@ dependencies = [
"anyhow",
"cap-std",
"shuffling-allocator",
"target-lexicon",
"wasi-cap-std-sync",
"wasmtime",
"wasmtime-cli-flags",
"wasmtime-wasi",
"wasmtime-wasi-crypto",
"wasmtime-wasi-nn",
@@ -3408,10 +3417,9 @@ version = "0.37.0"
dependencies = [
"anyhow",
"async-trait",
"clap 3.1.12",
"clap 3.1.15",
"criterion",
"env_logger 0.9.0",
"file-per-thread-logger",
"filecheck",
"humantime 2.1.0",
"lazy_static",
@@ -3421,7 +3429,6 @@ dependencies = [
"more-asserts",
"num_cpus",
"once_cell",
"pretty_env_logger",
"rayon",
"rustix",
"target-lexicon",
@@ -3432,6 +3439,7 @@ dependencies = [
"wasmparser",
"wasmtime",
"wasmtime-cache",
"wasmtime-cli-flags",
"wasmtime-cranelift",
"wasmtime-environ",
"wasmtime-runtime",
@@ -3444,6 +3452,18 @@ dependencies = [
"winapi",
]
[[package]]
name = "wasmtime-cli-flags"
version = "0.37.0"
dependencies = [
"anyhow",
"clap 3.1.15",
"file-per-thread-logger",
"pretty_env_logger",
"rayon",
"wasmtime",
]
[[package]]
name = "wasmtime-cranelift"
version = "0.37.0"

View File

@@ -23,6 +23,7 @@ doc = false
[dependencies]
wasmtime = { path = "crates/wasmtime", version = "0.37.0", default-features = false, features = ['cache', 'cranelift'] }
wasmtime-cache = { path = "crates/cache", version = "=0.37.0" }
wasmtime-cli-flags = { path = "crates/cli-flags", version = "=0.37.0" }
wasmtime-cranelift = { path = "crates/cranelift", version = "=0.37.0" }
wasmtime-environ = { path = "crates/environ", version = "=0.37.0" }
wasmtime-wast = { path = "crates/wast", version = "=0.37.0" }
@@ -32,10 +33,7 @@ wasmtime-wasi-nn = { path = "crates/wasi-nn", version = "0.37.0", optional = tru
clap = { version = "3.1.12", features = ["color", "suggestions", "derive"] }
anyhow = "1.0.19"
target-lexicon = { version = "0.12.0", default-features = false }
pretty_env_logger = "0.4.0"
file-per-thread-logger = "0.1.1"
libc = "0.2.60"
rayon = "1.5.0"
humantime = "2.0.0"
wasmparser = "0.84.0"
lazy_static = "1.4.0"
@@ -63,6 +61,7 @@ memchr = "2.4"
async-trait = "0.1"
wat = "1.0.42"
once_cell = "1.9.0"
rayon = "1.5.0"
[build-dependencies]
anyhow = "1.0.19"
@@ -79,6 +78,7 @@ members = [
"cranelift/serde",
"crates/bench-api",
"crates/c-api",
"crates/cli-flags",
"crates/misc/run-examples",
"examples/fib-debug/wasm",
"examples/wasi/wasm",
@@ -105,11 +105,11 @@ jitdump = ["wasmtime/jitdump"]
vtune = ["wasmtime/vtune"]
wasi-crypto = ["wasmtime-wasi-crypto"]
wasi-nn = ["wasmtime-wasi-nn"]
memory-init-cow = ["wasmtime/memory-init-cow"]
pooling-allocator = ["wasmtime/pooling-allocator"]
memory-init-cow = ["wasmtime/memory-init-cow", "wasmtime-cli-flags/memory-init-cow"]
pooling-allocator = ["wasmtime/pooling-allocator", "wasmtime-cli-flags/pooling-allocator"]
all-arch = ["wasmtime/all-arch"]
posix-signals-on-macos = ["wasmtime/posix-signals-on-macos"]
wasm-backtrace = ["wasmtime/wasm-backtrace"]
wasm-backtrace = ["wasmtime/wasm-backtrace", "wasmtime-cli-flags/wasm-backtrace"]
# Stub feature that does nothing, for Cargo-features compatibility: the new
# backend is the default now.

View File

@@ -17,7 +17,9 @@ crate-type = ["rlib", "cdylib"]
[dependencies]
anyhow = "1.0"
shuffling-allocator = { version = "1.1.1", optional = true }
target-lexicon = "0.12"
wasmtime = { path = "../wasmtime", default-features = true }
wasmtime-cli-flags = { path = "../cli-flags", default-features = true }
wasmtime-wasi = { path = "../wasi" }
wasmtime-wasi-crypto = { path = "../wasi-crypto", optional = true }
wasmtime-wasi-nn = { path = "../wasi-nn", optional = true }

View File

@@ -90,6 +90,8 @@
//! execution_timer: ptr::null_mut(),
//! execution_start,
//! execution_end,
//! execution_flags_ptr: ptr::null(),
//! execution_flags_len: 0,
//! };
//!
//! let mut bench_api = ptr::null_mut();
@@ -138,7 +140,9 @@ use anyhow::{anyhow, Context, Result};
use std::os::raw::{c_int, c_void};
use std::slice;
use std::{env, path::PathBuf};
use target_lexicon::Triple;
use wasmtime::{Config, Engine, Instance, Linker, Module, Store};
use wasmtime_cli_flags::CommonOptions;
use wasmtime_wasi::{sync::WasiCtxBuilder, WasiCtx};
pub type ExitCode = c_int;
@@ -190,6 +194,11 @@ pub struct WasmBenchConfig {
pub execution_timer: *mut u8,
pub execution_start: extern "C" fn(*mut u8),
pub execution_end: extern "C" fn(*mut u8),
/// The (optional) flags to use when running Wasmtime. These correspond to
/// the flags used when running Wasmtime from the command line.
pub execution_flags_ptr: *const u8,
pub execution_flags_len: usize,
}
impl WasmBenchConfig {
@@ -228,6 +237,22 @@ impl WasmBenchConfig {
std::str::from_utf8(stdin_path).context("given stdin path is not valid UTF-8")?;
Ok(Some(stdin_path.into()))
}
fn execution_flags(&self) -> Result<Option<Config>> {
if self.execution_flags_ptr.is_null() {
return Ok(None);
}
let execution_flags = unsafe {
std::slice::from_raw_parts(self.execution_flags_ptr, self.execution_flags_len)
};
let execution_flags = std::str::from_utf8(execution_flags)
.context("given execution flags string is not valid UTF-8")?;
let options = CommonOptions::parse_from_str(execution_flags)?;
let config = options.config(Some(&Triple::host().to_string()))?;
Ok(Some(config))
}
}
/// Exposes a C-compatible way of creating the engine from the bytes of a single
@@ -256,8 +281,10 @@ pub extern "C" fn wasm_bench_create(
let stdout_path = config.stdout_path()?;
let stderr_path = config.stderr_path()?;
let stdin_path = config.stdin_path()?;
let engine_config = config.execution_flags()?;
let state = Box::new(BenchState::new(
engine_config,
config.compilation_timer,
config.compilation_start,
config.compilation_end,
@@ -393,6 +420,7 @@ struct HostState {
impl BenchState {
fn new(
engine_config: Option<Config>,
compilation_timer: *mut u8,
compilation_start: extern "C" fn(*mut u8),
compilation_end: extern "C" fn(*mut u8),
@@ -405,9 +433,7 @@ impl BenchState {
make_wasi_cx: impl FnMut() -> Result<WasiCtx> + 'static,
) -> Result<Self> {
// NB: do not configure a code cache.
let mut config = Config::new();
config.wasm_simd(true);
let engine = Engine::new(&config)?;
let engine = Engine::new(&engine_config.unwrap_or(Config::new()))?;
let mut linker = Linker::<HostState>::new(&engine);
// Define the benchmarking start/end functions.

View File

@@ -0,0 +1,28 @@
[package]
name = "wasmtime-cli-flags"
version = "0.37.0"
authors = ["The Wasmtime Project Developers"]
description = "Exposes common CLI flags used for running Wasmtime"
license = "Apache-2.0 WITH LLVM-exception"
repository = "https://github.com/bytecodealliance/wasmtime"
documentation = "https://docs.rs/wasmtime-cache/"
edition = "2021"
[dependencies]
anyhow = "1.0.19"
clap = { version = "3.1.12", features = ["color", "suggestions", "derive"] }
file-per-thread-logger = "0.1.1"
pretty_env_logger = "0.4.0"
rayon = "1.5.0"
wasmtime = { path = "../wasmtime", version = "0.37.0", default-features = false }
[features]
default = [
"wasmtime/cache",
"wasmtime/cranelift",
"wasmtime/jitdump",
"wasmtime/vtune",
]
pooling-allocator = []
memory-init-cow = []
wasm-backtrace = []

220
crates/cli-flags/LICENSE Normal file
View File

@@ -0,0 +1,220 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--- LLVM Exceptions to the Apache 2.0 License ----
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into an Object form of such source code, you
may redistribute such embedded portions in such Object form without complying
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
In addition, if you combine or link compiled forms of this Software with
software that is licensed under the GPLv2 ("Combined Software") and if a
court of competent jurisdiction determines that the patent provision (Section
3), the indemnity provision (Section 9) or other Section of the License
conflicts with the conditions of the GPLv2, you may retroactively and
prospectively choose to deem waived or otherwise exclude such Section(s) of
the License, but only in their entirety and only with respect to the Combined
Software.

723
crates/cli-flags/src/lib.rs Normal file
View File

@@ -0,0 +1,723 @@
//! Contains the common Wasmtime command line interface (CLI) flags.
#![deny(trivial_numeric_casts, unused_extern_crates, unstable_features)]
#![warn(unused_import_braces)]
#![cfg_attr(feature = "clippy", plugin(clippy(conf_file = "../../clippy.toml")))]
#![cfg_attr(feature = "cargo-clippy", allow(clippy::new_without_default))]
#![cfg_attr(
feature = "cargo-clippy",
warn(
clippy::float_arithmetic,
clippy::mut_mut,
clippy::nonminimal_bool,
clippy::map_unwrap_or,
clippy::unicode_not_nfc,
clippy::use_self
)
)]
use anyhow::{bail, Context, Result};
use clap::Parser;
use std::collections::HashMap;
use std::path::PathBuf;
use wasmtime::{Config, ProfilingStrategy};
#[cfg(feature = "pooling-allocator")]
use wasmtime::{InstanceLimits, PoolingAllocationStrategy};
pub const SUPPORTED_WASM_FEATURES: &[(&str, &str)] = &[
("all", "enables all supported WebAssembly features"),
(
"bulk-memory",
"enables support for bulk memory instructions",
),
(
"multi-memory",
"enables support for the multi-memory proposal",
),
("multi-value", "enables support for multi-value functions"),
("reference-types", "enables support for reference types"),
("simd", "enables support for proposed SIMD instructions"),
("threads", "enables support for WebAssembly threads"),
("memory64", "enables support for 64-bit memories"),
];
pub const SUPPORTED_WASI_MODULES: &[(&str, &str)] = &[
(
"default",
"enables all stable WASI modules (no experimental modules)",
),
(
"wasi-common",
"enables support for the WASI common APIs, see https://github.com/WebAssembly/WASI",
),
(
"experimental-wasi-nn",
"enables support for the WASI neural network API (experimental), see https://github.com/WebAssembly/wasi-nn",
),
(
"experimental-wasi-crypto",
"enables support for the WASI cryptography APIs (experimental), see https://github.com/WebAssembly/wasi-crypto",
),
];
fn pick_profiling_strategy(jitdump: bool, vtune: bool) -> Result<ProfilingStrategy> {
Ok(match (jitdump, vtune) {
(true, false) => ProfilingStrategy::JitDump,
(false, true) => ProfilingStrategy::VTune,
(true, true) => {
println!("Can't enable --jitdump and --vtune at the same time. Profiling not enabled.");
ProfilingStrategy::None
}
_ => ProfilingStrategy::None,
})
}
fn init_file_per_thread_logger(prefix: &'static str) {
file_per_thread_logger::initialize(prefix);
// Extending behavior of default spawner:
// https://docs.rs/rayon/1.1.0/rayon/struct.ThreadPoolBuilder.html#method.spawn_handler
// Source code says DefaultSpawner is implementation detail and
// shouldn't be used directly.
rayon::ThreadPoolBuilder::new()
.spawn_handler(move |thread| {
let mut b = std::thread::Builder::new();
if let Some(name) = thread.name() {
b = b.name(name.to_owned());
}
if let Some(stack_size) = thread.stack_size() {
b = b.stack_size(stack_size);
}
b.spawn(move || {
file_per_thread_logger::initialize(prefix);
thread.run()
})?;
Ok(())
})
.build_global()
.unwrap();
}
/// Common options for commands that translate WebAssembly modules
#[derive(Parser)]
#[cfg_attr(test, derive(Debug, PartialEq))]
pub struct CommonOptions {
/// Use specified configuration file
#[clap(long, parse(from_os_str), value_name = "CONFIG_PATH")]
pub config: Option<PathBuf>,
/// Disable logging.
#[clap(long, conflicts_with = "log-to-files")]
pub disable_logging: bool,
/// Log to per-thread log files instead of stderr.
#[clap(long)]
pub log_to_files: bool,
/// Generate debug information
#[clap(short = 'g')]
pub debug_info: bool,
/// Disable cache system
#[clap(long)]
pub disable_cache: bool,
/// Enables or disables WebAssembly features
#[clap(long, value_name = "FEATURE,FEATURE,...", parse(try_from_str = parse_wasm_features))]
pub wasm_features: Option<WasmFeatures>,
/// Enables or disables WASI modules
#[clap(long, value_name = "MODULE,MODULE,...", parse(try_from_str = parse_wasi_modules))]
pub wasi_modules: Option<WasiModules>,
/// Generate jitdump file (supported on --features=profiling build)
#[clap(long, conflicts_with = "vtune")]
pub jitdump: bool,
/// Generate vtune (supported on --features=vtune build)
#[clap(long, conflicts_with = "jitdump")]
pub vtune: bool,
/// Run optimization passes on translated functions, on by default
#[clap(short = 'O', long)]
pub optimize: bool,
/// Optimization level for generated functions
/// Supported levels: 0 (none), 1, 2 (most), or s (size); default is "most"
#[clap(
long,
value_name = "LEVEL",
parse(try_from_str = parse_opt_level),
verbatim_doc_comment,
)]
pub opt_level: Option<wasmtime::OptLevel>,
/// Set a Cranelift setting to a given value.
/// Use `wasmtime settings` to list Cranelift settings for a target.
#[clap(long = "cranelift-set", value_name = "NAME=VALUE", number_of_values = 1, verbatim_doc_comment, parse(try_from_str = parse_cranelift_flag))]
pub cranelift_set: Vec<(String, String)>,
/// Enable a Cranelift boolean setting or preset.
/// Use `wasmtime settings` to list Cranelift settings for a target.
#[clap(
long,
value_name = "SETTING",
number_of_values = 1,
verbatim_doc_comment
)]
pub cranelift_enable: Vec<String>,
/// Maximum size in bytes of wasm memory before it becomes dynamically
/// relocatable instead of up-front-reserved.
#[clap(long, value_name = "MAXIMUM")]
pub static_memory_maximum_size: Option<u64>,
/// Force using a "static" style for all wasm memories.
#[clap(long)]
pub static_memory_forced: bool,
/// Byte size of the guard region after static memories are allocated.
#[clap(long, value_name = "SIZE")]
pub static_memory_guard_size: Option<u64>,
/// Byte size of the guard region after dynamic memories are allocated.
#[clap(long, value_name = "SIZE")]
pub dynamic_memory_guard_size: Option<u64>,
/// Enable Cranelift's internal debug verifier (expensive)
#[clap(long)]
pub enable_cranelift_debug_verifier: bool,
/// Enable Cranelift's internal NaN canonicalization
#[clap(long)]
pub enable_cranelift_nan_canonicalization: bool,
/// Enable execution fuel with N units fuel, where execution will trap after
/// running out of fuel.
///
/// Most WebAssembly instructions consume 1 unit of fuel. Some instructions,
/// such as `nop`, `drop`, `block`, and `loop`, consume 0 units, as any
/// execution cost associated with them involves other instructions which do
/// consume fuel.
#[clap(long, value_name = "N")]
pub fuel: Option<u64>,
/// Executing wasm code will yield when a global epoch counter
/// changes, allowing for async operation without blocking the
/// executor.
#[clap(long)]
pub epoch_interruption: bool,
/// Disables the on-by-default address map from native code to wasm code.
#[clap(long)]
pub disable_address_map: bool,
/// Disables the default of attempting to initialize linear memory via a
/// copy-on-write mapping.
#[cfg(feature = "memory-init-cow")]
#[clap(long)]
pub disable_memory_init_cow: bool,
/// Enables the pooling allocator, in place of the on-demand
/// allocator.
#[cfg(feature = "pooling-allocator")]
#[clap(long)]
pub pooling_allocator: bool,
}
impl CommonOptions {
pub fn parse_from_str(s: &str) -> Result<Self> {
let parts = s.split(" ");
let options =
Self::try_parse_from(parts).context("unable to parse options from passed flags")?;
Ok(options)
}
pub fn init_logging(&self) {
if self.disable_logging {
return;
}
if self.log_to_files {
let prefix = "wasmtime.dbg.";
init_file_per_thread_logger(prefix);
} else {
pretty_env_logger::init();
}
}
pub fn config(&self, target: Option<&str>) -> Result<Config> {
let mut config = Config::new();
// Set the target before setting any cranelift options, since the
// target will reset any target-specific options.
if let Some(target) = target {
config.target(target)?;
}
config
.cranelift_debug_verifier(self.enable_cranelift_debug_verifier)
.debug_info(self.debug_info)
.cranelift_opt_level(self.opt_level())
.profiler(pick_profiling_strategy(self.jitdump, self.vtune)?)?
.cranelift_nan_canonicalization(self.enable_cranelift_nan_canonicalization);
self.enable_wasm_features(&mut config);
for name in &self.cranelift_enable {
unsafe {
config.cranelift_flag_enable(name)?;
}
}
for (name, value) in &self.cranelift_set {
unsafe {
config.cranelift_flag_set(name, value)?;
}
}
if !self.disable_cache {
match &self.config {
Some(path) => {
config.cache_config_load(path)?;
}
None => {
config.cache_config_load_default()?;
}
}
}
if let Some(max) = self.static_memory_maximum_size {
config.static_memory_maximum_size(max);
}
config.static_memory_forced(self.static_memory_forced);
if let Some(size) = self.static_memory_guard_size {
config.static_memory_guard_size(size);
}
if let Some(size) = self.dynamic_memory_guard_size {
config.dynamic_memory_guard_size(size);
}
// If fuel has been configured, set the `consume fuel` flag on the config.
if self.fuel.is_some() {
config.consume_fuel(true);
}
config.epoch_interruption(self.epoch_interruption);
config.generate_address_map(!self.disable_address_map);
#[cfg(feature = "memory-init-cow")]
config.memory_init_cow(!self.disable_memory_init_cow);
#[cfg(feature = "pooling-allocator")]
{
if self.pooling_allocator {
let instance_limits = InstanceLimits::default();
config.allocation_strategy(wasmtime::InstanceAllocationStrategy::Pooling {
strategy: PoolingAllocationStrategy::NextAvailable,
instance_limits,
});
}
}
Ok(config)
}
pub fn enable_wasm_features(&self, config: &mut Config) {
let WasmFeatures {
simd,
bulk_memory,
reference_types,
multi_value,
threads,
multi_memory,
memory64,
} = self.wasm_features.unwrap_or_default();
if let Some(enable) = simd {
config.wasm_simd(enable);
}
if let Some(enable) = bulk_memory {
config.wasm_bulk_memory(enable);
}
if let Some(enable) = reference_types {
#[cfg(feature = "wasm-backtrace")]
config.wasm_reference_types(enable);
drop(enable); // suppress unused warnings
}
if let Some(enable) = multi_value {
config.wasm_multi_value(enable);
}
if let Some(enable) = threads {
config.wasm_threads(enable);
}
if let Some(enable) = multi_memory {
config.wasm_multi_memory(enable);
}
if let Some(enable) = memory64 {
config.wasm_memory64(enable);
}
}
pub fn opt_level(&self) -> wasmtime::OptLevel {
match (self.optimize, self.opt_level.clone()) {
(true, _) => wasmtime::OptLevel::Speed,
(false, other) => other.unwrap_or(wasmtime::OptLevel::Speed),
}
}
}
fn parse_opt_level(opt_level: &str) -> Result<wasmtime::OptLevel> {
match opt_level {
"s" => Ok(wasmtime::OptLevel::SpeedAndSize),
"0" => Ok(wasmtime::OptLevel::None),
"1" => Ok(wasmtime::OptLevel::Speed),
"2" => Ok(wasmtime::OptLevel::Speed),
other => bail!(
"unknown optimization level `{}`, only 0,1,2,s accepted",
other
),
}
}
#[derive(Default, Clone, Copy)]
#[cfg_attr(test, derive(Debug, PartialEq))]
pub struct WasmFeatures {
pub reference_types: Option<bool>,
pub multi_value: Option<bool>,
pub bulk_memory: Option<bool>,
pub simd: Option<bool>,
pub threads: Option<bool>,
pub multi_memory: Option<bool>,
pub memory64: Option<bool>,
}
fn parse_wasm_features(features: &str) -> Result<WasmFeatures> {
let features = features.trim();
let mut all = None;
let mut values: HashMap<_, _> = SUPPORTED_WASM_FEATURES
.iter()
.map(|(name, _)| (name.to_string(), None))
.collect();
if features == "all" {
all = Some(true);
} else if features == "-all" {
all = Some(false);
} else {
for feature in features.split(',') {
let feature = feature.trim();
if feature.is_empty() {
continue;
}
let (feature, value) = if feature.starts_with('-') {
(&feature[1..], false)
} else {
(feature, true)
};
if feature == "all" {
bail!("'all' cannot be specified with other WebAssembly features");
}
match values.get_mut(feature) {
Some(v) => *v = Some(value),
None => bail!("unsupported WebAssembly feature '{}'", feature),
}
}
}
Ok(WasmFeatures {
reference_types: all.or(values["reference-types"]),
multi_value: all.or(values["multi-value"]),
bulk_memory: all.or(values["bulk-memory"]),
simd: all.or(values["simd"]),
threads: all.or(values["threads"]),
multi_memory: all.or(values["multi-memory"]),
memory64: all.or(values["memory64"]),
})
}
fn parse_wasi_modules(modules: &str) -> Result<WasiModules> {
let modules = modules.trim();
match modules {
"default" => Ok(WasiModules::default()),
"-default" => Ok(WasiModules::none()),
_ => {
// Starting from the default set of WASI modules, enable or disable a list of
// comma-separated modules.
let mut wasi_modules = WasiModules::default();
let mut set = |module: &str, enable: bool| match module {
"" => Ok(()),
"wasi-common" => Ok(wasi_modules.wasi_common = enable),
"experimental-wasi-nn" => Ok(wasi_modules.wasi_nn = enable),
"experimental-wasi-crypto" => Ok(wasi_modules.wasi_crypto = enable),
"default" => bail!("'default' cannot be specified with other WASI modules"),
_ => bail!("unsupported WASI module '{}'", module),
};
for module in modules.split(',') {
let module = module.trim();
let (module, value) = if module.starts_with('-') {
(&module[1..], false)
} else {
(module, true)
};
set(module, value)?;
}
Ok(wasi_modules)
}
}
}
/// Select which WASI modules are available at runtime for use by Wasm programs.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct WasiModules {
/// Enable the wasi-common implementation; eventually this should be split into its separate
/// parts once the implementation allows for it (e.g. wasi-fs, wasi-clocks, etc.).
pub wasi_common: bool,
/// Enable the experimental wasi-nn implementation.
pub wasi_nn: bool,
/// Enable the experimental wasi-crypto implementation.
pub wasi_crypto: bool,
}
impl Default for WasiModules {
fn default() -> Self {
Self {
wasi_common: true,
wasi_nn: false,
wasi_crypto: false,
}
}
}
impl WasiModules {
/// Enable no modules.
pub fn none() -> Self {
Self {
wasi_common: false,
wasi_nn: false,
wasi_crypto: false,
}
}
}
fn parse_cranelift_flag(name_and_value: &str) -> Result<(String, String)> {
let mut split = name_and_value.splitn(2, '=');
let name = if let Some(name) = split.next() {
name.to_string()
} else {
bail!("missing name in cranelift flag");
};
let value = if let Some(value) = split.next() {
value.to_string()
} else {
bail!("missing value in cranelift flag");
};
Ok((name, value))
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_all_features() -> Result<()> {
let options = CommonOptions::try_parse_from(vec!["foo", "--wasm-features=all"])?;
let WasmFeatures {
reference_types,
multi_value,
bulk_memory,
simd,
threads,
multi_memory,
memory64,
} = options.wasm_features.unwrap();
assert_eq!(reference_types, Some(true));
assert_eq!(multi_value, Some(true));
assert_eq!(bulk_memory, Some(true));
assert_eq!(simd, Some(true));
assert_eq!(threads, Some(true));
assert_eq!(multi_memory, Some(true));
assert_eq!(memory64, Some(true));
Ok(())
}
#[test]
fn test_no_features() -> Result<()> {
let options = CommonOptions::try_parse_from(vec!["foo", "--wasm-features=-all"])?;
let WasmFeatures {
reference_types,
multi_value,
bulk_memory,
simd,
threads,
multi_memory,
memory64,
} = options.wasm_features.unwrap();
assert_eq!(reference_types, Some(false));
assert_eq!(multi_value, Some(false));
assert_eq!(bulk_memory, Some(false));
assert_eq!(simd, Some(false));
assert_eq!(threads, Some(false));
assert_eq!(multi_memory, Some(false));
assert_eq!(memory64, Some(false));
Ok(())
}
#[test]
fn test_multiple_features() -> Result<()> {
let options = CommonOptions::try_parse_from(vec![
"foo",
"--wasm-features=-reference-types,simd,multi-memory,memory64",
])?;
let WasmFeatures {
reference_types,
multi_value,
bulk_memory,
simd,
threads,
multi_memory,
memory64,
} = options.wasm_features.unwrap();
assert_eq!(reference_types, Some(false));
assert_eq!(multi_value, None);
assert_eq!(bulk_memory, None);
assert_eq!(simd, Some(true));
assert_eq!(threads, None);
assert_eq!(multi_memory, Some(true));
assert_eq!(memory64, Some(true));
Ok(())
}
macro_rules! feature_test {
($test_name:ident, $name:ident, $flag:literal) => {
#[test]
fn $test_name() -> Result<()> {
let options =
CommonOptions::try_parse_from(vec!["foo", concat!("--wasm-features=", $flag)])?;
let WasmFeatures { $name, .. } = options.wasm_features.unwrap();
assert_eq!($name, Some(true));
let options = CommonOptions::try_parse_from(vec![
"foo",
concat!("--wasm-features=-", $flag),
])?;
let WasmFeatures { $name, .. } = options.wasm_features.unwrap();
assert_eq!($name, Some(false));
Ok(())
}
};
}
feature_test!(
test_reference_types_feature,
reference_types,
"reference-types"
);
feature_test!(test_multi_value_feature, multi_value, "multi-value");
feature_test!(test_bulk_memory_feature, bulk_memory, "bulk-memory");
feature_test!(test_simd_feature, simd, "simd");
feature_test!(test_threads_feature, threads, "threads");
feature_test!(test_multi_memory_feature, multi_memory, "multi-memory");
feature_test!(test_memory64_feature, memory64, "memory64");
#[test]
fn test_default_modules() {
let options = CommonOptions::try_parse_from(vec!["foo", "--wasi-modules=default"]).unwrap();
assert_eq!(
options.wasi_modules.unwrap(),
WasiModules {
wasi_common: true,
wasi_nn: false,
wasi_crypto: false
}
);
}
#[test]
fn test_empty_modules() {
let options = CommonOptions::try_parse_from(vec!["foo", "--wasi-modules="]).unwrap();
assert_eq!(
options.wasi_modules.unwrap(),
WasiModules {
wasi_common: true,
wasi_nn: false,
wasi_crypto: false
}
);
}
#[test]
fn test_some_modules() {
let options = CommonOptions::try_parse_from(vec![
"foo",
"--wasi-modules=experimental-wasi-nn,-wasi-common",
])
.unwrap();
assert_eq!(
options.wasi_modules.unwrap(),
WasiModules {
wasi_common: false,
wasi_nn: true,
wasi_crypto: false
}
);
}
#[test]
fn test_no_modules() {
let options =
CommonOptions::try_parse_from(vec!["foo", "--wasi-modules=-default"]).unwrap();
assert_eq!(
options.wasi_modules.unwrap(),
WasiModules {
wasi_common: false,
wasi_nn: false,
wasi_crypto: false
}
);
}
#[test]
fn test_parse_from_str() {
fn use_func(flags: &str) -> CommonOptions {
CommonOptions::parse_from_str(flags).unwrap()
}
fn use_clap_parser(flags: &[&str]) -> CommonOptions {
CommonOptions::try_parse_from(flags).unwrap()
}
assert_eq!(use_func(""), use_clap_parser(&[]));
assert_eq!(
use_func("foo --wasm-features=threads"),
use_clap_parser(&["foo", "--wasm-features=threads"])
);
assert_eq!(
use_func("foo --cranelift-set enable_simd=true"),
use_clap_parser(&["foo", "--cranelift-set", "enable_simd=true"])
);
}
}

View File

@@ -40,4 +40,9 @@ skip-tree = [
# This is somewhat unmaintained at this point and seems to pull in an old
# version of `env_logger`, so ignore it.
{ name = "pretty_env_logger", depth = 20 },
# This crate depends on an old version of the `uuid` crate: `wasmtime-cli ->
# listenfd -> uuid`. Once `listenfd` upgrades to `uuid` v1.0.0, this can be
# removed.
{ name = "listenfd", depth = 20 },
]

View File

@@ -53,11 +53,12 @@ const CRATES_TO_PUBLISH: &[&str] = &[
"wasi-common",
"wasi-cap-std-sync",
"wasi-tokio",
// other mic wasmtime crates
// other misc wasmtime crates
"wasmtime-wasi",
"wasmtime-wasi-nn",
"wasmtime-wasi-crypto",
"wasmtime-wast",
"wasmtime-cli-flags",
"wasmtime-cli",
];
@@ -76,6 +77,7 @@ const PUBLIC_CRATES: &[&str] = &[
"wasmtime-wasi",
"wasmtime-wasi-nn",
"wasmtime-wasi-crypto",
"wasmtime-cli-flags",
"wasmtime-cli",
// all cranelift crates are considered "public" in that they can't
// have breaking API changes in patch releases
@@ -452,9 +454,7 @@ fn verify(crates: &[Crate]) {
.arg("--manifest-path")
.arg(&krate.manifest)
.env("CARGO_TARGET_DIR", "./target");
if krate.name == "witx"
|| krate.name.contains("wasi-nn")
{
if krate.name == "witx" || krate.name.contains("wasi-nn") {
cmd.arg("--no-verify");
}
let status = cmd.status().unwrap();

View File

@@ -1,12 +1,12 @@
//! The module that implements the `wasmtime compile` command.
use crate::CommonOptions;
use anyhow::{bail, Context, Result};
use clap::Parser;
use std::fs;
use std::path::PathBuf;
use target_lexicon::Triple;
use wasmtime::Engine;
use wasmtime_cli_flags::CommonOptions;
lazy_static::lazy_static! {
static ref AFTER_HELP: String = {

View File

@@ -1,6 +1,5 @@
//! The module that implements the `wasmtime run` command.
use crate::{CommonOptions, WasiModules};
use anyhow::{anyhow, bail, Context as _, Result};
use clap::Parser;
use std::fs::File;
@@ -13,6 +12,7 @@ use std::{
process,
};
use wasmtime::{Engine, Func, Linker, Module, Store, Trap, Val, ValType};
use wasmtime_cli_flags::{CommonOptions, WasiModules};
use wasmtime_wasi::sync::{ambient_authority, Dir, TcpListener, WasiCtxBuilder};
#[cfg(feature = "wasi-nn")]

View File

@@ -1,10 +1,10 @@
//! The module that implements the `wasmtime wast` command.
use crate::CommonOptions;
use anyhow::{Context as _, Result};
use clap::Parser;
use std::path::PathBuf;
use wasmtime::{Engine, Store};
use wasmtime_cli_flags::CommonOptions;
use wasmtime_wast::WastContext;
lazy_static::lazy_static! {

View File

@@ -23,41 +23,7 @@
)
)]
const SUPPORTED_WASM_FEATURES: &[(&str, &str)] = &[
("all", "enables all supported WebAssembly features"),
(
"bulk-memory",
"enables support for bulk memory instructions",
),
(
"multi-memory",
"enables support for the multi-memory proposal",
),
("multi-value", "enables support for multi-value functions"),
("reference-types", "enables support for reference types"),
("simd", "enables support for proposed SIMD instructions"),
("threads", "enables support for WebAssembly threads"),
("memory64", "enables support for 64-bit memories"),
];
const SUPPORTED_WASI_MODULES: &[(&str, &str)] = &[
(
"default",
"enables all stable WASI modules (no experimental modules)",
),
(
"wasi-common",
"enables support for the WASI common APIs, see https://github.com/WebAssembly/WASI",
),
(
"experimental-wasi-nn",
"enables support for the WASI neural network API (experimental), see https://github.com/WebAssembly/wasi-nn",
),
(
"experimental-wasi-crypto",
"enables support for the WASI cryptography APIs (experimental), see https://github.com/WebAssembly/wasi-crypto",
),
];
use wasmtime_cli_flags::{SUPPORTED_WASI_MODULES, SUPPORTED_WASM_FEATURES};
lazy_static::lazy_static! {
static ref FLAG_EXPLANATIONS: String = {
@@ -90,644 +56,3 @@ lazy_static::lazy_static! {
}
pub mod commands;
use anyhow::{bail, Result};
use clap::Parser;
use std::collections::HashMap;
use std::path::PathBuf;
use wasmtime::{Config, ProfilingStrategy};
#[cfg(feature = "pooling-allocator")]
use wasmtime::{InstanceLimits, PoolingAllocationStrategy};
fn pick_profiling_strategy(jitdump: bool, vtune: bool) -> Result<ProfilingStrategy> {
Ok(match (jitdump, vtune) {
(true, false) => ProfilingStrategy::JitDump,
(false, true) => ProfilingStrategy::VTune,
(true, true) => {
println!("Can't enable --jitdump and --vtune at the same time. Profiling not enabled.");
ProfilingStrategy::None
}
_ => ProfilingStrategy::None,
})
}
fn init_file_per_thread_logger(prefix: &'static str) {
file_per_thread_logger::initialize(prefix);
// Extending behavior of default spawner:
// https://docs.rs/rayon/1.1.0/rayon/struct.ThreadPoolBuilder.html#method.spawn_handler
// Source code says DefaultSpawner is implementation detail and
// shouldn't be used directly.
rayon::ThreadPoolBuilder::new()
.spawn_handler(move |thread| {
let mut b = std::thread::Builder::new();
if let Some(name) = thread.name() {
b = b.name(name.to_owned());
}
if let Some(stack_size) = thread.stack_size() {
b = b.stack_size(stack_size);
}
b.spawn(move || {
file_per_thread_logger::initialize(prefix);
thread.run()
})?;
Ok(())
})
.build_global()
.unwrap();
}
/// Common options for commands that translate WebAssembly modules
#[derive(Parser)]
struct CommonOptions {
/// Use specified configuration file
#[clap(long, parse(from_os_str), value_name = "CONFIG_PATH")]
config: Option<PathBuf>,
/// Disable logging.
#[clap(long, conflicts_with = "log-to-files")]
disable_logging: bool,
/// Log to per-thread log files instead of stderr.
#[clap(long)]
log_to_files: bool,
/// Generate debug information
#[clap(short = 'g')]
debug_info: bool,
/// Disable cache system
#[clap(long)]
disable_cache: bool,
/// Enables or disables WebAssembly features
#[clap(long, value_name = "FEATURE,FEATURE,...", parse(try_from_str = parse_wasm_features))]
wasm_features: Option<WasmFeatures>,
/// Enables or disables WASI modules
#[clap(long, value_name = "MODULE,MODULE,...", parse(try_from_str = parse_wasi_modules))]
wasi_modules: Option<WasiModules>,
/// Generate jitdump file (supported on --features=profiling build)
#[clap(long, conflicts_with = "vtune")]
jitdump: bool,
/// Generate vtune (supported on --features=vtune build)
#[clap(long, conflicts_with = "jitdump")]
vtune: bool,
/// Run optimization passes on translated functions, on by default
#[clap(short = 'O', long)]
optimize: bool,
/// Optimization level for generated functions
/// Supported levels: 0 (none), 1, 2 (most), or s (size); default is "most"
#[clap(
long,
value_name = "LEVEL",
parse(try_from_str = parse_opt_level),
verbatim_doc_comment,
)]
opt_level: Option<wasmtime::OptLevel>,
/// Set a Cranelift setting to a given value.
/// Use `wasmtime settings` to list Cranelift settings for a target.
#[clap(long = "cranelift-set", value_name = "NAME=VALUE", number_of_values = 1, verbatim_doc_comment, parse(try_from_str = parse_cranelift_flag))]
cranelift_set: Vec<(String, String)>,
/// Enable a Cranelift boolean setting or preset.
/// Use `wasmtime settings` to list Cranelift settings for a target.
#[clap(
long,
value_name = "SETTING",
number_of_values = 1,
verbatim_doc_comment
)]
cranelift_enable: Vec<String>,
/// Maximum size in bytes of wasm memory before it becomes dynamically
/// relocatable instead of up-front-reserved.
#[clap(long, value_name = "MAXIMUM")]
static_memory_maximum_size: Option<u64>,
/// Force using a "static" style for all wasm memories.
#[clap(long)]
static_memory_forced: bool,
/// Byte size of the guard region after static memories are allocated.
#[clap(long, value_name = "SIZE")]
static_memory_guard_size: Option<u64>,
/// Byte size of the guard region after dynamic memories are allocated.
#[clap(long, value_name = "SIZE")]
dynamic_memory_guard_size: Option<u64>,
/// Enable Cranelift's internal debug verifier (expensive)
#[clap(long)]
enable_cranelift_debug_verifier: bool,
/// Enable Cranelift's internal NaN canonicalization
#[clap(long)]
enable_cranelift_nan_canonicalization: bool,
/// Enable execution fuel with N units fuel, where execution will trap after
/// running out of fuel.
///
/// Most WebAssembly instructions consume 1 unit of fuel. Some instructions,
/// such as `nop`, `drop`, `block`, and `loop`, consume 0 units, as any
/// execution cost associated with them involves other instructions which do
/// consume fuel.
#[clap(long, value_name = "N")]
fuel: Option<u64>,
/// Executing wasm code will yield when a global epoch counter
/// changes, allowing for async operation without blocking the
/// executor.
#[clap(long)]
epoch_interruption: bool,
/// Disables the on-by-default address map from native code to wasm code.
#[clap(long)]
disable_address_map: bool,
/// Disables the default of attempting to initialize linear memory via a
/// copy-on-write mapping.
#[cfg(feature = "memory-init-cow")]
#[clap(long)]
disable_memory_init_cow: bool,
/// Enables the pooling allocator, in place of the on-demand
/// allocator.
#[cfg(feature = "pooling-allocator")]
#[clap(long)]
pooling_allocator: bool,
}
impl CommonOptions {
fn init_logging(&self) {
if self.disable_logging {
return;
}
if self.log_to_files {
let prefix = "wasmtime.dbg.";
init_file_per_thread_logger(prefix);
} else {
pretty_env_logger::init();
}
}
fn config(&self, target: Option<&str>) -> Result<Config> {
let mut config = Config::new();
// Set the target before setting any cranelift options, since the
// target will reset any target-specific options.
if let Some(target) = target {
config.target(target)?;
}
config
.cranelift_debug_verifier(self.enable_cranelift_debug_verifier)
.debug_info(self.debug_info)
.cranelift_opt_level(self.opt_level())
.profiler(pick_profiling_strategy(self.jitdump, self.vtune)?)?
.cranelift_nan_canonicalization(self.enable_cranelift_nan_canonicalization);
self.enable_wasm_features(&mut config);
for name in &self.cranelift_enable {
unsafe {
config.cranelift_flag_enable(name)?;
}
}
for (name, value) in &self.cranelift_set {
unsafe {
config.cranelift_flag_set(name, value)?;
}
}
if !self.disable_cache {
match &self.config {
Some(path) => {
config.cache_config_load(path)?;
}
None => {
config.cache_config_load_default()?;
}
}
}
if let Some(max) = self.static_memory_maximum_size {
config.static_memory_maximum_size(max);
}
config.static_memory_forced(self.static_memory_forced);
if let Some(size) = self.static_memory_guard_size {
config.static_memory_guard_size(size);
}
if let Some(size) = self.dynamic_memory_guard_size {
config.dynamic_memory_guard_size(size);
}
// If fuel has been configured, set the `consume fuel` flag on the config.
if self.fuel.is_some() {
config.consume_fuel(true);
}
config.epoch_interruption(self.epoch_interruption);
config.generate_address_map(!self.disable_address_map);
#[cfg(feature = "memory-init-cow")]
config.memory_init_cow(!self.disable_memory_init_cow);
#[cfg(feature = "pooling-allocator")]
{
if self.pooling_allocator {
let instance_limits = InstanceLimits::default();
config.allocation_strategy(wasmtime::InstanceAllocationStrategy::Pooling {
strategy: PoolingAllocationStrategy::NextAvailable,
instance_limits,
});
}
}
Ok(config)
}
fn enable_wasm_features(&self, config: &mut Config) {
let WasmFeatures {
simd,
bulk_memory,
reference_types,
multi_value,
threads,
multi_memory,
memory64,
} = self.wasm_features.unwrap_or_default();
if let Some(enable) = simd {
config.wasm_simd(enable);
}
if let Some(enable) = bulk_memory {
config.wasm_bulk_memory(enable);
}
if let Some(enable) = reference_types {
#[cfg(feature = "wasm-backtrace")]
config.wasm_reference_types(enable);
drop(enable); // suppress unused warnings
}
if let Some(enable) = multi_value {
config.wasm_multi_value(enable);
}
if let Some(enable) = threads {
config.wasm_threads(enable);
}
if let Some(enable) = multi_memory {
config.wasm_multi_memory(enable);
}
if let Some(enable) = memory64 {
config.wasm_memory64(enable);
}
}
fn opt_level(&self) -> wasmtime::OptLevel {
match (self.optimize, self.opt_level.clone()) {
(true, _) => wasmtime::OptLevel::Speed,
(false, other) => other.unwrap_or(wasmtime::OptLevel::Speed),
}
}
}
fn parse_opt_level(opt_level: &str) -> Result<wasmtime::OptLevel> {
match opt_level {
"s" => Ok(wasmtime::OptLevel::SpeedAndSize),
"0" => Ok(wasmtime::OptLevel::None),
"1" => Ok(wasmtime::OptLevel::Speed),
"2" => Ok(wasmtime::OptLevel::Speed),
other => bail!(
"unknown optimization level `{}`, only 0,1,2,s accepted",
other
),
}
}
#[derive(Default, Clone, Copy)]
struct WasmFeatures {
reference_types: Option<bool>,
multi_value: Option<bool>,
bulk_memory: Option<bool>,
simd: Option<bool>,
threads: Option<bool>,
multi_memory: Option<bool>,
memory64: Option<bool>,
}
fn parse_wasm_features(features: &str) -> Result<WasmFeatures> {
let features = features.trim();
let mut all = None;
let mut values: HashMap<_, _> = SUPPORTED_WASM_FEATURES
.iter()
.map(|(name, _)| (name.to_string(), None))
.collect();
if features == "all" {
all = Some(true);
} else if features == "-all" {
all = Some(false);
} else {
for feature in features.split(',') {
let feature = feature.trim();
if feature.is_empty() {
continue;
}
let (feature, value) = if feature.starts_with('-') {
(&feature[1..], false)
} else {
(feature, true)
};
if feature == "all" {
bail!("'all' cannot be specified with other WebAssembly features");
}
match values.get_mut(feature) {
Some(v) => *v = Some(value),
None => bail!("unsupported WebAssembly feature '{}'", feature),
}
}
}
Ok(WasmFeatures {
reference_types: all.or(values["reference-types"]),
multi_value: all.or(values["multi-value"]),
bulk_memory: all.or(values["bulk-memory"]),
simd: all.or(values["simd"]),
threads: all.or(values["threads"]),
multi_memory: all.or(values["multi-memory"]),
memory64: all.or(values["memory64"]),
})
}
fn parse_wasi_modules(modules: &str) -> Result<WasiModules> {
let modules = modules.trim();
match modules {
"default" => Ok(WasiModules::default()),
"-default" => Ok(WasiModules::none()),
_ => {
// Starting from the default set of WASI modules, enable or disable a list of
// comma-separated modules.
let mut wasi_modules = WasiModules::default();
let mut set = |module: &str, enable: bool| match module {
"" => Ok(()),
"wasi-common" => Ok(wasi_modules.wasi_common = enable),
"experimental-wasi-nn" => Ok(wasi_modules.wasi_nn = enable),
"experimental-wasi-crypto" => Ok(wasi_modules.wasi_crypto = enable),
"default" => bail!("'default' cannot be specified with other WASI modules"),
_ => bail!("unsupported WASI module '{}'", module),
};
for module in modules.split(',') {
let module = module.trim();
let (module, value) = if module.starts_with('-') {
(&module[1..], false)
} else {
(module, true)
};
set(module, value)?;
}
Ok(wasi_modules)
}
}
}
/// Select which WASI modules are available at runtime for use by Wasm programs.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct WasiModules {
/// Enable the wasi-common implementation; eventually this should be split into its separate
/// parts once the implementation allows for it (e.g. wasi-fs, wasi-clocks, etc.).
pub wasi_common: bool,
/// Enable the experimental wasi-nn implementation.
pub wasi_nn: bool,
/// Enable the experimental wasi-crypto implementation.
pub wasi_crypto: bool,
}
impl Default for WasiModules {
fn default() -> Self {
Self {
wasi_common: true,
wasi_nn: false,
wasi_crypto: false,
}
}
}
impl WasiModules {
/// Enable no modules.
pub fn none() -> Self {
Self {
wasi_common: false,
wasi_nn: false,
wasi_crypto: false,
}
}
}
fn parse_cranelift_flag(name_and_value: &str) -> Result<(String, String)> {
let mut split = name_and_value.splitn(2, '=');
let name = if let Some(name) = split.next() {
name.to_string()
} else {
bail!("missing name in cranelift flag");
};
let value = if let Some(value) = split.next() {
value.to_string()
} else {
bail!("missing value in cranelift flag");
};
Ok((name, value))
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_all_features() -> Result<()> {
let options = CommonOptions::try_parse_from(vec!["foo", "--wasm-features=all"])?;
let WasmFeatures {
reference_types,
multi_value,
bulk_memory,
simd,
threads,
multi_memory,
memory64,
} = options.wasm_features.unwrap();
assert_eq!(reference_types, Some(true));
assert_eq!(multi_value, Some(true));
assert_eq!(bulk_memory, Some(true));
assert_eq!(simd, Some(true));
assert_eq!(threads, Some(true));
assert_eq!(multi_memory, Some(true));
assert_eq!(memory64, Some(true));
Ok(())
}
#[test]
fn test_no_features() -> Result<()> {
let options = CommonOptions::try_parse_from(vec!["foo", "--wasm-features=-all"])?;
let WasmFeatures {
reference_types,
multi_value,
bulk_memory,
simd,
threads,
multi_memory,
memory64,
} = options.wasm_features.unwrap();
assert_eq!(reference_types, Some(false));
assert_eq!(multi_value, Some(false));
assert_eq!(bulk_memory, Some(false));
assert_eq!(simd, Some(false));
assert_eq!(threads, Some(false));
assert_eq!(multi_memory, Some(false));
assert_eq!(memory64, Some(false));
Ok(())
}
#[test]
fn test_multiple_features() -> Result<()> {
let options = CommonOptions::try_parse_from(vec![
"foo",
"--wasm-features=-reference-types,simd,multi-memory,memory64",
])?;
let WasmFeatures {
reference_types,
multi_value,
bulk_memory,
simd,
threads,
multi_memory,
memory64,
} = options.wasm_features.unwrap();
assert_eq!(reference_types, Some(false));
assert_eq!(multi_value, None);
assert_eq!(bulk_memory, None);
assert_eq!(simd, Some(true));
assert_eq!(threads, None);
assert_eq!(multi_memory, Some(true));
assert_eq!(memory64, Some(true));
Ok(())
}
macro_rules! feature_test {
($test_name:ident, $name:ident, $flag:literal) => {
#[test]
fn $test_name() -> Result<()> {
let options =
CommonOptions::try_parse_from(vec!["foo", concat!("--wasm-features=", $flag)])?;
let WasmFeatures { $name, .. } = options.wasm_features.unwrap();
assert_eq!($name, Some(true));
let options = CommonOptions::try_parse_from(vec![
"foo",
concat!("--wasm-features=-", $flag),
])?;
let WasmFeatures { $name, .. } = options.wasm_features.unwrap();
assert_eq!($name, Some(false));
Ok(())
}
};
}
feature_test!(
test_reference_types_feature,
reference_types,
"reference-types"
);
feature_test!(test_multi_value_feature, multi_value, "multi-value");
feature_test!(test_bulk_memory_feature, bulk_memory, "bulk-memory");
feature_test!(test_simd_feature, simd, "simd");
feature_test!(test_threads_feature, threads, "threads");
feature_test!(test_multi_memory_feature, multi_memory, "multi-memory");
feature_test!(test_memory64_feature, memory64, "memory64");
#[test]
fn test_default_modules() {
let options = CommonOptions::try_parse_from(vec!["foo", "--wasi-modules=default"]).unwrap();
assert_eq!(
options.wasi_modules.unwrap(),
WasiModules {
wasi_common: true,
wasi_nn: false,
wasi_crypto: false
}
);
}
#[test]
fn test_empty_modules() {
let options = CommonOptions::try_parse_from(vec!["foo", "--wasi-modules="]).unwrap();
assert_eq!(
options.wasi_modules.unwrap(),
WasiModules {
wasi_common: true,
wasi_nn: false,
wasi_crypto: false
}
);
}
#[test]
fn test_some_modules() {
let options = CommonOptions::try_parse_from(vec![
"foo",
"--wasi-modules=experimental-wasi-nn,-wasi-common",
])
.unwrap();
assert_eq!(
options.wasi_modules.unwrap(),
WasiModules {
wasi_common: false,
wasi_nn: true,
wasi_crypto: false
}
);
}
#[test]
fn test_no_modules() {
let options =
CommonOptions::try_parse_from(vec!["foo", "--wasi-modules=-default"]).unwrap();
assert_eq!(
options.wasi_modules.unwrap(),
WasiModules {
wasi_common: false,
wasi_nn: false,
wasi_crypto: false
}
);
}
}