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