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}