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