Skip to main content

wasmer/backend/sys/entities/function/
typed.rs

1#[cfg(feature = "experimental-host-interrupt")]
2use crate::backend::sys::vm::interrupt_registry;
3use crate::backend::sys::{
4    engine::NativeEngineExt,
5    vm::{Trap, TrapCode},
6};
7use crate::{
8    FromToNativeWasmType, Function, NativeWasmTypeInto, RuntimeError, StoreContext, TypedFunction,
9    Value, WasmTypeList,
10    store::{AsStoreMut, AsStoreRef},
11};
12#[cfg(feature = "experimental-async")]
13use crate::{StoreAsync, store::AsStoreAsync};
14use std::future::Future;
15use wasmer_types::{FunctionType, RawValue, Type};
16
17macro_rules! impl_native_traits {
18    (  $( $x:ident ),* ) => {
19        #[allow(unused_parens, non_snake_case)]
20        impl<$( $x , )* Rets> TypedFunction<( $( $x ),* ), Rets>
21        where
22            $( $x: FromToNativeWasmType, )*
23            Rets: WasmTypeList,
24        {
25            /// Call the typed func and return results.
26            #[allow(unused_mut)]
27            #[allow(clippy::too_many_arguments)]
28            pub fn call_sys(&self, store: &mut impl AsStoreMut, $( $x: $x, )* ) -> Result<Rets, RuntimeError> {
29                let anyfunc = unsafe {
30                    *self.func.as_sys()
31                        .handle
32                        .get(store.as_store_ref().objects().as_sys())
33                        .anyfunc
34                        .as_ptr()
35                        .as_ref()
36                };
37                // Ensure all parameters come from the same context.
38                if $(!FromToNativeWasmType::is_from_store(&$x, store) ||)* false {
39                    return Err(RuntimeError::new(
40                        "cross-`Store` values are not supported",
41                    ));
42                }
43                // TODO: when `const fn` related features mature more, we can declare a single array
44                // of the correct size here.
45                let mut params_list = [ $( $x.to_native().into_raw(store) ),* ];
46                let mut rets_list_array = Rets::empty_array();
47                let rets_list: &mut [RawValue] = rets_list_array.as_mut();
48                let using_rets_array;
49                let args_rets: &mut [RawValue] = if params_list.len() > rets_list.len() {
50                    using_rets_array = false;
51                    params_list.as_mut()
52                } else {
53                    using_rets_array = true;
54                    for (i, &arg) in params_list.iter().enumerate() {
55                        rets_list[i] = arg;
56                    }
57                    rets_list.as_mut()
58                };
59
60                let store_id = store.objects_mut().id();
61
62                #[cfg(feature = "experimental-host-interrupt")]
63                let interrupt_guard = match interrupt_registry::install(store_id) {
64                    Ok(x) => x,
65                    Err(interrupt_registry::InstallError::AlreadyInterrupted) => {
66                        return Err(Trap::lib(TrapCode::HostInterrupt).into());
67                    }
68                };
69
70                // Install the store into the store context
71                let store_install_guard = unsafe {
72                    StoreContext::install(store.as_store_mut().inner as *mut _)
73                };
74
75                let mut r;
76                loop {
77                    let storeref = store.as_store_ref();
78                    let config = storeref.engine().tunables().vmconfig();
79                    r = unsafe {
80                        // Safety: This is the intended use-case for StoreContext::pause, as
81                        // documented in the function's doc comments.
82                        let pause_guard = StoreContext::pause(store_id);
83                        wasmer_vm::wasmer_call_trampoline(
84                            store.as_store_ref().signal_handler(),
85                            config,
86                            anyfunc.vmctx,
87                            anyfunc.call_trampoline,
88                            anyfunc.func_ptr,
89                            args_rets.as_mut_ptr() as *mut u8,
90                        )
91                    };
92                    let store_mut = store.as_store_mut();
93                    if let Some(callback) = store_mut.inner.on_called.take() {
94                        match callback(store_mut) {
95                            Ok(wasmer_types::OnCalledAction::InvokeAgain) => { continue; }
96                            Ok(wasmer_types::OnCalledAction::Finish) => { break; }
97                            Ok(wasmer_types::OnCalledAction::Trap(trap)) => { return Err(RuntimeError::user(trap)) },
98                            Err(trap) => { return Err(RuntimeError::user(trap)) },
99                        }
100                    }
101                    break;
102                }
103
104                drop(store_install_guard);
105                #[cfg(feature = "experimental-host-interrupt")]
106                drop(interrupt_guard);
107
108                r?;
109
110                let num_rets = rets_list.len();
111                if !using_rets_array && num_rets > 0 {
112                    let src_pointer = params_list.as_ptr();
113                    let rets_list = &mut rets_list_array.as_mut()[0] as *mut RawValue;
114                    unsafe {
115                        // TODO: we can probably remove this copy by doing some clever `transmute`s.
116                        // we know it's not overlapping because `using_rets_array` is false
117                        std::ptr::copy_nonoverlapping(src_pointer,
118                                                        rets_list,
119                                                        num_rets);
120                    }
121                }
122                Ok(unsafe { Rets::from_array(store, rets_list_array) })
123                // TODO: When the Host ABI and Wasm ABI are the same, we could do this instead:
124                // but we can't currently detect whether that's safe.
125                //
126                // let results = unsafe {
127                //     wasmer_vm::catch_traps_with_result(self.vmctx, || {
128                //         let f = std::mem::transmute::<_, unsafe extern "C" fn( *mut VMContext, $( $x, )*) -> Rets::CStruct>(self.address());
129                //         // We always pass the vmctx
130                //         f( self.vmctx, $( $x, )* )
131                //     }).map_err(RuntimeError::from_trap)?
132                // };
133                // Ok(Rets::from_c_struct(results))
134            }
135
136            /// Call the typed func asynchronously.
137            #[allow(unused_mut)]
138            #[allow(clippy::too_many_arguments)]
139            #[cfg(feature = "experimental-async")]
140            pub(crate) fn call_async_sys(
141                func: Function,
142                store: StoreAsync,
143                $( $x: $x, )*
144            ) -> impl Future<Output = Result<Rets, RuntimeError>> + 'static
145            where
146                $( $x: FromToNativeWasmType + 'static, )*
147            {
148                async move {
149                    let mut write = store.write_lock().await;
150                    let func_ty = func.ty(&mut write);
151                    let mut params_raw = [ $( $x.to_native().into_raw(&mut write) ),* ];
152                    let mut params_values = Vec::with_capacity(params_raw.len());
153                    {
154                        for (raw, ty) in params_raw.iter().zip(func_ty.params()) {
155                            unsafe {
156                                params_values.push(Value::from_raw(&mut write, *ty, *raw));
157                            }
158                        }
159                    }
160                    drop(write);
161
162                    let results = func.call_async(&store, params_values).await?;
163                    let mut write = store.write_lock().await;
164                    convert_results::<Rets>(&mut write, func_ty, &results)
165                }
166            }
167
168            #[doc(hidden)]
169            #[allow(missing_docs)]
170            #[allow(unused_mut)]
171            #[allow(clippy::too_many_arguments)]
172            pub fn call_raw_sys(&self, store: &mut impl AsStoreMut, mut params_list: Vec<RawValue> ) -> Result<Rets, RuntimeError> {
173                let anyfunc = unsafe {
174                    *self.func.as_sys()
175                        .handle
176                        .get(store.as_store_ref().objects().as_sys())
177                        .anyfunc
178                        .as_ptr()
179                        .as_ref()
180                };
181                // TODO: when `const fn` related features mature more, we can declare a single array
182                // of the correct size here.
183                let mut rets_list_array = Rets::empty_array();
184                let rets_list: &mut [RawValue] = rets_list_array.as_mut();
185                let using_rets_array;
186                let args_rets: &mut [RawValue] = if params_list.len() > rets_list.len() {
187                    using_rets_array = false;
188                    params_list.as_mut()
189                } else {
190                    using_rets_array = true;
191                    for (i, &arg) in params_list.iter().enumerate() {
192                        rets_list[i] = arg;
193                    }
194                    rets_list.as_mut()
195                };
196
197                let store_id = store.objects_mut().id();
198
199                #[cfg(feature = "experimental-host-interrupt")]
200                let interrupt_guard = match interrupt_registry::install(store_id) {
201                    Ok(x) => x,
202                    Err(interrupt_registry::InstallError::AlreadyInterrupted) => {
203                        return Err(Trap::lib(TrapCode::HostInterrupt).into());
204                    }
205                };
206
207                // Install the store into the store context
208                let store_install_guard = unsafe {
209                    StoreContext::install(store.as_store_mut().inner as *mut _)
210                };
211
212                let mut r;
213                loop {
214                    let storeref = store.as_store_ref();
215                    let config = storeref.engine().tunables().vmconfig();
216                    r = unsafe {
217                        // Safety: This is the intended use-case for StoreContext::pause, as
218                        // documented in the function's doc comments.
219                        let pause_guard = StoreContext::pause(store_id);
220                        wasmer_vm::wasmer_call_trampoline(
221                            store.as_store_ref().signal_handler(),
222                            config,
223                            anyfunc.vmctx,
224                            anyfunc.call_trampoline,
225                            anyfunc.func_ptr,
226                            args_rets.as_mut_ptr() as *mut u8,
227                        )
228                    };
229                    let store_mut = store.as_store_mut();
230                    if let Some(callback) = store_mut.inner.on_called.take() {
231                        // TODO: OnCalledAction is needed for asyncify. It will be refactored with https://github.com/wasmerio/wasmer/issues/3451
232                        match callback(store_mut) {
233                            Ok(wasmer_types::OnCalledAction::InvokeAgain) => { continue; }
234                            Ok(wasmer_types::OnCalledAction::Finish) => { break; }
235                            Ok(wasmer_types::OnCalledAction::Trap(trap)) => { return Err(RuntimeError::user(trap)) },
236                            Err(trap) => { return Err(RuntimeError::user(trap)) },
237                        }
238                    }
239                    break;
240                }
241
242                drop(store_install_guard);
243                #[cfg(feature = "experimental-host-interrupt")]
244                drop(interrupt_guard);
245
246                r?;
247
248                let num_rets = rets_list.len();
249                if !using_rets_array && num_rets > 0 {
250                    let src_pointer = params_list.as_ptr();
251                    let rets_list = &mut rets_list_array.as_mut()[0] as *mut RawValue;
252                    unsafe {
253                        // TODO: we can probably remove this copy by doing some clever `transmute`s.
254                        // we know it's not overlapping because `using_rets_array` is false
255                        std::ptr::copy_nonoverlapping(src_pointer,
256                                                        rets_list,
257                                                        num_rets);
258                    }
259                }
260                Ok(unsafe { Rets::from_array(store, rets_list_array) })
261                // TODO: When the Host ABI and Wasm ABI are the same, we could do this instead:
262                // but we can't currently detect whether that's safe.
263                //
264                // let results = unsafe {
265                //     wasmer_vm::catch_traps_with_result(self.vmctx, || {
266                //         let f = std::mem::transmute::<_, unsafe extern "C" fn( *mut VMContext, $( $x, )*) -> Rets::CStruct>(self.address());
267                //         // We always pass the vmctx
268                //         f( self.vmctx, $( $x, )* )
269                //     }).map_err(RuntimeError::from_trap)?
270                // };
271                // Ok(Rets::from_c_struct(results))
272            }
273        }
274    };
275}
276
277impl_native_traits!();
278impl_native_traits!(A1);
279impl_native_traits!(A1, A2);
280impl_native_traits!(A1, A2, A3);
281impl_native_traits!(A1, A2, A3, A4);
282impl_native_traits!(A1, A2, A3, A4, A5);
283impl_native_traits!(A1, A2, A3, A4, A5, A6);
284impl_native_traits!(A1, A2, A3, A4, A5, A6, A7);
285impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8);
286impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8, A9);
287impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10);
288impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11);
289impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12);
290impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13);
291impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14);
292impl_native_traits!(
293    A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15
294);
295impl_native_traits!(
296    A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16
297);
298impl_native_traits!(
299    A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17
300);
301impl_native_traits!(
302    A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18
303);
304impl_native_traits!(
305    A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19
306);
307impl_native_traits!(
308    A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20
309);
310
311fn convert_results<Rets>(
312    store: &mut impl AsStoreMut,
313    ty: FunctionType,
314    results: &[Value],
315) -> Result<Rets, RuntimeError>
316where
317    Rets: WasmTypeList,
318{
319    if results.len() != ty.results().len() {
320        return Err(RuntimeError::new("result arity mismatch"));
321    }
322    let mut raw_array = Rets::empty_array();
323    for ((slot, value_ty), value) in raw_array
324        .as_mut()
325        .iter_mut()
326        .zip(ty.results().iter())
327        .zip(results.iter())
328    {
329        debug_assert_eq!(value.ty(), *value_ty);
330        *slot = value.as_raw(store);
331    }
332    unsafe { Ok(Rets::from_array(store, raw_array)) }
333}