wasmtime/engine/
serialization.rs

1//! This module implements serialization and deserialization of `Engine`
2//! configuration data which is embedded into compiled artifacts of Wasmtime.
3//!
4//! The data serialized here is used to double-check that when a module is
5//! loaded from one host onto another that it's compatible with the target host.
6//! Additionally though this data is the first data read from a precompiled
7//! artifact so it's "extra hardened" to provide reasonable-ish error messages
8//! for mismatching wasmtime versions. Once something successfully deserializes
9//! here it's assumed it's meant for this wasmtime so error messages are in
10//! general much worse afterwards.
11//!
12//! Wasmtime AOT artifacts are ELF files so the data for the engine here is
13//! stored into a section of the output file. The structure of this section is:
14//!
15//! 1. A version byte, currently `VERSION`.
16//! 2. A byte indicating how long the next field is.
17//! 3. A version string of the length of the previous byte value.
18//! 4. A `postcard`-encoded `Metadata` structure.
19//!
20//! This is hoped to help distinguish easily Wasmtime-based ELF files from
21//! other random ELF files, as well as provide better error messages for
22//! using wasmtime artifacts across versions.
23
24use crate::prelude::*;
25use crate::{Engine, ModuleVersionStrategy, Precompiled};
26use core::fmt;
27use core::str::FromStr;
28use object::endian::Endianness;
29#[cfg(any(feature = "cranelift", feature = "winch"))]
30use object::write::{Object, StandardSegment};
31use object::{FileFlags, Object as _, ObjectSection, read::elf::ElfFile64};
32use serde_derive::{Deserialize, Serialize};
33use wasmtime_environ::obj;
34use wasmtime_environ::{FlagValue, ObjectKind, Tunables};
35
36const VERSION: u8 = 0;
37
38/// Verifies that the serialized engine in `mmap` is compatible with the
39/// `engine` provided.
40///
41/// This function will verify that the `mmap` provided can be deserialized
42/// successfully and that the contents are all compatible with the `engine`
43/// provided here, notably compatible wasm features are enabled, compatible
44/// compiler options, etc. If a mismatch is found and the compilation metadata
45/// specified is incompatible then an error is returned.
46pub fn check_compatible(engine: &Engine, mmap: &[u8], expected: ObjectKind) -> Result<()> {
47    // Parse the input `mmap` as an ELF file and see if the header matches the
48    // Wasmtime-generated header. This includes a Wasmtime-specific `os_abi` and
49    // the `e_flags` field should indicate whether `expected` matches or not.
50    //
51    // Note that errors generated here could mean that a precompiled module was
52    // loaded as a component, or vice versa, both of which aren't supposed to
53    // work.
54    //
55    // Ideally we'd only `File::parse` once and avoid the linear
56    // `section_by_name` search here but the general serialization code isn't
57    // structured well enough to make this easy and additionally it's not really
58    // a perf issue right now so doing that is left for another day's
59    // refactoring.
60    let obj = ElfFile64::<Endianness>::parse(mmap)
61        .map_err(obj::ObjectCrateErrorWrapper)
62        .context("failed to parse precompiled artifact as an ELF")?;
63    let expected_e_flags = match expected {
64        ObjectKind::Module => obj::EF_WASMTIME_MODULE,
65        ObjectKind::Component => obj::EF_WASMTIME_COMPONENT,
66    };
67    match obj.flags() {
68        FileFlags::Elf {
69            os_abi: obj::ELFOSABI_WASMTIME,
70            abi_version: 0,
71            e_flags,
72        } if e_flags & expected_e_flags == expected_e_flags => {}
73        _ => bail!("incompatible object file format"),
74    }
75
76    let data = obj
77        .section_by_name(obj::ELF_WASM_ENGINE)
78        .ok_or_else(|| anyhow!("failed to find section `{}`", obj::ELF_WASM_ENGINE))?
79        .data()
80        .map_err(obj::ObjectCrateErrorWrapper)?;
81    let (first, data) = data
82        .split_first()
83        .ok_or_else(|| anyhow!("invalid engine section"))?;
84    if *first != VERSION {
85        bail!("mismatched version in engine section");
86    }
87    let (len, data) = data
88        .split_first()
89        .ok_or_else(|| anyhow!("invalid engine section"))?;
90    let len = usize::from(*len);
91    let (version, data) = if data.len() < len + 1 {
92        bail!("engine section too small")
93    } else {
94        data.split_at(len)
95    };
96
97    match &engine.config().module_version {
98        ModuleVersionStrategy::WasmtimeVersion => {
99            let version = core::str::from_utf8(version)?;
100            if version != env!("CARGO_PKG_VERSION_MAJOR") {
101                bail!("Module was compiled with incompatible Wasmtime version '{version}'");
102            }
103        }
104        ModuleVersionStrategy::Custom(v) => {
105            let version = core::str::from_utf8(&version)?;
106            if version != v {
107                bail!("Module was compiled with incompatible version '{version}'");
108            }
109        }
110        ModuleVersionStrategy::None => { /* ignore the version info, accept all */ }
111    }
112    postcard::from_bytes::<Metadata<'_>>(data)?.check_compatible(engine)
113}
114
115#[cfg(any(feature = "cranelift", feature = "winch"))]
116pub fn append_compiler_info(engine: &Engine, obj: &mut Object<'_>, metadata: &Metadata<'_>) {
117    let section = obj.add_section(
118        obj.segment_name(StandardSegment::Data).to_vec(),
119        obj::ELF_WASM_ENGINE.as_bytes().to_vec(),
120        object::SectionKind::ReadOnlyData,
121    );
122    let mut data = Vec::new();
123    data.push(VERSION);
124    let version = match &engine.config().module_version {
125        ModuleVersionStrategy::WasmtimeVersion => env!("CARGO_PKG_VERSION_MAJOR"),
126        ModuleVersionStrategy::Custom(c) => c,
127        ModuleVersionStrategy::None => "",
128    };
129    // This precondition is checked in Config::module_version:
130    assert!(
131        version.len() < 256,
132        "package version must be less than 256 bytes"
133    );
134    data.push(version.len() as u8);
135    data.extend_from_slice(version.as_bytes());
136    data.extend(postcard::to_allocvec(metadata).unwrap());
137    obj.set_section_data(section, data, 1);
138}
139
140fn detect_precompiled<'data, R: object::ReadRef<'data>>(
141    obj: ElfFile64<'data, Endianness, R>,
142) -> Option<Precompiled> {
143    match obj.flags() {
144        FileFlags::Elf {
145            os_abi: obj::ELFOSABI_WASMTIME,
146            abi_version: 0,
147            e_flags,
148        } if e_flags & obj::EF_WASMTIME_MODULE != 0 => Some(Precompiled::Module),
149        FileFlags::Elf {
150            os_abi: obj::ELFOSABI_WASMTIME,
151            abi_version: 0,
152            e_flags,
153        } if e_flags & obj::EF_WASMTIME_COMPONENT != 0 => Some(Precompiled::Component),
154        _ => None,
155    }
156}
157
158pub fn detect_precompiled_bytes(bytes: &[u8]) -> Option<Precompiled> {
159    detect_precompiled(ElfFile64::parse(bytes).ok()?)
160}
161
162#[cfg(feature = "std")]
163pub fn detect_precompiled_file(path: impl AsRef<std::path::Path>) -> Result<Option<Precompiled>> {
164    let read_cache = object::ReadCache::new(std::fs::File::open(path)?);
165    let obj = ElfFile64::parse(&read_cache)?;
166    Ok(detect_precompiled(obj))
167}
168
169#[derive(Serialize, Deserialize)]
170pub struct Metadata<'a> {
171    target: String,
172    #[serde(borrow)]
173    shared_flags: Vec<(&'a str, FlagValue<'a>)>,
174    #[serde(borrow)]
175    isa_flags: Vec<(&'a str, FlagValue<'a>)>,
176    tunables: Tunables,
177    features: u64,
178}
179
180impl Metadata<'_> {
181    #[cfg(any(feature = "cranelift", feature = "winch"))]
182    pub fn new(engine: &Engine) -> Result<Metadata<'static>> {
183        let compiler = engine.try_compiler()?;
184        Ok(Metadata {
185            target: compiler.triple().to_string(),
186            shared_flags: compiler.flags(),
187            isa_flags: compiler.isa_flags(),
188            tunables: engine.tunables().clone(),
189            features: engine.features().bits(),
190        })
191    }
192
193    fn check_compatible(mut self, engine: &Engine) -> Result<()> {
194        self.check_triple(engine)?;
195        self.check_shared_flags(engine)?;
196        self.check_isa_flags(engine)?;
197        self.check_tunables(&engine.tunables())?;
198        self.check_features(&engine.features())?;
199        Ok(())
200    }
201
202    fn check_triple(&self, engine: &Engine) -> Result<()> {
203        let engine_target = engine.target();
204        let module_target =
205            target_lexicon::Triple::from_str(&self.target).map_err(|e| anyhow!(e))?;
206
207        if module_target.architecture != engine_target.architecture {
208            bail!(
209                "Module was compiled for architecture '{}'",
210                module_target.architecture
211            );
212        }
213
214        if module_target.operating_system != engine_target.operating_system {
215            bail!(
216                "Module was compiled for operating system '{}'",
217                module_target.operating_system
218            );
219        }
220
221        Ok(())
222    }
223
224    fn check_shared_flags(&mut self, engine: &Engine) -> Result<()> {
225        for (name, val) in self.shared_flags.iter() {
226            engine
227                .check_compatible_with_shared_flag(name, val)
228                .map_err(|s| anyhow::Error::msg(s))
229                .context("compilation settings of module incompatible with native host")?;
230        }
231        Ok(())
232    }
233
234    fn check_isa_flags(&mut self, engine: &Engine) -> Result<()> {
235        for (name, val) in self.isa_flags.iter() {
236            engine
237                .check_compatible_with_isa_flag(name, val)
238                .map_err(|s| anyhow::Error::msg(s))
239                .context("compilation settings of module incompatible with native host")?;
240        }
241        Ok(())
242    }
243
244    fn check_int<T: Eq + fmt::Display>(found: T, expected: T, feature: &str) -> Result<()> {
245        if found == expected {
246            return Ok(());
247        }
248
249        bail!(
250            "Module was compiled with a {feature} of '{found}' but '{expected}' is expected for the host"
251        );
252    }
253
254    fn check_bool(found: bool, expected: bool, feature: impl fmt::Display) -> Result<()> {
255        if found == expected {
256            return Ok(());
257        }
258
259        bail!(
260            "Module was compiled {} {} but it {} enabled for the host",
261            if found { "with" } else { "without" },
262            feature,
263            if expected { "is" } else { "is not" }
264        );
265    }
266
267    fn check_tunables(&mut self, other: &Tunables) -> Result<()> {
268        let Tunables {
269            collector,
270            memory_reservation,
271            memory_guard_size,
272            debug_native,
273            debug_guest,
274            parse_wasm_debuginfo,
275            consume_fuel,
276            epoch_interruption,
277            memory_may_move,
278            guard_before_linear_memory,
279            table_lazy_init,
280            relaxed_simd_deterministic,
281            winch_callable,
282            signals_based_traps,
283            memory_init_cow,
284            inlining,
285            inlining_intra_module,
286            inlining_small_callee_size,
287            inlining_sum_size_threshold,
288
289            // This doesn't affect compilation, it's just a runtime setting.
290            memory_reservation_for_growth: _,
291
292            // This does technically affect compilation but modules with/without
293            // trap information can be loaded into engines with the opposite
294            // setting just fine (it's just a section in the compiled file and
295            // whether it's present or not)
296            generate_address_map: _,
297
298            // Just a debugging aid, doesn't affect functionality at all.
299            debug_adapter_modules: _,
300        } = self.tunables;
301
302        Self::check_collector(collector, other.collector)?;
303        Self::check_int(
304            memory_reservation,
305            other.memory_reservation,
306            "memory reservation",
307        )?;
308        Self::check_int(
309            memory_guard_size,
310            other.memory_guard_size,
311            "memory guard size",
312        )?;
313        Self::check_bool(
314            debug_native,
315            other.debug_native,
316            "native debug information support",
317        )?;
318        Self::check_bool(debug_guest, other.debug_guest, "guest debug")?;
319        Self::check_bool(
320            parse_wasm_debuginfo,
321            other.parse_wasm_debuginfo,
322            "WebAssembly backtrace support",
323        )?;
324        Self::check_bool(consume_fuel, other.consume_fuel, "fuel support")?;
325        Self::check_bool(
326            epoch_interruption,
327            other.epoch_interruption,
328            "epoch interruption",
329        )?;
330        Self::check_bool(memory_may_move, other.memory_may_move, "memory may move")?;
331        Self::check_bool(
332            guard_before_linear_memory,
333            other.guard_before_linear_memory,
334            "guard before linear memory",
335        )?;
336        Self::check_bool(table_lazy_init, other.table_lazy_init, "table lazy init")?;
337        Self::check_bool(
338            relaxed_simd_deterministic,
339            other.relaxed_simd_deterministic,
340            "relaxed simd deterministic semantics",
341        )?;
342        Self::check_bool(
343            winch_callable,
344            other.winch_callable,
345            "Winch calling convention",
346        )?;
347        Self::check_bool(
348            signals_based_traps,
349            other.signals_based_traps,
350            "Signals-based traps",
351        )?;
352        Self::check_bool(
353            memory_init_cow,
354            other.memory_init_cow,
355            "memory initialization with CoW",
356        )?;
357        Self::check_bool(inlining, other.inlining, "function inlining")?;
358        Self::check_int(
359            inlining_small_callee_size,
360            other.inlining_small_callee_size,
361            "function inlining small-callee size",
362        )?;
363        Self::check_int(
364            inlining_sum_size_threshold,
365            other.inlining_sum_size_threshold,
366            "function inlining sum-size threshold",
367        )?;
368        Self::check_intra_module_inlining(inlining_intra_module, other.inlining_intra_module)?;
369
370        Ok(())
371    }
372
373    fn check_features(&mut self, other: &wasmparser::WasmFeatures) -> Result<()> {
374        let module_features = wasmparser::WasmFeatures::from_bits_truncate(self.features);
375        let missing_features = (*other & module_features) ^ module_features;
376        for (name, _) in missing_features.iter_names() {
377            let name = name.to_ascii_lowercase();
378            bail!(
379                "Module was compiled with support for WebAssembly feature \
380                `{name}` but it is not enabled for the host",
381            );
382        }
383        Ok(())
384    }
385
386    fn check_collector(
387        module: Option<wasmtime_environ::Collector>,
388        host: Option<wasmtime_environ::Collector>,
389    ) -> Result<()> {
390        match (module, host) {
391            // If the module doesn't require GC support it doesn't matter
392            // whether the host has GC support enabled or not.
393            (None, _) => Ok(()),
394            (Some(module), Some(host)) if module == host => Ok(()),
395
396            (Some(_), None) => {
397                bail!("module was compiled with GC however GC is disabled in the host")
398            }
399
400            (Some(module), Some(host)) => {
401                bail!(
402                    "module was compiled for the {module} collector but \
403                     the host is configured to use the {host} collector",
404                )
405            }
406        }
407    }
408
409    fn check_intra_module_inlining(
410        module: wasmtime_environ::IntraModuleInlining,
411        host: wasmtime_environ::IntraModuleInlining,
412    ) -> Result<()> {
413        if module == host {
414            return Ok(());
415        }
416
417        let desc = |cfg| match cfg {
418            wasmtime_environ::IntraModuleInlining::No => "without intra-module inlining",
419            wasmtime_environ::IntraModuleInlining::Yes => "with intra-module inlining",
420            wasmtime_environ::IntraModuleInlining::WhenUsingGc => {
421                "with intra-module inlining only when using GC"
422            }
423        };
424
425        let module = desc(module);
426        let host = desc(host);
427
428        bail!("module was compiled {module} however the host is configured {host}")
429    }
430}
431
432#[cfg(test)]
433mod test {
434    use super::*;
435    use crate::{Cache, Config, Module, OptLevel};
436    use std::{
437        collections::hash_map::DefaultHasher,
438        hash::{Hash, Hasher},
439    };
440    use tempfile::TempDir;
441
442    #[test]
443    fn test_architecture_mismatch() -> Result<()> {
444        let engine = Engine::default();
445        let mut metadata = Metadata::new(&engine)?;
446        metadata.target = "unknown-generic-linux".to_string();
447
448        match metadata.check_compatible(&engine) {
449            Ok(_) => unreachable!(),
450            Err(e) => assert_eq!(
451                e.to_string(),
452                "Module was compiled for architecture 'unknown'",
453            ),
454        }
455
456        Ok(())
457    }
458
459    // Note that this test runs on a platform that is known to use Cranelift
460    #[test]
461    #[cfg(all(target_arch = "x86_64", not(miri)))]
462    fn test_os_mismatch() -> Result<()> {
463        let engine = Engine::default();
464        let mut metadata = Metadata::new(&engine)?;
465
466        metadata.target = format!(
467            "{}-generic-unknown",
468            target_lexicon::Triple::host().architecture
469        );
470
471        match metadata.check_compatible(&engine) {
472            Ok(_) => unreachable!(),
473            Err(e) => assert_eq!(
474                e.to_string(),
475                "Module was compiled for operating system 'unknown'",
476            ),
477        }
478
479        Ok(())
480    }
481
482    #[test]
483    fn test_cranelift_flags_mismatch() -> Result<()> {
484        let engine = Engine::default();
485        let mut metadata = Metadata::new(&engine)?;
486
487        metadata
488            .shared_flags
489            .push(("preserve_frame_pointers", FlagValue::Bool(false)));
490
491        match metadata.check_compatible(&engine) {
492            Ok(_) => unreachable!(),
493            Err(e) => assert!(format!("{e:?}").starts_with(
494                "\
495compilation settings of module incompatible with native host
496
497Caused by:
498    setting \"preserve_frame_pointers\" is configured to Bool(false) which is not supported"
499            )),
500        }
501
502        Ok(())
503    }
504
505    #[test]
506    fn test_isa_flags_mismatch() -> Result<()> {
507        let engine = Engine::default();
508        let mut metadata = Metadata::new(&engine)?;
509
510        metadata
511            .isa_flags
512            .push(("not_a_flag", FlagValue::Bool(true)));
513
514        match metadata.check_compatible(&engine) {
515            Ok(_) => unreachable!(),
516            Err(e) => assert!(
517                format!("{e:?}").starts_with(
518                    "\
519compilation settings of module incompatible with native host
520
521Caused by:
522    don't know how to test for target-specific flag \"not_a_flag\" at runtime",
523                ),
524                "bad error {e:?}",
525            ),
526        }
527
528        Ok(())
529    }
530
531    #[test]
532    #[cfg_attr(miri, ignore)]
533    #[cfg(target_pointer_width = "64")] // different defaults on 32-bit platforms
534    fn test_tunables_int_mismatch() -> Result<()> {
535        let engine = Engine::default();
536        let mut metadata = Metadata::new(&engine)?;
537
538        metadata.tunables.memory_guard_size = 0;
539
540        match metadata.check_compatible(&engine) {
541            Ok(_) => unreachable!(),
542            Err(e) => assert_eq!(
543                e.to_string(),
544                "Module was compiled with a memory guard size of '0' but '33554432' is expected for the host"
545            ),
546        }
547
548        Ok(())
549    }
550
551    #[test]
552    fn test_tunables_bool_mismatch() -> Result<()> {
553        let mut config = Config::new();
554        config.epoch_interruption(true);
555
556        let engine = Engine::new(&config)?;
557        let mut metadata = Metadata::new(&engine)?;
558        metadata.tunables.epoch_interruption = false;
559
560        match metadata.check_compatible(&engine) {
561            Ok(_) => unreachable!(),
562            Err(e) => assert_eq!(
563                e.to_string(),
564                "Module was compiled without epoch interruption but it is enabled for the host"
565            ),
566        }
567
568        let mut config = Config::new();
569        config.epoch_interruption(false);
570
571        let engine = Engine::new(&config)?;
572        let mut metadata = Metadata::new(&engine)?;
573        metadata.tunables.epoch_interruption = true;
574
575        match metadata.check_compatible(&engine) {
576            Ok(_) => unreachable!(),
577            Err(e) => assert_eq!(
578                e.to_string(),
579                "Module was compiled with epoch interruption but it is not enabled for the host"
580            ),
581        }
582
583        Ok(())
584    }
585
586    /// This test is only run a platform that is known to implement threads
587    #[test]
588    #[cfg(all(target_arch = "x86_64", not(miri)))]
589    fn test_feature_mismatch() -> Result<()> {
590        let mut config = Config::new();
591        config.wasm_threads(true);
592
593        let engine = Engine::new(&config)?;
594        let mut metadata = Metadata::new(&engine)?;
595        metadata.features &= !wasmparser::WasmFeatures::THREADS.bits();
596
597        // If a feature is disabled in the module and enabled in the host,
598        // that's always ok.
599        metadata.check_compatible(&engine)?;
600
601        let mut config = Config::new();
602        config.wasm_threads(false);
603
604        let engine = Engine::new(&config)?;
605        let mut metadata = Metadata::new(&engine)?;
606        metadata.features |= wasmparser::WasmFeatures::THREADS.bits();
607
608        match metadata.check_compatible(&engine) {
609            Ok(_) => unreachable!(),
610            Err(e) => assert_eq!(
611                e.to_string(),
612                "Module was compiled with support for WebAssembly feature \
613                `threads` but it is not enabled for the host"
614            ),
615        }
616
617        Ok(())
618    }
619
620    #[test]
621    fn engine_weak_upgrades() {
622        let engine = Engine::default();
623        let weak = engine.weak();
624        weak.upgrade()
625            .expect("engine is still alive, so weak reference can upgrade");
626        drop(engine);
627        assert!(
628            weak.upgrade().is_none(),
629            "engine was dropped, so weak reference cannot upgrade"
630        );
631    }
632
633    #[test]
634    #[cfg_attr(miri, ignore)]
635    fn cache_accounts_for_opt_level() -> Result<()> {
636        let _ = env_logger::try_init();
637
638        let td = TempDir::new()?;
639        let config_path = td.path().join("config.toml");
640        std::fs::write(
641            &config_path,
642            &format!(
643                "
644                    [cache]
645                    directory = '{}'
646                ",
647                td.path().join("cache").display()
648            ),
649        )?;
650        let mut cfg = Config::new();
651        cfg.cranelift_opt_level(OptLevel::None)
652            .cache(Some(Cache::from_file(Some(&config_path))?));
653        let engine = Engine::new(&cfg)?;
654        Module::new(&engine, "(module (func))")?;
655        let cache_config = engine
656            .config()
657            .cache
658            .as_ref()
659            .expect("Missing cache config");
660        assert_eq!(cache_config.cache_hits(), 0);
661        assert_eq!(cache_config.cache_misses(), 1);
662        Module::new(&engine, "(module (func))")?;
663        assert_eq!(cache_config.cache_hits(), 1);
664        assert_eq!(cache_config.cache_misses(), 1);
665
666        let mut cfg = Config::new();
667        cfg.cranelift_opt_level(OptLevel::Speed)
668            .cache(Some(Cache::from_file(Some(&config_path))?));
669        let engine = Engine::new(&cfg)?;
670        let cache_config = engine
671            .config()
672            .cache
673            .as_ref()
674            .expect("Missing cache config");
675        Module::new(&engine, "(module (func))")?;
676        assert_eq!(cache_config.cache_hits(), 0);
677        assert_eq!(cache_config.cache_misses(), 1);
678        Module::new(&engine, "(module (func))")?;
679        assert_eq!(cache_config.cache_hits(), 1);
680        assert_eq!(cache_config.cache_misses(), 1);
681
682        let mut cfg = Config::new();
683        cfg.cranelift_opt_level(OptLevel::SpeedAndSize)
684            .cache(Some(Cache::from_file(Some(&config_path))?));
685        let engine = Engine::new(&cfg)?;
686        let cache_config = engine
687            .config()
688            .cache
689            .as_ref()
690            .expect("Missing cache config");
691        Module::new(&engine, "(module (func))")?;
692        assert_eq!(cache_config.cache_hits(), 0);
693        assert_eq!(cache_config.cache_misses(), 1);
694        Module::new(&engine, "(module (func))")?;
695        assert_eq!(cache_config.cache_hits(), 1);
696        assert_eq!(cache_config.cache_misses(), 1);
697
698        let mut cfg = Config::new();
699        cfg.debug_info(true)
700            .cache(Some(Cache::from_file(Some(&config_path))?));
701        let engine = Engine::new(&cfg)?;
702        let cache_config = engine
703            .config()
704            .cache
705            .as_ref()
706            .expect("Missing cache config");
707        Module::new(&engine, "(module (func))")?;
708        assert_eq!(cache_config.cache_hits(), 0);
709        assert_eq!(cache_config.cache_misses(), 1);
710        Module::new(&engine, "(module (func))")?;
711        assert_eq!(cache_config.cache_hits(), 1);
712        assert_eq!(cache_config.cache_misses(), 1);
713
714        Ok(())
715    }
716
717    #[test]
718    fn precompile_compatibility_key_accounts_for_opt_level() {
719        fn hash_for_config(cfg: &Config) -> u64 {
720            let engine = Engine::new(cfg).expect("Config should be valid");
721            let mut hasher = DefaultHasher::new();
722            engine.precompile_compatibility_hash().hash(&mut hasher);
723            hasher.finish()
724        }
725        let mut cfg = Config::new();
726        cfg.cranelift_opt_level(OptLevel::None);
727        let opt_none_hash = hash_for_config(&cfg);
728        cfg.cranelift_opt_level(OptLevel::Speed);
729        let opt_speed_hash = hash_for_config(&cfg);
730        assert_ne!(opt_none_hash, opt_speed_hash)
731    }
732
733    #[test]
734    fn precompile_compatibility_key_accounts_for_module_version_strategy() -> Result<()> {
735        fn hash_for_config(cfg: &Config) -> u64 {
736            let engine = Engine::new(cfg).expect("Config should be valid");
737            let mut hasher = DefaultHasher::new();
738            engine.precompile_compatibility_hash().hash(&mut hasher);
739            hasher.finish()
740        }
741        let mut cfg_custom_version = Config::new();
742        cfg_custom_version.module_version(ModuleVersionStrategy::Custom("1.0.1111".to_string()))?;
743        let custom_version_hash = hash_for_config(&cfg_custom_version);
744
745        let mut cfg_default_version = Config::new();
746        cfg_default_version.module_version(ModuleVersionStrategy::WasmtimeVersion)?;
747        let default_version_hash = hash_for_config(&cfg_default_version);
748
749        let mut cfg_none_version = Config::new();
750        cfg_none_version.module_version(ModuleVersionStrategy::None)?;
751        let none_version_hash = hash_for_config(&cfg_none_version);
752
753        assert_ne!(custom_version_hash, default_version_hash);
754        assert_ne!(custom_version_hash, none_version_hash);
755        assert_ne!(default_version_hash, none_version_hash);
756
757        Ok(())
758    }
759
760    #[test]
761    #[cfg_attr(miri, ignore)]
762    #[cfg(feature = "component-model")]
763    fn components_are_cached() -> Result<()> {
764        use crate::component::Component;
765
766        let td = TempDir::new()?;
767        let config_path = td.path().join("config.toml");
768        std::fs::write(
769            &config_path,
770            &format!(
771                "
772                    [cache]
773                    directory = '{}'
774                ",
775                td.path().join("cache").display()
776            ),
777        )?;
778        let mut cfg = Config::new();
779        cfg.cache(Some(Cache::from_file(Some(&config_path))?));
780        let engine = Engine::new(&cfg)?;
781        let cache_config = engine
782            .config()
783            .cache
784            .as_ref()
785            .expect("Missing cache config");
786        Component::new(&engine, "(component (core module (func)))")?;
787        assert_eq!(cache_config.cache_hits(), 0);
788        assert_eq!(cache_config.cache_misses(), 1);
789        Component::new(&engine, "(component (core module (func)))")?;
790        assert_eq!(cache_config.cache_hits(), 1);
791        assert_eq!(cache_config.cache_misses(), 1);
792
793        Ok(())
794    }
795}