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