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