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