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