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 } 206 207 impl Metadata<'_> { 208 #[cfg(any(feature = "cranelift", feature = "winch"))] 209 pub fn new(engine: &Engine) -> Metadata<'static> { 210 let wasmparser::WasmFeaturesInflated { 211 reference_types, 212 multi_value, 213 bulk_memory, 214 component_model, 215 simd, 216 threads, 217 tail_call, 218 multi_memory, 219 exceptions, 220 memory64, 221 relaxed_simd, 222 extended_const, 223 memory_control, 224 function_references, 225 gc, 226 custom_page_sizes, 227 shared_everything_threads, 228 component_model_values, 229 component_model_nested_names, 230 231 // Always on; we don't currently have knobs for these. 232 mutable_global: _, 233 saturating_float_to_int: _, 234 sign_extension: _, 235 floats: _, 236 } = engine.config().features.inflate(); 237 238 // These features are not implemented in Wasmtime yet. We match on them 239 // above so that once we do implement support for them, we won't 240 // silently ignore them during serialization. 241 assert!(!memory_control); 242 assert!(!component_model_values); 243 assert!(!component_model_nested_names); 244 assert!(!shared_everything_threads); 245 246 Metadata { 247 target: engine.compiler().triple().to_string(), 248 shared_flags: engine.compiler().flags(), 249 isa_flags: engine.compiler().isa_flags(), 250 tunables: engine.tunables().clone(), 251 features: WasmFeatures { 252 reference_types, 253 multi_value, 254 bulk_memory, 255 component_model, 256 simd, 257 threads, 258 tail_call, 259 multi_memory, 260 exceptions, 261 memory64, 262 relaxed_simd, 263 extended_const, 264 function_references, 265 gc, 266 custom_page_sizes, 267 }, 268 } 269 } 270 271 fn check_compatible(mut self, engine: &Engine) -> Result<()> { 272 self.check_triple(engine)?; 273 self.check_shared_flags(engine)?; 274 self.check_isa_flags(engine)?; 275 self.check_tunables(&engine.tunables())?; 276 self.check_features(&engine.config().features)?; 277 Ok(()) 278 } 279 280 fn check_triple(&self, engine: &Engine) -> Result<()> { 281 let engine_target = engine.target(); 282 let module_target = 283 target_lexicon::Triple::from_str(&self.target).map_err(|e| anyhow!(e))?; 284 285 if module_target.architecture != engine_target.architecture { 286 bail!( 287 "Module was compiled for architecture '{}'", 288 module_target.architecture 289 ); 290 } 291 292 if module_target.operating_system != engine_target.operating_system { 293 bail!( 294 "Module was compiled for operating system '{}'", 295 module_target.operating_system 296 ); 297 } 298 299 Ok(()) 300 } 301 302 fn check_shared_flags(&mut self, engine: &Engine) -> Result<()> { 303 for (name, val) in self.shared_flags.iter() { 304 engine 305 .check_compatible_with_shared_flag(name, val) 306 .map_err(|s| anyhow::Error::msg(s)) 307 .context("compilation settings of module incompatible with native host")?; 308 } 309 Ok(()) 310 } 311 312 fn check_isa_flags(&mut self, engine: &Engine) -> Result<()> { 313 for (name, val) in self.isa_flags.iter() { 314 engine 315 .check_compatible_with_isa_flag(name, val) 316 .map_err(|s| anyhow::Error::msg(s)) 317 .context("compilation settings of module incompatible with native host")?; 318 } 319 Ok(()) 320 } 321 322 fn check_int<T: Eq + core::fmt::Display>(found: T, expected: T, feature: &str) -> Result<()> { 323 if found == expected { 324 return Ok(()); 325 } 326 327 bail!( 328 "Module was compiled with a {} of '{}' but '{}' is expected for the host", 329 feature, 330 found, 331 expected 332 ); 333 } 334 335 fn check_bool(found: bool, expected: bool, feature: &str) -> Result<()> { 336 if found == expected { 337 return Ok(()); 338 } 339 340 bail!( 341 "Module was compiled {} {} but it {} enabled for the host", 342 if found { "with" } else { "without" }, 343 feature, 344 if expected { "is" } else { "is not" } 345 ); 346 } 347 348 fn check_tunables(&mut self, other: &Tunables) -> Result<()> { 349 let Tunables { 350 static_memory_reservation, 351 static_memory_offset_guard_size, 352 dynamic_memory_offset_guard_size, 353 generate_native_debuginfo, 354 parse_wasm_debuginfo, 355 consume_fuel, 356 epoch_interruption, 357 static_memory_bound_is_maximum, 358 guard_before_linear_memory, 359 table_lazy_init, 360 relaxed_simd_deterministic, 361 tail_callable, 362 winch_callable, 363 364 // This doesn't affect compilation, it's just a runtime setting. 365 dynamic_memory_growth_reserve: _, 366 367 // This does technically affect compilation but modules with/without 368 // trap information can be loaded into engines with the opposite 369 // setting just fine (it's just a section in the compiled file and 370 // whether it's present or not) 371 generate_address_map: _, 372 373 // Just a debugging aid, doesn't affect functionality at all. 374 debug_adapter_modules: _, 375 } = self.tunables; 376 377 Self::check_int( 378 static_memory_reservation, 379 other.static_memory_reservation, 380 "static memory reservation", 381 )?; 382 Self::check_int( 383 static_memory_offset_guard_size, 384 other.static_memory_offset_guard_size, 385 "static memory guard size", 386 )?; 387 Self::check_int( 388 dynamic_memory_offset_guard_size, 389 other.dynamic_memory_offset_guard_size, 390 "dynamic memory guard size", 391 )?; 392 Self::check_bool( 393 generate_native_debuginfo, 394 other.generate_native_debuginfo, 395 "debug information support", 396 )?; 397 Self::check_bool( 398 parse_wasm_debuginfo, 399 other.parse_wasm_debuginfo, 400 "WebAssembly backtrace support", 401 )?; 402 Self::check_bool(consume_fuel, other.consume_fuel, "fuel support")?; 403 Self::check_bool( 404 epoch_interruption, 405 other.epoch_interruption, 406 "epoch interruption", 407 )?; 408 Self::check_bool( 409 static_memory_bound_is_maximum, 410 other.static_memory_bound_is_maximum, 411 "pooling allocation support", 412 )?; 413 Self::check_bool( 414 guard_before_linear_memory, 415 other.guard_before_linear_memory, 416 "guard before linear memory", 417 )?; 418 Self::check_bool(table_lazy_init, other.table_lazy_init, "table lazy init")?; 419 Self::check_bool( 420 relaxed_simd_deterministic, 421 other.relaxed_simd_deterministic, 422 "relaxed simd deterministic semantics", 423 )?; 424 Self::check_bool(tail_callable, other.tail_callable, "WebAssembly tail calls")?; 425 Self::check_bool( 426 winch_callable, 427 other.winch_callable, 428 "Winch calling convention", 429 )?; 430 431 Ok(()) 432 } 433 434 fn check_cfg_bool( 435 cfg: bool, 436 cfg_str: &str, 437 found: bool, 438 expected: bool, 439 feature: &str, 440 ) -> Result<()> { 441 if cfg { 442 Self::check_bool(found, expected, feature) 443 } else { 444 assert!(!expected); 445 ensure!( 446 !found, 447 "Module was compiled with {feature} but support in the host \ 448 was disabled at compile time because the `{cfg_str}` Cargo \ 449 feature was not enabled", 450 ); 451 Ok(()) 452 } 453 } 454 455 fn check_features(&mut self, other: &wasmparser::WasmFeatures) -> Result<()> { 456 let WasmFeatures { 457 reference_types, 458 multi_value, 459 bulk_memory, 460 component_model, 461 simd, 462 tail_call, 463 threads, 464 multi_memory, 465 exceptions, 466 memory64, 467 relaxed_simd, 468 extended_const, 469 function_references, 470 gc, 471 custom_page_sizes, 472 } = self.features; 473 474 use wasmparser::WasmFeatures as F; 475 Self::check_cfg_bool( 476 cfg!(feature = "gc"), 477 "gc", 478 reference_types, 479 other.contains(F::REFERENCE_TYPES), 480 "WebAssembly reference types support", 481 )?; 482 Self::check_cfg_bool( 483 cfg!(feature = "gc"), 484 "gc", 485 function_references, 486 other.contains(F::FUNCTION_REFERENCES), 487 "WebAssembly function-references support", 488 )?; 489 Self::check_cfg_bool( 490 cfg!(feature = "gc"), 491 "gc", 492 gc, 493 other.contains(F::GC), 494 "WebAssembly garbage collection support", 495 )?; 496 497 Self::check_bool( 498 multi_value, 499 other.contains(F::MULTI_VALUE), 500 "WebAssembly multi-value support", 501 )?; 502 Self::check_bool( 503 bulk_memory, 504 other.contains(F::BULK_MEMORY), 505 "WebAssembly bulk memory support", 506 )?; 507 Self::check_bool( 508 component_model, 509 other.contains(F::COMPONENT_MODEL), 510 "WebAssembly component model support", 511 )?; 512 Self::check_bool(simd, other.contains(F::SIMD), "WebAssembly SIMD support")?; 513 Self::check_bool( 514 tail_call, 515 other.contains(F::TAIL_CALL), 516 "WebAssembly tail calls support", 517 )?; 518 Self::check_bool( 519 threads, 520 other.contains(F::THREADS), 521 "WebAssembly threads support", 522 )?; 523 Self::check_bool( 524 multi_memory, 525 other.contains(F::MULTI_MEMORY), 526 "WebAssembly multi-memory support", 527 )?; 528 Self::check_bool( 529 exceptions, 530 other.contains(F::EXCEPTIONS), 531 "WebAssembly exceptions support", 532 )?; 533 Self::check_bool( 534 memory64, 535 other.contains(F::MEMORY64), 536 "WebAssembly 64-bit memory support", 537 )?; 538 Self::check_bool( 539 extended_const, 540 other.contains(F::EXTENDED_CONST), 541 "WebAssembly extended-const support", 542 )?; 543 Self::check_bool( 544 relaxed_simd, 545 other.contains(F::RELAXED_SIMD), 546 "WebAssembly relaxed-simd support", 547 )?; 548 Self::check_bool( 549 custom_page_sizes, 550 other.contains(F::CUSTOM_PAGE_SIZES), 551 "WebAssembly custom-page-sizes support", 552 )?; 553 554 Ok(()) 555 } 556 } 557 558 #[cfg(test)] 559 mod test { 560 use super::*; 561 use crate::{Config, Module, OptLevel}; 562 use std::{ 563 collections::hash_map::DefaultHasher, 564 hash::{Hash, Hasher}, 565 }; 566 use tempfile::TempDir; 567 568 #[test] 569 fn test_architecture_mismatch() -> Result<()> { 570 let engine = Engine::default(); 571 let mut metadata = Metadata::new(&engine); 572 metadata.target = "unknown-generic-linux".to_string(); 573 574 match metadata.check_compatible(&engine) { 575 Ok(_) => unreachable!(), 576 Err(e) => assert_eq!( 577 e.to_string(), 578 "Module was compiled for architecture 'unknown'", 579 ), 580 } 581 582 Ok(()) 583 } 584 585 #[test] 586 fn test_os_mismatch() -> Result<()> { 587 let engine = Engine::default(); 588 let mut metadata = Metadata::new(&engine); 589 590 metadata.target = format!( 591 "{}-generic-unknown", 592 target_lexicon::Triple::host().architecture 593 ); 594 595 match metadata.check_compatible(&engine) { 596 Ok(_) => unreachable!(), 597 Err(e) => assert_eq!( 598 e.to_string(), 599 "Module was compiled for operating system 'unknown'", 600 ), 601 } 602 603 Ok(()) 604 } 605 606 #[test] 607 fn test_cranelift_flags_mismatch() -> Result<()> { 608 let engine = Engine::default(); 609 let mut metadata = Metadata::new(&engine); 610 611 metadata 612 .shared_flags 613 .push(("preserve_frame_pointers", FlagValue::Bool(false))); 614 615 match metadata.check_compatible(&engine) { 616 Ok(_) => unreachable!(), 617 Err(e) => assert!(format!("{:?}", e).starts_with( 618 "\ 619 compilation settings of module incompatible with native host 620 621 Caused by: 622 setting \"preserve_frame_pointers\" is configured to Bool(false) which is not supported" 623 )), 624 } 625 626 Ok(()) 627 } 628 629 #[test] 630 fn test_isa_flags_mismatch() -> Result<()> { 631 let engine = Engine::default(); 632 let mut metadata = Metadata::new(&engine); 633 634 metadata 635 .isa_flags 636 .push(("not_a_flag", FlagValue::Bool(true))); 637 638 match metadata.check_compatible(&engine) { 639 Ok(_) => unreachable!(), 640 Err(e) => assert!( 641 format!("{e:?}").starts_with( 642 "\ 643 compilation settings of module incompatible with native host 644 645 Caused by: 646 don't know how to test for target-specific flag \"not_a_flag\" at runtime", 647 ), 648 "bad error {e:?}", 649 ), 650 } 651 652 Ok(()) 653 } 654 655 #[test] 656 #[cfg_attr(miri, ignore)] 657 fn test_tunables_int_mismatch() -> Result<()> { 658 let engine = Engine::default(); 659 let mut metadata = Metadata::new(&engine); 660 661 metadata.tunables.static_memory_offset_guard_size = 0; 662 663 match metadata.check_compatible(&engine) { 664 Ok(_) => unreachable!(), 665 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"), 666 } 667 668 Ok(()) 669 } 670 671 #[test] 672 fn test_tunables_bool_mismatch() -> Result<()> { 673 let mut config = Config::new(); 674 config.epoch_interruption(true); 675 676 let engine = Engine::new(&config)?; 677 let mut metadata = Metadata::new(&engine); 678 metadata.tunables.epoch_interruption = false; 679 680 match metadata.check_compatible(&engine) { 681 Ok(_) => unreachable!(), 682 Err(e) => assert_eq!( 683 e.to_string(), 684 "Module was compiled without epoch interruption but it is enabled for the host" 685 ), 686 } 687 688 let mut config = Config::new(); 689 config.epoch_interruption(false); 690 691 let engine = Engine::new(&config)?; 692 let mut metadata = Metadata::new(&engine); 693 metadata.tunables.epoch_interruption = true; 694 695 match metadata.check_compatible(&engine) { 696 Ok(_) => unreachable!(), 697 Err(e) => assert_eq!( 698 e.to_string(), 699 "Module was compiled with epoch interruption but it is not enabled for the host" 700 ), 701 } 702 703 Ok(()) 704 } 705 706 #[test] 707 fn test_feature_mismatch() -> Result<()> { 708 let mut config = Config::new(); 709 config.wasm_threads(true); 710 711 let engine = Engine::new(&config)?; 712 let mut metadata = Metadata::new(&engine); 713 metadata.features.threads = false; 714 715 match metadata.check_compatible(&engine) { 716 Ok(_) => unreachable!(), 717 Err(e) => assert_eq!(e.to_string(), "Module was compiled without WebAssembly threads support but it is enabled for the host"), 718 } 719 720 let mut config = Config::new(); 721 config.wasm_threads(false); 722 723 let engine = Engine::new(&config)?; 724 let mut metadata = Metadata::new(&engine); 725 metadata.features.threads = true; 726 727 match metadata.check_compatible(&engine) { 728 Ok(_) => unreachable!(), 729 Err(e) => assert_eq!(e.to_string(), "Module was compiled with WebAssembly threads support but it is not enabled for the host"), 730 } 731 732 Ok(()) 733 } 734 735 #[test] 736 fn engine_weak_upgrades() { 737 let engine = Engine::default(); 738 let weak = engine.weak(); 739 weak.upgrade() 740 .expect("engine is still alive, so weak reference can upgrade"); 741 drop(engine); 742 assert!( 743 weak.upgrade().is_none(), 744 "engine was dropped, so weak reference cannot upgrade" 745 ); 746 } 747 748 #[test] 749 #[cfg_attr(miri, ignore)] 750 fn cache_accounts_for_opt_level() -> Result<()> { 751 let td = TempDir::new()?; 752 let config_path = td.path().join("config.toml"); 753 std::fs::write( 754 &config_path, 755 &format!( 756 " 757 [cache] 758 enabled = true 759 directory = '{}' 760 ", 761 td.path().join("cache").display() 762 ), 763 )?; 764 let mut cfg = Config::new(); 765 cfg.cranelift_opt_level(OptLevel::None) 766 .cache_config_load(&config_path)?; 767 let engine = Engine::new(&cfg)?; 768 Module::new(&engine, "(module (func))")?; 769 assert_eq!(engine.config().cache_config.cache_hits(), 0); 770 assert_eq!(engine.config().cache_config.cache_misses(), 1); 771 Module::new(&engine, "(module (func))")?; 772 assert_eq!(engine.config().cache_config.cache_hits(), 1); 773 assert_eq!(engine.config().cache_config.cache_misses(), 1); 774 775 let mut cfg = Config::new(); 776 cfg.cranelift_opt_level(OptLevel::Speed) 777 .cache_config_load(&config_path)?; 778 let engine = Engine::new(&cfg)?; 779 Module::new(&engine, "(module (func))")?; 780 assert_eq!(engine.config().cache_config.cache_hits(), 0); 781 assert_eq!(engine.config().cache_config.cache_misses(), 1); 782 Module::new(&engine, "(module (func))")?; 783 assert_eq!(engine.config().cache_config.cache_hits(), 1); 784 assert_eq!(engine.config().cache_config.cache_misses(), 1); 785 786 let mut cfg = Config::new(); 787 cfg.cranelift_opt_level(OptLevel::SpeedAndSize) 788 .cache_config_load(&config_path)?; 789 let engine = Engine::new(&cfg)?; 790 Module::new(&engine, "(module (func))")?; 791 assert_eq!(engine.config().cache_config.cache_hits(), 0); 792 assert_eq!(engine.config().cache_config.cache_misses(), 1); 793 Module::new(&engine, "(module (func))")?; 794 assert_eq!(engine.config().cache_config.cache_hits(), 1); 795 assert_eq!(engine.config().cache_config.cache_misses(), 1); 796 797 let mut cfg = Config::new(); 798 cfg.debug_info(true).cache_config_load(&config_path)?; 799 let engine = Engine::new(&cfg)?; 800 Module::new(&engine, "(module (func))")?; 801 assert_eq!(engine.config().cache_config.cache_hits(), 0); 802 assert_eq!(engine.config().cache_config.cache_misses(), 1); 803 Module::new(&engine, "(module (func))")?; 804 assert_eq!(engine.config().cache_config.cache_hits(), 1); 805 assert_eq!(engine.config().cache_config.cache_misses(), 1); 806 807 Ok(()) 808 } 809 810 #[test] 811 fn precompile_compatibility_key_accounts_for_opt_level() { 812 fn hash_for_config(cfg: &Config) -> u64 { 813 let engine = Engine::new(cfg).expect("Config should be valid"); 814 let mut hasher = DefaultHasher::new(); 815 engine.precompile_compatibility_hash().hash(&mut hasher); 816 hasher.finish() 817 } 818 let mut cfg = Config::new(); 819 cfg.cranelift_opt_level(OptLevel::None); 820 let opt_none_hash = hash_for_config(&cfg); 821 cfg.cranelift_opt_level(OptLevel::Speed); 822 let opt_speed_hash = hash_for_config(&cfg); 823 assert_ne!(opt_none_hash, opt_speed_hash) 824 } 825 826 #[test] 827 fn precompile_compatibility_key_accounts_for_module_version_strategy() -> Result<()> { 828 fn hash_for_config(cfg: &Config) -> u64 { 829 let engine = Engine::new(cfg).expect("Config should be valid"); 830 let mut hasher = DefaultHasher::new(); 831 engine.precompile_compatibility_hash().hash(&mut hasher); 832 hasher.finish() 833 } 834 let mut cfg_custom_version = Config::new(); 835 cfg_custom_version.module_version(ModuleVersionStrategy::Custom("1.0.1111".to_string()))?; 836 let custom_version_hash = hash_for_config(&cfg_custom_version); 837 838 let mut cfg_default_version = Config::new(); 839 cfg_default_version.module_version(ModuleVersionStrategy::WasmtimeVersion)?; 840 let default_version_hash = hash_for_config(&cfg_default_version); 841 842 let mut cfg_none_version = Config::new(); 843 cfg_none_version.module_version(ModuleVersionStrategy::None)?; 844 let none_version_hash = hash_for_config(&cfg_none_version); 845 846 assert_ne!(custom_version_hash, default_version_hash); 847 assert_ne!(custom_version_hash, none_version_hash); 848 assert_ne!(default_version_hash, none_version_hash); 849 850 Ok(()) 851 } 852 853 #[test] 854 #[cfg_attr(miri, ignore)] 855 #[cfg(feature = "component-model")] 856 fn components_are_cached() -> Result<()> { 857 use crate::component::Component; 858 859 let td = TempDir::new()?; 860 let config_path = td.path().join("config.toml"); 861 std::fs::write( 862 &config_path, 863 &format!( 864 " 865 [cache] 866 enabled = true 867 directory = '{}' 868 ", 869 td.path().join("cache").display() 870 ), 871 )?; 872 let mut cfg = Config::new(); 873 cfg.cache_config_load(&config_path)?; 874 let engine = Engine::new(&cfg)?; 875 Component::new(&engine, "(component (core module (func)))")?; 876 assert_eq!(engine.config().cache_config.cache_hits(), 0); 877 assert_eq!(engine.config().cache_config.cache_misses(), 1); 878 Component::new(&engine, "(component (core module (func)))")?; 879 assert_eq!(engine.config().cache_config.cache_hits(), 1); 880 assert_eq!(engine.config().cache_config.cache_misses(), 1); 881 882 Ok(()) 883 } 884 } 885