* Don't copy executable code into a `CodeMemory` This commit moves a copy from compiled artifacts into a `CodeMemory`. In general this commit drastically changes the meaning of a `CodeMemory`. Previously it was an iteratively-pushed-on structure that would accumulate executable code over time. Afterwards, however, it's a manager for an `MmapVec` which updates the permissions on text section to ensure that the pages are executable. By taking ownership of an `MmapVec` within a `CodeMemory` there's no need to copy any data around, which means that the `.text` section in the ELF image produced by Wasmtime is usable as-is after placement in memory and relocations have been resolved. This moves Wasmtime one step closer to being able to directly use a module after it's `mmap`'d into memory, optimizing when a module is loaded. * Fix windows section alignment * Review comments
140 lines
5.1 KiB
Rust
140 lines
5.1 KiB
Rust
//! Support for a calling of an imported function.
|
|
|
|
use crate::{Engine, FuncType, Trap};
|
|
use anyhow::Result;
|
|
use std::any::Any;
|
|
use std::panic::{self, AssertUnwindSafe};
|
|
use std::sync::Arc;
|
|
use wasmtime_environ::{EntityIndex, Module, ModuleType, PrimaryMap, SignatureIndex};
|
|
use wasmtime_jit::{CodeMemory, MmapVec};
|
|
use wasmtime_runtime::{
|
|
Imports, InstanceAllocationRequest, InstanceAllocator, InstanceHandle,
|
|
OnDemandInstanceAllocator, VMContext, VMFunctionBody, VMSharedSignatureIndex, VMTrampoline,
|
|
};
|
|
|
|
struct TrampolineState {
|
|
func: Box<dyn Fn(*mut VMContext, *mut u128) -> Result<(), Trap> + Send + Sync>,
|
|
#[allow(dead_code)]
|
|
code_memory: CodeMemory,
|
|
}
|
|
|
|
unsafe extern "C" fn stub_fn(
|
|
vmctx: *mut VMContext,
|
|
caller_vmctx: *mut VMContext,
|
|
values_vec: *mut u128,
|
|
) {
|
|
// 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(|| {
|
|
call_stub(vmctx, 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)) => wasmtime_runtime::raise_user_trap(Box::new(trap)),
|
|
|
|
// 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),
|
|
}
|
|
|
|
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)]
|
|
pub fn create_function(
|
|
ft: &FuncType,
|
|
func: Box<dyn Fn(*mut VMContext, *mut u128) -> Result<(), Trap> + Send + Sync>,
|
|
engine: &Engine,
|
|
) -> Result<(InstanceHandle, VMTrampoline)> {
|
|
let mut obj = engine.compiler().object()?;
|
|
let (t1, t2) = engine.compiler().emit_trampoline_obj(
|
|
ft.as_wasm_func_type(),
|
|
stub_fn as usize,
|
|
&mut obj,
|
|
)?;
|
|
let obj = MmapVec::from_obj(obj)?;
|
|
|
|
// 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);
|
|
let code = code_memory.publish()?;
|
|
|
|
// Extract the host/wasm trampolines from the results of compilation since
|
|
// we know their start/length.
|
|
let host_trampoline = code.text[t1.start as usize..][..t1.length as usize].as_ptr();
|
|
let wasm_trampoline = &code.text[t2.start as usize..][..t2.length as usize];
|
|
let wasm_trampoline = wasm_trampoline as *const [u8] as *mut [VMFunctionBody];
|
|
|
|
let sig = engine.signatures().register(ft.as_wasm_func_type());
|
|
|
|
unsafe {
|
|
let instance = create_raw_function(
|
|
wasm_trampoline,
|
|
sig,
|
|
Box::new(TrampolineState { func, code_memory }),
|
|
)?;
|
|
let host_trampoline = std::mem::transmute::<*const u8, VMTrampoline>(host_trampoline);
|
|
Ok((instance, host_trampoline))
|
|
}
|
|
}
|
|
|
|
pub unsafe fn create_raw_function(
|
|
func: *mut [VMFunctionBody],
|
|
sig: VMSharedSignatureIndex,
|
|
host_state: Box<dyn Any + Send + Sync>,
|
|
) -> Result<InstanceHandle> {
|
|
let mut module = Module::new();
|
|
let mut functions = PrimaryMap::new();
|
|
functions.push(Default::default());
|
|
|
|
let sig_id = SignatureIndex::from_u32(u32::max_value() - 1);
|
|
module.types.push(ModuleType::Function(sig_id));
|
|
let func_id = module.functions.push(sig_id);
|
|
module
|
|
.exports
|
|
.insert(String::new(), EntityIndex::Function(func_id));
|
|
|
|
Ok(
|
|
OnDemandInstanceAllocator::default().allocate(InstanceAllocationRequest {
|
|
module: Arc::new(module),
|
|
functions: &functions,
|
|
image_base: (*func).as_ptr() as usize,
|
|
imports: Imports::default(),
|
|
shared_signatures: sig.into(),
|
|
host_state,
|
|
store: None,
|
|
wasm_data: &[],
|
|
})?,
|
|
)
|
|
}
|