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::Endianness;
28 #[cfg(any(feature = "cranelift", feature = "winch"))]
29 use object::write::{Object, StandardSegment};
30 use object::{read::elf::ElfFile64, FileFlags, Object as _, ObjectSection};
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::<Endianness>::parse(mmap)
60         .map_err(obj::ObjectCrateErrorWrapper)
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 == 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         .map_err(obj::ObjectCrateErrorWrapper)?;
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)?;
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)?;
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)?.check_compatible(engine)
118 }
119 
120 #[cfg(any(feature = "cranelift", feature = "winch"))]
121 pub fn append_compiler_info(engine: &Engine, obj: &mut Object<'_>, metadata: &Metadata<'_>) {
122     let section = obj.add_section(
123         obj.segment_name(StandardSegment::Data).to_vec(),
124         obj::ELF_WASM_ENGINE.as_bytes().to_vec(),
125         object::SectionKind::ReadOnlyData,
126     );
127     let mut data = Vec::new();
128     data.push(VERSION);
129     let version = match &engine.config().module_version {
130         ModuleVersionStrategy::WasmtimeVersion => env!("CARGO_PKG_VERSION"),
131         ModuleVersionStrategy::Custom(c) => c,
132         ModuleVersionStrategy::None => "",
133     };
134     // This precondition is checked in Config::module_version:
135     assert!(
136         version.len() < 256,
137         "package version must be less than 256 bytes"
138     );
139     data.push(version.len() as u8);
140     data.extend_from_slice(version.as_bytes());
141     data.extend(postcard::to_allocvec(metadata).unwrap());
142     obj.set_section_data(section, data, 1);
143 }
144 
145 fn detect_precompiled<'data, R: object::ReadRef<'data>>(
146     obj: ElfFile64<'data, Endianness, R>,
147 ) -> Option<Precompiled> {
148     match obj.flags() {
149         FileFlags::Elf {
150             os_abi: obj::ELFOSABI_WASMTIME,
151             abi_version: 0,
152             e_flags,
153         } if e_flags & obj::EF_WASMTIME_MODULE != 0 => Some(Precompiled::Module),
154         FileFlags::Elf {
155             os_abi: obj::ELFOSABI_WASMTIME,
156             abi_version: 0,
157             e_flags,
158         } if e_flags & obj::EF_WASMTIME_COMPONENT != 0 => Some(Precompiled::Component),
159         _ => None,
160     }
161 }
162 
163 pub fn detect_precompiled_bytes(bytes: &[u8]) -> Option<Precompiled> {
164     detect_precompiled(ElfFile64::parse(bytes).ok()?)
165 }
166 
167 #[cfg(feature = "std")]
168 pub fn detect_precompiled_file(path: impl AsRef<std::path::Path>) -> Result<Option<Precompiled>> {
169     let read_cache = object::ReadCache::new(std::fs::File::open(path)?);
170     let obj = ElfFile64::parse(&read_cache)?;
171     Ok(detect_precompiled(obj))
172 }
173 
174 #[derive(Serialize, Deserialize)]
175 pub struct Metadata<'a> {
176     target: String,
177     #[serde(borrow)]
178     shared_flags: Vec<(&'a str, FlagValue<'a>)>,
179     #[serde(borrow)]
180     isa_flags: Vec<(&'a str, FlagValue<'a>)>,
181     tunables: Tunables,
182     features: WasmFeatures,
183 }
184 
185 // This exists because `wasmparser::WasmFeatures` isn't serializable
186 #[derive(Debug, Copy, Clone, Serialize, Deserialize)]
187 struct WasmFeatures {
188     reference_types: bool,
189     multi_value: bool,
190     bulk_memory: bool,
191     component_model: bool,
192     simd: bool,
193     tail_call: bool,
194     threads: bool,
195     multi_memory: bool,
196     exceptions: bool,
197     memory64: bool,
198     relaxed_simd: bool,
199     extended_const: bool,
200     function_references: bool,
201     gc: bool,
202     custom_page_sizes: bool,
203     component_model_async: bool,
204     component_model_async_builtins: bool,
205     component_model_async_stackful: bool,
206     gc_types: bool,
207     wide_arithmetic: bool,
208     stack_switching: bool,
209 }
210 
211 impl Metadata<'_> {
212     #[cfg(any(feature = "cranelift", feature = "winch"))]
213     pub fn new(engine: &Engine) -> Metadata<'static> {
214         let wasmparser::WasmFeaturesInflated {
215             reference_types,
216             multi_value,
217             bulk_memory,
218             component_model,
219             simd,
220             threads,
221             tail_call,
222             multi_memory,
223             exceptions,
224             memory64,
225             relaxed_simd,
226             extended_const,
227             memory_control,
228             function_references,
229             gc,
230             custom_page_sizes,
231             shared_everything_threads,
232             cm_async,
233             cm_async_builtins,
234             cm_async_stackful,
235             cm_nested_names,
236             cm_values,
237             legacy_exceptions,
238             gc_types,
239             stack_switching,
240             wide_arithmetic,
241 
242             // Always on; we don't currently have knobs for these.
243             mutable_global: _,
244             saturating_float_to_int: _,
245             sign_extension: _,
246             floats: _,
247         } = engine.features().inflate();
248 
249         // These features are not implemented in Wasmtime yet. We match on them
250         // above so that once we do implement support for them, we won't
251         // silently ignore them during serialization.
252         assert!(!memory_control);
253         assert!(!cm_nested_names);
254         assert!(!cm_values);
255         assert!(!shared_everything_threads);
256         assert!(!legacy_exceptions);
257 
258         Metadata {
259             target: engine.compiler().triple().to_string(),
260             shared_flags: engine.compiler().flags(),
261             isa_flags: engine.compiler().isa_flags(),
262             tunables: engine.tunables().clone(),
263             features: WasmFeatures {
264                 reference_types,
265                 multi_value,
266                 bulk_memory,
267                 component_model,
268                 simd,
269                 threads,
270                 tail_call,
271                 multi_memory,
272                 exceptions,
273                 memory64,
274                 relaxed_simd,
275                 extended_const,
276                 function_references,
277                 gc,
278                 custom_page_sizes,
279                 gc_types,
280                 wide_arithmetic,
281                 stack_switching,
282                 component_model_async: cm_async,
283                 component_model_async_builtins: cm_async_builtins,
284                 component_model_async_stackful: cm_async_stackful,
285             },
286         }
287     }
288 
289     fn check_compatible(mut self, engine: &Engine) -> Result<()> {
290         self.check_triple(engine)?;
291         self.check_shared_flags(engine)?;
292         self.check_isa_flags(engine)?;
293         self.check_tunables(&engine.tunables())?;
294         self.check_features(&engine.features())?;
295         Ok(())
296     }
297 
298     fn check_triple(&self, engine: &Engine) -> Result<()> {
299         let engine_target = engine.target();
300         let module_target =
301             target_lexicon::Triple::from_str(&self.target).map_err(|e| anyhow!(e))?;
302 
303         if module_target.architecture != engine_target.architecture {
304             bail!(
305                 "Module was compiled for architecture '{}'",
306                 module_target.architecture
307             );
308         }
309 
310         if module_target.operating_system != engine_target.operating_system {
311             bail!(
312                 "Module was compiled for operating system '{}'",
313                 module_target.operating_system
314             );
315         }
316 
317         Ok(())
318     }
319 
320     fn check_shared_flags(&mut self, engine: &Engine) -> Result<()> {
321         for (name, val) in self.shared_flags.iter() {
322             engine
323                 .check_compatible_with_shared_flag(name, val)
324                 .map_err(|s| anyhow::Error::msg(s))
325                 .context("compilation settings of module incompatible with native host")?;
326         }
327         Ok(())
328     }
329 
330     fn check_isa_flags(&mut self, engine: &Engine) -> Result<()> {
331         for (name, val) in self.isa_flags.iter() {
332             engine
333                 .check_compatible_with_isa_flag(name, val)
334                 .map_err(|s| anyhow::Error::msg(s))
335                 .context("compilation settings of module incompatible with native host")?;
336         }
337         Ok(())
338     }
339 
340     fn check_int<T: Eq + core::fmt::Display>(found: T, expected: T, feature: &str) -> Result<()> {
341         if found == expected {
342             return Ok(());
343         }
344 
345         bail!(
346             "Module was compiled with a {} of '{}' but '{}' is expected for the host",
347             feature,
348             found,
349             expected
350         );
351     }
352 
353     fn check_bool(found: bool, expected: bool, feature: &str) -> Result<()> {
354         if found == expected {
355             return Ok(());
356         }
357 
358         bail!(
359             "Module was compiled {} {} but it {} enabled for the host",
360             if found { "with" } else { "without" },
361             feature,
362             if expected { "is" } else { "is not" }
363         );
364     }
365 
366     fn check_tunables(&mut self, other: &Tunables) -> Result<()> {
367         let Tunables {
368             collector,
369             memory_reservation,
370             memory_guard_size,
371             generate_native_debuginfo,
372             parse_wasm_debuginfo,
373             consume_fuel,
374             epoch_interruption,
375             memory_may_move,
376             guard_before_linear_memory,
377             table_lazy_init,
378             relaxed_simd_deterministic,
379             winch_callable,
380             signals_based_traps,
381             memory_init_cow,
382             // This doesn't affect compilation, it's just a runtime setting.
383             memory_reservation_for_growth: _,
384 
385             // This does technically affect compilation but modules with/without
386             // trap information can be loaded into engines with the opposite
387             // setting just fine (it's just a section in the compiled file and
388             // whether it's present or not)
389             generate_address_map: _,
390 
391             // Just a debugging aid, doesn't affect functionality at all.
392             debug_adapter_modules: _,
393         } = self.tunables;
394 
395         Self::check_collector(collector, other.collector)?;
396         Self::check_int(
397             memory_reservation,
398             other.memory_reservation,
399             "memory reservation",
400         )?;
401         Self::check_int(
402             memory_guard_size,
403             other.memory_guard_size,
404             "memory guard size",
405         )?;
406         Self::check_bool(
407             generate_native_debuginfo,
408             other.generate_native_debuginfo,
409             "debug information support",
410         )?;
411         Self::check_bool(
412             parse_wasm_debuginfo,
413             other.parse_wasm_debuginfo,
414             "WebAssembly backtrace support",
415         )?;
416         Self::check_bool(consume_fuel, other.consume_fuel, "fuel support")?;
417         Self::check_bool(
418             epoch_interruption,
419             other.epoch_interruption,
420             "epoch interruption",
421         )?;
422         Self::check_bool(memory_may_move, other.memory_may_move, "memory may move")?;
423         Self::check_bool(
424             guard_before_linear_memory,
425             other.guard_before_linear_memory,
426             "guard before linear memory",
427         )?;
428         Self::check_bool(table_lazy_init, other.table_lazy_init, "table lazy init")?;
429         Self::check_bool(
430             relaxed_simd_deterministic,
431             other.relaxed_simd_deterministic,
432             "relaxed simd deterministic semantics",
433         )?;
434         Self::check_bool(
435             winch_callable,
436             other.winch_callable,
437             "Winch calling convention",
438         )?;
439         Self::check_bool(
440             signals_based_traps,
441             other.signals_based_traps,
442             "Signals-based traps",
443         )?;
444         Self::check_bool(
445             memory_init_cow,
446             other.memory_init_cow,
447             "memory initialization with CoW",
448         )?;
449 
450         Ok(())
451     }
452 
453     fn check_cfg_bool(
454         cfg: bool,
455         cfg_str: &str,
456         found: bool,
457         expected: bool,
458         feature: &str,
459     ) -> Result<()> {
460         if cfg {
461             Self::check_bool(found, expected, feature)
462         } else {
463             assert!(!expected);
464             ensure!(
465                 !found,
466                 "Module was compiled with {feature} but support in the host \
467                  was disabled at compile time because the `{cfg_str}` Cargo \
468                  feature was not enabled",
469             );
470             Ok(())
471         }
472     }
473 
474     fn check_features(&mut self, other: &wasmparser::WasmFeatures) -> Result<()> {
475         let WasmFeatures {
476             reference_types,
477             multi_value,
478             bulk_memory,
479             component_model,
480             simd,
481             tail_call,
482             threads,
483             multi_memory,
484             exceptions,
485             memory64,
486             relaxed_simd,
487             extended_const,
488             function_references,
489             gc,
490             custom_page_sizes,
491             component_model_async,
492             component_model_async_builtins,
493             component_model_async_stackful,
494             gc_types,
495             wide_arithmetic,
496             stack_switching,
497         } = self.features;
498 
499         use wasmparser::WasmFeatures as F;
500         Self::check_bool(
501             reference_types,
502             other.contains(F::REFERENCE_TYPES),
503             "WebAssembly reference types support",
504         )?;
505         Self::check_bool(
506             function_references,
507             other.contains(F::FUNCTION_REFERENCES),
508             "WebAssembly function-references support",
509         )?;
510         Self::check_bool(
511             gc,
512             other.contains(F::GC),
513             "WebAssembly garbage collection support",
514         )?;
515         Self::check_bool(
516             multi_value,
517             other.contains(F::MULTI_VALUE),
518             "WebAssembly multi-value support",
519         )?;
520         Self::check_bool(
521             bulk_memory,
522             other.contains(F::BULK_MEMORY),
523             "WebAssembly bulk memory support",
524         )?;
525         Self::check_bool(
526             component_model,
527             other.contains(F::COMPONENT_MODEL),
528             "WebAssembly component model support",
529         )?;
530         Self::check_bool(simd, other.contains(F::SIMD), "WebAssembly SIMD support")?;
531         Self::check_bool(
532             tail_call,
533             other.contains(F::TAIL_CALL),
534             "WebAssembly tail calls support",
535         )?;
536         Self::check_bool(
537             threads,
538             other.contains(F::THREADS),
539             "WebAssembly threads support",
540         )?;
541         Self::check_bool(
542             multi_memory,
543             other.contains(F::MULTI_MEMORY),
544             "WebAssembly multi-memory support",
545         )?;
546         Self::check_bool(
547             exceptions,
548             other.contains(F::EXCEPTIONS),
549             "WebAssembly exceptions support",
550         )?;
551         Self::check_bool(
552             memory64,
553             other.contains(F::MEMORY64),
554             "WebAssembly 64-bit memory support",
555         )?;
556         Self::check_bool(
557             extended_const,
558             other.contains(F::EXTENDED_CONST),
559             "WebAssembly extended-const support",
560         )?;
561         Self::check_bool(
562             relaxed_simd,
563             other.contains(F::RELAXED_SIMD),
564             "WebAssembly relaxed-simd support",
565         )?;
566         Self::check_bool(
567             custom_page_sizes,
568             other.contains(F::CUSTOM_PAGE_SIZES),
569             "WebAssembly custom-page-sizes support",
570         )?;
571         Self::check_bool(
572             component_model_async,
573             other.contains(F::CM_ASYNC),
574             "WebAssembly component model support for async lifts/lowers, futures, streams, and errors",
575         )?;
576         Self::check_bool(
577             component_model_async_builtins,
578             other.contains(F::CM_ASYNC_BUILTINS),
579             "WebAssembly component model support for async builtins",
580         )?;
581         Self::check_bool(
582             component_model_async_stackful,
583             other.contains(F::CM_ASYNC_STACKFUL),
584             "WebAssembly component model support for async stackful",
585         )?;
586         Self::check_cfg_bool(
587             cfg!(feature = "gc"),
588             "gc",
589             gc_types,
590             other.contains(F::GC_TYPES),
591             "support for WebAssembly gc types",
592         )?;
593         Self::check_bool(
594             wide_arithmetic,
595             other.contains(F::WIDE_ARITHMETIC),
596             "WebAssembly wide-arithmetic support",
597         )?;
598         Self::check_bool(
599             stack_switching,
600             other.contains(F::STACK_SWITCHING),
601             "WebAssembly stack switching support",
602         )?;
603         Ok(())
604     }
605 
606     fn check_collector(
607         module: Option<wasmtime_environ::Collector>,
608         host: Option<wasmtime_environ::Collector>,
609     ) -> Result<()> {
610         match (module, host) {
611             (None, None) => Ok(()),
612             (Some(module), Some(host)) if module == host => Ok(()),
613 
614             (None, Some(_)) => {
615                 bail!("module was compiled without GC but GC is enabled in the host")
616             }
617             (Some(_), None) => {
618                 bail!("module was compiled with GC however GC is disabled in the host")
619             }
620 
621             (Some(module), Some(host)) => {
622                 bail!(
623                     "module was compiled for the {module} collector but \
624                      the host is configured to use the {host} collector",
625                 )
626             }
627         }
628     }
629 }
630 
631 #[cfg(test)]
632 mod test {
633     use super::*;
634     use crate::{Config, Module, OptLevel};
635     use std::{
636         collections::hash_map::DefaultHasher,
637         hash::{Hash, Hasher},
638     };
639     use tempfile::TempDir;
640 
641     #[test]
642     fn test_architecture_mismatch() -> Result<()> {
643         let engine = Engine::default();
644         let mut metadata = Metadata::new(&engine);
645         metadata.target = "unknown-generic-linux".to_string();
646 
647         match metadata.check_compatible(&engine) {
648             Ok(_) => unreachable!(),
649             Err(e) => assert_eq!(
650                 e.to_string(),
651                 "Module was compiled for architecture 'unknown'",
652             ),
653         }
654 
655         Ok(())
656     }
657 
658     #[test]
659     #[cfg(target_arch = "x86_64")] // test on a platform that is known to use
660                                    // Cranelift
661     fn test_os_mismatch() -> Result<()> {
662         let engine = Engine::default();
663         let mut metadata = Metadata::new(&engine);
664 
665         metadata.target = format!(
666             "{}-generic-unknown",
667             target_lexicon::Triple::host().architecture
668         );
669 
670         match metadata.check_compatible(&engine) {
671             Ok(_) => unreachable!(),
672             Err(e) => assert_eq!(
673                 e.to_string(),
674                 "Module was compiled for operating system 'unknown'",
675             ),
676         }
677 
678         Ok(())
679     }
680 
681     #[test]
682     fn test_cranelift_flags_mismatch() -> Result<()> {
683         let engine = Engine::default();
684         let mut metadata = Metadata::new(&engine);
685 
686         metadata
687             .shared_flags
688             .push(("preserve_frame_pointers", FlagValue::Bool(false)));
689 
690         match metadata.check_compatible(&engine) {
691             Ok(_) => unreachable!(),
692             Err(e) => assert!(format!("{e:?}").starts_with(
693                 "\
694 compilation settings of module incompatible with native host
695 
696 Caused by:
697     setting \"preserve_frame_pointers\" is configured to Bool(false) which is not supported"
698             )),
699         }
700 
701         Ok(())
702     }
703 
704     #[test]
705     fn test_isa_flags_mismatch() -> Result<()> {
706         let engine = Engine::default();
707         let mut metadata = Metadata::new(&engine);
708 
709         metadata
710             .isa_flags
711             .push(("not_a_flag", FlagValue::Bool(true)));
712 
713         match metadata.check_compatible(&engine) {
714             Ok(_) => unreachable!(),
715             Err(e) => assert!(
716                 format!("{e:?}").starts_with(
717                     "\
718 compilation settings of module incompatible with native host
719 
720 Caused by:
721     don't know how to test for target-specific flag \"not_a_flag\" at runtime",
722                 ),
723                 "bad error {e:?}",
724             ),
725         }
726 
727         Ok(())
728     }
729 
730     #[test]
731     #[cfg_attr(miri, ignore)]
732     #[cfg(target_pointer_width = "64")] // different defaults on 32-bit platforms
733     fn test_tunables_int_mismatch() -> Result<()> {
734         let engine = Engine::default();
735         let mut metadata = Metadata::new(&engine);
736 
737         metadata.tunables.memory_guard_size = 0;
738 
739         match metadata.check_compatible(&engine) {
740             Ok(_) => unreachable!(),
741             Err(e) => assert_eq!(e.to_string(), "Module was compiled with a memory guard size of '0' but '33554432' is expected for the host"),
742         }
743 
744         Ok(())
745     }
746 
747     #[test]
748     fn test_tunables_bool_mismatch() -> Result<()> {
749         let mut config = Config::new();
750         config.epoch_interruption(true);
751 
752         let engine = Engine::new(&config)?;
753         let mut metadata = Metadata::new(&engine);
754         metadata.tunables.epoch_interruption = false;
755 
756         match metadata.check_compatible(&engine) {
757             Ok(_) => unreachable!(),
758             Err(e) => assert_eq!(
759                 e.to_string(),
760                 "Module was compiled without epoch interruption but it is enabled for the host"
761             ),
762         }
763 
764         let mut config = Config::new();
765         config.epoch_interruption(false);
766 
767         let engine = Engine::new(&config)?;
768         let mut metadata = Metadata::new(&engine);
769         metadata.tunables.epoch_interruption = true;
770 
771         match metadata.check_compatible(&engine) {
772             Ok(_) => unreachable!(),
773             Err(e) => assert_eq!(
774                 e.to_string(),
775                 "Module was compiled with epoch interruption but it is not enabled for the host"
776             ),
777         }
778 
779         Ok(())
780     }
781 
782     #[test]
783     #[cfg(target_arch = "x86_64")] // test on a platform that is known to
784                                    // implement threads
785     fn test_feature_mismatch() -> Result<()> {
786         let mut config = Config::new();
787         config.wasm_threads(true);
788 
789         let engine = Engine::new(&config)?;
790         let mut metadata = Metadata::new(&engine);
791         metadata.features.threads = false;
792 
793         match metadata.check_compatible(&engine) {
794             Ok(_) => unreachable!(),
795             Err(e) => assert_eq!(e.to_string(), "Module was compiled without WebAssembly threads support but it is enabled for the host"),
796         }
797 
798         let mut config = Config::new();
799         config.wasm_threads(false);
800 
801         let engine = Engine::new(&config)?;
802         let mut metadata = Metadata::new(&engine);
803         metadata.features.threads = true;
804 
805         match metadata.check_compatible(&engine) {
806             Ok(_) => unreachable!(),
807             Err(e) => assert_eq!(e.to_string(), "Module was compiled with WebAssembly threads support but it is not enabled for the host"),
808         }
809 
810         Ok(())
811     }
812 
813     #[test]
814     fn engine_weak_upgrades() {
815         let engine = Engine::default();
816         let weak = engine.weak();
817         weak.upgrade()
818             .expect("engine is still alive, so weak reference can upgrade");
819         drop(engine);
820         assert!(
821             weak.upgrade().is_none(),
822             "engine was dropped, so weak reference cannot upgrade"
823         );
824     }
825 
826     #[test]
827     #[cfg_attr(miri, ignore)]
828     fn cache_accounts_for_opt_level() -> Result<()> {
829         let td = TempDir::new()?;
830         let config_path = td.path().join("config.toml");
831         std::fs::write(
832             &config_path,
833             &format!(
834                 "
835                     [cache]
836                     enabled = true
837                     directory = '{}'
838                 ",
839                 td.path().join("cache").display()
840             ),
841         )?;
842         let mut cfg = Config::new();
843         cfg.cranelift_opt_level(OptLevel::None)
844             .cache_config_load(&config_path)?;
845         let engine = Engine::new(&cfg)?;
846         Module::new(&engine, "(module (func))")?;
847         assert_eq!(engine.config().cache_config.cache_hits(), 0);
848         assert_eq!(engine.config().cache_config.cache_misses(), 1);
849         Module::new(&engine, "(module (func))")?;
850         assert_eq!(engine.config().cache_config.cache_hits(), 1);
851         assert_eq!(engine.config().cache_config.cache_misses(), 1);
852 
853         let mut cfg = Config::new();
854         cfg.cranelift_opt_level(OptLevel::Speed)
855             .cache_config_load(&config_path)?;
856         let engine = Engine::new(&cfg)?;
857         Module::new(&engine, "(module (func))")?;
858         assert_eq!(engine.config().cache_config.cache_hits(), 0);
859         assert_eq!(engine.config().cache_config.cache_misses(), 1);
860         Module::new(&engine, "(module (func))")?;
861         assert_eq!(engine.config().cache_config.cache_hits(), 1);
862         assert_eq!(engine.config().cache_config.cache_misses(), 1);
863 
864         let mut cfg = Config::new();
865         cfg.cranelift_opt_level(OptLevel::SpeedAndSize)
866             .cache_config_load(&config_path)?;
867         let engine = Engine::new(&cfg)?;
868         Module::new(&engine, "(module (func))")?;
869         assert_eq!(engine.config().cache_config.cache_hits(), 0);
870         assert_eq!(engine.config().cache_config.cache_misses(), 1);
871         Module::new(&engine, "(module (func))")?;
872         assert_eq!(engine.config().cache_config.cache_hits(), 1);
873         assert_eq!(engine.config().cache_config.cache_misses(), 1);
874 
875         let mut cfg = Config::new();
876         cfg.debug_info(true).cache_config_load(&config_path)?;
877         let engine = Engine::new(&cfg)?;
878         Module::new(&engine, "(module (func))")?;
879         assert_eq!(engine.config().cache_config.cache_hits(), 0);
880         assert_eq!(engine.config().cache_config.cache_misses(), 1);
881         Module::new(&engine, "(module (func))")?;
882         assert_eq!(engine.config().cache_config.cache_hits(), 1);
883         assert_eq!(engine.config().cache_config.cache_misses(), 1);
884 
885         Ok(())
886     }
887 
888     #[test]
889     fn precompile_compatibility_key_accounts_for_opt_level() {
890         fn hash_for_config(cfg: &Config) -> u64 {
891             let engine = Engine::new(cfg).expect("Config should be valid");
892             let mut hasher = DefaultHasher::new();
893             engine.precompile_compatibility_hash().hash(&mut hasher);
894             hasher.finish()
895         }
896         let mut cfg = Config::new();
897         cfg.cranelift_opt_level(OptLevel::None);
898         let opt_none_hash = hash_for_config(&cfg);
899         cfg.cranelift_opt_level(OptLevel::Speed);
900         let opt_speed_hash = hash_for_config(&cfg);
901         assert_ne!(opt_none_hash, opt_speed_hash)
902     }
903 
904     #[test]
905     fn precompile_compatibility_key_accounts_for_module_version_strategy() -> Result<()> {
906         fn hash_for_config(cfg: &Config) -> u64 {
907             let engine = Engine::new(cfg).expect("Config should be valid");
908             let mut hasher = DefaultHasher::new();
909             engine.precompile_compatibility_hash().hash(&mut hasher);
910             hasher.finish()
911         }
912         let mut cfg_custom_version = Config::new();
913         cfg_custom_version.module_version(ModuleVersionStrategy::Custom("1.0.1111".to_string()))?;
914         let custom_version_hash = hash_for_config(&cfg_custom_version);
915 
916         let mut cfg_default_version = Config::new();
917         cfg_default_version.module_version(ModuleVersionStrategy::WasmtimeVersion)?;
918         let default_version_hash = hash_for_config(&cfg_default_version);
919 
920         let mut cfg_none_version = Config::new();
921         cfg_none_version.module_version(ModuleVersionStrategy::None)?;
922         let none_version_hash = hash_for_config(&cfg_none_version);
923 
924         assert_ne!(custom_version_hash, default_version_hash);
925         assert_ne!(custom_version_hash, none_version_hash);
926         assert_ne!(default_version_hash, none_version_hash);
927 
928         Ok(())
929     }
930 
931     #[test]
932     #[cfg_attr(miri, ignore)]
933     #[cfg(feature = "component-model")]
934     fn components_are_cached() -> Result<()> {
935         use crate::component::Component;
936 
937         let td = TempDir::new()?;
938         let config_path = td.path().join("config.toml");
939         std::fs::write(
940             &config_path,
941             &format!(
942                 "
943                     [cache]
944                     enabled = true
945                     directory = '{}'
946                 ",
947                 td.path().join("cache").display()
948             ),
949         )?;
950         let mut cfg = Config::new();
951         cfg.cache_config_load(&config_path)?;
952         let engine = Engine::new(&cfg)?;
953         Component::new(&engine, "(component (core module (func)))")?;
954         assert_eq!(engine.config().cache_config.cache_hits(), 0);
955         assert_eq!(engine.config().cache_config.cache_misses(), 1);
956         Component::new(&engine, "(component (core module (func)))")?;
957         assert_eq!(engine.config().cache_config.cache_hits(), 1);
958         assert_eq!(engine.config().cache_config.cache_misses(), 1);
959 
960         Ok(())
961     }
962 }
963