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