use crate::Engine;
use std::{
borrow::Borrow,
collections::{HashMap, HashSet},
fmt::Debug,
hash::{Hash, Hasher},
ops::Deref,
sync::{
atomic::{
AtomicUsize,
Ordering::{AcqRel, Acquire},
},
Arc, RwLock,
},
};
use wasmtime_environ::{
EngineOrModuleTypeIndex, ModuleInternedTypeIndex, ModuleTypes, PrimaryMap, TypeTrace,
WasmFuncType,
};
use wasmtime_runtime::VMSharedTypeIndex;
use wasmtime_slab::{Id as SlabId, Slab};
pub struct TypeCollection {
engine: Engine,
types: PrimaryMap<ModuleInternedTypeIndex, VMSharedTypeIndex>,
reverse_types: HashMap<VMSharedTypeIndex, ModuleInternedTypeIndex>,
}
impl Debug for TypeCollection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let TypeCollection {
engine: _,
types,
reverse_types: _,
} = self;
f.debug_struct("TypeCollection")
.field("types", types)
.finish_non_exhaustive()
}
}
impl TypeCollection {
pub fn new_for_module(engine: &Engine, types: &ModuleTypes) -> Self {
let engine = engine.clone();
let registry = engine.signatures();
let types = registry.0.write().unwrap().register_for_module(types);
let reverse_types = types.iter().map(|(k, v)| (*v, k)).collect();
Self {
engine,
types,
reverse_types,
}
}
pub fn as_module_map(&self) -> &PrimaryMap<ModuleInternedTypeIndex, VMSharedTypeIndex> {
&self.types
}
#[inline]
pub fn shared_type(&self, index: ModuleInternedTypeIndex) -> Option<VMSharedTypeIndex> {
self.types.get(index).copied()
}
pub fn module_local_type(&self, index: VMSharedTypeIndex) -> Option<ModuleInternedTypeIndex> {
self.reverse_types.get(&index).copied()
}
}
impl Drop for TypeCollection {
fn drop(&mut self) {
if !self.types.is_empty() {
self.engine
.signatures()
.0
.write()
.unwrap()
.unregister_type_collection(self);
}
}
}
#[inline]
fn shared_type_index_to_slab_id(index: VMSharedTypeIndex) -> SlabId {
SlabId::from_raw(index.bits())
}
#[inline]
fn slab_id_to_shared_type_index(id: SlabId) -> VMSharedTypeIndex {
VMSharedTypeIndex::new(id.into_raw())
}
pub struct RegisteredType {
engine: Engine,
entry: Entry,
}
impl Debug for RegisteredType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let RegisteredType { engine: _, entry } = self;
f.debug_struct("RegisteredType")
.field("entry", entry)
.finish_non_exhaustive()
}
}
impl Clone for RegisteredType {
fn clone(&self) -> Self {
self.entry.incref("cloning RegisteredType");
RegisteredType {
engine: self.engine.clone(),
entry: self.entry.clone(),
}
}
}
impl Drop for RegisteredType {
fn drop(&mut self) {
if self.entry.decref("dropping RegisteredType") {
self.engine
.signatures()
.0
.write()
.unwrap()
.unregister_entry(self.entry.0.index);
}
}
}
impl std::ops::Deref for RegisteredType {
type Target = WasmFuncType;
fn deref(&self) -> &Self::Target {
&self.entry.0.ty
}
}
impl PartialEq for RegisteredType {
fn eq(&self, other: &Self) -> bool {
let eq = Arc::ptr_eq(&self.entry.0, &other.entry.0);
if cfg!(debug_assertions) {
if eq {
assert!(Engine::same(&self.engine, &other.engine));
} else {
assert!(
self.entry.0.index != other.entry.0.index
|| !Engine::same(&self.engine, &other.engine)
);
}
}
eq
}
}
impl Eq for RegisteredType {}
impl Hash for RegisteredType {
fn hash<H: Hasher>(&self, state: &mut H) {
let ptr = Arc::as_ptr(&self.entry.0);
ptr.hash(state);
}
}
impl RegisteredType {
pub fn new(engine: &Engine, ty: WasmFuncType) -> RegisteredType {
let entry = {
let mut inner = engine.signatures().0.write().unwrap();
log::trace!("RegisteredType::new({ty:?})");
assert!(
inner.is_canonicalized(&ty),
"ty is not already canonicalized: {ty:?}"
);
inner.register_canonicalized(ty)
};
RegisteredType::from_parts(engine.clone(), entry)
}
pub fn root(engine: &Engine, index: VMSharedTypeIndex) -> Option<RegisteredType> {
let entry = {
let id = shared_type_index_to_slab_id(index);
let inner = engine.signatures().0.read().unwrap();
let e = inner.entries.get(id)?;
e.incref("RegisteredType::root");
e.clone()
};
Some(RegisteredType::from_parts(engine.clone(), entry))
}
fn from_parts(engine: Engine, entry: Entry) -> Self {
debug_assert!(entry.0.registrations.load(Acquire) != 0);
RegisteredType { engine, entry }
}
pub fn index(&self) -> VMSharedTypeIndex {
self.entry.0.index
}
pub fn engine(&self) -> &Engine {
&self.engine
}
}
#[derive(Debug)]
struct EntryInner {
ty: WasmFuncType,
index: VMSharedTypeIndex,
registrations: AtomicUsize,
}
#[derive(Clone, Debug)]
struct Entry(Arc<EntryInner>);
impl Deref for Entry {
type Target = WasmFuncType;
fn deref(&self) -> &Self::Target {
&self.0.ty
}
}
impl PartialEq for Entry {
fn eq(&self, other: &Self) -> bool {
self.0.ty == other.0.ty
}
}
impl Eq for Entry {}
impl Hash for Entry {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.ty.hash(state);
}
}
impl Borrow<WasmFuncType> for Entry {
fn borrow(&self) -> &WasmFuncType {
&self.0.ty
}
}
impl Entry {
fn incref(&self, why: &str) {
let old_count = self.0.registrations.fetch_add(1, AcqRel);
log::trace!(
"increment registration count for {:?} (registrations -> {}): {why}",
self.0.index,
old_count + 1
);
}
#[must_use = "caller must remove entry from registry if `decref` returns `true`"]
fn decref(&self, why: &str) -> bool {
let old_count = self.0.registrations.fetch_sub(1, AcqRel);
debug_assert_ne!(old_count, 0);
log::trace!(
"decrement registration count for {:?} (registrations -> {}): {why}",
self.0.index,
old_count - 1
);
old_count == 1
}
}
#[derive(Debug, Default)]
struct TypeRegistryInner {
map: HashSet<Entry>,
entries: Slab<Entry>,
drop_stack: Vec<VMSharedTypeIndex>,
}
impl TypeRegistryInner {
fn register_for_module(
&mut self,
types: &ModuleTypes,
) -> PrimaryMap<ModuleInternedTypeIndex, VMSharedTypeIndex> {
log::trace!("Registering module types");
let mut map = PrimaryMap::default();
for (idx, ty) in types.wasm_types() {
let mut ty = ty.clone();
self.canonicalize(&map, &mut ty);
let entry = self.register_canonicalized(ty);
let map_idx = map.push(entry.0.index);
assert_eq!(idx, map_idx);
}
map
}
fn is_canonicalized(&self, ty: &WasmFuncType) -> bool {
let result = ty.trace::<_, ()>(&mut |index| match index {
EngineOrModuleTypeIndex::Module(_) => Err(()),
EngineOrModuleTypeIndex::Engine(id) => {
let id = VMSharedTypeIndex::new(id);
let id = shared_type_index_to_slab_id(id);
assert!(
self.entries.contains(id),
"canonicalized in a different engine? {ty:?}"
);
Ok(())
}
});
result.is_ok()
}
fn canonicalize(
&self,
module_to_shared: &PrimaryMap<ModuleInternedTypeIndex, VMSharedTypeIndex>,
ty: &mut WasmFuncType,
) {
ty.canonicalize(&mut |module_index| module_to_shared[module_index].bits());
debug_assert!(self.is_canonicalized(ty))
}
fn register_new(&mut self, ty: WasmFuncType) -> Entry {
assert!(
self.is_canonicalized(&ty),
"ty is not already canonicalized: {ty:?}"
);
ty.trace::<_, ()>(&mut |idx| match idx {
EngineOrModuleTypeIndex::Engine(id) => {
let id = VMSharedTypeIndex::new(id);
let i = shared_type_index_to_slab_id(id);
let e = &self.entries[i];
e.incref("new type references existing type in TypeRegistryInner::register_new");
Ok(())
}
EngineOrModuleTypeIndex::Module(_) => unreachable!("should be canonicalized"),
})
.unwrap();
let id = self.entries.next_id();
let index = slab_id_to_shared_type_index(id);
log::trace!("create {index:?} = {ty:?} (registrations -> 1)");
let entry = Entry(Arc::new(EntryInner {
ty,
index,
registrations: AtomicUsize::new(1),
}));
let is_new_entry = self.map.insert(entry.clone());
assert!(is_new_entry);
let id = self.entries.alloc(entry.clone());
assert_eq!(id, shared_type_index_to_slab_id(index));
entry
}
fn register_canonicalized(&mut self, ty: WasmFuncType) -> Entry {
assert!(
self.is_canonicalized(&ty),
"type is not already canonicalized: {ty:?}"
);
if let Some(entry) = self.map.get(&ty) {
entry.incref(
"registering already-registered type in TypeRegistryInner::register_canonicalized",
);
entry.clone()
} else {
self.register_new(ty)
}
}
fn unregister_type_collection(&mut self, collection: &TypeCollection) {
for (_, id) in collection.types.iter() {
let i = shared_type_index_to_slab_id(*id);
let e = &self.entries[i];
if e.decref("TypeRegistryInner::unregister_type_collection") {
self.unregister_entry(*id);
}
}
}
fn unregister_entry(&mut self, index: VMSharedTypeIndex) {
log::trace!("unregistering {index:?}");
debug_assert!(self.drop_stack.is_empty());
self.drop_stack.push(index);
while let Some(index) = self.drop_stack.pop() {
let slab_id = shared_type_index_to_slab_id(index);
let entry = &self.entries[slab_id];
let registrations = entry.0.registrations.load(Acquire);
if registrations != 0 {
log::trace!(
"{index:?} was concurrently resurrected and no longer has zero \
registrations (registrations -> {registrations})"
);
continue;
}
entry
.0
.ty
.trace::<_, ()>(&mut |child_index| match child_index {
EngineOrModuleTypeIndex::Engine(child_index) => {
let child_index = VMSharedTypeIndex::new(child_index);
let child_slab_id = shared_type_index_to_slab_id(child_index);
let child_entry = &self.entries[child_slab_id];
if child_entry.decref(
"referenced by unregistered type in TypeCollection::unregister_entry",
) {
self.drop_stack.push(child_index);
}
Ok(())
}
EngineOrModuleTypeIndex::Module(_) => {
unreachable!("should be canonicalized")
}
})
.unwrap();
log::trace!("removing {index:?} from registry");
self.map.remove(entry);
self.entries.dealloc(slab_id);
}
}
}
#[cfg(debug_assertions)]
impl Drop for TypeRegistryInner {
fn drop(&mut self) {
assert!(
self.map.is_empty(),
"type registry not empty: still have registered types in self.map"
);
assert!(
self.entries.is_empty(),
"type registry not empty: not all entries are vacant"
);
}
}
#[derive(Debug)]
pub struct TypeRegistry(RwLock<TypeRegistryInner>);
impl TypeRegistry {
pub fn new() -> Self {
Self(RwLock::new(TypeRegistryInner::default()))
}
pub fn borrow(&self, index: VMSharedTypeIndex) -> Option<impl Deref<Target = WasmFuncType>> {
let id = shared_type_index_to_slab_id(index);
let inner = self.0.read().unwrap();
inner.entries.get(id).cloned()
}
}