Repository: bytecodealliance/wasm-tools
Pull request #1496 (2024-04-15 15:21:08)

Turn `WasmFeatures` into a bitflags type
This PR reduces the size of `WasmFeatures` from 23 bytes down to just 4 bytes.

As proposed by @alexcrichton here: https://github.com/bytecodealliance/wasm-tools/issues/1491#issuecomment-2049892243

Robbepop: @alexcrichton Implemented nearly all of your code review comments.

Patch:
diff --git a/Cargo.toml b/Cargo.toml
index d4ca4496..c2da1911 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -58,6 +58,7 @@ pretty_assertions = "1.3.0"
 semver = "1.0.0"
 smallvec = "1.11.1"
 libtest-mimic = "0.7.0"
+bitflags = "2.5.0"
 
 wasm-compose = { version = "0.203.0", path = "crates/wasm-compose" }
 wasm-encoder = { version = "0.203.0", path = "crates/wasm-encoder" }
diff --git a/crates/wasm-compose/src/graph.rs b/crates/wasm-compose/src/graph.rs
index 06c40e89..9e6387b8 100644
--- a/crates/wasm-compose/src/graph.rs
+++ b/crates/wasm-compose/src/graph.rs
@@ -98,10 +98,8 @@ impl<'a> Component<'a> {
     fn parse(name: String, path: Option<PathBuf>, bytes: Cow<'a, [u8]>) -> Result<Self> {
         let mut parser = Parser::new(0);
         let mut parsers = Vec::new();
-        let mut validator = Validator::new_with_features(WasmFeatures {
-            component_model: true,
-            ..Default::default()
-        });
+        let mut validator =
+            Validator::new_with_features(WasmFeatures::default() | WasmFeatures::COMPONENT_MODEL);
         let mut imports = IndexMap::new();
         let mut exports = IndexMap::new();
 
@@ -992,12 +990,9 @@ impl<'a> CompositionGraph<'a> {
         let bytes = CompositionGraphEncoder::new(options, self).encode()?;
 
         if options.validate {
-            Validator::new_with_features(WasmFeatures {
-                component_model: true,
-                ..Default::default()
-            })
-            .validate_all(&bytes)
-            .context("failed to validate encoded graph bytes")?;
+            Validator::new_with_features(WasmFeatures::default() | WasmFeatures::COMPONENT_MODEL)
+                .validate_all(&bytes)
+                .context("failed to validate encoded graph bytes")?;
         }
 
         Ok(bytes)
diff --git a/crates/wasm-encoder/src/core/types.rs b/crates/wasm-encoder/src/core/types.rs
index 85ef0aef..8b5f340b 100644
--- a/crates/wasm-encoder/src/core/types.rs
+++ b/crates/wasm-encoder/src/core/types.rs
@@ -650,6 +650,8 @@ impl Section for TypeSection {
 
 #[cfg(test)]
 mod tests {
+    use wasmparser::WasmFeatures;
+
     use super::*;
     use crate::Module;
 
@@ -666,10 +668,8 @@ mod tests {
         module.section(&types);
         let wasm_bytes = module.finish();
 
-        let mut validator = wasmparser::Validator::new_with_features(wasmparser::WasmFeatures {
-            gc: false,
-            ..Default::default()
-        });
+        let mut validator =
+            wasmparser::Validator::new_with_features(WasmFeatures::default() & !WasmFeatures::GC);
 
         validator.validate_all(&wasm_bytes).expect(
             "Encoding pre Wasm GC type should not accidentally use Wasm GC specific encoding",
diff --git a/crates/wasm-mutate/src/lib.rs b/crates/wasm-mutate/src/lib.rs
index 09d2df88..6cbbab56 100644
--- a/crates/wasm-mutate/src/lib.rs
+++ b/crates/wasm-mutate/src/lib.rs
@@ -320,11 +320,11 @@ impl<'wasm> WasmMutate<'wasm> {
 
 #[cfg(test)]
 pub(crate) fn validate(bytes: &[u8]) {
-    let mut validator = wasmparser::Validator::new_with_features(wasmparser::WasmFeatures {
-        memory64: true,
-        multi_memory: true,
-        ..Default::default()
-    });
+    use wasmparser::WasmFeatures;
+
+    let mut validator = wasmparser::Validator::new_with_features(
+        WasmFeatures::default() | WasmFeatures::MEMORY64 | WasmFeatures::MULTI_MEMORY,
+    );
     let err = match validator.validate_all(bytes) {
         Ok(_) => return,
         Err(e) => e,
diff --git a/crates/wasm-shrink/src/lib.rs b/crates/wasm-shrink/src/lib.rs
index e8854f83..1fa0fae8 100755
--- a/crates/wasm-shrink/src/lib.rs
+++ b/crates/wasm-shrink/src/lib.rs
@@ -8,6 +8,7 @@ use std::collections::HashSet;
 use anyhow::{Context, Result};
 use rand::{rngs::SmallRng, Rng, SeedableRng};
 use wasm_mutate::WasmMutate;
+use wasmparser::WasmFeatures;
 
 #[rustfmt::skip]
 static EMPTY_WASM: &'static [u8] = &[
@@ -216,33 +217,24 @@ impl ShrinkRun {
     }
 
     fn validate_wasm(&self, wasm: &[u8]) -> Result<()> {
-        let mut validator = wasmparser::Validator::new_with_features(wasmparser::WasmFeatures {
-            // TODO: we should have CLI flags for each Wasm proposal.
-            reference_types: true,
-            multi_value: true,
-            bulk_memory: true,
-            simd: true,
-            threads: true,
-            shared_everything_threads: false,
-            tail_call: true,
-            multi_memory: true,
-            exceptions: true,
-            memory64: true,
-            relaxed_simd: true,
-            extended_const: true,
-            mutable_global: true,
-            saturating_float_to_int: true,
-            sign_extension: true,
-            component_model: false,
-            function_references: false,
-            gc: false,
-            custom_page_sizes: false,
-            component_model_values: false,
-            component_model_nested_names: false,
-
-            floats: true,
-            memory_control: true,
-        });
+        let mut validator = wasmparser::Validator::new_with_features(
+            WasmFeatures::REFERENCE_TYPES
+                | WasmFeatures::MULTI_VALUE
+                | WasmFeatures::BULK_MEMORY
+                | WasmFeatures::SIMD
+                | WasmFeatures::THREADS
+                | WasmFeatures::TAIL_CALL
+                | WasmFeatures::MULTI_MEMORY
+                | WasmFeatures::EXCEPTIONS
+                | WasmFeatures::MEMORY64
+                | WasmFeatures::RELAXED_SIMD
+                | WasmFeatures::EXTENDED_CONST
+                | WasmFeatures::MUTABLE_GLOBAL
+                | WasmFeatures::SATURATING_FLOAT_TO_INT
+                | WasmFeatures::SIGN_EXTENSION
+                | WasmFeatures::FLOATS
+                | WasmFeatures::MEMORY_CONTROL,
+        );
 
         validator.validate_all(wasm)?;
         Ok(())
diff --git a/crates/wasmparser/src/validator.rs b/crates/wasmparser/src/validator.rs
index 964335a9..8df6535b 100644
--- a/crates/wasmparser/src/validator.rs
+++ b/crates/wasmparser/src/validator.rs
@@ -17,6 +17,7 @@ use crate::{
     limits::*, BinaryReaderError, Encoding, FromReader, FunctionBody, HeapType, Parser, Payload,
     RefType, Result, SectionLimited, ValType, WASM_COMPONENT_VERSION, WASM_MODULE_VERSION,
 };
+use bitflags::bitflags;
 use std::mem;
 use std::ops::Range;
 use std::sync::Arc;
@@ -197,98 +198,71 @@ impl Default for State {
     }
 }
 
-/// Flags for features that are enabled for validation.
-#[derive(Hash, Debug, Copy, Clone)]
-pub struct WasmFeatures {
-    /// The WebAssembly `mutable-global` proposal (enabled by default)
-    pub mutable_global: bool,
-    /// The WebAssembly `nontrapping-float-to-int-conversions` proposal (enabled by default)
-    pub saturating_float_to_int: bool,
-    /// The WebAssembly `sign-extension-ops` proposal (enabled by default)
-    pub sign_extension: bool,
-    /// The WebAssembly reference types proposal (enabled by default)
-    pub reference_types: bool,
-    /// The WebAssembly multi-value proposal (enabled by default)
-    pub multi_value: bool,
-    /// The WebAssembly bulk memory operations proposal (enabled by default)
-    pub bulk_memory: bool,
-    /// The WebAssembly SIMD proposal (enabled by default)
-    pub simd: bool,
-    /// The WebAssembly Relaxed SIMD proposal (enabled by default)
-    pub relaxed_simd: bool,
-    /// The WebAssembly threads proposal (enabled by default)
-    pub threads: bool,
-    /// The WebAssembly shared-everything-threads proposal; includes new
-    /// component model built-ins.
-    pub shared_everything_threads: bool,
-    /// The WebAssembly tail-call proposal (enabled by default)
-    pub tail_call: bool,
-    /// Whether or not floating-point instructions are enabled.
-    ///
-    /// This is enabled by default can be used to disallow floating-point
-    /// operators and types.
-    ///
-    /// This does not correspond to a WebAssembly proposal but is instead
-    /// intended for embeddings which have stricter-than-usual requirements
-    /// about execution. Floats in WebAssembly can have different NaN patterns
-    /// across hosts which can lead to host-dependent execution which some
-    /// runtimes may not desire.
-    pub floats: bool,
-    /// The WebAssembly multi memory proposal (enabled by default)
-    pub multi_memory: bool,
-    /// The WebAssembly exception handling proposal
-    pub exceptions: bool,
-    /// The WebAssembly memory64 proposal
-    pub memory64: bool,
-    /// The WebAssembly extended_const proposal
-    pub extended_const: bool,
-    /// The WebAssembly component model proposal.
-    pub component_model: bool,
-    /// The WebAssembly typed function references proposal
-    pub function_references: bool,
-    /// The WebAssembly memory control proposal
-    pub memory_control: bool,
-    /// The WebAssembly gc proposal
-    pub gc: bool,
-    /// The WebAssembly [custom-page-sizes
-    /// proposal](https://github.com/WebAssembly/custom-page-sizes).
-    pub custom_page_sizes: bool,
-    /// Support for the `value` type in the component model proposal.
-    pub component_model_values: bool,
-    /// Support for the nested namespaces and projects in component model names.
-    pub component_model_nested_names: bool,
+bitflags! {
+    /// Flags for features that are enabled for validation.
+    #[derive(Hash, Debug, Copy, Clone)]
+    pub struct WasmFeatures: u32 {
+        /// The WebAssembly `mutable-global` proposal (enabled by default)
+        const MUTABLE_GLOBAL = 1;
+        /// The WebAssembly `saturating-float-to-int` proposal (enabled by default)
+        const SATURATING_FLOAT_TO_INT = 1 << 1;
+        /// The WebAssembly `sign-extension-ops` proposal (enabled by default)
+        const SIGN_EXTENSION = 1 << 2;
+        /// The WebAssembly reference types proposal (enabled by default)
+        const REFERENCE_TYPES = 1 << 3;
+        /// The WebAssembly multi-value proposal (enabled by default)
+        const MULTI_VALUE = 1 << 4;
+        /// The WebAssembly bulk memory operations proposal (enabled by default)
+        const BULK_MEMORY = 1 << 5;
+        /// The WebAssembly SIMD proposal (enabled by default)
+        const SIMD = 1 << 6;
+        /// The WebAssembly Relaxed SIMD proposal (enabled by default)
+        const RELAXED_SIMD = 1 << 7;
+        /// The WebAssembly threads proposal (enabled by default)
+        const THREADS = 1 << 8;
+        /// The WebAssembly shared-everything-threads proposal; includes new
+        /// component model built-ins.
+        const SHARED_EVERYTHING_THREADS = 1 << 9;
+        /// The WebAssembly tail-call proposal (enabled by default)
+        const TAIL_CALL = 1 << 10;
+        /// Whether or not floating-point instructions are enabled.
+        ///
+        /// This is enabled by default can be used to disallow floating-point
+        /// operators and types.
+        ///
+        /// This does not correspond to a WebAssembly proposal but is instead
+        /// intended for embeddings which have stricter-than-usual requirements
+        /// about execution. Floats in WebAssembly can have different NaN patterns
+        /// across hosts which can lead to host-dependent execution which some
+        /// runtimes may not desire.
+        const FLOATS = 1 << 11;
+        /// The WebAssembly multi memory proposal (enabled by default)
+        const MULTI_MEMORY = 1 << 12;
+        /// The WebAssembly exception handling proposal
+        const EXCEPTIONS = 1 << 13;
+        /// The WebAssembly memory64 proposal
+        const MEMORY64 = 1 << 14;
+        /// The WebAssembly extended_const proposal
+        const EXTENDED_CONST = 1 << 15;
+        /// The WebAssembly component model proposal.
+        const COMPONENT_MODEL = 1 << 16;
+        /// The WebAssembly typed function references proposal
+        const FUNCTION_REFERENCES = 1 << 17;
+        /// The WebAssembly memory control proposal
+        const MEMORY_CONTROL = 1 << 18;
+        /// The WebAssembly gc proposal
+        const GC = 1 << 19;
+        /// The WebAssembly [custom-page-sizes
+        /// proposal](https://github.com/WebAssembly/custom-page-sizes).
+        const CUSTOM_PAGE_SIZE = 1 << 20;
+        /// Support for the `value` type in the component model proposal.
+        const COMPONENT_MODEL_VALUES = 1 << 21;
+        /// Support for the nested namespaces and projects in component model names.
+        const COMPONENT_MODEL_NESTED_NAMES = 1 << 22;
+    }
 }
 
 impl WasmFeatures {
-    /// Returns [`WasmFeatures`] with all features enabled.
-    pub fn all() -> Self {
-        WasmFeatures {
-            mutable_global: true,
-            saturating_float_to_int: true,
-            sign_extension: true,
-            reference_types: true,
-            multi_value: true,
-            bulk_memory: true,
-            simd: true,
-            relaxed_simd: true,
-            threads: true,
-            shared_everything_threads: true,
-            tail_call: true,
-            floats: true,
-            multi_memory: true,
-            exceptions: true,
-            memory64: true,
-            extended_const: true,
-            component_model: true,
-            function_references: true,
-            memory_control: true,
-            gc: true,
-            custom_page_sizes: true,
-            component_model_values: true,
-            component_model_nested_names: true,
-        }
-    }
-
     /// NOTE: This only checks that the value type corresponds to the feature set!!
     ///
     /// To check that reference types are valid, we need access to the module
@@ -297,7 +271,7 @@ impl WasmFeatures {
         match ty {
             ValType::I32 | ValType::I64 => Ok(()),
             ValType::F32 | ValType::F64 => {
-                if self.floats {
+                if self.contains(Self::FLOATS) {
                     Ok(())
                 } else {
                     Err("floating-point support is disabled")
@@ -305,7 +279,7 @@ impl WasmFeatures {
             }
             ValType::Ref(r) => self.check_ref_type(r),
             ValType::V128 => {
-                if self.simd {
+                if self.contains(Self::SIMD) {
                     Ok(())
                 } else {
                     Err("SIMD support is not enabled")
@@ -315,7 +289,7 @@ impl WasmFeatures {
     }
 
     pub(crate) fn check_ref_type(&self, r: RefType) -> Result<(), &'static str> {
-        if !self.reference_types {
+        if !self.contains(Self::REFERENCE_TYPES) {
             return Err("reference types support is not enabled");
         }
         match (r.heap_type(), r.is_nullable()) {
@@ -325,7 +299,7 @@ impl WasmFeatures {
             // Non-nullable func/extern references requires the
             // `function-references` proposal.
             (HeapType::Func | HeapType::Extern, false) => {
-                if self.function_references {
+                if self.contains(Self::FUNCTION_REFERENCES) {
                     Ok(())
                 } else {
                     Err("function references required for non-nullable types")
@@ -335,7 +309,7 @@ impl WasmFeatures {
             // Indexed types require either the function-references or gc
             // proposal as gc implies function references here.
             (HeapType::Concrete(_), _) => {
-                if self.function_references || self.gc {
+                if self.contains(Self::FUNCTION_REFERENCES) || self.contains(Self::GC) {
                     Ok(())
                 } else {
                     Err("function references required for index reference types")
@@ -354,7 +328,7 @@ impl WasmFeatures {
                 | HeapType::NoFunc,
                 _,
             ) => {
-                if self.gc {
+                if self.contains(Self::GC) {
                     Ok(())
                 } else {
                     Err("heap types not supported without the gc feature")
@@ -363,7 +337,7 @@ impl WasmFeatures {
 
             // These types were added in the exception-handling proposal.
             (HeapType::Exn | HeapType::NoExn, _) => {
-                if self.exceptions {
+                if self.contains(Self::EXCEPTIONS) {
                     Ok(())
                 } else {
                     Err("exception refs not supported without the exception handling feature")
@@ -375,34 +349,19 @@ impl WasmFeatures {
 
 impl Default for WasmFeatures {
     fn default() -> WasmFeatures {
-        WasmFeatures {
-            // Off-by-default features.
-            exceptions: false,
-            memory64: false,
-            extended_const: false,
-            function_references: false,
-            memory_control: false,
-            gc: false,
-            custom_page_sizes: false,
-            component_model_values: false,
-            component_model_nested_names: false,
-            shared_everything_threads: false,
-
-            // On-by-default features (phase 4 or greater).
-            mutable_global: true,
-            saturating_float_to_int: true,
-            sign_extension: true,
-            bulk_memory: true,
-            multi_value: true,
-            reference_types: true,
-            tail_call: true,
-            simd: true,
-            floats: true,
-            relaxed_simd: true,
-            threads: true,
-            multi_memory: true,
-            component_model: true,
-        }
+        Self::MUTABLE_GLOBAL
+            | Self::SATURATING_FLOAT_TO_INT
+            | Self::SIGN_EXTENSION
+            | Self::REFERENCE_TYPES
+            | Self::MULTI_VALUE
+            | Self::BULK_MEMORY
+            | Self::SIMD
+            | Self::RELAXED_SIMD
+            | Self::THREADS
+            | Self::TAIL_CALL
+            | Self::FLOATS
+            | Self::MULTI_MEMORY
+            | Self::COMPONENT_MODEL
     }
 }
 
@@ -626,7 +585,7 @@ impl Validator {
                 }
             }
             Encoding::Component => {
-                if !self.features.component_model {
+                if !self.features.contains(WasmFeatures::COMPONENT_MODEL) {
                     bail!(
                         range.start,
                         "unknown binary version and encoding combination: {num:#x} and 0x1, \
@@ -772,7 +731,7 @@ impl Validator {
     ///
     /// This method should only be called when parsing a module.
     pub fn tag_section(&mut self, section: &crate::TagSectionReader<'_>) -> Result<()> {
-        if !self.features.exceptions {
+        if !self.features.contains(WasmFeatures::EXCEPTIONS) {
             return Err(BinaryReaderError::new(
                 "exceptions proposal not enabled",
                 section.range().start,
@@ -1437,7 +1396,7 @@ impl Validator {
     {
         let offset = section.range().start;
 
-        if !self.features.component_model {
+        if !self.features.contains(WasmFeatures::COMPONENT_MODEL) {
             return Err(BinaryReaderError::new(
                 "component model feature is not enabled",
                 offset,
@@ -1488,10 +1447,8 @@ mod tests {
         "#,
         )?;
 
-        let mut validator = Validator::new_with_features(WasmFeatures {
-            exceptions: true,
-            ..Default::default()
-        });
+        let mut validator =
+            Validator::new_with_features(WasmFeatures::default() | WasmFeatures::EXCEPTIONS);
 
         let types = validator.validate_all(&bytes)?;
 
@@ -1579,10 +1536,8 @@ mod tests {
         "#,
         )?;
 
-        let mut validator = Validator::new_with_features(WasmFeatures {
-            component_model: true,
-            ..Default::default()
-        });
+        let mut validator =
+            Validator::new_with_features(WasmFeatures::default() | WasmFeatures::COMPONENT_MODEL);
 
         let types = validator.validate_all(&bytes)?;
 
@@ -1614,10 +1569,8 @@ mod tests {
         "#,
         )?;
 
-        let mut validator = Validator::new_with_features(WasmFeatures {
-            component_model: true,
-            ..Default::default()
-        });
+        let mut validator =
+            Validator::new_with_features(WasmFeatures::default() | WasmFeatures::COMPONENT_MODEL);
 
         let types = validator.validate_all(&bytes)?;
 
diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs
index 77ab9f1f..aeb73748 100644
--- a/crates/wasmparser/src/validator/component.rs
+++ b/crates/wasmparser/src/validator/component.rs
@@ -1191,7 +1191,7 @@ impl ComponentState {
         types: &mut TypeList,
         offset: usize,
     ) -> Result<()> {
-        if !features.component_model_values {
+        if !features.contains(WasmFeatures::COMPONENT_MODEL_VALUES) {
             bail!(
                 offset,
                 "support for component model `value`s is not enabled"
@@ -2987,7 +2987,7 @@ impl ComponentState {
     }
 
     fn check_value_support(&self, features: &WasmFeatures, offset: usize) -> Result<()> {
-        if !features.component_model_values {
+        if !features.contains(WasmFeatures::COMPONENT_MODEL_VALUES) {
             bail!(
                 offset,
                 "support for component model `value`s is not enabled"
diff --git a/crates/wasmparser/src/validator/core.rs b/crates/wasmparser/src/validator/core.rs
index eaadea51..a71c34b7 100644
--- a/crates/wasmparser/src/validator/core.rs
+++ b/crates/wasmparser/src/validator/core.rs
@@ -155,7 +155,7 @@ impl ModuleState {
                 }
             }
             TableInit::Expr(expr) => {
-                if !features.function_references {
+                if !features.contains(WasmFeatures::FUNCTION_REFERENCES) {
                     bail!(
                         offset,
                         "tables with expression initializers require \
@@ -225,7 +225,7 @@ impl ModuleState {
                 self.check_const_expr(&offset_expr, ValType::I32, features, types)?;
             }
             ElementKind::Passive | ElementKind::Declared => {
-                if !features.bulk_memory {
+                if !features.contains(WasmFeatures::BULK_MEMORY) {
                     return Err(BinaryReaderError::new(
                         "bulk memory must be enabled",
                         offset,
@@ -317,7 +317,7 @@ impl ModuleState {
             }
 
             fn validate_extended_const(&mut self, op: &str) -> Result<()> {
-                if self.ops.features.extended_const {
+                if self.ops.features.contains(WasmFeatures::EXTENDED_CONST) {
                     Ok(())
                 } else {
                     Err(BinaryReaderError::new(
@@ -331,7 +331,7 @@ impl ModuleState {
             }
 
             fn validate_gc(&mut self, op: &str) -> Result<()> {
-                if self.features.gc {
+                if self.features.contains(WasmFeatures::GC) {
                     Ok(())
                 } else {
                     Err(BinaryReaderError::new(
@@ -348,7 +348,8 @@ impl ModuleState {
                 let module = &self.resources.module;
                 let global = module.global_at(index, self.offset)?;
 
-                if index >= module.num_imported_globals && !self.features.gc {
+                if index >= module.num_imported_globals && !self.features.contains(WasmFeatures::GC)
+                {
                     return Err(BinaryReaderError::new(
                         "constant expression required: global.get of locally defined global",
                         self.offset,
@@ -557,7 +558,7 @@ impl Module {
         check_limit: bool,
     ) -> Result<()> {
         debug_assert!(rec_group.is_explicit_rec_group() || rec_group.types().len() == 1);
-        if rec_group.is_explicit_rec_group() && !features.gc {
+        if rec_group.is_explicit_rec_group() && !features.contains(WasmFeatures::GC) {
             bail!(
                 offset,
                 "rec group usage requires `gc` proposal to be enabled"
@@ -603,7 +604,7 @@ impl Module {
         offset: usize,
     ) -> Result<()> {
         let ty = &types[id];
-        if !features.gc && (!ty.is_final || ty.supertype_idx.is_some()) {
+        if !features.contains(WasmFeatures::GC) && (!ty.is_final || ty.supertype_idx.is_some()) {
             bail!(offset, "gc proposal must be enabled to use subtypes");
         }
 
@@ -639,7 +640,7 @@ impl Module {
                 for ty in t.params().iter().chain(t.results()) {
                     check(ty)?;
                 }
-                if t.results().len() > 1 && !features.multi_value {
+                if t.results().len() > 1 && !features.contains(WasmFeatures::MULTI_VALUE) {
                     return Err(BinaryReaderError::new(
                         "func type returns multiple values but the multi-value feature is not enabled",
                         offset,
@@ -647,7 +648,7 @@ impl Module {
                 }
             }
             CompositeType::Array(t) => {
-                if !features.gc {
+                if !features.contains(WasmFeatures::GC) {
                     return Err(BinaryReaderError::new(
                         "array indexed types not supported without the gc feature",
                         offset,
@@ -659,7 +660,7 @@ impl Module {
                 };
             }
             CompositeType::Struct(t) => {
-                if !features.gc {
+                if !features.contains(WasmFeatures::GC) {
                     return Err(BinaryReaderError::new(
                         "struct indexed types not supported without the gc feature",
                         offset,
@@ -704,7 +705,7 @@ impl Module {
                 (self.tags.len(), MAX_WASM_TAGS, "tags")
             }
             TypeRef::Global(ty) => {
-                if !features.mutable_global && ty.mutable {
+                if !features.contains(WasmFeatures::MUTABLE_GLOBAL) && ty.mutable {
                     return Err(BinaryReaderError::new(
                         "mutable global support is not enabled",
                         offset,
@@ -737,7 +738,7 @@ impl Module {
         check_limit: bool,
         types: &TypeList,
     ) -> Result<()> {
-        if !features.mutable_global {
+        if !features.contains(WasmFeatures::MUTABLE_GLOBAL) {
             if let EntityType::Global(global_type) = ty {
                 if global_type.mutable {
                     return Err(BinaryReaderError::new(
@@ -877,7 +878,7 @@ impl Module {
     ) -> Result<()> {
         self.check_limits(ty.initial, ty.maximum, offset)?;
         let (page_size, page_size_log2) = if let Some(page_size_log2) = ty.page_size_log2 {
-            if !features.custom_page_sizes {
+            if !features.contains(WasmFeatures::CUSTOM_PAGE_SIZE) {
                 return Err(BinaryReaderError::new(
                     "the custom page sizes proposal must be enabled to \
                      customize a memory's page size",
@@ -897,7 +898,7 @@ impl Module {
             (DEFAULT_WASM_PAGE_SIZE, page_size_log2)
         };
         let (true_maximum, err) = if ty.memory64 {
-            if !features.memory64 {
+            if !features.contains(WasmFeatures::MEMORY64) {
                 return Err(BinaryReaderError::new(
                     "memory64 must be enabled for 64-bit memories",
                     offset,
@@ -926,7 +927,7 @@ impl Module {
             }
         }
         if ty.shared {
-            if !features.threads {
+            if !features.contains(WasmFeatures::THREADS) {
                 return Err(BinaryReaderError::new(
                     "threads must be enabled for shared memories",
                     offset,
@@ -1030,7 +1031,7 @@ impl Module {
         types: &TypeList,
         offset: usize,
     ) -> Result<()> {
-        if !features.exceptions {
+        if !features.contains(WasmFeatures::EXCEPTIONS) {
             return Err(BinaryReaderError::new(
                 "exceptions proposal not enabled",
                 offset,
@@ -1052,7 +1053,7 @@ impl Module {
         features: &WasmFeatures,
         offset: usize,
     ) -> Result<()> {
-        if ty.shared && !features.shared_everything_threads {
+        if ty.shared && !features.contains(WasmFeatures::SHARED_EVERYTHING_THREADS) {
             return Err(BinaryReaderError::new(
                 "shared globals require the shared-everything-threads proposal",
                 offset,
@@ -1077,7 +1078,7 @@ impl Module {
     }
 
     pub fn max_tables(&self, features: &WasmFeatures) -> usize {
-        if features.reference_types {
+        if features.contains(WasmFeatures::REFERENCE_TYPES) {
             MAX_WASM_TABLES
         } else {
             1
@@ -1085,7 +1086,7 @@ impl Module {
     }
 
     pub fn max_memories(&self, features: &WasmFeatures) -> usize {
-        if features.multi_memory {
+        if features.contains(WasmFeatures::MULTI_MEMORY) {
             MAX_WASM_MEMORIES
         } else {
             1
diff --git a/crates/wasmparser/src/validator/core/canonical.rs b/crates/wasmparser/src/validator/core/canonical.rs
index 4e4b9e97..9a43fd3b 100644
--- a/crates/wasmparser/src/validator/core/canonical.rs
+++ b/crates/wasmparser/src/validator/core/canonical.rs
@@ -140,7 +140,7 @@ impl<'a> TypeCanonicalizer<'a> {
     }
 
     fn allow_gc(&self) -> bool {
-        self.features.map_or(true, |f| f.gc)
+        self.features.map_or(true, |f| f.contains(WasmFeatures::GC))
     }
 
     fn canonicalize_rec_group(&mut self, rec_group: &mut RecGroup) -> Result<()> {
diff --git a/crates/wasmparser/src/validator/func.rs b/crates/wasmparser/src/validator/func.rs
index 9627cffe..2f18fb55 100644
--- a/crates/wasmparser/src/validator/func.rs
+++ b/crates/wasmparser/src/validator/func.rs
@@ -79,7 +79,7 @@ impl<T: WasmModuleResources> FuncValidator<T> {
     pub fn validate(&mut self, body: &FunctionBody<'_>) -> Result<()> {
         let mut reader = body.get_binary_reader();
         self.read_locals(&mut reader)?;
-        reader.allow_memarg64(self.validator.features.memory64);
+        reader.allow_memarg64(self.validator.features.contains(WasmFeatures::MEMORY64));
         while !reader.eof() {
             reader.visit_operator(&mut self.visitor(reader.original_position()))??;
         }
diff --git a/crates/wasmparser/src/validator/names.rs b/crates/wasmparser/src/validator/names.rs
index ee9c71a4..c5250b17 100644
--- a/crates/wasmparser/src/validator/names.rs
+++ b/crates/wasmparser/src/validator/names.rs
@@ -279,10 +279,7 @@ impl ComponentName {
         Self::new_with_features(
             name,
             offset,
-            WasmFeatures {
-                component_model: true,
-                ..Default::default()
-            },
+            WasmFeatures::default() | WasmFeatures::COMPONENT_MODEL,
         )
     }
 
@@ -634,7 +631,10 @@ impl<'a> ComponentNameParser<'a> {
         self.expect_str(":")?;
         self.take_kebab()?;
 
-        if self.features.component_model_nested_names {
+        if self
+            .features
+            .contains(WasmFeatures::COMPONENT_MODEL_NESTED_NAMES)
+        {
             // Take the remaining package namespaces and name
             while self.next.starts_with(':') {
                 self.expect_str(":")?;
@@ -647,7 +647,10 @@ impl<'a> ComponentNameParser<'a> {
             self.expect_str("/")?;
             self.take_kebab()?;
 
-            if self.features.component_model_nested_names {
+            if self
+                .features
+                .contains(WasmFeatures::COMPONENT_MODEL_NESTED_NAMES)
+            {
                 while self.next.starts_with('/') {
                     self.expect_str("/")?;
                     self.take_kebab()?;
diff --git a/crates/wasmparser/src/validator/operators.rs b/crates/wasmparser/src/validator/operators.rs
index c0a70c14..5ad1c96a 100644
--- a/crates/wasmparser/src/validator/operators.rs
+++ b/crates/wasmparser/src/validator/operators.rs
@@ -737,7 +737,7 @@ where
     }
 
     fn check_floats_enabled(&self) -> Result<()> {
-        if !self.features.floats {
+        if !self.features.contains(WasmFeatures::FLOATS) {
             bail!(self.offset, "floating-point instruction disallowed");
         }
         Ok(())
@@ -768,7 +768,7 @@ where
                 .resources
                 .check_value_type(t, &self.features, self.offset),
             BlockType::FuncType(idx) => {
-                if !self.features.multi_value {
+                if !self.features.contains(WasmFeatures::MULTI_VALUE) {
                     bail!(
                         self.offset,
                         "blocks, loops, and ifs may only produce a resulttype \
@@ -1207,7 +1207,7 @@ macro_rules! validate_proposal {
 
     (validate self mvp) => {};
     (validate $self:ident $proposal:ident) => {
-        $self.check_enabled($self.0.features.$proposal, validate_proposal!(desc $proposal))?
+        $self.check_enabled($self.0.features.contains(validate_proposal!(bitflags $proposal)), validate_proposal!(desc $proposal))?
     };
 
     (desc simd) => ("SIMD");
@@ -1222,6 +1222,19 @@ macro_rules! validate_proposal {
     (desc function_references) => ("function references");
     (desc memory_control) => ("memory control");
     (desc gc) => ("gc");
+
+    (bitflags sign_extension) => (WasmFeatures::SIGN_EXTENSION);
+    (bitflags saturating_float_to_int) => (WasmFeatures::SATURATING_FLOAT_TO_INT);
+    (bitflags bulk_memory) => (WasmFeatures::BULK_MEMORY);
+    (bitflags simd) => (WasmFeatures::SIMD);
+    (bitflags relaxed_simd) => (WasmFeatures::RELAXED_SIMD);
+    (bitflags exceptions) => (WasmFeatures::EXCEPTIONS);
+    (bitflags tail_call) => (WasmFeatures::TAIL_CALL);
+    (bitflags reference_types) => (WasmFeatures::REFERENCE_TYPES);
+    (bitflags function_references) => (WasmFeatures::FUNCTION_REFERENCES);
+    (bitflags threads) => (WasmFeatures::THREADS);
+    (bitflags gc) => (WasmFeatures::GC);
+    (bitflags memory_control) => (WasmFeatures::MEMORY_CONTROL);
 }
 
 impl<'a, T> VisitOperator<'a> for WasmProposalValidator<'_, '_, T>
@@ -1509,7 +1522,7 @@ where
         table_index: u32,
         table_byte: u8,
     ) -> Self::Output {
-        if table_byte != 0 && !self.features.reference_types {
+        if table_byte != 0 && !self.features.contains(WasmFeatures::REFERENCE_TYPES) {
             bail!(
                 self.offset,
                 "reference-types not enabled: zero byte expected"
@@ -1754,7 +1767,7 @@ where
         Ok(())
     }
     fn visit_memory_size(&mut self, mem: u32, mem_byte: u8) -> Self::Output {
-        if mem_byte != 0 && !self.features.multi_memory {
+        if mem_byte != 0 && !self.features.contains(WasmFeatures::MULTI_MEMORY) {
             bail!(self.offset, "multi-memory not enabled: zero byte expected");
         }
         let index_ty = self.check_memory_index(mem)?;
@@ -1762,7 +1775,7 @@ where
         Ok(())
     }
     fn visit_memory_grow(&mut self, mem: u32, mem_byte: u8) -> Self::Output {
-        if mem_byte != 0 && !self.features.multi_memory {
+        if mem_byte != 0 && !self.features.contains(WasmFeatures::MULTI_MEMORY) {
             bail!(self.offset, "multi-memory not enabled: zero byte expected");
         }
         let index_ty = self.check_memory_index(mem)?;
diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs
index fa959527..988d7af4 100644
--- a/crates/wit-component/src/encoding.rs
+++ b/crates/wit-component/src/encoding.rs
@@ -2113,10 +2113,9 @@ impl ComponentEncoder {
         let bytes = state.component.finish();
 
         if self.validate {
-            let mut validator = Validator::new_with_features(WasmFeatures {
-                component_model: true,
-                ..Default::default()
-            });
+            let mut validator = Validator::new_with_features(
+                WasmFeatures::default() | WasmFeatures::COMPONENT_MODEL,
+            );
 
             validator
                 .validate_all(&bytes)
diff --git a/crates/wit-component/src/semver_check.rs b/crates/wit-component/src/semver_check.rs
index 7854c28d..66d01566 100644
--- a/crates/wit-component/src/semver_check.rs
+++ b/crates/wit-component/src/semver_check.rs
@@ -90,12 +90,9 @@ pub fn semver_check(mut resolve: Resolve, prev: WorldId, new: WorldId) -> Result
     // error message is produced here an attempt is made to make it more
     // understandable but there's only but so good these errors can be with this
     // strategy.
-    Validator::new_with_features(WasmFeatures {
-        component_model: true,
-        ..Default::default()
-    })
-    .validate_all(&bytes)
-    .context("new world is not semver-compatible with the previous world")?;
+    Validator::new_with_features(WasmFeatures::default() | WasmFeatures::COMPONENT_MODEL)
+        .validate_all(&bytes)
+        .context("new world is not semver-compatible with the previous world")?;
 
     Ok(())
 }
diff --git a/crates/wit-component/src/targets.rs b/crates/wit-component/src/targets.rs
index cfc252f2..c7a291ee 100644
--- a/crates/wit-component/src/targets.rs
+++ b/crates/wit-component/src/targets.rs
@@ -36,12 +36,9 @@ pub fn targets(resolve: &Resolve, world: WorldId, component_to_test: &[u8]) -> R
 
     let bytes = root_component.finish();
 
-    Validator::new_with_features(WasmFeatures {
-        component_model: true,
-        ..Default::default()
-    })
-    .validate_all(&bytes)
-    .context("failed to validate encoded bytes")?;
+    Validator::new_with_features(WasmFeatures::default() | WasmFeatures::COMPONENT_MODEL)
+        .validate_all(&bytes)
+        .context("failed to validate encoded bytes")?;
 
     Ok(())
 }
diff --git a/fuzz/src/lib.rs b/fuzz/src/lib.rs
index bf4806c8..2cf513f1 100644
--- a/fuzz/src/lib.rs
+++ b/fuzz/src/lib.rs
@@ -1,6 +1,7 @@
 use libfuzzer_sys::arbitrary::{Result, Unstructured};
 use std::fmt::Debug;
 use wasm_smith::{Component, Config, Module};
+use wasmparser::WasmFeatures;
 
 pub mod incremental_parse;
 pub mod mutate;
@@ -77,24 +78,26 @@ pub fn generate_valid_component(
 }
 
 pub fn validator_for_config(config: &Config) -> wasmparser::Validator {
-    wasmparser::Validator::new_with_features(wasmparser::WasmFeatures {
-        multi_value: config.multi_value_enabled,
-        multi_memory: config.max_memories > 1,
-        bulk_memory: config.bulk_memory_enabled,
-        reference_types: config.reference_types_enabled,
-        simd: config.simd_enabled,
-        relaxed_simd: config.relaxed_simd_enabled,
-        memory64: config.memory64_enabled,
-        threads: config.threads_enabled,
-        exceptions: config.exceptions_enabled,
-        // TODO: determine our larger story for function-references in
-        // wasm-tools and whether we should just have a Wasm GC flag since
-        // function-references is effectively part of the Wasm GC proposal at
-        // this point.
-        function_references: config.gc_enabled,
-        gc: config.gc_enabled,
-        ..wasmparser::WasmFeatures::default()
-    })
+    let mut features = WasmFeatures::default();
+    features.set(WasmFeatures::MULTI_VALUE, config.multi_value_enabled);
+    features.set(WasmFeatures::MULTI_MEMORY, config.max_memories > 1);
+    features.set(WasmFeatures::BULK_MEMORY, config.bulk_memory_enabled);
+    features.set(
+        WasmFeatures::REFERENCE_TYPES,
+        config.reference_types_enabled,
+    );
+    features.set(WasmFeatures::SIMD, config.simd_enabled);
+    features.set(WasmFeatures::RELAXED_SIMD, config.relaxed_simd_enabled);
+    features.set(WasmFeatures::MEMORY64, config.memory64_enabled);
+    features.set(WasmFeatures::THREADS, config.threads_enabled);
+    features.set(WasmFeatures::EXCEPTIONS, config.exceptions_enabled);
+    // TODO: determine our larger story for function-references in
+    // wasm-tools and whether we should just have a Wasm GC flag since
+    // function-references is effectively part of the Wasm GC proposal at
+    // this point.
+    features.set(WasmFeatures::FUNCTION_REFERENCES, config.gc_enabled);
+    features.set(WasmFeatures::GC, config.gc_enabled);
+    wasmparser::Validator::new_with_features(features)
 }
 
 // Optionally log the module and its configuration if we've gotten this
diff --git a/fuzz/src/mutate.rs b/fuzz/src/mutate.rs
index 4b201e27..e365ea68 100644
--- a/fuzz/src/mutate.rs
+++ b/fuzz/src/mutate.rs
@@ -64,10 +64,10 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
     // wasm-smith's generation of modules and wasm-mutate otherwise won't add
     // itself if it doesn't already exist.
     let mut features = WasmFeatures::default();
-    features.relaxed_simd = config.relaxed_simd_enabled;
-    features.multi_memory = config.max_memories > 1;
-    features.memory64 = config.memory64_enabled;
-    features.threads = config.threads_enabled;
+    features.set(WasmFeatures::RELAXED_SIMD, config.relaxed_simd_enabled);
+    features.set(WasmFeatures::MULTI_MEMORY, config.max_memories > 1);
+    features.set(WasmFeatures::MEMORY64, config.memory64_enabled);
+    features.set(WasmFeatures::THREADS, config.threads_enabled);
 
     for (i, mutated_wasm) in iterator.take(10).enumerate() {
         let mutated_wasm = match mutated_wasm {
diff --git a/fuzz/src/no_traps.rs b/fuzz/src/no_traps.rs
index 70915bc6..375d75a6 100644
--- a/fuzz/src/no_traps.rs
+++ b/fuzz/src/no_traps.rs
@@ -1,4 +1,5 @@
 use arbitrary::{Result, Unstructured};
+use wasmparser::WasmFeatures;
 #[cfg(feature = "wasmtime")]
 use wasmtime::*;
 
@@ -108,18 +109,19 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
 
 fn validate_module(config: wasm_smith::Config, wasm_bytes: &Vec<u8>) {
     // Validate the module or component and assert that it passes validation.
-    let mut validator = wasmparser::Validator::new_with_features(wasmparser::WasmFeatures {
-        component_model: false,
-        multi_value: config.multi_value_enabled,
-        multi_memory: config.max_memories > 1,
-        bulk_memory: true,
-        reference_types: true,
-        simd: config.simd_enabled,
-        relaxed_simd: config.relaxed_simd_enabled,
-        memory64: config.memory64_enabled,
-        threads: config.threads_enabled,
-        exceptions: config.exceptions_enabled,
-        ..wasmparser::WasmFeatures::default()
+    let mut validator = wasmparser::Validator::new_with_features({
+        let mut features = WasmFeatures::default();
+        features.remove(WasmFeatures::COMPONENT_MODEL);
+        features.set(WasmFeatures::MULTI_VALUE, config.multi_value_enabled);
+        features.set(WasmFeatures::MULTI_MEMORY, config.max_memories > 1);
+        features.insert(WasmFeatures::BULK_MEMORY);
+        features.insert(WasmFeatures::REFERENCE_TYPES);
+        features.set(WasmFeatures::SIMD, config.simd_enabled);
+        features.set(WasmFeatures::RELAXED_SIMD, config.relaxed_simd_enabled);
+        features.set(WasmFeatures::MEMORY64, config.memory64_enabled);
+        features.set(WasmFeatures::THREADS, config.threads_enabled);
+        features.set(WasmFeatures::EXCEPTIONS, config.exceptions_enabled);
+        features
     });
     if let Err(e) = validator.validate_all(wasm_bytes) {
         panic!("Invalid module: {}", e);
diff --git a/fuzz/src/roundtrip_wit.rs b/fuzz/src/roundtrip_wit.rs
index 9a4ef1da..d5e91c58 100644
--- a/fuzz/src/roundtrip_wit.rs
+++ b/fuzz/src/roundtrip_wit.rs
@@ -1,5 +1,6 @@
 use arbitrary::{Result, Unstructured};
 use std::path::Path;
+use wasmparser::WasmFeatures;
 use wit_component::*;
 use wit_parser::{Resolve, SourceMap};
 
@@ -49,10 +50,9 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
             .encode()
             .unwrap();
         write_file("dummy.component.wasm", &wasm);
-        wasmparser::Validator::new_with_features(wasmparser::WasmFeatures {
-            component_model: true,
-            ..Default::default()
-        })
+        wasmparser::Validator::new_with_features(
+            WasmFeatures::default() | WasmFeatures::COMPONENT_MODEL,
+        )
         .validate_all(&wasm)
         .unwrap();
 
diff --git a/fuzz/src/validate.rs b/fuzz/src/validate.rs
index 66410ae1..12eae6af 100644
--- a/fuzz/src/validate.rs
+++ b/fuzz/src/validate.rs
@@ -27,31 +27,7 @@ pub fn validate_maybe_invalid_module(u: &mut Unstructured<'_>) -> Result<()> {
 
 pub fn validate_raw_bytes(u: &mut Unstructured<'_>) -> Result<()> {
     // Enable arbitrary combinations of features to validate the input bytes.
-    let validator = Validator::new_with_features(WasmFeatures {
-        reference_types: u.arbitrary()?,
-        multi_value: u.arbitrary()?,
-        threads: u.arbitrary()?,
-        shared_everything_threads: u.arbitrary()?,
-        simd: u.arbitrary()?,
-        component_model: u.arbitrary()?,
-        tail_call: u.arbitrary()?,
-        bulk_memory: u.arbitrary()?,
-        floats: u.arbitrary()?,
-        multi_memory: u.arbitrary()?,
-        memory64: u.arbitrary()?,
-        exceptions: u.arbitrary()?,
-        relaxed_simd: u.arbitrary()?,
-        extended_const: u.arbitrary()?,
-        mutable_global: u.arbitrary()?,
-        saturating_float_to_int: u.arbitrary()?,
-        sign_extension: u.arbitrary()?,
-        memory_control: u.arbitrary()?,
-        function_references: u.arbitrary()?,
-        gc: u.arbitrary()?,
-        custom_page_sizes: u.arbitrary()?,
-        component_model_values: u.arbitrary()?,
-        component_model_nested_names: u.arbitrary()?,
-    });
+    let validator = Validator::new_with_features(WasmFeatures::from_bits_truncate(u.arbitrary()?));
     let wasm = u.bytes(u.len())?;
     crate::log_wasm(wasm, "");
     validate_all(validator, wasm);
diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs
index fa07d42c..bc3615dd 100644
--- a/src/bin/wasm-tools/component.rs
+++ b/src/bin/wasm-tools/component.rs
@@ -8,6 +8,7 @@ use anyhow::{bail, Context, Result};
 use clap::Parser;
 
 use wasm_tools::Output;
+use wasmparser::WasmFeatures;
 use wat::Detect;
 use wit_component::{
     embed_component_metadata, ComponentEncoder, DecodedWasm, Linker, StringEncoding, WitPrinter,
@@ -552,10 +553,9 @@ impl WitOpts {
 
         let bytes = wit_component::encode(None, decoded.resolve(), decoded.package())?;
         if !self.skip_validation {
-            wasmparser::Validator::new_with_features(wasmparser::WasmFeatures {
-                component_model: true,
-                ..Default::default()
-            })
+            wasmparser::Validator::new_with_features(
+                WasmFeatures::default() | WasmFeatures::COMPONENT_MODEL,
+            )
             .validate_all(&bytes)?;
         }
         self.output.output(Output::Wasm {
diff --git a/src/bin/wasm-tools/compose.rs b/src/bin/wasm-tools/compose.rs
index 0d781e1a..c2fbc89d 100644
--- a/src/bin/wasm-tools/compose.rs
+++ b/src/bin/wasm-tools/compose.rs
@@ -66,18 +66,15 @@ impl Opts {
         if config.skip_validation {
             log::debug!("output validation was skipped");
         } else {
-            Validator::new_with_features(WasmFeatures {
-                component_model: true,
-                ..Default::default()
-            })
-            .validate_all(&bytes)
-            .with_context(|| {
-                let output = match self.output.output_path() {
-                    Some(s) => format!(" `{}`", s.display()),
-                    None => String::new(),
-                };
-                format!("failed to validate output component{output}")
-            })?;
+            Validator::new_with_features(WasmFeatures::default() | WasmFeatures::COMPONENT_MODEL)
+                .validate_all(&bytes)
+                .with_context(|| {
+                    let output = match self.output.output_path() {
+                        Some(s) => format!(" `{}`", s.display()),
+                        None => String::new(),
+                    };
+                    format!("failed to validate output component{output}")
+                })?;
 
             log::debug!("output component validated successfully");
         }
diff --git a/src/bin/wasm-tools/validate.rs b/src/bin/wasm-tools/validate.rs
index 2352194d..2fdfbdb6 100644
--- a/src/bin/wasm-tools/validate.rs
+++ b/src/bin/wasm-tools/validate.rs
@@ -97,31 +97,36 @@ impl Opts {
 fn parse_features(arg: &str) -> Result<WasmFeatures> {
     let mut ret = WasmFeatures::default();
 
-    const FEATURES: &[(&str, fn(&mut WasmFeatures) -> &mut bool)] = &[
-        ("reference-types", |f| &mut f.reference_types),
-        ("function-references", |f| &mut f.function_references),
-        ("simd", |f| &mut f.simd),
-        ("threads", |f| &mut f.threads),
-        ("shared-everything-threads", |f| {
-            &mut f.shared_everything_threads
-        }),
-        ("bulk-memory", |f| &mut f.bulk_memory),
-        ("multi-value", |f| &mut f.multi_value),
-        ("tail-call", |f| &mut f.tail_call),
-        ("component-model", |f| &mut f.component_model),
-        ("component-model-values", |f| &mut f.component_model_values),
-        ("multi-memory", |f| &mut f.multi_memory),
-        ("exception-handling", |f| &mut f.exceptions),
-        ("memory64", |f| &mut f.memory64),
-        ("extended-const", |f| &mut f.extended_const),
-        ("floats", |f| &mut f.floats),
-        ("saturating-float-to-int", |f| {
-            &mut f.saturating_float_to_int
-        }),
-        ("sign-extension", |f| &mut f.sign_extension),
-        ("mutable-global", |f| &mut f.mutable_global),
-        ("relaxed-simd", |f| &mut f.relaxed_simd),
-        ("gc", |f| &mut f.gc),
+    const FEATURES: &[(&str, WasmFeatures)] = &[
+        ("reference-types", WasmFeatures::REFERENCE_TYPES),
+        ("function-references", WasmFeatures::FUNCTION_REFERENCES),
+        ("simd", WasmFeatures::SIMD),
+        ("threads", WasmFeatures::THREADS),
+        (
+            "shared-everything-threads",
+            WasmFeatures::SHARED_EVERYTHING_THREADS,
+        ),
+        ("bulk-memory", WasmFeatures::BULK_MEMORY),
+        ("multi-value", WasmFeatures::MULTI_VALUE),
+        ("tail-call", WasmFeatures::TAIL_CALL),
+        ("component-model", WasmFeatures::COMPONENT_MODEL),
+        (
+            "component-model-values",
+            WasmFeatures::COMPONENT_MODEL_VALUES,
+        ),
+        ("multi-memory", WasmFeatures::MULTI_MEMORY),
+        ("exception-handling", WasmFeatures::EXCEPTIONS),
+        ("memory64", WasmFeatures::MEMORY64),
+        ("extended-const", WasmFeatures::EXTENDED_CONST),
+        ("floats", WasmFeatures::FLOATS),
+        (
+            "saturating-float-to-int",
+            WasmFeatures::SATURATING_FLOAT_TO_INT,
+        ),
+        ("sign-extension", WasmFeatures::SIGN_EXTENSION),
+        ("mutable-global", WasmFeatures::MUTABLE_GLOBAL),
+        ("relaxed-simd", WasmFeatures::RELAXED_SIMD),
+        ("gc", WasmFeatures::GC),
     ];
 
     for part in arg.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()) {
@@ -132,18 +137,17 @@ fn parse_features(arg: &str) -> Result<WasmFeatures> {
         };
         match part {
             "all" => {
-                for (name, accessor) in FEATURES {
+                for (name, feature) in FEATURES {
                     // don't count this under "all" for now.
                     if *name == "deterministic" {
                         continue;
                     }
-
-                    *accessor(&mut ret) = enable;
+                    ret.set(*feature, enable);
                 }
             }
 
             name => {
-                let (_, accessor) = FEATURES.iter().find(|(n, _)| *n == name).ok_or_else(|| {
+                let (_, feature) = FEATURES.iter().find(|(n, _)| *n == name).ok_or_else(|| {
                     anyhow!(
                         "unknown feature `{}`\nValid features: {}",
                         name,
@@ -154,7 +158,7 @@ fn parse_features(arg: &str) -> Result<WasmFeatures> {
                             .join(", "),
                     )
                 })?;
-                *accessor(&mut ret) = enable;
+                ret.set(*feature, enable);
             }
         }
     }


Test patch:
diff --git a/crates/wasm-compose/tests/compose.rs b/crates/wasm-compose/tests/compose.rs
index a3b95281..e9e28f23 100644
--- a/crates/wasm-compose/tests/compose.rs
+++ b/crates/wasm-compose/tests/compose.rs
@@ -82,17 +82,14 @@ fn component_composing() -> Result<()> {
             let bytes =
                 r.with_context(|| format!("failed to encode for test case `{}`", test_case))?;
 
-            Validator::new_with_features(WasmFeatures {
-                component_model: true,
-                ..Default::default()
-            })
-            .validate_all(&bytes)
-            .with_context(|| {
-                format!(
-                    "failed to validate component bytes for test case `{}`",
-                    test_case
-                )
-            })?;
+            Validator::new_with_features(WasmFeatures::default() | WasmFeatures::COMPONENT_MODEL)
+                .validate_all(&bytes)
+                .with_context(|| {
+                    format!(
+                        "failed to validate component bytes for test case `{}`",
+                        test_case
+                    )
+                })?;
 
             wit_component::decode(&bytes).with_context(|| {
                 format!(
diff --git a/crates/wasm-smith/tests/common/mod.rs b/crates/wasm-smith/tests/common/mod.rs
index b47c8b14..6aad976a 100644
--- a/crates/wasm-smith/tests/common/mod.rs
+++ b/crates/wasm-smith/tests/common/mod.rs
@@ -2,32 +2,34 @@ use wasm_smith::Config;
 use wasmparser::{types::Types, Validator, WasmFeatures};
 
 pub fn parser_features_from_config(config: &Config) -> WasmFeatures {
-    WasmFeatures {
-        mutable_global: true,
-        saturating_float_to_int: config.saturating_float_to_int_enabled,
-        sign_extension: config.sign_extension_ops_enabled,
-        reference_types: config.reference_types_enabled,
-        multi_value: config.multi_value_enabled,
-        bulk_memory: config.bulk_memory_enabled,
-        simd: config.simd_enabled,
-        relaxed_simd: config.relaxed_simd_enabled,
-        multi_memory: config.max_memories > 1,
-        exceptions: config.exceptions_enabled,
-        memory64: config.memory64_enabled,
-        tail_call: config.tail_call_enabled,
-        function_references: config.gc_enabled,
-        gc: config.gc_enabled,
-        custom_page_sizes: config.custom_page_sizes_enabled,
-
-        threads: false,
-        shared_everything_threads: false,
-        floats: true,
-        extended_const: false,
-        component_model: false,
-        memory_control: false,
-        component_model_values: false,
-        component_model_nested_names: false,
-    }
+    let mut features = WasmFeatures::MUTABLE_GLOBAL | WasmFeatures::FLOATS;
+    features.set(
+        WasmFeatures::SATURATING_FLOAT_TO_INT,
+        config.saturating_float_to_int_enabled,
+    );
+    features.set(
+        WasmFeatures::SIGN_EXTENSION,
+        config.sign_extension_ops_enabled,
+    );
+    features.set(
+        WasmFeatures::REFERENCE_TYPES,
+        config.reference_types_enabled,
+    );
+    features.set(WasmFeatures::MULTI_VALUE, config.multi_value_enabled);
+    features.set(WasmFeatures::BULK_MEMORY, config.bulk_memory_enabled);
+    features.set(WasmFeatures::SIMD, config.simd_enabled);
+    features.set(WasmFeatures::RELAXED_SIMD, config.relaxed_simd_enabled);
+    features.set(WasmFeatures::MULTI_MEMORY, config.max_memories > 1);
+    features.set(WasmFeatures::EXCEPTIONS, config.exceptions_enabled);
+    features.set(WasmFeatures::MEMORY64, config.memory64_enabled);
+    features.set(WasmFeatures::TAIL_CALL, config.tail_call_enabled);
+    features.set(WasmFeatures::FUNCTION_REFERENCES, config.gc_enabled);
+    features.set(WasmFeatures::GC, config.gc_enabled);
+    features.set(
+        WasmFeatures::CUSTOM_PAGE_SIZE,
+        config.custom_page_sizes_enabled,
+    );
+    features
 }
 
 pub fn validate(validator: &mut Validator, bytes: &[u8]) -> Types {
diff --git a/crates/wasm-smith/tests/component.rs b/crates/wasm-smith/tests/component.rs
index 7d5a4015..89fdebff 100644
--- a/crates/wasm-smith/tests/component.rs
+++ b/crates/wasm-smith/tests/component.rs
@@ -18,11 +18,9 @@ fn smoke_test_component() {
             ok_count += 1;
             let component = component.to_bytes();
 
-            let mut validator =
-                wasmparser::Validator::new_with_features(wasmparser::WasmFeatures {
-                    component_model: true,
-                    ..Default::default()
-                });
+            let mut validator = wasmparser::Validator::new_with_features(
+                wasmparser::WasmFeatures::default() | wasmparser::WasmFeatures::COMPONENT_MODEL,
+            );
             if let Err(e) = validator.validate_all(&component) {
                 std::fs::write("component.wasm", &component).unwrap();
                 panic!(
diff --git a/crates/wasm-smith/tests/core.rs b/crates/wasm-smith/tests/core.rs
index 1454f629..9b18fcde 100644
--- a/crates/wasm-smith/tests/core.rs
+++ b/crates/wasm-smith/tests/core.rs
@@ -69,7 +69,7 @@ fn multi_value_disabled() {
         if let Ok(module) = Module::new(cfg, &mut u) {
             let wasm_bytes = module.to_bytes();
             let mut features = wasm_features();
-            features.multi_value = false;
+            features.remove(WasmFeatures::MULTI_VALUE);
             let mut validator = Validator::new_with_features(features);
             validate(&mut validator, &wasm_bytes);
         }
@@ -143,14 +143,5 @@ fn smoke_test_wasm_gc() {
 }
 
 fn wasm_features() -> WasmFeatures {
-    WasmFeatures {
-        multi_memory: true,
-        relaxed_simd: true,
-        memory64: true,
-        exceptions: true,
-        tail_call: true,
-        function_references: true,
-        gc: true,
-        ..WasmFeatures::default()
-    }
+    WasmFeatures::all()
 }
diff --git a/crates/wit-component/tests/interfaces.rs b/crates/wit-component/tests/interfaces.rs
index 0fec470c..1c7668f8 100644
--- a/crates/wit-component/tests/interfaces.rs
+++ b/crates/wit-component/tests/interfaces.rs
@@ -2,6 +2,7 @@ use anyhow::{Context, Result};
 use pretty_assertions::assert_eq;
 use std::fs;
 use std::path::Path;
+use wasmparser::WasmFeatures;
 use wit_component::WitPrinter;
 use wit_parser::{PackageId, Resolve, UnresolvedPackage};
 
@@ -47,10 +48,7 @@ fn run_test(path: &Path, is_dir: bool) -> Result<()> {
 
     assert_print(&resolve, package, path, is_dir)?;
 
-    let features = wasmparser::WasmFeatures {
-        component_model: true,
-        ..Default::default()
-    };
+    let features = WasmFeatures::default() | WasmFeatures::COMPONENT_MODEL;
 
     // First convert the WIT package to a binary WebAssembly output, then
     // convert that binary wasm to textual wasm, then assert it matches the
diff --git a/tests/roundtrip.rs b/tests/roundtrip.rs
index d604b874..519363d6 100644
--- a/tests/roundtrip.rs
+++ b/tests/roundtrip.rs
@@ -576,87 +576,53 @@ impl TestState {
     }
 
     fn wasmparser_validator_for(&self, test: &Path) -> Validator {
-        let mut features = WasmFeatures {
-            threads: true,
-            shared_everything_threads: false,
-            reference_types: true,
-            simd: true,
-            relaxed_simd: true,
-            exceptions: true,
-            bulk_memory: true,
-            tail_call: true,
-            component_model: false,
-            floats: true,
-            multi_value: true,
-            multi_memory: true,
-            memory64: true,
-            extended_const: true,
-            saturating_float_to_int: true,
-            sign_extension: true,
-            mutable_global: true,
-            function_references: true,
-            memory_control: true,
-            gc: true,
-            custom_page_sizes: true,
-            component_model_values: true,
-            component_model_nested_names: false,
-        };
+        let mut features = WasmFeatures::all()
+            & !WasmFeatures::SHARED_EVERYTHING_THREADS
+            & !WasmFeatures::COMPONENT_MODEL
+            & !WasmFeatures::COMPONENT_MODEL_NESTED_NAMES;
         for part in test.iter().filter_map(|t| t.to_str()) {
             match part {
                 "testsuite" => {
                     features = WasmFeatures::default();
-                    features.component_model = false;
+                    features.remove(WasmFeatures::COMPONENT_MODEL);
 
                     // NB: when these proposals are merged upstream in the spec
                     // repo then this should be removed. Currently this hasn't
                     // happened so this is required to get tests passing for
                     // when these proposals are enabled by default.
-                    features.multi_memory = false;
-                    features.threads = false;
+                    features.remove(WasmFeatures::MULTI_MEMORY);
+                    features.remove(WasmFeatures::THREADS);
                 }
                 "missing-features" => {
-                    features = WasmFeatures::default();
-                    features.simd = false;
-                    features.reference_types = false;
-                    features.multi_value = false;
-                    features.sign_extension = false;
-                    features.saturating_float_to_int = false;
-                    features.mutable_global = false;
-                    features.bulk_memory = false;
-                    features.function_references = false;
-                    features.gc = false;
-                    features.custom_page_sizes = false;
-                    features.component_model = false;
-                    features.component_model_values = false;
-                    features.shared_everything_threads = false;
+                    features = WasmFeatures::empty() | WasmFeatures::FLOATS;
                 }
-                "floats-disabled.wast" => features.floats = false,
+                "floats-disabled.wast" => features.remove(WasmFeatures::FLOATS),
                 "threads" => {
-                    features.threads = true;
-                    features.bulk_memory = false;
-                    features.reference_types = false;
+                    features.insert(WasmFeatures::THREADS);
+                    features.remove(WasmFeatures::BULK_MEMORY);
+                    features.remove(WasmFeatures::REFERENCE_TYPES);
                 }
-                "simd" => features.simd = true,
-                "exception-handling" => features.exceptions = true,
-                "tail-call" => features.tail_call = true,
-                "memory64" => features.memory64 = true,
-                "component-model" => features.component_model = true,
+                "simd" => features.insert(WasmFeatures::SIMD),
+                "exception-handling" => features.insert(WasmFeatures::EXCEPTIONS),
+                "tail-call" => features.insert(WasmFeatures::TAIL_CALL),
+                "memory64" => features.insert(WasmFeatures::MEMORY64),
+                "component-model" => features.insert(WasmFeatures::COMPONENT_MODEL),
                 "shared-everything-threads" => {
-                    features.component_model = true;
-                    features.shared_everything_threads = true;
+                    features.insert(WasmFeatures::COMPONENT_MODEL);
+                    features.insert(WasmFeatures::SHARED_EVERYTHING_THREADS);
                 }
-                "multi-memory" => features.multi_memory = true,
-                "extended-const" => features.extended_const = true,
-                "function-references" => features.function_references = true,
-                "relaxed-simd" => features.relaxed_simd = true,
-                "reference-types" => features.reference_types = true,
+                "multi-memory" => features.insert(WasmFeatures::MULTI_MEMORY),
+                "extended-const" => features.insert(WasmFeatures::EXTENDED_CONST),
+                "function-references" => features.insert(WasmFeatures::FUNCTION_REFERENCES),
+                "relaxed-simd" => features.insert(WasmFeatures::RELAXED_SIMD),
+                "reference-types" => features.insert(WasmFeatures::REFERENCE_TYPES),
                 "gc" => {
-                    features.function_references = true;
-                    features.gc = true;
+                    features.insert(WasmFeatures::FUNCTION_REFERENCES);
+                    features.insert(WasmFeatures::GC);
                 }
-                "custom-page-sizes" => features.custom_page_sizes = true,
+                "custom-page-sizes" => features.insert(WasmFeatures::CUSTOM_PAGE_SIZE),
                 "import-extended.wast" => {
-                    features.component_model_nested_names = true;
+                    features.insert(WasmFeatures::COMPONENT_MODEL_NESTED_NAMES);
                 }
                 _ => {}
             }