1 use crate::signatures::SignatureRegistry; 2 use crate::Config; 3 use anyhow::Result; 4 use once_cell::sync::OnceCell; 5 #[cfg(feature = "parallel-compilation")] 6 use rayon::prelude::*; 7 use std::sync::atomic::{AtomicU64, Ordering}; 8 use std::sync::Arc; 9 #[cfg(feature = "cache")] 10 use wasmtime_cache::CacheConfig; 11 use wasmtime_environ::FlagValue; 12 use wasmtime_jit::ProfilingAgent; 13 use wasmtime_runtime::{debug_builtins, CompiledModuleIdAllocator, InstanceAllocator}; 14 15 /// An `Engine` which is a global context for compilation and management of wasm 16 /// modules. 17 /// 18 /// An engine can be safely shared across threads and is a cheap cloneable 19 /// handle to the actual engine. The engine itself will be deallocated once all 20 /// references to it have gone away. 21 /// 22 /// Engines store global configuration preferences such as compilation settings, 23 /// enabled features, etc. You'll likely only need at most one of these for a 24 /// program. 25 /// 26 /// ## Engines and `Clone` 27 /// 28 /// Using `clone` on an `Engine` is a cheap operation. It will not create an 29 /// entirely new engine, but rather just a new reference to the existing engine. 30 /// In other words it's a shallow copy, not a deep copy. 31 /// 32 /// ## Engines and `Default` 33 /// 34 /// You can create an engine with default configuration settings using 35 /// `Engine::default()`. Be sure to consult the documentation of [`Config`] for 36 /// default settings. 37 #[derive(Clone)] 38 pub struct Engine { 39 inner: Arc<EngineInner>, 40 } 41 42 struct EngineInner { 43 config: Config, 44 #[cfg(compiler)] 45 compiler: Box<dyn wasmtime_environ::Compiler>, 46 allocator: Box<dyn InstanceAllocator>, 47 profiler: Box<dyn ProfilingAgent>, 48 signatures: SignatureRegistry, 49 epoch: AtomicU64, 50 unique_id_allocator: CompiledModuleIdAllocator, 51 52 // One-time check of whether the compiler's settings, if present, are 53 // compatible with the native host. 54 compatible_with_native_host: OnceCell<Result<(), String>>, 55 } 56 57 impl Engine { 58 /// Creates a new [`Engine`] with the specified compilation and 59 /// configuration settings. 60 /// 61 /// # Errors 62 /// 63 /// This method can fail if the `config` is invalid or some 64 /// configurations are incompatible. 65 /// 66 /// For example, feature `reference_types` will need to set 67 /// the compiler setting `enable_safepoints` and `unwind_info` 68 /// to `true`, but explicitly disable these two compiler settings 69 /// will cause errors. 70 pub fn new(config: &Config) -> Result<Engine> { 71 // Ensure that wasmtime_runtime's signal handlers are configured. This 72 // is the per-program initialization required for handling traps, such 73 // as configuring signals, vectored exception handlers, etc. 74 wasmtime_runtime::init_traps(crate::module::is_wasm_trap_pc); 75 debug_builtins::ensure_exported(); 76 77 let registry = SignatureRegistry::new(); 78 let mut config = config.clone(); 79 config.validate()?; 80 81 #[cfg(compiler)] 82 let compiler = config.build_compiler()?; 83 84 let allocator = config.build_allocator()?; 85 allocator.adjust_tunables(&mut config.tunables); 86 let profiler = config.build_profiler()?; 87 88 Ok(Engine { 89 inner: Arc::new(EngineInner { 90 #[cfg(compiler)] 91 compiler, 92 config, 93 allocator, 94 profiler, 95 signatures: registry, 96 epoch: AtomicU64::new(0), 97 unique_id_allocator: CompiledModuleIdAllocator::new(), 98 compatible_with_native_host: OnceCell::new(), 99 }), 100 }) 101 } 102 103 /// Eagerly initialize thread-local functionality shared by all [`Engine`]s. 104 /// 105 /// Wasmtime's implementation on some platforms may involve per-thread 106 /// setup that needs to happen whenever WebAssembly is invoked. This setup 107 /// can take on the order of a few hundred microseconds, whereas the 108 /// overhead of calling WebAssembly is otherwise on the order of a few 109 /// nanoseconds. This setup cost is paid once per-OS-thread. If your 110 /// application is sensitive to the latencies of WebAssembly function 111 /// calls, even those that happen first on a thread, then this function 112 /// can be used to improve the consistency of each call into WebAssembly 113 /// by explicitly frontloading the cost of the one-time setup per-thread. 114 /// 115 /// Note that this function is not required to be called in any embedding. 116 /// Wasmtime will automatically initialize thread-local-state as necessary 117 /// on calls into WebAssembly. This is provided for use cases where the 118 /// latency of WebAssembly calls are extra-important, which is not 119 /// necessarily true of all embeddings. 120 pub fn tls_eager_initialize() { 121 wasmtime_runtime::tls_eager_initialize(); 122 } 123 124 /// Returns the configuration settings that this engine is using. 125 #[inline] 126 pub fn config(&self) -> &Config { 127 &self.inner.config 128 } 129 130 #[cfg(compiler)] 131 pub(crate) fn compiler(&self) -> &dyn wasmtime_environ::Compiler { 132 &*self.inner.compiler 133 } 134 135 pub(crate) fn allocator(&self) -> &dyn InstanceAllocator { 136 self.inner.allocator.as_ref() 137 } 138 139 pub(crate) fn profiler(&self) -> &dyn ProfilingAgent { 140 self.inner.profiler.as_ref() 141 } 142 143 #[cfg(feature = "cache")] 144 pub(crate) fn cache_config(&self) -> &CacheConfig { 145 &self.config().cache_config 146 } 147 148 /// Returns whether the engine `a` and `b` refer to the same configuration. 149 pub fn same(a: &Engine, b: &Engine) -> bool { 150 Arc::ptr_eq(&a.inner, &b.inner) 151 } 152 153 pub(crate) fn signatures(&self) -> &SignatureRegistry { 154 &self.inner.signatures 155 } 156 157 pub(crate) fn epoch_counter(&self) -> &AtomicU64 { 158 &self.inner.epoch 159 } 160 161 pub(crate) fn current_epoch(&self) -> u64 { 162 self.epoch_counter().load(Ordering::Relaxed) 163 } 164 165 /// Increments the epoch. 166 /// 167 /// When using epoch-based interruption, currently-executing Wasm 168 /// code within this engine will trap or yield "soon" when the 169 /// epoch deadline is reached or exceeded. (The configuration, and 170 /// the deadline, are set on the `Store`.) The intent of the 171 /// design is for this method to be called by the embedder at some 172 /// regular cadence, for example by a thread that wakes up at some 173 /// interval, or by a signal handler. 174 /// 175 /// See [`Config::epoch_interruption`](crate::Config::epoch_interruption) 176 /// for an introduction to epoch-based interruption and pointers 177 /// to the other relevant methods. 178 /// 179 /// ## Signal Safety 180 /// 181 /// This method is signal-safe: it does not make any syscalls, and 182 /// performs only an atomic increment to the epoch value in 183 /// memory. 184 pub fn increment_epoch(&self) { 185 self.inner.epoch.fetch_add(1, Ordering::Relaxed); 186 } 187 188 pub(crate) fn unique_id_allocator(&self) -> &CompiledModuleIdAllocator { 189 &self.inner.unique_id_allocator 190 } 191 192 /// Ahead-of-time (AOT) compiles a WebAssembly module. 193 /// 194 /// The `bytes` provided must be in one of two formats: 195 /// 196 /// * A [binary-encoded][binary] WebAssembly module. This is always supported. 197 /// * A [text-encoded][text] instance of the WebAssembly text format. 198 /// This is only supported when the `wat` feature of this crate is enabled. 199 /// If this is supplied then the text format will be parsed before validation. 200 /// Note that the `wat` feature is enabled by default. 201 /// 202 /// This method may be used to compile a module for use with a different target 203 /// host. The output of this method may be used with 204 /// [`Module::deserialize`](crate::Module::deserialize) on hosts compatible 205 /// with the [`Config`] associated with this [`Engine`]. 206 /// 207 /// The output of this method is safe to send to another host machine for later 208 /// execution. As the output is already a compiled module, translation and code 209 /// generation will be skipped and this will improve the performance of constructing 210 /// a [`Module`](crate::Module) from the output of this method. 211 /// 212 /// [binary]: https://webassembly.github.io/spec/core/binary/index.html 213 /// [text]: https://webassembly.github.io/spec/core/text/index.html 214 #[cfg(compiler)] 215 #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs 216 pub fn precompile_module(&self, bytes: &[u8]) -> Result<Vec<u8>> { 217 #[cfg(feature = "wat")] 218 let bytes = wat::parse_bytes(&bytes)?; 219 let (mmap, _, types) = crate::Module::build_artifacts(self, &bytes)?; 220 crate::module::SerializedModule::from_artifacts(self, &mmap, &types) 221 .to_bytes(&self.config().module_version) 222 } 223 224 pub(crate) fn run_maybe_parallel< 225 A: Send, 226 B: Send, 227 E: Send, 228 F: Fn(A) -> Result<B, E> + Send + Sync, 229 >( 230 &self, 231 input: Vec<A>, 232 f: F, 233 ) -> Result<Vec<B>, E> { 234 if self.config().parallel_compilation { 235 #[cfg(feature = "parallel-compilation")] 236 return input 237 .into_par_iter() 238 .map(|a| f(a)) 239 .collect::<Result<Vec<B>, E>>(); 240 } 241 242 // In case the parallel-compilation feature is disabled or the parallel_compilation config 243 // was turned off dynamically fallback to the non-parallel version. 244 input 245 .into_iter() 246 .map(|a| f(a)) 247 .collect::<Result<Vec<B>, E>>() 248 } 249 250 /// Executes `f1` and `f2` in parallel if parallel compilation is enabled at 251 /// both runtime and compile time, otherwise runs them synchronously. 252 #[allow(dead_code)] // only used for the component-model feature right now 253 pub(crate) fn join_maybe_parallel<T, U>( 254 &self, 255 f1: impl FnOnce() -> T + Send, 256 f2: impl FnOnce() -> U + Send, 257 ) -> (T, U) 258 where 259 T: Send, 260 U: Send, 261 { 262 if self.config().parallel_compilation { 263 #[cfg(feature = "parallel-compilation")] 264 return rayon::join(f1, f2); 265 } 266 (f1(), f2()) 267 } 268 269 /// Returns the target triple which this engine is compiling code for 270 /// and/or running code for. 271 pub(crate) fn target(&self) -> target_lexicon::Triple { 272 // If a compiler is configured, use that target. 273 #[cfg(compiler)] 274 return self.compiler().triple().clone(); 275 276 // ... otherwise it's the native target 277 #[cfg(not(compiler))] 278 return target_lexicon::Triple::host(); 279 } 280 281 /// Verify that this engine's configuration is compatible with loading 282 /// modules onto the native host platform. 283 /// 284 /// This method is used as part of `Module::new` to ensure that this 285 /// engine can indeed load modules for the configured compiler (if any). 286 /// Note that if cranelift is disabled this trivially returns `Ok` because 287 /// loaded serialized modules are checked separately. 288 pub(crate) fn check_compatible_with_native_host(&self) -> Result<()> { 289 self.inner 290 .compatible_with_native_host 291 .get_or_init(|| self._check_compatible_with_native_host()) 292 .clone() 293 .map_err(anyhow::Error::msg) 294 } 295 fn _check_compatible_with_native_host(&self) -> Result<(), String> { 296 #[cfg(compiler)] 297 { 298 let compiler = self.compiler(); 299 300 // Check to see that the config's target matches the host 301 let target = compiler.triple(); 302 if *target != target_lexicon::Triple::host() { 303 return Err(format!( 304 "target '{}' specified in the configuration does not match the host", 305 target 306 )); 307 } 308 309 // Also double-check all compiler settings 310 for (key, value) in compiler.flags().iter() { 311 self.check_compatible_with_shared_flag(key, value)?; 312 } 313 for (key, value) in compiler.isa_flags().iter() { 314 self.check_compatible_with_isa_flag(key, value)?; 315 } 316 } 317 Ok(()) 318 } 319 320 /// Checks to see whether the "shared flag", something enabled for 321 /// individual compilers, is compatible with the native host platform. 322 /// 323 /// This is used both when validating an engine's compilation settings are 324 /// compatible with the host as well as when deserializing modules from 325 /// disk to ensure they're compatible with the current host. 326 /// 327 /// Note that most of the settings here are not configured by users that 328 /// often. While theoretically possible via `Config` methods the more 329 /// interesting flags are the ISA ones below. Typically the values here 330 /// represent global configuration for wasm features. Settings here 331 /// currently rely on the compiler informing us of all settings, including 332 /// those disabled. Settings then fall in a few buckets: 333 /// 334 /// * Some settings must be enabled, such as `avoid_div_traps`. 335 /// * Some settings must have a particular value, such as 336 /// `libcall_call_conv`. 337 /// * Some settings do not matter as to their value, such as `opt_level`. 338 pub(crate) fn check_compatible_with_shared_flag( 339 &self, 340 flag: &str, 341 value: &FlagValue, 342 ) -> Result<(), String> { 343 let ok = match flag { 344 // These settings must all have be enabled, since their value 345 // can affect the way the generated code performs or behaves at 346 // runtime. 347 "avoid_div_traps" => *value == FlagValue::Bool(true), 348 "libcall_call_conv" => *value == FlagValue::Enum("isa_default".into()), 349 "preserve_frame_pointers" => *value == FlagValue::Bool(true), 350 351 // Features wasmtime doesn't use should all be disabled, since 352 // otherwise if they are enabled it could change the behavior of 353 // generated code. 354 "enable_llvm_abi_extensions" => *value == FlagValue::Bool(false), 355 "enable_pinned_reg" => *value == FlagValue::Bool(false), 356 "enable_probestack" => *value == FlagValue::Bool(false), 357 "use_colocated_libcalls" => *value == FlagValue::Bool(false), 358 "use_pinned_reg_as_heap_base" => *value == FlagValue::Bool(false), 359 360 // If reference types are enabled this must be enabled, otherwise 361 // this setting can have any value. 362 "enable_safepoints" => { 363 if self.config().features.reference_types { 364 *value == FlagValue::Bool(true) 365 } else { 366 return Ok(()) 367 } 368 } 369 370 // Windows requires unwind info as part of its ABI. 371 "unwind_info" => { 372 if self.target().operating_system == target_lexicon::OperatingSystem::Windows { 373 *value == FlagValue::Bool(true) 374 } else { 375 return Ok(()) 376 } 377 } 378 379 // These settings don't affect the interface or functionality of 380 // the module itself, so their configuration values shouldn't 381 // matter. 382 "enable_heap_access_spectre_mitigation" 383 | "enable_table_access_spectre_mitigation" 384 | "enable_nan_canonicalization" 385 | "enable_jump_tables" 386 | "enable_float" 387 | "enable_simd" 388 | "enable_verifier" 389 | "regalloc_checker" 390 | "regalloc_verbose_logs" 391 | "is_pic" 392 | "machine_code_cfg_info" 393 | "tls_model" // wasmtime doesn't use tls right now 394 | "opt_level" // opt level doesn't change semantics 395 | "enable_alias_analysis" // alias analysis-based opts don't change semantics 396 | "probestack_func_adjusts_sp" // probestack above asserted disabled 397 | "probestack_size_log2" // probestack above asserted disabled 398 | "regalloc" // shouldn't change semantics 399 | "enable_atomics" => return Ok(()), 400 401 // Everything else is unknown and needs to be added somewhere to 402 // this list if encountered. 403 _ => { 404 return Err(format!("unknown shared setting {:?} configured to {:?}", flag, value)) 405 } 406 }; 407 408 if !ok { 409 return Err(format!( 410 "setting {:?} is configured to {:?} which is not supported", 411 flag, value, 412 )); 413 } 414 Ok(()) 415 } 416 417 /// Same as `check_compatible_with_native_host` except used for ISA-specific 418 /// flags. This is used to test whether a configured ISA flag is indeed 419 /// available on the host platform itself. 420 pub(crate) fn check_compatible_with_isa_flag( 421 &self, 422 flag: &str, 423 value: &FlagValue, 424 ) -> Result<(), String> { 425 match value { 426 // ISA flags are used for things like CPU features, so if they're 427 // disabled then it's compatible with the native host. 428 FlagValue::Bool(false) => return Ok(()), 429 430 // Fall through below where we test at runtime that features are 431 // available. 432 FlagValue::Bool(true) => {} 433 434 // Only `bool` values are supported right now, other settings would 435 // need more support here. 436 _ => { 437 return Err(format!( 438 "isa-specific feature {:?} configured to unknown value {:?}", 439 flag, value 440 )) 441 } 442 } 443 444 #[allow(unused_assignments)] 445 let mut enabled = None; 446 447 #[cfg(target_arch = "aarch64")] 448 { 449 enabled = match flag { 450 "has_lse" => Some(std::arch::is_aarch64_feature_detected!("lse")), 451 // No effect on its own, but in order to simplify the code on a 452 // platform without pointer authentication support we fail if 453 // "has_pauth" is enabled, but "sign_return_address" is not. 454 "has_pauth" => Some(std::arch::is_aarch64_feature_detected!("paca")), 455 // No effect on its own. 456 "sign_return_address_all" => Some(true), 457 // The pointer authentication instructions act as a `NOP` when 458 // unsupported (but keep in mind "has_pauth" as well), so it is 459 // safe to enable them. 460 "sign_return_address" => Some(true), 461 // No effect on its own. 462 "sign_return_address_with_bkey" => Some(true), 463 // fall through to the very bottom to indicate that support is 464 // not enabled to test whether this feature is enabled on the 465 // host. 466 _ => None, 467 }; 468 } 469 470 // There is no is_s390x_feature_detected macro yet, so for now 471 // we use getauxval from the libc crate directly. 472 #[cfg(all(target_arch = "s390x", target_os = "linux"))] 473 { 474 let v = unsafe { libc::getauxval(libc::AT_HWCAP) }; 475 const HWCAP_S390X_VXRS_EXT2: libc::c_ulong = 32768; 476 477 enabled = match flag { 478 // There is no separate HWCAP bit for mie2, so assume 479 // that any machine with vxrs_ext2 also has mie2. 480 "has_vxrs_ext2" | "has_mie2" => Some((v & HWCAP_S390X_VXRS_EXT2) != 0), 481 // fall through to the very bottom to indicate that support is 482 // not enabled to test whether this feature is enabled on the 483 // host. 484 _ => None, 485 } 486 } 487 488 #[cfg(target_arch = "x86_64")] 489 { 490 enabled = match flag { 491 "has_sse3" => Some(std::is_x86_feature_detected!("sse3")), 492 "has_ssse3" => Some(std::is_x86_feature_detected!("ssse3")), 493 "has_sse41" => Some(std::is_x86_feature_detected!("sse4.1")), 494 "has_sse42" => Some(std::is_x86_feature_detected!("sse4.2")), 495 "has_popcnt" => Some(std::is_x86_feature_detected!("popcnt")), 496 "has_avx" => Some(std::is_x86_feature_detected!("avx")), 497 "has_avx2" => Some(std::is_x86_feature_detected!("avx2")), 498 "has_fma" => Some(std::is_x86_feature_detected!("fma")), 499 "has_bmi1" => Some(std::is_x86_feature_detected!("bmi1")), 500 "has_bmi2" => Some(std::is_x86_feature_detected!("bmi2")), 501 "has_avx512bitalg" => Some(std::is_x86_feature_detected!("avx512bitalg")), 502 "has_avx512dq" => Some(std::is_x86_feature_detected!("avx512dq")), 503 "has_avx512f" => Some(std::is_x86_feature_detected!("avx512f")), 504 "has_avx512vl" => Some(std::is_x86_feature_detected!("avx512vl")), 505 "has_avx512vbmi" => Some(std::is_x86_feature_detected!("avx512vbmi")), 506 "has_lzcnt" => Some(std::is_x86_feature_detected!("lzcnt")), 507 508 // fall through to the very bottom to indicate that support is 509 // not enabled to test whether this feature is enabled on the 510 // host. 511 _ => None, 512 }; 513 } 514 515 match enabled { 516 Some(true) => return Ok(()), 517 Some(false) => { 518 return Err(format!( 519 "compilation setting {:?} is enabled, but not available on the host", 520 flag 521 )) 522 } 523 // fall through 524 None => {} 525 } 526 527 Err(format!( 528 "cannot test if target-specific flag {:?} is available at runtime", 529 flag 530 )) 531 } 532 } 533 534 impl Default for Engine { 535 fn default() -> Engine { 536 Engine::new(&Config::default()).unwrap() 537 } 538 } 539 540 #[cfg(test)] 541 mod tests { 542 use crate::{Config, Engine, Module, OptLevel}; 543 544 use anyhow::Result; 545 use tempfile::TempDir; 546 547 #[test] 548 fn cache_accounts_for_opt_level() -> Result<()> { 549 let td = TempDir::new()?; 550 let config_path = td.path().join("config.toml"); 551 std::fs::write( 552 &config_path, 553 &format!( 554 " 555 [cache] 556 enabled = true 557 directory = '{}' 558 ", 559 td.path().join("cache").display() 560 ), 561 )?; 562 let mut cfg = Config::new(); 563 cfg.cranelift_opt_level(OptLevel::None) 564 .cache_config_load(&config_path)?; 565 let engine = Engine::new(&cfg)?; 566 Module::new(&engine, "(module (func))")?; 567 assert_eq!(engine.config().cache_config.cache_hits(), 0); 568 assert_eq!(engine.config().cache_config.cache_misses(), 1); 569 Module::new(&engine, "(module (func))")?; 570 assert_eq!(engine.config().cache_config.cache_hits(), 1); 571 assert_eq!(engine.config().cache_config.cache_misses(), 1); 572 573 let mut cfg = Config::new(); 574 cfg.cranelift_opt_level(OptLevel::Speed) 575 .cache_config_load(&config_path)?; 576 let engine = Engine::new(&cfg)?; 577 Module::new(&engine, "(module (func))")?; 578 assert_eq!(engine.config().cache_config.cache_hits(), 0); 579 assert_eq!(engine.config().cache_config.cache_misses(), 1); 580 Module::new(&engine, "(module (func))")?; 581 assert_eq!(engine.config().cache_config.cache_hits(), 1); 582 assert_eq!(engine.config().cache_config.cache_misses(), 1); 583 584 let mut cfg = Config::new(); 585 cfg.cranelift_opt_level(OptLevel::SpeedAndSize) 586 .cache_config_load(&config_path)?; 587 let engine = Engine::new(&cfg)?; 588 Module::new(&engine, "(module (func))")?; 589 assert_eq!(engine.config().cache_config.cache_hits(), 0); 590 assert_eq!(engine.config().cache_config.cache_misses(), 1); 591 Module::new(&engine, "(module (func))")?; 592 assert_eq!(engine.config().cache_config.cache_hits(), 1); 593 assert_eq!(engine.config().cache_config.cache_misses(), 1); 594 595 let mut cfg = Config::new(); 596 cfg.debug_info(true).cache_config_load(&config_path)?; 597 let engine = Engine::new(&cfg)?; 598 Module::new(&engine, "(module (func))")?; 599 assert_eq!(engine.config().cache_config.cache_hits(), 0); 600 assert_eq!(engine.config().cache_config.cache_misses(), 1); 601 Module::new(&engine, "(module (func))")?; 602 assert_eq!(engine.config().cache_config.cache_hits(), 1); 603 assert_eq!(engine.config().cache_config.cache_misses(), 1); 604 605 Ok(()) 606 } 607 } 608