wasmtime/engine/
serialization.rs

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
24use crate::prelude::*;
25use crate::{Engine, ModuleVersionStrategy, Precompiled};
26use core::str::FromStr;
27use object::endian::Endianness;
28#[cfg(any(feature = "cranelift", feature = "winch"))]
29use object::write::{Object, StandardSegment};
30use object::{read::elf::ElfFile64, FileFlags, Object as _, ObjectSection};
31use serde_derive::{Deserialize, Serialize};
32use wasmtime_environ::obj;
33use wasmtime_environ::{FlagValue, ObjectKind, Tunables};
34
35const 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.
45pub 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"))]
121pub 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
145fn 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
163pub fn detect_precompiled_bytes(bytes: &[u8]) -> Option<Precompiled> {
164    detect_precompiled(ElfFile64::parse(bytes).ok()?)
165}
166
167#[cfg(feature = "std")]
168pub 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)]
175pub 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)]
187struct 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
209impl 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)]
614mod 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                "\
676compilation settings of module incompatible with native host
677
678Caused 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                    "\
700compilation settings of module incompatible with native host
701
702Caused 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}