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:
Alex Crichton
2021-09-03 15:14:21 -05:00
committed by GitHub
parent 0473e1990a
commit c73673559b
7 changed files with 83 additions and 39 deletions

View File

@@ -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(())
}
}

View File

@@ -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(),
},
),
})