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