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