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