Skip to main content

shape_value/
shape_array.rs

1//! Unified array backing store for VM and JIT.
2//!
3//! `ShapeArray` is a `#[repr(C)]` container of `ValueWord` values with a
4//! C-ABI-stable memory layout that the JIT can access inline:
5//!
6//! ```text
7//! offset  0: data  — *mut ValueWord (pointer to element buffer)
8//! offset  8: len   — u64 (number of elements)
9//! offset 16: cap   — u64 (allocated capacity)
10//! ```
11//!
12//! Since `ValueWord` is `#[repr(transparent)]` over `u64`, the raw pointer
13//! can be read as `*mut u64` by JIT-generated machine code without any
14//! casting or ABI mismatch.
15//!
16//! `ShapeArray` replaces both:
17//! - `Vec<ValueWord>` (VM-side `VMArray = Arc<Vec<ValueWord>>`)
18//! - `JitArray` (JIT-side `#[repr(C)]` array of raw `u64`)
19//!
20//! by providing one type with correct ValueWord clone/drop semantics **and**
21//! a stable C layout for the JIT.
22
23use crate::value_word::ValueWord;
24use std::alloc::{self, Layout};
25use std::slice;
26
27/// Unified array with C-ABI-stable layout for JIT and VM.
28///
29/// Elements are `ValueWord` values stored in a contiguous heap buffer.
30/// Clone/Drop correctly manage heap-tagged ValueWord reference counts.
31#[repr(C)]
32pub struct ShapeArray {
33    /// Pointer to element buffer (heap-allocated). Null when empty.
34    data: *mut ValueWord,
35    /// Number of elements currently stored.
36    len: u64,
37    /// Allocated capacity (number of ValueWord elements).
38    cap: u64,
39}
40
41// ShapeArray owns its buffer and ValueWord elements are Send+Sync.
42unsafe impl Send for ShapeArray {}
43unsafe impl Sync for ShapeArray {}
44
45/// Compile-time layout assertions for JIT offset stability.
46const _: () = {
47    assert!(std::mem::size_of::<ShapeArray>() == 24);
48    assert!(std::mem::size_of::<ValueWord>() == 8);
49};
50
51/// Byte offsets for JIT inline access.
52impl ShapeArray {
53    /// Byte offset of the `data` pointer field.
54    pub const OFFSET_DATA: usize = 0;
55    /// Byte offset of the `len` field.
56    pub const OFFSET_LEN: usize = 8;
57    /// Byte offset of the `cap` field.
58    pub const OFFSET_CAP: usize = 16;
59}
60
61impl ShapeArray {
62    /// Create an empty array with no allocation.
63    #[inline]
64    pub fn new() -> Self {
65        Self {
66            data: std::ptr::null_mut(),
67            len: 0,
68            cap: 0,
69        }
70    }
71
72    /// Create an array with pre-allocated capacity.
73    pub fn with_capacity(cap: usize) -> Self {
74        if cap == 0 {
75            return Self::new();
76        }
77        let layout = Layout::array::<ValueWord>(cap).unwrap();
78        let data = unsafe { alloc::alloc(layout) as *mut ValueWord };
79        if data.is_null() {
80            alloc::handle_alloc_error(layout);
81        }
82        Self {
83            data,
84            len: 0,
85            cap: cap as u64,
86        }
87    }
88
89    /// Create an array from an iterator of ValueWord values.
90    pub fn from_iter(iter: impl IntoIterator<Item = ValueWord>) -> Self {
91        let vec: Vec<ValueWord> = iter.into_iter().collect();
92        Self::from_vec(vec)
93    }
94
95    /// Create an array from an owned `Vec<ValueWord>`.
96    pub fn from_vec(vec: Vec<ValueWord>) -> Self {
97        if vec.is_empty() {
98            return Self::new();
99        }
100        let len = vec.len() as u64;
101        let cap = vec.capacity() as u64;
102        let mut vec = std::mem::ManuallyDrop::new(vec);
103        let data = vec.as_mut_ptr();
104        Self { data, len, cap }
105    }
106
107    /// Create an array by cloning from a slice of ValueWord.
108    pub fn from_slice(elements: &[ValueWord]) -> Self {
109        if elements.is_empty() {
110            return Self::new();
111        }
112        let cap = elements.len();
113        let layout = Layout::array::<ValueWord>(cap).unwrap();
114        let data = unsafe { alloc::alloc(layout) as *mut ValueWord };
115        if data.is_null() {
116            alloc::handle_alloc_error(layout);
117        }
118        // Clone each element (bumps Arc refcounts for heap-tagged values).
119        for (i, elem) in elements.iter().enumerate() {
120            unsafe {
121                std::ptr::write(data.add(i), elem.clone());
122            }
123        }
124        Self {
125            data,
126            len: elements.len() as u64,
127            cap: cap as u64,
128        }
129    }
130
131    /// Create from a raw u64 slice (for JIT interop).
132    ///
133    /// # Safety
134    /// The caller must ensure that each u64 is a valid ValueWord bit pattern.
135    /// For heap-tagged values, the caller must ensure the Arc refcount has been
136    /// incremented appropriately (or that this array takes ownership).
137    pub unsafe fn from_raw_u64_slice(elements: &[u64]) -> Self {
138        if elements.is_empty() {
139            return Self::new();
140        }
141        let cap = elements.len();
142        let layout = Layout::array::<ValueWord>(cap).unwrap();
143        // SAFETY: layout is valid and non-zero.
144        let data = unsafe { alloc::alloc(layout) as *mut ValueWord };
145        if data.is_null() {
146            alloc::handle_alloc_error(layout);
147        }
148        // SAFETY: ValueWord is repr(transparent) over u64, so this is a direct copy.
149        // Caller guarantees valid bit patterns and proper refcount management.
150        unsafe {
151            std::ptr::copy_nonoverlapping(
152                elements.as_ptr() as *const ValueWord,
153                data,
154                elements.len(),
155            );
156        }
157        Self {
158            data,
159            len: elements.len() as u64,
160            cap: cap as u64,
161        }
162    }
163
164    /// Number of elements.
165    #[inline]
166    pub fn len(&self) -> usize {
167        self.len as usize
168    }
169
170    /// Check if empty.
171    #[inline]
172    pub fn is_empty(&self) -> bool {
173        self.len == 0
174    }
175
176    /// Allocated capacity.
177    #[inline]
178    pub fn capacity(&self) -> usize {
179        self.cap as usize
180    }
181
182    /// View elements as a slice.
183    #[inline]
184    pub fn as_slice(&self) -> &[ValueWord] {
185        if self.data.is_null() || self.len == 0 {
186            return &[];
187        }
188        unsafe { slice::from_raw_parts(self.data, self.len as usize) }
189    }
190
191    /// View elements as a mutable slice.
192    #[inline]
193    pub fn as_mut_slice(&mut self) -> &mut [ValueWord] {
194        if self.data.is_null() || self.len == 0 {
195            return &mut [];
196        }
197        unsafe { slice::from_raw_parts_mut(self.data, self.len as usize) }
198    }
199
200    /// View raw buffer as u64 slice (for JIT interop).
201    #[inline]
202    pub fn as_raw_u64_slice(&self) -> &[u64] {
203        if self.data.is_null() || self.len == 0 {
204            return &[];
205        }
206        // ValueWord is repr(transparent) over u64.
207        unsafe { slice::from_raw_parts(self.data as *const u64, self.len as usize) }
208    }
209
210    /// Raw pointer to the data buffer (for JIT inline access).
211    #[inline]
212    pub fn as_ptr(&self) -> *const ValueWord {
213        self.data
214    }
215
216    /// Raw mutable pointer to the data buffer (for JIT inline access).
217    #[inline]
218    pub fn as_mut_ptr(&mut self) -> *mut ValueWord {
219        self.data
220    }
221
222    /// Get element by index (bounds-checked).
223    #[inline]
224    pub fn get(&self, index: usize) -> Option<&ValueWord> {
225        if index < self.len as usize {
226            unsafe { Some(&*self.data.add(index)) }
227        } else {
228            None
229        }
230    }
231
232    /// Push an element (amortized O(1) with doubling growth).
233    pub fn push(&mut self, value: ValueWord) {
234        if self.len == self.cap {
235            self.grow();
236        }
237        unsafe {
238            std::ptr::write(self.data.add(self.len as usize), value);
239        }
240        self.len += 1;
241    }
242
243    /// Pop the last element.
244    pub fn pop(&mut self) -> Option<ValueWord> {
245        if self.len == 0 {
246            return None;
247        }
248        self.len -= 1;
249        unsafe { Some(std::ptr::read(self.data.add(self.len as usize))) }
250    }
251
252    /// Iterate over elements.
253    #[inline]
254    pub fn iter(&self) -> slice::Iter<'_, ValueWord> {
255        self.as_slice().iter()
256    }
257
258    /// Get first element.
259    #[inline]
260    pub fn first(&self) -> Option<&ValueWord> {
261        if self.len > 0 {
262            unsafe { Some(&*self.data) }
263        } else {
264            None
265        }
266    }
267
268    /// Get last element.
269    #[inline]
270    pub fn last(&self) -> Option<&ValueWord> {
271        if self.len > 0 {
272            unsafe { Some(&*self.data.add(self.len as usize - 1)) }
273        } else {
274            None
275        }
276    }
277
278    /// Convert to Vec<ValueWord> (copies the data).
279    pub fn to_vec(&self) -> Vec<ValueWord> {
280        self.as_slice().to_vec()
281    }
282
283    /// Consume and convert into Vec<ValueWord> (transfers ownership, no copy).
284    pub fn into_vec(self) -> Vec<ValueWord> {
285        if self.data.is_null() || self.len == 0 {
286            std::mem::forget(self);
287            return Vec::new();
288        }
289        let vec = unsafe { Vec::from_raw_parts(self.data, self.len as usize, self.cap as usize) };
290        std::mem::forget(self); // Don't run our Drop
291        vec
292    }
293
294    /// Grow the buffer using amortized doubling.
295    fn grow(&mut self) {
296        let new_cap = if self.cap == 0 { 4 } else { self.cap * 2 };
297        let new_layout = Layout::array::<ValueWord>(new_cap as usize).unwrap();
298
299        let new_data = if self.data.is_null() {
300            unsafe { alloc::alloc(new_layout) as *mut ValueWord }
301        } else {
302            let old_layout = Layout::array::<ValueWord>(self.cap as usize).unwrap();
303            unsafe {
304                alloc::realloc(self.data as *mut u8, old_layout, new_layout.size())
305                    as *mut ValueWord
306            }
307        };
308
309        if new_data.is_null() {
310            alloc::handle_alloc_error(new_layout);
311        }
312        self.data = new_data;
313        self.cap = new_cap;
314    }
315}
316
317impl Drop for ShapeArray {
318    fn drop(&mut self) {
319        if !self.data.is_null() && self.cap > 0 {
320            // Drop each ValueWord element (decrements Arc refcounts for heap-tagged values).
321            for i in 0..self.len as usize {
322                unsafe {
323                    std::ptr::drop_in_place(self.data.add(i));
324                }
325            }
326            let layout = Layout::array::<ValueWord>(self.cap as usize).unwrap();
327            unsafe {
328                alloc::dealloc(self.data as *mut u8, layout);
329            }
330        }
331    }
332}
333
334impl Clone for ShapeArray {
335    fn clone(&self) -> Self {
336        Self::from_slice(self.as_slice())
337    }
338}
339
340impl Default for ShapeArray {
341    fn default() -> Self {
342        Self::new()
343    }
344}
345
346impl std::fmt::Debug for ShapeArray {
347    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
348        f.debug_struct("ShapeArray")
349            .field("len", &self.len)
350            .field("cap", &self.cap)
351            .finish()
352    }
353}
354
355impl std::ops::Index<usize> for ShapeArray {
356    type Output = ValueWord;
357
358    #[inline]
359    fn index(&self, index: usize) -> &ValueWord {
360        assert!(
361            index < self.len as usize,
362            "ShapeArray index out of bounds: {} >= {}",
363            index,
364            self.len
365        );
366        unsafe { &*self.data.add(index) }
367    }
368}
369
370impl std::ops::IndexMut<usize> for ShapeArray {
371    #[inline]
372    fn index_mut(&mut self, index: usize) -> &mut ValueWord {
373        assert!(
374            index < self.len as usize,
375            "ShapeArray index out of bounds: {} >= {}",
376            index,
377            self.len
378        );
379        unsafe { &mut *self.data.add(index) }
380    }
381}
382
383impl<'a> IntoIterator for &'a ShapeArray {
384    type Item = &'a ValueWord;
385    type IntoIter = slice::Iter<'a, ValueWord>;
386
387    fn into_iter(self) -> Self::IntoIter {
388        self.iter()
389    }
390}
391
392impl PartialEq for ShapeArray {
393    fn eq(&self, other: &Self) -> bool {
394        if self.len != other.len {
395            return false;
396        }
397        // Compare raw u64 bits (ValueWord is repr(transparent) over u64).
398        self.as_raw_u64_slice() == other.as_raw_u64_slice()
399    }
400}
401
402impl From<Vec<ValueWord>> for ShapeArray {
403    fn from(vec: Vec<ValueWord>) -> Self {
404        Self::from_vec(vec)
405    }
406}
407
408impl From<ShapeArray> for Vec<ValueWord> {
409    fn from(arr: ShapeArray) -> Self {
410        arr.into_vec()
411    }
412}
413
414impl From<std::sync::Arc<Vec<ValueWord>>> for ShapeArray {
415    /// Convert from the legacy `VMArray` (`Arc<Vec<ValueWord>>`).
416    /// Clones the elements since the Arc may be shared.
417    fn from(arc: std::sync::Arc<Vec<ValueWord>>) -> Self {
418        Self::from_slice(&arc)
419    }
420}
421
422#[cfg(test)]
423mod tests {
424    use super::*;
425
426    #[test]
427    fn test_repr_c_layout() {
428        assert_eq!(
429            std::mem::offset_of!(ShapeArray, data),
430            ShapeArray::OFFSET_DATA
431        );
432        assert_eq!(
433            std::mem::offset_of!(ShapeArray, len),
434            ShapeArray::OFFSET_LEN
435        );
436        assert_eq!(
437            std::mem::offset_of!(ShapeArray, cap),
438            ShapeArray::OFFSET_CAP
439        );
440        assert_eq!(std::mem::size_of::<ShapeArray>(), 24);
441    }
442
443    #[test]
444    fn test_new_empty() {
445        let arr = ShapeArray::new();
446        assert_eq!(arr.len(), 0);
447        assert!(arr.is_empty());
448        assert_eq!(arr.as_slice().len(), 0);
449    }
450
451    #[test]
452    fn test_push_pop() {
453        let mut arr = ShapeArray::new();
454        arr.push(ValueWord::from_f64(1.0));
455        arr.push(ValueWord::from_i64(2));
456        arr.push(ValueWord::from_bool(true));
457        assert_eq!(arr.len(), 3);
458
459        let v = arr.pop().unwrap();
460        assert_eq!(v.as_bool(), Some(true));
461        assert_eq!(arr.len(), 2);
462
463        let v = arr.pop().unwrap();
464        assert_eq!(v.as_i64(), Some(2));
465
466        let v = arr.pop().unwrap();
467        assert_eq!(v.as_f64(), Some(1.0));
468
469        assert!(arr.pop().is_none());
470    }
471
472    #[test]
473    fn test_from_vec() {
474        let vec = vec![ValueWord::from_f64(10.0), ValueWord::from_f64(20.0)];
475        let arr = ShapeArray::from_vec(vec);
476        assert_eq!(arr.len(), 2);
477        assert_eq!(arr[0].as_f64(), Some(10.0));
478        assert_eq!(arr[1].as_f64(), Some(20.0));
479    }
480
481    #[test]
482    fn test_from_slice() {
483        let elements = [
484            ValueWord::from_i64(1),
485            ValueWord::from_i64(2),
486            ValueWord::from_i64(3),
487        ];
488        let arr = ShapeArray::from_slice(&elements);
489        assert_eq!(arr.len(), 3);
490        assert_eq!(arr[0].as_i64(), Some(1));
491        assert_eq!(arr[2].as_i64(), Some(3));
492    }
493
494    #[test]
495    fn test_clone() {
496        let mut arr = ShapeArray::new();
497        arr.push(ValueWord::from_string(std::sync::Arc::new(
498            "hello".to_string(),
499        )));
500        arr.push(ValueWord::from_f64(42.0));
501        let cloned = arr.clone();
502        assert_eq!(cloned.len(), 2);
503        assert_eq!(cloned[0].as_str(), Some("hello"));
504        assert_eq!(cloned[1].as_f64(), Some(42.0));
505    }
506
507    #[test]
508    fn test_into_vec() {
509        let mut arr = ShapeArray::new();
510        arr.push(ValueWord::from_i64(5));
511        arr.push(ValueWord::from_i64(10));
512        let vec = arr.into_vec();
513        assert_eq!(vec.len(), 2);
514        assert_eq!(vec[0].as_i64(), Some(5));
515        assert_eq!(vec[1].as_i64(), Some(10));
516    }
517
518    #[test]
519    fn test_growth() {
520        let mut arr = ShapeArray::new();
521        for i in 0..100 {
522            arr.push(ValueWord::from_i64(i));
523        }
524        assert_eq!(arr.len(), 100);
525        for i in 0..100 {
526            assert_eq!(arr[i].as_i64(), Some(i as i64));
527        }
528    }
529
530    #[test]
531    fn test_index_access() {
532        let mut arr = ShapeArray::from_vec(vec![
533            ValueWord::from_f64(10.0),
534            ValueWord::from_f64(20.0),
535            ValueWord::from_f64(30.0),
536        ]);
537        assert_eq!(arr[0].as_f64(), Some(10.0));
538        assert_eq!(arr[1].as_f64(), Some(20.0));
539        arr[1] = ValueWord::from_f64(99.0);
540        assert_eq!(arr[1].as_f64(), Some(99.0));
541    }
542
543    #[test]
544    fn test_with_capacity() {
545        let mut arr = ShapeArray::with_capacity(10);
546        assert_eq!(arr.len(), 0);
547        assert!(arr.is_empty());
548        assert!(arr.capacity() >= 10);
549        arr.push(ValueWord::from_i64(42));
550        assert_eq!(arr.len(), 1);
551        assert_eq!(arr[0].as_i64(), Some(42));
552    }
553
554    #[test]
555    fn test_first_last() {
556        let arr = ShapeArray::from_vec(vec![
557            ValueWord::from_i64(10),
558            ValueWord::from_i64(20),
559            ValueWord::from_i64(30),
560        ]);
561        assert_eq!(arr.first().unwrap().as_i64(), Some(10));
562        assert_eq!(arr.last().unwrap().as_i64(), Some(30));
563
564        let empty = ShapeArray::new();
565        assert!(empty.first().is_none());
566        assert!(empty.last().is_none());
567    }
568
569    #[test]
570    fn test_iter() {
571        let arr = ShapeArray::from_vec(vec![
572            ValueWord::from_i64(1),
573            ValueWord::from_i64(2),
574            ValueWord::from_i64(3),
575        ]);
576        let sum: i64 = arr.iter().map(|nb| nb.as_i64().unwrap_or(0)).sum();
577        assert_eq!(sum, 6);
578    }
579
580    #[test]
581    fn test_raw_u64_interop() {
582        let values = [ValueWord::from_f64(1.0), ValueWord::from_f64(2.0)];
583        let arr = ShapeArray::from_slice(&values);
584        let raw = arr.as_raw_u64_slice();
585        assert_eq!(raw.len(), 2);
586        // Each u64 should match the ValueWord bits
587        assert_eq!(raw[0], 1.0f64.to_bits());
588        assert_eq!(raw[1], 2.0f64.to_bits());
589    }
590
591    #[test]
592    fn test_heap_values_cloned_correctly() {
593        use std::sync::Arc;
594        let s = Arc::new("test".to_string());
595        let nb = ValueWord::from_string(s.clone());
596
597        let mut arr = ShapeArray::new();
598        arr.push(nb.clone());
599        arr.push(nb.clone());
600
601        // Clone the array - should bump refcounts
602        let arr2 = arr.clone();
603        assert_eq!(arr2.len(), 2);
604        assert_eq!(arr2[0].as_str(), Some("test"));
605
606        // Drop both - no double-free
607        drop(arr);
608        drop(arr2);
609
610        // Original string arc should still be valid
611        assert_eq!(&*s, "test");
612    }
613
614    #[test]
615    fn test_empty_into_vec() {
616        let arr = ShapeArray::new();
617        let vec = arr.into_vec();
618        assert!(vec.is_empty());
619    }
620}