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