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