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