xref: /wasmtime-44.0.1/crates/cli-flags/src/lib.rs (revision ca5a9db0)
1 //! Contains the common Wasmtime command line interface (CLI) flags.
2 
3 #![deny(trivial_numeric_casts, unused_extern_crates, unstable_features)]
4 #![warn(unused_import_braces)]
5 #![cfg_attr(feature = "clippy", plugin(clippy(conf_file = "../../clippy.toml")))]
6 #![cfg_attr(feature = "cargo-clippy", allow(clippy::new_without_default))]
7 #![cfg_attr(
8     feature = "cargo-clippy",
9     warn(
10         clippy::float_arithmetic,
11         clippy::mut_mut,
12         clippy::nonminimal_bool,
13         clippy::map_unwrap_or,
14         clippy::unicode_not_nfc,
15         clippy::use_self
16     )
17 )]
18 
19 use anyhow::{bail, Result};
20 use clap::Parser;
21 use std::collections::HashMap;
22 use std::path::PathBuf;
23 use wasmtime::{Config, Strategy};
24 
25 pub const SUPPORTED_WASM_FEATURES: &[(&str, &str)] = &[
26     ("all", "enables all supported WebAssembly features"),
27     (
28         "bulk-memory",
29         "enables support for bulk memory instructions",
30     ),
31     (
32         "multi-memory",
33         "enables support for the multi-memory proposal",
34     ),
35     ("multi-value", "enables support for multi-value functions"),
36     ("reference-types", "enables support for reference types"),
37     ("simd", "enables support for proposed SIMD instructions"),
38     (
39         "relaxed-simd",
40         "enables support for the relaxed simd proposal",
41     ),
42     ("tail-call", "enables support for WebAssembly tail calls"),
43     ("threads", "enables support for WebAssembly threads"),
44     ("memory64", "enables support for 64-bit memories"),
45     #[cfg(feature = "component-model")]
46     ("component-model", "enables support for the component model"),
47     (
48         "function-references",
49         "enables support for typed function references",
50     ),
51 ];
52 
53 pub const SUPPORTED_WASI_MODULES: &[(&str, &str)] = &[
54     (
55         "default",
56         "enables all stable WASI modules (no experimental modules)",
57     ),
58     (
59         "wasi-common",
60         "enables support for the WASI common APIs, see https://github.com/WebAssembly/WASI",
61     ),
62     (
63         "experimental-wasi-nn",
64         "enables support for the WASI neural network API (experimental), see https://github.com/WebAssembly/wasi-nn",
65     ),
66     (
67         "experimental-wasi-threads",
68         "enables support for the WASI threading API (experimental), see https://github.com/WebAssembly/wasi-threads",
69     ),
70     (
71         "experimental-wasi-http",
72         "enables support for the WASI HTTP APIs (experimental), see https://github.com/WebAssembly/wasi-http",
73     ),
74 ];
75 
76 fn init_file_per_thread_logger(prefix: &'static str) {
77     file_per_thread_logger::initialize(prefix);
78 
79     // Extending behavior of default spawner:
80     // https://docs.rs/rayon/1.1.0/rayon/struct.ThreadPoolBuilder.html#method.spawn_handler
81     // Source code says DefaultSpawner is implementation detail and
82     // shouldn't be used directly.
83     rayon::ThreadPoolBuilder::new()
84         .spawn_handler(move |thread| {
85             let mut b = std::thread::Builder::new();
86             if let Some(name) = thread.name() {
87                 b = b.name(name.to_owned());
88             }
89             if let Some(stack_size) = thread.stack_size() {
90                 b = b.stack_size(stack_size);
91             }
92             b.spawn(move || {
93                 file_per_thread_logger::initialize(prefix);
94                 thread.run()
95             })?;
96             Ok(())
97         })
98         .build_global()
99         .unwrap();
100 }
101 
102 /// Common options for commands that translate WebAssembly modules
103 #[derive(Parser)]
104 #[cfg_attr(test, derive(Debug, PartialEq))]
105 pub struct CommonOptions {
106     /// Use specified configuration file
107     #[clap(long, value_name = "CONFIG_PATH")]
108     pub config: Option<PathBuf>,
109 
110     /// Disable logging
111     #[clap(long, conflicts_with = "log_to_files")]
112     pub disable_logging: bool,
113 
114     /// Log to per-thread log files instead of stderr
115     #[clap(long)]
116     pub log_to_files: bool,
117 
118     /// Generate debug information
119     #[clap(short = 'g')]
120     pub debug_info: bool,
121 
122     /// Disable cache system
123     #[clap(long)]
124     pub disable_cache: bool,
125 
126     /// Disable parallel compilation
127     #[clap(long)]
128     pub disable_parallel_compilation: bool,
129 
130     /// Enable or disable WebAssembly features
131     #[clap(long, value_name = "FEATURE,FEATURE,...", value_parser = parse_wasm_features)]
132     pub wasm_features: Option<WasmFeatures>,
133 
134     /// Enable or disable WASI modules
135     #[clap(long, value_name = "MODULE,MODULE,...", value_parser = parse_wasi_modules)]
136     pub wasi_modules: Option<WasiModules>,
137 
138     /// Generate jitdump file (supported on --features=profiling build)
139     /// Run optimization passes on translated functions, on by default
140     #[clap(short = 'O', long)]
141     pub optimize: bool,
142 
143     /// Optimization level for generated functions
144     /// Supported levels: 0 (none), 1, 2 (most), or s (size); default is "most"
145     #[clap(
146         long,
147         value_name = "LEVEL",
148         value_parser = parse_opt_level,
149         verbatim_doc_comment,
150     )]
151     pub opt_level: Option<wasmtime::OptLevel>,
152 
153     /// Set a Cranelift setting to a given value.
154     /// Use `wasmtime settings` to list Cranelift settings for a target.
155     #[clap(
156         long = "cranelift-set",
157         value_name = "NAME=VALUE",
158         number_of_values = 1,
159         verbatim_doc_comment,
160         value_parser = parse_cranelift_flag,
161     )]
162     pub cranelift_set: Vec<(String, String)>,
163 
164     /// Enable a Cranelift boolean setting or preset.
165     /// Use `wasmtime settings` to list Cranelift settings for a target.
166     #[clap(
167         long,
168         value_name = "SETTING",
169         number_of_values = 1,
170         verbatim_doc_comment
171     )]
172     pub cranelift_enable: Vec<String>,
173 
174     /// Maximum size in bytes of wasm memory before it becomes dynamically
175     /// relocatable instead of up-front-reserved.
176     #[clap(long, value_name = "MAXIMUM")]
177     pub static_memory_maximum_size: Option<u64>,
178 
179     /// Force using a "static" style for all wasm memories
180     #[clap(long)]
181     pub static_memory_forced: bool,
182 
183     /// Byte size of the guard region after static memories are allocated
184     #[clap(long, value_name = "SIZE")]
185     pub static_memory_guard_size: Option<u64>,
186 
187     /// Byte size of the guard region after dynamic memories are allocated
188     #[clap(long, value_name = "SIZE")]
189     pub dynamic_memory_guard_size: Option<u64>,
190 
191     /// Bytes to reserve at the end of linear memory for growth for dynamic
192     /// memories.
193     #[clap(long, value_name = "SIZE")]
194     pub dynamic_memory_reserved_for_growth: Option<u64>,
195 
196     /// Enable Cranelift's internal debug verifier (expensive)
197     #[clap(long)]
198     pub enable_cranelift_debug_verifier: bool,
199 
200     /// Enable Cranelift's internal NaN canonicalization
201     #[clap(long)]
202     pub enable_cranelift_nan_canonicalization: bool,
203 
204     /// Enable execution fuel with N units fuel, where execution will trap after
205     /// running out of fuel.
206     ///
207     /// Most WebAssembly instructions consume 1 unit of fuel. Some instructions,
208     /// such as `nop`, `drop`, `block`, and `loop`, consume 0 units, as any
209     /// execution cost associated with them involves other instructions which do
210     /// consume fuel.
211     #[clap(long, value_name = "N")]
212     pub fuel: Option<u64>,
213 
214     /// Executing wasm code will yield when a global epoch counter
215     /// changes, allowing for async operation without blocking the
216     /// executor.
217     #[clap(long)]
218     pub epoch_interruption: bool,
219 
220     /// Disable the on-by-default address map from native code to wasm code
221     #[clap(long)]
222     pub disable_address_map: bool,
223 
224     /// Disable the default of attempting to initialize linear memory via a
225     /// copy-on-write mapping
226     #[clap(long)]
227     pub disable_memory_init_cow: bool,
228 
229     /// Enable the pooling allocator, in place of the on-demand
230     /// allocator.
231     #[cfg(feature = "pooling-allocator")]
232     #[clap(long)]
233     pub pooling_allocator: bool,
234 
235     /// Maximum stack size, in bytes, that wasm is allowed to consume before a
236     /// stack overflow is reported.
237     #[clap(long)]
238     pub max_wasm_stack: Option<usize>,
239 
240     /// Whether or not to force deterministic and host-independent behavior of
241     /// the relaxed-simd instructions.
242     ///
243     /// By default these instructions may have architecture-specific behavior as
244     /// allowed by the specification, but this can be used to force the behavior
245     /// of these instructions to match the deterministic behavior classified in
246     /// the specification. Note that enabling this option may come at a
247     /// performance cost.
248     #[clap(long)]
249     pub relaxed_simd_deterministic: bool,
250     /// Explicitly specify the name of the compiler to use for WebAssembly.
251     ///
252     /// Currently only `cranelift` and `winch` are supported, but not all builds
253     /// of Wasmtime have both built in.
254     #[clap(long)]
255     pub compiler: Option<String>,
256 }
257 
258 impl CommonOptions {
259     pub fn init_logging(&self) {
260         if self.disable_logging {
261             return;
262         }
263         if self.log_to_files {
264             let prefix = "wasmtime.dbg.";
265             init_file_per_thread_logger(prefix);
266         } else {
267             pretty_env_logger::init();
268         }
269     }
270 
271     pub fn config(&self, target: Option<&str>) -> Result<Config> {
272         let mut config = Config::new();
273 
274         config.strategy(match self.compiler.as_deref() {
275             None => Strategy::Auto,
276             Some("cranelift") => Strategy::Cranelift,
277             Some("winch") => Strategy::Winch,
278             Some(s) => bail!("unknown compiler: {s}"),
279         });
280 
281         // Set the target before setting any cranelift options, since the
282         // target will reset any target-specific options.
283         if let Some(target) = target {
284             config.target(target)?;
285         }
286 
287         config
288             .cranelift_debug_verifier(self.enable_cranelift_debug_verifier)
289             .debug_info(self.debug_info)
290             .cranelift_opt_level(self.opt_level())
291             .cranelift_nan_canonicalization(self.enable_cranelift_nan_canonicalization);
292 
293         self.enable_wasm_features(&mut config);
294 
295         for name in &self.cranelift_enable {
296             unsafe {
297                 config.cranelift_flag_enable(name);
298             }
299         }
300 
301         for (name, value) in &self.cranelift_set {
302             unsafe {
303                 config.cranelift_flag_set(name, value);
304             }
305         }
306 
307         if !self.disable_cache {
308             match &self.config {
309                 Some(path) => {
310                     config.cache_config_load(path)?;
311                 }
312                 None => {
313                     config.cache_config_load_default()?;
314                 }
315             }
316         }
317 
318         if self.disable_parallel_compilation {
319             config.parallel_compilation(false);
320         }
321 
322         if let Some(max) = self.static_memory_maximum_size {
323             config.static_memory_maximum_size(max);
324         }
325 
326         config.static_memory_forced(self.static_memory_forced);
327 
328         if let Some(size) = self.static_memory_guard_size {
329             config.static_memory_guard_size(size);
330         }
331 
332         if let Some(size) = self.dynamic_memory_guard_size {
333             config.dynamic_memory_guard_size(size);
334         }
335         if let Some(size) = self.dynamic_memory_reserved_for_growth {
336             config.dynamic_memory_reserved_for_growth(size);
337         }
338 
339         // If fuel has been configured, set the `consume fuel` flag on the config.
340         if self.fuel.is_some() {
341             config.consume_fuel(true);
342         }
343 
344         config.epoch_interruption(self.epoch_interruption);
345         config.generate_address_map(!self.disable_address_map);
346         config.memory_init_cow(!self.disable_memory_init_cow);
347 
348         #[cfg(feature = "pooling-allocator")]
349         {
350             if self.pooling_allocator {
351                 config.allocation_strategy(wasmtime::InstanceAllocationStrategy::pooling());
352             }
353         }
354 
355         if let Some(max) = self.max_wasm_stack {
356             config.max_wasm_stack(max);
357         }
358 
359         config.relaxed_simd_deterministic(self.relaxed_simd_deterministic);
360 
361         Ok(config)
362     }
363 
364     pub fn enable_wasm_features(&self, config: &mut Config) {
365         let WasmFeatures {
366             simd,
367             relaxed_simd,
368             bulk_memory,
369             reference_types,
370             multi_value,
371             tail_call,
372             threads,
373             multi_memory,
374             memory64,
375             #[cfg(feature = "component-model")]
376             component_model,
377             function_references,
378         } = self.wasm_features.unwrap_or_default();
379 
380         if let Some(enable) = simd {
381             config.wasm_simd(enable);
382         }
383         if let Some(enable) = relaxed_simd {
384             config.wasm_relaxed_simd(enable);
385         }
386         if let Some(enable) = bulk_memory {
387             config.wasm_bulk_memory(enable);
388         }
389         if let Some(enable) = reference_types {
390             config.wasm_reference_types(enable);
391         }
392         if let Some(enable) = function_references {
393             config.wasm_function_references(enable);
394         }
395         if let Some(enable) = multi_value {
396             config.wasm_multi_value(enable);
397         }
398         if let Some(enable) = tail_call {
399             config.wasm_tail_call(enable);
400         }
401         if let Some(enable) = threads {
402             config.wasm_threads(enable);
403         }
404         if let Some(enable) = multi_memory {
405             config.wasm_multi_memory(enable);
406         }
407         if let Some(enable) = memory64 {
408             config.wasm_memory64(enable);
409         }
410         #[cfg(feature = "component-model")]
411         if let Some(enable) = component_model {
412             config.wasm_component_model(enable);
413         }
414     }
415 
416     pub fn opt_level(&self) -> wasmtime::OptLevel {
417         match (self.optimize, self.opt_level.clone()) {
418             (true, _) => wasmtime::OptLevel::Speed,
419             (false, other) => other.unwrap_or(wasmtime::OptLevel::Speed),
420         }
421     }
422 }
423 
424 fn parse_opt_level(opt_level: &str) -> Result<wasmtime::OptLevel> {
425     match opt_level {
426         "s" => Ok(wasmtime::OptLevel::SpeedAndSize),
427         "0" => Ok(wasmtime::OptLevel::None),
428         "1" => Ok(wasmtime::OptLevel::Speed),
429         "2" => Ok(wasmtime::OptLevel::Speed),
430         other => bail!(
431             "unknown optimization level `{}`, only 0,1,2,s accepted",
432             other
433         ),
434     }
435 }
436 
437 #[derive(Default, Clone, Copy)]
438 #[cfg_attr(test, derive(Debug, PartialEq))]
439 pub struct WasmFeatures {
440     pub reference_types: Option<bool>,
441     pub multi_value: Option<bool>,
442     pub bulk_memory: Option<bool>,
443     pub simd: Option<bool>,
444     pub relaxed_simd: Option<bool>,
445     pub tail_call: Option<bool>,
446     pub threads: Option<bool>,
447     pub multi_memory: Option<bool>,
448     pub memory64: Option<bool>,
449     #[cfg(feature = "component-model")]
450     pub component_model: Option<bool>,
451     pub function_references: Option<bool>,
452 }
453 
454 fn parse_wasm_features(features: &str) -> Result<WasmFeatures> {
455     let features = features.trim();
456 
457     let mut all = None;
458     let mut values: HashMap<_, _> = SUPPORTED_WASM_FEATURES
459         .iter()
460         .map(|(name, _)| (name.to_string(), None))
461         .collect();
462 
463     if features == "all" {
464         all = Some(true);
465     } else if features == "-all" {
466         all = Some(false);
467     } else {
468         for feature in features.split(',') {
469             let feature = feature.trim();
470 
471             if feature.is_empty() {
472                 continue;
473             }
474 
475             let (feature, value) = if feature.starts_with('-') {
476                 (&feature[1..], false)
477             } else {
478                 (feature, true)
479             };
480 
481             if feature == "all" {
482                 bail!("'all' cannot be specified with other WebAssembly features");
483             }
484 
485             match values.get_mut(feature) {
486                 Some(v) => *v = Some(value),
487                 None => bail!("unsupported WebAssembly feature '{}'", feature),
488             }
489         }
490     }
491 
492     Ok(WasmFeatures {
493         reference_types: all.or(values["reference-types"]),
494         multi_value: all.or(values["multi-value"]),
495         bulk_memory: all.or(values["bulk-memory"]),
496         simd: all.or(values["simd"]),
497         relaxed_simd: all.or(values["relaxed-simd"]),
498         tail_call: all.or(values["tail-call"]),
499         threads: all.or(values["threads"]),
500         multi_memory: all.or(values["multi-memory"]),
501         memory64: all.or(values["memory64"]),
502         #[cfg(feature = "component-model")]
503         component_model: all.or(values["component-model"]),
504         function_references: all.or(values["function-references"]),
505     })
506 }
507 
508 fn parse_wasi_modules(modules: &str) -> Result<WasiModules> {
509     let modules = modules.trim();
510     match modules {
511         "default" => Ok(WasiModules::default()),
512         "-default" => Ok(WasiModules::none()),
513         _ => {
514             // Starting from the default set of WASI modules, enable or disable a list of
515             // comma-separated modules.
516             let mut wasi_modules = WasiModules::default();
517             let mut set = |module: &str, enable: bool| match module {
518                 "" => Ok(()),
519                 "wasi-common" => Ok(wasi_modules.wasi_common = enable),
520                 "experimental-wasi-nn" => Ok(wasi_modules.wasi_nn = enable),
521                 "experimental-wasi-threads" => Ok(wasi_modules.wasi_threads = enable),
522                 "experimental-wasi-http" => Ok(wasi_modules.wasi_http = enable),
523                 "default" => bail!("'default' cannot be specified with other WASI modules"),
524                 _ => bail!("unsupported WASI module '{}'", module),
525             };
526 
527             for module in modules.split(',') {
528                 let module = module.trim();
529                 let (module, value) = if module.starts_with('-') {
530                     (&module[1..], false)
531                 } else {
532                     (module, true)
533                 };
534                 set(module, value)?;
535             }
536 
537             Ok(wasi_modules)
538         }
539     }
540 }
541 
542 /// Select which WASI modules are available at runtime for use by Wasm programs.
543 #[derive(Debug, Clone, Copy, PartialEq)]
544 pub struct WasiModules {
545     /// Enable the wasi-common implementation; eventually this should be split into its separate
546     /// parts once the implementation allows for it (e.g. wasi-fs, wasi-clocks, etc.).
547     pub wasi_common: bool,
548 
549     /// Enable the experimental wasi-nn implementation.
550     pub wasi_nn: bool,
551 
552     /// Enable the experimental wasi-threads implementation.
553     pub wasi_threads: bool,
554 
555     /// Enable the experimental wasi-http implementation
556     pub wasi_http: bool,
557 }
558 
559 impl Default for WasiModules {
560     fn default() -> Self {
561         Self {
562             wasi_common: true,
563             wasi_nn: false,
564             wasi_threads: false,
565             wasi_http: false,
566         }
567     }
568 }
569 
570 impl WasiModules {
571     /// Enable no modules.
572     pub fn none() -> Self {
573         Self {
574             wasi_common: false,
575             wasi_nn: false,
576             wasi_threads: false,
577             wasi_http: false,
578         }
579     }
580 }
581 
582 fn parse_cranelift_flag(name_and_value: &str) -> Result<(String, String)> {
583     let mut split = name_and_value.splitn(2, '=');
584     let name = if let Some(name) = split.next() {
585         name.to_string()
586     } else {
587         bail!("missing name in cranelift flag");
588     };
589     let value = if let Some(value) = split.next() {
590         value.to_string()
591     } else {
592         bail!("missing value in cranelift flag");
593     };
594     Ok((name, value))
595 }
596 
597 #[cfg(test)]
598 mod test {
599     use super::*;
600 
601     #[test]
602     fn test_all_features() -> Result<()> {
603         let options = CommonOptions::try_parse_from(vec!["foo", "--wasm-features=all"])?;
604 
605         let WasmFeatures {
606             reference_types,
607             multi_value,
608             bulk_memory,
609             simd,
610             relaxed_simd,
611             tail_call,
612             threads,
613             multi_memory,
614             memory64,
615             function_references,
616             #[cfg(feature = "component-model")]
617             component_model,
618         } = options.wasm_features.unwrap();
619 
620         assert_eq!(reference_types, Some(true));
621         assert_eq!(multi_value, Some(true));
622         assert_eq!(bulk_memory, Some(true));
623         assert_eq!(simd, Some(true));
624         assert_eq!(tail_call, Some(true));
625         assert_eq!(threads, Some(true));
626         assert_eq!(multi_memory, Some(true));
627         assert_eq!(memory64, Some(true));
628         assert_eq!(function_references, Some(true));
629         assert_eq!(relaxed_simd, Some(true));
630         #[cfg(feature = "component-model")]
631         assert_eq!(component_model, Some(true));
632 
633         Ok(())
634     }
635 
636     #[test]
637     fn test_no_features() -> Result<()> {
638         let options = CommonOptions::try_parse_from(vec!["foo", "--wasm-features=-all"])?;
639 
640         let WasmFeatures {
641             reference_types,
642             multi_value,
643             bulk_memory,
644             simd,
645             relaxed_simd,
646             tail_call,
647             threads,
648             multi_memory,
649             memory64,
650             function_references,
651             #[cfg(feature = "component-model")]
652             component_model,
653         } = options.wasm_features.unwrap();
654 
655         assert_eq!(reference_types, Some(false));
656         assert_eq!(multi_value, Some(false));
657         assert_eq!(bulk_memory, Some(false));
658         assert_eq!(simd, Some(false));
659         assert_eq!(tail_call, Some(false));
660         assert_eq!(threads, Some(false));
661         assert_eq!(multi_memory, Some(false));
662         assert_eq!(memory64, Some(false));
663         assert_eq!(function_references, Some(false));
664         assert_eq!(relaxed_simd, Some(false));
665         #[cfg(feature = "component-model")]
666         assert_eq!(component_model, Some(false));
667 
668         Ok(())
669     }
670 
671     #[test]
672     fn test_multiple_features() -> Result<()> {
673         let options = CommonOptions::try_parse_from(vec![
674             "foo",
675             "--wasm-features=-reference-types,simd,multi-memory,memory64",
676         ])?;
677 
678         let WasmFeatures {
679             reference_types,
680             multi_value,
681             bulk_memory,
682             simd,
683             relaxed_simd,
684             tail_call,
685             threads,
686             multi_memory,
687             memory64,
688             function_references,
689             #[cfg(feature = "component-model")]
690             component_model,
691         } = options.wasm_features.unwrap();
692 
693         assert_eq!(reference_types, Some(false));
694         assert_eq!(multi_value, None);
695         assert_eq!(bulk_memory, None);
696         assert_eq!(simd, Some(true));
697         assert_eq!(tail_call, None);
698         assert_eq!(threads, None);
699         assert_eq!(multi_memory, Some(true));
700         assert_eq!(memory64, Some(true));
701         assert_eq!(function_references, None);
702         assert_eq!(relaxed_simd, None);
703         #[cfg(feature = "component-model")]
704         assert_eq!(component_model, None);
705 
706         Ok(())
707     }
708 
709     macro_rules! feature_test {
710         ($test_name:ident, $name:ident, $flag:literal) => {
711             #[test]
712             fn $test_name() -> Result<()> {
713                 let options =
714                     CommonOptions::try_parse_from(vec!["foo", concat!("--wasm-features=", $flag)])?;
715 
716                 let WasmFeatures { $name, .. } = options.wasm_features.unwrap();
717 
718                 assert_eq!($name, Some(true));
719 
720                 let options = CommonOptions::try_parse_from(vec![
721                     "foo",
722                     concat!("--wasm-features=-", $flag),
723                 ])?;
724 
725                 let WasmFeatures { $name, .. } = options.wasm_features.unwrap();
726 
727                 assert_eq!($name, Some(false));
728 
729                 Ok(())
730             }
731         };
732     }
733 
734     feature_test!(
735         test_reference_types_feature,
736         reference_types,
737         "reference-types"
738     );
739     feature_test!(test_multi_value_feature, multi_value, "multi-value");
740     feature_test!(test_bulk_memory_feature, bulk_memory, "bulk-memory");
741     feature_test!(test_simd_feature, simd, "simd");
742     feature_test!(test_relaxed_simd_feature, relaxed_simd, "relaxed-simd");
743     feature_test!(test_tail_call_feature, tail_call, "tail-call");
744     feature_test!(test_threads_feature, threads, "threads");
745     feature_test!(test_multi_memory_feature, multi_memory, "multi-memory");
746     feature_test!(test_memory64_feature, memory64, "memory64");
747 
748     #[test]
749     fn test_default_modules() {
750         let options = CommonOptions::try_parse_from(vec!["foo", "--wasi-modules=default"]).unwrap();
751         assert_eq!(
752             options.wasi_modules.unwrap(),
753             WasiModules {
754                 wasi_common: true,
755                 wasi_nn: false,
756                 wasi_threads: false,
757                 wasi_http: false,
758             }
759         );
760     }
761 
762     #[test]
763     fn test_empty_modules() {
764         let options = CommonOptions::try_parse_from(vec!["foo", "--wasi-modules="]).unwrap();
765         assert_eq!(
766             options.wasi_modules.unwrap(),
767             WasiModules {
768                 wasi_common: true,
769                 wasi_nn: false,
770                 wasi_threads: false,
771                 wasi_http: false
772             }
773         );
774     }
775 
776     #[test]
777     fn test_some_modules() {
778         let options = CommonOptions::try_parse_from(vec![
779             "foo",
780             "--wasi-modules=experimental-wasi-nn,-wasi-common",
781         ])
782         .unwrap();
783         assert_eq!(
784             options.wasi_modules.unwrap(),
785             WasiModules {
786                 wasi_common: false,
787                 wasi_nn: true,
788                 wasi_threads: false,
789                 wasi_http: false,
790             }
791         );
792     }
793 
794     #[test]
795     fn test_no_modules() {
796         let options =
797             CommonOptions::try_parse_from(vec!["foo", "--wasi-modules=-default"]).unwrap();
798         assert_eq!(
799             options.wasi_modules.unwrap(),
800             WasiModules {
801                 wasi_common: false,
802                 wasi_nn: false,
803                 wasi_threads: false,
804                 wasi_http: false,
805             }
806         );
807     }
808 }
809