1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
//! Support for a calling of an imported function.
use crate::{Engine, FuncType, ValRaw};
use anyhow::Result;
use std::panic::{self, AssertUnwindSafe};
use std::ptr::NonNull;
use wasmtime_jit::CodeMemory;
use wasmtime_runtime::{
StoreBox, VMArrayCallHostFuncContext, VMContext, VMFuncRef, VMOpaqueContext,
};
struct TrampolineState<F> {
func: F,
#[allow(dead_code)]
code_memory: CodeMemory,
}
/// Shim to call a host-defined function that uses the array calling convention.
///
/// Together with `VMArrayCallHostFuncContext`, this implements the transition
/// from a raw, non-closure function pointer to a Rust closure that associates
/// data and function together.
///
/// Also shepherds panics and traps across Wasm.
unsafe extern "C" fn array_call_shim<F>(
vmctx: *mut VMOpaqueContext,
caller_vmctx: *mut VMOpaqueContext,
values_vec: *mut ValRaw,
values_vec_len: usize,
) where
F: Fn(*mut VMContext, &mut [ValRaw]) -> Result<()> + 'static,
{
// 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
// to unwind past an `extern "C"` function. Here we are in an `extern "C"`
// function and the cross into wasm was through an `extern "C"` function at
// the base of the stack as well. We'll need to wait for assorted RFCs and
// language features to enable this to be done in a sound and stable fashion
// before avoiding catching the panic here.
//
// Also note that there are intentionally no local variables on this stack
// frame. The reason for that is that some of the "raise" functions we have
// below will trigger a longjmp, which won't run local destructors if we
// have any. To prevent leaks we avoid having any local destructors by
// avoiding local variables.
let result = panic::catch_unwind(AssertUnwindSafe(|| {
let vmctx = VMArrayCallHostFuncContext::from_opaque(vmctx);
// 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>);
let values_vec = std::slice::from_raw_parts_mut(values_vec, values_vec_len);
(state.func)(VMContext::from_opaque(caller_vmctx), values_vec)
}));
match result {
Ok(Ok(())) => {}
// If a trap was raised (an error returned from the imported function)
// then we smuggle the trap through `Box<dyn Error>` through to the
// call-site, which gets unwrapped in `Trap::from_runtime` later on as we
// convert from the internal `Trap` type to our own `Trap` type in this
// crate.
Ok(Err(trap)) => crate::trap::raise(trap.into()),
// And finally if the imported function panicked, then we trigger the
// form of unwinding that's safe to jump over wasm code on all
// platforms.
Err(panic) => wasmtime_runtime::resume_panic(panic),
}
}
#[cfg(any(feature = "cranelift", feature = "winch"))]
pub fn create_array_call_function<F>(
ft: &FuncType,
func: F,
engine: &Engine,
) -> Result<StoreBox<VMArrayCallHostFuncContext>>
where
F: Fn(*mut VMContext, &mut [ValRaw]) -> Result<()> + Send + Sync + 'static,
{
use std::ptr;
let mut obj = engine
.compiler()
.object(wasmtime_environ::ObjectKind::Module)?;
let (wasm_call_range, native_call_range) = engine
.compiler()
.emit_trampolines_for_array_call_host_func(
ft.as_wasm_func_type(),
array_call_shim::<F> as usize,
&mut obj,
)?;
engine.append_bti(&mut obj);
let obj = wasmtime_jit::ObjectBuilder::new(obj, &engine.config().tunables).finish()?;
// Copy the results of JIT compilation into executable memory, and this will
// also take care of unwind table registration.
let mut code_memory = CodeMemory::new(obj)?;
code_memory.publish()?;
engine.profiler().register_module(&code_memory, &|_| None);
// Extract the host/wasm trampolines from the results of compilation since
// we know their start/length.
let text = code_memory.text();
let array_call = array_call_shim::<F>;
let wasm_call = text[wasm_call_range.start as usize..].as_ptr() as *mut _;
let wasm_call = Some(NonNull::new(wasm_call).unwrap());
let native_call = text[native_call_range.start as usize..].as_ptr() as *mut _;
let native_call = NonNull::new(native_call).unwrap();
let sig = engine.signatures().register(ft.as_wasm_func_type());
unsafe {
Ok(VMArrayCallHostFuncContext::new(
VMFuncRef {
array_call,
wasm_call,
native_call,
type_index: sig,
vmctx: ptr::null_mut(),
},
Box::new(TrampolineState { func, code_memory }),
))
}
}