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