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