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