component_test_util/
lib.rs

1use anyhow::Result;
2use arbitrary::Arbitrary;
3use std::mem::MaybeUninit;
4use wasmtime::component::__internal::{
5    CanonicalAbiInfo, InstanceType, InterfaceType, LiftContext, LowerContext,
6};
7use wasmtime::component::{ComponentNamedList, ComponentType, Func, Lift, Lower, TypedFunc, Val};
8use wasmtime::{AsContextMut, Config, Engine};
9
10pub trait TypedFuncExt<P, R> {
11    fn call_and_post_return(&self, store: impl AsContextMut, params: P) -> Result<R>;
12}
13
14impl<P, R> TypedFuncExt<P, R> for TypedFunc<P, R>
15where
16    P: ComponentNamedList + Lower,
17    R: ComponentNamedList + Lift,
18{
19    fn call_and_post_return(&self, mut store: impl AsContextMut, params: P) -> Result<R> {
20        let result = self.call(&mut store, params)?;
21        self.post_return(&mut store)?;
22        Ok(result)
23    }
24}
25
26pub trait FuncExt {
27    fn call_and_post_return(
28        &self,
29        store: impl AsContextMut,
30        params: &[Val],
31        results: &mut [Val],
32    ) -> Result<()>;
33}
34
35impl FuncExt for Func {
36    fn call_and_post_return(
37        &self,
38        mut store: impl AsContextMut,
39        params: &[Val],
40        results: &mut [Val],
41    ) -> Result<()> {
42        self.call(&mut store, params, results)?;
43        self.post_return(&mut store)?;
44        Ok(())
45    }
46}
47
48pub fn config() -> Config {
49    drop(env_logger::try_init());
50
51    let mut config = Config::new();
52    config.wasm_component_model(true);
53
54    // When `WASMTIME_TEST_NO_HOG_MEMORY` is set it means we're in qemu. The
55    // component model tests create a disproportionate number of instances so
56    // try to cut down on virtual memory usage by avoiding 4G reservations.
57    if std::env::var("WASMTIME_TEST_NO_HOG_MEMORY").is_ok() {
58        config.memory_reservation(0);
59        config.memory_guard_size(0);
60    }
61    config
62}
63
64pub fn engine() -> Engine {
65    Engine::new(&config()).unwrap()
66}
67
68pub fn async_engine() -> Engine {
69    let mut config = config();
70    config.async_support(true);
71    Engine::new(&config).unwrap()
72}
73
74/// Newtype wrapper for `f32` whose `PartialEq` impl considers NaNs equal to each other.
75#[derive(Copy, Clone, Debug, Arbitrary)]
76pub struct Float32(pub f32);
77
78/// Newtype wrapper for `f64` whose `PartialEq` impl considers NaNs equal to each other.
79#[derive(Copy, Clone, Debug, Arbitrary)]
80pub struct Float64(pub f64);
81
82macro_rules! forward_impls {
83    ($($a:ty => $b:ty,)*) => ($(
84        unsafe impl ComponentType for $a {
85            type Lower = <$b as ComponentType>::Lower;
86
87            const ABI: CanonicalAbiInfo = <$b as ComponentType>::ABI;
88
89            #[inline]
90            fn typecheck(ty: &InterfaceType, types: &InstanceType<'_>) -> Result<()> {
91                <$b as ComponentType>::typecheck(ty, types)
92            }
93        }
94
95        unsafe impl Lower for $a {
96            fn lower<U>(
97                &self,
98                cx: &mut LowerContext<'_, U>,
99                ty: InterfaceType,
100                dst: &mut MaybeUninit<Self::Lower>,
101            ) -> Result<()> {
102                <$b as Lower>::lower(&self.0, cx, ty, dst)
103            }
104
105            fn store<U>(&self, cx: &mut LowerContext<'_, U>, ty: InterfaceType, offset: usize) -> Result<()> {
106                <$b as Lower>::store(&self.0, cx, ty, offset)
107            }
108        }
109
110        unsafe impl Lift for $a {
111            fn lift(cx: &mut LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result<Self> {
112                Ok(Self(<$b as Lift>::lift(cx, ty, src)?))
113            }
114
115            fn load(cx: &mut LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result<Self> {
116                Ok(Self(<$b as Lift>::load(cx, ty, bytes)?))
117            }
118        }
119
120        impl PartialEq for $a {
121            fn eq(&self, other: &Self) -> bool {
122                self.0 == other.0 || (self.0.is_nan() && other.0.is_nan())
123            }
124        }
125    )*)
126}
127
128forward_impls! {
129    Float32 => f32,
130    Float64 => f64,
131}
132
133/// Helper method to apply `wast_config` to `config`.
134pub fn apply_wast_config(config: &mut Config, wast_config: &wasmtime_wast_util::WastConfig) {
135    use wasmtime_environ::TripleExt;
136    use wasmtime_wast_util::{Collector, Compiler};
137
138    config.strategy(match wast_config.compiler {
139        Compiler::CraneliftNative | Compiler::CraneliftPulley => wasmtime::Strategy::Cranelift,
140        Compiler::Winch => wasmtime::Strategy::Winch,
141    });
142    if let Compiler::CraneliftPulley = wast_config.compiler {
143        config
144            .target(&target_lexicon::Triple::pulley_host().to_string())
145            .unwrap();
146    }
147    config.collector(match wast_config.collector {
148        Collector::Auto => wasmtime::Collector::Auto,
149        Collector::Null => wasmtime::Collector::Null,
150        Collector::DeferredReferenceCounting => wasmtime::Collector::DeferredReferenceCounting,
151    });
152}
153
154/// Helper method to apply `test_config` to `config`.
155pub fn apply_test_config(config: &mut Config, test_config: &wasmtime_wast_util::TestConfig) {
156    let wasmtime_wast_util::TestConfig {
157        memory64,
158        custom_page_sizes,
159        multi_memory,
160        threads,
161        gc,
162        function_references,
163        relaxed_simd,
164        reference_types,
165        tail_call,
166        extended_const,
167        wide_arithmetic,
168        component_model_async,
169        nan_canonicalization,
170        simd,
171
172        hogs_memory: _,
173        gc_types: _,
174    } = *test_config;
175    // Note that all of these proposals/features are currently default-off to
176    // ensure that we annotate all tests accurately with what features they
177    // need, even in the future when features are stabilized.
178    let memory64 = memory64.unwrap_or(false);
179    let custom_page_sizes = custom_page_sizes.unwrap_or(false);
180    let multi_memory = multi_memory.unwrap_or(false);
181    let threads = threads.unwrap_or(false);
182    let gc = gc.unwrap_or(false);
183    let tail_call = tail_call.unwrap_or(false);
184    let extended_const = extended_const.unwrap_or(false);
185    let wide_arithmetic = wide_arithmetic.unwrap_or(false);
186    let component_model_async = component_model_async.unwrap_or(false);
187    let nan_canonicalization = nan_canonicalization.unwrap_or(false);
188    let relaxed_simd = relaxed_simd.unwrap_or(false);
189
190    // Some proposals in wasm depend on previous proposals. For example the gc
191    // proposal depends on function-references which depends on reference-types.
192    // To avoid needing to enable all of them at once implicitly enable
193    // downstream proposals once the end proposal is enabled (e.g. when enabling
194    // gc that also enables function-references and reference-types).
195    let function_references = gc || function_references.unwrap_or(false);
196    let reference_types = function_references || reference_types.unwrap_or(false);
197    let simd = relaxed_simd || simd.unwrap_or(false);
198
199    config
200        .wasm_multi_memory(multi_memory)
201        .wasm_threads(threads)
202        .wasm_memory64(memory64)
203        .wasm_function_references(function_references)
204        .wasm_gc(gc)
205        .wasm_reference_types(reference_types)
206        .wasm_relaxed_simd(relaxed_simd)
207        .wasm_simd(simd)
208        .wasm_tail_call(tail_call)
209        .wasm_custom_page_sizes(custom_page_sizes)
210        .wasm_extended_const(extended_const)
211        .wasm_wide_arithmetic(wide_arithmetic)
212        .wasm_component_model_async(component_model_async)
213        .cranelift_nan_canonicalization(nan_canonicalization);
214}