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