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