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