Skip to content

Commit ea410ed

Browse files
committed
Add a new API to make Func::call faster
The fastest way to call a WebAssembly function with Wasmtime is to use the `TypedFunc` API and methods. This is only available to Rust code, however, due to the usage of generics. The C API as a result is left to only be able to use `Func::call`, which is quite slow today. While `Func::call` has a lot of reasons that it's slow, some major contributors are: * Space must be allocated for the arguments/return values to call the trampoline with. This `u128` storage is allocated on all `Func::call`-based calls today. * The function's type is loaded to typecheck the arguments, and this requires taking an rwlock in the `Engine` as well as cloning out the `FuncType` itself. * For the C API the slice of inputs needs to be translated to a slice of `Val`, and the results are translated from a vector of `Val` back to a vector of `wasmtime_val_t`. These two operations are particularly costly and the goal of this commit is to solve these two issues. The solution implemented here is a new structure, called `FuncStorage`, which can be created within an `Engine` on a per-function-type basis. This storage is then used with a new API, `Func::call_with_storage`, which removes the first two slowdowns mentioned above. Each `FuncStorage` stores a copy of the `FuncType` it's intended to be used with. Additionally it stores an appropriately-sized `Vec<u128>` for storage of trampoline-encoded arguments. The final bullet above is solved with tweaks to the `Func::call_with_storage` API relative to `Func::call` where the parameters/results are both iterators instead of slices. This new API is intended to be a "power user" API for the Rust crate, but is expected to be more commonly used with the C API since it's such a large performance improvement to calling wasm functions. Overall I'm not overly happy with this API. It solves a lot of the slow `wasmtime_func_call` problem, but the APIs added here are pretty unfortunate I think. Ideally we could solve this issue with no additional API surface area. For example the first bullet could be solved with a solution along the lines of #3294 where vectors are stored in a `Store` and reused per-call. The third bullet could probably be fixed with the same style and also changing `Func::call` to taking a `&mut [Val]` as an argument instead of returning a boxed slice. The second bullet though is probably one of the harder ones to fix. Each `Func` could store it's fully-fleshed-out `FuncType`, but that's a relatively large impact and would also likely require changing `FuncType` to internally use `Arc<[WasmType]>` or similar. In any case I'm hoping that this can help spur on some creativity for someone to find a better solution to this issue.
1 parent 36b7e81 commit ea410ed

File tree

4 files changed

+272
-97
lines changed

4 files changed

+272
-97
lines changed

crates/c-api/include/wasmtime/func.h

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,63 @@ WASM_API_EXTERN bool wasmtime_caller_export_get(
172172
*/
173173
WASM_API_EXTERN wasmtime_context_t* wasmtime_caller_context(wasmtime_caller_t* caller);
174174

175+
176+
/**
177+
* \typedef wasmtime_func_storage_t
178+
* \brief Alias to #wasmtime_func_storage_t
179+
*
180+
* \brief Structure used to optimize calling WebAssembly functions.
181+
* \struct wasmtime_func_storage_t
182+
*
183+
* This structure is used in conjunction with the
184+
* #wasmtime_func_call_with_storage API where storage necessary for the call
185+
* into WebAssembly can be allocated ahead of time with a particular function
186+
* type, and then when calling a function of that type the storage can be used
187+
* to avoid extra allocations when calling the function.
188+
*/
189+
typedef struct wasmtime_func_storage wasmtime_func_storage_t;
190+
191+
/**
192+
* \brief Creates a new storage area used to invoke functions of the specified
193+
* type signature.
194+
*
195+
* \param engine the engine in which the area will be used
196+
* \param ty the function type this allocated area can be used to call
197+
*
198+
* The returned pointer must be deleted with #wasmtime_func_storage_delete when
199+
* it's done being used.
200+
*/
201+
wasmtime_func_storage_t *wasmtime_func_storage_new(wasm_engine_t *engine, wasm_functype_t *ty);
202+
203+
/**
204+
* \brief Deallocates a previously allocated #wasmtime_func_storage_t
205+
*/
206+
void wasmtime_func_storage_delete(wasmtime_func_storage_t *storage);
207+
208+
/**
209+
* \brief Same as #wasmtime_func_call, but has an extra #wasmtime_func_storage_t
210+
* parameter.
211+
*
212+
* This function behaves the same way as #wasmtime_func_call for a host to call
213+
* a WebAssembly function. Unlike #wasmtime_func_call, though, this function
214+
* uses a #wasmtime_func_storage_t to help optimize this call with pre-allocated
215+
* storage.
216+
*
217+
* In addition to the errors of #wasmtime_func_call this function can also
218+
* return an error if the function type of `storage` does not match the type for
219+
* the function being called.
220+
*/
221+
WASM_API_EXTERN wasmtime_error_t *wasmtime_func_call_with_storage(
222+
wasmtime_context_t *store,
223+
const wasmtime_func_t *func,
224+
wasmtime_func_storage_t *storage,
225+
const wasmtime_val_t *args,
226+
size_t nargs,
227+
wasmtime_val_t *results,
228+
size_t nresults,
229+
wasm_trap_t **trap
230+
);
231+
175232
#ifdef __cplusplus
176233
} // extern "C"
177234
#endif

crates/c-api/src/func.rs

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
use crate::wasm_trap_t;
22
use crate::{
3-
wasm_extern_t, wasm_functype_t, wasm_store_t, wasm_val_t, wasm_val_vec_t, wasmtime_error_t,
4-
wasmtime_extern_t, wasmtime_val_t, wasmtime_val_union, CStoreContext, CStoreContextMut,
3+
wasm_engine_t, wasm_extern_t, wasm_functype_t, wasm_store_t, wasm_val_t, wasm_val_vec_t,
4+
wasmtime_error_t, wasmtime_extern_t, wasmtime_val_t, wasmtime_val_union, CStoreContext,
5+
CStoreContextMut,
56
};
67
use anyhow::anyhow;
78
use std::ffi::c_void;
89
use std::mem::MaybeUninit;
910
use std::panic::{self, AssertUnwindSafe};
1011
use std::ptr;
1112
use std::str;
12-
use wasmtime::{AsContextMut, Caller, Extern, Func, Trap, Val};
13+
use wasmtime::{AsContextMut, Caller, Extern, Func, FuncStorage, Trap, Val};
1314

1415
#[derive(Clone)]
1516
#[repr(transparent)]
@@ -336,3 +337,59 @@ pub unsafe extern "C" fn wasmtime_caller_export_get(
336337
crate::initialize(item, which.into());
337338
true
338339
}
340+
341+
pub struct wasmtime_func_storage_t {
342+
storage: FuncStorage,
343+
}
344+
345+
#[no_mangle]
346+
pub unsafe extern "C" fn wasmtime_func_storage_new(
347+
engine: &wasm_engine_t,
348+
ty: &wasm_functype_t,
349+
) -> Box<wasmtime_func_storage_t> {
350+
Box::new(wasmtime_func_storage_t {
351+
storage: FuncStorage::new(&engine.engine, ty.ty().ty.clone()),
352+
})
353+
}
354+
355+
#[no_mangle]
356+
pub extern "C" fn wasmtime_func_storage_delete(_: Box<wasmtime_func_storage_t>) {}
357+
358+
#[no_mangle]
359+
pub unsafe extern "C" fn wasmtime_func_call_with_storage(
360+
store: CStoreContextMut<'_>,
361+
func: &Func,
362+
storage: &mut wasmtime_func_storage_t,
363+
args: *const wasmtime_val_t,
364+
nargs: usize,
365+
results: *mut MaybeUninit<wasmtime_val_t>,
366+
nresults: usize,
367+
trap_ret: &mut *mut wasm_trap_t,
368+
) -> Option<Box<wasmtime_error_t>> {
369+
let params = crate::slice_from_raw_parts(args, nargs)
370+
.iter()
371+
.map(|i| i.to_val());
372+
let results = crate::slice_from_raw_parts_mut(results, nresults);
373+
374+
let result = func.call_with_storage(store, &mut storage.storage, params);
375+
match result {
376+
Ok(func_results) => {
377+
if func_results.len() != results.len() {
378+
return Some(Box::new(wasmtime_error_t::from(anyhow!(
379+
"wrong number of results provided"
380+
))));
381+
}
382+
for (slot, val) in results.iter_mut().zip(func_results) {
383+
crate::initialize(slot, wasmtime_val_t::from_val(val));
384+
}
385+
None
386+
}
387+
Err(trap) => match trap.downcast::<Trap>() {
388+
Ok(trap) => {
389+
*trap_ret = Box::into_raw(Box::new(wasm_trap_t::new(trap)));
390+
None
391+
}
392+
Err(err) => Some(Box::new(wasmtime_error_t::from(err))),
393+
},
394+
}
395+
}

0 commit comments

Comments
 (0)