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