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