From a0979505371dd69a3ba27a097d3f259abc5272d5 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Tue, 30 Mar 2021 23:56:56 -0700 Subject: [PATCH] Replace WebAssembly feature CLI options with `--wasm-features`. This commit hides the existing WebAssembly feature CLI options (e.g. `--enable-simd`) and adds a `--wasm-features` flag that enables multiple (or all) WebAssembly features. Features can be disabled by prefixing the value with `-`, e.g. `--wasm-features=-simd`. --- Cargo.lock | 1 + Cargo.toml | 1 + RELEASES.md | 10 +- src/bin/wasmtime.rs | 4 +- src/commands/compile.rs | 43 ++++-- src/commands/run.rs | 8 +- src/commands/wasm2obj.rs | 16 +- src/commands/wast.rs | 7 + src/lib.rs | 324 +++++++++++++++++++++++++++++++++++---- 9 files changed, 356 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9a09dd8fe63a..b7c03544fc9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3273,6 +3273,7 @@ dependencies = [ "file-per-thread-logger", "filecheck", "humantime 2.1.0", + "lazy_static", "libc", "log", "more-asserts", diff --git a/Cargo.toml b/Cargo.toml index efdae9083e0b..a66027c480a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ log = "0.4.8" rayon = "1.2.1" humantime = "2.0.0" wasmparser = "0.77.0" +lazy_static = "1.4.0" [dev-dependencies] env_logger = "0.8.1" diff --git a/RELEASES.md b/RELEASES.md index b19e2b2105f9..07a048bbe87e 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -16,16 +16,20 @@ ### Changed +* Wasmtime CLI options to enable WebAssembly features have been replaced with a + singular `--wasm-features` option. The previous options are still supported, but + are not displayed in help text. + * Breaking: the CLI option `--cranelift-flags` was changed to `--cranelift-flag`. * Breaking: the CLI option `--enable-reference-types=false` has been changed to - `--disable-reference-types` as it is enabled by default. + `--wasm-features=-reference-types`. * Breaking: the CLI option `--enable-multi-value=false` has been changed to - `--disable-multi-value` as it is enabled by default. + `--wasm-features=-multi-value`. * Breaking: the CLI option `--enable-bulk-memory=false` has been changed to - `--disable-bulk-memory` as it is enabled by default. + `--wasm-features=-bulk-memory`. * Modules serialized with `Module::serialize` can now be deserialized with `Module::deserialize` on a compatible host that does not have to match the diff --git a/src/bin/wasmtime.rs b/src/bin/wasmtime.rs index 8b7c67caad0e..46490227ef16 100644 --- a/src/bin/wasmtime.rs +++ b/src/bin/wasmtime.rs @@ -6,7 +6,7 @@ use anyhow::Result; use structopt::{clap::AppSettings, clap::ErrorKind, StructOpt}; use wasmtime_cli::commands::{ - CompileCommand, ConfigCommand, RunCommand, WasmToObjCommand, WastCommand, WASM2OBJ_AFTER_HELP, + CompileCommand, ConfigCommand, RunCommand, WasmToObjCommand, WastCommand, }; /// Wasmtime WebAssembly Runtime @@ -43,7 +43,7 @@ enum WasmtimeApp { /// Runs a WebAssembly module Run(RunCommand), /// Translates a WebAssembly module to native object file - #[structopt(name = "wasm2obj", after_help = WASM2OBJ_AFTER_HELP)] + #[structopt(name = "wasm2obj")] WasmToObj(WasmToObjCommand), /// Runs a WebAssembly test script file Wast(WastCommand), diff --git a/src/commands/compile.rs b/src/commands/compile.rs index 77a38940c66c..b6656e1eca73 100644 --- a/src/commands/compile.rs +++ b/src/commands/compile.rs @@ -12,6 +12,31 @@ use structopt::{ use target_lexicon::Triple; use wasmtime::{Config, Engine, Module}; +lazy_static::lazy_static! { + static ref AFTER_HELP: String = { + format!( + "By default, no CPU features or presets will be enabled for the compilation.\n\ + \n\ + {}\ + \n\ + Usage examples:\n\ + \n\ + Compiling a WebAssembly module for the current platform:\n\ + \n \ + wasmtime compile example.wasm + \n\ + Specifying the output file:\n\ + \n \ + wasmtime compile -o output.cwasm input.wasm\n\ + \n\ + Compiling for a specific platform (Linux) and CPU preset (Skylake):\n\ + \n \ + wasmtime compile --target x86_64-unknown-linux --skylake foo.wasm\n", + crate::WASM_FEATURES.as_str() + ) + }; +} + /// Compiles a WebAssembly module. #[derive(StructOpt)] #[structopt( @@ -22,23 +47,7 @@ use wasmtime::{Config, Engine, Module}; group = ArgGroup::with_name("preset-x64"), group = ArgGroup::with_name("aarch64").multiple(true).conflicts_with_all(&["x64", "preset-x64"]), group = ArgGroup::with_name("preset-aarch64").conflicts_with_all(&["x64", "preset-x64"]), - after_help = "By default, no CPU flags will be enabled for the compilation.\n\ - \n\ - Use the various preset and CPU flag options for the environment being targeted.\n\ - \n\ - Usage examples:\n\ - \n\ - Compiling a WebAssembly module for the current platform:\n\ - \n \ - wasmtime compile example.wasm - \n\ - Specifying the output file:\n\ - \n \ - wasmtime compile -o output.cwasm input.wasm\n\ - \n\ - Compiling for a specific platform (Linux) and CPU preset (Skylake):\n\ - \n \ - wasmtime compile --target x86_64-unknown-linux --skylake foo.wasm\n" + after_help = AFTER_HELP.as_str() )] pub struct CompileCommand { #[structopt(flatten)] diff --git a/src/commands/run.rs b/src/commands/run.rs index 83ede6a06caf..6f27dc995763 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -68,9 +68,15 @@ fn parse_preloads(s: &str) -> Result<(String, PathBuf)> { Ok((parts[0].into(), parts[1].into())) } +lazy_static::lazy_static! { + static ref AFTER_HELP: String = { + crate::WASM_FEATURES.to_string() + }; +} + /// Runs a WebAssembly module #[derive(StructOpt)] -#[structopt(name = "run", setting = AppSettings::TrailingVarArg)] +#[structopt(name = "run", setting = AppSettings::TrailingVarArg, after_help = AFTER_HELP.as_str())] pub struct RunCommand { #[structopt(flatten)] common: CommonOptions, diff --git a/src/commands/wasm2obj.rs b/src/commands/wasm2obj.rs index efb118d615c7..ab914d8edc1b 100644 --- a/src/commands/wasm2obj.rs +++ b/src/commands/wasm2obj.rs @@ -11,9 +11,17 @@ use std::{ use structopt::{clap::AppSettings, StructOpt}; use target_lexicon::Triple; -/// The after help text for the `wasm2obj` command. -pub const WASM2OBJ_AFTER_HELP: &str = "The translation is dependent on the environment chosen.\n\ - The default is a dummy environment that produces placeholder values."; +lazy_static::lazy_static! { + static ref AFTER_HELP: String = { + format!( + "The translation is dependent on the environment chosen.\n\ + The default is a dummy environment that produces placeholder values.\n\ + \n\ + {}", + crate::WASM_FEATURES.as_str() + ) + }; +} /// Translates a WebAssembly module to native object file #[derive(StructOpt)] @@ -21,7 +29,7 @@ pub const WASM2OBJ_AFTER_HELP: &str = "The translation is dependent on the envir name = "wasm2obj", version = env!("CARGO_PKG_VERSION"), setting = AppSettings::ColoredHelp, - after_help = WASM2OBJ_AFTER_HELP, + after_help = AFTER_HELP.as_str(), )] pub struct WasmToObjCommand { #[structopt(flatten)] diff --git a/src/commands/wast.rs b/src/commands/wast.rs index b757db41b1d8..cd749e61a640 100644 --- a/src/commands/wast.rs +++ b/src/commands/wast.rs @@ -7,12 +7,19 @@ use structopt::{clap::AppSettings, StructOpt}; use wasmtime::{Engine, Store}; use wasmtime_wast::WastContext; +lazy_static::lazy_static! { + static ref AFTER_HELP: String = { + crate::WASM_FEATURES.to_string() + }; +} + /// Runs a WebAssembly test script file #[derive(StructOpt)] #[structopt( name = "wast", version = env!("CARGO_PKG_VERSION"), setting = AppSettings::ColoredHelp, + after_help = AFTER_HELP.as_str(), )] pub struct WastCommand { #[structopt(flatten)] diff --git a/src/lib.rs b/src/lib.rs index 259be6de6483..39af826d0315 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,10 +23,52 @@ ) )] +const SUPPORTED_WASM_FEATURES: &[(&str, &str)] = &[ + ("all", "enables all supported WebAssembly features"), + ( + "bulk-memory", + "enables support for bulk memory instructions", + ), + ( + "module-linking", + "enables support for the module-linking proposal", + ), + ( + "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"), +]; + +lazy_static::lazy_static! { + static ref WASM_FEATURES: String = { + use std::fmt::Write; + + let mut s = String::new(); + writeln!(&mut s, "Supported values for `--wasm-features`:").unwrap(); + writeln!(&mut s).unwrap(); + + let max = SUPPORTED_WASM_FEATURES.iter().max_by_key(|(name, _)| name.len()).unwrap(); + + for (name, desc) in SUPPORTED_WASM_FEATURES.iter() { + writeln!(&mut s, "{:width$} {}", name, desc, width = max.0.len() + 2).unwrap(); + } + + writeln!(&mut s).unwrap(); + writeln!(&mut s, "Features prefixed with '-' will be disabled.").unwrap(); + + s + }; +} + pub mod commands; mod obj; use anyhow::{bail, Result}; +use std::collections::HashMap; use std::path::PathBuf; use structopt::StructOpt; use target_lexicon::Triple; @@ -108,38 +150,42 @@ struct CommonOptions { #[structopt(long)] disable_cache: bool, - /// Enable support for proposed SIMD instructions - #[structopt(long)] + /// Enable support for proposed SIMD instructions (deprecated; use `--wasm-features=simd`) + #[structopt(long, hidden = true)] enable_simd: bool, - /// Disable support for reference types - #[structopt(long)] - disable_reference_types: bool, + /// Enable support for reference types (deprecated; use `--wasm-features=reference-types`) + #[structopt(long, hidden = true)] + enable_reference_types: bool, - /// Disable support for multi-value functions - #[structopt(long)] - disable_multi_value: bool, + /// Enable support for multi-value functions (deprecated; use `--wasm-features=multi-value`) + #[structopt(long, hidden = true)] + enable_multi_value: bool, - /// Enable support for Wasm threads - #[structopt(long)] + /// Enable support for Wasm threads (deprecated; use `--wasm-features=threads`) + #[structopt(long, hidden = true)] enable_threads: bool, - /// Disable support for bulk memory instructions - #[structopt(long)] - disable_bulk_memory: bool, + /// Enable support for bulk memory instructions (deprecated; use `--wasm-features=bulk-memory`) + #[structopt(long, hidden = true)] + enable_bulk_memory: bool, - /// Enable support for the multi-memory proposal - #[structopt(long)] + /// Enable support for the multi-memory proposal (deprecated; use `--wasm-features=multi-memory`) + #[structopt(long, hidden = true)] enable_multi_memory: bool, - /// Enable support for the module-linking proposal - #[structopt(long)] + /// Enable support for the module-linking proposal (deprecated; use `--wasm-features=module-linking`) + #[structopt(long, hidden = true)] enable_module_linking: bool, - /// Enable all experimental Wasm features - #[structopt(long)] + /// Enable all experimental Wasm features (deprecated; use `--wasm-features=all`) + #[structopt(long, hidden = true)] enable_all: bool, + /// Enables or disables WebAssembly features + #[structopt(long, value_name = "FEATURE,FEATURE,...", parse(try_from_str = parse_wasm_features))] + wasm_features: Option, + /// Use Lightbeam for all compilation #[structopt(long, conflicts_with = "cranelift")] lightbeam: bool, @@ -156,12 +202,13 @@ struct CommonOptions { #[structopt(short = "O", long)] optimize: bool, - /// Optimization level for generated functions: 0 (none), 1, 2 (most), or s - /// (size); defaults to "most" + /// Optimization level for generated functions + /// Supported levels: 0 (none), 1, 2 (most), or s (size); default is "most" #[structopt( long, value_name = "LEVEL", parse(try_from_str = parse_opt_level), + verbatim_doc_comment, )] opt_level: Option, @@ -207,6 +254,7 @@ impl CommonOptions { pretty_env_logger::init(); } } + fn config(&self, target: Option<&str>) -> Result { let mut config = Config::new(); @@ -218,20 +266,13 @@ impl CommonOptions { config .cranelift_debug_verifier(self.enable_cranelift_debug_verifier) .debug_info(self.debug_info) - .wasm_simd(self.enable_simd || self.enable_all) - .wasm_bulk_memory(!self.disable_bulk_memory || self.enable_all) - .wasm_reference_types( - (!self.disable_reference_types || cfg!(target_arch = "x86_64")) || self.enable_all, - ) - .wasm_multi_value(!self.disable_multi_value || self.enable_all) - .wasm_threads(self.enable_threads || self.enable_all) - .wasm_multi_memory(self.enable_multi_memory || self.enable_all) - .wasm_module_linking(self.enable_module_linking || self.enable_all) .cranelift_opt_level(self.opt_level()) .strategy(pick_compilation_strategy(self.cranelift, self.lightbeam)?)? .profiler(pick_profiling_strategy(self.jitdump, self.vtune)?)? .cranelift_nan_canonicalization(self.enable_cranelift_nan_canonicalization); + self.enable_wasm_features(&mut config); + if let Some(preset) = &self.cranelift_preset { unsafe { config.cranelift_flag_enable(preset)?; @@ -254,18 +295,39 @@ impl CommonOptions { } } } + if let Some(max) = self.static_memory_maximum_size { config.static_memory_maximum_size(max); } + 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); } + Ok(config) } + fn enable_wasm_features(&self, config: &mut Config) { + let features = self.wasm_features.unwrap_or_default(); + + config + .wasm_simd(features.simd || self.enable_simd || self.enable_all) + .wasm_bulk_memory(features.bulk_memory || self.enable_bulk_memory || self.enable_all) + .wasm_reference_types( + features.reference_types || self.enable_reference_types || self.enable_all, + ) + .wasm_multi_value(features.multi_value || self.enable_multi_value || self.enable_all) + .wasm_threads(features.threads || self.enable_threads || self.enable_all) + .wasm_multi_memory(features.multi_memory || self.enable_multi_memory || self.enable_all) + .wasm_module_linking( + features.module_linking || self.enable_module_linking || self.enable_all, + ); + } + fn opt_level(&self) -> wasmtime::OptLevel { match (self.optimize, self.opt_level.clone()) { (true, _) => wasmtime::OptLevel::Speed, @@ -287,6 +349,59 @@ fn parse_opt_level(opt_level: &str) -> Result { } } +fn parse_wasm_features(features: &str) -> Result { + 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(wasmparser::WasmFeatures { + reference_types: all.unwrap_or(values["reference-types"].unwrap_or(true)), + multi_value: all.unwrap_or(values["multi-value"].unwrap_or(true)), + bulk_memory: all.unwrap_or(values["bulk-memory"].unwrap_or(true)), + module_linking: all.unwrap_or(values["module-linking"].unwrap_or(false)), + simd: all.unwrap_or(values["simd"].unwrap_or(false)), + threads: all.unwrap_or(values["threads"].unwrap_or(false)), + tail_call: false, + deterministic_only: false, + multi_memory: all.unwrap_or(values["multi-memory"].unwrap_or(false)), + exceptions: false, + memory64: false, + }) +} + struct CraneliftFlag { name: String, value: String, @@ -309,6 +424,153 @@ fn parse_cranelift_flag(name_and_value: &str) -> Result { fn parse_target(s: &str) -> Result { use std::str::FromStr; - Triple::from_str(&s).map_err(|e| anyhow::anyhow!(e)) } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_all_features() -> Result<()> { + let options = CommonOptions::from_iter_safe(vec!["foo", "--wasm-features=all"])?; + + let wasmparser::WasmFeatures { + reference_types, + multi_value, + bulk_memory, + module_linking, + simd, + threads, + tail_call, + deterministic_only, + multi_memory, + exceptions, + memory64, + } = options.wasm_features.unwrap(); + + assert!(reference_types); + assert!(multi_value); + assert!(bulk_memory); + assert!(module_linking); + assert!(simd); + assert!(threads); + assert!(!tail_call); // Not supported + assert!(!deterministic_only); // Not supported + assert!(multi_memory); + assert!(!exceptions); // Not supported + assert!(!memory64); // Not supported + + Ok(()) + } + + #[test] + fn test_no_features() -> Result<()> { + let options = CommonOptions::from_iter_safe(vec!["foo", "--wasm-features=-all"])?; + + let wasmparser::WasmFeatures { + reference_types, + multi_value, + bulk_memory, + module_linking, + simd, + threads, + tail_call, + deterministic_only, + multi_memory, + exceptions, + memory64, + } = options.wasm_features.unwrap(); + + assert!(!reference_types); + assert!(!multi_value); + assert!(!bulk_memory); + assert!(!module_linking); + assert!(!simd); + assert!(!threads); + assert!(!tail_call); + assert!(!deterministic_only); + assert!(!multi_memory); + assert!(!exceptions); + assert!(!memory64); + + Ok(()) + } + + #[test] + fn test_multiple_features() -> Result<()> { + let options = CommonOptions::from_iter_safe(vec![ + "foo", + "--wasm-features=-reference-types,simd,multi-memory", + ])?; + + let wasmparser::WasmFeatures { + reference_types, + multi_value, + bulk_memory, + module_linking, + simd, + threads, + tail_call, + deterministic_only, + multi_memory, + exceptions, + memory64, + } = options.wasm_features.unwrap(); + + assert!(!reference_types); + assert!(multi_value); + assert!(bulk_memory); + assert!(!module_linking); + assert!(simd); + assert!(!threads); + assert!(!tail_call); // Not supported + assert!(!deterministic_only); // Not supported + assert!(multi_memory); + assert!(!exceptions); // Not supported + assert!(!memory64); // Not supported + + Ok(()) + } + + macro_rules! feature_test { + ($test_name:ident, $name:ident, $flag:literal) => { + #[test] + fn $test_name() -> Result<()> { + let options = + CommonOptions::from_iter_safe(vec!["foo", concat!("--wasm-features=", $flag)])?; + + let wasmparser::WasmFeatures { $name, .. } = options.wasm_features.unwrap(); + + assert!($name); + + let options = CommonOptions::from_iter_safe(vec![ + "foo", + concat!("--wasm-features=-", $flag), + ])?; + + let wasmparser::WasmFeatures { $name, .. } = options.wasm_features.unwrap(); + + assert!(!$name); + + 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_module_linking_feature, + module_linking, + "module-linking" + ); + feature_test!(test_simd_feature, simd, "simd"); + feature_test!(test_threads_feature, threads, "threads"); + feature_test!(test_multi_memory_feature, multi_memory, "multi-memory"); +}