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