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