Avoid vector allocations in wasm->host calls (#3294)
This commit improves the runtime support for wasm-to-host invocations for functions created with `Func::new` or `wasmtime_func_new` in the C API. Previously a `Vec` (sometimes a `SmallVec`) would be dynamically allocated on each host call to store the arguments that are coming from wasm and going to the host. In the case of the `wasmtime` crate we need to decode the `u128`-stored values, and in the case of the C API we need to decode the `Val` into the C API's `wasmtime_val_t`. The technique used in this commit is to store a singular `Vec<T>` inside the "store", be it the literal `Store<T>` or within the `T` in the case of the C API, which can be reused across wasm->host calls. This means that we're unlikely to actually perform dynamic memory allocation and instead we should hit a faster path where the `Vec` always has enough capacity. Note that this is just a mild improvement for `Func::new`-based functions. It's still the case that `Func::wrap` is much faster, but unfortunately the C API doesn't have access to `Func::wrap`, so the main motivation here is accelerating the C API.
This commit is contained in:
@@ -5,7 +5,7 @@ use crate::{
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use std::ffi::c_void;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::mem::{self, MaybeUninit};
|
||||
use std::panic::{self, AssertUnwindSafe};
|
||||
use std::ptr;
|
||||
use std::str;
|
||||
@@ -217,18 +217,21 @@ pub(crate) unsafe fn c_callback_to_rust_fn(
|
||||
finalizer: Option<extern "C" fn(*mut std::ffi::c_void)>,
|
||||
) -> impl Fn(Caller<'_, crate::StoreData>, &[Val], &mut [Val]) -> Result<(), Trap> {
|
||||
let foreign = crate::ForeignData { data, finalizer };
|
||||
move |caller, params, results| {
|
||||
let params = params
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|p| wasmtime_val_t::from_val(p))
|
||||
.collect::<Vec<_>>();
|
||||
let mut out_results = (0..results.len())
|
||||
.map(|_| wasmtime_val_t {
|
||||
kind: crate::WASMTIME_I32,
|
||||
of: wasmtime_val_union { i32: 0 },
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
move |mut caller, params, results| {
|
||||
// Convert `params/results` to `wasmtime_val_t`. Use the previous
|
||||
// storage in `hostcall_val_storage` to help avoid allocations all the
|
||||
// time.
|
||||
let mut vals = mem::take(&mut caller.data_mut().hostcall_val_storage);
|
||||
debug_assert!(vals.is_empty());
|
||||
vals.reserve(params.len() + results.len());
|
||||
vals.extend(params.iter().cloned().map(|p| wasmtime_val_t::from_val(p)));
|
||||
vals.extend((0..results.len()).map(|_| wasmtime_val_t {
|
||||
kind: crate::WASMTIME_I32,
|
||||
of: wasmtime_val_union { i32: 0 },
|
||||
}));
|
||||
let (params, out_results) = vals.split_at_mut(params.len());
|
||||
|
||||
// Invoke the C function pointer, getting the results.
|
||||
let mut caller = wasmtime_caller_t { caller };
|
||||
let out = callback(
|
||||
foreign.data,
|
||||
@@ -242,9 +245,16 @@ pub(crate) unsafe fn c_callback_to_rust_fn(
|
||||
return Err(trap.trap);
|
||||
}
|
||||
|
||||
// Translate the `wasmtime_val_t` results into the `results` space
|
||||
for (i, result) in out_results.iter().enumerate() {
|
||||
results[i] = unsafe { result.to_val() };
|
||||
}
|
||||
|
||||
// Move our `vals` storage back into the store now that we no longer
|
||||
// need it. This'll get picked up by the next hostcall and reuse our
|
||||
// same storage.
|
||||
vals.truncate(0);
|
||||
caller.caller.data_mut().hostcall_val_storage = vals;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{wasm_engine_t, wasmtime_error_t, ForeignData};
|
||||
use crate::{wasm_engine_t, wasmtime_error_t, wasmtime_val_t, ForeignData};
|
||||
use std::cell::UnsafeCell;
|
||||
use std::ffi::c_void;
|
||||
use std::sync::Arc;
|
||||
@@ -67,6 +67,10 @@ pub struct StoreData {
|
||||
foreign: crate::ForeignData,
|
||||
#[cfg(feature = "wasi")]
|
||||
pub(crate) wasi: Option<wasmtime_wasi::WasiCtx>,
|
||||
|
||||
/// Temporary storage for usage during a wasm->host call to store values
|
||||
/// in a slice we pass to the C API.
|
||||
pub hostcall_val_storage: Vec<wasmtime_val_t>,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -85,6 +89,7 @@ pub extern "C" fn wasmtime_store_new(
|
||||
foreign: ForeignData { data, finalizer },
|
||||
#[cfg(feature = "wasi")]
|
||||
wasi: None,
|
||||
hostcall_val_storage: Vec::new(),
|
||||
},
|
||||
),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user