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}