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