wasmtime/runtime/func/
typed.rs

1use super::invoke_wasm_and_catch_traps;
2use crate::prelude::*;
3use crate::runtime::vm::VMFuncRef;
4use crate::store::{AutoAssertNoGc, StoreOpaque};
5use crate::{
6    AsContext, AsContextMut, Engine, Func, FuncType, HeapType, NoFunc, RefType, StoreContextMut,
7    ValRaw, ValType,
8};
9use core::ffi::c_void;
10use core::marker;
11use core::mem::{self, MaybeUninit};
12use core::ptr::{self, NonNull};
13use wasmtime_environ::VMSharedTypeIndex;
14
15/// A statically typed WebAssembly function.
16///
17/// Values of this type represent statically type-checked WebAssembly functions.
18/// The function within a [`TypedFunc`] is statically known to have `Params` as its
19/// parameters and `Results` as its results.
20///
21/// This structure is created via [`Func::typed`] or [`TypedFunc::new_unchecked`].
22/// For more documentation about this see those methods.
23pub struct TypedFunc<Params, Results> {
24    _a: marker::PhantomData<fn(Params) -> Results>,
25    ty: FuncType,
26    func: Func,
27}
28
29impl<Params, Results> Clone for TypedFunc<Params, Results> {
30    fn clone(&self) -> TypedFunc<Params, Results> {
31        Self {
32            _a: marker::PhantomData,
33            ty: self.ty.clone(),
34            func: self.func,
35        }
36    }
37}
38
39impl<Params, Results> TypedFunc<Params, Results>
40where
41    Params: WasmParams,
42    Results: WasmResults,
43{
44    /// An unchecked version of [`Func::typed`] which does not perform a
45    /// typecheck and simply assumes that the type declared here matches the
46    /// type of this function.
47    ///
48    /// The semantics of this function are the same as [`Func::typed`] except
49    /// that no error is returned because no typechecking is done.
50    ///
51    /// # Unsafety
52    ///
53    /// This function only safe to call if `typed` would otherwise return `Ok`
54    /// for the same `Params` and `Results` specified. If `typed` would return
55    /// an error then the returned `TypedFunc` is memory unsafe to invoke.
56    pub unsafe fn new_unchecked(store: impl AsContext, func: Func) -> TypedFunc<Params, Results> {
57        let store = store.as_context().0;
58        unsafe { Self::_new_unchecked(store, func) }
59    }
60
61    pub(crate) unsafe fn _new_unchecked(
62        store: &StoreOpaque,
63        func: Func,
64    ) -> TypedFunc<Params, Results> {
65        let ty = func.load_ty(store);
66        TypedFunc {
67            _a: marker::PhantomData,
68            ty,
69            func,
70        }
71    }
72
73    /// Returns the underlying [`Func`] that this is wrapping, losing the static
74    /// type information in the process.
75    pub fn func(&self) -> &Func {
76        &self.func
77    }
78
79    /// Invokes this WebAssembly function with the specified parameters.
80    ///
81    /// Returns either the results of the call, or a [`Trap`] if one happened.
82    ///
83    /// For more information, see the [`Func::typed`] and [`Func::call`]
84    /// documentation.
85    ///
86    /// # Errors
87    ///
88    /// For more information on errors see the documentation on [`Func::call`].
89    ///
90    /// # Panics
91    ///
92    /// This function will panic if it is called when the underlying [`Func`] is
93    /// connected to an asynchronous store.
94    ///
95    /// [`Trap`]: crate::Trap
96    #[inline]
97    pub fn call(&self, mut store: impl AsContextMut, params: Params) -> Result<Results> {
98        let mut store = store.as_context_mut();
99        assert!(
100            !store.0.async_support(),
101            "must use `call_async` with async stores"
102        );
103
104        let func = self.func.vm_func_ref(store.0);
105        unsafe { Self::call_raw(&mut store, &self.ty, func, params) }
106    }
107
108    /// Invokes this WebAssembly function with the specified parameters.
109    ///
110    /// Returns either the results of the call, or a [`Trap`] if one happened.
111    ///
112    /// For more information, see the [`Func::typed`] and [`Func::call_async`]
113    /// documentation.
114    ///
115    /// # Errors
116    ///
117    /// For more information on errors see the documentation on [`Func::call`].
118    ///
119    /// # Panics
120    ///
121    /// This function will panic if it is called when the underlying [`Func`] is
122    /// connected to a synchronous store.
123    ///
124    /// [`Trap`]: crate::Trap
125    #[cfg(feature = "async")]
126    pub async fn call_async(
127        &self,
128        mut store: impl AsContextMut<Data: Send>,
129        params: Params,
130    ) -> Result<Results>
131    where
132        Params: Sync,
133        Results: Sync,
134    {
135        let mut store = store.as_context_mut();
136        assert!(
137            store.0.async_support(),
138            "must use `call` with non-async stores"
139        );
140
141        store
142            .on_fiber(|store| {
143                let func = self.func.vm_func_ref(store.0);
144                unsafe { Self::call_raw(store, &self.ty, func, params) }
145            })
146            .await?
147    }
148
149    /// Do a raw call of a typed function.
150    ///
151    /// # Safety
152    ///
153    /// `func` must be of the given type, and it additionally must be a valid
154    /// store-owned pointer within the `store` provided.
155    pub(crate) unsafe fn call_raw<T>(
156        store: &mut StoreContextMut<'_, T>,
157        ty: &FuncType,
158        func: ptr::NonNull<VMFuncRef>,
159        params: Params,
160    ) -> Result<Results> {
161        // double-check that params/results match for this function's type in
162        // debug mode.
163        //
164        // SAFETY: this function requires that `ptr` is a valid function
165        // pointer.
166        unsafe {
167            if cfg!(debug_assertions) {
168                Self::debug_typecheck(store.0, func.as_ref().type_index);
169            }
170        }
171
172        // Validate that all runtime values flowing into this store indeed
173        // belong within this store, otherwise it would be unsafe for store
174        // values to cross each other.
175
176        union Storage<T: Copy, U: Copy> {
177            params: MaybeUninit<T>,
178            results: U,
179        }
180
181        let mut storage = Storage::<Params::ValRawStorage, Results::ValRawStorage> {
182            params: MaybeUninit::uninit(),
183        };
184
185        {
186            let mut store = AutoAssertNoGc::new(store.0);
187            // SAFETY: it's safe to use a union field here as the field itself
188            // is `MaybeUninit<_>` meaning nothing is accidentally considered
189            // initialized.
190            let dst: &mut MaybeUninit<_> = unsafe { &mut storage.params };
191            params.store(&mut store, ty, dst)?;
192        }
193
194        // Try to capture only a single variable (a tuple) in the closure below.
195        // This means the size of the closure is one pointer and is much more
196        // efficient to move in memory. This closure is actually invoked on the
197        // other side of a C++ shim, so it can never be inlined enough to make
198        // the memory go away, so the size matters here for performance.
199        let mut captures = (func, storage);
200
201        let result = invoke_wasm_and_catch_traps(store, |caller, vm| {
202            let (func_ref, storage) = &mut captures;
203            let storage_len = mem::size_of_val::<Storage<_, _>>(storage) / mem::size_of::<ValRaw>();
204            let storage: *mut Storage<_, _> = storage;
205            let storage = storage.cast::<ValRaw>();
206            let storage = core::ptr::slice_from_raw_parts_mut(storage, storage_len);
207            let storage = NonNull::new(storage).unwrap();
208
209            // SAFETY: this function's own contract is that `func_ref` is safe
210            // to call and additionally that the params/results are correctly
211            // ascribed for this function call to be safe.
212            unsafe { VMFuncRef::array_call(*func_ref, vm, caller, storage) }
213        });
214
215        let (_, storage) = captures;
216        result?;
217
218        let mut store = AutoAssertNoGc::new(store.0);
219        // SAFETY: this function is itself unsafe to ensure that the result type
220        // ascription is correct for `Results` and matches the actual function.
221        // Additionally given the correct type ascription all of the `results`
222        // accessed here should be validly initialized.
223        unsafe { Ok(Results::load(&mut store, &storage.results)) }
224    }
225
226    /// Purely a debug-mode assertion, not actually used in release builds.
227    fn debug_typecheck(store: &StoreOpaque, func: VMSharedTypeIndex) {
228        let ty = FuncType::from_shared_type_index(store.engine(), func);
229        Params::typecheck(store.engine(), ty.params(), TypeCheckPosition::Param)
230            .expect("params should match");
231        Results::typecheck(store.engine(), ty.results(), TypeCheckPosition::Result)
232            .expect("results should match");
233    }
234}
235
236#[doc(hidden)]
237#[derive(Copy, Clone)]
238pub enum TypeCheckPosition {
239    Param,
240    Result,
241}
242
243/// A trait implemented for types which can be arguments and results for
244/// closures passed to [`Func::wrap`] as well as parameters to [`Func::typed`].
245///
246/// This trait should not be implemented by user types. This trait may change at
247/// any time internally. The types which implement this trait, however, are
248/// stable over time.
249///
250/// For more information see [`Func::wrap`] and [`Func::typed`]
251pub unsafe trait WasmTy: Send {
252    // Do a "static" (aka at time of `func.typed::<P, R>()`) ahead-of-time type
253    // check for this type at the given position. You probably don't need to
254    // override this trait method.
255    #[doc(hidden)]
256    #[inline]
257    fn typecheck(engine: &Engine, actual: ValType, position: TypeCheckPosition) -> Result<()> {
258        let expected = Self::valtype();
259        debug_assert!(expected.comes_from_same_engine(engine));
260        debug_assert!(actual.comes_from_same_engine(engine));
261        match position {
262            // The caller is expecting to receive a `T` and the callee is
263            // actually returning a `U`, so ensure that `U <: T`.
264            TypeCheckPosition::Result => actual.ensure_matches(engine, &expected),
265            // The caller is expecting to pass a `T` and the callee is expecting
266            // to receive a `U`, so ensure that `T <: U`.
267            TypeCheckPosition::Param => match (expected.as_ref(), actual.as_ref()) {
268                // ... except that this technically-correct check would overly
269                // restrict the usefulness of our typed function APIs for the
270                // specific case of concrete reference types. Let's work through
271                // an example.
272                //
273                // Consider functions that take a `(ref param $some_func_type)`
274                // parameter:
275                //
276                // * We cannot have a static `wasmtime::SomeFuncTypeRef` type
277                //   that implements `WasmTy` specifically for `(ref null
278                //   $some_func_type)` because Wasm modules, and their types,
279                //   are loaded dynamically at runtime.
280                //
281                // * Therefore the embedder's only option for `T <: (ref null
282                //   $some_func_type)` is `T = (ref null nofunc)` aka
283                //   `Option<wasmtime::NoFunc>`.
284                //
285                // * But that static type means they can *only* pass in the null
286                //   function reference as an argument to the typed function.
287                //   This is way too restrictive! For ergonomics, we want them
288                //   to be able to pass in a `wasmtime::Func` whose type is
289                //   `$some_func_type`!
290                //
291                // To lift this constraint and enable better ergonomics for
292                // embedders, we allow `top(T) <: top(U)` -- i.e. they are part
293                // of the same type hierarchy and a dynamic cast could possibly
294                // succeed -- for the specific case of concrete heap type
295                // parameters, and fall back to dynamic type checks on the
296                // arguments passed to each invocation, as necessary.
297                (Some(expected_ref), Some(actual_ref)) if actual_ref.heap_type().is_concrete() => {
298                    expected_ref
299                        .heap_type()
300                        .top()
301                        .ensure_matches(engine, &actual_ref.heap_type().top())
302                }
303                _ => expected.ensure_matches(engine, &actual),
304            },
305        }
306    }
307
308    // The value type that this Type represents.
309    #[doc(hidden)]
310    fn valtype() -> ValType;
311
312    #[doc(hidden)]
313    fn may_gc() -> bool {
314        match Self::valtype() {
315            ValType::Ref(_) => true,
316            ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => false,
317        }
318    }
319
320    // Dynamic checks that this value is being used with the correct store
321    // context.
322    #[doc(hidden)]
323    fn compatible_with_store(&self, store: &StoreOpaque) -> bool;
324
325    // Dynamic checks that `self <: actual` for concrete type arguments. See the
326    // comment above in `WasmTy::typecheck`.
327    //
328    // Only ever called for concrete reference type arguments, so any type which
329    // is not in a type hierarchy with concrete reference types can implement
330    // this with `unreachable!()`.
331    #[doc(hidden)]
332    fn dynamic_concrete_type_check(
333        &self,
334        store: &StoreOpaque,
335        nullable: bool,
336        actual: &HeapType,
337    ) -> Result<()>;
338
339    // Is this a GC-managed reference that actually points to a GC object? That
340    // is, `self` is *not* an `i31`, null reference, or uninhabited type.
341    //
342    // Note that it is okay if this returns false positives (i.e. `true` for
343    // `Rooted<AnyRef>` without actually looking up the rooted `anyref` in the
344    // store and reflecting on it to determine whether it is actually an
345    // `i31`). However, it is not okay if this returns false negatives.
346    #[doc(hidden)]
347    #[inline]
348    fn is_vmgcref_and_points_to_object(&self) -> bool {
349        Self::valtype().is_vmgcref_type_and_points_to_object()
350    }
351
352    // Store `self` into `ptr`.
353    //
354    // NB: We _must not_ trigger a GC when passing refs from host code into Wasm
355    // (e.g. returned from a host function or passed as arguments to a Wasm
356    // function). After insertion into the activations table, the reference is
357    // no longer rooted. If multiple references are being sent from the host
358    // into Wasm and we allowed GCs during insertion, then the following events
359    // could happen:
360    //
361    // * Reference A is inserted into the activations table. This does not
362    //   trigger a GC, but does fill the table to capacity.
363    //
364    // * The caller's reference to A is removed. Now the only reference to A is
365    //   from the activations table.
366    //
367    // * Reference B is inserted into the activations table. Because the table
368    //   is at capacity, a GC is triggered.
369    //
370    // * A is reclaimed because the only reference keeping it alive was the
371    //   activation table's reference (it isn't inside any Wasm frames on the
372    //   stack yet, so stack scanning and stack maps don't increment its
373    //   reference count).
374    //
375    // * We transfer control to Wasm, giving it A and B. Wasm uses A. That's a
376    //   use-after-free bug.
377    //
378    // In conclusion, to prevent uses-after-free bugs, we cannot GC while
379    // converting types into their raw ABI forms.
380    #[doc(hidden)]
381    fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()>;
382
383    // Load a version of `Self` from the `ptr` provided.
384    //
385    // # Safety
386    //
387    // This function is unsafe as it's up to the caller to ensure that `ptr` is
388    // valid for this given type.
389    #[doc(hidden)]
390    unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self;
391}
392
393macro_rules! integers {
394    ($($primitive:ident/$get_primitive:ident => $ty:ident)*) => ($(
395        unsafe impl WasmTy for $primitive {
396            #[inline]
397            fn valtype() -> ValType {
398                ValType::$ty
399            }
400            #[inline]
401            fn compatible_with_store(&self, _: &StoreOpaque) -> bool {
402                true
403            }
404            #[inline]
405            fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &HeapType) -> Result<()> {
406                unreachable!()
407            }
408            #[inline]
409            fn store(self, _store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
410                ptr.write(ValRaw::$primitive(self));
411                Ok(())
412            }
413            #[inline]
414            unsafe fn load(_store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
415                ptr.$get_primitive()
416            }
417        }
418    )*)
419}
420
421integers! {
422    i32/get_i32 => I32
423    i64/get_i64 => I64
424    u32/get_u32 => I32
425    u64/get_u64 => I64
426}
427
428macro_rules! floats {
429    ($($float:ident/$int:ident/$get_float:ident => $ty:ident)*) => ($(
430        unsafe impl WasmTy for $float {
431            #[inline]
432            fn valtype() -> ValType {
433                ValType::$ty
434            }
435            #[inline]
436            fn compatible_with_store(&self, _: &StoreOpaque) -> bool {
437                true
438            }
439            #[inline]
440            fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &HeapType) -> Result<()> {
441                unreachable!()
442            }
443            #[inline]
444            fn store(self, _store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
445                ptr.write(ValRaw::$float(self.to_bits()));
446                Ok(())
447            }
448            #[inline]
449            unsafe fn load(_store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
450                $float::from_bits(ptr.$get_float())
451            }
452        }
453    )*)
454}
455
456floats! {
457    f32/u32/get_f32 => F32
458    f64/u64/get_f64 => F64
459}
460
461unsafe impl WasmTy for NoFunc {
462    #[inline]
463    fn valtype() -> ValType {
464        ValType::Ref(RefType::new(false, HeapType::NoFunc))
465    }
466
467    #[inline]
468    fn compatible_with_store(&self, _store: &StoreOpaque) -> bool {
469        match self._inner {}
470    }
471
472    #[inline]
473    fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &HeapType) -> Result<()> {
474        match self._inner {}
475    }
476
477    #[inline]
478    fn is_vmgcref_and_points_to_object(&self) -> bool {
479        match self._inner {}
480    }
481
482    #[inline]
483    fn store(self, _store: &mut AutoAssertNoGc<'_>, _ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
484        match self._inner {}
485    }
486
487    #[inline]
488    unsafe fn load(_store: &mut AutoAssertNoGc<'_>, _ptr: &ValRaw) -> Self {
489        unreachable!("NoFunc is uninhabited")
490    }
491}
492
493unsafe impl WasmTy for Option<NoFunc> {
494    #[inline]
495    fn valtype() -> ValType {
496        ValType::Ref(RefType::new(true, HeapType::NoFunc))
497    }
498
499    #[inline]
500    fn compatible_with_store(&self, _store: &StoreOpaque) -> bool {
501        true
502    }
503
504    #[inline]
505    fn dynamic_concrete_type_check(
506        &self,
507        _: &StoreOpaque,
508        nullable: bool,
509        ty: &HeapType,
510    ) -> Result<()> {
511        if nullable {
512            // `(ref null nofunc) <: (ref null $f)` for all function types `$f`.
513            Ok(())
514        } else {
515            bail!("argument type mismatch: expected non-nullable (ref {ty}), found null reference")
516        }
517    }
518
519    #[inline]
520    fn store(self, _store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
521        ptr.write(ValRaw::funcref(ptr::null_mut()));
522        Ok(())
523    }
524
525    #[inline]
526    unsafe fn load(_store: &mut AutoAssertNoGc<'_>, _ptr: &ValRaw) -> Self {
527        None
528    }
529}
530
531unsafe impl WasmTy for Func {
532    #[inline]
533    fn valtype() -> ValType {
534        ValType::Ref(RefType::new(false, HeapType::Func))
535    }
536
537    #[inline]
538    fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
539        self.store == store.id()
540    }
541
542    #[inline]
543    fn dynamic_concrete_type_check(
544        &self,
545        store: &StoreOpaque,
546        _nullable: bool,
547        expected: &HeapType,
548    ) -> Result<()> {
549        let expected = expected.unwrap_concrete_func();
550        self.ensure_matches_ty(store, expected)
551            .context("argument type mismatch for reference to concrete type")
552    }
553
554    #[inline]
555    fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
556        let abi = self.vm_func_ref(store);
557        ptr.write(ValRaw::funcref(abi.cast::<c_void>().as_ptr()));
558        Ok(())
559    }
560
561    #[inline]
562    unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
563        let p = NonNull::new(ptr.get_funcref()).unwrap().cast();
564
565        // SAFETY: it's an unsafe contract of `load` that it's only provided
566        // valid wasm values owned by `store`.
567        unsafe { Func::from_vm_func_ref(store.id(), p) }
568    }
569}
570
571unsafe impl WasmTy for Option<Func> {
572    #[inline]
573    fn valtype() -> ValType {
574        ValType::FUNCREF
575    }
576
577    #[inline]
578    fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
579        if let Some(f) = self {
580            f.compatible_with_store(store)
581        } else {
582            true
583        }
584    }
585
586    fn dynamic_concrete_type_check(
587        &self,
588        store: &StoreOpaque,
589        nullable: bool,
590        expected: &HeapType,
591    ) -> Result<()> {
592        if let Some(f) = self {
593            let expected = expected.unwrap_concrete_func();
594            f.ensure_matches_ty(store, expected)
595                .context("argument type mismatch for reference to concrete type")
596        } else if nullable {
597            Ok(())
598        } else {
599            bail!(
600                "argument type mismatch: expected non-nullable (ref {expected}), found null reference"
601            )
602        }
603    }
604
605    #[inline]
606    fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
607        let raw = if let Some(f) = self {
608            f.vm_func_ref(store).as_ptr()
609        } else {
610            ptr::null_mut()
611        };
612        ptr.write(ValRaw::funcref(raw.cast::<c_void>()));
613        Ok(())
614    }
615
616    #[inline]
617    unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
618        let ptr = NonNull::new(ptr.get_funcref())?.cast();
619
620        // SAFETY: it's an unsafe contract of `load` that it's only provided
621        // valid wasm values owned by `store`.
622        unsafe { Some(Func::from_vm_func_ref(store.id(), ptr)) }
623    }
624}
625
626/// A trait used for [`Func::typed`] and with [`TypedFunc`] to represent the set of
627/// parameters for wasm functions.
628///
629/// This is implemented for bare types that can be passed to wasm as well as
630/// tuples of those types.
631pub unsafe trait WasmParams: Send {
632    #[doc(hidden)]
633    type ValRawStorage: Copy;
634
635    #[doc(hidden)]
636    fn typecheck(
637        engine: &Engine,
638        params: impl ExactSizeIterator<Item = crate::ValType>,
639        position: TypeCheckPosition,
640    ) -> Result<()>;
641
642    #[doc(hidden)]
643    fn vmgcref_pointing_to_object_count(&self) -> usize;
644
645    #[doc(hidden)]
646    fn store(
647        self,
648        store: &mut AutoAssertNoGc<'_>,
649        func_ty: &FuncType,
650        dst: &mut MaybeUninit<Self::ValRawStorage>,
651    ) -> Result<()>;
652}
653
654// Forward an impl from `T` to `(T,)` for convenience if there's only one
655// parameter.
656unsafe impl<T> WasmParams for T
657where
658    T: WasmTy,
659{
660    type ValRawStorage = <(T,) as WasmParams>::ValRawStorage;
661
662    fn typecheck(
663        engine: &Engine,
664        params: impl ExactSizeIterator<Item = crate::ValType>,
665        position: TypeCheckPosition,
666    ) -> Result<()> {
667        <(T,) as WasmParams>::typecheck(engine, params, position)
668    }
669
670    #[inline]
671    fn vmgcref_pointing_to_object_count(&self) -> usize {
672        T::is_vmgcref_and_points_to_object(self) as usize
673    }
674
675    #[inline]
676    fn store(
677        self,
678        store: &mut AutoAssertNoGc<'_>,
679        func_ty: &FuncType,
680        dst: &mut MaybeUninit<Self::ValRawStorage>,
681    ) -> Result<()> {
682        <(T,) as WasmParams>::store((self,), store, func_ty, dst)
683    }
684}
685
686macro_rules! impl_wasm_params {
687    ($n:tt $($t:ident)*) => {
688        #[allow(non_snake_case, reason = "macro-generated code")]
689        unsafe impl<$($t: WasmTy,)*> WasmParams for ($($t,)*) {
690            type ValRawStorage = [ValRaw; $n];
691
692            fn typecheck(
693                _engine: &Engine,
694                mut params: impl ExactSizeIterator<Item = crate::ValType>,
695                _position: TypeCheckPosition,
696            ) -> Result<()> {
697                let mut _n = 0;
698
699                $(
700                    match params.next() {
701                        Some(t) => {
702                            _n += 1;
703                            $t::typecheck(_engine, t, _position)?
704                        },
705                        None => bail!("expected {} types, found {}", $n, params.len() + _n),
706                    }
707                )*
708
709                match params.next() {
710                    None => Ok(()),
711                    Some(_) => {
712                        _n += 1;
713                        bail!("expected {} types, found {}", $n, params.len() + _n)
714                    },
715                }
716            }
717
718            #[inline]
719            fn vmgcref_pointing_to_object_count(&self) -> usize {
720                let ($($t,)*) = self;
721                0 $(
722                    + $t.is_vmgcref_and_points_to_object() as usize
723                )*
724            }
725
726
727            #[inline]
728            fn store(
729                self,
730                _store: &mut AutoAssertNoGc<'_>,
731                _func_ty: &FuncType,
732                _ptr: &mut MaybeUninit<Self::ValRawStorage>,
733            ) -> Result<()> {
734                let ($($t,)*) = self;
735
736                let mut _i = 0;
737                $(
738                    if !$t.compatible_with_store(_store) {
739                        bail!("attempt to pass cross-`Store` value to Wasm as function argument");
740                    }
741
742                    if $t::valtype().is_ref() {
743                        let param_ty = _func_ty.param(_i).unwrap();
744                        let ref_ty = param_ty.unwrap_ref();
745                        let heap_ty = ref_ty.heap_type();
746                        if heap_ty.is_concrete() {
747                            $t.dynamic_concrete_type_check(_store, ref_ty.is_nullable(), heap_ty)?;
748                        }
749                    }
750
751                    let dst = map_maybe_uninit!(_ptr[_i]);
752                    $t.store(_store, dst)?;
753
754                    _i += 1;
755                )*
756                Ok(())
757            }
758        }
759    };
760}
761
762for_each_function_signature!(impl_wasm_params);
763
764/// A trait used for [`Func::typed`] and with [`TypedFunc`] to represent the set of
765/// results for wasm functions.
766pub unsafe trait WasmResults: WasmParams {
767    #[doc(hidden)]
768    unsafe fn load(store: &mut AutoAssertNoGc<'_>, abi: &Self::ValRawStorage) -> Self;
769}
770
771// Forwards from a bare type `T` to the 1-tuple type `(T,)`
772unsafe impl<T: WasmTy> WasmResults for T {
773    unsafe fn load(store: &mut AutoAssertNoGc<'_>, abi: &Self::ValRawStorage) -> Self {
774        // SAFETY: the one-element tuple and single-type impls behave the same
775        // way.
776        unsafe { <(T,) as WasmResults>::load(store, abi).0 }
777    }
778}
779
780macro_rules! impl_wasm_results {
781    ($n:tt $($t:ident)*) => {
782        #[allow(non_snake_case, reason = "macro-generated code")]
783        unsafe impl<$($t: WasmTy,)*> WasmResults for ($($t,)*) {
784            unsafe fn load(_store: &mut AutoAssertNoGc<'_>, abi: &Self::ValRawStorage) -> Self {
785                let [$($t,)*] = abi;
786
787                (
788                    // SAFETY: this is forwarding the unsafe contract of the outer
789                    // function to the inner functions here.
790                    $(unsafe { $t::load(_store, $t) },)*
791                )
792            }
793        }
794    };
795}
796
797for_each_function_signature!(impl_wasm_results);