Skip to main content

wasmgc/
array.rs

1use core::{
2    any::TypeId,
3    fmt,
4    iter::FusedIterator,
5    marker::PhantomData,
6    mem::MaybeUninit,
7    ops::{Bound, RangeBounds},
8};
9
10#[cfg(feature = "alloc")]
11use alloc::vec::Vec;
12
13use crate::{
14    Gc, GcHeapObj, GcValue, abi,
15    schema::{Operation, OperationKind, Schema, SchemaData, SchemaKind},
16    value::GcAbi,
17};
18
19/// An owned root to a fixed-length, mutable WasmGC array.
20///
21/// This is an alias for [`Gc<Array<T>>`]. Elements implement [`GcValue`] and may
22/// be scalars, GC references, nullable GC references, or [`crate::ExternRef`].
23///
24/// Cloning an array shares its object; writes through either root are visible
25/// through both. Getters copy element values rather than returning Rust
26/// references into the array. Reading a reference element creates an independent
27/// root to the stored object. Array equality compares elements; [`Gc::ref_eq`]
28/// compares the array objects themselves.
29///
30/// Copies between GC arrays use `array.copy`, including overlapping ranges.
31/// Copies between GC storage and Rust slices transfer elements one at a time:
32/// built-in scalars use a generated Wasm loop, while other types use their
33/// [`GcValue`] conversions in batches. Neither path exposes GC storage as
34/// linear memory. The `alloc` feature adds `to_vec` and conversion from `Vec<T>`.
35///
36/// Array lengths and indices are 32-bit on both wasm32 and wasm64. Constructors
37/// reject lengths above `u32::MAX`; an engine may fail smaller allocations too.
38/// Roots have the same ownership and instance restrictions as [`Gc`].
39///
40/// # Examples
41///
42/// Compile for a Wasm target and postprocess the resulting module with `wasmgc`
43/// before executing this example.
44///
45/// ```no_run
46/// use wasmgc::{Gc, GcArray};
47///
48/// let values = GcArray::from_slice([1i32, 2, 3]);
49/// let alias = values.clone();
50/// alias.set(1, 7);
51/// assert_eq!(values.get(1), Some(7));
52/// assert!(Gc::ref_eq(&values, &alias));
53///
54/// let copy = GcArray::<i32>::new_default(values.len());
55/// copy.copy_from(0, &values, ..);
56/// copy.set(1, 9);
57/// assert_eq!(values.get(1), Some(7));
58/// assert!(!Gc::ref_eq(&values, &copy));
59/// ```
60pub type GcArray<T> = Gc<Array<T>>;
61
62/// The heap-type description for an array of [`GcValue`] elements.
63///
64/// Use [`GcArray<T>`] to construct and own an array object. `Array<T>` supplies
65/// its element schema to [`GcHeapObj`]; it does not contain the elements in Rust
66/// memory. See [`GcArray`] for an example.
67pub struct Array<T: GcValue> {
68    element: PhantomData<fn() -> T>,
69}
70
71// SAFETY: The promoted static schema contains one mutable element using
72// T's declared storage kind. T's GcValue contract validates that storage and
73// any referenced schema, and all ArrayOps<T> use this same schema.
74unsafe impl<T: GcValue> GcHeapObj for Array<T> {
75    const KIND: SchemaKind = SchemaKind::Array;
76
77    const SCHEMA: *const Schema =
78        (&SchemaData::array(T::KIND) as *const SchemaData<1>).cast::<Schema>();
79}
80
81const COPY_BATCH_SIZE: usize = 64;
82
83// A read batch owns every initialized ABI result until take transfers it to T.
84// The counters track root ownership as well as initialized numeric slots.
85struct AbiValues<T: GcValue> {
86    values: [MaybeUninit<T::Abi>; COPY_BATCH_SIZE],
87    initialized: usize,
88    consumed: usize,
89}
90
91impl<T: GcValue> AbiValues<T> {
92    fn take(&mut self) -> T {
93        let index = self.consumed;
94        self.consumed += 1;
95        // SAFETY: Callers take at most initialized entries, each written by the
96        // read shim as a valid owned ABI result. Advancing consumed first prevents
97        // Drop from decoding that same result again if conversion panics.
98        unsafe { T::from_abi(self.values[index].assume_init_read()) }
99    }
100}
101
102impl<T: GcValue> Drop for AbiValues<T> {
103    fn drop(&mut self) {
104        // An output destructor or conversion may panic partway through a batch.
105        // Release the remaining owned results as well as the ones already returned.
106        while self.consumed < self.initialized {
107            drop(self.take());
108        }
109    }
110}
111
112fn wasm_len(value: usize) -> u32 {
113    u32::try_from(value).expect("wasmgc: GC array length or offset exceeds u32::MAX")
114}
115
116fn check_range(offset: usize, count: usize, length: usize) {
117    assert!(
118        offset <= length && count <= length - offset,
119        "wasmgc: buffer range out of bounds"
120    );
121}
122
123fn range_bounds(range: impl RangeBounds<usize>, length: usize) -> (usize, usize) {
124    let start = match range.start_bound() {
125        Bound::Included(start) => *start,
126        Bound::Excluded(start) => start.checked_add(1).expect("wasmgc: range start overflow"),
127        Bound::Unbounded => 0,
128    };
129    let end = match range.end_bound() {
130        Bound::Included(end) => end.checked_add(1).expect("wasmgc: range end overflow"),
131        Bound::Excluded(end) => *end,
132        Bound::Unbounded => length,
133    };
134    assert!(
135        start <= end && end <= length,
136        "wasmgc: buffer range out of bounds"
137    );
138    (start, end)
139}
140
141impl<T: GcValue> Gc<Array<T>> {
142    /// Creates an array of `length` elements initialized to `value`, using `array.new`.
143    ///
144    /// The construction value is consumed after its representation has been stored.
145    /// For a reference value, every element refers to the same object; construction
146    /// does not clone that object.
147    ///
148    /// # Panics
149    ///
150    /// Panics if `length` exceeds `u32::MAX`.
151    pub fn array_new(length: usize, value: T) -> Self {
152        let value_abi = value.to_abi();
153        // SAFETY: value retains the encoded input through allocation and storage.
154        // The shim returns one owned, nonzero root with the Array<T> schema.
155        unsafe { Self::from_raw(ArrayOps::<T>::array_new(wasm_len(length), value_abi)) }
156    }
157
158    /// Creates an array initialized with Wasm storage defaults, using `array.new_default`.
159    ///
160    /// Numeric elements are zero, booleans are false, and nullable references are
161    /// null. This uses the default for [`GcValue::KIND`], without calling Rust's
162    /// [`Default`] implementation. For [`crate::ExternRef`], reading a null
163    /// element still produces a nonzero owned root.
164    ///
165    /// The element storage must be defaultable. Non-null GC-reference elements
166    /// are rejected at compile time, even for a zero length. Use [`Self::array_new`]
167    /// to supply an initial value, or [`Self::empty`] to construct an empty array
168    /// of any supported element type.
169    ///
170    /// # Panics
171    ///
172    /// Panics if `length` exceeds `u32::MAX`.
173    pub fn new_default(length: usize) -> Self {
174        const {
175            assert!(
176                T::KIND.is_defaultable(),
177                "wasmgc: new_default requires defaultable Wasm storage; use array_new for non-null references"
178            );
179        }
180        // SAFETY: The const assertion permits only defaultable storage. The
181        // generated allocation transfers one nonzero root for this array schema.
182        unsafe { Self::from_raw(ArrayOps::<T>::new_default(wasm_len(length))) }
183    }
184
185    /// Returns the number of elements, using `array.len`.
186    ///
187    /// The length is fixed when the array is constructed.
188    pub fn len(&self) -> usize {
189        // SAFETY: self retains a live Array<T> root and the matching shim reads
190        // its length without consuming that root.
191        unsafe { ArrayOps::<T>::len(self.as_raw()) as usize }
192    }
193
194    /// Returns the element at `index`, or `None` if it is out of bounds.
195    ///
196    /// This uses `array.get`, or `array.get_u` for packed integer storage.
197    /// Reference elements return new owned roots to the stored objects.
198    /// See [`GcArray`] for an example.
199    pub fn get(&self, index: usize) -> Option<T> {
200        if index >= self.len() {
201            return None;
202        }
203        // SAFETY: The index is below this fixed array's u32 length. The matching
204        // getter returns a valid scalar or a newly owned root of T's declared kind.
205        Some(unsafe { T::from_abi(ArrayOps::<T>::get(self.as_raw(), index as u32)) })
206    }
207
208    /// Replaces the element at `index`, using `array.set`.
209    ///
210    /// Reference inputs borrow a root until the reference is stored. The array
211    /// then retains the object independently. See [`GcArray`] for an example.
212    ///
213    /// # Panics
214    ///
215    /// Panics if `index` is out of bounds.
216    pub fn set(&self, index: usize, value: T::SetterValue<'_>) {
217        assert!(index < self.len(), "wasmgc: array index out of bounds");
218        let value_abi = T::setter_abi(&value);
219        // SAFETY: The checked index fits the array and u32. self and the complete
220        // setter value retain all required roots until the matching write returns.
221        unsafe { ArrayOps::<T>::set(self.as_raw(), index as u32, value_abi) }
222    }
223
224    /// Replaces every element with `value`, using `array.fill`.
225    ///
226    /// For a reference value, every element retains the same object. The input
227    /// root is borrowed only for the duration of the operation.
228    pub fn fill(&self, value: T::SetterValue<'_>) {
229        let value_abi = T::setter_abi(&value);
230        // SAFETY: self roots the destination and value retains the encoded input.
231        // The generated shim fills exactly the destination's current length.
232        unsafe { ArrayOps::<T>::fill(self.as_raw(), value_abi) }
233    }
234
235    /// Copies `length` elements from `source` into this array, using `array.copy`.
236    ///
237    /// Elements starting at `source_offset` are copied to `destination`. The
238    /// arrays may be the same object, and overlapping ranges are supported.
239    /// Reference elements retain the same objects without cloning them.
240    ///
241    /// # Panics
242    ///
243    /// Panics if either range falls outside its array. A zero-length range may
244    /// start at the array's end.
245    pub fn copy(&self, destination: usize, source: &Self, source_offset: usize, length: usize) {
246        check_range(source_offset, length, source.len());
247        check_range(destination, length, self.len());
248        // SAFETY: Both complete ranges were checked against their live arrays;
249        // array.copy permits either root to refer to the same array.
250        unsafe { self.copy_unchecked(destination, source, source_offset, length) }
251    }
252}
253
254impl<T: GcValue> Gc<Array<T>> {
255    /// Creates an empty array, using `array.new_fixed` with no elements.
256    ///
257    /// No initial value is required, including for non-null reference elements.
258    /// [`Default`] also constructs an empty array.
259    pub fn empty() -> Self {
260        // SAFETY: A zero-element array requires no defaultable storage or input
261        // values, and the shim transfers one owned root of Array<T>.
262        unsafe { Self::from_raw(ArrayOps::<T>::new_fixed()) }
263    }
264
265    /// Creates an array filled with a single Rust [`Default`] value.
266    ///
267    /// Calls `T::default()` once and repeats its stored representation. For a
268    /// reference type, all elements share that default object. Compare
269    /// [`Self::new_default`], which uses Wasm storage defaults instead.
270    ///
271    /// # Panics
272    ///
273    /// Panics if `length` exceeds `u32::MAX`.
274    pub fn defaulted(length: usize) -> Self
275    where
276        T: Default,
277    {
278        Self::array_new(length, T::default())
279    }
280
281    /// Returns whether the array has no elements.
282    pub fn is_empty(&self) -> bool {
283        self.len() == 0
284    }
285
286    /// Copies a range of `source` to `destination` in this array.
287    ///
288    /// This is the Rust-range form of [`Self::copy`]. It supports aliasing and
289    /// overlap and preserves reference identity.
290    ///
291    /// # Panics
292    ///
293    /// Panics if `range` is invalid or outside `source`, if its bounds overflow,
294    /// or if the copied elements do not fit at `destination`.
295    ///
296    /// # Examples
297    ///
298    /// Compile for a Wasm target and postprocess before execution.
299    ///
300    /// ```no_run
301    /// use wasmgc::GcArray;
302    ///
303    /// let source = GcArray::from_slice([1i32, 2, 3, 4]);
304    /// let destination = GcArray::<i32>::new_default(3);
305    /// destination.copy_from(1, &source, 1..3);
306    /// assert_eq!(destination, [0, 2, 3]);
307    /// ```
308    pub fn copy_from(&self, destination: usize, source: &Self, range: impl RangeBounds<usize>) {
309        let (start, end) = range_bounds(range, source.len());
310        let length = end - start;
311        check_range(destination, length, self.len());
312        // SAFETY: range_bounds validated the complete source range and
313        // check_range validated the destination, including zero-length copies.
314        unsafe { self.copy_unchecked(destination, source, start, length) }
315    }
316
317    /// Copies a range to another position within this array.
318    ///
319    /// Overlapping ranges behave as if the source values were read before any
320    /// destination value was replaced.
321    ///
322    /// # Panics
323    ///
324    /// Panics under the same conditions as [`Self::copy_from`].
325    pub fn copy_within(&self, range: impl RangeBounds<usize>, destination: usize) {
326        self.copy_from(destination, self, range);
327    }
328
329    /// Returns an iterator over copied element values.
330    ///
331    /// Values are read as the iterator advances, so writes through other aliases
332    /// are observed by subsequent reads. Each reference element creates an
333    /// independently owned root. See [`GcArrayIter`] for an example.
334    pub fn iter(&self) -> GcArrayIter<'_, T> {
335        GcArrayIter::new(self, self.len())
336    }
337
338    /// Copies a Rust slice, array, or vector into a new GC array.
339    ///
340    /// The source elements remain alive until all values have been stored.
341    /// Reference elements keep the same objects without requiring [`Clone`].
342    /// An owned source container is dropped after copying.
343    ///
344    /// This copies between linear memory and GC storage, element by element;
345    /// it does not transfer a Rust allocation into the GC heap.
346    /// See [`GcArray`] for an example.
347    ///
348    /// # Panics
349    ///
350    /// Panics if the source length exceeds `u32::MAX`.
351    pub fn from_slice(values: impl AsRef<[T]>) -> Self {
352        let values = values.as_ref();
353        let length = wasm_len(values.len());
354        if let Some(ops) = scalar_ops::<T>() {
355            // SAFETY: scalar_ops matches T by TypeId to a built-in scalar with the
356            // shim's exact memory layout. The source slice remains readable through
357            // the copy, which returns one owned root of Array<T>.
358            return unsafe { Self::from_raw((ops.from_slice)(values.as_ptr().cast(), length)) };
359        }
360
361        let Some((first, rest)) = values.split_first() else {
362            return Self::empty();
363        };
364        // SAFETY: The nonempty source retains first through the allocation.
365        // The matching shim returns one owned array root; wrapping it here also
366        // releases that root if a later custom conversion panics.
367        let array = unsafe { Self::from_raw(ArrayOps::<T>::array_new(length, first.to_abi())) };
368        // SAFETY: array has values.len() elements, so rest exactly fits after
369        // the first element. The source retains every borrowed input root.
370        unsafe { array.write_unchecked(1, rest) };
371        array
372    }
373
374    /// Copies elements starting at `offset` into `output`.
375    ///
376    /// Each previous output element is dropped when replaced. Reference reads
377    /// produce owned roots independent of the array's root.
378    ///
379    /// # Panics
380    ///
381    /// Panics if the output range falls outside the array.
382    pub fn read(&self, offset: usize, output: &mut [T]) {
383        check_range(offset, output.len(), self.len());
384        // SAFETY: check_range validated the entire source range. output is
385        // an exclusive slice of initialized T values to replace.
386        unsafe { self.read_unchecked(offset, output) }
387    }
388
389    /// Copies `input` into this array starting at `offset`.
390    ///
391    /// The source retains its values until all writes finish. Reference writes
392    /// store the same objects rather than cloning them.
393    ///
394    /// # Panics
395    ///
396    /// Panics if the input range falls outside the array.
397    pub fn write(&self, offset: usize, input: &[T]) {
398        check_range(offset, input.len(), self.len());
399        // SAFETY: check_range validated the complete destination range, and
400        // the input slice keeps its values and any borrowed roots alive.
401        unsafe { self.write_unchecked(offset, input) }
402    }
403
404    /// Copies all elements into `output`, dropping its previous values.
405    ///
406    /// Reference elements receive independently owned roots.
407    ///
408    /// # Panics
409    ///
410    /// Panics if `output.len()` differs from this array's length.
411    pub fn copy_to_slice(&self, output: &mut [T]) {
412        assert_eq!(output.len(), self.len(), "wasmgc: slice lengths differ");
413        // SAFETY: Equal lengths make the complete source range valid, and
414        // output exclusively borrows initialized destination values.
415        unsafe { self.read_unchecked(0, output) }
416    }
417
418    /// Replaces all elements with values copied from `input`.
419    ///
420    /// Reference elements retain the source objects without cloning them.
421    ///
422    /// # Panics
423    ///
424    /// Panics if `input.len()` differs from this array's length.
425    pub fn copy_from_slice(&self, input: &[T]) {
426        assert_eq!(input.len(), self.len(), "wasmgc: slice lengths differ");
427        // SAFETY: Equal lengths make the complete destination range valid;
428        // input keeps the source values alive until the copy completes.
429        unsafe { self.write_unchecked(0, input) }
430    }
431}
432
433impl<T: GcValue> Gc<Array<T>> {
434    /// Copies an already-validated pair of GC ranges.
435    ///
436    /// # Safety
437    ///
438    /// Both complete ranges must fit within their respective arrays. Because
439    /// array lengths are u32, each offset and the count must also fit in u32.
440    unsafe fn copy_unchecked(
441        &self,
442        destination: usize,
443        source: &Self,
444        source_offset: usize,
445        length: usize,
446    ) {
447        // SAFETY: The caller guarantees both ranges fit and can be narrowed to
448        // u32. Both roots remain borrowed, use the same element schema, and
449        // array.copy explicitly permits overlap.
450        unsafe {
451            ArrayOps::<T>::copy(
452                self.as_raw(),
453                destination as u32,
454                source.as_raw(),
455                source_offset as u32,
456                length as u32,
457            )
458        }
459    }
460
461    /// Reads an already-validated range into initialized Rust values.
462    ///
463    /// # Safety
464    ///
465    /// `offset..offset + output.len()` must fit in this array. This also ensures
466    /// the offset and length fit the generated shim's u32 parameters.
467    unsafe fn read_unchecked(&self, offset: usize, output: &mut [T]) {
468        if let Some(ops) = scalar_ops::<T>() {
469            // SAFETY: TypeId selection proves T has the concrete scalar shim's
470            // layout. The caller validates the array range, and output provides an
471            // exclusive writable slice for the entire copy. Bool reads normalize
472            // the stored i32 to a valid Rust bool byte.
473            unsafe {
474                (ops.read)(
475                    self.as_raw(),
476                    offset as u32,
477                    output.as_mut_ptr().cast(),
478                    output.len() as u32,
479                )
480            }
481            return;
482        }
483
484        for (index, output) in output.chunks_mut(COPY_BATCH_SIZE).enumerate() {
485            let mut values = self.read_abi(offset + index * COPY_BATCH_SIZE, output.len());
486            for value in output {
487                *value = values.take();
488            }
489        }
490    }
491
492    /// Writes Rust values into an already-validated range.
493    ///
494    /// # Safety
495    ///
496    /// `offset..offset + input.len()` must fit in this array. The source slice
497    /// must retain the resources required by every encoded element until its
498    /// write finishes, as required by [`GcValue`].
499    unsafe fn write_unchecked(&self, offset: usize, input: &[T]) {
500        if let Some(ops) = scalar_ops::<T>() {
501            // SAFETY: TypeId selection proves T matches the concrete scalar shim's
502            // layout. The caller validates the array range, and input provides
503            // readable scalar bytes for the duration of the copy.
504            unsafe {
505                (ops.write)(
506                    self.as_raw(),
507                    offset as u32,
508                    input.as_ptr().cast(),
509                    input.len() as u32,
510                )
511            }
512            return;
513        }
514
515        let mut values = [MaybeUninit::<T::Abi>::uninit(); COPY_BATCH_SIZE];
516        for (index, input) in input.chunks(COPY_BATCH_SIZE).enumerate() {
517            for (value, input) in values.iter_mut().zip(input) {
518                value.write(input.to_abi());
519            }
520            // SAFETY: Each element of this batch was initialized by to_abi, and
521            // the input slice retains its borrowed roots. The caller's checked
522            // range covers this batch; its count never exceeds the ABI buffer.
523            unsafe {
524                ArrayOps::<T>::write_abi(
525                    self.as_raw(),
526                    (offset + index * COPY_BATCH_SIZE) as u32,
527                    values.as_ptr().cast(),
528                    input.len() as u32,
529                )
530            }
531        }
532    }
533
534    // Callers must supply a valid array range of at most COPY_BATCH_SIZE entries.
535    // The returned guard owns the fresh roots produced by the read shim.
536    fn read_abi(&self, offset: usize, length: usize) -> AbiValues<T> {
537        let mut values = AbiValues {
538            values: [MaybeUninit::<T::Abi>::uninit(); COPY_BATCH_SIZE],
539            initialized: 0,
540            consumed: 0,
541        };
542        // SAFETY: read_unchecked and to_vec bound their batches by both the
543        // array's length and COPY_BATCH_SIZE. The buffer has enough writable ABI
544        // slots, and the shim initializes each with a valid owned result. The
545        // guard assumes ownership of all initialized results after the call.
546        unsafe {
547            ArrayOps::<T>::read_abi(
548                self.as_raw(),
549                offset as u32,
550                values.values.as_mut_ptr().cast(),
551                length as u32,
552            )
553        }
554        values.initialized = length;
555        values
556    }
557}
558
559#[cfg(feature = "alloc")]
560impl<T: GcValue> Gc<Array<T>> {
561    /// Copies all elements into a new Rust vector.
562    ///
563    /// Reference elements receive independently owned roots to the stored objects.
564    /// This method requires the `alloc` feature.
565    ///
566    /// # Examples
567    ///
568    /// Compile for a Wasm target and postprocess before execution.
569    ///
570    /// ```no_run
571    /// use wasmgc::GcArray;
572    ///
573    /// let array = GcArray::from_slice([4u8, 5, 6]);
574    /// assert_eq!(array.to_vec(), [4, 5, 6]);
575    /// ```
576    pub fn to_vec(&self) -> Vec<T> {
577        if let Some(ops) = scalar_ops::<T>() {
578            let length = self.len();
579            let mut result = Vec::<T>::with_capacity(length);
580            // SAFETY: The vector has capacity for the array's entire u32 length.
581            // TypeId selection proves T's scalar layout, including normalization of
582            // bools to 0/1. The shim initializes all elements before set_len exposes
583            // them to safe Rust; built-in scalars need no previous values dropped.
584            unsafe {
585                (ops.read)(self.as_raw(), 0, result.as_mut_ptr().cast(), length as u32);
586                result.set_len(length);
587            }
588            return result;
589        }
590
591        let length = self.len();
592        let mut result = Vec::with_capacity(length);
593        let mut offset = 0;
594        while offset < length {
595            let count = COPY_BATCH_SIZE.min(length - offset);
596            let mut values = self.read_abi(offset, count);
597            for _ in 0..count {
598                result.push(values.take());
599            }
600            offset += count;
601        }
602        result
603    }
604}
605
606impl<T: GcValue> Default for GcArray<T> {
607    fn default() -> Self {
608        Self::empty()
609    }
610}
611
612impl<T: GcValue> From<&[T]> for GcArray<T> {
613    fn from(values: &[T]) -> Self {
614        Self::from_slice(values)
615    }
616}
617
618impl<T: GcValue, const N: usize> From<[T; N]> for GcArray<T> {
619    fn from(values: [T; N]) -> Self {
620        Self::from_slice(values)
621    }
622}
623
624impl<T: GcValue, const N: usize> From<&[T; N]> for GcArray<T> {
625    fn from(values: &[T; N]) -> Self {
626        Self::from_slice(values)
627    }
628}
629
630#[cfg(feature = "alloc")]
631impl<T: GcValue> From<Vec<T>> for GcArray<T> {
632    fn from(values: Vec<T>) -> Self {
633        Self::from_slice(values)
634    }
635}
636
637impl<T: GcValue + fmt::Debug> fmt::Debug for GcArray<T> {
638    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
639        f.debug_list().entries(self.iter()).finish()
640    }
641}
642
643impl<T: GcValue + PartialEq> PartialEq for GcArray<T> {
644    fn eq(&self, other: &Self) -> bool {
645        self.iter().eq(other.iter())
646    }
647}
648
649impl<T: GcValue + Eq> Eq for GcArray<T> {}
650
651impl<T: GcValue + PartialEq> PartialEq<[T]> for GcArray<T> {
652    fn eq(&self, other: &[T]) -> bool {
653        self.len() == other.len() && self.iter().zip(other).all(|(left, right)| left.eq(right))
654    }
655}
656
657impl<T: GcValue + PartialEq> PartialEq<&[T]> for GcArray<T> {
658    fn eq(&self, other: &&[T]) -> bool {
659        self == *other
660    }
661}
662
663impl<T: GcValue + PartialEq, const N: usize> PartialEq<[T; N]> for GcArray<T> {
664    fn eq(&self, other: &[T; N]) -> bool {
665        self == other.as_slice()
666    }
667}
668
669/// An iterator over values read from a borrowed [`GcArray`].
670///
671/// Created by [`GcArray::iter`] or iteration over `&GcArray<T>`. The iterator
672/// borrows the root but returns owned values; reference elements receive their
673/// own roots. It reads each element when visited, so it is not a snapshot of
674/// the array's contents.
675///
676/// # Examples
677///
678/// Compile for a Wasm target and postprocess before execution.
679///
680/// ```no_run
681/// use wasmgc::GcArray;
682///
683/// let array = GcArray::from_slice([1i32, 2, 3]);
684/// let mut values = array.iter();
685/// assert_eq!(values.next(), Some(1));
686/// array.set(1, 8);
687/// assert_eq!(values.next(), Some(8));
688/// assert_eq!(values.next_back(), Some(3));
689/// ```
690pub struct GcArrayIter<'a, T: GcValue> {
691    array: &'a GcArray<T>,
692    front: usize,
693    back: usize,
694}
695
696impl<'a, T: GcValue> GcArrayIter<'a, T> {
697    fn new(array: &'a GcArray<T>, length: usize) -> Self {
698        Self {
699            array,
700            front: 0,
701            back: length,
702        }
703    }
704
705    fn element(&self, index: usize) -> T {
706        // SAFETY: next/next_back check the front/back bounds derived from this
707        // fixed-length array before calling element. The borrowed root stays live,
708        // and the matching getter returns a valid scalar or a new owned T root.
709        unsafe { T::from_abi(ArrayOps::<T>::get(self.array.as_raw(), index as u32)) }
710    }
711}
712
713impl<T: GcValue> Iterator for GcArrayIter<'_, T> {
714    type Item = T;
715
716    fn next(&mut self) -> Option<T> {
717        if self.front == self.back {
718            return None;
719        }
720        let value = self.element(self.front);
721        self.front += 1;
722        Some(value)
723    }
724
725    fn size_hint(&self) -> (usize, Option<usize>) {
726        (self.len(), Some(self.len()))
727    }
728
729    fn nth(&mut self, n: usize) -> Option<T> {
730        self.front += n.min(self.len());
731        self.next()
732    }
733
734    fn count(self) -> usize {
735        self.len()
736    }
737}
738
739impl<T: GcValue> DoubleEndedIterator for GcArrayIter<'_, T> {
740    fn next_back(&mut self) -> Option<T> {
741        if self.front == self.back {
742            return None;
743        }
744        self.back -= 1;
745        Some(self.element(self.back))
746    }
747
748    fn nth_back(&mut self, n: usize) -> Option<T> {
749        self.back -= n.min(self.len());
750        self.next_back()
751    }
752}
753
754impl<T: GcValue> ExactSizeIterator for GcArrayIter<'_, T> {
755    fn len(&self) -> usize {
756        self.back - self.front
757    }
758}
759
760impl<T: GcValue> FusedIterator for GcArrayIter<'_, T> {}
761
762impl<'a, T: GcValue> IntoIterator for &'a GcArray<T> {
763    type Item = T;
764
765    type IntoIter = GcArrayIter<'a, T>;
766
767    fn into_iter(self) -> Self::IntoIter {
768        self.iter()
769    }
770}
771
772struct ArrayOps<T: GcValue>(PhantomData<fn() -> T>);
773
774impl<T: GcValue> ArrayOps<T> {
775    const SCHEMA: *const Schema = <Array<T> as GcHeapObj>::SCHEMA;
776}
777
778struct ScalarOps {
779    from_slice: unsafe extern "C" fn(*const (), u32) -> u32,
780    read: unsafe extern "C" fn(u32, u32, *mut (), u32),
781    write: unsafe extern "C" fn(u32, u32, *const (), u32),
782}
783
784fn scalar_ops<T: GcValue>() -> Option<ScalarOps> {
785    macro_rules! scalar {
786        ($($ty:ty),* $(,)?) => {
787            $(
788                if TypeId::of::<T>() == TypeId::of::<$ty>() {
789                    return Some(ScalarOps {
790                        from_slice: ArrayOps::<$ty>::from_slice,
791                        read: ArrayOps::<$ty>::read,
792                        write: ArrayOps::<$ty>::write,
793                    });
794                }
795            )*
796        };
797    }
798
799    // Compare actual Rust types rather than storage kinds: a custom GcValue
800    // can lower to a scalar while having a completely different memory layout.
801    // Returning concrete shims also keeps invalid raw-copy shims out of debug builds.
802    scalar!(i8, u8, i16, u16, i32, u32, i64, u64, f32, f64, bool);
803    None
804}
805
806macro_rules! operation {
807    ($name:ident, $descriptor:ident, $kind:ident, ($($param:ident: $ty:ty),*) -> $result:ty) => {
808        const $descriptor: Operation = Operation::new(
809            Self::SCHEMA,
810            OperationKind::$kind,
811            0,
812            Self::$name as *const (),
813        );
814
815        /// Executes the described operation after Wasm postprocessing.
816        ///
817        /// # Safety
818        ///
819        /// Root arguments must be live slots for the schema and root table
820        /// declared by the operation. Element arguments must satisfy T's ABI
821        /// contract, and any borrowed roots must remain live until the call
822        /// returns. Array ranges must be in bounds. Pointer arguments must
823        /// cover the operation's full input or output buffer with the declared
824        /// element representation; output buffers must be exclusively writable.
825        /// The module must be postprocessed before executing this shim.
826        #[inline(never)]
827        unsafe extern "C" fn $name($($param: $ty),*) -> $result {
828            let arguments = ($($param,)*);
829            // SAFETY: The descriptor points to this exact shim and records its
830            // operation. The local tuple retains every parameter in declaration
831            // order, and the selected marker matches the numeric result ABI.
832            unsafe {
833                <$result as GcAbi>::marker(
834                    (&Self::$descriptor as *const Operation).cast(),
835                    (&raw const arguments).cast(),
836                )
837            }
838        }
839    };
840    ($name:ident, $descriptor:ident, $kind:ident, ($($param:ident: $ty:ty),*)) => {
841        const $descriptor: Operation = Operation::new(
842            Self::SCHEMA,
843            OperationKind::$kind,
844            0,
845            Self::$name as *const (),
846        );
847
848        /// Executes the described write operation after Wasm postprocessing.
849        ///
850        /// # Safety
851        ///
852        /// Root arguments must be live slots for the declared schema and table.
853        /// Element arguments must satisfy T's ABI contract, and all borrowed
854        /// roots must outlive the call. Every array range must be in bounds.
855        /// Pointer arguments must cover the complete buffer with the operation's
856        /// ABI or scalar representation, remaining readable or exclusively
857        /// writable as appropriate. Postprocessing must replace this shim
858        /// before it executes.
859        #[inline(never)]
860        unsafe extern "C" fn $name($($param: $ty),*) {
861            let arguments = ($($param,)*);
862            // SAFETY: The descriptor identifies this exact void-returning shim,
863            // and the live tuple retains every declared parameter for discovery
864            // before the postprocessor replaces the function body.
865            unsafe {
866                abi::marker_void(
867                    (&Self::$descriptor as *const Operation).cast(),
868                    (&raw const arguments).cast(),
869                )
870            }
871        }
872    };
873}
874
875impl<T: GcValue> ArrayOps<T> {
876    operation!(array_new, ARRAY_NEW, ArrayNew, (length: u32, value: T::Abi) -> u32);
877    operation!(new_fixed, NEW_FIXED, ArrayNewFixed, () -> u32);
878    operation!(new_default, NEW_DEFAULT, ArrayNewDefault, (length: u32) -> u32);
879    operation!(len, LEN, ArrayLen, (handle: u32) -> u32);
880    operation!(get, GET, ArrayGet, (handle: u32, index: u32) -> T::Abi);
881    operation!(set, SET, ArraySet, (handle: u32, index: u32, value: T::Abi));
882    operation!(fill, FILL, ArrayFill, (handle: u32, value: T::Abi));
883    operation!(
884        copy, COPY, ArrayCopy,
885        (destination: u32, destination_offset: u32, source: u32, source_offset: u32, length: u32)
886    );
887}
888
889impl<T: GcValue> ArrayOps<T> {
890    operation!(
891        read_abi, READ_ABI, ArrayReadAbi,
892        (handle: u32, offset: u32, pointer: *mut (), length: u32)
893    );
894    operation!(
895        write_abi, WRITE_ABI, ArrayWriteAbi,
896        (handle: u32, offset: u32, pointer: *const (), length: u32)
897    );
898
899    // Only scalar_ops may select these raw-memory shims, after proving a
900    // concrete built-in Rust scalar type and its matching memory representation.
901    operation!(from_slice, FROM_SLICE, ArrayFromSlice, (pointer: *const (), length: u32) -> u32);
902    operation!(read, READ, ArrayRead, (handle: u32, offset: u32, pointer: *mut (), length: u32));
903    operation!(write, WRITE, ArrayWrite, (handle: u32, offset: u32, pointer: *const (), length: u32));
904}