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