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