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