use crate::signatures::SignatureRegistry; use crate::{Config, Trap}; use anyhow::Result; #[cfg(feature = "parallel-compilation")] use rayon::prelude::*; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; #[cfg(feature = "cache")] use wasmtime_cache::CacheConfig; use wasmtime_runtime::{debug_builtins, CompiledModuleIdAllocator, InstanceAllocator}; /// An `Engine` which is a global context for compilation and management of wasm /// modules. /// /// An engine can be safely shared across threads and is a cheap cloneable /// handle to the actual engine. The engine itself will be deallocated once all /// references to it have gone away. /// /// Engines store global configuration preferences such as compilation settings, /// enabled features, etc. You'll likely only need at most one of these for a /// program. /// /// ## Engines and `Clone` /// /// Using `clone` on an `Engine` is a cheap operation. It will not create an /// entirely new engine, but rather just a new reference to the existing engine. /// In other words it's a shallow copy, not a deep copy. /// /// ## Engines and `Default` /// /// You can create an engine with default configuration settings using /// `Engine::default()`. Be sure to consult the documentation of [`Config`] for /// default settings. #[derive(Clone)] pub struct Engine { inner: Arc, } struct EngineInner { config: Config, #[cfg(compiler)] compiler: Box, allocator: Box, signatures: SignatureRegistry, epoch: AtomicU64, unique_id_allocator: CompiledModuleIdAllocator, } impl Engine { /// Creates a new [`Engine`] with the specified compilation and /// configuration settings. pub fn new(config: &Config) -> Result { // Ensure that wasmtime_runtime's signal handlers are configured. This // is the per-program initialization required for handling traps, such // as configuring signals, vectored exception handlers, etc. wasmtime_runtime::init_traps(crate::module::GlobalModuleRegistry::is_wasm_trap_pc); debug_builtins::ensure_exported(); let registry = SignatureRegistry::new(); let mut config = config.clone(); let allocator = config.build_allocator()?; allocator.adjust_tunables(&mut config.tunables); Ok(Engine { inner: Arc::new(EngineInner { #[cfg(compiler)] compiler: config.compiler.build()?, config, allocator, signatures: registry, epoch: AtomicU64::new(0), unique_id_allocator: CompiledModuleIdAllocator::new(), }), }) } /// Eagerly initialize thread-local functionality shared by all [`Engine`]s. /// /// Wasmtime's implementation on some platforms may involve per-thread /// setup that needs to happen whenever WebAssembly is invoked. This setup /// can take on the order of a few hundred microseconds, whereas the /// overhead of calling WebAssembly is otherwise on the order of a few /// nanoseconds. This setup cost is paid once per-OS-thread. If your /// application is sensitive to the latencies of WebAssembly function /// calls, even those that happen first on a thread, then this function /// can be used to improve the consistency of each call into WebAssembly /// by explicitly frontloading the cost of the one-time setup per-thread. /// /// Note that this function is not required to be called in any embedding. /// Wasmtime will automatically initialize thread-local-state as necessary /// on calls into WebAssembly. This is provided for use cases where the /// latency of WebAssembly calls are extra-important, which is not /// necessarily true of all embeddings. pub fn tls_eager_initialize() -> Result<(), Trap> { wasmtime_runtime::tls_eager_initialize().map_err(Trap::from_runtime_box) } /// Returns the configuration settings that this engine is using. #[inline] pub fn config(&self) -> &Config { &self.inner.config } #[cfg(compiler)] pub(crate) fn compiler(&self) -> &dyn wasmtime_environ::Compiler { &*self.inner.compiler } pub(crate) fn allocator(&self) -> &dyn InstanceAllocator { self.inner.allocator.as_ref() } #[cfg(feature = "cache")] pub(crate) fn cache_config(&self) -> &CacheConfig { &self.config().cache_config } /// Returns whether the engine `a` and `b` refer to the same configuration. pub fn same(a: &Engine, b: &Engine) -> bool { Arc::ptr_eq(&a.inner, &b.inner) } pub(crate) fn signatures(&self) -> &SignatureRegistry { &self.inner.signatures } pub(crate) fn epoch_counter(&self) -> &AtomicU64 { &self.inner.epoch } pub(crate) fn current_epoch(&self) -> u64 { self.epoch_counter().load(Ordering::Relaxed) } /// Increments the epoch. /// /// When using epoch-based interruption, currently-executing Wasm /// code within this engine will trap or yield "soon" when the /// epoch deadline is reached or exceeded. (The configuration, and /// the deadline, are set on the `Store`.) The intent of the /// design is for this method to be called by the embedder at some /// regular cadence, for example by a thread that wakes up at some /// interval, or by a signal handler. /// /// See [`Config::epoch_interruption`](crate::Config::epoch_interruption) /// for an introduction to epoch-based interruption and pointers /// to the other relevant methods. /// /// ## Signal Safety /// /// This method is signal-safe: it does not make any syscalls, and /// performs only an atomic increment to the epoch value in /// memory. pub fn increment_epoch(&self) { self.inner.epoch.fetch_add(1, Ordering::Relaxed); } pub(crate) fn unique_id_allocator(&self) -> &CompiledModuleIdAllocator { &self.inner.unique_id_allocator } /// Ahead-of-time (AOT) compiles a WebAssembly module. /// /// The `bytes` provided must be in one of two formats: /// /// * A [binary-encoded][binary] WebAssembly module. This is always supported. /// * A [text-encoded][text] instance of the WebAssembly text format. /// This is only supported when the `wat` feature of this crate is enabled. /// If this is supplied then the text format will be parsed before validation. /// Note that the `wat` feature is enabled by default. /// /// This method may be used to compile a module for use with a different target /// host. The output of this method may be used with /// [`Module::deserialize`](crate::Module::deserialize) on hosts compatible /// with the [`Config`] associated with this [`Engine`]. /// /// The output of this method is safe to send to another host machine for later /// execution. As the output is already a compiled module, translation and code /// generation will be skipped and this will improve the performance of constructing /// a [`Module`](crate::Module) from the output of this method. /// /// [binary]: https://webassembly.github.io/spec/core/binary/index.html /// [text]: https://webassembly.github.io/spec/core/text/index.html #[cfg(compiler)] #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs pub fn precompile_module(&self, bytes: &[u8]) -> Result> { #[cfg(feature = "wat")] let bytes = wat::parse_bytes(&bytes)?; let (_, artifacts, types) = crate::Module::build_artifacts(self, &bytes)?; let artifacts = artifacts.into_iter().map(|i| i.0).collect::>(); crate::module::SerializedModule::from_artifacts(self, &artifacts, &types) .to_bytes(&self.config().module_version) } pub(crate) fn run_maybe_parallel< A: Send, B: Send, E: Send, F: Fn(A) -> Result + Send + Sync, >( &self, input: Vec, f: F, ) -> Result, E> { if self.config().parallel_compilation { #[cfg(feature = "parallel-compilation")] return input .into_par_iter() .map(|a| f(a)) .collect::, E>>(); } // In case the parallel-compilation feature is disabled or the parallel_compilation config // was turned off dynamically fallback to the non-parallel version. input .into_iter() .map(|a| f(a)) .collect::, E>>() } } impl Default for Engine { fn default() -> Engine { Engine::new(&Config::default()).unwrap() } } #[cfg(test)] mod tests { use crate::{Config, Engine, Module, OptLevel}; use anyhow::Result; use tempfile::TempDir; #[test] fn cache_accounts_for_opt_level() -> Result<()> { let td = TempDir::new()?; let config_path = td.path().join("config.toml"); std::fs::write( &config_path, &format!( " [cache] enabled = true directory = '{}' ", td.path().join("cache").display() ), )?; let mut cfg = Config::new(); cfg.cranelift_opt_level(OptLevel::None) .cache_config_load(&config_path)?; let engine = Engine::new(&cfg)?; Module::new(&engine, "(module (func))")?; assert_eq!(engine.config().cache_config.cache_hits(), 0); assert_eq!(engine.config().cache_config.cache_misses(), 1); Module::new(&engine, "(module (func))")?; assert_eq!(engine.config().cache_config.cache_hits(), 1); assert_eq!(engine.config().cache_config.cache_misses(), 1); let mut cfg = Config::new(); cfg.cranelift_opt_level(OptLevel::Speed) .cache_config_load(&config_path)?; let engine = Engine::new(&cfg)?; Module::new(&engine, "(module (func))")?; assert_eq!(engine.config().cache_config.cache_hits(), 0); assert_eq!(engine.config().cache_config.cache_misses(), 1); Module::new(&engine, "(module (func))")?; assert_eq!(engine.config().cache_config.cache_hits(), 1); assert_eq!(engine.config().cache_config.cache_misses(), 1); let mut cfg = Config::new(); cfg.cranelift_opt_level(OptLevel::SpeedAndSize) .cache_config_load(&config_path)?; let engine = Engine::new(&cfg)?; Module::new(&engine, "(module (func))")?; assert_eq!(engine.config().cache_config.cache_hits(), 0); assert_eq!(engine.config().cache_config.cache_misses(), 1); Module::new(&engine, "(module (func))")?; assert_eq!(engine.config().cache_config.cache_hits(), 1); assert_eq!(engine.config().cache_config.cache_misses(), 1); // FIXME(#1523) need debuginfo on aarch64 before we run this test there if !cfg!(target_arch = "aarch64") { let mut cfg = Config::new(); cfg.debug_info(true).cache_config_load(&config_path)?; let engine = Engine::new(&cfg)?; Module::new(&engine, "(module (func))")?; assert_eq!(engine.config().cache_config.cache_hits(), 0); assert_eq!(engine.config().cache_config.cache_misses(), 1); Module::new(&engine, "(module (func))")?; assert_eq!(engine.config().cache_config.cache_hits(), 1); assert_eq!(engine.config().cache_config.cache_misses(), 1); } Ok(()) } }