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_nan_canonicalization" 335 | "enable_jump_tables" 336 | "enable_float" 337 | "enable_simd" 338 | "enable_verifier" 339 | "is_pic" 340 | "machine_code_cfg_info" 341 | "tls_model" // wasmtime doesn't use tls right now 342 | "opt_level" // opt level doesn't change semantics 343 | "probestack_func_adjusts_sp" // probestack above asserted disabled 344 | "probestack_size_log2" // probestack above asserted disabled 345 | "regalloc" // shouldn't change semantics 346 | "enable_atomics" => return Ok(()), 347 348 // Everything else is unknown and needs to be added somewhere to 349 // this list if encountered. 350 _ => { 351 return Err(format!("unknown shared setting {:?} configured to {:?}", flag, value)) 352 } 353 }; 354 355 if !ok { 356 return Err(format!( 357 "setting {:?} is configured to {:?} which is not supported", 358 flag, value, 359 )); 360 } 361 Ok(()) 362 } 363 364 /// Same as `check_compatible_with_native_host` except used for ISA-specific 365 /// flags. This is used to test whether a configured ISA flag is indeed 366 /// available on the host platform itself. 367 pub(crate) fn check_compatible_with_isa_flag( 368 &self, 369 flag: &str, 370 value: &FlagValue, 371 ) -> Result<(), String> { 372 match value { 373 // ISA flags are used for things like CPU features, so if they're 374 // disabled then it's compatible with the native host. 375 FlagValue::Bool(false) => return Ok(()), 376 377 // Fall through below where we test at runtime that features are 378 // available. 379 FlagValue::Bool(true) => {} 380 381 // Only `bool` values are supported right now, other settings would 382 // need more support here. 383 _ => { 384 return Err(format!( 385 "isa-specific feature {:?} configured to unknown value {:?}", 386 flag, value 387 )) 388 } 389 } 390 #[cfg(target_arch = "x86_64")] 391 { 392 let enabled = match flag { 393 "has_sse3" => Some(std::is_x86_feature_detected!("sse3")), 394 "has_ssse3" => Some(std::is_x86_feature_detected!("ssse3")), 395 "has_sse41" => Some(std::is_x86_feature_detected!("sse4.1")), 396 "has_sse42" => Some(std::is_x86_feature_detected!("sse4.2")), 397 "has_popcnt" => Some(std::is_x86_feature_detected!("popcnt")), 398 "has_avx" => Some(std::is_x86_feature_detected!("avx")), 399 "has_avx2" => Some(std::is_x86_feature_detected!("avx2")), 400 "has_bmi1" => Some(std::is_x86_feature_detected!("bmi1")), 401 "has_bmi2" => Some(std::is_x86_feature_detected!("bmi2")), 402 "has_avx512bitalg" => Some(std::is_x86_feature_detected!("avx512bitalg")), 403 "has_avx512dq" => Some(std::is_x86_feature_detected!("avx512dq")), 404 "has_avx512f" => Some(std::is_x86_feature_detected!("avx512f")), 405 "has_avx512vl" => Some(std::is_x86_feature_detected!("avx512vl")), 406 "has_avx512vbmi" => Some(std::is_x86_feature_detected!("avx512vbmi")), 407 "has_lzcnt" => Some(std::is_x86_feature_detected!("lzcnt")), 408 409 // fall through to the very bottom to indicate that support is 410 // not enabled to test whether this feature is enabled on the 411 // host. 412 _ => None, 413 }; 414 match enabled { 415 Some(true) => return Ok(()), 416 Some(false) => { 417 return Err(format!( 418 "compilation setting {:?} is enabled but not available on the host", 419 flag 420 )) 421 } 422 // fall through 423 None => {} 424 } 425 } 426 Err(format!( 427 "cannot test if target-specific flag {:?} is available at runtime", 428 flag 429 )) 430 } 431 } 432 433 impl Default for Engine { 434 fn default() -> Engine { 435 Engine::new(&Config::default()).unwrap() 436 } 437 } 438 439 #[cfg(test)] 440 mod tests { 441 use crate::{Config, Engine, Module, OptLevel}; 442 use anyhow::Result; 443 use tempfile::TempDir; 444 445 #[test] 446 fn cache_accounts_for_opt_level() -> Result<()> { 447 let td = TempDir::new()?; 448 let config_path = td.path().join("config.toml"); 449 std::fs::write( 450 &config_path, 451 &format!( 452 " 453 [cache] 454 enabled = true 455 directory = '{}' 456 ", 457 td.path().join("cache").display() 458 ), 459 )?; 460 let mut cfg = Config::new(); 461 cfg.cranelift_opt_level(OptLevel::None) 462 .cache_config_load(&config_path)?; 463 let engine = Engine::new(&cfg)?; 464 Module::new(&engine, "(module (func))")?; 465 assert_eq!(engine.config().cache_config.cache_hits(), 0); 466 assert_eq!(engine.config().cache_config.cache_misses(), 1); 467 Module::new(&engine, "(module (func))")?; 468 assert_eq!(engine.config().cache_config.cache_hits(), 1); 469 assert_eq!(engine.config().cache_config.cache_misses(), 1); 470 471 let mut cfg = Config::new(); 472 cfg.cranelift_opt_level(OptLevel::Speed) 473 .cache_config_load(&config_path)?; 474 let engine = Engine::new(&cfg)?; 475 Module::new(&engine, "(module (func))")?; 476 assert_eq!(engine.config().cache_config.cache_hits(), 0); 477 assert_eq!(engine.config().cache_config.cache_misses(), 1); 478 Module::new(&engine, "(module (func))")?; 479 assert_eq!(engine.config().cache_config.cache_hits(), 1); 480 assert_eq!(engine.config().cache_config.cache_misses(), 1); 481 482 let mut cfg = Config::new(); 483 cfg.cranelift_opt_level(OptLevel::SpeedAndSize) 484 .cache_config_load(&config_path)?; 485 let engine = Engine::new(&cfg)?; 486 Module::new(&engine, "(module (func))")?; 487 assert_eq!(engine.config().cache_config.cache_hits(), 0); 488 assert_eq!(engine.config().cache_config.cache_misses(), 1); 489 Module::new(&engine, "(module (func))")?; 490 assert_eq!(engine.config().cache_config.cache_hits(), 1); 491 assert_eq!(engine.config().cache_config.cache_misses(), 1); 492 493 // FIXME(#1523) need debuginfo on aarch64 before we run this test there 494 if !cfg!(target_arch = "aarch64") { 495 let mut cfg = Config::new(); 496 cfg.debug_info(true).cache_config_load(&config_path)?; 497 let engine = Engine::new(&cfg)?; 498 Module::new(&engine, "(module (func))")?; 499 assert_eq!(engine.config().cache_config.cache_hits(), 0); 500 assert_eq!(engine.config().cache_config.cache_misses(), 1); 501 Module::new(&engine, "(module (func))")?; 502 assert_eq!(engine.config().cache_config.cache_hits(), 1); 503 assert_eq!(engine.config().cache_config.cache_misses(), 1); 504 } 505 506 Ok(()) 507 } 508 } 509