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