Repository: bytecodealliance/wasm-tools
Pull request #1499 (2024-04-16 17:19:49)

`wasmparser`: Allow exhaustive matching on wasm features
Resolves https://github.com/bytecodealliance/wasm-tools/pull/1496#issuecomment-2059497166


Patch:
diff --git a/crates/wasmparser/src/validator.rs b/crates/wasmparser/src/validator.rs
index 8df6535b..00810fc2 100644
--- a/crates/wasmparser/src/validator.rs
+++ b/crates/wasmparser/src/validator.rs
@@ -198,33 +198,110 @@ impl Default for State {
     }
 }
 
-bitflags! {
+macro_rules! define_wasm_features {
+    (
+        $(#[$outer:meta])*
+        pub struct WasmFeatures: $repr:ty {
+            $(
+                $(#[$inner:ident $($args:tt)*])*
+                pub $field:ident: $const:ident($flag:expr) = $default:expr;
+            )*
+        }
+    ) => {
+        bitflags! {
+            $(#[$outer])*
+            pub struct WasmFeatures: $repr {
+                $(
+                    $(#[$inner $($args)*])*
+                    #[doc = "\nDefaults to `"]
+                    #[doc = stringify!($default)]
+                    #[doc = "`.\n"]
+                    const $const = $flag;
+                )*
+            }
+        }
+
+        impl Default for WasmFeatures {
+            #[inline]
+            fn default() -> Self {
+                let mut features = WasmFeatures::empty();
+                $(
+                    features.set(WasmFeatures::$const, $default);
+                )*
+                features
+            }
+        }
+
+        impl WasmFeatures {
+            /// Construct a bit-packed `WasmFeatures` from the inflated struct version.
+            #[inline]
+            pub fn from_inflated(inflated: WasmFeaturesInflated) -> Self {
+                let mut features = WasmFeatures::empty();
+                $(
+                    features.set(WasmFeatures::$const, inflated.$field);
+                )*
+                features
+            }
+
+            /// Inflate these bit-packed features into a struct with a field per
+            /// feature.
+            ///
+            /// Although the inflated struct takes up much more memory than the
+            /// bit-packed version, its fields can be exhaustively matched
+            /// upon. This makes it useful for temporarily checking against,
+            /// while keeping the bit-packed version as the method of storing
+            /// the features for longer periods of time.
+            #[inline]
+            pub fn inflate(&self) -> WasmFeaturesInflated {
+                WasmFeaturesInflated {
+                    $(
+                        $field: self.contains(WasmFeatures::$const),
+                    )*
+                }
+            }
+        }
+
+        /// Inflated version of [`WasmFeatures`][crate::WasmFeatures] that
+        /// allows for exhaustive matching on fields.
+        pub struct WasmFeaturesInflated {
+            $(
+                $(#[$inner $($args)*])*
+                #[doc = "\nDefaults to `"]
+                #[doc = stringify!($default)]
+                #[doc = "`.\n"]
+                pub $field: bool,
+            )*
+        }
+    };
+}
+
+define_wasm_features! {
     /// 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 `mutable-global` proposal.
+        pub mutable_global: MUTABLE_GLOBAL(1) = true;
+        /// The WebAssembly `saturating-float-to-int` proposal.
+        pub saturating_float_to_int: SATURATING_FLOAT_TO_INT(1 << 1) = true;
+        /// The WebAssembly `sign-extension-ops` proposal.
+        pub sign_extension: SIGN_EXTENSION(1 << 2) = true;
+        /// The WebAssembly reference types proposal.
+        pub reference_types: REFERENCE_TYPES(1 << 3) = true;
+        /// The WebAssembly multi-value proposal.
+        pub multi_value: MULTI_VALUE(1 << 4) = true;
+        /// The WebAssembly bulk memory operations proposal.
+        pub bulk_memory: BULK_MEMORY(1 << 5) = true;
+        /// The WebAssembly SIMD proposal.
+        pub simd: SIMD(1 << 6) = true;
+        /// The WebAssembly Relaxed SIMD proposal.
+        pub relaxed_simd: RELAXED_SIMD(1 << 7) = true;
+        /// The WebAssembly threads proposal.
+        pub threads: THREADS(1 << 8) = true;
         /// 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;
+        pub shared_everything_threads: SHARED_EVERYTHING_THREADS(1 << 9) = false;
+        /// The WebAssembly tail-call proposal.
+        pub tail_call: TAIL_CALL(1 << 10) = true;
         /// Whether or not floating-point instructions are enabled.
         ///
         /// This is enabled by default can be used to disallow floating-point
@@ -235,30 +312,44 @@ bitflags! {
         /// 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;
+        pub floats: FLOATS(1 << 11) = true;
+        /// The WebAssembly multi memory proposal.
+        pub multi_memory: MULTI_MEMORY(1 << 12) = true;
+        /// The WebAssembly exception handling proposal.
+        pub exceptions: EXCEPTIONS(1 << 13) = false;
+        /// The WebAssembly memory64 proposal.
+        pub memory64: MEMORY64(1 << 14) = false;
+        /// The WebAssembly extended_const proposal.
+        pub extended_const: EXTENDED_CONST(1 << 15) = false;
         /// 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;
+        pub component_model: COMPONENT_MODEL(1 << 16) = true;
+        /// The WebAssembly typed function references proposal.
+        pub function_references: FUNCTION_REFERENCES(1 << 17) = false;
+        /// The WebAssembly memory control proposal.
+        pub memory_control: MEMORY_CONTROL(1 << 18) = false;
+        /// The WebAssembly gc proposal.
+        pub gc: GC(1 << 19) = false;
         /// The WebAssembly [custom-page-sizes
         /// proposal](https://github.com/WebAssembly/custom-page-sizes).
-        const CUSTOM_PAGE_SIZE = 1 << 20;
+        pub custom_page_sizes: CUSTOM_PAGE_SIZES(1 << 20) = false;
         /// Support for the `value` type in the component model proposal.
-        const COMPONENT_MODEL_VALUES = 1 << 21;
+        pub component_model_values: COMPONENT_MODEL_VALUES(1 << 21) = false;
         /// Support for the nested namespaces and projects in component model names.
-        const COMPONENT_MODEL_NESTED_NAMES = 1 << 22;
+        pub component_model_nested_names: COMPONENT_MODEL_NESTED_NAMES(1 << 22) = false;
+    }
+}
+
+impl From<WasmFeaturesInflated> for WasmFeatures {
+    #[inline]
+    fn from(inflated: WasmFeaturesInflated) -> Self {
+        Self::from_inflated(inflated)
+    }
+}
+
+impl From<WasmFeatures> for WasmFeaturesInflated {
+    #[inline]
+    fn from(features: WasmFeatures) -> Self {
+        features.inflate()
     }
 }
 
@@ -347,24 +438,6 @@ impl WasmFeatures {
     }
 }
 
-impl Default for WasmFeatures {
-    fn default() -> WasmFeatures {
-        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
-    }
-}
-
 /// Possible return values from [`Validator::payload`].
 #[allow(clippy::large_enum_variant)]
 pub enum ValidPayload<'a> {
diff --git a/crates/wasmparser/src/validator/core.rs b/crates/wasmparser/src/validator/core.rs
index a71c34b7..5999538f 100644
--- a/crates/wasmparser/src/validator/core.rs
+++ b/crates/wasmparser/src/validator/core.rs
@@ -878,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.contains(WasmFeatures::CUSTOM_PAGE_SIZE) {
+            if !features.contains(WasmFeatures::CUSTOM_PAGE_SIZES) {
                 return Err(BinaryReaderError::new(
                     "the custom page sizes proposal must be enabled to \
                      customize a memory's page size",
diff --git a/crates/wasmparser/src/validator/types.rs b/crates/wasmparser/src/validator/types.rs
index d82ba55d..1640ed8d 100644
--- a/crates/wasmparser/src/validator/types.rs
+++ b/crates/wasmparser/src/validator/types.rs
@@ -1518,6 +1518,22 @@ impl<'a> TypesRef<'a> {
         self.list.get(id)
     }
 
+    /// Get the id of the rec group that the given type id was defined within.
+    pub fn rec_group_id_of(&self, id: CoreTypeId) -> RecGroupId {
+        self.list.rec_group_id_of(id)
+    }
+
+    /// Get the types within a rec group.
+    pub fn rec_group_elements(&self, id: RecGroupId) -> impl ExactSizeIterator<Item = CoreTypeId> {
+        let range = &self.list.rec_group_elements[id];
+        (range.start.index..range.end.index).map(|index| CoreTypeId { index })
+    }
+
+    /// Get the super type of the given type id, if any.
+    pub fn supertype_of(&self, id: CoreTypeId) -> Option<CoreTypeId> {
+        self.list.supertype_of(id)
+    }
+
     /// Gets a core WebAssembly type id from a type index.
     ///
     /// Note that this is in contrast to [`TypesRef::component_type_at`] which


Test patch:
diff --git a/crates/wasm-smith/tests/common/mod.rs b/crates/wasm-smith/tests/common/mod.rs
index 6aad976a..8a238776 100644
--- a/crates/wasm-smith/tests/common/mod.rs
+++ b/crates/wasm-smith/tests/common/mod.rs
@@ -26,7 +26,7 @@ pub fn parser_features_from_config(config: &Config) -> WasmFeatures {
     features.set(WasmFeatures::FUNCTION_REFERENCES, config.gc_enabled);
     features.set(WasmFeatures::GC, config.gc_enabled);
     features.set(
-        WasmFeatures::CUSTOM_PAGE_SIZE,
+        WasmFeatures::CUSTOM_PAGE_SIZES,
         config.custom_page_sizes_enabled,
     );
     features
diff --git a/tests/roundtrip.rs b/tests/roundtrip.rs
index 519363d6..3929ac5c 100644
--- a/tests/roundtrip.rs
+++ b/tests/roundtrip.rs
@@ -620,7 +620,7 @@ impl TestState {
                     features.insert(WasmFeatures::FUNCTION_REFERENCES);
                     features.insert(WasmFeatures::GC);
                 }
-                "custom-page-sizes" => features.insert(WasmFeatures::CUSTOM_PAGE_SIZE),
+                "custom-page-sizes" => features.insert(WasmFeatures::CUSTOM_PAGE_SIZES),
                 "import-extended.wast" => {
                     features.insert(WasmFeatures::COMPONENT_MODEL_NESTED_NAMES);
                 }