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)]
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 /// Configures whether the WebAssembly threads proposal will be enabled.
67 ///
68 /// The [WebAssembly threads proposal][threads] is not currently fully
69 /// standardized and is undergoing development. Support for this feature can
70 /// be enabled through this method for appropriate WebAssembly modules.
71 ///
72 /// This feature gates items such as shared memories and atomic
73 /// instructions.
74 ///
75 /// This is `false` by default.
76 ///
77 /// [threads]: https://github.com/webassembly/threads
78 pub fn threads(&mut self, enable: bool) -> &mut Self {
79 self.threads = enable;
80 self
81 }
82
83 /// Configures whether the WebAssembly reference types proposal will be
84 /// enabled.
85 ///
86 /// The [WebAssembly reference types proposal][proposal] is now
87 /// fully standardized and enabled by default.
88 ///
89 /// This feature gates items such as the `externref` type and multiple tables
90 /// being in a module. Note that enabling the reference types feature will
91 /// also enable the bulk memory feature.
92 ///
93 /// This is `true` by default.
94 ///
95 /// [proposal]: https://github.com/webassembly/reference-types
96 pub fn reference_types(&mut self, enable: bool) -> &mut Self {
97 self.reference_types = enable;
98 // The reference types proposal depends on the bulk memory proposal
99 if enable {
100 self.bulk_memory(true);
101 }
102 self
103 }
104
105 /// Configures whether the WebAssembly SIMD proposal will be
106 /// enabled.
107 ///
108 /// The [WebAssembly SIMD proposal][proposal] is not currently
109 /// fully standardized and is undergoing development. Support for this
110 /// feature can be enabled through this method for appropriate WebAssembly
111 /// modules.
112 ///
113 /// This feature gates items such as the `v128` type and all of its
114 /// operators being in a module.
115 ///
116 /// This is `false` by default.
117 ///
118 /// [proposal]: https://github.com/webassembly/simd
119 pub fn simd(&mut self, enable: bool) -> &mut Self {
120 self.simd = enable;
121 self
122 }
123
124 /// Configures whether the WebAssembly bulk memory operations proposal will
125 /// be enabled.
126 ///
127 /// The [WebAssembly bulk memory operations proposal][proposal] is now
128 /// fully standardized and enabled by default.
129 ///
130 /// This feature gates items such as the `memory.copy` instruction, passive
131 /// data/table segments, etc, being in a module.
132 ///
133 /// This is `true` by default.
134 ///
135 /// [proposal]: https://github.com/webassembly/bulk-memory-operations
136 pub fn bulk_memory(&mut self, enable: bool) -> &mut Self {
137 self.bulk_memory = enable;
138 // In case is false, we disable both threads and reference types
139 // since they both depend on bulk memory
140 if !enable {
141 self.reference_types(false);
142 }
143 self
144 }
145
146 /// Configures whether the WebAssembly multi-value proposal will
147 /// be enabled.
148 ///
149 /// The [WebAssembly multi-value proposal][proposal] is now fully
150 /// standardized and enabled by default, except with the singlepass
151 /// compiler which does not support it.
152 ///
153 /// This feature gates functions and blocks returning multiple values in a
154 /// module, for example.
155 ///
156 /// This is `true` by default.
157 ///
158 /// [proposal]: https://github.com/webassembly/multi-value
159 pub fn multi_value(&mut self, enable: bool) -> &mut Self {
160 self.multi_value = enable;
161 self
162 }
163
164 /// Configures whether the WebAssembly tail-call proposal will
165 /// be enabled.
166 ///
167 /// The [WebAssembly tail-call proposal][proposal] is not
168 /// currently fully standardized and is undergoing development.
169 /// Support for this feature can be enabled through this method for
170 /// appropriate WebAssembly modules.
171 ///
172 /// This feature gates tail-call functions in WebAssembly.
173 ///
174 /// This is `false` by default.
175 ///
176 /// [proposal]: https://github.com/webassembly/tail-call
177 pub fn tail_call(&mut self, enable: bool) -> &mut Self {
178 self.tail_call = enable;
179 self
180 }
181
182 /// Configures whether the WebAssembly module linking proposal will
183 /// be enabled.
184 ///
185 /// The [WebAssembly module linking proposal][proposal] is not
186 /// currently fully standardized and is undergoing development.
187 /// Support for this feature can be enabled through this method for
188 /// appropriate WebAssembly modules.
189 ///
190 /// This feature allows WebAssembly modules to define, import and
191 /// export modules and instances.
192 ///
193 /// This is `false` by default.
194 ///
195 /// [proposal]: https://github.com/webassembly/module-linking
196 pub fn module_linking(&mut self, enable: bool) -> &mut Self {
197 self.module_linking = enable;
198 self
199 }
200
201 /// Configures whether the WebAssembly multi-memory proposal will
202 /// be enabled.
203 ///
204 /// The [WebAssembly multi-memory proposal][proposal] is not
205 /// currently fully standardized and is undergoing development.
206 /// Support for this feature can be enabled through this method for
207 /// appropriate WebAssembly modules.
208 ///
209 /// This feature adds the ability to use multiple memories within a
210 /// single Wasm module.
211 ///
212 /// This is `false` by default.
213 ///
214 /// [proposal]: https://github.com/WebAssembly/multi-memory
215 pub fn multi_memory(&mut self, enable: bool) -> &mut Self {
216 self.multi_memory = enable;
217 self
218 }
219
220 /// Configures whether the WebAssembly 64-bit memory proposal will
221 /// be enabled.
222 ///
223 /// The [WebAssembly 64-bit memory proposal][proposal] is not
224 /// currently fully standardized and is undergoing development.
225 /// Support for this feature can be enabled through this method for
226 /// appropriate WebAssembly modules.
227 ///
228 /// This feature gates support for linear memory of sizes larger than
229 /// 2^32 bits.
230 ///
231 /// This is `false` by default.
232 ///
233 /// [proposal]: https://github.com/WebAssembly/memory64
234 pub fn memory64(&mut self, enable: bool) -> &mut Self {
235 self.memory64 = enable;
236 self
237 }
238
239 /// Configures whether the WebAssembly exception-handling proposal will be enabled.
240 ///
241 /// The [WebAssembly exception-handling proposal][eh] is not currently fully
242 /// standardized and is undergoing development. Support for this feature can
243 /// be enabled through this method for appropriate WebAssembly modules.
244 ///
245 /// This is `false` by default.
246 ///
247 /// [eh]: https://github.com/webassembly/exception-handling
248 pub fn exceptions(&mut self, enable: bool) -> &mut Self {
249 self.exceptions = enable;
250 self
251 }
252
253 /// Checks if this features set contains all the features required by another set
254 pub fn contains_features(&self, required: &Self) -> bool {
255 // Check all required features
256 (!required.simd || self.simd)
257 && (!required.bulk_memory || self.bulk_memory)
258 && (!required.reference_types || self.reference_types)
259 && (!required.threads || self.threads)
260 && (!required.multi_value || self.multi_value)
261 && (!required.exceptions || self.exceptions)
262 && (!required.tail_call || self.tail_call)
263 && (!required.module_linking || self.module_linking)
264 && (!required.multi_memory || self.multi_memory)
265 && (!required.memory64 || self.memory64)
266 && (!required.relaxed_simd || self.relaxed_simd)
267 && (!required.extended_const || self.extended_const)
268 }
269
270 #[cfg(feature = "detect-wasm-features")]
271 /// Detects required WebAssembly features from a module binary.
272 ///
273 /// This method analyzes a WebAssembly module's binary to determine which
274 /// features it requires. It does this by:
275 /// 1. Attempting to validate the module with different feature sets
276 /// 2. Analyzing validation errors to detect required features
277 /// 3. Parsing the module to detect certain common patterns
278 ///
279 /// # Arguments
280 ///
281 /// * `wasm_bytes` - The binary content of the WebAssembly module
282 ///
283 /// # Returns
284 ///
285 /// A new `Features` instance with the detected features enabled.
286 pub fn detect_from_wasm(wasm_bytes: &[u8]) -> Result<Self, wasmparser::BinaryReaderError> {
287 let mut features = Self::default();
288
289 // Simple test for exceptions - try to validate with exceptions disabled
290 let mut exceptions_test = WasmFeatures::default();
291 // Enable most features except exceptions
292 exceptions_test.set(WasmFeatures::BULK_MEMORY, true);
293 exceptions_test.set(WasmFeatures::REFERENCE_TYPES, true);
294 exceptions_test.set(WasmFeatures::SIMD, true);
295 exceptions_test.set(WasmFeatures::MULTI_VALUE, true);
296 exceptions_test.set(WasmFeatures::THREADS, true);
297 exceptions_test.set(WasmFeatures::TAIL_CALL, true);
298 exceptions_test.set(WasmFeatures::MULTI_MEMORY, true);
299 exceptions_test.set(WasmFeatures::MEMORY64, true);
300 exceptions_test.set(WasmFeatures::EXCEPTIONS, false);
301
302 let mut validator = Validator::new_with_features(exceptions_test);
303
304 if let Err(e) = validator.validate_all(wasm_bytes) {
305 let err_msg = e.to_string();
306 if err_msg.contains("exception") {
307 features.exceptions(true);
308 }
309 }
310
311 // Now try with all features enabled to catch anything we might have missed
312 let mut wasm_features = WasmFeatures::default();
313 wasm_features.set(WasmFeatures::EXCEPTIONS, true);
314 wasm_features.set(WasmFeatures::BULK_MEMORY, true);
315 wasm_features.set(WasmFeatures::REFERENCE_TYPES, true);
316 wasm_features.set(WasmFeatures::SIMD, true);
317 wasm_features.set(WasmFeatures::MULTI_VALUE, true);
318 wasm_features.set(WasmFeatures::THREADS, true);
319 wasm_features.set(WasmFeatures::TAIL_CALL, true);
320 wasm_features.set(WasmFeatures::MULTI_MEMORY, true);
321 wasm_features.set(WasmFeatures::MEMORY64, true);
322
323 let mut validator = Validator::new_with_features(wasm_features);
324 match validator.validate_all(wasm_bytes) {
325 Err(e) => {
326 // If validation fails due to missing feature support, check which feature it is
327 let err_msg = e.to_string().to_lowercase();
328
329 if err_msg.contains("exception") || err_msg.contains("try/catch") {
330 features.exceptions(true);
331 }
332
333 if err_msg.contains("bulk memory") {
334 features.bulk_memory(true);
335 }
336
337 if err_msg.contains("reference type") {
338 features.reference_types(true);
339 }
340
341 if err_msg.contains("simd") {
342 features.simd(true);
343 }
344
345 if err_msg.contains("multi value") || err_msg.contains("multiple values") {
346 features.multi_value(true);
347 }
348
349 if err_msg.contains("thread") || err_msg.contains("shared memory") {
350 features.threads(true);
351 }
352
353 if err_msg.contains("tail call") {
354 features.tail_call(true);
355 }
356
357 if err_msg.contains("module linking") {
358 features.module_linking(true);
359 }
360
361 if err_msg.contains("multi memory") {
362 features.multi_memory(true);
363 }
364
365 if err_msg.contains("memory64") {
366 features.memory64(true);
367 }
368 }
369 Ok(_) => {
370 // The module validated successfully with all features enabled,
371 // which means it could potentially use any of them.
372 // We'll do a more detailed analysis by parsing the module.
373 }
374 }
375
376 // A simple pass to detect certain common patterns
377 for payload in Parser::new(0).parse_all(wasm_bytes) {
378 let payload = payload?;
379 if let Payload::CustomSection(section) = payload {
380 let name = section.name();
381 // Exception handling has a custom section
382 if name.contains("exception") {
383 features.exceptions(true);
384 }
385 }
386 }
387
388 Ok(features)
389 }
390}
391
392impl Default for Features {
393 fn default() -> Self {
394 Self::new()
395 }
396}
397
398#[cfg(test)]
399mod test_features {
400 use super::*;
401 #[test]
402 fn default_features() {
403 let default = Features::default();
404 assert_eq!(
405 default,
406 Features {
407 threads: true,
408 reference_types: true,
409 simd: true,
410 bulk_memory: true,
411 multi_value: true,
412 tail_call: false,
413 module_linking: false,
414 multi_memory: false,
415 memory64: false,
416 exceptions: false,
417 relaxed_simd: false,
418 extended_const: false,
419 }
420 );
421 }
422
423 #[test]
424 fn enable_threads() {
425 let mut features = Features::new();
426 features.bulk_memory(false).threads(true);
427
428 assert!(features.threads);
429 }
430
431 #[test]
432 fn enable_reference_types() {
433 let mut features = Features::new();
434 features.bulk_memory(false).reference_types(true);
435 assert!(features.reference_types);
436 assert!(features.bulk_memory);
437 }
438
439 #[test]
440 fn enable_simd() {
441 let mut features = Features::new();
442 features.simd(true);
443 assert!(features.simd);
444 }
445
446 #[test]
447 fn enable_multi_value() {
448 let mut features = Features::new();
449 features.multi_value(true);
450 assert!(features.multi_value);
451 }
452
453 #[test]
454 fn enable_bulk_memory() {
455 let mut features = Features::new();
456 features.bulk_memory(true);
457 assert!(features.bulk_memory);
458 }
459
460 #[test]
461 fn disable_bulk_memory() {
462 let mut features = Features::new();
463 features
464 .threads(true)
465 .reference_types(true)
466 .bulk_memory(false);
467 assert!(!features.bulk_memory);
468 assert!(!features.reference_types);
469 }
470
471 #[test]
472 fn enable_tail_call() {
473 let mut features = Features::new();
474 features.tail_call(true);
475 assert!(features.tail_call);
476 }
477
478 #[test]
479 fn enable_module_linking() {
480 let mut features = Features::new();
481 features.module_linking(true);
482 assert!(features.module_linking);
483 }
484
485 #[test]
486 fn enable_multi_memory() {
487 let mut features = Features::new();
488 features.multi_memory(true);
489 assert!(features.multi_memory);
490 }
491
492 #[test]
493 fn enable_memory64() {
494 let mut features = Features::new();
495 features.memory64(true);
496 assert!(features.memory64);
497 }
498}