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:
Alex Crichton
2020-03-12 16:17:48 -05:00
committed by GitHub
parent 34f768ddd5
commit 3e2be43502
23 changed files with 599 additions and 447 deletions

View File

@@ -3,10 +3,10 @@ use crate::trampoline::generate_func_export;
use crate::trap::Trap;
use crate::types::FuncType;
use crate::values::Val;
use std::cmp::max;
use std::ptr;
use std::rc::Rc;
use wasmtime_environ::ir;
use wasmtime_runtime::{Export, InstanceHandle};
use wasmtime_runtime::{ExportFunction, InstanceHandle, VMTrampoline};
/// A trait representing a function that can be imported and called from inside
/// WebAssembly.
@@ -87,45 +87,42 @@ pub trait Callable {
pub(crate) trait WrappedCallable {
fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), Trap>;
fn signature(&self) -> &ir::Signature {
match self.wasmtime_export() {
Export::Function { signature, .. } => signature,
_ => panic!("unexpected export type in Callable"),
}
}
fn wasmtime_handle(&self) -> &InstanceHandle;
fn wasmtime_export(&self) -> &Export;
fn wasmtime_function(&self) -> &ExportFunction;
}
pub(crate) struct WasmtimeFn {
store: Store,
instance: InstanceHandle,
export: Export,
export: ExportFunction,
trampoline: VMTrampoline,
}
impl WasmtimeFn {
pub fn new(store: &Store, instance: InstanceHandle, export: Export) -> WasmtimeFn {
pub fn new(
store: &Store,
instance: InstanceHandle,
export: ExportFunction,
trampoline: VMTrampoline,
) -> WasmtimeFn {
WasmtimeFn {
store: store.clone(),
instance,
export,
trampoline,
}
}
}
impl WrappedCallable for WasmtimeFn {
fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), Trap> {
use std::cmp::max;
use std::mem;
let (vmctx, body, signature) = match self.wasmtime_export() {
Export::Function {
vmctx,
address,
signature,
} => (*vmctx, *address, signature.clone()),
_ => panic!("unexpected export type in Callable"),
};
let f = self.wasmtime_function();
let signature = self
.store
.compiler()
.signatures()
.lookup(f.signature)
.expect("missing signature");
if signature.params.len() - 2 != params.len() {
return Err(Trap::new(format!(
"expected {} arguments, got {}",
@@ -141,7 +138,6 @@ impl WrappedCallable for WasmtimeFn {
)));
}
let value_size = mem::size_of::<u128>();
let mut values_vec = vec![0; max(params.len(), results.len())];
// Store the argument values into `values_vec`.
@@ -155,20 +151,13 @@ impl WrappedCallable for WasmtimeFn {
}
}
// Get the trampoline to call for this function.
let exec_code_buf = self
.store
.compiler_mut()
.get_published_trampoline(&signature, value_size)
.map_err(|e| Trap::new(format!("trampoline error: {:?}", e)))?;
// Call the trampoline.
if let Err(error) = unsafe {
wasmtime_runtime::wasmtime_call_trampoline(
vmctx,
f.vmctx,
ptr::null_mut(),
exec_code_buf,
body,
self.trampoline,
f.address,
values_vec.as_mut_ptr() as *mut u8,
)
} {
@@ -189,7 +178,7 @@ impl WrappedCallable for WasmtimeFn {
fn wasmtime_handle(&self) -> &InstanceHandle {
&self.instance
}
fn wasmtime_export(&self) -> &Export {
fn wasmtime_function(&self) -> &ExportFunction {
&self.export
}
}
@@ -197,7 +186,7 @@ impl WrappedCallable for WasmtimeFn {
pub struct NativeCallable {
callable: Rc<dyn Callable + 'static>,
instance: InstanceHandle,
export: Export,
export: ExportFunction,
}
impl NativeCallable {
@@ -219,7 +208,7 @@ impl WrappedCallable for NativeCallable {
fn wasmtime_handle(&self) -> &InstanceHandle {
&self.instance
}
fn wasmtime_export(&self) -> &Export {
fn wasmtime_function(&self) -> &ExportFunction {
&self.export
}
}

View File

@@ -83,10 +83,10 @@ impl Extern {
pub(crate) fn get_wasmtime_export(&self) -> wasmtime_runtime::Export {
match self {
Extern::Func(f) => f.wasmtime_export().clone(),
Extern::Global(g) => g.wasmtime_export().clone(),
Extern::Memory(m) => m.wasmtime_export().clone(),
Extern::Table(t) => t.wasmtime_export().clone(),
Extern::Func(f) => f.wasmtime_function().clone().into(),
Extern::Global(g) => g.wasmtime_export.clone().into(),
Extern::Memory(m) => m.wasmtime_export.clone().into(),
Extern::Table(t) => t.wasmtime_export.clone().into(),
}
}
@@ -96,17 +96,17 @@ impl Extern {
export: wasmtime_runtime::Export,
) -> Extern {
match export {
wasmtime_runtime::Export::Function { .. } => {
Extern::Func(Func::from_wasmtime_function(export, store, instance_handle))
wasmtime_runtime::Export::Function(f) => {
Extern::Func(Func::from_wasmtime_function(f, store, instance_handle))
}
wasmtime_runtime::Export::Memory { .. } => {
Extern::Memory(Memory::from_wasmtime_memory(export, store, instance_handle))
wasmtime_runtime::Export::Memory(m) => {
Extern::Memory(Memory::from_wasmtime_memory(m, store, instance_handle))
}
wasmtime_runtime::Export::Global { .. } => {
Extern::Global(Global::from_wasmtime_global(export, store, instance_handle))
wasmtime_runtime::Export::Global(g) => {
Extern::Global(Global::from_wasmtime_global(g, store, instance_handle))
}
wasmtime_runtime::Export::Table { .. } => {
Extern::Table(Table::from_wasmtime_table(export, store, instance_handle))
wasmtime_runtime::Export::Table(t) => {
Extern::Table(Table::from_wasmtime_table(t, store, instance_handle))
}
}
}
@@ -165,7 +165,7 @@ impl From<Table> for Extern {
pub struct Global {
store: Store,
ty: GlobalType,
wasmtime_export: wasmtime_runtime::Export,
wasmtime_export: wasmtime_runtime::ExportGlobal,
wasmtime_handle: InstanceHandle,
}
@@ -202,17 +202,10 @@ impl Global {
&self.ty
}
fn wasmtime_global_definition(&self) -> *mut wasmtime_runtime::VMGlobalDefinition {
match self.wasmtime_export {
wasmtime_runtime::Export::Global { definition, .. } => definition,
_ => panic!("global definition not found"),
}
}
/// Returns the current [`Val`] of this global.
pub fn get(&self) -> Val {
let definition = unsafe { &mut *self.wasmtime_global_definition() };
unsafe {
let definition = &mut *self.wasmtime_export.definition;
match self.ty().content() {
ValType::I32 => Val::from(*definition.as_i32()),
ValType::I64 => Val::from(*definition.as_i64()),
@@ -243,8 +236,8 @@ impl Global {
if !val.comes_from_same_store(&self.store) {
bail!("cross-`Store` values are not supported");
}
let definition = unsafe { &mut *self.wasmtime_global_definition() };
unsafe {
let definition = &mut *self.wasmtime_export.definition;
match val {
Val::I32(i) => *definition.as_i32_mut() = i,
Val::I64(i) => *definition.as_i64_mut() = i,
@@ -256,28 +249,19 @@ impl Global {
Ok(())
}
pub(crate) fn wasmtime_export(&self) -> &wasmtime_runtime::Export {
&self.wasmtime_export
}
pub(crate) fn from_wasmtime_global(
export: wasmtime_runtime::Export,
wasmtime_export: wasmtime_runtime::ExportGlobal,
store: &Store,
wasmtime_handle: InstanceHandle,
) -> Global {
let global = if let wasmtime_runtime::Export::Global { ref global, .. } = export {
global
} else {
panic!("wasmtime export is not global")
};
// The original export is coming from wasmtime_runtime itself we should
// support all the types coming out of it, so assert such here.
let ty = GlobalType::from_wasmtime_global(&global)
let ty = GlobalType::from_wasmtime_global(&wasmtime_export.global)
.expect("core wasm global type should be supported");
Global {
store: store.clone(),
ty: ty,
wasmtime_export: export,
wasmtime_export,
wasmtime_handle,
}
}
@@ -303,11 +287,11 @@ pub struct Table {
store: Store,
ty: TableType,
wasmtime_handle: InstanceHandle,
wasmtime_export: wasmtime_runtime::Export,
wasmtime_export: wasmtime_runtime::ExportTable,
}
fn set_table_item(
handle: &mut InstanceHandle,
handle: &InstanceHandle,
table_index: wasm::DefinedTableIndex,
item_index: u32,
item: wasmtime_runtime::VMCallerCheckedAnyfunc,
@@ -331,18 +315,13 @@ impl Table {
/// Returns an error if `init` does not match the element type of the table.
pub fn new(store: &Store, ty: TableType, init: Val) -> Result<Table> {
let item = into_checked_anyfunc(init, store)?;
let (mut wasmtime_handle, wasmtime_export) = generate_table_export(store, &ty)?;
let (wasmtime_handle, wasmtime_export) = generate_table_export(store, &ty)?;
// Initialize entries with the init value.
match wasmtime_export {
wasmtime_runtime::Export::Table { definition, .. } => {
let index = wasmtime_handle.table_index(unsafe { &*definition });
let len = unsafe { (*definition).current_elements };
for i in 0..len {
set_table_item(&mut wasmtime_handle, index, i, item.clone())?;
}
}
_ => unreachable!("export should be a table"),
let definition = unsafe { &*wasmtime_export.definition };
let index = wasmtime_handle.table_index(definition);
for i in 0..definition.current_elements {
set_table_item(&wasmtime_handle, index, i, item.clone())?;
}
Ok(Table {
@@ -360,11 +339,9 @@ impl Table {
}
fn wasmtime_table_index(&self) -> wasm::DefinedTableIndex {
match self.wasmtime_export {
wasmtime_runtime::Export::Table { definition, .. } => {
self.wasmtime_handle.table_index(unsafe { &*definition })
}
_ => panic!("global definition not found"),
unsafe {
self.wasmtime_handle
.table_index(&*self.wasmtime_export.definition)
}
}
@@ -385,19 +362,13 @@ impl Table {
/// the right type to be stored in this table.
pub fn set(&self, index: u32, val: Val) -> Result<()> {
let table_index = self.wasmtime_table_index();
let mut wasmtime_handle = self.wasmtime_handle.clone();
let item = into_checked_anyfunc(val, &self.store)?;
set_table_item(&mut wasmtime_handle, table_index, index, item)
set_table_item(&self.wasmtime_handle, table_index, index, item)
}
/// Returns the current size of this table.
pub fn size(&self) -> u32 {
match self.wasmtime_export {
wasmtime_runtime::Export::Table { definition, .. } => unsafe {
(*definition).current_elements
},
_ => panic!("global definition not found"),
}
unsafe { (&*self.wasmtime_export.definition).current_elements }
}
/// Grows the size of this table by `delta` more elements, initialization
@@ -462,26 +433,17 @@ impl Table {
Ok(())
}
pub(crate) fn wasmtime_export(&self) -> &wasmtime_runtime::Export {
&self.wasmtime_export
}
pub(crate) fn from_wasmtime_table(
export: wasmtime_runtime::Export,
wasmtime_export: wasmtime_runtime::ExportTable,
store: &Store,
instance_handle: wasmtime_runtime::InstanceHandle,
wasmtime_handle: wasmtime_runtime::InstanceHandle,
) -> Table {
let table = if let wasmtime_runtime::Export::Table { ref table, .. } = export {
table
} else {
panic!("wasmtime export is not table")
};
let ty = TableType::from_wasmtime_table(&table.table);
let ty = TableType::from_wasmtime_table(&wasmtime_export.table.table);
Table {
store: store.clone(),
ty: ty,
wasmtime_handle: instance_handle,
wasmtime_export: export,
ty,
wasmtime_handle,
wasmtime_export,
}
}
}
@@ -511,7 +473,7 @@ pub struct Memory {
store: Store,
ty: MemoryType,
wasmtime_handle: InstanceHandle,
wasmtime_export: wasmtime_runtime::Export,
wasmtime_export: wasmtime_runtime::ExportMemory,
}
impl Memory {
@@ -536,13 +498,6 @@ impl Memory {
&self.ty
}
fn wasmtime_memory_definition(&self) -> *mut wasmtime_runtime::VMMemoryDefinition {
match self.wasmtime_export {
wasmtime_runtime::Export::Memory { definition, .. } => definition,
_ => panic!("memory definition not found"),
}
}
/// Returns this memory as a slice view that can be read natively in Rust.
///
/// # Safety
@@ -584,7 +539,7 @@ impl Memory {
/// and in general you probably want to result to unsafe accessors and the
/// `data` methods below.
pub unsafe fn data_unchecked_mut(&self) -> &mut [u8] {
let definition = &*self.wasmtime_memory_definition();
let definition = &*self.wasmtime_export.definition;
slice::from_raw_parts_mut(definition.base, definition.current_length)
}
@@ -595,14 +550,14 @@ impl Memory {
/// of [`Memory::data_unchecked`] to make sure that you can safely
/// read/write the memory.
pub fn data_ptr(&self) -> *mut u8 {
unsafe { (*self.wasmtime_memory_definition()).base }
unsafe { (*self.wasmtime_export.definition).base }
}
/// Returns the byte length of this memory.
///
/// The returned value will be a multiple of the wasm page size, 64k.
pub fn data_size(&self) -> usize {
unsafe { (*self.wasmtime_memory_definition()).current_length }
unsafe { (*self.wasmtime_export.definition).current_length }
}
/// Returns the size, in pages, of this wasm memory.
@@ -625,39 +580,26 @@ impl Memory {
/// Returns an error if memory could not be grown, for example if it exceeds
/// the maximum limits of this memory.
pub fn grow(&self, delta: u32) -> Result<u32> {
match self.wasmtime_export {
wasmtime_runtime::Export::Memory { definition, .. } => {
let definition = unsafe { &(*definition) };
let index = self.wasmtime_handle.memory_index(definition);
self.wasmtime_handle
.clone()
.memory_grow(index, delta)
.ok_or_else(|| anyhow!("failed to grow memory"))
}
_ => panic!("memory definition not found"),
}
}
pub(crate) fn wasmtime_export(&self) -> &wasmtime_runtime::Export {
&self.wasmtime_export
let index = self
.wasmtime_handle
.memory_index(unsafe { &*self.wasmtime_export.definition });
self.wasmtime_handle
.clone()
.memory_grow(index, delta)
.ok_or_else(|| anyhow!("failed to grow memory"))
}
pub(crate) fn from_wasmtime_memory(
export: wasmtime_runtime::Export,
wasmtime_export: wasmtime_runtime::ExportMemory,
store: &Store,
instance_handle: wasmtime_runtime::InstanceHandle,
wasmtime_handle: wasmtime_runtime::InstanceHandle,
) -> Memory {
let memory = if let wasmtime_runtime::Export::Memory { ref memory, .. } = export {
memory
} else {
panic!("wasmtime export is not memory")
};
let ty = MemoryType::from_wasmtime_memory(&memory.memory);
let ty = MemoryType::from_wasmtime_memory(&wasmtime_export.memory.memory);
Memory {
store: store.clone(),
ty: ty,
wasmtime_handle: instance_handle,
wasmtime_export: export,
wasmtime_handle,
wasmtime_export,
}
}
}

View File

@@ -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);
}
}

View File

@@ -4,7 +4,7 @@ use crate::runtime::{Config, Store};
use crate::trap::Trap;
use anyhow::{bail, Error, Result};
use wasmtime_jit::{CompiledModule, Resolver};
use wasmtime_runtime::{Export, InstanceHandle, InstantiationError};
use wasmtime_runtime::{Export, InstanceHandle, InstantiationError, SignatureRegistry};
struct SimpleResolver<'a> {
imports: &'a [Extern],
@@ -22,6 +22,7 @@ fn instantiate(
config: &Config,
compiled_module: &CompiledModule,
imports: &[Extern],
sig_registry: &SignatureRegistry,
) -> Result<InstanceHandle, Error> {
let mut resolver = SimpleResolver { imports };
unsafe {
@@ -29,6 +30,7 @@ fn instantiate(
.instantiate(
config.validating_config.operator_config.enable_bulk_memory,
&mut resolver,
sig_registry,
)
.map_err(|e| -> Error {
match e {
@@ -122,26 +124,28 @@ impl Instance {
}
let config = store.engine().config();
let instance_handle = instantiate(config, module.compiled_module(), imports)?;
let instance_handle = instantiate(
config,
module.compiled_module(),
imports,
store.compiler().signatures(),
)?;
let exports = {
let mut exports = Vec::with_capacity(module.exports().len());
for export in module.exports() {
let name = export.name().to_string();
let export = instance_handle.lookup(&name).expect("export");
exports.push(Extern::from_wasmtime_export(
store,
instance_handle.clone(),
export,
));
}
exports.into_boxed_slice()
};
let mut exports = Vec::with_capacity(module.exports().len());
for export in module.exports() {
let name = export.name().to_string();
let export = instance_handle.lookup(&name).expect("export");
exports.push(Extern::from_wasmtime_export(
store,
instance_handle.clone(),
export,
));
}
module.register_frame_info();
Ok(Instance {
instance_handle,
module: module.clone(),
exports,
exports: exports.into_boxed_slice(),
})
}

View File

@@ -3,17 +3,20 @@
use crate::runtime::Store;
use anyhow::Result;
use std::any::Any;
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use wasmtime_environ::entity::PrimaryMap;
use wasmtime_environ::wasm::DefinedFuncIndex;
use wasmtime_environ::Module;
use wasmtime_runtime::{Imports, InstanceHandle, VMFunctionBody};
use wasmtime_runtime::{
Imports, InstanceHandle, VMFunctionBody, VMSharedSignatureIndex, VMTrampoline,
};
pub(crate) fn create_handle(
module: Module,
store: &Store,
finished_functions: PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
trampolines: HashMap<VMSharedSignatureIndex, VMTrampoline>,
state: Box<dyn Any>,
) -> Result<InstanceHandle> {
let imports = Imports::new(
@@ -38,6 +41,7 @@ pub(crate) fn create_handle(
Arc::new(module),
store.compiler().trap_registry().register_traps(Vec::new()),
finished_functions.into_boxed_slice(),
trampolines,
imports,
&data_initializers,
signatures.into_boxed_slice(),

View File

@@ -5,12 +5,14 @@ use crate::{Callable, FuncType, Store, Trap, Val};
use anyhow::{bail, Result};
use std::any::Any;
use std::cmp;
use std::collections::HashMap;
use std::mem;
use std::panic::{self, AssertUnwindSafe};
use std::rc::Rc;
use wasmtime_environ::entity::{EntityRef, PrimaryMap};
use wasmtime_environ::ir::types;
use wasmtime_environ::isa::TargetIsa;
use wasmtime_environ::wasm::{DefinedFuncIndex, FuncIndex};
use wasmtime_environ::wasm::FuncIndex;
use wasmtime_environ::{
ir, settings, CompiledFunction, CompiledFunctionUnwindInfo, Export, Module,
};
@@ -21,7 +23,7 @@ use wasmtime_jit::trampoline::{
binemit, pretty_error, Context, FunctionBuilder, FunctionBuilderContext,
};
use wasmtime_jit::{native, CodeMemory};
use wasmtime_runtime::{InstanceHandle, VMContext, VMFunctionBody};
use wasmtime_runtime::{InstanceHandle, VMContext, VMFunctionBody, VMTrampoline};
struct TrampolineState {
func: Rc<dyn Callable + 'static>,
@@ -145,7 +147,7 @@ fn make_trampoline(
stub_sig.params.push(ir::AbiParam::new(pointer_type));
// Compute the size of the values vector. The vmctx and caller vmctx are passed separately.
let value_size = 16;
let value_size = mem::size_of::<u128>();
let values_vec_len = ((value_size as usize)
* cmp::max(signature.params.len() - 2, signature.returns.len()))
as u32;
@@ -264,10 +266,12 @@ pub fn create_handle_with_function(
let mut fn_builder_ctx = FunctionBuilderContext::new();
let mut module = Module::new();
let mut finished_functions: PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]> =
PrimaryMap::new();
let mut finished_functions = PrimaryMap::new();
let mut trampolines = HashMap::new();
let mut code_memory = CodeMemory::new();
// First up we manufacture a trampoline which has the ABI specified by `ft`
// and calls into `stub_fn`...
let sig_id = module.local.signatures.push(sig.clone());
let func_id = module.local.functions.push(sig_id);
module
@@ -280,16 +284,31 @@ pub fn create_handle_with_function(
func_id.index() as u32,
&sig,
);
code_memory.publish();
finished_functions.push(trampoline);
let trampoline_state = TrampolineState::new(func.clone(), code_memory);
// ... and then we also need a trampoline with the standard "trampoline ABI"
// which enters into the ABI specified by `ft`. Note that this is only used
// if `Func::call` is called on an object created by `Func::new`.
let trampoline = wasmtime_jit::make_trampoline(
&*isa,
&mut code_memory,
&mut fn_builder_ctx,
&sig,
mem::size_of::<u128>(),
)?;
let sig_id = store.compiler().signatures().register(&sig);
trampolines.insert(sig_id, trampoline);
// Next up we wrap everything up into an `InstanceHandle` by publishing our
// code memory (makes it executable) and ensuring all our various bits of
// state make it into the instance constructors.
code_memory.publish();
let trampoline_state = TrampolineState::new(func.clone(), code_memory);
create_handle(
module,
store,
finished_functions,
trampolines,
Box::new(trampoline_state),
)
}
@@ -297,6 +316,7 @@ pub fn create_handle_with_function(
pub unsafe fn create_handle_with_raw_function(
ft: &FuncType,
func: *mut [VMFunctionBody],
trampoline: VMTrampoline,
store: &Store,
state: Box<dyn Any>,
) -> Result<InstanceHandle> {
@@ -314,6 +334,7 @@ pub unsafe fn create_handle_with_raw_function(
let mut module = Module::new();
let mut finished_functions = PrimaryMap::new();
let mut trampolines = HashMap::new();
let sig_id = module.local.signatures.push(sig.clone());
let func_id = module.local.functions.push(sig_id);
@@ -321,6 +342,8 @@ pub unsafe fn create_handle_with_raw_function(
.exports
.insert("trampoline".to_string(), Export::Function(func_id));
finished_functions.push(func);
let sig_id = store.compiler().signatures().register(&sig);
trampolines.insert(sig_id, trampoline);
create_handle(module, store, finished_functions, state)
create_handle(module, store, finished_functions, trampolines, state)
}

View File

@@ -30,6 +30,12 @@ pub fn create_global(store: &Store, gt: &GlobalType, val: Val) -> Result<Instanc
"global".to_string(),
wasmtime_environ::Export::Global(global_id),
);
let handle = create_handle(module, store, PrimaryMap::new(), Box::new(()))?;
let handle = create_handle(
module,
store,
PrimaryMap::new(),
Default::default(),
Box::new(()),
)?;
Ok(handle)
}

View File

@@ -23,5 +23,11 @@ pub fn create_handle_with_memory(store: &Store, memory: &MemoryType) -> Result<I
wasmtime_environ::Export::Memory(memory_id),
);
create_handle(module, store, PrimaryMap::new(), Box::new(()))
create_handle(
module,
store,
PrimaryMap::new(),
Default::default(),
Box::new(()),
)
}

View File

@@ -14,16 +14,21 @@ use super::{Callable, FuncType, GlobalType, MemoryType, Store, TableType, Val};
use anyhow::Result;
use std::any::Any;
use std::rc::Rc;
use wasmtime_runtime::VMFunctionBody;
use wasmtime_runtime::{VMFunctionBody, VMTrampoline};
pub fn generate_func_export(
ft: &FuncType,
func: &Rc<dyn Callable + 'static>,
store: &Store,
) -> Result<(wasmtime_runtime::InstanceHandle, wasmtime_runtime::Export)> {
) -> Result<(
wasmtime_runtime::InstanceHandle,
wasmtime_runtime::ExportFunction,
)> {
let instance = create_handle_with_function(ft, func, store)?;
let export = instance.lookup("trampoline").expect("trampoline export");
Ok((instance, export))
match instance.lookup("trampoline").expect("trampoline export") {
wasmtime_runtime::Export::Function(f) => Ok((instance, f)),
_ => unreachable!(),
}
}
/// Note that this is `unsafe` since `func` must be a valid function pointer and
@@ -32,38 +37,59 @@ pub fn generate_func_export(
pub unsafe fn generate_raw_func_export(
ft: &FuncType,
func: *mut [VMFunctionBody],
trampoline: VMTrampoline,
store: &Store,
state: Box<dyn Any>,
) -> Result<(wasmtime_runtime::InstanceHandle, wasmtime_runtime::Export)> {
let instance = func::create_handle_with_raw_function(ft, func, store, state)?;
let export = instance.lookup("trampoline").expect("trampoline export");
Ok((instance, export))
) -> Result<(
wasmtime_runtime::InstanceHandle,
wasmtime_runtime::ExportFunction,
)> {
let instance = func::create_handle_with_raw_function(ft, func, trampoline, store, state)?;
match instance.lookup("trampoline").expect("trampoline export") {
wasmtime_runtime::Export::Function(f) => Ok((instance, f)),
_ => unreachable!(),
}
}
pub fn generate_global_export(
store: &Store,
gt: &GlobalType,
val: Val,
) -> Result<(wasmtime_runtime::InstanceHandle, wasmtime_runtime::Export)> {
) -> Result<(
wasmtime_runtime::InstanceHandle,
wasmtime_runtime::ExportGlobal,
)> {
let instance = create_global(store, gt, val)?;
let export = instance.lookup("global").expect("global export");
Ok((instance, export))
match instance.lookup("global").expect("global export") {
wasmtime_runtime::Export::Global(g) => Ok((instance, g)),
_ => unreachable!(),
}
}
pub fn generate_memory_export(
store: &Store,
m: &MemoryType,
) -> Result<(wasmtime_runtime::InstanceHandle, wasmtime_runtime::Export)> {
) -> Result<(
wasmtime_runtime::InstanceHandle,
wasmtime_runtime::ExportMemory,
)> {
let instance = create_handle_with_memory(store, m)?;
let export = instance.lookup("memory").expect("memory export");
Ok((instance, export))
match instance.lookup("memory").expect("memory export") {
wasmtime_runtime::Export::Memory(m) => Ok((instance, m)),
_ => unreachable!(),
}
}
pub fn generate_table_export(
store: &Store,
t: &TableType,
) -> Result<(wasmtime_runtime::InstanceHandle, wasmtime_runtime::Export)> {
) -> Result<(
wasmtime_runtime::InstanceHandle,
wasmtime_runtime::ExportTable,
)> {
let instance = create_handle_with_table(store, t)?;
let export = instance.lookup("table").expect("table export");
Ok((instance, export))
match instance.lookup("table").expect("table export") {
wasmtime_runtime::Export::Table(t) => Ok((instance, t)),
_ => unreachable!(),
}
}

View File

@@ -26,5 +26,11 @@ pub fn create_handle_with_table(store: &Store, table: &TableType) -> Result<Inst
wasmtime_environ::Export::Table(table_id),
);
create_handle(module, store, PrimaryMap::new(), Box::new(()))
create_handle(
module,
store,
PrimaryMap::new(),
Default::default(),
Box::new(()),
)
}

View File

@@ -201,19 +201,11 @@ pub(crate) fn into_checked_anyfunc(
vmctx: ptr::null_mut(),
},
Val::FuncRef(f) => {
let (vmctx, func_ptr, signature) = match f.wasmtime_export() {
wasmtime_runtime::Export::Function {
vmctx,
address,
signature,
} => (*vmctx, *address, signature),
_ => panic!("expected function export"),
};
let type_index = store.compiler().signatures().register(signature);
let f = f.wasmtime_function();
wasmtime_runtime::VMCallerCheckedAnyfunc {
func_ptr,
type_index,
vmctx,
func_ptr: f.address,
type_index: f.signature,
vmctx: f.vmctx,
}
}
_ => bail!("val is not funcref"),
@@ -227,15 +219,10 @@ pub(crate) fn from_checked_anyfunc(
if item.type_index == wasmtime_runtime::VMSharedSignatureIndex::default() {
Val::AnyRef(AnyRef::Null);
}
let signature = store
.compiler()
.signatures()
.lookup(item.type_index)
.expect("signature");
let instance_handle = unsafe { wasmtime_runtime::InstanceHandle::from_vmctx(item.vmctx) };
let export = wasmtime_runtime::Export::Function {
let export = wasmtime_runtime::ExportFunction {
address: item.func_ptr,
signature,
signature: item.type_index,
vmctx: item.vmctx,
};
let f = Func::from_wasmtime_function(export, store, instance_handle);

View File

@@ -289,3 +289,41 @@ fn get_from_module() -> anyhow::Result<()> {
assert!(f2.get1::<i32, f32>().is_err());
Ok(())
}
#[test]
fn call_wrapped_func() -> Result<()> {
let store = Store::default();
let f = Func::wrap4(&store, |a: i32, b: i64, c: f32, d: f64| {
assert_eq!(a, 1);
assert_eq!(b, 2);
assert_eq!(c, 3.0);
assert_eq!(d, 4.0);
});
f.call(&[Val::I32(1), Val::I64(2), 3.0f32.into(), 4.0f64.into()])?;
f.get4::<i32, i64, f32, f64, ()>()?(1, 2, 3.0, 4.0)?;
let f = Func::wrap0(&store, || 1i32);
let results = f.call(&[])?;
assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_i32(), 1);
assert_eq!(f.get0::<i32>()?()?, 1);
let f = Func::wrap0(&store, || 2i64);
let results = f.call(&[])?;
assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_i64(), 2);
assert_eq!(f.get0::<i64>()?()?, 2);
let f = Func::wrap0(&store, || 3.0f32);
let results = f.call(&[])?;
assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_f32(), 3.0);
assert_eq!(f.get0::<f32>()?()?, 3.0);
let f = Func::wrap0(&store, || 4.0f64);
let results = f.call(&[])?;
assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_f64(), 4.0);
assert_eq!(f.get0::<f64>()?()?, 4.0);
Ok(())
}