wasmtime: Add support for func.ref and table.grow with funcrefs
`funcref`s are implemented as `NonNull<VMCallerCheckedAnyfunc>`. This should be more efficient than using a `VMExternRef` that points at a `VMCallerCheckedAnyfunc` because it gets rid of an indirection, dynamic allocation, and some reference counting. Note that the null function reference is *NOT* a null pointer; it is a `VMCallerCheckedAnyfunc` that has a null `func_ptr` member. Part of #929
This commit is contained in:
@@ -205,7 +205,7 @@ impl Global {
|
||||
|
||||
/// Returns the value type of this `global`.
|
||||
pub fn val_type(&self) -> ValType {
|
||||
ValType::from_wasmtime_type(self.wasmtime_export.global.ty)
|
||||
ValType::from_wasm_type(&self.wasmtime_export.global.wasm_ty)
|
||||
.expect("core wasm type should be supported")
|
||||
}
|
||||
|
||||
@@ -298,14 +298,10 @@ fn set_table_item(
|
||||
instance: &InstanceHandle,
|
||||
table_index: wasm::DefinedTableIndex,
|
||||
item_index: u32,
|
||||
item: wasmtime_runtime::VMCallerCheckedAnyfunc,
|
||||
item: *mut wasmtime_runtime::VMCallerCheckedAnyfunc,
|
||||
) -> Result<()> {
|
||||
instance
|
||||
.table_set(
|
||||
table_index,
|
||||
item_index,
|
||||
runtime::TableElement::FuncRef(item),
|
||||
)
|
||||
.table_set(table_index, item_index, item.into())
|
||||
.map_err(|()| anyhow!("table element index out of bounds"))
|
||||
}
|
||||
|
||||
@@ -329,7 +325,7 @@ impl Table {
|
||||
let definition = unsafe { &*wasmtime_export.definition };
|
||||
let index = instance.table_index(definition);
|
||||
for i in 0..definition.current_elements {
|
||||
set_table_item(&instance, index, i, item.clone())?;
|
||||
set_table_item(&instance, index, i, item)?;
|
||||
}
|
||||
|
||||
Ok(Table {
|
||||
@@ -356,7 +352,7 @@ impl Table {
|
||||
let item = self.instance.table_get(table_index, index)?;
|
||||
match item {
|
||||
runtime::TableElement::FuncRef(f) => {
|
||||
Some(from_checked_anyfunc(f, &self.instance.store))
|
||||
Some(unsafe { from_checked_anyfunc(f, &self.instance.store) })
|
||||
}
|
||||
runtime::TableElement::ExternRef(None) => Some(Val::ExternRef(None)),
|
||||
runtime::TableElement::ExternRef(Some(x)) => Some(Val::ExternRef(Some(ExternRef {
|
||||
@@ -398,8 +394,7 @@ impl Table {
|
||||
let orig_size = match self.ty().element() {
|
||||
ValType::FuncRef => {
|
||||
let init = into_checked_anyfunc(init, &self.instance.store)?;
|
||||
self.instance
|
||||
.defined_table_grow(index, delta, runtime::TableElement::FuncRef(init))
|
||||
self.instance.defined_table_grow(index, delta, init.into())
|
||||
}
|
||||
ValType::ExternRef => {
|
||||
let init = match init {
|
||||
|
||||
@@ -6,10 +6,11 @@ use std::cmp::max;
|
||||
use std::fmt;
|
||||
use std::mem;
|
||||
use std::panic::{self, AssertUnwindSafe};
|
||||
use std::ptr;
|
||||
use std::ptr::{self, NonNull};
|
||||
use std::rc::Weak;
|
||||
use wasmtime_runtime::{raise_user_trap, ExportFunction, VMTrampoline};
|
||||
use wasmtime_runtime::{Export, InstanceHandle, VMContext, VMFunctionBody};
|
||||
use wasmtime_runtime::{
|
||||
raise_user_trap, Export, InstanceHandle, VMContext, VMFunctionBody, VMTrampoline,
|
||||
};
|
||||
|
||||
/// A WebAssembly function which can be called.
|
||||
///
|
||||
@@ -140,8 +141,8 @@ use wasmtime_runtime::{Export, InstanceHandle, VMContext, VMFunctionBody};
|
||||
#[derive(Clone)]
|
||||
pub struct Func {
|
||||
instance: StoreInstanceHandle,
|
||||
export: ExportFunction,
|
||||
trampoline: VMTrampoline,
|
||||
export: wasmtime_runtime::ExportFunction,
|
||||
}
|
||||
|
||||
macro_rules! getters {
|
||||
@@ -175,10 +176,11 @@ macro_rules! getters {
|
||||
.context("Type mismatch in return type")?;
|
||||
ensure!(results.next().is_none(), "Type mismatch: too many return values (expected 1)");
|
||||
|
||||
// Pass the instance into the closure so that we keep it live for the lifetime
|
||||
// of the closure. Pass the export in so that we can call it.
|
||||
// Pass the instance into the closure so that we keep it live for
|
||||
// the lifetime of the closure. Pass the `anyfunc` in so that we can
|
||||
// call it.
|
||||
let instance = self.instance.clone();
|
||||
let export = self.export.clone();
|
||||
let anyfunc = self.export.anyfunc;
|
||||
|
||||
// ... and then once we've passed the typechecks we can hand out our
|
||||
// object since our `transmute` below should be safe!
|
||||
@@ -191,12 +193,12 @@ macro_rules! getters {
|
||||
*mut VMContext,
|
||||
$($args,)*
|
||||
) -> R,
|
||||
>(export.address);
|
||||
>(anyfunc.as_ref().func_ptr.as_ptr());
|
||||
let mut ret = None;
|
||||
$(let $args = $args.into_abi();)*
|
||||
|
||||
invoke_wasm_and_catch_traps(export.vmctx, &instance.store, || {
|
||||
ret = Some(fnptr(export.vmctx, ptr::null_mut(), $($args,)*));
|
||||
invoke_wasm_and_catch_traps(anyfunc.as_ref().vmctx, &instance.store, || {
|
||||
ret = Some(fnptr(anyfunc.as_ref().vmctx, ptr::null_mut(), $($args,)*));
|
||||
})?;
|
||||
|
||||
Ok(ret.unwrap())
|
||||
@@ -282,8 +284,8 @@ impl Func {
|
||||
crate::trampoline::generate_func_export(&ty, func, store).expect("generated func");
|
||||
Func {
|
||||
instance,
|
||||
export,
|
||||
trampoline,
|
||||
export,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,7 +490,10 @@ impl Func {
|
||||
pub fn ty(&self) -> FuncType {
|
||||
// Signatures should always be registered in the store's registry of
|
||||
// shared signatures, so we should be able to unwrap safely here.
|
||||
let sig = self.instance.store.lookup_signature(self.export.signature);
|
||||
let sig = self
|
||||
.instance
|
||||
.store
|
||||
.lookup_signature(unsafe { self.export.anyfunc.as_ref().type_index });
|
||||
|
||||
// This is only called with `Export::Function`, and since it's coming
|
||||
// from wasmtime_runtime itself we should support all the types coming
|
||||
@@ -498,13 +503,19 @@ impl Func {
|
||||
|
||||
/// Returns the number of parameters that this function takes.
|
||||
pub fn param_arity(&self) -> usize {
|
||||
let sig = self.instance.store.lookup_signature(self.export.signature);
|
||||
let sig = self
|
||||
.instance
|
||||
.store
|
||||
.lookup_signature(unsafe { self.export.anyfunc.as_ref().type_index });
|
||||
sig.params.len()
|
||||
}
|
||||
|
||||
/// Returns the number of results this function produces.
|
||||
pub fn result_arity(&self) -> usize {
|
||||
let sig = self.instance.store.lookup_signature(self.export.signature);
|
||||
let sig = self
|
||||
.instance
|
||||
.store
|
||||
.lookup_signature(unsafe { self.export.anyfunc.as_ref().type_index });
|
||||
sig.returns.len()
|
||||
}
|
||||
|
||||
@@ -553,14 +564,17 @@ impl Func {
|
||||
}
|
||||
|
||||
// Call the trampoline.
|
||||
invoke_wasm_and_catch_traps(self.export.vmctx, &self.instance.store, || unsafe {
|
||||
(self.trampoline)(
|
||||
self.export.vmctx,
|
||||
ptr::null_mut(),
|
||||
self.export.address,
|
||||
values_vec.as_mut_ptr(),
|
||||
)
|
||||
})?;
|
||||
unsafe {
|
||||
let anyfunc = self.export.anyfunc.as_ref();
|
||||
invoke_wasm_and_catch_traps(anyfunc.vmctx, &self.instance.store, || {
|
||||
(self.trampoline)(
|
||||
anyfunc.vmctx,
|
||||
ptr::null_mut(),
|
||||
anyfunc.func_ptr.as_ptr(),
|
||||
values_vec.as_mut_ptr(),
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
// Load the return values out of `values_vec`.
|
||||
let mut results = Vec::with_capacity(my_ty.results().len());
|
||||
@@ -578,6 +592,12 @@ impl Func {
|
||||
&self.export
|
||||
}
|
||||
|
||||
pub(crate) fn caller_checked_anyfunc(
|
||||
&self,
|
||||
) -> NonNull<wasmtime_runtime::VMCallerCheckedAnyfunc> {
|
||||
self.export.anyfunc
|
||||
}
|
||||
|
||||
pub(crate) fn from_wasmtime_function(
|
||||
export: wasmtime_runtime::ExportFunction,
|
||||
instance: StoreInstanceHandle,
|
||||
@@ -586,7 +606,7 @@ impl Func {
|
||||
// on that module as well, so unwrap the result here since otherwise
|
||||
// it's a bug in wasmtime.
|
||||
let trampoline = instance
|
||||
.trampoline(export.signature)
|
||||
.trampoline(unsafe { export.anyfunc.as_ref().type_index })
|
||||
.expect("failed to retrieve trampoline from module");
|
||||
|
||||
Func {
|
||||
|
||||
@@ -5,7 +5,9 @@ use std::any::Any;
|
||||
use std::mem;
|
||||
use wasmtime_environ::EntityIndex;
|
||||
use wasmtime_jit::{CompiledModule, Resolver};
|
||||
use wasmtime_runtime::{InstantiationError, VMContext, VMFunctionBody};
|
||||
use wasmtime_runtime::{
|
||||
InstantiationError, StackMapRegistry, VMContext, VMExternRefActivationsTable, VMFunctionBody,
|
||||
};
|
||||
|
||||
struct SimpleResolver<'a> {
|
||||
imports: &'a [Extern],
|
||||
@@ -50,8 +52,8 @@ fn instantiate(
|
||||
config.memory_creator.as_ref().map(|a| a as _),
|
||||
store.interrupts().clone(),
|
||||
host,
|
||||
&*store.externref_activations_table() as *const _ as *mut _,
|
||||
&*store.stack_map_registry() as *const _ as *mut _,
|
||||
&**store.externref_activations_table() as *const VMExternRefActivationsTable as *mut _,
|
||||
&**store.stack_map_registry() as *const StackMapRegistry as *mut _,
|
||||
)?;
|
||||
|
||||
// After we've created the `InstanceHandle` we still need to run
|
||||
@@ -95,7 +97,9 @@ fn instantiate(
|
||||
mem::transmute::<
|
||||
*const VMFunctionBody,
|
||||
unsafe extern "C" fn(*mut VMContext, *mut VMContext),
|
||||
>(f.address)(f.vmctx, vmctx_ptr)
|
||||
>(f.anyfunc.as_ref().func_ptr.as_ptr())(
|
||||
f.anyfunc.as_ref().vmctx, vmctx_ptr
|
||||
)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,7 +427,7 @@ impl Linker {
|
||||
|
||||
Ok(())
|
||||
});
|
||||
self.insert(module_name, export.name(), Extern::Func(func))?;
|
||||
self.insert(module_name, export.name(), func.into())?;
|
||||
} else if export.name() == "memory" && export.ty().memory().is_some() {
|
||||
// Allow an exported "memory" memory for now.
|
||||
} else if export.name() == "__indirect_function_table" && export.ty().table().is_some()
|
||||
|
||||
@@ -10,7 +10,8 @@ use wasmtime_environ::entity::PrimaryMap;
|
||||
use wasmtime_environ::wasm::DefinedFuncIndex;
|
||||
use wasmtime_environ::Module;
|
||||
use wasmtime_runtime::{
|
||||
Imports, InstanceHandle, VMFunctionBody, VMSharedSignatureIndex, VMTrampoline,
|
||||
Imports, InstanceHandle, StackMapRegistry, VMExternRefActivationsTable, VMFunctionBody,
|
||||
VMSharedSignatureIndex, VMTrampoline,
|
||||
};
|
||||
|
||||
pub(crate) fn create_handle(
|
||||
@@ -46,8 +47,8 @@ pub(crate) fn create_handle(
|
||||
signatures.into_boxed_slice(),
|
||||
state,
|
||||
store.interrupts().clone(),
|
||||
&*store.externref_activations_table() as *const _ as *mut _,
|
||||
&*store.stack_map_registry() as *const _ as *mut _,
|
||||
&**store.externref_activations_table() as *const VMExternRefActivationsTable as *mut _,
|
||||
&**store.stack_map_registry() as *const StackMapRegistry as *mut _,
|
||||
)?;
|
||||
Ok(store.add_instance(handle))
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use super::create_handle::create_handle;
|
||||
use crate::trampoline::StoreInstanceHandle;
|
||||
use crate::Store;
|
||||
use crate::{GlobalType, Mutability, Val};
|
||||
use crate::{GlobalType, Mutability, Store, Val};
|
||||
use anyhow::{bail, Result};
|
||||
use wasmtime_environ::entity::PrimaryMap;
|
||||
use wasmtime_environ::{wasm, EntityIndex, Module};
|
||||
|
||||
pub fn create_global(store: &Store, gt: &GlobalType, val: Val) -> Result<StoreInstanceHandle> {
|
||||
let global = wasm::Global {
|
||||
wasm_ty: gt.content().to_wasm_type(),
|
||||
ty: match gt.content().get_wasmtime_type() {
|
||||
Some(t) => t,
|
||||
None => bail!("cannot support {:?} as a wasm global type", gt.content()),
|
||||
|
||||
@@ -10,6 +10,7 @@ pub fn create_handle_with_table(store: &Store, table: &TableType) -> Result<Stor
|
||||
let mut module = Module::new();
|
||||
|
||||
let table = wasm::Table {
|
||||
wasm_ty: table.element().to_wasm_type(),
|
||||
minimum: table.limits().min(),
|
||||
maximum: table.limits().max(),
|
||||
ty: match table.element() {
|
||||
|
||||
@@ -106,25 +106,7 @@ impl ValType {
|
||||
ValType::F32 => Some(ir::types::F32),
|
||||
ValType::F64 => Some(ir::types::F64),
|
||||
ValType::V128 => Some(ir::types::I8X16),
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
ValType::ExternRef => Some(ir::types::R64),
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
ValType::ExternRef => Some(ir::types::R32),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_wasmtime_type(ty: ir::Type) -> Option<ValType> {
|
||||
match ty {
|
||||
ir::types::I32 => Some(ValType::I32),
|
||||
ir::types::I64 => Some(ValType::I64),
|
||||
ir::types::F32 => Some(ValType::F32),
|
||||
ir::types::F64 => Some(ValType::F64),
|
||||
ir::types::I8X16 => Some(ValType::V128),
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
ir::types::R64 => Some(ValType::ExternRef),
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
ir::types::R32 => Some(ValType::ExternRef),
|
||||
ValType::ExternRef => Some(wasmtime_runtime::ref_type()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -353,7 +335,7 @@ impl GlobalType {
|
||||
/// Returns `None` if the wasmtime global has a type that we can't
|
||||
/// represent, but that should only very rarely happen and indicate a bug.
|
||||
pub(crate) fn from_wasmtime_global(global: &wasm::Global) -> Option<GlobalType> {
|
||||
let ty = ValType::from_wasmtime_type(global.ty)?;
|
||||
let ty = ValType::from_wasm_type(&global.wasm_ty)?;
|
||||
let mutability = if global.mutability {
|
||||
Mutability::Var
|
||||
} else {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::r#ref::ExternRef;
|
||||
use crate::{Func, Store, ValType};
|
||||
use anyhow::{bail, Result};
|
||||
use std::ptr;
|
||||
use wasmtime_runtime::VMExternRef;
|
||||
use std::ptr::{self, NonNull};
|
||||
use wasmtime_runtime::{self as runtime, VMExternRef};
|
||||
|
||||
/// Possible runtime values that a WebAssembly module can either consume or
|
||||
/// produce.
|
||||
@@ -26,11 +26,18 @@ pub enum Val {
|
||||
/// `f64::from_bits` to create an `f64` value.
|
||||
F64(u64),
|
||||
|
||||
/// An `externref` value which can hold opaque data to the wasm instance itself.
|
||||
/// An `externref` value which can hold opaque data to the Wasm instance
|
||||
/// itself.
|
||||
///
|
||||
/// `ExternRef(None)` is the null external reference, created by `ref.null
|
||||
/// extern` in Wasm.
|
||||
ExternRef(Option<ExternRef>),
|
||||
|
||||
/// A first-class reference to a WebAssembly function.
|
||||
FuncRef(Func),
|
||||
///
|
||||
/// `FuncRef(None)` is the null function reference, created by `ref.null
|
||||
/// func` in Wasm.
|
||||
FuncRef(Option<Func>),
|
||||
|
||||
/// A 128-bit number
|
||||
V128(u128),
|
||||
@@ -94,7 +101,14 @@ impl Val {
|
||||
.insert_with_gc(x.inner, store.stack_map_registry());
|
||||
ptr::write(p as *mut *mut u8, externref_ptr)
|
||||
}
|
||||
_ => unimplemented!("Val::write_value_to"),
|
||||
Val::FuncRef(f) => ptr::write(
|
||||
p as *mut *mut runtime::VMCallerCheckedAnyfunc,
|
||||
if let Some(f) = f {
|
||||
f.caller_checked_anyfunc().as_ptr()
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +130,10 @@ impl Val {
|
||||
}))
|
||||
}
|
||||
}
|
||||
_ => unimplemented!("Val::read_value_from: {:?}", ty),
|
||||
ValType::FuncRef => {
|
||||
let func = ptr::read(p as *const *mut runtime::VMCallerCheckedAnyfunc);
|
||||
from_checked_anyfunc(func, store)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +143,7 @@ impl Val {
|
||||
(I64(i64) i64 unwrap_i64 *e)
|
||||
(F32(f32) f32 unwrap_f32 f32::from_bits(*e))
|
||||
(F64(f64) f64 unwrap_f64 f64::from_bits(*e))
|
||||
(FuncRef(&Func) funcref unwrap_funcref e)
|
||||
(FuncRef(Option<&Func>) funcref unwrap_funcref e.as_ref())
|
||||
(V128(u128) v128 unwrap_v128 *e)
|
||||
}
|
||||
|
||||
@@ -160,7 +177,8 @@ impl Val {
|
||||
|
||||
pub(crate) fn comes_from_same_store(&self, store: &Store) -> bool {
|
||||
match self {
|
||||
Val::FuncRef(f) => Store::same(store, f.store()),
|
||||
Val::FuncRef(Some(f)) => Store::same(store, f.store()),
|
||||
Val::FuncRef(None) => true,
|
||||
|
||||
// TODO: need to implement this once we actually finalize what
|
||||
// `externref` will look like and it's actually implemented to pass it
|
||||
@@ -211,51 +229,47 @@ impl From<Option<ExternRef>> for Val {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<Func>> for Val {
|
||||
fn from(val: Option<Func>) -> Val {
|
||||
Val::FuncRef(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Func> for Val {
|
||||
fn from(val: Func) -> Val {
|
||||
Val::FuncRef(val)
|
||||
Val::FuncRef(Some(val))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn into_checked_anyfunc(
|
||||
val: Val,
|
||||
store: &Store,
|
||||
) -> Result<wasmtime_runtime::VMCallerCheckedAnyfunc> {
|
||||
) -> Result<*mut wasmtime_runtime::VMCallerCheckedAnyfunc> {
|
||||
if !val.comes_from_same_store(store) {
|
||||
bail!("cross-`Store` values are not supported");
|
||||
}
|
||||
Ok(match val {
|
||||
Val::ExternRef(None) => wasmtime_runtime::VMCallerCheckedAnyfunc {
|
||||
func_ptr: ptr::null(),
|
||||
type_index: wasmtime_runtime::VMSharedSignatureIndex::default(),
|
||||
vmctx: ptr::null_mut(),
|
||||
},
|
||||
Val::FuncRef(f) => {
|
||||
let f = f.wasmtime_function();
|
||||
wasmtime_runtime::VMCallerCheckedAnyfunc {
|
||||
func_ptr: f.address,
|
||||
type_index: f.signature,
|
||||
vmctx: f.vmctx,
|
||||
}
|
||||
}
|
||||
Val::FuncRef(None) => ptr::null_mut(),
|
||||
Val::FuncRef(Some(f)) => f.caller_checked_anyfunc().as_ptr(),
|
||||
_ => bail!("val is not funcref"),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn from_checked_anyfunc(
|
||||
item: wasmtime_runtime::VMCallerCheckedAnyfunc,
|
||||
pub(crate) unsafe fn from_checked_anyfunc(
|
||||
anyfunc: *mut wasmtime_runtime::VMCallerCheckedAnyfunc,
|
||||
store: &Store,
|
||||
) -> Val {
|
||||
if item.type_index == wasmtime_runtime::VMSharedSignatureIndex::default() {
|
||||
return Val::ExternRef(None);
|
||||
}
|
||||
let instance_handle = unsafe { wasmtime_runtime::InstanceHandle::from_vmctx(item.vmctx) };
|
||||
let export = wasmtime_runtime::ExportFunction {
|
||||
address: item.func_ptr,
|
||||
signature: item.type_index,
|
||||
vmctx: item.vmctx,
|
||||
let anyfunc = match NonNull::new(anyfunc) {
|
||||
None => return Val::FuncRef(None),
|
||||
Some(f) => f,
|
||||
};
|
||||
|
||||
debug_assert!(
|
||||
anyfunc.as_ref().type_index != wasmtime_runtime::VMSharedSignatureIndex::default()
|
||||
);
|
||||
let instance_handle = wasmtime_runtime::InstanceHandle::from_vmctx(anyfunc.as_ref().vmctx);
|
||||
let export = wasmtime_runtime::ExportFunction { anyfunc };
|
||||
let instance = store.existing_instance_handle(instance_handle);
|
||||
let f = Func::from_wasmtime_function(export, instance);
|
||||
Val::FuncRef(f)
|
||||
Val::FuncRef(Some(f))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user