wasmtime: Implement global.{get,set} for externref globals (#1969)
* 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
* wasmtime: Enable `{extern,func}ref` globals in the API
This commit is contained in:
@@ -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()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,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.
|
||||
@@ -494,19 +495,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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<DefinedFuncIndex, *mut [VMFunctionBody]>,
|
||||
trampolines: HashMap<VMSharedSignatureIndex, VMTrampoline>,
|
||||
state: Box<dyn Any>,
|
||||
func_imports: PrimaryMap<FuncIndex, VMFunctionImport>,
|
||||
) -> Result<StoreInstanceHandle> {
|
||||
let imports = Imports::new(
|
||||
PrimaryMap::new(),
|
||||
func_imports,
|
||||
PrimaryMap::new(),
|
||||
PrimaryMap::new(),
|
||||
PrimaryMap::new(),
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<StoreInstanceHandle> {
|
||||
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<StoreIn
|
||||
Val::I64(i) => 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<StoreIn
|
||||
PrimaryMap::new(),
|
||||
Default::default(),
|
||||
Box::new(()),
|
||||
func_imports,
|
||||
)?;
|
||||
|
||||
if let Some(x) = externref_init {
|
||||
match handle.lookup("global").unwrap() {
|
||||
wasmtime_runtime::Export::Global(g) => unsafe {
|
||||
*(*g.definition).as_externref_mut() = Some(x.inner);
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(handle)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ pub fn create_handle_with_memory(
|
||||
PrimaryMap::new(),
|
||||
Default::default(),
|
||||
Box::new(()),
|
||||
PrimaryMap::new(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -32,5 +32,6 @@ pub fn create_handle_with_table(store: &Store, table: &TableType) -> Result<Stor
|
||||
PrimaryMap::new(),
|
||||
Default::default(),
|
||||
Box::new(()),
|
||||
PrimaryMap::new(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -99,15 +99,15 @@ impl ValType {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_wasmtime_type(&self) -> Option<ir::Type> {
|
||||
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<ir::Signature> {
|
||||
/// 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::<Option<Vec<_>>>()?;
|
||||
.map(|p| AbiParam::new(p.get_wasmtime_type()))
|
||||
.collect::<Vec<_>>();
|
||||
let returns = self
|
||||
.results
|
||||
.iter()
|
||||
.map(|p| p.get_wasmtime_type().map(AbiParam::new))
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
.map(|p| AbiParam::new(p.get_wasmtime_type()))
|
||||
.collect::<Vec<_>>();
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user