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