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::{FileFlags, Object as _, ObjectSection, read::elf::ElfFile64}; 32 use serde_derive::{Deserialize, Serialize}; 33 use wasmtime_environ::obj; 34 use wasmtime_environ::{FlagValue, ObjectKind, Tunables}; 35 36 const VERSION: u8 = 0; 37 38 /// Verifies that the serialized engine in `mmap` is compatible with the 39 /// `engine` provided. 40 /// 41 /// This function will verify that the `mmap` provided can be deserialized 42 /// successfully and that the contents are all compatible with the `engine` 43 /// provided here, notably compatible wasm features are enabled, compatible 44 /// compiler options, etc. If a mismatch is found and the compilation metadata 45 /// specified is incompatible then an error is returned. 46 pub fn check_compatible(engine: &Engine, mmap: &[u8], expected: ObjectKind) -> Result<()> { 47 // Parse the input `mmap` as an ELF file and see if the header matches the 48 // Wasmtime-generated header. This includes a Wasmtime-specific `os_abi` and 49 // the `e_flags` field should indicate whether `expected` matches or not. 50 // 51 // Note that errors generated here could mean that a precompiled module was 52 // loaded as a component, or vice versa, both of which aren't supposed to 53 // work. 54 // 55 // Ideally we'd only `File::parse` once and avoid the linear 56 // `section_by_name` search here but the general serialization code isn't 57 // structured well enough to make this easy and additionally it's not really 58 // a perf issue right now so doing that is left for another day's 59 // refactoring. 60 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 => { /* ignore the version info, accept all */ } 117 } 118 postcard::from_bytes::<Metadata<'_>>(data)?.check_compatible(engine) 119 } 120 121 #[cfg(any(feature = "cranelift", feature = "winch"))] 122 pub 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 // This precondition is checked in Config::module_version: 136 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 146 fn 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 164 pub fn detect_precompiled_bytes(bytes: &[u8]) -> Option<Precompiled> { 165 detect_precompiled(ElfFile64::parse(bytes).ok()?) 166 } 167 168 #[cfg(feature = "std")] 169 pub 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)] 176 pub 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 186 impl 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 // This doesn't affect compilation, it's just a runtime setting. 297 memory_reservation_for_growth: _, 298 299 // This does technically affect compilation but modules with/without 300 // trap information can be loaded into engines with the opposite 301 // setting just fine (it's just a section in the compiled file and 302 // whether it's present or not) 303 generate_address_map: _, 304 305 // Just a debugging aid, doesn't affect functionality at all. 306 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 // If the module doesn't require GC support it doesn't matter 398 // whether the host has GC support enabled or not. 399 (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)] 439 mod 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 // Note that this test runs on a platform that is known to use Cranelift 466 #[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 "\ 501 compilation settings of module incompatible with native host 502 503 Caused 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 "\ 525 compilation settings of module incompatible with native host 526 527 Caused 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")] // different defaults on 32-bit platforms 540 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 /// This test is only run a platform that is known to implement threads 593 #[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 // If a feature is disabled in the module and enabled in the host, 604 // that's always ok. 605 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 } 802