Skip to main content

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