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