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