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