use core::{
any::TypeId,
fmt,
iter::FusedIterator,
marker::PhantomData,
mem::MaybeUninit,
ops::{Bound, RangeBounds},
};
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use crate::{
Gc, GcHeapObj, GcValue, abi,
schema::{Operation, OperationKind, Schema, SchemaData, SchemaKind},
value::GcAbi,
};
pub type GcArray<T> = Gc<Array<T>>;
pub struct Array<T: GcValue> {
element: PhantomData<fn() -> T>,
}
unsafe impl<T: GcValue> GcHeapObj for Array<T> {
const KIND: SchemaKind = SchemaKind::Array;
const SCHEMA: *const Schema =
(&SchemaData::array(T::KIND) as *const SchemaData<1>).cast::<Schema>();
}
const COPY_BATCH_SIZE: usize = 64;
struct AbiValues<T: GcValue> {
values: [MaybeUninit<T::Abi>; COPY_BATCH_SIZE],
initialized: usize,
consumed: usize,
}
impl<T: GcValue> AbiValues<T> {
fn take(&mut self) -> T {
let index = self.consumed;
self.consumed += 1;
unsafe { T::from_abi(self.values[index].assume_init_read()) }
}
}
impl<T: GcValue> Drop for AbiValues<T> {
fn drop(&mut self) {
while self.consumed < self.initialized {
drop(self.take());
}
}
}
fn wasm_len(value: usize) -> u32 {
u32::try_from(value).expect("wasmgc: GC array length or offset exceeds u32::MAX")
}
fn check_range(offset: usize, count: usize, length: usize) {
assert!(
offset <= length && count <= length - offset,
"wasmgc: buffer range out of bounds"
);
}
fn range_bounds(range: impl RangeBounds<usize>, length: usize) -> (usize, usize) {
let start = match range.start_bound() {
Bound::Included(start) => *start,
Bound::Excluded(start) => start.checked_add(1).expect("wasmgc: range start overflow"),
Bound::Unbounded => 0,
};
let end = match range.end_bound() {
Bound::Included(end) => end.checked_add(1).expect("wasmgc: range end overflow"),
Bound::Excluded(end) => *end,
Bound::Unbounded => length,
};
assert!(
start <= end && end <= length,
"wasmgc: buffer range out of bounds"
);
(start, end)
}
impl<T: GcValue> Gc<Array<T>> {
pub fn array_new(length: usize, value: T) -> Self {
let value_abi = value.to_abi();
unsafe { Self::from_raw(ArrayOps::<T>::array_new(wasm_len(length), value_abi)) }
}
pub fn new_default(length: usize) -> Self {
const {
assert!(
T::KIND.is_defaultable(),
"wasmgc: new_default requires defaultable Wasm storage; use array_new for non-null references"
);
}
unsafe { Self::from_raw(ArrayOps::<T>::new_default(wasm_len(length))) }
}
pub fn len(&self) -> usize {
unsafe { ArrayOps::<T>::len(self.as_raw()) as usize }
}
pub fn get(&self, index: usize) -> Option<T> {
if index >= self.len() {
return None;
}
Some(unsafe { T::from_abi(ArrayOps::<T>::get(self.as_raw(), index as u32)) })
}
pub fn set(&self, index: usize, value: T::SetterValue<'_>) {
assert!(index < self.len(), "wasmgc: array index out of bounds");
let value_abi = T::setter_abi(&value);
unsafe { ArrayOps::<T>::set(self.as_raw(), index as u32, value_abi) }
}
pub fn fill(&self, value: T::SetterValue<'_>) {
let value_abi = T::setter_abi(&value);
unsafe { ArrayOps::<T>::fill(self.as_raw(), value_abi) }
}
pub fn copy(&self, destination: usize, source: &Self, source_offset: usize, length: usize) {
check_range(source_offset, length, source.len());
check_range(destination, length, self.len());
unsafe { self.copy_unchecked(destination, source, source_offset, length) }
}
}
impl<T: GcValue> Gc<Array<T>> {
pub fn empty() -> Self {
unsafe { Self::from_raw(ArrayOps::<T>::new_fixed()) }
}
pub fn defaulted(length: usize) -> Self
where
T: Default,
{
Self::array_new(length, T::default())
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn copy_from(&self, destination: usize, source: &Self, range: impl RangeBounds<usize>) {
let (start, end) = range_bounds(range, source.len());
let length = end - start;
check_range(destination, length, self.len());
unsafe { self.copy_unchecked(destination, source, start, length) }
}
pub fn copy_within(&self, range: impl RangeBounds<usize>, destination: usize) {
self.copy_from(destination, self, range);
}
pub fn iter(&self) -> GcArrayIter<'_, T> {
GcArrayIter::new(self, self.len())
}
pub fn from_slice(values: impl AsRef<[T]>) -> Self {
let values = values.as_ref();
let length = wasm_len(values.len());
if let Some(ops) = scalar_ops::<T>() {
return unsafe { Self::from_raw((ops.from_slice)(values.as_ptr().cast(), length)) };
}
let Some((first, rest)) = values.split_first() else {
return Self::empty();
};
let array = unsafe { Self::from_raw(ArrayOps::<T>::array_new(length, first.to_abi())) };
unsafe { array.write_unchecked(1, rest) };
array
}
pub fn read(&self, offset: usize, output: &mut [T]) {
check_range(offset, output.len(), self.len());
unsafe { self.read_unchecked(offset, output) }
}
pub fn write(&self, offset: usize, input: &[T]) {
check_range(offset, input.len(), self.len());
unsafe { self.write_unchecked(offset, input) }
}
pub fn copy_to_slice(&self, output: &mut [T]) {
assert_eq!(output.len(), self.len(), "wasmgc: slice lengths differ");
unsafe { self.read_unchecked(0, output) }
}
pub fn copy_from_slice(&self, input: &[T]) {
assert_eq!(input.len(), self.len(), "wasmgc: slice lengths differ");
unsafe { self.write_unchecked(0, input) }
}
}
impl<T: GcValue> Gc<Array<T>> {
unsafe fn copy_unchecked(
&self,
destination: usize,
source: &Self,
source_offset: usize,
length: usize,
) {
unsafe {
ArrayOps::<T>::copy(
self.as_raw(),
destination as u32,
source.as_raw(),
source_offset as u32,
length as u32,
)
}
}
unsafe fn read_unchecked(&self, offset: usize, output: &mut [T]) {
if let Some(ops) = scalar_ops::<T>() {
unsafe {
(ops.read)(
self.as_raw(),
offset as u32,
output.as_mut_ptr().cast(),
output.len() as u32,
)
}
return;
}
for (index, output) in output.chunks_mut(COPY_BATCH_SIZE).enumerate() {
let mut values = self.read_abi(offset + index * COPY_BATCH_SIZE, output.len());
for value in output {
*value = values.take();
}
}
}
unsafe fn write_unchecked(&self, offset: usize, input: &[T]) {
if let Some(ops) = scalar_ops::<T>() {
unsafe {
(ops.write)(
self.as_raw(),
offset as u32,
input.as_ptr().cast(),
input.len() as u32,
)
}
return;
}
let mut values = [MaybeUninit::<T::Abi>::uninit(); COPY_BATCH_SIZE];
for (index, input) in input.chunks(COPY_BATCH_SIZE).enumerate() {
for (value, input) in values.iter_mut().zip(input) {
value.write(input.to_abi());
}
unsafe {
ArrayOps::<T>::write_abi(
self.as_raw(),
(offset + index * COPY_BATCH_SIZE) as u32,
values.as_ptr().cast(),
input.len() as u32,
)
}
}
}
fn read_abi(&self, offset: usize, length: usize) -> AbiValues<T> {
let mut values = AbiValues {
values: [MaybeUninit::<T::Abi>::uninit(); COPY_BATCH_SIZE],
initialized: 0,
consumed: 0,
};
unsafe {
ArrayOps::<T>::read_abi(
self.as_raw(),
offset as u32,
values.values.as_mut_ptr().cast(),
length as u32,
)
}
values.initialized = length;
values
}
}
#[cfg(feature = "alloc")]
impl<T: GcValue> Gc<Array<T>> {
pub fn to_vec(&self) -> Vec<T> {
if let Some(ops) = scalar_ops::<T>() {
let length = self.len();
let mut result = Vec::<T>::with_capacity(length);
unsafe {
(ops.read)(self.as_raw(), 0, result.as_mut_ptr().cast(), length as u32);
result.set_len(length);
}
return result;
}
let length = self.len();
let mut result = Vec::with_capacity(length);
let mut offset = 0;
while offset < length {
let count = COPY_BATCH_SIZE.min(length - offset);
let mut values = self.read_abi(offset, count);
for _ in 0..count {
result.push(values.take());
}
offset += count;
}
result
}
}
impl<T: GcValue> Default for GcArray<T> {
fn default() -> Self {
Self::empty()
}
}
impl<T: GcValue> From<&[T]> for GcArray<T> {
fn from(values: &[T]) -> Self {
Self::from_slice(values)
}
}
impl<T: GcValue, const N: usize> From<[T; N]> for GcArray<T> {
fn from(values: [T; N]) -> Self {
Self::from_slice(values)
}
}
impl<T: GcValue, const N: usize> From<&[T; N]> for GcArray<T> {
fn from(values: &[T; N]) -> Self {
Self::from_slice(values)
}
}
#[cfg(feature = "alloc")]
impl<T: GcValue> From<Vec<T>> for GcArray<T> {
fn from(values: Vec<T>) -> Self {
Self::from_slice(values)
}
}
impl<T: GcValue + fmt::Debug> fmt::Debug for GcArray<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.iter()).finish()
}
}
impl<T: GcValue + PartialEq> PartialEq for GcArray<T> {
fn eq(&self, other: &Self) -> bool {
self.iter().eq(other.iter())
}
}
impl<T: GcValue + Eq> Eq for GcArray<T> {}
impl<T: GcValue + PartialEq> PartialEq<[T]> for GcArray<T> {
fn eq(&self, other: &[T]) -> bool {
self.len() == other.len() && self.iter().zip(other).all(|(left, right)| left.eq(right))
}
}
impl<T: GcValue + PartialEq> PartialEq<&[T]> for GcArray<T> {
fn eq(&self, other: &&[T]) -> bool {
self == *other
}
}
impl<T: GcValue + PartialEq, const N: usize> PartialEq<[T; N]> for GcArray<T> {
fn eq(&self, other: &[T; N]) -> bool {
self == other.as_slice()
}
}
pub struct GcArrayIter<'a, T: GcValue> {
array: &'a GcArray<T>,
front: usize,
back: usize,
}
impl<'a, T: GcValue> GcArrayIter<'a, T> {
fn new(array: &'a GcArray<T>, length: usize) -> Self {
Self {
array,
front: 0,
back: length,
}
}
fn element(&self, index: usize) -> T {
unsafe { T::from_abi(ArrayOps::<T>::get(self.array.as_raw(), index as u32)) }
}
}
impl<T: GcValue> Iterator for GcArrayIter<'_, T> {
type Item = T;
fn next(&mut self) -> Option<T> {
if self.front == self.back {
return None;
}
let value = self.element(self.front);
self.front += 1;
Some(value)
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.len(), Some(self.len()))
}
fn nth(&mut self, n: usize) -> Option<T> {
self.front += n.min(self.len());
self.next()
}
fn count(self) -> usize {
self.len()
}
}
impl<T: GcValue> DoubleEndedIterator for GcArrayIter<'_, T> {
fn next_back(&mut self) -> Option<T> {
if self.front == self.back {
return None;
}
self.back -= 1;
Some(self.element(self.back))
}
fn nth_back(&mut self, n: usize) -> Option<T> {
self.back -= n.min(self.len());
self.next_back()
}
}
impl<T: GcValue> ExactSizeIterator for GcArrayIter<'_, T> {
fn len(&self) -> usize {
self.back - self.front
}
}
impl<T: GcValue> FusedIterator for GcArrayIter<'_, T> {}
impl<'a, T: GcValue> IntoIterator for &'a GcArray<T> {
type Item = T;
type IntoIter = GcArrayIter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
struct ArrayOps<T: GcValue>(PhantomData<fn() -> T>);
impl<T: GcValue> ArrayOps<T> {
const SCHEMA: *const Schema = <Array<T> as GcHeapObj>::SCHEMA;
}
struct ScalarOps {
from_slice: unsafe extern "C" fn(*const (), u32) -> u32,
read: unsafe extern "C" fn(u32, u32, *mut (), u32),
write: unsafe extern "C" fn(u32, u32, *const (), u32),
}
fn scalar_ops<T: GcValue>() -> Option<ScalarOps> {
macro_rules! scalar {
($($ty:ty),* $(,)?) => {
$(
if TypeId::of::<T>() == TypeId::of::<$ty>() {
return Some(ScalarOps {
from_slice: ArrayOps::<$ty>::from_slice,
read: ArrayOps::<$ty>::read,
write: ArrayOps::<$ty>::write,
});
}
)*
};
}
scalar!(i8, u8, i16, u16, i32, u32, i64, u64, f32, f64, bool);
None
}
macro_rules! operation {
($name:ident, $descriptor:ident, $kind:ident, ($($param:ident: $ty:ty),*) -> $result:ty) => {
const $descriptor: Operation = Operation::new(
Self::SCHEMA,
OperationKind::$kind,
0,
Self::$name as *const (),
);
#[inline(never)]
unsafe extern "C" fn $name($($param: $ty),*) -> $result {
let arguments = ($($param,)*);
unsafe {
<$result as GcAbi>::marker(
(&Self::$descriptor as *const Operation).cast(),
(&raw const arguments).cast(),
)
}
}
};
($name:ident, $descriptor:ident, $kind:ident, ($($param:ident: $ty:ty),*)) => {
const $descriptor: Operation = Operation::new(
Self::SCHEMA,
OperationKind::$kind,
0,
Self::$name as *const (),
);
#[inline(never)]
unsafe extern "C" fn $name($($param: $ty),*) {
let arguments = ($($param,)*);
unsafe {
abi::marker_void(
(&Self::$descriptor as *const Operation).cast(),
(&raw const arguments).cast(),
)
}
}
};
}
impl<T: GcValue> ArrayOps<T> {
operation!(array_new, ARRAY_NEW, ArrayNew, (length: u32, value: T::Abi) -> u32);
operation!(new_fixed, NEW_FIXED, ArrayNewFixed, () -> u32);
operation!(new_default, NEW_DEFAULT, ArrayNewDefault, (length: u32) -> u32);
operation!(len, LEN, ArrayLen, (handle: u32) -> u32);
operation!(get, GET, ArrayGet, (handle: u32, index: u32) -> T::Abi);
operation!(set, SET, ArraySet, (handle: u32, index: u32, value: T::Abi));
operation!(fill, FILL, ArrayFill, (handle: u32, value: T::Abi));
operation!(
copy, COPY, ArrayCopy,
(destination: u32, destination_offset: u32, source: u32, source_offset: u32, length: u32)
);
}
impl<T: GcValue> ArrayOps<T> {
operation!(
read_abi, READ_ABI, ArrayReadAbi,
(handle: u32, offset: u32, pointer: *mut (), length: u32)
);
operation!(
write_abi, WRITE_ABI, ArrayWriteAbi,
(handle: u32, offset: u32, pointer: *const (), length: u32)
);
operation!(from_slice, FROM_SLICE, ArrayFromSlice, (pointer: *const (), length: u32) -> u32);
operation!(read, READ, ArrayRead, (handle: u32, offset: u32, pointer: *mut (), length: u32));
operation!(write, WRITE, ArrayWrite, (handle: u32, offset: u32, pointer: *const (), length: u32));
}