use tinywasm_types::Shared;
use crate::interpreter::ValueRef;
use crate::shared::{AtomicU32, Ordering};
use crate::{Result, Store, Trap};
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct StoreId(u32);
impl StoreId {
pub(crate) fn fresh() -> Self {
static NEXT: AtomicU32 = AtomicU32::new(0);
let id = NEXT.fetch_add(1, Ordering::Relaxed);
assert_ne!(id, u32::MAX, "Store identity space exhausted");
StoreId(id)
}
}
#[cfg(feature = "debug")]
impl core::fmt::Debug for StoreId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("StoreId(..)")
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub(crate) enum ReferentKind {
I31,
HostExtern,
Struct,
Array,
Exception,
}
#[derive(Clone)]
pub(crate) struct RootedItem {
pub(crate) store: StoreId,
pub(crate) value: ValueRef,
pub(crate) kind: ReferentKind,
pub(crate) _token: Option<Shared<()>>,
}
impl PartialEq for RootedItem {
fn eq(&self, other: &Self) -> bool {
self.kind == other.kind
&& self.value == other.value
&& (self.kind == ReferentKind::I31 || self.store == other.store)
}
}
impl Eq for RootedItem {}
#[cfg(feature = "debug")]
impl core::fmt::Debug for RootedItem {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Reference").field("kind", &self.kind).finish_non_exhaustive()
}
}
pub(crate) trait StoredRef: Clone + Sized {
fn from_rooted_item(item: RootedItem) -> Self;
fn rooted_item(&self) -> &RootedItem;
}
macro_rules! reference_types {
($($(#[$meta:meta])* $name:ident),* $(,)?) => {$($(
#[$meta]
)*
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub struct $name(RootedItem);
impl StoredRef for $name {
fn from_rooted_item(item: RootedItem) -> Self { Self(item) }
fn rooted_item(&self) -> &RootedItem { &self.0 }
}
)*};
}
reference_types! {
AnyRef,
EqRef,
I31Ref,
StructRef,
ArrayRef,
ExternRef,
ExnRef,
}
impl AnyRef {
pub fn as_eq(&self) -> Option<EqRef> {
matches!(self.0.kind, ReferentKind::I31 | ReferentKind::Struct | ReferentKind::Array)
.then(|| EqRef(self.0.clone()))
}
pub fn as_i31(&self) -> Option<I31Ref> {
(self.0.kind == ReferentKind::I31).then(|| I31Ref(self.0.clone()))
}
pub fn as_struct(&self) -> Option<StructRef> {
(self.0.kind == ReferentKind::Struct).then(|| StructRef(self.0.clone()))
}
pub fn as_array(&self) -> Option<ArrayRef> {
(self.0.kind == ReferentKind::Array).then(|| ArrayRef(self.0.clone()))
}
pub fn to_extern(&self) -> ExternRef {
ExternRef(self.0.clone())
}
}
macro_rules! impl_to_any {
($($ty:ty),* $(,)?) => {$(
impl $ty {
pub fn to_any(&self) -> AnyRef { AnyRef(self.0.clone()) }
}
)*};
}
impl_to_any!(EqRef, I31Ref, StructRef, ArrayRef, ExternRef);
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct FuncRef {
store: StoreId,
addr: u32,
}
impl FuncRef {
pub(crate) const fn new(store: StoreId, addr: u32) -> Self {
Self { store, addr }
}
pub(crate) fn addr(self, store: StoreId) -> Option<u32> {
(self.store == store).then_some(self.addr)
}
}
#[cfg(feature = "debug")]
impl core::fmt::Debug for FuncRef {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("FuncRef(..)")
}
}
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub enum RefValue {
Null,
Func(FuncRef),
Extern(ExternRef),
Any(AnyRef),
Exn(ExnRef),
}
impl ExternRef {
pub fn try_new(store: &mut Store, key: u32) -> Result<Self> {
let value = ValueRef::try_from_host_any(key).ok_or(Trap::InvalidReference)?;
store.root_reference(value, ReferentKind::HostExtern)
}
pub fn key(&self, store: &Store) -> Result<u32> {
let value = store.resolve_ref(self)?;
let addr = value.addr().filter(|_| value.is_host_any()).ok_or(Trap::InvalidReference)?;
Ok(addr & !(1 << 30))
}
}
impl I31Ref {
pub fn try_new(store: &mut Store, value: i32) -> Result<Self> {
if !(-(1 << 30)..(1 << 30)).contains(&value) {
return Err(Trap::InvalidReference.into());
}
store.root_reference(ValueRef::from_i31(value), ReferentKind::I31)
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
#[test]
fn host_reference_key_fits_internal_encoding() {
assert!(ValueRef::try_from_host_any((1 << 30) - 2).is_some());
assert!(ValueRef::try_from_host_any((1 << 30) - 1).is_none());
}
}