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