From fb6913cd18fe57ac931d542f73e6d912a2a198ae Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Thu, 2 Jul 2020 10:41:38 -0700 Subject: [PATCH 1/2] wasmtime: Implement `global.{get,set}` for externref globals We use libcalls to implement these -- unlike `table.{get,set}`, for which we create inline JIT fast paths -- because no known toolchain actually uses externref globals. Part of #929 --- build.rs | 6 +- crates/environ/src/func_environ.rs | 108 +++++++++++++----- crates/runtime/src/instance.rs | 27 ++++- crates/runtime/src/libcalls.rs | 50 +++++++- crates/runtime/src/vmcontext.rs | 35 ++++++ crates/wasmtime/src/externals.rs | 24 ++++ tests/all/gc.rs | 94 +++++++++++++++ .../mutable_externref_globals.wast | 13 +++ 8 files changed, 321 insertions(+), 36 deletions(-) create mode 100644 tests/misc_testsuite/reference-types/mutable_externref_globals.wast diff --git a/build.rs b/build.rs index fa89812ed91b..a55f3b5da16f 100644 --- a/build.rs +++ b/build.rs @@ -201,11 +201,7 @@ fn ignore(testsuite: &str, testname: &str, strategy: &str) -> bool { ("simd", "simd_splat") => return true, // FIXME Unsupported feature: proposed SIMD operator I32x4TruncSatF32x4S // Still working on implementing these. See #929. - ("reference_types", "global") - | ("reference_types", "linking") - | ("reference_types", "ref_func") - | ("reference_types", "ref_null") - | ("reference_types", "table_fill") => { + ("reference_types", "table_fill") => { return true; } diff --git a/crates/environ/src/func_environ.rs b/crates/environ/src/func_environ.rs index f1e738748232..e325d7696963 100644 --- a/crates/environ/src/func_environ.rs +++ b/crates/environ/src/func_environ.rs @@ -175,6 +175,10 @@ declare_builtin_functions! { /// Returns an index to do a GC and then insert a `VMExternRef` into the /// `VMExternRefActivationsTable`. activations_table_insert_with_gc(vmctx, reference) -> (); + /// Returns an index for Wasm's `global.get` instruction for `externref`s. + externref_global_get(vmctx, i32) -> (reference); + /// Returns an index for Wasm's `global.get` instruction for `externref`s. + externref_global_set(vmctx, i32, reference) -> (); } impl BuiltinFunctionIndex { @@ -432,6 +436,28 @@ impl<'module_environment> FuncEnvironment<'module_environment> { new_ref_count } + + fn get_global_location( + &mut self, + func: &mut ir::Function, + index: GlobalIndex, + ) -> (ir::GlobalValue, i32) { + let pointer_type = self.pointer_type(); + let vmctx = self.vmctx(func); + if let Some(def_index) = self.module.defined_global_index(index) { + let offset = i32::try_from(self.offsets.vmctx_vmglobal_definition(def_index)).unwrap(); + (vmctx, offset) + } else { + let from_offset = self.offsets.vmctx_vmglobal_import_from(index); + let global = func.create_global_value(ir::GlobalValueData::Load { + base: vmctx, + offset: Offset32::new(i32::try_from(from_offset).unwrap()), + global_type: pointer_type, + readonly: true, + }); + (global, 0) + } + } } // TODO: This is necessary as if Lightbeam used `FuncEnvironment` directly it would cause @@ -1042,19 +1068,56 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m fn translate_custom_global_get( &mut self, - _: cranelift_codegen::cursor::FuncCursor<'_>, - _: cranelift_wasm::GlobalIndex, + mut pos: cranelift_codegen::cursor::FuncCursor<'_>, + index: cranelift_wasm::GlobalIndex, ) -> WasmResult { - unreachable!("we don't make any custom globals") + debug_assert_eq!( + self.module.globals[index].wasm_ty, + WasmType::ExternRef, + "We only use GlobalVariable::Custom for externref" + ); + + let builtin_index = BuiltinFunctionIndex::externref_global_get(); + let builtin_sig = self + .builtin_function_signatures + .externref_global_get(&mut pos.func); + + let (vmctx, builtin_addr) = + self.translate_load_builtin_function_address(&mut pos, builtin_index); + + let global_index_arg = pos.ins().iconst(I32, index.as_u32() as i64); + let call_inst = + pos.ins() + .call_indirect(builtin_sig, builtin_addr, &[vmctx, global_index_arg]); + + Ok(pos.func.dfg.first_result(call_inst)) } fn translate_custom_global_set( &mut self, - _: cranelift_codegen::cursor::FuncCursor<'_>, - _: cranelift_wasm::GlobalIndex, - _: ir::Value, + mut pos: cranelift_codegen::cursor::FuncCursor<'_>, + index: cranelift_wasm::GlobalIndex, + value: ir::Value, ) -> WasmResult<()> { - unreachable!("we don't make any custom globals") + debug_assert_eq!( + self.module.globals[index].wasm_ty, + WasmType::ExternRef, + "We only use GlobalVariable::Custom for externref" + ); + + let builtin_index = BuiltinFunctionIndex::externref_global_set(); + let builtin_sig = self + .builtin_function_signatures + .externref_global_set(&mut pos.func); + + let (vmctx, builtin_addr) = + self.translate_load_builtin_function_address(&mut pos, builtin_index); + + let global_index_arg = pos.ins().iconst(I32, index.as_u32() as i64); + pos.ins() + .call_indirect(builtin_sig, builtin_addr, &[vmctx, global_index_arg, value]); + + Ok(()) } fn make_heap(&mut self, func: &mut ir::Function, index: MemoryIndex) -> WasmResult { @@ -1141,28 +1204,19 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m func: &mut ir::Function, index: GlobalIndex, ) -> WasmResult { - let pointer_type = self.pointer_type(); - - let (ptr, offset) = { - let vmctx = self.vmctx(func); - if let Some(def_index) = self.module.defined_global_index(index) { - let offset = - i32::try_from(self.offsets.vmctx_vmglobal_definition(def_index)).unwrap(); - (vmctx, offset) - } else { - let from_offset = self.offsets.vmctx_vmglobal_import_from(index); - let global = func.create_global_value(ir::GlobalValueData::Load { - base: vmctx, - offset: Offset32::new(i32::try_from(from_offset).unwrap()), - global_type: pointer_type, - readonly: true, - }); - (global, 0) - } - }; + // Although `ExternRef`s live at the same memory location as any other + // type of global at the same index would, getting or setting them + // requires ref counting barriers. Therefore, we need to use + // `GlobalVariable::Custom`, as that is the only kind of + // `GlobalVariable` for which `cranelift-wasm` supports custom access + // translation. + if self.module.globals[index].wasm_ty == WasmType::ExternRef { + return Ok(GlobalVariable::Custom); + } + let (gv, offset) = self.get_global_location(func, index); Ok(GlobalVariable::Memory { - gv: ptr, + gv, offset: offset.into(), ty: self.module.globals[index].ty, }) diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs index 4395fefd467b..ea5045ea83da 100644 --- a/crates/runtime/src/instance.rs +++ b/crates/runtime/src/instance.rs @@ -29,7 +29,7 @@ use wasmtime_environ::entity::{packed_option::ReservedValue, BoxedSlice, EntityR use wasmtime_environ::wasm::{ DataIndex, DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, ElemIndex, FuncIndex, GlobalIndex, GlobalInit, MemoryIndex, SignatureIndex, TableElementType, - TableIndex, + TableIndex, WasmType, }; use wasmtime_environ::{ir, DataInitializer, EntityIndex, Module, TableElements, VMOffsets}; @@ -226,6 +226,21 @@ impl Instance { unsafe { self.globals_ptr().add(index) } } + /// Get a raw pointer to the global at the given index regardless whether it + /// is defined locally or imported from another module. + /// + /// Panics if the index is out of bound or is the reserved value. + pub(crate) fn defined_or_imported_global_ptr( + &self, + index: GlobalIndex, + ) -> *mut VMGlobalDefinition { + if let Some(index) = self.module.local.defined_global_index(index) { + self.global_ptr(index) + } else { + self.imported_global(index).from + } + } + /// Return a pointer to the `VMGlobalDefinition`s. fn globals_ptr(&self) -> *mut VMGlobalDefinition { unsafe { self.vmctx_plus_offset(self.offsets.vmctx_globals_begin()) } @@ -1390,8 +1405,16 @@ fn initialize_globals(instance: &Instance) { }; *to = from; } + GlobalInit::RefFunc(f) => { + *(*to).as_anyfunc_mut() = instance.get_caller_checked_anyfunc(f).unwrap() + as *const VMCallerCheckedAnyfunc; + } + GlobalInit::RefNullConst => match global.wasm_ty { + WasmType::FuncRef => *(*to).as_anyfunc_mut() = ptr::null(), + WasmType::ExternRef => *(*to).as_externref_mut() = None, + ty => panic!("unsupported reference type for global: {:?}", ty), + }, GlobalInit::Import => panic!("locally-defined global initialized as import"), - GlobalInit::RefNullConst | GlobalInit::RefFunc(_) => unimplemented!(), } } } diff --git a/crates/runtime/src/libcalls.rs b/crates/runtime/src/libcalls.rs index eb62d21311cf..c1ff290656d4 100644 --- a/crates/runtime/src/libcalls.rs +++ b/crates/runtime/src/libcalls.rs @@ -60,9 +60,11 @@ use crate::externref::VMExternRef; use crate::table::Table; use crate::traphandlers::raise_lib_trap; use crate::vmcontext::{VMCallerCheckedAnyfunc, VMContext}; -use std::ptr::NonNull; +use std::mem; +use std::ptr::{self, NonNull}; use wasmtime_environ::wasm::{ - DataIndex, DefinedMemoryIndex, ElemIndex, MemoryIndex, TableElementType, TableIndex, + DataIndex, DefinedMemoryIndex, ElemIndex, GlobalIndex, MemoryIndex, TableElementType, + TableIndex, }; /// Implementation of f32.ceil @@ -430,3 +432,47 @@ pub unsafe extern "C" fn wasmtime_activations_table_insert_with_gc( let registry = &**instance.stack_map_registry(); activations_table.insert_with_gc(externref, registry); } + +/// Perform a Wasm `global.get` for `externref` globals. +pub unsafe extern "C" fn wasmtime_externref_global_get( + vmctx: *mut VMContext, + index: u32, +) -> *mut u8 { + let index = GlobalIndex::from_u32(index); + let instance = (&mut *vmctx).instance(); + let global = instance.defined_or_imported_global_ptr(index); + match (*global).as_externref().clone() { + None => ptr::null_mut(), + Some(externref) => { + let raw = externref.as_raw(); + let activations_table = &**instance.externref_activations_table(); + let registry = &**instance.stack_map_registry(); + activations_table.insert_with_gc(externref, registry); + raw + } + } +} + +/// Perform a Wasm `global.set` for `externref` globals. +pub unsafe extern "C" fn wasmtime_externref_global_set( + vmctx: *mut VMContext, + index: u32, + externref: *mut u8, +) { + let externref = if externref.is_null() { + None + } else { + Some(VMExternRef::clone_from_raw(externref)) + }; + + let index = GlobalIndex::from_u32(index); + let instance = (&mut *vmctx).instance(); + let global = instance.defined_or_imported_global_ptr(index); + + // Swap the new `externref` value into the global before we drop the old + // value. This protects against an `externref` with a `Drop` implementation + // that calls back into Wasm and touches this global again (we want to avoid + // it observing a halfway-deinitialized value). + let old = mem::replace((*global).as_externref_mut(), externref); + drop(old); +} diff --git a/crates/runtime/src/vmcontext.rs b/crates/runtime/src/vmcontext.rs index c98c75b362da..971bd1eb7299 100644 --- a/crates/runtime/src/vmcontext.rs +++ b/crates/runtime/src/vmcontext.rs @@ -1,6 +1,7 @@ //! This file declares `VMContext` and several related structs which contain //! fields that compiled wasm code accesses directly. +use crate::externref::VMExternRef; use crate::instance::Instance; use std::any::Any; use std::ptr::NonNull; @@ -267,6 +268,7 @@ pub struct VMGlobalDefinition { #[cfg(test)] mod test_vmglobal_definition { use super::VMGlobalDefinition; + use crate::externref::VMExternRef; use more_asserts::assert_ge; use std::mem::{align_of, size_of}; use wasmtime_environ::{Module, VMOffsets}; @@ -296,6 +298,11 @@ mod test_vmglobal_definition { let offsets = VMOffsets::new(size_of::<*mut u8>() as u8, &module.local); assert_eq!(offsets.vmctx_globals_begin() % 16, 0); } + + #[test] + fn check_vmglobal_can_contain_externref() { + assert!(size_of::() <= size_of::()); + } } impl VMGlobalDefinition { @@ -423,6 +430,30 @@ impl VMGlobalDefinition { pub unsafe fn as_u128_bits_mut(&mut self) -> &mut [u8; 16] { &mut *(self.storage.as_mut().as_mut_ptr() as *mut [u8; 16]) } + + /// Return a reference to the value as an externref. + #[allow(clippy::cast_ptr_alignment)] + pub unsafe fn as_externref(&self) -> &Option { + &*(self.storage.as_ref().as_ptr() as *const Option) + } + + /// Return a mutable reference to the value as an externref. + #[allow(clippy::cast_ptr_alignment)] + pub unsafe fn as_externref_mut(&mut self) -> &mut Option { + &mut *(self.storage.as_mut().as_mut_ptr() as *mut Option) + } + + /// Return a reference to the value as an anyfunc. + #[allow(clippy::cast_ptr_alignment)] + pub unsafe fn as_anyfunc(&self) -> *const VMCallerCheckedAnyfunc { + *(self.storage.as_ref().as_ptr() as *const *const VMCallerCheckedAnyfunc) + } + + /// Return a mutable reference to the value as an anyfunc. + #[allow(clippy::cast_ptr_alignment)] + pub unsafe fn as_anyfunc_mut(&mut self) -> &mut *const VMCallerCheckedAnyfunc { + &mut *(self.storage.as_mut().as_mut_ptr() as *mut *const VMCallerCheckedAnyfunc) + } } /// An index into the shared signature registry, usable for checking signatures @@ -559,6 +590,10 @@ impl VMBuiltinFunctionsArray { wasmtime_drop_externref as usize; ptrs[BuiltinFunctionIndex::activations_table_insert_with_gc().index() as usize] = wasmtime_activations_table_insert_with_gc as usize; + ptrs[BuiltinFunctionIndex::externref_global_get().index() as usize] = + wasmtime_externref_global_get as usize; + ptrs[BuiltinFunctionIndex::externref_global_set().index() as usize] = + wasmtime_externref_global_set as usize; if cfg!(debug_assertions) { for i in 0..ptrs.len() { diff --git a/crates/wasmtime/src/externals.rs b/crates/wasmtime/src/externals.rs index 93c2ddc66544..03b4ef442262 100644 --- a/crates/wasmtime/src/externals.rs +++ b/crates/wasmtime/src/externals.rs @@ -7,6 +7,8 @@ use crate::{ ValType, }; use anyhow::{anyhow, bail, Result}; +use std::mem; +use std::ptr; use std::slice; use wasmtime_environ::wasm; use wasmtime_runtime::{self as runtime, InstanceHandle}; @@ -227,6 +229,15 @@ impl Global { ValType::I64 => Val::from(*definition.as_i64()), ValType::F32 => Val::F32(*definition.as_u32()), ValType::F64 => Val::F64(*definition.as_u64()), + ValType::ExternRef => Val::ExternRef( + definition + .as_externref() + .clone() + .map(|inner| ExternRef { inner }), + ), + ValType::FuncRef => { + from_checked_anyfunc(definition.as_anyfunc() as *mut _, &self.instance.store) + } ty => unimplemented!("Global::get for {:?}", ty), } } @@ -256,6 +267,19 @@ impl Global { Val::I64(i) => *definition.as_i64_mut() = i, Val::F32(f) => *definition.as_u32_mut() = f, Val::F64(f) => *definition.as_u64_mut() = f, + Val::FuncRef(f) => { + *definition.as_anyfunc_mut() = f.map_or(ptr::null(), |f| { + f.caller_checked_anyfunc().as_ptr() as *const _ + }); + } + Val::ExternRef(x) => { + // In case the old value's `Drop` implementation is + // re-entrant and tries to touch this global again, do a + // replace, and then drop. This way no one can observe a + // halfway-deinitialized value. + let old = mem::replace(definition.as_externref_mut(), x.map(|x| x.inner)); + drop(old); + } _ => unimplemented!("Global::set for {:?}", val.ty()), } } diff --git a/tests/all/gc.rs b/tests/all/gc.rs index b2a47d31046d..3e19b31758c2 100644 --- a/tests/all/gc.rs +++ b/tests/all/gc.rs @@ -517,3 +517,97 @@ fn pass_externref_into_wasm_during_destructor_in_gc() -> anyhow::Result<()> { } } } + +#[test] +fn gc_on_drop_in_mutable_externref_global() -> anyhow::Result<()> { + let (store, module) = ref_types_module( + r#" + (module + (global $g (mut externref) (ref.null extern)) + + (func (export "set-g") (param externref) + (global.set $g (local.get 0)) + ) + ) + "#, + )?; + + let instance = Instance::new(&store, &module, &[])?; + let set_g = instance.get_func("set-g").unwrap(); + + let gc_count = Rc::new(Cell::new(0)); + + // Put a `GcOnDrop` into the global. + { + let args = vec![Val::ExternRef(Some(ExternRef::new(GcOnDrop { + store: store.clone(), + gc_count: gc_count.clone(), + })))]; + set_g.call(&args)?; + } + + // Remove the `GcOnDrop` from the `VMExternRefActivationsTable`. + store.gc(); + + // Overwrite the `GcOnDrop` global value, causing it to be dropped, and + // triggering a GC. + assert_eq!(gc_count.get(), 0); + set_g.call(&[Val::ExternRef(None)])?; + assert_eq!(gc_count.get(), 1); + + Ok(()) +} + +#[test] +fn touch_own_externref_global_on_drop() -> anyhow::Result<()> { + let (store, module) = ref_types_module( + r#" + (module + (global $g (export "g") (mut externref) (ref.null extern)) + + (func (export "set-g") (param externref) + (global.set $g (local.get 0)) + ) + ) + "#, + )?; + + let instance = Instance::new(&store, &module, &[])?; + let g = instance.get_global("g").unwrap(); + let set_g = instance.get_func("set-g").unwrap(); + + let touched = Rc::new(Cell::new(false)); + + { + let args = vec![Val::ExternRef(Some(ExternRef::new(TouchGlobalOnDrop { + g, + touched: touched.clone(), + })))]; + set_g.call(&args)?; + } + + // Remove the `TouchGlobalOnDrop` from the `VMExternRefActivationsTable`. + store.gc(); + + assert!(!touched.get()); + set_g.call(&[Val::ExternRef(Some(ExternRef::new("hello".to_string())))])?; + assert!(touched.get()); + + return Ok(()); + + struct TouchGlobalOnDrop { + g: Global, + touched: Rc>, + } + + impl Drop for TouchGlobalOnDrop { + fn drop(&mut self) { + // From the `Drop` implementation, we see the new global value, not + // `self`. + let r = self.g.get().unwrap_externref().unwrap(); + assert!(r.data().is::()); + assert_eq!(r.data().downcast_ref::().unwrap(), "hello"); + self.touched.set(true); + } + } +} diff --git a/tests/misc_testsuite/reference-types/mutable_externref_globals.wast b/tests/misc_testsuite/reference-types/mutable_externref_globals.wast new file mode 100644 index 000000000000..5d4b5f839d8f --- /dev/null +++ b/tests/misc_testsuite/reference-types/mutable_externref_globals.wast @@ -0,0 +1,13 @@ +;; This test contains the changes in +;; https://github.com/WebAssembly/reference-types/pull/104, and can be deleted +;; once that merges and we update our upstream tests. + +(module + (global $mr (mut externref) (ref.null extern)) + (func (export "get-mr") (result externref) (global.get $mr)) + (func (export "set-mr") (param externref) (global.set $mr (local.get 0))) +) + +(assert_return (invoke "get-mr") (ref.null extern)) +(assert_return (invoke "set-mr" (ref.extern 10))) +(assert_return (invoke "get-mr") (ref.extern 10)) From b04bd00c256112c06c31186effbdd777e7f8331f Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Thu, 2 Jul 2020 12:57:12 -0700 Subject: [PATCH 2/2] wasmtime: Enable `{extern,func}ref` globals in the API --- crates/runtime/src/lib.rs | 11 +++ crates/runtime/src/sig_registry.rs | 14 ++++ crates/wasmtime/src/func.rs | 14 ++-- crates/wasmtime/src/runtime.rs | 11 +++ .../wasmtime/src/trampoline/create_handle.rs | 7 +- crates/wasmtime/src/trampoline/func.rs | 22 +++--- crates/wasmtime/src/trampoline/global.rs | 57 +++++++++++++-- crates/wasmtime/src/trampoline/memory.rs | 1 + crates/wasmtime/src/trampoline/table.rs | 1 + crates/wasmtime/src/types.rs | 34 +++++---- tests/all/externals.rs | 70 +++++++++++++++++++ 11 files changed, 199 insertions(+), 43 deletions(-) diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs index c12021df2d0e..3384952c4299 100644 --- a/crates/runtime/src/lib.rs +++ b/crates/runtime/src/lib.rs @@ -67,3 +67,14 @@ pub fn ref_type() -> wasmtime_environ::ir::Type { unreachable!() } } + +/// The Cranelift IR type used for pointer types for this target architecture. +pub fn pointer_type() -> wasmtime_environ::ir::Type { + if cfg!(target_pointer_width = "32") { + wasmtime_environ::ir::types::I32 + } else if cfg!(target_pointer_width = "64") { + wasmtime_environ::ir::types::I64 + } else { + unreachable!() + } +} diff --git a/crates/runtime/src/sig_registry.rs b/crates/runtime/src/sig_registry.rs index a0427ca37d5c..30e5514bbdfe 100644 --- a/crates/runtime/src/sig_registry.rs +++ b/crates/runtime/src/sig_registry.rs @@ -65,4 +65,18 @@ impl SignatureRegistry { pub fn lookup_wasm(&self, idx: VMSharedSignatureIndex) -> Option { self.index2wasm.get(&idx).cloned() } + + /// Looks up both a shared Wasm function signature and its associated native + /// `ir::Signature` within this registry. + /// + /// Note that for this operation to be semantically correct the `idx` must + /// have previously come from a call to `register` of this same object. + pub fn lookup_wasm_and_native_signatures( + &self, + idx: VMSharedSignatureIndex, + ) -> Option<(WasmFuncType, ir::Signature)> { + let wasm = self.lookup_wasm(idx)?; + let native = self.lookup_native(idx)?; + Some((wasm, native)) + } } diff --git a/crates/wasmtime/src/func.rs b/crates/wasmtime/src/func.rs index 9c2081846f2d..a9f0b9a8cb94 100644 --- a/crates/wasmtime/src/func.rs +++ b/crates/wasmtime/src/func.rs @@ -9,7 +9,8 @@ use std::panic::{self, AssertUnwindSafe}; use std::ptr::{self, NonNull}; use std::rc::Weak; use wasmtime_runtime::{ - raise_user_trap, Export, InstanceHandle, VMContext, VMFunctionBody, VMTrampoline, + raise_user_trap, Export, InstanceHandle, VMContext, VMFunctionBody, VMSharedSignatureIndex, + VMTrampoline, }; /// A WebAssembly function which can be called. @@ -486,19 +487,20 @@ impl Func { func.into_func(store) } + pub(crate) fn sig_index(&self) -> VMSharedSignatureIndex { + unsafe { self.export.anyfunc.as_ref().type_index } + } + /// Returns the underlying wasm type that this `Func` has. 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(unsafe { self.export.anyfunc.as_ref().type_index }); + let wft = self.instance.store.lookup_signature(self.sig_index()); // This is only called with `Export::Function`, and since it's coming // from wasmtime_runtime itself we should support all the types coming // out of it, so assert such here. - FuncType::from_wasm_func_type(&sig).expect("core wasm signature should be supported") + FuncType::from_wasm_func_type(&wft).expect("core wasm signature should be supported") } /// Returns the number of parameters that this function takes. diff --git a/crates/wasmtime/src/runtime.rs b/crates/wasmtime/src/runtime.rs index 2c301c8352a8..9e6e6905078a 100644 --- a/crates/wasmtime/src/runtime.rs +++ b/crates/wasmtime/src/runtime.rs @@ -900,6 +900,17 @@ impl Store { .expect("failed to lookup signature") } + pub(crate) fn lookup_wasm_and_native_signatures( + &self, + sig_index: VMSharedSignatureIndex, + ) -> (wasm::WasmFuncType, ir::Signature) { + self.inner + .signatures + .borrow() + .lookup_wasm_and_native_signatures(sig_index) + .expect("failed to lookup signature") + } + pub(crate) fn register_signature( &self, wasm_sig: wasm::WasmFuncType, diff --git a/crates/wasmtime/src/trampoline/create_handle.rs b/crates/wasmtime/src/trampoline/create_handle.rs index e97773318d5f..b2a4b219e37c 100644 --- a/crates/wasmtime/src/trampoline/create_handle.rs +++ b/crates/wasmtime/src/trampoline/create_handle.rs @@ -7,11 +7,11 @@ use std::any::Any; use std::collections::HashMap; use std::sync::Arc; use wasmtime_environ::entity::PrimaryMap; -use wasmtime_environ::wasm::DefinedFuncIndex; +use wasmtime_environ::wasm::{DefinedFuncIndex, FuncIndex}; use wasmtime_environ::Module; use wasmtime_runtime::{ Imports, InstanceHandle, StackMapRegistry, VMExternRefActivationsTable, VMFunctionBody, - VMSharedSignatureIndex, VMTrampoline, + VMFunctionImport, VMSharedSignatureIndex, VMTrampoline, }; pub(crate) fn create_handle( @@ -20,9 +20,10 @@ pub(crate) fn create_handle( finished_functions: PrimaryMap, trampolines: HashMap, state: Box, + func_imports: PrimaryMap, ) -> Result { let imports = Imports::new( - PrimaryMap::new(), + func_imports, PrimaryMap::new(), PrimaryMap::new(), PrimaryMap::new(), diff --git a/crates/wasmtime/src/trampoline/func.rs b/crates/wasmtime/src/trampoline/func.rs index fd095e6d150a..d9a12133ff83 100644 --- a/crates/wasmtime/src/trampoline/func.rs +++ b/crates/wasmtime/src/trampoline/func.rs @@ -3,7 +3,7 @@ use super::create_handle::create_handle; use crate::trampoline::StoreInstanceHandle; use crate::{FuncType, Store, Trap}; -use anyhow::{bail, Result}; +use anyhow::Result; use std::any::Any; use std::cmp; use std::collections::HashMap; @@ -211,10 +211,7 @@ pub fn create_handle_with_function( let isa = store.engine().config().target_isa(); let pointer_type = isa.pointer_type(); - let sig = match ft.get_wasmtime_signature(pointer_type) { - Some(sig) => sig, - None => bail!("not a supported core wasm signature {:?}", ft), - }; + let sig = ft.get_wasmtime_signature(pointer_type); let mut fn_builder_ctx = FunctionBuilderContext::new(); let mut module = Module::new(); @@ -259,6 +256,7 @@ pub fn create_handle_with_function( finished_functions, trampolines, Box::new(trampoline_state), + PrimaryMap::new(), ) .map(|instance| (instance, trampoline)) } @@ -277,10 +275,7 @@ pub unsafe fn create_handle_with_raw_function( }; let pointer_type = isa.pointer_type(); - let sig = match ft.get_wasmtime_signature(pointer_type) { - Some(sig) => sig, - None => bail!("not a supported core wasm signature {:?}", ft), - }; + let sig = ft.get_wasmtime_signature(pointer_type); let mut module = Module::new(); let mut finished_functions = PrimaryMap::new(); @@ -298,5 +293,12 @@ pub unsafe fn create_handle_with_raw_function( let sig_id = store.register_signature(ft.to_wasm_func_type(), sig); trampolines.insert(sig_id, trampoline); - create_handle(module, store, finished_functions, trampolines, state) + create_handle( + module, + store, + finished_functions, + trampolines, + state, + PrimaryMap::new(), + ) } diff --git a/crates/wasmtime/src/trampoline/global.rs b/crates/wasmtime/src/trampoline/global.rs index db09d4b81eef..6a98cdf65616 100644 --- a/crates/wasmtime/src/trampoline/global.rs +++ b/crates/wasmtime/src/trampoline/global.rs @@ -1,17 +1,19 @@ use super::create_handle::create_handle; use crate::trampoline::StoreInstanceHandle; use crate::{GlobalType, Mutability, Store, Val}; -use anyhow::{bail, Result}; +use anyhow::Result; use wasmtime_environ::entity::PrimaryMap; use wasmtime_environ::{wasm, EntityIndex, Module}; +use wasmtime_runtime::VMFunctionImport; pub fn create_global(store: &Store, gt: &GlobalType, val: Val) -> Result { + let mut module = Module::new(); + let mut func_imports = PrimaryMap::new(); + let mut externref_init = None; + 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()), - }, + ty: gt.content().get_wasmtime_type(), mutability: match gt.mutability() { Mutability::Const => false, Mutability::Var => true, @@ -21,10 +23,42 @@ pub fn create_global(store: &Store, gt: &GlobalType, val: Val) -> Result wasm::GlobalInit::I64Const(i), Val::F32(f) => wasm::GlobalInit::F32Const(f), Val::F64(f) => wasm::GlobalInit::F64Const(f), + Val::ExternRef(None) | Val::FuncRef(None) => wasm::GlobalInit::RefNullConst, + Val::ExternRef(Some(x)) => { + // There is no `GlobalInit` variant for using an existing + // `externref` that isn't an import (because Wasm can't create + // an `externref` by itself). Therefore, initialize the global + // as null, and then monkey patch it after instantiation below. + externref_init = Some(x); + wasm::GlobalInit::RefNullConst + } + Val::FuncRef(Some(f)) => { + // Add a function import to the stub module, and then initialize + // our global with a `ref.func` to grab that imported function. + let shared_sig_index = f.sig_index(); + let local_sig_index = module + .local + .signatures + .push(store.lookup_wasm_and_native_signatures(shared_sig_index)); + let func_index = module.local.functions.push(local_sig_index); + module.local.num_imported_funcs = 1; + module + .imports + .push(("".into(), "".into(), EntityIndex::Function(func_index))); + + let f = f.caller_checked_anyfunc(); + let f = unsafe { f.as_ref() }; + func_imports.push(VMFunctionImport { + body: f.func_ptr, + vmctx: f.vmctx, + }); + + wasm::GlobalInit::RefFunc(func_index) + } _ => unimplemented!("create_global for {:?}", gt), }, }; - let mut module = Module::new(); + let global_id = module.local.globals.push(global); module .exports @@ -35,6 +69,17 @@ pub fn create_global(store: &Store, gt: &GlobalType, val: Val) -> Result unsafe { + *(*g.definition).as_externref_mut() = Some(x.inner); + }, + _ => unreachable!(), + } + } + Ok(handle) } diff --git a/crates/wasmtime/src/trampoline/memory.rs b/crates/wasmtime/src/trampoline/memory.rs index 43538cd23f5b..98437441a2ce 100644 --- a/crates/wasmtime/src/trampoline/memory.rs +++ b/crates/wasmtime/src/trampoline/memory.rs @@ -35,6 +35,7 @@ pub fn create_handle_with_memory( PrimaryMap::new(), Default::default(), Box::new(()), + PrimaryMap::new(), ) } diff --git a/crates/wasmtime/src/trampoline/table.rs b/crates/wasmtime/src/trampoline/table.rs index a3d26a778726..b42575e6532f 100644 --- a/crates/wasmtime/src/trampoline/table.rs +++ b/crates/wasmtime/src/trampoline/table.rs @@ -32,5 +32,6 @@ pub fn create_handle_with_table(store: &Store, table: &TableType) -> Result Option { + pub(crate) fn get_wasmtime_type(&self) -> ir::Type { match self { - ValType::I32 => Some(ir::types::I32), - ValType::I64 => Some(ir::types::I64), - ValType::F32 => Some(ir::types::F32), - ValType::F64 => Some(ir::types::F64), - ValType::V128 => Some(ir::types::I8X16), - ValType::ExternRef => Some(wasmtime_runtime::ref_type()), - _ => None, + ValType::I32 => ir::types::I32, + ValType::I64 => ir::types::I64, + ValType::F32 => ir::types::F32, + ValType::F64 => ir::types::F64, + ValType::V128 => ir::types::I8X16, + ValType::ExternRef => wasmtime_runtime::ref_type(), + ValType::FuncRef => wasmtime_runtime::pointer_type(), } } @@ -248,34 +248,32 @@ impl FuncType { } } - /// Returns `Some` if this function signature was compatible with cranelift, - /// or `None` if one of the types/results wasn't supported or compatible - /// with cranelift. - pub(crate) fn get_wasmtime_signature(&self, pointer_type: ir::Type) -> Option { + /// Get the Cranelift-compatible function signature. + pub(crate) fn get_wasmtime_signature(&self, pointer_type: ir::Type) -> ir::Signature { use wasmtime_environ::ir::{AbiParam, ArgumentPurpose, Signature}; use wasmtime_jit::native; let call_conv = native::call_conv(); let mut params = self .params .iter() - .map(|p| p.get_wasmtime_type().map(AbiParam::new)) - .collect::>>()?; + .map(|p| AbiParam::new(p.get_wasmtime_type())) + .collect::>(); let returns = self .results .iter() - .map(|p| p.get_wasmtime_type().map(AbiParam::new)) - .collect::>>()?; + .map(|p| AbiParam::new(p.get_wasmtime_type())) + .collect::>(); params.insert( 0, AbiParam::special(pointer_type, ArgumentPurpose::VMContext), ); params.insert(1, AbiParam::new(pointer_type)); - Some(Signature { + Signature { params, returns, call_conv, - }) + } } /// Returns `None` if any types in the signature can't be converted to the diff --git a/tests/all/externals.rs b/tests/all/externals.rs index 5d842abbfd59..01a364f18880 100644 --- a/tests/all/externals.rs +++ b/tests/all/externals.rs @@ -111,3 +111,73 @@ fn cross_store() -> anyhow::Result<()> { Ok(()) } + +#[test] +fn get_set_externref_globals_via_api() -> anyhow::Result<()> { + let mut cfg = Config::new(); + cfg.wasm_reference_types(true); + let engine = Engine::new(&cfg); + let store = Store::new(&engine); + + // Initialize with a null externref. + + let global = Global::new( + &store, + GlobalType::new(ValType::ExternRef, Mutability::Var), + Val::ExternRef(None), + )?; + assert!(global.get().unwrap_externref().is_none()); + + global.set(Val::ExternRef(Some(ExternRef::new("hello".to_string()))))?; + let r = global.get().unwrap_externref().unwrap(); + assert!(r.data().is::()); + assert_eq!(r.data().downcast_ref::().unwrap(), "hello"); + + // Initialize with a non-null externref. + + let global = Global::new( + &store, + GlobalType::new(ValType::ExternRef, Mutability::Const), + Val::ExternRef(Some(ExternRef::new(42_i32))), + )?; + let r = global.get().unwrap_externref().unwrap(); + assert!(r.data().is::()); + assert_eq!(r.data().downcast_ref::().copied().unwrap(), 42); + + Ok(()) +} + +#[test] +fn get_set_funcref_globals_via_api() -> anyhow::Result<()> { + let mut cfg = Config::new(); + cfg.wasm_reference_types(true); + let engine = Engine::new(&cfg); + let store = Store::new(&engine); + + let f = Func::wrap(&store, || {}); + + // Initialize with a null funcref. + + let global = Global::new( + &store, + GlobalType::new(ValType::FuncRef, Mutability::Var), + Val::FuncRef(None), + )?; + assert!(global.get().unwrap_funcref().is_none()); + + global.set(Val::FuncRef(Some(f.clone())))?; + let f2 = global.get().unwrap_funcref().cloned().unwrap(); + assert_eq!(f.ty(), f2.ty()); + + // Initialize with a non-null funcref. + + let global = Global::new( + &store, + GlobalType::new(ValType::FuncRef, Mutability::Var), + Val::FuncRef(Some(f.clone())), + )?; + let f2 = global.get().unwrap_funcref().cloned().unwrap(); + assert_eq!(f.ty(), f2.ty()); + + Ok(()) +}