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(" ").filter(|s| !s.is_empty()); 233 // The first argument is the name of the executable, which we don't use 234 // here, but have to provide because `clap` skips over it, and otherwise 235 // our first CLI flag will be ignored. 236 let parts = Some("wasmtime").into_iter().chain(parts); 237 let options = 238 Self::try_parse_from(parts).context("unable to parse options from passed flags")?; 239 Ok(options) 240 } 241 242 pub fn init_logging(&self) { 243 if self.disable_logging { 244 return; 245 } 246 if self.log_to_files { 247 let prefix = "wasmtime.dbg."; 248 init_file_per_thread_logger(prefix); 249 } else { 250 pretty_env_logger::init(); 251 } 252 } 253 254 pub fn config(&self, target: Option<&str>) -> Result<Config> { 255 let mut config = Config::new(); 256 257 // Set the target before setting any cranelift options, since the 258 // target will reset any target-specific options. 259 if let Some(target) = target { 260 config.target(target)?; 261 } 262 263 config 264 .cranelift_debug_verifier(self.enable_cranelift_debug_verifier) 265 .debug_info(self.debug_info) 266 .cranelift_opt_level(self.opt_level()) 267 .profiler(pick_profiling_strategy(self.jitdump, self.vtune)?) 268 .cranelift_nan_canonicalization(self.enable_cranelift_nan_canonicalization); 269 270 self.enable_wasm_features(&mut config); 271 272 for name in &self.cranelift_enable { 273 unsafe { 274 config.cranelift_flag_enable(name); 275 } 276 } 277 278 for (name, value) in &self.cranelift_set { 279 unsafe { 280 config.cranelift_flag_set(name, value); 281 } 282 } 283 284 if !self.disable_cache { 285 match &self.config { 286 Some(path) => { 287 config.cache_config_load(path)?; 288 } 289 None => { 290 config.cache_config_load_default()?; 291 } 292 } 293 } 294 295 if let Some(max) = self.static_memory_maximum_size { 296 config.static_memory_maximum_size(max); 297 } 298 299 config.static_memory_forced(self.static_memory_forced); 300 301 if let Some(size) = self.static_memory_guard_size { 302 config.static_memory_guard_size(size); 303 } 304 305 if let Some(size) = self.dynamic_memory_guard_size { 306 config.dynamic_memory_guard_size(size); 307 } 308 309 // If fuel has been configured, set the `consume fuel` flag on the config. 310 if self.fuel.is_some() { 311 config.consume_fuel(true); 312 } 313 314 config.epoch_interruption(self.epoch_interruption); 315 config.generate_address_map(!self.disable_address_map); 316 #[cfg(feature = "memory-init-cow")] 317 config.memory_init_cow(!self.disable_memory_init_cow); 318 319 #[cfg(feature = "pooling-allocator")] 320 { 321 if self.pooling_allocator { 322 let instance_limits = InstanceLimits::default(); 323 config.allocation_strategy(wasmtime::InstanceAllocationStrategy::Pooling { 324 strategy: PoolingAllocationStrategy::NextAvailable, 325 instance_limits, 326 }); 327 } 328 } 329 330 Ok(config) 331 } 332 333 pub fn enable_wasm_features(&self, config: &mut Config) { 334 let WasmFeatures { 335 simd, 336 bulk_memory, 337 reference_types, 338 multi_value, 339 threads, 340 multi_memory, 341 memory64, 342 #[cfg(feature = "component-model")] 343 component_model, 344 } = self.wasm_features.unwrap_or_default(); 345 346 if let Some(enable) = simd { 347 config.wasm_simd(enable); 348 } 349 if let Some(enable) = bulk_memory { 350 config.wasm_bulk_memory(enable); 351 } 352 if let Some(enable) = reference_types { 353 config.wasm_reference_types(enable); 354 } 355 if let Some(enable) = multi_value { 356 config.wasm_multi_value(enable); 357 } 358 if let Some(enable) = threads { 359 config.wasm_threads(enable); 360 } 361 if let Some(enable) = multi_memory { 362 config.wasm_multi_memory(enable); 363 } 364 if let Some(enable) = memory64 { 365 config.wasm_memory64(enable); 366 } 367 #[cfg(feature = "component-model")] 368 if let Some(enable) = component_model { 369 config.wasm_component_model(enable); 370 } 371 } 372 373 pub fn opt_level(&self) -> wasmtime::OptLevel { 374 match (self.optimize, self.opt_level.clone()) { 375 (true, _) => wasmtime::OptLevel::Speed, 376 (false, other) => other.unwrap_or(wasmtime::OptLevel::Speed), 377 } 378 } 379 } 380 381 fn parse_opt_level(opt_level: &str) -> Result<wasmtime::OptLevel> { 382 match opt_level { 383 "s" => Ok(wasmtime::OptLevel::SpeedAndSize), 384 "0" => Ok(wasmtime::OptLevel::None), 385 "1" => Ok(wasmtime::OptLevel::Speed), 386 "2" => Ok(wasmtime::OptLevel::Speed), 387 other => bail!( 388 "unknown optimization level `{}`, only 0,1,2,s accepted", 389 other 390 ), 391 } 392 } 393 394 #[derive(Default, Clone, Copy)] 395 #[cfg_attr(test, derive(Debug, PartialEq))] 396 pub struct WasmFeatures { 397 pub reference_types: Option<bool>, 398 pub multi_value: Option<bool>, 399 pub bulk_memory: Option<bool>, 400 pub simd: Option<bool>, 401 pub threads: Option<bool>, 402 pub multi_memory: Option<bool>, 403 pub memory64: Option<bool>, 404 #[cfg(feature = "component-model")] 405 pub component_model: Option<bool>, 406 } 407 408 fn parse_wasm_features(features: &str) -> Result<WasmFeatures> { 409 let features = features.trim(); 410 411 let mut all = None; 412 let mut values: HashMap<_, _> = SUPPORTED_WASM_FEATURES 413 .iter() 414 .map(|(name, _)| (name.to_string(), None)) 415 .collect(); 416 417 if features == "all" { 418 all = Some(true); 419 } else if features == "-all" { 420 all = Some(false); 421 } else { 422 for feature in features.split(',') { 423 let feature = feature.trim(); 424 425 if feature.is_empty() { 426 continue; 427 } 428 429 let (feature, value) = if feature.starts_with('-') { 430 (&feature[1..], false) 431 } else { 432 (feature, true) 433 }; 434 435 if feature == "all" { 436 bail!("'all' cannot be specified with other WebAssembly features"); 437 } 438 439 match values.get_mut(feature) { 440 Some(v) => *v = Some(value), 441 None => bail!("unsupported WebAssembly feature '{}'", feature), 442 } 443 } 444 } 445 446 Ok(WasmFeatures { 447 reference_types: all.or(values["reference-types"]), 448 multi_value: all.or(values["multi-value"]), 449 bulk_memory: all.or(values["bulk-memory"]), 450 simd: all.or(values["simd"]), 451 threads: all.or(values["threads"]), 452 multi_memory: all.or(values["multi-memory"]), 453 memory64: all.or(values["memory64"]), 454 #[cfg(feature = "component-model")] 455 component_model: all.or(values["component-model"]), 456 }) 457 } 458 459 fn parse_wasi_modules(modules: &str) -> Result<WasiModules> { 460 let modules = modules.trim(); 461 match modules { 462 "default" => Ok(WasiModules::default()), 463 "-default" => Ok(WasiModules::none()), 464 _ => { 465 // Starting from the default set of WASI modules, enable or disable a list of 466 // comma-separated modules. 467 let mut wasi_modules = WasiModules::default(); 468 let mut set = |module: &str, enable: bool| match module { 469 "" => Ok(()), 470 "wasi-common" => Ok(wasi_modules.wasi_common = enable), 471 "experimental-wasi-nn" => Ok(wasi_modules.wasi_nn = enable), 472 "experimental-wasi-crypto" => Ok(wasi_modules.wasi_crypto = enable), 473 "default" => bail!("'default' cannot be specified with other WASI modules"), 474 _ => bail!("unsupported WASI module '{}'", module), 475 }; 476 477 for module in modules.split(',') { 478 let module = module.trim(); 479 let (module, value) = if module.starts_with('-') { 480 (&module[1..], false) 481 } else { 482 (module, true) 483 }; 484 set(module, value)?; 485 } 486 487 Ok(wasi_modules) 488 } 489 } 490 } 491 492 /// Select which WASI modules are available at runtime for use by Wasm programs. 493 #[derive(Debug, Clone, Copy, PartialEq)] 494 pub struct WasiModules { 495 /// Enable the wasi-common implementation; eventually this should be split into its separate 496 /// parts once the implementation allows for it (e.g. wasi-fs, wasi-clocks, etc.). 497 pub wasi_common: bool, 498 499 /// Enable the experimental wasi-nn implementation. 500 pub wasi_nn: bool, 501 502 /// Enable the experimental wasi-crypto implementation. 503 pub wasi_crypto: bool, 504 } 505 506 impl Default for WasiModules { 507 fn default() -> Self { 508 Self { 509 wasi_common: true, 510 wasi_nn: false, 511 wasi_crypto: false, 512 } 513 } 514 } 515 516 impl WasiModules { 517 /// Enable no modules. 518 pub fn none() -> Self { 519 Self { 520 wasi_common: false, 521 wasi_nn: false, 522 wasi_crypto: false, 523 } 524 } 525 } 526 527 fn parse_cranelift_flag(name_and_value: &str) -> Result<(String, String)> { 528 let mut split = name_and_value.splitn(2, '='); 529 let name = if let Some(name) = split.next() { 530 name.to_string() 531 } else { 532 bail!("missing name in cranelift flag"); 533 }; 534 let value = if let Some(value) = split.next() { 535 value.to_string() 536 } else { 537 bail!("missing value in cranelift flag"); 538 }; 539 Ok((name, value)) 540 } 541 542 #[cfg(test)] 543 mod test { 544 use super::*; 545 546 #[test] 547 fn test_all_features() -> Result<()> { 548 let options = CommonOptions::try_parse_from(vec!["foo", "--wasm-features=all"])?; 549 550 let WasmFeatures { 551 reference_types, 552 multi_value, 553 bulk_memory, 554 simd, 555 threads, 556 multi_memory, 557 memory64, 558 } = options.wasm_features.unwrap(); 559 560 assert_eq!(reference_types, Some(true)); 561 assert_eq!(multi_value, Some(true)); 562 assert_eq!(bulk_memory, Some(true)); 563 assert_eq!(simd, Some(true)); 564 assert_eq!(threads, Some(true)); 565 assert_eq!(multi_memory, Some(true)); 566 assert_eq!(memory64, Some(true)); 567 568 Ok(()) 569 } 570 571 #[test] 572 fn test_no_features() -> Result<()> { 573 let options = CommonOptions::try_parse_from(vec!["foo", "--wasm-features=-all"])?; 574 575 let WasmFeatures { 576 reference_types, 577 multi_value, 578 bulk_memory, 579 simd, 580 threads, 581 multi_memory, 582 memory64, 583 } = options.wasm_features.unwrap(); 584 585 assert_eq!(reference_types, Some(false)); 586 assert_eq!(multi_value, Some(false)); 587 assert_eq!(bulk_memory, Some(false)); 588 assert_eq!(simd, Some(false)); 589 assert_eq!(threads, Some(false)); 590 assert_eq!(multi_memory, Some(false)); 591 assert_eq!(memory64, Some(false)); 592 593 Ok(()) 594 } 595 596 #[test] 597 fn test_multiple_features() -> Result<()> { 598 let options = CommonOptions::try_parse_from(vec![ 599 "foo", 600 "--wasm-features=-reference-types,simd,multi-memory,memory64", 601 ])?; 602 603 let WasmFeatures { 604 reference_types, 605 multi_value, 606 bulk_memory, 607 simd, 608 threads, 609 multi_memory, 610 memory64, 611 } = options.wasm_features.unwrap(); 612 613 assert_eq!(reference_types, Some(false)); 614 assert_eq!(multi_value, None); 615 assert_eq!(bulk_memory, None); 616 assert_eq!(simd, Some(true)); 617 assert_eq!(threads, None); 618 assert_eq!(multi_memory, Some(true)); 619 assert_eq!(memory64, Some(true)); 620 621 Ok(()) 622 } 623 624 macro_rules! feature_test { 625 ($test_name:ident, $name:ident, $flag:literal) => { 626 #[test] 627 fn $test_name() -> Result<()> { 628 let options = 629 CommonOptions::try_parse_from(vec!["foo", concat!("--wasm-features=", $flag)])?; 630 631 let WasmFeatures { $name, .. } = options.wasm_features.unwrap(); 632 633 assert_eq!($name, Some(true)); 634 635 let options = CommonOptions::try_parse_from(vec![ 636 "foo", 637 concat!("--wasm-features=-", $flag), 638 ])?; 639 640 let WasmFeatures { $name, .. } = options.wasm_features.unwrap(); 641 642 assert_eq!($name, Some(false)); 643 644 Ok(()) 645 } 646 }; 647 } 648 649 feature_test!( 650 test_reference_types_feature, 651 reference_types, 652 "reference-types" 653 ); 654 feature_test!(test_multi_value_feature, multi_value, "multi-value"); 655 feature_test!(test_bulk_memory_feature, bulk_memory, "bulk-memory"); 656 feature_test!(test_simd_feature, simd, "simd"); 657 feature_test!(test_threads_feature, threads, "threads"); 658 feature_test!(test_multi_memory_feature, multi_memory, "multi-memory"); 659 feature_test!(test_memory64_feature, memory64, "memory64"); 660 661 #[test] 662 fn test_default_modules() { 663 let options = CommonOptions::try_parse_from(vec!["foo", "--wasi-modules=default"]).unwrap(); 664 assert_eq!( 665 options.wasi_modules.unwrap(), 666 WasiModules { 667 wasi_common: true, 668 wasi_nn: false, 669 wasi_crypto: false 670 } 671 ); 672 } 673 674 #[test] 675 fn test_empty_modules() { 676 let options = CommonOptions::try_parse_from(vec!["foo", "--wasi-modules="]).unwrap(); 677 assert_eq!( 678 options.wasi_modules.unwrap(), 679 WasiModules { 680 wasi_common: true, 681 wasi_nn: false, 682 wasi_crypto: false 683 } 684 ); 685 } 686 687 #[test] 688 fn test_some_modules() { 689 let options = CommonOptions::try_parse_from(vec![ 690 "foo", 691 "--wasi-modules=experimental-wasi-nn,-wasi-common", 692 ]) 693 .unwrap(); 694 assert_eq!( 695 options.wasi_modules.unwrap(), 696 WasiModules { 697 wasi_common: false, 698 wasi_nn: true, 699 wasi_crypto: false 700 } 701 ); 702 } 703 704 #[test] 705 fn test_no_modules() { 706 let options = 707 CommonOptions::try_parse_from(vec!["foo", "--wasi-modules=-default"]).unwrap(); 708 assert_eq!( 709 options.wasi_modules.unwrap(), 710 WasiModules { 711 wasi_common: false, 712 wasi_nn: false, 713 wasi_crypto: false 714 } 715 ); 716 } 717 718 #[test] 719 fn test_parse_from_str() { 720 fn use_func(flags: &str) -> CommonOptions { 721 CommonOptions::parse_from_str(flags).unwrap() 722 } 723 fn use_clap_parser(flags: &[&str]) -> CommonOptions { 724 CommonOptions::try_parse_from(flags).unwrap() 725 } 726 727 assert_eq!(use_func(""), use_clap_parser(&[])); 728 assert_eq!( 729 use_func("--wasm-features=threads"), 730 use_clap_parser(&["foo", "--wasm-features=threads"]) 731 ); 732 assert_eq!( 733 use_func("--cranelift-set enable_simd=true"), 734 use_clap_parser(&["foo", "--cranelift-set", "enable_simd=true"]) 735 ); 736 } 737 } 738