Files
wasmtime/crates/wasmtime/src/trampoline/func.rs
Alex Crichton ef3ec594ce Don't copy executable code into a CodeMemory (#3265)
* 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
2021-08-30 13:38:35 -05:00

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: &[],
})?,
)
}