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