1 use crate::signatures::SignatureRegistry; 2 use crate::{Config, Trap}; 3 use anyhow::Result; 4 #[cfg(feature = "parallel-compilation")] 5 use rayon::prelude::*; 6 use std::sync::atomic::{AtomicU64, Ordering}; 7 use std::sync::Arc; 8 #[cfg(feature = "cache")] 9 use wasmtime_cache::CacheConfig; 10 use wasmtime_runtime::{debug_builtins, CompiledModuleIdAllocator, InstanceAllocator}; 11 12 /// An `Engine` which is a global context for compilation and management of wasm 13 /// modules. 14 /// 15 /// An engine can be safely shared across threads and is a cheap cloneable 16 /// handle to the actual engine. The engine itself will be deallocated once all 17 /// references to it have gone away. 18 /// 19 /// Engines store global configuration preferences such as compilation settings, 20 /// enabled features, etc. You'll likely only need at most one of these for a 21 /// program. 22 /// 23 /// ## Engines and `Clone` 24 /// 25 /// Using `clone` on an `Engine` is a cheap operation. It will not create an 26 /// entirely new engine, but rather just a new reference to the existing engine. 27 /// In other words it's a shallow copy, not a deep copy. 28 /// 29 /// ## Engines and `Default` 30 /// 31 /// You can create an engine with default configuration settings using 32 /// `Engine::default()`. Be sure to consult the documentation of [`Config`] for 33 /// default settings. 34 #[derive(Clone)] 35 pub struct Engine { 36 inner: Arc<EngineInner>, 37 } 38 39 struct EngineInner { 40 config: Config, 41 #[cfg(compiler)] 42 compiler: Box<dyn wasmtime_environ::Compiler>, 43 allocator: Box<dyn InstanceAllocator>, 44 signatures: SignatureRegistry, 45 epoch: AtomicU64, 46 unique_id_allocator: CompiledModuleIdAllocator, 47 } 48 49 impl Engine { 50 /// Creates a new [`Engine`] with the specified compilation and 51 /// configuration settings. 52 pub fn new(config: &Config) -> Result<Engine> { 53 // Ensure that wasmtime_runtime's signal handlers are configured. This 54 // is the per-program initialization required for handling traps, such 55 // as configuring signals, vectored exception handlers, etc. 56 wasmtime_runtime::init_traps(crate::module::GlobalModuleRegistry::is_wasm_trap_pc); 57 debug_builtins::ensure_exported(); 58 59 let registry = SignatureRegistry::new(); 60 let mut config = config.clone(); 61 let allocator = config.build_allocator()?; 62 allocator.adjust_tunables(&mut config.tunables); 63 64 Ok(Engine { 65 inner: Arc::new(EngineInner { 66 #[cfg(compiler)] 67 compiler: config.compiler.build()?, 68 config, 69 allocator, 70 signatures: registry, 71 epoch: AtomicU64::new(0), 72 unique_id_allocator: CompiledModuleIdAllocator::new(), 73 }), 74 }) 75 } 76 77 /// Eagerly initialize thread-local functionality shared by all [`Engine`]s. 78 /// 79 /// Wasmtime's implementation on some platforms may involve per-thread 80 /// setup that needs to happen whenever WebAssembly is invoked. This setup 81 /// can take on the order of a few hundred microseconds, whereas the 82 /// overhead of calling WebAssembly is otherwise on the order of a few 83 /// nanoseconds. This setup cost is paid once per-OS-thread. If your 84 /// application is sensitive to the latencies of WebAssembly function 85 /// calls, even those that happen first on a thread, then this function 86 /// can be used to improve the consistency of each call into WebAssembly 87 /// by explicitly frontloading the cost of the one-time setup per-thread. 88 /// 89 /// Note that this function is not required to be called in any embedding. 90 /// Wasmtime will automatically initialize thread-local-state as necessary 91 /// on calls into WebAssembly. This is provided for use cases where the 92 /// latency of WebAssembly calls are extra-important, which is not 93 /// necessarily true of all embeddings. 94 pub fn tls_eager_initialize() -> Result<(), Trap> { 95 wasmtime_runtime::tls_eager_initialize().map_err(Trap::from_runtime_box) 96 } 97 98 /// Returns the configuration settings that this engine is using. 99 #[inline] 100 pub fn config(&self) -> &Config { 101 &self.inner.config 102 } 103 104 #[cfg(compiler)] 105 pub(crate) fn compiler(&self) -> &dyn wasmtime_environ::Compiler { 106 &*self.inner.compiler 107 } 108 109 pub(crate) fn allocator(&self) -> &dyn InstanceAllocator { 110 self.inner.allocator.as_ref() 111 } 112 113 #[cfg(feature = "cache")] 114 pub(crate) fn cache_config(&self) -> &CacheConfig { 115 &self.config().cache_config 116 } 117 118 /// Returns whether the engine `a` and `b` refer to the same configuration. 119 pub fn same(a: &Engine, b: &Engine) -> bool { 120 Arc::ptr_eq(&a.inner, &b.inner) 121 } 122 123 pub(crate) fn signatures(&self) -> &SignatureRegistry { 124 &self.inner.signatures 125 } 126 127 pub(crate) fn epoch_counter(&self) -> &AtomicU64 { 128 &self.inner.epoch 129 } 130 131 pub(crate) fn current_epoch(&self) -> u64 { 132 self.epoch_counter().load(Ordering::Relaxed) 133 } 134 135 /// Increments the epoch. 136 /// 137 /// When using epoch-based interruption, currently-executing Wasm 138 /// code within this engine will trap or yield "soon" when the 139 /// epoch deadline is reached or exceeded. (The configuration, and 140 /// the deadline, are set on the `Store`.) The intent of the 141 /// design is for this method to be called by the embedder at some 142 /// regular cadence, for example by a thread that wakes up at some 143 /// interval, or by a signal handler. 144 /// 145 /// See [`Config::epoch_interruption`](crate::Config::epoch_interruption) 146 /// for an introduction to epoch-based interruption and pointers 147 /// to the other relevant methods. 148 /// 149 /// ## Signal Safety 150 /// 151 /// This method is signal-safe: it does not make any syscalls, and 152 /// performs only an atomic increment to the epoch value in 153 /// memory. 154 pub fn increment_epoch(&self) { 155 self.inner.epoch.fetch_add(1, Ordering::Relaxed); 156 } 157 158 pub(crate) fn unique_id_allocator(&self) -> &CompiledModuleIdAllocator { 159 &self.inner.unique_id_allocator 160 } 161 162 /// Ahead-of-time (AOT) compiles a WebAssembly module. 163 /// 164 /// The `bytes` provided must be in one of two formats: 165 /// 166 /// * A [binary-encoded][binary] WebAssembly module. This is always supported. 167 /// * A [text-encoded][text] instance of the WebAssembly text format. 168 /// This is only supported when the `wat` feature of this crate is enabled. 169 /// If this is supplied then the text format will be parsed before validation. 170 /// Note that the `wat` feature is enabled by default. 171 /// 172 /// This method may be used to compile a module for use with a different target 173 /// host. The output of this method may be used with 174 /// [`Module::deserialize`](crate::Module::deserialize) on hosts compatible 175 /// with the [`Config`] associated with this [`Engine`]. 176 /// 177 /// The output of this method is safe to send to another host machine for later 178 /// execution. As the output is already a compiled module, translation and code 179 /// generation will be skipped and this will improve the performance of constructing 180 /// a [`Module`](crate::Module) from the output of this method. 181 /// 182 /// [binary]: https://webassembly.github.io/spec/core/binary/index.html 183 /// [text]: https://webassembly.github.io/spec/core/text/index.html 184 #[cfg(compiler)] 185 #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs 186 pub fn precompile_module(&self, bytes: &[u8]) -> Result<Vec<u8>> { 187 #[cfg(feature = "wat")] 188 let bytes = wat::parse_bytes(&bytes)?; 189 let (_, artifacts, types) = crate::Module::build_artifacts(self, &bytes)?; 190 let artifacts = artifacts.into_iter().map(|i| i.0).collect::<Vec<_>>(); 191 crate::module::SerializedModule::from_artifacts(self, &artifacts, &types) 192 .to_bytes(&self.config().module_version) 193 } 194 195 pub(crate) fn run_maybe_parallel< 196 A: Send, 197 B: Send, 198 E: Send, 199 F: Fn(A) -> Result<B, E> + Send + Sync, 200 >( 201 &self, 202 input: Vec<A>, 203 f: F, 204 ) -> Result<Vec<B>, E> { 205 if self.config().parallel_compilation { 206 #[cfg(feature = "parallel-compilation")] 207 return input 208 .into_par_iter() 209 .map(|a| f(a)) 210 .collect::<Result<Vec<B>, E>>(); 211 } 212 213 // In case the parallel-compilation feature is disabled or the parallel_compilation config 214 // was turned off dynamically fallback to the non-parallel version. 215 input 216 .into_iter() 217 .map(|a| f(a)) 218 .collect::<Result<Vec<B>, E>>() 219 } 220 } 221 222 impl Default for Engine { 223 fn default() -> Engine { 224 Engine::new(&Config::default()).unwrap() 225 } 226 } 227 228 #[cfg(test)] 229 mod tests { 230 use crate::{Config, Engine, Module, OptLevel}; 231 use anyhow::Result; 232 use tempfile::TempDir; 233 234 #[test] 235 fn cache_accounts_for_opt_level() -> Result<()> { 236 let td = TempDir::new()?; 237 let config_path = td.path().join("config.toml"); 238 std::fs::write( 239 &config_path, 240 &format!( 241 " 242 [cache] 243 enabled = true 244 directory = '{}' 245 ", 246 td.path().join("cache").display() 247 ), 248 )?; 249 let mut cfg = Config::new(); 250 cfg.cranelift_opt_level(OptLevel::None) 251 .cache_config_load(&config_path)?; 252 let engine = Engine::new(&cfg)?; 253 Module::new(&engine, "(module (func))")?; 254 assert_eq!(engine.config().cache_config.cache_hits(), 0); 255 assert_eq!(engine.config().cache_config.cache_misses(), 1); 256 Module::new(&engine, "(module (func))")?; 257 assert_eq!(engine.config().cache_config.cache_hits(), 1); 258 assert_eq!(engine.config().cache_config.cache_misses(), 1); 259 260 let mut cfg = Config::new(); 261 cfg.cranelift_opt_level(OptLevel::Speed) 262 .cache_config_load(&config_path)?; 263 let engine = Engine::new(&cfg)?; 264 Module::new(&engine, "(module (func))")?; 265 assert_eq!(engine.config().cache_config.cache_hits(), 0); 266 assert_eq!(engine.config().cache_config.cache_misses(), 1); 267 Module::new(&engine, "(module (func))")?; 268 assert_eq!(engine.config().cache_config.cache_hits(), 1); 269 assert_eq!(engine.config().cache_config.cache_misses(), 1); 270 271 let mut cfg = Config::new(); 272 cfg.cranelift_opt_level(OptLevel::SpeedAndSize) 273 .cache_config_load(&config_path)?; 274 let engine = Engine::new(&cfg)?; 275 Module::new(&engine, "(module (func))")?; 276 assert_eq!(engine.config().cache_config.cache_hits(), 0); 277 assert_eq!(engine.config().cache_config.cache_misses(), 1); 278 Module::new(&engine, "(module (func))")?; 279 assert_eq!(engine.config().cache_config.cache_hits(), 1); 280 assert_eq!(engine.config().cache_config.cache_misses(), 1); 281 282 // FIXME(#1523) need debuginfo on aarch64 before we run this test there 283 if !cfg!(target_arch = "aarch64") { 284 let mut cfg = Config::new(); 285 cfg.debug_info(true).cache_config_load(&config_path)?; 286 let engine = Engine::new(&cfg)?; 287 Module::new(&engine, "(module (func))")?; 288 assert_eq!(engine.config().cache_config.cache_hits(), 0); 289 assert_eq!(engine.config().cache_config.cache_misses(), 1); 290 Module::new(&engine, "(module (func))")?; 291 assert_eq!(engine.config().cache_config.cache_hits(), 1); 292 assert_eq!(engine.config().cache_config.cache_misses(), 1); 293 } 294 295 Ok(()) 296 } 297 } 298