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