Skip to main content

wasmtime/runtime/func/
typed.rs

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