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