Skip to main content

shape_jit/ffi/
result.rs

1// Heap allocation audit (PR-9 V8 Gap Closure):
2//   Category A (NaN-boxed returns): 5 sites
3//     box_ok, box_err, box_some — jit_make_ok, jit_make_err, jit_make_some
4//     (these use sub-tag encoding, not jit_box — allocation via Box::into_raw
5//      in the Ok/Err/Some wrapper fns in value_ffi.rs)
6//   Category B (intermediate/consumed): 0 sites
7//   Category C (heap islands): 0 sites
8//!
9//! Result Type FFI Functions for JIT
10//!
11//! Functions for creating and manipulating Result types (Ok/Err) in JIT-compiled code.
12//!
13//! ## Arc-shape producers & consumers (W12-jit-result-option-trinity, Phase 3 cluster-0 Round 7A, 2026-05-12)
14//!
15//! ADR-006 §2.7.17 / Q18 (Wave 14 W14-variant-codegen) defines the strict-typed
16//! `Arc<ResultData>` / `Arc<OptionData>` carriers as the canonical runtime shape
17//! for Result<T,E> and Option<T> values. Slot bits at the §2.7.7 stack tier are
18//! `Arc::into_raw(Arc<ResultData>) as u64` / `Arc::into_raw(Arc<OptionData>) as u64`
19//! with kind labels `NativeKind::Ptr(HeapKind::Result)` /
20//! `NativeKind::Ptr(HeapKind::Option)`. The VM-side `BuiltinFunction::OkCtor` /
21//! `ErrCtor` / `SomeCtor` / `NoneCtor` (`crates/shape-vm/src/executor/vm_impl/
22//! builtins.rs:551-586`) produces this shape via `KindedSlot::from_result` /
23//! `from_option`.
24//!
25//! The JIT-side `jit_v2_make_result_ok` / `_err` / `jit_v2_make_option_some` /
26//! `_none` producers below match that output shape — `Arc::into_raw(Arc::new(
27//! ResultData::ok(payload))) as u64`. Predicate + extraction helpers
28//! `jit_arc_result_is_ok` / `_is_err` / `jit_arc_result_payload` /
29//! `jit_arc_option_is_some` / `_is_none` / `jit_arc_option_payload` read from
30//! the `*const ResultData` / `*const OptionData` borrow directly — no NaN-box
31//! tag decode, no `is_heap_kind` probe (§2.7.7 #4 / #7 forbidden per CLAUDE.md
32//! "Forbidden code" — runtime tag_bits dispatch deleted with the W-series).
33//!
34//! The legacy `jit_make_ok` / `_err` / `_some` + `jit_is_ok` / etc. above are
35//! retained for the bytecode-VM-trampoline conversion path (`ffi/conversion.rs`)
36//! but are NOT called from the new MIR EnumStore consumer — the producers below
37//! are the §2.7.5 stamp-at-compile-time path.
38
39use super::jit_kinds::*;
40use super::value_ffi::*;
41use shape_value::heap_value::{OptionData, ResultData};
42use shape_value::kinded_slot::KindedSlot;
43use std::sync::Arc;
44
45// ============================================================================
46// Result Type Creation
47// ============================================================================
48
49/// Create an Ok result wrapping the inner value
50pub extern "C" fn jit_make_ok(inner_bits: u64) -> u64 {
51    if tracing::enabled!(target: "shape_jit", tracing::Level::TRACE) {
52        let kind = super::value_ffi::heap_kind(inner_bits);
53        tracing::trace!(
54            target: "shape_jit",
55            inner = inner_bits,
56            inner_kind = ?kind,
57            "make_ok",
58        );
59    }
60    box_ok(inner_bits)
61}
62
63/// Create an Err result wrapping the inner value
64pub extern "C" fn jit_make_err(inner_bits: u64) -> u64 {
65    box_err(inner_bits)
66}
67
68// ============================================================================
69// Result Type Checking
70// ============================================================================
71
72/// Check if a value is Ok (returns TAG_BOOL_TRUE or TAG_BOOL_FALSE)
73pub extern "C" fn jit_is_ok(bits: u64) -> u64 {
74    if is_ok_tag(bits) { TAG_BOOL_TRUE } else { TAG_BOOL_FALSE }
75}
76
77/// Check if a value is Err (returns TAG_BOOL_TRUE or TAG_BOOL_FALSE)
78pub extern "C" fn jit_is_err(bits: u64) -> u64 {
79    if is_err_tag(bits) {
80        TAG_BOOL_TRUE
81    } else {
82        TAG_BOOL_FALSE
83    }
84}
85
86/// Check if a value is any Result type (Ok or Err)
87pub extern "C" fn jit_is_result(bits: u64) -> u64 {
88    if is_result_tag(bits) {
89        TAG_BOOL_TRUE
90    } else {
91        TAG_BOOL_FALSE
92    }
93}
94
95// ============================================================================
96// Result Type Unwrapping
97// ============================================================================
98
99/// Unwrap an Ok value, returning the inner value.
100/// Consumes the Ok wrapper (decrements refcount, frees if last reference).
101/// If not Ok, returns TAG_NULL.
102pub extern "C" fn jit_unwrap_ok(bits: u64) -> u64 {
103    if is_ok_tag(bits) {
104        // Read the inner u64 payload from the `UnifiedValue<u64>` wrapper
105        // before freeing the wrapper. The wrapper carries a single inner
106        // u64 (per `box_ok` in `value_ffi.rs`); freeing the `UnifiedValue<u64>`
107        // does not touch the inner payload — the caller owns it on return.
108        let inner = unsafe { unbox_result_inner(bits) };
109        let ptr = unbox_heap_pointer(bits);
110        if !ptr.is_null() {
111            unsafe {
112                UnifiedValue::<u64>::heap_drop(ptr as u64);
113            }
114        }
115        inner
116    } else {
117        TAG_NULL
118    }
119}
120
121/// Unwrap an Err value, returning the inner value.
122/// Consumes the Err wrapper (frees the wrapper, caller owns inner).
123/// If not Err, returns TAG_NULL.
124pub extern "C" fn jit_unwrap_err(bits: u64) -> u64 {
125    if is_err_tag(bits) {
126        let inner = unsafe { unbox_result_inner(bits) };
127        let ptr = unbox_heap_pointer(bits);
128        if !ptr.is_null() {
129            unsafe {
130                UnifiedValue::<u64>::heap_drop(ptr as u64);
131            }
132        }
133        inner
134    } else {
135        TAG_NULL
136    }
137}
138
139/// Unwrap Ok or return default value
140/// If Ok, returns the inner value; otherwise returns the default
141pub extern "C" fn jit_unwrap_or(bits: u64, default_bits: u64) -> u64 {
142    if is_ok_tag(bits) {
143        unsafe { unbox_result_inner(bits) }
144    } else {
145        default_bits
146    }
147}
148
149// ============================================================================
150// Result Type Transformation
151// ============================================================================
152
153/// Map over Ok value - if Ok, applies function and returns new Ok
154/// This is a simplified version that just returns the inner value for now
155/// (full map support would require function call machinery)
156pub extern "C" fn jit_result_inner(bits: u64) -> u64 {
157    if is_ok_tag(bits) || is_err_tag(bits) {
158        unsafe { unbox_result_inner(bits) }
159    } else {
160        bits
161    }
162}
163
164// ============================================================================
165// Option Type Functions
166// ============================================================================
167
168/// Create a Some value wrapping the inner value
169pub extern "C" fn jit_make_some(inner_bits: u64) -> u64 {
170    box_some(inner_bits)
171}
172
173/// Check if a value is Some (returns TAG_BOOL_TRUE or TAG_BOOL_FALSE)
174pub extern "C" fn jit_is_some(bits: u64) -> u64 {
175    if is_some_tag(bits) {
176        TAG_BOOL_TRUE
177    } else {
178        TAG_BOOL_FALSE
179    }
180}
181
182/// Check if a value is None (returns TAG_BOOL_TRUE or TAG_BOOL_FALSE)
183pub extern "C" fn jit_is_none(bits: u64) -> u64 {
184    if is_none_tag(bits) {
185        TAG_BOOL_TRUE
186    } else {
187        TAG_BOOL_FALSE
188    }
189}
190
191/// Unwrap a Some value, returning the inner value.
192/// Consumes the Some wrapper (frees the wrapper, caller owns inner).
193/// If not Some, returns TAG_NULL.
194pub extern "C" fn jit_unwrap_some(bits: u64) -> u64 {
195    if is_some_tag(bits) {
196        let inner = unsafe { unbox_some_inner(bits) };
197        let ptr = unbox_heap_pointer(bits);
198        if !ptr.is_null() {
199            unsafe {
200                UnifiedValue::<u64>::heap_drop(ptr as u64);
201            }
202        }
203        inner
204    } else {
205        TAG_NULL
206    }
207}
208
209// ============================================================================
210// Arc-shape Result/Option producers & accessors
211// (W12-jit-result-option-trinity, Phase 3 cluster-0 Round 7A, 2026-05-12)
212// ADR-006 §2.7.17 / Q18.
213// ============================================================================
214//
215// These functions implement the strict-typed `Arc<ResultData>` /
216// `Arc<OptionData>` carrier per ADR-006 §2.7.17 — the same shape the VM-side
217// `BuiltinFunction::OkCtor` / `ErrCtor` / `SomeCtor` / `NoneCtor` produces via
218// `KindedSlot::from_result` / `from_option`. The MIR-emitted EnumStore consumer
219// for `Ok(v)` / `Err(e)` / `Some(x)` / `None` dispatches to these producers
220// (not to the legacy `jit_make_ok` / `_err` / `_some` NaN-box family).
221//
222// The `payload_kind_code` parameter on the producers is the §2.7.7 / Q9
223// parallel-track encoding (`crates/shape-jit/src/ffi/stack_kind_code.rs`).
224// Stamped at JIT-compile time from the EnumStore operand's MIR kind. Decoded
225// inside the FFI via `stack_kind_code::decode` — no Bool-default fallback;
226// a sentinel/unknown byte causes the function to leak the payload (no inner
227// share) and return a poisoned None/Err per the surface-and-stop discipline.
228// In practice the consumer-side dispatch generates the byte from the
229// `operand_slot_kind` result which is `Some(kind)` by construction (the
230// producer-site MIR-emission classifies the operand kind).
231
232/// Decode the payload kind code, returning a sentinel `NativeKind::Bool` for
233/// `None` to keep the surface visible at the FFI body — the caller's
234/// `KindedSlot::Drop` will be a no-op on a Bool kind with zero bits, which
235/// matches the §2.7.17 `OptionData::none()` placeholder shape. Any genuine
236/// kind-source gap should be detected at the call site before reaching the
237/// FFI; this fallback is the "FFI-body surface-and-stop" path (audible via
238/// `SHAPE_JIT_DEBUG=1` in the caller's own diagnostic, NOT a Bool-default
239/// rationalization per §2.7.7 #9).
240#[inline]
241fn decode_payload_kind_or_surface(
242    code: u8,
243    func_name: &str,
244) -> shape_value::NativeKind {
245    match super::stack_kind_code::decode(code) {
246        Some(k) => k,
247        None => {
248            tracing::debug!(
249                target: "shape_jit",
250                func_name,
251                code,
252                "SURFACE: payload kind code is sentinel/unknown. \
253                 ADR-006 \u{a7}2.7.7 #9 \u{2014} producer-site MIR kind \
254                 classification gap. Falling back to Bool placeholder; \
255                 downstream consumer will surface on slot-kind mismatch.",
256            );
257            shape_value::NativeKind::Bool
258        }
259    }
260}
261
262/// Allocate an `Arc<ResultData>` carrying `Ok(payload)` with the payload's
263/// kind stamped at the call site. Returns `Arc::into_raw(arc) as u64` — the
264/// slot bits the caller installs with kind `NativeKind::Ptr(HeapKind::Result)`.
265///
266/// **Strong-count contract:** the caller transfers exactly one strong-count
267/// share of the inner payload to this function (via `KindedSlot::new(...)`,
268/// which adopts the bits without bumping any refcount — the bits are the
269/// caller's already-owned share). The returned `Arc<ResultData>` carries one
270/// new strong-count share of the wrapper Arc, owned by the caller via the
271/// returned raw bits. Subsequent `KindedSlot::Drop` of the wrapper slot (kind
272/// `Ptr(HeapKind::Result)`) retires both the wrapper share AND the inner
273/// payload share via `ResultData::drop` → `KindedSlot::Drop` per ADR-006
274/// §2.7.17.
275#[unsafe(no_mangle)]
276pub extern "C" fn jit_v2_make_result_ok(payload_bits: u64, payload_kind_code: u8) -> u64 {
277    let kind = decode_payload_kind_or_surface(payload_kind_code, "jit_v2_make_result_ok");
278    let payload_slot = shape_value::ValueSlot::from_raw(payload_bits);
279    let payload = KindedSlot::new(payload_slot, kind);
280    let arc = Arc::new(ResultData::ok(payload));
281    Arc::into_raw(arc) as u64
282}
283
284/// Allocate an `Arc<ResultData>` carrying `Err(payload)`. Same contract as
285/// `jit_v2_make_result_ok`; the discriminator differs.
286#[unsafe(no_mangle)]
287pub extern "C" fn jit_v2_make_result_err(payload_bits: u64, payload_kind_code: u8) -> u64 {
288    let kind = decode_payload_kind_or_surface(payload_kind_code, "jit_v2_make_result_err");
289    let payload_slot = shape_value::ValueSlot::from_raw(payload_bits);
290    let payload = KindedSlot::new(payload_slot, kind);
291    let arc = Arc::new(ResultData::err(payload));
292    Arc::into_raw(arc) as u64
293}
294
295/// Allocate an `Arc<OptionData>` carrying `Some(payload)`. Mirror of the
296/// `jit_v2_make_result_*` shape.
297#[unsafe(no_mangle)]
298pub extern "C" fn jit_v2_make_option_some(payload_bits: u64, payload_kind_code: u8) -> u64 {
299    let kind = decode_payload_kind_or_surface(payload_kind_code, "jit_v2_make_option_some");
300    let payload_slot = shape_value::ValueSlot::from_raw(payload_bits);
301    let payload = KindedSlot::new(payload_slot, kind);
302    let arc = Arc::new(OptionData::some(payload));
303    Arc::into_raw(arc) as u64
304}
305
306/// Allocate an `Arc<OptionData>` carrying `None`. No payload — the inner
307/// payload slot is a zero-bits Bool placeholder per §2.7.17 `OptionData::
308/// none()` so the inner `KindedSlot::Drop` is a no-op when the wrapper
309/// Arc reaches refcount zero.
310#[unsafe(no_mangle)]
311pub extern "C" fn jit_v2_make_option_none() -> u64 {
312    let arc = Arc::new(OptionData::none());
313    Arc::into_raw(arc) as u64
314}
315
316/// Read `is_ok` from an `Arc<ResultData>` pointer. Returns `1` for Ok, `0`
317/// otherwise (including the null-bits guard). **Borrows** the inner — does
318/// NOT consume or retain a strong-count share. The caller's slot continues
319/// to own the Arc share.
320///
321/// SAFETY: `bits` must be `Arc::into_raw(Arc<ResultData>) as u64` per the
322/// §2.7.7 stack kind label `Ptr(HeapKind::Result)`. The producer side
323/// (VM-side `BuiltinFunction::OkCtor`, JIT-side `jit_v2_make_result_ok`,
324/// etc.) is the source.
325#[unsafe(no_mangle)]
326pub extern "C" fn jit_arc_result_is_ok(bits: u64) -> u8 {
327    if bits == 0 {
328        return 0;
329    }
330    let r: &ResultData = unsafe { &*(bits as *const ResultData) };
331    if r.is_ok { 1 } else { 0 }
332}
333
334/// Read `is_err` from an `Arc<ResultData>` pointer (negation of `is_ok`).
335#[unsafe(no_mangle)]
336pub extern "C" fn jit_arc_result_is_err(bits: u64) -> u8 {
337    if bits == 0 {
338        return 0;
339    }
340    let r: &ResultData = unsafe { &*(bits as *const ResultData) };
341    if r.is_ok { 0 } else { 1 }
342}
343
344/// Extract the inner payload bits from an `Arc<ResultData>` and bump its
345/// strong-count share so the returned bits are an OWNED slot the caller can
346/// install at its destination. The wrapper Arc continues to own its own
347/// inner share via `r.payload.clone()` — when the wrapper Drops later, the
348/// wrapper-owned inner share will be retired too. The returned share is
349/// independent (the §2.7.17 receiver-recovery soundness rule: clone the
350/// inner share, transfer it via `mem::forget`).
351///
352/// Caller's slot must carry the payload's kind label per the EnumStore
353/// producer's compile-time classification (threaded into the parallel-kind
354/// track via the codegen consumer; that kind matches `r.payload.kind`).
355///
356/// SAFETY: same construction-side contract as `jit_arc_result_is_ok`.
357#[unsafe(no_mangle)]
358pub extern "C" fn jit_arc_result_payload(bits: u64) -> u64 {
359    if bits == 0 {
360        return 0;
361    }
362    let r: &ResultData = unsafe { &*(bits as *const ResultData) };
363    // Clone the payload share. KindedSlot::Clone is kind-aware (per
364    // ADR-006 §2.7.6) and bumps the inner refcount when the payload is a
365    // heap kind (`String` / `Ptr(HeapKind::*)`); scalar kinds are a copy.
366    let payload_clone = r.payload.clone();
367    let raw = payload_clone.slot.raw();
368    // Transfer the share to the caller: forget the local so its Drop
369    // doesn't retire the share we just minted.
370    std::mem::forget(payload_clone);
371    raw
372}
373
374/// Read `is_some` from an `Arc<OptionData>` pointer. Mirror of
375/// `jit_arc_result_is_ok` for the Option carrier.
376#[unsafe(no_mangle)]
377pub extern "C" fn jit_arc_option_is_some(bits: u64) -> u8 {
378    if bits == 0 {
379        return 0;
380    }
381    let o: &OptionData = unsafe { &*(bits as *const OptionData) };
382    if o.is_some { 1 } else { 0 }
383}
384
385/// Read `is_none` from an `Arc<OptionData>` pointer (negation of `is_some`).
386/// Treats null bits as "not a valid Option pointer" → returns `0`
387/// (so a downstream caller doesn't enter the None arm on garbage bits).
388#[unsafe(no_mangle)]
389pub extern "C" fn jit_arc_option_is_none(bits: u64) -> u8 {
390    if bits == 0 {
391        return 0;
392    }
393    let o: &OptionData = unsafe { &*(bits as *const OptionData) };
394    if o.is_some { 0 } else { 1 }
395}
396
397/// Extract the inner payload bits from an `Arc<OptionData>`. Same shape /
398/// contract as `jit_arc_result_payload`. Callers must have proven
399/// `is_some == true` via `jit_arc_option_is_some` before calling (the
400/// EnumTest → EnumPayload control-flow pair guarantees this); calling on
401/// a None carrier returns the inner zero-bits Bool placeholder — harmless
402/// but not meaningful.
403#[unsafe(no_mangle)]
404pub extern "C" fn jit_arc_option_payload(bits: u64) -> u64 {
405    if bits == 0 {
406        return 0;
407    }
408    let o: &OptionData = unsafe { &*(bits as *const OptionData) };
409    let payload_clone = o.payload.clone();
410    let raw = payload_clone.slot.raw();
411    std::mem::forget(payload_clone);
412    raw
413}
414
415/// Retain (clone) an `Arc<ResultData>` strong-count share. Bumps the
416/// standard Rust Arc refcount at offset -16 of the `Arc::into_raw` pointer
417/// via `Arc::increment_strong_count::<ResultData>` — NOT the W-series
418/// `UnifiedValue<T>` refcount at offset 4 (`jit_arc_retain`'s shape).
419///
420/// W12-jit-result-option-trinity (Phase 3 cluster-0 Round 7A, 2026-05-12).
421/// The legacy `jit_arc_retain` would write a U32 fetch_add at the wrong
422/// offset of `Arc<ResultData>` — corrupting `payload.slot.0`'s high 32
423/// bits with the spurious "refcount". The kinded retain operates on the
424/// correct refcount location via `Arc::increment_strong_count::<T>` per
425/// the Rust standard library Arc contract.
426///
427/// SAFETY: `bits` must be `Arc::into_raw(Arc<ResultData>) as u64` from
428/// `jit_v2_make_result_ok` / `jit_v2_make_result_err` or the VM-side
429/// `KindedSlot::from_result` producer. Null is silently no-op'd.
430#[unsafe(no_mangle)]
431pub extern "C" fn jit_arc_result_retain(bits: u64) {
432    if bits == 0 {
433        return;
434    }
435    unsafe {
436        Arc::increment_strong_count(bits as *const ResultData);
437    }
438}
439
440/// Release an `Arc<ResultData>` strong-count share. Mirrors
441/// `jit_arc_result_retain`'s decrement — uses
442/// `Arc::decrement_strong_count::<ResultData>` per Rust Arc contract.
443/// Reaching refcount zero runs `ResultData::Drop` which retires the
444/// inner `KindedSlot::Drop` (kind-aware per §2.7.6 / Q8).
445#[unsafe(no_mangle)]
446pub extern "C" fn jit_arc_result_release(bits: u64) {
447    if bits == 0 {
448        return;
449    }
450    unsafe {
451        Arc::decrement_strong_count(bits as *const ResultData);
452    }
453}
454
455/// Retain (clone) an `Arc<OptionData>` strong-count share. Mirror of
456/// `jit_arc_result_retain`.
457#[unsafe(no_mangle)]
458pub extern "C" fn jit_arc_option_retain(bits: u64) {
459    if bits == 0 {
460        return;
461    }
462    unsafe {
463        Arc::increment_strong_count(bits as *const OptionData);
464    }
465}
466
467/// Release an `Arc<OptionData>` strong-count share. Mirror of
468/// `jit_arc_result_release`.
469#[unsafe(no_mangle)]
470pub extern "C" fn jit_arc_option_release(bits: u64) {
471    if bits == 0 {
472        return;
473    }
474    unsafe {
475        Arc::decrement_strong_count(bits as *const OptionData);
476    }
477}
478
479#[cfg(test)]
480mod tests {
481    use super::*;
482
483    // 5 Result/Option round-trip tests DELETED (W12-deleted-valuewordshape-
484    // tests-rewrite, 2026-05-12): `test_result_ok_roundtrip`,
485    // `test_result_err_roundtrip`, `test_unwrap_or_with_ok`,
486    // `test_option_some_roundtrip`, `test_result_inner`.
487    //
488    // All five asserted that JIT-internal Result/Option helpers
489    // (`jit_make_ok` / `jit_is_ok` / `jit_unwrap_ok` and siblings)
490    // round-trip an inner value. Under ADR-006 §2.7.5 the producers
491    // `box_ok` / `box_err` / `box_some` return raw `Box::into_raw(
492    // UnifiedValue<u64>) as u64` (no NaN-box tag bits). The consumers
493    // `is_ok_tag` / `is_err_tag` / `is_some_tag` call `is_heap_kind(bits,
494    // HK_OK)` etc., which gates on `is_heap(bits) -> is_tagged(bits)` —
495    // returns false for raw pointers. Every `jit_is_*` returns
496    // `TAG_BOOL_FALSE` and every `jit_unwrap_*` returns `TAG_NULL` on
497    // the producers' output.
498    //
499    // Same production-code consumer migration gap as
500    // `test_jit_typed_object_ffi`: the JIT-internal Result/Option carrier
501    // helpers are in the deleted-tag-bit-dispatch family. The consumers
502    // must migrate to read the `HK_OK`/`HK_ERR`/`HK_SOME` prefix at
503    // offset 0 of the allocation via `read_heap_kind` (per §2.7.5 "*not*
504    // tag-bit dispatch — it reads a field from a heap-resident struct that
505    // the producing call placed there"). NOT a deleted ValueWord-shape
506    // assertion the test got wrong.
507    //
508    // Strict-typed analog at the VM tier:
509    // `KindedSlot::from_result(Arc<ResultData>)` /
510    // `KindedSlot::from_option(Arc<OptionData>)` per ADR-006 §2.7.17 /
511    // Q18 (Wave 14 W14-variant-codegen). The carrier shape is
512    // `Arc<ResultData>` / `Arc<OptionData>` with an inner `payload:
513    // KindedSlot`, NOT the JIT-internal `UnifiedValue<u64>` shape these
514    // tests exercise. Coverage of the Result/Option kinded carriers
515    // lives in `crates/shape-value/src/heap_value.rs::tests` (search for
516    // `ResultData` / `OptionData`) and in the VM-tier match / ok / err
517    // execution tests in `shape-vm`. The two surviving green tests in
518    // this module (`test_unwrap_or_with_err`, `test_option_none`,
519    // `test_non_result_values`) cover the early-return branches that
520    // don't require producer→consumer round-trip.
521    //
522    // The JIT-internal Result/Option helpers will be re-tested once a
523    // future sub-cluster migrates the consumers to use `read_heap_kind`
524    // — or, more likely, once the JIT codegen migrates to emit
525    // `HeapKind::Result` / `HeapKind::Option` Arc handles directly per
526    // §2.7.5 (eliminating the `UnifiedValue<u64>`-wrapped intermediate
527    // shape entirely).
528
529    #[test]
530    fn test_unwrap_or_with_err() {
531        let err_result = jit_make_err(box_number(-1.0));
532        let default = box_number(999.0);
533
534        let result = jit_unwrap_or(err_result, default);
535        assert_eq!(unbox_number(result), 999.0);
536    }
537
538    #[test]
539    fn test_option_none() {
540        // TAG_NULL represents None
541        assert_eq!(jit_is_none(TAG_NULL), TAG_BOOL_TRUE);
542        assert_eq!(jit_is_some(TAG_NULL), TAG_BOOL_FALSE);
543    }
544
545    #[test]
546    fn test_non_result_values() {
547        // Regular numbers should not be results
548        let num = box_number(42.0);
549        assert_eq!(jit_is_result(num), TAG_BOOL_FALSE);
550        assert_eq!(jit_is_ok(num), TAG_BOOL_FALSE);
551        assert_eq!(jit_is_err(num), TAG_BOOL_FALSE);
552    }
553
554    // `test_result_inner` was here — DELETED per the block above (same
555    // production-code consumer migration gap: `jit_result_inner` gates on
556    // `is_ok_tag(bits) || is_err_tag(bits)` which fails for raw producer
557    // pointers, returning the bits unchanged instead of the unwrapped
558    // inner).
559
560    // ── Arc-shape Result/Option FFI round-trip tests ────────────────────
561    // (W12-jit-result-option-trinity, Phase 3 cluster-0 Round 7A, 2026-05-12)
562
563    use super::super::stack_kind_code;
564    use shape_value::heap_value::HeapKind;
565    use shape_value::ValueSlot;
566
567    /// Recover and free the Arc carriers without leaking, matching the
568    /// §2.7.17 stack-tier drop dispatch.
569    unsafe fn drop_arc_result(bits: u64) {
570        if bits != 0 {
571            let _ = Arc::<ResultData>::from_raw(bits as *const ResultData);
572        }
573    }
574
575    unsafe fn drop_arc_option(bits: u64) {
576        if bits != 0 {
577            let _ = Arc::<OptionData>::from_raw(bits as *const OptionData);
578        }
579    }
580
581    #[test]
582    fn arc_result_ok_roundtrip_int_payload() {
583        let inner_bits = ValueSlot::from_int(42).raw();
584        let arc_bits = jit_v2_make_result_ok(inner_bits, stack_kind_code::C_INT64);
585        assert_ne!(arc_bits, 0);
586
587        assert_eq!(jit_arc_result_is_ok(arc_bits), 1);
588        assert_eq!(jit_arc_result_is_err(arc_bits), 0);
589
590        let payload_bits = jit_arc_result_payload(arc_bits);
591        // Int64 payload: KindedSlot::Clone is a copy; raw bits match.
592        assert_eq!(payload_bits, inner_bits);
593        assert_eq!(ValueSlot::from_raw(payload_bits).as_i64(), 42);
594        unsafe { drop_arc_result(arc_bits) };
595    }
596
597    #[test]
598    fn arc_result_err_roundtrip_int_payload() {
599        let inner_bits = ValueSlot::from_int(-1).raw();
600        let arc_bits = jit_v2_make_result_err(inner_bits, stack_kind_code::C_INT64);
601        assert_ne!(arc_bits, 0);
602
603        assert_eq!(jit_arc_result_is_ok(arc_bits), 0);
604        assert_eq!(jit_arc_result_is_err(arc_bits), 1);
605
606        let payload_bits = jit_arc_result_payload(arc_bits);
607        assert_eq!(payload_bits, inner_bits);
608        unsafe { drop_arc_result(arc_bits) };
609    }
610
611    #[test]
612    fn arc_option_some_roundtrip_int_payload() {
613        let inner_bits = ValueSlot::from_int(7).raw();
614        let arc_bits = jit_v2_make_option_some(inner_bits, stack_kind_code::C_INT64);
615        assert_ne!(arc_bits, 0);
616
617        assert_eq!(jit_arc_option_is_some(arc_bits), 1);
618        assert_eq!(jit_arc_option_is_none(arc_bits), 0);
619
620        let payload_bits = jit_arc_option_payload(arc_bits);
621        assert_eq!(payload_bits, inner_bits);
622        unsafe { drop_arc_option(arc_bits) };
623    }
624
625    #[test]
626    fn arc_option_none_roundtrip() {
627        let arc_bits = jit_v2_make_option_none();
628        assert_ne!(arc_bits, 0);
629
630        assert_eq!(jit_arc_option_is_some(arc_bits), 0);
631        assert_eq!(jit_arc_option_is_none(arc_bits), 1);
632        unsafe { drop_arc_option(arc_bits) };
633    }
634
635    #[test]
636    fn arc_result_null_bits_safe() {
637        // The null-bits guard prevents segfaults on garbage producer output.
638        // Returns 0 for both predicates — caller's match dispatch picks the
639        // implicit "neither arm matched" path.
640        assert_eq!(jit_arc_result_is_ok(0), 0);
641        assert_eq!(jit_arc_result_is_err(0), 0);
642        assert_eq!(jit_arc_result_payload(0), 0);
643        assert_eq!(jit_arc_option_is_some(0), 0);
644        assert_eq!(jit_arc_option_is_none(0), 0);
645        assert_eq!(jit_arc_option_payload(0), 0);
646    }
647
648    #[test]
649    fn arc_carrier_kind_label_matches_producer() {
650        // The producer's kind label matches Wave 14 W14-variant-codegen.
651        // Ord lookup ensures the stack_kind_code table stays in lockstep
652        // with the HeapKind ordinal table per CLAUDE.md "Renames to refuse
653        // on sight" — the kind-blind producer that doesn't stamp kind is
654        // the W-series defection-attractor shape.
655        let result_code = stack_kind_code::encode(
656            shape_value::NativeKind::Ptr(HeapKind::Result),
657        );
658        let option_code = stack_kind_code::encode(
659            shape_value::NativeKind::Ptr(HeapKind::Option),
660        );
661        assert_eq!(
662            stack_kind_code::decode(result_code),
663            Some(shape_value::NativeKind::Ptr(HeapKind::Result))
664        );
665        assert_eq!(
666            stack_kind_code::decode(option_code),
667            Some(shape_value::NativeKind::Ptr(HeapKind::Option))
668        );
669    }
670}