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, ©));
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}