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