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 gc_types: bool, 205 wide_arithmetic: bool, 206 stack_switching: 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_async, 233 legacy_exceptions, 234 gc_types, 235 stack_switching, 236 wide_arithmetic, 237 238 // Always on; we don't currently have knobs for these. 239 mutable_global: _, 240 saturating_float_to_int: _, 241 sign_extension: _, 242 floats: _, 243 } = engine.features().inflate(); 244 245 // These features are not implemented in Wasmtime yet. We match on them 246 // above so that once we do implement support for them, we won't 247 // silently ignore them during serialization. 248 assert!(!memory_control); 249 assert!(!component_model_values); 250 assert!(!component_model_nested_names); 251 assert!(!shared_everything_threads); 252 assert!(!legacy_exceptions); 253 254 Metadata { 255 target: engine.compiler().triple().to_string(), 256 shared_flags: engine.compiler().flags(), 257 isa_flags: engine.compiler().isa_flags(), 258 tunables: engine.tunables().clone(), 259 features: WasmFeatures { 260 reference_types, 261 multi_value, 262 bulk_memory, 263 component_model, 264 simd, 265 threads, 266 tail_call, 267 multi_memory, 268 exceptions, 269 memory64, 270 relaxed_simd, 271 extended_const, 272 function_references, 273 gc, 274 custom_page_sizes, 275 component_model_async, 276 gc_types, 277 wide_arithmetic, 278 stack_switching, 279 }, 280 } 281 } 282 283 fn check_compatible(mut self, engine: &Engine) -> Result<()> { 284 self.check_triple(engine)?; 285 self.check_shared_flags(engine)?; 286 self.check_isa_flags(engine)?; 287 self.check_tunables(&engine.tunables())?; 288 self.check_features(&engine.features())?; 289 Ok(()) 290 } 291 292 fn check_triple(&self, engine: &Engine) -> Result<()> { 293 let engine_target = engine.target(); 294 let module_target = 295 target_lexicon::Triple::from_str(&self.target).map_err(|e| anyhow!(e))?; 296 297 if module_target.architecture != engine_target.architecture { 298 bail!( 299 "Module was compiled for architecture '{}'", 300 module_target.architecture 301 ); 302 } 303 304 if module_target.operating_system != engine_target.operating_system { 305 bail!( 306 "Module was compiled for operating system '{}'", 307 module_target.operating_system 308 ); 309 } 310 311 Ok(()) 312 } 313 314 fn check_shared_flags(&mut self, engine: &Engine) -> Result<()> { 315 for (name, val) in self.shared_flags.iter() { 316 engine 317 .check_compatible_with_shared_flag(name, val) 318 .map_err(|s| anyhow::Error::msg(s)) 319 .context("compilation settings of module incompatible with native host")?; 320 } 321 Ok(()) 322 } 323 324 fn check_isa_flags(&mut self, engine: &Engine) -> Result<()> { 325 for (name, val) in self.isa_flags.iter() { 326 engine 327 .check_compatible_with_isa_flag(name, val) 328 .map_err(|s| anyhow::Error::msg(s)) 329 .context("compilation settings of module incompatible with native host")?; 330 } 331 Ok(()) 332 } 333 334 fn check_int<T: Eq + core::fmt::Display>(found: T, expected: T, feature: &str) -> Result<()> { 335 if found == expected { 336 return Ok(()); 337 } 338 339 bail!( 340 "Module was compiled with a {} of '{}' but '{}' is expected for the host", 341 feature, 342 found, 343 expected 344 ); 345 } 346 347 fn check_bool(found: bool, expected: bool, feature: &str) -> Result<()> { 348 if found == expected { 349 return Ok(()); 350 } 351 352 bail!( 353 "Module was compiled {} {} but it {} enabled for the host", 354 if found { "with" } else { "without" }, 355 feature, 356 if expected { "is" } else { "is not" } 357 ); 358 } 359 360 fn check_tunables(&mut self, other: &Tunables) -> Result<()> { 361 let Tunables { 362 collector, 363 memory_reservation, 364 memory_guard_size, 365 generate_native_debuginfo, 366 parse_wasm_debuginfo, 367 consume_fuel, 368 epoch_interruption, 369 memory_may_move, 370 guard_before_linear_memory, 371 table_lazy_init, 372 relaxed_simd_deterministic, 373 winch_callable, 374 signals_based_traps, 375 memory_init_cow, 376 // This doesn't affect compilation, it's just a runtime setting. 377 memory_reservation_for_growth: _, 378 379 // This does technically affect compilation but modules with/without 380 // trap information can be loaded into engines with the opposite 381 // setting just fine (it's just a section in the compiled file and 382 // whether it's present or not) 383 generate_address_map: _, 384 385 // Just a debugging aid, doesn't affect functionality at all. 386 debug_adapter_modules: _, 387 } = self.tunables; 388 389 Self::check_collector(collector, other.collector)?; 390 Self::check_int( 391 memory_reservation, 392 other.memory_reservation, 393 "memory reservation", 394 )?; 395 Self::check_int( 396 memory_guard_size, 397 other.memory_guard_size, 398 "memory guard size", 399 )?; 400 Self::check_bool( 401 generate_native_debuginfo, 402 other.generate_native_debuginfo, 403 "debug information support", 404 )?; 405 Self::check_bool( 406 parse_wasm_debuginfo, 407 other.parse_wasm_debuginfo, 408 "WebAssembly backtrace support", 409 )?; 410 Self::check_bool(consume_fuel, other.consume_fuel, "fuel support")?; 411 Self::check_bool( 412 epoch_interruption, 413 other.epoch_interruption, 414 "epoch interruption", 415 )?; 416 Self::check_bool(memory_may_move, other.memory_may_move, "memory may move")?; 417 Self::check_bool( 418 guard_before_linear_memory, 419 other.guard_before_linear_memory, 420 "guard before linear memory", 421 )?; 422 Self::check_bool(table_lazy_init, other.table_lazy_init, "table lazy init")?; 423 Self::check_bool( 424 relaxed_simd_deterministic, 425 other.relaxed_simd_deterministic, 426 "relaxed simd deterministic semantics", 427 )?; 428 Self::check_bool( 429 winch_callable, 430 other.winch_callable, 431 "Winch calling convention", 432 )?; 433 Self::check_bool( 434 signals_based_traps, 435 other.signals_based_traps, 436 "Signals-based traps", 437 )?; 438 Self::check_bool( 439 memory_init_cow, 440 other.memory_init_cow, 441 "memory initialization with CoW", 442 )?; 443 444 Ok(()) 445 } 446 447 fn check_cfg_bool( 448 cfg: bool, 449 cfg_str: &str, 450 found: bool, 451 expected: bool, 452 feature: &str, 453 ) -> Result<()> { 454 if cfg { 455 Self::check_bool(found, expected, feature) 456 } else { 457 assert!(!expected); 458 ensure!( 459 !found, 460 "Module was compiled with {feature} but support in the host \ 461 was disabled at compile time because the `{cfg_str}` Cargo \ 462 feature was not enabled", 463 ); 464 Ok(()) 465 } 466 } 467 468 fn check_features(&mut self, other: &wasmparser::WasmFeatures) -> Result<()> { 469 let WasmFeatures { 470 reference_types, 471 multi_value, 472 bulk_memory, 473 component_model, 474 simd, 475 tail_call, 476 threads, 477 multi_memory, 478 exceptions, 479 memory64, 480 relaxed_simd, 481 extended_const, 482 function_references, 483 gc, 484 custom_page_sizes, 485 component_model_async, 486 gc_types, 487 wide_arithmetic, 488 stack_switching, 489 } = self.features; 490 491 use wasmparser::WasmFeatures as F; 492 Self::check_bool( 493 reference_types, 494 other.contains(F::REFERENCE_TYPES), 495 "WebAssembly reference types support", 496 )?; 497 Self::check_bool( 498 function_references, 499 other.contains(F::FUNCTION_REFERENCES), 500 "WebAssembly function-references support", 501 )?; 502 Self::check_bool( 503 gc, 504 other.contains(F::GC), 505 "WebAssembly garbage collection support", 506 )?; 507 Self::check_bool( 508 multi_value, 509 other.contains(F::MULTI_VALUE), 510 "WebAssembly multi-value support", 511 )?; 512 Self::check_bool( 513 bulk_memory, 514 other.contains(F::BULK_MEMORY), 515 "WebAssembly bulk memory support", 516 )?; 517 Self::check_bool( 518 component_model, 519 other.contains(F::COMPONENT_MODEL), 520 "WebAssembly component model support", 521 )?; 522 Self::check_bool(simd, other.contains(F::SIMD), "WebAssembly SIMD support")?; 523 Self::check_bool( 524 tail_call, 525 other.contains(F::TAIL_CALL), 526 "WebAssembly tail calls support", 527 )?; 528 Self::check_bool( 529 threads, 530 other.contains(F::THREADS), 531 "WebAssembly threads support", 532 )?; 533 Self::check_bool( 534 multi_memory, 535 other.contains(F::MULTI_MEMORY), 536 "WebAssembly multi-memory support", 537 )?; 538 Self::check_bool( 539 exceptions, 540 other.contains(F::EXCEPTIONS), 541 "WebAssembly exceptions support", 542 )?; 543 Self::check_bool( 544 memory64, 545 other.contains(F::MEMORY64), 546 "WebAssembly 64-bit memory support", 547 )?; 548 Self::check_bool( 549 extended_const, 550 other.contains(F::EXTENDED_CONST), 551 "WebAssembly extended-const support", 552 )?; 553 Self::check_bool( 554 relaxed_simd, 555 other.contains(F::RELAXED_SIMD), 556 "WebAssembly relaxed-simd support", 557 )?; 558 Self::check_bool( 559 custom_page_sizes, 560 other.contains(F::CUSTOM_PAGE_SIZES), 561 "WebAssembly custom-page-sizes support", 562 )?; 563 Self::check_bool( 564 component_model_async, 565 other.contains(F::COMPONENT_MODEL_ASYNC), 566 "WebAssembly component model support for async lifts/lowers, futures, streams, and errors", 567 )?; 568 Self::check_cfg_bool( 569 cfg!(feature = "gc"), 570 "gc", 571 gc_types, 572 other.contains(F::GC_TYPES), 573 "support for WebAssembly gc types", 574 )?; 575 Self::check_bool( 576 wide_arithmetic, 577 other.contains(F::WIDE_ARITHMETIC), 578 "WebAssembly wide-arithmetic support", 579 )?; 580 Self::check_bool( 581 stack_switching, 582 other.contains(F::STACK_SWITCHING), 583 "WebAssembly stack switching support", 584 )?; 585 Ok(()) 586 } 587 588 fn check_collector( 589 module: Option<wasmtime_environ::Collector>, 590 host: Option<wasmtime_environ::Collector>, 591 ) -> Result<()> { 592 match (module, host) { 593 (None, None) => Ok(()), 594 (Some(module), Some(host)) if module == host => Ok(()), 595 596 (None, Some(_)) => { 597 bail!("module was compiled without GC but GC is enabled in the host") 598 } 599 (Some(_), None) => { 600 bail!("module was compiled with GC however GC is disabled in the host") 601 } 602 603 (Some(module), Some(host)) => { 604 bail!( 605 "module was compiled for the {module} collector but \ 606 the host is configured to use the {host} collector", 607 ) 608 } 609 } 610 } 611 } 612 613 #[cfg(test)] 614 mod test { 615 use super::*; 616 use crate::{Config, Module, OptLevel}; 617 use std::{ 618 collections::hash_map::DefaultHasher, 619 hash::{Hash, Hasher}, 620 }; 621 use tempfile::TempDir; 622 623 #[test] 624 fn test_architecture_mismatch() -> Result<()> { 625 let engine = Engine::default(); 626 let mut metadata = Metadata::new(&engine); 627 metadata.target = "unknown-generic-linux".to_string(); 628 629 match metadata.check_compatible(&engine) { 630 Ok(_) => unreachable!(), 631 Err(e) => assert_eq!( 632 e.to_string(), 633 "Module was compiled for architecture 'unknown'", 634 ), 635 } 636 637 Ok(()) 638 } 639 640 #[test] 641 #[cfg(target_arch = "x86_64")] // test on a platform that is known to use 642 // Cranelift 643 fn test_os_mismatch() -> Result<()> { 644 let engine = Engine::default(); 645 let mut metadata = Metadata::new(&engine); 646 647 metadata.target = format!( 648 "{}-generic-unknown", 649 target_lexicon::Triple::host().architecture 650 ); 651 652 match metadata.check_compatible(&engine) { 653 Ok(_) => unreachable!(), 654 Err(e) => assert_eq!( 655 e.to_string(), 656 "Module was compiled for operating system 'unknown'", 657 ), 658 } 659 660 Ok(()) 661 } 662 663 #[test] 664 fn test_cranelift_flags_mismatch() -> Result<()> { 665 let engine = Engine::default(); 666 let mut metadata = Metadata::new(&engine); 667 668 metadata 669 .shared_flags 670 .push(("preserve_frame_pointers", FlagValue::Bool(false))); 671 672 match metadata.check_compatible(&engine) { 673 Ok(_) => unreachable!(), 674 Err(e) => assert!(format!("{e:?}").starts_with( 675 "\ 676 compilation settings of module incompatible with native host 677 678 Caused by: 679 setting \"preserve_frame_pointers\" is configured to Bool(false) which is not supported" 680 )), 681 } 682 683 Ok(()) 684 } 685 686 #[test] 687 fn test_isa_flags_mismatch() -> Result<()> { 688 let engine = Engine::default(); 689 let mut metadata = Metadata::new(&engine); 690 691 metadata 692 .isa_flags 693 .push(("not_a_flag", FlagValue::Bool(true))); 694 695 match metadata.check_compatible(&engine) { 696 Ok(_) => unreachable!(), 697 Err(e) => assert!( 698 format!("{e:?}").starts_with( 699 "\ 700 compilation settings of module incompatible with native host 701 702 Caused by: 703 don't know how to test for target-specific flag \"not_a_flag\" at runtime", 704 ), 705 "bad error {e:?}", 706 ), 707 } 708 709 Ok(()) 710 } 711 712 #[test] 713 #[cfg_attr(miri, ignore)] 714 #[cfg(target_pointer_width = "64")] // different defaults on 32-bit platforms 715 fn test_tunables_int_mismatch() -> Result<()> { 716 let engine = Engine::default(); 717 let mut metadata = Metadata::new(&engine); 718 719 metadata.tunables.memory_guard_size = 0; 720 721 match metadata.check_compatible(&engine) { 722 Ok(_) => unreachable!(), 723 Err(e) => assert_eq!(e.to_string(), "Module was compiled with a memory guard size of '0' but '33554432' is expected for the host"), 724 } 725 726 Ok(()) 727 } 728 729 #[test] 730 fn test_tunables_bool_mismatch() -> Result<()> { 731 let mut config = Config::new(); 732 config.epoch_interruption(true); 733 734 let engine = Engine::new(&config)?; 735 let mut metadata = Metadata::new(&engine); 736 metadata.tunables.epoch_interruption = false; 737 738 match metadata.check_compatible(&engine) { 739 Ok(_) => unreachable!(), 740 Err(e) => assert_eq!( 741 e.to_string(), 742 "Module was compiled without epoch interruption but it is enabled for the host" 743 ), 744 } 745 746 let mut config = Config::new(); 747 config.epoch_interruption(false); 748 749 let engine = Engine::new(&config)?; 750 let mut metadata = Metadata::new(&engine); 751 metadata.tunables.epoch_interruption = true; 752 753 match metadata.check_compatible(&engine) { 754 Ok(_) => unreachable!(), 755 Err(e) => assert_eq!( 756 e.to_string(), 757 "Module was compiled with epoch interruption but it is not enabled for the host" 758 ), 759 } 760 761 Ok(()) 762 } 763 764 #[test] 765 #[cfg(target_arch = "x86_64")] // test on a platform that is known to 766 // implement threads 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