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") {
101                bail!(
102                    "Module was compiled with incompatible Wasmtime version '{}'",
103                    version
104                );
105            }
106        }
107        ModuleVersionStrategy::Custom(v) => {
108            let version = core::str::from_utf8(&version)?;
109            if version != v {
110                bail!(
111                    "Module was compiled with incompatible version '{}'",
112                    version
113                );
114            }
115        }
116        ModuleVersionStrategy::None => { /* ignore the version info, accept all */ }
117    }
118    postcard::from_bytes::<Metadata<'_>>(data)?.check_compatible(engine)
119}
120
121#[cfg(any(feature = "cranelift", feature = "winch"))]
122pub fn append_compiler_info(engine: &Engine, obj: &mut Object<'_>, metadata: &Metadata<'_>) {
123    let section = obj.add_section(
124        obj.segment_name(StandardSegment::Data).to_vec(),
125        obj::ELF_WASM_ENGINE.as_bytes().to_vec(),
126        object::SectionKind::ReadOnlyData,
127    );
128    let mut data = Vec::new();
129    data.push(VERSION);
130    let version = match &engine.config().module_version {
131        ModuleVersionStrategy::WasmtimeVersion => env!("CARGO_PKG_VERSION"),
132        ModuleVersionStrategy::Custom(c) => c,
133        ModuleVersionStrategy::None => "",
134    };
135    // This precondition is checked in Config::module_version:
136    assert!(
137        version.len() < 256,
138        "package version must be less than 256 bytes"
139    );
140    data.push(version.len() as u8);
141    data.extend_from_slice(version.as_bytes());
142    data.extend(postcard::to_allocvec(metadata).unwrap());
143    obj.set_section_data(section, data, 1);
144}
145
146fn detect_precompiled<'data, R: object::ReadRef<'data>>(
147    obj: ElfFile64<'data, Endianness, R>,
148) -> Option<Precompiled> {
149    match obj.flags() {
150        FileFlags::Elf {
151            os_abi: obj::ELFOSABI_WASMTIME,
152            abi_version: 0,
153            e_flags,
154        } if e_flags & obj::EF_WASMTIME_MODULE != 0 => Some(Precompiled::Module),
155        FileFlags::Elf {
156            os_abi: obj::ELFOSABI_WASMTIME,
157            abi_version: 0,
158            e_flags,
159        } if e_flags & obj::EF_WASMTIME_COMPONENT != 0 => Some(Precompiled::Component),
160        _ => None,
161    }
162}
163
164pub fn detect_precompiled_bytes(bytes: &[u8]) -> Option<Precompiled> {
165    detect_precompiled(ElfFile64::parse(bytes).ok()?)
166}
167
168#[cfg(feature = "std")]
169pub fn detect_precompiled_file(path: impl AsRef<std::path::Path>) -> Result<Option<Precompiled>> {
170    let read_cache = object::ReadCache::new(std::fs::File::open(path)?);
171    let obj = ElfFile64::parse(&read_cache)?;
172    Ok(detect_precompiled(obj))
173}
174
175#[derive(Serialize, Deserialize)]
176pub struct Metadata<'a> {
177    target: String,
178    #[serde(borrow)]
179    shared_flags: Vec<(&'a str, FlagValue<'a>)>,
180    #[serde(borrow)]
181    isa_flags: Vec<(&'a str, FlagValue<'a>)>,
182    tunables: Tunables,
183    features: u64,
184}
185
186impl Metadata<'_> {
187    #[cfg(any(feature = "cranelift", feature = "winch"))]
188    pub fn new(engine: &Engine) -> Metadata<'static> {
189        Metadata {
190            target: engine.compiler().triple().to_string(),
191            shared_flags: engine.compiler().flags(),
192            isa_flags: engine.compiler().isa_flags(),
193            tunables: engine.tunables().clone(),
194            features: engine.features().bits(),
195        }
196    }
197
198    fn check_compatible(mut self, engine: &Engine) -> Result<()> {
199        self.check_triple(engine)?;
200        self.check_shared_flags(engine)?;
201        self.check_isa_flags(engine)?;
202        self.check_tunables(&engine.tunables())?;
203        self.check_features(&engine.features())?;
204        Ok(())
205    }
206
207    fn check_triple(&self, engine: &Engine) -> Result<()> {
208        let engine_target = engine.target();
209        let module_target =
210            target_lexicon::Triple::from_str(&self.target).map_err(|e| anyhow!(e))?;
211
212        if module_target.architecture != engine_target.architecture {
213            bail!(
214                "Module was compiled for architecture '{}'",
215                module_target.architecture
216            );
217        }
218
219        if module_target.operating_system != engine_target.operating_system {
220            bail!(
221                "Module was compiled for operating system '{}'",
222                module_target.operating_system
223            );
224        }
225
226        Ok(())
227    }
228
229    fn check_shared_flags(&mut self, engine: &Engine) -> Result<()> {
230        for (name, val) in self.shared_flags.iter() {
231            engine
232                .check_compatible_with_shared_flag(name, val)
233                .map_err(|s| anyhow::Error::msg(s))
234                .context("compilation settings of module incompatible with native host")?;
235        }
236        Ok(())
237    }
238
239    fn check_isa_flags(&mut self, engine: &Engine) -> Result<()> {
240        for (name, val) in self.isa_flags.iter() {
241            engine
242                .check_compatible_with_isa_flag(name, val)
243                .map_err(|s| anyhow::Error::msg(s))
244                .context("compilation settings of module incompatible with native host")?;
245        }
246        Ok(())
247    }
248
249    fn check_int<T: Eq + fmt::Display>(found: T, expected: T, feature: &str) -> Result<()> {
250        if found == expected {
251            return Ok(());
252        }
253
254        bail!(
255            "Module was compiled with a {} of '{}' but '{}' is expected for the host",
256            feature,
257            found,
258            expected
259        );
260    }
261
262    fn check_bool(found: bool, expected: bool, feature: impl fmt::Display) -> Result<()> {
263        if found == expected {
264            return Ok(());
265        }
266
267        bail!(
268            "Module was compiled {} {} but it {} enabled for the host",
269            if found { "with" } else { "without" },
270            feature,
271            if expected { "is" } else { "is not" }
272        );
273    }
274
275    fn check_tunables(&mut self, other: &Tunables) -> Result<()> {
276        let Tunables {
277            collector,
278            memory_reservation,
279            memory_guard_size,
280            generate_native_debuginfo,
281            parse_wasm_debuginfo,
282            consume_fuel,
283            epoch_interruption,
284            memory_may_move,
285            guard_before_linear_memory,
286            table_lazy_init,
287            relaxed_simd_deterministic,
288            winch_callable,
289            signals_based_traps,
290            memory_init_cow,
291            // This doesn't affect compilation, it's just a runtime setting.
292            memory_reservation_for_growth: _,
293
294            // This does technically affect compilation but modules with/without
295            // trap information can be loaded into engines with the opposite
296            // setting just fine (it's just a section in the compiled file and
297            // whether it's present or not)
298            generate_address_map: _,
299
300            // Just a debugging aid, doesn't affect functionality at all.
301            debug_adapter_modules: _,
302        } = self.tunables;
303
304        Self::check_collector(collector, other.collector)?;
305        Self::check_int(
306            memory_reservation,
307            other.memory_reservation,
308            "memory reservation",
309        )?;
310        Self::check_int(
311            memory_guard_size,
312            other.memory_guard_size,
313            "memory guard size",
314        )?;
315        Self::check_bool(
316            generate_native_debuginfo,
317            other.generate_native_debuginfo,
318            "debug information support",
319        )?;
320        Self::check_bool(
321            parse_wasm_debuginfo,
322            other.parse_wasm_debuginfo,
323            "WebAssembly backtrace support",
324        )?;
325        Self::check_bool(consume_fuel, other.consume_fuel, "fuel support")?;
326        Self::check_bool(
327            epoch_interruption,
328            other.epoch_interruption,
329            "epoch interruption",
330        )?;
331        Self::check_bool(memory_may_move, other.memory_may_move, "memory may move")?;
332        Self::check_bool(
333            guard_before_linear_memory,
334            other.guard_before_linear_memory,
335            "guard before linear memory",
336        )?;
337        Self::check_bool(table_lazy_init, other.table_lazy_init, "table lazy init")?;
338        Self::check_bool(
339            relaxed_simd_deterministic,
340            other.relaxed_simd_deterministic,
341            "relaxed simd deterministic semantics",
342        )?;
343        Self::check_bool(
344            winch_callable,
345            other.winch_callable,
346            "Winch calling convention",
347        )?;
348        Self::check_bool(
349            signals_based_traps,
350            other.signals_based_traps,
351            "Signals-based traps",
352        )?;
353        Self::check_bool(
354            memory_init_cow,
355            other.memory_init_cow,
356            "memory initialization with CoW",
357        )?;
358
359        Ok(())
360    }
361
362    fn check_cfg_bool(
363        cfg: bool,
364        cfg_str: &str,
365        found: bool,
366        expected: bool,
367        feature: impl fmt::Display,
368    ) -> Result<()> {
369        if cfg {
370            Self::check_bool(found, expected, feature)
371        } else {
372            assert!(!expected);
373            ensure!(
374                !found,
375                "Module was compiled with {feature} but support in the host \
376                 was disabled at compile time because the `{cfg_str}` Cargo \
377                 feature was not enabled",
378            );
379            Ok(())
380        }
381    }
382
383    fn check_features(&mut self, other: &wasmparser::WasmFeatures) -> Result<()> {
384        let module_features = wasmparser::WasmFeatures::from_bits_truncate(self.features);
385        let difference = *other ^ module_features;
386        for (name, flag) in difference.iter_names() {
387            let found = module_features.contains(flag);
388            let expected = other.contains(flag);
389            // Give a slightly more specialized error message for the `GC_TYPES`
390            // feature which isn't actually part of wasm itself but is gated by
391            // compile-time crate features.
392            if flag == wasmparser::WasmFeatures::GC_TYPES {
393                Self::check_cfg_bool(
394                    cfg!(feature = "gc"),
395                    "gc",
396                    found,
397                    expected,
398                    WasmFeature(name),
399                )?;
400            } else {
401                Self::check_bool(found, expected, WasmFeature(name))?;
402            }
403        }
404
405        return Ok(());
406
407        struct WasmFeature<'a>(&'a str);
408
409        impl fmt::Display for WasmFeature<'_> {
410            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
411                write!(f, "support for WebAssembly feature `")?;
412                for c in self.0.chars().map(|c| c.to_lowercase()) {
413                    write!(f, "{c}")?;
414                }
415                write!(f, "`")?;
416                Ok(())
417            }
418        }
419    }
420
421    fn check_collector(
422        module: Option<wasmtime_environ::Collector>,
423        host: Option<wasmtime_environ::Collector>,
424    ) -> Result<()> {
425        match (module, host) {
426            (None, None) => Ok(()),
427            (Some(module), Some(host)) if module == host => Ok(()),
428
429            (None, Some(_)) => {
430                bail!("module was compiled without GC but GC is enabled in the host")
431            }
432            (Some(_), None) => {
433                bail!("module was compiled with GC however GC is disabled in the host")
434            }
435
436            (Some(module), Some(host)) => {
437                bail!(
438                    "module was compiled for the {module} collector but \
439                     the host is configured to use the {host} collector",
440                )
441            }
442        }
443    }
444}
445
446#[cfg(test)]
447mod test {
448    use super::*;
449    use crate::{Cache, Config, Module, OptLevel};
450    use std::{
451        collections::hash_map::DefaultHasher,
452        hash::{Hash, Hasher},
453    };
454    use tempfile::TempDir;
455
456    #[test]
457    fn test_architecture_mismatch() -> Result<()> {
458        let engine = Engine::default();
459        let mut metadata = Metadata::new(&engine);
460        metadata.target = "unknown-generic-linux".to_string();
461
462        match metadata.check_compatible(&engine) {
463            Ok(_) => unreachable!(),
464            Err(e) => assert_eq!(
465                e.to_string(),
466                "Module was compiled for architecture 'unknown'",
467            ),
468        }
469
470        Ok(())
471    }
472
473    #[test]
474    #[cfg(target_arch = "x86_64")] // test on a platform that is known to use
475    // Cranelift
476    fn test_os_mismatch() -> Result<()> {
477        let engine = Engine::default();
478        let mut metadata = Metadata::new(&engine);
479
480        metadata.target = format!(
481            "{}-generic-unknown",
482            target_lexicon::Triple::host().architecture
483        );
484
485        match metadata.check_compatible(&engine) {
486            Ok(_) => unreachable!(),
487            Err(e) => assert_eq!(
488                e.to_string(),
489                "Module was compiled for operating system 'unknown'",
490            ),
491        }
492
493        Ok(())
494    }
495
496    #[test]
497    fn test_cranelift_flags_mismatch() -> Result<()> {
498        let engine = Engine::default();
499        let mut metadata = Metadata::new(&engine);
500
501        metadata
502            .shared_flags
503            .push(("preserve_frame_pointers", FlagValue::Bool(false)));
504
505        match metadata.check_compatible(&engine) {
506            Ok(_) => unreachable!(),
507            Err(e) => assert!(format!("{e:?}").starts_with(
508                "\
509compilation settings of module incompatible with native host
510
511Caused by:
512    setting \"preserve_frame_pointers\" is configured to Bool(false) which is not supported"
513            )),
514        }
515
516        Ok(())
517    }
518
519    #[test]
520    fn test_isa_flags_mismatch() -> Result<()> {
521        let engine = Engine::default();
522        let mut metadata = Metadata::new(&engine);
523
524        metadata
525            .isa_flags
526            .push(("not_a_flag", FlagValue::Bool(true)));
527
528        match metadata.check_compatible(&engine) {
529            Ok(_) => unreachable!(),
530            Err(e) => assert!(
531                format!("{e:?}").starts_with(
532                    "\
533compilation settings of module incompatible with native host
534
535Caused by:
536    don't know how to test for target-specific flag \"not_a_flag\" at runtime",
537                ),
538                "bad error {e:?}",
539            ),
540        }
541
542        Ok(())
543    }
544
545    #[test]
546    #[cfg_attr(miri, ignore)]
547    #[cfg(target_pointer_width = "64")] // different defaults on 32-bit platforms
548    fn test_tunables_int_mismatch() -> Result<()> {
549        let engine = Engine::default();
550        let mut metadata = Metadata::new(&engine);
551
552        metadata.tunables.memory_guard_size = 0;
553
554        match metadata.check_compatible(&engine) {
555            Ok(_) => unreachable!(),
556            Err(e) => assert_eq!(
557                e.to_string(),
558                "Module was compiled with a memory guard size of '0' but '33554432' is expected for the host"
559            ),
560        }
561
562        Ok(())
563    }
564
565    #[test]
566    fn test_tunables_bool_mismatch() -> Result<()> {
567        let mut config = Config::new();
568        config.epoch_interruption(true);
569
570        let engine = Engine::new(&config)?;
571        let mut metadata = Metadata::new(&engine);
572        metadata.tunables.epoch_interruption = false;
573
574        match metadata.check_compatible(&engine) {
575            Ok(_) => unreachable!(),
576            Err(e) => assert_eq!(
577                e.to_string(),
578                "Module was compiled without epoch interruption but it is enabled for the host"
579            ),
580        }
581
582        let mut config = Config::new();
583        config.epoch_interruption(false);
584
585        let engine = Engine::new(&config)?;
586        let mut metadata = Metadata::new(&engine);
587        metadata.tunables.epoch_interruption = true;
588
589        match metadata.check_compatible(&engine) {
590            Ok(_) => unreachable!(),
591            Err(e) => assert_eq!(
592                e.to_string(),
593                "Module was compiled with epoch interruption but it is not enabled for the host"
594            ),
595        }
596
597        Ok(())
598    }
599
600    #[test]
601    #[cfg(target_arch = "x86_64")] // test on a platform that is known to
602    // implement threads
603    fn test_feature_mismatch() -> Result<()> {
604        let mut config = Config::new();
605        config.wasm_threads(true);
606
607        let engine = Engine::new(&config)?;
608        let mut metadata = Metadata::new(&engine);
609        metadata.features &= !wasmparser::WasmFeatures::THREADS.bits();
610
611        match metadata.check_compatible(&engine) {
612            Ok(_) => unreachable!(),
613            Err(e) => assert_eq!(
614                e.to_string(),
615                "Module was compiled without support for WebAssembly feature \
616                 `threads` but it is enabled for the host"
617            ),
618        }
619
620        let mut config = Config::new();
621        config.wasm_threads(false);
622
623        let engine = Engine::new(&config)?;
624        let mut metadata = Metadata::new(&engine);
625        metadata.features |= wasmparser::WasmFeatures::THREADS.bits();
626
627        match metadata.check_compatible(&engine) {
628            Ok(_) => unreachable!(),
629            Err(e) => assert_eq!(
630                e.to_string(),
631                "Module was compiled with support for WebAssembly feature \
632                `threads` but it is not enabled for the host"
633            ),
634        }
635
636        Ok(())
637    }
638
639    #[test]
640    fn engine_weak_upgrades() {
641        let engine = Engine::default();
642        let weak = engine.weak();
643        weak.upgrade()
644            .expect("engine is still alive, so weak reference can upgrade");
645        drop(engine);
646        assert!(
647            weak.upgrade().is_none(),
648            "engine was dropped, so weak reference cannot upgrade"
649        );
650    }
651
652    #[test]
653    #[cfg_attr(miri, ignore)]
654    fn cache_accounts_for_opt_level() -> Result<()> {
655        let td = TempDir::new()?;
656        let config_path = td.path().join("config.toml");
657        std::fs::write(
658            &config_path,
659            &format!(
660                "
661                    [cache]
662                    directory = '{}'
663                ",
664                td.path().join("cache").display()
665            ),
666        )?;
667        let mut cfg = Config::new();
668        cfg.cranelift_opt_level(OptLevel::None)
669            .cache(Some(Cache::from_file(Some(&config_path))?));
670        let engine = Engine::new(&cfg)?;
671        Module::new(&engine, "(module (func))")?;
672        let cache_config = engine
673            .config()
674            .cache
675            .as_ref()
676            .expect("Missing cache config");
677        assert_eq!(cache_config.cache_hits(), 0);
678        assert_eq!(cache_config.cache_misses(), 1);
679        Module::new(&engine, "(module (func))")?;
680        assert_eq!(cache_config.cache_hits(), 1);
681        assert_eq!(cache_config.cache_misses(), 1);
682
683        let mut cfg = Config::new();
684        cfg.cranelift_opt_level(OptLevel::Speed)
685            .cache(Some(Cache::from_file(Some(&config_path))?));
686        let engine = Engine::new(&cfg)?;
687        let cache_config = engine
688            .config()
689            .cache
690            .as_ref()
691            .expect("Missing cache config");
692        Module::new(&engine, "(module (func))")?;
693        assert_eq!(cache_config.cache_hits(), 0);
694        assert_eq!(cache_config.cache_misses(), 1);
695        Module::new(&engine, "(module (func))")?;
696        assert_eq!(cache_config.cache_hits(), 1);
697        assert_eq!(cache_config.cache_misses(), 1);
698
699        let mut cfg = Config::new();
700        cfg.cranelift_opt_level(OptLevel::SpeedAndSize)
701            .cache(Some(Cache::from_file(Some(&config_path))?));
702        let engine = Engine::new(&cfg)?;
703        let cache_config = engine
704            .config()
705            .cache
706            .as_ref()
707            .expect("Missing cache config");
708        Module::new(&engine, "(module (func))")?;
709        assert_eq!(cache_config.cache_hits(), 0);
710        assert_eq!(cache_config.cache_misses(), 1);
711        Module::new(&engine, "(module (func))")?;
712        assert_eq!(cache_config.cache_hits(), 1);
713        assert_eq!(cache_config.cache_misses(), 1);
714
715        let mut cfg = Config::new();
716        cfg.debug_info(true)
717            .cache(Some(Cache::from_file(Some(&config_path))?));
718        let engine = Engine::new(&cfg)?;
719        let cache_config = engine
720            .config()
721            .cache
722            .as_ref()
723            .expect("Missing cache config");
724        Module::new(&engine, "(module (func))")?;
725        assert_eq!(cache_config.cache_hits(), 0);
726        assert_eq!(cache_config.cache_misses(), 1);
727        Module::new(&engine, "(module (func))")?;
728        assert_eq!(cache_config.cache_hits(), 1);
729        assert_eq!(cache_config.cache_misses(), 1);
730
731        Ok(())
732    }
733
734    #[test]
735    fn precompile_compatibility_key_accounts_for_opt_level() {
736        fn hash_for_config(cfg: &Config) -> u64 {
737            let engine = Engine::new(cfg).expect("Config should be valid");
738            let mut hasher = DefaultHasher::new();
739            engine.precompile_compatibility_hash().hash(&mut hasher);
740            hasher.finish()
741        }
742        let mut cfg = Config::new();
743        cfg.cranelift_opt_level(OptLevel::None);
744        let opt_none_hash = hash_for_config(&cfg);
745        cfg.cranelift_opt_level(OptLevel::Speed);
746        let opt_speed_hash = hash_for_config(&cfg);
747        assert_ne!(opt_none_hash, opt_speed_hash)
748    }
749
750    #[test]
751    fn precompile_compatibility_key_accounts_for_module_version_strategy() -> Result<()> {
752        fn hash_for_config(cfg: &Config) -> u64 {
753            let engine = Engine::new(cfg).expect("Config should be valid");
754            let mut hasher = DefaultHasher::new();
755            engine.precompile_compatibility_hash().hash(&mut hasher);
756            hasher.finish()
757        }
758        let mut cfg_custom_version = Config::new();
759        cfg_custom_version.module_version(ModuleVersionStrategy::Custom("1.0.1111".to_string()))?;
760        let custom_version_hash = hash_for_config(&cfg_custom_version);
761
762        let mut cfg_default_version = Config::new();
763        cfg_default_version.module_version(ModuleVersionStrategy::WasmtimeVersion)?;
764        let default_version_hash = hash_for_config(&cfg_default_version);
765
766        let mut cfg_none_version = Config::new();
767        cfg_none_version.module_version(ModuleVersionStrategy::None)?;
768        let none_version_hash = hash_for_config(&cfg_none_version);
769
770        assert_ne!(custom_version_hash, default_version_hash);
771        assert_ne!(custom_version_hash, none_version_hash);
772        assert_ne!(default_version_hash, none_version_hash);
773
774        Ok(())
775    }
776
777    #[test]
778    #[cfg_attr(miri, ignore)]
779    #[cfg(feature = "component-model")]
780    fn components_are_cached() -> Result<()> {
781        use crate::component::Component;
782
783        let td = TempDir::new()?;
784        let config_path = td.path().join("config.toml");
785        std::fs::write(
786            &config_path,
787            &format!(
788                "
789                    [cache]
790                    directory = '{}'
791                ",
792                td.path().join("cache").display()
793            ),
794        )?;
795        let mut cfg = Config::new();
796        cfg.cache(Some(Cache::from_file(Some(&config_path))?));
797        let engine = Engine::new(&cfg)?;
798        let cache_config = engine
799            .config()
800            .cache
801            .as_ref()
802            .expect("Missing cache config");
803        Component::new(&engine, "(component (core module (func)))")?;
804        assert_eq!(cache_config.cache_hits(), 0);
805        assert_eq!(cache_config.cache_misses(), 1);
806        Component::new(&engine, "(component (core module (func)))")?;
807        assert_eq!(cache_config.cache_hits(), 1);
808        assert_eq!(cache_config.cache_misses(), 1);
809
810        Ok(())
811    }
812}