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