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

@@ -215,7 +215,9 @@ fn ignore(testsuite: &str, testname: &str, strategy: &str) -> bool {
("reference_types", "table_copy_on_imported_tables") ("reference_types", "table_copy_on_imported_tables")
| ("reference_types", "externref_id_function") | ("reference_types", "externref_id_function")
| ("reference_types", "table_size") => { | ("reference_types", "table_size")
| ("reference_types", "simple_ref_is_null")
| ("reference_types", "table_grow_with_funcref") => {
// TODO(#1886): Ignore if this isn't x64, because Cranelift only // TODO(#1886): Ignore if this isn't x64, because Cranelift only
// supports reference types on x64. // supports reference types on x64.
return env::var("CARGO_CFG_TARGET_ARCH").unwrap() != "x86_64"; return env::var("CARGO_CFG_TARGET_ARCH").unwrap() != "x86_64";

View File

@@ -97,6 +97,7 @@ pub fn parse_import_section<'data>(
ImportSectionEntryType::Global(ref ty) => { ImportSectionEntryType::Global(ref ty) => {
environ.declare_global_import( environ.declare_global_import(
Global { Global {
wasm_ty: ty.content_type,
ty: type_to_type(ty.content_type, environ).unwrap(), ty: type_to_type(ty.content_type, environ).unwrap(),
mutability: ty.mutable, mutability: ty.mutable,
initializer: GlobalInit::Import, initializer: GlobalInit::Import,
@@ -229,6 +230,7 @@ pub fn parse_global_section(
} }
}; };
let global = Global { let global = Global {
wasm_ty: content_type,
ty: type_to_type(content_type, environ).unwrap(), ty: type_to_type(content_type, environ).unwrap(),
mutability: mutable, mutability: mutable,
initializer, initializer,

View File

@@ -67,10 +67,18 @@ entity_impl!(DataIndex);
pub struct ElemIndex(u32); pub struct ElemIndex(u32);
entity_impl!(ElemIndex); entity_impl!(ElemIndex);
/// WebAssembly global. /// A WebAssembly global.
///
/// Note that we record both the original Wasm type and the Cranelift IR type
/// used to represent it. This is because multiple different kinds of Wasm types
/// might be represented with the same Cranelift IR type. For example, both a
/// Wasm `i64` and a `funcref` might be represented with a Cranelift `i64` on
/// 64-bit architectures, and when GC is not required for func refs.
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
pub struct Global { pub struct Global {
/// The type of the value stored in the global. /// The Wasm type of the value stored in the global.
pub wasm_ty: crate::WasmType,
/// The Cranelift IR type of the value stored in the global.
pub ty: ir::Type, pub ty: ir::Type,
/// A flag indicating whether the value may change at runtime. /// A flag indicating whether the value may change at runtime.
pub mutability: bool, pub mutability: bool,

View File

@@ -42,7 +42,7 @@ pub extern "C" fn wasm_table_new(
) -> Option<Box<wasm_table_t>> { ) -> Option<Box<wasm_table_t>> {
let init: Val = match init { let init: Val = match init {
Some(init) => init.r.into(), Some(init) => init.r.into(),
None => Val::ExternRef(None), None => Val::FuncRef(None),
}; };
let table = Table::new(&store.store, tt.ty().ty.clone(), init).ok()?; let table = Table::new(&store.store, tt.ty().ty.clone(), init).ok()?;
Some(Box::new(wasm_table_t { Some(Box::new(wasm_table_t {
@@ -60,8 +60,8 @@ pub extern "C" fn wasmtime_funcref_table_new(
out: &mut *mut wasm_table_t, out: &mut *mut wasm_table_t,
) -> Option<Box<wasmtime_error_t>> { ) -> Option<Box<wasmtime_error_t>> {
let init: Val = match init { let init: Val = match init {
Some(val) => Val::FuncRef(val.func().borrow().clone()), Some(val) => Val::FuncRef(Some(val.func().borrow().clone())),
None => Val::ExternRef(None), None => Val::FuncRef(None),
}; };
handle_result( handle_result(
Table::new(&store.store, tt.ty().ty.clone(), init), Table::new(&store.store, tt.ty().ty.clone(), init),
@@ -85,7 +85,7 @@ pub extern "C" fn wasm_table_type(t: &wasm_table_t) -> Box<wasm_tabletype_t> {
pub extern "C" fn wasm_table_get(t: &wasm_table_t, index: wasm_table_size_t) -> *mut wasm_ref_t { pub extern "C" fn wasm_table_get(t: &wasm_table_t, index: wasm_table_size_t) -> *mut wasm_ref_t {
match t.table().borrow().get(index) { match t.table().borrow().get(index) {
Some(val) => into_funcref(val), Some(val) => into_funcref(val),
None => into_funcref(Val::ExternRef(None)), None => into_funcref(Val::FuncRef(None)),
} }
} }
@@ -99,14 +99,14 @@ pub extern "C" fn wasmtime_funcref_table_get(
Some(val) => { Some(val) => {
*ptr = match val { *ptr = match val {
// TODO: what do do about creating new `HostRef` handles here? // TODO: what do do about creating new `HostRef` handles here?
Val::FuncRef(f) => { Val::FuncRef(None) => ptr::null_mut(),
Val::FuncRef(Some(f)) => {
let store = match t.table().as_ref().store() { let store = match t.table().as_ref().store() {
None => return false, None => return false,
Some(store) => store, Some(store) => store,
}; };
Box::into_raw(Box::new(HostRef::new(&store, f).into())) Box::into_raw(Box::new(HostRef::new(&store, f).into()))
} }
Val::ExternRef(None) => ptr::null_mut(),
_ => return false, _ => return false,
}; };
} }
@@ -133,14 +133,14 @@ pub extern "C" fn wasmtime_funcref_table_set(
val: Option<&wasm_func_t>, val: Option<&wasm_func_t>,
) -> Option<Box<wasmtime_error_t>> { ) -> Option<Box<wasmtime_error_t>> {
let val = match val { let val = match val {
Some(val) => Val::FuncRef(val.func().borrow().clone()), Some(val) => Val::FuncRef(Some(val.func().borrow().clone())),
None => Val::ExternRef(None), None => Val::FuncRef(None),
}; };
handle_result(t.table().borrow().set(index, val), |()| {}) handle_result(t.table().borrow().set(index, val), |()| {})
} }
fn into_funcref(val: Val) -> *mut wasm_ref_t { fn into_funcref(val: Val) -> *mut wasm_ref_t {
if let Val::ExternRef(None) = val { if let Val::FuncRef(None) = val {
return ptr::null_mut(); return ptr::null_mut();
} }
let externref = match val.externref() { let externref = match val.externref() {
@@ -155,7 +155,7 @@ unsafe fn from_funcref(r: *mut wasm_ref_t) -> Val {
if !r.is_null() { if !r.is_null() {
Box::from_raw(r).r.into() Box::from_raw(r).r.into()
} else { } else {
Val::ExternRef(None) Val::FuncRef(None)
} }
} }
@@ -182,8 +182,8 @@ pub extern "C" fn wasmtime_funcref_table_grow(
prev_size: Option<&mut wasm_table_size_t>, prev_size: Option<&mut wasm_table_size_t>,
) -> Option<Box<wasmtime_error_t>> { ) -> Option<Box<wasmtime_error_t>> {
let val = match init { let val = match init {
Some(val) => Val::FuncRef(val.func().borrow().clone()), Some(val) => Val::FuncRef(Some(val.func().borrow().clone())),
None => Val::ExternRef(None), None => Val::FuncRef(None),
}; };
handle_result(t.table().borrow().grow(delta, val), |prev| { handle_result(t.table().borrow().grow(delta, val), |prev| {
if let Some(ptr) = prev_size { if let Some(ptr) = prev_size {

View File

@@ -10,8 +10,8 @@ use cranelift_codegen::ir::{AbiParam, ArgumentPurpose, Function, InstBuilder, Si
use cranelift_codegen::isa::{self, TargetFrontendConfig}; use cranelift_codegen::isa::{self, TargetFrontendConfig};
use cranelift_entity::EntityRef; use cranelift_entity::EntityRef;
use cranelift_wasm::{ use cranelift_wasm::{
self, FuncIndex, GlobalIndex, GlobalVariable, MemoryIndex, SignatureIndex, TableElementType, self, FuncIndex, GlobalIndex, GlobalVariable, MemoryIndex, SignatureIndex, TableIndex,
TableIndex, TargetEnvironment, WasmError, WasmResult, TargetEnvironment, WasmError, WasmResult, WasmType,
}; };
#[cfg(feature = "lightbeam")] #[cfg(feature = "lightbeam")]
use cranelift_wasm::{DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex}; use cranelift_wasm::{DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex};
@@ -68,6 +68,10 @@ macro_rules! declare_builtin_functions {
AbiParam::new(self.reference_type) AbiParam::new(self.reference_type)
} }
fn pointer(&self) -> AbiParam {
AbiParam::new(self.pointer_type)
}
fn i32(&self) -> AbiParam { fn i32(&self) -> AbiParam {
AbiParam::new(I32) AbiParam::new(I32)
} }
@@ -161,8 +165,10 @@ declare_builtin_functions! {
memory_init(vmctx, i32, i32, i32, i32, i32) -> (); memory_init(vmctx, i32, i32, i32, i32, i32) -> ();
/// Returns an index for wasm's `data.drop` instruction. /// Returns an index for wasm's `data.drop` instruction.
data_drop(vmctx, i32) -> (); data_drop(vmctx, i32) -> ();
/// Returns an index for Wasm's `table.grow` instruction for `funcref`s.
table_grow_funcref(vmctx, i32, i32, pointer) -> (i32);
/// Returns an index for Wasm's `table.grow` instruction for `externref`s. /// Returns an index for Wasm's `table.grow` instruction for `externref`s.
table_grow_extern_ref(vmctx, i32, i32, reference) -> (i32); table_grow_externref(vmctx, i32, i32, reference) -> (i32);
} }
impl BuiltinFunctionIndex { impl BuiltinFunctionIndex {
@@ -280,26 +286,6 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
} }
} }
fn get_table_grow_func(
&mut self,
func: &mut Function,
table_index: TableIndex,
) -> WasmResult<(ir::SigRef, BuiltinFunctionIndex, usize)> {
match self.module.table_plans[table_index].table.ty {
TableElementType::Func => Err(WasmError::Unsupported(
"the `table.grow` instruction is not supported with `funcref` yet".into(),
)),
TableElementType::Val(ty) => {
assert_eq!(ty, self.reference_type());
Ok((
self.builtin_function_signatures.table_grow_extern_ref(func),
BuiltinFunctionIndex::table_grow_extern_ref(),
table_index.as_u32() as usize,
))
}
}
}
fn get_table_copy_func( fn get_table_copy_func(
&mut self, &mut self,
func: &mut Function, func: &mut Function,
@@ -552,6 +538,10 @@ impl<'module_environment> TargetEnvironment for FuncEnvironment<'module_environm
fn target_config(&self) -> TargetFrontendConfig { fn target_config(&self) -> TargetFrontendConfig {
self.target_config self.target_config
} }
fn reference_type(&self, ty: WasmType) -> ir::Type {
crate::reference_type(ty, self.pointer_type())
}
} }
impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'module_environment> { impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'module_environment> {
@@ -604,9 +594,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
}); });
let element_size = match self.module.table_plans[index].style { let element_size = match self.module.table_plans[index].style {
TableStyle::CallerChecksSignature => { TableStyle::CallerChecksSignature => u64::from(self.pointer_type().bytes()),
u64::from(self.offsets.size_of_vmcaller_checked_anyfunc())
}
}; };
Ok(func.create_table(ir::TableData { Ok(func.create_table(ir::TableData {
@@ -626,24 +614,34 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
delta: ir::Value, delta: ir::Value,
init_value: ir::Value, init_value: ir::Value,
) -> WasmResult<ir::Value> { ) -> WasmResult<ir::Value> {
let (func_sig, func_idx, table_index_arg) = let (func_idx, func_sig) =
self.get_table_grow_func(&mut pos.func, table_index)?; match self.module.table_plans[table_index].table.wasm_ty {
WasmType::FuncRef => (
let table_index_arg = pos.ins().iconst(I32, table_index_arg as i64); BuiltinFunctionIndex::table_grow_funcref(),
self.builtin_function_signatures
.table_grow_funcref(&mut pos.func),
),
WasmType::ExternRef => (
BuiltinFunctionIndex::table_grow_externref(),
self.builtin_function_signatures
.table_grow_externref(&mut pos.func),
),
_ => return Err(WasmError::Unsupported(
"`table.grow` with a table element type that is not `funcref` or `externref`"
.into(),
)),
};
let (vmctx, func_addr) = self.translate_load_builtin_function_address(&mut pos, func_idx); let (vmctx, func_addr) = self.translate_load_builtin_function_address(&mut pos, func_idx);
let table_index_arg = pos.ins().iconst(I32, table_index.as_u32() as i64);
let call_inst = pos.ins().call_indirect( let call_inst = pos.ins().call_indirect(
func_sig, func_sig,
func_addr, func_addr,
&[vmctx, table_index_arg, delta, init_value], &[vmctx, table_index_arg, delta, init_value],
); );
Ok(pos
.func Ok(pos.func.dfg.first_result(call_inst))
.dfg
.inst_results(call_inst)
.first()
.copied()
.unwrap())
} }
fn translate_table_get( fn translate_table_get(
@@ -684,14 +682,50 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
)) ))
} }
fn translate_ref_null(
&mut self,
mut pos: cranelift_codegen::cursor::FuncCursor,
ty: WasmType,
) -> WasmResult<ir::Value> {
Ok(match ty {
WasmType::FuncRef => pos.ins().iconst(self.pointer_type(), 0),
WasmType::ExternRef => pos.ins().null(self.reference_type(ty)),
_ => {
return Err(WasmError::Unsupported(
"`ref.null T` that is not a `funcref` or an `externref`".into(),
));
}
})
}
fn translate_ref_is_null(
&mut self,
mut pos: cranelift_codegen::cursor::FuncCursor,
value: ir::Value,
) -> WasmResult<ir::Value> {
let bool_is_null = match pos.func.dfg.value_type(value) {
// `externref`
ty if ty.is_ref() => pos.ins().is_null(value),
// `funcref`
ty if ty == self.pointer_type() => {
pos.ins()
.icmp_imm(cranelift_codegen::ir::condcodes::IntCC::Equal, value, 0)
}
_ => unreachable!(),
};
Ok(pos.ins().bint(ir::types::I32, bool_is_null))
}
fn translate_ref_func( fn translate_ref_func(
&mut self, &mut self,
_: cranelift_codegen::cursor::FuncCursor<'_>, mut pos: cranelift_codegen::cursor::FuncCursor<'_>,
_: FuncIndex, func_index: FuncIndex,
) -> WasmResult<ir::Value> { ) -> WasmResult<ir::Value> {
Err(WasmError::Unsupported( let vmctx = self.vmctx(&mut pos.func);
"the `ref.func` instruction is not supported yet".into(), let vmctx = pos.ins().global_value(self.pointer_type(), vmctx);
)) let offset = self.offsets.vmctx_anyfunc(func_index);
Ok(pos.ins().iadd_imm(vmctx, i64::from(offset)))
} }
fn translate_custom_global_get( fn translate_custom_global_get(
@@ -861,18 +895,25 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
let table_entry_addr = pos.ins().table_addr(pointer_type, table, callee, 0); let table_entry_addr = pos.ins().table_addr(pointer_type, table, callee, 0);
// Dereference table_entry_addr to get the function address. // Dereference the table entry to get the pointer to the
// `VMCallerCheckedAnyfunc`.
let anyfunc_ptr =
pos.ins()
.load(pointer_type, ir::MemFlags::trusted(), table_entry_addr, 0);
// Check for whether the table element is null, and trap if so.
pos.ins()
.trapz(anyfunc_ptr, ir::TrapCode::IndirectCallToNull);
// Dereference anyfunc pointer to get the function address.
let mem_flags = ir::MemFlags::trusted(); let mem_flags = ir::MemFlags::trusted();
let func_addr = pos.ins().load( let func_addr = pos.ins().load(
pointer_type, pointer_type,
mem_flags, mem_flags,
table_entry_addr, anyfunc_ptr,
i32::from(self.offsets.vmcaller_checked_anyfunc_func_ptr()), i32::from(self.offsets.vmcaller_checked_anyfunc_func_ptr()),
); );
// Check whether `func_addr` is null.
pos.ins().trapz(func_addr, ir::TrapCode::IndirectCallToNull);
// If necessary, check the signature. // If necessary, check the signature.
match self.module.table_plans[table_index].style { match self.module.table_plans[table_index].style {
TableStyle::CallerChecksSignature => { TableStyle::CallerChecksSignature => {
@@ -893,7 +934,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
let callee_sig_id = pos.ins().load( let callee_sig_id = pos.ins().load(
sig_id_type, sig_id_type,
mem_flags, mem_flags,
table_entry_addr, anyfunc_ptr,
i32::from(self.offsets.vmcaller_checked_anyfunc_type_index()), i32::from(self.offsets.vmcaller_checked_anyfunc_type_index()),
); );
@@ -910,7 +951,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
let vmctx = pos.ins().load( let vmctx = pos.ins().load(
pointer_type, pointer_type,
mem_flags, mem_flags,
table_entry_addr, anyfunc_ptr,
i32::from(self.offsets.vmcaller_checked_anyfunc_vmctx()), i32::from(self.offsets.vmcaller_checked_anyfunc_vmctx()),
); );
real_call_args.push(vmctx); real_call_args.push(vmctx);

View File

@@ -72,3 +72,18 @@ pub const WASM_MAX_PAGES: u32 = 0x10000;
/// Version number of this crate. /// Version number of this crate.
pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub(crate) fn reference_type(
wasm_ty: cranelift_wasm::WasmType,
pointer_type: ir::Type,
) -> ir::Type {
match wasm_ty {
cranelift_wasm::WasmType::FuncRef => pointer_type,
cranelift_wasm::WasmType::ExternRef => match pointer_type {
ir::types::I32 => ir::types::R32,
ir::types::I64 => ir::types::R64,
_ => panic!("unsupported pointer type"),
},
_ => panic!("unsupported Wasm reference type"),
}
}

View File

@@ -92,6 +92,10 @@ impl<'data> TargetEnvironment for ModuleEnvironment<'data> {
fn target_config(&self) -> TargetFrontendConfig { fn target_config(&self) -> TargetFrontendConfig {
self.result.target_config self.result.target_config
} }
fn reference_type(&self, ty: cranelift_wasm::WasmType) -> ir::Type {
crate::reference_type(ty, self.pointer_type())
}
} }
/// This trait is useful for `translate_module` because it tells how to translate /// This trait is useful for `translate_module` because it tells how to translate

View File

@@ -15,6 +15,7 @@
// tables: [VMTableDefinition; module.num_defined_tables], // tables: [VMTableDefinition; module.num_defined_tables],
// memories: [VMMemoryDefinition; module.num_defined_memories], // memories: [VMMemoryDefinition; module.num_defined_memories],
// globals: [VMGlobalDefinition; module.num_defined_globals], // globals: [VMGlobalDefinition; module.num_defined_globals],
// anyfuncs: [VMCallerCheckedAnyfunc; module.num_imported_functions + module.num_defined_functions],
// builtins: VMBuiltinFunctionsArray, // builtins: VMBuiltinFunctionsArray,
// } // }
@@ -62,6 +63,8 @@ pub struct VMOffsets {
pub num_imported_memories: u32, pub num_imported_memories: u32,
/// The number of imported globals in the module. /// The number of imported globals in the module.
pub num_imported_globals: u32, pub num_imported_globals: u32,
/// The number of defined functions in the module.
pub num_defined_functions: u32,
/// The number of defined tables in the module. /// The number of defined tables in the module.
pub num_defined_tables: u32, pub num_defined_tables: u32,
/// The number of defined memories in the module. /// The number of defined memories in the module.
@@ -80,6 +83,7 @@ impl VMOffsets {
num_imported_tables: cast_to_u32(module.num_imported_tables), num_imported_tables: cast_to_u32(module.num_imported_tables),
num_imported_memories: cast_to_u32(module.num_imported_memories), num_imported_memories: cast_to_u32(module.num_imported_memories),
num_imported_globals: cast_to_u32(module.num_imported_globals), num_imported_globals: cast_to_u32(module.num_imported_globals),
num_defined_functions: cast_to_u32(module.functions.len()),
num_defined_tables: cast_to_u32(module.table_plans.len()), num_defined_tables: cast_to_u32(module.table_plans.len()),
num_defined_memories: cast_to_u32(module.memory_plans.len()), num_defined_memories: cast_to_u32(module.memory_plans.len()),
num_defined_globals: cast_to_u32(module.globals.len()), num_defined_globals: cast_to_u32(module.globals.len()),
@@ -390,8 +394,8 @@ impl VMOffsets {
align(offset, 16) align(offset, 16)
} }
/// The offset of the builtin functions array. /// The offset of the `anyfuncs` array.
pub fn vmctx_builtin_functions_begin(&self) -> u32 { pub fn vmctx_anyfuncs_begin(&self) -> u32 {
self.vmctx_globals_begin() self.vmctx_globals_begin()
.checked_add( .checked_add(
self.num_defined_globals self.num_defined_globals
@@ -401,6 +405,19 @@ impl VMOffsets {
.unwrap() .unwrap()
} }
/// The offset of the builtin functions array.
pub fn vmctx_builtin_functions_begin(&self) -> u32 {
self.vmctx_anyfuncs_begin()
.checked_add(
self.num_imported_functions
.checked_add(self.num_defined_functions)
.unwrap()
.checked_mul(u32::from(self.size_of_vmcaller_checked_anyfunc()))
.unwrap(),
)
.unwrap()
}
/// Return the size of the `VMContext` allocation. /// Return the size of the `VMContext` allocation.
pub fn size_of_vmctx(&self) -> u32 { pub fn size_of_vmctx(&self) -> u32 {
self.vmctx_builtin_functions_begin() self.vmctx_builtin_functions_begin()
@@ -516,6 +533,19 @@ impl VMOffsets {
.unwrap() .unwrap()
} }
/// Return the offset to the `VMCallerCheckedAnyfunc` for the given function
/// index (either imported or defined).
pub fn vmctx_anyfunc(&self, index: FuncIndex) -> u32 {
self.vmctx_anyfuncs_begin()
.checked_add(
index
.as_u32()
.checked_mul(u32::from(self.size_of_vmcaller_checked_anyfunc()))
.unwrap(),
)
.unwrap()
}
/// Return the offset to the `body` field in `*const VMFunctionBody` index `index`. /// Return the offset to the `body` field in `*const VMFunctionBody` index `index`.
pub fn vmctx_vmfunction_import_body(&self, index: FuncIndex) -> u32 { pub fn vmctx_vmfunction_import_body(&self, index: FuncIndex) -> u32 {
self.vmctx_vmfunction_import(index) self.vmctx_vmfunction_import(index)

View File

@@ -32,7 +32,9 @@ pub fn resolve_imports(
match (import, &export) { match (import, &export) {
(EntityIndex::Function(func_index), Some(Export::Function(f))) => { (EntityIndex::Function(func_index), Some(Export::Function(f))) => {
let import_signature = module.local.native_func_signature(*func_index); let import_signature = module.local.native_func_signature(*func_index);
let signature = signatures.lookup_native(f.signature).unwrap(); let signature = signatures
.lookup_native(unsafe { f.anyfunc.as_ref().type_index })
.unwrap();
if signature != *import_signature { if signature != *import_signature {
// TODO: If the difference is in the calling convention, // TODO: If the difference is in the calling convention,
// we could emit a wrapper function to fix it up. // we could emit a wrapper function to fix it up.
@@ -43,8 +45,8 @@ pub fn resolve_imports(
))); )));
} }
function_imports.push(VMFunctionImport { function_imports.push(VMFunctionImport {
body: f.address, body: unsafe { f.anyfunc.as_ref().func_ptr },
vmctx: f.vmctx, vmctx: unsafe { f.anyfunc.as_ref().vmctx },
}); });
} }
(EntityIndex::Function(_), Some(_)) => { (EntityIndex::Function(_), Some(_)) => {
@@ -169,16 +171,20 @@ fn is_global_compatible(exported: &Global, imported: &Global) -> bool {
} }
let Global { let Global {
wasm_ty: exported_wasm_ty,
ty: exported_ty, ty: exported_ty,
mutability: exported_mutability, mutability: exported_mutability,
initializer: _exported_initializer, initializer: _exported_initializer,
} = exported; } = exported;
let Global { let Global {
wasm_ty: imported_wasm_ty,
ty: imported_ty, ty: imported_ty,
mutability: imported_mutability, mutability: imported_mutability,
initializer: _imported_initializer, initializer: _imported_initializer,
} = imported; } = imported;
exported_ty == imported_ty && imported_mutability == exported_mutability exported_wasm_ty == imported_wasm_ty
&& exported_ty == imported_ty
&& imported_mutability == exported_mutability
} }
fn is_table_element_type_compatible( fn is_table_element_type_compatible(

View File

@@ -1,7 +1,7 @@
use crate::vmcontext::{ use crate::vmcontext::{
VMContext, VMFunctionBody, VMGlobalDefinition, VMMemoryDefinition, VMSharedSignatureIndex, VMCallerCheckedAnyfunc, VMContext, VMGlobalDefinition, VMMemoryDefinition, VMTableDefinition,
VMTableDefinition,
}; };
use std::ptr::NonNull;
use wasmtime_environ::wasm::Global; use wasmtime_environ::wasm::Global;
use wasmtime_environ::{MemoryPlan, TablePlan}; use wasmtime_environ::{MemoryPlan, TablePlan};
@@ -24,14 +24,11 @@ pub enum Export {
/// A function export value. /// A function export value.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ExportFunction { pub struct ExportFunction {
/// The address of the native-code function. /// The `VMCallerCheckedAnyfunc` for this exported function.
pub address: *const VMFunctionBody,
/// Pointer to the containing `VMContext`.
pub vmctx: *mut VMContext,
/// The function signature declaration, used for compatibilty checking.
/// ///
/// Note that this indexes within the module associated with `vmctx`. /// Note that exported functions cannot be a null funcref, so this is a
pub signature: VMSharedSignatureIndex, /// non-null pointer.
pub anyfunc: NonNull<VMCallerCheckedAnyfunc>,
} }
impl From<ExportFunction> for Export { impl From<ExportFunction> for Export {

View File

@@ -545,7 +545,10 @@ impl VMExternRefActivationsTable {
return Err(externref); return Err(externref);
} }
debug_assert!((*next.as_ref().get()).is_none()); debug_assert!(
(*next.as_ref().get()).is_none(),
"slots >= the `next` bump finger are always `None`"
);
ptr::write(next.as_ptr(), UnsafeCell::new(Some(externref))); ptr::write(next.as_ptr(), UnsafeCell::new(Some(externref)));
let next = NonNull::new_unchecked(next.as_ptr().add(1)); let next = NonNull::new_unchecked(next.as_ptr().add(1));
@@ -1121,6 +1124,7 @@ mod tests {
num_imported_tables: 0, num_imported_tables: 0,
num_imported_memories: 0, num_imported_memories: 0,
num_imported_globals: 0, num_imported_globals: 0,
num_defined_functions: 0,
num_defined_tables: 0, num_defined_tables: 0,
num_defined_memories: 0, num_defined_memories: 0,
num_defined_globals: 0, num_defined_globals: 0,
@@ -1147,6 +1151,7 @@ mod tests {
num_imported_tables: 0, num_imported_tables: 0,
num_imported_memories: 0, num_imported_memories: 0,
num_imported_globals: 0, num_imported_globals: 0,
num_defined_functions: 0,
num_defined_tables: 0, num_defined_tables: 0,
num_defined_memories: 0, num_defined_memories: 0,
num_defined_globals: 0, num_defined_globals: 0,

View File

@@ -21,13 +21,15 @@ use std::any::Any;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::ptr::NonNull;
use std::sync::Arc; use std::sync::Arc;
use std::{mem, ptr, slice}; use std::{mem, ptr, slice};
use thiserror::Error; use thiserror::Error;
use wasmtime_environ::entity::{packed_option::ReservedValue, BoxedSlice, EntityRef, PrimaryMap}; use wasmtime_environ::entity::{packed_option::ReservedValue, BoxedSlice, EntityRef, PrimaryMap};
use wasmtime_environ::wasm::{ use wasmtime_environ::wasm::{
DataIndex, DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, DataIndex, DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex,
ElemIndex, FuncIndex, GlobalIndex, GlobalInit, MemoryIndex, SignatureIndex, TableIndex, ElemIndex, FuncIndex, GlobalIndex, GlobalInit, MemoryIndex, SignatureIndex, TableElementType,
TableIndex,
}; };
use wasmtime_environ::{ir, DataInitializer, EntityIndex, Module, TableElements, VMOffsets}; use wasmtime_environ::{ir, DataInitializer, EntityIndex, Module, TableElements, VMOffsets};
@@ -54,7 +56,7 @@ pub(crate) struct Instance {
/// Passive elements in this instantiation. As `elem.drop`s happen, these /// Passive elements in this instantiation. As `elem.drop`s happen, these
/// entries get removed. A missing entry is considered equivalent to an /// entries get removed. A missing entry is considered equivalent to an
/// empty slice. /// empty slice.
passive_elements: RefCell<HashMap<ElemIndex, Box<[VMCallerCheckedAnyfunc]>>>, passive_elements: RefCell<HashMap<ElemIndex, Box<[*mut VMCallerCheckedAnyfunc]>>>,
/// Passive data segments from our module. As `data.drop`s happen, entries /// Passive data segments from our module. As `data.drop`s happen, entries
/// get removed. A missing entry is considered equivalent to an empty slice. /// get removed. A missing entry is considered equivalent to an empty slice.
@@ -273,23 +275,10 @@ impl Instance {
pub fn lookup_by_declaration(&self, export: &EntityIndex) -> Export { pub fn lookup_by_declaration(&self, export: &EntityIndex) -> Export {
match export { match export {
EntityIndex::Function(index) => { EntityIndex::Function(index) => {
let signature = self.signature_id(self.module.local.functions[*index]); let anyfunc = self.get_caller_checked_anyfunc(*index).unwrap();
let (address, vmctx) = let anyfunc =
if let Some(def_index) = self.module.local.defined_func_index(*index) { NonNull::new(anyfunc as *const VMCallerCheckedAnyfunc as *mut _).unwrap();
( ExportFunction { anyfunc }.into()
self.finished_functions[def_index] as *const _,
self.vmctx_ptr(),
)
} else {
let import = self.imported_function(*index);
(import.body, import.vmctx)
};
ExportFunction {
address,
signature,
vmctx,
}
.into()
} }
EntityIndex::Table(index) => { EntityIndex::Table(index) => {
let (definition, vmctx) = let (definition, vmctx) =
@@ -448,6 +437,11 @@ impl Instance {
foreign_instance.memory_size(foreign_index) foreign_instance.memory_size(foreign_index)
} }
pub(crate) fn table_element_type(&self, table_index: TableIndex) -> TableElementType {
let table = self.get_table(table_index);
table.element_type()
}
/// Grow table by the specified amount of elements, filling them with /// Grow table by the specified amount of elements, filling them with
/// `init_value`. /// `init_value`.
/// ///
@@ -513,30 +507,25 @@ impl Instance {
Layout::from_size_align(size, align).unwrap() Layout::from_size_align(size, align).unwrap()
} }
/// Get a `VMCallerCheckedAnyfunc` for the given `FuncIndex`. /// Get a `&VMCallerCheckedAnyfunc` for the given `FuncIndex`.
fn get_caller_checked_anyfunc(&self, index: FuncIndex) -> VMCallerCheckedAnyfunc { ///
/// Returns `None` if the index is the reserved index value.
///
/// The returned reference is a stable reference that won't be moved and can
/// be passed into JIT code.
pub(crate) fn get_caller_checked_anyfunc(
&self,
index: FuncIndex,
) -> Option<&VMCallerCheckedAnyfunc> {
if index == FuncIndex::reserved_value() { if index == FuncIndex::reserved_value() {
return VMCallerCheckedAnyfunc::default(); return None;
} }
let sig = self.module.local.functions[index]; Some(unsafe { &*self.anyfunc_ptr(index) })
let type_index = self.signature_id(sig);
let (func_ptr, vmctx) = if let Some(def_index) = self.module.local.defined_func_index(index)
{
(
self.finished_functions[def_index] as *const _,
self.vmctx_ptr(),
)
} else {
let import = self.imported_function(index);
(import.body, import.vmctx)
};
VMCallerCheckedAnyfunc {
func_ptr,
type_index,
vmctx,
} }
unsafe fn anyfunc_ptr(&self, index: FuncIndex) -> *mut VMCallerCheckedAnyfunc {
self.vmctx_plus_offset(self.offsets.vmctx_anyfunc(index))
} }
/// The `table.init` operation: initializes a portion of a table with a /// The `table.init` operation: initializes a portion of a table with a
@@ -574,7 +563,7 @@ impl Instance {
// TODO(#983): investigate replacing this get/set loop with a `memcpy`. // TODO(#983): investigate replacing this get/set loop with a `memcpy`.
for (dst, src) in (dst..dst + len).zip(src..src + len) { for (dst, src) in (dst..dst + len).zip(src..src + len) {
table table
.set(dst, TableElement::FuncRef(elem[src as usize].clone())) .set(dst, TableElement::FuncRef(elem[src as usize]))
.expect("should never panic because we already did the bounds check above"); .expect("should never panic because we already did the bounds check above");
} }
@@ -932,6 +921,30 @@ impl InstanceHandle {
*instance.externref_activations_table() = externref_activations_table; *instance.externref_activations_table() = externref_activations_table;
*instance.stack_map_registry() = stack_map_registry; *instance.stack_map_registry() = stack_map_registry;
for (index, sig) in instance.module.local.functions.iter() {
let type_index = instance.signature_id(*sig);
let (func_ptr, vmctx) =
if let Some(def_index) = instance.module.local.defined_func_index(index) {
(
NonNull::new(instance.finished_functions[def_index] as *mut _).unwrap(),
instance.vmctx_ptr(),
)
} else {
let import = instance.imported_function(index);
(import.body, import.vmctx)
};
ptr::write(
instance.anyfunc_ptr(index),
VMCallerCheckedAnyfunc {
func_ptr,
type_index,
vmctx,
},
);
}
// Perform infallible initialization in this constructor, while fallible // Perform infallible initialization in this constructor, while fallible
// initialization is deferred to the `initialize` method. // initialization is deferred to the `initialize` method.
initialize_passive_elements(instance); initialize_passive_elements(instance);
@@ -1246,12 +1259,14 @@ fn initialize_tables(instance: &Instance) -> Result<(), InstantiationError> {
} }
for (i, func_idx) in init.elements.iter().enumerate() { for (i, func_idx) in init.elements.iter().enumerate() {
let anyfunc = instance.get_caller_checked_anyfunc(*func_idx); let anyfunc = instance.get_caller_checked_anyfunc(*func_idx).map_or(
ptr::null_mut(),
|f: &VMCallerCheckedAnyfunc| {
f as *const VMCallerCheckedAnyfunc as *mut VMCallerCheckedAnyfunc
},
);
table table
.set( .set(u32::try_from(start + i).unwrap(), anyfunc.into())
u32::try_from(start + i).unwrap(),
TableElement::FuncRef(anyfunc),
)
.unwrap(); .unwrap();
} }
} }
@@ -1280,7 +1295,14 @@ fn initialize_passive_elements(instance: &Instance) {
*idx, *idx,
segments segments
.iter() .iter()
.map(|s| instance.get_caller_checked_anyfunc(*s)) .map(|s| {
instance.get_caller_checked_anyfunc(*s).map_or(
ptr::null_mut(),
|f: &VMCallerCheckedAnyfunc| {
f as *const VMCallerCheckedAnyfunc as *mut _
},
)
})
.collect(), .collect(),
) )
}), }),

View File

@@ -56,3 +56,14 @@ pub use crate::vmcontext::{
/// Version number of this crate. /// Version number of this crate.
pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub const VERSION: &str = env!("CARGO_PKG_VERSION");
/// The Cranelift IR type used for reference types for this target architecture.
pub fn ref_type() -> wasmtime_environ::ir::Type {
if cfg!(target_pointer_width = "32") {
wasmtime_environ::ir::types::R32
} else if cfg!(target_pointer_width = "64") {
wasmtime_environ::ir::types::R64
} else {
unreachable!()
}
}

View File

@@ -57,10 +57,12 @@
//! ``` //! ```
use crate::externref::VMExternRef; use crate::externref::VMExternRef;
use crate::table::{Table, TableElement}; use crate::table::Table;
use crate::traphandlers::raise_lib_trap; use crate::traphandlers::raise_lib_trap;
use crate::vmcontext::VMContext; use crate::vmcontext::{VMCallerCheckedAnyfunc, VMContext};
use wasmtime_environ::wasm::{DataIndex, DefinedMemoryIndex, ElemIndex, MemoryIndex, TableIndex}; use wasmtime_environ::wasm::{
DataIndex, DefinedMemoryIndex, ElemIndex, MemoryIndex, TableElementType, TableIndex,
};
/// Implementation of f32.ceil /// Implementation of f32.ceil
pub extern "C" fn wasmtime_f32_ceil(x: f32) -> f32 { pub extern "C" fn wasmtime_f32_ceil(x: f32) -> f32 {
@@ -226,24 +228,38 @@ pub unsafe extern "C" fn wasmtime_imported_memory32_size(
instance.imported_memory_size(memory_index) instance.imported_memory_size(memory_index)
} }
/// Implementation of `table.grow` for `externref`s. /// Implementation of `table.grow`.
pub unsafe extern "C" fn wasmtime_table_grow_extern_ref( pub unsafe extern "C" fn wasmtime_table_grow(
vmctx: *mut VMContext, vmctx: *mut VMContext,
table_index: u32, table_index: u32,
delta: u32, delta: u32,
// NB: we don't know whether this is a pointer to a `VMCallerCheckedAnyfunc`
// or is a `VMExternRef` until we look at the table type.
init_value: *mut u8, init_value: *mut u8,
) -> u32 { ) -> u32 {
let instance = (&mut *vmctx).instance();
let table_index = TableIndex::from_u32(table_index);
match instance.table_element_type(table_index) {
TableElementType::Func => {
let func = init_value as *mut VMCallerCheckedAnyfunc;
instance
.table_grow(table_index, delta, func.into())
.unwrap_or(-1_i32 as u32)
}
TableElementType::Val(ty) => {
debug_assert_eq!(ty, crate::ref_type());
let init_value = if init_value.is_null() { let init_value = if init_value.is_null() {
None None
} else { } else {
Some(VMExternRef::clone_from_raw(init_value)) Some(VMExternRef::clone_from_raw(init_value))
}; };
let instance = (&mut *vmctx).instance();
let table_index = TableIndex::from_u32(table_index);
instance instance
.table_grow(table_index, delta, TableElement::ExternRef(init_value)) .table_grow(table_index, delta, init_value.into())
.unwrap_or(-1_i32 as u32) .unwrap_or(-1_i32 as u32)
}
}
} }
/// Implementation of `table.copy`. /// Implementation of `table.copy`.

View File

@@ -6,6 +6,7 @@ use crate::vmcontext::{VMCallerCheckedAnyfunc, VMTableDefinition};
use crate::{Trap, VMExternRef}; use crate::{Trap, VMExternRef};
use std::cell::RefCell; use std::cell::RefCell;
use std::convert::{TryFrom, TryInto}; use std::convert::{TryFrom, TryInto};
use std::ptr;
use wasmtime_environ::wasm::TableElementType; use wasmtime_environ::wasm::TableElementType;
use wasmtime_environ::{ir, TablePlan, TableStyle}; use wasmtime_environ::{ir, TablePlan, TableStyle};
@@ -20,34 +21,27 @@ pub struct Table {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum TableElement { pub enum TableElement {
/// A `funcref`. /// A `funcref`.
FuncRef(VMCallerCheckedAnyfunc), FuncRef(*mut VMCallerCheckedAnyfunc),
/// An `exrernref`. /// An `exrernref`.
ExternRef(Option<VMExternRef>), ExternRef(Option<VMExternRef>),
} }
#[derive(Debug)] #[derive(Debug)]
enum TableElements { enum TableElements {
FuncRefs(Vec<VMCallerCheckedAnyfunc>), FuncRefs(Vec<*mut VMCallerCheckedAnyfunc>),
ExternRefs(Vec<Option<VMExternRef>>), ExternRefs(Vec<Option<VMExternRef>>),
} }
impl Table { impl Table {
/// Create a new table instance with specified minimum and maximum number of elements. /// Create a new table instance with specified minimum and maximum number of elements.
pub fn new(plan: &TablePlan) -> Self { pub fn new(plan: &TablePlan) -> Self {
let elements =
RefCell::new(match plan.table.ty {
TableElementType::Func => TableElements::FuncRefs(vec![
VMCallerCheckedAnyfunc::default();
usize::try_from(plan.table.minimum).unwrap()
]),
TableElementType::Val(ty)
if (cfg!(target_pointer_width = "64") && ty == ir::types::R64)
|| (cfg!(target_pointer_width = "32") && ty == ir::types::R32) =>
{
let min = usize::try_from(plan.table.minimum).unwrap(); let min = usize::try_from(plan.table.minimum).unwrap();
let elements = RefCell::new(match plan.table.ty {
TableElementType::Func => TableElements::FuncRefs(vec![ptr::null_mut(); min]),
TableElementType::Val(ty) => {
debug_assert_eq!(ty, crate::ref_type());
TableElements::ExternRefs(vec![None; min]) TableElements::ExternRefs(vec![None; min])
} }
TableElementType::Val(ty) => unimplemented!("unsupported table type ({})", ty),
}); });
match plan.style { match plan.style {
TableStyle::CallerChecksSignature => Self { TableStyle::CallerChecksSignature => Self {
@@ -57,6 +51,14 @@ impl Table {
} }
} }
/// Returns the type of the elements in this table.
pub fn element_type(&self) -> TableElementType {
match &*self.elements.borrow() {
TableElements::FuncRefs(_) => TableElementType::Func,
TableElements::ExternRefs(_) => TableElementType::Val(crate::ref_type()),
}
}
/// Returns the number of allocated elements. /// Returns the number of allocated elements.
pub fn size(&self) -> u32 { pub fn size(&self) -> u32 {
match &*self.elements.borrow() { match &*self.elements.borrow() {
@@ -199,7 +201,7 @@ impl Table {
} }
} }
impl TryFrom<TableElement> for VMCallerCheckedAnyfunc { impl TryFrom<TableElement> for *mut VMCallerCheckedAnyfunc {
type Error = TableElement; type Error = TableElement;
fn try_from(e: TableElement) -> Result<Self, Self::Error> { fn try_from(e: TableElement) -> Result<Self, Self::Error> {
@@ -221,8 +223,8 @@ impl TryFrom<TableElement> for Option<VMExternRef> {
} }
} }
impl From<VMCallerCheckedAnyfunc> for TableElement { impl From<*mut VMCallerCheckedAnyfunc> for TableElement {
fn from(f: VMCallerCheckedAnyfunc) -> TableElement { fn from(f: *mut VMCallerCheckedAnyfunc) -> TableElement {
TableElement::FuncRef(f) TableElement::FuncRef(f)
} }
} }

View File

@@ -3,8 +3,9 @@
use crate::instance::Instance; use crate::instance::Instance;
use std::any::Any; use std::any::Any;
use std::ptr::NonNull;
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
use std::{ptr, u32}; use std::u32;
use wasmtime_environ::BuiltinFunctionIndex; use wasmtime_environ::BuiltinFunctionIndex;
/// An imported function. /// An imported function.
@@ -12,7 +13,7 @@ use wasmtime_environ::BuiltinFunctionIndex;
#[repr(C)] #[repr(C)]
pub struct VMFunctionImport { pub struct VMFunctionImport {
/// A pointer to the imported function body. /// A pointer to the imported function body.
pub body: *const VMFunctionBody, pub body: NonNull<VMFunctionBody>,
/// A pointer to the `VMContext` that owns the function. /// A pointer to the `VMContext` that owns the function.
pub vmctx: *mut VMContext, pub vmctx: *mut VMContext,
@@ -475,7 +476,7 @@ impl Default for VMSharedSignatureIndex {
#[repr(C)] #[repr(C)]
pub struct VMCallerCheckedAnyfunc { pub struct VMCallerCheckedAnyfunc {
/// Function body. /// Function body.
pub func_ptr: *const VMFunctionBody, pub func_ptr: NonNull<VMFunctionBody>,
/// Function signature id. /// Function signature id.
pub type_index: VMSharedSignatureIndex, pub type_index: VMSharedSignatureIndex,
/// Function `VMContext`. /// Function `VMContext`.
@@ -513,16 +514,6 @@ mod test_vmcaller_checked_anyfunc {
} }
} }
impl Default for VMCallerCheckedAnyfunc {
fn default() -> Self {
Self {
func_ptr: ptr::null_mut(),
type_index: Default::default(),
vmctx: ptr::null_mut(),
}
}
}
/// An array that stores addresses of builtin functions. We translate code /// An array that stores addresses of builtin functions. We translate code
/// to use indirect calls. This way, we don't have to patch the code. /// to use indirect calls. This way, we don't have to patch the code.
#[repr(C)] #[repr(C)]
@@ -549,8 +540,10 @@ impl VMBuiltinFunctionsArray {
ptrs[BuiltinFunctionIndex::imported_memory32_size().index() as usize] = ptrs[BuiltinFunctionIndex::imported_memory32_size().index() as usize] =
wasmtime_imported_memory32_size as usize; wasmtime_imported_memory32_size as usize;
ptrs[BuiltinFunctionIndex::table_copy().index() as usize] = wasmtime_table_copy as usize; ptrs[BuiltinFunctionIndex::table_copy().index() as usize] = wasmtime_table_copy as usize;
ptrs[BuiltinFunctionIndex::table_grow_extern_ref().index() as usize] = ptrs[BuiltinFunctionIndex::table_grow_funcref().index() as usize] =
wasmtime_table_grow_extern_ref as usize; wasmtime_table_grow as usize;
ptrs[BuiltinFunctionIndex::table_grow_externref().index() as usize] =
wasmtime_table_grow as usize;
ptrs[BuiltinFunctionIndex::table_init().index() as usize] = wasmtime_table_init as usize; ptrs[BuiltinFunctionIndex::table_init().index() as usize] = wasmtime_table_init as usize;
ptrs[BuiltinFunctionIndex::elem_drop().index() as usize] = wasmtime_elem_drop as usize; ptrs[BuiltinFunctionIndex::elem_drop().index() as usize] = wasmtime_elem_drop as usize;
ptrs[BuiltinFunctionIndex::defined_memory_copy().index() as usize] = ptrs[BuiltinFunctionIndex::defined_memory_copy().index() as usize] =

View File

@@ -205,7 +205,7 @@ impl Global {
/// Returns the value type of this `global`. /// Returns the value type of this `global`.
pub fn val_type(&self) -> ValType { 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") .expect("core wasm type should be supported")
} }
@@ -298,14 +298,10 @@ fn set_table_item(
instance: &InstanceHandle, instance: &InstanceHandle,
table_index: wasm::DefinedTableIndex, table_index: wasm::DefinedTableIndex,
item_index: u32, item_index: u32,
item: wasmtime_runtime::VMCallerCheckedAnyfunc, item: *mut wasmtime_runtime::VMCallerCheckedAnyfunc,
) -> Result<()> { ) -> Result<()> {
instance instance
.table_set( .table_set(table_index, item_index, item.into())
table_index,
item_index,
runtime::TableElement::FuncRef(item),
)
.map_err(|()| anyhow!("table element index out of bounds")) .map_err(|()| anyhow!("table element index out of bounds"))
} }
@@ -329,7 +325,7 @@ impl Table {
let definition = unsafe { &*wasmtime_export.definition }; let definition = unsafe { &*wasmtime_export.definition };
let index = instance.table_index(definition); let index = instance.table_index(definition);
for i in 0..definition.current_elements { for i in 0..definition.current_elements {
set_table_item(&instance, index, i, item.clone())?; set_table_item(&instance, index, i, item)?;
} }
Ok(Table { Ok(Table {
@@ -356,7 +352,7 @@ impl Table {
let item = self.instance.table_get(table_index, index)?; let item = self.instance.table_get(table_index, index)?;
match item { match item {
runtime::TableElement::FuncRef(f) => { 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(None) => Some(Val::ExternRef(None)),
runtime::TableElement::ExternRef(Some(x)) => Some(Val::ExternRef(Some(ExternRef { runtime::TableElement::ExternRef(Some(x)) => Some(Val::ExternRef(Some(ExternRef {
@@ -398,8 +394,7 @@ impl Table {
let orig_size = match self.ty().element() { let orig_size = match self.ty().element() {
ValType::FuncRef => { ValType::FuncRef => {
let init = into_checked_anyfunc(init, &self.instance.store)?; let init = into_checked_anyfunc(init, &self.instance.store)?;
self.instance self.instance.defined_table_grow(index, delta, init.into())
.defined_table_grow(index, delta, runtime::TableElement::FuncRef(init))
} }
ValType::ExternRef => { ValType::ExternRef => {
let init = match init { let init = match init {

View File

@@ -6,10 +6,11 @@ use std::cmp::max;
use std::fmt; use std::fmt;
use std::mem; use std::mem;
use std::panic::{self, AssertUnwindSafe}; use std::panic::{self, AssertUnwindSafe};
use std::ptr; use std::ptr::{self, NonNull};
use std::rc::Weak; use std::rc::Weak;
use wasmtime_runtime::{raise_user_trap, ExportFunction, VMTrampoline}; use wasmtime_runtime::{
use wasmtime_runtime::{Export, InstanceHandle, VMContext, VMFunctionBody}; raise_user_trap, Export, InstanceHandle, VMContext, VMFunctionBody, VMTrampoline,
};
/// A WebAssembly function which can be called. /// A WebAssembly function which can be called.
/// ///
@@ -140,8 +141,8 @@ use wasmtime_runtime::{Export, InstanceHandle, VMContext, VMFunctionBody};
#[derive(Clone)] #[derive(Clone)]
pub struct Func { pub struct Func {
instance: StoreInstanceHandle, instance: StoreInstanceHandle,
export: ExportFunction,
trampoline: VMTrampoline, trampoline: VMTrampoline,
export: wasmtime_runtime::ExportFunction,
} }
macro_rules! getters { macro_rules! getters {
@@ -175,10 +176,11 @@ macro_rules! getters {
.context("Type mismatch in return type")?; .context("Type mismatch in return type")?;
ensure!(results.next().is_none(), "Type mismatch: too many return values (expected 1)"); 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 // Pass the instance into the closure so that we keep it live for
// of the closure. Pass the export in so that we can call it. // the lifetime of the closure. Pass the `anyfunc` in so that we can
// call it.
let instance = self.instance.clone(); 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 // ... and then once we've passed the typechecks we can hand out our
// object since our `transmute` below should be safe! // object since our `transmute` below should be safe!
@@ -191,12 +193,12 @@ macro_rules! getters {
*mut VMContext, *mut VMContext,
$($args,)* $($args,)*
) -> R, ) -> R,
>(export.address); >(anyfunc.as_ref().func_ptr.as_ptr());
let mut ret = None; let mut ret = None;
$(let $args = $args.into_abi();)* $(let $args = $args.into_abi();)*
invoke_wasm_and_catch_traps(export.vmctx, &instance.store, || { invoke_wasm_and_catch_traps(anyfunc.as_ref().vmctx, &instance.store, || {
ret = Some(fnptr(export.vmctx, ptr::null_mut(), $($args,)*)); ret = Some(fnptr(anyfunc.as_ref().vmctx, ptr::null_mut(), $($args,)*));
})?; })?;
Ok(ret.unwrap()) Ok(ret.unwrap())
@@ -282,8 +284,8 @@ impl Func {
crate::trampoline::generate_func_export(&ty, func, store).expect("generated func"); crate::trampoline::generate_func_export(&ty, func, store).expect("generated func");
Func { Func {
instance, instance,
export,
trampoline, trampoline,
export,
} }
} }
@@ -488,7 +490,10 @@ impl Func {
pub fn ty(&self) -> FuncType { pub fn ty(&self) -> FuncType {
// Signatures should always be registered in the store's registry of // Signatures should always be registered in the store's registry of
// shared signatures, so we should be able to unwrap safely here. // 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 // This is only called with `Export::Function`, and since it's coming
// from wasmtime_runtime itself we should support all the types 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. /// Returns the number of parameters that this function takes.
pub fn param_arity(&self) -> usize { 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() sig.params.len()
} }
/// Returns the number of results this function produces. /// Returns the number of results this function produces.
pub fn result_arity(&self) -> usize { 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() sig.returns.len()
} }
@@ -553,14 +564,17 @@ impl Func {
} }
// Call the trampoline. // Call the trampoline.
invoke_wasm_and_catch_traps(self.export.vmctx, &self.instance.store, || unsafe { unsafe {
let anyfunc = self.export.anyfunc.as_ref();
invoke_wasm_and_catch_traps(anyfunc.vmctx, &self.instance.store, || {
(self.trampoline)( (self.trampoline)(
self.export.vmctx, anyfunc.vmctx,
ptr::null_mut(), ptr::null_mut(),
self.export.address, anyfunc.func_ptr.as_ptr(),
values_vec.as_mut_ptr(), values_vec.as_mut_ptr(),
) )
})?; })?;
}
// Load the return values out of `values_vec`. // Load the return values out of `values_vec`.
let mut results = Vec::with_capacity(my_ty.results().len()); let mut results = Vec::with_capacity(my_ty.results().len());
@@ -578,6 +592,12 @@ impl Func {
&self.export &self.export
} }
pub(crate) fn caller_checked_anyfunc(
&self,
) -> NonNull<wasmtime_runtime::VMCallerCheckedAnyfunc> {
self.export.anyfunc
}
pub(crate) fn from_wasmtime_function( pub(crate) fn from_wasmtime_function(
export: wasmtime_runtime::ExportFunction, export: wasmtime_runtime::ExportFunction,
instance: StoreInstanceHandle, instance: StoreInstanceHandle,
@@ -586,7 +606,7 @@ impl Func {
// on that module as well, so unwrap the result here since otherwise // on that module as well, so unwrap the result here since otherwise
// it's a bug in wasmtime. // it's a bug in wasmtime.
let trampoline = instance let trampoline = instance
.trampoline(export.signature) .trampoline(unsafe { export.anyfunc.as_ref().type_index })
.expect("failed to retrieve trampoline from module"); .expect("failed to retrieve trampoline from module");
Func { Func {

View File

@@ -5,7 +5,9 @@ use std::any::Any;
use std::mem; use std::mem;
use wasmtime_environ::EntityIndex; use wasmtime_environ::EntityIndex;
use wasmtime_jit::{CompiledModule, Resolver}; use wasmtime_jit::{CompiledModule, Resolver};
use wasmtime_runtime::{InstantiationError, VMContext, VMFunctionBody}; use wasmtime_runtime::{
InstantiationError, StackMapRegistry, VMContext, VMExternRefActivationsTable, VMFunctionBody,
};
struct SimpleResolver<'a> { struct SimpleResolver<'a> {
imports: &'a [Extern], imports: &'a [Extern],
@@ -50,8 +52,8 @@ fn instantiate(
config.memory_creator.as_ref().map(|a| a as _), config.memory_creator.as_ref().map(|a| a as _),
store.interrupts().clone(), store.interrupts().clone(),
host, host,
&*store.externref_activations_table() as *const _ as *mut _, &**store.externref_activations_table() as *const VMExternRefActivationsTable as *mut _,
&*store.stack_map_registry() as *const _ as *mut _, &**store.stack_map_registry() as *const StackMapRegistry as *mut _,
)?; )?;
// After we've created the `InstanceHandle` we still need to run // After we've created the `InstanceHandle` we still need to run
@@ -95,7 +97,9 @@ fn instantiate(
mem::transmute::< mem::transmute::<
*const VMFunctionBody, *const VMFunctionBody,
unsafe extern "C" fn(*mut VMContext, *mut VMContext), 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(()) 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() { } else if export.name() == "memory" && export.ty().memory().is_some() {
// Allow an exported "memory" memory for now. // Allow an exported "memory" memory for now.
} else if export.name() == "__indirect_function_table" && export.ty().table().is_some() } 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::wasm::DefinedFuncIndex;
use wasmtime_environ::Module; use wasmtime_environ::Module;
use wasmtime_runtime::{ use wasmtime_runtime::{
Imports, InstanceHandle, VMFunctionBody, VMSharedSignatureIndex, VMTrampoline, Imports, InstanceHandle, StackMapRegistry, VMExternRefActivationsTable, VMFunctionBody,
VMSharedSignatureIndex, VMTrampoline,
}; };
pub(crate) fn create_handle( pub(crate) fn create_handle(
@@ -46,8 +47,8 @@ pub(crate) fn create_handle(
signatures.into_boxed_slice(), signatures.into_boxed_slice(),
state, state,
store.interrupts().clone(), store.interrupts().clone(),
&*store.externref_activations_table() as *const _ as *mut _, &**store.externref_activations_table() as *const VMExternRefActivationsTable as *mut _,
&*store.stack_map_registry() as *const _ as *mut _, &**store.stack_map_registry() as *const StackMapRegistry as *mut _,
)?; )?;
Ok(store.add_instance(handle)) Ok(store.add_instance(handle))
} }

View File

@@ -1,13 +1,13 @@
use super::create_handle::create_handle; use super::create_handle::create_handle;
use crate::trampoline::StoreInstanceHandle; use crate::trampoline::StoreInstanceHandle;
use crate::Store; use crate::{GlobalType, Mutability, Store, Val};
use crate::{GlobalType, Mutability, Val};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use wasmtime_environ::entity::PrimaryMap; use wasmtime_environ::entity::PrimaryMap;
use wasmtime_environ::{wasm, EntityIndex, Module}; use wasmtime_environ::{wasm, EntityIndex, Module};
pub fn create_global(store: &Store, gt: &GlobalType, val: Val) -> Result<StoreInstanceHandle> { pub fn create_global(store: &Store, gt: &GlobalType, val: Val) -> Result<StoreInstanceHandle> {
let global = wasm::Global { let global = wasm::Global {
wasm_ty: gt.content().to_wasm_type(),
ty: match gt.content().get_wasmtime_type() { ty: match gt.content().get_wasmtime_type() {
Some(t) => t, Some(t) => t,
None => bail!("cannot support {:?} as a wasm global type", gt.content()), 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 mut module = Module::new();
let table = wasm::Table { let table = wasm::Table {
wasm_ty: table.element().to_wasm_type(),
minimum: table.limits().min(), minimum: table.limits().min(),
maximum: table.limits().max(), maximum: table.limits().max(),
ty: match table.element() { ty: match table.element() {

View File

@@ -106,25 +106,7 @@ impl ValType {
ValType::F32 => Some(ir::types::F32), ValType::F32 => Some(ir::types::F32),
ValType::F64 => Some(ir::types::F64), ValType::F64 => Some(ir::types::F64),
ValType::V128 => Some(ir::types::I8X16), ValType::V128 => Some(ir::types::I8X16),
#[cfg(target_pointer_width = "64")] ValType::ExternRef => Some(wasmtime_runtime::ref_type()),
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),
_ => None, _ => None,
} }
} }
@@ -353,7 +335,7 @@ impl GlobalType {
/// Returns `None` if the wasmtime global has a type that we can't /// 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. /// represent, but that should only very rarely happen and indicate a bug.
pub(crate) fn from_wasmtime_global(global: &wasm::Global) -> Option<GlobalType> { 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 { let mutability = if global.mutability {
Mutability::Var Mutability::Var
} else { } else {

View File

@@ -1,8 +1,8 @@
use crate::r#ref::ExternRef; use crate::r#ref::ExternRef;
use crate::{Func, Store, ValType}; use crate::{Func, Store, ValType};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use std::ptr; use std::ptr::{self, NonNull};
use wasmtime_runtime::VMExternRef; use wasmtime_runtime::{self as runtime, VMExternRef};
/// Possible runtime values that a WebAssembly module can either consume or /// Possible runtime values that a WebAssembly module can either consume or
/// produce. /// produce.
@@ -26,11 +26,18 @@ pub enum Val {
/// `f64::from_bits` to create an `f64` value. /// `f64::from_bits` to create an `f64` value.
F64(u64), 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>), ExternRef(Option<ExternRef>),
/// A first-class reference to a WebAssembly function. /// 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 /// A 128-bit number
V128(u128), V128(u128),
@@ -94,7 +101,14 @@ impl Val {
.insert_with_gc(x.inner, store.stack_map_registry()); .insert_with_gc(x.inner, store.stack_map_registry());
ptr::write(p as *mut *mut u8, externref_ptr) 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) (I64(i64) i64 unwrap_i64 *e)
(F32(f32) f32 unwrap_f32 f32::from_bits(*e)) (F32(f32) f32 unwrap_f32 f32::from_bits(*e))
(F64(f64) f64 unwrap_f64 f64::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) (V128(u128) v128 unwrap_v128 *e)
} }
@@ -160,7 +177,8 @@ impl Val {
pub(crate) fn comes_from_same_store(&self, store: &Store) -> bool { pub(crate) fn comes_from_same_store(&self, store: &Store) -> bool {
match self { 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 // TODO: need to implement this once we actually finalize what
// `externref` will look like and it's actually implemented to pass it // `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 { impl From<Func> for Val {
fn from(val: Func) -> Val { fn from(val: Func) -> Val {
Val::FuncRef(val) Val::FuncRef(Some(val))
} }
} }
pub(crate) fn into_checked_anyfunc( pub(crate) fn into_checked_anyfunc(
val: Val, val: Val,
store: &Store, store: &Store,
) -> Result<wasmtime_runtime::VMCallerCheckedAnyfunc> { ) -> Result<*mut wasmtime_runtime::VMCallerCheckedAnyfunc> {
if !val.comes_from_same_store(store) { if !val.comes_from_same_store(store) {
bail!("cross-`Store` values are not supported"); bail!("cross-`Store` values are not supported");
} }
Ok(match val { Ok(match val {
Val::ExternRef(None) => wasmtime_runtime::VMCallerCheckedAnyfunc { Val::FuncRef(None) => ptr::null_mut(),
func_ptr: ptr::null(), Val::FuncRef(Some(f)) => f.caller_checked_anyfunc().as_ptr(),
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,
}
}
_ => bail!("val is not funcref"), _ => bail!("val is not funcref"),
}) })
} }
pub(crate) fn from_checked_anyfunc( pub(crate) unsafe fn from_checked_anyfunc(
item: wasmtime_runtime::VMCallerCheckedAnyfunc, anyfunc: *mut wasmtime_runtime::VMCallerCheckedAnyfunc,
store: &Store, store: &Store,
) -> Val { ) -> Val {
if item.type_index == wasmtime_runtime::VMSharedSignatureIndex::default() { let anyfunc = match NonNull::new(anyfunc) {
return Val::ExternRef(None); None => return Val::FuncRef(None),
} Some(f) => f,
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,
}; };
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 instance = store.existing_instance_handle(instance_handle);
let f = Func::from_wasmtime_function(export, instance); let f = Func::from_wasmtime_function(export, instance);
Val::FuncRef(f) Val::FuncRef(Some(f))
} }

View File

@@ -35,7 +35,7 @@ pub fn link_spectest(linker: &mut Linker) -> Result<()> {
linker.define("spectest", "global_f64", g)?; linker.define("spectest", "global_f64", g)?;
let ty = TableType::new(ValType::FuncRef, Limits::new(10, Some(20))); let ty = TableType::new(ValType::FuncRef, Limits::new(10, Some(20)));
let table = Table::new(linker.store(), ty, Val::ExternRef(None))?; let table = Table::new(linker.store(), ty, Val::FuncRef(None))?;
linker.define("spectest", "table", table)?; linker.define("spectest", "table", table)?;
let ty = MemoryType::new(Limits::new(1, Some(2))); let ty = MemoryType::new(Limits::new(1, Some(2)));

View File

@@ -23,6 +23,7 @@ fn runtime_value(store: &Store, v: &wast::Expression<'_>) -> Result<Val> {
F64Const(x) => Val::F64(x.bits), F64Const(x) => Val::F64(x.bits),
V128Const(x) => Val::V128(u128::from_le_bytes(x.to_le_bytes())), V128Const(x) => Val::V128(u128::from_le_bytes(x.to_le_bytes())),
RefNull(RefType::Extern) => Val::ExternRef(None), RefNull(RefType::Extern) => Val::ExternRef(None),
RefNull(RefType::Func) => Val::FuncRef(None),
RefExtern(x) => Val::ExternRef(Some(ExternRef::new(store, *x))), RefExtern(x) => Val::ExternRef(Some(ExternRef::new(store, *x))),
other => bail!("couldn't convert {:?} to a runtime value", other), other => bail!("couldn't convert {:?} to a runtime value", other),
}) })
@@ -420,6 +421,7 @@ fn val_matches(actual: &Val, expected: &wast::AssertExpression) -> Result<bool>
false false
} }
} }
(Val::FuncRef(x), wast::AssertExpression::RefNull(wast::RefType::Func)) => x.is_none(),
_ => bail!( _ => bail!(
"don't know how to compare {:?} and {:?} yet", "don't know how to compare {:?} and {:?} yet",
actual, actual,

View File

@@ -28,27 +28,27 @@ fn bad_tables() {
// get out of bounds // get out of bounds
let ty = TableType::new(ValType::FuncRef, Limits::new(0, Some(1))); let ty = TableType::new(ValType::FuncRef, Limits::new(0, Some(1)));
let t = Table::new(&Store::default(), ty.clone(), Val::ExternRef(None)).unwrap(); let t = Table::new(&Store::default(), ty.clone(), Val::FuncRef(None)).unwrap();
assert!(t.get(0).is_none()); assert!(t.get(0).is_none());
assert!(t.get(u32::max_value()).is_none()); assert!(t.get(u32::max_value()).is_none());
// set out of bounds or wrong type // set out of bounds or wrong type
let ty = TableType::new(ValType::FuncRef, Limits::new(1, Some(1))); let ty = TableType::new(ValType::FuncRef, Limits::new(1, Some(1)));
let t = Table::new(&Store::default(), ty.clone(), Val::ExternRef(None)).unwrap(); let t = Table::new(&Store::default(), ty.clone(), Val::FuncRef(None)).unwrap();
assert!(t.set(0, Val::I32(0)).is_err()); assert!(t.set(0, Val::I32(0)).is_err());
assert!(t.set(0, Val::ExternRef(None)).is_ok()); assert!(t.set(0, Val::FuncRef(None)).is_ok());
assert!(t.set(1, Val::ExternRef(None)).is_err()); assert!(t.set(1, Val::FuncRef(None)).is_err());
// grow beyond max // grow beyond max
let ty = TableType::new(ValType::FuncRef, Limits::new(1, Some(1))); let ty = TableType::new(ValType::FuncRef, Limits::new(1, Some(1)));
let t = Table::new(&Store::default(), ty.clone(), Val::ExternRef(None)).unwrap(); let t = Table::new(&Store::default(), ty.clone(), Val::FuncRef(None)).unwrap();
assert!(t.grow(0, Val::ExternRef(None)).is_ok()); assert!(t.grow(0, Val::FuncRef(None)).is_ok());
assert!(t.grow(1, Val::ExternRef(None)).is_err()); assert!(t.grow(1, Val::FuncRef(None)).is_err());
assert_eq!(t.size(), 1); assert_eq!(t.size(), 1);
// grow wrong type // grow wrong type
let ty = TableType::new(ValType::FuncRef, Limits::new(1, Some(2))); let ty = TableType::new(ValType::FuncRef, Limits::new(1, Some(2)));
let t = Table::new(&Store::default(), ty.clone(), Val::ExternRef(None)).unwrap(); let t = Table::new(&Store::default(), ty.clone(), Val::FuncRef(None)).unwrap();
assert!(t.grow(1, Val::I32(0)).is_err()); assert!(t.grow(1, Val::I32(0)).is_err());
assert_eq!(t.size(), 1); assert_eq!(t.size(), 1);
} }
@@ -69,7 +69,7 @@ fn cross_store() -> anyhow::Result<()> {
let ty = MemoryType::new(Limits::new(1, None)); let ty = MemoryType::new(Limits::new(1, None));
let memory = Memory::new(&store2, ty); let memory = Memory::new(&store2, ty);
let ty = TableType::new(ValType::FuncRef, Limits::new(1, None)); let ty = TableType::new(ValType::FuncRef, Limits::new(1, None));
let table = Table::new(&store2, ty, Val::ExternRef(None))?; let table = Table::new(&store2, ty, Val::FuncRef(None))?;
let need_func = Module::new(&engine, r#"(module (import "" "" (func)))"#)?; let need_func = Module::new(&engine, r#"(module (import "" "" (func)))"#)?;
assert!(Instance::new(&store1, &need_func, &[func.into()]).is_err()); assert!(Instance::new(&store1, &need_func, &[func.into()]).is_err());
@@ -85,8 +85,8 @@ fn cross_store() -> anyhow::Result<()> {
// ============ Cross-store globals ============== // ============ Cross-store globals ==============
let store1val = Val::FuncRef(Func::wrap(&store1, || {})); let store1val = Val::FuncRef(Some(Func::wrap(&store1, || {})));
let store2val = Val::FuncRef(Func::wrap(&store2, || {})); let store2val = Val::FuncRef(Some(Func::wrap(&store2, || {})));
let ty = GlobalType::new(ValType::FuncRef, Mutability::Var); let ty = GlobalType::new(ValType::FuncRef, Mutability::Var);
assert!(Global::new(&store2, ty.clone(), store1val.clone()).is_err()); assert!(Global::new(&store2, ty.clone(), store1val.clone()).is_err());

85
tests/all/funcref.rs Normal file
View File

@@ -0,0 +1,85 @@
use super::ref_types_module;
use wasmtime::*;
#[test]
fn pass_funcref_in_and_out_of_wasm() -> anyhow::Result<()> {
let (store, module) = ref_types_module(
r#"
(module
(func (export "func") (param funcref) (result funcref)
local.get 0
)
)
"#,
)?;
let instance = Instance::new(&store, &module, &[])?;
let func = instance.get_func("func").unwrap();
// Pass in a non-null funcref.
{
let results = func.call(&[Val::FuncRef(Some(func.clone()))])?;
assert_eq!(results.len(), 1);
// Can't compare `Func` for equality, so this is the best we can do here.
let result_func = results[0].unwrap_funcref().unwrap();
assert_eq!(func.ty(), result_func.ty());
}
// Pass in a null funcref.
{
let results = func.call(&[Val::FuncRef(None)])?;
assert_eq!(results.len(), 1);
let result_func = results[0].unwrap_funcref();
assert!(result_func.is_none());
}
// Pass in a `funcref` from another instance.
{
let other_instance = Instance::new(&store, &module, &[])?;
let other_instance_func = other_instance.get_func("func").unwrap();
let results = func.call(&[Val::FuncRef(Some(other_instance_func.clone()))])?;
assert_eq!(results.len(), 1);
// Can't compare `Func` for equality, so this is the best we can do here.
let result_func = results[0].unwrap_funcref().unwrap();
assert_eq!(other_instance_func.ty(), result_func.ty());
}
// Passing in a `funcref` from another store fails.
{
let (other_store, other_module) = ref_types_module(r#"(module (func (export "f")))"#)?;
let other_store_instance = Instance::new(&other_store, &other_module, &[])?;
let f = other_store_instance.get_func("f").unwrap();
assert!(func.call(&[Val::FuncRef(Some(f))]).is_err());
}
Ok(())
}
#[test]
fn receive_null_funcref_from_wasm() -> anyhow::Result<()> {
let (store, module) = ref_types_module(
r#"
(module
(func (export "get-null") (result funcref)
ref.null func
)
)
"#,
)?;
let instance = Instance::new(&store, &module, &[])?;
let get_null = instance.get_func("get-null").unwrap();
let results = get_null.call(&[])?;
assert_eq!(results.len(), 1);
let result_func = results[0].unwrap_funcref();
assert!(result_func.is_none());
Ok(())
}

View File

@@ -1,21 +1,8 @@
use super::ref_types_module;
use std::cell::Cell; use std::cell::Cell;
use std::rc::Rc; use std::rc::Rc;
use wasmtime::*; use wasmtime::*;
fn ref_types_module(source: &str) -> anyhow::Result<(Store, Module)> {
let _ = env_logger::try_init();
let mut config = Config::new();
config.wasm_reference_types(true);
let engine = Engine::new(&config);
let store = Store::new(&engine);
let module = Module::new(&engine, source)?;
Ok((store, module))
}
#[test] #[test]
fn smoke_test_gc() -> anyhow::Result<()> { fn smoke_test_gc() -> anyhow::Result<()> {
let (store, module) = ref_types_module( let (store, module) = ref_types_module(

View File

@@ -23,6 +23,7 @@ fn test_invoke_func_via_table() -> Result<()> {
.unwrap() .unwrap()
.funcref() .funcref()
.unwrap() .unwrap()
.unwrap()
.clone(); .clone();
let result = f.call(&[]).unwrap(); let result = f.call(&[]).unwrap();
assert_eq!(result[0].unwrap_i64(), 42); assert_eq!(result[0].unwrap_i64(), 42);

View File

@@ -59,11 +59,11 @@ fn link_twice_bad() -> Result<()> {
// tables // tables
let ty = TableType::new(ValType::FuncRef, Limits::new(1, None)); let ty = TableType::new(ValType::FuncRef, Limits::new(1, None));
let table = Table::new(&store, ty, Val::ExternRef(None))?; let table = Table::new(&store, ty, Val::FuncRef(None))?;
linker.define("", "", table.clone())?; linker.define("", "", table.clone())?;
assert!(linker.define("", "", table.clone()).is_err()); assert!(linker.define("", "", table.clone()).is_err());
let ty = TableType::new(ValType::FuncRef, Limits::new(2, None)); let ty = TableType::new(ValType::FuncRef, Limits::new(2, None));
let table = Table::new(&store, ty, Val::ExternRef(None))?; let table = Table::new(&store, ty, Val::FuncRef(None))?;
assert!(linker.define("", "", table.clone()).is_err()); assert!(linker.define("", "", table.clone()).is_err());
Ok(()) Ok(())
} }

View File

@@ -21,4 +21,26 @@ mod wast;
// TODO(#1886): Cranelift only supports reference types on x64. // TODO(#1886): Cranelift only supports reference types on x64.
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
mod funcref;
#[cfg(target_arch = "x86_64")]
mod gc; mod gc;
/// A helper to compile a module in a new store with reference types enabled.
#[cfg(target_arch = "x86_64")]
pub(crate) fn ref_types_module(
source: &str,
) -> anyhow::Result<(wasmtime::Store, wasmtime::Module)> {
use wasmtime::*;
let _ = env_logger::try_init();
let mut config = Config::new();
config.wasm_reference_types(true);
let engine = Engine::new(&config);
let store = Store::new(&engine);
let module = Module::new(&engine, source)?;
Ok((store, module))
}

View File

@@ -4,9 +4,9 @@ use wasmtime::*;
fn get_none() { fn get_none() {
let store = Store::default(); let store = Store::default();
let ty = TableType::new(ValType::FuncRef, Limits::new(1, None)); let ty = TableType::new(ValType::FuncRef, Limits::new(1, None));
let table = Table::new(&store, ty, Val::ExternRef(None)).unwrap(); let table = Table::new(&store, ty, Val::FuncRef(None)).unwrap();
match table.get(0) { match table.get(0) {
Some(Val::ExternRef(None)) => {} Some(Val::FuncRef(None)) => {}
_ => panic!(), _ => panic!(),
} }
assert!(table.get(1).is_none()); assert!(table.get(1).is_none());

View File

@@ -11,10 +11,10 @@ fn use_func_after_drop() -> Result<()> {
assert_eq!(closed_over_data, "abcd"); assert_eq!(closed_over_data, "abcd");
}); });
let ty = TableType::new(ValType::FuncRef, Limits::new(1, None)); let ty = TableType::new(ValType::FuncRef, Limits::new(1, None));
table = Table::new(&store, ty, Val::ExternRef(None))?; table = Table::new(&store, ty, Val::FuncRef(None))?;
table.set(0, func.into())?; table.set(0, func.into())?;
} }
let func = table.get(0).unwrap().funcref().unwrap().clone(); let func = table.get(0).unwrap().funcref().unwrap().unwrap().clone();
let func = func.get0::<()>()?; let func = func.get0::<()>()?;
func()?; func()?;
Ok(()) Ok(())

View File

@@ -0,0 +1,17 @@
(module
(func (export "func_is_null") (param funcref) (result i32)
(ref.is_null func (local.get 0))
)
(func (export "func_is_null_with_non_null_funcref") (result i32)
(call 0 (ref.func 0))
)
(func (export "extern_is_null") (param externref) (result i32)
(ref.is_null extern (local.get 0))
)
)
(assert_return (invoke "func_is_null" (ref.null func)) (i32.const 1))
(assert_return (invoke "func_is_null_with_non_null_funcref") (i32.const 0))
(assert_return (invoke "extern_is_null" (ref.null extern)) (i32.const 1))
(assert_return (invoke "extern_is_null" (ref.extern 1)) (i32.const 0))

View File

@@ -0,0 +1,13 @@
(module
(table $t 0 funcref)
(func (export "size") (result i32)
(table.size $t)
)
(func $f (export "grow-by-1") (result i32)
(table.grow $t (ref.func $f) (i32.const 1))
)
)
(assert_return (invoke "size") (i32.const 0))
(assert_return (invoke "grow-by-1") (i32.const 0))
(assert_return (invoke "size") (i32.const 1))