Pre-generate trampoline functions (#957)
* Refactor wasmtime_runtime::Export Instead of an enumeration with variants that have data fields have an enumeration where each variant has a struct, and each struct has the data fields. This allows us to store the structs in the `wasmtime` API and avoid lots of `panic!` calls and various extraneous matches. * Pre-generate trampoline functions The `wasmtime` crate supports calling arbitrary function signatures in wasm code, and to do this it generates "trampoline functions" which have a known ABI that then internally convert to a particular signature's ABI and call it. These trampoline functions are currently generated on-the-fly and are cached in the global `Store` structure. This, however, is suboptimal for a few reasons: * Due to how code memory is managed each trampoline resides in its own 64kb allocation of memory. This means if you have N trampolines you're using N * 64kb of memory, which is quite a lot of overhead! * Trampolines are never free'd, even if the referencing module goes away. This is similar to #925. * Trampolines are a source of shared state which prevents `Store` from being easily thread safe. This commit refactors how trampolines are managed inside of the `wasmtime` crate and jit/runtime internals. All trampolines are now allocated in the same pass of `CodeMemory` that the main module is allocated into. A trampoline is generated per-signature in a module as well, instead of per-function. This cache of trampolines is stored directly inside of an `Instance`. Trampolines are stored based on `VMSharedSignatureIndex` so they can be looked up from the internals of the `ExportFunction` value. The `Func` API has been updated with various bits and pieces to ensure the right trampolines are registered in the right places. Overall this should ensure that all trampolines necessary are generated up-front rather than lazily. This allows us to remove the trampoline cache from the `Compiler` type, and move one step closer to making `Compiler` threadsafe for usage across multiple threads. Note that as one small caveat the `Func::wrap*` family of functions don't need to generate a trampoline at runtime, they actually generate the trampoline at compile time which gets passed in. Also in addition to shuffling a lot of code around this fixes one minor bug found in `code_memory.rs`, where `self.position` was loaded before allocation, but the allocation may push a new chunk which would cause `self.position` to be zero instead. * Pass the `SignatureRegistry` as an argument to where it's needed. This avoids the need for storing it in an `Arc`. * Ignore tramoplines for functions with lots of arguments Co-authored-by: Dan Gohman <sunfish@mozilla.com>
This commit is contained in:
@@ -184,23 +184,53 @@ macro_rules! wrappers {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
unsafe extern "C" fn trampoline<F, $($args,)* R>(
|
||||
callee_vmctx: *mut VMContext,
|
||||
caller_vmctx: *mut VMContext,
|
||||
ptr: *const VMFunctionBody,
|
||||
args: *mut u128,
|
||||
)
|
||||
where
|
||||
F: Fn($($args),*) -> R + 'static,
|
||||
$($args: WasmTy,)*
|
||||
R: WasmRet,
|
||||
{
|
||||
let ptr = mem::transmute::<
|
||||
*const VMFunctionBody,
|
||||
unsafe extern "C" fn(
|
||||
*mut VMContext,
|
||||
*mut VMContext,
|
||||
$($args::Abi,)*
|
||||
) -> R::Abi,
|
||||
>(ptr);
|
||||
|
||||
let mut _next = args as *const u128;
|
||||
$(let $args = $args::load(&mut _next);)*
|
||||
|
||||
let ret = ptr(callee_vmctx, caller_vmctx, $($args),*);
|
||||
R::store(ret, args);
|
||||
}
|
||||
|
||||
let mut _args = Vec::new();
|
||||
$($args::push(&mut _args);)*
|
||||
let mut ret = Vec::new();
|
||||
R::push(&mut ret);
|
||||
let ty = FuncType::new(_args.into(), ret.into());
|
||||
unsafe {
|
||||
let trampoline = trampoline::<F, $($args,)* R>;
|
||||
let (instance, export) = crate::trampoline::generate_raw_func_export(
|
||||
&ty,
|
||||
std::slice::from_raw_parts_mut(
|
||||
shim::<F, $($args,)* R> as *mut _,
|
||||
0,
|
||||
),
|
||||
trampoline,
|
||||
store,
|
||||
Box::new(func),
|
||||
)
|
||||
.expect("failed to generate export");
|
||||
let callable = Rc::new(WasmtimeFn::new(store, instance, export));
|
||||
let callable = Rc::new(WasmtimeFn::new(store, instance, export, trampoline));
|
||||
Func::from_wrapped(store, ty, callable)
|
||||
}
|
||||
}
|
||||
@@ -214,8 +244,8 @@ macro_rules! getters {
|
||||
)*) => ($(
|
||||
$(#[$doc])*
|
||||
#[allow(non_snake_case)]
|
||||
pub fn $name<$($args,)* R>(&self)
|
||||
-> anyhow::Result<impl Fn($($args,)*) -> Result<R, Trap>>
|
||||
pub fn $name<'a, $($args,)* R>(&'a self)
|
||||
-> anyhow::Result<impl Fn($($args,)*) -> Result<R, Trap> + 'a>
|
||||
where
|
||||
$($args: WasmTy,)*
|
||||
R: WasmTy,
|
||||
@@ -239,28 +269,23 @@ macro_rules! getters {
|
||||
|
||||
// ... and then once we've passed the typechecks we can hand out our
|
||||
// object since our `transmute` below should be safe!
|
||||
let (address, vmctx) = match self.wasmtime_export() {
|
||||
wasmtime_runtime::Export::Function { address, vmctx, signature: _} => {
|
||||
(*address, *vmctx)
|
||||
}
|
||||
_ => panic!("expected function export"),
|
||||
};
|
||||
let f = self.wasmtime_function();
|
||||
Ok(move |$($args: $args),*| -> Result<R, Trap> {
|
||||
unsafe {
|
||||
let f = mem::transmute::<
|
||||
let fnptr = mem::transmute::<
|
||||
*const VMFunctionBody,
|
||||
unsafe extern "C" fn(
|
||||
*mut VMContext,
|
||||
*mut VMContext,
|
||||
$($args::Abi,)*
|
||||
) -> R::Abi,
|
||||
>(address);
|
||||
>(f.address);
|
||||
let mut ret = None;
|
||||
$(let $args = $args.into_abi();)*
|
||||
wasmtime_runtime::catch_traps(vmctx, || {
|
||||
ret = Some(f(vmctx, ptr::null_mut(), $($args,)*));
|
||||
wasmtime_runtime::catch_traps(f.vmctx, || {
|
||||
ret = Some(fnptr(f.vmctx, ptr::null_mut(), $($args,)*));
|
||||
}).map_err(Trap::from_jit)?;
|
||||
Ok(R::from_abi(vmctx, ret.unwrap()))
|
||||
Ok(R::from_abi(f.vmctx, ret.unwrap()))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -553,25 +578,37 @@ impl Func {
|
||||
Ok(results.into_boxed_slice())
|
||||
}
|
||||
|
||||
pub(crate) fn wasmtime_export(&self) -> &wasmtime_runtime::Export {
|
||||
self.callable.wasmtime_export()
|
||||
pub(crate) fn wasmtime_function(&self) -> &wasmtime_runtime::ExportFunction {
|
||||
self.callable.wasmtime_function()
|
||||
}
|
||||
|
||||
pub(crate) fn from_wasmtime_function(
|
||||
export: wasmtime_runtime::Export,
|
||||
export: wasmtime_runtime::ExportFunction,
|
||||
store: &Store,
|
||||
instance_handle: InstanceHandle,
|
||||
) -> Self {
|
||||
// Signatures should always be registered in the store's registry of
|
||||
// shared signatures, so we should be able to unwrap safely here.
|
||||
let sig = store
|
||||
.compiler()
|
||||
.signatures()
|
||||
.lookup(export.signature)
|
||||
.expect("failed to lookup signature");
|
||||
|
||||
// This is only called with `Export::Function`, and since it's coming
|
||||
// from wasmtime_runtime itself we should support all the types coming
|
||||
// out of it, so assert such here.
|
||||
let ty = if let wasmtime_runtime::Export::Function { signature, .. } = &export {
|
||||
FuncType::from_wasmtime_signature(signature.clone())
|
||||
.expect("core wasm signature should be supported")
|
||||
} else {
|
||||
panic!("expected function export")
|
||||
};
|
||||
let callable = WasmtimeFn::new(store, instance_handle, export);
|
||||
let ty = FuncType::from_wasmtime_signature(sig)
|
||||
.expect("core wasm signature should be supported");
|
||||
|
||||
// Each function signature in a module should have a trampoline stored
|
||||
// on that module as well, so unwrap the result here since otherwise
|
||||
// it's a bug in wasmtime.
|
||||
let trampoline = instance_handle
|
||||
.trampoline(export.signature)
|
||||
.expect("failed to retrieve trampoline from module");
|
||||
|
||||
let callable = WasmtimeFn::new(store, instance_handle, export, trampoline);
|
||||
Func::from_wrapped(store, ty, Rc::new(callable))
|
||||
}
|
||||
|
||||
@@ -727,6 +764,10 @@ pub trait WasmTy {
|
||||
fn from_abi(vmctx: *mut VMContext, abi: Self::Abi) -> Self;
|
||||
#[doc(hidden)]
|
||||
fn into_abi(self) -> Self::Abi;
|
||||
#[doc(hidden)]
|
||||
unsafe fn load(ptr: &mut *const u128) -> Self::Abi;
|
||||
#[doc(hidden)]
|
||||
unsafe fn store(abi: Self::Abi, ptr: *mut u128);
|
||||
}
|
||||
|
||||
impl WasmTy for () {
|
||||
@@ -743,6 +784,10 @@ impl WasmTy for () {
|
||||
fn into_abi(self) -> Self::Abi {
|
||||
self
|
||||
}
|
||||
#[inline]
|
||||
unsafe fn load(_ptr: &mut *const u128) -> Self::Abi {}
|
||||
#[inline]
|
||||
unsafe fn store(_abi: Self::Abi, _ptr: *mut u128) {}
|
||||
}
|
||||
|
||||
impl WasmTy for i32 {
|
||||
@@ -767,6 +812,16 @@ impl WasmTy for i32 {
|
||||
fn into_abi(self) -> Self::Abi {
|
||||
self
|
||||
}
|
||||
#[inline]
|
||||
unsafe fn load(ptr: &mut *const u128) -> Self::Abi {
|
||||
let ret = **ptr as Self;
|
||||
*ptr = (*ptr).add(1);
|
||||
return ret;
|
||||
}
|
||||
#[inline]
|
||||
unsafe fn store(abi: Self::Abi, ptr: *mut u128) {
|
||||
*ptr = abi as u128;
|
||||
}
|
||||
}
|
||||
|
||||
impl WasmTy for i64 {
|
||||
@@ -791,6 +846,16 @@ impl WasmTy for i64 {
|
||||
fn into_abi(self) -> Self::Abi {
|
||||
self
|
||||
}
|
||||
#[inline]
|
||||
unsafe fn load(ptr: &mut *const u128) -> Self::Abi {
|
||||
let ret = **ptr as Self;
|
||||
*ptr = (*ptr).add(1);
|
||||
return ret;
|
||||
}
|
||||
#[inline]
|
||||
unsafe fn store(abi: Self::Abi, ptr: *mut u128) {
|
||||
*ptr = abi as u128;
|
||||
}
|
||||
}
|
||||
|
||||
impl WasmTy for f32 {
|
||||
@@ -815,6 +880,16 @@ impl WasmTy for f32 {
|
||||
fn into_abi(self) -> Self::Abi {
|
||||
self
|
||||
}
|
||||
#[inline]
|
||||
unsafe fn load(ptr: &mut *const u128) -> Self::Abi {
|
||||
let ret = f32::from_bits(**ptr as u32);
|
||||
*ptr = (*ptr).add(1);
|
||||
return ret;
|
||||
}
|
||||
#[inline]
|
||||
unsafe fn store(abi: Self::Abi, ptr: *mut u128) {
|
||||
*ptr = abi.to_bits() as u128;
|
||||
}
|
||||
}
|
||||
|
||||
impl WasmTy for f64 {
|
||||
@@ -839,6 +914,16 @@ impl WasmTy for f64 {
|
||||
fn into_abi(self) -> Self::Abi {
|
||||
self
|
||||
}
|
||||
#[inline]
|
||||
unsafe fn load(ptr: &mut *const u128) -> Self::Abi {
|
||||
let ret = f64::from_bits(**ptr as u64);
|
||||
*ptr = (*ptr).add(1);
|
||||
return ret;
|
||||
}
|
||||
#[inline]
|
||||
unsafe fn store(abi: Self::Abi, ptr: *mut u128) {
|
||||
*ptr = abi.to_bits() as u128;
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait implemented for types which can be returned from closures passed to
|
||||
@@ -858,6 +943,8 @@ pub trait WasmRet {
|
||||
fn matches(tys: impl Iterator<Item = ValType>) -> anyhow::Result<()>;
|
||||
#[doc(hidden)]
|
||||
fn into_abi(self) -> Self::Abi;
|
||||
#[doc(hidden)]
|
||||
unsafe fn store(abi: Self::Abi, ptr: *mut u128);
|
||||
}
|
||||
|
||||
impl<T: WasmTy> WasmRet for T {
|
||||
@@ -874,6 +961,11 @@ impl<T: WasmTy> WasmRet for T {
|
||||
fn into_abi(self) -> Self::Abi {
|
||||
T::into_abi(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn store(abi: Self::Abi, ptr: *mut u128) {
|
||||
T::store(abi, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: WasmTy> WasmRet for Result<T, Trap> {
|
||||
@@ -889,7 +981,7 @@ impl<T: WasmTy> WasmRet for Result<T, Trap> {
|
||||
#[inline]
|
||||
fn into_abi(self) -> Self::Abi {
|
||||
match self {
|
||||
Ok(val) => return val.into_abi(),
|
||||
Ok(val) => return T::into_abi(val),
|
||||
Err(trap) => handle_trap(trap),
|
||||
}
|
||||
|
||||
@@ -897,4 +989,9 @@ impl<T: WasmTy> WasmRet for Result<T, Trap> {
|
||||
unsafe { wasmtime_runtime::raise_user_trap(Box::new(trap)) }
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn store(abi: Self::Abi, ptr: *mut u128) {
|
||||
T::store(abi, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user