From b04bd00c256112c06c31186effbdd777e7f8331f Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Thu, 2 Jul 2020 12:57:12 -0700 Subject: [PATCH] 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(()) +}