Skip to main content

shape_value/v2/
closure_raw.rs

1//! Raw `TypedClosureHeader` allocation + accessor helpers.
2//!
3//! This module is the VM-side counterpart to the JIT's
4//! [`crate::v2::closure_layout`] layout definitions. It provides a stable
5//! C-ABI-compatible way to allocate, retain, release, read, and write
6//! `TypedClosureHeader` blocks without going through `HeapValue::Closure`.
7//!
8//! # Closure-spec §13 H3.B.1
9//!
10//! The H3.B migration replaces `HeapValue::Closure { function_id, upvalues }`
11//! (an `Arc<HeapValue>` carrying a `Vec<Upvalue>`) with a raw
12//! `*const TypedClosureHeader` block matching the JIT's Phase H1 memory
13//! layout. This module is the shared infrastructure both the VM's
14//! `op_make_closure_heap` and the JIT's `emit_heap_closure` converge on.
15//!
16//! H3.B.1 introduces this module and its helpers; H3.B.2 wires them into
17//! the 20+ `HeapValue::Closure` consumer sites.
18//!
19//! # Memory layout (same as `closure_layout::TypedClosureHeader`)
20//!
21//! ```text
22//!   Offset  Size  Field
23//!   ------  ----  -----
24//!     0       8   HeapHeader (refcount @ 0, kind @ 4, flags @ 6, _pad @ 7)
25//!     8       4   function_id (u32)
26//!    12       4   type_id (u32, ClosureTypeId.0)
27//!    16       N   captures[] (C-laid-out per ClosureLayout)
28//! ```
29//!
30//! Every capture slot is 8-byte wide in practice (the layout rounds up to
31//! 8-byte alignment), but the **typed width** at the slot is dictated by
32//! the `FieldKind`: `F64`/`I64`/`U64` use 8 bytes; `I32`/`U32` use 4;
33//! `I16`/`U16` use 2; `I8`/`U8`/`Bool` use 1; `Ptr` uses 8 and participates
34//! in the `heap_capture_mask` retain/release cycle.
35
36use super::closure_layout::{ClosureLayout, SHARED_CELL_VALUE_OFFSET, SharedCell, TypedClosureHeader};
37use super::heap_header::{HEAP_KIND_V2_CLOSURE, HeapHeader};
38use super::struct_layout::FieldKind;
39use crate::kinded_slot::KindedSlot;
40use crate::native_kind::NativeKind;
41use crate::slot::ValueSlot;
42use std::sync::Arc;
43
44/// Owning handle for a raw `TypedClosureHeader` block paired with its layout.
45///
46/// # Closure spec §14.6 (H6.5)
47///
48/// Wraps a `*const TypedClosureHeader` returned by
49/// [`alloc_typed_closure`] alongside the `Arc<ClosureLayout>` needed to
50/// decode/release its captures. `Clone` bumps the block's refcount via
51/// [`retain_typed_closure`]; `Drop` decrements via
52/// [`release_typed_closure`] so ownership mirrors the `Arc<HeapValue>`
53/// convention used by every other heap-backed value.
54///
55/// The raw pointer is stashed as `*const u8` internally (erased) because
56/// `TypedClosureHeader` is `!Send + !Sync`; the owner's manual
57/// `unsafe impl Send + Sync` is justified by the fact that the block
58/// itself is immutable (refcount aside) and the layout is already `Send +
59/// Sync` via `Arc`.
60///
61/// # Safety invariant
62///
63/// For every live `OwnedClosureBlock`, `ptr` was allocated by
64/// [`alloc_typed_closure`] with the exact `layout` carried in this owner,
65/// and the block is refcount-owned by this instance.
66pub struct OwnedClosureBlock {
67    /// Raw pointer to the block. Erased to `*const u8` so the outer type
68    /// can implement `Send + Sync` without leaking `TypedClosureHeader`'s
69    /// raw-pointer auto-trait status.
70    ptr: *const u8,
71    /// Program-lifetime layout reference. Shared with the JIT's
72    /// `closure_function_layouts` side-table so cloning is cheap.
73    layout: Arc<ClosureLayout>,
74}
75
76// SAFETY: The raw pointer is only dereferenced via the `unsafe` helpers
77// in this module, which uphold their own aliasing / lifetime invariants.
78// The block's only mutable state is the `HeapHeader::refcount` atomic,
79// which is already thread-safe. Every other byte is immutable for the
80// lifetime of the `OwnedClosureBlock`.
81unsafe impl Send for OwnedClosureBlock {}
82// SAFETY: Same justification as Send — the interior is atomic-protected
83// or immutable, matching the `Arc<HeapValue>` convention.
84unsafe impl Sync for OwnedClosureBlock {}
85
86impl OwnedClosureBlock {
87    /// Construct an `OwnedClosureBlock` from a freshly-allocated raw
88    /// pointer. The caller transfers exactly one refcount share.
89    ///
90    /// # Safety
91    ///
92    /// - `ptr` must have been returned by [`alloc_typed_closure`] with the
93    ///   exact `layout` passed in here.
94    /// - The caller must not call [`release_typed_closure`] on `ptr`
95    ///   independently — `Drop` takes over that responsibility.
96    #[inline]
97    pub unsafe fn from_raw(ptr: *const u8, layout: Arc<ClosureLayout>) -> Self {
98        Self { ptr, layout }
99    }
100
101    /// Borrow the underlying raw pointer. The returned pointer is live for
102    /// at least as long as `self`.
103    #[inline]
104    pub fn as_ptr(&self) -> *const u8 {
105        self.ptr
106    }
107
108    /// Borrow the underlying raw pointer typed as `TypedClosureHeader`.
109    #[inline]
110    pub fn as_header_ptr(&self) -> *const TypedClosureHeader {
111        self.ptr as *const TypedClosureHeader
112    }
113
114    /// Borrow the layout that describes this block's captures. Shared with
115    /// the program's `closure_function_layouts` side-table; clones are
116    /// cheap Arc bumps.
117    #[inline]
118    pub fn layout(&self) -> &Arc<ClosureLayout> {
119        &self.layout
120    }
121
122    /// Read capture `idx`'s raw 8-byte payload paired with its
123    /// `NativeKind` from the layout's per-capture kind track (ADR-006
124    /// §2.7.8 / Q10).
125    ///
126    /// This is the cell-bound mirror of the §2.7.7 stack-side
127    /// `read_owned_kinded` accessor: returns `(bits, kind)` lockstep so
128    /// the caller can route through `clone_with_kind` /
129    /// `drop_with_kind` (the canonical `KindedSlot` dispatch) without
130    /// reconstructing the kind from the slot bits or probing a tag.
131    ///
132    /// For `Immutable` captures the returned `bits` are the raw payload
133    /// bit pattern (e.g. `f64::to_bits(v)`, `Arc::into_raw::<T>` raw
134    /// pointer) and `kind` classifies it directly.
135    ///
136    /// For `OwnedMutable` and `Shared` captures the returned `bits` are
137    /// the raw cell pointer (`*mut T` from `Box::into_raw` or `*const
138    /// SharedCell` from `Arc::into_raw`); `kind` classifies the cell's
139    /// **interior** payload — the same shape `capture_inner_kind`
140    /// resolves to at the `FieldKind` level, but lifted to `NativeKind`
141    /// so heap-bearing interior payloads dispatch through the same
142    /// table the stack-tier uses. Wave-β `B6-variables-loadptr` consumes
143    /// this accessor to migrate the `Load*Ptr` / `Store*Ptr` handlers off
144    /// `NotImplemented(SURFACE)`.
145    ///
146    /// # Safety
147    ///
148    /// The block's captures area for `idx` must have been initialised
149    /// (zero-initialised by `alloc_typed_closure` and then written by
150    /// the make-closure init stage). The 8-byte read is always
151    /// in-bounds because the layout rounds total size up to 8-byte
152    /// alignment and `idx < layout.capture_count()`.
153    ///
154    /// # Panics
155    ///
156    /// Panics if `idx >= self.layout.capture_count()`.
157    #[inline]
158    pub unsafe fn read_capture_kinded(&self, idx: usize) -> (u64, crate::native_kind::NativeKind) {
159        assert!(
160            idx < self.layout.capture_count(),
161            "OwnedClosureBlock::read_capture_kinded: idx {} out of range (capture_count = {})",
162            idx,
163            self.layout.capture_count()
164        );
165        let off = self.layout.heap_capture_offset(idx);
166        // SAFETY: caller upholds the construction-side init contract; the
167        // 8-byte read at `heap_capture_offset(idx)` is in-bounds per the
168        // layout's geometry (every capture slot is at least 8 bytes wide
169        // — narrower kinds zero-extend in the `read_capture_as_value_bits`
170        // path; this raw read sees the same on-block bytes the JIT and
171        // VM consumers see).
172        let bits = unsafe { std::ptr::read(self.ptr.add(off) as *const u64) };
173        let kind = self.layout.capture_native_kind(idx);
174        (bits, kind)
175    }
176}
177
178impl Clone for OwnedClosureBlock {
179    /// Bumps the block's refcount and the layout Arc's refcount.
180    #[inline]
181    fn clone(&self) -> Self {
182        // SAFETY: `self.ptr` was validated at construction; the live
183        // invariant is preserved by the outer type.
184        unsafe {
185            retain_typed_closure(self.ptr);
186        }
187        Self {
188            ptr: self.ptr,
189            layout: Arc::clone(&self.layout),
190        }
191    }
192}
193
194impl Drop for OwnedClosureBlock {
195    /// Releases the block's refcount share. If this was the last share
196    /// the block is walked (`heap_capture_mask` bits drop their shares)
197    /// and deallocated. The layout Arc is decremented by the default
198    /// field-drop below.
199    #[inline]
200    fn drop(&mut self) {
201        // SAFETY: construction invariant guarantees `ptr` was allocated
202        // by `alloc_typed_closure` with `self.layout`; double-frees are
203        // prevented because there is exactly one `OwnedClosureBlock`
204        // owning this share.
205        unsafe {
206            release_typed_closure(self.ptr as *mut u8, &self.layout);
207        }
208    }
209}
210
211impl std::fmt::Debug for OwnedClosureBlock {
212    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213        // SAFETY: `self.ptr` is live per the construction invariant; the
214        // reads here are in-bounds for a live block.
215        let fid = unsafe { typed_closure_function_id(self.ptr) };
216        let tid = unsafe { typed_closure_type_id(self.ptr) };
217        f.debug_struct("OwnedClosureBlock")
218            .field("fn_id", &fid)
219            .field("type_id", &tid)
220            .field("captures", &self.layout.capture_count())
221            .finish()
222    }
223}
224
225/// Allocate a zero-initialised `TypedClosureHeader` block matching the given
226/// layout. The `HeapHeader` is written with `refcount = 1`, `kind =
227/// HEAP_KIND_V2_CLOSURE`, `flags = 0`. The `function_id` and `type_id`
228/// fields are written from the arguments. The captures area is zero-filled
229/// — callers are responsible for writing each capture at its typed offset
230/// via [`write_capture_raw_u64`] before handing the pointer out.
231///
232/// # Safety
233///
234/// Returns a freshly-allocated block with `refcount = 1`. The caller takes
235/// ownership of that single refcount share; dropping it requires a matching
236/// [`release_typed_closure`] call. Double-free, use-after-free, and
237/// mismatched layout are the usual raw-pointer hazards.
238///
239/// # Panics
240///
241/// Panics if `std::alloc::Layout::from_size_align` rejects the computed
242/// size (i.e. never, in practice: `total_heap_size()` is always ≥ 16 and
243/// `from_size_align` with align = 8 is valid for all `size ≤ isize::MAX`).
244#[inline]
245pub unsafe fn alloc_typed_closure(
246    function_id: u16,
247    type_id: u32,
248    layout: &ClosureLayout,
249) -> *mut u8 {
250    let size = layout.total_heap_size();
251    let alloc_layout = std::alloc::Layout::from_size_align(size, 8)
252        .expect("TypedClosureHeader size/align must be valid (size ≥ 16, align = 8)");
253    // SAFETY: `Layout::from_size_align` returned Ok above, so the call is
254    // well-formed. `alloc_zeroed` is the JIT-shim-compatible allocator
255    // (matches `jit_v2_alloc_struct`'s allocator choice) so VM-allocated
256    // closures can be freed by JIT-generated release glue and vice versa.
257    let ptr = unsafe { std::alloc::alloc_zeroed(alloc_layout) };
258    if ptr.is_null() {
259        std::alloc::handle_alloc_error(alloc_layout);
260    }
261    // SAFETY: `ptr` is a fresh allocation of at least 16 bytes; writing the
262    // 16-byte `TypedClosureHeader` prefix (HeapHeader + function_id +
263    // type_id) is in-bounds.
264    unsafe {
265        std::ptr::write(
266            ptr as *mut HeapHeader,
267            HeapHeader::new(HEAP_KIND_V2_CLOSURE),
268        );
269        let hdr = ptr as *mut TypedClosureHeader;
270        (*hdr).function_id = function_id as u32;
271        (*hdr).type_id = type_id;
272    }
273    ptr
274}
275
276/// Read the `function_id` from a `TypedClosureHeader` block.
277///
278/// # Safety
279///
280/// `ptr` must point to a live `TypedClosureHeader` block with
281/// `HEAP_KIND_V2_CLOSURE`.
282#[inline]
283pub unsafe fn typed_closure_function_id(ptr: *const u8) -> u16 {
284    // SAFETY: caller upholds that `ptr` is a live TypedClosureHeader block.
285    unsafe { (*(ptr as *const TypedClosureHeader)).function_id as u16 }
286}
287
288/// Read the `type_id` from a `TypedClosureHeader` block.
289///
290/// # Safety
291///
292/// `ptr` must point to a live `TypedClosureHeader` block with
293/// `HEAP_KIND_V2_CLOSURE`.
294#[inline]
295pub unsafe fn typed_closure_type_id(ptr: *const u8) -> u32 {
296    // SAFETY: caller upholds that `ptr` is a live TypedClosureHeader block.
297    unsafe { (*(ptr as *const TypedClosureHeader)).type_id }
298}
299
300/// Read the `HeapHeader` kind tag for a `TypedClosureHeader` block.
301///
302/// Useful for cross-variant dispatch where the caller has only a generic
303/// heap pointer and needs to check whether it is a closure block.
304///
305/// # Safety
306///
307/// `ptr` must point to a live heap block whose first 8 bytes are a valid
308/// `HeapHeader`.
309#[inline]
310pub unsafe fn typed_closure_kind(ptr: *const u8) -> u16 {
311    // SAFETY: caller upholds that `ptr` points to a live `HeapHeader`.
312    unsafe { (*(ptr as *const HeapHeader)).kind }
313}
314
315/// Retain (bump refcount) on a `TypedClosureHeader` block.
316///
317/// Uses relaxed ordering — matches `HeapHeader::retain`.
318///
319/// # Safety
320///
321/// `ptr` must point to a live `TypedClosureHeader` block.
322#[inline]
323pub unsafe fn retain_typed_closure(ptr: *const u8) {
324    // SAFETY: caller upholds that `ptr` is a live TypedClosureHeader block.
325    unsafe { (*(ptr as *const HeapHeader)).retain() };
326}
327
328/// Release one refcount share of a `TypedClosureHeader` block. If the
329/// refcount reaches zero, this function walks all three per-capture masks
330/// to release each mutable-cell and heap-typed capture, then frees the
331/// block itself. The three masks are:
332///
333/// - `heap_capture_mask` — bit `i` set means capture `i` is an immutable
334///   Ptr holding one `Arc<T>` strong-count share for the `T` matching the
335///   layout's `capture_native_kinds[i]` (per ADR-006 §2.7.8 / Q10).
336///   Released via `drop_with_kind(bits, kind)` — the canonical
337///   `KindedSlot::Drop` table — replacing the deleted `Arc<HeapValue>`
338///   blanket decrement.
339/// - `owned_mutable_capture_mask` — bit `i` set means capture `i` is
340///   `CaptureKind::OwnedMutable`; the slot holds a typed `*mut T` from
341///   `Box::into_raw`. Released via `drop_owned_mutable_capture`, which
342///   reconstructs the matching `Box<T>` per `capture_inner_kind(i)` and
343///   reclaims it; for `Ptr` interior kind the heap-refcount share encoded
344///   in the cell's payload is released first via `drop_with_kind`.
345/// - `shared_capture_mask` — bit `i` set means capture `i` is
346///   `CaptureKind::Shared`; the slot holds `*const SharedCell` from
347///   `Arc::into_raw`. Released via `drop_shared_capture`, which retires
348///   any heap-refcount share carried by the cell's payload (via
349///   `drop_with_kind`) and then `Arc::from_raw`s the cell to release
350///   the strong-count share.
351///
352/// The three masks are mutually exclusive per index — `ClosureLayout`'s
353/// constructor enforces this — so no slot is released twice.
354///
355/// # Safety
356///
357/// - `ptr` must point to a live `TypedClosureHeader` block whose layout
358///   matches the `layout` argument.
359/// - After this call returns, `ptr` must not be dereferenced — the block
360///   may have been deallocated.
361/// - Each heap-typed capture (bit set in `layout.heap_capture_mask`) must
362///   contain a valid raw `ValueWord` bit pattern for which `drop_raw_bits`
363///   semantics apply (i.e. either a NaN-boxed Arc<HeapValue> or an owned
364///   heap pointer; inline values are a no-op on release).
365/// - Each `OwnedMutable` capture's slot must contain a non-null pointer
366///   that was produced by `Box::into_raw(Box::new(v))` for some
367///   `ValueWord` `v` and has not been reclaimed yet.
368/// - Each `Shared` capture's slot must contain a non-null pointer that
369///   was produced by `Arc::into_raw(Arc::new(Mutex::new(v)))` for some
370///   `ValueWord` `v` and represents one live strong-count share.
371/// - If the caller has already transferred the heap-typed capture shares
372///   elsewhere (for instance, the JIT finalizer moves them into
373///   `Upvalue`s) the caller MUST use [`dealloc_typed_closure_no_drop`]
374///   instead to avoid a double-decrement.
375#[inline]
376pub unsafe fn release_typed_closure(ptr: *mut u8, layout: &ClosureLayout) {
377    use crate::v2::closure_layout::CaptureKind;
378
379    // SAFETY: caller upholds that `ptr` is a live block. Reading the
380    // HeapHeader and calling `release` is always safe on such a block.
381    let reached_zero = unsafe { (*(ptr as *mut HeapHeader)).release() };
382    if !reached_zero {
383        return;
384    }
385
386    // Refcount hit zero — walk each capture and dispatch on its
387    // `CaptureKind`. The three branches are mutually exclusive per
388    // capture index by construction in `ClosureLayout::from_capture_types`,
389    // so each slot is released exactly once.
390    for i in 0..layout.capture_count() {
391        match layout.capture_storage_kind(i) {
392            CaptureKind::Immutable => {
393                // Immutable captures: only Ptr slots own a refcount share
394                // (tracked by `heap_capture_mask`). Non-Ptr immutable
395                // slots are pure value carriers — releasing them is a
396                // no-op.
397                if layout.is_heap_capture(i) {
398                    let off = layout.heap_capture_offset(i);
399                    // SAFETY: heap_capture_mask bits are only set for
400                    // Ptr-shaped 8-byte slots; the read is in-bounds.
401                    let bits = unsafe { std::ptr::read(ptr.add(off) as *const u64) };
402                    // ADR-006 §2.7.8 / Q10: per-capture kind-aware drop.
403                    // The slot's `NativeKind` lives in the layout's
404                    // `capture_native_kinds[i]` companion track (set at
405                    // construction per §2.7.8); routing through
406                    // `drop_with_kind(bits, kind)` retires the matching
407                    // `Arc<T>` strong-count share via the canonical
408                    // `KindedSlot::Drop` table. This replaces the
409                    // forbidden `Arc<HeapValue>` blanket decrement
410                    // (the deleted `release_raw_heap_share` shape) and
411                    // the forbidden `vw_drop(bits)` (§2.7.7 #8).
412                    let kind = layout.capture_native_kind(i);
413                    // SAFETY: `is_heap_capture(i)` confirms FieldKind::Ptr;
414                    // the slot bits are one `Arc<T>` strong-count share
415                    // for the `T` corresponding to `kind` (per the
416                    // construction-side contract on `write_capture_typed`
417                    // / `make_closure` initialisers).
418                    unsafe { drop_with_kind(bits, kind) };
419                }
420            }
421            CaptureKind::OwnedMutable => {
422                // SAFETY: OwnedMutable slots hold typed `*mut T` from
423                // `alloc_owned_mutable_<kind>`. `drop_owned_mutable_capture`
424                // dispatches on `capture_inner_kind(i)` to reconstruct the
425                // matching `Box<T>` and reclaim it; for `Ptr` interior
426                // kind it releases the heap-refcount share encoded in
427                // the cell's payload first.
428                unsafe { drop_owned_mutable_capture(layout, ptr, i) };
429            }
430            CaptureKind::Shared => {
431                // SAFETY: Shared slots hold `*const SharedCell` from
432                // `Arc::into_raw`. `drop_shared_capture` releases any
433                // heap-refcount share encoded in the cell's payload (for
434                // Ptr interior kinds), then reclaims the Arc strong-count
435                // share — freeing the cell when this was the last share.
436                unsafe { drop_shared_capture(layout, ptr, i) };
437            }
438        }
439    }
440
441    // SAFETY: `ptr` was allocated with `alloc_zeroed` using the matching
442    // size/align layout. This path is fast-moved into
443    // `dealloc_typed_closure_no_drop` for deallocation.
444    unsafe { dealloc_typed_closure_no_drop(ptr, layout) };
445}
446
447/// Deallocate a `TypedClosureHeader` block **without** walking the
448/// heap-capture mask. The caller is responsible for having already
449/// consumed or released each heap-typed capture's refcount share.
450///
451/// This is the right entry point when the caller has physically moved
452/// capture shares out of the block (for instance the JIT's
453/// `jit_finalize_heap_closure`, which transfers heap-typed captures into
454/// `Upvalue`s). Calling [`release_typed_closure`] in that situation would
455/// double-release each capture.
456///
457/// # Safety
458///
459/// - `ptr` must point to a block originally allocated by
460///   [`alloc_typed_closure`] (or `jit_v2_alloc_struct` with the same
461///   size/align contract — both use `std::alloc::alloc_zeroed` with
462///   `Layout::from_size_align(layout.total_heap_size(), 8)`).
463/// - The caller must have already dealt with every heap-typed capture's
464///   refcount share — this function does NOT release them.
465/// - After this call returns, `ptr` must not be dereferenced.
466#[inline]
467pub unsafe fn dealloc_typed_closure_no_drop(ptr: *mut u8, layout: &ClosureLayout) {
468    let size = layout.total_heap_size();
469    let alloc_layout = std::alloc::Layout::from_size_align(size, 8)
470        .expect("TypedClosureHeader size/align must be valid");
471    // SAFETY: caller upholds that `ptr` was allocated with `alloc_zeroed`
472    // using the matching size/align layout.
473    unsafe { std::alloc::dealloc(ptr, alloc_layout) };
474}
475
476// ---------------------------------------------------------------------------
477// Per-FieldKind shared-capture payload helpers (Wave B / phase-3c-closure-y1).
478//
479// A `CaptureKind::Shared` capture stores `*const SharedCell` in its closure
480// slot; the cell's 8-byte payload at `SHARED_CELL_VALUE_OFFSET` is reinterpreted
481// through the *interior* `FieldKind` (`ClosureLayout::capture_inner_kind`).
482// These helpers acquire the cell's spinlock, perform a single 8-byte
483// load/store at the constant offset, and release the lock — keeping the JIT's
484// hardcoded offset stable while letting the interpreter operate on raw
485// native values rather than NaN-boxed `ValueWord`s.
486//
487// All read helpers return raw native values (i64/f64/bool/etc.), not
488// `ValueWord`. Sub-8-byte integer payloads are written zero-extended to 8
489// bytes (so a SharedCell read as i64 round-trips losslessly through any
490// narrower kind, but a sub-8-byte writer truncates to its declared width).
491// ---------------------------------------------------------------------------
492
493/// Pointer to the 8-byte payload of a `SharedCell`.
494///
495/// # Safety
496///
497/// `cell` must point to a live `SharedCell` (not freed, not aliased with
498/// `&mut`). The returned pointer is only valid for as long as `cell`.
499#[inline]
500unsafe fn shared_cell_payload_ptr(cell: *const SharedCell) -> *const u8 {
501    // SAFETY: caller upholds `cell` is live; the payload offset is a
502    // compile-time constant.
503    unsafe { (cell as *const u8).add(SHARED_CELL_VALUE_OFFSET as usize) }
504}
505
506/// Read a `f64` from a `SharedCell`'s payload while holding its lock.
507///
508/// # Safety
509///
510/// `cell` must point to a live `SharedCell` whose interior `FieldKind` is
511/// `F64`. Bit patterns written through any other `write_shared_<kind>` will
512/// be misinterpreted on read.
513#[inline]
514pub unsafe fn read_shared_f64(cell: *const SharedCell) -> f64 {
515    // SAFETY: caller upholds `cell` is live; we briefly reborrow `&*cell`
516    // to acquire the lock via the standard guard API. The guard releases
517    // on drop after the load completes.
518    let cell_ref = unsafe { &*cell };
519    let _g = cell_ref.lock();
520    // SAFETY: payload is 8-byte aligned (offset 8 with 8-aligned base) and
521    // 8 bytes long; we read it through the raw pointer rather than through
522    // the guard's `&ValueWord` Deref so we control the bit-level
523    // reinterpretation.
524    unsafe { std::ptr::read(shared_cell_payload_ptr(cell) as *const f64) }
525}
526
527/// Write a `f64` to a `SharedCell`'s payload while holding its lock.
528///
529/// # Safety
530///
531/// `cell` must point to a live `SharedCell` whose interior `FieldKind` is
532/// `F64`.
533#[inline]
534pub unsafe fn write_shared_f64(cell: *const SharedCell, value: f64) {
535    let cell_ref = unsafe { &*cell };
536    let _g = cell_ref.lock();
537    // SAFETY: payload is 8-byte aligned and 8 bytes long.
538    unsafe { std::ptr::write(shared_cell_payload_ptr(cell) as *mut f64, value) };
539}
540
541/// Read an `i64` from a `SharedCell`'s payload.
542///
543/// # Safety
544///
545/// `cell` must point to a live `SharedCell` whose interior `FieldKind` is
546/// `I64`.
547#[inline]
548pub unsafe fn read_shared_i64(cell: *const SharedCell) -> i64 {
549    let cell_ref = unsafe { &*cell };
550    let _g = cell_ref.lock();
551    unsafe { std::ptr::read(shared_cell_payload_ptr(cell) as *const i64) }
552}
553
554/// Write an `i64` to a `SharedCell`'s payload.
555///
556/// # Safety
557///
558/// `cell` must point to a live `SharedCell` whose interior `FieldKind` is
559/// `I64`.
560#[inline]
561pub unsafe fn write_shared_i64(cell: *const SharedCell, value: i64) {
562    let cell_ref = unsafe { &*cell };
563    let _g = cell_ref.lock();
564    unsafe { std::ptr::write(shared_cell_payload_ptr(cell) as *mut i64, value) };
565}
566
567/// Read a `u64` from a `SharedCell`'s payload.
568///
569/// # Safety
570///
571/// `cell` must point to a live `SharedCell` whose interior `FieldKind` is
572/// `U64`.
573#[inline]
574pub unsafe fn read_shared_u64(cell: *const SharedCell) -> u64 {
575    let cell_ref = unsafe { &*cell };
576    let _g = cell_ref.lock();
577    unsafe { std::ptr::read(shared_cell_payload_ptr(cell) as *const u64) }
578}
579
580/// Write a `u64` to a `SharedCell`'s payload.
581///
582/// # Safety
583///
584/// `cell` must point to a live `SharedCell` whose interior `FieldKind` is
585/// `U64`.
586#[inline]
587pub unsafe fn write_shared_u64(cell: *const SharedCell, value: u64) {
588    let cell_ref = unsafe { &*cell };
589    let _g = cell_ref.lock();
590    unsafe { std::ptr::write(shared_cell_payload_ptr(cell) as *mut u64, value) };
591}
592
593/// Read an `i32` from a `SharedCell`'s payload, truncating the upper bytes.
594///
595/// # Safety
596///
597/// `cell` must point to a live `SharedCell` whose interior `FieldKind` is
598/// `I32`.
599#[inline]
600pub unsafe fn read_shared_i32(cell: *const SharedCell) -> i32 {
601    let cell_ref = unsafe { &*cell };
602    let _g = cell_ref.lock();
603    // SAFETY: read the low 4 bytes of the 8-byte payload. Per the
604    // `write_shared_i32` contract the low bytes hold the signed value
605    // (sign-extended to 8 bytes on write).
606    unsafe { std::ptr::read(shared_cell_payload_ptr(cell) as *const i32) }
607}
608
609/// Write an `i32` to a `SharedCell`'s payload, sign-extending to 8 bytes.
610///
611/// # Safety
612///
613/// `cell` must point to a live `SharedCell` whose interior `FieldKind` is
614/// `I32`.
615#[inline]
616pub unsafe fn write_shared_i32(cell: *const SharedCell, value: i32) {
617    let cell_ref = unsafe { &*cell };
618    let _g = cell_ref.lock();
619    // Sign-extend to 8 bytes so the high half holds the sign bit and an
620    // i64-shaped reader (e.g. the JIT lowering, if one ever emerges)
621    // observes the correct value.
622    unsafe { std::ptr::write(shared_cell_payload_ptr(cell) as *mut i64, value as i64) };
623}
624
625/// Read a `u32` from a `SharedCell`'s payload, truncating the upper bytes.
626///
627/// # Safety
628///
629/// `cell` must point to a live `SharedCell` whose interior `FieldKind` is
630/// `U32`.
631#[inline]
632pub unsafe fn read_shared_u32(cell: *const SharedCell) -> u32 {
633    let cell_ref = unsafe { &*cell };
634    let _g = cell_ref.lock();
635    unsafe { std::ptr::read(shared_cell_payload_ptr(cell) as *const u32) }
636}
637
638/// Write a `u32` to a `SharedCell`'s payload, zero-extending to 8 bytes.
639///
640/// # Safety
641///
642/// `cell` must point to a live `SharedCell` whose interior `FieldKind` is
643/// `U32`.
644#[inline]
645pub unsafe fn write_shared_u32(cell: *const SharedCell, value: u32) {
646    let cell_ref = unsafe { &*cell };
647    let _g = cell_ref.lock();
648    unsafe { std::ptr::write(shared_cell_payload_ptr(cell) as *mut u64, value as u64) };
649}
650
651/// Read an `i16` from a `SharedCell`'s payload, truncating the upper bytes.
652///
653/// # Safety
654///
655/// `cell` must point to a live `SharedCell` whose interior `FieldKind` is
656/// `I16`.
657#[inline]
658pub unsafe fn read_shared_i16(cell: *const SharedCell) -> i16 {
659    let cell_ref = unsafe { &*cell };
660    let _g = cell_ref.lock();
661    unsafe { std::ptr::read(shared_cell_payload_ptr(cell) as *const i16) }
662}
663
664/// Write an `i16` to a `SharedCell`'s payload, sign-extending to 8 bytes.
665///
666/// # Safety
667///
668/// `cell` must point to a live `SharedCell` whose interior `FieldKind` is
669/// `I16`.
670#[inline]
671pub unsafe fn write_shared_i16(cell: *const SharedCell, value: i16) {
672    let cell_ref = unsafe { &*cell };
673    let _g = cell_ref.lock();
674    unsafe { std::ptr::write(shared_cell_payload_ptr(cell) as *mut i64, value as i64) };
675}
676
677/// Read a `u16` from a `SharedCell`'s payload, truncating the upper bytes.
678///
679/// # Safety
680///
681/// `cell` must point to a live `SharedCell` whose interior `FieldKind` is
682/// `U16`.
683#[inline]
684pub unsafe fn read_shared_u16(cell: *const SharedCell) -> u16 {
685    let cell_ref = unsafe { &*cell };
686    let _g = cell_ref.lock();
687    unsafe { std::ptr::read(shared_cell_payload_ptr(cell) as *const u16) }
688}
689
690/// Write a `u16` to a `SharedCell`'s payload, zero-extending to 8 bytes.
691///
692/// # Safety
693///
694/// `cell` must point to a live `SharedCell` whose interior `FieldKind` is
695/// `U16`.
696#[inline]
697pub unsafe fn write_shared_u16(cell: *const SharedCell, value: u16) {
698    let cell_ref = unsafe { &*cell };
699    let _g = cell_ref.lock();
700    unsafe { std::ptr::write(shared_cell_payload_ptr(cell) as *mut u64, value as u64) };
701}
702
703/// Read an `i8` from a `SharedCell`'s payload, truncating the upper bytes.
704///
705/// # Safety
706///
707/// `cell` must point to a live `SharedCell` whose interior `FieldKind` is
708/// `I8`.
709#[inline]
710pub unsafe fn read_shared_i8(cell: *const SharedCell) -> i8 {
711    let cell_ref = unsafe { &*cell };
712    let _g = cell_ref.lock();
713    unsafe { std::ptr::read(shared_cell_payload_ptr(cell) as *const i8) }
714}
715
716/// Write an `i8` to a `SharedCell`'s payload, sign-extending to 8 bytes.
717///
718/// # Safety
719///
720/// `cell` must point to a live `SharedCell` whose interior `FieldKind` is
721/// `I8`.
722#[inline]
723pub unsafe fn write_shared_i8(cell: *const SharedCell, value: i8) {
724    let cell_ref = unsafe { &*cell };
725    let _g = cell_ref.lock();
726    unsafe { std::ptr::write(shared_cell_payload_ptr(cell) as *mut i64, value as i64) };
727}
728
729/// Read a `u8` from a `SharedCell`'s payload, truncating the upper bytes.
730///
731/// # Safety
732///
733/// `cell` must point to a live `SharedCell` whose interior `FieldKind` is
734/// `U8`.
735#[inline]
736pub unsafe fn read_shared_u8(cell: *const SharedCell) -> u8 {
737    let cell_ref = unsafe { &*cell };
738    let _g = cell_ref.lock();
739    unsafe { std::ptr::read(shared_cell_payload_ptr(cell) as *const u8) }
740}
741
742/// Write a `u8` to a `SharedCell`'s payload, zero-extending to 8 bytes.
743///
744/// # Safety
745///
746/// `cell` must point to a live `SharedCell` whose interior `FieldKind` is
747/// `U8`.
748#[inline]
749pub unsafe fn write_shared_u8(cell: *const SharedCell, value: u8) {
750    let cell_ref = unsafe { &*cell };
751    let _g = cell_ref.lock();
752    unsafe { std::ptr::write(shared_cell_payload_ptr(cell) as *mut u64, value as u64) };
753}
754
755/// Read a `bool` from a `SharedCell`'s payload — `false` iff every byte of
756/// the 8-byte payload is zero, `true` otherwise. (`write_shared_bool`
757/// stores `0` or `1`, so this is just the standard "any non-zero byte"
758/// test.)
759///
760/// # Safety
761///
762/// `cell` must point to a live `SharedCell` whose interior `FieldKind` is
763/// `Bool`.
764#[inline]
765pub unsafe fn read_shared_bool(cell: *const SharedCell) -> bool {
766    let cell_ref = unsafe { &*cell };
767    let _g = cell_ref.lock();
768    // SAFETY: read just the low byte; the writer zeros the upper 7 bytes
769    // so this is a single u8 load.
770    unsafe { std::ptr::read(shared_cell_payload_ptr(cell) as *const u8) != 0 }
771}
772
773/// Write a `bool` to a `SharedCell`'s payload as a 0/1 byte, zero-extended
774/// to 8 bytes.
775///
776/// # Safety
777///
778/// `cell` must point to a live `SharedCell` whose interior `FieldKind` is
779/// `Bool`.
780#[inline]
781pub unsafe fn write_shared_bool(cell: *const SharedCell, value: bool) {
782    let cell_ref = unsafe { &*cell };
783    let _g = cell_ref.lock();
784    let byte: u64 = if value { 1 } else { 0 };
785    unsafe { std::ptr::write(shared_cell_payload_ptr(cell) as *mut u64, byte) };
786}
787
788/// Read the raw 8-byte pointer payload of a `SharedCell` whose interior
789/// `FieldKind` is `Ptr`. The returned `u64` is a `ValueWord` bit pattern
790/// (NaN-boxed Arc/Box pointer) that can be `clone_from_bits`'d to obtain a
791/// retained share.
792///
793/// # Safety
794///
795/// `cell` must point to a live `SharedCell` whose interior `FieldKind` is
796/// `Ptr`.
797#[inline]
798pub unsafe fn read_shared_ptr(cell: *const SharedCell) -> u64 {
799    let cell_ref = unsafe { &*cell };
800    let _g = cell_ref.lock();
801    unsafe { std::ptr::read(shared_cell_payload_ptr(cell) as *const u64) }
802}
803
804/// Write a raw 8-byte pointer payload to a `SharedCell` whose interior
805/// `FieldKind` is `Ptr`. The caller is responsible for refcount semantics
806/// — this writer does NOT release the previous payload nor retain the new
807/// one. For `Ptr` payloads the standard pattern is to read the old bits,
808/// release them, then write the new (already-retained) bits.
809///
810/// # Safety
811///
812/// `cell` must point to a live `SharedCell` whose interior `FieldKind` is
813/// `Ptr`.
814#[inline]
815pub unsafe fn write_shared_ptr(cell: *const SharedCell, bits: u64) {
816    let cell_ref = unsafe { &*cell };
817    let _g = cell_ref.lock();
818    unsafe { std::ptr::write(shared_cell_payload_ptr(cell) as *mut u64, bits) };
819}
820
821/// Release a `Shared` capture: read the cell pointer at the slot, decode
822/// the interior `FieldKind`, drop any heap refcount share carried by a
823/// `Ptr` payload, and finally `Arc::from_raw` + drop the cell to release
824/// its strong-count share.
825///
826/// This is the per-capture handler invoked by `release_typed_closure`'s
827/// dispatch on `capture_storage_kind(i)`. The contract pairs with
828/// `drop_owned_mutable_capture` (defined by the parallel-track migration of
829/// owned-mutable storage) — both are reached only when the closure
830/// refcount has hit zero, and each handler is responsible for fully
831/// reclaiming its slot's resources.
832///
833/// # Safety
834///
835/// - `base` must point to a live `TypedClosureHeader` block whose layout
836///   matches `layout`, and capture `i` must be `CaptureKind::Shared`
837///   (mask bit `shared_capture_mask & (1 << i)` set).
838/// - The slot at `base.add(layout.heap_capture_offset(i))` must contain
839///   a non-null `*const SharedCell` produced by `Arc::into_raw` on a
840///   freshly-allocated `Arc<SharedCell>` (or null, in which case the
841///   release is a no-op).
842/// - For `Ptr` interior kind the payload bits must be a valid `ValueWord`
843///   bit pattern for which `release_raw_value_bits` semantics apply.
844/// - After this call the slot must not be read again (the `Arc::from_raw`
845///   may have freed the underlying `SharedCell`).
846#[inline]
847pub unsafe fn drop_shared_capture(layout: &ClosureLayout, base: *mut u8, i: usize) {
848    let off = layout.heap_capture_offset(i);
849    // SAFETY: caller upholds that `base` + `off` is in-bounds for an
850    // 8-byte read (per `ClosureLayout` invariants Shared captures live at
851    // an 8-byte Ptr slot).
852    let cell_ptr = unsafe { std::ptr::read(base.add(off) as *const *const SharedCell) };
853    if cell_ptr.is_null() {
854        return;
855    }
856
857    // For Ptr payloads we must release the heap refcount share encoded in
858    // the cell's 8-byte payload before reclaiming the cell allocation
859    // itself. Other interior kinds are scalar bytes — no refcount.
860    let inner_kind = layout.capture_inner_kind(i);
861    if inner_kind == FieldKind::Ptr {
862        // SAFETY: cell_ptr is non-null and was produced by Arc::into_raw,
863        // so reborrowing it as `&SharedCell` is sound while the strong
864        // count is still ≥ 1 (it is — we still hold the share we are
865        // about to reclaim).
866        let cell_ref = unsafe { &*cell_ptr };
867        let bits = {
868            let _g = cell_ref.lock();
869            // SAFETY: payload offset is 8, payload is 8 bytes wide.
870            unsafe { std::ptr::read(shared_cell_payload_ptr(cell_ptr) as *const u64) }
871        };
872        // ADR-006 §2.7.8 / Q10: route the Ptr-payload share retire through
873        // the per-capture `NativeKind` carried by the layout's
874        // `capture_native_kinds[i]` track. Same canonical
875        // `KindedSlot::Drop` dispatch as the Immutable-Ptr branch in
876        // `release_typed_closure`. The `SharedCell` itself also carries a
877        // single-slot `kind` companion (set at construction per §2.7.8 /
878        // Q10 — see `closure_layout::SharedCell::new`); the layout's
879        // per-capture kind and the cell's per-slot kind are required to
880        // agree by the §2.7.8 lockstep invariant. Reading from the layout
881        // keeps this single-sourced — the layout is the storage-tier
882        // descriptor for the closure block.
883        let kind = layout.capture_native_kind(i);
884        // SAFETY: `inner_kind == FieldKind::Ptr` confirms the cell payload
885        // is an `Arc<T>` share owned by this slot for the `T` matching
886        // `kind` (per the construction-side contract).
887        unsafe { drop_with_kind(bits, kind) };
888    }
889
890    // Reclaim the Arc strong-count share. If we held the last share the
891    // SharedCell is freed here; otherwise the strong count just drops by
892    // one.
893    // SAFETY: cell_ptr came from `Arc::into_raw(Arc::new(SharedCell::new(...)))`
894    // (per the Shared-capture construction contract) and represents
895    // exactly one strong-count share owned by this slot.
896    unsafe { drop(Arc::from_raw(cell_ptr)) };
897}
898
899// `release_raw_heap_share` was deleted at the §2.7.8 / Q10
900// G-owned-closure-block close. It violated the §1 single-discriminator rule
901// by performing a blanket `Arc<HeapValue>::decrement_strong_count` on every
902// Ptr-capture slot regardless of which `T` the slot's bits actually came
903// from — incompatible with ADR-005's typed-pointer storage discipline
904// (`HeapValue::TypedArray(Arc<TypedArrayData>)` etc.). Every former call
905// site has migrated to `drop_with_kind(bits, kind)` reading the layout's
906// per-capture `NativeKind` track.
907
908/// Write a raw 8-byte capture slot at the given index.
909///
910/// The caller is responsible for encoding the value in the format the
911/// consumer (JIT-inlined closure body, VM dispatch, or
912/// `jit_finalize_heap_closure`) expects — typically the raw `ValueWord`
913/// bit pattern (`ValueWord::into_raw_bits`) for `Ptr`/`I64`/`U64` kinds,
914/// native little-endian for narrower numeric kinds, 0/1 byte for `Bool`.
915/// `write_capture_typed` provides a higher-level wrapper.
916///
917/// # Safety
918///
919/// - `ptr` must point to a live `TypedClosureHeader` block whose layout
920///   has at least `idx + 1` captures.
921/// - The 8-byte write at `heap_capture_offset(idx)` is always in-bounds
922///   because the layout rounds total size up to 8-byte alignment.
923#[inline]
924pub unsafe fn write_capture_raw_u64(ptr: *mut u8, layout: &ClosureLayout, idx: usize, bits: u64) {
925    let off = layout.heap_capture_offset(idx);
926    // SAFETY: `ptr + off` is in-bounds (layout total size ≥ off + 8);
927    // 8-byte write at an 8-byte-aligned address is a valid store.
928    unsafe { std::ptr::write(ptr.add(off) as *mut u64, bits) };
929}
930
931/// Read a capture slot as a typed `u64` bit pattern suitable for
932/// `ValueWord::from_raw_bits`.
933///
934/// The read width is dictated by the capture's `FieldKind`: narrower
935/// integer kinds are sign/zero-extended to i64; `Bool` reads a single
936/// byte; `Ptr` / `I64` / `U64` reads 8 bytes verbatim; `F64` reads an
937/// f64 and re-encodes via `ValueWord::from_f64` so that the returned
938/// bits are always NaN-box-decodable.
939///
940/// # Safety
941///
942/// `ptr` must point to a live `TypedClosureHeader` block whose layout
943/// matches the `layout` argument and has at least `idx + 1` captures.
944#[inline]
945pub unsafe fn read_capture_as_value_bits(
946    ptr: *const u8,
947    layout: &ClosureLayout,
948    idx: usize,
949) -> u64 {
950    let kind = layout.capture_kind(idx);
951    let off = layout.heap_capture_offset(idx);
952    // SAFETY: caller upholds live block; offsets are in-bounds per layout.
953    //
954    // Strict-typed bulldozer: NaN-box re-encoding via `ValueWord::from_*` is
955    // gone. Each kind's slot already holds the canonical native bit pattern;
956    // narrower-than-8-byte kinds are sign- or zero-extended into u64.
957    unsafe {
958        let field_ptr = ptr.add(off);
959        match kind {
960            FieldKind::F64 | FieldKind::I64 | FieldKind::U64 | FieldKind::Ptr => {
961                std::ptr::read(field_ptr as *const u64)
962            }
963            FieldKind::I32 => std::ptr::read(field_ptr as *const i32) as i64 as u64,
964            FieldKind::U32 => std::ptr::read(field_ptr as *const u32) as u64,
965            FieldKind::I16 => std::ptr::read(field_ptr as *const i16) as i64 as u64,
966            FieldKind::U16 => std::ptr::read(field_ptr as *const u16) as u64,
967            FieldKind::I8 => std::ptr::read(field_ptr as *const i8) as i64 as u64,
968            FieldKind::U8 => std::ptr::read(field_ptr as *const u8) as u64,
969            FieldKind::Bool => (std::ptr::read(field_ptr as *const u8) != 0) as u64,
970        }
971    }
972}
973
974/// Write a capture slot from a `ValueWord` bit pattern, selecting the
975/// correct native width based on the capture's `FieldKind`.
976///
977/// This is the mirror of [`read_capture_as_value_bits`]: a `ValueWord`
978/// round-trip through write + read preserves the observed value.
979///
980/// # Safety
981///
982/// `ptr` must point to a live `TypedClosureHeader` block whose layout
983/// matches the `layout` argument and has at least `idx + 1` captures.
984/// The caller is responsible for any refcount retain that heap-typed
985/// captures (`FieldKind::Ptr`) require — this function does NOT bump
986/// the refcount; it only stores the bit pattern.
987#[inline]
988pub unsafe fn write_capture_typed(ptr: *mut u8, layout: &ClosureLayout, idx: usize, bits: u64) {
989    let kind = layout.capture_kind(idx);
990    let off = layout.heap_capture_offset(idx);
991    // SAFETY: caller upholds live block; offsets are in-bounds per layout.
992    //
993    // Strict-typed bulldozer: ValueWord ext-method decoding (`as_i64`,
994    // `as_number_coerce`, `as_bool`) is gone. The `bits` value is already
995    // the raw native bit pattern in the caller's chosen FieldKind:
996    //   - F64    : `f64::to_bits(v)`
997    //   - I64    : `v as u64` (i64 reinterpreted)
998    //   - U64    : `v` directly
999    //   - I/U32/16/8 : sign- or zero-extended to u64
1000    //   - Bool   : 0 or 1
1001    //   - Ptr    : `Arc::into_raw(v) as u64`
1002    unsafe {
1003        let field_ptr = ptr.add(off);
1004        match kind {
1005            FieldKind::F64 | FieldKind::I64 | FieldKind::U64 | FieldKind::Ptr => {
1006                std::ptr::write(field_ptr as *mut u64, bits);
1007            }
1008            FieldKind::I32 => std::ptr::write(field_ptr as *mut i32, bits as i32),
1009            FieldKind::U32 => std::ptr::write(field_ptr as *mut u32, bits as u32),
1010            FieldKind::I16 => std::ptr::write(field_ptr as *mut i16, bits as i16),
1011            FieldKind::U16 => std::ptr::write(field_ptr as *mut u16, bits as u16),
1012            FieldKind::I8 => std::ptr::write(field_ptr as *mut i8, bits as i8),
1013            FieldKind::U8 => std::ptr::write(field_ptr as *mut u8, bits as u8),
1014            FieldKind::Bool => std::ptr::write(field_ptr as *mut u8, (bits & 1) as u8),
1015        }
1016    }
1017}
1018
1019/// Get the current refcount of a `TypedClosureHeader` block (for
1020/// debugging / tests).
1021///
1022/// # Safety
1023///
1024/// `ptr` must point to a live `TypedClosureHeader` block.
1025#[inline]
1026pub unsafe fn typed_closure_refcount(ptr: *const u8) -> u32 {
1027    // SAFETY: caller upholds that `ptr` is a live TypedClosureHeader block.
1028    unsafe { (*(ptr as *const HeapHeader)).get_refcount() }
1029}
1030
1031// ---------------------------------------------------------------------------
1032// Per-FieldKind OwnedMutable cell helpers (Wave B / D2 dispatch).
1033//
1034// An `OwnedMutable` capture's slot holds a typed `*mut T` pointer obtained
1035// from `Box::into_raw(Box::new(initial))`, where `T` matches the interior
1036// `FieldKind` (`capture_inner_kind(i)`). Each slot owns exactly one box;
1037// closure Drop reclaims it via the matching `Box::from_raw` cast.
1038//
1039// These helpers are the kind-specialised entry points the JIT FFI and VM
1040// executor will consume in Wave C/D. They expose:
1041//
1042// - `alloc_owned_mutable_<kind>(initial) -> *mut <T>` — leak a fresh box.
1043// - `read_owned_mutable_<kind>(ptr) -> <T>` — load the cell payload.
1044// - `write_owned_mutable_<kind>(ptr, value)` — store a new payload.
1045//
1046// All read/write helpers are `unsafe` because the caller must guarantee
1047// the pointer is non-null and points to a live cell of the matching type;
1048// they are kept `#[inline]` so the JIT can match this body byte-for-byte
1049// when emitting inline lowerings later.
1050// ---------------------------------------------------------------------------
1051
1052/// Allocate a fresh `OwnedMutable` cell holding an `i64` payload.
1053///
1054/// Returns a `*mut i64` produced by `Box::into_raw(Box::new(initial))`.
1055/// The caller must eventually reclaim the box via `Box::from_raw` (or
1056/// indirectly via [`drop_owned_mutable_capture`] on the owning closure).
1057#[inline]
1058pub fn alloc_owned_mutable_i64(initial: i64) -> *mut i64 {
1059    Box::into_raw(Box::new(initial))
1060}
1061
1062/// Read the `i64` payload of an `OwnedMutable` cell.
1063///
1064/// # Safety
1065///
1066/// `ptr` must be non-null and point to a live `Box<i64>` cell allocated
1067/// by [`alloc_owned_mutable_i64`].
1068#[inline]
1069pub unsafe fn read_owned_mutable_i64(ptr: *mut i64) -> i64 {
1070    // SAFETY: caller upholds the pointer is live and properly typed.
1071    unsafe { *ptr }
1072}
1073
1074/// Write the `i64` payload of an `OwnedMutable` cell.
1075///
1076/// # Safety
1077///
1078/// `ptr` must be non-null and point to a live `Box<i64>` cell allocated
1079/// by [`alloc_owned_mutable_i64`].
1080#[inline]
1081pub unsafe fn write_owned_mutable_i64(ptr: *mut i64, value: i64) {
1082    // SAFETY: caller upholds the pointer is live and properly typed.
1083    unsafe { *ptr = value };
1084}
1085
1086/// Allocate a fresh `OwnedMutable` cell holding an `f64` payload.
1087#[inline]
1088pub fn alloc_owned_mutable_f64(initial: f64) -> *mut f64 {
1089    Box::into_raw(Box::new(initial))
1090}
1091
1092/// Read the `f64` payload of an `OwnedMutable` cell.
1093///
1094/// # Safety
1095///
1096/// `ptr` must be non-null and point to a live `Box<f64>` cell.
1097#[inline]
1098pub unsafe fn read_owned_mutable_f64(ptr: *mut f64) -> f64 {
1099    // SAFETY: caller upholds the pointer is live and properly typed.
1100    unsafe { *ptr }
1101}
1102
1103/// Write the `f64` payload of an `OwnedMutable` cell.
1104///
1105/// # Safety
1106///
1107/// `ptr` must be non-null and point to a live `Box<f64>` cell.
1108#[inline]
1109pub unsafe fn write_owned_mutable_f64(ptr: *mut f64, value: f64) {
1110    // SAFETY: caller upholds the pointer is live and properly typed.
1111    unsafe { *ptr = value };
1112}
1113
1114/// Allocate a fresh `OwnedMutable` cell holding an `i32` payload.
1115#[inline]
1116pub fn alloc_owned_mutable_i32(initial: i32) -> *mut i32 {
1117    Box::into_raw(Box::new(initial))
1118}
1119
1120/// Read the `i32` payload of an `OwnedMutable` cell.
1121///
1122/// # Safety
1123///
1124/// `ptr` must be non-null and point to a live `Box<i32>` cell.
1125#[inline]
1126pub unsafe fn read_owned_mutable_i32(ptr: *mut i32) -> i32 {
1127    // SAFETY: caller upholds the pointer is live and properly typed.
1128    unsafe { *ptr }
1129}
1130
1131/// Write the `i32` payload of an `OwnedMutable` cell.
1132///
1133/// # Safety
1134///
1135/// `ptr` must be non-null and point to a live `Box<i32>` cell.
1136#[inline]
1137pub unsafe fn write_owned_mutable_i32(ptr: *mut i32, value: i32) {
1138    // SAFETY: caller upholds the pointer is live and properly typed.
1139    unsafe { *ptr = value };
1140}
1141
1142/// Allocate a fresh `OwnedMutable` cell holding an `i16` payload.
1143#[inline]
1144pub fn alloc_owned_mutable_i16(initial: i16) -> *mut i16 {
1145    Box::into_raw(Box::new(initial))
1146}
1147
1148/// Read the `i16` payload of an `OwnedMutable` cell.
1149///
1150/// # Safety
1151///
1152/// `ptr` must be non-null and point to a live `Box<i16>` cell.
1153#[inline]
1154pub unsafe fn read_owned_mutable_i16(ptr: *mut i16) -> i16 {
1155    // SAFETY: caller upholds the pointer is live and properly typed.
1156    unsafe { *ptr }
1157}
1158
1159/// Write the `i16` payload of an `OwnedMutable` cell.
1160///
1161/// # Safety
1162///
1163/// `ptr` must be non-null and point to a live `Box<i16>` cell.
1164#[inline]
1165pub unsafe fn write_owned_mutable_i16(ptr: *mut i16, value: i16) {
1166    // SAFETY: caller upholds the pointer is live and properly typed.
1167    unsafe { *ptr = value };
1168}
1169
1170/// Allocate a fresh `OwnedMutable` cell holding an `i8` payload.
1171#[inline]
1172pub fn alloc_owned_mutable_i8(initial: i8) -> *mut i8 {
1173    Box::into_raw(Box::new(initial))
1174}
1175
1176/// Read the `i8` payload of an `OwnedMutable` cell.
1177///
1178/// # Safety
1179///
1180/// `ptr` must be non-null and point to a live `Box<i8>` cell.
1181#[inline]
1182pub unsafe fn read_owned_mutable_i8(ptr: *mut i8) -> i8 {
1183    // SAFETY: caller upholds the pointer is live and properly typed.
1184    unsafe { *ptr }
1185}
1186
1187/// Write the `i8` payload of an `OwnedMutable` cell.
1188///
1189/// # Safety
1190///
1191/// `ptr` must be non-null and point to a live `Box<i8>` cell.
1192#[inline]
1193pub unsafe fn write_owned_mutable_i8(ptr: *mut i8, value: i8) {
1194    // SAFETY: caller upholds the pointer is live and properly typed.
1195    unsafe { *ptr = value };
1196}
1197
1198/// Allocate a fresh `OwnedMutable` cell holding a `u64` payload.
1199#[inline]
1200pub fn alloc_owned_mutable_u64(initial: u64) -> *mut u64 {
1201    Box::into_raw(Box::new(initial))
1202}
1203
1204/// Read the `u64` payload of an `OwnedMutable` cell.
1205///
1206/// # Safety
1207///
1208/// `ptr` must be non-null and point to a live `Box<u64>` cell.
1209#[inline]
1210pub unsafe fn read_owned_mutable_u64(ptr: *mut u64) -> u64 {
1211    // SAFETY: caller upholds the pointer is live and properly typed.
1212    unsafe { *ptr }
1213}
1214
1215/// Write the `u64` payload of an `OwnedMutable` cell.
1216///
1217/// # Safety
1218///
1219/// `ptr` must be non-null and point to a live `Box<u64>` cell.
1220#[inline]
1221pub unsafe fn write_owned_mutable_u64(ptr: *mut u64, value: u64) {
1222    // SAFETY: caller upholds the pointer is live and properly typed.
1223    unsafe { *ptr = value };
1224}
1225
1226/// Allocate a fresh `OwnedMutable` cell holding a `u32` payload.
1227#[inline]
1228pub fn alloc_owned_mutable_u32(initial: u32) -> *mut u32 {
1229    Box::into_raw(Box::new(initial))
1230}
1231
1232/// Read the `u32` payload of an `OwnedMutable` cell.
1233///
1234/// # Safety
1235///
1236/// `ptr` must be non-null and point to a live `Box<u32>` cell.
1237#[inline]
1238pub unsafe fn read_owned_mutable_u32(ptr: *mut u32) -> u32 {
1239    // SAFETY: caller upholds the pointer is live and properly typed.
1240    unsafe { *ptr }
1241}
1242
1243/// Write the `u32` payload of an `OwnedMutable` cell.
1244///
1245/// # Safety
1246///
1247/// `ptr` must be non-null and point to a live `Box<u32>` cell.
1248#[inline]
1249pub unsafe fn write_owned_mutable_u32(ptr: *mut u32, value: u32) {
1250    // SAFETY: caller upholds the pointer is live and properly typed.
1251    unsafe { *ptr = value };
1252}
1253
1254/// Allocate a fresh `OwnedMutable` cell holding a `u16` payload.
1255#[inline]
1256pub fn alloc_owned_mutable_u16(initial: u16) -> *mut u16 {
1257    Box::into_raw(Box::new(initial))
1258}
1259
1260/// Read the `u16` payload of an `OwnedMutable` cell.
1261///
1262/// # Safety
1263///
1264/// `ptr` must be non-null and point to a live `Box<u16>` cell.
1265#[inline]
1266pub unsafe fn read_owned_mutable_u16(ptr: *mut u16) -> u16 {
1267    // SAFETY: caller upholds the pointer is live and properly typed.
1268    unsafe { *ptr }
1269}
1270
1271/// Write the `u16` payload of an `OwnedMutable` cell.
1272///
1273/// # Safety
1274///
1275/// `ptr` must be non-null and point to a live `Box<u16>` cell.
1276#[inline]
1277pub unsafe fn write_owned_mutable_u16(ptr: *mut u16, value: u16) {
1278    // SAFETY: caller upholds the pointer is live and properly typed.
1279    unsafe { *ptr = value };
1280}
1281
1282/// Allocate a fresh `OwnedMutable` cell holding a `u8` payload.
1283#[inline]
1284pub fn alloc_owned_mutable_u8(initial: u8) -> *mut u8 {
1285    Box::into_raw(Box::new(initial))
1286}
1287
1288/// Read the `u8` payload of an `OwnedMutable` cell.
1289///
1290/// # Safety
1291///
1292/// `ptr` must be non-null and point to a live `Box<u8>` cell.
1293#[inline]
1294pub unsafe fn read_owned_mutable_u8(ptr: *mut u8) -> u8 {
1295    // SAFETY: caller upholds the pointer is live and properly typed.
1296    unsafe { *ptr }
1297}
1298
1299/// Write the `u8` payload of an `OwnedMutable` cell.
1300///
1301/// # Safety
1302///
1303/// `ptr` must be non-null and point to a live `Box<u8>` cell.
1304#[inline]
1305pub unsafe fn write_owned_mutable_u8(ptr: *mut u8, value: u8) {
1306    // SAFETY: caller upholds the pointer is live and properly typed.
1307    unsafe { *ptr = value };
1308}
1309
1310/// Allocate a fresh `OwnedMutable` cell holding a `bool` payload.
1311#[inline]
1312pub fn alloc_owned_mutable_bool(initial: bool) -> *mut bool {
1313    Box::into_raw(Box::new(initial))
1314}
1315
1316/// Read the `bool` payload of an `OwnedMutable` cell.
1317///
1318/// # Safety
1319///
1320/// `ptr` must be non-null and point to a live `Box<bool>` cell.
1321#[inline]
1322pub unsafe fn read_owned_mutable_bool(ptr: *mut bool) -> bool {
1323    // SAFETY: caller upholds the pointer is live and properly typed.
1324    unsafe { *ptr }
1325}
1326
1327/// Write the `bool` payload of an `OwnedMutable` cell.
1328///
1329/// # Safety
1330///
1331/// `ptr` must be non-null and point to a live `Box<bool>` cell.
1332#[inline]
1333pub unsafe fn write_owned_mutable_bool(ptr: *mut bool, value: bool) {
1334    // SAFETY: caller upholds the pointer is live and properly typed.
1335    unsafe { *ptr = value };
1336}
1337
1338/// Allocate a fresh `OwnedMutable` cell holding a `Ptr` payload.
1339///
1340/// The cell stores the raw 8-byte heap-pointer bit pattern (a
1341/// NaN-boxed `ValueWord` carrying an `Arc<HeapValue>` share or an owned
1342/// heap pointer). The interior bits are released through
1343/// [`release_raw_value_bits`] inside [`drop_owned_mutable_capture`]
1344/// before the box itself is reclaimed, mirroring the existing
1345/// `heap_capture_mask` semantics for immutable Ptr captures.
1346#[inline]
1347pub fn alloc_owned_mutable_ptr(initial: u64) -> *mut u64 {
1348    Box::into_raw(Box::new(initial))
1349}
1350
1351/// Read the raw `u64` (Ptr-shaped) payload of an `OwnedMutable` cell.
1352///
1353/// # Safety
1354///
1355/// `ptr` must be non-null and point to a live `Box<u64>` cell allocated
1356/// via [`alloc_owned_mutable_ptr`]. The returned bits are caller-owned
1357/// from a refcount standpoint exactly to the extent the cell owned
1358/// them; cloning into a separately-owned share is the caller's
1359/// responsibility (see `ValueWord::clone_from_bits`).
1360#[inline]
1361pub unsafe fn read_owned_mutable_ptr(ptr: *mut u64) -> u64 {
1362    // SAFETY: caller upholds the pointer is live and properly typed.
1363    unsafe { *ptr }
1364}
1365
1366/// Write a new `u64` (Ptr-shaped) payload into an `OwnedMutable` cell.
1367///
1368/// # Safety
1369///
1370/// `ptr` must be non-null and point to a live `Box<u64>` cell. The
1371/// caller is responsible for releasing the previous payload's refcount
1372/// share (if any) BEFORE calling this — this function does not
1373/// retain/release.
1374#[inline]
1375pub unsafe fn write_owned_mutable_ptr(ptr: *mut u64, value: u64) {
1376    // SAFETY: caller upholds the pointer is live and properly typed.
1377    unsafe { *ptr = value };
1378}
1379
1380// ---------------------------------------------------------------------------
1381// Per-CaptureKind drop helpers (D4 from the Wave A playbook).
1382//
1383// `release_typed_closure` dispatches on `capture_kinds[i]` and calls one of
1384// these helpers per slot. `drop_owned_mutable_capture` reconstructs the
1385// typed `Box<T>` matching `capture_inner_kind(i)` and drops it; if the
1386// interior kind is `Ptr`, the heap-refcount share encoded in the cell's
1387// bits is released first.
1388//
1389// `drop_shared_capture` is implemented above (alongside the per-FieldKind
1390// SharedCell payload helpers in the Shared-storage migration block); it
1391// shares the same `(layout, base, i)` contract.
1392// ---------------------------------------------------------------------------
1393
1394/// Drop the `OwnedMutable` capture at index `i` of a closure block.
1395///
1396/// Reads the typed `*mut T` from the slot at
1397/// `layout.heap_capture_offset(i)`, dispatches on
1398/// `layout.capture_inner_kind(i)`, and reclaims the box via
1399/// `Box::from_raw`. For `FieldKind::Ptr` the interior bits carry one
1400/// heap-refcount share that is released via [`release_raw_value_bits`]
1401/// BEFORE the box is freed — mirroring the immutable-Ptr semantics that
1402/// `heap_capture_mask` enforces for non-mutable captures.
1403///
1404/// # Safety
1405///
1406/// - `base` must point to a live `TypedClosureHeader` block whose layout
1407///   matches `layout` and has at least `i + 1` captures.
1408/// - `layout.capture_kinds[i]` must be `CaptureKind::OwnedMutable`.
1409/// - The slot at `layout.heap_capture_offset(i)` must contain a non-null
1410///   pointer obtained from the matching `alloc_owned_mutable_<kind>` for
1411///   the interior `FieldKind`, or it may be null (which is a no-op).
1412/// - The block must currently be in the refcount-zero teardown phase —
1413///   no other thread may concurrently access this slot.
1414#[inline]
1415pub unsafe fn drop_owned_mutable_capture(layout: &ClosureLayout, base: *mut u8, i: usize) {
1416    let off = layout.heap_capture_offset(i);
1417    // SAFETY: caller upholds that the slot is in-bounds and 8-byte aligned;
1418    // the slot stores a single-pointer-sized value (Ptr slot).
1419    let raw = unsafe { std::ptr::read(base.add(off) as *const *mut u8) };
1420    if raw.is_null() {
1421        return;
1422    }
1423    match layout.capture_inner_kind(i) {
1424        FieldKind::I64 => {
1425            // SAFETY: slot was produced by `alloc_owned_mutable_i64`.
1426            unsafe { drop(Box::from_raw(raw as *mut i64)) };
1427        }
1428        FieldKind::F64 => {
1429            // SAFETY: slot was produced by `alloc_owned_mutable_f64`.
1430            unsafe { drop(Box::from_raw(raw as *mut f64)) };
1431        }
1432        FieldKind::I32 => {
1433            // SAFETY: slot was produced by `alloc_owned_mutable_i32`.
1434            unsafe { drop(Box::from_raw(raw as *mut i32)) };
1435        }
1436        FieldKind::I16 => {
1437            // SAFETY: slot was produced by `alloc_owned_mutable_i16`.
1438            unsafe { drop(Box::from_raw(raw as *mut i16)) };
1439        }
1440        FieldKind::I8 => {
1441            // SAFETY: slot was produced by `alloc_owned_mutable_i8`.
1442            unsafe { drop(Box::from_raw(raw as *mut i8)) };
1443        }
1444        FieldKind::U64 => {
1445            // SAFETY: slot was produced by `alloc_owned_mutable_u64`.
1446            unsafe { drop(Box::from_raw(raw as *mut u64)) };
1447        }
1448        FieldKind::U32 => {
1449            // SAFETY: slot was produced by `alloc_owned_mutable_u32`.
1450            unsafe { drop(Box::from_raw(raw as *mut u32)) };
1451        }
1452        FieldKind::U16 => {
1453            // SAFETY: slot was produced by `alloc_owned_mutable_u16`.
1454            unsafe { drop(Box::from_raw(raw as *mut u16)) };
1455        }
1456        FieldKind::U8 => {
1457            // SAFETY: slot was produced by `alloc_owned_mutable_u8`.
1458            unsafe { drop(Box::from_raw(raw as *mut u8)) };
1459        }
1460        FieldKind::Bool => {
1461            // SAFETY: slot was produced by `alloc_owned_mutable_bool`.
1462            unsafe { drop(Box::from_raw(raw as *mut bool)) };
1463        }
1464        FieldKind::Ptr => {
1465            // Interior is a heap-refcount share — release it before
1466            // freeing the box. Read the bits, decrement the inner
1467            // share via the per-capture `NativeKind`-keyed dispatch,
1468            // then reclaim the box itself.
1469            // SAFETY: slot was produced by `alloc_owned_mutable_ptr`,
1470            // so the box holds exactly one `u64` cell with the raw
1471            // `Arc<T>::into_raw` bits per the construction-side
1472            // contract.
1473            let cell = raw as *mut u64;
1474            let bits = unsafe { *cell };
1475            // ADR-006 §2.7.8 / Q10: route through `drop_with_kind` using
1476            // the layout's per-capture kind track — the canonical
1477            // `KindedSlot::Drop` dispatch retires exactly one
1478            // `Arc<T>` strong-count share for the `T` matching the
1479            // capture's `NativeKind`. Replaces the forbidden
1480            // `Arc<HeapValue>` blanket decrement.
1481            let kind = layout.capture_native_kind(i);
1482            // SAFETY: FieldKind::Ptr confirms `bits` is an `Arc<T>`
1483            // share for the `T` matching `kind`; the construction-side
1484            // contract on `alloc_owned_mutable_ptr` stored it.
1485            unsafe { drop_with_kind(bits, kind) };
1486            // SAFETY: reclaim the now-empty `Box<u64>`.
1487            unsafe { drop(Box::from_raw(cell)) };
1488        }
1489    }
1490}
1491
1492// ---------------------------------------------------------------------------
1493// §2.7.8 / Q10 — Cell-storage kind-awareness (Phase 1.B-vm Wave 6.5 B7).
1494//
1495// The §2.7.7 stack-side parallel-`Vec<NativeKind>` invariant extends to
1496// every cell-storage struct that holds raw heap-pointer bits in the
1497// runtime/VM tier. Below is the closure-cell incarnation: a kind-aware
1498// capture-cell store that pairs `Vec<u64>` raw payload with a parallel
1499// `Vec<NativeKind>` track in lockstep, plus the matching `clone_with_kind`
1500// / `drop_with_kind` dispatch (mirrored from `KindedSlot::Clone` /
1501// `KindedSlot::Drop` — the canonical refcount-dispatch table in
1502// `crates/shape-value/src/kinded_slot.rs`).
1503//
1504// This struct is the structural foundation Wave-β cluster
1505// `B6-variables-loadptr` consumes when it migrates the `Load*Ptr` /
1506// `Store*Ptr` handlers off `NotImplemented(SURFACE)`. The closure block's
1507// raw byte buffer (allocated via `alloc_typed_closure`) and the
1508// `OwnedClosureBlock` handle continue to exist as today; `ClosureCell`
1509// adds the parallel-kind track that the cell-bound consumer surface
1510// (variables/mod.rs Load*Ptr handlers) requires per §2.7.8.
1511//
1512// See `docs/adr/006-value-and-memory-model.md` §2.7.8 + §17 Q10.
1513// Playbook anchor: `docs/cluster-audits/phase-1b-vm-wave-6-5-playbook.md`
1514// §10 row B7-closure-cells.
1515// ---------------------------------------------------------------------------
1516
1517/// WB2.4 retain-on-read mirror of `KindedSlot::Clone`. Bumps the matching
1518/// `Arc<T>` strong-count for a heap-bearing kind, no-op for inline scalars.
1519///
1520/// Implemented by constructing a transient `KindedSlot` and forgetting
1521/// it — `KindedSlot::Clone` carries the canonical per-`NativeKind`
1522/// dispatch, so this routes every retain through the single discriminator
1523/// without duplicating the table.
1524///
1525/// # Safety
1526///
1527/// `bits` must be a valid representation of `kind` per the construction-side
1528/// contract (for heap kinds: result of `Arc::into_raw::<T>` for the matching
1529/// `T`; for inline scalars: native bit pattern of the kind).
1530#[inline]
1531pub(crate) unsafe fn clone_with_kind(bits: u64, kind: NativeKind) {
1532    if bits == 0 {
1533        return;
1534    }
1535    // SAFETY: caller upholds the construction-side contract (see fn doc).
1536    // `KindedSlot::clone()` bumps exactly one strong-count share for the
1537    // matching `Arc<T>` per kind; we keep the cloned slot (which owns the
1538    // bumped share) and leak the original via `mem::forget` so the borrowed
1539    // `bits` continue to represent the original share owned by the caller's
1540    // cell.
1541    unsafe {
1542        let original = KindedSlot::new(ValueSlot::from_raw(bits), kind);
1543        let cloned = original.clone();
1544        std::mem::forget(original);
1545        // `cloned` carries the +1 strong-count we added; dropping it would
1546        // cancel the retain we just performed, so leak it. The caller's
1547        // freshly-cloned slot owns the new share.
1548        std::mem::forget(cloned);
1549    }
1550}
1551
1552/// WB2.4 release-on-overwrite mirror of `KindedSlot::Drop`. Decrements the
1553/// matching `Arc<T>` strong-count for a heap-bearing kind, no-op for inline
1554/// scalars.
1555///
1556/// Implemented by reconstructing the owning `KindedSlot` from `(bits, kind)`
1557/// and letting Rust's Drop dispatch through `KindedSlot::drop` — the single
1558/// per-`NativeKind` table.
1559///
1560/// # Safety
1561///
1562/// `bits` must be a valid representation of `kind` and must represent
1563/// exactly one strong-count share that the caller is consuming with this
1564/// release. Calling `drop_with_kind` twice on the same bits is a
1565/// double-free for heap kinds.
1566#[inline]
1567pub(crate) unsafe fn drop_with_kind(bits: u64, kind: NativeKind) {
1568    if bits == 0 {
1569        return;
1570    }
1571    // SAFETY: caller upholds that `bits` is one strong-count share for
1572    // `kind`; reconstructing the `KindedSlot` and letting it drop retires
1573    // exactly one share via the canonical dispatch table.
1574    unsafe {
1575        let _retire = KindedSlot::new(ValueSlot::from_raw(bits), kind);
1576    }
1577}
1578
1579/// Kind-aware closure capture cell store (§2.7.8 / Q10).
1580///
1581/// Carries two parallel arrays in lockstep:
1582///
1583/// - `bits: Vec<u64>` — 8-byte raw payload per cell (the same shape as
1584///   the existing closure block's capture slots, but stored separately as
1585///   a kind-tracked side-store for cells whose kind is not derivable from
1586///   `ClosureLayout::capture_inner_kind` alone — i.e. heap captures whose
1587///   `NativeKind::Ptr(HeapKind)` discriminator is finer than `FieldKind::Ptr`).
1588/// - `kinds: Vec<NativeKind>` — 1-byte interpretation per cell.
1589///
1590/// **Index invariant:** `bits.len() == kinds.len()` at every observable
1591/// boundary (method entry/exit). Mixed lengths are a bug.
1592///
1593/// **Drop discipline:** every cell is released through `drop_with_kind`
1594/// — never bare `vw_drop` (forbidden #8 per §2.7.7) or "drop only if
1595/// heap-shaped" probes (forbidden #7). Inline-scalar kinds are no-op
1596/// drops; heap-bearing kinds retire one `Arc<T>` strong-count share per
1597/// the dispatch in `KindedSlot::drop`.
1598///
1599/// **Construction:** every push/pop/read accepts/returns `(bits, kind)`
1600/// lockstep. There is no kind-less constructor — cells are post-proof per
1601/// §2.7.5.1, so each cell carries a known `NativeKind` by construction.
1602///
1603/// **Forbidden shapes (mirror of §2.7.7's stack-side list):**
1604/// - `Vec<KindedSlot>` for the cell store (§2.7.5 — `KindedSlot` is a
1605///   runtime-tier carrier, not the storage-tier shape).
1606/// - 16-byte cell slots / packed tag bits in the `u64` (§2.1 — 8-byte
1607///   slot invariant).
1608/// - `Vec<Option<NativeKind>>` for the kind track (§2.7.5.1 — cells are
1609///   post-proof; every cell has a concrete kind by construction).
1610/// - `NativeKind::Unknown` / `Pending` / `Dynamic` placeholders (deleted
1611///   from the enum).
1612/// - Bool-default fallback for any cell write (§2.7.7 #9 — the W-series
1613///   rationalization; surface to supervisor on a kind-source gap instead).
1614///
1615/// **Wave-β consumer migration:** the `Load*Ptr` / `Store*Ptr` handlers
1616/// in `executor/variables/mod.rs` (the 130 mandatory + 33 sibling sites
1617/// cluster B partial-closed leaving as `NotImplemented(SURFACE)`) will be
1618/// migrated by Wave-β cluster `B6-variables-loadptr` to thread the kind
1619/// through the cell-bound read paths via this struct.
1620#[derive(Debug)]
1621pub struct ClosureCell {
1622    /// Raw payload — 8-byte per cell. Cell `i` holds `bits[i]` interpreted
1623    /// per `kinds[i]` (e.g. an `Arc::into_raw::<TypedArrayData>` raw pointer
1624    /// when `kinds[i] == NativeKind::Ptr(HeapKind::TypedArray)`, or a native
1625    /// `f64` bit pattern when `kinds[i] == NativeKind::Float64`).
1626    pub bits: Vec<u64>,
1627    /// Per-cell kind track. Lockstep with `bits` per the §2.7.8 index
1628    /// invariant.
1629    pub kinds: Vec<NativeKind>,
1630}
1631
1632impl ClosureCell {
1633    /// Create an empty cell store.
1634    #[inline]
1635    pub fn new() -> Self {
1636        Self {
1637            bits: Vec::new(),
1638            kinds: Vec::new(),
1639        }
1640    }
1641
1642    /// Create an empty cell store with the given capacity reserved on
1643    /// both parallel tracks.
1644    #[inline]
1645    pub fn with_capacity(cap: usize) -> Self {
1646        Self {
1647            bits: Vec::with_capacity(cap),
1648            kinds: Vec::with_capacity(cap),
1649        }
1650    }
1651
1652    /// Number of live cells. The §2.7.8 index invariant guarantees this
1653    /// equals `self.kinds.len()`.
1654    #[inline]
1655    pub fn len(&self) -> usize {
1656        debug_assert_eq!(
1657            self.bits.len(),
1658            self.kinds.len(),
1659            "ClosureCell index invariant: bits.len() == kinds.len()"
1660        );
1661        self.bits.len()
1662    }
1663
1664    /// Whether the cell store is empty.
1665    #[inline]
1666    pub fn is_empty(&self) -> bool {
1667        self.bits.is_empty()
1668    }
1669
1670    /// Append a cell. The caller transfers ownership of `bits`'s
1671    /// strong-count share (for heap kinds) into the cell store; the
1672    /// matching `drop_with_kind` discharge happens at pop / truncate /
1673    /// `Drop` time.
1674    ///
1675    /// # Safety
1676    ///
1677    /// `bits` must be a valid representation of `kind` per the
1678    /// construction-side contract — for heap kinds, the result of
1679    /// `Arc::into_raw::<T>` for the matching `T`; for inline scalars, the
1680    /// native bit pattern.
1681    #[inline]
1682    pub unsafe fn push(&mut self, bits: u64, kind: NativeKind) {
1683        self.bits.push(bits);
1684        self.kinds.push(kind);
1685        debug_assert_eq!(
1686            self.bits.len(),
1687            self.kinds.len(),
1688            "ClosureCell::push violated bits.len() == kinds.len() invariant"
1689        );
1690    }
1691
1692    /// Remove and return the last `(bits, kind)`. The caller takes
1693    /// ownership of the share (for heap kinds) and is responsible for
1694    /// `drop_with_kind` (or transferring it elsewhere). Pop does NOT
1695    /// clone — `vec.pop()` is move-out semantics.
1696    #[inline]
1697    pub fn pop(&mut self) -> Option<(u64, NativeKind)> {
1698        match (self.bits.pop(), self.kinds.pop()) {
1699            (Some(b), Some(k)) => Some((b, k)),
1700            (None, None) => None,
1701            _ => {
1702                // The §2.7.8 index invariant rules this out at every
1703                // observable boundary; reaching here is a hard bug.
1704                unreachable!("ClosureCell index invariant violated: bits/kinds desync on pop")
1705            }
1706        }
1707    }
1708
1709    /// Read cell `idx` as `(bits, kind)` without consuming it. The
1710    /// returned `bits` is a borrowed copy — for heap kinds the caller
1711    /// must `clone_with_kind(bits, kind)` to obtain an independently-
1712    /// owned share before storing it elsewhere (the cell retains its
1713    /// share).
1714    ///
1715    /// # Panics
1716    ///
1717    /// Panics if `idx >= self.len()`.
1718    #[inline]
1719    pub fn read(&self, idx: usize) -> (u64, NativeKind) {
1720        debug_assert_eq!(
1721            self.bits.len(),
1722            self.kinds.len(),
1723            "ClosureCell::read on desynced cell store"
1724        );
1725        (self.bits[idx], self.kinds[idx])
1726    }
1727
1728    /// Read cell `idx` and return a runtime-tier `KindedSlot` carrier
1729    /// with a freshly-cloned share (for heap kinds; inline scalars are
1730    /// `Copy`-equivalent). This is the §2.7.7 retain-on-read pattern,
1731    /// extended to cells per §2.7.8.
1732    ///
1733    /// # Panics
1734    ///
1735    /// Panics if `idx >= self.len()`.
1736    #[inline]
1737    pub fn read_kinded(&self, idx: usize) -> KindedSlot {
1738        let (bits, kind) = self.read(idx);
1739        // SAFETY: cells are post-proof; `bits`/`kind` represent a valid
1740        // strong-count share owned by this cell store. `clone_with_kind`
1741        // bumps the share so the returned `KindedSlot` owns an
1742        // independent share, leaving the cell's own share intact.
1743        unsafe { clone_with_kind(bits, kind) };
1744        KindedSlot::new(ValueSlot::from_raw(bits), kind)
1745    }
1746
1747    /// Overwrite cell `idx` with a new `(bits, kind)` pair, returning the
1748    /// old `(bits, kind)`. The caller is responsible for `drop_with_kind`
1749    /// on the returned previous value (or transferring it elsewhere) and
1750    /// for ensuring the new `bits` carry one fresh strong-count share for
1751    /// the new `kind`.
1752    ///
1753    /// # Safety
1754    ///
1755    /// New `bits` must be a valid representation of new `kind` per the
1756    /// construction-side contract (for heap kinds: one strong-count share
1757    /// from `Arc::into_raw::<T>` for the matching `T`).
1758    ///
1759    /// # Panics
1760    ///
1761    /// Panics if `idx >= self.len()`.
1762    #[inline]
1763    pub unsafe fn replace(
1764        &mut self,
1765        idx: usize,
1766        bits: u64,
1767        kind: NativeKind,
1768    ) -> (u64, NativeKind) {
1769        debug_assert_eq!(
1770            self.bits.len(),
1771            self.kinds.len(),
1772            "ClosureCell::replace on desynced cell store"
1773        );
1774        let prev_bits = std::mem::replace(&mut self.bits[idx], bits);
1775        let prev_kind = std::mem::replace(&mut self.kinds[idx], kind);
1776        (prev_bits, prev_kind)
1777    }
1778
1779    /// Truncate the cell store to `new_len` cells, releasing every cell
1780    /// at index `>= new_len` via `drop_with_kind`. No-op if
1781    /// `new_len >= self.len()`.
1782    #[inline]
1783    pub fn truncate(&mut self, new_len: usize) {
1784        let old_len = self.len();
1785        if new_len >= old_len {
1786            return;
1787        }
1788        // Release tail in reverse so refcount semantics match the
1789        // last-pushed-first-dropped order.
1790        for i in (new_len..old_len).rev() {
1791            let bits = self.bits[i];
1792            let kind = self.kinds[i];
1793            // SAFETY: the §2.7.8 push-side contract guarantees every
1794            // cell carries a valid `(bits, kind)` representation owning
1795            // one strong-count share. Releasing here matches that share.
1796            unsafe { drop_with_kind(bits, kind) };
1797        }
1798        // Now that every tail cell's heap share has been retired via
1799        // `drop_with_kind`, shrink both parallel tracks. The element
1800        // types (`u64` and `NativeKind`) are `Copy`, so `Vec::truncate`'s
1801        // own element-drop is a trivial no-op — no double-release risk.
1802        self.bits.truncate(new_len);
1803        self.kinds.truncate(new_len);
1804        debug_assert_eq!(
1805            self.bits.len(),
1806            self.kinds.len(),
1807            "ClosureCell::truncate violated bits.len() == kinds.len() invariant"
1808        );
1809    }
1810}
1811
1812impl Default for ClosureCell {
1813    #[inline]
1814    fn default() -> Self {
1815        Self::new()
1816    }
1817}
1818
1819impl Drop for ClosureCell {
1820    /// Releases every live cell via `drop_with_kind` per §2.7.8. The
1821    /// drop order is tail-first to mirror the last-pushed-first-dropped
1822    /// convention used by `KindedSlot`-bearing collections.
1823    fn drop(&mut self) {
1824        // Iterate in reverse so refcount-bearing cells release in
1825        // last-pushed-first-dropped order. `truncate(0)` already does
1826        // tail-first release per the impl above; calling it here yields
1827        // the same dispatch as a manual loop.
1828        self.truncate(0);
1829    }
1830}
1831
1832#[cfg(test)]
1833mod closure_cell_tests {
1834    //! §2.7.8 / Q10 structural-extension tests for `ClosureCell`.
1835    //!
1836    //! These tests exercise the lockstep `bits.len() == kinds.len()`
1837    //! invariant, push/pop/read/replace/truncate signatures, and the
1838    //! kind-aware drop discipline. Heap-kind refcount semantics are
1839    //! covered by the `KindedSlot` test suite (`kinded_slot.rs`); these
1840    //! tests focus on the cell-store shape itself.
1841    use super::*;
1842
1843    #[test]
1844    fn empty_cell_satisfies_invariant() {
1845        let cell = ClosureCell::new();
1846        assert_eq!(cell.len(), 0);
1847        assert!(cell.is_empty());
1848        assert_eq!(cell.bits.len(), cell.kinds.len());
1849    }
1850
1851    #[test]
1852    fn push_pop_inline_scalars_round_trip() {
1853        let mut cell = ClosureCell::with_capacity(4);
1854        // SAFETY: inline-scalar bits are valid representations of their
1855        // kinds (no heap shares to track).
1856        unsafe {
1857            cell.push(42u64, NativeKind::Int64);
1858            cell.push(f64::to_bits(3.14), NativeKind::Float64);
1859            cell.push(1u64, NativeKind::Bool);
1860        }
1861        assert_eq!(cell.len(), 3);
1862        assert_eq!(cell.pop(), Some((1u64, NativeKind::Bool)));
1863        assert_eq!(cell.pop(), Some((f64::to_bits(3.14), NativeKind::Float64)));
1864        assert_eq!(cell.pop(), Some((42u64, NativeKind::Int64)));
1865        assert_eq!(cell.pop(), None);
1866    }
1867
1868    #[test]
1869    fn read_returns_lockstep_pair() {
1870        let mut cell = ClosureCell::new();
1871        // SAFETY: inline-scalar bits.
1872        unsafe {
1873            cell.push(7u64, NativeKind::Int64);
1874            cell.push(0u64, NativeKind::Bool);
1875        }
1876        assert_eq!(cell.read(0), (7u64, NativeKind::Int64));
1877        assert_eq!(cell.read(1), (0u64, NativeKind::Bool));
1878    }
1879
1880    #[test]
1881    fn replace_returns_previous_pair() {
1882        let mut cell = ClosureCell::new();
1883        // SAFETY: inline-scalar bits.
1884        unsafe {
1885            cell.push(1u64, NativeKind::Int64);
1886            let prev = cell.replace(0, 99u64, NativeKind::UInt64);
1887            assert_eq!(prev, (1u64, NativeKind::Int64));
1888            assert_eq!(cell.read(0), (99u64, NativeKind::UInt64));
1889        }
1890    }
1891
1892    #[test]
1893    fn truncate_drops_tail() {
1894        let mut cell = ClosureCell::new();
1895        // SAFETY: inline-scalar bits — drop is a no-op for these kinds.
1896        unsafe {
1897            cell.push(1u64, NativeKind::Int64);
1898            cell.push(2u64, NativeKind::Int64);
1899            cell.push(3u64, NativeKind::Int64);
1900        }
1901        cell.truncate(1);
1902        assert_eq!(cell.len(), 1);
1903        assert_eq!(cell.read(0), (1u64, NativeKind::Int64));
1904    }
1905
1906    #[test]
1907    fn drop_releases_all_cells() {
1908        // Use a heap-bearing kind to confirm the dispatch path runs
1909        // through `KindedSlot::drop`. Construct via the canonical
1910        // `KindedSlot::from_string` and decompose into `(bits, kind)` so
1911        // the cell store owns the share.
1912        let mut cell = ClosureCell::new();
1913        let slot = KindedSlot::from_string("hello §2.7.8");
1914        let bits = slot.slot.raw();
1915        let kind = slot.kind;
1916        std::mem::forget(slot); // transfer the share into the cell
1917        // SAFETY: `bits`/`kind` carry one strong-count share transferred
1918        // via `mem::forget` above; the cell store now owns it.
1919        unsafe { cell.push(bits, kind) };
1920        assert_eq!(cell.len(), 1);
1921        // Dropping the cell store should retire the share via
1922        // `drop_with_kind` -> `KindedSlot::drop`.
1923        drop(cell);
1924        // No assertion on the freed Arc — miri / valgrind catch
1925        // double-free or leak. The test passing without UB is the
1926        // signal.
1927    }
1928
1929    #[test]
1930    fn pop_then_explicit_drop_round_trip() {
1931        let mut cell = ClosureCell::new();
1932        let slot = KindedSlot::from_string("popped");
1933        let bits = slot.slot.raw();
1934        let kind = slot.kind;
1935        std::mem::forget(slot);
1936        // SAFETY: same construction-side contract as above.
1937        unsafe { cell.push(bits, kind) };
1938
1939        let (b, k) = cell.pop().expect("non-empty");
1940        // Caller takes ownership; matching drop_with_kind retires the
1941        // share once.
1942        // SAFETY: `b`/`k` are exactly what we just pushed and popped.
1943        unsafe { drop_with_kind(b, k) };
1944    }
1945}
1946
1947#[cfg(test)]
1948mod owned_closure_block_kinded_tests {
1949    //! ADR-006 §2.7.8 / Q10 structural-extension tests for the
1950    //! `OwnedClosureBlock` per-capture kind track.
1951    //!
1952    //! These exercise the new `read_capture_kinded(idx) -> (u64, NativeKind)`
1953    //! accessor (the cell-bound mirror of the §2.7.7 stack-side
1954    //! `read_owned_kinded`) on the existing raw-byte closure block.
1955    //! Heap-kind refcount semantics are covered by the `KindedSlot` and
1956    //! `closure_cell_tests` suites; these tests focus on the
1957    //! layout-driven kind dispatch through the `OwnedClosureBlock` handle.
1958    use super::*;
1959    use crate::v2::closure_layout::{CaptureKind, ClosureLayout};
1960    use crate::v2::concrete_type::ConcreteType;
1961    use std::sync::Arc;
1962
1963    /// Build an immutable-only Arc<ClosureLayout>.
1964    fn arc_immutable_layout(types: &[ConcreteType]) -> Arc<ClosureLayout> {
1965        let kinds = vec![CaptureKind::Immutable; types.len()];
1966        Arc::new(ClosureLayout::from_capture_types(types, &kinds))
1967    }
1968
1969    #[test]
1970    fn read_capture_kinded_inline_scalar_returns_layout_kind() {
1971        // Single I64 capture initialised to a known bit pattern; the
1972        // accessor returns the lockstep `(bits, kind)` pair from the
1973        // layout's `capture_native_kinds[0]`.
1974        let layout = arc_immutable_layout(&[ConcreteType::I64]);
1975        // SAFETY: alloc + write are paired; no concurrent access.
1976        unsafe {
1977            let ptr = alloc_typed_closure(0, 0, &layout);
1978            write_capture_typed(ptr, &layout, 0, 0xDEAD_BEEF_CAFE_BABE);
1979            let block = OwnedClosureBlock::from_raw(ptr, Arc::clone(&layout));
1980            let (bits, kind) = block.read_capture_kinded(0);
1981            assert_eq!(bits, 0xDEAD_BEEF_CAFE_BABE);
1982            assert_eq!(kind, NativeKind::Int64);
1983        }
1984    }
1985
1986    #[test]
1987    fn read_capture_kinded_f64_returns_float_kind() {
1988        let layout = arc_immutable_layout(&[ConcreteType::F64]);
1989        // SAFETY: alloc + write are paired.
1990        unsafe {
1991            let ptr = alloc_typed_closure(0, 0, &layout);
1992            write_capture_typed(ptr, &layout, 0, f64::to_bits(2.5));
1993            let block = OwnedClosureBlock::from_raw(ptr, Arc::clone(&layout));
1994            let (bits, kind) = block.read_capture_kinded(0);
1995            assert_eq!(f64::from_bits(bits), 2.5);
1996            assert_eq!(kind, NativeKind::Float64);
1997        }
1998    }
1999
2000    #[test]
2001    fn read_capture_kinded_string_returns_string_kind() {
2002        // String capture maps to NativeKind::String per the §2.7.8
2003        // derivation; the accessor surfaces this for B6-round-2's
2004        // `Load*Ptr` consumer to route through `clone_with_kind`.
2005        let layout = arc_immutable_layout(&[ConcreteType::String]);
2006        // SAFETY: alloc + write are paired. A null Ptr-slot is fine —
2007        // we're not exercising the share-bearing path here.
2008        unsafe {
2009            let ptr = alloc_typed_closure(0, 0, &layout);
2010            // Slot stays zero-initialised; `read_capture_kinded` should
2011            // still return the layout's kind for slot 0.
2012            let block = OwnedClosureBlock::from_raw(ptr, Arc::clone(&layout));
2013            let (bits, kind) = block.read_capture_kinded(0);
2014            assert_eq!(bits, 0);
2015            assert_eq!(kind, NativeKind::String);
2016        }
2017    }
2018
2019    #[test]
2020    fn read_capture_kinded_multiple_captures_lockstep() {
2021        // Mixed-kind layout: per-capture kinds match per-capture types,
2022        // demonstrating that `read_capture_kinded` walks the kind track
2023        // in lockstep with the bit slots.
2024        let layout = arc_immutable_layout(&[
2025            ConcreteType::F64,
2026            ConcreteType::I32,
2027            ConcreteType::Bool,
2028        ]);
2029        // SAFETY: alloc + per-slot writes are paired.
2030        unsafe {
2031            let ptr = alloc_typed_closure(0, 0, &layout);
2032            write_capture_typed(ptr, &layout, 0, f64::to_bits(1.5));
2033            write_capture_typed(ptr, &layout, 1, (-7i32) as u32 as u64);
2034            write_capture_typed(ptr, &layout, 2, 1);
2035            let block = OwnedClosureBlock::from_raw(ptr, Arc::clone(&layout));
2036
2037            let (b0, k0) = block.read_capture_kinded(0);
2038            assert_eq!(f64::from_bits(b0), 1.5);
2039            assert_eq!(k0, NativeKind::Float64);
2040
2041            let (b1, k1) = block.read_capture_kinded(1);
2042            assert_eq!(b1 as i32, -7);
2043            assert_eq!(k1, NativeKind::Int32);
2044
2045            let (b2, k2) = block.read_capture_kinded(2);
2046            assert_eq!(b2 & 0xFF, 1);
2047            assert_eq!(k2, NativeKind::Bool);
2048        }
2049    }
2050}
2051