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