shape_value/vm_closure_handle.rs
1//! `VmClosureHandle` — a read-only shim over the raw closure backing.
2//!
3//! # Closure spec §14.2
4//!
5//! Closure state is stored in [`crate::heap_value::HeapValue::ClosureRaw`],
6//! backed by a raw `*const TypedClosureHeader` block whose capture layout
7//! is described by a [`ClosureLayout`]. `VmClosureHandle` is the stable
8//! read API over that backing — readers go through the handle so any
9//! future backing changes remain contained.
10//!
11//! Track A.5 retired the legacy `HeapValue::Closure { function_id,
12//! upvalues }` variant and its `ClosureBacking::Legacy` shim variant —
13//! every closure is now `ClosureRaw`-backed.
14//!
15//! # API surface
16//!
17//! ```ignore
18//! handle.function_id() // u32 function table index
19//! handle.type_id() // u32 ClosureTypeId
20//! handle.capture_count()
21//! handle.capture_as_value(i) // widen capture i to ValueWord
22//! handle.captures_as_values() // Vec<ValueWord> of all captures
23//! handle.refcount() // exact refcount on the block
24//! ```
25
26use crate::v2::closure_layout::{CaptureKind, ClosureLayout, SharedCell, TypedClosureHeader};
27use crate::v2::closure_raw::{
28 read_capture_as_value_bits, typed_closure_function_id, typed_closure_refcount,
29 typed_closure_type_id,
30};
31
32/// Read-only handle to a closure's function id, type id, and captures.
33///
34/// Construction is a cheap reborrow over the `TypedClosureHeader`
35/// block + its `ClosureLayout` — no allocation, no refcount traffic.
36///
37/// # Safety invariant
38///
39/// `ptr` must have been returned by
40/// [`crate::v2::closure_raw::alloc_typed_closure`] paired with the
41/// exact `layout` reference carried here, and must still be live for
42/// the borrow `'a`.
43pub struct VmClosureHandle<'a> {
44 ptr: *const TypedClosureHeader,
45 layout: &'a ClosureLayout,
46}
47
48impl<'a> VmClosureHandle<'a> {
49 /// Construct a handle over a raw `TypedClosureHeader` block.
50 ///
51 /// # Safety
52 ///
53 /// * `ptr` must have been allocated by
54 /// [`crate::v2::closure_raw::alloc_typed_closure`] with the same
55 /// `layout` that is passed in here.
56 /// * `ptr` must remain live for the duration of the borrow `'a`.
57 /// * The block's capture slots must be initialised — each typed
58 /// width from `layout` must contain a value that
59 /// [`read_capture_as_value_bits`] can safely decode.
60 #[inline]
61 pub unsafe fn raw(ptr: *const TypedClosureHeader, layout: &'a ClosureLayout) -> Self {
62 VmClosureHandle { ptr, layout }
63 }
64
65 /// Function table index for the closure body.
66 #[inline]
67 pub fn function_id(&self) -> u32 {
68 // SAFETY: the Raw constructor requires `ptr` to be a live
69 // `TypedClosureHeader`; reading the 4-byte `function_id`
70 // at offset 8 is in-bounds.
71 unsafe { typed_closure_function_id(self.ptr as *const u8) as u32 }
72 }
73
74 /// `ClosureTypeId` for the closure's capture layout.
75 #[inline]
76 pub fn type_id(&self) -> u32 {
77 // SAFETY: see `function_id` above — same live-block
78 // invariant covers the 4-byte read at offset 12.
79 unsafe { typed_closure_type_id(self.ptr as *const u8) }
80 }
81
82 /// Number of captures.
83 #[inline]
84 pub fn capture_count(&self) -> usize {
85 self.layout.capture_count()
86 }
87
88 // Pattern C bridge `capture_as_value` deleted by the strict-typing
89 // bulldozer. Closure capture readers must use the typed-kind APIs
90 // (capture_raw_bits + per-kind decode at compile-time-known sites,
91 // or capture_owned_mutable_ptr / capture_shared_cell_ptr for cell
92 // captures). No runtime per-FieldKind decode at the read boundary.
93
94 /// Track A.1B: raw 8-byte bits for capture `i` **as stored in the
95 /// closure's slot**, without running SharedCell or OwnedMutable
96 /// auto-deref.
97 ///
98 /// For [`CaptureKind::Immutable`] captures this returns the ValueWord
99 /// bit pattern (identical to [`Self::capture_as_value`]'s result).
100 ///
101 /// For [`CaptureKind::OwnedMutable`] captures this returns the raw
102 /// `*mut ValueWord` pointer bits (cast to `u64`).
103 ///
104 /// For [`CaptureKind::Shared`] captures this returns the raw
105 /// `*const SharedCell` pointer bits (cast to `u64`).
106 ///
107 /// Used by the closure-call plumbing to populate `frame.upvalues` so
108 /// the A.1B MIR opcodes
109 /// (`LoadOwnedMutableCapture` / `LoadSharedCapture` etc.) can
110 /// recover the underlying cell pointer via
111 /// `Upvalue::clone_inner_bits_for_raw_pointer_access`.
112 ///
113 /// # Panics
114 ///
115 /// Panics if `i >= self.capture_count()`.
116 #[inline]
117 pub fn capture_execution_bits(&self, i: usize) -> u64 {
118 match self.layout.capture_storage_kind(i) {
119 CaptureKind::Immutable => {
120 // SAFETY: see `capture_as_value` — Raw constructor
121 // + layout + in-bounds index upheld.
122 unsafe { read_capture_as_value_bits(self.ptr as *const u8, self.layout, i) }
123 }
124 CaptureKind::OwnedMutable => {
125 // SAFETY: same invariants as
126 // `capture_owned_mutable_ptr`; reinterpreting the
127 // `*mut ValueWord` as `u64` is a lossless cast.
128 unsafe { owned_mutable_cell_ptr(self.ptr, self.layout, i) as u64 }
129 }
130 CaptureKind::Shared => {
131 // SAFETY: same invariants as
132 // `capture_shared_cell_ptr`; reinterpreting the
133 // `*const SharedCell` as `u64` is a lossless cast.
134 unsafe { shared_cell_ptr(self.ptr, self.layout, i) as u64 }
135 }
136 }
137 }
138
139 /// Track A.1B: raw pointer to the `*mut ValueWord` box cell for an
140 /// `OwnedMutable` capture.
141 ///
142 /// Returns `None` when capture `i` is not
143 /// [`CaptureKind::OwnedMutable`].
144 ///
145 /// # Safety
146 ///
147 /// Callers must dereference the returned pointer only for the
148 /// lifetime of the borrowing handle `'a` — the closure block's
149 /// refcount keeps the `Box<ValueWord>` allocation alive. Writing
150 /// through the pointer replaces the cell's value in place; no retain
151 /// or release is needed.
152 #[inline]
153 pub fn capture_owned_mutable_ptr(&self, i: usize) -> Option<*mut u64> {
154 match self.layout.capture_storage_kind(i) {
155 CaptureKind::OwnedMutable => {
156 Some(unsafe { owned_mutable_cell_ptr(self.ptr, self.layout, i) })
157 }
158 _ => None,
159 }
160 }
161
162 /// Track A.1B: raw pointer to the `*const SharedCell` (
163 /// `Arc<parking_lot::Mutex<ValueWord>>`) for a `Shared` capture.
164 ///
165 /// Returns `None` when capture `i` is not [`CaptureKind::Shared`].
166 ///
167 /// # Safety
168 ///
169 /// The returned pointer is derived from `Arc::into_raw` in A.1B's
170 /// `op_make_closure`. Reborrowing it as `&SharedCell` for the
171 /// lifetime of a `lock()` guard is sound while the handle's `'a`
172 /// lives. Callers MUST acquire the mutex before reading or writing
173 /// the inner `ValueWord` (the cell is shared across nested closures,
174 /// possibly on different threads).
175 #[inline]
176 pub fn capture_shared_cell_ptr(&self, i: usize) -> Option<*const SharedCell> {
177 match self.layout.capture_storage_kind(i) {
178 CaptureKind::Shared => Some(unsafe { shared_cell_ptr(self.ptr, self.layout, i) }),
179 _ => None,
180 }
181 }
182
183 /// Exact refcount on the `TypedClosureHeader` block — i.e. the
184 /// number of live shares on the block.
185 #[inline]
186 pub fn refcount(&self) -> u32 {
187 // SAFETY: live-block invariant from the Raw constructor
188 // covers the 4-byte atomic load at offset 0.
189 unsafe { typed_closure_refcount(self.ptr as *const u8) }
190 }
191}
192
193// ── Track A.1B helpers: raw pointer accessors for mutable-cell captures ──
194
195/// Read the 8-byte pointer slot for an `OwnedMutable` capture.
196///
197/// # Safety
198///
199/// Caller must have verified via `layout.capture_storage_kind(i) ==
200/// CaptureKind::OwnedMutable` that the slot holds `*mut ValueWord` bits.
201/// `ptr` must be a live `TypedClosureHeader` allocation from
202/// `alloc_typed_closure(&layout)`.
203#[inline]
204unsafe fn owned_mutable_cell_ptr(
205 ptr: *const TypedClosureHeader,
206 layout: &ClosureLayout,
207 i: usize,
208) -> *mut u64 {
209 let off = layout.heap_capture_offset(i);
210 // SAFETY: the `Ptr` slot is 8-byte aligned and in-bounds per the
211 // layout invariants (verified by `ClosureLayout::from_capture_types`).
212 unsafe { std::ptr::read((ptr as *const u8).add(off) as *const *mut u64) }
213}
214
215/// Read the 8-byte pointer slot for a `Shared` capture.
216///
217/// # Safety
218///
219/// Caller must have verified via `layout.capture_storage_kind(i) ==
220/// CaptureKind::Shared` that the slot holds `*const SharedCell` bits.
221/// `ptr` must be a live `TypedClosureHeader` allocation from
222/// `alloc_typed_closure(&layout)`.
223#[inline]
224unsafe fn shared_cell_ptr(
225 ptr: *const TypedClosureHeader,
226 layout: &ClosureLayout,
227 i: usize,
228) -> *const SharedCell {
229 let off = layout.heap_capture_offset(i);
230 // SAFETY: see `owned_mutable_cell_ptr` above.
231 unsafe { std::ptr::read((ptr as *const u8).add(off) as *const *const SharedCell) }
232}
233