diff --git a/Cargo.toml b/Cargo.toml index d97eda6d9d..ccce2ba94c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -275,7 +275,7 @@ validate = [ 'wasmparser/validate', 'wasmparser/features', ] -print = [] +print = ['dep:bitflags', 'wasmparser/features'] parse = [] smith = ['wasm-smith', 'arbitrary', 'dep:serde', 'dep:serde_derive', 'dep:serde_json'] shrink = ['wasm-shrink', 'is_executable'] diff --git a/crates/wasm-encoder/src/reencode.rs b/crates/wasm-encoder/src/reencode.rs index d6812f1393..6e34f7b98b 100644 --- a/crates/wasm-encoder/src/reencode.rs +++ b/crates/wasm-encoder/src/reencode.rs @@ -693,7 +693,7 @@ pub mod utils { let data_range = start as usize..end; data.get(data_range).ok_or(Error::InvalidCodeSectionSize) }; - for section in parser.parse_all(data) { + for section in parser.clone().parse_all(data) { match section? { wasmparser::Payload::Version { encoding: wasmparser::Encoding::Module, @@ -849,7 +849,7 @@ pub mod utils { // that users overriding `parse_code_section` always get // that function called. let section = get_original_section(unchecked_range.clone())?; - let reader = wasmparser::BinaryReader::new(section, unchecked_range.start); + let reader = parser.binary_reader(section, unchecked_range.start); let section = wasmparser::CodeSectionReader::new(reader)?; reencoder.parse_code_section(&mut codes, section)?; module.section(&codes); diff --git a/crates/wasmparser/src/parser.rs b/crates/wasmparser/src/parser.rs index 6ae027a798..da28e9f94c 100644 --- a/crates/wasmparser/src/parser.rs +++ b/crates/wasmparser/src/parser.rs @@ -474,6 +474,29 @@ impl Parser { self.features = features; } + /// Creates a new [`BinaryReader`] for `data` which uses the same set of + /// wasm features as this parser. + /// + /// This is useful when parsing a range of the input, such as a section's + /// contents, outside of this parser while still respecting the features + /// configured with [`Parser::set_features`]. + /// + /// For more information see [`BinaryReader::new`]. + /// + /// # Panics + /// + /// Panics in the same situations as [`BinaryReader::new`]. + pub fn binary_reader<'a>(&self, data: &'a [u8], original_offset: u64) -> BinaryReader<'a> { + #[cfg(feature = "features")] + { + BinaryReader::new_features(data, original_offset, self.features) + } + #[cfg(not(feature = "features"))] + { + BinaryReader::new(data, original_offset) + } + } + /// Returns the original offset that this parser is currently at. pub fn offset(&self) -> u64 { self.offset @@ -640,11 +663,7 @@ impl Parser { } else { (data, eof) }; - let mut reader = BinaryReader::new(data, self.offset); - #[cfg(feature = "features")] - { - reader.set_features(self.features); - } + let mut reader = self.binary_reader(data, self.offset); match self.parse_reader(&mut reader, eof) { Ok(payload) => { // Be sure to update our offset with how far we got in the reader diff --git a/crates/wasmprinter/Cargo.toml b/crates/wasmprinter/Cargo.toml index a1136d2c93..a9017ff96f 100644 --- a/crates/wasmprinter/Cargo.toml +++ b/crates/wasmprinter/Cargo.toml @@ -25,4 +25,5 @@ wat = { path = "../wat" } [features] default = ['component-model', 'validate'] component-model = ['wasmparser/component-model'] -validate = ['wasmparser/validate', 'wasmparser/features'] +validate = ['wasmparser/validate', 'features'] +features = ['wasmparser/features'] diff --git a/crates/wasmprinter/src/lib.rs b/crates/wasmprinter/src/lib.rs index de13090f7e..76a52866ec 100644 --- a/crates/wasmprinter/src/lib.rs +++ b/crates/wasmprinter/src/lib.rs @@ -65,6 +65,8 @@ pub struct Config { fold_instructions: bool, indent_text: String, print_operand_stack: bool, + #[cfg(feature = "features")] + features: wasmparser::WasmFeatures, } impl Default for Config { @@ -76,6 +78,8 @@ impl Default for Config { fold_instructions: false, indent_text: " ".to_string(), print_operand_stack: false, + #[cfg(feature = "features")] + features: wasmparser::WasmFeatures::all(), } } } @@ -285,6 +289,15 @@ impl Config { self } + /// Select the set of WebAssembly features used when decoding the input. + /// + /// The default setting is [`WasmFeatures::all`](wasmparser::WasmFeatures::all). + #[cfg(feature = "features")] + pub fn features(&mut self, features: wasmparser::WasmFeatures) -> &mut Self { + self.features = features; + self + } + /// Print a WebAssembly binary. /// /// This function takes an entire `wasm` binary blob and prints it to the @@ -441,11 +454,13 @@ impl Printer<'_, '_> { let mut expected = None; let mut states: Vec = Vec::new(); let mut parser = Parser::new(0); + #[cfg(feature = "features")] + parser.set_features(self.config.features); #[cfg(feature = "component-model")] let mut parsers = Vec::new(); let mut validator = if self.config.print_operand_stack { - operand_stack::Validator::new() + operand_stack::Validator::new(self.config) } else { None }; diff --git a/crates/wasmprinter/src/operand_stack.rs b/crates/wasmprinter/src/operand_stack.rs index 924b483a25..1aa2865804 100644 --- a/crates/wasmprinter/src/operand_stack.rs +++ b/crates/wasmprinter/src/operand_stack.rs @@ -1,7 +1,7 @@ use crate::{Config, PrintTermcolor, Printer}; use wasmparser::{ BinaryReader, Frame, OperatorsReader, Payload, Result, ValType, ValidPayload, - ValidatorResources, WasmFeatures, + ValidatorResources, }; pub struct Validator { @@ -10,9 +10,9 @@ pub struct Validator { } impl Validator { - pub fn new() -> Option { + pub fn new(config: &Config) -> Option { Some(Validator { - validator: wasmparser::Validator::new_with_features(WasmFeatures::all()), + validator: wasmparser::Validator::new_with_features(config.features), last_function: None, }) } diff --git a/crates/wasmprinter/src/operand_stack_disabled.rs b/crates/wasmprinter/src/operand_stack_disabled.rs index 85c75d2141..f334eb0ae6 100644 --- a/crates/wasmprinter/src/operand_stack_disabled.rs +++ b/crates/wasmprinter/src/operand_stack_disabled.rs @@ -3,7 +3,7 @@ use wasmparser::{BinaryReader, OperatorsReader, Payload, Result}; pub enum Validator {} impl Validator { - pub fn new() -> Option { + pub fn new(_config: &crate::Config) -> Option { None } diff --git a/src/bin/wasm-tools/print.rs b/src/bin/wasm-tools/print.rs index 50cf345723..7001465944 100644 --- a/src/bin/wasm-tools/print.rs +++ b/src/bin/wasm-tools/print.rs @@ -49,10 +49,15 @@ Exit status: 0 on success, nonzero if the input file fails to parse. ")] +// `-f` is already taken by `--fold-instructions`, so `--features` is long-only. +#[clap(mut_arg("features", |a| a.short(None)))] pub struct Opts { #[clap(flatten)] generate_dwarf: wasm_tools::GenerateDwarfArg, + #[clap(flatten)] + features: wasm_tools::CliFeatures, + #[clap(flatten)] io: wasm_tools::InputOutput, @@ -102,6 +107,7 @@ impl Opts { let wasm = self.io.get_input_wasm(Some(&self.generate_dwarf))?; let mut config = wasmprinter::Config::new(); + config.features(self.features.features_from(wasmparser::WasmFeatures::all())); config.print_offsets(self.print_offsets); config.print_skeleton(self.skeleton); config.name_unnamed(self.name_unnamed); diff --git a/src/bin/wasm-tools/validate.rs b/src/bin/wasm-tools/validate.rs index 108db1c073..17f2e8ba2a 100644 --- a/src/bin/wasm-tools/validate.rs +++ b/src/bin/wasm-tools/validate.rs @@ -1,10 +1,9 @@ use addr2line::LookupResult; -use anyhow::{Result, bail}; -use bitflags::Flags; +use anyhow::Result; use std::fmt::Write; use std::time::Instant; +use wasm_tools::CliFeatures; use wasm_tools::addr2line::Addr2lineModules; -use wasmparser::WasmFeatures; /// Validate a WebAssembly binary /// @@ -56,38 +55,6 @@ pub struct Opts { validate_custom: bool, } -// Helper structure extracted used to parse the feature flags for `validate`. -#[derive(clap::Parser)] -pub struct CliFeatures { - /// Comma-separated list of WebAssembly features to enable during - /// validation. - /// - /// If a "-" character is present in front of a feature it will disable that - /// feature. For example "-simd" will disable the simd proposal. - /// - /// The placeholder "all" can be used to enable all wasm features and the - /// term "-all" can be used to disable all features. - /// - /// The default set of features enabled are all WebAssembly proposals that - /// are at phase 4 or after. This means that the default set of features - /// accepted are relatively bleeding edge. Versions of the WebAssembly - /// specification can also be selected. The "wasm1" or "mvp" feature can - /// select the original WebAssembly specification and "wasm2" can be used to - /// select the 2.0 version. - /// - /// Available feature options can be found in the wasmparser crate: - /// - #[clap(long, short = 'f', value_parser = parse_features)] - features: Vec>, -} - -#[derive(Clone)] -enum FeatureAction { - Reset(WasmFeatures), - Enable(WasmFeatures), - Disable(WasmFeatures), -} - impl Opts { pub fn general_opts(&self) -> &wasm_tools::GeneralOpts { &self.general @@ -169,110 +136,3 @@ impl Opts { } } } - -impl CliFeatures { - pub fn features(&self) -> WasmFeatures { - let mut ret = WasmFeatures::default(); - - for action in self.features.iter().flat_map(|v| v) { - match action { - FeatureAction::Enable(features) => { - ret |= *features; - } - FeatureAction::Disable(features) => { - ret &= !*features; - } - FeatureAction::Reset(features) => { - ret = *features; - } - } - } - - ret - } -} - -fn parse_features(arg: &str) -> Result> { - let mut ret = Vec::new(); - - const GROUPS: &[(&str, WasmFeatures)] = &[ - ("mvp", WasmFeatures::MVP), - ("wasm1", WasmFeatures::WASM1), - ("wasm2", WasmFeatures::WASM2), - ("wasm3", WasmFeatures::WASM3), - ("lime1", WasmFeatures::LIME1), - ]; - - enum Action { - ChangeAll, - Group(WasmFeatures), - Modify(WasmFeatures), - } - - fn actions() -> impl Iterator { - WasmFeatures::FLAGS - .iter() - .map(|f| (f.name(), Action::Modify(*f.value()))) - .chain( - GROUPS - .iter() - .map(|(name, features)| (*name, Action::Group(*features))), - ) - .chain([("all", Action::ChangeAll)]) - } - - fn flag_name(name: &str) -> String { - name.to_lowercase().replace('_', "-") - } - - 'outer: for part in arg.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()) { - let (enable, part) = if let Some(part) = part.strip_prefix("-") { - (false, part) - } else { - (true, part) - }; - for (name, action) in actions() { - if part != flag_name(name) { - continue; - } - match action { - Action::ChangeAll => { - ret.push(if enable { - FeatureAction::Enable(WasmFeatures::all()) - } else { - FeatureAction::Disable(WasmFeatures::all()) - }); - } - Action::Modify(feature) => { - ret.push(if enable { - FeatureAction::Enable(feature) - } else { - FeatureAction::Disable(feature) - }); - } - Action::Group(features) => { - if !enable { - bail!("cannot disable `{part}`, it can only be enabled"); - } - ret.push(FeatureAction::Reset(features)); - } - } - continue 'outer; - } - - let mut error = format!("unknown feature `{part}`\n"); - error.push_str("Valid features: "); - let mut first = true; - for (name, _) in actions() { - if first { - first = false; - } else { - error.push_str(", "); - } - error.push_str(&flag_name(name)); - } - bail!("{error}") - } - - Ok(ret) -} diff --git a/src/bin/wasm-tools/wast.rs b/src/bin/wasm-tools/wast.rs index 0724cbfe2d..a42eee18e8 100644 --- a/src/bin/wasm-tools/wast.rs +++ b/src/bin/wasm-tools/wast.rs @@ -1,4 +1,3 @@ -use crate::validate::CliFeatures; use anyhow::{Context, Result, bail}; use clap::Parser; use rayon::prelude::*; @@ -9,6 +8,7 @@ use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::str; use wasm_encoder::reencode::{Reencode, ReencodeComponent, RoundtripReencoder}; +use wasm_tools::CliFeatures; use wasm_tools::parse_binary_wasm; use wasmprinter::PrintFmtWrite; use wast::component::{Component, ComponentKind}; @@ -203,8 +203,7 @@ impl Opts { return Ok(()); } }; - let mut parser = wasmparser::Parser::new(0); - parser.set_features(self.features.features()); + let parser = self.parser(); if let Err(e) = parse_binary_wasm(parser, &bytes, is_custom) { self.assert_error_matches(test, &format!("{e:?}"), message)?; @@ -284,8 +283,7 @@ impl Opts { } => { // ensure module parses successfully let binary_wasm = module.encode()?; - let mut parser = wasmparser::Parser::new(0); - parser.set_features(self.features.features()); + let parser = self.parser(); // For `assert_invalid` tests it should be the case that // everything parses. If a wasmparser-specific @@ -394,7 +392,7 @@ impl Opts { enable_snapshot: bool, ) -> Result<()> { // Test that we can print these bytes. - let text1 = wasmprinter::print_bytes(contents).context("failed to print wasm")?; + let text1 = self.print(contents, false)?; if enable_snapshot && self.assert(Assert::SnapshotPrint) { self.snapshot("print", test, &text1) .context("failed to validate the `print` snapshot")?; @@ -403,11 +401,7 @@ impl Opts { // Test that we can print these bytes with instructions in folded form. let mut text1_folded = String::new(); if self.assert(Assert::TestFolded) { - let mut folding_printer = wasmprinter::Config::new(); - folding_printer.fold_instructions(true); - folding_printer - .print(contents, &mut PrintFmtWrite(&mut text1_folded)) - .context("failed to print wasm in folded form")?; + text1_folded = self.print(contents, true)?; if enable_snapshot && self.assert(Assert::SnapshotFolded) { self.snapshot("print-folded", test, &text1_folded) @@ -422,7 +416,7 @@ impl Opts { // Convert the text versions back to binary and compare to original let binary2 = wat::parse_str(&text1).context("failed to parse `wat` from `wasmprinter`")?; - let text2 = wasmprinter::print_bytes(&binary2).context("failed to print wasm")?; + let text2 = self.print(&binary2, false)?; if roundtrip_binary { self.binary_compare(&binary2, contents) .context("failed to compare original binary with roundtrip through wasmparser->wasmprinter->wat")?; @@ -436,18 +430,12 @@ impl Opts { if self.assert(Assert::TestFolded) { let binary2_from_folded = wat::parse_str(&text1_folded) .context("failed to parse folded `wat` from `wasmprinter`")?; - let mut text2_folded = String::new(); - let mut folding_printer = wasmprinter::Config::new(); - folding_printer.fold_instructions(true); - folding_printer - .print(contents, &mut PrintFmtWrite(&mut text2_folded)) - .context("failed to print wasm in folded form")?; + let text2_folded = self.print(contents, true)?; if roundtrip_binary { self.binary_compare(&binary2_from_folded, contents) .context("failed to compare original `wat` with roundtrip folded `wat`")?; } - let text3 = - wasmprinter::print_bytes(binary2_from_folded).context("failed to print wasm")?; + let text3 = self.print(&binary2_from_folded, false)?; if text1_folded != text2_folded { anyhow::bail!( "original folded print does not match roundtrip folded print\n{}", @@ -466,14 +454,14 @@ impl Opts { if wasmparser::Parser::is_component(contents) { let mut reencode = Default::default(); RoundtripReencoder - .parse_component(&mut reencode, wasmparser::Parser::new(0), contents) + .parse_component(&mut reencode, self.parser(), contents) .context("failed to reencode module")?; self.binary_compare(&reencode.finish(), contents) .context("failed to compare reencoded module with original encoding")?; } else { let mut reencode = Default::default(); RoundtripReencoder - .parse_core_module(&mut reencode, wasmparser::Parser::new(0), contents) + .parse_core_module(&mut reencode, self.parser(), contents) .context("failed to reencode module")?; self.binary_compare(&reencode.finish(), contents) .context("failed to compare reencoded module with original encoding")?; @@ -482,6 +470,32 @@ impl Opts { Ok(()) } + /// Prints `contents` with `wasmprinter`, decoding it with respect to + /// `self.features`. + fn print(&self, contents: &[u8], fold_instructions: bool) -> Result { + let mut config = wasmprinter::Config::new(); + config.features(self.features.features()); + config.fold_instructions(fold_instructions); + let mut text = String::new(); + config + .print(contents, &mut PrintFmtWrite(&mut text)) + .with_context(|| { + if fold_instructions { + "failed to print wasm in folded form" + } else { + "failed to print wasm" + } + })?; + Ok(text) + } + + /// Creates a parser which decodes with respect to `self.features`. + fn parser(&self) -> wasmparser::Parser { + let mut parser = wasmparser::Parser::new(0); + parser.set_features(self.features.features()); + parser + } + /// Tests that `contents` is valid wasm binary with respect to /// `self.features`. fn test_wasm_valid(&self, _test: &Path, contents: &[u8], validate_custom: bool) -> Result<()> { diff --git a/src/features.rs b/src/features.rs new file mode 100644 index 0000000000..31b7a447de --- /dev/null +++ b/src/features.rs @@ -0,0 +1,151 @@ +//! Shared parsing of the `--features` flag for subcommands. + +use anyhow::{Result, bail}; +use bitflags::Flags; +use wasmparser::WasmFeatures; + +/// Helper structure used to parse the `--features` flag of subcommands. +#[derive(clap::Parser)] +pub struct CliFeatures { + /// Comma-separated list of WebAssembly features to enable during + /// parsing and validation. + /// + /// If a "-" character is present in front of a feature it will disable that + /// feature. For example "-simd" will disable the simd proposal. + /// + /// The placeholder "all" can be used to enable all wasm features and the + /// term "-all" can be used to disable all features. + /// + /// The default set of features enabled are all WebAssembly proposals that + /// are at phase 4 or after. This means that the default set of features + /// accepted are relatively bleeding edge. Versions of the WebAssembly + /// specification can also be selected. The "wasm1" or "mvp" feature can + /// select the original WebAssembly specification and "wasm2" can be used to + /// select the 2.0 version. + /// + /// Available feature options can be found in the wasmparser crate: + /// + #[clap(long, short = 'f', value_parser = parse_features)] + features: Vec>, +} + +#[derive(Clone)] +enum FeatureAction { + Reset(WasmFeatures), + Enable(WasmFeatures), + Disable(WasmFeatures), +} + +impl CliFeatures { + /// Returns the set of features configured, starting from + /// [`WasmFeatures::default`]. + pub fn features(&self) -> WasmFeatures { + self.features_from(WasmFeatures::default()) + } + + /// Returns the set of features configured, starting from `base`. + pub fn features_from(&self, base: WasmFeatures) -> WasmFeatures { + let mut ret = base; + + for action in self.features.iter().flat_map(|v| v) { + match action { + FeatureAction::Enable(features) => { + ret |= *features; + } + FeatureAction::Disable(features) => { + ret &= !*features; + } + FeatureAction::Reset(features) => { + ret = *features; + } + } + } + + ret + } +} + +fn parse_features(arg: &str) -> Result> { + let mut ret = Vec::new(); + + const GROUPS: &[(&str, WasmFeatures)] = &[ + ("mvp", WasmFeatures::MVP), + ("wasm1", WasmFeatures::WASM1), + ("wasm2", WasmFeatures::WASM2), + ("wasm3", WasmFeatures::WASM3), + ("lime1", WasmFeatures::LIME1), + ]; + + enum Action { + ChangeAll, + Group(WasmFeatures), + Modify(WasmFeatures), + } + + fn actions() -> impl Iterator { + WasmFeatures::FLAGS + .iter() + .map(|f| (f.name(), Action::Modify(*f.value()))) + .chain( + GROUPS + .iter() + .map(|(name, features)| (*name, Action::Group(*features))), + ) + .chain([("all", Action::ChangeAll)]) + } + + fn flag_name(name: &str) -> String { + name.to_lowercase().replace('_', "-") + } + + 'outer: for part in arg.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()) { + let (enable, part) = if let Some(part) = part.strip_prefix("-") { + (false, part) + } else { + (true, part) + }; + for (name, action) in actions() { + if part != flag_name(name) { + continue; + } + match action { + Action::ChangeAll => { + ret.push(if enable { + FeatureAction::Enable(WasmFeatures::all()) + } else { + FeatureAction::Disable(WasmFeatures::all()) + }); + } + Action::Modify(feature) => { + ret.push(if enable { + FeatureAction::Enable(feature) + } else { + FeatureAction::Disable(feature) + }); + } + Action::Group(features) => { + if !enable { + bail!("cannot disable `{part}`, it can only be enabled"); + } + ret.push(FeatureAction::Reset(features)); + } + } + continue 'outer; + } + + let mut error = format!("unknown feature `{part}`\n"); + error.push_str("Valid features: "); + let mut first = true; + for (name, _) in actions() { + if first { + first = false; + } else { + error.push_str(", "); + } + error.push_str(&flag_name(name)); + } + bail!("{error}") + } + + Ok(ret) +} diff --git a/src/lib.rs b/src/lib.rs index 85aa3eff4e..3e4402991e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,10 @@ use termcolor::{Ansi, ColorChoice, NoColor, StandardStream, WriteColor}; #[cfg(any(feature = "addr2line", feature = "validate"))] pub mod addr2line; +#[cfg(any(feature = "validate", feature = "print"))] +mod features; +#[cfg(any(feature = "validate", feature = "print"))] +pub use features::CliFeatures; #[cfg(any(feature = "component", feature = "wit-dylib"))] pub mod wit; diff --git a/tests/cli/help-print-short.wat.stdout b/tests/cli/help-print-short.wat.stdout index 68f1f84126..662e15ac89 100644 --- a/tests/cli/help-print-short.wat.stdout +++ b/tests/cli/help-print-short.wat.stdout @@ -11,6 +11,9 @@ Options: files -g Shorthand for `--generate-dwarf full` + --features + Comma-separated list of WebAssembly features to enable during parsing + and validation -o, --output Where to place output -v, --verbose... diff --git a/tests/cli/help-print.wat.stdout b/tests/cli/help-print.wat.stdout index b273ee6438..b4b1592f1e 100644 --- a/tests/cli/help-print.wat.stdout +++ b/tests/cli/help-print.wat.stdout @@ -25,6 +25,26 @@ Options: -g Shorthand for `--generate-dwarf full` + --features + Comma-separated list of WebAssembly features to enable during parsing + and validation. + + If a "-" character is present in front of a feature it will disable + that feature. For example "-simd" will disable the simd proposal. + + The placeholder "all" can be used to enable all wasm features and the + term "-all" can be used to disable all features. + + The default set of features enabled are all WebAssembly proposals that + are at phase 4 or after. This means that the default set of features + accepted are relatively bleeding edge. Versions of the WebAssembly + specification can also be selected. The "wasm1" or "mvp" feature can + select the original WebAssembly specification and "wasm2" can be used + to select the 2.0 version. + + Available feature options can be found in the wasmparser crate: + + -o, --output Where to place output. diff --git a/tests/cli/help-validate-short.wat.stdout b/tests/cli/help-validate-short.wat.stdout index edc9350349..b19cd7e910 100644 --- a/tests/cli/help-validate-short.wat.stdout +++ b/tests/cli/help-validate-short.wat.stdout @@ -12,8 +12,8 @@ Options: -g Shorthand for `--generate-dwarf full` -f, --features - Comma-separated list of WebAssembly features to enable during - validation + Comma-separated list of WebAssembly features to enable during parsing + and validation -v, --verbose... Use verbose output (-v info, -vv debug, -vvv trace) --color diff --git a/tests/cli/help-validate.wat.stdout b/tests/cli/help-validate.wat.stdout index 05a248213e..6bf1f8fece 100644 --- a/tests/cli/help-validate.wat.stdout +++ b/tests/cli/help-validate.wat.stdout @@ -35,8 +35,8 @@ Options: Shorthand for `--generate-dwarf full` -f, --features - Comma-separated list of WebAssembly features to enable during - validation. + Comma-separated list of WebAssembly features to enable during parsing + and validation. If a "-" character is present in front of a feature it will disable that feature. For example "-simd" will disable the simd proposal.