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::str::FromStr;
27 use object::endian::NativeEndian;
28 #[cfg(any(feature = "cranelift", feature = "winch"))]
29 use object::write::{Object, StandardSegment};
30 use object::{read::elf::ElfFile64, FileFlags, Object as _, ObjectSection, SectionKind};
31 use serde_derive::{Deserialize, Serialize};
32 use wasmtime_environ::obj;
33 use wasmtime_environ::{FlagValue, ObjectKind, Tunables};
34 
35 const VERSION: u8 = 0;
36 
37 /// Verifies that the serialized engine in `mmap` is compatible with the
38 /// `engine` provided.
39 ///
40 /// This function will verify that the `mmap` provided can be deserialized
41 /// successfully and that the contents are all compatible with the `engine`
42 /// provided here, notably compatible wasm features are enabled, compatible
43 /// compiler options, etc. If a mismatch is found and the compilation metadata
44 /// specified is incompatible then an error is returned.
45 pub fn check_compatible(engine: &Engine, mmap: &[u8], expected: ObjectKind) -> Result<()> {
46     // Parse the input `mmap` as an ELF file and see if the header matches the
47     // Wasmtime-generated header. This includes a Wasmtime-specific `os_abi` and
48     // the `e_flags` field should indicate whether `expected` matches or not.
49     //
50     // Note that errors generated here could mean that a precompiled module was
51     // loaded as a component, or vice versa, both of which aren't supposed to
52     // work.
53     //
54     // Ideally we'd only `File::parse` once and avoid the linear
55     // `section_by_name` search here but the general serialization code isn't
56     // structured well enough to make this easy and additionally it's not really
57     // a perf issue right now so doing that is left for another day's
58     // refactoring.
59     let obj = ElfFile64::<NativeEndian>::parse(mmap)
60         .err2anyhow()
61         .context("failed to parse precompiled artifact as an ELF")?;
62     let expected_e_flags = match expected {
63         ObjectKind::Module => obj::EF_WASMTIME_MODULE,
64         ObjectKind::Component => obj::EF_WASMTIME_COMPONENT,
65     };
66     match obj.flags() {
67         FileFlags::Elf {
68             os_abi: obj::ELFOSABI_WASMTIME,
69             abi_version: 0,
70             e_flags,
71         } if e_flags == expected_e_flags => {}
72         _ => bail!("incompatible object file format"),
73     }
74 
75     let data = obj
76         .section_by_name(obj::ELF_WASM_ENGINE)
77         .ok_or_else(|| anyhow!("failed to find section `{}`", obj::ELF_WASM_ENGINE))?
78         .data()
79         .err2anyhow()?;
80     let (first, data) = data
81         .split_first()
82         .ok_or_else(|| anyhow!("invalid engine section"))?;
83     if *first != VERSION {
84         bail!("mismatched version in engine section");
85     }
86     let (len, data) = data
87         .split_first()
88         .ok_or_else(|| anyhow!("invalid engine section"))?;
89     let len = usize::from(*len);
90     let (version, data) = if data.len() < len + 1 {
91         bail!("engine section too small")
92     } else {
93         data.split_at(len)
94     };
95 
96     match &engine.config().module_version {
97         ModuleVersionStrategy::WasmtimeVersion => {
98             let version = core::str::from_utf8(version).err2anyhow()?;
99             if version != env!("CARGO_PKG_VERSION") {
100                 bail!(
101                     "Module was compiled with incompatible Wasmtime version '{}'",
102                     version
103                 );
104             }
105         }
106         ModuleVersionStrategy::Custom(v) => {
107             let version = core::str::from_utf8(&version).err2anyhow()?;
108             if version != v {
109                 bail!(
110                     "Module was compiled with incompatible version '{}'",
111                     version
112                 );
113             }
114         }
115         ModuleVersionStrategy::None => { /* ignore the version info, accept all */ }
116     }
117     postcard::from_bytes::<Metadata<'_>>(data)
118         .err2anyhow()?
119         .check_compatible(engine)
120 }
121 
122 #[cfg(any(feature = "cranelift", feature = "winch"))]
123 pub fn append_compiler_info(engine: &Engine, obj: &mut Object<'_>, metadata: &Metadata<'_>) {
124     let section = obj.add_section(
125         obj.segment_name(StandardSegment::Data).to_vec(),
126         obj::ELF_WASM_ENGINE.as_bytes().to_vec(),
127         SectionKind::ReadOnlyData,
128     );
129     let mut data = Vec::new();
130     data.push(VERSION);
131     let version = match &engine.config().module_version {
132         ModuleVersionStrategy::WasmtimeVersion => env!("CARGO_PKG_VERSION"),
133         ModuleVersionStrategy::Custom(c) => c,
134         ModuleVersionStrategy::None => "",
135     };
136     // This precondition is checked in Config::module_version:
137     assert!(
138         version.len() < 256,
139         "package version must be less than 256 bytes"
140     );
141     data.push(version.len() as u8);
142     data.extend_from_slice(version.as_bytes());
143     data.extend(postcard::to_allocvec(metadata).unwrap());
144     obj.set_section_data(section, data, 1);
145 }
146 
147 fn detect_precompiled<'data, R: object::ReadRef<'data>>(
148     obj: ElfFile64<'data, NativeEndian, R>,
149 ) -> Option<Precompiled> {
150     match obj.flags() {
151         FileFlags::Elf {
152             os_abi: obj::ELFOSABI_WASMTIME,
153             abi_version: 0,
154             e_flags: obj::EF_WASMTIME_MODULE,
155         } => Some(Precompiled::Module),
156         FileFlags::Elf {
157             os_abi: obj::ELFOSABI_WASMTIME,
158             abi_version: 0,
159             e_flags: obj::EF_WASMTIME_COMPONENT,
160         } => Some(Precompiled::Component),
161         _ => None,
162     }
163 }
164 
165 pub fn detect_precompiled_bytes(bytes: &[u8]) -> Option<Precompiled> {
166     detect_precompiled(ElfFile64::parse(bytes).ok()?)
167 }
168 
169 #[cfg(feature = "std")]
170 pub fn detect_precompiled_file(path: impl AsRef<std::path::Path>) -> Result<Option<Precompiled>> {
171     let read_cache = object::ReadCache::new(std::fs::File::open(path)?);
172     let obj = ElfFile64::parse(&read_cache)?;
173     Ok(detect_precompiled(obj))
174 }
175 
176 #[derive(Serialize, Deserialize)]
177 pub struct Metadata<'a> {
178     target: String,
179     #[serde(borrow)]
180     shared_flags: Vec<(&'a str, FlagValue<'a>)>,
181     #[serde(borrow)]
182     isa_flags: Vec<(&'a str, FlagValue<'a>)>,
183     tunables: Tunables,
184     features: WasmFeatures,
185 }
186 
187 // This exists because `wasmparser::WasmFeatures` isn't serializable
188 #[derive(Debug, Copy, Clone, Serialize, Deserialize)]
189 struct WasmFeatures {
190     reference_types: bool,
191     multi_value: bool,
192     bulk_memory: bool,
193     component_model: bool,
194     simd: bool,
195     tail_call: bool,
196     threads: bool,
197     multi_memory: bool,
198     exceptions: bool,
199     memory64: bool,
200     relaxed_simd: bool,
201     extended_const: bool,
202     function_references: bool,
203     gc: bool,
204     custom_page_sizes: bool,
205     component_model_more_flags: bool,
206 }
207 
208 impl Metadata<'_> {
209     #[cfg(any(feature = "cranelift", feature = "winch"))]
210     pub fn new(engine: &Engine) -> Metadata<'static> {
211         let wasmparser::WasmFeaturesInflated {
212             reference_types,
213             multi_value,
214             bulk_memory,
215             component_model,
216             simd,
217             threads,
218             tail_call,
219             multi_memory,
220             exceptions,
221             memory64,
222             relaxed_simd,
223             extended_const,
224             memory_control,
225             function_references,
226             gc,
227             custom_page_sizes,
228             shared_everything_threads,
229             component_model_values,
230             component_model_nested_names,
231             component_model_more_flags,
232 
233             // Always on; we don't currently have knobs for these.
234             mutable_global: _,
235             saturating_float_to_int: _,
236             sign_extension: _,
237             floats: _,
238         } = engine.config().features.inflate();
239 
240         // These features are not implemented in Wasmtime yet. We match on them
241         // above so that once we do implement support for them, we won't
242         // silently ignore them during serialization.
243         assert!(!memory_control);
244         assert!(!component_model_values);
245         assert!(!component_model_nested_names);
246         assert!(!shared_everything_threads);
247 
248         Metadata {
249             target: engine.compiler().triple().to_string(),
250             shared_flags: engine.compiler().flags(),
251             isa_flags: engine.compiler().isa_flags(),
252             tunables: engine.tunables().clone(),
253             features: WasmFeatures {
254                 reference_types,
255                 multi_value,
256                 bulk_memory,
257                 component_model,
258                 simd,
259                 threads,
260                 tail_call,
261                 multi_memory,
262                 exceptions,
263                 memory64,
264                 relaxed_simd,
265                 extended_const,
266                 function_references,
267                 gc,
268                 custom_page_sizes,
269                 component_model_more_flags,
270             },
271         }
272     }
273 
274     fn check_compatible(mut self, engine: &Engine) -> Result<()> {
275         self.check_triple(engine)?;
276         self.check_shared_flags(engine)?;
277         self.check_isa_flags(engine)?;
278         self.check_tunables(&engine.tunables())?;
279         self.check_features(&engine.config().features)?;
280         Ok(())
281     }
282 
283     fn check_triple(&self, engine: &Engine) -> Result<()> {
284         let engine_target = engine.target();
285         let module_target =
286             target_lexicon::Triple::from_str(&self.target).map_err(|e| anyhow!(e))?;
287 
288         if module_target.architecture != engine_target.architecture {
289             bail!(
290                 "Module was compiled for architecture '{}'",
291                 module_target.architecture
292             );
293         }
294 
295         if module_target.operating_system != engine_target.operating_system {
296             bail!(
297                 "Module was compiled for operating system '{}'",
298                 module_target.operating_system
299             );
300         }
301 
302         Ok(())
303     }
304 
305     fn check_shared_flags(&mut self, engine: &Engine) -> Result<()> {
306         for (name, val) in self.shared_flags.iter() {
307             engine
308                 .check_compatible_with_shared_flag(name, val)
309                 .map_err(|s| anyhow::Error::msg(s))
310                 .context("compilation settings of module incompatible with native host")?;
311         }
312         Ok(())
313     }
314 
315     fn check_isa_flags(&mut self, engine: &Engine) -> Result<()> {
316         for (name, val) in self.isa_flags.iter() {
317             engine
318                 .check_compatible_with_isa_flag(name, val)
319                 .map_err(|s| anyhow::Error::msg(s))
320                 .context("compilation settings of module incompatible with native host")?;
321         }
322         Ok(())
323     }
324 
325     fn check_int<T: Eq + core::fmt::Display>(found: T, expected: T, feature: &str) -> Result<()> {
326         if found == expected {
327             return Ok(());
328         }
329 
330         bail!(
331             "Module was compiled with a {} of '{}' but '{}' is expected for the host",
332             feature,
333             found,
334             expected
335         );
336     }
337 
338     fn check_bool(found: bool, expected: bool, feature: &str) -> Result<()> {
339         if found == expected {
340             return Ok(());
341         }
342 
343         bail!(
344             "Module was compiled {} {} but it {} enabled for the host",
345             if found { "with" } else { "without" },
346             feature,
347             if expected { "is" } else { "is not" }
348         );
349     }
350 
351     fn check_tunables(&mut self, other: &Tunables) -> Result<()> {
352         let Tunables {
353             static_memory_reservation,
354             static_memory_offset_guard_size,
355             dynamic_memory_offset_guard_size,
356             generate_native_debuginfo,
357             parse_wasm_debuginfo,
358             consume_fuel,
359             epoch_interruption,
360             static_memory_bound_is_maximum,
361             guard_before_linear_memory,
362             table_lazy_init,
363             relaxed_simd_deterministic,
364             tail_callable,
365             winch_callable,
366 
367             // This doesn't affect compilation, it's just a runtime setting.
368             dynamic_memory_growth_reserve: _,
369 
370             // This does technically affect compilation but modules with/without
371             // trap information can be loaded into engines with the opposite
372             // setting just fine (it's just a section in the compiled file and
373             // whether it's present or not)
374             generate_address_map: _,
375 
376             // Just a debugging aid, doesn't affect functionality at all.
377             debug_adapter_modules: _,
378         } = self.tunables;
379 
380         Self::check_int(
381             static_memory_reservation,
382             other.static_memory_reservation,
383             "static memory reservation",
384         )?;
385         Self::check_int(
386             static_memory_offset_guard_size,
387             other.static_memory_offset_guard_size,
388             "static memory guard size",
389         )?;
390         Self::check_int(
391             dynamic_memory_offset_guard_size,
392             other.dynamic_memory_offset_guard_size,
393             "dynamic memory guard size",
394         )?;
395         Self::check_bool(
396             generate_native_debuginfo,
397             other.generate_native_debuginfo,
398             "debug information support",
399         )?;
400         Self::check_bool(
401             parse_wasm_debuginfo,
402             other.parse_wasm_debuginfo,
403             "WebAssembly backtrace support",
404         )?;
405         Self::check_bool(consume_fuel, other.consume_fuel, "fuel support")?;
406         Self::check_bool(
407             epoch_interruption,
408             other.epoch_interruption,
409             "epoch interruption",
410         )?;
411         Self::check_bool(
412             static_memory_bound_is_maximum,
413             other.static_memory_bound_is_maximum,
414             "pooling allocation support",
415         )?;
416         Self::check_bool(
417             guard_before_linear_memory,
418             other.guard_before_linear_memory,
419             "guard before linear memory",
420         )?;
421         Self::check_bool(table_lazy_init, other.table_lazy_init, "table lazy init")?;
422         Self::check_bool(
423             relaxed_simd_deterministic,
424             other.relaxed_simd_deterministic,
425             "relaxed simd deterministic semantics",
426         )?;
427         Self::check_bool(tail_callable, other.tail_callable, "WebAssembly tail calls")?;
428         Self::check_bool(
429             winch_callable,
430             other.winch_callable,
431             "Winch calling convention",
432         )?;
433 
434         Ok(())
435     }
436 
437     fn check_cfg_bool(
438         cfg: bool,
439         cfg_str: &str,
440         found: bool,
441         expected: bool,
442         feature: &str,
443     ) -> Result<()> {
444         if cfg {
445             Self::check_bool(found, expected, feature)
446         } else {
447             assert!(!expected);
448             ensure!(
449                 !found,
450                 "Module was compiled with {feature} but support in the host \
451                  was disabled at compile time because the `{cfg_str}` Cargo \
452                  feature was not enabled",
453             );
454             Ok(())
455         }
456     }
457 
458     fn check_features(&mut self, other: &wasmparser::WasmFeatures) -> Result<()> {
459         let WasmFeatures {
460             reference_types,
461             multi_value,
462             bulk_memory,
463             component_model,
464             simd,
465             tail_call,
466             threads,
467             multi_memory,
468             exceptions,
469             memory64,
470             relaxed_simd,
471             extended_const,
472             function_references,
473             gc,
474             custom_page_sizes,
475             component_model_more_flags,
476         } = self.features;
477 
478         use wasmparser::WasmFeatures as F;
479         Self::check_cfg_bool(
480             cfg!(feature = "gc"),
481             "gc",
482             reference_types,
483             other.contains(F::REFERENCE_TYPES),
484             "WebAssembly reference types support",
485         )?;
486         Self::check_cfg_bool(
487             cfg!(feature = "gc"),
488             "gc",
489             function_references,
490             other.contains(F::FUNCTION_REFERENCES),
491             "WebAssembly function-references support",
492         )?;
493         Self::check_cfg_bool(
494             cfg!(feature = "gc"),
495             "gc",
496             gc,
497             other.contains(F::GC),
498             "WebAssembly garbage collection support",
499         )?;
500 
501         Self::check_bool(
502             multi_value,
503             other.contains(F::MULTI_VALUE),
504             "WebAssembly multi-value support",
505         )?;
506         Self::check_bool(
507             bulk_memory,
508             other.contains(F::BULK_MEMORY),
509             "WebAssembly bulk memory support",
510         )?;
511         Self::check_bool(
512             component_model,
513             other.contains(F::COMPONENT_MODEL),
514             "WebAssembly component model support",
515         )?;
516         Self::check_bool(simd, other.contains(F::SIMD), "WebAssembly SIMD support")?;
517         Self::check_bool(
518             tail_call,
519             other.contains(F::TAIL_CALL),
520             "WebAssembly tail calls support",
521         )?;
522         Self::check_bool(
523             threads,
524             other.contains(F::THREADS),
525             "WebAssembly threads support",
526         )?;
527         Self::check_bool(
528             multi_memory,
529             other.contains(F::MULTI_MEMORY),
530             "WebAssembly multi-memory support",
531         )?;
532         Self::check_bool(
533             exceptions,
534             other.contains(F::EXCEPTIONS),
535             "WebAssembly exceptions support",
536         )?;
537         Self::check_bool(
538             memory64,
539             other.contains(F::MEMORY64),
540             "WebAssembly 64-bit memory support",
541         )?;
542         Self::check_bool(
543             extended_const,
544             other.contains(F::EXTENDED_CONST),
545             "WebAssembly extended-const support",
546         )?;
547         Self::check_bool(
548             relaxed_simd,
549             other.contains(F::RELAXED_SIMD),
550             "WebAssembly relaxed-simd support",
551         )?;
552         Self::check_bool(
553             custom_page_sizes,
554             other.contains(F::CUSTOM_PAGE_SIZES),
555             "WebAssembly custom-page-sizes support",
556         )?;
557         Self::check_bool(
558             component_model_more_flags,
559             other.contains(F::COMPONENT_MODEL_MORE_FLAGS),
560             "WebAssembly component model support for more than 32 flags",
561         )?;
562 
563         Ok(())
564     }
565 }
566 
567 #[cfg(test)]
568 mod test {
569     use super::*;
570     use crate::{Config, Module, OptLevel};
571     use std::{
572         collections::hash_map::DefaultHasher,
573         hash::{Hash, Hasher},
574     };
575     use tempfile::TempDir;
576 
577     #[test]
578     fn test_architecture_mismatch() -> Result<()> {
579         let engine = Engine::default();
580         let mut metadata = Metadata::new(&engine);
581         metadata.target = "unknown-generic-linux".to_string();
582 
583         match metadata.check_compatible(&engine) {
584             Ok(_) => unreachable!(),
585             Err(e) => assert_eq!(
586                 e.to_string(),
587                 "Module was compiled for architecture 'unknown'",
588             ),
589         }
590 
591         Ok(())
592     }
593 
594     #[test]
595     fn test_os_mismatch() -> Result<()> {
596         let engine = Engine::default();
597         let mut metadata = Metadata::new(&engine);
598 
599         metadata.target = format!(
600             "{}-generic-unknown",
601             target_lexicon::Triple::host().architecture
602         );
603 
604         match metadata.check_compatible(&engine) {
605             Ok(_) => unreachable!(),
606             Err(e) => assert_eq!(
607                 e.to_string(),
608                 "Module was compiled for operating system 'unknown'",
609             ),
610         }
611 
612         Ok(())
613     }
614 
615     #[test]
616     fn test_cranelift_flags_mismatch() -> Result<()> {
617         let engine = Engine::default();
618         let mut metadata = Metadata::new(&engine);
619 
620         metadata
621             .shared_flags
622             .push(("preserve_frame_pointers", FlagValue::Bool(false)));
623 
624         match metadata.check_compatible(&engine) {
625             Ok(_) => unreachable!(),
626             Err(e) => assert!(format!("{:?}", e).starts_with(
627                 "\
628 compilation settings of module incompatible with native host
629 
630 Caused by:
631     setting \"preserve_frame_pointers\" is configured to Bool(false) which is not supported"
632             )),
633         }
634 
635         Ok(())
636     }
637 
638     #[test]
639     fn test_isa_flags_mismatch() -> Result<()> {
640         let engine = Engine::default();
641         let mut metadata = Metadata::new(&engine);
642 
643         metadata
644             .isa_flags
645             .push(("not_a_flag", FlagValue::Bool(true)));
646 
647         match metadata.check_compatible(&engine) {
648             Ok(_) => unreachable!(),
649             Err(e) => assert!(
650                 format!("{e:?}").starts_with(
651                     "\
652 compilation settings of module incompatible with native host
653 
654 Caused by:
655     don't know how to test for target-specific flag \"not_a_flag\" at runtime",
656                 ),
657                 "bad error {e:?}",
658             ),
659         }
660 
661         Ok(())
662     }
663 
664     #[test]
665     #[cfg_attr(miri, ignore)]
666     fn test_tunables_int_mismatch() -> Result<()> {
667         let engine = Engine::default();
668         let mut metadata = Metadata::new(&engine);
669 
670         metadata.tunables.static_memory_offset_guard_size = 0;
671 
672         match metadata.check_compatible(&engine) {
673             Ok(_) => unreachable!(),
674             Err(e) => assert_eq!(e.to_string(), "Module was compiled with a static memory guard size of '0' but '2147483648' is expected for the host"),
675         }
676 
677         Ok(())
678     }
679 
680     #[test]
681     fn test_tunables_bool_mismatch() -> Result<()> {
682         let mut config = Config::new();
683         config.epoch_interruption(true);
684 
685         let engine = Engine::new(&config)?;
686         let mut metadata = Metadata::new(&engine);
687         metadata.tunables.epoch_interruption = false;
688 
689         match metadata.check_compatible(&engine) {
690             Ok(_) => unreachable!(),
691             Err(e) => assert_eq!(
692                 e.to_string(),
693                 "Module was compiled without epoch interruption but it is enabled for the host"
694             ),
695         }
696 
697         let mut config = Config::new();
698         config.epoch_interruption(false);
699 
700         let engine = Engine::new(&config)?;
701         let mut metadata = Metadata::new(&engine);
702         metadata.tunables.epoch_interruption = true;
703 
704         match metadata.check_compatible(&engine) {
705             Ok(_) => unreachable!(),
706             Err(e) => assert_eq!(
707                 e.to_string(),
708                 "Module was compiled with epoch interruption but it is not enabled for the host"
709             ),
710         }
711 
712         Ok(())
713     }
714 
715     #[test]
716     fn test_feature_mismatch() -> Result<()> {
717         let mut config = Config::new();
718         config.wasm_threads(true);
719 
720         let engine = Engine::new(&config)?;
721         let mut metadata = Metadata::new(&engine);
722         metadata.features.threads = false;
723 
724         match metadata.check_compatible(&engine) {
725             Ok(_) => unreachable!(),
726             Err(e) => assert_eq!(e.to_string(), "Module was compiled without WebAssembly threads support but it is enabled for the host"),
727         }
728 
729         let mut config = Config::new();
730         config.wasm_threads(false);
731 
732         let engine = Engine::new(&config)?;
733         let mut metadata = Metadata::new(&engine);
734         metadata.features.threads = true;
735 
736         match metadata.check_compatible(&engine) {
737             Ok(_) => unreachable!(),
738             Err(e) => assert_eq!(e.to_string(), "Module was compiled with WebAssembly threads support but it is not enabled for the host"),
739         }
740 
741         Ok(())
742     }
743 
744     #[test]
745     fn engine_weak_upgrades() {
746         let engine = Engine::default();
747         let weak = engine.weak();
748         weak.upgrade()
749             .expect("engine is still alive, so weak reference can upgrade");
750         drop(engine);
751         assert!(
752             weak.upgrade().is_none(),
753             "engine was dropped, so weak reference cannot upgrade"
754         );
755     }
756 
757     #[test]
758     #[cfg_attr(miri, ignore)]
759     fn cache_accounts_for_opt_level() -> Result<()> {
760         let td = TempDir::new()?;
761         let config_path = td.path().join("config.toml");
762         std::fs::write(
763             &config_path,
764             &format!(
765                 "
766                     [cache]
767                     enabled = true
768                     directory = '{}'
769                 ",
770                 td.path().join("cache").display()
771             ),
772         )?;
773         let mut cfg = Config::new();
774         cfg.cranelift_opt_level(OptLevel::None)
775             .cache_config_load(&config_path)?;
776         let engine = Engine::new(&cfg)?;
777         Module::new(&engine, "(module (func))")?;
778         assert_eq!(engine.config().cache_config.cache_hits(), 0);
779         assert_eq!(engine.config().cache_config.cache_misses(), 1);
780         Module::new(&engine, "(module (func))")?;
781         assert_eq!(engine.config().cache_config.cache_hits(), 1);
782         assert_eq!(engine.config().cache_config.cache_misses(), 1);
783 
784         let mut cfg = Config::new();
785         cfg.cranelift_opt_level(OptLevel::Speed)
786             .cache_config_load(&config_path)?;
787         let engine = Engine::new(&cfg)?;
788         Module::new(&engine, "(module (func))")?;
789         assert_eq!(engine.config().cache_config.cache_hits(), 0);
790         assert_eq!(engine.config().cache_config.cache_misses(), 1);
791         Module::new(&engine, "(module (func))")?;
792         assert_eq!(engine.config().cache_config.cache_hits(), 1);
793         assert_eq!(engine.config().cache_config.cache_misses(), 1);
794 
795         let mut cfg = Config::new();
796         cfg.cranelift_opt_level(OptLevel::SpeedAndSize)
797             .cache_config_load(&config_path)?;
798         let engine = Engine::new(&cfg)?;
799         Module::new(&engine, "(module (func))")?;
800         assert_eq!(engine.config().cache_config.cache_hits(), 0);
801         assert_eq!(engine.config().cache_config.cache_misses(), 1);
802         Module::new(&engine, "(module (func))")?;
803         assert_eq!(engine.config().cache_config.cache_hits(), 1);
804         assert_eq!(engine.config().cache_config.cache_misses(), 1);
805 
806         let mut cfg = Config::new();
807         cfg.debug_info(true).cache_config_load(&config_path)?;
808         let engine = Engine::new(&cfg)?;
809         Module::new(&engine, "(module (func))")?;
810         assert_eq!(engine.config().cache_config.cache_hits(), 0);
811         assert_eq!(engine.config().cache_config.cache_misses(), 1);
812         Module::new(&engine, "(module (func))")?;
813         assert_eq!(engine.config().cache_config.cache_hits(), 1);
814         assert_eq!(engine.config().cache_config.cache_misses(), 1);
815 
816         Ok(())
817     }
818 
819     #[test]
820     fn precompile_compatibility_key_accounts_for_opt_level() {
821         fn hash_for_config(cfg: &Config) -> u64 {
822             let engine = Engine::new(cfg).expect("Config should be valid");
823             let mut hasher = DefaultHasher::new();
824             engine.precompile_compatibility_hash().hash(&mut hasher);
825             hasher.finish()
826         }
827         let mut cfg = Config::new();
828         cfg.cranelift_opt_level(OptLevel::None);
829         let opt_none_hash = hash_for_config(&cfg);
830         cfg.cranelift_opt_level(OptLevel::Speed);
831         let opt_speed_hash = hash_for_config(&cfg);
832         assert_ne!(opt_none_hash, opt_speed_hash)
833     }
834 
835     #[test]
836     fn precompile_compatibility_key_accounts_for_module_version_strategy() -> Result<()> {
837         fn hash_for_config(cfg: &Config) -> u64 {
838             let engine = Engine::new(cfg).expect("Config should be valid");
839             let mut hasher = DefaultHasher::new();
840             engine.precompile_compatibility_hash().hash(&mut hasher);
841             hasher.finish()
842         }
843         let mut cfg_custom_version = Config::new();
844         cfg_custom_version.module_version(ModuleVersionStrategy::Custom("1.0.1111".to_string()))?;
845         let custom_version_hash = hash_for_config(&cfg_custom_version);
846 
847         let mut cfg_default_version = Config::new();
848         cfg_default_version.module_version(ModuleVersionStrategy::WasmtimeVersion)?;
849         let default_version_hash = hash_for_config(&cfg_default_version);
850 
851         let mut cfg_none_version = Config::new();
852         cfg_none_version.module_version(ModuleVersionStrategy::None)?;
853         let none_version_hash = hash_for_config(&cfg_none_version);
854 
855         assert_ne!(custom_version_hash, default_version_hash);
856         assert_ne!(custom_version_hash, none_version_hash);
857         assert_ne!(default_version_hash, none_version_hash);
858 
859         Ok(())
860     }
861 
862     #[test]
863     #[cfg_attr(miri, ignore)]
864     #[cfg(feature = "component-model")]
865     fn components_are_cached() -> Result<()> {
866         use crate::component::Component;
867 
868         let td = TempDir::new()?;
869         let config_path = td.path().join("config.toml");
870         std::fs::write(
871             &config_path,
872             &format!(
873                 "
874                     [cache]
875                     enabled = true
876                     directory = '{}'
877                 ",
878                 td.path().join("cache").display()
879             ),
880         )?;
881         let mut cfg = Config::new();
882         cfg.cache_config_load(&config_path)?;
883         let engine = Engine::new(&cfg)?;
884         Component::new(&engine, "(component (core module (func)))")?;
885         assert_eq!(engine.config().cache_config.cache_hits(), 0);
886         assert_eq!(engine.config().cache_config.cache_misses(), 1);
887         Component::new(&engine, "(component (core module (func)))")?;
888         assert_eq!(engine.config().cache_config.cache_hits(), 1);
889         assert_eq!(engine.config().cache_config.cache_misses(), 1);
890 
891         Ok(())
892     }
893 }
894