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