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 
24 use crate::prelude::*;
25 use crate::{Engine, ModuleVersionStrategy, Precompiled};
26 use core::fmt;
27 use core::str::FromStr;
28 use object::endian::Endianness;
29 #[cfg(any(feature = "cranelift", feature = "winch"))]
30 use object::write::{Object, StandardSegment};
31 use object::{FileFlags, Object as _, ObjectSection, read::elf::ElfFile64};
32 use serde_derive::{Deserialize, Serialize};
33 use wasmtime_environ::obj;
34 use wasmtime_environ::{FlagValue, ObjectKind, Tunables};
35 
36 const 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.
46 pub 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"))]
122 pub 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 
146 fn 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 
164 pub fn detect_precompiled_bytes(bytes: &[u8]) -> Option<Precompiled> {
165     detect_precompiled(ElfFile64::parse(bytes).ok()?)
166 }
167 
168 #[cfg(feature = "std")]
169 pub 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)]
176 pub 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 
186 impl 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             inlining,
292             inlining_intra_module,
293             inlining_small_callee_size,
294             inlining_sum_size_threshold,
295 
296             // This doesn't affect compilation, it's just a runtime setting.
297             memory_reservation_for_growth: _,
298 
299             // This does technically affect compilation but modules with/without
300             // trap information can be loaded into engines with the opposite
301             // setting just fine (it's just a section in the compiled file and
302             // whether it's present or not)
303             generate_address_map: _,
304 
305             // Just a debugging aid, doesn't affect functionality at all.
306             debug_adapter_modules: _,
307         } = self.tunables;
308 
309         Self::check_collector(collector, other.collector)?;
310         Self::check_int(
311             memory_reservation,
312             other.memory_reservation,
313             "memory reservation",
314         )?;
315         Self::check_int(
316             memory_guard_size,
317             other.memory_guard_size,
318             "memory guard size",
319         )?;
320         Self::check_bool(
321             generate_native_debuginfo,
322             other.generate_native_debuginfo,
323             "debug information support",
324         )?;
325         Self::check_bool(
326             parse_wasm_debuginfo,
327             other.parse_wasm_debuginfo,
328             "WebAssembly backtrace support",
329         )?;
330         Self::check_bool(consume_fuel, other.consume_fuel, "fuel support")?;
331         Self::check_bool(
332             epoch_interruption,
333             other.epoch_interruption,
334             "epoch interruption",
335         )?;
336         Self::check_bool(memory_may_move, other.memory_may_move, "memory may move")?;
337         Self::check_bool(
338             guard_before_linear_memory,
339             other.guard_before_linear_memory,
340             "guard before linear memory",
341         )?;
342         Self::check_bool(table_lazy_init, other.table_lazy_init, "table lazy init")?;
343         Self::check_bool(
344             relaxed_simd_deterministic,
345             other.relaxed_simd_deterministic,
346             "relaxed simd deterministic semantics",
347         )?;
348         Self::check_bool(
349             winch_callable,
350             other.winch_callable,
351             "Winch calling convention",
352         )?;
353         Self::check_bool(
354             signals_based_traps,
355             other.signals_based_traps,
356             "Signals-based traps",
357         )?;
358         Self::check_bool(
359             memory_init_cow,
360             other.memory_init_cow,
361             "memory initialization with CoW",
362         )?;
363         Self::check_bool(inlining, other.inlining, "function inlining")?;
364         Self::check_int(
365             inlining_small_callee_size,
366             other.inlining_small_callee_size,
367             "function inlining small-callee size",
368         )?;
369         Self::check_int(
370             inlining_sum_size_threshold,
371             other.inlining_sum_size_threshold,
372             "function inlining sum-size threshold",
373         )?;
374         Self::check_intra_module_inlining(inlining_intra_module, other.inlining_intra_module)?;
375 
376         Ok(())
377     }
378 
379     fn check_cfg_bool(
380         cfg: bool,
381         cfg_str: &str,
382         found: bool,
383         expected: bool,
384         feature: impl fmt::Display,
385     ) -> Result<()> {
386         if cfg {
387             Self::check_bool(found, expected, feature)
388         } else {
389             assert!(!expected);
390             ensure!(
391                 !found,
392                 "Module was compiled with {feature} but support in the host \
393                  was disabled at compile time because the `{cfg_str}` Cargo \
394                  feature was not enabled",
395             );
396             Ok(())
397         }
398     }
399 
400     fn check_features(&mut self, other: &wasmparser::WasmFeatures) -> Result<()> {
401         let module_features = wasmparser::WasmFeatures::from_bits_truncate(self.features);
402         let difference = *other ^ module_features;
403         for (name, flag) in difference.iter_names() {
404             let found = module_features.contains(flag);
405             let expected = other.contains(flag);
406             // Give a slightly more specialized error message for the `GC_TYPES`
407             // feature which isn't actually part of wasm itself but is gated by
408             // compile-time crate features.
409             if flag == wasmparser::WasmFeatures::GC_TYPES {
410                 Self::check_cfg_bool(
411                     cfg!(feature = "gc"),
412                     "gc",
413                     found,
414                     expected,
415                     WasmFeature(name),
416                 )?;
417             } else {
418                 Self::check_bool(found, expected, WasmFeature(name))?;
419             }
420         }
421 
422         return Ok(());
423 
424         struct WasmFeature<'a>(&'a str);
425 
426         impl fmt::Display for WasmFeature<'_> {
427             fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
428                 write!(f, "support for WebAssembly feature `")?;
429                 for c in self.0.chars().map(|c| c.to_lowercase()) {
430                     write!(f, "{c}")?;
431                 }
432                 write!(f, "`")?;
433                 Ok(())
434             }
435         }
436     }
437 
438     fn check_collector(
439         module: Option<wasmtime_environ::Collector>,
440         host: Option<wasmtime_environ::Collector>,
441     ) -> Result<()> {
442         match (module, host) {
443             (None, None) => Ok(()),
444             (Some(module), Some(host)) if module == host => Ok(()),
445 
446             (None, Some(_)) => {
447                 bail!("module was compiled without GC but GC is enabled in the host")
448             }
449             (Some(_), None) => {
450                 bail!("module was compiled with GC however GC is disabled in the host")
451             }
452 
453             (Some(module), Some(host)) => {
454                 bail!(
455                     "module was compiled for the {module} collector but \
456                      the host is configured to use the {host} collector",
457                 )
458             }
459         }
460     }
461 
462     fn check_intra_module_inlining(
463         module: wasmtime_environ::IntraModuleInlining,
464         host: wasmtime_environ::IntraModuleInlining,
465     ) -> Result<()> {
466         if module == host {
467             return Ok(());
468         }
469 
470         let desc = |cfg| match cfg {
471             wasmtime_environ::IntraModuleInlining::No => "without intra-module inlining",
472             wasmtime_environ::IntraModuleInlining::Yes => "with intra-module inlining",
473             wasmtime_environ::IntraModuleInlining::WhenUsingGc => {
474                 "with intra-module inlining only when using GC"
475             }
476         };
477 
478         let module = desc(module);
479         let host = desc(host);
480 
481         bail!("module was compiled {module} however the host is configured {host}")
482     }
483 }
484 
485 #[cfg(test)]
486 mod test {
487     use super::*;
488     use crate::{Cache, Config, Module, OptLevel};
489     use std::{
490         collections::hash_map::DefaultHasher,
491         hash::{Hash, Hasher},
492     };
493     use tempfile::TempDir;
494 
495     #[test]
496     fn test_architecture_mismatch() -> Result<()> {
497         let engine = Engine::default();
498         let mut metadata = Metadata::new(&engine);
499         metadata.target = "unknown-generic-linux".to_string();
500 
501         match metadata.check_compatible(&engine) {
502             Ok(_) => unreachable!(),
503             Err(e) => assert_eq!(
504                 e.to_string(),
505                 "Module was compiled for architecture 'unknown'",
506             ),
507         }
508 
509         Ok(())
510     }
511 
512     // Note that this test runs on a platform that is known to use Cranelift
513     #[test]
514     #[cfg(all(target_arch = "x86_64", not(miri)))]
515     fn test_os_mismatch() -> Result<()> {
516         let engine = Engine::default();
517         let mut metadata = Metadata::new(&engine);
518 
519         metadata.target = format!(
520             "{}-generic-unknown",
521             target_lexicon::Triple::host().architecture
522         );
523 
524         match metadata.check_compatible(&engine) {
525             Ok(_) => unreachable!(),
526             Err(e) => assert_eq!(
527                 e.to_string(),
528                 "Module was compiled for operating system 'unknown'",
529             ),
530         }
531 
532         Ok(())
533     }
534 
535     #[test]
536     fn test_cranelift_flags_mismatch() -> Result<()> {
537         let engine = Engine::default();
538         let mut metadata = Metadata::new(&engine);
539 
540         metadata
541             .shared_flags
542             .push(("preserve_frame_pointers", FlagValue::Bool(false)));
543 
544         match metadata.check_compatible(&engine) {
545             Ok(_) => unreachable!(),
546             Err(e) => assert!(format!("{e:?}").starts_with(
547                 "\
548 compilation settings of module incompatible with native host
549 
550 Caused by:
551     setting \"preserve_frame_pointers\" is configured to Bool(false) which is not supported"
552             )),
553         }
554 
555         Ok(())
556     }
557 
558     #[test]
559     fn test_isa_flags_mismatch() -> Result<()> {
560         let engine = Engine::default();
561         let mut metadata = Metadata::new(&engine);
562 
563         metadata
564             .isa_flags
565             .push(("not_a_flag", FlagValue::Bool(true)));
566 
567         match metadata.check_compatible(&engine) {
568             Ok(_) => unreachable!(),
569             Err(e) => assert!(
570                 format!("{e:?}").starts_with(
571                     "\
572 compilation settings of module incompatible with native host
573 
574 Caused by:
575     don't know how to test for target-specific flag \"not_a_flag\" at runtime",
576                 ),
577                 "bad error {e:?}",
578             ),
579         }
580 
581         Ok(())
582     }
583 
584     #[test]
585     #[cfg_attr(miri, ignore)]
586     #[cfg(target_pointer_width = "64")] // different defaults on 32-bit platforms
587     fn test_tunables_int_mismatch() -> Result<()> {
588         let engine = Engine::default();
589         let mut metadata = Metadata::new(&engine);
590 
591         metadata.tunables.memory_guard_size = 0;
592 
593         match metadata.check_compatible(&engine) {
594             Ok(_) => unreachable!(),
595             Err(e) => assert_eq!(
596                 e.to_string(),
597                 "Module was compiled with a memory guard size of '0' but '33554432' is expected for the host"
598             ),
599         }
600 
601         Ok(())
602     }
603 
604     #[test]
605     fn test_tunables_bool_mismatch() -> Result<()> {
606         let mut config = Config::new();
607         config.epoch_interruption(true);
608 
609         let engine = Engine::new(&config)?;
610         let mut metadata = Metadata::new(&engine);
611         metadata.tunables.epoch_interruption = false;
612 
613         match metadata.check_compatible(&engine) {
614             Ok(_) => unreachable!(),
615             Err(e) => assert_eq!(
616                 e.to_string(),
617                 "Module was compiled without epoch interruption but it is enabled for the host"
618             ),
619         }
620 
621         let mut config = Config::new();
622         config.epoch_interruption(false);
623 
624         let engine = Engine::new(&config)?;
625         let mut metadata = Metadata::new(&engine);
626         metadata.tunables.epoch_interruption = true;
627 
628         match metadata.check_compatible(&engine) {
629             Ok(_) => unreachable!(),
630             Err(e) => assert_eq!(
631                 e.to_string(),
632                 "Module was compiled with epoch interruption but it is not enabled for the host"
633             ),
634         }
635 
636         Ok(())
637     }
638 
639     /// This test is only run a platform that is known to implement threads
640     #[test]
641     #[cfg(all(target_arch = "x86_64", not(miri)))]
642     fn test_feature_mismatch() -> Result<()> {
643         let mut config = Config::new();
644         config.wasm_threads(true);
645 
646         let engine = Engine::new(&config)?;
647         let mut metadata = Metadata::new(&engine);
648         metadata.features &= !wasmparser::WasmFeatures::THREADS.bits();
649 
650         match metadata.check_compatible(&engine) {
651             Ok(_) => unreachable!(),
652             Err(e) => assert_eq!(
653                 e.to_string(),
654                 "Module was compiled without support for WebAssembly feature \
655                  `threads` but it is enabled for the host"
656             ),
657         }
658 
659         let mut config = Config::new();
660         config.wasm_threads(false);
661 
662         let engine = Engine::new(&config)?;
663         let mut metadata = Metadata::new(&engine);
664         metadata.features |= wasmparser::WasmFeatures::THREADS.bits();
665 
666         match metadata.check_compatible(&engine) {
667             Ok(_) => unreachable!(),
668             Err(e) => assert_eq!(
669                 e.to_string(),
670                 "Module was compiled with support for WebAssembly feature \
671                 `threads` but it is not enabled for the host"
672             ),
673         }
674 
675         Ok(())
676     }
677 
678     #[test]
679     fn engine_weak_upgrades() {
680         let engine = Engine::default();
681         let weak = engine.weak();
682         weak.upgrade()
683             .expect("engine is still alive, so weak reference can upgrade");
684         drop(engine);
685         assert!(
686             weak.upgrade().is_none(),
687             "engine was dropped, so weak reference cannot upgrade"
688         );
689     }
690 
691     #[test]
692     #[cfg_attr(miri, ignore)]
693     fn cache_accounts_for_opt_level() -> Result<()> {
694         let td = TempDir::new()?;
695         let config_path = td.path().join("config.toml");
696         std::fs::write(
697             &config_path,
698             &format!(
699                 "
700                     [cache]
701                     directory = '{}'
702                 ",
703                 td.path().join("cache").display()
704             ),
705         )?;
706         let mut cfg = Config::new();
707         cfg.cranelift_opt_level(OptLevel::None)
708             .cache(Some(Cache::from_file(Some(&config_path))?));
709         let engine = Engine::new(&cfg)?;
710         Module::new(&engine, "(module (func))")?;
711         let cache_config = engine
712             .config()
713             .cache
714             .as_ref()
715             .expect("Missing cache config");
716         assert_eq!(cache_config.cache_hits(), 0);
717         assert_eq!(cache_config.cache_misses(), 1);
718         Module::new(&engine, "(module (func))")?;
719         assert_eq!(cache_config.cache_hits(), 1);
720         assert_eq!(cache_config.cache_misses(), 1);
721 
722         let mut cfg = Config::new();
723         cfg.cranelift_opt_level(OptLevel::Speed)
724             .cache(Some(Cache::from_file(Some(&config_path))?));
725         let engine = Engine::new(&cfg)?;
726         let cache_config = engine
727             .config()
728             .cache
729             .as_ref()
730             .expect("Missing cache config");
731         Module::new(&engine, "(module (func))")?;
732         assert_eq!(cache_config.cache_hits(), 0);
733         assert_eq!(cache_config.cache_misses(), 1);
734         Module::new(&engine, "(module (func))")?;
735         assert_eq!(cache_config.cache_hits(), 1);
736         assert_eq!(cache_config.cache_misses(), 1);
737 
738         let mut cfg = Config::new();
739         cfg.cranelift_opt_level(OptLevel::SpeedAndSize)
740             .cache(Some(Cache::from_file(Some(&config_path))?));
741         let engine = Engine::new(&cfg)?;
742         let cache_config = engine
743             .config()
744             .cache
745             .as_ref()
746             .expect("Missing cache config");
747         Module::new(&engine, "(module (func))")?;
748         assert_eq!(cache_config.cache_hits(), 0);
749         assert_eq!(cache_config.cache_misses(), 1);
750         Module::new(&engine, "(module (func))")?;
751         assert_eq!(cache_config.cache_hits(), 1);
752         assert_eq!(cache_config.cache_misses(), 1);
753 
754         let mut cfg = Config::new();
755         cfg.debug_info(true)
756             .cache(Some(Cache::from_file(Some(&config_path))?));
757         let engine = Engine::new(&cfg)?;
758         let cache_config = engine
759             .config()
760             .cache
761             .as_ref()
762             .expect("Missing cache config");
763         Module::new(&engine, "(module (func))")?;
764         assert_eq!(cache_config.cache_hits(), 0);
765         assert_eq!(cache_config.cache_misses(), 1);
766         Module::new(&engine, "(module (func))")?;
767         assert_eq!(cache_config.cache_hits(), 1);
768         assert_eq!(cache_config.cache_misses(), 1);
769 
770         Ok(())
771     }
772 
773     #[test]
774     fn precompile_compatibility_key_accounts_for_opt_level() {
775         fn hash_for_config(cfg: &Config) -> u64 {
776             let engine = Engine::new(cfg).expect("Config should be valid");
777             let mut hasher = DefaultHasher::new();
778             engine.precompile_compatibility_hash().hash(&mut hasher);
779             hasher.finish()
780         }
781         let mut cfg = Config::new();
782         cfg.cranelift_opt_level(OptLevel::None);
783         let opt_none_hash = hash_for_config(&cfg);
784         cfg.cranelift_opt_level(OptLevel::Speed);
785         let opt_speed_hash = hash_for_config(&cfg);
786         assert_ne!(opt_none_hash, opt_speed_hash)
787     }
788 
789     #[test]
790     fn precompile_compatibility_key_accounts_for_module_version_strategy() -> Result<()> {
791         fn hash_for_config(cfg: &Config) -> u64 {
792             let engine = Engine::new(cfg).expect("Config should be valid");
793             let mut hasher = DefaultHasher::new();
794             engine.precompile_compatibility_hash().hash(&mut hasher);
795             hasher.finish()
796         }
797         let mut cfg_custom_version = Config::new();
798         cfg_custom_version.module_version(ModuleVersionStrategy::Custom("1.0.1111".to_string()))?;
799         let custom_version_hash = hash_for_config(&cfg_custom_version);
800 
801         let mut cfg_default_version = Config::new();
802         cfg_default_version.module_version(ModuleVersionStrategy::WasmtimeVersion)?;
803         let default_version_hash = hash_for_config(&cfg_default_version);
804 
805         let mut cfg_none_version = Config::new();
806         cfg_none_version.module_version(ModuleVersionStrategy::None)?;
807         let none_version_hash = hash_for_config(&cfg_none_version);
808 
809         assert_ne!(custom_version_hash, default_version_hash);
810         assert_ne!(custom_version_hash, none_version_hash);
811         assert_ne!(default_version_hash, none_version_hash);
812 
813         Ok(())
814     }
815 
816     #[test]
817     #[cfg_attr(miri, ignore)]
818     #[cfg(feature = "component-model")]
819     fn components_are_cached() -> Result<()> {
820         use crate::component::Component;
821 
822         let td = TempDir::new()?;
823         let config_path = td.path().join("config.toml");
824         std::fs::write(
825             &config_path,
826             &format!(
827                 "
828                     [cache]
829                     directory = '{}'
830                 ",
831                 td.path().join("cache").display()
832             ),
833         )?;
834         let mut cfg = Config::new();
835         cfg.cache(Some(Cache::from_file(Some(&config_path))?));
836         let engine = Engine::new(&cfg)?;
837         let cache_config = engine
838             .config()
839             .cache
840             .as_ref()
841             .expect("Missing cache config");
842         Component::new(&engine, "(component (core module (func)))")?;
843         assert_eq!(cache_config.cache_hits(), 0);
844         assert_eq!(cache_config.cache_misses(), 1);
845         Component::new(&engine, "(component (core module (func)))")?;
846         assert_eq!(cache_config.cache_hits(), 1);
847         assert_eq!(cache_config.cache_misses(), 1);
848 
849         Ok(())
850     }
851 }
852