wasmer_types/
features.rs

1use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
2#[cfg(feature = "enable-serde")]
3use serde::{Deserialize, Serialize};
4#[cfg(feature = "detect-wasm-features")]
5use wasmparser::{Parser, Payload, Validator, WasmFeatures};
6
7/// Controls which experimental features will be enabled.
8/// Features usually have a corresponding [WebAssembly proposal].
9///
10/// [WebAssembly proposal]: https://github.com/WebAssembly/proposals
11#[derive(Clone, Debug, Eq, PartialEq)]
12#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
13#[cfg_attr(feature = "artifact-size", derive(loupe::MemoryUsage))]
14#[derive(RkyvSerialize, RkyvDeserialize, Archive)]
15#[rkyv(derive(Debug), compare(PartialEq))]
16pub struct Features {
17    /// Threads proposal should be enabled
18    pub threads: bool,
19    /// Reference Types proposal should be enabled
20    pub reference_types: bool,
21    /// SIMD proposal should be enabled
22    pub simd: bool,
23    /// Bulk Memory proposal should be enabled
24    pub bulk_memory: bool,
25    /// Multi Value proposal should be enabled
26    pub multi_value: bool,
27    /// Tail call proposal should be enabled
28    pub tail_call: bool,
29    /// Module Linking proposal should be enabled
30    pub module_linking: bool,
31    /// Multi Memory proposal should be enabled
32    pub multi_memory: bool,
33    /// 64-bit Memory proposal should be enabled
34    pub memory64: bool,
35    /// Wasm exceptions proposal should be enabled
36    pub exceptions: bool,
37    /// Relaxed SIMD proposal should be enabled
38    pub relaxed_simd: bool,
39    /// Extended constant expressions proposal should be enabled
40    pub extended_const: bool,
41}
42
43impl Features {
44    /// Create a new feature
45    pub fn new() -> Self {
46        Self {
47            threads: true,
48            // Reference types should be on by default
49            reference_types: true,
50            // SIMD should be on by default
51            simd: true,
52            // Bulk Memory should be on by default
53            bulk_memory: true,
54            // Multivalue should be on by default
55            multi_value: true,
56            tail_call: false,
57            module_linking: false,
58            multi_memory: false,
59            memory64: false,
60            exceptions: false,
61            relaxed_simd: false,
62            extended_const: false,
63        }
64    }
65
66    /// Configures whether the WebAssembly threads proposal will be enabled.
67    ///
68    /// The [WebAssembly threads proposal][threads] is not currently fully
69    /// standardized and is undergoing development. Support for this feature can
70    /// be enabled through this method for appropriate WebAssembly modules.
71    ///
72    /// This feature gates items such as shared memories and atomic
73    /// instructions.
74    ///
75    /// This is `false` by default.
76    ///
77    /// [threads]: https://github.com/webassembly/threads
78    pub fn threads(&mut self, enable: bool) -> &mut Self {
79        self.threads = enable;
80        self
81    }
82
83    /// Configures whether the WebAssembly reference types proposal will be
84    /// enabled.
85    ///
86    /// The [WebAssembly reference types proposal][proposal] is now
87    /// fully standardized and enabled by default.
88    ///
89    /// This feature gates items such as the `externref` type and multiple tables
90    /// being in a module. Note that enabling the reference types feature will
91    /// also enable the bulk memory feature.
92    ///
93    /// This is `true` by default.
94    ///
95    /// [proposal]: https://github.com/webassembly/reference-types
96    pub fn reference_types(&mut self, enable: bool) -> &mut Self {
97        self.reference_types = enable;
98        // The reference types proposal depends on the bulk memory proposal
99        if enable {
100            self.bulk_memory(true);
101        }
102        self
103    }
104
105    /// Configures whether the WebAssembly SIMD proposal will be
106    /// enabled.
107    ///
108    /// The [WebAssembly SIMD proposal][proposal] is not currently
109    /// fully standardized and is undergoing development. Support for this
110    /// feature can be enabled through this method for appropriate WebAssembly
111    /// modules.
112    ///
113    /// This feature gates items such as the `v128` type and all of its
114    /// operators being in a module.
115    ///
116    /// This is `false` by default.
117    ///
118    /// [proposal]: https://github.com/webassembly/simd
119    pub fn simd(&mut self, enable: bool) -> &mut Self {
120        self.simd = enable;
121        self
122    }
123
124    /// Configures whether the WebAssembly bulk memory operations proposal will
125    /// be enabled.
126    ///
127    /// The [WebAssembly bulk memory operations proposal][proposal] is now
128    /// fully standardized and enabled by default.
129    ///
130    /// This feature gates items such as the `memory.copy` instruction, passive
131    /// data/table segments, etc, being in a module.
132    ///
133    /// This is `true` by default.
134    ///
135    /// [proposal]: https://github.com/webassembly/bulk-memory-operations
136    pub fn bulk_memory(&mut self, enable: bool) -> &mut Self {
137        self.bulk_memory = enable;
138        // In case is false, we disable both threads and reference types
139        // since they both depend on bulk memory
140        if !enable {
141            self.reference_types(false);
142        }
143        self
144    }
145
146    /// Configures whether the WebAssembly multi-value proposal will
147    /// be enabled.
148    ///
149    /// The [WebAssembly multi-value proposal][proposal] is now fully
150    /// standardized and enabled by default, except with the singlepass
151    /// compiler which does not support it.
152    ///
153    /// This feature gates functions and blocks returning multiple values in a
154    /// module, for example.
155    ///
156    /// This is `true` by default.
157    ///
158    /// [proposal]: https://github.com/webassembly/multi-value
159    pub fn multi_value(&mut self, enable: bool) -> &mut Self {
160        self.multi_value = enable;
161        self
162    }
163
164    /// Configures whether the WebAssembly tail-call proposal will
165    /// be enabled.
166    ///
167    /// The [WebAssembly tail-call proposal][proposal] is not
168    /// currently fully standardized and is undergoing development.
169    /// Support for this feature can be enabled through this method for
170    /// appropriate WebAssembly modules.
171    ///
172    /// This feature gates tail-call functions in WebAssembly.
173    ///
174    /// This is `false` by default.
175    ///
176    /// [proposal]: https://github.com/webassembly/tail-call
177    pub fn tail_call(&mut self, enable: bool) -> &mut Self {
178        self.tail_call = enable;
179        self
180    }
181
182    /// Configures whether the WebAssembly module linking proposal will
183    /// be enabled.
184    ///
185    /// The [WebAssembly module linking proposal][proposal] is not
186    /// currently fully standardized and is undergoing development.
187    /// Support for this feature can be enabled through this method for
188    /// appropriate WebAssembly modules.
189    ///
190    /// This feature allows WebAssembly modules to define, import and
191    /// export modules and instances.
192    ///
193    /// This is `false` by default.
194    ///
195    /// [proposal]: https://github.com/webassembly/module-linking
196    pub fn module_linking(&mut self, enable: bool) -> &mut Self {
197        self.module_linking = enable;
198        self
199    }
200
201    /// Configures whether the WebAssembly multi-memory proposal will
202    /// be enabled.
203    ///
204    /// The [WebAssembly multi-memory proposal][proposal] is not
205    /// currently fully standardized and is undergoing development.
206    /// Support for this feature can be enabled through this method for
207    /// appropriate WebAssembly modules.
208    ///
209    /// This feature adds the ability to use multiple memories within a
210    /// single Wasm module.
211    ///
212    /// This is `false` by default.
213    ///
214    /// [proposal]: https://github.com/WebAssembly/multi-memory
215    pub fn multi_memory(&mut self, enable: bool) -> &mut Self {
216        self.multi_memory = enable;
217        self
218    }
219
220    /// Configures whether the WebAssembly 64-bit memory proposal will
221    /// be enabled.
222    ///
223    /// The [WebAssembly 64-bit memory proposal][proposal] is not
224    /// currently fully standardized and is undergoing development.
225    /// Support for this feature can be enabled through this method for
226    /// appropriate WebAssembly modules.
227    ///
228    /// This feature gates support for linear memory of sizes larger than
229    /// 2^32 bits.
230    ///
231    /// This is `false` by default.
232    ///
233    /// [proposal]: https://github.com/WebAssembly/memory64
234    pub fn memory64(&mut self, enable: bool) -> &mut Self {
235        self.memory64 = enable;
236        self
237    }
238
239    /// Configures whether the WebAssembly exception-handling proposal will be enabled.
240    ///
241    /// The [WebAssembly exception-handling proposal][eh] is not currently fully
242    /// standardized and is undergoing development. Support for this feature can
243    /// be enabled through this method for appropriate WebAssembly modules.
244    ///
245    /// This is `false` by default.
246    ///
247    /// [eh]: https://github.com/webassembly/exception-handling
248    pub fn exceptions(&mut self, enable: bool) -> &mut Self {
249        self.exceptions = enable;
250        self
251    }
252
253    /// Checks if this features set contains all the features required by another set
254    pub fn contains_features(&self, required: &Self) -> bool {
255        // Check all required features
256        (!required.simd || self.simd)
257            && (!required.bulk_memory || self.bulk_memory)
258            && (!required.reference_types || self.reference_types)
259            && (!required.threads || self.threads)
260            && (!required.multi_value || self.multi_value)
261            && (!required.exceptions || self.exceptions)
262            && (!required.tail_call || self.tail_call)
263            && (!required.module_linking || self.module_linking)
264            && (!required.multi_memory || self.multi_memory)
265            && (!required.memory64 || self.memory64)
266            && (!required.relaxed_simd || self.relaxed_simd)
267            && (!required.extended_const || self.extended_const)
268    }
269
270    #[cfg(feature = "detect-wasm-features")]
271    /// Detects required WebAssembly features from a module binary.
272    ///
273    /// This method analyzes a WebAssembly module's binary to determine which
274    /// features it requires. It does this by:
275    /// 1. Attempting to validate the module with different feature sets
276    /// 2. Analyzing validation errors to detect required features
277    /// 3. Parsing the module to detect certain common patterns
278    ///
279    /// # Arguments
280    ///
281    /// * `wasm_bytes` - The binary content of the WebAssembly module
282    ///
283    /// # Returns
284    ///
285    /// A new `Features` instance with the detected features enabled.
286    pub fn detect_from_wasm(wasm_bytes: &[u8]) -> Result<Self, wasmparser::BinaryReaderError> {
287        let mut features = Self::default();
288
289        // Simple test for exceptions - try to validate with exceptions disabled
290        let mut exceptions_test = WasmFeatures::default();
291        // Enable most features except exceptions
292        exceptions_test.set(WasmFeatures::BULK_MEMORY, true);
293        exceptions_test.set(WasmFeatures::REFERENCE_TYPES, true);
294        exceptions_test.set(WasmFeatures::SIMD, true);
295        exceptions_test.set(WasmFeatures::MULTI_VALUE, true);
296        exceptions_test.set(WasmFeatures::THREADS, true);
297        exceptions_test.set(WasmFeatures::TAIL_CALL, true);
298        exceptions_test.set(WasmFeatures::MULTI_MEMORY, true);
299        exceptions_test.set(WasmFeatures::MEMORY64, true);
300        exceptions_test.set(WasmFeatures::EXCEPTIONS, false);
301
302        let mut validator = Validator::new_with_features(exceptions_test);
303
304        if let Err(e) = validator.validate_all(wasm_bytes) {
305            let err_msg = e.to_string();
306            if err_msg.contains("exception") {
307                features.exceptions(true);
308            }
309        }
310
311        // Now try with all features enabled to catch anything we might have missed
312        let mut wasm_features = WasmFeatures::default();
313        wasm_features.set(WasmFeatures::EXCEPTIONS, true);
314        wasm_features.set(WasmFeatures::BULK_MEMORY, true);
315        wasm_features.set(WasmFeatures::REFERENCE_TYPES, true);
316        wasm_features.set(WasmFeatures::SIMD, true);
317        wasm_features.set(WasmFeatures::MULTI_VALUE, true);
318        wasm_features.set(WasmFeatures::THREADS, true);
319        wasm_features.set(WasmFeatures::TAIL_CALL, true);
320        wasm_features.set(WasmFeatures::MULTI_MEMORY, true);
321        wasm_features.set(WasmFeatures::MEMORY64, true);
322
323        let mut validator = Validator::new_with_features(wasm_features);
324        match validator.validate_all(wasm_bytes) {
325            Err(e) => {
326                // If validation fails due to missing feature support, check which feature it is
327                let err_msg = e.to_string().to_lowercase();
328
329                if err_msg.contains("exception") || err_msg.contains("try/catch") {
330                    features.exceptions(true);
331                }
332
333                if err_msg.contains("bulk memory") {
334                    features.bulk_memory(true);
335                }
336
337                if err_msg.contains("reference type") {
338                    features.reference_types(true);
339                }
340
341                if err_msg.contains("simd") {
342                    features.simd(true);
343                }
344
345                if err_msg.contains("multi value") || err_msg.contains("multiple values") {
346                    features.multi_value(true);
347                }
348
349                if err_msg.contains("thread") || err_msg.contains("shared memory") {
350                    features.threads(true);
351                }
352
353                if err_msg.contains("tail call") {
354                    features.tail_call(true);
355                }
356
357                if err_msg.contains("module linking") {
358                    features.module_linking(true);
359                }
360
361                if err_msg.contains("multi memory") {
362                    features.multi_memory(true);
363                }
364
365                if err_msg.contains("memory64") {
366                    features.memory64(true);
367                }
368            }
369            Ok(_) => {
370                // The module validated successfully with all features enabled,
371                // which means it could potentially use any of them.
372                // We'll do a more detailed analysis by parsing the module.
373            }
374        }
375
376        // A simple pass to detect certain common patterns
377        for payload in Parser::new(0).parse_all(wasm_bytes) {
378            let payload = payload?;
379            if let Payload::CustomSection(section) = payload {
380                let name = section.name();
381                // Exception handling has a custom section
382                if name.contains("exception") {
383                    features.exceptions(true);
384                }
385            }
386        }
387
388        Ok(features)
389    }
390}
391
392impl Default for Features {
393    fn default() -> Self {
394        Self::new()
395    }
396}
397
398#[cfg(test)]
399mod test_features {
400    use super::*;
401    #[test]
402    fn default_features() {
403        let default = Features::default();
404        assert_eq!(
405            default,
406            Features {
407                threads: true,
408                reference_types: true,
409                simd: true,
410                bulk_memory: true,
411                multi_value: true,
412                tail_call: false,
413                module_linking: false,
414                multi_memory: false,
415                memory64: false,
416                exceptions: false,
417                relaxed_simd: false,
418                extended_const: false,
419            }
420        );
421    }
422
423    #[test]
424    fn enable_threads() {
425        let mut features = Features::new();
426        features.bulk_memory(false).threads(true);
427
428        assert!(features.threads);
429    }
430
431    #[test]
432    fn enable_reference_types() {
433        let mut features = Features::new();
434        features.bulk_memory(false).reference_types(true);
435        assert!(features.reference_types);
436        assert!(features.bulk_memory);
437    }
438
439    #[test]
440    fn enable_simd() {
441        let mut features = Features::new();
442        features.simd(true);
443        assert!(features.simd);
444    }
445
446    #[test]
447    fn enable_multi_value() {
448        let mut features = Features::new();
449        features.multi_value(true);
450        assert!(features.multi_value);
451    }
452
453    #[test]
454    fn enable_bulk_memory() {
455        let mut features = Features::new();
456        features.bulk_memory(true);
457        assert!(features.bulk_memory);
458    }
459
460    #[test]
461    fn disable_bulk_memory() {
462        let mut features = Features::new();
463        features
464            .threads(true)
465            .reference_types(true)
466            .bulk_memory(false);
467        assert!(!features.bulk_memory);
468        assert!(!features.reference_types);
469    }
470
471    #[test]
472    fn enable_tail_call() {
473        let mut features = Features::new();
474        features.tail_call(true);
475        assert!(features.tail_call);
476    }
477
478    #[test]
479    fn enable_module_linking() {
480        let mut features = Features::new();
481        features.module_linking(true);
482        assert!(features.module_linking);
483    }
484
485    #[test]
486    fn enable_multi_memory() {
487        let mut features = Features::new();
488        features.multi_memory(true);
489        assert!(features.multi_memory);
490    }
491
492    #[test]
493    fn enable_memory64() {
494        let mut features = Features::new();
495        features.memory64(true);
496        assert!(features.memory64);
497    }
498}