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     component_model_multiple_returns: bool,
207     gc_types: bool,
208 }
209 
210 impl Metadata<'_> {
211     #[cfg(any(feature = "cranelift", feature = "winch"))]
212     pub fn new(engine: &Engine) -> Metadata<'static> {
213         let wasmparser::WasmFeaturesInflated {
214             reference_types,
215             multi_value,
216             bulk_memory,
217             component_model,
218             simd,
219             threads,
220             tail_call,
221             multi_memory,
222             exceptions,
223             memory64,
224             relaxed_simd,
225             extended_const,
226             memory_control,
227             function_references,
228             gc,
229             custom_page_sizes,
230             shared_everything_threads,
231             component_model_values,
232             component_model_nested_names,
233             component_model_more_flags,
234             component_model_multiple_returns,
235             legacy_exceptions,
236             gc_types,
237             stack_switching,
238 
239             // Always on; we don't currently have knobs for these.
240             mutable_global: _,
241             saturating_float_to_int: _,
242             sign_extension: _,
243             floats: _,
244         } = engine.features().inflate();
245 
246         // These features are not implemented in Wasmtime yet. We match on them
247         // above so that once we do implement support for them, we won't
248         // silently ignore them during serialization.
249         assert!(!memory_control);
250         assert!(!component_model_values);
251         assert!(!component_model_nested_names);
252         assert!(!shared_everything_threads);
253         assert!(!legacy_exceptions);
254         assert!(!stack_switching);
255 
256         Metadata {
257             target: engine.compiler().triple().to_string(),
258             shared_flags: engine.compiler().flags(),
259             isa_flags: engine.compiler().isa_flags(),
260             tunables: engine.tunables().clone(),
261             features: WasmFeatures {
262                 reference_types,
263                 multi_value,
264                 bulk_memory,
265                 component_model,
266                 simd,
267                 threads,
268                 tail_call,
269                 multi_memory,
270                 exceptions,
271                 memory64,
272                 relaxed_simd,
273                 extended_const,
274                 function_references,
275                 gc,
276                 custom_page_sizes,
277                 component_model_more_flags,
278                 component_model_multiple_returns,
279                 gc_types,
280             },
281         }
282     }
283 
284     fn check_compatible(mut self, engine: &Engine) -> Result<()> {
285         self.check_triple(engine)?;
286         self.check_shared_flags(engine)?;
287         self.check_isa_flags(engine)?;
288         self.check_tunables(&engine.tunables())?;
289         self.check_features(&engine.features())?;
290         Ok(())
291     }
292 
293     fn check_triple(&self, engine: &Engine) -> Result<()> {
294         let engine_target = engine.target();
295         let module_target =
296             target_lexicon::Triple::from_str(&self.target).map_err(|e| anyhow!(e))?;
297 
298         if module_target.architecture != engine_target.architecture {
299             bail!(
300                 "Module was compiled for architecture '{}'",
301                 module_target.architecture
302             );
303         }
304 
305         if module_target.operating_system != engine_target.operating_system {
306             bail!(
307                 "Module was compiled for operating system '{}'",
308                 module_target.operating_system
309             );
310         }
311 
312         Ok(())
313     }
314 
315     fn check_shared_flags(&mut self, engine: &Engine) -> Result<()> {
316         for (name, val) in self.shared_flags.iter() {
317             engine
318                 .check_compatible_with_shared_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_isa_flags(&mut self, engine: &Engine) -> Result<()> {
326         for (name, val) in self.isa_flags.iter() {
327             engine
328                 .check_compatible_with_isa_flag(name, val)
329                 .map_err(|s| anyhow::Error::msg(s))
330                 .context("compilation settings of module incompatible with native host")?;
331         }
332         Ok(())
333     }
334 
335     fn check_int<T: Eq + core::fmt::Display>(found: T, expected: T, feature: &str) -> Result<()> {
336         if found == expected {
337             return Ok(());
338         }
339 
340         bail!(
341             "Module was compiled with a {} of '{}' but '{}' is expected for the host",
342             feature,
343             found,
344             expected
345         );
346     }
347 
348     fn check_bool(found: bool, expected: bool, feature: &str) -> Result<()> {
349         if found == expected {
350             return Ok(());
351         }
352 
353         bail!(
354             "Module was compiled {} {} but it {} enabled for the host",
355             if found { "with" } else { "without" },
356             feature,
357             if expected { "is" } else { "is not" }
358         );
359     }
360 
361     fn check_tunables(&mut self, other: &Tunables) -> Result<()> {
362         let Tunables {
363             static_memory_reservation,
364             static_memory_offset_guard_size,
365             dynamic_memory_offset_guard_size,
366             generate_native_debuginfo,
367             parse_wasm_debuginfo,
368             consume_fuel,
369             epoch_interruption,
370             static_memory_bound_is_maximum,
371             guard_before_linear_memory,
372             table_lazy_init,
373             relaxed_simd_deterministic,
374             winch_callable,
375             signals_based_traps,
376             // This doesn't affect compilation, it's just a runtime setting.
377             dynamic_memory_growth_reserve: _,
378 
379             // This does technically affect compilation but modules with/without
380             // trap information can be loaded into engines with the opposite
381             // setting just fine (it's just a section in the compiled file and
382             // whether it's present or not)
383             generate_address_map: _,
384 
385             // Just a debugging aid, doesn't affect functionality at all.
386             debug_adapter_modules: _,
387         } = self.tunables;
388 
389         Self::check_int(
390             static_memory_reservation,
391             other.static_memory_reservation,
392             "static memory reservation",
393         )?;
394         Self::check_int(
395             static_memory_offset_guard_size,
396             other.static_memory_offset_guard_size,
397             "static memory guard size",
398         )?;
399         Self::check_int(
400             dynamic_memory_offset_guard_size,
401             other.dynamic_memory_offset_guard_size,
402             "dynamic memory guard size",
403         )?;
404         Self::check_bool(
405             generate_native_debuginfo,
406             other.generate_native_debuginfo,
407             "debug information support",
408         )?;
409         Self::check_bool(
410             parse_wasm_debuginfo,
411             other.parse_wasm_debuginfo,
412             "WebAssembly backtrace support",
413         )?;
414         Self::check_bool(consume_fuel, other.consume_fuel, "fuel support")?;
415         Self::check_bool(
416             epoch_interruption,
417             other.epoch_interruption,
418             "epoch interruption",
419         )?;
420         Self::check_bool(
421             static_memory_bound_is_maximum,
422             other.static_memory_bound_is_maximum,
423             "pooling allocation support",
424         )?;
425         Self::check_bool(
426             guard_before_linear_memory,
427             other.guard_before_linear_memory,
428             "guard before linear memory",
429         )?;
430         Self::check_bool(table_lazy_init, other.table_lazy_init, "table lazy init")?;
431         Self::check_bool(
432             relaxed_simd_deterministic,
433             other.relaxed_simd_deterministic,
434             "relaxed simd deterministic semantics",
435         )?;
436         Self::check_bool(
437             winch_callable,
438             other.winch_callable,
439             "Winch calling convention",
440         )?;
441         Self::check_bool(
442             signals_based_traps,
443             other.signals_based_traps,
444             "Signals-based traps",
445         )?;
446 
447         Ok(())
448     }
449 
450     fn check_cfg_bool(
451         cfg: bool,
452         cfg_str: &str,
453         found: bool,
454         expected: bool,
455         feature: &str,
456     ) -> Result<()> {
457         if cfg {
458             Self::check_bool(found, expected, feature)
459         } else {
460             assert!(!expected);
461             ensure!(
462                 !found,
463                 "Module was compiled with {feature} but support in the host \
464                  was disabled at compile time because the `{cfg_str}` Cargo \
465                  feature was not enabled",
466             );
467             Ok(())
468         }
469     }
470 
471     fn check_features(&mut self, other: &wasmparser::WasmFeatures) -> Result<()> {
472         let WasmFeatures {
473             reference_types,
474             multi_value,
475             bulk_memory,
476             component_model,
477             simd,
478             tail_call,
479             threads,
480             multi_memory,
481             exceptions,
482             memory64,
483             relaxed_simd,
484             extended_const,
485             function_references,
486             gc,
487             custom_page_sizes,
488             component_model_more_flags,
489             component_model_multiple_returns,
490             gc_types,
491         } = self.features;
492 
493         use wasmparser::WasmFeatures as F;
494         Self::check_bool(
495             reference_types,
496             other.contains(F::REFERENCE_TYPES),
497             "WebAssembly reference types support",
498         )?;
499         Self::check_bool(
500             function_references,
501             other.contains(F::FUNCTION_REFERENCES),
502             "WebAssembly function-references support",
503         )?;
504         Self::check_bool(
505             gc,
506             other.contains(F::GC),
507             "WebAssembly garbage collection support",
508         )?;
509         Self::check_bool(
510             multi_value,
511             other.contains(F::MULTI_VALUE),
512             "WebAssembly multi-value support",
513         )?;
514         Self::check_bool(
515             bulk_memory,
516             other.contains(F::BULK_MEMORY),
517             "WebAssembly bulk memory support",
518         )?;
519         Self::check_bool(
520             component_model,
521             other.contains(F::COMPONENT_MODEL),
522             "WebAssembly component model support",
523         )?;
524         Self::check_bool(simd, other.contains(F::SIMD), "WebAssembly SIMD support")?;
525         Self::check_bool(
526             tail_call,
527             other.contains(F::TAIL_CALL),
528             "WebAssembly tail calls support",
529         )?;
530         Self::check_bool(
531             threads,
532             other.contains(F::THREADS),
533             "WebAssembly threads support",
534         )?;
535         Self::check_bool(
536             multi_memory,
537             other.contains(F::MULTI_MEMORY),
538             "WebAssembly multi-memory support",
539         )?;
540         Self::check_bool(
541             exceptions,
542             other.contains(F::EXCEPTIONS),
543             "WebAssembly exceptions support",
544         )?;
545         Self::check_bool(
546             memory64,
547             other.contains(F::MEMORY64),
548             "WebAssembly 64-bit memory support",
549         )?;
550         Self::check_bool(
551             extended_const,
552             other.contains(F::EXTENDED_CONST),
553             "WebAssembly extended-const support",
554         )?;
555         Self::check_bool(
556             relaxed_simd,
557             other.contains(F::RELAXED_SIMD),
558             "WebAssembly relaxed-simd support",
559         )?;
560         Self::check_bool(
561             custom_page_sizes,
562             other.contains(F::CUSTOM_PAGE_SIZES),
563             "WebAssembly custom-page-sizes support",
564         )?;
565         Self::check_bool(
566             component_model_more_flags,
567             other.contains(F::COMPONENT_MODEL_MORE_FLAGS),
568             "WebAssembly component model support for more than 32 flags",
569         )?;
570         Self::check_bool(
571             component_model_multiple_returns,
572             other.contains(F::COMPONENT_MODEL_MULTIPLE_RETURNS),
573             "WebAssembly component model support for multiple returns",
574         )?;
575         Self::check_cfg_bool(
576             cfg!(feature = "gc"),
577             "gc",
578             gc_types,
579             other.contains(F::GC_TYPES),
580             "support for WebAssembly gc types",
581         )?;
582 
583         Ok(())
584     }
585 }
586 
587 #[cfg(test)]
588 mod test {
589     use super::*;
590     use crate::{Config, Module, OptLevel};
591     use std::{
592         collections::hash_map::DefaultHasher,
593         hash::{Hash, Hasher},
594     };
595     use tempfile::TempDir;
596 
597     #[test]
598     fn test_architecture_mismatch() -> Result<()> {
599         let engine = Engine::default();
600         let mut metadata = Metadata::new(&engine);
601         metadata.target = "unknown-generic-linux".to_string();
602 
603         match metadata.check_compatible(&engine) {
604             Ok(_) => unreachable!(),
605             Err(e) => assert_eq!(
606                 e.to_string(),
607                 "Module was compiled for architecture 'unknown'",
608             ),
609         }
610 
611         Ok(())
612     }
613 
614     #[test]
615     fn test_os_mismatch() -> Result<()> {
616         let engine = Engine::default();
617         let mut metadata = Metadata::new(&engine);
618 
619         metadata.target = format!(
620             "{}-generic-unknown",
621             target_lexicon::Triple::host().architecture
622         );
623 
624         match metadata.check_compatible(&engine) {
625             Ok(_) => unreachable!(),
626             Err(e) => assert_eq!(
627                 e.to_string(),
628                 "Module was compiled for operating system 'unknown'",
629             ),
630         }
631 
632         Ok(())
633     }
634 
635     #[test]
636     fn test_cranelift_flags_mismatch() -> Result<()> {
637         let engine = Engine::default();
638         let mut metadata = Metadata::new(&engine);
639 
640         metadata
641             .shared_flags
642             .push(("preserve_frame_pointers", FlagValue::Bool(false)));
643 
644         match metadata.check_compatible(&engine) {
645             Ok(_) => unreachable!(),
646             Err(e) => assert!(format!("{e:?}").starts_with(
647                 "\
648 compilation settings of module incompatible with native host
649 
650 Caused by:
651     setting \"preserve_frame_pointers\" is configured to Bool(false) which is not supported"
652             )),
653         }
654 
655         Ok(())
656     }
657 
658     #[test]
659     fn test_isa_flags_mismatch() -> Result<()> {
660         let engine = Engine::default();
661         let mut metadata = Metadata::new(&engine);
662 
663         metadata
664             .isa_flags
665             .push(("not_a_flag", FlagValue::Bool(true)));
666 
667         match metadata.check_compatible(&engine) {
668             Ok(_) => unreachable!(),
669             Err(e) => assert!(
670                 format!("{e:?}").starts_with(
671                     "\
672 compilation settings of module incompatible with native host
673 
674 Caused by:
675     don't know how to test for target-specific flag \"not_a_flag\" at runtime",
676                 ),
677                 "bad error {e:?}",
678             ),
679         }
680 
681         Ok(())
682     }
683 
684     #[test]
685     #[cfg_attr(miri, ignore)]
686     fn test_tunables_int_mismatch() -> Result<()> {
687         let engine = Engine::default();
688         let mut metadata = Metadata::new(&engine);
689 
690         metadata.tunables.static_memory_offset_guard_size = 0;
691 
692         match metadata.check_compatible(&engine) {
693             Ok(_) => unreachable!(),
694             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"),
695         }
696 
697         Ok(())
698     }
699 
700     #[test]
701     fn test_tunables_bool_mismatch() -> Result<()> {
702         let mut config = Config::new();
703         config.epoch_interruption(true);
704 
705         let engine = Engine::new(&config)?;
706         let mut metadata = Metadata::new(&engine);
707         metadata.tunables.epoch_interruption = false;
708 
709         match metadata.check_compatible(&engine) {
710             Ok(_) => unreachable!(),
711             Err(e) => assert_eq!(
712                 e.to_string(),
713                 "Module was compiled without epoch interruption but it is enabled for the host"
714             ),
715         }
716 
717         let mut config = Config::new();
718         config.epoch_interruption(false);
719 
720         let engine = Engine::new(&config)?;
721         let mut metadata = Metadata::new(&engine);
722         metadata.tunables.epoch_interruption = true;
723 
724         match metadata.check_compatible(&engine) {
725             Ok(_) => unreachable!(),
726             Err(e) => assert_eq!(
727                 e.to_string(),
728                 "Module was compiled with epoch interruption but it is not enabled for the host"
729             ),
730         }
731 
732         Ok(())
733     }
734 
735     #[test]
736     fn test_feature_mismatch() -> Result<()> {
737         let mut config = Config::new();
738         config.wasm_threads(true);
739 
740         let engine = Engine::new(&config)?;
741         let mut metadata = Metadata::new(&engine);
742         metadata.features.threads = false;
743 
744         match metadata.check_compatible(&engine) {
745             Ok(_) => unreachable!(),
746             Err(e) => assert_eq!(e.to_string(), "Module was compiled without WebAssembly threads support but it is enabled for the host"),
747         }
748 
749         let mut config = Config::new();
750         config.wasm_threads(false);
751 
752         let engine = Engine::new(&config)?;
753         let mut metadata = Metadata::new(&engine);
754         metadata.features.threads = true;
755 
756         match metadata.check_compatible(&engine) {
757             Ok(_) => unreachable!(),
758             Err(e) => assert_eq!(e.to_string(), "Module was compiled with WebAssembly threads support but it is not enabled for the host"),
759         }
760 
761         Ok(())
762     }
763 
764     #[test]
765     fn engine_weak_upgrades() {
766         let engine = Engine::default();
767         let weak = engine.weak();
768         weak.upgrade()
769             .expect("engine is still alive, so weak reference can upgrade");
770         drop(engine);
771         assert!(
772             weak.upgrade().is_none(),
773             "engine was dropped, so weak reference cannot upgrade"
774         );
775     }
776 
777     #[test]
778     #[cfg_attr(miri, ignore)]
779     fn cache_accounts_for_opt_level() -> Result<()> {
780         let td = TempDir::new()?;
781         let config_path = td.path().join("config.toml");
782         std::fs::write(
783             &config_path,
784             &format!(
785                 "
786                     [cache]
787                     enabled = true
788                     directory = '{}'
789                 ",
790                 td.path().join("cache").display()
791             ),
792         )?;
793         let mut cfg = Config::new();
794         cfg.cranelift_opt_level(OptLevel::None)
795             .cache_config_load(&config_path)?;
796         let engine = Engine::new(&cfg)?;
797         Module::new(&engine, "(module (func))")?;
798         assert_eq!(engine.config().cache_config.cache_hits(), 0);
799         assert_eq!(engine.config().cache_config.cache_misses(), 1);
800         Module::new(&engine, "(module (func))")?;
801         assert_eq!(engine.config().cache_config.cache_hits(), 1);
802         assert_eq!(engine.config().cache_config.cache_misses(), 1);
803 
804         let mut cfg = Config::new();
805         cfg.cranelift_opt_level(OptLevel::Speed)
806             .cache_config_load(&config_path)?;
807         let engine = Engine::new(&cfg)?;
808         Module::new(&engine, "(module (func))")?;
809         assert_eq!(engine.config().cache_config.cache_hits(), 0);
810         assert_eq!(engine.config().cache_config.cache_misses(), 1);
811         Module::new(&engine, "(module (func))")?;
812         assert_eq!(engine.config().cache_config.cache_hits(), 1);
813         assert_eq!(engine.config().cache_config.cache_misses(), 1);
814 
815         let mut cfg = Config::new();
816         cfg.cranelift_opt_level(OptLevel::SpeedAndSize)
817             .cache_config_load(&config_path)?;
818         let engine = Engine::new(&cfg)?;
819         Module::new(&engine, "(module (func))")?;
820         assert_eq!(engine.config().cache_config.cache_hits(), 0);
821         assert_eq!(engine.config().cache_config.cache_misses(), 1);
822         Module::new(&engine, "(module (func))")?;
823         assert_eq!(engine.config().cache_config.cache_hits(), 1);
824         assert_eq!(engine.config().cache_config.cache_misses(), 1);
825 
826         let mut cfg = Config::new();
827         cfg.debug_info(true).cache_config_load(&config_path)?;
828         let engine = Engine::new(&cfg)?;
829         Module::new(&engine, "(module (func))")?;
830         assert_eq!(engine.config().cache_config.cache_hits(), 0);
831         assert_eq!(engine.config().cache_config.cache_misses(), 1);
832         Module::new(&engine, "(module (func))")?;
833         assert_eq!(engine.config().cache_config.cache_hits(), 1);
834         assert_eq!(engine.config().cache_config.cache_misses(), 1);
835 
836         Ok(())
837     }
838 
839     #[test]
840     fn precompile_compatibility_key_accounts_for_opt_level() {
841         fn hash_for_config(cfg: &Config) -> u64 {
842             let engine = Engine::new(cfg).expect("Config should be valid");
843             let mut hasher = DefaultHasher::new();
844             engine.precompile_compatibility_hash().hash(&mut hasher);
845             hasher.finish()
846         }
847         let mut cfg = Config::new();
848         cfg.cranelift_opt_level(OptLevel::None);
849         let opt_none_hash = hash_for_config(&cfg);
850         cfg.cranelift_opt_level(OptLevel::Speed);
851         let opt_speed_hash = hash_for_config(&cfg);
852         assert_ne!(opt_none_hash, opt_speed_hash)
853     }
854 
855     #[test]
856     fn precompile_compatibility_key_accounts_for_module_version_strategy() -> Result<()> {
857         fn hash_for_config(cfg: &Config) -> u64 {
858             let engine = Engine::new(cfg).expect("Config should be valid");
859             let mut hasher = DefaultHasher::new();
860             engine.precompile_compatibility_hash().hash(&mut hasher);
861             hasher.finish()
862         }
863         let mut cfg_custom_version = Config::new();
864         cfg_custom_version.module_version(ModuleVersionStrategy::Custom("1.0.1111".to_string()))?;
865         let custom_version_hash = hash_for_config(&cfg_custom_version);
866 
867         let mut cfg_default_version = Config::new();
868         cfg_default_version.module_version(ModuleVersionStrategy::WasmtimeVersion)?;
869         let default_version_hash = hash_for_config(&cfg_default_version);
870 
871         let mut cfg_none_version = Config::new();
872         cfg_none_version.module_version(ModuleVersionStrategy::None)?;
873         let none_version_hash = hash_for_config(&cfg_none_version);
874 
875         assert_ne!(custom_version_hash, default_version_hash);
876         assert_ne!(custom_version_hash, none_version_hash);
877         assert_ne!(default_version_hash, none_version_hash);
878 
879         Ok(())
880     }
881 
882     #[test]
883     #[cfg_attr(miri, ignore)]
884     #[cfg(feature = "component-model")]
885     fn components_are_cached() -> Result<()> {
886         use crate::component::Component;
887 
888         let td = TempDir::new()?;
889         let config_path = td.path().join("config.toml");
890         std::fs::write(
891             &config_path,
892             &format!(
893                 "
894                     [cache]
895                     enabled = true
896                     directory = '{}'
897                 ",
898                 td.path().join("cache").display()
899             ),
900         )?;
901         let mut cfg = Config::new();
902         cfg.cache_config_load(&config_path)?;
903         let engine = Engine::new(&cfg)?;
904         Component::new(&engine, "(component (core module (func)))")?;
905         assert_eq!(engine.config().cache_config.cache_hits(), 0);
906         assert_eq!(engine.config().cache_config.cache_misses(), 1);
907         Component::new(&engine, "(component (core module (func)))")?;
908         assert_eq!(engine.config().cache_config.cache_hits(), 1);
909         assert_eq!(engine.config().cache_config.cache_misses(), 1);
910 
911         Ok(())
912     }
913 }
914