Skip to main content

shape_value/v2/
typed_array.rs

1//! Typed contiguous array for v2 runtime.
2//!
3//! `TypedArray<T>` is a 24-byte `#[repr(C)]` heap object with a `HeapHeader`,
4//! a pointer to a contiguous `T` buffer, length, and capacity. The compiler
5//! monomorphizes: `Array<number>` and `Array<i32>` are different `TypedArray`
6//! instantiations with no element-level type checking.
7//!
8//! ## Memory layout (24 bytes)
9//!
10//! ```text
11//! Offset  Size  Field
12//! ------  ----  -----
13//!   0       8   header (HeapHeader — refcount at offset 0)
14//!   8       8   data (*mut T — pointer to contiguous T buffer)
15//!  16       4   len (element count)
16//!  20       4   cap (allocated capacity)
17//! ```
18
19use super::heap_header::{HeapHeader, HEAP_KIND_V2_TYPED_ARRAY};
20use std::alloc::{Layout, alloc, dealloc, realloc};
21use std::ptr;
22
23/// Typed contiguous array with refcounted header.
24///
25/// Allocated on the heap via raw allocator. The `data` pointer points to a
26/// separate allocation holding `cap` elements of type `T`.
27#[repr(C)]
28pub struct TypedArray<T> {
29    /// 8-byte v2 heap header (refcount at offset 0).
30    pub header: HeapHeader,
31    /// Pointer to contiguous T buffer.
32    pub data: *mut T,
33    /// Number of elements currently stored.
34    pub len: u32,
35    /// Allocated capacity in number of elements.
36    pub cap: u32,
37}
38
39// Compile-time size assertion.
40const _: () = {
41    assert!(std::mem::size_of::<TypedArray<f64>>() == 24);
42    assert!(std::mem::size_of::<TypedArray<i32>>() == 24);
43    assert!(std::mem::size_of::<TypedArray<u8>>() == 24);
44    // Wave 2 Agent A1 (2026-05-14) — F32 + Char scalar monomorphizations.
45    assert!(std::mem::size_of::<TypedArray<f32>>() == 24);
46    assert!(std::mem::size_of::<TypedArray<char>>() == 24);
47};
48
49impl<T: Copy> TypedArray<T> {
50    /// Allocate a new empty TypedArray with capacity 0.
51    ///
52    /// Returns a raw pointer to the heap-allocated array. The caller is
53    /// responsible for eventually calling `drop_array` to free it.
54    pub fn new() -> *mut Self {
55        Self::with_capacity(0)
56    }
57
58    /// Allocate a new TypedArray with the given capacity.
59    ///
60    /// Returns a raw pointer to the heap-allocated array.
61    pub fn with_capacity(cap: u32) -> *mut Self {
62        let layout = Layout::new::<Self>();
63        let ptr = unsafe { alloc(layout) as *mut Self };
64        assert!(!ptr.is_null(), "allocation failed for TypedArray");
65
66        let data = if cap > 0 {
67            let data_layout = Layout::array::<T>(cap as usize).expect("invalid array layout");
68            let data_ptr = unsafe { alloc(data_layout) as *mut T };
69            assert!(!data_ptr.is_null(), "allocation failed for TypedArray data");
70            data_ptr
71        } else {
72            ptr::null_mut()
73        };
74
75        unsafe {
76            ptr::write(
77                ptr,
78                Self {
79                    header: HeapHeader::new(HEAP_KIND_V2_TYPED_ARRAY),
80                    data,
81                    len: 0,
82                    cap,
83                },
84            );
85        }
86
87        ptr
88    }
89
90    /// Create a TypedArray from a slice, copying all elements.
91    pub fn from_slice(slice: &[T]) -> *mut Self {
92        let len = slice.len() as u32;
93        let ptr = Self::with_capacity(len);
94        unsafe {
95            if len > 0 {
96                ptr::copy_nonoverlapping(slice.as_ptr(), (*ptr).data, slice.len());
97            }
98            (*ptr).len = len;
99        }
100        ptr
101    }
102
103    /// Get an element by index, returning `None` if out of bounds.
104    ///
105    /// # Safety
106    /// `this` must point to a valid, live `TypedArray<T>`.
107    #[inline]
108    pub unsafe fn get(this: *const Self, index: u32) -> Option<T> {
109        unsafe {
110            if index >= (*this).len {
111                None
112            } else {
113                Some(ptr::read((*this).data.add(index as usize)))
114            }
115        }
116    }
117
118    /// Get an element by index without bounds checking.
119    ///
120    /// # Safety
121    /// `this` must point to a valid, live `TypedArray<T>`, and `index` must
122    /// be less than the array's length.
123    #[inline]
124    pub unsafe fn get_unchecked(this: *const Self, index: u32) -> T {
125        unsafe { ptr::read((*this).data.add(index as usize)) }
126    }
127
128    /// Set an element by index. Panics if out of bounds.
129    ///
130    /// # Safety
131    /// `this` must point to a valid, live `TypedArray<T>`.
132    #[inline]
133    pub unsafe fn set(this: *mut Self, index: u32, val: T) {
134        unsafe {
135            assert!(
136                index < (*this).len,
137                "TypedArray::set index {} out of bounds (len {})",
138                index,
139                (*this).len
140            );
141            ptr::write((*this).data.add(index as usize), val);
142        }
143    }
144
145    /// Push an element, growing the buffer if necessary (doubling strategy).
146    ///
147    /// # Safety
148    /// `this` must point to a valid, live `TypedArray<T>`.
149    pub unsafe fn push(this: *mut Self, val: T) {
150        unsafe {
151            let arr = &mut *this;
152            if arr.len == arr.cap {
153                Self::grow(this);
154            }
155            let arr = &mut *this;
156            ptr::write(arr.data.add(arr.len as usize), val);
157            arr.len += 1;
158        }
159    }
160
161    /// Pop the last element, returning `None` if empty.
162    ///
163    /// # Safety
164    /// `this` must point to a valid, live `TypedArray<T>`.
165    pub unsafe fn pop(this: *mut Self) -> Option<T> {
166        unsafe {
167            let arr = &mut *this;
168            if arr.len == 0 {
169                None
170            } else {
171                arr.len -= 1;
172                Some(ptr::read(arr.data.add(arr.len as usize)))
173            }
174        }
175    }
176
177    /// Get the number of elements.
178    ///
179    /// # Safety
180    /// `this` must point to a valid, live `TypedArray<T>`.
181    #[inline]
182    pub unsafe fn len(this: *const Self) -> u32 {
183        unsafe { (*this).len }
184    }
185
186    /// Get the allocated capacity.
187    ///
188    /// # Safety
189    /// `this` must point to a valid, live `TypedArray<T>`.
190    #[inline]
191    pub unsafe fn capacity(this: *const Self) -> u32 {
192        unsafe { (*this).cap }
193    }
194
195    /// Check if the array is empty.
196    ///
197    /// # Safety
198    /// `this` must point to a valid, live `TypedArray<T>`.
199    #[inline]
200    pub unsafe fn is_empty(this: *const Self) -> bool {
201        unsafe { (*this).len == 0 }
202    }
203
204    /// Get the elements as a slice.
205    ///
206    /// # Safety
207    /// `this` must point to a valid, live `TypedArray<T>`.
208    #[inline]
209    pub unsafe fn as_slice<'a>(this: *const Self) -> &'a [T] {
210        unsafe {
211            if (*this).len == 0 {
212                &[]
213            } else {
214                std::slice::from_raw_parts((*this).data, (*this).len as usize)
215            }
216        }
217    }
218
219    /// Get the elements as a mutable slice.
220    ///
221    /// # Safety
222    /// `this` must point to a valid, live `TypedArray<T>`.
223    #[inline]
224    pub unsafe fn as_mut_slice<'a>(this: *mut Self) -> &'a mut [T] {
225        unsafe {
226            if (*this).len == 0 {
227                &mut []
228            } else {
229                std::slice::from_raw_parts_mut((*this).data, (*this).len as usize)
230            }
231        }
232    }
233
234    /// Deallocate the array and its data buffer.
235    ///
236    /// # Safety
237    /// `ptr` must point to a `TypedArray<T>` that was allocated by this module.
238    /// After calling this, `ptr` is invalid.
239    pub unsafe fn drop_array(ptr: *mut Self) {
240        unsafe {
241            let arr = &*ptr;
242            // Free the data buffer if it was allocated.
243            if arr.cap > 0 && !arr.data.is_null() {
244                let data_layout =
245                    Layout::array::<T>(arr.cap as usize).expect("invalid array layout");
246                dealloc(arr.data as *mut u8, data_layout);
247            }
248            // Free the TypedArray struct itself.
249            let layout = Layout::new::<Self>();
250            dealloc(ptr as *mut u8, layout);
251        }
252    }
253
254    /// Grow the data buffer (doubling strategy, minimum 4).
255    ///
256    /// # Safety
257    /// `this` must point to a valid, live `TypedArray<T>`.
258    unsafe fn grow(this: *mut Self) {
259        unsafe {
260            let arr = &mut *this;
261            let new_cap = if arr.cap == 0 {
262                4
263            } else {
264                arr.cap.checked_mul(2).expect("capacity overflow")
265            };
266            let new_layout = Layout::array::<T>(new_cap as usize).expect("invalid array layout");
267
268            let new_data = if arr.cap == 0 || arr.data.is_null() {
269                alloc(new_layout) as *mut T
270            } else {
271                let old_layout =
272                    Layout::array::<T>(arr.cap as usize).expect("invalid array layout");
273                realloc(arr.data as *mut u8, old_layout, new_layout.size()) as *mut T
274            };
275            assert!(!new_data.is_null(), "reallocation failed for TypedArray");
276
277            arr.data = new_data;
278            arr.cap = new_cap;
279        }
280    }
281}
282
283/// Heap-element-aware drop dispatch for `TypedArray<*const T>` where `T:
284/// HeapElement`.
285///
286/// Per ADR-006 §2.7.24 Q25.A SUPERSEDED + R20 S2-prime audit deliverable (b)
287/// §4.1.B decision: `drop_array_heap` walks the element buffer and calls
288/// `T::release_elem(elem_ptr)` for each stored pointer, then frees the data
289/// buffer + the TypedArray struct itself. Per-T dispatch is monomorphized at
290/// compile time via the `HeapElement` trait — no runtime `NativeKind` probe.
291///
292/// Pairs with the POD-element `drop_array` for `T: Copy` (above). Callers
293/// pick at compile time based on whether the element type is POD (plain
294/// scalar like f64/i64) or HeapHeader-equipped (`*const StringObj` /
295/// `*const DecimalObj` / ...).
296impl<T: super::heap_element::HeapElement> TypedArray<*const T> {
297    /// Deallocate the array, releasing per-element shares via
298    /// `T::release_elem`, then freeing the data buffer + the struct.
299    ///
300    /// # Safety
301    /// `ptr` must point to a `TypedArray<*const T>` that was allocated by
302    /// this module. Each stored `*const T` must be a valid pointer to a
303    /// live `T` allocation with at least one refcount share owned by this
304    /// array. After this call, `ptr` is invalid.
305    pub unsafe fn drop_array_heap(ptr: *mut Self) {
306        unsafe {
307            let arr = &*ptr;
308            if arr.cap > 0 && !arr.data.is_null() {
309                // Walk element buffer; release per-element shares.
310                for i in 0..arr.len {
311                    let elem_ptr = ptr::read(arr.data.add(i as usize));
312                    T::release_elem(elem_ptr);
313                }
314                // Free the data buffer.
315                let data_layout = Layout::array::<*const T>(arr.cap as usize)
316                    .expect("invalid array layout");
317                dealloc(arr.data as *mut u8, data_layout);
318            }
319            // Free the TypedArray struct itself.
320            let layout = Layout::new::<Self>();
321            dealloc(ptr as *mut u8, layout);
322        }
323    }
324}
325
326// ── Element-type discriminants — canonical home ──────────────────────────────
327//
328// The compile-time element type `T` of a `TypedArray<T>` is preserved at
329// runtime in the `_pad` byte (offset 7) of the `HeapHeader`. The bytecode
330// compiler / VM allocation handlers stamp this byte immediately after
331// `with_capacity`; `retain_v2_typed_array` / `release_v2_typed_array` below
332// (and the `shape-vm` consumer paths in `v2_handlers/v2_array_detect.rs`,
333// re-exporting these constants) read it to pick the monomorphized
334// `drop_array` / `drop_array_heap`.
335//
336// This is the *canonical* definition; `shape-vm::executor::v2_handlers::
337// v2_array_detect` re-exports it via `pub use`. r5c-2-β-δ-(α): moved here
338// from `v2_array_detect` so the kind-blind release function below — needed
339// by the 4 `Ptr(HeapKind::TypedArray)` lockstep dispatch tables, two of
340// which live in this `shape-value` crate (`kinded_slot.rs`, `closure_layout.
341// rs`, `heap_value.rs`) — can dispatch without a constant duplicated across
342// the `shape-vm` crate boundary.
343
344/// `_pad`-byte discriminant for an unstamped / unknown element type.
345pub const ELEM_TYPE_UNKNOWN: u8 = 0;
346/// `_pad`-byte discriminant for `TypedArray<f64>`.
347pub const ELEM_TYPE_F64: u8 = 1;
348/// `_pad`-byte discriminant for `TypedArray<i64>`.
349pub const ELEM_TYPE_I64: u8 = 2;
350/// `_pad`-byte discriminant for `TypedArray<i32>`.
351pub const ELEM_TYPE_I32: u8 = 3;
352/// `_pad`-byte discriminant for `TypedArray<u8>` carrying `bool` elements.
353pub const ELEM_TYPE_BOOL: u8 = 4;
354/// `_pad`-byte discriminant for `TypedArray<i8>`.
355pub const ELEM_TYPE_I8: u8 = 5;
356/// `_pad`-byte discriminant for `TypedArray<u8>` carrying `u8` elements.
357pub const ELEM_TYPE_U8: u8 = 6;
358/// `_pad`-byte discriminant for `TypedArray<i16>`.
359pub const ELEM_TYPE_I16: u8 = 7;
360/// `_pad`-byte discriminant for `TypedArray<u16>`.
361pub const ELEM_TYPE_U16: u8 = 8;
362/// `_pad`-byte discriminant for `TypedArray<u32>`.
363pub const ELEM_TYPE_U32: u8 = 9;
364// Discriminant 10 reserved for `Array<u64>` (deferred — see v2_array_detect).
365/// `_pad`-byte discriminant for `TypedArray<f32>`.
366pub const ELEM_TYPE_F32: u8 = 11;
367/// `_pad`-byte discriminant for `TypedArray<char>`.
368pub const ELEM_TYPE_CHAR: u8 = 12;
369/// `_pad`-byte discriminant for `TypedArray<*const StringObj>`.
370pub const ELEM_TYPE_STRING: u8 = 13;
371/// `_pad`-byte discriminant for `TypedArray<*const DecimalObj>`.
372pub const ELEM_TYPE_DECIMAL: u8 = 14;
373/// `_pad`-byte discriminant for `TypedArray<*const TypedObjectStorage>`.
374pub const ELEM_TYPE_TYPED_OBJECT: u8 = 15;
375
376/// Read the element-type discriminant stamped in the `_pad` byte (offset 7).
377///
378/// # Safety
379/// `ptr` must point to a live `TypedArray<T>` (HeapHeader at offset 0).
380#[inline]
381pub unsafe fn read_elem_type(ptr: *const u8) -> u8 {
382    unsafe { *ptr.add(7) }
383}
384
385/// Retain (bump the refcount of) a v2-raw `*mut TypedArray<T>` carrier.
386///
387/// This is the retain half of the `NativeKind::Ptr(HeapKind::TypedArray)`
388/// dispatch arm shared by the four lockstep clone/drop tables (VM stack
389/// `clone_with_kind`, `KindedSlot::clone`, `SharedCell::clone`,
390/// `TypedObjectStorage` field clone). The element type is irrelevant for a
391/// retain — only the `HeapHeader` refcount at offset 0 is touched.
392///
393/// # Safety
394/// `ptr` must be a non-null `*mut TypedArray<T>` produced by this module's
395/// allocator (`with_capacity` / `with_capacity_generic` / `from_slice`).
396#[inline]
397pub unsafe fn retain_v2_typed_array(ptr: *mut u8) {
398    unsafe { super::refcount::v2_retain(ptr as *const HeapHeader) };
399}
400
401/// Release one refcount share of a v2-raw `*mut TypedArray<T>` carrier; on
402/// the last share, read the stamped element type and free the array via the
403/// matching monomorphized `drop_array` / `drop_array_heap`.
404///
405/// This is the release half of the `NativeKind::Ptr(HeapKind::TypedArray)`
406/// dispatch arm. POD element kinds (`f64` / `i64` / `i32` / `i8` / `u8` /
407/// `i16` / `u16` / `u32` / `f32` / `char` / `bool`) route to `drop_array`;
408/// the heap-element kinds (`String` / `Decimal` / `TypedObject`) route to
409/// `drop_array_heap`, which walks the buffer releasing per-element shares.
410///
411/// # Safety
412/// `ptr` must be a non-null `*mut TypedArray<T>` produced by this module's
413/// allocator, with `T` matching the stamped `_pad` discriminant, and the
414/// caller must own exactly one refcount share being retired here. After the
415/// last share is retired the pointer is invalid.
416pub unsafe fn release_v2_typed_array(ptr: *mut u8) {
417    unsafe {
418        if !super::refcount::v2_release(ptr as *const HeapHeader) {
419            return;
420        }
421        // Refcount reached zero — this thread owns the deallocation.
422        match read_elem_type(ptr) {
423            ELEM_TYPE_F64 => TypedArray::<f64>::drop_array(ptr as *mut TypedArray<f64>),
424            ELEM_TYPE_I64 => TypedArray::<i64>::drop_array(ptr as *mut TypedArray<i64>),
425            ELEM_TYPE_I32 => TypedArray::<i32>::drop_array(ptr as *mut TypedArray<i32>),
426            ELEM_TYPE_BOOL | ELEM_TYPE_U8 => {
427                TypedArray::<u8>::drop_array(ptr as *mut TypedArray<u8>)
428            }
429            ELEM_TYPE_I8 => TypedArray::<i8>::drop_array(ptr as *mut TypedArray<i8>),
430            ELEM_TYPE_I16 => TypedArray::<i16>::drop_array(ptr as *mut TypedArray<i16>),
431            ELEM_TYPE_U16 => TypedArray::<u16>::drop_array(ptr as *mut TypedArray<u16>),
432            ELEM_TYPE_U32 => TypedArray::<u32>::drop_array(ptr as *mut TypedArray<u32>),
433            ELEM_TYPE_F32 => TypedArray::<f32>::drop_array(ptr as *mut TypedArray<f32>),
434            ELEM_TYPE_CHAR => TypedArray::<char>::drop_array(ptr as *mut TypedArray<char>),
435            ELEM_TYPE_STRING => {
436                TypedArray::<*const super::string_obj::StringObj>::drop_array_heap(
437                    ptr as *mut TypedArray<*const super::string_obj::StringObj>,
438                )
439            }
440            ELEM_TYPE_DECIMAL => {
441                TypedArray::<*const super::decimal_obj::DecimalObj>::drop_array_heap(
442                    ptr as *mut TypedArray<*const super::decimal_obj::DecimalObj>,
443                )
444            }
445            ELEM_TYPE_TYPED_OBJECT => {
446                TypedArray::<*const crate::heap_value::TypedObjectStorage>::drop_array_heap(
447                    ptr as *mut TypedArray<*const crate::heap_value::TypedObjectStorage>,
448                )
449            }
450            // An unstamped (`ELEM_TYPE_UNKNOWN`) or unrecognised discriminant
451            // at refcount-0 means the producer-side stamp contract was
452            // violated. The element-buffer monomorphization is unknown so a
453            // typed `drop_array` cannot run; free only the 24-byte struct
454            // header (leaking the element buffer is strictly preferable to
455            // a misaligned `dealloc` / use-after-free). This is a hard bug
456            // upstream — surface it loudly in debug builds.
457            other => {
458                debug_assert!(
459                    false,
460                    "release_v2_typed_array: TypedArray at {:p} has unstamped \
461                     element-type discriminant {} — producer-side stamp_elem_type \
462                     contract violated (ADR-006 §2.7.7)",
463                    ptr, other
464                );
465                let layout = Layout::new::<TypedArray<u8>>();
466                dealloc(ptr, layout);
467            }
468        }
469    }
470}
471
472// Allocation + size-only operations available for non-Copy element types
473// (e.g. `TypedObjectPtr` with manual `Drop`). Per ADR-006 §2.7.24 Q25.B
474// SUPERSEDED + Wave 2 Round 3b C2-joint ckpt-1 — `HashMapData<V>` (in
475// `crates/shape-value/src/heap_value.rs`) instantiates `TypedArray<V>` for
476// `V = TypedObjectPtr` / `TraitObjectPtr` (transparent newtypes with manual
477// Drop), which are not `Copy`. The methods here are size/allocation only —
478// no `ptr::read` / `ptr::write` that would require `T: Copy` for soundness.
479//
480// Methods needing element copy semantics (`get_unchecked`, `set`, `push`,
481// `pop`, `from_slice`) remain bounded by `T: Copy` in the impl block above;
482// non-Copy element types use the per-element-Drop-aware paths in
483// `HashMapValueElem::release_typed_array`.
484impl<T> TypedArray<T> {
485    /// Allocate a new empty TypedArray with capacity 0 — non-Copy variant.
486    ///
487    /// Returns a raw pointer to the heap-allocated array. The caller is
488    /// responsible for eventually freeing it via `HashMapValueElem::
489    /// release_typed_array` (for `HashMapData<V>` value buffers) or the
490    /// equivalent per-T release path.
491    #[doc(alias = "new")]
492    pub fn new_generic() -> *mut Self {
493        Self::with_capacity_generic(0)
494    }
495
496    /// Allocate a new TypedArray with the given capacity — non-Copy variant.
497    ///
498    /// Returns a raw pointer to the heap-allocated array. No elements are
499    /// written; the data buffer is uninitialized memory of length `cap *
500    /// size_of::<T>()`.
501    #[doc(alias = "with_capacity")]
502    pub fn with_capacity_generic(cap: u32) -> *mut Self {
503        let layout = Layout::new::<Self>();
504        let ptr = unsafe { alloc(layout) as *mut Self };
505        assert!(!ptr.is_null(), "allocation failed for TypedArray");
506
507        let data = if cap > 0 {
508            let data_layout = Layout::array::<T>(cap as usize).expect("invalid array layout");
509            let data_ptr = unsafe { alloc(data_layout) as *mut T };
510            assert!(!data_ptr.is_null(), "allocation failed for TypedArray data");
511            data_ptr
512        } else {
513            ptr::null_mut()
514        };
515
516        unsafe {
517            ptr::write(
518                ptr,
519                Self {
520                    header: HeapHeader::new(HEAP_KIND_V2_TYPED_ARRAY),
521                    data,
522                    len: 0,
523                    cap,
524                },
525            );
526        }
527
528        ptr
529    }
530
531    /// Get the number of elements — non-Copy variant.
532    ///
533    /// # Safety
534    /// `this` must point to a valid, live `TypedArray<T>`.
535    #[inline]
536    pub unsafe fn len_generic(this: *const Self) -> u32 {
537        unsafe { (*this).len }
538    }
539
540    /// Get the allocated capacity — non-Copy variant.
541    ///
542    /// # Safety
543    /// `this` must point to a valid, live `TypedArray<T>`.
544    #[inline]
545    pub unsafe fn capacity_generic(this: *const Self) -> u32 {
546        unsafe { (*this).cap }
547    }
548
549    /// Check if the array is empty — non-Copy variant.
550    ///
551    /// # Safety
552    /// `this` must point to a valid, live `TypedArray<T>`.
553    #[inline]
554    pub unsafe fn is_empty_generic(this: *const Self) -> bool {
555        unsafe { (*this).len == 0 }
556    }
557
558    /// Get the elements as a slice — non-Copy variant.
559    ///
560    /// # Safety
561    /// `this` must point to a valid, live `TypedArray<T>`.
562    #[inline]
563    pub unsafe fn as_slice_generic<'a>(this: *const Self) -> &'a [T] {
564        unsafe {
565            if (*this).len == 0 {
566                &[]
567            } else {
568                std::slice::from_raw_parts((*this).data, (*this).len as usize)
569            }
570        }
571    }
572}
573
574#[cfg(test)]
575mod tests {
576    use super::*;
577
578    #[test]
579    fn test_size_of_typed_array() {
580        assert_eq!(std::mem::size_of::<TypedArray<f64>>(), 24);
581        assert_eq!(std::mem::size_of::<TypedArray<i32>>(), 24);
582        assert_eq!(std::mem::size_of::<TypedArray<i64>>(), 24);
583        assert_eq!(std::mem::size_of::<TypedArray<u8>>(), 24);
584    }
585
586    #[test]
587    fn test_field_offsets() {
588        let arr = TypedArray::<f64>::with_capacity(0);
589        unsafe {
590            let base = arr as *const u8 as usize;
591            let header_offset = &(*arr).header as *const _ as usize - base;
592            let data_offset = &(*arr).data as *const _ as usize - base;
593            let len_offset = &(*arr).len as *const _ as usize - base;
594            let cap_offset = &(*arr).cap as *const _ as usize - base;
595
596            assert_eq!(header_offset, 0);
597            assert_eq!(data_offset, 8);
598            assert_eq!(len_offset, 16);
599            assert_eq!(cap_offset, 20);
600
601            TypedArray::drop_array(arr);
602        }
603    }
604
605    #[test]
606    fn test_new_empty() {
607        let arr = TypedArray::<f64>::new();
608        unsafe {
609            assert_eq!(TypedArray::len(arr), 0);
610            assert_eq!(TypedArray::capacity(arr), 0);
611            assert!(TypedArray::is_empty(arr));
612            assert_eq!((*arr).header.kind(), HEAP_KIND_V2_TYPED_ARRAY);
613            assert_eq!((*arr).header.get_refcount(), 1);
614            TypedArray::drop_array(arr);
615        }
616    }
617
618    #[test]
619    fn test_with_capacity() {
620        let arr = TypedArray::<f64>::with_capacity(16);
621        unsafe {
622            assert_eq!(TypedArray::len(arr), 0);
623            assert_eq!(TypedArray::capacity(arr), 16);
624            assert!(TypedArray::is_empty(arr));
625            TypedArray::drop_array(arr);
626        }
627    }
628
629    #[test]
630    fn test_push_and_get_f64() {
631        let arr = TypedArray::<f64>::new();
632        unsafe {
633            TypedArray::push(arr, 1.0);
634            TypedArray::push(arr, 2.5);
635            TypedArray::push(arr, 3.14);
636
637            assert_eq!(TypedArray::len(arr), 3);
638            assert!(!TypedArray::is_empty(arr));
639
640            assert_eq!(TypedArray::get(arr, 0), Some(1.0));
641            assert_eq!(TypedArray::get(arr, 1), Some(2.5));
642            assert_eq!(TypedArray::get(arr, 2), Some(3.14));
643            assert_eq!(TypedArray::get(arr, 3), None); // out of bounds
644
645            TypedArray::drop_array(arr);
646        }
647    }
648
649    #[test]
650    fn test_push_and_get_i32() {
651        let arr = TypedArray::<i32>::new();
652        unsafe {
653            TypedArray::push(arr, 42);
654            TypedArray::push(arr, -7);
655            TypedArray::push(arr, 0);
656
657            assert_eq!(TypedArray::len(arr), 3);
658            assert_eq!(TypedArray::get(arr, 0), Some(42));
659            assert_eq!(TypedArray::get(arr, 1), Some(-7));
660            assert_eq!(TypedArray::get(arr, 2), Some(0));
661            assert_eq!(TypedArray::get(arr, 3), None);
662
663            TypedArray::drop_array(arr);
664        }
665    }
666
667    #[test]
668    fn test_push_and_get_i64() {
669        let arr = TypedArray::<i64>::new();
670        unsafe {
671            TypedArray::push(arr, i64::MAX);
672            TypedArray::push(arr, i64::MIN);
673
674            assert_eq!(TypedArray::get(arr, 0), Some(i64::MAX));
675            assert_eq!(TypedArray::get(arr, 1), Some(i64::MIN));
676
677            TypedArray::drop_array(arr);
678        }
679    }
680
681    #[test]
682    fn test_push_and_get_u8_bool() {
683        let arr = TypedArray::<u8>::new();
684        unsafe {
685            TypedArray::push(arr, 1u8); // true
686            TypedArray::push(arr, 0u8); // false
687            TypedArray::push(arr, 1u8); // true
688
689            assert_eq!(TypedArray::len(arr), 3);
690            assert_eq!(TypedArray::get(arr, 0), Some(1));
691            assert_eq!(TypedArray::get(arr, 1), Some(0));
692            assert_eq!(TypedArray::get(arr, 2), Some(1));
693
694            TypedArray::drop_array(arr);
695        }
696    }
697
698    #[test]
699    fn test_get_unchecked() {
700        let arr = TypedArray::<f64>::from_slice(&[10.0, 20.0, 30.0]);
701        unsafe {
702            assert_eq!(TypedArray::get_unchecked(arr, 0), 10.0);
703            assert_eq!(TypedArray::get_unchecked(arr, 1), 20.0);
704            assert_eq!(TypedArray::get_unchecked(arr, 2), 30.0);
705            TypedArray::drop_array(arr);
706        }
707    }
708
709    #[test]
710    fn test_set() {
711        let arr = TypedArray::<f64>::from_slice(&[1.0, 2.0, 3.0]);
712        unsafe {
713            TypedArray::set(arr, 1, 99.0);
714            assert_eq!(TypedArray::get(arr, 1), Some(99.0));
715
716            // Other elements unchanged
717            assert_eq!(TypedArray::get(arr, 0), Some(1.0));
718            assert_eq!(TypedArray::get(arr, 2), Some(3.0));
719
720            TypedArray::drop_array(arr);
721        }
722    }
723
724    #[test]
725    #[should_panic(expected = "out of bounds")]
726    fn test_set_out_of_bounds() {
727        let arr = TypedArray::<f64>::from_slice(&[1.0, 2.0]);
728        unsafe {
729            TypedArray::set(arr, 5, 99.0);
730            // Leak is fine in a panic test
731        }
732    }
733
734    #[test]
735    fn test_pop() {
736        let arr = TypedArray::<i32>::from_slice(&[10, 20, 30]);
737        unsafe {
738            assert_eq!(TypedArray::pop(arr), Some(30));
739            assert_eq!(TypedArray::len(arr), 2);
740
741            assert_eq!(TypedArray::pop(arr), Some(20));
742            assert_eq!(TypedArray::len(arr), 1);
743
744            assert_eq!(TypedArray::pop(arr), Some(10));
745            assert_eq!(TypedArray::len(arr), 0);
746
747            assert_eq!(TypedArray::pop(arr), None);
748            assert!(TypedArray::is_empty(arr));
749
750            TypedArray::drop_array(arr);
751        }
752    }
753
754    #[test]
755    fn test_from_slice() {
756        let data = [1.0f64, 2.0, 3.0, 4.0, 5.0];
757        let arr = TypedArray::from_slice(&data);
758        unsafe {
759            assert_eq!(TypedArray::len(arr), 5);
760            assert_eq!(TypedArray::capacity(arr), 5);
761
762            for (i, &expected) in data.iter().enumerate() {
763                assert_eq!(TypedArray::get(arr, i as u32), Some(expected));
764            }
765
766            TypedArray::drop_array(arr);
767        }
768    }
769
770    #[test]
771    fn test_from_empty_slice() {
772        let arr = TypedArray::<f64>::from_slice(&[]);
773        unsafe {
774            assert_eq!(TypedArray::len(arr), 0);
775            assert_eq!(TypedArray::capacity(arr), 0);
776            assert!(TypedArray::is_empty(arr));
777            TypedArray::drop_array(arr);
778        }
779    }
780
781    #[test]
782    fn test_as_slice() {
783        let arr = TypedArray::from_slice(&[10i32, 20, 30]);
784        unsafe {
785            let s = TypedArray::as_slice(arr);
786            assert_eq!(s, &[10, 20, 30]);
787            TypedArray::drop_array(arr);
788        }
789    }
790
791    #[test]
792    fn test_as_mut_slice() {
793        let arr = TypedArray::from_slice(&[1.0f64, 2.0, 3.0]);
794        unsafe {
795            let s = TypedArray::as_mut_slice(arr);
796            s[1] = 99.0;
797            assert_eq!(TypedArray::get(arr, 1), Some(99.0));
798            TypedArray::drop_array(arr);
799        }
800    }
801
802    #[test]
803    fn test_as_slice_empty() {
804        let arr = TypedArray::<f64>::new();
805        unsafe {
806            let s = TypedArray::as_slice(arr);
807            assert!(s.is_empty());
808            TypedArray::drop_array(arr);
809        }
810    }
811
812    #[test]
813    fn test_capacity_growth() {
814        let arr = TypedArray::<f64>::new();
815        unsafe {
816            // Start with cap 0, first push should grow to 4
817            TypedArray::push(arr, 1.0);
818            assert!(TypedArray::capacity(arr) >= 1);
819
820            // Push enough to trigger several doublings
821            for i in 2..=20 {
822                TypedArray::push(arr, i as f64);
823            }
824            assert_eq!(TypedArray::len(arr), 20);
825
826            // Verify all values
827            for i in 0..20 {
828                assert_eq!(TypedArray::get(arr, i), Some((i + 1) as f64));
829            }
830
831            TypedArray::drop_array(arr);
832        }
833    }
834
835    #[test]
836    fn test_header_kind() {
837        let arr = TypedArray::<f64>::new();
838        unsafe {
839            assert_eq!((*arr).header.kind(), HEAP_KIND_V2_TYPED_ARRAY);
840            assert_eq!((*arr).header.get_refcount(), 1);
841            TypedArray::drop_array(arr);
842        }
843    }
844
845    #[test]
846    fn test_drop_safety() {
847        // Create and drop many arrays to verify no leaks (under Miri/valgrind).
848        unsafe {
849            for _ in 0..100 {
850                let arr = TypedArray::<f64>::new();
851                for i in 0..50 {
852                    TypedArray::push(arr, i as f64);
853                }
854                TypedArray::drop_array(arr);
855            }
856            // Empty arrays
857            for _ in 0..100 {
858                let arr = TypedArray::<i32>::new();
859                TypedArray::drop_array(arr);
860            }
861        }
862    }
863
864    #[test]
865    fn test_get_out_of_bounds_returns_none() {
866        let arr = TypedArray::<f64>::new();
867        unsafe {
868            // Empty array: any index is out of bounds
869            assert_eq!(TypedArray::get(arr, 0), None);
870            assert_eq!(TypedArray::get(arr, 100), None);
871            assert_eq!(TypedArray::get(arr, u32::MAX), None);
872
873            TypedArray::push(arr, 1.0);
874            assert_eq!(TypedArray::get(arr, 0), Some(1.0));
875            assert_eq!(TypedArray::get(arr, 1), None);
876
877            TypedArray::drop_array(arr);
878        }
879    }
880
881    #[test]
882    fn test_refcount_with_typed_array() {
883        use crate::v2::refcount::{v2_get_refcount, v2_retain, v2_release};
884
885        let arr = TypedArray::<f64>::from_slice(&[1.0, 2.0]);
886        unsafe {
887            let header_ptr = arr as *const HeapHeader;
888
889            assert_eq!(v2_get_refcount(header_ptr), 1);
890
891            v2_retain(header_ptr);
892            assert_eq!(v2_get_refcount(header_ptr), 2);
893
894            assert!(!v2_release(header_ptr)); // 2 -> 1
895            assert_eq!(v2_get_refcount(header_ptr), 1);
896
897            // Don't call v2_release to 0 here since we use drop_array for cleanup
898            TypedArray::drop_array(arr);
899        }
900    }
901
902    // ──────────────────────────────────────────────────────────────────────
903    // drop_array_heap tests per ADR-006 §2.7.24 Q25.A SUPERSEDED + R20
904    // S2-prime audit deliverable (b) §4.1.B.
905    // ──────────────────────────────────────────────────────────────────────
906
907    #[test]
908    fn test_drop_array_heap_string_obj() {
909        use crate::v2::string_obj::StringObj;
910        unsafe {
911            // Allocate a TypedArray<*const StringObj> with capacity 4.
912            let arr: *mut TypedArray<*const StringObj> = TypedArray::with_capacity(4);
913            // Push 3 StringObj pointers.
914            let s1 = StringObj::new("hello");
915            let s2 = StringObj::new("world");
916            let s3 = StringObj::new("!");
917            TypedArray::push(arr, s1 as *const StringObj);
918            TypedArray::push(arr, s2 as *const StringObj);
919            TypedArray::push(arr, s3 as *const StringObj);
920            assert_eq!(TypedArray::len(arr), 3);
921            // drop_array_heap releases per-element shares then dealloc the
922            // buffer + struct.
923            TypedArray::<*const StringObj>::drop_array_heap(arr);
924        }
925    }
926
927    #[test]
928    fn test_drop_array_heap_decimal_obj() {
929        use crate::v2::decimal_obj::DecimalObj;
930        use rust_decimal::Decimal;
931        use rust_decimal::prelude::FromPrimitive;
932        unsafe {
933            let arr: *mut TypedArray<*const DecimalObj> = TypedArray::with_capacity(4);
934            let d1 = DecimalObj::new(Decimal::from_f64(1.5).unwrap());
935            let d2 = DecimalObj::new(Decimal::from_f64(2.5).unwrap());
936            let d3 = DecimalObj::new(Decimal::ZERO);
937            TypedArray::push(arr, d1 as *const DecimalObj);
938            TypedArray::push(arr, d2 as *const DecimalObj);
939            TypedArray::push(arr, d3 as *const DecimalObj);
940            assert_eq!(TypedArray::len(arr), 3);
941            TypedArray::<*const DecimalObj>::drop_array_heap(arr);
942        }
943    }
944
945    #[test]
946    fn test_drop_array_heap_empty() {
947        use crate::v2::string_obj::StringObj;
948        unsafe {
949            // Empty TypedArray (no allocated buffer).
950            let arr: *mut TypedArray<*const StringObj> = TypedArray::new();
951            TypedArray::<*const StringObj>::drop_array_heap(arr);
952        }
953    }
954
955    // ──────────────────────────────────────────────────────────────────────
956    // Wave 2 Agent A1 (2026-05-14) — F32 + Char monomorphization smokes.
957    // ──────────────────────────────────────────────────────────────────────
958
959    #[test]
960    fn test_size_of_typed_array_f32_char() {
961        assert_eq!(std::mem::size_of::<TypedArray<f32>>(), 24);
962        assert_eq!(std::mem::size_of::<TypedArray<char>>(), 24);
963    }
964
965    #[test]
966    fn test_push_and_get_f32() {
967        let arr = TypedArray::<f32>::new();
968        unsafe {
969            TypedArray::push(arr, 1.5_f32);
970            TypedArray::push(arr, 2.25_f32);
971            TypedArray::push(arr, std::f32::consts::PI);
972            assert_eq!(TypedArray::len(arr), 3);
973            assert_eq!(TypedArray::get(arr, 0), Some(1.5_f32));
974            assert_eq!(TypedArray::get(arr, 1), Some(2.25_f32));
975            assert_eq!(TypedArray::get(arr, 2), Some(std::f32::consts::PI));
976            assert_eq!(TypedArray::get(arr, 3), None);
977            TypedArray::drop_array(arr);
978        }
979    }
980
981    #[test]
982    fn test_push_and_get_char() {
983        let arr = TypedArray::<char>::new();
984        unsafe {
985            TypedArray::push(arr, 'a');
986            TypedArray::push(arr, '☃');
987            TypedArray::push(arr, '👋');
988            assert_eq!(TypedArray::len(arr), 3);
989            assert_eq!(TypedArray::get(arr, 0), Some('a'));
990            assert_eq!(TypedArray::get(arr, 1), Some('☃'));
991            assert_eq!(TypedArray::get(arr, 2), Some('👋'));
992            assert_eq!(TypedArray::get(arr, 3), None);
993            TypedArray::drop_array(arr);
994        }
995    }
996
997    #[test]
998    fn test_from_slice_f32() {
999        let data: [f32; 5] = [1.0, 2.0, 3.0, 4.0, 5.0];
1000        let arr = TypedArray::from_slice(&data);
1001        unsafe {
1002            assert_eq!(TypedArray::len(arr), 5);
1003            for (i, &expected) in data.iter().enumerate() {
1004                assert_eq!(TypedArray::get(arr, i as u32), Some(expected));
1005            }
1006            TypedArray::drop_array(arr);
1007        }
1008    }
1009
1010    #[test]
1011    fn test_from_slice_char() {
1012        let data = ['h', 'i', '!'];
1013        let arr = TypedArray::from_slice(&data);
1014        unsafe {
1015            assert_eq!(TypedArray::len(arr), 3);
1016            for (i, &expected) in data.iter().enumerate() {
1017                assert_eq!(TypedArray::get(arr, i as u32), Some(expected));
1018            }
1019            TypedArray::drop_array(arr);
1020        }
1021    }
1022
1023    #[test]
1024    fn test_drop_array_heap_with_held_share() {
1025        use crate::v2::refcount::{v2_get_refcount, v2_retain};
1026        use crate::v2::string_obj::StringObj;
1027        unsafe {
1028            // Allocate one StringObj with refcount 2 (one for the array, one held
1029            // externally). drop_array_heap should decrement to 1, not deallocate.
1030            let arr: *mut TypedArray<*const StringObj> = TypedArray::with_capacity(2);
1031            let s = StringObj::new("shared");
1032            v2_retain(&(*s).header); // refcount = 2
1033            TypedArray::push(arr, s as *const StringObj);
1034
1035            TypedArray::<*const StringObj>::drop_array_heap(arr);
1036
1037            // External share still valid; refcount should be 1.
1038            assert_eq!(v2_get_refcount(&(*s).header), 1);
1039            assert_eq!(StringObj::as_str(s), "shared");
1040            // Clean up.
1041            StringObj::drop(s);
1042        }
1043    }
1044
1045    // ── r5c-2-β-δ-(α): kind-blind retain / release regression tests ─────────
1046
1047    /// `retain_v2_typed_array` bumps the on-header refcount; a paired
1048    /// `release_v2_typed_array` retires it. A POD-element array at refcount
1049    /// 1 is freed by the single release that drives the count to zero.
1050    #[test]
1051    fn release_v2_typed_array_pod_drop_balance() {
1052        use crate::v2::refcount::v2_get_refcount;
1053        unsafe {
1054            let arr = TypedArray::<i64>::with_capacity(4);
1055            TypedArray::push(arr, 10);
1056            TypedArray::push(arr, 20);
1057            TypedArray::push(arr, 30);
1058            super::stamp_elem_type_for_test(arr as *mut u8, ELEM_TYPE_I64);
1059            let hdr = arr as *const HeapHeader;
1060            assert_eq!(v2_get_refcount(hdr), 1);
1061
1062            // Retain (mirror of the `Ptr(HeapKind::TypedArray)` clone arm).
1063            retain_v2_typed_array(arr as *mut u8);
1064            assert_eq!(v2_get_refcount(hdr), 2);
1065
1066            // Release once — back to 1, NOT freed.
1067            release_v2_typed_array(arr as *mut u8);
1068            assert_eq!(v2_get_refcount(hdr), 1);
1069            // The array is still live and readable.
1070            assert_eq!(TypedArray::get(arr, 1), Some(20));
1071
1072            // Final release drives the count to 0 → free (no leak, no
1073            // double-free; ASAN/miri would flag a misaligned dealloc).
1074            release_v2_typed_array(arr as *mut u8);
1075        }
1076    }
1077
1078    /// A heap-element array (`TypedArray<*const StringObj>`) released at
1079    /// refcount 0 must route through `drop_array_heap`, retiring each
1080    /// element's per-pointer share. An externally-held element share
1081    /// survives the array's free.
1082    #[test]
1083    fn release_v2_typed_array_heap_elem_drop_balance() {
1084        use crate::v2::refcount::{v2_get_refcount, v2_retain};
1085        use crate::v2::string_obj::StringObj;
1086        unsafe {
1087            let arr = TypedArray::<*const StringObj>::with_capacity(2);
1088            super::stamp_elem_type_for_test(arr as *mut u8, ELEM_TYPE_STRING);
1089            let s = StringObj::new("kept");
1090            v2_retain(&(*s).header); // refcount 2: one for the array, one external.
1091            TypedArray::push(arr, s as *const StringObj);
1092
1093            retain_v2_typed_array(arr as *mut u8); // array refcount 2.
1094            release_v2_typed_array(arr as *mut u8); // → 1, array still live.
1095            assert_eq!(StringObj::as_str(s), "kept");
1096
1097            // Final release → array refcount 0 → drop_array_heap walks the
1098            // buffer, releasing the element's per-pointer share (2 → 1).
1099            release_v2_typed_array(arr as *mut u8);
1100            assert_eq!(v2_get_refcount(&(*s).header), 1);
1101            StringObj::drop(s);
1102        }
1103    }
1104
1105    /// A repeated retain/release cycle (mirror of a closure-captured array
1106    /// read many times) leaves the refcount balanced — no drift.
1107    #[test]
1108    fn release_v2_typed_array_repeated_cycle_balances() {
1109        use crate::v2::refcount::v2_get_refcount;
1110        unsafe {
1111            let arr = TypedArray::<i64>::with_capacity(1);
1112            TypedArray::push(arr, 99);
1113            super::stamp_elem_type_for_test(arr as *mut u8, ELEM_TYPE_I64);
1114            let hdr = arr as *const HeapHeader;
1115            for _ in 0..1000 {
1116                retain_v2_typed_array(arr as *mut u8);
1117                release_v2_typed_array(arr as *mut u8);
1118            }
1119            assert_eq!(v2_get_refcount(hdr), 1);
1120            assert_eq!(TypedArray::get(arr, 0), Some(99));
1121            release_v2_typed_array(arr as *mut u8);
1122        }
1123    }
1124}
1125
1126/// Test-only `_pad`-byte element-type stamp. The production stamp lives in
1127/// `shape-vm`'s `v2_array_detect::stamp_elem_type`; this mirror lets the
1128/// `shape-value` crate's own unit tests exercise `release_v2_typed_array`'s
1129/// stamped-element-type dispatch without a `shape-vm` dependency.
1130#[cfg(test)]
1131pub(crate) unsafe fn stamp_elem_type_for_test(ptr: *mut u8, elem_type: u8) {
1132    if ptr.is_null() {
1133        return;
1134    }
1135    unsafe {
1136        *ptr.add(7) = elem_type;
1137    }
1138}