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