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, Hash)]
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    /// Create a new feature set with all features enabled.
67    pub fn all() -> Self {
68        Self {
69            threads: true,
70            reference_types: true,
71            simd: true,
72            bulk_memory: true,
73            multi_value: true,
74            tail_call: true,
75            module_linking: true,
76            multi_memory: true,
77            memory64: true,
78            exceptions: true,
79            relaxed_simd: true,
80            extended_const: true,
81        }
82    }
83
84    /// Create a new feature set with all features disabled.
85    pub fn none() -> Self {
86        Self {
87            threads: false,
88            reference_types: false,
89            simd: false,
90            bulk_memory: false,
91            multi_value: false,
92            tail_call: false,
93            module_linking: false,
94            multi_memory: false,
95            memory64: false,
96            exceptions: false,
97            relaxed_simd: false,
98            extended_const: false,
99        }
100    }
101
102    /// Configures whether the WebAssembly threads proposal will be enabled.
103    ///
104    /// The [WebAssembly threads proposal][threads] is not currently fully
105    /// standardized and is undergoing development. Support for this feature can
106    /// be enabled through this method for appropriate WebAssembly modules.
107    ///
108    /// This feature gates items such as shared memories and atomic
109    /// instructions.
110    ///
111    /// This is `false` by default.
112    ///
113    /// [threads]: https://github.com/webassembly/threads
114    pub fn threads(&mut self, enable: bool) -> &mut Self {
115        self.threads = enable;
116        self
117    }
118
119    /// Configures whether the WebAssembly reference types proposal will be
120    /// enabled.
121    ///
122    /// The [WebAssembly reference types proposal][proposal] is now
123    /// fully standardized and enabled by default.
124    ///
125    /// This feature gates items such as the `externref` type and multiple tables
126    /// being in a module. Note that enabling the reference types feature will
127    /// also enable the bulk memory feature.
128    ///
129    /// This is `true` by default.
130    ///
131    /// [proposal]: https://github.com/webassembly/reference-types
132    pub fn reference_types(&mut self, enable: bool) -> &mut Self {
133        self.reference_types = enable;
134        // The reference types proposal depends on the bulk memory proposal
135        if enable {
136            self.bulk_memory(true);
137        }
138        self
139    }
140
141    /// Configures whether the WebAssembly SIMD proposal will be
142    /// enabled.
143    ///
144    /// The [WebAssembly SIMD proposal][proposal] is not currently
145    /// fully standardized and is undergoing development. Support for this
146    /// feature can be enabled through this method for appropriate WebAssembly
147    /// modules.
148    ///
149    /// This feature gates items such as the `v128` type and all of its
150    /// operators being in a module.
151    ///
152    /// This is `false` by default.
153    ///
154    /// [proposal]: https://github.com/webassembly/simd
155    pub fn simd(&mut self, enable: bool) -> &mut Self {
156        self.simd = enable;
157        self
158    }
159
160    /// Configures whether the WebAssembly Relaxed SIMD proposal will be
161    /// enabled.
162    ///
163    /// The [WebAssembly Relaxed SIMD proposal][proposal] is not currently
164    /// fully standardized and is undergoing development. Support for this
165    /// feature can be enabled through this method for appropriate WebAssembly
166    /// modules.
167    ///
168    /// This is `false` by default.
169    ///
170    /// [proposal]: https://github.com/WebAssembly/relaxed-simd
171    pub fn relaxed_simd(&mut self, enable: bool) -> &mut Self {
172        self.relaxed_simd = enable;
173        self
174    }
175
176    /// Configures whether the WebAssembly bulk memory operations proposal will
177    /// be enabled.
178    ///
179    /// The [WebAssembly bulk memory operations proposal][proposal] is now
180    /// fully standardized and enabled by default.
181    ///
182    /// This feature gates items such as the `memory.copy` instruction, passive
183    /// data/table segments, etc, being in a module.
184    ///
185    /// This is `true` by default.
186    ///
187    /// [proposal]: https://github.com/webassembly/bulk-memory-operations
188    pub fn bulk_memory(&mut self, enable: bool) -> &mut Self {
189        self.bulk_memory = enable;
190        // In case is false, we disable both threads and reference types
191        // since they both depend on bulk memory
192        if !enable {
193            self.reference_types(false);
194        }
195        self
196    }
197
198    /// Configures whether the WebAssembly multi-value proposal will
199    /// be enabled.
200    ///
201    /// The [WebAssembly multi-value proposal][proposal] is now fully
202    /// standardized and enabled by default, except with the singlepass
203    /// compiler which does not support it.
204    ///
205    /// This feature gates functions and blocks returning multiple values in a
206    /// module, for example.
207    ///
208    /// This is `true` by default.
209    ///
210    /// [proposal]: https://github.com/webassembly/multi-value
211    pub fn multi_value(&mut self, enable: bool) -> &mut Self {
212        self.multi_value = enable;
213        self
214    }
215
216    /// Configures whether the WebAssembly tail-call proposal will
217    /// be enabled.
218    ///
219    /// The [WebAssembly tail-call proposal][proposal] is not
220    /// currently fully standardized and is undergoing development.
221    /// Support for this feature can be enabled through this method for
222    /// appropriate WebAssembly modules.
223    ///
224    /// This feature gates tail-call functions in WebAssembly.
225    ///
226    /// This is `false` by default.
227    ///
228    /// [proposal]: https://github.com/webassembly/tail-call
229    pub fn tail_call(&mut self, enable: bool) -> &mut Self {
230        self.tail_call = enable;
231        self
232    }
233
234    /// Configures whether the WebAssembly module linking proposal will
235    /// be enabled.
236    ///
237    /// The [WebAssembly module linking proposal][proposal] is not
238    /// currently fully standardized and is undergoing development.
239    /// Support for this feature can be enabled through this method for
240    /// appropriate WebAssembly modules.
241    ///
242    /// This feature allows WebAssembly modules to define, import and
243    /// export modules and instances.
244    ///
245    /// This is `false` by default.
246    ///
247    /// [proposal]: https://github.com/webassembly/module-linking
248    pub fn module_linking(&mut self, enable: bool) -> &mut Self {
249        self.module_linking = enable;
250        self
251    }
252
253    /// Configures whether the WebAssembly multi-memory proposal will
254    /// be enabled.
255    ///
256    /// The [WebAssembly multi-memory proposal][proposal] is not
257    /// currently fully standardized and is undergoing development.
258    /// Support for this feature can be enabled through this method for
259    /// appropriate WebAssembly modules.
260    ///
261    /// This feature adds the ability to use multiple memories within a
262    /// single Wasm module.
263    ///
264    /// This is `false` by default.
265    ///
266    /// [proposal]: https://github.com/WebAssembly/multi-memory
267    pub fn multi_memory(&mut self, enable: bool) -> &mut Self {
268        self.multi_memory = enable;
269        self
270    }
271
272    /// Configures whether the WebAssembly 64-bit memory proposal will
273    /// be enabled.
274    ///
275    /// The [WebAssembly 64-bit memory proposal][proposal] is not
276    /// currently fully standardized and is undergoing development.
277    /// Support for this feature can be enabled through this method for
278    /// appropriate WebAssembly modules.
279    ///
280    /// This feature gates support for linear memory of sizes larger than
281    /// 2^32 bits.
282    ///
283    /// This is `false` by default.
284    ///
285    /// [proposal]: https://github.com/WebAssembly/memory64
286    pub fn memory64(&mut self, enable: bool) -> &mut Self {
287        self.memory64 = enable;
288        self
289    }
290
291    /// Configures whether the WebAssembly exception-handling proposal will be enabled.
292    ///
293    /// The [WebAssembly exception-handling proposal][eh] is not currently fully
294    /// standardized and is undergoing development. Support for this feature can
295    /// be enabled through this method for appropriate WebAssembly modules.
296    ///
297    /// This is `false` by default.
298    ///
299    /// [eh]: https://github.com/webassembly/exception-handling
300    pub fn exceptions(&mut self, enable: bool) -> &mut Self {
301        self.exceptions = enable;
302        self
303    }
304
305    /// Checks if this features set contains all the features required by another set
306    pub fn contains_features(&self, required: &Self) -> bool {
307        // Check all required features
308        (!required.simd || self.simd)
309            && (!required.bulk_memory || self.bulk_memory)
310            && (!required.reference_types || self.reference_types)
311            && (!required.threads || self.threads)
312            && (!required.multi_value || self.multi_value)
313            && (!required.exceptions || self.exceptions)
314            && (!required.tail_call || self.tail_call)
315            && (!required.module_linking || self.module_linking)
316            && (!required.multi_memory || self.multi_memory)
317            && (!required.memory64 || self.memory64)
318            && (!required.relaxed_simd || self.relaxed_simd)
319            && (!required.extended_const || self.extended_const)
320    }
321
322    #[cfg(feature = "detect-wasm-features")]
323    /// Detects required WebAssembly features from a module binary.
324    ///
325    /// This method analyzes a WebAssembly module's binary to determine which
326    /// features it requires. It does this by:
327    /// 1. Attempting to validate the module with different feature sets
328    /// 2. Analyzing validation errors to detect required features
329    /// 3. Parsing the module to detect certain common patterns
330    ///
331    /// # Arguments
332    ///
333    /// * `wasm_bytes` - The binary content of the WebAssembly module
334    ///
335    /// # Returns
336    ///
337    /// A new `Features` instance with the detected features enabled.
338    pub fn detect_from_wasm(wasm_bytes: &[u8]) -> Result<Self, wasmparser::BinaryReaderError> {
339        let mut features = Self::default();
340
341        // Simple test for exceptions - try to validate with exceptions disabled
342        let mut exceptions_test = WasmFeatures::default();
343        // Enable most features except exceptions
344        exceptions_test.set(WasmFeatures::BULK_MEMORY, true);
345        exceptions_test.set(WasmFeatures::REFERENCE_TYPES, true);
346        exceptions_test.set(WasmFeatures::SIMD, true);
347        exceptions_test.set(WasmFeatures::MULTI_VALUE, true);
348        exceptions_test.set(WasmFeatures::THREADS, true);
349        exceptions_test.set(WasmFeatures::TAIL_CALL, true);
350        exceptions_test.set(WasmFeatures::MULTI_MEMORY, true);
351        exceptions_test.set(WasmFeatures::MEMORY64, true);
352        exceptions_test.set(WasmFeatures::EXCEPTIONS, false);
353
354        let mut validator = Validator::new_with_features(exceptions_test);
355
356        if let Err(e) = validator.validate_all(wasm_bytes) {
357            let err_msg = e.to_string();
358            if err_msg.contains("exception") {
359                features.exceptions(true);
360            }
361        }
362
363        // Now try with all features enabled to catch anything we might have missed
364        let mut wasm_features = WasmFeatures::default();
365        wasm_features.set(WasmFeatures::EXCEPTIONS, true);
366        wasm_features.set(WasmFeatures::BULK_MEMORY, true);
367        wasm_features.set(WasmFeatures::REFERENCE_TYPES, true);
368        wasm_features.set(WasmFeatures::SIMD, true);
369        wasm_features.set(WasmFeatures::MULTI_VALUE, true);
370        wasm_features.set(WasmFeatures::THREADS, true);
371        wasm_features.set(WasmFeatures::TAIL_CALL, true);
372        wasm_features.set(WasmFeatures::MULTI_MEMORY, true);
373        wasm_features.set(WasmFeatures::MEMORY64, true);
374
375        let mut validator = Validator::new_with_features(wasm_features);
376        match validator.validate_all(wasm_bytes) {
377            Err(e) => {
378                // If validation fails due to missing feature support, check which feature it is
379                let err_msg = e.to_string().to_lowercase();
380
381                if err_msg.contains("exception") || err_msg.contains("try/catch") {
382                    features.exceptions(true);
383                }
384
385                if err_msg.contains("bulk memory") {
386                    features.bulk_memory(true);
387                }
388
389                if err_msg.contains("reference type") {
390                    features.reference_types(true);
391                }
392
393                if err_msg.contains("simd") {
394                    features.simd(true);
395                }
396
397                if err_msg.contains("multi value") || err_msg.contains("multiple values") {
398                    features.multi_value(true);
399                }
400
401                if err_msg.contains("thread") || err_msg.contains("shared memory") {
402                    features.threads(true);
403                }
404
405                if err_msg.contains("tail call") {
406                    features.tail_call(true);
407                }
408
409                if err_msg.contains("module linking") {
410                    features.module_linking(true);
411                }
412
413                if err_msg.contains("multi memory") {
414                    features.multi_memory(true);
415                }
416
417                if err_msg.contains("memory64") {
418                    features.memory64(true);
419                }
420            }
421            Ok(_) => {
422                // The module validated successfully with all features enabled,
423                // which means it could potentially use any of them.
424                // We'll do a more detailed analysis by parsing the module.
425            }
426        }
427
428        // A simple pass to detect certain common patterns
429        for payload in Parser::new(0).parse_all(wasm_bytes) {
430            let payload = payload?;
431            if let Payload::CustomSection(section) = payload {
432                let name = section.name();
433                // Exception handling has a custom section
434                if name.contains("exception") {
435                    features.exceptions(true);
436                }
437            }
438        }
439
440        Ok(features)
441    }
442
443    /// Extend this feature set with another set.
444    ///
445    /// Self will be modified to include all features that are required by
446    /// either set.
447    pub fn extend(&mut self, other: &Self) {
448        // Written this way to cause compile errors when new features are added.
449        let Self {
450            threads,
451            reference_types,
452            simd,
453            bulk_memory,
454            multi_value,
455            tail_call,
456            module_linking,
457            multi_memory,
458            memory64,
459            exceptions,
460            relaxed_simd,
461            extended_const,
462        } = other.clone();
463
464        *self = Self {
465            threads: self.threads || threads,
466            reference_types: self.reference_types || reference_types,
467            simd: self.simd || simd,
468            bulk_memory: self.bulk_memory || bulk_memory,
469            multi_value: self.multi_value || multi_value,
470            tail_call: self.tail_call || tail_call,
471            module_linking: self.module_linking || module_linking,
472            multi_memory: self.multi_memory || multi_memory,
473            memory64: self.memory64 || memory64,
474            exceptions: self.exceptions || exceptions,
475            relaxed_simd: self.relaxed_simd || relaxed_simd,
476            extended_const: self.extended_const || extended_const,
477        };
478    }
479}
480
481impl Default for Features {
482    fn default() -> Self {
483        Self::new()
484    }
485}
486
487#[cfg(test)]
488mod test_features {
489    use super::*;
490    #[test]
491    fn default_features() {
492        let default = Features::default();
493        assert_eq!(
494            default,
495            Features {
496                threads: true,
497                reference_types: true,
498                simd: true,
499                bulk_memory: true,
500                multi_value: true,
501                tail_call: false,
502                module_linking: false,
503                multi_memory: false,
504                memory64: false,
505                exceptions: false,
506                relaxed_simd: false,
507                extended_const: false,
508            }
509        );
510    }
511
512    #[test]
513    fn features_extend() {
514        let all = Features::all();
515        let mut target = Features::none();
516        target.extend(&all);
517        assert_eq!(target, all);
518    }
519
520    #[test]
521    fn enable_threads() {
522        let mut features = Features::new();
523        features.bulk_memory(false).threads(true);
524
525        assert!(features.threads);
526    }
527
528    #[test]
529    fn enable_reference_types() {
530        let mut features = Features::new();
531        features.bulk_memory(false).reference_types(true);
532        assert!(features.reference_types);
533        assert!(features.bulk_memory);
534    }
535
536    #[test]
537    fn enable_simd() {
538        let mut features = Features::new();
539        features.simd(true);
540        assert!(features.simd);
541    }
542
543    #[test]
544    fn enable_multi_value() {
545        let mut features = Features::new();
546        features.multi_value(true);
547        assert!(features.multi_value);
548    }
549
550    #[test]
551    fn enable_bulk_memory() {
552        let mut features = Features::new();
553        features.bulk_memory(true);
554        assert!(features.bulk_memory);
555    }
556
557    #[test]
558    fn disable_bulk_memory() {
559        let mut features = Features::new();
560        features
561            .threads(true)
562            .reference_types(true)
563            .bulk_memory(false);
564        assert!(!features.bulk_memory);
565        assert!(!features.reference_types);
566    }
567
568    #[test]
569    fn enable_tail_call() {
570        let mut features = Features::new();
571        features.tail_call(true);
572        assert!(features.tail_call);
573    }
574
575    #[test]
576    fn enable_module_linking() {
577        let mut features = Features::new();
578        features.module_linking(true);
579        assert!(features.module_linking);
580    }
581
582    #[test]
583    fn enable_multi_memory() {
584        let mut features = Features::new();
585        features.multi_memory(true);
586        assert!(features.multi_memory);
587    }
588
589    #[test]
590    fn enable_memory64() {
591        let mut features = Features::new();
592        features.memory64(true);
593        assert!(features.memory64);
594    }
595}