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