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     InstanceFlags, VMComponentContext, VMLowering, VMLoweringCallee,
8 };
9 use crate::runtime::vm::{VMFuncRef, VMMemoryDefinition, VMOpaqueContext};
10 use crate::{AsContextMut, 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, StringEncoding, TypeFuncIndex, MAX_FLAT_PARAMS,
17     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             handle_result(|| {
59                 call_host::<_, _, _, _>(
60                     cx,
61                     ty,
62                     flags,
63                     memory,
64                     realloc,
65                     string_encoding,
66                     core::slice::from_raw_parts_mut(storage, storage_len),
67                     |store, args| (*data)(store, args),
68                 )
69             })
70         }
71     }
72 
73     pub(crate) fn new_dynamic<T, F>(func: F) -> Arc<HostFunc>
74     where
75         F: Fn(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static,
76     {
77         Arc::new(HostFunc {
78             entrypoint: dynamic_entrypoint::<T, F>,
79             // This function performs dynamic type checks and subsequently does
80             // not need to perform up-front type checks. Instead everything is
81             // dynamically managed at runtime.
82             typecheck: Box::new(move |_expected_index, _expected_types| Ok(())),
83             func: Box::new(func),
84         })
85     }
86 
87     pub fn typecheck(&self, ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()> {
88         (self.typecheck)(ty, types)
89     }
90 
91     pub fn lowering(&self) -> VMLowering {
92         let data = &*self.func as *const (dyn Any + Send + Sync) as *mut u8;
93         VMLowering {
94             callee: self.entrypoint,
95             data,
96         }
97     }
98 }
99 
100 fn typecheck<P, R>(ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()>
101 where
102     P: ComponentNamedList + Lift,
103     R: ComponentNamedList + Lower,
104 {
105     let ty = &types.types[ty];
106     P::typecheck(&InterfaceType::Tuple(ty.params), types)
107         .context("type mismatch with parameters")?;
108     R::typecheck(&InterfaceType::Tuple(ty.results), types).context("type mismatch with results")?;
109     Ok(())
110 }
111 
112 /// The "meat" of calling a host function from wasm.
113 ///
114 /// This function is delegated to from implementations of
115 /// `HostFunc::from_closure`. Most of the arguments from the `entrypoint` are
116 /// forwarded here except for the `data` pointer which is encapsulated in the
117 /// `closure` argument here.
118 ///
119 /// This function is parameterized over:
120 ///
121 /// * `T` - the type of store this function works with (an unsafe assertion)
122 /// * `Params` - the parameters to the host function, viewed as a tuple
123 /// * `Return` - the result of the host function
124 /// * `F` - the `closure` to actually receive the `Params` and return the
125 ///   `Return`
126 ///
127 /// It's expected that `F` will "un-tuple" the arguments to pass to a host
128 /// closure.
129 ///
130 /// This function is in general `unsafe` as the validity of all the parameters
131 /// must be upheld. Generally that's done by ensuring this is only called from
132 /// the select few places it's intended to be called from.
133 unsafe fn call_host<T, Params, Return, F>(
134     cx: *mut VMOpaqueContext,
135     ty: TypeFuncIndex,
136     mut flags: InstanceFlags,
137     memory: *mut VMMemoryDefinition,
138     realloc: *mut VMFuncRef,
139     string_encoding: StringEncoding,
140     storage: &mut [MaybeUninit<ValRaw>],
141     closure: F,
142 ) -> Result<()>
143 where
144     Params: Lift,
145     Return: Lower,
146     F: FnOnce(StoreContextMut<'_, T>, Params) -> Result<Return>,
147 {
148     /// Representation of arguments to this function when a return pointer is in
149     /// use, namely the argument list is followed by a single value which is the
150     /// return pointer.
151     #[repr(C)]
152     struct ReturnPointer<T> {
153         args: T,
154         retptr: ValRaw,
155     }
156 
157     /// Representation of arguments to this function when the return value is
158     /// returned directly, namely the arguments and return value all start from
159     /// the beginning (aka this is a `union`, not a `struct`).
160     #[repr(C)]
161     union ReturnStack<T: Copy, U: Copy> {
162         args: T,
163         ret: U,
164     }
165 
166     let cx = VMComponentContext::from_opaque(cx);
167     let instance = (*cx).instance();
168     let mut cx = StoreContextMut::from_raw((*instance).store());
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 types = (*instance).component_types();
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 handle_result(func: impl FnOnce() -> Result<()>) {
293     match crate::runtime::vm::catch_unwind_and_longjmp(func) {
294         Ok(()) => {}
295         Err(e) => crate::trap::raise(e),
296     }
297 }
298 
299 unsafe fn call_host_dynamic<T, F>(
300     cx: *mut VMOpaqueContext,
301     ty: TypeFuncIndex,
302     mut flags: InstanceFlags,
303     memory: *mut VMMemoryDefinition,
304     realloc: *mut VMFuncRef,
305     string_encoding: StringEncoding,
306     storage: &mut [MaybeUninit<ValRaw>],
307     closure: F,
308 ) -> Result<()>
309 where
310     F: FnOnce(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()>,
311 {
312     let cx = VMComponentContext::from_opaque(cx);
313     let instance = (*cx).instance();
314     let mut store = StoreContextMut::from_raw((*instance).store());
315     let types = (*instance).component_types();
316     let options = Options::new(
317         store.0.id(),
318         NonNull::new(memory),
319         NonNull::new(realloc),
320         string_encoding,
321     );
322 
323     // Perform a dynamic check that this instance can indeed be left. Exiting
324     // the component is disallowed, for example, when the `realloc` function
325     // calls a canonical import.
326     if !flags.may_leave() {
327         bail!("cannot leave component instance");
328     }
329 
330     let args;
331     let ret_index;
332 
333     let func_ty = &types[ty];
334     let param_tys = &types[func_ty.params];
335     let result_tys = &types[func_ty.results];
336     let mut cx = LiftContext::new(store.0, &options, types, instance);
337     cx.enter_call();
338     if let Some(param_count) = param_tys.abi.flat_count(MAX_FLAT_PARAMS) {
339         // NB: can use `MaybeUninit::slice_assume_init_ref` when that's stable
340         let mut iter =
341             mem::transmute::<&[MaybeUninit<ValRaw>], &[ValRaw]>(&storage[..param_count]).iter();
342         args = param_tys
343             .types
344             .iter()
345             .map(|ty| Val::lift(&mut cx, *ty, &mut iter))
346             .collect::<Result<Box<[_]>>>()?;
347         ret_index = param_count;
348         assert!(iter.next().is_none());
349     } else {
350         let mut offset =
351             validate_inbounds_dynamic(&param_tys.abi, cx.memory(), storage[0].assume_init_ref())?;
352         args = param_tys
353             .types
354             .iter()
355             .map(|ty| {
356                 let abi = types.canonical_abi(ty);
357                 let size = usize::try_from(abi.size32).unwrap();
358                 let memory = &cx.memory()[abi.next_field32_size(&mut offset)..][..size];
359                 Val::load(&mut cx, *ty, memory)
360             })
361             .collect::<Result<Box<[_]>>>()?;
362         ret_index = 1;
363     };
364 
365     let mut result_vals = Vec::with_capacity(result_tys.types.len());
366     for _ in result_tys.types.iter() {
367         result_vals.push(Val::Bool(false));
368     }
369     closure(store.as_context_mut(), &args, &mut result_vals)?;
370     flags.set_may_leave(false);
371 
372     let mut cx = LowerContext::new(store, &options, types, instance);
373     if let Some(cnt) = result_tys.abi.flat_count(MAX_FLAT_RESULTS) {
374         let mut dst = storage[..cnt].iter_mut();
375         for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) {
376             val.lower(&mut cx, *ty, &mut dst)?;
377         }
378         assert!(dst.next().is_none());
379     } else {
380         let ret_ptr = storage[ret_index].assume_init_ref();
381         let mut ptr = validate_inbounds_dynamic(&result_tys.abi, cx.as_slice_mut(), ret_ptr)?;
382         for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) {
383             let offset = types.canonical_abi(ty).next_field32_size(&mut ptr);
384             val.store(&mut cx, *ty, offset)?;
385         }
386     }
387 
388     flags.set_may_leave(true);
389 
390     cx.exit_call()?;
391 
392     return Ok(());
393 }
394 
395 fn validate_inbounds_dynamic(abi: &CanonicalAbiInfo, memory: &[u8], ptr: &ValRaw) -> Result<usize> {
396     // FIXME: needs memory64 support
397     let ptr = usize::try_from(ptr.get_u32()).err2anyhow()?;
398     if ptr % usize::try_from(abi.align32).err2anyhow()? != 0 {
399         bail!("pointer not aligned");
400     }
401     let end = match ptr.checked_add(usize::try_from(abi.size32).unwrap()) {
402         Some(n) => n,
403         None => bail!("pointer size overflow"),
404     };
405     if end > memory.len() {
406         bail!("pointer out of bounds")
407     }
408     Ok(ptr)
409 }
410 
411 extern "C" fn dynamic_entrypoint<T, F>(
412     cx: *mut VMOpaqueContext,
413     data: *mut u8,
414     ty: TypeFuncIndex,
415     flags: InstanceFlags,
416     memory: *mut VMMemoryDefinition,
417     realloc: *mut VMFuncRef,
418     string_encoding: StringEncoding,
419     storage: *mut MaybeUninit<ValRaw>,
420     storage_len: usize,
421 ) where
422     F: Fn(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static,
423 {
424     let data = data as *const F;
425     unsafe {
426         handle_result(|| {
427             call_host_dynamic::<T, _>(
428                 cx,
429                 ty,
430                 flags,
431                 memory,
432                 realloc,
433                 string_encoding,
434                 core::slice::from_raw_parts_mut(storage, storage_len),
435                 |store, params, results| (*data)(store, params, results),
436             )
437         })
438     }
439 }
440