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::{
32 FileFlags, Object as _,
33 elf::FileHeader64,
34 read::elf::{ElfFile64, FileHeader, SectionHeader},
35};
36use serde_derive::{Deserialize, Serialize};
37use wasmtime_environ::{FlagValue, ObjectKind, OperatorCostStrategy, Tunables, obj};
38
39const VERSION: u8 = 0;
40
41pub fn check_compatible(engine: &Engine, mmap: &[u8], expected: ObjectKind) -> Result<()> {
50 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).contains(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 => { }
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"))]
131pub 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 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
151fn 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.contains(obj::EF_WASMTIME_MODULE) => Some(Precompiled::Module),
160 FileFlags::Elf {
161 os_abi: obj::ELFOSABI_WASMTIME,
162 abi_version: 0,
163 e_flags,
164 } if e_flags.contains(obj::EF_WASMTIME_COMPONENT) => Some(Precompiled::Component),
165 _ => None,
166 }
167}
168
169pub fn detect_precompiled_bytes(bytes: &[u8]) -> Option<Precompiled> {
170 detect_precompiled(ElfFile64::parse(bytes).ok()?)
171}
172
173#[cfg(feature = "std")]
174pub 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)]
181pub 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
191impl Metadata<'_> {
192 #[cfg(any(feature = "cranelift", feature = "winch"))]
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
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
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
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
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
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
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
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
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 debug_symbols,
302 parse_wasm_debuginfo,
303 consume_fuel,
304 ref operator_cost,
305 epoch_interruption,
306 mmu_interruption,
307 memory_may_move,
308 guard_before_linear_memory,
309 table_lazy_init,
310 relaxed_simd_deterministic,
311 winch_callable,
312 signals_based_traps,
313 memory_init_cow,
314 inlining,
315 inlining_small_callee_size,
316 inlining_sum_size_threshold,
317 concurrency_support,
318 recording,
319 asan_stack_switching,
320 memory_watchpoints,
321
322 memory_reservation_for_growth: _,
324
325 generate_address_map: _,
330
331 debug_adapter_modules: _,
333
334 gc_zeal_alloc_counter: _,
336
337 gc_heap_reservation,
338 gc_heap_guard_size,
339 gc_heap_may_move,
340 gc_heap_initial_size,
341
342 gc_heap_reservation_for_growth: _,
344
345 metadata_for_internal_asserts: _,
349 metadata_for_gc_heap_corruption: _,
350
351 branch_hinting: _,
354 } = self.tunables;
355
356 Self::check_collector(collector, other.collector)?;
357 Self::check_int(
358 memory_reservation,
359 other.memory_reservation,
360 "memory reservation",
361 )?;
362 Self::check_int(
363 memory_guard_size,
364 other.memory_guard_size,
365 "memory guard size",
366 )?;
367 Self::check_bool(
368 debug_native,
369 other.debug_native,
370 "native debug information support",
371 )?;
372 Self::check_bool(debug_guest, other.debug_guest, "guest debug")?;
373 Self::check_bool(debug_symbols, other.debug_symbols, "debug symbols")?;
374 Self::check_bool(
375 parse_wasm_debuginfo,
376 other.parse_wasm_debuginfo,
377 "WebAssembly backtrace support",
378 )?;
379 Self::check_bool(consume_fuel, other.consume_fuel, "fuel support")?;
380 Self::check_cost(consume_fuel, operator_cost, &other.operator_cost)?;
381 Self::check_bool(
382 epoch_interruption,
383 other.epoch_interruption,
384 "epoch interruption",
385 )?;
386 Self::check_bool(mmu_interruption, other.mmu_interruption, "MMU interruption")?;
387 Self::check_bool(memory_may_move, other.memory_may_move, "memory may move")?;
388 Self::check_bool(
389 guard_before_linear_memory,
390 other.guard_before_linear_memory,
391 "guard before linear memory",
392 )?;
393 Self::check_bool(table_lazy_init, other.table_lazy_init, "table lazy init")?;
394 Self::check_bool(
395 relaxed_simd_deterministic,
396 other.relaxed_simd_deterministic,
397 "relaxed simd deterministic semantics",
398 )?;
399 Self::check_bool(
400 winch_callable,
401 other.winch_callable,
402 "Winch calling convention",
403 )?;
404 Self::check_bool(
405 signals_based_traps,
406 other.signals_based_traps,
407 "Signals-based traps",
408 )?;
409 Self::check_bool(
410 memory_init_cow,
411 other.memory_init_cow,
412 "memory initialization with CoW",
413 )?;
414 Self::check_int(
415 inlining_small_callee_size,
416 other.inlining_small_callee_size,
417 "function inlining small-callee size",
418 )?;
419 Self::check_int(
420 inlining_sum_size_threshold,
421 other.inlining_sum_size_threshold,
422 "function inlining sum-size threshold",
423 )?;
424 Self::check_bool(
425 concurrency_support,
426 other.concurrency_support,
427 "concurrency support",
428 )?;
429 Self::check_bool(recording, other.recording, "RR recording support")?;
430 Self::check_bool(
431 asan_stack_switching,
432 other.asan_stack_switching,
433 "AddressSanitizer stack-switching support",
434 )?;
435 Self::check_bool(
436 memory_watchpoints,
437 other.memory_watchpoints,
438 "memory watchpoint support",
439 )?;
440 Self::check_inlining(inlining, other.inlining)?;
441 Self::check_int(
442 gc_heap_reservation,
443 other.gc_heap_reservation,
444 "GC heap reservation",
445 )?;
446 Self::check_int(
447 gc_heap_guard_size,
448 other.gc_heap_guard_size,
449 "GC heap guard size",
450 )?;
451 Self::check_int(
452 gc_heap_initial_size,
453 other.gc_heap_initial_size,
454 "GC heap initial size",
455 )?;
456 Self::check_bool(gc_heap_may_move, other.gc_heap_may_move, "GC heap may move")?;
457
458 Ok(())
459 }
460
461 fn check_features(&mut self, other: &wasmparser::WasmFeatures) -> Result<()> {
462 let module_features = wasmparser::WasmFeatures::from_bits_truncate(self.features);
463 let missing_features = (*other & module_features) ^ module_features;
464 for (name, _) in missing_features.iter_names() {
465 let name = name.to_ascii_lowercase();
466 bail!(
467 "Module was compiled with support for WebAssembly feature \
468 `{name}` but it is not enabled for the host",
469 );
470 }
471 Ok(())
472 }
473
474 fn check_collector(
475 module: Option<wasmtime_environ::Collector>,
476 host: Option<wasmtime_environ::Collector>,
477 ) -> Result<()> {
478 match (module, host) {
479 (None, _) => Ok(()),
482 (Some(module), Some(host)) if module == host => Ok(()),
483
484 (Some(_), None) => {
485 bail!("module was compiled with GC however GC is disabled in the host")
486 }
487
488 (Some(module), Some(host)) => {
489 bail!(
490 "module was compiled for the {module} collector but \
491 the host is configured to use the {host} collector",
492 )
493 }
494 }
495 }
496
497 fn check_inlining(
498 module: wasmtime_environ::Inlining,
499 host: wasmtime_environ::Inlining,
500 ) -> Result<()> {
501 if module == host {
502 return Ok(());
503 }
504
505 let desc = |cfg| match cfg {
506 wasmtime_environ::Inlining::No => "without intra-module inlining",
507 wasmtime_environ::Inlining::Yes => "with intra-module inlining",
508 wasmtime_environ::Inlining::InterModuleAndIntraGc => {
509 "with intra-module inlining only when using GC"
510 }
511 wasmtime_environ::Inlining::Intrinsics => "with intrinsic inlining",
512 wasmtime_environ::Inlining::InterModule => "with inter-module inlining",
513 };
514
515 let module = desc(module);
516 let host = desc(host);
517
518 bail!("module was compiled {module} however the host is configured {host}")
519 }
520}
521
522#[cfg(test)]
523mod test {
524 use super::*;
525 use crate::{Cache, Config, Module, OptLevel};
526 use std::{
527 collections::hash_map::DefaultHasher,
528 hash::{Hash, Hasher},
529 };
530 use tempfile::TempDir;
531
532 #[test]
533 fn test_architecture_mismatch() -> Result<()> {
534 let engine = Engine::default();
535 let mut metadata = Metadata::new(&engine)?;
536 metadata.target = "unknown-generic-linux".to_string().into();
537
538 match metadata.check_compatible(&engine) {
539 Ok(_) => unreachable!(),
540 Err(e) => assert_eq!(
541 e.to_string(),
542 "Module was compiled for architecture 'unknown'",
543 ),
544 }
545
546 Ok(())
547 }
548
549 #[test]
551 #[cfg(all(target_arch = "x86_64", not(miri)))]
552 fn test_os_mismatch() -> Result<()> {
553 let engine = Engine::default();
554 let mut metadata = Metadata::new(&engine)?;
555
556 metadata.target = format!(
557 "{}-generic-unknown",
558 target_lexicon::Triple::host().architecture
559 )
560 .into();
561
562 match metadata.check_compatible(&engine) {
563 Ok(_) => unreachable!(),
564 Err(e) => assert_eq!(
565 e.to_string(),
566 "Module was compiled for operating system 'unknown'",
567 ),
568 }
569
570 Ok(())
571 }
572
573 fn assert_contains(error: &Error, msg: &str) {
574 let msg = msg.trim();
575 if error.chain().any(|e| e.to_string().contains(msg)) {
576 return;
577 }
578
579 panic!("failed to find:\n\n'''{msg}\n'''\n\nwithin error message:\n\n'''{error:?}'''")
580 }
581
582 #[test]
583 fn test_cranelift_flags_mismatch() -> Result<()> {
584 let engine = Engine::default();
585 let mut metadata = Metadata::new(&engine)?;
586
587 metadata
588 .shared_flags
589 .push(("preserve_frame_pointers", FlagValue::Bool(false)))?;
590
591 match metadata.check_compatible(&engine) {
592 Ok(_) => unreachable!(),
593 Err(e) => {
594 assert_contains(
595 &e,
596 "compilation settings of module incompatible with native host",
597 );
598 assert_contains(
599 &e,
600 "setting \"preserve_frame_pointers\" is configured to Bool(false) which is not supported",
601 );
602 }
603 }
604
605 Ok(())
606 }
607
608 #[test]
609 fn test_isa_flags_mismatch() -> Result<()> {
610 let engine = Engine::default();
611 let mut metadata = Metadata::new(&engine)?;
612
613 metadata
614 .isa_flags
615 .push(("not_a_flag", FlagValue::Bool(true)))?;
616
617 match metadata.check_compatible(&engine) {
618 Ok(_) => unreachable!(),
619 Err(e) => {
620 assert_contains(
621 &e,
622 "compilation settings of module incompatible with native host",
623 );
624 assert_contains(
625 &e,
626 "don't know how to test for target-specific flag \"not_a_flag\" at runtime",
627 );
628 }
629 }
630
631 Ok(())
632 }
633
634 #[test]
635 #[cfg_attr(any(miri, not(has_native_signals)), ignore)]
636 #[cfg(target_pointer_width = "64")] fn test_tunables_int_mismatch() -> Result<()> {
638 let engine = Engine::default();
639 let mut metadata = Metadata::new(&engine)?;
640
641 metadata.tunables.memory_guard_size = 0;
642
643 match metadata.check_compatible(&engine) {
644 Ok(_) => unreachable!(),
645 Err(e) => assert_eq!(
646 e.to_string(),
647 "Module was compiled with a memory guard size of '0' but '33554432' is expected for the host"
648 ),
649 }
650
651 Ok(())
652 }
653
654 #[test]
655 fn test_tunables_bool_mismatch() -> Result<()> {
656 let mut config = Config::new();
657 config.epoch_interruption(true);
658
659 let engine = Engine::new(&config)?;
660 let mut metadata = Metadata::new(&engine)?;
661 metadata.tunables.epoch_interruption = false;
662
663 match metadata.check_compatible(&engine) {
664 Ok(_) => unreachable!(),
665 Err(e) => assert_eq!(
666 e.to_string(),
667 "Module was compiled without epoch interruption but it is enabled for the host"
668 ),
669 }
670
671 let mut config = Config::new();
672 config.epoch_interruption(false);
673
674 let engine = Engine::new(&config)?;
675 let mut metadata = Metadata::new(&engine)?;
676 metadata.tunables.epoch_interruption = true;
677
678 match metadata.check_compatible(&engine) {
679 Ok(_) => unreachable!(),
680 Err(e) => assert_eq!(
681 e.to_string(),
682 "Module was compiled with epoch interruption but it is not enabled for the host"
683 ),
684 }
685
686 Ok(())
687 }
688
689 #[test]
691 #[cfg(all(target_arch = "x86_64", not(miri)))]
692 fn test_feature_mismatch() -> Result<()> {
693 let mut config = Config::new();
694 config.wasm_threads(true);
695
696 let engine = Engine::new(&config)?;
697 let mut metadata = Metadata::new(&engine)?;
698 metadata.features &= !wasmparser::WasmFeatures::THREADS.bits();
699
700 metadata.check_compatible(&engine)?;
703
704 let mut config = Config::new();
705 config.wasm_threads(false);
706
707 let engine = Engine::new(&config)?;
708 let mut metadata = Metadata::new(&engine)?;
709 metadata.features |= wasmparser::WasmFeatures::THREADS.bits();
710
711 match metadata.check_compatible(&engine) {
712 Ok(_) => unreachable!(),
713 Err(e) => assert_eq!(
714 e.to_string(),
715 "Module was compiled with support for WebAssembly feature \
716 `threads` but it is not enabled for the host"
717 ),
718 }
719
720 Ok(())
721 }
722
723 #[test]
724 fn engine_weak_upgrades() {
725 let engine = Engine::default();
726 let weak = engine.weak();
727 weak.upgrade()
728 .expect("engine is still alive, so weak reference can upgrade");
729 drop(engine);
730 assert!(
731 weak.upgrade().is_none(),
732 "engine was dropped, so weak reference cannot upgrade"
733 );
734 }
735
736 #[test]
737 #[cfg_attr(miri, ignore)]
738 fn cache_accounts_for_opt_level() -> Result<()> {
739 let _ = env_logger::try_init();
740
741 let td = TempDir::new()?;
742 let config_path = td.path().join("config.toml");
743 std::fs::write(
744 &config_path,
745 &format!(
746 "
747 [cache]
748 directory = '{}'
749 ",
750 td.path().join("cache").display()
751 ),
752 )?;
753 let mut cfg = Config::new();
754 cfg.cranelift_opt_level(OptLevel::None)
755 .cache(Some(Cache::from_file(Some(&config_path))?));
756 let engine = Engine::new(&cfg)?;
757 Module::new(&engine, "(module (func))")?;
758 let cache_config = engine
759 .config()
760 .cache
761 .as_ref()
762 .expect("Missing cache config");
763 assert_eq!(cache_config.cache_hits(), 0);
764 assert_eq!(cache_config.cache_misses(), 1);
765 Module::new(&engine, "(module (func))")?;
766 assert_eq!(cache_config.cache_hits(), 1);
767 assert_eq!(cache_config.cache_misses(), 1);
768
769 let mut cfg = Config::new();
770 cfg.cranelift_opt_level(OptLevel::Speed)
771 .cache(Some(Cache::from_file(Some(&config_path))?));
772 let engine = Engine::new(&cfg)?;
773 let cache_config = engine
774 .config()
775 .cache
776 .as_ref()
777 .expect("Missing cache config");
778 Module::new(&engine, "(module (func))")?;
779 assert_eq!(cache_config.cache_hits(), 0);
780 assert_eq!(cache_config.cache_misses(), 1);
781 Module::new(&engine, "(module (func))")?;
782 assert_eq!(cache_config.cache_hits(), 1);
783 assert_eq!(cache_config.cache_misses(), 1);
784
785 let mut cfg = Config::new();
786 cfg.cranelift_opt_level(OptLevel::SpeedAndSize)
787 .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 Module::new(&engine, "(module (func))")?;
795 assert_eq!(cache_config.cache_hits(), 0);
796 assert_eq!(cache_config.cache_misses(), 1);
797 Module::new(&engine, "(module (func))")?;
798 assert_eq!(cache_config.cache_hits(), 1);
799 assert_eq!(cache_config.cache_misses(), 1);
800
801 let mut cfg = Config::new();
802 cfg.debug_info(true)
803 .cache(Some(Cache::from_file(Some(&config_path))?));
804 let engine = Engine::new(&cfg)?;
805 let cache_config = engine
806 .config()
807 .cache
808 .as_ref()
809 .expect("Missing cache config");
810 Module::new(&engine, "(module (func))")?;
811 assert_eq!(cache_config.cache_hits(), 0);
812 assert_eq!(cache_config.cache_misses(), 1);
813 Module::new(&engine, "(module (func))")?;
814 assert_eq!(cache_config.cache_hits(), 1);
815 assert_eq!(cache_config.cache_misses(), 1);
816
817 Ok(())
818 }
819
820 #[test]
821 fn precompile_compatibility_key_accounts_for_opt_level() {
822 fn hash_for_config(cfg: &Config) -> u64 {
823 let engine = Engine::new(cfg).expect("Config should be valid");
824 let mut hasher = DefaultHasher::new();
825 engine.precompile_compatibility_hash().hash(&mut hasher);
826 hasher.finish()
827 }
828 let mut cfg = Config::new();
829 cfg.cranelift_opt_level(OptLevel::None);
830 let opt_none_hash = hash_for_config(&cfg);
831 cfg.cranelift_opt_level(OptLevel::Speed);
832 let opt_speed_hash = hash_for_config(&cfg);
833 assert_ne!(opt_none_hash, opt_speed_hash)
834 }
835
836 #[test]
837 fn precompile_compatibility_key_accounts_for_module_version_strategy() -> Result<()> {
838 fn hash_for_config(cfg: &Config) -> u64 {
839 let engine = Engine::new(cfg).expect("Config should be valid");
840 let mut hasher = DefaultHasher::new();
841 engine.precompile_compatibility_hash().hash(&mut hasher);
842 hasher.finish()
843 }
844 let mut cfg_custom_version = Config::new();
845 cfg_custom_version.module_version(ModuleVersionStrategy::Custom("1.0.1111".to_string()))?;
846 let custom_version_hash = hash_for_config(&cfg_custom_version);
847
848 let mut cfg_default_version = Config::new();
849 cfg_default_version.module_version(ModuleVersionStrategy::WasmtimeVersion)?;
850 let default_version_hash = hash_for_config(&cfg_default_version);
851
852 let mut cfg_none_version = Config::new();
853 cfg_none_version.module_version(ModuleVersionStrategy::None)?;
854 let none_version_hash = hash_for_config(&cfg_none_version);
855
856 assert_ne!(custom_version_hash, default_version_hash);
857 assert_ne!(custom_version_hash, none_version_hash);
858 assert_ne!(default_version_hash, none_version_hash);
859
860 Ok(())
861 }
862
863 #[test]
864 #[cfg_attr(miri, ignore)]
865 #[cfg(feature = "component-model")]
866 fn components_are_cached() -> Result<()> {
867 use crate::component::Component;
868
869 let td = TempDir::new()?;
870 let config_path = td.path().join("config.toml");
871 std::fs::write(
872 &config_path,
873 &format!(
874 "
875 [cache]
876 directory = '{}'
877 ",
878 td.path().join("cache").display()
879 ),
880 )?;
881 let mut cfg = Config::new();
882 cfg.cache(Some(Cache::from_file(Some(&config_path))?));
883 let engine = Engine::new(&cfg)?;
884 let cache_config = engine
885 .config()
886 .cache
887 .as_ref()
888 .expect("Missing cache config");
889 Component::new(&engine, "(component (core module (func)))")?;
890 assert_eq!(cache_config.cache_hits(), 0);
891 assert_eq!(cache_config.cache_misses(), 1);
892 Component::new(&engine, "(component (core module (func)))")?;
893 assert_eq!(cache_config.cache_hits(), 1);
894 assert_eq!(cache_config.cache_misses(), 1);
895
896 Ok(())
897 }
898}