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