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