Skip to main content

wasmtime/runtime/component/func/
host.rs

1//! Implementation of calling Rust-defined functions from components.
2
3#[cfg(feature = "component-model-async")]
4use crate::component::concurrent;
5#[cfg(feature = "component-model-async")]
6use crate::component::concurrent::{Accessor, Status};
7use crate::component::func::{LiftContext, LowerContext};
8use crate::component::matching::InstanceType;
9use crate::component::storage::{slice_to_storage, slice_to_storage_mut};
10use crate::component::types::ComponentFunc;
11use crate::component::{ComponentNamedList, Instance, Lift, Lower, Val};
12use crate::prelude::*;
13use crate::runtime::vm::component::{
14    ComponentInstance, VMComponentContext, VMLowering, VMLoweringCallee,
15};
16use crate::runtime::vm::{VMOpaqueContext, VMStore};
17use crate::store::Asyncness;
18use crate::{AsContextMut, CallHook, StoreContextMut, ValRaw};
19use alloc::sync::Arc;
20use core::any::Any;
21use core::mem::{self, MaybeUninit};
22#[cfg(feature = "async")]
23use core::pin::Pin;
24use core::ptr::NonNull;
25use wasmtime_environ::component::{
26    CanonicalAbiInfo, InterfaceType, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS, OptionsIndex, TypeFuncIndex,
27};
28
29/// A host function suitable for passing into a component.
30///
31/// This structure represents a monomorphic host function that can only be used
32/// in the specific context of a particular store. This is generally not too
33/// too safe to use and is only meant for internal use.
34pub struct HostFunc {
35    /// The raw function pointer which Cranelift will invoke.
36    entrypoint: VMLoweringCallee,
37
38    /// The implementation of type-checking to ensure that this function
39    /// ascribes to the provided function type.
40    ///
41    /// This is used, for example, when a component imports a host function and
42    /// this will determine if the host function can be imported with the given
43    /// type.
44    typecheck: fn(TypeFuncIndex, &InstanceType<'_>) -> Result<()>,
45
46    /// The actual host function.
47    ///
48    /// This is frequently an empty allocation in the sense that the underlying
49    /// type is a zero-sized-type. Host functions are allowed, though, to close
50    /// over the environment as well.
51    func: Box<dyn Any + Send + Sync>,
52
53    /// Whether or not this host function was defined in such a way that async
54    /// stack switching is required when calling it.
55    asyncness: Asyncness,
56}
57
58impl core::fmt::Debug for HostFunc {
59    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
60        f.debug_struct("HostFunc").finish_non_exhaustive()
61    }
62}
63
64enum HostResult<T> {
65    Done(Result<T>),
66    #[cfg(feature = "component-model-async")]
67    Future(Pin<Box<dyn Future<Output = Result<T>> + Send>>),
68}
69
70impl HostFunc {
71    /// Creates a new host function based on the implementation of `func`.
72    ///
73    /// The `asyncness` parameter indicates whether the `func` requires
74    /// wasm to be on a fiber. This is used to propagate to the `Store` during
75    /// instantiation to ensure that this guarantee is met.
76    ///
77    /// Note that if `asyncness` is mistaken then that'll result in panics
78    /// in Wasmtime, but not memory unsafety.
79    fn new<T, F, P, R>(asyncness: Asyncness, func: F) -> Arc<HostFunc>
80    where
81        T: 'static,
82        R: Send + Sync + 'static,
83        F: HostFn<T, P, R> + Send + Sync + 'static,
84    {
85        Arc::new(HostFunc {
86            entrypoint: F::cabi_entrypoint,
87            typecheck: F::typecheck,
88            func: Box::new(func),
89            asyncness,
90        })
91    }
92
93    /// Equivalent for `Linker::func_wrap`
94    pub(crate) fn func_wrap<T, F, P, R>(func: F) -> Arc<HostFunc>
95    where
96        T: 'static,
97        F: Fn(StoreContextMut<T>, P) -> Result<R> + Send + Sync + 'static,
98        P: ComponentNamedList + Lift + 'static,
99        R: ComponentNamedList + Lower + 'static,
100    {
101        Self::new(
102            Asyncness::No,
103            StaticHostFn::<_, false>::new(move |store, params| {
104                HostResult::Done(func(store, params))
105            }),
106        )
107    }
108
109    /// Equivalent for `Linker::func_wrap_async`
110    #[cfg(feature = "async")]
111    pub(crate) fn func_wrap_async<T, F, P, R>(func: F) -> Arc<HostFunc>
112    where
113        T: 'static,
114        F: Fn(StoreContextMut<'_, T>, P) -> Box<dyn Future<Output = Result<R>> + Send + '_>
115            + Send
116            + Sync
117            + 'static,
118        P: ComponentNamedList + Lift + 'static,
119        R: ComponentNamedList + Lower + 'static,
120    {
121        Self::new(
122            Asyncness::Yes,
123            StaticHostFn::<_, false>::new(move |store, params| {
124                HostResult::Done(
125                    store
126                        .block_on(|store| Pin::from(func(store, params)))
127                        .and_then(|r| r),
128                )
129            }),
130        )
131    }
132
133    /// Equivalent for `Linker::func_wrap_concurrent`
134    #[cfg(feature = "component-model-async")]
135    pub(crate) fn func_wrap_concurrent<T, F, P, R>(func: F) -> Arc<HostFunc>
136    where
137        T: 'static,
138        F: Fn(&Accessor<T>, P) -> Pin<Box<dyn Future<Output = Result<R>> + Send + '_>>
139            + Send
140            + Sync
141            + 'static,
142        P: ComponentNamedList + Lift + 'static,
143        R: ComponentNamedList + Lower + 'static,
144    {
145        let func = Arc::new(func);
146        Self::new(
147            Asyncness::Yes,
148            StaticHostFn::<_, true>::new(move |store, params| {
149                let func = func.clone();
150                HostResult::Future(Box::pin(
151                    store.wrap_call(move |accessor| func(accessor, params)),
152                ))
153            }),
154        )
155    }
156
157    /// Equivalent of `Linker::func_new`
158    pub(crate) fn func_new<T, F>(func: F) -> Arc<HostFunc>
159    where
160        T: 'static,
161        F: Fn(StoreContextMut<'_, T>, ComponentFunc, &[Val], &mut [Val]) -> Result<()>
162            + Send
163            + Sync
164            + 'static,
165    {
166        Self::new(
167            Asyncness::No,
168            DynamicHostFn::<_, false>::new(
169                move |store, ty, mut params_and_results, result_start| {
170                    let (params, results) = params_and_results.split_at_mut(result_start);
171                    let result = func(store, ty, params, results).map(move |()| params_and_results);
172                    HostResult::Done(result)
173                },
174            ),
175        )
176    }
177
178    /// Equivalent of `Linker::func_new_async`
179    #[cfg(feature = "async")]
180    pub(crate) fn func_new_async<T, F>(func: F) -> Arc<HostFunc>
181    where
182        T: 'static,
183        F: for<'a> Fn(
184                StoreContextMut<'a, T>,
185                ComponentFunc,
186                &'a [Val],
187                &'a mut [Val],
188            ) -> Box<dyn Future<Output = Result<()>> + Send + 'a>
189            + Send
190            + Sync
191            + 'static,
192    {
193        Self::new(
194            Asyncness::Yes,
195            DynamicHostFn::<_, false>::new(
196                move |store, ty, mut params_and_results, result_start| {
197                    let (params, results) = params_and_results.split_at_mut(result_start);
198                    let result = store
199                        .with_blocking(|store, cx| {
200                            cx.block_on(Pin::from(func(store, ty, params, results)))
201                        })
202                        .and_then(|r| r);
203                    let result = result.map(move |()| params_and_results);
204                    HostResult::Done(result)
205                },
206            ),
207        )
208    }
209
210    /// Equivalent of `Linker::func_new_concurrent`
211    #[cfg(feature = "component-model-async")]
212    pub(crate) fn func_new_concurrent<T, F>(func: F) -> Arc<HostFunc>
213    where
214        T: 'static,
215        F: for<'a> Fn(
216                &'a Accessor<T>,
217                ComponentFunc,
218                &'a [Val],
219                &'a mut [Val],
220            ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>
221            + Send
222            + Sync
223            + 'static,
224    {
225        let func = Arc::new(func);
226        Self::new(
227            Asyncness::Yes,
228            DynamicHostFn::<_, true>::new(
229                move |store, ty, mut params_and_results, result_start| {
230                    let func = func.clone();
231                    HostResult::Future(Box::pin(store.wrap_call(move |accessor| {
232                        Box::pin(async move {
233                            let (params, results) = params_and_results.split_at_mut(result_start);
234                            func(accessor, ty, params, results).await?;
235                            Ok(params_and_results)
236                        })
237                    })))
238                },
239            ),
240        )
241    }
242
243    pub fn typecheck(&self, ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()> {
244        (self.typecheck)(ty, types)
245    }
246
247    pub fn lowering(&self) -> VMLowering {
248        let data = NonNull::from(&*self.func).cast();
249        VMLowering {
250            callee: NonNull::new(self.entrypoint as *mut _).unwrap().into(),
251            data: data.into(),
252        }
253    }
254
255    pub fn asyncness(&self) -> Asyncness {
256        self.asyncness
257    }
258}
259
260/// Argument to [`HostFn::lift_params`]
261enum Source<'a> {
262    /// The parameters come from flat wasm arguments which are provided here.
263    Flat(&'a [ValRaw]),
264    /// The parameters come from linear memory at the provided offset, which is
265    /// already validated to be in-bounds.
266    Memory(usize),
267}
268
269/// Argument to [`HostFn::lower_result`]
270enum Destination<'a> {
271    /// The result is stored in flat parameters whose storage is provided here.
272    Flat(&'a mut [MaybeUninit<ValRaw>]),
273    /// The result is stored in linear memory at the provided offset, which is
274    /// already validated to be in-bounds.
275    Memory(usize),
276}
277
278/// Consolidation of functionality of invoking a host function.
279///
280/// This trait primarily serves as a deduplication of the "static" and
281/// "dynamic" host function paths where all default functions here are shared
282/// (source-wise at least) across the two styles of host functions.
283trait HostFn<T, P, R>
284where
285    T: 'static,
286    R: Send + Sync + 'static,
287{
288    /// Whether or not this is `async` function from the perspective of the
289    /// component model.
290    const ASYNC: bool;
291
292    /// Performs a type-check to ensure that this host function can be imported
293    /// with the provided signature that a component is using.
294    fn typecheck(ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()>;
295
296    /// Execute this host function.
297    fn run(&self, store: StoreContextMut<'_, T>, params: P) -> HostResult<R>;
298
299    /// Performs the lifting operation to convert arguments from the canonical
300    /// ABI in wasm memory/arguments into their Rust representation.
301    fn lift_params(cx: &mut LiftContext<'_>, ty: TypeFuncIndex, source: Source<'_>) -> Result<P>;
302
303    /// Performs the lowering operation to convert the result from its Rust
304    /// representation to the canonical ABI representation.
305    fn lower_result(
306        cx: &mut LowerContext<'_, T>,
307        ty: TypeFuncIndex,
308        result: R,
309        dst: Destination<'_>,
310    ) -> Result<()>;
311
312    /// Raw entrypoint invoked by Cranelift.
313    ///
314    /// # Safety
315    ///
316    /// This function is only safe when called from a trusted source which
317    /// upholds at least these invariants:
318    ///
319    /// * `cx` is a valid pointer which comes from calling wasm.
320    /// * `data` is a valid pointer to `Self`
321    /// * `ty` and `options` are valid within the context of `cx`
322    /// * `storage` and `storage_len` are valid pointers and correspond to
323    ///   correctly initialized wasm arguments/results according to the
324    ///   canonical ABI specified by `ty` and `options`.
325    ///
326    /// The code elsewhere in this trait is all downstream of this `unsafe`,
327    /// and upholding this `unsafe` invariant requires Cranelift, function
328    /// translation, the canonical ABI, and Wasmtime to all stay in sync.
329    /// Basically we can't statically rule out this `unsafe`, we just gotta
330    /// not have bugs.
331    unsafe extern "C" fn cabi_entrypoint(
332        cx: NonNull<VMOpaqueContext>,
333        data: NonNull<u8>,
334        ty: u32,
335        options: u32,
336        storage: NonNull<MaybeUninit<ValRaw>>,
337        storage_len: usize,
338    ) -> bool
339    where
340        Self: Sized,
341    {
342        let cx = unsafe { VMComponentContext::from_opaque(cx) };
343        unsafe {
344            ComponentInstance::enter_host_from_wasm(cx, |store, instance| {
345                let mut store = store.unchecked_context_mut();
346                let ty = TypeFuncIndex::from_u32(ty);
347                let options = OptionsIndex::from_u32(options);
348                let storage = NonNull::slice_from_raw_parts(storage, storage_len).as_mut();
349                let data = data.cast::<Self>().as_ref();
350
351                store.0.call_hook(CallHook::CallingHost)?;
352                let res = data.entrypoint(store.as_context_mut(), instance, ty, options, storage);
353                store.0.call_hook(CallHook::ReturningFromHost)?;
354
355                res
356            })
357        }
358    }
359
360    /// "Rust" entrypoint after panic-handling infrastructure is set up and raw
361    /// arguments are translated to Rust types.
362    fn entrypoint(
363        &self,
364        mut store: StoreContextMut<'_, T>,
365        instance: Instance,
366        ty: TypeFuncIndex,
367        options: OptionsIndex,
368        storage: &mut [MaybeUninit<ValRaw>],
369    ) -> Result<()> {
370        let vminstance = instance.id().get(store.0);
371        let async_ = vminstance.component().env_component().options[options].async_;
372
373        // If this is a synchronous-lower of a host-async function, then the
374        // guest is blocking. Test, in the context of the guest task, if that's
375        // allowed.
376        if !async_ && Self::ASYNC {
377            store.0.check_blocking()?;
378        }
379
380        // Enter the host by pushing a `HostTask` into the concurrent state.
381        store.0.enter_host_call()?;
382
383        let task_exited = if async_ {
384            #[cfg(feature = "component-model-async")]
385            {
386                self.call_async_lower(store.as_context_mut(), instance, ty, options, storage)?
387            }
388            #[cfg(not(feature = "component-model-async"))]
389            unreachable!(
390                "async-lowered imports should have failed validation \
391                 when `component-model-async` feature disabled"
392            );
393        } else {
394            self.call_sync_lower(store.as_context_mut(), instance, ty, options, storage)?;
395            true
396        };
397
398        // If the host task exited, then it's popped and deallocated.
399        //
400        // Note that if the host task did not exit then the `call_async_lower`
401        // function transitively would have updated the current guest thread to
402        // the caller of this host function.
403        if task_exited {
404            store.0.exit_host_call()?;
405        }
406
407        Ok(())
408    }
409
410    /// Implementation of the "sync" ABI.
411    ///
412    /// This is the implementation of invoking a host function through the
413    /// synchronous ABI of the component model, or when a function doesn't have
414    /// the `async` option when lowered. Note that the host function itself
415    /// can still be async, in which case this will block here waiting for it
416    /// to finish.
417    fn call_sync_lower(
418        &self,
419        mut store: StoreContextMut<'_, T>,
420        instance: Instance,
421        ty: TypeFuncIndex,
422        options: OptionsIndex,
423        storage: &mut [MaybeUninit<ValRaw>],
424    ) -> Result<()> {
425        let mut lift = LiftContext::new(store.0.store_opaque_mut(), options, instance);
426        let (params, rest) = self.load_params(&mut lift, ty, MAX_FLAT_PARAMS, storage)?;
427
428        let ret = match self.run(store.as_context_mut(), params) {
429            HostResult::Done(result) => result?,
430            #[cfg(feature = "component-model-async")]
431            HostResult::Future(future) => concurrent::poll_and_block(store.0, future)?,
432        };
433
434        let mut lower = LowerContext::new(store, options, instance);
435        let fty = &lower.types[ty];
436        let result_tys = &lower.types[fty.results];
437        let dst = if let Some(cnt) = result_tys.abi.flat_count(MAX_FLAT_RESULTS) {
438            Destination::Flat(&mut storage[..cnt])
439        } else {
440            // SAFETY: due to the contract of `entrypoint` we know that the
441            // return pointer, located after the parameters, is initialized
442            // by wasm and safe to read.
443            let ptr = unsafe { rest[0].assume_init_ref() };
444            Destination::Memory(validate_inbounds_dynamic(
445                &result_tys.abi,
446                lower.as_slice_mut(),
447                ptr,
448            )?)
449        };
450        Self::lower_result_and_exit_call(&mut lower, ty, ret, dst)
451    }
452
453    /// Implementation of the "async" ABI of the component model.
454    ///
455    /// This is invoked when a component has the `async` options specified on
456    /// its `canon lower` for a host function. Note that the host function may
457    /// be either sync or async, and that's handled here too.
458    #[cfg(feature = "component-model-async")]
459    fn call_async_lower(
460        &self,
461        store: StoreContextMut<'_, T>,
462        instance: Instance,
463        ty: TypeFuncIndex,
464        options: OptionsIndex,
465        storage: &mut [MaybeUninit<ValRaw>],
466    ) -> Result<bool> {
467        use wasmtime_environ::component::MAX_FLAT_ASYNC_PARAMS;
468
469        let (component, store) = instance.component_and_store_mut(store.0);
470        let mut store = StoreContextMut(store);
471        let types = component.types();
472        let fty = &types[ty];
473
474        // Lift the parameters, either from flat storage or from linear
475        // memory.
476        let mut lift = LiftContext::new(store.0.store_opaque_mut(), options, instance);
477        let (params, rest) = self.load_params(&mut lift, ty, MAX_FLAT_ASYNC_PARAMS, storage)?;
478
479        // Load/validate the return pointer, if present.
480        let retptr = if !lift.types[fty.results].types.is_empty() {
481            let mut lower = LowerContext::new(store.as_context_mut(), options, instance);
482            // SAFETY: see `load_params` below about how the return pointer
483            // should be safe to use.
484            let ptr = unsafe { rest[0].assume_init_ref() };
485            let result_tys = &lower.types[fty.results];
486            validate_inbounds_dynamic(&result_tys.abi, lower.as_slice_mut(), ptr)?
487        } else {
488            // If there's no return pointer then `R` should have an
489            // empty flat representation. In this situation pretend the return
490            // pointer was 0 so we have something to shepherd along into the
491            // closure below.
492            0
493        };
494
495        let host_result = self.run(store.as_context_mut(), params);
496
497        let task = match host_result {
498            HostResult::Done(result) => {
499                Self::lower_result_and_exit_call(
500                    &mut LowerContext::new(store, options, instance),
501                    ty,
502                    result?,
503                    Destination::Memory(retptr),
504                )?;
505                None
506            }
507            #[cfg(feature = "component-model-async")]
508            HostResult::Future(future) => {
509                instance.first_poll(store, future, move |store, ret| {
510                    Self::lower_result_and_exit_call(
511                        &mut LowerContext::new(store, options, instance),
512                        ty,
513                        ret,
514                        Destination::Memory(retptr),
515                    )
516                })?
517            }
518        };
519
520        storage[0].write(ValRaw::u32(if let Some(task) = task {
521            Status::Started.pack(Some(task))
522        } else {
523            Status::Returned.pack(None)
524        }));
525
526        Ok(task.is_none())
527    }
528
529    /// Loads parameters the wasm arguments `storage`.
530    ///
531    /// This will internally decide the ABI source of the parameters and use
532    /// `storage` appropriately.
533    fn load_params<'a>(
534        &self,
535        lift: &mut LiftContext<'_>,
536        ty: TypeFuncIndex,
537        max_flat_params: usize,
538        storage: &'a [MaybeUninit<ValRaw>],
539    ) -> Result<(P, &'a [MaybeUninit<ValRaw>])> {
540        let fty = &lift.types[ty];
541        let param_tys = &lift.types[fty.params];
542        let param_flat_count = param_tys.abi.flat_count(max_flat_params);
543        let src = match param_flat_count {
544            Some(cnt) => {
545                let params = &storage[..cnt];
546                // SAFETY: due to the contract of `entrypoint` we are
547                // guaranteed that all flat parameters are initialized by
548                // compiled wasm.
549                Source::Flat(unsafe { mem::transmute::<&[MaybeUninit<ValRaw>], &[ValRaw]>(params) })
550            }
551            None => {
552                // SAFETY: due to the contract of `entrypoint` we are
553                // guaranteed that the return pointer is initialized by
554                // compiled wasm.
555                let ptr = unsafe { storage[0].assume_init_ref() };
556                Source::Memory(validate_inbounds_dynamic(
557                    &param_tys.abi,
558                    lift.memory(),
559                    ptr,
560                )?)
561            }
562        };
563        let params = Self::lift_params(lift, ty, src)?;
564        Ok((params, &storage[param_flat_count.unwrap_or(1)..]))
565    }
566
567    /// Stores the result `ret` into `dst` which is calculated per the ABI.
568    fn lower_result_and_exit_call(
569        lower: &mut LowerContext<'_, T>,
570        ty: TypeFuncIndex,
571        ret: R,
572        dst: Destination<'_>,
573    ) -> Result<()> {
574        let caller_instance = lower.options().instance;
575        let mut flags = lower.instance_mut().instance_flags(caller_instance);
576        unsafe {
577            flags.set_may_leave(false);
578        }
579        Self::lower_result(lower, ty, ret, dst)?;
580        unsafe {
581            flags.set_may_leave(true);
582        }
583        lower.validate_scope_exit()?;
584        Ok(())
585    }
586}
587
588/// Implementation of a "static" host function where the parameters and results
589/// of a function are known at compile time.
590#[repr(transparent)]
591struct StaticHostFn<F, const ASYNC: bool>(F);
592
593impl<F, const ASYNC: bool> StaticHostFn<F, ASYNC> {
594    fn new<T, P, R>(func: F) -> Self
595    where
596        T: 'static,
597        P: ComponentNamedList + Lift + 'static,
598        R: ComponentNamedList + Lower + 'static,
599        F: Fn(StoreContextMut<'_, T>, P) -> HostResult<R>,
600    {
601        Self(func)
602    }
603}
604
605impl<T, F, P, R, const ASYNC: bool> HostFn<T, P, R> for StaticHostFn<F, ASYNC>
606where
607    T: 'static,
608    F: Fn(StoreContextMut<'_, T>, P) -> HostResult<R>,
609    P: ComponentNamedList + Lift + 'static,
610    R: ComponentNamedList + Lower + 'static,
611{
612    const ASYNC: bool = ASYNC;
613
614    fn typecheck(ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()> {
615        let ty = &types.types[ty];
616        if ASYNC != ty.async_ {
617            bail!("type mismatch with async");
618        }
619        P::typecheck(&InterfaceType::Tuple(ty.params), types)
620            .context("type mismatch with parameters")?;
621        R::typecheck(&InterfaceType::Tuple(ty.results), types)
622            .context("type mismatch with results")?;
623        Ok(())
624    }
625
626    fn run(&self, store: StoreContextMut<'_, T>, params: P) -> HostResult<R> {
627        (self.0)(store, params)
628    }
629
630    fn lift_params(cx: &mut LiftContext<'_>, ty: TypeFuncIndex, src: Source<'_>) -> Result<P> {
631        let ty = InterfaceType::Tuple(cx.types[ty].params);
632        match src {
633            Source::Flat(storage) => {
634                // SAFETY: the contract of `ComponentType` for `P` means that
635                // it's safe to interpret the parameters `storage` as
636                // `P::Lower`. The contract of `entrypoint` is that everything
637                // is initialized correctly internally.
638                let storage: &P::Lower = unsafe { slice_to_storage(storage) };
639                P::linear_lift_from_flat(cx, ty, storage)
640            }
641            Source::Memory(offset) => {
642                P::linear_lift_from_memory(cx, ty, &cx.memory()[offset..][..P::SIZE32])
643            }
644        }
645    }
646
647    fn lower_result(
648        cx: &mut LowerContext<'_, T>,
649        ty: TypeFuncIndex,
650        ret: R,
651        dst: Destination<'_>,
652    ) -> Result<()> {
653        let fty = &cx.types[ty];
654        let ty = InterfaceType::Tuple(fty.results);
655        match dst {
656            Destination::Flat(storage) => {
657                // SAFETY: the contract of `ComponentType` for `R` means that
658                // it's safe to reinterpret `ValRaw` storage to initialize as
659                // `R::Lower`.
660                let storage: &mut MaybeUninit<R::Lower> = unsafe { slice_to_storage_mut(storage) };
661                ret.linear_lower_to_flat(cx, ty, storage)
662            }
663            Destination::Memory(ptr) => ret.linear_lower_to_memory(cx, ty, ptr),
664        }
665    }
666}
667
668/// Implementation of a "dynamic" host function where the number of parameters,
669/// types of parameters, and result type/presence, are all not known at compile
670/// time.
671///
672/// This is intended for more-dynamic use cases than `StaticHostFn` above such
673/// as demos, gluing things together quickly, and `wast` testing.
674struct DynamicHostFn<F, const ASYNC: bool>(F);
675
676impl<F, const ASYNC: bool> DynamicHostFn<F, ASYNC> {
677    fn new<T>(func: F) -> Self
678    where
679        T: 'static,
680        F: Fn(StoreContextMut<'_, T>, ComponentFunc, Vec<Val>, usize) -> HostResult<Vec<Val>>,
681    {
682        Self(func)
683    }
684}
685
686impl<T, F, const ASYNC: bool> HostFn<T, (ComponentFunc, Vec<Val>), Vec<Val>>
687    for DynamicHostFn<F, ASYNC>
688where
689    T: 'static,
690    F: Fn(StoreContextMut<'_, T>, ComponentFunc, Vec<Val>, usize) -> HostResult<Vec<Val>>,
691{
692    const ASYNC: bool = ASYNC;
693
694    /// This function performs dynamic type checks on its parameters and
695    /// results and subsequently does not need to perform up-front type
696    /// checks. However, we _do_ verify async-ness here.
697    fn typecheck(ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()> {
698        let ty = &types.types[ty];
699        if ASYNC != ty.async_ {
700            bail!("type mismatch with async");
701        }
702
703        Ok(())
704    }
705
706    fn run(
707        &self,
708        store: StoreContextMut<'_, T>,
709        (ty, mut params): (ComponentFunc, Vec<Val>),
710    ) -> HostResult<Vec<Val>> {
711        let offset = params.len();
712        for _ in 0..ty.results().len() {
713            params.push(Val::Bool(false));
714        }
715        (self.0)(store, ty, params, offset)
716    }
717
718    fn lift_params(
719        cx: &mut LiftContext<'_>,
720        ty: TypeFuncIndex,
721        src: Source<'_>,
722    ) -> Result<(ComponentFunc, Vec<Val>)> {
723        let param_tys = &cx.types[cx.types[ty].params];
724        let mut params = Vec::new();
725        match src {
726            Source::Flat(storage) => {
727                let mut iter = storage.iter();
728                for ty in param_tys.types.iter() {
729                    params.push(Val::lift(cx, *ty, &mut iter)?);
730                }
731                assert!(iter.next().is_none());
732            }
733            Source::Memory(mut offset) => {
734                for ty in param_tys.types.iter() {
735                    let abi = cx.types.canonical_abi(ty);
736                    let size = usize::try_from(abi.size32).unwrap();
737                    let memory = &cx.memory()[abi.next_field32_size(&mut offset)..][..size];
738                    params.push(Val::load(cx, *ty, memory)?);
739                }
740            }
741        }
742
743        Ok((ComponentFunc::from(ty, &cx.instance_type()), params))
744    }
745
746    fn lower_result(
747        cx: &mut LowerContext<'_, T>,
748        ty: TypeFuncIndex,
749        result_vals: Vec<Val>,
750        dst: Destination<'_>,
751    ) -> Result<()> {
752        let fty = &cx.types[ty];
753        let param_tys = &cx.types[fty.params];
754        let result_tys = &cx.types[fty.results];
755        let result_vals = &result_vals[param_tys.types.len()..];
756        match dst {
757            Destination::Flat(storage) => {
758                let mut dst = storage.iter_mut();
759                for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) {
760                    val.lower(cx, *ty, &mut dst)?;
761                }
762                assert!(dst.next().is_none());
763            }
764            Destination::Memory(mut ptr) => {
765                for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) {
766                    let offset = cx.types.canonical_abi(ty).next_field32_size(&mut ptr);
767                    val.store(cx, *ty, offset)?;
768                }
769            }
770        }
771        Ok(())
772    }
773}
774
775pub(crate) fn validate_inbounds_dynamic(
776    abi: &CanonicalAbiInfo,
777    memory: &[u8],
778    ptr: &ValRaw,
779) -> Result<usize> {
780    // FIXME(#4311): needs memory64 support
781    let ptr = usize::try_from(ptr.get_u32())?;
782    if ptr % usize::try_from(abi.align32)? != 0 {
783        bail!("pointer not aligned");
784    }
785    let end = match ptr.checked_add(usize::try_from(abi.size32).unwrap()) {
786        Some(n) => n,
787        None => bail!("pointer size overflow"),
788    };
789    if end > memory.len() {
790        bail!("pointer out of bounds")
791    }
792    Ok(ptr)
793}