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