Skip to main content

shape_jit/ffi/v2/
mod.rs

1//! v2 typed FFI functions for JIT-compiled code.
2//!
3//! These functions use native types (f64, i64, i32, raw pointers) instead of
4//! NaN-boxed u64 values. They are called from JIT-compiled v2 code via direct
5//! extern "C" calls.
6
7pub mod collection_arc;
8pub mod typed_map;
9
10// jit_v2_map_* re-exports removed — see typed_map.rs SURFACE comment.
11// The kind-blind ValueWord-shape map FFI is gone; the strict-typing
12// rebuild routes through `Arc<HashMapData>` + `KindedSlot` per ADR-006
13// §2.7.5 / §2.7.6 / Q8.
14//
15// W12-jit-collection-arc-ffi-ctors-and-refcount (Phase 3 cluster-0
16// Round 9 / 8B.1, 2026-05-13): 8 typed-Arc collection ctors + 16
17// per-HeapKind kinded retain/release entries live in `collection_arc`.
18// Carrier shape is `Arc::into_raw(Arc<XData>) as u64` per audit §5,
19// distinct from the W11 `UnifiedValue<T>` HeapHeader-style allocations
20// in the rest of this module. Round 10 (8B.2) wires the MIR EnumStore
21// consumer + `jit_call_method` shell to dispatch through these
22// entry-points; until then they are inert at the program surface.
23
24use shape_value::heap_value::TypedObjectStorage;
25use shape_value::v2::decimal_obj::DecimalObj;
26use shape_value::v2::heap_header::HeapHeader;
27use shape_value::v2::string_obj::StringObj;
28use shape_value::v2::typed_array::TypedArray;
29
30// W11-fup-C (Phase 3d, 2026-05-18): stamp the v2-raw element-type byte
31// at `HeapHeader._pad` (offset 7) on every JIT-side TypedArray allocator
32// — mirror of the VM-side `op_new_typed_array_*` handlers at
33// `crates/shape-vm/src/executor/v2_handlers/array.rs:40-81` which call
34// `stamp_elem_type(ptr, ELEM_TYPE_<KIND>)` immediately after `with_capacity`.
35// Without the stamp, the canonical `as_v2_typed_array` detection at
36// `crates/shape-vm/src/executor/v2_handlers/v2_array_detect.rs:181-216`
37// returns `None` (reads `_pad = 0 = ELEM_TYPE_UNKNOWN`) — the JIT-allocated
38// array's print path, method-dispatch path, and any other consumer of the
39// canonical v2-detect interface would all silently fall back to non-typed
40// rendering (the empirical pre-fix `print(arr)` output was the raw pointer
41// `100218864737360` printed as a u64 scalar).
42//
43// Per ADR-006 §2.7.5 stamp-at-compile-time: the element kind is statically
44// proven at the JIT codegen site (`mir_compiler/v2_array.rs:166-176`
45// dispatch table picks the matching `jit_v2_array_new_<kind>` FuncRef per
46// the producing slot's `NativeKind`), so the stamp byte authoritatively
47// records compile-time-proven element kind into the v2-raw allocation's
48// heap-header at construction time — the same compile-time-proof shape
49// the VM-side `op_new_typed_array_*` handlers use.
50use shape_vm::executor::v2_handlers::v2_array_detect::{
51    stamp_elem_type, ELEM_TYPE_BOOL, ELEM_TYPE_CHAR, ELEM_TYPE_DECIMAL, ELEM_TYPE_F32,
52    ELEM_TYPE_F64, ELEM_TYPE_I16, ELEM_TYPE_I32, ELEM_TYPE_I64, ELEM_TYPE_I8,
53    ELEM_TYPE_STRING, ELEM_TYPE_TYPED_OBJECT, ELEM_TYPE_U16, ELEM_TYPE_U32, ELEM_TYPE_U8,
54};
55
56// ============================================================================
57// W16.2-J.3 (2026-05-22): macro-generated per-kind TypedArray FFI symbols
58// ============================================================================
59//
60// Mirror of the VM-side W16.2-J.2 opcode macro-gen for the per-kind JIT FFI
61// surface. Generates `jit_v2_array_new_<suffix>` / `jit_v2_array_get_<suffix>`
62// / `jit_v2_array_set_<suffix>` per `(suffix, native_ty, ELEM_TYPE_<KIND>)`
63// triple. 14 `TypedArrayKind` variants covered: F64, I64, I32, Bool, I8, U8,
64// I16, U16, U32, F32, Char, String, Decimal, TypedObject.
65//
66// Each allocator stamps the element-kind byte at `HeapHeader._pad` (offset 7)
67// per ADR-006 §2.7.5 producer-side stamp + W11-fup-C 2026-05-18 stamp
68// discipline (`crates/shape-vm/src/executor/v2_handlers/array.rs:40-81`
69// matching VM-side `op_new_typed_array_*` shape). The element kind is
70// statically proven at the JIT codegen site that picks the FuncRef, so the
71// stamp byte authoritatively records the compile-time-proven kind into the
72// v2-raw allocation at construction time — zero-tag runtime preserved.
73//
74// `Bool` uses `u8` storage to match the VM-side `TypedArray<u8>` carrier
75// (`crates/shape-vm/src/executor/v2_handlers/v2_array_detect.rs:1476-1490`).
76// `Char` uses `char` storage (4-byte `Copy`) matching the VM; the get/set
77// FFI signatures use `u32` for C-ABI-safety with `char::from_u32_unchecked`
78// at the boundary (the kind is statically proven so the bits are always a
79// valid Unicode scalar). `String` / `Decimal` / `TypedObject` use the
80// `*const T` heap-element carrier per ADR-006 §2.7.24 Q25.A SUPERSEDED +
81// audit `v0.3-w16-2-j-audit.md` §1.D; the FFI signatures use `*const u8` at
82// the boundary to match the existing `jit_v2_field_load_ptr` carrier
83// convention. Per-element refcount discipline is the caller's responsibility
84// per the VM-side `NewStringV2` / `TypedArrayPushString` / `_Decimal` /
85// `_TypedObject` transfer convention.
86//
87// The 3 heap-element kinds retain the legacy `jit_new_typed_array_<kind>`
88// allocator names (consumer-wired via `compiler/ffi_builder.rs:232-235`);
89// the macro additionally generates `jit_v2_array_new_<kind>` aliases that
90// share the same body, keeping the symbol surface uniform for the macro-
91// generated `get`/`set` entries.
92
93/// Generate `new`, `get`, `set` FFI symbols for one `TypedArrayKind` variant
94/// whose elements are POD scalar (`f64`, `i64`, `i32`, etc.).
95///
96/// The macro takes the fully-spelled symbol names so the expansion matches
97/// the existing `jit_v2_array_<op>_<suffix>` convention referenced from
98/// `crates/shape-jit/src/compiler/ffi_builder.rs` and
99/// `crates/shape-jit/src/ffi_symbols/v2_symbols.rs`.
100macro_rules! v2_typed_array_scalar {
101    (
102        new = $new_fn:ident,
103        get = $get_fn:ident,
104        set = $set_fn:ident,
105        ty = $native_ty:ty,
106        elem_byte = $elem_byte:expr,
107        kind_label = $kind_label:literal $(,)?
108    ) => {
109        #[doc = concat!(
110            "Allocate an empty `TypedArray<", stringify!($native_ty), ">` of given capacity. ",
111            "Stamps `", $kind_label, "` at `HeapHeader._pad` offset 7 per ADR-006 §2.7.5."
112        )]
113        #[unsafe(no_mangle)]
114        pub extern "C" fn $new_fn(capacity: u32) -> *mut TypedArray<$native_ty> {
115            let ptr = TypedArray::<$native_ty>::with_capacity(capacity);
116            // SAFETY: `with_capacity` returned a freshly-allocated TypedArray
117            // with a valid HeapHeader at offset 0; stamping the `_pad` byte
118            // at offset 7 is the documented W11-fup-C producer-side stamp.
119            unsafe { stamp_elem_type(ptr as *mut u8, $elem_byte) };
120            ptr
121        }
122
123        #[doc = concat!(
124            "Read the element at `index` from a `TypedArray<", stringify!($native_ty), ">`. ",
125            "Panics on out-of-bounds."
126        )]
127        #[unsafe(no_mangle)]
128        pub extern "C" fn $get_fn(
129            arr: *const TypedArray<$native_ty>,
130            index: i64,
131        ) -> $native_ty {
132            unsafe {
133                if index < 0 || index as u32 >= (*arr).len {
134                    panic!(
135                        concat!(
136                            "v2 array ", $kind_label, " index {} out of bounds (len {})"
137                        ),
138                        index,
139                        (*arr).len
140                    );
141                }
142                TypedArray::get_unchecked(arr, index as u32)
143            }
144        }
145
146        #[doc = concat!(
147            "Write `val` to the element at `index` in a `TypedArray<",
148            stringify!($native_ty), ">`."
149        )]
150        #[unsafe(no_mangle)]
151        pub extern "C" fn $set_fn(
152            arr: *mut TypedArray<$native_ty>,
153            index: i64,
154            val: $native_ty,
155        ) {
156            unsafe {
157                TypedArray::set(arr, index as u32, val);
158            }
159        }
160    };
161}
162
163// ── 11 POD-element kinds (f64 / i64 / i32 / bool / i8 / u8 / i16 / u16 /
164//    u32 / f32 / char) ──────────────────────────────────────────────────────
165
166v2_typed_array_scalar! {
167    new = jit_v2_array_new_f64,
168    get = jit_v2_array_get_f64,
169    set = jit_v2_array_set_f64,
170    ty = f64,
171    elem_byte = ELEM_TYPE_F64,
172    kind_label = "f64",
173}
174
175v2_typed_array_scalar! {
176    new = jit_v2_array_new_i64,
177    get = jit_v2_array_get_i64,
178    set = jit_v2_array_set_i64,
179    ty = i64,
180    elem_byte = ELEM_TYPE_I64,
181    kind_label = "i64",
182}
183
184v2_typed_array_scalar! {
185    new = jit_v2_array_new_i32,
186    get = jit_v2_array_get_i32,
187    set = jit_v2_array_set_i32,
188    ty = i32,
189    elem_byte = ELEM_TYPE_I32,
190    kind_label = "i32",
191}
192
193// `Bool` uses `u8` storage per the VM-side `TypedArray<u8>` carrier
194// (1 byte / element, 0 = false, 1 = true). Same shape as the dedicated
195// `U8` kind below; distinguished at the runtime element-type-tag layer
196// (`ELEM_TYPE_BOOL` vs `ELEM_TYPE_U8`).
197v2_typed_array_scalar! {
198    new = jit_v2_array_new_bool,
199    get = jit_v2_array_get_bool,
200    set = jit_v2_array_set_bool,
201    ty = u8,
202    elem_byte = ELEM_TYPE_BOOL,
203    kind_label = "bool",
204}
205
206// W12 S1 (2026-05-13) — sized-integer monomorphizations for `TypedArray<i8>` /
207// `<u8>` / `<i16>` / `<u16>` / `<u32>`. `U64` deliberately omitted per the
208// `v2_array_detect.rs::V2ElemType` doc (deferred to S1.5 per the
209// `NativeKind::UInt64` carrier-disambiguation ruling).
210v2_typed_array_scalar! {
211    new = jit_v2_array_new_i8,
212    get = jit_v2_array_get_i8,
213    set = jit_v2_array_set_i8,
214    ty = i8,
215    elem_byte = ELEM_TYPE_I8,
216    kind_label = "i8",
217}
218
219v2_typed_array_scalar! {
220    new = jit_v2_array_new_u8,
221    get = jit_v2_array_get_u8,
222    set = jit_v2_array_set_u8,
223    ty = u8,
224    elem_byte = ELEM_TYPE_U8,
225    kind_label = "u8",
226}
227
228v2_typed_array_scalar! {
229    new = jit_v2_array_new_i16,
230    get = jit_v2_array_get_i16,
231    set = jit_v2_array_set_i16,
232    ty = i16,
233    elem_byte = ELEM_TYPE_I16,
234    kind_label = "i16",
235}
236
237v2_typed_array_scalar! {
238    new = jit_v2_array_new_u16,
239    get = jit_v2_array_get_u16,
240    set = jit_v2_array_set_u16,
241    ty = u16,
242    elem_byte = ELEM_TYPE_U16,
243    kind_label = "u16",
244}
245
246v2_typed_array_scalar! {
247    new = jit_v2_array_new_u32,
248    get = jit_v2_array_get_u32,
249    set = jit_v2_array_set_u32,
250    ty = u32,
251    elem_byte = ELEM_TYPE_U32,
252    kind_label = "u32",
253}
254
255// Wave 2 Agent A1 (2026-05-14) — `F32` scalar bucket.
256v2_typed_array_scalar! {
257    new = jit_v2_array_new_f32,
258    get = jit_v2_array_get_f32,
259    set = jit_v2_array_set_f32,
260    ty = f32,
261    elem_byte = ELEM_TYPE_F32,
262    kind_label = "f32",
263}
264
265// `Char` storage is `TypedArray<char>` (4-byte `Copy`) matching the VM-side
266// carrier at `crates/shape-vm/src/executor/v2_handlers/v2_array_detect.rs:1587`.
267// `char` is not C-ABI-safe as a scalar parameter/return per the
268// `improper_ctypes_definitions` lint, so the FFI signatures use `u32`
269// codepoint at the boundary with `char::from_u32_unchecked` /
270// `<char as Into<u32>>` conversion. The kind is statically proven at the JIT
271// codegen site, so the bits are always a valid Unicode scalar value (per
272// the matching VM-side compile-time-proof at the producing
273// `NewTypedArrayChar` opcode).
274
275#[doc = "Allocate an empty `TypedArray<char>` of given capacity. \
276Stamps `ELEM_TYPE_CHAR` at `HeapHeader._pad` offset 7 per ADR-006 §2.7.5."]
277#[unsafe(no_mangle)]
278pub extern "C" fn jit_v2_array_new_char(capacity: u32) -> *mut TypedArray<char> {
279    let ptr = TypedArray::<char>::with_capacity(capacity);
280    unsafe { stamp_elem_type(ptr as *mut u8, ELEM_TYPE_CHAR) };
281    ptr
282}
283
284#[doc = "Read the codepoint at `index` from a `TypedArray<char>`. Panics on \
285out-of-bounds."]
286#[unsafe(no_mangle)]
287pub extern "C" fn jit_v2_array_get_char(
288    arr: *const TypedArray<char>,
289    index: i64,
290) -> u32 {
291    unsafe {
292        if index < 0 || index as u32 >= (*arr).len {
293            panic!(
294                "v2 array char index {} out of bounds (len {})",
295                index,
296                (*arr).len
297            );
298        }
299        TypedArray::<char>::get_unchecked(arr, index as u32) as u32
300    }
301}
302
303#[doc = "Write a Unicode codepoint to the element at `index` in a \
304`TypedArray<char>`. The caller must ensure `val` is a valid Unicode scalar \
305value (the JIT codegen site proves this at compile time)."]
306#[unsafe(no_mangle)]
307pub extern "C" fn jit_v2_array_set_char(
308    arr: *mut TypedArray<char>,
309    index: i64,
310    val: u32,
311) {
312    // SAFETY: the JIT codegen site that emits this call has compile-time-proven
313    // the producing slot's NativeKind::Char, so `val` is always a valid
314    // Unicode scalar value (matching the VM-side `NewTypedArrayChar` +
315    // `TypedArraySetChar` producer compile-time proof).
316    let c = unsafe { char::from_u32_unchecked(val) };
317    unsafe {
318        TypedArray::set(arr, index as u32, c);
319    }
320}
321
322// ── 3 heap-element kinds (`*const StringObj` / `*const DecimalObj` /
323//    `*const TypedObjectStorage`) ───────────────────────────────────────────
324//
325// Element type is `*const T` where `T: HeapElement`, not a flat scalar. The
326// allocators mirror the scalar shape (`TypedArray::with_capacity` returning a
327// `*mut TypedArray<*const T>` carrier); per-element refcount discipline is
328// the caller's responsibility (the bytecode emits `TypedArrayPushString` /
329// `TypedArrayPushDecimal` / `TypedArrayPushTypedObject` consuming the
330// per-element share allocated by the corresponding `NewStringV2` /
331// `NewDecimalV2` / `op_new_typed_object` site — matches the VM-side handler
332// at `crates/shape-vm/src/executor/v2_handlers/array.rs:803-858`).
333//
334// Two allocator names per heap kind: the legacy `jit_new_typed_array_<kind>`
335// (consumer-wired via `crates/shape-jit/src/compiler/ffi_builder.rs:232-235`)
336// AND the macro-uniform `jit_v2_array_new_<kind>` (forwarder for code paths
337// that walk the per-kind symbol matrix). Bodies are identical.
338//
339// `get` / `set` FFI signatures use `*const u8` for the pointer payload to
340// match the existing `jit_v2_field_load_ptr` / `_store_ptr` carrier
341// convention. Callers transfer per-element refcount shares per the VM-side
342// transfer rules.
343
344/// Generate `get` / `set` FFI symbols for one heap-element `TypedArrayKind`
345/// variant. The `new` allocator is defined separately to keep the legacy
346/// `jit_new_typed_array_<kind>` name + add the macro-uniform alias.
347macro_rules! v2_typed_array_heap_get_set {
348    (
349        get = $get_fn:ident,
350        set = $set_fn:ident,
351        elem_obj_ty = $elem_obj_ty:ty,
352        kind_label = $kind_label:literal $(,)?
353    ) => {
354        #[doc = concat!(
355            "Read the per-element heap pointer at `index` from a ",
356            "`TypedArray<*const ", stringify!($elem_obj_ty), ">`. ",
357            "Panics on out-of-bounds. Caller is responsible for per-element ",
358            "retain discipline before transferring the share."
359        )]
360        #[unsafe(no_mangle)]
361        pub extern "C" fn $get_fn(
362            arr: *const TypedArray<*const $elem_obj_ty>,
363            index: i64,
364        ) -> *const u8 {
365            unsafe {
366                if index < 0 || index as u32 >= (*arr).len {
367                    panic!(
368                        concat!(
369                            "v2 array ", $kind_label, " index {} out of bounds (len {})"
370                        ),
371                        index,
372                        (*arr).len
373                    );
374                }
375                TypedArray::<*const $elem_obj_ty>::get_unchecked(arr, index as u32)
376                    as *const u8
377            }
378        }
379
380        #[doc = concat!(
381            "Write a per-element heap pointer at `index` in a ",
382            "`TypedArray<*const ", stringify!($elem_obj_ty), ">`. The caller ",
383            "must have an owning refcount share for `val` and is responsible ",
384            "for releasing any previous element at `index` before this call."
385        )]
386        #[unsafe(no_mangle)]
387        pub extern "C" fn $set_fn(
388            arr: *mut TypedArray<*const $elem_obj_ty>,
389            index: i64,
390            val: *const u8,
391        ) {
392            unsafe {
393                TypedArray::set(arr, index as u32, val as *const $elem_obj_ty);
394            }
395        }
396    };
397}
398
399#[doc = "Allocate an empty `TypedArray<*const StringObj>` of given capacity. \
400Stamps `ELEM_TYPE_STRING` at `HeapHeader._pad` offset 7 per ADR-006 §2.7.5. \
401ckpt-6-prime Group X JIT FFI String/Decimal BUILD (2026-05-15)."]
402#[unsafe(no_mangle)]
403pub extern "C" fn jit_new_typed_array_string(
404    capacity: u32,
405) -> *mut TypedArray<*const StringObj> {
406    let ptr = TypedArray::<*const StringObj>::with_capacity(capacity);
407    unsafe { stamp_elem_type(ptr as *mut u8, ELEM_TYPE_STRING) };
408    ptr
409}
410
411/// Macro-uniform alias for `jit_new_typed_array_string`. Same body; exists so
412/// the per-kind symbol matrix (`jit_v2_array_new_<suffix>`) is complete for
413/// all 14 `TypedArrayKind` variants. Consumer-wired alias to the legacy name.
414#[unsafe(no_mangle)]
415pub extern "C" fn jit_v2_array_new_string(
416    capacity: u32,
417) -> *mut TypedArray<*const StringObj> {
418    jit_new_typed_array_string(capacity)
419}
420
421v2_typed_array_heap_get_set! {
422    get = jit_v2_array_get_string,
423    set = jit_v2_array_set_string,
424    elem_obj_ty = StringObj,
425    kind_label = "string",
426}
427
428#[doc = "Allocate an empty `TypedArray<*const DecimalObj>` of given capacity. \
429Stamps `ELEM_TYPE_DECIMAL` at `HeapHeader._pad` offset 7 per ADR-006 §2.7.5. \
430ckpt-6-prime Group X JIT FFI String/Decimal BUILD (2026-05-15)."]
431#[unsafe(no_mangle)]
432pub extern "C" fn jit_new_typed_array_decimal(
433    capacity: u32,
434) -> *mut TypedArray<*const DecimalObj> {
435    let ptr = TypedArray::<*const DecimalObj>::with_capacity(capacity);
436    unsafe { stamp_elem_type(ptr as *mut u8, ELEM_TYPE_DECIMAL) };
437    ptr
438}
439
440/// Macro-uniform alias for `jit_new_typed_array_decimal`. See
441/// `jit_v2_array_new_string` for rationale.
442#[unsafe(no_mangle)]
443pub extern "C" fn jit_v2_array_new_decimal(
444    capacity: u32,
445) -> *mut TypedArray<*const DecimalObj> {
446    jit_new_typed_array_decimal(capacity)
447}
448
449v2_typed_array_heap_get_set! {
450    get = jit_v2_array_get_decimal,
451    set = jit_v2_array_set_decimal,
452    elem_obj_ty = DecimalObj,
453    kind_label = "decimal",
454}
455
456/// Phase 4b Round 4 W16.2-A op_new_array-typed-object-element (2026-05-18).
457///
458/// JIT-side allocator for `TypedArray<*const TypedObjectStorage>` carriers.
459/// Mirror of `jit_new_typed_array_string` / `_decimal` — allocates an empty
460/// typed-array with given capacity, stamps the v2-raw element-type byte at
461/// `HeapHeader._pad` (offset 7) per the W11-fup-C 2026-05-18 stamp-discipline
462/// rule, returns the raw `*mut TypedArray<*const TypedObjectStorage>` carrier.
463///
464/// Per-element refcount discipline (caller's responsibility): the JIT-emitted
465/// code allocates fresh `TypedObjectStorage` instances via
466/// `TypedObjectStorage::_new` (refcount = 1) and transfers per-element share
467/// to the array via the matching `TypedArrayPushTypedObject` opcode (or its
468/// inline JIT analogue if/when wired). Mirror of the VM-side handler at
469/// `crates/shape-vm/src/executor/v2_handlers/array.rs::NewTypedArrayTypedObject`.
470#[unsafe(no_mangle)]
471pub extern "C" fn jit_new_typed_array_typed_object(
472    capacity: u32,
473) -> *mut TypedArray<*const TypedObjectStorage> {
474    let ptr = TypedArray::<*const TypedObjectStorage>::with_capacity(capacity);
475    unsafe { stamp_elem_type(ptr as *mut u8, ELEM_TYPE_TYPED_OBJECT) };
476    ptr
477}
478
479/// Macro-uniform alias for `jit_new_typed_array_typed_object`. See
480/// `jit_v2_array_new_string` for rationale.
481#[unsafe(no_mangle)]
482pub extern "C" fn jit_v2_array_new_typed_object(
483    capacity: u32,
484) -> *mut TypedArray<*const TypedObjectStorage> {
485    jit_new_typed_array_typed_object(capacity)
486}
487
488v2_typed_array_heap_get_set! {
489    get = jit_v2_array_get_typed_object,
490    set = jit_v2_array_set_typed_object,
491    elem_obj_ty = TypedObjectStorage,
492    kind_label = "typed_object",
493}
494
495/// JIT-compile-time per-element materializer for `*const StringObj` constants
496/// inside Array<string> literal aggregates. Allocates a fresh `StringObj`
497/// (refcount = 1) and boosts the refcount by one for the constant's permanent
498/// share — the JIT-emitted code pushes the returned pointer onto a freshly
499/// allocated `TypedArray<*const StringObj>` and transfers the share to the
500/// array's per-element slot (matches the VM-side `NewStringV2` +
501/// `TypedArrayPushString` transfer convention at
502/// `crates/shape-vm/src/executor/v2_handlers/array.rs:803-828`).
503///
504/// Mirrors `crate::ffi::string::arc_string_constant`'s permanent-share
505/// discipline for the §2.7.5 `Arc<String>` carrier, but uses the v2-raw
506/// `StringObj` carrier per ADR-006 §2.7.5 + §2.7.24 Q25.A SUPERSEDED + audit
507/// deliverable (b) §4.1.B.
508///
509/// ckpt-6-prime Group X JIT FFI String/Decimal BUILD (2026-05-15) — per-
510/// element NewStringV2 equivalent at the JIT mir_compiler dispatch site.
511pub fn string_obj_constant(s: &str) -> *const StringObj {
512    let ptr = StringObj::new(s);
513    // SAFETY: ptr was just produced by StringObj::new with refcount=1.
514    // Bump the refcount to 2 to retain the constant's permanent share.
515    // The constant's "active share" is the one the JIT-emitted code
516    // pushes onto the freshly allocated TypedArray; the permanent share
517    // is the one that survives Drop chains across multiple invocations
518    // of the JIT-compiled function. Same lifecycle as
519    // `crate::ffi::string::arc_string_constant`.
520    unsafe {
521        (*ptr).header.retain();
522    }
523    ptr as *const StringObj
524}
525
526/// JIT-compile-time per-element materializer for `*const DecimalObj`
527/// constants inside Array<decimal> literal aggregates. Same permanent-share
528/// discipline as `string_obj_constant`. The `bits` argument is the
529/// `Decimal::serialize()` byte-array packed into a `[u8; 16]` carrier —
530/// `Decimal::deserialize(bits)` reconstructs the `Decimal` value at JIT
531/// compile time, then `DecimalObj::new(value)` allocates a refcounted
532/// `DecimalObj` heap object.
533///
534/// ckpt-6-prime Group X JIT FFI String/Decimal BUILD (2026-05-15) — per-
535/// element NewDecimalV2 equivalent at the JIT mir_compiler dispatch site.
536pub fn decimal_obj_constant(value: rust_decimal::Decimal) -> *const DecimalObj {
537    let ptr = DecimalObj::new(value);
538    // SAFETY: ptr was just produced by DecimalObj::new with refcount=1.
539    // See `string_obj_constant` for the permanent-share rationale.
540    unsafe {
541        (*ptr).header.retain();
542    }
543    ptr as *const DecimalObj
544}
545
546/// Generic typed-array push dispatcher (R7.2).
547///
548/// Accepts an erased typed-array pointer, the element's bit pattern zero/sign-
549/// extended to 64 bits, and the element's byte size. Dispatches to the
550/// matching `TypedArray<T>::push` instantiation by `elem_size`.
551///
552/// `TypedArray<T>` has identical struct layout regardless of T (the `T` only
553/// appears behind a `*mut T`), and `Layout::array::<T>` is identical for types
554/// of the same size & alignment, so routing 8-byte pushes through
555/// `TypedArray<i64>` is byte-equivalent to going through `TypedArray<f64>`.
556///
557/// # Safety
558/// `arr` must point to a live `TypedArray<T>` whose element type has size
559/// `elem_size` (1, 4, or 8). `bits` must contain the value to store in the
560/// low `elem_size` bytes.
561#[unsafe(no_mangle)]
562pub extern "C" fn jit_v2_array_push(arr: *mut HeapHeader, bits: u64, elem_size: u8) {
563    unsafe {
564        match elem_size {
565            1 => TypedArray::<u8>::push(arr as *mut TypedArray<u8>, bits as u8),
566            4 => TypedArray::<i32>::push(arr as *mut TypedArray<i32>, bits as i32),
567            8 => TypedArray::<i64>::push(arr as *mut TypedArray<i64>, bits as i64),
568            _ => unreachable!("jit_v2_array_push: invalid elem_size {}", elem_size),
569        }
570    }
571}
572
573#[unsafe(no_mangle)]
574pub extern "C" fn jit_v2_array_len_f64(arr: *const TypedArray<f64>) -> u32 {
575    unsafe { TypedArray::len(arr) }
576}
577
578/// SIMD-accelerated sum over a `TypedArray<f64>` (Phase C.3).
579///
580/// Uses `wide::f64x4` for 4-lane parallel addition when `len >= 16`. Below
581/// that threshold, the vector load/splat overhead exceeds the savings so we
582/// fall back to scalar accumulation. Returns `0.0` for null or empty arrays.
583///
584/// # Safety
585/// `arr` must be a valid `TypedArray<f64>*` (or null).
586#[unsafe(no_mangle)]
587pub extern "C" fn jit_v2_array_sum_f64(arr: *const TypedArray<f64>) -> f64 {
588    if arr.is_null() {
589        return 0.0;
590    }
591    let (data, len) = unsafe { ((*arr).data as *const f64, (*arr).len as usize) };
592    if len == 0 || data.is_null() {
593        return 0.0;
594    }
595    unsafe { simd_sum_f64_inner(data, len) }
596}
597
598/// SIMD-accelerated sum over a `TypedArray<i64>` (Phase C.3). Uses wrapping
599/// arithmetic (matches Shape's v2 int-sum semantics — no overflow panic).
600///
601/// # Safety
602/// `arr` must be a valid `TypedArray<i64>*` (or null).
603#[unsafe(no_mangle)]
604pub extern "C" fn jit_v2_array_sum_i64(arr: *const TypedArray<i64>) -> i64 {
605    if arr.is_null() {
606        return 0;
607    }
608    let (data, len) = unsafe { ((*arr).data as *const i64, (*arr).len as usize) };
609    if len == 0 || data.is_null() {
610        return 0;
611    }
612    unsafe { simd_sum_i64_inner(data, len) }
613}
614
615/// SIMD reduction threshold — below this, setup cost dominates.
616const SIMD_SUM_THRESHOLD: usize = 16;
617
618// ── Min / Max / Mean / Sum-of-squares over Array<number> ─────────────────
619
620/// SIMD-accelerated minimum over a `TypedArray<f64>`. Returns `NaN` for null
621/// or empty arrays (matches `Vec<number>.min()` semantics on empty input).
622/// NaN propagates naturally — `fast_min(NaN, v)` yields `NaN` on all
623/// compliant backends.
624///
625/// # Safety
626/// `arr` must be a valid `TypedArray<f64>*` (or null).
627#[unsafe(no_mangle)]
628pub extern "C" fn jit_v2_array_min_f64(arr: *const TypedArray<f64>) -> f64 {
629    if arr.is_null() {
630        return f64::NAN;
631    }
632    let (data, len) = unsafe { ((*arr).data as *const f64, (*arr).len as usize) };
633    if len == 0 || data.is_null() {
634        return f64::NAN;
635    }
636    unsafe { simd_min_f64_inner(data, len) }
637}
638
639/// SIMD-accelerated maximum over a `TypedArray<f64>`. Returns `NaN` for null
640/// or empty arrays. NaN propagates.
641///
642/// # Safety
643/// `arr` must be a valid `TypedArray<f64>*` (or null).
644#[unsafe(no_mangle)]
645pub extern "C" fn jit_v2_array_max_f64(arr: *const TypedArray<f64>) -> f64 {
646    if arr.is_null() {
647        return f64::NAN;
648    }
649    let (data, len) = unsafe { ((*arr).data as *const f64, (*arr).len as usize) };
650    if len == 0 || data.is_null() {
651        return f64::NAN;
652    }
653    unsafe { simd_max_f64_inner(data, len) }
654}
655
656/// SIMD-accelerated mean (arithmetic average) over a `TypedArray<f64>`.
657/// Returns `NaN` for null or empty arrays.
658///
659/// # Safety
660/// `arr` must be a valid `TypedArray<f64>*` (or null).
661#[unsafe(no_mangle)]
662pub extern "C" fn jit_v2_array_mean_f64(arr: *const TypedArray<f64>) -> f64 {
663    if arr.is_null() {
664        return f64::NAN;
665    }
666    let (data, len) = unsafe { ((*arr).data as *const f64, (*arr).len as usize) };
667    if len == 0 || data.is_null() {
668        return f64::NAN;
669    }
670    let sum = unsafe { simd_sum_f64_inner(data, len) };
671    sum / (len as f64)
672}
673
674/// SIMD-accelerated sum-of-squares over a `TypedArray<f64>` — `Σ x²`.
675/// Single-pass: load, multiply, accumulate. Useful as a building block for
676/// variance/std and for `arr.map(|x| x*x).sum()` patterns. Returns `0.0`
677/// for null or empty arrays.
678///
679/// # Safety
680/// `arr` must be a valid `TypedArray<f64>*` (or null).
681#[unsafe(no_mangle)]
682pub extern "C" fn jit_v2_array_sum_squares_f64(arr: *const TypedArray<f64>) -> f64 {
683    if arr.is_null() {
684        return 0.0;
685    }
686    let (data, len) = unsafe { ((*arr).data as *const f64, (*arr).len as usize) };
687    if len == 0 || data.is_null() {
688        return 0.0;
689    }
690    unsafe { simd_sum_squares_f64_inner(data, len) }
691}
692
693// ── Element-wise scalar ops returning a new Array<number> ────────────────
694
695/// Allocate a new `TypedArray<f64>` with length equal to `arr.len`, populated
696/// by multiplying each element by `factor`. Returns a null pointer when the
697/// receiver is null.
698///
699/// # Safety
700/// `arr` must be a valid `TypedArray<f64>*` (or null).
701#[unsafe(no_mangle)]
702pub extern "C" fn jit_v2_array_scale_f64(
703    arr: *const TypedArray<f64>,
704    factor: f64,
705) -> *mut TypedArray<f64> {
706    if arr.is_null() {
707        return std::ptr::null_mut();
708    }
709    let (data, len) = unsafe { ((*arr).data as *const f64, (*arr).len as usize) };
710    let out = TypedArray::<f64>::with_capacity(len as u32);
711    if len == 0 || data.is_null() {
712        return out;
713    }
714    unsafe {
715        let out_data = (*out).data as *mut f64;
716        simd_scale_f64_inner(data, out_data, len, factor);
717        (*out).len = len as u32;
718    }
719    out
720}
721
722/// Allocate a new `TypedArray<f64>` with length equal to `arr.len`, populated
723/// by adding `offset` to each element. Returns a null pointer when the
724/// receiver is null.
725///
726/// # Safety
727/// `arr` must be a valid `TypedArray<f64>*` (or null).
728#[unsafe(no_mangle)]
729pub extern "C" fn jit_v2_array_add_scalar_f64(
730    arr: *const TypedArray<f64>,
731    offset: f64,
732) -> *mut TypedArray<f64> {
733    if arr.is_null() {
734        return std::ptr::null_mut();
735    }
736    let (data, len) = unsafe { ((*arr).data as *const f64, (*arr).len as usize) };
737    let out = TypedArray::<f64>::with_capacity(len as u32);
738    if len == 0 || data.is_null() {
739        return out;
740    }
741    unsafe {
742        let out_data = (*out).data as *mut f64;
743        simd_add_scalar_f64_inner(data, out_data, len, offset);
744        (*out).len = len as u32;
745    }
746    out
747}
748
749// ── Element-wise binary ops (two arrays) ─────────────────────────────────
750
751/// Allocate a new `TypedArray<f64>` holding the element-wise sum of `a` and
752/// `b`. Requires matching lengths — panics on mismatch to mirror Shape's
753/// `dot()`/runtime length-mismatch semantics. Returns null for null inputs.
754///
755/// # Safety
756/// `a` and `b` must be valid `TypedArray<f64>*` (or null).
757#[unsafe(no_mangle)]
758pub extern "C" fn jit_v2_array_add_f64(
759    a: *const TypedArray<f64>,
760    b: *const TypedArray<f64>,
761) -> *mut TypedArray<f64> {
762    if a.is_null() || b.is_null() {
763        return std::ptr::null_mut();
764    }
765    let (a_data, a_len) = unsafe { ((*a).data as *const f64, (*a).len as usize) };
766    let (b_data, b_len) = unsafe { ((*b).data as *const f64, (*b).len as usize) };
767    if a_len != b_len {
768        panic!(
769            "v2 array_add_f64: length mismatch ({} vs {})",
770            a_len, b_len
771        );
772    }
773    let out = TypedArray::<f64>::with_capacity(a_len as u32);
774    if a_len == 0 {
775        return out;
776    }
777    unsafe {
778        let out_data = (*out).data as *mut f64;
779        simd_binary_add_f64_inner(a_data, b_data, out_data, a_len);
780        (*out).len = a_len as u32;
781    }
782    out
783}
784
785/// Allocate a new `TypedArray<f64>` holding the element-wise product of `a`
786/// and `b`. Requires matching lengths.
787///
788/// # Safety
789/// `a` and `b` must be valid `TypedArray<f64>*` (or null).
790#[unsafe(no_mangle)]
791pub extern "C" fn jit_v2_array_mul_f64(
792    a: *const TypedArray<f64>,
793    b: *const TypedArray<f64>,
794) -> *mut TypedArray<f64> {
795    if a.is_null() || b.is_null() {
796        return std::ptr::null_mut();
797    }
798    let (a_data, a_len) = unsafe { ((*a).data as *const f64, (*a).len as usize) };
799    let (b_data, b_len) = unsafe { ((*b).data as *const f64, (*b).len as usize) };
800    if a_len != b_len {
801        panic!(
802            "v2 array_mul_f64: length mismatch ({} vs {})",
803            a_len, b_len
804        );
805    }
806    let out = TypedArray::<f64>::with_capacity(a_len as u32);
807    if a_len == 0 {
808        return out;
809    }
810    unsafe {
811        let out_data = (*out).data as *mut f64;
812        simd_binary_mul_f64_inner(a_data, b_data, out_data, a_len);
813        (*out).len = a_len as u32;
814    }
815    out
816}
817
818#[inline]
819unsafe fn simd_sum_f64_inner(data: *const f64, len: usize) -> f64 {
820    use wide::f64x4;
821    if len < SIMD_SUM_THRESHOLD {
822        let mut s = 0.0_f64;
823        for i in 0..len {
824            s += unsafe { *data.add(i) };
825        }
826        return s;
827    }
828    let chunks = len / 4;
829    let mut acc = f64x4::splat(0.0);
830    for i in 0..chunks {
831        let b = i * 4;
832        let v = unsafe {
833            f64x4::from([
834                *data.add(b),
835                *data.add(b + 1),
836                *data.add(b + 2),
837                *data.add(b + 3),
838            ])
839        };
840        acc += v;
841    }
842    let parts = acc.to_array();
843    let mut s = parts[0] + parts[1] + parts[2] + parts[3];
844    for i in (chunks * 4)..len {
845        s += unsafe { *data.add(i) };
846    }
847    s
848}
849
850#[inline]
851unsafe fn simd_sum_i64_inner(data: *const i64, len: usize) -> i64 {
852    use wide::i64x4;
853    if len < SIMD_SUM_THRESHOLD {
854        let mut s: i64 = 0;
855        for i in 0..len {
856            s = s.wrapping_add(unsafe { *data.add(i) });
857        }
858        return s;
859    }
860    let chunks = len / 4;
861    let mut acc = i64x4::splat(0);
862    for i in 0..chunks {
863        let b = i * 4;
864        let v = unsafe {
865            i64x4::from([
866                *data.add(b),
867                *data.add(b + 1),
868                *data.add(b + 2),
869                *data.add(b + 3),
870            ])
871        };
872        // wide::i64x4 lacks AddAssign; rebind the accumulator.
873        acc = acc + v;
874    }
875    let parts = acc.to_array();
876    let mut s = parts[0]
877        .wrapping_add(parts[1])
878        .wrapping_add(parts[2])
879        .wrapping_add(parts[3]);
880    for i in (chunks * 4)..len {
881        s = s.wrapping_add(unsafe { *data.add(i) });
882    }
883    s
884}
885
886/// Load four f64 values starting at `data[base]` into an `f64x4` lane.
887///
888/// # Safety
889/// `data` must point to at least `base + 4` valid `f64` values.
890#[inline]
891unsafe fn load_f64x4(data: *const f64, base: usize) -> wide::f64x4 {
892    unsafe {
893        wide::f64x4::from([
894            *data.add(base),
895            *data.add(base + 1),
896            *data.add(base + 2),
897            *data.add(base + 3),
898        ])
899    }
900}
901
902/// Detect a NaN anywhere in a f64 buffer. Uses SIMD lanes via a
903/// bitwise-equal comparison-against-self (NaN is the only value that is
904/// not equal to itself under IEEE 754).
905///
906/// # Safety
907/// `data` must point to at least `len` valid `f64` values.
908#[inline]
909unsafe fn contains_nan_f64(data: *const f64, len: usize) -> bool {
910    if len < SIMD_SUM_THRESHOLD {
911        for i in 0..len {
912            if unsafe { *data.add(i) }.is_nan() {
913                return true;
914            }
915        }
916        return false;
917    }
918    let chunks = len / 4;
919    for i in 0..chunks {
920        let v = unsafe { load_f64x4(data, i * 4) };
921        // NaN != NaN — a SIMD self-compare leaves NaN lanes as 0x0, other
922        // lanes as all-ones. `wide` doesn't expose a portable movemask, so
923        // we materialize the 4 lanes and scalar-check.
924        let arr = v.to_array();
925        if arr[0].is_nan() || arr[1].is_nan() || arr[2].is_nan() || arr[3].is_nan() {
926            return true;
927        }
928    }
929    for i in (chunks * 4)..len {
930        if unsafe { *data.add(i) }.is_nan() {
931            return true;
932        }
933    }
934    false
935}
936
937#[inline]
938unsafe fn simd_min_f64_inner(data: *const f64, len: usize) -> f64 {
939    // Hardware `min_pd` does NOT reliably propagate NaN (it returns the
940    // non-NaN operand in whichever slot based on the comparison order).
941    // Do a cheap SIMD NaN scan first — if present, short-circuit to NaN
942    // to match scalar `f64::min` semantics that our consumers expect.
943    if unsafe { contains_nan_f64(data, len) } {
944        return f64::NAN;
945    }
946    if len < SIMD_SUM_THRESHOLD {
947        let mut m = unsafe { *data };
948        for i in 1..len {
949            let v = unsafe { *data.add(i) };
950            if v < m {
951                m = v;
952            }
953        }
954        return m;
955    }
956    let chunks = len / 4;
957    let mut acc = unsafe { load_f64x4(data, 0) };
958    for i in 1..chunks {
959        let v = unsafe { load_f64x4(data, i * 4) };
960        acc = acc.fast_min(v);
961    }
962    let parts = acc.to_array();
963    let mut m = parts[0];
964    for &p in &parts[1..] {
965        if p < m {
966            m = p;
967        }
968    }
969    for i in (chunks * 4)..len {
970        let v = unsafe { *data.add(i) };
971        if v < m {
972            m = v;
973        }
974    }
975    m
976}
977
978#[inline]
979unsafe fn simd_max_f64_inner(data: *const f64, len: usize) -> f64 {
980    if unsafe { contains_nan_f64(data, len) } {
981        return f64::NAN;
982    }
983    if len < SIMD_SUM_THRESHOLD {
984        let mut m = unsafe { *data };
985        for i in 1..len {
986            let v = unsafe { *data.add(i) };
987            if v > m {
988                m = v;
989            }
990        }
991        return m;
992    }
993    let chunks = len / 4;
994    let mut acc = unsafe { load_f64x4(data, 0) };
995    for i in 1..chunks {
996        let v = unsafe { load_f64x4(data, i * 4) };
997        acc = acc.fast_max(v);
998    }
999    let parts = acc.to_array();
1000    let mut m = parts[0];
1001    for &p in &parts[1..] {
1002        if p > m {
1003            m = p;
1004        }
1005    }
1006    for i in (chunks * 4)..len {
1007        let v = unsafe { *data.add(i) };
1008        if v > m {
1009            m = v;
1010        }
1011    }
1012    m
1013}
1014
1015#[inline]
1016unsafe fn simd_sum_squares_f64_inner(data: *const f64, len: usize) -> f64 {
1017    use wide::f64x4;
1018    if len < SIMD_SUM_THRESHOLD {
1019        let mut s = 0.0_f64;
1020        for i in 0..len {
1021            let v = unsafe { *data.add(i) };
1022            s += v * v;
1023        }
1024        return s;
1025    }
1026    let chunks = len / 4;
1027    let mut acc = f64x4::splat(0.0);
1028    for i in 0..chunks {
1029        let v = unsafe { load_f64x4(data, i * 4) };
1030        acc += v * v;
1031    }
1032    let parts = acc.to_array();
1033    let mut s = parts[0] + parts[1] + parts[2] + parts[3];
1034    for i in (chunks * 4)..len {
1035        let v = unsafe { *data.add(i) };
1036        s += v * v;
1037    }
1038    s
1039}
1040
1041#[inline]
1042unsafe fn simd_scale_f64_inner(src: *const f64, dst: *mut f64, len: usize, factor: f64) {
1043    use wide::f64x4;
1044    if len < SIMD_SUM_THRESHOLD {
1045        for i in 0..len {
1046            unsafe { *dst.add(i) = *src.add(i) * factor };
1047        }
1048        return;
1049    }
1050    let chunks = len / 4;
1051    let splat = f64x4::splat(factor);
1052    for i in 0..chunks {
1053        let base = i * 4;
1054        let v = unsafe { load_f64x4(src, base) };
1055        let r = (v * splat).to_array();
1056        unsafe {
1057            *dst.add(base) = r[0];
1058            *dst.add(base + 1) = r[1];
1059            *dst.add(base + 2) = r[2];
1060            *dst.add(base + 3) = r[3];
1061        }
1062    }
1063    for i in (chunks * 4)..len {
1064        unsafe { *dst.add(i) = *src.add(i) * factor };
1065    }
1066}
1067
1068#[inline]
1069unsafe fn simd_add_scalar_f64_inner(src: *const f64, dst: *mut f64, len: usize, offset: f64) {
1070    use wide::f64x4;
1071    if len < SIMD_SUM_THRESHOLD {
1072        for i in 0..len {
1073            unsafe { *dst.add(i) = *src.add(i) + offset };
1074        }
1075        return;
1076    }
1077    let chunks = len / 4;
1078    let splat = f64x4::splat(offset);
1079    for i in 0..chunks {
1080        let base = i * 4;
1081        let v = unsafe { load_f64x4(src, base) };
1082        let r = (v + splat).to_array();
1083        unsafe {
1084            *dst.add(base) = r[0];
1085            *dst.add(base + 1) = r[1];
1086            *dst.add(base + 2) = r[2];
1087            *dst.add(base + 3) = r[3];
1088        }
1089    }
1090    for i in (chunks * 4)..len {
1091        unsafe { *dst.add(i) = *src.add(i) + offset };
1092    }
1093}
1094
1095#[inline]
1096unsafe fn simd_binary_add_f64_inner(
1097    a: *const f64,
1098    b: *const f64,
1099    dst: *mut f64,
1100    len: usize,
1101) {
1102    if len < SIMD_SUM_THRESHOLD {
1103        for i in 0..len {
1104            unsafe { *dst.add(i) = *a.add(i) + *b.add(i) };
1105        }
1106        return;
1107    }
1108    let chunks = len / 4;
1109    for i in 0..chunks {
1110        let base = i * 4;
1111        let va = unsafe { load_f64x4(a, base) };
1112        let vb = unsafe { load_f64x4(b, base) };
1113        let r = (va + vb).to_array();
1114        unsafe {
1115            *dst.add(base) = r[0];
1116            *dst.add(base + 1) = r[1];
1117            *dst.add(base + 2) = r[2];
1118            *dst.add(base + 3) = r[3];
1119        }
1120    }
1121    for i in (chunks * 4)..len {
1122        unsafe { *dst.add(i) = *a.add(i) + *b.add(i) };
1123    }
1124}
1125
1126#[inline]
1127unsafe fn simd_binary_mul_f64_inner(
1128    a: *const f64,
1129    b: *const f64,
1130    dst: *mut f64,
1131    len: usize,
1132) {
1133    if len < SIMD_SUM_THRESHOLD {
1134        for i in 0..len {
1135            unsafe { *dst.add(i) = *a.add(i) * *b.add(i) };
1136        }
1137        return;
1138    }
1139    let chunks = len / 4;
1140    for i in 0..chunks {
1141        let base = i * 4;
1142        let va = unsafe { load_f64x4(a, base) };
1143        let vb = unsafe { load_f64x4(b, base) };
1144        let r = (va * vb).to_array();
1145        unsafe {
1146            *dst.add(base) = r[0];
1147            *dst.add(base + 1) = r[1];
1148            *dst.add(base + 2) = r[2];
1149            *dst.add(base + 3) = r[3];
1150        }
1151    }
1152    for i in (chunks * 4)..len {
1153        unsafe { *dst.add(i) = *a.add(i) * *b.add(i) };
1154    }
1155}
1156
1157// ============================================================================
1158// Per-kind `len` accessors for the 3 non-f64 base kinds (i64 / i32 / bool).
1159// The `len_f64` companion is defined above (next to the f64 SIMD ops). `len`
1160// is not in the audit §1.D macro scope (new/get/set only) so these stay as
1161// hand-written entries until a follow-up extends the macro.
1162// ============================================================================
1163
1164#[unsafe(no_mangle)]
1165pub extern "C" fn jit_v2_array_len_i64(arr: *const TypedArray<i64>) -> u32 {
1166    unsafe { TypedArray::len(arr) }
1167}
1168
1169#[unsafe(no_mangle)]
1170pub extern "C" fn jit_v2_array_len_i32(arr: *const TypedArray<i32>) -> u32 {
1171    unsafe { TypedArray::len(arr) }
1172}
1173
1174#[unsafe(no_mangle)]
1175pub extern "C" fn jit_v2_array_len_bool(arr: *const TypedArray<u8>) -> u32 {
1176    unsafe { TypedArray::len(arr) }
1177}
1178
1179// ============================================================================
1180// Struct field access FFI
1181// ============================================================================
1182
1183#[unsafe(no_mangle)]
1184pub extern "C" fn jit_v2_field_load_f64(ptr: *const u8, offset: u32) -> f64 {
1185    unsafe { (ptr.add(offset as usize) as *const f64).read_unaligned() }
1186}
1187
1188#[unsafe(no_mangle)]
1189pub extern "C" fn jit_v2_field_load_i64(ptr: *const u8, offset: u32) -> i64 {
1190    unsafe { (ptr.add(offset as usize) as *const i64).read_unaligned() }
1191}
1192
1193#[unsafe(no_mangle)]
1194pub extern "C" fn jit_v2_field_load_i32(ptr: *const u8, offset: u32) -> i32 {
1195    unsafe { (ptr.add(offset as usize) as *const i32).read_unaligned() }
1196}
1197
1198#[unsafe(no_mangle)]
1199pub extern "C" fn jit_v2_field_load_ptr(ptr: *const u8, offset: u32) -> *const u8 {
1200    unsafe { (ptr.add(offset as usize) as *const *const u8).read_unaligned() }
1201}
1202
1203#[unsafe(no_mangle)]
1204pub extern "C" fn jit_v2_field_store_f64(ptr: *mut u8, offset: u32, val: f64) {
1205    unsafe {
1206        (ptr.add(offset as usize) as *mut f64).write_unaligned(val);
1207    }
1208}
1209
1210#[unsafe(no_mangle)]
1211pub extern "C" fn jit_v2_field_store_i64(ptr: *mut u8, offset: u32, val: i64) {
1212    unsafe {
1213        (ptr.add(offset as usize) as *mut i64).write_unaligned(val);
1214    }
1215}
1216
1217#[unsafe(no_mangle)]
1218pub extern "C" fn jit_v2_field_store_i32(ptr: *mut u8, offset: u32, val: i32) {
1219    unsafe {
1220        (ptr.add(offset as usize) as *mut i32).write_unaligned(val);
1221    }
1222}
1223
1224#[unsafe(no_mangle)]
1225pub extern "C" fn jit_v2_field_store_ptr(ptr: *mut u8, offset: u32, val: *const u8) {
1226    unsafe {
1227        (ptr.add(offset as usize) as *mut *const u8).write_unaligned(val);
1228    }
1229}
1230
1231// ============================================================================
1232// Refcount FFI
1233// ============================================================================
1234
1235#[unsafe(no_mangle)]
1236pub extern "C" fn jit_v2_retain(ptr: *const u8) {
1237    unsafe {
1238        let header = ptr as *const HeapHeader;
1239        (*header).retain();
1240    }
1241}
1242
1243#[unsafe(no_mangle)]
1244pub extern "C" fn jit_v2_release(ptr: *const u8) {
1245    unsafe {
1246        let header = ptr as *const HeapHeader;
1247        if (*header).release() {
1248            // Refcount reached zero — deallocate.
1249            // For now, we only deallocate the struct itself.
1250            // Future: dispatch on kind for proper cleanup of nested resources.
1251            let kind = (*header).kind();
1252            let _ = kind; // TODO: dispatch cleanup based on kind
1253            std::alloc::dealloc(
1254                ptr as *mut u8,
1255                std::alloc::Layout::from_size_align(8, 8).unwrap(), // minimum — real size TBD
1256            );
1257        }
1258    }
1259}
1260
1261// ── r5c-2-β-δ-(α): v2-raw `TypedArray<T>` retain / release ──────────────────
1262//
1263// JIT-side retain/release for a `NativeKind::Ptr(HeapKind::TypedArray)` slot.
1264// The carrier is the v2-raw `*mut TypedArray<T>` flat struct (24-byte
1265// `repr(C)`, HeapHeader at offset 0, separate element buffer). The generic
1266// `arc_retain` / `arc_release` are WRONG for this carrier — they assume a
1267// `UnifiedValue<T>` HeapHeader at offset +4 and a single-allocation layout,
1268// so `arc_release` deallocs the wrong size and leaks the element buffer
1269// (heap corruption). These two route through the same kind-blind helpers the
1270// VM uses (`retain_v2_typed_array` / `release_v2_typed_array` in
1271// `shape_value::v2::typed_array`), keeping VM and JIT on one carrier.
1272
1273/// Retain (bump refcount of) a v2-raw `*mut TypedArray<T>` carrier.
1274#[unsafe(no_mangle)]
1275pub extern "C" fn jit_v2_typed_array_retain(ptr: *const u8) {
1276    if ptr.is_null() {
1277        return;
1278    }
1279    unsafe { shape_value::v2::typed_array::retain_v2_typed_array(ptr as *mut u8) };
1280}
1281
1282/// Release one refcount share of a v2-raw `*mut TypedArray<T>` carrier;
1283/// on the last share, free via the stamped-element-type `drop_array` /
1284/// `drop_array_heap`.
1285#[unsafe(no_mangle)]
1286pub extern "C" fn jit_v2_typed_array_release(ptr: *const u8) {
1287    if ptr.is_null() {
1288        return;
1289    }
1290    unsafe { shape_value::v2::typed_array::release_v2_typed_array(ptr as *mut u8) };
1291}
1292
1293// ============================================================================
1294// Struct allocation FFI
1295// ============================================================================
1296
1297/// Allocate a v2 struct of the given total size (including header).
1298/// Initializes the HeapHeader with refcount=1 and the given kind.
1299/// Returns a pointer to the start of the struct (i.e., to the HeapHeader).
1300#[unsafe(no_mangle)]
1301pub extern "C" fn jit_v2_alloc_struct(size: u32, kind: u16) -> *mut u8 {
1302    let align = 8; // all v2 structs are 8-byte aligned
1303    let layout = std::alloc::Layout::from_size_align(size as usize, align).unwrap();
1304    let ptr = unsafe { std::alloc::alloc_zeroed(layout) };
1305    // Initialize the header
1306    unsafe {
1307        let header = ptr as *mut HeapHeader;
1308        std::ptr::write(header, HeapHeader::new(kind));
1309    }
1310    ptr
1311}
1312
1313#[cfg(test)]
1314mod tests {
1315    use super::*;
1316    use shape_value::v2::heap_header::HEAP_KIND_V2_STRUCT;
1317
1318    // ── Test-only shims for the pre-R7.2 typed push helpers ──────────────
1319    //
1320    // R7.2 consolidated the four `jit_v2_array_push_{f64,i64,i32,bool}`
1321    // entry points into a single `jit_v2_array_push(ptr, bits, elem_size)`
1322    // dispatcher. These shims keep the tests below readable while exercising
1323    // the same underlying push paths.
1324
1325    fn jit_v2_array_push_f64(arr: *mut TypedArray<f64>, val: f64) {
1326        jit_v2_array_push(arr as *mut HeapHeader, val.to_bits(), 8);
1327    }
1328
1329    fn jit_v2_array_push_i64(arr: *mut TypedArray<i64>, val: i64) {
1330        jit_v2_array_push(arr as *mut HeapHeader, val as u64, 8);
1331    }
1332
1333    fn jit_v2_array_push_i32(arr: *mut TypedArray<i32>, val: i32) {
1334        jit_v2_array_push(arr as *mut HeapHeader, (val as u32) as u64, 4);
1335    }
1336
1337    fn jit_v2_array_push_bool(arr: *mut TypedArray<u8>, val: u8) {
1338        jit_v2_array_push(arr as *mut HeapHeader, val as u64, 1);
1339    }
1340
1341    // ── Phase C.3 SIMD sum tests ─────────────────────────────────────────
1342
1343    #[test]
1344    fn test_simd_sum_f64_small_scalar_path() {
1345        // Below SIMD_SUM_THRESHOLD — exercises scalar accumulation.
1346        let arr = jit_v2_array_new_f64(8);
1347        for i in 0..8 {
1348            jit_v2_array_push_f64(arr, (i + 1) as f64); // 1..=8
1349        }
1350        let sum = jit_v2_array_sum_f64(arr);
1351        assert!((sum - 36.0).abs() < 1e-12);
1352        unsafe { TypedArray::drop_array(arr) };
1353    }
1354
1355    #[test]
1356    fn test_simd_sum_f64_large_vector_path() {
1357        // Above SIMD_SUM_THRESHOLD with non-multiple-of-4 length (exercises
1358        // both the f64x4 loop and the scalar remainder).
1359        let arr = jit_v2_array_new_f64(128);
1360        let mut expected = 0.0_f64;
1361        for i in 0..101 {
1362            let v = i as f64 * 0.5;
1363            jit_v2_array_push_f64(arr, v);
1364            expected += v;
1365        }
1366        let sum = jit_v2_array_sum_f64(arr);
1367        assert!(
1368            (sum - expected).abs() < 1e-9,
1369            "sum={} expected={}",
1370            sum,
1371            expected
1372        );
1373        unsafe { TypedArray::drop_array(arr) };
1374    }
1375
1376    #[test]
1377    fn test_simd_sum_f64_empty() {
1378        let arr = jit_v2_array_new_f64(0);
1379        let sum = jit_v2_array_sum_f64(arr);
1380        assert_eq!(sum, 0.0);
1381        unsafe { TypedArray::drop_array(arr) };
1382    }
1383
1384    #[test]
1385    fn test_simd_sum_f64_null_safe() {
1386        assert_eq!(jit_v2_array_sum_f64(std::ptr::null()), 0.0);
1387    }
1388
1389    #[test]
1390    fn test_simd_sum_i64_small_scalar_path() {
1391        let arr = jit_v2_array_new_i64(16);
1392        for i in 0..10 {
1393            jit_v2_array_push_i64(arr, (i + 1) as i64);
1394        }
1395        let sum = jit_v2_array_sum_i64(arr);
1396        assert_eq!(sum, 55);
1397        unsafe { TypedArray::drop_array(arr) };
1398    }
1399
1400    #[test]
1401    fn test_simd_sum_i64_large_vector_path() {
1402        let arr = jit_v2_array_new_i64(128);
1403        let mut expected: i64 = 0;
1404        for i in 0..103 {
1405            let v = i as i64;
1406            jit_v2_array_push_i64(arr, v);
1407            expected = expected.wrapping_add(v);
1408        }
1409        let sum = jit_v2_array_sum_i64(arr);
1410        assert_eq!(sum, expected);
1411        unsafe { TypedArray::drop_array(arr) };
1412    }
1413
1414    #[test]
1415    fn test_simd_sum_i64_wrapping_overflow() {
1416        // Two i64::MAX values should wrap without panicking. Padded to 16
1417        // elements so we go down the SIMD path that also uses wrapping adds.
1418        let arr = jit_v2_array_new_i64(16);
1419        jit_v2_array_push_i64(arr, i64::MAX);
1420        jit_v2_array_push_i64(arr, 1);
1421        for _ in 2..16 {
1422            jit_v2_array_push_i64(arr, 0);
1423        }
1424        let sum = jit_v2_array_sum_i64(arr);
1425        assert_eq!(sum, i64::MAX.wrapping_add(1));
1426        unsafe { TypedArray::drop_array(arr) };
1427    }
1428
1429    #[test]
1430    fn test_array_f64_roundtrip() {
1431        let arr = jit_v2_array_new_f64(4);
1432        jit_v2_array_push_f64(arr, 1.0);
1433        jit_v2_array_push_f64(arr, 2.5);
1434        jit_v2_array_push_f64(arr, 3.14);
1435        assert_eq!(jit_v2_array_len_f64(arr), 3);
1436        assert!((jit_v2_array_get_f64(arr, 0) - 1.0).abs() < f64::EPSILON);
1437        assert!((jit_v2_array_get_f64(arr, 1) - 2.5).abs() < f64::EPSILON);
1438        assert!((jit_v2_array_get_f64(arr, 2) - 3.14).abs() < f64::EPSILON);
1439        unsafe { TypedArray::drop_array(arr) };
1440    }
1441
1442    #[test]
1443    fn test_array_i64_roundtrip() {
1444        let arr = jit_v2_array_new_i64(4);
1445        jit_v2_array_push_i64(arr, 42);
1446        jit_v2_array_push_i64(arr, -100);
1447        assert_eq!(jit_v2_array_len_i64(arr), 2);
1448        assert_eq!(jit_v2_array_get_i64(arr, 0), 42);
1449        assert_eq!(jit_v2_array_get_i64(arr, 1), -100);
1450        unsafe { TypedArray::drop_array(arr) };
1451    }
1452
1453    #[test]
1454    fn test_array_i32_roundtrip() {
1455        let arr = jit_v2_array_new_i32(4);
1456        jit_v2_array_push_i32(arr, 7);
1457        jit_v2_array_push_i32(arr, -3);
1458        assert_eq!(jit_v2_array_len_i32(arr), 2);
1459        assert_eq!(jit_v2_array_get_i32(arr, 0), 7);
1460        assert_eq!(jit_v2_array_get_i32(arr, 1), -3);
1461        unsafe { TypedArray::drop_array(arr) };
1462    }
1463
1464    #[test]
1465    fn test_array_bool_roundtrip() {
1466        // Bool elements are stored as u8 internally (0 = false, 1 = true).
1467        let arr = jit_v2_array_new_bool(4);
1468        jit_v2_array_push_bool(arr, 1);
1469        jit_v2_array_push_bool(arr, 0);
1470        jit_v2_array_push_bool(arr, 1);
1471        assert_eq!(jit_v2_array_len_bool(arr), 3);
1472        assert_eq!(jit_v2_array_get_bool(arr, 0), 1);
1473        assert_eq!(jit_v2_array_get_bool(arr, 1), 0);
1474        assert_eq!(jit_v2_array_get_bool(arr, 2), 1);
1475        unsafe { TypedArray::drop_array(arr) };
1476    }
1477
1478    #[test]
1479    fn test_array_set_bool() {
1480        let arr = jit_v2_array_new_bool(4);
1481        jit_v2_array_push_bool(arr, 0);
1482        jit_v2_array_push_bool(arr, 0);
1483        jit_v2_array_set_bool(arr, 0, 1);
1484        assert_eq!(jit_v2_array_get_bool(arr, 0), 1);
1485        assert_eq!(jit_v2_array_get_bool(arr, 1), 0);
1486        unsafe { TypedArray::drop_array(arr) };
1487    }
1488
1489    #[test]
1490    fn test_array_set_f64() {
1491        let arr = jit_v2_array_new_f64(4);
1492        jit_v2_array_push_f64(arr, 1.0);
1493        jit_v2_array_push_f64(arr, 2.0);
1494        jit_v2_array_set_f64(arr, 0, 99.0);
1495        assert!((jit_v2_array_get_f64(arr, 0) - 99.0).abs() < f64::EPSILON);
1496        assert!((jit_v2_array_get_f64(arr, 1) - 2.0).abs() < f64::EPSILON);
1497        unsafe { TypedArray::drop_array(arr) };
1498    }
1499
1500    #[test]
1501    fn test_array_get_oob_returns_none_via_typed_array() {
1502        // Can't use #[should_panic] on extern "C" functions (UB).
1503        // Instead, test bounds via the underlying TypedArray::get which returns None.
1504        let arr = jit_v2_array_new_f64(4);
1505        jit_v2_array_push_f64(arr, 1.0);
1506        unsafe {
1507            assert_eq!(TypedArray::get(arr, 5), None);
1508            TypedArray::drop_array(arr);
1509        }
1510    }
1511
1512    #[test]
1513    fn test_field_load_store_f64() {
1514        let ptr = jit_v2_alloc_struct(24, HEAP_KIND_V2_STRUCT);
1515        jit_v2_field_store_f64(ptr, 8, 3.14);
1516        let val = jit_v2_field_load_f64(ptr, 8);
1517        assert!((val - 3.14).abs() < f64::EPSILON);
1518        unsafe { std::alloc::dealloc(ptr, std::alloc::Layout::from_size_align(24, 8).unwrap()) };
1519    }
1520
1521    #[test]
1522    fn test_field_load_store_i64() {
1523        let ptr = jit_v2_alloc_struct(24, HEAP_KIND_V2_STRUCT);
1524        jit_v2_field_store_i64(ptr, 8, -42);
1525        assert_eq!(jit_v2_field_load_i64(ptr, 8), -42);
1526        unsafe { std::alloc::dealloc(ptr, std::alloc::Layout::from_size_align(24, 8).unwrap()) };
1527    }
1528
1529    #[test]
1530    fn test_field_load_store_i32() {
1531        let ptr = jit_v2_alloc_struct(16, HEAP_KIND_V2_STRUCT);
1532        jit_v2_field_store_i32(ptr, 8, 999);
1533        assert_eq!(jit_v2_field_load_i32(ptr, 8), 999);
1534        unsafe { std::alloc::dealloc(ptr, std::alloc::Layout::from_size_align(16, 8).unwrap()) };
1535    }
1536
1537    #[test]
1538    fn test_alloc_struct_initializes_header() {
1539        let ptr = jit_v2_alloc_struct(24, HEAP_KIND_V2_STRUCT);
1540        unsafe {
1541            let header = &*(ptr as *const HeapHeader);
1542            assert_eq!(header.kind(), HEAP_KIND_V2_STRUCT);
1543            assert_eq!(header.get_refcount(), 1);
1544            std::alloc::dealloc(ptr, std::alloc::Layout::from_size_align(24, 8).unwrap());
1545        }
1546    }
1547
1548    // ── SIMD min/max/mean/sum-of-squares tests ────────────────────────────
1549
1550    fn fill_f64(arr: *mut TypedArray<f64>, vals: &[f64]) {
1551        for &v in vals {
1552            jit_v2_array_push_f64(arr, v);
1553        }
1554    }
1555
1556    #[test]
1557    fn test_simd_min_f64_small_scalar_path() {
1558        let arr = jit_v2_array_new_f64(8);
1559        fill_f64(arr, &[3.0, 1.5, 4.0, -2.0, 0.5, 7.0, -9.0, 2.0]);
1560        let m = jit_v2_array_min_f64(arr);
1561        assert_eq!(m, -9.0);
1562        unsafe { TypedArray::drop_array(arr) };
1563    }
1564
1565    #[test]
1566    fn test_simd_min_f64_large_vector_path() {
1567        let arr = jit_v2_array_new_f64(64);
1568        let mut expected = f64::INFINITY;
1569        for i in 0..33 {
1570            // non-multiple of 4 for remainder coverage
1571            let v = (17 - i) as f64 * 0.25;
1572            jit_v2_array_push_f64(arr, v);
1573            if v < expected {
1574                expected = v;
1575            }
1576        }
1577        let m = jit_v2_array_min_f64(arr);
1578        assert!((m - expected).abs() < 1e-12);
1579        unsafe { TypedArray::drop_array(arr) };
1580    }
1581
1582    #[test]
1583    fn test_simd_min_f64_empty_and_null() {
1584        let arr = jit_v2_array_new_f64(0);
1585        assert!(jit_v2_array_min_f64(arr).is_nan());
1586        unsafe { TypedArray::drop_array(arr) };
1587        assert!(jit_v2_array_min_f64(std::ptr::null()).is_nan());
1588    }
1589
1590    #[test]
1591    fn test_simd_min_f64_single_element() {
1592        let arr = jit_v2_array_new_f64(1);
1593        jit_v2_array_push_f64(arr, 42.5);
1594        assert_eq!(jit_v2_array_min_f64(arr), 42.5);
1595        unsafe { TypedArray::drop_array(arr) };
1596    }
1597
1598    #[test]
1599    fn test_simd_min_f64_nan_propagates() {
1600        // Scalar path
1601        let arr = jit_v2_array_new_f64(4);
1602        fill_f64(arr, &[1.0, 2.0, f64::NAN, 4.0]);
1603        assert!(jit_v2_array_min_f64(arr).is_nan());
1604        unsafe { TypedArray::drop_array(arr) };
1605        // SIMD path (>= 16 elements)
1606        let arr = jit_v2_array_new_f64(20);
1607        let mut v = vec![1.0_f64; 20];
1608        v[7] = f64::NAN;
1609        fill_f64(arr, &v);
1610        assert!(jit_v2_array_min_f64(arr).is_nan());
1611        unsafe { TypedArray::drop_array(arr) };
1612    }
1613
1614    #[test]
1615    fn test_simd_max_f64_small_scalar_path() {
1616        let arr = jit_v2_array_new_f64(8);
1617        fill_f64(arr, &[3.0, 1.5, 4.0, -2.0, 0.5, 7.0, -9.0, 2.0]);
1618        let m = jit_v2_array_max_f64(arr);
1619        assert_eq!(m, 7.0);
1620        unsafe { TypedArray::drop_array(arr) };
1621    }
1622
1623    #[test]
1624    fn test_simd_max_f64_large_vector_path() {
1625        let arr = jit_v2_array_new_f64(64);
1626        let mut expected = f64::NEG_INFINITY;
1627        for i in 0..37 {
1628            let v = (i as f64 * 1.3) - 5.0;
1629            jit_v2_array_push_f64(arr, v);
1630            if v > expected {
1631                expected = v;
1632            }
1633        }
1634        let m = jit_v2_array_max_f64(arr);
1635        assert!((m - expected).abs() < 1e-12);
1636        unsafe { TypedArray::drop_array(arr) };
1637    }
1638
1639    #[test]
1640    fn test_simd_max_f64_nan_propagates() {
1641        let arr = jit_v2_array_new_f64(20);
1642        let mut v = vec![1.0_f64; 20];
1643        v[3] = f64::NAN;
1644        fill_f64(arr, &v);
1645        assert!(jit_v2_array_max_f64(arr).is_nan());
1646        unsafe { TypedArray::drop_array(arr) };
1647    }
1648
1649    #[test]
1650    fn test_simd_mean_f64_small() {
1651        let arr = jit_v2_array_new_f64(4);
1652        fill_f64(arr, &[1.0, 2.0, 3.0, 4.0]);
1653        let m = jit_v2_array_mean_f64(arr);
1654        assert!((m - 2.5).abs() < 1e-12);
1655        unsafe { TypedArray::drop_array(arr) };
1656    }
1657
1658    #[test]
1659    fn test_simd_mean_f64_large() {
1660        let arr = jit_v2_array_new_f64(64);
1661        let mut total = 0.0_f64;
1662        for i in 0..50 {
1663            let v = i as f64 + 0.5;
1664            jit_v2_array_push_f64(arr, v);
1665            total += v;
1666        }
1667        let expected = total / 50.0;
1668        let m = jit_v2_array_mean_f64(arr);
1669        assert!((m - expected).abs() < 1e-9, "mean={} expected={}", m, expected);
1670        unsafe { TypedArray::drop_array(arr) };
1671    }
1672
1673    #[test]
1674    fn test_simd_mean_f64_empty_is_nan() {
1675        let arr = jit_v2_array_new_f64(0);
1676        assert!(jit_v2_array_mean_f64(arr).is_nan());
1677        unsafe { TypedArray::drop_array(arr) };
1678        assert!(jit_v2_array_mean_f64(std::ptr::null()).is_nan());
1679    }
1680
1681    #[test]
1682    fn test_simd_sum_squares_f64_small() {
1683        let arr = jit_v2_array_new_f64(4);
1684        fill_f64(arr, &[1.0, 2.0, 3.0, 4.0]);
1685        // 1 + 4 + 9 + 16 = 30
1686        let s = jit_v2_array_sum_squares_f64(arr);
1687        assert!((s - 30.0).abs() < 1e-12);
1688        unsafe { TypedArray::drop_array(arr) };
1689    }
1690
1691    #[test]
1692    fn test_simd_sum_squares_f64_large() {
1693        let arr = jit_v2_array_new_f64(64);
1694        let mut expected = 0.0_f64;
1695        for i in 0..50 {
1696            let v = (i as f64 - 25.0) * 0.5;
1697            jit_v2_array_push_f64(arr, v);
1698            expected += v * v;
1699        }
1700        let s = jit_v2_array_sum_squares_f64(arr);
1701        assert!((s - expected).abs() < 1e-8);
1702        unsafe { TypedArray::drop_array(arr) };
1703    }
1704
1705    #[test]
1706    fn test_simd_sum_squares_f64_empty() {
1707        let arr = jit_v2_array_new_f64(0);
1708        assert_eq!(jit_v2_array_sum_squares_f64(arr), 0.0);
1709        unsafe { TypedArray::drop_array(arr) };
1710        assert_eq!(jit_v2_array_sum_squares_f64(std::ptr::null()), 0.0);
1711    }
1712
1713    // ── SIMD allocating transforms ────────────────────────────────────────
1714
1715    fn collect_f64(arr: *const TypedArray<f64>) -> Vec<f64> {
1716        let len = unsafe { (*arr).len } as usize;
1717        let mut out = Vec::with_capacity(len);
1718        for i in 0..len {
1719            out.push(unsafe { TypedArray::<f64>::get_unchecked(arr, i as u32) });
1720        }
1721        out
1722    }
1723
1724    #[test]
1725    fn test_simd_scale_f64_small() {
1726        let a = jit_v2_array_new_f64(4);
1727        fill_f64(a, &[1.0, 2.0, 3.0, 4.0]);
1728        let out = jit_v2_array_scale_f64(a, 2.5);
1729        assert_eq!(collect_f64(out), vec![2.5, 5.0, 7.5, 10.0]);
1730        unsafe {
1731            TypedArray::drop_array(a);
1732            TypedArray::drop_array(out);
1733        }
1734    }
1735
1736    #[test]
1737    fn test_simd_scale_f64_large() {
1738        let a = jit_v2_array_new_f64(32);
1739        for i in 0..20 {
1740            jit_v2_array_push_f64(a, i as f64);
1741        }
1742        let out = jit_v2_array_scale_f64(a, -0.5);
1743        let got = collect_f64(out);
1744        for i in 0..20 {
1745            assert!((got[i] - (i as f64 * -0.5)).abs() < 1e-12);
1746        }
1747        unsafe {
1748            TypedArray::drop_array(a);
1749            TypedArray::drop_array(out);
1750        }
1751    }
1752
1753    #[test]
1754    fn test_simd_scale_f64_empty() {
1755        let a = jit_v2_array_new_f64(0);
1756        let out = jit_v2_array_scale_f64(a, 3.0);
1757        assert_eq!(unsafe { (*out).len }, 0);
1758        unsafe {
1759            TypedArray::drop_array(a);
1760            TypedArray::drop_array(out);
1761        }
1762    }
1763
1764    #[test]
1765    fn test_simd_add_scalar_f64_small() {
1766        let a = jit_v2_array_new_f64(3);
1767        fill_f64(a, &[1.0, 2.0, 3.0]);
1768        let out = jit_v2_array_add_scalar_f64(a, 10.0);
1769        assert_eq!(collect_f64(out), vec![11.0, 12.0, 13.0]);
1770        unsafe {
1771            TypedArray::drop_array(a);
1772            TypedArray::drop_array(out);
1773        }
1774    }
1775
1776    #[test]
1777    fn test_simd_add_scalar_f64_large() {
1778        let a = jit_v2_array_new_f64(32);
1779        for i in 0..25 {
1780            jit_v2_array_push_f64(a, i as f64);
1781        }
1782        let out = jit_v2_array_add_scalar_f64(a, 100.0);
1783        let got = collect_f64(out);
1784        for i in 0..25 {
1785            assert!((got[i] - (i as f64 + 100.0)).abs() < 1e-12);
1786        }
1787        unsafe {
1788            TypedArray::drop_array(a);
1789            TypedArray::drop_array(out);
1790        }
1791    }
1792
1793    #[test]
1794    fn test_simd_add_f64_small() {
1795        let a = jit_v2_array_new_f64(4);
1796        let b = jit_v2_array_new_f64(4);
1797        fill_f64(a, &[1.0, 2.0, 3.0, 4.0]);
1798        fill_f64(b, &[10.0, 20.0, 30.0, 40.0]);
1799        let out = jit_v2_array_add_f64(a, b);
1800        assert_eq!(collect_f64(out), vec![11.0, 22.0, 33.0, 44.0]);
1801        unsafe {
1802            TypedArray::drop_array(a);
1803            TypedArray::drop_array(b);
1804            TypedArray::drop_array(out);
1805        }
1806    }
1807
1808    #[test]
1809    fn test_simd_add_f64_large() {
1810        let a = jit_v2_array_new_f64(32);
1811        let b = jit_v2_array_new_f64(32);
1812        for i in 0..23 {
1813            jit_v2_array_push_f64(a, i as f64);
1814            jit_v2_array_push_f64(b, (i * 2) as f64);
1815        }
1816        let out = jit_v2_array_add_f64(a, b);
1817        let got = collect_f64(out);
1818        for i in 0..23 {
1819            assert!((got[i] - (i as f64 + (i * 2) as f64)).abs() < 1e-12);
1820        }
1821        unsafe {
1822            TypedArray::drop_array(a);
1823            TypedArray::drop_array(b);
1824            TypedArray::drop_array(out);
1825        }
1826    }
1827
1828    #[test]
1829    fn test_simd_mul_f64_small() {
1830        let a = jit_v2_array_new_f64(4);
1831        let b = jit_v2_array_new_f64(4);
1832        fill_f64(a, &[1.0, 2.0, 3.0, 4.0]);
1833        fill_f64(b, &[10.0, 20.0, 30.0, 40.0]);
1834        let out = jit_v2_array_mul_f64(a, b);
1835        assert_eq!(collect_f64(out), vec![10.0, 40.0, 90.0, 160.0]);
1836        unsafe {
1837            TypedArray::drop_array(a);
1838            TypedArray::drop_array(b);
1839            TypedArray::drop_array(out);
1840        }
1841    }
1842
1843    #[test]
1844    fn test_simd_mul_f64_large() {
1845        let a = jit_v2_array_new_f64(32);
1846        let b = jit_v2_array_new_f64(32);
1847        for i in 0..20 {
1848            jit_v2_array_push_f64(a, (i as f64 + 1.0) * 0.5);
1849            jit_v2_array_push_f64(b, (i as f64 + 1.0) * 2.0);
1850        }
1851        let out = jit_v2_array_mul_f64(a, b);
1852        let got = collect_f64(out);
1853        for i in 0..20 {
1854            let expected = ((i as f64 + 1.0) * 0.5) * ((i as f64 + 1.0) * 2.0);
1855            assert!((got[i] - expected).abs() < 1e-12);
1856        }
1857        unsafe {
1858            TypedArray::drop_array(a);
1859            TypedArray::drop_array(b);
1860            TypedArray::drop_array(out);
1861        }
1862    }
1863
1864    #[test]
1865    fn test_simd_add_f64_empty() {
1866        let a = jit_v2_array_new_f64(0);
1867        let b = jit_v2_array_new_f64(0);
1868        let out = jit_v2_array_add_f64(a, b);
1869        assert_eq!(unsafe { (*out).len }, 0);
1870        unsafe {
1871            TypedArray::drop_array(a);
1872            TypedArray::drop_array(b);
1873            TypedArray::drop_array(out);
1874        }
1875    }
1876
1877    #[test]
1878    fn test_simd_add_f64_null_inputs() {
1879        assert!(jit_v2_array_add_f64(std::ptr::null(), std::ptr::null()).is_null());
1880    }
1881
1882    #[test]
1883    fn test_retain_increments_refcount() {
1884        let ptr = jit_v2_alloc_struct(24, HEAP_KIND_V2_STRUCT);
1885        unsafe {
1886            let header = &*(ptr as *const HeapHeader);
1887            assert_eq!(header.get_refcount(), 1);
1888            jit_v2_retain(ptr);
1889            assert_eq!(header.get_refcount(), 2);
1890            jit_v2_retain(ptr);
1891            assert_eq!(header.get_refcount(), 3);
1892            // Clean up manually (don't use jit_v2_release which would dealloc wrong size)
1893            std::alloc::dealloc(ptr, std::alloc::Layout::from_size_align(24, 8).unwrap());
1894        }
1895    }
1896}