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:
Nick Fitzgerald
2020-06-18 11:04:40 -07:00
parent ddc2ce8080
commit 58bb5dd953
37 changed files with 603 additions and 305 deletions

View File

@@ -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 {

View File

@@ -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 {

View File

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

View File

@@ -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()

View File

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

View File

@@ -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()),

View File

@@ -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() {

View File

@@ -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 {

View File

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