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