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