Skip to main content

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