use crate::{GcHeap, GcStore, VMSharedTypeIndex, I31};
use anyhow::{Context, Result};
use std::num::NonZeroU32;
use wasmtime_environ::VMGcKind;
#[repr(transparent)]
pub struct VMGcHeader(u64);
unsafe impl GcHeapObject for VMGcHeader {
#[inline]
fn is(_: &VMGcHeader) -> bool {
true
}
}
impl VMGcHeader {
pub fn externref() -> Self {
let kind = VMGcKind::ExternRef as u32;
let upper = u64::from(kind) << 32;
let lower = u64::from(u32::MAX);
Self(upper | lower)
}
pub fn kind(&self) -> VMGcKind {
let upper = u32::try_from(self.0 >> 32).unwrap();
VMGcKind::from_u32(upper)
}
pub fn reserved_u30(&self) -> u32 {
let upper = u32::try_from(self.0 >> 32).unwrap();
upper & VMGcKind::UNUSED_MASK
}
pub fn set_reserved_u30(&mut self, value: u32) {
assert_eq!(
value & VMGcKind::MASK,
0,
"VMGcHeader::set_reserved_u30 with value using more than 30 bits"
);
self.0 |= u64::from(value) << 32;
}
pub unsafe fn unchecked_set_reserved_u30(&mut self, value: u32) {
self.0 |= u64::from(value) << 32;
}
pub fn ty(&self) -> Option<VMSharedTypeIndex> {
let lower_mask = u64::from(u32::MAX);
let lower = u32::try_from(self.0 & lower_mask).unwrap();
if lower == u32::MAX {
None
} else {
Some(VMSharedTypeIndex::new(lower))
}
}
}
#[cfg(test)]
mod vm_gc_header_tests {
use super::*;
#[test]
fn size_align() {
assert_eq!(std::mem::size_of::<VMGcHeader>(), 8);
assert_eq!(std::mem::align_of::<VMGcHeader>(), 8);
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct VMGcRef(NonZeroU32);
impl<T> From<TypedGcRef<T>> for VMGcRef {
#[inline]
fn from(value: TypedGcRef<T>) -> Self {
value.gc_ref
}
}
impl std::fmt::LowerHex for VMGcRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl std::fmt::UpperHex for VMGcRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl std::fmt::Pointer for VMGcRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:#x}", self)
}
}
impl VMGcRef {
pub const ONLY_EXTERN_REF_AND_I31: bool = true;
pub const I31_REF_DISCRIMINANT: u32 = 1;
pub fn from_raw_u32(raw: u32) -> Option<Self> {
Some(Self::from_raw_non_zero_u32(NonZeroU32::new(raw)?))
}
pub fn from_heap_index(index: NonZeroU32) -> Option<Self> {
if (index.get() & Self::I31_REF_DISCRIMINANT) == 0 {
Some(Self::from_raw_non_zero_u32(index))
} else {
None
}
}
pub fn from_raw_non_zero_u32(raw: NonZeroU32) -> Self {
VMGcRef(raw)
}
#[inline]
pub fn from_i31(val: I31) -> Self {
let val = (val.get_u32() << 1) | Self::I31_REF_DISCRIMINANT;
debug_assert_ne!(val, 0);
let non_zero = unsafe { NonZeroU32::new_unchecked(val) };
VMGcRef::from_raw_non_zero_u32(non_zero)
}
pub fn from_r64(raw: u64) -> Result<Option<Self>> {
let raw = u32::try_from(raw & (u32::MAX as u64))
.with_context(|| format!("invalid r64: {raw:#x} cannot be converted into a u32"))?;
Ok(Self::from_raw_u32(raw))
}
pub fn unchecked_copy(&self) -> Self {
VMGcRef(self.0)
}
pub fn as_heap_index(&self) -> Option<NonZeroU32> {
if self.is_i31() {
None
} else {
Some(self.0)
}
}
pub fn as_raw_u32(&self) -> u32 {
self.0.get()
}
pub fn into_r64(self) -> u64 {
u64::from(self.0.get())
}
pub fn as_r64(&self) -> u64 {
u64::from(self.0.get())
}
pub fn into_typed<T>(self, gc_heap: &impl GcHeap) -> Result<TypedGcRef<T>, Self>
where
T: GcHeapObject,
{
if self.is_i31() {
return Err(self);
}
if T::is(gc_heap.header(&self)) {
Ok(TypedGcRef {
gc_ref: self,
_phantom: std::marker::PhantomData,
})
} else {
Err(self)
}
}
pub fn into_typed_unchecked<T>(self) -> TypedGcRef<T>
where
T: GcHeapObject,
{
debug_assert!(!self.is_i31());
TypedGcRef {
gc_ref: self,
_phantom: std::marker::PhantomData,
}
}
pub fn as_typed<T>(&self, gc_heap: &impl GcHeap) -> Option<&TypedGcRef<T>>
where
T: GcHeapObject,
{
if self.is_i31() {
return None;
}
if T::is(gc_heap.header(&self)) {
let ptr = self as *const VMGcRef;
let ret = unsafe { &*ptr.cast() };
assert!(matches!(
ret,
TypedGcRef {
gc_ref: VMGcRef(_),
_phantom
}
));
Some(ret)
} else {
None
}
}
pub fn as_typed_unchecked<T>(&self) -> &TypedGcRef<T>
where
T: GcHeapObject,
{
debug_assert!(!self.is_i31());
let ptr = self as *const VMGcRef;
let ret = unsafe { &*ptr.cast() };
assert!(matches!(
ret,
TypedGcRef {
gc_ref: VMGcRef(_),
_phantom
}
));
ret
}
pub fn gc_header<'a>(&self, gc_heap: &'a dyn GcHeap) -> Option<&'a VMGcHeader> {
if self.is_i31() {
None
} else {
Some(gc_heap.header(self))
}
}
#[inline]
pub fn is_i31(&self) -> bool {
let val = self.0.get();
(val & Self::I31_REF_DISCRIMINANT) != 0
}
#[inline]
pub fn as_i31(&self) -> Option<I31> {
if self.is_i31() {
let val = self.0.get();
Some(I31::wrapping_u32(val >> 1))
} else {
None
}
}
#[inline]
pub fn unwrap_i31(&self) -> I31 {
self.as_i31().unwrap()
}
#[inline]
pub fn is_extern_ref(&self) -> bool {
assert!(Self::ONLY_EXTERN_REF_AND_I31);
!self.is_i31()
}
}
pub unsafe trait GcHeapObject: Send + Sync {
fn is(header: &VMGcHeader) -> bool;
}
#[derive(Debug, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct TypedGcRef<T> {
gc_ref: VMGcRef,
_phantom: std::marker::PhantomData<*mut T>,
}
impl<T> TypedGcRef<T>
where
T: GcHeapObject,
{
pub fn clone(&self, gc_store: &mut GcStore) -> Self {
Self {
gc_ref: gc_store.clone_gc_ref(&self.gc_ref),
_phantom: std::marker::PhantomData,
}
}
pub fn drop(self, gc_store: &mut GcStore) {
gc_store.drop_gc_ref(self.gc_ref);
}
pub fn unchecked_copy(&self) -> Self {
Self {
gc_ref: self.gc_ref.unchecked_copy(),
_phantom: std::marker::PhantomData,
}
}
}
impl<T> TypedGcRef<T> {
pub fn as_untyped(&self) -> &VMGcRef {
&self.gc_ref
}
}