1 use crate::component::func::{LiftContext, LowerContext, Options};
2 use crate::component::matching::InstanceType;
3 use crate::component::storage::slice_to_storage_mut;
4 use crate::component::{ComponentNamedList, ComponentType, Lift, Lower, Val};
5 use crate::prelude::*;
6 use crate::runtime::vm::component::{
7     ComponentInstance, InstanceFlags, VMComponentContext, VMLowering, VMLoweringCallee,
8 };
9 use crate::runtime::vm::{VMFuncRef, VMGlobalDefinition, VMMemoryDefinition, VMOpaqueContext};
10 use crate::{AsContextMut, CallHook, StoreContextMut, ValRaw};
11 use alloc::sync::Arc;
12 use core::any::Any;
13 use core::mem::{self, MaybeUninit};
14 use core::ptr::NonNull;
15 use wasmtime_environ::component::{
16     CanonicalAbiInfo, ComponentTypes, InterfaceType, StringEncoding, TypeFuncIndex,
17     MAX_FLAT_PARAMS, MAX_FLAT_RESULTS,
18 };
19 
20 pub struct HostFunc {
21     entrypoint: VMLoweringCallee,
22     typecheck: Box<dyn (Fn(TypeFuncIndex, &InstanceType<'_>) -> Result<()>) + Send + Sync>,
23     func: Box<dyn Any + Send + Sync>,
24 }
25 
26 impl core::fmt::Debug for HostFunc {
27     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
28         f.debug_struct("HostFunc").finish_non_exhaustive()
29     }
30 }
31 
32 impl HostFunc {
33     pub(crate) fn from_closure<T, F, P, R>(func: F) -> Arc<HostFunc>
34     where
35         F: Fn(StoreContextMut<T>, P) -> Result<R> + Send + Sync + 'static,
36         P: ComponentNamedList + Lift + 'static,
37         R: ComponentNamedList + Lower + 'static,
38     {
39         let entrypoint = Self::entrypoint::<T, F, P, R>;
40         Arc::new(HostFunc {
41             entrypoint,
42             typecheck: Box::new(typecheck::<P, R>),
43             func: Box::new(func),
44         })
45     }
46 
47     extern "C" fn entrypoint<T, F, P, R>(
48         cx: NonNull<VMOpaqueContext>,
49         data: NonNull<u8>,
50         ty: u32,
51         flags: NonNull<VMGlobalDefinition>,
52         memory: *mut VMMemoryDefinition,
53         realloc: *mut VMFuncRef,
54         string_encoding: u8,
55         async_: u8,
56         storage: NonNull<MaybeUninit<ValRaw>>,
57         storage_len: usize,
58     ) -> bool
59     where
60         F: Fn(StoreContextMut<T>, P) -> Result<R>,
61         P: ComponentNamedList + Lift + 'static,
62         R: ComponentNamedList + Lower + 'static,
63     {
64         let data = data.as_ptr() as *const F;
65         unsafe {
66             call_host_and_handle_result::<T>(cx, |instance, types, store| {
67                 call_host::<_, _, _, _>(
68                     instance,
69                     types,
70                     store,
71                     TypeFuncIndex::from_u32(ty),
72                     InstanceFlags::from_raw(flags),
73                     memory,
74                     realloc,
75                     StringEncoding::from_u8(string_encoding).unwrap(),
76                     async_ != 0,
77                     NonNull::slice_from_raw_parts(storage, storage_len).as_mut(),
78                     |store, args| (*data)(store, args),
79                 )
80             })
81         }
82     }
83 
84     pub(crate) fn new_dynamic<T, F>(func: F) -> Arc<HostFunc>
85     where
86         F: Fn(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static,
87     {
88         Arc::new(HostFunc {
89             entrypoint: dynamic_entrypoint::<T, F>,
90             // This function performs dynamic type checks and subsequently does
91             // not need to perform up-front type checks. Instead everything is
92             // dynamically managed at runtime.
93             typecheck: Box::new(move |_expected_index, _expected_types| Ok(())),
94             func: Box::new(func),
95         })
96     }
97 
98     pub fn typecheck(&self, ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()> {
99         (self.typecheck)(ty, types)
100     }
101 
102     pub fn lowering(&self) -> VMLowering {
103         let data = NonNull::from(&*self.func).cast();
104         VMLowering {
105             callee: self.entrypoint,
106             data: data.into(),
107         }
108     }
109 }
110 
111 fn typecheck<P, R>(ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()>
112 where
113     P: ComponentNamedList + Lift,
114     R: ComponentNamedList + Lower,
115 {
116     let ty = &types.types[ty];
117     P::typecheck(&InterfaceType::Tuple(ty.params), types)
118         .context("type mismatch with parameters")?;
119     R::typecheck(&InterfaceType::Tuple(ty.results), types).context("type mismatch with results")?;
120     Ok(())
121 }
122 
123 /// The "meat" of calling a host function from wasm.
124 ///
125 /// This function is delegated to from implementations of
126 /// `HostFunc::from_closure`. Most of the arguments from the `entrypoint` are
127 /// forwarded here except for the `data` pointer which is encapsulated in the
128 /// `closure` argument here.
129 ///
130 /// This function is parameterized over:
131 ///
132 /// * `T` - the type of store this function works with (an unsafe assertion)
133 /// * `Params` - the parameters to the host function, viewed as a tuple
134 /// * `Return` - the result of the host function
135 /// * `F` - the `closure` to actually receive the `Params` and return the
136 ///   `Return`
137 ///
138 /// It's expected that `F` will "un-tuple" the arguments to pass to a host
139 /// closure.
140 ///
141 /// This function is in general `unsafe` as the validity of all the parameters
142 /// must be upheld. Generally that's done by ensuring this is only called from
143 /// the select few places it's intended to be called from.
144 unsafe fn call_host<T, Params, Return, F>(
145     instance: *mut ComponentInstance,
146     types: &Arc<ComponentTypes>,
147     mut cx: StoreContextMut<'_, T>,
148     ty: TypeFuncIndex,
149     mut flags: InstanceFlags,
150     memory: *mut VMMemoryDefinition,
151     realloc: *mut VMFuncRef,
152     string_encoding: StringEncoding,
153     async_: bool,
154     storage: &mut [MaybeUninit<ValRaw>],
155     closure: F,
156 ) -> Result<()>
157 where
158     Params: Lift,
159     Return: Lower,
160     F: FnOnce(StoreContextMut<'_, T>, Params) -> Result<Return>,
161 {
162     if async_ {
163         todo!()
164     }
165 
166     /// Representation of arguments to this function when a return pointer is in
167     /// use, namely the argument list is followed by a single value which is the
168     /// return pointer.
169     #[repr(C)]
170     struct ReturnPointer<T> {
171         args: T,
172         retptr: ValRaw,
173     }
174 
175     /// Representation of arguments to this function when the return value is
176     /// returned directly, namely the arguments and return value all start from
177     /// the beginning (aka this is a `union`, not a `struct`).
178     #[repr(C)]
179     union ReturnStack<T: Copy, U: Copy> {
180         args: T,
181         ret: U,
182     }
183 
184     let options = Options::new(
185         cx.0.id(),
186         NonNull::new(memory),
187         NonNull::new(realloc),
188         string_encoding,
189     );
190 
191     // Perform a dynamic check that this instance can indeed be left. Exiting
192     // the component is disallowed, for example, when the `realloc` function
193     // calls a canonical import.
194     if !flags.may_leave() {
195         bail!("cannot leave component instance");
196     }
197 
198     let ty = &types[ty];
199     let param_tys = InterfaceType::Tuple(ty.params);
200     let result_tys = InterfaceType::Tuple(ty.results);
201 
202     // There's a 2x2 matrix of whether parameters and results are stored on the
203     // stack or on the heap. Each of the 4 branches here have a different
204     // representation of the storage of arguments/returns.
205     //
206     // Also note that while four branches are listed here only one is taken for
207     // any particular `Params` and `Return` combination. This should be
208     // trivially DCE'd by LLVM. Perhaps one day with enough const programming in
209     // Rust we can make monomorphizations of this function codegen only one
210     // branch, but today is not that day.
211     let mut storage: Storage<'_, Params, Return> = if Params::flatten_count() <= MAX_FLAT_PARAMS {
212         if Return::flatten_count() <= MAX_FLAT_RESULTS {
213             Storage::Direct(slice_to_storage_mut(storage))
214         } else {
215             Storage::ResultsIndirect(slice_to_storage_mut(storage).assume_init_ref())
216         }
217     } else {
218         if Return::flatten_count() <= MAX_FLAT_RESULTS {
219             Storage::ParamsIndirect(slice_to_storage_mut(storage))
220         } else {
221             Storage::Indirect(slice_to_storage_mut(storage).assume_init_ref())
222         }
223     };
224     let mut lift = LiftContext::new(cx.0, &options, types, instance);
225     lift.enter_call();
226     let params = storage.lift_params(&mut lift, param_tys)?;
227 
228     let ret = closure(cx.as_context_mut(), params)?;
229     flags.set_may_leave(false);
230     let mut lower = LowerContext::new(cx, &options, types, instance);
231     storage.lower_results(&mut lower, result_tys, ret)?;
232     flags.set_may_leave(true);
233 
234     lower.exit_call()?;
235 
236     return Ok(());
237 
238     enum Storage<'a, P: ComponentType, R: ComponentType> {
239         Direct(&'a mut MaybeUninit<ReturnStack<P::Lower, R::Lower>>),
240         ParamsIndirect(&'a mut MaybeUninit<ReturnStack<ValRaw, R::Lower>>),
241         ResultsIndirect(&'a ReturnPointer<P::Lower>),
242         Indirect(&'a ReturnPointer<ValRaw>),
243     }
244 
245     impl<P, R> Storage<'_, P, R>
246     where
247         P: ComponentType + Lift,
248         R: ComponentType + Lower,
249     {
250         unsafe fn lift_params(&self, cx: &mut LiftContext<'_>, ty: InterfaceType) -> Result<P> {
251             match self {
252                 Storage::Direct(storage) => P::lift(cx, ty, &storage.assume_init_ref().args),
253                 Storage::ResultsIndirect(storage) => P::lift(cx, ty, &storage.args),
254                 Storage::ParamsIndirect(storage) => {
255                     let ptr = validate_inbounds::<P>(cx.memory(), &storage.assume_init_ref().args)?;
256                     P::load(cx, ty, &cx.memory()[ptr..][..P::SIZE32])
257                 }
258                 Storage::Indirect(storage) => {
259                     let ptr = validate_inbounds::<P>(cx.memory(), &storage.args)?;
260                     P::load(cx, ty, &cx.memory()[ptr..][..P::SIZE32])
261                 }
262             }
263         }
264 
265         unsafe fn lower_results<T>(
266             &mut self,
267             cx: &mut LowerContext<'_, T>,
268             ty: InterfaceType,
269             ret: R,
270         ) -> Result<()> {
271             match self {
272                 Storage::Direct(storage) => ret.lower(cx, ty, map_maybe_uninit!(storage.ret)),
273                 Storage::ParamsIndirect(storage) => {
274                     ret.lower(cx, ty, map_maybe_uninit!(storage.ret))
275                 }
276                 Storage::ResultsIndirect(storage) => {
277                     let ptr = validate_inbounds::<R>(cx.as_slice_mut(), &storage.retptr)?;
278                     ret.store(cx, ty, ptr)
279                 }
280                 Storage::Indirect(storage) => {
281                     let ptr = validate_inbounds::<R>(cx.as_slice_mut(), &storage.retptr)?;
282                     ret.store(cx, ty, ptr)
283                 }
284             }
285         }
286     }
287 }
288 
289 fn validate_inbounds<T: ComponentType>(memory: &[u8], ptr: &ValRaw) -> Result<usize> {
290     // FIXME(#4311): needs memory64 support
291     let ptr = usize::try_from(ptr.get_u32())?;
292     if ptr % usize::try_from(T::ALIGN32)? != 0 {
293         bail!("pointer not aligned");
294     }
295     let end = match ptr.checked_add(T::SIZE32) {
296         Some(n) => n,
297         None => bail!("pointer size overflow"),
298     };
299     if end > memory.len() {
300         bail!("pointer out of bounds")
301     }
302     Ok(ptr)
303 }
304 
305 unsafe fn call_host_and_handle_result<T>(
306     cx: NonNull<VMOpaqueContext>,
307     func: impl FnOnce(
308         *mut ComponentInstance,
309         &Arc<ComponentTypes>,
310         StoreContextMut<'_, T>,
311     ) -> Result<()>,
312 ) -> bool {
313     let cx = VMComponentContext::from_opaque(cx);
314     let instance = cx.as_ref().instance();
315     let types = (*instance).component_types();
316     let raw_store = (*instance).store();
317     let mut store = StoreContextMut(&mut *raw_store.cast());
318 
319     crate::runtime::vm::catch_unwind_and_record_trap(|| {
320         store.0.call_hook(CallHook::CallingHost)?;
321         let res = func(instance, types, store.as_context_mut());
322         store.0.call_hook(CallHook::ReturningFromHost)?;
323         res
324     })
325 }
326 
327 unsafe fn call_host_dynamic<T, F>(
328     instance: *mut ComponentInstance,
329     types: &Arc<ComponentTypes>,
330     mut store: StoreContextMut<'_, T>,
331     ty: TypeFuncIndex,
332     mut flags: InstanceFlags,
333     memory: *mut VMMemoryDefinition,
334     realloc: *mut VMFuncRef,
335     string_encoding: StringEncoding,
336     async_: bool,
337     storage: &mut [MaybeUninit<ValRaw>],
338     closure: F,
339 ) -> Result<()>
340 where
341     F: FnOnce(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()>,
342 {
343     if async_ {
344         todo!()
345     }
346 
347     let options = Options::new(
348         store.0.id(),
349         NonNull::new(memory),
350         NonNull::new(realloc),
351         string_encoding,
352     );
353 
354     // Perform a dynamic check that this instance can indeed be left. Exiting
355     // the component is disallowed, for example, when the `realloc` function
356     // calls a canonical import.
357     if !flags.may_leave() {
358         bail!("cannot leave component instance");
359     }
360 
361     let args;
362     let ret_index;
363 
364     let func_ty = &types[ty];
365     let param_tys = &types[func_ty.params];
366     let result_tys = &types[func_ty.results];
367     let mut cx = LiftContext::new(store.0, &options, types, instance);
368     cx.enter_call();
369     if let Some(param_count) = param_tys.abi.flat_count(MAX_FLAT_PARAMS) {
370         // NB: can use `MaybeUninit::slice_assume_init_ref` when that's stable
371         let mut iter =
372             mem::transmute::<&[MaybeUninit<ValRaw>], &[ValRaw]>(&storage[..param_count]).iter();
373         args = param_tys
374             .types
375             .iter()
376             .map(|ty| Val::lift(&mut cx, *ty, &mut iter))
377             .collect::<Result<Box<[_]>>>()?;
378         ret_index = param_count;
379         assert!(iter.next().is_none());
380     } else {
381         let mut offset =
382             validate_inbounds_dynamic(&param_tys.abi, cx.memory(), storage[0].assume_init_ref())?;
383         args = param_tys
384             .types
385             .iter()
386             .map(|ty| {
387                 let abi = types.canonical_abi(ty);
388                 let size = usize::try_from(abi.size32).unwrap();
389                 let memory = &cx.memory()[abi.next_field32_size(&mut offset)..][..size];
390                 Val::load(&mut cx, *ty, memory)
391             })
392             .collect::<Result<Box<[_]>>>()?;
393         ret_index = 1;
394     };
395 
396     let mut result_vals = Vec::with_capacity(result_tys.types.len());
397     for _ in result_tys.types.iter() {
398         result_vals.push(Val::Bool(false));
399     }
400     closure(store.as_context_mut(), &args, &mut result_vals)?;
401     flags.set_may_leave(false);
402 
403     let mut cx = LowerContext::new(store, &options, types, instance);
404     if let Some(cnt) = result_tys.abi.flat_count(MAX_FLAT_RESULTS) {
405         let mut dst = storage[..cnt].iter_mut();
406         for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) {
407             val.lower(&mut cx, *ty, &mut dst)?;
408         }
409         assert!(dst.next().is_none());
410     } else {
411         let ret_ptr = storage[ret_index].assume_init_ref();
412         let mut ptr = validate_inbounds_dynamic(&result_tys.abi, cx.as_slice_mut(), ret_ptr)?;
413         for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) {
414             let offset = types.canonical_abi(ty).next_field32_size(&mut ptr);
415             val.store(&mut cx, *ty, offset)?;
416         }
417     }
418 
419     flags.set_may_leave(true);
420 
421     cx.exit_call()?;
422 
423     return Ok(());
424 }
425 
426 fn validate_inbounds_dynamic(abi: &CanonicalAbiInfo, memory: &[u8], ptr: &ValRaw) -> Result<usize> {
427     // FIXME(#4311): needs memory64 support
428     let ptr = usize::try_from(ptr.get_u32())?;
429     if ptr % usize::try_from(abi.align32)? != 0 {
430         bail!("pointer not aligned");
431     }
432     let end = match ptr.checked_add(usize::try_from(abi.size32).unwrap()) {
433         Some(n) => n,
434         None => bail!("pointer size overflow"),
435     };
436     if end > memory.len() {
437         bail!("pointer out of bounds")
438     }
439     Ok(ptr)
440 }
441 
442 extern "C" fn dynamic_entrypoint<T, F>(
443     cx: NonNull<VMOpaqueContext>,
444     data: NonNull<u8>,
445     ty: u32,
446     flags: NonNull<VMGlobalDefinition>,
447     memory: *mut VMMemoryDefinition,
448     realloc: *mut VMFuncRef,
449     string_encoding: u8,
450     async_: u8,
451     storage: NonNull<MaybeUninit<ValRaw>>,
452     storage_len: usize,
453 ) -> bool
454 where
455     F: Fn(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static,
456 {
457     let data = data.as_ptr() as *const F;
458     unsafe {
459         call_host_and_handle_result(cx, |instance, types, store| {
460             call_host_dynamic::<T, _>(
461                 instance,
462                 types,
463                 store,
464                 TypeFuncIndex::from_u32(ty),
465                 InstanceFlags::from_raw(flags),
466                 memory,
467                 realloc,
468                 StringEncoding::from_u8(string_encoding).unwrap(),
469                 async_ != 0,
470                 NonNull::slice_from_raw_parts(storage, storage_len).as_mut(),
471                 |store, params, results| (*data)(store, params, results),
472             )
473         })
474     }
475 }
476