wasmtime/
engine.rs

1use crate::signatures::SignatureRegistry;
2use crate::Config;
3use anyhow::{Context, Result};
4use object::write::{Object, StandardSegment};
5use object::SectionKind;
6use once_cell::sync::OnceCell;
7#[cfg(feature = "parallel-compilation")]
8use rayon::prelude::*;
9use std::path::Path;
10use std::sync::atomic::{AtomicU64, Ordering};
11use std::sync::Arc;
12#[cfg(feature = "cache")]
13use wasmtime_cache::CacheConfig;
14use wasmtime_environ::obj;
15use wasmtime_environ::{FlagValue, ObjectKind};
16use wasmtime_jit::{CodeMemory, ProfilingAgent};
17use wasmtime_runtime::{debug_builtins, CompiledModuleIdAllocator, InstanceAllocator, MmapVec};
18
19mod serialization;
20
21/// An `Engine` which is a global context for compilation and management of wasm
22/// modules.
23///
24/// An engine can be safely shared across threads and is a cheap cloneable
25/// handle to the actual engine. The engine itself will be deallocated once all
26/// references to it have gone away.
27///
28/// Engines store global configuration preferences such as compilation settings,
29/// enabled features, etc. You'll likely only need at most one of these for a
30/// program.
31///
32/// ## Engines and `Clone`
33///
34/// Using `clone` on an `Engine` is a cheap operation. It will not create an
35/// entirely new engine, but rather just a new reference to the existing engine.
36/// In other words it's a shallow copy, not a deep copy.
37///
38/// ## Engines and `Default`
39///
40/// You can create an engine with default configuration settings using
41/// `Engine::default()`. Be sure to consult the documentation of [`Config`] for
42/// default settings.
43#[derive(Clone)]
44pub struct Engine {
45    inner: Arc<EngineInner>,
46}
47
48struct EngineInner {
49    config: Config,
50    #[cfg(compiler)]
51    compiler: Box<dyn wasmtime_environ::Compiler>,
52    allocator: Box<dyn InstanceAllocator + Send + Sync>,
53    profiler: Box<dyn ProfilingAgent>,
54    signatures: SignatureRegistry,
55    epoch: AtomicU64,
56    unique_id_allocator: CompiledModuleIdAllocator,
57
58    // One-time check of whether the compiler's settings, if present, are
59    // compatible with the native host.
60    compatible_with_native_host: OnceCell<Result<(), String>>,
61}
62
63impl Engine {
64    /// Creates a new [`Engine`] with the specified compilation and
65    /// configuration settings.
66    ///
67    /// # Errors
68    ///
69    /// This method can fail if the `config` is invalid or some
70    /// configurations are incompatible.
71    ///
72    /// For example, feature `reference_types` will need to set
73    /// the compiler setting `enable_safepoints` and `unwind_info`
74    /// to `true`, but explicitly disable these two compiler settings
75    /// will cause errors.
76    pub fn new(config: &Config) -> Result<Engine> {
77        // Ensure that wasmtime_runtime's signal handlers are configured. This
78        // is the per-program initialization required for handling traps, such
79        // as configuring signals, vectored exception handlers, etc.
80        wasmtime_runtime::init_traps(crate::module::is_wasm_trap_pc);
81        debug_builtins::ensure_exported();
82
83        let registry = SignatureRegistry::new();
84        let mut config = config.clone();
85        config.validate()?;
86
87        #[cfg(compiler)]
88        let compiler = config.build_compiler()?;
89        drop(&mut config); // silence warnings without `cfg(compiler)`
90
91        let allocator = config.build_allocator()?;
92        let profiler = config.build_profiler()?;
93
94        Ok(Engine {
95            inner: Arc::new(EngineInner {
96                #[cfg(compiler)]
97                compiler,
98                config,
99                allocator,
100                profiler,
101                signatures: registry,
102                epoch: AtomicU64::new(0),
103                unique_id_allocator: CompiledModuleIdAllocator::new(),
104                compatible_with_native_host: OnceCell::new(),
105            }),
106        })
107    }
108
109    /// Eagerly initialize thread-local functionality shared by all [`Engine`]s.
110    ///
111    /// Wasmtime's implementation on some platforms may involve per-thread
112    /// setup that needs to happen whenever WebAssembly is invoked. This setup
113    /// can take on the order of a few hundred microseconds, whereas the
114    /// overhead of calling WebAssembly is otherwise on the order of a few
115    /// nanoseconds. This setup cost is paid once per-OS-thread. If your
116    /// application is sensitive to the latencies of WebAssembly function
117    /// calls, even those that happen first on a thread, then this function
118    /// can be used to improve the consistency of each call into WebAssembly
119    /// by explicitly frontloading the cost of the one-time setup per-thread.
120    ///
121    /// Note that this function is not required to be called in any embedding.
122    /// Wasmtime will automatically initialize thread-local-state as necessary
123    /// on calls into WebAssembly. This is provided for use cases where the
124    /// latency of WebAssembly calls are extra-important, which is not
125    /// necessarily true of all embeddings.
126    pub fn tls_eager_initialize() {
127        wasmtime_runtime::tls_eager_initialize();
128    }
129
130    /// Returns the configuration settings that this engine is using.
131    #[inline]
132    pub fn config(&self) -> &Config {
133        &self.inner.config
134    }
135
136    #[cfg(compiler)]
137    pub(crate) fn compiler(&self) -> &dyn wasmtime_environ::Compiler {
138        &*self.inner.compiler
139    }
140
141    pub(crate) fn allocator(&self) -> &dyn InstanceAllocator {
142        self.inner.allocator.as_ref()
143    }
144
145    pub(crate) fn profiler(&self) -> &dyn ProfilingAgent {
146        self.inner.profiler.as_ref()
147    }
148
149    #[cfg(feature = "cache")]
150    pub(crate) fn cache_config(&self) -> &CacheConfig {
151        &self.config().cache_config
152    }
153
154    /// Returns whether the engine `a` and `b` refer to the same configuration.
155    pub fn same(a: &Engine, b: &Engine) -> bool {
156        Arc::ptr_eq(&a.inner, &b.inner)
157    }
158
159    pub(crate) fn signatures(&self) -> &SignatureRegistry {
160        &self.inner.signatures
161    }
162
163    pub(crate) fn epoch_counter(&self) -> &AtomicU64 {
164        &self.inner.epoch
165    }
166
167    pub(crate) fn current_epoch(&self) -> u64 {
168        self.epoch_counter().load(Ordering::Relaxed)
169    }
170
171    /// Increments the epoch.
172    ///
173    /// When using epoch-based interruption, currently-executing Wasm
174    /// code within this engine will trap or yield "soon" when the
175    /// epoch deadline is reached or exceeded. (The configuration, and
176    /// the deadline, are set on the `Store`.) The intent of the
177    /// design is for this method to be called by the embedder at some
178    /// regular cadence, for example by a thread that wakes up at some
179    /// interval, or by a signal handler.
180    ///
181    /// See [`Config::epoch_interruption`](crate::Config::epoch_interruption)
182    /// for an introduction to epoch-based interruption and pointers
183    /// to the other relevant methods.
184    ///
185    /// ## Signal Safety
186    ///
187    /// This method is signal-safe: it does not make any syscalls, and
188    /// performs only an atomic increment to the epoch value in
189    /// memory.
190    pub fn increment_epoch(&self) {
191        self.inner.epoch.fetch_add(1, Ordering::Relaxed);
192    }
193
194    pub(crate) fn unique_id_allocator(&self) -> &CompiledModuleIdAllocator {
195        &self.inner.unique_id_allocator
196    }
197
198    /// Ahead-of-time (AOT) compiles a WebAssembly module.
199    ///
200    /// The `bytes` provided must be in one of two formats:
201    ///
202    /// * A [binary-encoded][binary] WebAssembly module. This is always supported.
203    /// * A [text-encoded][text] instance of the WebAssembly text format.
204    ///   This is only supported when the `wat` feature of this crate is enabled.
205    ///   If this is supplied then the text format will be parsed before validation.
206    ///   Note that the `wat` feature is enabled by default.
207    ///
208    /// This method may be used to compile a module for use with a different target
209    /// host. The output of this method may be used with
210    /// [`Module::deserialize`](crate::Module::deserialize) on hosts compatible
211    /// with the [`Config`] associated with this [`Engine`].
212    ///
213    /// The output of this method is safe to send to another host machine for later
214    /// execution. As the output is already a compiled module, translation and code
215    /// generation will be skipped and this will improve the performance of constructing
216    /// a [`Module`](crate::Module) from the output of this method.
217    ///
218    /// [binary]: https://webassembly.github.io/spec/core/binary/index.html
219    /// [text]: https://webassembly.github.io/spec/core/text/index.html
220    #[cfg(compiler)]
221    #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs
222    pub fn precompile_module(&self, bytes: &[u8]) -> Result<Vec<u8>> {
223        #[cfg(feature = "wat")]
224        let bytes = wat::parse_bytes(&bytes)?;
225        let (mmap, _) = crate::Module::build_artifacts(self, &bytes)?;
226        Ok(mmap.to_vec())
227    }
228
229    /// Same as [`Engine::precompile_module`] except for a
230    /// [`Component`](crate::component::Component)
231    #[cfg(compiler)]
232    #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs
233    #[cfg(feature = "component-model")]
234    #[cfg_attr(nightlydoc, doc(cfg(feature = "component-model")))]
235    pub fn precompile_component(&self, bytes: &[u8]) -> Result<Vec<u8>> {
236        #[cfg(feature = "wat")]
237        let bytes = wat::parse_bytes(&bytes)?;
238        let (mmap, _) = crate::component::Component::build_artifacts(self, &bytes)?;
239        Ok(mmap.to_vec())
240    }
241
242    pub(crate) fn run_maybe_parallel<
243        A: Send,
244        B: Send,
245        E: Send,
246        F: Fn(A) -> Result<B, E> + Send + Sync,
247    >(
248        &self,
249        input: Vec<A>,
250        f: F,
251    ) -> Result<Vec<B>, E> {
252        if self.config().parallel_compilation {
253            #[cfg(feature = "parallel-compilation")]
254            return input
255                .into_par_iter()
256                .map(|a| f(a))
257                .collect::<Result<Vec<B>, E>>();
258        }
259
260        // In case the parallel-compilation feature is disabled or the parallel_compilation config
261        // was turned off dynamically fallback to the non-parallel version.
262        input
263            .into_iter()
264            .map(|a| f(a))
265            .collect::<Result<Vec<B>, E>>()
266    }
267
268    /// Executes `f1` and `f2` in parallel if parallel compilation is enabled at
269    /// both runtime and compile time, otherwise runs them synchronously.
270    #[allow(dead_code)] // only used for the component-model feature right now
271    pub(crate) fn join_maybe_parallel<T, U>(
272        &self,
273        f1: impl FnOnce() -> T + Send,
274        f2: impl FnOnce() -> U + Send,
275    ) -> (T, U)
276    where
277        T: Send,
278        U: Send,
279    {
280        if self.config().parallel_compilation {
281            #[cfg(feature = "parallel-compilation")]
282            return rayon::join(f1, f2);
283        }
284        (f1(), f2())
285    }
286
287    /// Returns the target triple which this engine is compiling code for
288    /// and/or running code for.
289    pub(crate) fn target(&self) -> target_lexicon::Triple {
290        // If a compiler is configured, use that target.
291        #[cfg(compiler)]
292        return self.compiler().triple().clone();
293
294        // ... otherwise it's the native target
295        #[cfg(not(compiler))]
296        return target_lexicon::Triple::host();
297    }
298
299    /// Verify that this engine's configuration is compatible with loading
300    /// modules onto the native host platform.
301    ///
302    /// This method is used as part of `Module::new` to ensure that this
303    /// engine can indeed load modules for the configured compiler (if any).
304    /// Note that if cranelift is disabled this trivially returns `Ok` because
305    /// loaded serialized modules are checked separately.
306    pub(crate) fn check_compatible_with_native_host(&self) -> Result<()> {
307        self.inner
308            .compatible_with_native_host
309            .get_or_init(|| self._check_compatible_with_native_host())
310            .clone()
311            .map_err(anyhow::Error::msg)
312    }
313
314    fn _check_compatible_with_native_host(&self) -> Result<(), String> {
315        #[cfg(compiler)]
316        {
317            let compiler = self.compiler();
318
319            // Check to see that the config's target matches the host
320            let target = compiler.triple();
321            if *target != target_lexicon::Triple::host() {
322                return Err(format!(
323                    "target '{}' specified in the configuration does not match the host",
324                    target
325                ));
326            }
327
328            // Also double-check all compiler settings
329            for (key, value) in compiler.flags().iter() {
330                self.check_compatible_with_shared_flag(key, value)?;
331            }
332            for (key, value) in compiler.isa_flags().iter() {
333                self.check_compatible_with_isa_flag(key, value)?;
334            }
335        }
336        Ok(())
337    }
338
339    /// Checks to see whether the "shared flag", something enabled for
340    /// individual compilers, is compatible with the native host platform.
341    ///
342    /// This is used both when validating an engine's compilation settings are
343    /// compatible with the host as well as when deserializing modules from
344    /// disk to ensure they're compatible with the current host.
345    ///
346    /// Note that most of the settings here are not configured by users that
347    /// often. While theoretically possible via `Config` methods the more
348    /// interesting flags are the ISA ones below. Typically the values here
349    /// represent global configuration for wasm features. Settings here
350    /// currently rely on the compiler informing us of all settings, including
351    /// those disabled. Settings then fall in a few buckets:
352    ///
353    /// * Some settings must be enabled, such as `preserve_frame_pointers`.
354    /// * Some settings must have a particular value, such as
355    ///   `libcall_call_conv`.
356    /// * Some settings do not matter as to their value, such as `opt_level`.
357    pub(crate) fn check_compatible_with_shared_flag(
358        &self,
359        flag: &str,
360        value: &FlagValue,
361    ) -> Result<(), String> {
362        let target = self.target();
363        let ok = match flag {
364            // These settings must all have be enabled, since their value
365            // can affect the way the generated code performs or behaves at
366            // runtime.
367            "libcall_call_conv" => *value == FlagValue::Enum("isa_default".into()),
368            "preserve_frame_pointers" => *value == FlagValue::Bool(true),
369            "enable_probestack" => *value == FlagValue::Bool(crate::config::probestack_supported(target.architecture)),
370            "probestack_strategy" => *value == FlagValue::Enum("inline".into()),
371
372            // Features wasmtime doesn't use should all be disabled, since
373            // otherwise if they are enabled it could change the behavior of
374            // generated code.
375            "enable_llvm_abi_extensions" => *value == FlagValue::Bool(false),
376            "enable_pinned_reg" => *value == FlagValue::Bool(false),
377            "use_colocated_libcalls" => *value == FlagValue::Bool(false),
378            "use_pinned_reg_as_heap_base" => *value == FlagValue::Bool(false),
379
380            // If reference types are enabled this must be enabled, otherwise
381            // this setting can have any value.
382            "enable_safepoints" => {
383                if self.config().features.reference_types {
384                    *value == FlagValue::Bool(true)
385                } else {
386                    return Ok(())
387                }
388            }
389
390            // Windows requires unwind info as part of its ABI.
391            "unwind_info" => {
392                if target.operating_system == target_lexicon::OperatingSystem::Windows {
393                    *value == FlagValue::Bool(true)
394                } else {
395                    return Ok(())
396                }
397            }
398
399            // These settings don't affect the interface or functionality of
400            // the module itself, so their configuration values shouldn't
401            // matter.
402            "enable_heap_access_spectre_mitigation"
403            | "enable_table_access_spectre_mitigation"
404            | "enable_nan_canonicalization"
405            | "enable_jump_tables"
406            | "enable_float"
407            | "enable_simd"
408            | "enable_verifier"
409            | "regalloc_checker"
410            | "regalloc_verbose_logs"
411            | "is_pic"
412            | "machine_code_cfg_info"
413            | "tls_model" // wasmtime doesn't use tls right now
414            | "opt_level" // opt level doesn't change semantics
415            | "use_egraphs" // optimizing with egraphs doesn't change semantics
416            | "enable_alias_analysis" // alias analysis-based opts don't change semantics
417            | "probestack_func_adjusts_sp" // probestack above asserted disabled
418            | "probestack_size_log2" // probestack above asserted disabled
419            | "regalloc" // shouldn't change semantics
420            | "enable_incremental_compilation_cache_checks" // shouldn't change semantics
421            | "enable_atomics" => return Ok(()),
422
423            // Everything else is unknown and needs to be added somewhere to
424            // this list if encountered.
425            _ => {
426                return Err(format!("unknown shared setting {:?} configured to {:?}", flag, value))
427            }
428        };
429
430        if !ok {
431            return Err(format!(
432                "setting {:?} is configured to {:?} which is not supported",
433                flag, value,
434            ));
435        }
436        Ok(())
437    }
438
439    /// Same as `check_compatible_with_native_host` except used for ISA-specific
440    /// flags. This is used to test whether a configured ISA flag is indeed
441    /// available on the host platform itself.
442    pub(crate) fn check_compatible_with_isa_flag(
443        &self,
444        flag: &str,
445        value: &FlagValue,
446    ) -> Result<(), String> {
447        match value {
448            // ISA flags are used for things like CPU features, so if they're
449            // disabled then it's compatible with the native host.
450            FlagValue::Bool(false) => return Ok(()),
451
452            // Fall through below where we test at runtime that features are
453            // available.
454            FlagValue::Bool(true) => {}
455
456            // Only `bool` values are supported right now, other settings would
457            // need more support here.
458            _ => {
459                return Err(format!(
460                    "isa-specific feature {:?} configured to unknown value {:?}",
461                    flag, value
462                ))
463            }
464        }
465
466        #[allow(unused_assignments)]
467        let mut enabled = None;
468
469        #[cfg(target_arch = "aarch64")]
470        {
471            enabled = match flag {
472                "has_lse" => Some(std::arch::is_aarch64_feature_detected!("lse")),
473                // No effect on its own, but in order to simplify the code on a
474                // platform without pointer authentication support we fail if
475                // "has_pauth" is enabled, but "sign_return_address" is not.
476                "has_pauth" => Some(std::arch::is_aarch64_feature_detected!("paca")),
477                // No effect on its own.
478                "sign_return_address_all" => Some(true),
479                // The pointer authentication instructions act as a `NOP` when
480                // unsupported (but keep in mind "has_pauth" as well), so it is
481                // safe to enable them.
482                "sign_return_address" => Some(true),
483                // No effect on its own.
484                "sign_return_address_with_bkey" => Some(true),
485                // The `BTI` instruction acts as a `NOP` when unsupported, so it
486                // is safe to enable it.
487                "use_bti" => Some(true),
488                // fall through to the very bottom to indicate that support is
489                // not enabled to test whether this feature is enabled on the
490                // host.
491                _ => None,
492            };
493        }
494
495        // There is no is_s390x_feature_detected macro yet, so for now
496        // we use getauxval from the libc crate directly.
497        #[cfg(all(target_arch = "s390x", target_os = "linux"))]
498        {
499            let v = unsafe { libc::getauxval(libc::AT_HWCAP) };
500            const HWCAP_S390X_VXRS_EXT2: libc::c_ulong = 32768;
501
502            enabled = match flag {
503                // There is no separate HWCAP bit for mie2, so assume
504                // that any machine with vxrs_ext2 also has mie2.
505                "has_vxrs_ext2" | "has_mie2" => Some((v & HWCAP_S390X_VXRS_EXT2) != 0),
506                // fall through to the very bottom to indicate that support is
507                // not enabled to test whether this feature is enabled on the
508                // host.
509                _ => None,
510            }
511        }
512
513        #[cfg(target_arch = "riscv64")]
514        {
515            enabled = match flag {
516                // make sure `test_isa_flags_mismatch` test pass.
517                "not_a_flag" => None,
518                // due to `is_riscv64_feature_detected` is not stable.
519                // we cannot use it.
520                _ => Some(true),
521            }
522        }
523
524        #[cfg(target_arch = "x86_64")]
525        {
526            enabled = match flag {
527                "has_sse3" => Some(std::is_x86_feature_detected!("sse3")),
528                "has_ssse3" => Some(std::is_x86_feature_detected!("ssse3")),
529                "has_sse41" => Some(std::is_x86_feature_detected!("sse4.1")),
530                "has_sse42" => Some(std::is_x86_feature_detected!("sse4.2")),
531                "has_popcnt" => Some(std::is_x86_feature_detected!("popcnt")),
532                "has_avx" => Some(std::is_x86_feature_detected!("avx")),
533                "has_avx2" => Some(std::is_x86_feature_detected!("avx2")),
534                "has_fma" => Some(std::is_x86_feature_detected!("fma")),
535                "has_bmi1" => Some(std::is_x86_feature_detected!("bmi1")),
536                "has_bmi2" => Some(std::is_x86_feature_detected!("bmi2")),
537                "has_avx512bitalg" => Some(std::is_x86_feature_detected!("avx512bitalg")),
538                "has_avx512dq" => Some(std::is_x86_feature_detected!("avx512dq")),
539                "has_avx512f" => Some(std::is_x86_feature_detected!("avx512f")),
540                "has_avx512vl" => Some(std::is_x86_feature_detected!("avx512vl")),
541                "has_avx512vbmi" => Some(std::is_x86_feature_detected!("avx512vbmi")),
542                "has_lzcnt" => Some(std::is_x86_feature_detected!("lzcnt")),
543
544                // fall through to the very bottom to indicate that support is
545                // not enabled to test whether this feature is enabled on the
546                // host.
547                _ => None,
548            };
549        }
550
551        match enabled {
552            Some(true) => return Ok(()),
553            Some(false) => {
554                return Err(format!(
555                    "compilation setting {:?} is enabled, but not available on the host",
556                    flag
557                ))
558            }
559            // fall through
560            None => {}
561        }
562
563        Err(format!(
564            "cannot test if target-specific flag {:?} is available at runtime",
565            flag
566        ))
567    }
568
569    #[cfg(compiler)]
570    pub(crate) fn append_compiler_info(&self, obj: &mut Object<'_>) {
571        serialization::append_compiler_info(self, obj);
572    }
573
574    #[cfg(compiler)]
575    pub(crate) fn append_bti(&self, obj: &mut Object<'_>) {
576        let section = obj.add_section(
577            obj.segment_name(StandardSegment::Data).to_vec(),
578            obj::ELF_WASM_BTI.as_bytes().to_vec(),
579            SectionKind::ReadOnlyData,
580        );
581        let contents = if self.compiler().is_branch_protection_enabled() {
582            1
583        } else {
584            0
585        };
586        obj.append_section_data(section, &[contents], 1);
587    }
588
589    /// Loads a `CodeMemory` from the specified in-memory slice, copying it to a
590    /// uniquely owned mmap.
591    ///
592    /// The `expected` marker here is whether the bytes are expected to be a
593    /// precompiled module or a component.
594    pub(crate) fn load_code_bytes(
595        &self,
596        bytes: &[u8],
597        expected: ObjectKind,
598    ) -> Result<Arc<CodeMemory>> {
599        self.load_code(MmapVec::from_slice(bytes)?, expected)
600    }
601
602    /// Like `load_code_bytes`, but creates a mmap from a file on disk.
603    pub(crate) fn load_code_file(
604        &self,
605        path: &Path,
606        expected: ObjectKind,
607    ) -> Result<Arc<CodeMemory>> {
608        self.load_code(
609            MmapVec::from_file(path).with_context(|| {
610                format!("failed to create file mapping for: {}", path.display())
611            })?,
612            expected,
613        )
614    }
615
616    pub(crate) fn load_code(&self, mmap: MmapVec, expected: ObjectKind) -> Result<Arc<CodeMemory>> {
617        serialization::check_compatible(self, &mmap, expected)?;
618        let mut code = CodeMemory::new(mmap)?;
619        code.publish()?;
620        Ok(Arc::new(code))
621    }
622}
623
624impl Default for Engine {
625    fn default() -> Engine {
626        Engine::new(&Config::default()).unwrap()
627    }
628}
629
630#[cfg(test)]
631mod tests {
632    use crate::{Config, Engine, Module, OptLevel};
633
634    use anyhow::Result;
635    use tempfile::TempDir;
636
637    #[test]
638    fn cache_accounts_for_opt_level() -> Result<()> {
639        let td = TempDir::new()?;
640        let config_path = td.path().join("config.toml");
641        std::fs::write(
642            &config_path,
643            &format!(
644                "
645                    [cache]
646                    enabled = true
647                    directory = '{}'
648                ",
649                td.path().join("cache").display()
650            ),
651        )?;
652        let mut cfg = Config::new();
653        cfg.cranelift_opt_level(OptLevel::None)
654            .cache_config_load(&config_path)?;
655        let engine = Engine::new(&cfg)?;
656        Module::new(&engine, "(module (func))")?;
657        assert_eq!(engine.config().cache_config.cache_hits(), 0);
658        assert_eq!(engine.config().cache_config.cache_misses(), 1);
659        Module::new(&engine, "(module (func))")?;
660        assert_eq!(engine.config().cache_config.cache_hits(), 1);
661        assert_eq!(engine.config().cache_config.cache_misses(), 1);
662
663        let mut cfg = Config::new();
664        cfg.cranelift_opt_level(OptLevel::Speed)
665            .cache_config_load(&config_path)?;
666        let engine = Engine::new(&cfg)?;
667        Module::new(&engine, "(module (func))")?;
668        assert_eq!(engine.config().cache_config.cache_hits(), 0);
669        assert_eq!(engine.config().cache_config.cache_misses(), 1);
670        Module::new(&engine, "(module (func))")?;
671        assert_eq!(engine.config().cache_config.cache_hits(), 1);
672        assert_eq!(engine.config().cache_config.cache_misses(), 1);
673
674        let mut cfg = Config::new();
675        cfg.cranelift_opt_level(OptLevel::SpeedAndSize)
676            .cache_config_load(&config_path)?;
677        let engine = Engine::new(&cfg)?;
678        Module::new(&engine, "(module (func))")?;
679        assert_eq!(engine.config().cache_config.cache_hits(), 0);
680        assert_eq!(engine.config().cache_config.cache_misses(), 1);
681        Module::new(&engine, "(module (func))")?;
682        assert_eq!(engine.config().cache_config.cache_hits(), 1);
683        assert_eq!(engine.config().cache_config.cache_misses(), 1);
684
685        let mut cfg = Config::new();
686        cfg.debug_info(true).cache_config_load(&config_path)?;
687        let engine = Engine::new(&cfg)?;
688        Module::new(&engine, "(module (func))")?;
689        assert_eq!(engine.config().cache_config.cache_hits(), 0);
690        assert_eq!(engine.config().cache_config.cache_misses(), 1);
691        Module::new(&engine, "(module (func))")?;
692        assert_eq!(engine.config().cache_config.cache_hits(), 1);
693        assert_eq!(engine.config().cache_config.cache_misses(), 1);
694
695        Ok(())
696    }
697}