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