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 .map_err(obj::ObjectCrateErrorWrapper) 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 .map_err(obj::ObjectCrateErrorWrapper)?; 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)?; 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)?; 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)?.check_compatible(engine) 118 } 119 120 #[cfg(any(feature = "cranelift", feature = "winch"))] 121 pub fn append_compiler_info(engine: &Engine, obj: &mut Object<'_>, metadata: &Metadata<'_>) { 122 let section = obj.add_section( 123 obj.segment_name(StandardSegment::Data).to_vec(), 124 obj::ELF_WASM_ENGINE.as_bytes().to_vec(), 125 SectionKind::ReadOnlyData, 126 ); 127 let mut data = Vec::new(); 128 data.push(VERSION); 129 let version = match &engine.config().module_version { 130 ModuleVersionStrategy::WasmtimeVersion => env!("CARGO_PKG_VERSION"), 131 ModuleVersionStrategy::Custom(c) => c, 132 ModuleVersionStrategy::None => "", 133 }; 134 // This precondition is checked in Config::module_version: 135 assert!( 136 version.len() < 256, 137 "package version must be less than 256 bytes" 138 ); 139 data.push(version.len() as u8); 140 data.extend_from_slice(version.as_bytes()); 141 data.extend(postcard::to_allocvec(metadata).unwrap()); 142 obj.set_section_data(section, data, 1); 143 } 144 145 fn detect_precompiled<'data, R: object::ReadRef<'data>>( 146 obj: ElfFile64<'data, Endianness, R>, 147 ) -> Option<Precompiled> { 148 match obj.flags() { 149 FileFlags::Elf { 150 os_abi: obj::ELFOSABI_WASMTIME, 151 abi_version: 0, 152 e_flags: obj::EF_WASMTIME_MODULE, 153 } => Some(Precompiled::Module), 154 FileFlags::Elf { 155 os_abi: obj::ELFOSABI_WASMTIME, 156 abi_version: 0, 157 e_flags: obj::EF_WASMTIME_COMPONENT, 158 } => Some(Precompiled::Component), 159 _ => None, 160 } 161 } 162 163 pub fn detect_precompiled_bytes(bytes: &[u8]) -> Option<Precompiled> { 164 detect_precompiled(ElfFile64::parse(bytes).ok()?) 165 } 166 167 #[cfg(feature = "std")] 168 pub fn detect_precompiled_file(path: impl AsRef<std::path::Path>) -> Result<Option<Precompiled>> { 169 let read_cache = object::ReadCache::new(std::fs::File::open(path)?); 170 let obj = ElfFile64::parse(&read_cache)?; 171 Ok(detect_precompiled(obj)) 172 } 173 174 #[derive(Serialize, Deserialize)] 175 pub struct Metadata<'a> { 176 target: String, 177 #[serde(borrow)] 178 shared_flags: Vec<(&'a str, FlagValue<'a>)>, 179 #[serde(borrow)] 180 isa_flags: Vec<(&'a str, FlagValue<'a>)>, 181 tunables: Tunables, 182 features: WasmFeatures, 183 } 184 185 // This exists because `wasmparser::WasmFeatures` isn't serializable 186 #[derive(Debug, Copy, Clone, Serialize, Deserialize)] 187 struct WasmFeatures { 188 reference_types: bool, 189 multi_value: bool, 190 bulk_memory: bool, 191 component_model: bool, 192 simd: bool, 193 tail_call: bool, 194 threads: bool, 195 multi_memory: bool, 196 exceptions: bool, 197 memory64: bool, 198 relaxed_simd: bool, 199 extended_const: bool, 200 function_references: bool, 201 gc: bool, 202 custom_page_sizes: bool, 203 component_model_more_flags: bool, 204 component_model_multiple_returns: bool, 205 gc_types: bool, 206 wide_arithmetic: bool, 207 } 208 209 impl Metadata<'_> { 210 #[cfg(any(feature = "cranelift", feature = "winch"))] 211 pub fn new(engine: &Engine) -> Metadata<'static> { 212 let wasmparser::WasmFeaturesInflated { 213 reference_types, 214 multi_value, 215 bulk_memory, 216 component_model, 217 simd, 218 threads, 219 tail_call, 220 multi_memory, 221 exceptions, 222 memory64, 223 relaxed_simd, 224 extended_const, 225 memory_control, 226 function_references, 227 gc, 228 custom_page_sizes, 229 shared_everything_threads, 230 component_model_values, 231 component_model_nested_names, 232 component_model_more_flags, 233 component_model_multiple_returns, 234 component_model_async, 235 legacy_exceptions, 236 gc_types, 237 stack_switching, 238 wide_arithmetic, 239 240 // Always on; we don't currently have knobs for these. 241 mutable_global: _, 242 saturating_float_to_int: _, 243 sign_extension: _, 244 floats: _, 245 } = engine.features().inflate(); 246 247 // These features are not implemented in Wasmtime yet. We match on them 248 // above so that once we do implement support for them, we won't 249 // silently ignore them during serialization. 250 assert!(!memory_control); 251 assert!(!component_model_values); 252 assert!(!component_model_nested_names); 253 assert!(!shared_everything_threads); 254 assert!(!legacy_exceptions); 255 assert!(!stack_switching); 256 assert!(!component_model_async); 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 #[cfg(target_arch = "x86_64")] // test on a platform that is known to use 647 // Cranelift 648 fn test_os_mismatch() -> Result<()> { 649 let engine = Engine::default(); 650 let mut metadata = Metadata::new(&engine); 651 652 metadata.target = format!( 653 "{}-generic-unknown", 654 target_lexicon::Triple::host().architecture 655 ); 656 657 match metadata.check_compatible(&engine) { 658 Ok(_) => unreachable!(), 659 Err(e) => assert_eq!( 660 e.to_string(), 661 "Module was compiled for operating system 'unknown'", 662 ), 663 } 664 665 Ok(()) 666 } 667 668 #[test] 669 fn test_cranelift_flags_mismatch() -> Result<()> { 670 let engine = Engine::default(); 671 let mut metadata = Metadata::new(&engine); 672 673 metadata 674 .shared_flags 675 .push(("preserve_frame_pointers", FlagValue::Bool(false))); 676 677 match metadata.check_compatible(&engine) { 678 Ok(_) => unreachable!(), 679 Err(e) => assert!(format!("{e:?}").starts_with( 680 "\ 681 compilation settings of module incompatible with native host 682 683 Caused by: 684 setting \"preserve_frame_pointers\" is configured to Bool(false) which is not supported" 685 )), 686 } 687 688 Ok(()) 689 } 690 691 #[test] 692 fn test_isa_flags_mismatch() -> Result<()> { 693 let engine = Engine::default(); 694 let mut metadata = Metadata::new(&engine); 695 696 metadata 697 .isa_flags 698 .push(("not_a_flag", FlagValue::Bool(true))); 699 700 match metadata.check_compatible(&engine) { 701 Ok(_) => unreachable!(), 702 Err(e) => assert!( 703 format!("{e:?}").starts_with( 704 "\ 705 compilation settings of module incompatible with native host 706 707 Caused by: 708 don't know how to test for target-specific flag \"not_a_flag\" at runtime", 709 ), 710 "bad error {e:?}", 711 ), 712 } 713 714 Ok(()) 715 } 716 717 #[test] 718 #[cfg_attr(miri, ignore)] 719 #[cfg(target_pointer_width = "64")] // different defaults on 32-bit platforms 720 fn test_tunables_int_mismatch() -> Result<()> { 721 let engine = Engine::default(); 722 let mut metadata = Metadata::new(&engine); 723 724 metadata.tunables.memory_guard_size = 0; 725 726 match metadata.check_compatible(&engine) { 727 Ok(_) => unreachable!(), 728 Err(e) => assert_eq!(e.to_string(), "Module was compiled with a memory guard size of '0' but '33554432' is expected for the host"), 729 } 730 731 Ok(()) 732 } 733 734 #[test] 735 fn test_tunables_bool_mismatch() -> Result<()> { 736 let mut config = Config::new(); 737 config.epoch_interruption(true); 738 739 let engine = Engine::new(&config)?; 740 let mut metadata = Metadata::new(&engine); 741 metadata.tunables.epoch_interruption = false; 742 743 match metadata.check_compatible(&engine) { 744 Ok(_) => unreachable!(), 745 Err(e) => assert_eq!( 746 e.to_string(), 747 "Module was compiled without epoch interruption but it is enabled for the host" 748 ), 749 } 750 751 let mut config = Config::new(); 752 config.epoch_interruption(false); 753 754 let engine = Engine::new(&config)?; 755 let mut metadata = Metadata::new(&engine); 756 metadata.tunables.epoch_interruption = true; 757 758 match metadata.check_compatible(&engine) { 759 Ok(_) => unreachable!(), 760 Err(e) => assert_eq!( 761 e.to_string(), 762 "Module was compiled with epoch interruption but it is not enabled for the host" 763 ), 764 } 765 766 Ok(()) 767 } 768 769 #[test] 770 #[cfg(target_arch = "x86_64")] // test on a platform that is known to 771 // implement threads 772 fn test_feature_mismatch() -> Result<()> { 773 let mut config = Config::new(); 774 config.wasm_threads(true); 775 776 let engine = Engine::new(&config)?; 777 let mut metadata = Metadata::new(&engine); 778 metadata.features.threads = false; 779 780 match metadata.check_compatible(&engine) { 781 Ok(_) => unreachable!(), 782 Err(e) => assert_eq!(e.to_string(), "Module was compiled without WebAssembly threads support but it is enabled for the host"), 783 } 784 785 let mut config = Config::new(); 786 config.wasm_threads(false); 787 788 let engine = Engine::new(&config)?; 789 let mut metadata = Metadata::new(&engine); 790 metadata.features.threads = true; 791 792 match metadata.check_compatible(&engine) { 793 Ok(_) => unreachable!(), 794 Err(e) => assert_eq!(e.to_string(), "Module was compiled with WebAssembly threads support but it is not enabled for the host"), 795 } 796 797 Ok(()) 798 } 799 800 #[test] 801 fn engine_weak_upgrades() { 802 let engine = Engine::default(); 803 let weak = engine.weak(); 804 weak.upgrade() 805 .expect("engine is still alive, so weak reference can upgrade"); 806 drop(engine); 807 assert!( 808 weak.upgrade().is_none(), 809 "engine was dropped, so weak reference cannot upgrade" 810 ); 811 } 812 813 #[test] 814 #[cfg_attr(miri, ignore)] 815 fn cache_accounts_for_opt_level() -> Result<()> { 816 let td = TempDir::new()?; 817 let config_path = td.path().join("config.toml"); 818 std::fs::write( 819 &config_path, 820 &format!( 821 " 822 [cache] 823 enabled = true 824 directory = '{}' 825 ", 826 td.path().join("cache").display() 827 ), 828 )?; 829 let mut cfg = Config::new(); 830 cfg.cranelift_opt_level(OptLevel::None) 831 .cache_config_load(&config_path)?; 832 let engine = Engine::new(&cfg)?; 833 Module::new(&engine, "(module (func))")?; 834 assert_eq!(engine.config().cache_config.cache_hits(), 0); 835 assert_eq!(engine.config().cache_config.cache_misses(), 1); 836 Module::new(&engine, "(module (func))")?; 837 assert_eq!(engine.config().cache_config.cache_hits(), 1); 838 assert_eq!(engine.config().cache_config.cache_misses(), 1); 839 840 let mut cfg = Config::new(); 841 cfg.cranelift_opt_level(OptLevel::Speed) 842 .cache_config_load(&config_path)?; 843 let engine = Engine::new(&cfg)?; 844 Module::new(&engine, "(module (func))")?; 845 assert_eq!(engine.config().cache_config.cache_hits(), 0); 846 assert_eq!(engine.config().cache_config.cache_misses(), 1); 847 Module::new(&engine, "(module (func))")?; 848 assert_eq!(engine.config().cache_config.cache_hits(), 1); 849 assert_eq!(engine.config().cache_config.cache_misses(), 1); 850 851 let mut cfg = Config::new(); 852 cfg.cranelift_opt_level(OptLevel::SpeedAndSize) 853 .cache_config_load(&config_path)?; 854 let engine = Engine::new(&cfg)?; 855 Module::new(&engine, "(module (func))")?; 856 assert_eq!(engine.config().cache_config.cache_hits(), 0); 857 assert_eq!(engine.config().cache_config.cache_misses(), 1); 858 Module::new(&engine, "(module (func))")?; 859 assert_eq!(engine.config().cache_config.cache_hits(), 1); 860 assert_eq!(engine.config().cache_config.cache_misses(), 1); 861 862 let mut cfg = Config::new(); 863 cfg.debug_info(true).cache_config_load(&config_path)?; 864 let engine = Engine::new(&cfg)?; 865 Module::new(&engine, "(module (func))")?; 866 assert_eq!(engine.config().cache_config.cache_hits(), 0); 867 assert_eq!(engine.config().cache_config.cache_misses(), 1); 868 Module::new(&engine, "(module (func))")?; 869 assert_eq!(engine.config().cache_config.cache_hits(), 1); 870 assert_eq!(engine.config().cache_config.cache_misses(), 1); 871 872 Ok(()) 873 } 874 875 #[test] 876 fn precompile_compatibility_key_accounts_for_opt_level() { 877 fn hash_for_config(cfg: &Config) -> u64 { 878 let engine = Engine::new(cfg).expect("Config should be valid"); 879 let mut hasher = DefaultHasher::new(); 880 engine.precompile_compatibility_hash().hash(&mut hasher); 881 hasher.finish() 882 } 883 let mut cfg = Config::new(); 884 cfg.cranelift_opt_level(OptLevel::None); 885 let opt_none_hash = hash_for_config(&cfg); 886 cfg.cranelift_opt_level(OptLevel::Speed); 887 let opt_speed_hash = hash_for_config(&cfg); 888 assert_ne!(opt_none_hash, opt_speed_hash) 889 } 890 891 #[test] 892 fn precompile_compatibility_key_accounts_for_module_version_strategy() -> Result<()> { 893 fn hash_for_config(cfg: &Config) -> u64 { 894 let engine = Engine::new(cfg).expect("Config should be valid"); 895 let mut hasher = DefaultHasher::new(); 896 engine.precompile_compatibility_hash().hash(&mut hasher); 897 hasher.finish() 898 } 899 let mut cfg_custom_version = Config::new(); 900 cfg_custom_version.module_version(ModuleVersionStrategy::Custom("1.0.1111".to_string()))?; 901 let custom_version_hash = hash_for_config(&cfg_custom_version); 902 903 let mut cfg_default_version = Config::new(); 904 cfg_default_version.module_version(ModuleVersionStrategy::WasmtimeVersion)?; 905 let default_version_hash = hash_for_config(&cfg_default_version); 906 907 let mut cfg_none_version = Config::new(); 908 cfg_none_version.module_version(ModuleVersionStrategy::None)?; 909 let none_version_hash = hash_for_config(&cfg_none_version); 910 911 assert_ne!(custom_version_hash, default_version_hash); 912 assert_ne!(custom_version_hash, none_version_hash); 913 assert_ne!(default_version_hash, none_version_hash); 914 915 Ok(()) 916 } 917 918 #[test] 919 #[cfg_attr(miri, ignore)] 920 #[cfg(feature = "component-model")] 921 fn components_are_cached() -> Result<()> { 922 use crate::component::Component; 923 924 let td = TempDir::new()?; 925 let config_path = td.path().join("config.toml"); 926 std::fs::write( 927 &config_path, 928 &format!( 929 " 930 [cache] 931 enabled = true 932 directory = '{}' 933 ", 934 td.path().join("cache").display() 935 ), 936 )?; 937 let mut cfg = Config::new(); 938 cfg.cache_config_load(&config_path)?; 939 let engine = Engine::new(&cfg)?; 940 Component::new(&engine, "(component (core module (func)))")?; 941 assert_eq!(engine.config().cache_config.cache_hits(), 0); 942 assert_eq!(engine.config().cache_config.cache_misses(), 1); 943 Component::new(&engine, "(component (core module (func)))")?; 944 assert_eq!(engine.config().cache_config.cache_hits(), 1); 945 assert_eq!(engine.config().cache_config.cache_misses(), 1); 946 947 Ok(()) 948 } 949 } 950