use crate::{
prelude::*,
runtime::vm::{GcHeap, GcStore, VMGcRef},
store::AutoAssertNoGc,
vm::GcStructLayout,
AnyRef, ExternRef, HeapType, RootedGcRefImpl, StorageType, Val, ValType, V128,
};
use core::{fmt, mem};
use wasmtime_environ::VMGcKind;
#[derive(Debug, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct VMStructRef(VMGcRef);
impl fmt::Pointer for VMStructRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Pointer::fmt(&self.0, f)
}
}
impl From<VMStructRef> for VMGcRef {
#[inline]
fn from(x: VMStructRef) -> Self {
x.0
}
}
impl VMGcRef {
pub fn is_structref(&self, gc_heap: &(impl GcHeap + ?Sized)) -> bool {
if self.is_i31() {
return false;
}
let header = gc_heap.header(&self);
header.kind().matches(VMGcKind::StructRef)
}
pub fn into_structref(self, gc_heap: &impl GcHeap) -> Result<VMStructRef, VMGcRef> {
if self.is_structref(gc_heap) {
Ok(self.into_structref_unchecked())
} else {
Err(self)
}
}
#[inline]
pub fn into_structref_unchecked(self) -> VMStructRef {
debug_assert!(!self.is_i31());
VMStructRef(self)
}
pub fn as_structref(&self, gc_heap: &(impl GcHeap + ?Sized)) -> Option<&VMStructRef> {
if self.is_structref(gc_heap) {
Some(self.as_structref_unchecked())
} else {
None
}
}
pub fn as_structref_unchecked(&self) -> &VMStructRef {
debug_assert!(!self.is_i31());
let ptr = self as *const VMGcRef;
let ret = unsafe { &*ptr.cast() };
assert!(matches!(ret, VMStructRef(VMGcRef { .. })));
ret
}
}
impl VMStructRef {
pub fn as_gc_ref(&self) -> &VMGcRef {
&self.0
}
pub fn clone(&self, gc_store: &mut GcStore) -> Self {
Self(gc_store.clone_gc_ref(&self.0))
}
pub fn drop(self, gc_store: &mut GcStore) {
gc_store.drop_gc_ref(self.0);
}
pub fn unchecked_copy(&self) -> Self {
Self(self.0.unchecked_copy())
}
pub fn read_field(
&self,
store: &mut AutoAssertNoGc,
layout: &GcStructLayout,
ty: &StorageType,
field: usize,
) -> Val {
let offset = layout.fields[field];
let data = store.unwrap_gc_store_mut().struct_data(self, layout.size);
match ty {
StorageType::I8 => Val::I32(data.read_u8(offset).into()),
StorageType::I16 => Val::I32(data.read_u16(offset).into()),
StorageType::ValType(ValType::I32) => Val::I32(data.read_i32(offset)),
StorageType::ValType(ValType::I64) => Val::I64(data.read_i64(offset)),
StorageType::ValType(ValType::F32) => Val::F32(data.read_u32(offset)),
StorageType::ValType(ValType::F64) => Val::F64(data.read_u64(offset)),
StorageType::ValType(ValType::V128) => Val::V128(data.read_v128(offset)),
StorageType::ValType(ValType::Ref(r)) => match r.heap_type().top() {
HeapType::Extern => {
let raw = data.read_u32(offset);
Val::ExternRef(ExternRef::_from_raw(store, raw))
}
HeapType::Any => {
let raw = data.read_u32(offset);
Val::AnyRef(AnyRef::_from_raw(store, raw))
}
HeapType::Func => todo!("funcrefs inside gc objects not yet implemented"),
otherwise => unreachable!("not a top type: {otherwise:?}"),
},
}
}
pub fn write_field(
&self,
store: &mut AutoAssertNoGc,
layout: &GcStructLayout,
ty: &StorageType,
field: usize,
val: Val,
) -> Result<()> {
debug_assert!(val._matches_ty(&store, &ty.unpack())?);
let offset = layout.fields[field];
let mut data = store.gc_store_mut()?.struct_data(self, layout.size);
match val {
Val::I32(i) if ty.is_i8() => data.write_i8(offset, i as i8),
Val::I32(i) if ty.is_i16() => data.write_i16(offset, i as i16),
Val::I32(i) => data.write_i32(offset, i),
Val::I64(i) => data.write_i64(offset, i),
Val::F32(f) => data.write_u32(offset, f),
Val::F64(f) => data.write_u64(offset, f),
Val::V128(v) => data.write_v128(offset, v),
Val::ExternRef(e) => {
let raw = data.read_u32(offset);
let mut gc_ref = VMGcRef::from_raw_u32(raw);
let e = match e {
Some(e) => Some(e.try_gc_ref(store)?.unchecked_copy()),
None => None,
};
store.gc_store_mut()?.write_gc_ref(&mut gc_ref, e.as_ref());
let mut data = store.gc_store_mut()?.struct_data(self, layout.size);
data.write_u32(offset, gc_ref.map_or(0, |r| r.as_raw_u32()));
}
Val::AnyRef(a) => {
let raw = data.read_u32(offset);
let mut gc_ref = VMGcRef::from_raw_u32(raw);
let a = match a {
Some(a) => Some(a.try_gc_ref(store)?.unchecked_copy()),
None => None,
};
store.gc_store_mut()?.write_gc_ref(&mut gc_ref, a.as_ref());
let mut data = store.gc_store_mut()?.struct_data(self, layout.size);
data.write_u32(offset, gc_ref.map_or(0, |r| r.as_raw_u32()));
}
Val::FuncRef(_) => todo!("funcrefs inside gc objects not yet implemented"),
}
Ok(())
}
pub fn initialize_field(
&self,
store: &mut AutoAssertNoGc,
layout: &GcStructLayout,
ty: &StorageType,
field: usize,
val: Val,
) -> Result<()> {
debug_assert!(val._matches_ty(&store, &ty.unpack())?);
let offset = layout.fields[field];
match val {
Val::I32(i) if ty.is_i8() => store
.gc_store_mut()?
.struct_data(self, layout.size)
.write_i8(offset, i as i8),
Val::I32(i) if ty.is_i16() => store
.gc_store_mut()?
.struct_data(self, layout.size)
.write_i16(offset, i as i16),
Val::I32(i) => store
.gc_store_mut()?
.struct_data(self, layout.size)
.write_i32(offset, i),
Val::I64(i) => store
.gc_store_mut()?
.struct_data(self, layout.size)
.write_i64(offset, i),
Val::F32(f) => store
.gc_store_mut()?
.struct_data(self, layout.size)
.write_u32(offset, f),
Val::F64(f) => store
.gc_store_mut()?
.struct_data(self, layout.size)
.write_u64(offset, f),
Val::V128(v) => store
.gc_store_mut()?
.struct_data(self, layout.size)
.write_v128(offset, v),
Val::ExternRef(x) => {
let x = match x {
None => 0,
Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(),
};
store
.gc_store_mut()?
.struct_data(self, layout.size)
.write_u32(offset, x);
}
Val::AnyRef(x) => {
let x = match x {
None => 0,
Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(),
};
store
.gc_store_mut()?
.struct_data(self, layout.size)
.write_u32(offset, x);
}
Val::FuncRef(_) => {
todo!("funcrefs in GC objects")
}
}
Ok(())
}
}
pub trait PodValType<const SIZE: usize>: Copy {
fn read_le(le_bytes: &[u8; SIZE]) -> Self;
fn write_le(&self, into: &mut [u8; SIZE]);
}
macro_rules! impl_pod_val_type {
( $( $t:ty , )* ) => {
$(
impl PodValType<{core::mem::size_of::<$t>()}> for $t {
fn read_le(le_bytes: &[u8; core::mem::size_of::<$t>()]) -> Self {
<$t>::from_le_bytes(*le_bytes)
}
fn write_le(&self, into: &mut [u8; core::mem::size_of::<$t>()]) {
*into = self.to_le_bytes();
}
}
)*
};
}
impl_pod_val_type! {
u8,
u16,
u32,
u64,
i8,
i16,
i32,
i64,
}
impl PodValType<{ mem::size_of::<V128>() }> for V128 {
fn read_le(le_bytes: &[u8; mem::size_of::<V128>()]) -> Self {
u128::from_le_bytes(*le_bytes).into()
}
fn write_le(&self, into: &mut [u8; mem::size_of::<V128>()]) {
*into = self.as_u128().to_le_bytes();
}
}
pub struct VMStructDataMut<'a> {
data: &'a mut [u8],
}
macro_rules! impl_pod_methods {
( $( $t:ty, $read:ident, $write:ident; )* ) => {
$(
#[doc = stringify!($t)]
#[inline]
pub fn $read(&self, offset: u32) -> $t {
self.read_pod::<{ mem::size_of::<$t>() }, $t>(offset)
}
#[doc = stringify!($t)]
#[inline]
pub fn $write(&mut self, offset: u32, val: $t) {
self.write_pod::<{ mem::size_of::<$t>() }, $t>(offset, val);
}
)*
};
}
impl<'a> VMStructDataMut<'a> {
#[inline]
pub fn new(data: &'a mut [u8]) -> Self {
Self { data }
}
#[inline]
fn read_pod<const N: usize, T>(&self, offset: u32) -> T
where
T: PodValType<N>,
{
assert_eq!(N, mem::size_of::<T>());
let offset = usize::try_from(offset).unwrap();
let end = offset.checked_add(N).unwrap();
let bytes = self.data.get(offset..end).expect("out of bounds field");
T::read_le(bytes.try_into().unwrap())
}
#[inline]
fn write_pod<const N: usize, T>(&mut self, offset: u32, val: T)
where
T: PodValType<N>,
{
assert_eq!(N, mem::size_of::<T>());
let offset = usize::try_from(offset).unwrap();
let end = offset.checked_add(N).unwrap();
let into = self.data.get_mut(offset..end).expect("out of bounds field");
val.write_le(into.try_into().unwrap());
}
impl_pod_methods! {
u8, read_u8, write_u8;
u16, read_u16, write_u16;
u32, read_u32, write_u32;
u64, read_u64, write_u64;
i8, read_i8, write_i8;
i16, read_i16, write_i16;
i32, read_i32, write_i32;
i64, read_i64, write_i64;
V128, read_v128, write_v128;
}
}