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