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