Remove an indirect function call in Func::new (#3293)
This commit optimizes the runtime execution of `Func::new` by removing an indirect function call that happens whenever a host function is called. This indirection was generally done to prevent monomoprhizing a lot into consumer code but the few extra functions this makes monomorphic are fairly small, and in general wasm->host call performance is pretty important. While not a massive win this is expected to improve codegen, especially because with the indirect call removed the compiler should now be able to prove more often when a `Func::new` closure doesn't panic or return an error.
This commit is contained in:
@@ -1894,11 +1894,11 @@ impl HostFunc {
|
|||||||
let ty_clone = ty.clone();
|
let ty_clone = ty.clone();
|
||||||
|
|
||||||
// Create a trampoline that converts raw u128 values to `Val`
|
// Create a trampoline that converts raw u128 values to `Val`
|
||||||
let func = Box::new(move |caller_vmctx, values_vec: *mut u128| unsafe {
|
let func = move |caller_vmctx, values_vec: *mut u128| unsafe {
|
||||||
Caller::with(caller_vmctx, |caller| {
|
Caller::with(caller_vmctx, |caller| {
|
||||||
Func::invoke(caller, &ty_clone, values_vec, &func)
|
Func::invoke(caller, &ty_clone, values_vec, &func)
|
||||||
})
|
})
|
||||||
});
|
};
|
||||||
|
|
||||||
let (instance, trampoline) = crate::trampoline::create_function(&ty, func, engine)
|
let (instance, trampoline) = crate::trampoline::create_function(&ty, func, engine)
|
||||||
.expect("failed to create function");
|
.expect("failed to create function");
|
||||||
|
|||||||
@@ -12,17 +12,19 @@ use wasmtime_runtime::{
|
|||||||
OnDemandInstanceAllocator, VMContext, VMFunctionBody, VMSharedSignatureIndex, VMTrampoline,
|
OnDemandInstanceAllocator, VMContext, VMFunctionBody, VMSharedSignatureIndex, VMTrampoline,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TrampolineState {
|
struct TrampolineState<F> {
|
||||||
func: Box<dyn Fn(*mut VMContext, *mut u128) -> Result<(), Trap> + Send + Sync>,
|
func: F,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
code_memory: CodeMemory,
|
code_memory: CodeMemory,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn stub_fn(
|
unsafe extern "C" fn stub_fn<F>(
|
||||||
vmctx: *mut VMContext,
|
vmctx: *mut VMContext,
|
||||||
caller_vmctx: *mut VMContext,
|
caller_vmctx: *mut VMContext,
|
||||||
values_vec: *mut u128,
|
values_vec: *mut u128,
|
||||||
) {
|
) where
|
||||||
|
F: Fn(*mut VMContext, *mut u128) -> Result<(), Trap> + 'static,
|
||||||
|
{
|
||||||
// Here we are careful to use `catch_unwind` to ensure Rust panics don't
|
// Here we are careful to use `catch_unwind` to ensure Rust panics don't
|
||||||
// unwind past us. The primary reason for this is that Rust considers it UB
|
// unwind past us. The primary reason for this is that Rust considers it UB
|
||||||
// to unwind past an `extern "C"` function. Here we are in an `extern "C"`
|
// to unwind past an `extern "C"` function. Here we are in an `extern "C"`
|
||||||
@@ -37,7 +39,13 @@ unsafe extern "C" fn stub_fn(
|
|||||||
// have any. To prevent leaks we avoid having any local destructors by
|
// have any. To prevent leaks we avoid having any local destructors by
|
||||||
// avoiding local variables.
|
// avoiding local variables.
|
||||||
let result = panic::catch_unwind(AssertUnwindSafe(|| {
|
let result = panic::catch_unwind(AssertUnwindSafe(|| {
|
||||||
call_stub(vmctx, caller_vmctx, values_vec)
|
// Double-check ourselves in debug mode, but we control
|
||||||
|
// the `Any` here so an unsafe downcast should also
|
||||||
|
// work.
|
||||||
|
let state = (*vmctx).host_state();
|
||||||
|
debug_assert!(state.is::<TrampolineState<F>>());
|
||||||
|
let state = &*(state as *const _ as *const TrampolineState<F>);
|
||||||
|
(state.func)(caller_vmctx, values_vec)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
@@ -55,31 +63,21 @@ unsafe extern "C" fn stub_fn(
|
|||||||
// platforms.
|
// platforms.
|
||||||
Err(panic) => wasmtime_runtime::resume_panic(panic),
|
Err(panic) => wasmtime_runtime::resume_panic(panic),
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn call_stub(
|
|
||||||
vmctx: *mut VMContext,
|
|
||||||
caller_vmctx: *mut VMContext,
|
|
||||||
values_vec: *mut u128,
|
|
||||||
) -> Result<(), Trap> {
|
|
||||||
let instance = InstanceHandle::from_vmctx(vmctx);
|
|
||||||
let state = &instance
|
|
||||||
.host_state()
|
|
||||||
.downcast_ref::<TrampolineState>()
|
|
||||||
.expect("state");
|
|
||||||
(state.func)(caller_vmctx, values_vec)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(compiler)]
|
#[cfg(compiler)]
|
||||||
pub fn create_function(
|
pub fn create_function<F>(
|
||||||
ft: &FuncType,
|
ft: &FuncType,
|
||||||
func: Box<dyn Fn(*mut VMContext, *mut u128) -> Result<(), Trap> + Send + Sync>,
|
func: F,
|
||||||
engine: &Engine,
|
engine: &Engine,
|
||||||
) -> Result<(InstanceHandle, VMTrampoline)> {
|
) -> Result<(InstanceHandle, VMTrampoline)>
|
||||||
|
where
|
||||||
|
F: Fn(*mut VMContext, *mut u128) -> Result<(), Trap> + Send + Sync + 'static,
|
||||||
|
{
|
||||||
let mut obj = engine.compiler().object()?;
|
let mut obj = engine.compiler().object()?;
|
||||||
let (t1, t2) = engine.compiler().emit_trampoline_obj(
|
let (t1, t2) = engine.compiler().emit_trampoline_obj(
|
||||||
ft.as_wasm_func_type(),
|
ft.as_wasm_func_type(),
|
||||||
stub_fn as usize,
|
stub_fn::<F> as usize,
|
||||||
&mut obj,
|
&mut obj,
|
||||||
)?;
|
)?;
|
||||||
let obj = MmapVec::from_obj(obj)?;
|
let obj = MmapVec::from_obj(obj)?;
|
||||||
|
|||||||
Reference in New Issue
Block a user