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