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}