Skip to main content

shape_jit/ffi/
jit_kinds.rs

1//! JIT-specific heap kinds and allocation types.
2//!
3//! Contains heap kind constants for JIT-only types (values >= 128),
4//! the `JitAlloc<T>` and `UnifiedValue<T>` structs, and allocation helpers.
5//!
6//! Per ADR-006 §2.7.5, the JIT FFI boundary carries raw `u64` plus a parallel
7//! `NativeKind` companion stamped at JIT compile time from the call signature.
8//! The `u64` returned by `jit_box` / `unified_box` here is the raw
9//! `Box::into_raw(...) as u64` of a `JitAlloc<T>` or `UnifiedValue<T>` heap
10//! allocation — there is no tag-bit packing, no payload-mask projection, and
11//! no runtime kind discrimination from the bits themselves. Consumers that
12//! need a runtime-tier carrier wrap the pair as
13//! `KindedSlot::new(ValueSlot::from_raw(bits), kind)` per §2.7.5; consumers
14//! that need to read the per-allocation `kind: u16` discriminator (e.g.
15//! `read_heap_kind` for matrix/duration/etc. dispatch on the JitAlloc prefix)
16//! read it from offset 0 of the allocation directly.
17//!
18//! `read_heap_kind` reads the `u16` kind discriminator at offset 0 of a
19//! `JitAlloc`-prefixed allocation. This is *not* tag-bit dispatch — it reads a
20//! field from a heap-resident struct that the producing call placed there.
21
22use shape_value::HeapKind;
23use shape_value::NativeKind;
24
25// ============================================================================
26// JIT-specific heap kinds (values >= 128, outside VM's HeapKind enum range)
27// ============================================================================
28
29pub const HK_JIT_FUNCTION: u16 = 128;
30pub const HK_JIT_TABLE_REF: u16 = 130;
31/// Plain HashMap<String, u64> objects (JIT-only, distinct from TypedObject).
32pub const HK_JIT_OBJECT: u16 = 131;
33
34// ============================================================================
35// JIT FFI carrier helpers (ADR-006 §2.7.5)
36// ============================================================================
37
38/// The canonical JIT-FFI carrier is a `(u64, NativeKind)` pair: raw bits plus
39/// a parallel kind companion stamped at JIT compile time from the call
40/// signature. Consumers assemble a runtime-tier `KindedSlot` from this pair
41/// via `KindedSlot::new(ValueSlot::from_raw(bits), kind)` per §2.7.5/Q7 when
42/// crossing into runtime-tier dispatch surfaces.
43pub type JitFfiCarrier = (u64, NativeKind);
44
45/// Build the `NativeKind` companion for a JIT-owned heap allocation whose
46/// `JitAlloc` / `UnifiedValue` prefix carries `kind`. JIT-private allocations
47/// (`HK_JIT_FUNCTION`, `HK_JIT_TABLE_REF`, `HK_JIT_OBJECT`) and other prefix
48/// kinds map to their `HeapKind` counterpart so the §2.7.5 carrier pair can
49/// flow into runtime-tier `KindedSlot` dispatch. The mapping is intentionally
50/// limited to kinds that have a `HeapKind` variant; sites that need a
51/// JIT-only-shape kind on the runtime side surface-and-stop to the W10
52/// playbook §5.
53#[inline]
54pub fn native_kind_for_heap_kind(heap_kind: HeapKind) -> NativeKind {
55    NativeKind::Ptr(heap_kind)
56}
57
58// ============================================================================
59// JIT Heap Allocation Infrastructure
60// ============================================================================
61
62/// Prefix for JIT heap allocations. Stored at offset 0 of every JIT-owned
63/// heap value, enabling type discrimination via `read_heap_kind()`.
64///
65/// Layout: `[kind: u16][_pad: 6 bytes][data: T]` -- data starts at offset 8.
66#[repr(C)]
67pub struct JitAlloc<T> {
68    /// HeapKind discriminator (matches HK_* / HEAP_KIND_* constants).
69    pub kind: u16,
70    _pad: [u8; 6],
71    /// The actual value.
72    pub data: T,
73}
74
75/// Byte offset of `data` within a `JitAlloc<T>`.
76pub const JIT_ALLOC_DATA_OFFSET: usize = 8;
77
78// ============================================================================
79// Unified Heap Value (refcounted variant of JitAlloc)
80// ============================================================================
81
82/// Generic unified heap value with the standard header format.
83///
84/// Layout: `[kind: u16][flags: u8][_reserved: u8][refcount: AtomicU32][data: T]`
85/// Data starts at offset 8, same as `JitAlloc`.
86///
87/// The `kind: u16` at offset 0 is layout-compatible with `JitAlloc<T>` so
88/// `read_heap_kind` works on both shapes uniformly.
89#[repr(C)]
90pub struct UnifiedValue<T> {
91    pub kind: u16,
92    pub flags: u8,
93    pub _reserved: u8,
94    pub refcount: std::sync::atomic::AtomicU32,
95    pub data: T,
96}
97
98impl<T> UnifiedValue<T> {
99    #[inline]
100    pub fn new(kind: u16, data: T) -> Self {
101        Self {
102            kind,
103            flags: 0,
104            _reserved: 0,
105            refcount: std::sync::atomic::AtomicU32::new(1),
106            data,
107        }
108    }
109
110    /// Allocate this value on the heap and return the raw `Box::into_raw`
111    /// pointer cast to `u64`. Per §2.7.5 the JIT-FFI boundary carries this
112    /// `u64` directly alongside a parallel `NativeKind` companion supplied
113    /// from the call signature.
114    #[inline]
115    pub fn heap_box(self) -> u64 {
116        let ptr = Box::into_raw(Box::new(self));
117        ptr as u64
118    }
119
120    /// # Safety
121    /// `bits` must be a `Box::into_raw`-returned pointer to a live
122    /// `UnifiedValue<T>` allocation (or one produced by `heap_box` on the same
123    /// `T`). Callers also vouch that the parallel `NativeKind` they have for
124    /// this slot is consistent with `T` per the §2.7.5 stamp.
125    #[inline]
126    pub unsafe fn from_heap_bits(bits: u64) -> &'static Self {
127        let ptr = bits as *const Self;
128        debug_assert!(!ptr.is_null(), "UnifiedValue::from_heap_bits: null pointer");
129        unsafe { &*ptr }
130    }
131
132    /// # Safety
133    /// Same as `from_heap_bits`, plus exclusive access must be guaranteed.
134    #[inline]
135    pub unsafe fn from_heap_bits_mut(bits: u64) -> &'static mut Self {
136        let ptr = bits as *mut Self;
137        debug_assert!(
138            !ptr.is_null(),
139            "UnifiedValue::from_heap_bits_mut: null pointer"
140        );
141        unsafe { &mut *ptr }
142    }
143
144    /// # Safety
145    /// Must only be called once per allocation. `bits` must be a
146    /// `Box::into_raw`-returned pointer to a live `UnifiedValue<T>`.
147    #[inline]
148    pub unsafe fn heap_drop(bits: u64) {
149        let ptr = bits as *mut Self;
150        unsafe { drop(Box::from_raw(ptr)) };
151    }
152}
153
154/// Allocate a `UnifiedValue<T>` on the heap and return the raw pointer cast
155/// to `u64`. Companion `NativeKind` flows through the JIT-emitted call
156/// signature per §2.7.5.
157#[inline]
158pub fn unified_box<T>(kind: u16, data: T) -> u64 {
159    UnifiedValue::new(kind, data).heap_box()
160}
161
162/// Read a `&T` from a `UnifiedValue<T>` allocation pointed to by `bits`.
163///
164/// # Safety
165/// `bits` must be a `Box::into_raw`-returned pointer to a live `UnifiedValue<T>`
166/// allocation (or, equivalently, a `JitAlloc<T>` — both have `data` at
167/// offset 8 with the same `T` layout). The caller's parallel `NativeKind`
168/// must be consistent with `T` per §2.7.5.
169#[inline]
170pub unsafe fn unified_unbox<T>(bits: u64) -> &'static T {
171    &unsafe { UnifiedValue::<T>::from_heap_bits(bits) }.data
172}
173
174/// Read a `&mut T` from a `UnifiedValue<T>` allocation pointed to by `bits`.
175///
176/// # Safety
177/// Same as `unified_unbox`, plus exclusive access must be guaranteed.
178#[inline]
179pub unsafe fn unified_unbox_mut<T>(bits: u64) -> &'static mut T {
180    &mut unsafe { UnifiedValue::<T>::from_heap_bits_mut(bits) }.data
181}
182
183// ============================================================================
184// JitAlloc helpers
185// ============================================================================
186
187/// Allocate a `JitAlloc<T>` with `kind` prefix on the heap and return the raw
188/// pointer cast to `u64`.
189///
190/// Per §2.7.5 the JIT-FFI boundary carries this `u64` directly alongside a
191/// parallel `NativeKind` companion supplied from the call signature; the
192/// `kind: u16` field at offset 0 is the per-allocation prefix discriminator
193/// readable via `read_heap_kind` (independent of the slot-level `NativeKind`).
194#[inline]
195pub fn jit_box<T>(kind: u16, data: T) -> u64 {
196    let alloc = Box::new(JitAlloc {
197        kind,
198        _pad: [0; 6],
199        data,
200    });
201    let ptr = Box::into_raw(alloc);
202    ptr as u64
203}
204
205/// Read the `kind: u16` discriminator at offset 0 of a `JitAlloc`- or
206/// `UnifiedValue`-prefixed allocation.
207///
208/// # Safety
209/// `bits` must be a non-null `Box::into_raw`-returned pointer to a live
210/// `JitAlloc<_>` / `UnifiedValue<_>` allocation. The first 2 bytes must be
211/// the `kind` prefix.
212#[inline]
213pub unsafe fn read_heap_kind(bits: u64) -> u16 {
214    let ptr = bits as *const u16;
215    unsafe { *ptr }
216}
217
218/// Get a reference to the data within a `JitAlloc<T>`.
219///
220/// The returned reference borrows from the heap allocation with an unbounded
221/// lifetime. Callers MUST either:
222/// - Use the reference only within the current scope (do not store it), OR
223/// - Immediately clone/copy the data if it needs to outlive the current call.
224///
225/// The reference is only valid as long as the `JitAlloc` has not been freed
226/// via `jit_drop`. Holding this reference across a `jit_drop` call on the
227/// same `bits` value is undefined behavior.
228///
229/// # Safety
230/// - `bits` must be a `Box::into_raw`-returned pointer to a live
231///   `JitAlloc<T>`.
232/// - The caller must not hold the returned reference past the lifetime of
233///   the allocation (i.e., must not use it after `jit_drop` is called).
234/// - The pointee must have been allocated as `JitAlloc<T>` (correct type).
235#[inline]
236pub unsafe fn jit_unbox<T>(bits: u64) -> &'static T {
237    let ptr = bits as *const JitAlloc<T>;
238    debug_assert!(!ptr.is_null(), "jit_unbox called with null payload pointer");
239    unsafe { &(*ptr).data }
240}
241
242/// Get a mutable reference to the data within a `JitAlloc<T>`.
243///
244/// Same safety requirements as `jit_unbox`, plus:
245/// - The caller must ensure exclusive access (no other references exist).
246///
247/// # Safety
248/// - `bits` must be a `Box::into_raw`-returned pointer to a live
249///   `JitAlloc<T>`.
250/// - No other references (mutable or shared) to the same allocation may exist.
251/// - The caller must not hold the returned reference past the lifetime of
252///   the allocation.
253#[inline]
254pub unsafe fn jit_unbox_mut<T>(bits: u64) -> &'static mut T {
255    let ptr = bits as *mut JitAlloc<T>;
256    debug_assert!(
257        !ptr.is_null(),
258        "jit_unbox_mut called with null payload pointer"
259    );
260    unsafe { &mut (*ptr).data }
261}
262
263/// Deallocate a `JitAlloc<T>`.
264///
265/// # Safety
266/// Must only be called once per allocation. `bits` must be a
267/// `Box::into_raw`-returned pointer to `JitAlloc<T>`.
268#[inline]
269pub unsafe fn jit_drop<T>(bits: u64) {
270    let ptr = bits as *mut JitAlloc<T>;
271    unsafe { drop(Box::from_raw(ptr)) };
272}