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