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