use core::ops::Range;
use crate::canon::{ArrayLayout, Layout, RefKind, StructLayout};
use crate::store::{
anyref_handle_slot, read_slot, slot_accepts, write_slot, AsContext, AsContextMut, GcObject,
StoreInner,
};
use crate::value::gc_type::{ArrayType, FieldType, StorageType, StructType};
use crate::value::{Mutability, Val};
use crate::{Error, Result};
use super::gc_ref::{gc_object, AnyRef, Rooted};
fn root_new_gc<T>(inner: &mut StoreInner, idx: u32) -> Rooted<T> {
let handle = anyref_handle_slot(idx);
inner.push_gc_root(handle, RefKind::Any);
let generation = inner.gc.generation(idx).unwrap_or(0);
Rooted::from_raw_gen(handle, generation, inner.store_id(), RefKind::Any)
}
#[derive(Debug)]
pub struct StructRef {
_private: (),
}
#[derive(Debug)]
pub struct StructRefPre {
store: u64,
ty: StructType,
layout: StructLayout,
}
impl StructRefPre {
pub fn new(mut store: impl AsContextMut, ty: StructType) -> Self {
let ctx = store.as_context_mut();
assert!(
ty.engine().same(ctx.engine()),
"GC type used with a store from a different engine"
);
let fields: Vec<_> = ty.fields().collect();
StructRefPre {
store: ctx.inner().store_id(),
ty,
layout: Layout::for_struct(&fields),
}
}
}
impl StructRef {
pub fn new(
mut store: impl AsContextMut,
allocator: &StructRefPre,
fields: &[Val],
) -> Result<Rooted<StructRef>> {
let slots = allocator.layout.fields();
let size = allocator.layout.size();
if fields.len() != slots.len() {
return Err(Error::msg("wrong number of struct fields"));
}
let mut ctx = store.as_context_mut();
ctx.inner().check_handle(allocator.store);
let field_types: Vec<_> = allocator.ty.fields().collect();
for ((slot, field_ty), v) in slots.iter().zip(&field_types).zip(fields) {
if !slot_accepts(*slot, v) || !field_value_matches(ctx.inner(), field_ty, v)? {
return Err(Error::msg("struct field value has the wrong type"));
}
}
let type_id = allocator.ty.canonical_id();
let charge = ctx.inner().gc_object_charge(size);
ctx.0.gc_reserve_host(charge)?;
let inner = ctx.inner_mut();
let mut data = vec![0u8; size];
for (slot, v) in slots.iter().zip(fields) {
write_slot(*slot, &mut data, *v);
}
let object = GcObject::new_struct(type_id, data.into_boxed_slice());
let idx = inner.alloc_host_gc(type_id, object)?;
Ok(root_new_gc(inner, idx))
}
}
impl Rooted<StructRef> {
pub fn field(&self, store: impl AsContext, index: usize) -> Result<Val> {
let ctx = store.as_context();
let inner = ctx.inner();
let slot = self.gc_slot_checked(inner)?;
let obj = gc_object(inner, slot)?;
let layout = Layout::for_struct(&inner.engine().struct_fields(obj.header.type_id)?);
let field = layout
.field(index)
.ok_or_else(|| Error::msg("struct field index out of bounds"))?;
inner.root_host_result(read_slot(field, &obj.data))
}
pub fn ty(&self, store: impl AsContext) -> Result<StructType> {
let ctx = store.as_context();
let inner = ctx.inner();
let slot = self.gc_slot_checked(inner)?;
let type_id = gc_object(inner, slot)?.header.type_id;
Ok(StructType::from_id(inner.engine(), type_id))
}
pub fn matches_ty(&self, store: impl AsContext, ty: &StructType) -> Result<bool> {
let ctx = store.as_context();
let inner = ctx.inner();
assert!(
ty.engine().same(inner.engine()),
"struct type belongs to a different engine"
);
let slot = self.gc_slot_checked(inner)?;
let type_id = gc_object(inner, slot)?.header.type_id;
Ok(inner.engine().is_subtype(type_id, ty.canonical_id()))
}
pub fn set_field(&self, mut store: impl AsContextMut, index: usize, value: Val) -> Result<()> {
let mut ctx = store.as_context_mut();
let inner = ctx.inner_mut();
let slot = self.gc_slot_checked(inner)?;
let type_id = gc_object(inner, slot)?.header.type_id;
let fields = inner.engine().struct_fields(type_id)?;
let field_ty = fields
.get(index)
.ok_or_else(|| Error::msg("struct field index out of bounds"))?;
if field_ty.mutability() != Mutability::Var {
return Err(Error::msg("struct field is not mutable"));
}
let field = Layout::for_struct(&fields)
.field(index)
.ok_or_else(|| Error::msg("struct field index out of bounds"))?;
if !slot_accepts(field, &value) || !field_value_matches(inner, field_ty, &value)? {
return Err(Error::msg("struct field value has the wrong type"));
}
let obj = inner
.gc_object_mut(slot)
.ok_or_else(|| Error::msg("dangling gc reference"))?;
write_slot(field, &mut obj.data, value);
Ok(())
}
pub fn to_anyref(self) -> Rooted<AnyRef> {
self.cast()
}
}
impl From<Rooted<StructRef>> for Rooted<AnyRef> {
fn from(r: Rooted<StructRef>) -> Self {
r.cast()
}
}
#[derive(Debug)]
pub struct ArrayRef {
_private: (),
}
#[derive(Debug)]
pub struct ArrayRefPre {
store: u64,
ty: ArrayType,
layout: ArrayLayout,
}
impl ArrayRefPre {
pub fn new(mut store: impl AsContextMut, ty: ArrayType) -> Self {
let ctx = store.as_context_mut();
assert!(
ty.engine().same(ctx.engine()),
"GC type used with a store from a different engine"
);
let layout = Layout::for_array(&ty.field_type());
ArrayRefPre {
store: ctx.inner().store_id(),
ty,
layout,
}
}
}
impl ArrayRef {
pub fn new(
store: impl AsContextMut,
allocator: &ArrayRefPre,
elem: &Val,
len: u32,
) -> Result<Rooted<ArrayRef>> {
let count = len as usize;
Self::alloc(store, allocator, count, |_| elem)
}
pub fn new_fixed(
store: impl AsContextMut,
allocator: &ArrayRefPre,
elems: &[Val],
) -> Result<Rooted<ArrayRef>> {
Self::alloc(store, allocator, elems.len(), |i| &elems[i])
}
pub fn new_from_i8_slice(
mut store: impl AsContextMut,
allocator: &ArrayRefPre,
elems: &[u8],
) -> Result<Rooted<ArrayRef>> {
let mut ctx = store.as_context_mut();
let charge = Self::packed_charge(ctx.inner(), allocator, StorageType::I8, elems.len())?;
ctx.0.gc_reserve_host(charge)?;
Self::finish_packed(ctx.inner_mut(), allocator, elems.to_vec())
}
pub fn new_zeroed_i8(
mut store: impl AsContextMut,
allocator: &ArrayRefPre,
len: u32,
) -> Result<Rooted<ArrayRef>> {
let mut ctx = store.as_context_mut();
let charge = Self::packed_charge(ctx.inner(), allocator, StorageType::I8, len as usize)?;
ctx.0.gc_reserve_host(charge)?;
Self::finish_packed(ctx.inner_mut(), allocator, zeroed_body(len as usize)?)
}
pub fn new_from_i64_slice(
mut store: impl AsContextMut,
allocator: &ArrayRefPre,
elems: &[u64],
) -> Result<Rooted<ArrayRef>> {
let mut ctx = store.as_context_mut();
let storage = StorageType::ValType(crate::ValType::I64);
let charge = Self::packed_charge(ctx.inner(), allocator, storage, elems.len())?;
ctx.0.gc_reserve_host(charge)?;
Self::finish_packed(ctx.inner_mut(), allocator, i64_body(elems)?)
}
#[cfg(feature = "async")]
pub async fn new_from_i8_slice_async<T: 'static>(
mut store: impl AsContextMut<Data = T>,
allocator: &ArrayRefPre,
elems: &[u8],
) -> Result<Rooted<ArrayRef>> {
let mut ctx = store.as_context_mut();
let charge = Self::packed_charge(ctx.inner(), allocator, StorageType::I8, elems.len())?;
ctx.store_mut().gc_reserve_host_async(charge).await?;
Self::finish_packed(ctx.inner_mut(), allocator, elems.to_vec())
}
pub fn new_from_i16_slice(
mut store: impl AsContextMut,
allocator: &ArrayRefPre,
elems: &[u16],
) -> Result<Rooted<ArrayRef>> {
let mut ctx = store.as_context_mut();
let charge = Self::packed_charge(ctx.inner(), allocator, StorageType::I16, elems.len())?;
ctx.0.gc_reserve_host(charge)?;
Self::finish_packed(ctx.inner_mut(), allocator, i16_body(elems))
}
#[cfg(feature = "async")]
pub async fn new_from_i16_slice_async<T: 'static>(
mut store: impl AsContextMut<Data = T>,
allocator: &ArrayRefPre,
elems: &[u16],
) -> Result<Rooted<ArrayRef>> {
let mut ctx = store.as_context_mut();
let charge = Self::packed_charge(ctx.inner(), allocator, StorageType::I16, elems.len())?;
ctx.store_mut().gc_reserve_host_async(charge).await?;
Self::finish_packed(ctx.inner_mut(), allocator, i16_body(elems))
}
fn packed_charge(
inner: &StoreInner,
allocator: &ArrayRefPre,
expected: StorageType,
count: usize,
) -> Result<usize> {
inner.check_handle(allocator.store);
if allocator.ty.element_type() != expected {
return Err(Error::msg(format!(
"element type mismatch: cannot initialize a non-{expected:?} array from a slice"
)));
}
u32::try_from(count).map_err(|_| Error::msg("array too large"))?;
let byte_len = count
.checked_mul(allocator.layout.stride())
.ok_or_else(|| Error::msg("array too large"))?;
Ok(inner.gc_object_charge(byte_len))
}
fn finish_packed(
inner: &mut StoreInner,
allocator: &ArrayRefPre,
body: Vec<u8>,
) -> Result<Rooted<ArrayRef>> {
inner.check_handle(allocator.store);
let type_id = allocator.ty.canonical_id();
let object = GcObject::new_array(type_id, body.into_boxed_slice());
let idx = inner.alloc_host_gc(type_id, object)?;
Ok(root_new_gc(inner, idx))
}
fn alloc<'a>(
mut store: impl AsContextMut,
allocator: &ArrayRefPre,
count: usize,
elem_at: impl Fn(usize) -> &'a Val,
) -> Result<Rooted<ArrayRef>> {
let stride = allocator.layout.stride();
let byte_len = count
.checked_mul(stride)
.ok_or_else(|| Error::msg("array too large"))?;
let mut ctx = store.as_context_mut();
ctx.inner().check_handle(allocator.store);
let field_ty = allocator.ty.field_type();
for i in 0..count {
if !slot_accepts(allocator.layout.elem_at(0), elem_at(i))
|| !field_value_matches(ctx.inner(), &field_ty, elem_at(i))?
{
return Err(Error::msg("array element value has the wrong type"));
}
}
let type_id = allocator.ty.canonical_id();
let charge = ctx.inner().gc_object_charge(byte_len);
ctx.0.gc_reserve_host(charge)?;
let inner = ctx.inner_mut();
let mut data = vec![0u8; byte_len];
for i in 0..count {
write_slot(allocator.layout.elem_at(i), &mut data, *elem_at(i));
}
let object = GcObject::new_array(type_id, data.into_boxed_slice());
let idx = inner.alloc_host_gc(type_id, object)?;
Ok(root_new_gc(inner, idx))
}
}
impl Rooted<ArrayRef> {
pub fn len(&self, store: impl AsContext) -> Result<u32> {
let ctx = store.as_context();
let inner = ctx.inner();
let slot = self.gc_slot_checked(inner)?;
let obj = gc_object(inner, slot)?;
let stride = Layout::for_array(&inner.engine().array_field(obj.header.type_id)?).stride();
Ok(obj.array_len(stride))
}
pub fn get(&self, store: impl AsContext, index: u32) -> Result<Val> {
let ctx = store.as_context();
let inner = ctx.inner();
let slot = self.gc_slot_checked(inner)?;
let obj = gc_object(inner, slot)?;
let layout = Layout::for_array(&inner.engine().array_field(obj.header.type_id)?);
if index >= obj.array_len(layout.stride()) {
return Err(Error::msg("array index out of bounds"));
}
inner.root_host_result(read_slot(layout.elem_at(index as usize), &obj.data))
}
pub fn copy_to_i8_slice(&self, store: impl AsContext, dst: &mut [u8]) -> Result<()> {
let ctx = store.as_context();
let body = self.packed_body(ctx.inner(), StorageType::I8, dst.len())?;
dst.copy_from_slice(body);
Ok(())
}
pub fn copy_to_i16_slice(&self, store: impl AsContext, dst: &mut [u16]) -> Result<()> {
let ctx = store.as_context();
let body = self.packed_body(ctx.inner(), StorageType::I16, dst.len())?;
let (pairs, _) = body.as_chunks::<2>();
for (unit, &pair) in dst.iter_mut().zip(pairs) {
*unit = u16::from_le_bytes(pair);
}
Ok(())
}
pub fn copy_to_i64_slice(&self, store: impl AsContext, dst: &mut [u64]) -> Result<()> {
let ctx = store.as_context();
let body = self.packed_body(
ctx.inner(),
StorageType::ValType(crate::ValType::I64),
dst.len(),
)?;
for (unit, &bytes) in dst.iter_mut().zip(body.as_chunks::<8>().0) {
*unit = u64::from_le_bytes(bytes);
}
Ok(())
}
fn packed_body(
self,
inner: &StoreInner,
expected: StorageType,
dst_len: usize,
) -> Result<&[u8]> {
let slot = self.gc_slot_checked(inner)?;
let obj = gc_object(inner, slot)?;
let field_ty = inner.engine().array_field(obj.header.type_id)?;
if *field_ty.element_type() != expected {
return Err(Error::msg(format!(
"element type mismatch: cannot copy a non-{expected:?} array into a slice"
)));
}
let len = obj.array_len(Layout::for_array(&field_ty).stride());
let dst_len = u32::try_from(dst_len).map_err(|_| Error::msg("destination too large"))?;
if dst_len != len {
return Err(Error::msg(format!(
"destination slice length is {dst_len} but the array length is {len}"
)));
}
Ok(&obj.data)
}
pub fn read_i8(&self, store: impl AsContext, offset: u32, dst: &mut [u8]) -> Result<()> {
let ctx = store.as_context();
let body = self.packed_range(ctx.inner(), StorageType::I8, offset, dst.len())?;
dst.copy_from_slice(body);
Ok(())
}
pub fn read_i16(&self, store: impl AsContext, offset: u32, dst: &mut [u16]) -> Result<()> {
let ctx = store.as_context();
let body = self.packed_range(ctx.inner(), StorageType::I16, offset, dst.len())?;
let (pairs, _) = body.as_chunks::<2>();
for (unit, &pair) in dst.iter_mut().zip(pairs) {
*unit = u16::from_le_bytes(pair);
}
Ok(())
}
fn packed_range(
self,
inner: &StoreInner,
expected: StorageType,
offset: u32,
count: usize,
) -> Result<&[u8]> {
let slot = self.gc_slot_checked(inner)?;
let obj = gc_object(inner, slot)?;
let field_ty = inner.engine().array_field(obj.header.type_id)?;
if *field_ty.element_type() != expected {
return Err(Error::msg(format!(
"element type mismatch: cannot read a non-{expected:?} array into a slice"
)));
}
let stride = Layout::for_array(&field_ty).stride();
let range = element_byte_range(offset, count, stride)
.ok_or_else(|| Error::msg("array read range overflows"))?;
obj.data
.get(range)
.ok_or_else(|| Error::msg("array read out of bounds"))
}
pub fn write_i8(&self, mut store: impl AsContextMut, offset: u32, src: &[u8]) -> Result<()> {
let mut ctx = store.as_context_mut();
let body = self.packed_body_mut(ctx.inner_mut(), StorageType::I8, offset, src.len())?;
body.copy_from_slice(src);
Ok(())
}
pub fn write_i16(&self, mut store: impl AsContextMut, offset: u32, src: &[u16]) -> Result<()> {
let mut ctx = store.as_context_mut();
let body = self.packed_body_mut(ctx.inner_mut(), StorageType::I16, offset, src.len())?;
let (pairs, _) = body.as_chunks_mut::<2>();
for (pair, unit) in pairs.iter_mut().zip(src) {
*pair = unit.to_le_bytes();
}
Ok(())
}
fn packed_body_mut(
self,
inner: &mut StoreInner,
expected: StorageType,
offset: u32,
count: usize,
) -> Result<&mut [u8]> {
let slot = self.gc_slot_checked(inner)?;
let field_ty = inner
.engine()
.array_field(gc_object(inner, slot)?.header.type_id)?;
if *field_ty.element_type() != expected {
return Err(Error::msg(format!(
"element type mismatch: cannot write a slice into a non-{expected:?} array"
)));
}
if field_ty.mutability() != Mutability::Var {
return Err(Error::msg("array element is not mutable"));
}
let stride = Layout::for_array(&field_ty).stride();
let range = element_byte_range(offset, count, stride)
.ok_or_else(|| Error::msg("array write range overflows"))?;
let obj = inner
.gc_object_mut(slot)
.ok_or_else(|| Error::msg("dangling gc reference"))?;
obj.data
.get_mut(range)
.ok_or_else(|| Error::msg("array write out of bounds"))
}
pub fn set(&self, mut store: impl AsContextMut, index: u32, value: Val) -> Result<()> {
let mut ctx = store.as_context_mut();
let inner = ctx.inner_mut();
let slot = self.gc_slot_checked(inner)?;
let type_id = gc_object(inner, slot)?.header.type_id;
let field_ty = inner.engine().array_field(type_id)?;
let len = gc_object(inner, slot)?.array_len(Layout::for_array(&field_ty).stride());
if index >= len {
return Err(Error::msg("array index out of bounds"));
}
if field_ty.mutability() != Mutability::Var {
return Err(Error::msg("array element is not mutable"));
}
let elem = Layout::for_array(&field_ty).elem_at(index as usize);
if !slot_accepts(elem, &value) || !field_value_matches(inner, &field_ty, &value)? {
return Err(Error::msg("array element value has the wrong type"));
}
let obj = inner
.gc_object_mut(slot)
.ok_or_else(|| Error::msg("dangling gc reference"))?;
write_slot(elem, &mut obj.data, value);
Ok(())
}
pub fn to_anyref(self) -> Rooted<AnyRef> {
self.cast()
}
}
fn field_value_matches(inner: &StoreInner, ty: &FieldType, value: &Val) -> Result<bool> {
match ty.element_type() {
StorageType::I8 | StorageType::I16 => Ok(matches!(value, Val::I32(_))),
StorageType::ValType(ty) => crate::extern_::val_matches_in_store(inner, value, ty),
}
}
impl From<Rooted<ArrayRef>> for Rooted<AnyRef> {
fn from(r: Rooted<ArrayRef>) -> Self {
r.cast()
}
}
fn i16_body(elems: &[u16]) -> Vec<u8> {
elems.iter().flat_map(|unit| unit.to_le_bytes()).collect()
}
fn zeroed_body(len: usize) -> Result<Vec<u8>> {
let mut body = Vec::new();
body.try_reserve_exact(len).map_err(Error::new)?;
body.resize(len, 0);
Ok(body)
}
fn i64_body(elems: &[u64]) -> Result<Vec<u8>> {
let byte_len = elems
.len()
.checked_mul(8)
.ok_or_else(|| Error::msg("array too large"))?;
let mut body = zeroed_body(byte_len)?;
for (bytes, unit) in body.as_chunks_mut::<8>().0.iter_mut().zip(elems) {
*bytes = unit.to_le_bytes();
}
Ok(body)
}
fn element_byte_range(offset: u32, count: usize, stride: usize) -> Option<Range<usize>> {
let start = usize::try_from(offset).ok()?.checked_mul(stride)?;
let end = count.checked_mul(stride)?.checked_add(start)?;
Some(start..end)
}