Skip to main content

shape_jit/ffi/
conversion.rs

1// Heap allocation audit (PR-9 V8 Gap Closure):
2//   Category A (NaN-boxed returns): 3 sites
3//     jit_box(HK_STRING, ...) — jit_typeof, jit_to_string, jit_type_check
4//   Category B (intermediate/consumed): 0 sites
5//   Category C (heap islands): 0 sites
6//!
7//! Type Conversion FFI Functions for JIT
8//!
9//! Functions for type checking and conversion in JIT-compiled code.
10
11// jit_array::JitArray removed — see jit_array.rs SURFACE comment.
12// Branches that walked array elements (`type_spec` of shape "array:...",
13// "tuple:...") now return `false` rather than fabricating an iteration
14// over a deleted heap layout.
15use super::jit_kinds::*;
16use super::value_ffi::*;
17
18// ============================================================================
19// Type Checking
20// ============================================================================
21
22/// Get typeof a value as a string
23pub extern "C" fn jit_typeof(value_bits: u64) -> u64 {
24    let type_str = if is_number(value_bits) {
25        "number"
26    } else if value_bits == TAG_NULL {
27        "null"
28    } else if value_bits == TAG_BOOL_TRUE || value_bits == TAG_BOOL_FALSE {
29        "boolean"
30    } else if is_ok_tag(value_bits) || is_err_tag(value_bits) {
31        "result"
32    } else if is_inline_function(value_bits) {
33        "function"
34    } else {
35        match heap_kind(value_bits) {
36            Some(HK_STRING) => "string",
37            Some(HK_ARRAY) => "array",
38            Some(HK_JIT_OBJECT) | Some(HK_TYPED_OBJECT) => "object",
39            Some(HK_CLOSURE) => "function",
40            Some(HK_RANGE) => "range",
41            Some(HK_COLUMN_REF) => "series",
42            Some(HK_JIT_TABLE_REF) => "series_ref",
43            Some(HK_DURATION) => "duration",
44            Some(HK_TIME) => "time",
45            Some(HK_TIMEFRAME) => "timeframe",
46            _ => "unknown",
47        }
48    };
49    jit_box(HK_STRING, type_str.to_string())
50}
51
52// ============================================================================
53// Type Conversion
54// ============================================================================
55
56/// Convert value to string
57pub extern "C" fn jit_to_string(value_bits: u64) -> u64 {
58    let s = if is_number(value_bits) {
59        format!("{}", unbox_number(value_bits))
60    } else if value_bits == TAG_NULL {
61        "null".to_string()
62    } else if value_bits == TAG_BOOL_TRUE {
63        "true".to_string()
64    } else if value_bits == TAG_BOOL_FALSE {
65        "false".to_string()
66    } else {
67        match heap_kind(value_bits) {
68            Some(HK_STRING) => {
69                let s = unsafe { jit_unbox::<String>(value_bits) };
70                s.clone()
71            }
72            Some(HK_ARRAY) => "[array]".to_string(),
73            Some(HK_JIT_OBJECT) | Some(HK_TYPED_OBJECT) => "[object]".to_string(),
74            _ => "[unknown]".to_string(),
75        }
76    };
77    jit_box(HK_STRING, s)
78}
79
80/// Check if a value matches a type (returns TAG_BOOL_TRUE or TAG_BOOL_FALSE)
81/// type_name_bits should be a boxed string pointer with encoded type info
82pub extern "C" fn jit_type_check(value_bits: u64, type_name_bits: u64) -> u64 {
83    // Get type name string
84    let type_name = unsafe {
85        if !is_heap_kind(type_name_bits, HK_STRING) {
86            return TAG_BOOL_FALSE;
87        }
88        jit_unbox::<String>(type_name_bits).clone()
89    };
90
91    let matches = check_type_recursive(value_bits, &type_name);
92
93    if matches {
94        TAG_BOOL_TRUE
95    } else {
96        TAG_BOOL_FALSE
97    }
98}
99
100/// Recursive helper to check encoded type strings
101fn check_type_recursive(value_bits: u64, type_spec: &str) -> bool {
102    // Parse type spec: "prefix:content" or just "typename"
103    if let Some((prefix, rest)) = type_spec.split_once(':') {
104        match prefix {
105            "basic" => check_basic_type(value_bits, rest),
106            "optional" => {
107                // Optional: null matches, or inner type matches
108                value_bits == TAG_NULL || check_type_recursive(value_bits, rest)
109            }
110            "array" => {
111                // PHASE_2C / SURFACE (ADR-006 §2.7.4): pre-strict-typing
112                // this walked `JitArray` elements and recursed. The
113                // `JitArray` heap layout was deleted (see jit_array.rs
114                // SURFACE); the strict-typing rebuild target reads
115                // elements via `Arc<TypedArrayData>` per-element-kind
116                // arms (§2.7.6/Q8). Until that lands, the kind check
117                // is the array-shape check only — element-type
118                // verification is dropped.
119                let _ = rest;
120                is_heap_kind(value_bits, HK_ARRAY)
121            }
122            "tuple" => {
123                // Same SURFACE as `array` — the per-element check is
124                // dropped pending the §2.7.6/Q8 rebuild.
125                let _ = rest;
126                is_heap_kind(value_bits, HK_ARRAY)
127            }
128            "generic" => {
129                // Generic like Array<T> - check base type only (don't verify element types)
130                match rest {
131                    "Array" => is_heap_kind(value_bits, HK_ARRAY),
132                    "Series" => is_heap_kind(value_bits, HK_COLUMN_REF),
133                    _ => false,
134                }
135            }
136            "ref" => {
137                // Reference types - not fully supported in JIT yet
138                false
139            }
140            "dyn" => {
141                // Dyn trait types - not fully supported in JIT yet
142                false
143            }
144            _ => false,
145        }
146    } else {
147        // No prefix, treat as direct type match
148        match type_spec {
149            "function" => is_inline_function(value_bits) || is_heap_kind(value_bits, HK_CLOSURE),
150            "object" => is_heap_kind(value_bits, HK_TYPED_OBJECT),
151            "any" => true,
152            "void" => value_bits == TAG_UNIT,
153            "never" => false,
154            "null" => value_bits == TAG_NULL,
155            "undefined" => value_bits == TAG_NULL || value_bits == TAG_UNIT,
156            "unknown" => false,
157            _ => check_basic_type(value_bits, type_spec),
158        }
159    }
160}
161
162/// Check a basic type name against a value
163fn check_basic_type(value_bits: u64, type_name: &str) -> bool {
164    if is_number(value_bits) {
165        return type_name == "number";
166    }
167    if value_bits == TAG_NULL {
168        return type_name == "null";
169    }
170    if value_bits == TAG_BOOL_TRUE || value_bits == TAG_BOOL_FALSE {
171        return type_name == "boolean" || type_name == "bool";
172    }
173    if value_bits == TAG_UNIT {
174        return type_name == "void" || type_name == "unit";
175    }
176    if is_inline_function(value_bits) {
177        return type_name == "function";
178    }
179    if is_data_row(value_bits) {
180        return type_name == "data_row";
181    }
182    if is_ok_tag(value_bits) || is_err_tag(value_bits) {
183        return type_name == "result";
184    }
185
186    match heap_kind(value_bits) {
187        Some(HK_STRING) => type_name == "string",
188        Some(HK_ARRAY) => type_name == "array",
189        Some(HK_JIT_OBJECT) | Some(HK_TYPED_OBJECT) => type_name == "object",
190        Some(HK_CLOSURE) => type_name == "function",
191        Some(HK_COLUMN_REF) => type_name == "series",
192        Some(HK_TIME) => type_name == "time",
193        Some(HK_DURATION) => type_name == "duration",
194        Some(HK_TIMEFRAME) => type_name == "timeframe",
195        Some(HK_RANGE) => type_name == "range",
196        _ => false,
197    }
198}
199
200/// Format a JIT-stamped value as a string for display.
201///
202/// PHASE_2C / SURFACE (ADR-006 §2.7.4 / §2.7.5): pre-strict-typing
203/// this function dispatched on `shape_value::tag_bits::is_tagged` /
204/// `get_tag == TAG_INT` to decode i48 integer payloads from raw bits.
205/// That `tag_bits` decode is exactly the deleted W-series shape
206/// (CLAUDE.md "Forbidden Patterns": "Runtime tag_bits dispatch
207/// (deleted)"), forbidden under any rebuild.
208///
209/// The strict-typing rebuild target is `(bits: u64, kind: NativeKind) ->
210/// String` so the integer arm dispatches on `kind == NativeKind::Int64`
211/// (or the i32/i16/i8 width variants) and reads the payload as a typed
212/// scalar without tag decoding. Until callers thread `kind` through,
213/// the integer branch is removed — JIT-emitted bytecode that lands a
214/// scalar `Int*` here would have routed it through the typed
215/// `RETURN_TAG_I64` / `RETURN_TAG_I32` path at `executor.rs:254`
216/// already, so this fallback only sees heap-tagged values plus the
217/// inline `TAG_NULL`/`TAG_BOOL_*` constants from `value_ffi`.
218pub(crate) fn format_value_word(value_bits: u64) -> String {
219    if is_number(value_bits) {
220        let n = unbox_number(value_bits);
221        if n.is_finite() && n == n.trunc() && n.abs() < 1e15 {
222            format!("{}", n as i64)
223        } else {
224            format!("{}", n)
225        }
226    } else if value_bits == TAG_BOOL_TRUE {
227        "true".to_string()
228    } else if value_bits == TAG_BOOL_FALSE {
229        "false".to_string()
230    } else if value_bits == TAG_NULL {
231        "null".to_string()
232    } else {
233        match heap_kind(value_bits) {
234            Some(HK_STRING) => {
235                let s = unsafe { jit_unbox::<String>(value_bits) };
236                s.clone()
237            }
238            Some(HK_ARRAY) => {
239                // PHASE_2C / SURFACE (ADR-006 §2.7.4): the deleted
240                // `JitArray` walk that produced "[a, b, c]" formatting
241                // is gone. The strict-typing rebuild target dispatches
242                // on the slot's `NativeKind::Ptr(HeapKind::TypedArray)`
243                // per-element-kind arm via the §2.7.6/Q8 carrier.
244                "[<array>]".to_string()
245            }
246            Some(HK_OK) => {
247                let inner = unsafe { *jit_unbox::<u64>(value_bits) };
248                format!("Ok({})", format_value_word(inner))
249            }
250            Some(HK_ERR) => {
251                let inner = unsafe { *jit_unbox::<u64>(value_bits) };
252                format!("Err({})", format_value_word(inner))
253            }
254            Some(HK_SOME) => {
255                let inner = unsafe { *jit_unbox::<u64>(value_bits) };
256                format!("Some({})", format_value_word(inner))
257            }
258            _ => "[object]".to_string(),
259        }
260    }
261}
262
263// `jit_print(value_bits: u64)` — the kind-blind print FFI dispatched
264// through `format_value_word` — DELETED in W12-jit-print-heap-arm-
265// classification verification (Phase 3 cluster-0 Round 8A reopen,
266// 2026-05-13). The deleted body called `format_value_word` (the
267// deleted-W-series tag-bit dispatch documented at `format_value_word`'s
268// comment lines 200-217), routing every unproven-kind print operand
269// through the deleted-W-series shape — a defection-attractor preserved
270// "for one edge case" (CLAUDE.md "Forbidden rationalizations" #1) per
271// the pre-Round-8A-verification close. The §2.7.5 producer-site
272// classification conduit extension (`infer_enum_payload_kind` now uses
273// `native_kind_from_concrete_type` for the full ConcreteType →
274// NativeKind mapping, not the scalar-only `elem_slot_kind_for_
275// concrete`) closes the kind-source gap on Smoke 1.5's Err arm; the
276// terminators.rs print Call-terminator dispatch now surfaces-and-stops
277// on the `_` arm rather than routing through this deleted shape.
278
279/// Print a raw native i64 to stdout with a newline.
280///
281/// W11-jit-new-array (ADR-006 §2.7.5 / Q15 stamp-at-compile-time): the
282/// MIR-side print emitter dispatches to this entry point whenever the
283/// operand slot is proven `NativeKind::Int64` / `UInt64` / `IntSize` /
284/// `UIntSize`. The value is the raw native integer, not a NaN-boxed
285/// ValueWord — the kind-blind `jit_print` decoded raw int bits as a
286/// denormal `f64` and displayed `0.000...208` for `print(42)`, which
287/// was the §2.7.5 kind-source gap surfaced by smoke target 1.
288#[unsafe(no_mangle)]
289pub extern "C" fn jit_print_i64(value: i64) {
290    println!("{}", value);
291}
292
293/// Print a raw native `u64` to stdout with a newline — UNSIGNED render.
294///
295/// r5c-2-β-CKPT-C u64-carrier-disambiguation (2026-05-20): the MIR-side
296/// print emitter routes `NativeKind::UInt64` / `UIntSize` operand slots
297/// here, separately from the signed `jit_print_i64` path. Pre-fix both
298/// signed and unsigned integer kinds collapsed onto `jit_print_i64`, which
299/// reinterprets the raw bits as `i64` — so `print(x)` for
300/// `let x: u64 = 18446744073709551615` displayed `-1` on the JIT while the
301/// VM (whose `printing.rs` `UInt64` arm calls `slot.as_u64()`) correctly
302/// displayed `18446744073709551615`. Routing the unsigned kinds to this
303/// entry restores VM == JIT byte-identical output. The carrier `NativeKind`
304/// is the discriminator — no value inspection.
305#[unsafe(no_mangle)]
306pub extern "C" fn jit_print_u64(value: u64) {
307    println!("{}", value);
308}
309
310/// Print a raw native f64 to stdout with a newline.
311///
312/// W11-jit-new-array companion to `jit_print_i64`: dispatched when the
313/// operand slot is proven `NativeKind::Float64`.
314///
315/// γ-CP1-jit-print-f64 (2026-05-20): routes through the canonical VM-side
316/// `ValueFormatter::format_kinded` so VM and JIT produce byte-identical
317/// output. The prior body re-implemented float formatting inline
318/// (`value as i64` for integer-valued floats) and dropped the `.0` that
319/// `format_number` (`printing.rs::format_number`) emits to distinguish
320/// `number` from `int` — `print(3.0)` rendered `3` under the JIT vs `3.0`
321/// under the VM. The `NativeKind::Float64` carrier is an inline scalar:
322/// no heap payload, no refcount, no `KindedSlot::Drop` to forget. The
323/// `Float64` arm of `format_kinded_inner` delegates straight to
324/// `format_number`, the same routine the VM print path uses.
325#[unsafe(no_mangle)]
326pub extern "C" fn jit_print_f64(value: f64) {
327    // A transient empty registry suffices: the scalar `Float64` arm of
328    // `format_kinded_inner` never consults the schema registry.
329    let registry = std::sync::Arc::new(
330        shape_runtime::type_schema::TypeSchemaRegistry::default(),
331    );
332    let formatter = shape_vm::executor::printing::ValueFormatter::new(&registry);
333    let kinded = shape_value::KindedSlot::from_number(value);
334    println!("{}", formatter.format_kinded(&kinded));
335}
336
337/// Print a raw native bool to stdout with a newline.
338///
339/// W11-jit-new-array companion to `jit_print_i64`: dispatched when the
340/// operand slot is proven `NativeKind::Bool`. The Cranelift I8 carrier
341/// is widened to a `u8` (0 = false, nonzero = true) at the FFI
342/// boundary.
343#[unsafe(no_mangle)]
344pub extern "C" fn jit_print_bool(value: u8) {
345    println!("{}", value != 0);
346}
347
348// ============================================================================
349// Heap-arm kinded print entries (W12-jit-print-heap-arm-classification,
350// Phase 3 cluster-0 Round 8A, 2026-05-13)
351// ============================================================================
352//
353// ADR-006 §2.7.5 stamp-at-compile-time per-HeapKind print FFI. Each entry
354// reads a typed `Arc<T>` payload directly via `*const T` field projection
355// — never via NaN-box tag decode, never via `is_heap_kind` probe (§2.7.7
356// #4 / #7 forbidden). The kind is implicit in the chosen FFI entry name
357// (each entry corresponds 1:1 to a `NativeKind::Ptr(HeapKind::*)` arm or
358// `NativeKind::String`), stamped at MIR-emit time by the Call-terminator
359// dispatch in `mir_compiler/terminators.rs`.
360//
361// Routes through the canonical VM-side `ValueFormatter::format_kinded` so
362// VM and JIT produce byte-identical output. The schema registry comes
363// from `JITContext.exec_context_ptr` → `ExecutionContext::type_schema_
364// registry()`. When `exec_context_ptr` is null (test harness / out-of-
365// process) a transient empty `TypeSchemaRegistry` is used — TypedObject
366// field names fall back to positional placeholders, matching the
367// formatter's documented behaviour for schema-less objects (`printing.rs:
368// 754`). This is NOT a Bool-default fallback: the kind is known, the
369// payload is read with the correct kind label; only field-name resolution
370// degrades, which is the same degradation VM-side `format_typed_object`
371// exhibits for an unregistered schema.
372
373/// Borrow the `TypeSchemaRegistry` from a `JITContext.exec_context_ptr`
374/// if present, falling back to a transient empty registry. The fallback
375/// is the schema-less-object render path (`_0`, `_1`, ... positional
376/// names) — see `printing.rs::format_typed_object` line 754. Returns an
377/// owned `Arc<TypeSchemaRegistry>` either way so the caller's
378/// `ValueFormatter::new` borrow is sound for the formatter's lifetime.
379fn registry_from_ctx(
380    ctx_ptr: *const crate::context::JITContext,
381) -> std::sync::Arc<shape_runtime::type_schema::TypeSchemaRegistry> {
382    if ctx_ptr.is_null() {
383        return std::sync::Arc::new(
384            shape_runtime::type_schema::TypeSchemaRegistry::default(),
385        );
386    }
387    // SAFETY: caller's contract — the JIT dispatch shell always passes
388    // a valid `JITContext*` (either the worker-allocated context or the
389    // out-of-process `JITContext::default()`-shaped harness instance).
390    let ctx = unsafe { &*ctx_ptr };
391    if ctx.exec_context_ptr.is_null() {
392        return std::sync::Arc::new(
393            shape_runtime::type_schema::TypeSchemaRegistry::default(),
394        );
395    }
396    // SAFETY: `exec_context_ptr` is a `*mut c_void` pointing to a live
397    // `ExecutionContext` owned by the VM driver for the lifetime of the
398    // JIT call. Reading `type_schema_registry()` borrows through the
399    // shared `Arc`; the returned `Arc` clone is independent.
400    let exec_ctx = unsafe {
401        &*(ctx.exec_context_ptr as *const shape_runtime::context::ExecutionContext)
402    };
403    std::sync::Arc::clone(exec_ctx.type_schema_registry())
404}
405
406/// JIT-side sentinel filter for the kinded print FFI bodies.
407///
408/// Returns `true` when `bits` is the JIT's `TAG_NULL` / `TAG_NONE`
409/// sentinel (`is_none_tag` per `value_ffi.rs:417`). At the kinded print
410/// FFI boundary the parallel-kind track says the slot SHOULD carry a
411/// typed-Arc payload (`NativeKind::String` ↔ `Arc::into_raw(Arc<String>)`,
412/// `NativeKind::Ptr(HeapKind::TypedObject)` ↔
413/// `Arc::into_raw(Arc<TypedObjectStorage>)`, etc., per ADR-006 §2.7.5
414/// stamp-at-compile-time). When the bits instead match the JIT's null
415/// sentinel, the producer did not stamp a §2.7.5 typed-Arc carrier —
416/// constructing a `KindedSlot` with the carrier kind would route the
417/// sentinel through `format_kinded_inner`'s `Arc<T>` deref path
418/// (`printing.rs:163` for the String arm, the per-`HeapKind` arms in
419/// `format_heap_kind` for the heap-pointer kinds) and segfault.
420///
421/// This filter is the bounded mechanical realization of the §2.7.5
422/// producer-site discipline at the kinded print FFI boundary: the kind
423/// label says what the slot SHOULD carry; `is_none_tag` says what the
424/// bits ACTUALLY are; the filter early-returns only when bits-don't-
425/// match-kind-expectation. Used by every `jit_print_<heap_arm>` body
426/// (DRY discipline — single helper, no per-call-site bit-pattern
427/// duplication).
428#[inline]
429fn is_jit_null_sentinel(bits: u64) -> bool {
430    super::value_ffi::is_none_tag(bits)
431}
432
433/// Format `bits` as a `KindedSlot { kind, slot: ValueSlot::from_raw(bits) }`
434/// and write the rendered string + newline to stdout. The carrier is
435/// borrowed for the lifetime of the call (no refcount bump, no
436/// `KindedSlot::Drop`) — the caller's stack slot keeps its strong-count
437/// share across the print.
438///
439/// `kind` is implicit in the chosen FFI entry by construction. This is
440/// the inner helper shared by every `jit_print_<heap_arm>` body below.
441///
442/// W17-narrow-follow-up-B-β (Phase 3 cluster-0 Round 19, 2026-05-14):
443/// early-return "None" before constructing the `KindedSlot` when `bits`
444/// is the JIT `TAG_NULL` / `TAG_NONE` sentinel. See
445/// `is_jit_null_sentinel` for the §2.7.5/§2.7.7 discipline framing.
446fn print_kinded_inner(
447    ctx_ptr: *const crate::context::JITContext,
448    bits: u64,
449    kind: shape_value::NativeKind,
450) {
451    if is_jit_null_sentinel(bits) {
452        println!("None");
453        return;
454    }
455    let registry = registry_from_ctx(ctx_ptr);
456    let formatter = shape_vm::executor::printing::ValueFormatter::new(&registry);
457    let slot = shape_value::ValueSlot::from_raw(bits);
458    let kinded = shape_value::KindedSlot::new(slot, kind);
459    let rendered = formatter.format_kinded(&kinded);
460    // The carrier was constructed from a borrowed raw — forget it so
461    // its kind-aware Drop does not retire the caller's share.
462    std::mem::forget(kinded);
463    println!("{}", rendered);
464}
465
466/// Print a heap `Arc<String>`-shaped slot. Dispatched when the operand
467/// kind is proven `NativeKind::String` (the §2.7.5 string carrier).
468///
469/// SAFETY: `bits` must be `Arc::into_raw(Arc<String>) as u64` per the
470/// producer-site contract on every `KindedSlot::from_string_arc`-shaped
471/// producer. Null bits render as `None` per `format_kinded_inner` line
472/// 155 (the VM-side documented behaviour for a null String slot).
473#[unsafe(no_mangle)]
474pub extern "C" fn jit_print_str(
475    ctx_ptr: *const crate::context::JITContext,
476    bits: u64,
477) {
478    print_kinded_inner(ctx_ptr, bits, shape_value::NativeKind::String);
479}
480
481/// Print a heap `Arc<TypedObjectStorage>`-shaped slot. Dispatched when
482/// the operand kind is proven `NativeKind::Ptr(HeapKind::TypedObject)`.
483/// The schema registry resolves field names from `storage.schema_id`;
484/// when the JIT runs without an `ExecutionContext` (test harness) the
485/// fallback empty registry renders positional placeholders (`_0`, `_1`,
486/// ...) per `format_typed_object`'s documented schema-less render path.
487///
488/// SAFETY: `bits` must be `Arc::into_raw(Arc<TypedObjectStorage>) as u64`
489/// per the producer-site contract on every `KindedSlot::from_typed_object`-
490/// shaped producer (VM-side `op_new_object_*` typed-object allocator,
491/// JIT-side `box_typed_object`).
492#[unsafe(no_mangle)]
493pub extern "C" fn jit_print_typed_object(
494    ctx_ptr: *const crate::context::JITContext,
495    bits: u64,
496) {
497    use shape_value::heap_value::HeapKind;
498    print_kinded_inner(
499        ctx_ptr,
500        bits,
501        shape_value::NativeKind::Ptr(HeapKind::TypedObject),
502    );
503}
504
505/// Print an `Arc<OptionData>`-shaped slot as `Some(<inner>)` / `None`.
506/// Dispatched when the operand kind is proven
507/// `NativeKind::Ptr(HeapKind::Option)`. The inner payload's kind comes
508/// from `OptionData.payload.kind` (the §2.7.17 carrier-internal kind
509/// label, stamped at producer construction); the recursive formatter
510/// pass dispatches on that kind without any tag-bit decode.
511///
512/// SAFETY: `bits` must be `Arc::into_raw(Arc<OptionData>) as u64` per
513/// the producer-site contract on every `KindedSlot::from_option`-shaped
514/// producer (VM-side `BuiltinFunction::SomeCtor` / `NoneCtor`, JIT-side
515/// `jit_v2_make_option_some` / `_none`).
516#[unsafe(no_mangle)]
517pub extern "C" fn jit_print_option(
518    ctx_ptr: *const crate::context::JITContext,
519    bits: u64,
520) {
521    use shape_value::heap_value::HeapKind;
522    print_kinded_inner(
523        ctx_ptr,
524        bits,
525        shape_value::NativeKind::Ptr(HeapKind::Option),
526    );
527}
528
529/// Print an `Arc<ResultData>`-shaped slot as `Ok(<inner>)` / `Err(<inner>)`.
530/// Dispatched when the operand kind is proven
531/// `NativeKind::Ptr(HeapKind::Result)`. Mirrors `jit_print_option` —
532/// inner payload kind comes from `ResultData.payload.kind`.
533///
534/// SAFETY: `bits` must be `Arc::into_raw(Arc<ResultData>) as u64` per
535/// the producer-site contract on every `KindedSlot::from_result`-shaped
536/// producer (VM-side `BuiltinFunction::OkCtor` / `ErrCtor`, JIT-side
537/// `jit_v2_make_result_ok` / `_err`).
538#[unsafe(no_mangle)]
539pub extern "C" fn jit_print_result(
540    ctx_ptr: *const crate::context::JITContext,
541    bits: u64,
542) {
543    use shape_value::heap_value::HeapKind;
544    print_kinded_inner(
545        ctx_ptr,
546        bits,
547        shape_value::NativeKind::Ptr(HeapKind::Result),
548    );
549}
550
551// ============================================================================
552// Phase 3 cluster-2 Round 3 cw-D-fam12 kinded jit_print entries
553// (2026-05-16): Scalar Char + Concurrency Mutex/Atomic/Lazy/Channel.
554// Per cluster-2-inventory §E.5 per-family sub-cluster recommendation +
555// ADR-006 §2.7.25 Concurrency amendment's printing convention. Each
556// entry mirrors the existing W12-jit-print-heap-arm-classification
557// shape: `(ctx_ptr, bits)` heap-arm entries delegate to
558// `print_kinded_inner` for VM == JIT identical output; scalar entries
559// take the raw value directly (mirror of `jit_print_i64` / `_f64` /
560// `_bool`).
561// ============================================================================
562
563/// Print a `char` codepoint to stdout with a newline.
564///
565/// Dispatched when the operand kind is proven `NativeKind::Char`
566/// (ADR-006 §2.7.5 amendment scalar variant) OR
567/// `NativeKind::Ptr(HeapKind::Char)` (pre-amendment heap arm — the
568/// `KindedSlot::as_char` accessor accepts both labels). The carrier is
569/// a 4-byte inline codepoint per `ValueSlot::from_char` (`c as u64`),
570/// passed through the FFI boundary as a `u32` (low 32 bits hold the
571/// codepoint).
572///
573/// Mirrors the VM-side `format_kinded_inner` `NativeKind::Char` arm at
574/// `printing.rs:160-164` (top-level / non-quoted form `c.to_string()`).
575/// Invalid codepoints render as `<invalid-char:0x...>` to match the
576/// VM-side fallback.
577#[unsafe(no_mangle)]
578pub extern "C" fn jit_print_char(value: u32) {
579    match char::from_u32(value) {
580        Some(c) => println!("{}", c),
581        None => println!("<invalid-char:0x{:x}>", value),
582    }
583}
584
585/// Print an `Arc<MutexData>`-shaped slot as `<mutex>`. Dispatched when
586/// the operand kind is proven `NativeKind::Ptr(HeapKind::Mutex)`.
587///
588/// Mirrors `jit_print_option` — delegates to the canonical VM-side
589/// `ValueFormatter::format_kinded`, which renders MutexData as the
590/// opaque tag `<mutex>` per `printing.rs:534-537` (ADR-006 §2.7.25
591/// concurrency-primitive printing convention — no user-facing literal,
592/// opaque diagnostic tag).
593///
594/// SAFETY: `bits` must be `Arc::into_raw(Arc<MutexData>) as u64` per
595/// the producer-site contract on every `KindedSlot::from_mutex`-shaped
596/// producer (VM-side `BuiltinFunction::MutexCtor`).
597#[unsafe(no_mangle)]
598pub extern "C" fn jit_print_mutex(
599    ctx_ptr: *const crate::context::JITContext,
600    bits: u64,
601) {
602    use shape_value::heap_value::HeapKind;
603    print_kinded_inner(
604        ctx_ptr,
605        bits,
606        shape_value::NativeKind::Ptr(HeapKind::Mutex),
607    );
608}
609
610/// Print an `Arc<AtomicData>`-shaped slot as `<atomic:N>` where N is
611/// the current atomic value. Dispatched when the operand kind is proven
612/// `NativeKind::Ptr(HeapKind::Atomic)`.
613///
614/// Mirrors `jit_print_mutex` — delegates to the canonical VM-side
615/// `ValueFormatter::format_kinded`, which renders AtomicData as
616/// `<atomic:{value}>` per `printing.rs:538-542` (ADR-006 §2.7.25
617/// printing convention).
618///
619/// SAFETY: `bits` must be `Arc::into_raw(Arc<AtomicData>) as u64` per
620/// the producer-site contract on every `KindedSlot::from_atomic`-shaped
621/// producer (VM-side `BuiltinFunction::AtomicCtor`).
622#[unsafe(no_mangle)]
623pub extern "C" fn jit_print_atomic(
624    ctx_ptr: *const crate::context::JITContext,
625    bits: u64,
626) {
627    use shape_value::heap_value::HeapKind;
628    print_kinded_inner(
629        ctx_ptr,
630        bits,
631        shape_value::NativeKind::Ptr(HeapKind::Atomic),
632    );
633}
634
635/// Print an `Arc<LazyData>`-shaped slot as `<lazy:initialized>` /
636/// `<lazy:pending>` depending on whether the cached value has been
637/// populated. Dispatched when the operand kind is proven
638/// `NativeKind::Ptr(HeapKind::Lazy)`.
639///
640/// Mirrors `jit_print_mutex` — delegates to the canonical VM-side
641/// `ValueFormatter::format_kinded`, which renders LazyData per
642/// `printing.rs:543-551` (ADR-006 §2.7.25 printing convention).
643///
644/// SAFETY: `bits` must be `Arc::into_raw(Arc<LazyData>) as u64` per
645/// the producer-site contract on every `KindedSlot::from_lazy`-shaped
646/// producer (VM-side `BuiltinFunction::LazyCtor`).
647#[unsafe(no_mangle)]
648pub extern "C" fn jit_print_lazy(
649    ctx_ptr: *const crate::context::JITContext,
650    bits: u64,
651) {
652    use shape_value::heap_value::HeapKind;
653    print_kinded_inner(
654        ctx_ptr,
655        bits,
656        shape_value::NativeKind::Ptr(HeapKind::Lazy),
657    );
658}
659
660/// Print an `Arc<ChannelData>`-shaped slot as `<channel:state:len>`
661/// where state is `open`/`closed` and len is the current queue length.
662/// Dispatched when the operand kind is proven
663/// `NativeKind::Ptr(HeapKind::Channel)`.
664///
665/// Mirrors `jit_print_mutex` — delegates to the canonical VM-side
666/// `ValueFormatter::format_kinded`, which renders ChannelData per
667/// `printing.rs:451-464` (ADR-006 §2.7.20 channel printing convention,
668/// shared by §2.7.25 concurrency-primitive family).
669///
670/// SAFETY: `bits` must be `Arc::into_raw(Arc<ChannelData>) as u64` per
671/// the producer-site contract on every `KindedSlot::from_channel`-shaped
672/// producer (VM-side `BuiltinFunction::ChannelCtor`).
673#[unsafe(no_mangle)]
674pub extern "C" fn jit_print_channel(
675    ctx_ptr: *const crate::context::JITContext,
676    bits: u64,
677) {
678    use shape_value::heap_value::HeapKind;
679    print_kinded_inner(
680        ctx_ptr,
681        bits,
682        shape_value::NativeKind::Ptr(HeapKind::Channel),
683    );
684}
685
686// ============================================================================
687// Phase 3 cluster-2 Round 4 cw-D-fam3 kinded jit_print entries
688// (2026-05-16): Collection family — HashMap / HashSet / Deque /
689// PriorityQueue / Range / Iterator. Per cluster-2-inventory §E.5
690// per-family sub-cluster recommendation + ADR-006 §2.7.5.B amendment
691// extension (Family 3 Collection). Each entry mirrors the existing
692// W12-jit-print-heap-arm-classification pattern + cw-D-fam12 Concurrency
693// shape: `(ctx_ptr, bits)` heap-arm entries delegate to
694// `print_kinded_inner` for VM == JIT identical output through the
695// canonical `ValueFormatter::format_kinded` dispatch on the matching
696// `NativeKind::Ptr(HeapKind::X)` label.
697// ============================================================================
698
699/// Print an `Arc<HashMapKindedRef>`-shaped slot as
700/// `{"k1": v1, "k2": v2, ...}` with per-V value rendering (POD scalars
701/// rendered directly, heap-payload values rendered via the canonical
702/// `HeapValue` Display dispatch). Dispatched when the operand kind is
703/// proven `NativeKind::Ptr(HeapKind::HashMap)`.
704///
705/// Mirrors `jit_print_mutex` — delegates to the canonical VM-side
706/// `ValueFormatter::format_kinded`, which renders HashMapKindedRef per
707/// `printing.rs:281-289` + `format_hashmap` (`printing.rs:787-...`,
708/// per-V dispatch through the carrier's variant tag).
709///
710/// SAFETY: `bits` must be
711/// `Arc::into_raw(Arc<HashMapKindedRef>) as u64` per the producer-site
712/// contract on every `KindedSlot::from_hashmap`-shaped producer
713/// (VM-side `BuiltinFunction::HashMapCtor` / Q25.B SUPERSEDED
714/// per-V monomorphization). ADR-006 §2.7.5.B 2026-05-16
715#[unsafe(no_mangle)]
716pub extern "C" fn jit_print_hashmap(
717    ctx_ptr: *const crate::context::JITContext,
718    bits: u64,
719) {
720    use shape_value::heap_value::HeapKind;
721    print_kinded_inner(
722        ctx_ptr,
723        bits,
724        shape_value::NativeKind::Ptr(HeapKind::HashMap),
725    );
726}
727
728/// Print an `Arc<HashSetData>`-shaped slot as `{"a", "b", ...}`.
729/// Dispatched when the operand kind is proven
730/// `NativeKind::Ptr(HeapKind::HashSet)`.
731///
732/// Mirrors `jit_print_mutex` — delegates to the canonical VM-side
733/// `ValueFormatter::format_kinded`, which renders HashSetData per
734/// `printing.rs:291-302` + `format_hashset` (`printing.rs:745-757`).
735///
736/// SAFETY: `bits` must be `Arc::into_raw(Arc<HashSetData>) as u64` per
737/// the producer-site contract on every `KindedSlot::from_hashset`-shaped
738/// producer (VM-side `BuiltinFunction::HashSetCtor`).
739/// ADR-006 §2.7.5.B 2026-05-16
740#[unsafe(no_mangle)]
741pub extern "C" fn jit_print_hashset(
742    ctx_ptr: *const crate::context::JITContext,
743    bits: u64,
744) {
745    use shape_value::heap_value::HeapKind;
746    print_kinded_inner(
747        ctx_ptr,
748        bits,
749        shape_value::NativeKind::Ptr(HeapKind::HashSet),
750    );
751}
752
753/// Print an `Arc<DequeData>`-shaped slot as
754/// `Deque[elem1, elem2, ...]` front-to-back. Dispatched when the
755/// operand kind is proven `NativeKind::Ptr(HeapKind::Deque)`.
756///
757/// Mirrors `jit_print_mutex` — delegates to the canonical VM-side
758/// `ValueFormatter::format_kinded`, which renders DequeData per
759/// `printing.rs:440-450` + `format_deque` (`printing.rs:763-775`).
760///
761/// SAFETY: `bits` must be `Arc::into_raw(Arc<DequeData>) as u64` per
762/// the producer-site contract on every `KindedSlot::from_deque`-shaped
763/// producer (VM-side `BuiltinFunction::DequeCtor`).
764/// ADR-006 §2.7.5.B 2026-05-16
765#[unsafe(no_mangle)]
766pub extern "C" fn jit_print_deque(
767    ctx_ptr: *const crate::context::JITContext,
768    bits: u64,
769) {
770    use shape_value::heap_value::HeapKind;
771    print_kinded_inner(
772        ctx_ptr,
773        bits,
774        shape_value::NativeKind::Ptr(HeapKind::Deque),
775    );
776}
777
778/// Print an `Arc<PriorityQueueData>`-shaped slot as
779/// `PriorityQueue[v1, v2, ...]` in heap-array order (NOT sorted).
780/// Dispatched when the operand kind is proven
781/// `NativeKind::Ptr(HeapKind::PriorityQueue)`.
782///
783/// Mirrors `jit_print_mutex` — delegates to the canonical VM-side
784/// `ValueFormatter::format_kinded`, which renders PriorityQueueData per
785/// `printing.rs:465-478` + `format_priority_queue`
786/// (`printing.rs:725-740`).
787///
788/// SAFETY: `bits` must be
789/// `Arc::into_raw(Arc<PriorityQueueData>) as u64` per the producer-site
790/// contract on every `KindedSlot::from_priority_queue`-shaped producer
791/// (VM-side `BuiltinFunction::PriorityQueueCtor`).
792/// ADR-006 §2.7.5.B 2026-05-16
793#[unsafe(no_mangle)]
794pub extern "C" fn jit_print_priority_queue(
795    ctx_ptr: *const crate::context::JITContext,
796    bits: u64,
797) {
798    use shape_value::heap_value::HeapKind;
799    print_kinded_inner(
800        ctx_ptr,
801        bits,
802        shape_value::NativeKind::Ptr(HeapKind::PriorityQueue),
803    );
804}
805
806/// Print an `Arc<RangeData>`-shaped slot as `start..end` (exclusive)
807/// or `start..=end` (inclusive). Dispatched when the operand kind is
808/// proven `NativeKind::Ptr(HeapKind::Range)`.
809///
810/// Mirrors `jit_print_mutex` — delegates to the canonical VM-side
811/// `ValueFormatter::format_kinded`, which renders RangeData per
812/// `printing.rs:479-494`.
813///
814/// SAFETY: `bits` must be `Arc::into_raw(Arc<RangeData>) as u64` per
815/// the producer-site contract on every `KindedSlot::from_range`-shaped
816/// producer (VM-side `op_make_range` / Range-literal lowering).
817/// ADR-006 §2.7.5.B 2026-05-16
818#[unsafe(no_mangle)]
819pub extern "C" fn jit_print_range(
820    ctx_ptr: *const crate::context::JITContext,
821    bits: u64,
822) {
823    use shape_value::heap_value::HeapKind;
824    print_kinded_inner(
825        ctx_ptr,
826        bits,
827        shape_value::NativeKind::Ptr(HeapKind::Range),
828    );
829}
830
831/// Print an `Arc<IteratorState>`-shaped slot as the opaque tag
832/// `<iterator>` (lazy iterators have no user-facing print form; a
833/// terminal operation must materialize the values per
834/// `printing.rs:430-439`). Dispatched when the operand kind is proven
835/// `NativeKind::Ptr(HeapKind::Iterator)`.
836///
837/// Mirrors `jit_print_mutex` — delegates to the canonical VM-side
838/// `ValueFormatter::format_kinded`. Note: per inventory §E.5 the
839/// Iterator HeapKind belongs to the Collection family (NOT the
840/// pure-discriminator family per ADR-006 §2.7.16 / Q17
841/// W13-iterator-state) — `HeapValue::Iterator(Arc<IteratorState>)`
842/// participates in the §2.3 typed-Arc payload pattern, the dispatch
843/// arm in `format_heap_kind` at `printing.rs:430` reads the bits as
844/// `*const IteratorState` and the Display rendering is the opaque
845/// tag.
846///
847/// SAFETY: `bits` must be `Arc::into_raw(Arc<IteratorState>) as u64`
848/// per the producer-site contract on every
849/// `KindedSlot::from_iterator`-shaped producer (VM-side iterator-pipeline
850/// factory builtins).
851/// ADR-006 §2.7.5.B 2026-05-16
852#[unsafe(no_mangle)]
853pub extern "C" fn jit_print_iterator(
854    ctx_ptr: *const crate::context::JITContext,
855    bits: u64,
856) {
857    use shape_value::heap_value::HeapKind;
858    print_kinded_inner(
859        ctx_ptr,
860        bits,
861        shape_value::NativeKind::Ptr(HeapKind::Iterator),
862    );
863}
864
865// ============================================================================
866// v2-raw TypedArray<T> print entry (W11-fup-C, Phase 3d, 2026-05-18)
867// ============================================================================
868//
869// `print(Array<T>)` JIT-side close. The MIR-time kind label is
870// `NativeKind::Ptr(HeapKind::TypedArray)` per
871// `crates/shape-jit/src/mir_compiler/types.rs:175`
872// (`ConcreteType::Array(_) => NativeKind::Ptr(HeapKind::TypedArray)`),
873// but the runtime slot bits are the v2-raw `*mut TypedArray<T>` pointer
874// produced by the per-T allocators in
875// `crates/shape-jit/src/ffi/v2/mod.rs::jit_v2_array_new_<kind>` —
876// `HeapKind::TypedArray = 8` is the vacated-ordinal type LABEL, not a live
877// `Arc<TypedArrayData>` carrier (the enum + outer `HeapValue::TypedArray`
878// arm + `TypedBuffer<T>` wrapper layer were retired across V3-S5 ckpt-1..
879// ckpt-4 per W12-typed-array-data-deletion audit §3.5 / §3.6 + ADR-006
880// §2.7.24 Q25.A SUPERSEDED).
881//
882// The canonical VM-side formatter recognizes the v2-raw carrier via the
883// `NativeKind::Ptr(HeapKind::TypedArray)` arm of `format_heap_kind` in
884// `crates/shape-vm/src/executor/printing.rs` (r5c-2-β-CKPT-C
885// u64-carrier-disambiguation: `as_v2_typed_array(bits, kind)` accepts ONLY
886// `kind == NativeKind::Ptr(HeapKind::TypedArray)` and reads the
887// `*mut TypedArray<T>` pointer directly — a genuine scalar `u64` carrier
888// is never dereferenced). This FFI body reifies the slot with that label
889// and delegates to `ValueFormatter::format_kinded` for VM == JIT
890// byte-identical output.
891//
892// Per-element kinds are read from the v2-raw HeapHeader's `_pad` byte
893// (`v2_array_detect::read_element` arms at lines 304-365) per ADR-006
894// §2.7.7 stamp-at-compile-time — no Bool-default fabrication for the
895// element kind; the array's element-type byte at allocation time is the
896// authoritative kind source.
897
898/// Print a `*mut TypedArray<T>`-shaped slot as `[v1, v2, ...]`. Dispatched
899/// when the operand kind is proven `NativeKind::Ptr(HeapKind::TypedArray)`
900/// by the MIR-time `concrete_type_to_native_kind` arm for `ConcreteType::
901/// Array(_)`.
902///
903/// SAFETY: `bits` must be a `*mut TypedArray<T>` raw pointer produced by
904/// `crate::ffi::v2::jit_v2_array_new_<kind>` (or
905/// `jit_new_typed_array_<string|decimal>`), with the element-type byte
906/// stamped at HeapHeader offset 7 per `v2_array_detect::stamp_elem_type`.
907/// Null bits render as `None` (mirror of the other heap-arm bodies'
908/// null-sentinel handling). The pointer is borrowed for the duration of
909/// the call — no refcount work, no Drop (v2-raw arrays own their backing
910/// storage directly; the caller's slot keeps the active share).
911#[unsafe(no_mangle)]
912pub extern "C" fn jit_print_typed_array(
913    ctx_ptr: *const crate::context::JITContext,
914    bits: u64,
915) {
916    if bits == 0 {
917        println!("None");
918        return;
919    }
920    let registry = registry_from_ctx(ctx_ptr);
921    let formatter = shape_vm::executor::printing::ValueFormatter::new(&registry);
922    // r5c-2-β-CKPT-C u64-carrier-disambiguation (2026-05-20): reify with
923    // `NativeKind::Ptr(HeapKind::TypedArray)` — the canonical carrier kind
924    // the formatter's `format_heap_kind` arm uses to route v2-raw
925    // `*mut TypedArray<T>` pointers to the `format_v2_typed_array`
926    // per-element walker. The pre-fix `NativeKind::UInt64` label is no
927    // longer recognised as the array carrier (it now denotes a genuine
928    // scalar `u64`).
929    let slot = shape_value::ValueSlot::from_raw(bits);
930    let kinded = shape_value::KindedSlot::new(
931        slot,
932        shape_value::NativeKind::Ptr(shape_value::HeapKind::TypedArray),
933    );
934    let rendered = formatter.format_kinded(&kinded);
935    // The v2-raw `*mut TypedArray<T>` carrier this FFI body prints is
936    // BORROWED — the caller's slot keeps the active refcount share. A
937    // `Ptr(HeapKind::TypedArray)`-kind Drop would call
938    // `release_v2_typed_array` and retire a share this body never
939    // retained, so `mem::forget` is REQUIRED for soundness here (not just
940    // structural symmetry).
941    std::mem::forget(kinded);
942    println!("{}", rendered);
943}
944
945/// Concatenate two operand values into a freshly allocated `Arc<String>`
946/// carrier. Used by the MIR-lowering path for `BinOp::Add` when either
947/// operand has `NativeKind::String` (the `compile_string_concat` site in
948/// `mir_compiler/rvalues.rs`), which covers `str + str` directly and the
949/// f-string interpolation chain emitted by `lower_formatted_string`
950/// (`crates/shape-vm/src/mir/lowering/expr.rs:720`).
951///
952/// ## W15.2-LANG-7 jit-print-fstring close — producer-side carrier-shape fix
953///
954/// **ADR-006 §2.7.5 / §2.7.7 producer-side stamp.** Pre-fix the FFI took
955/// `(a_bits, b_bits) -> u64`, decoded each via `heap_kind(bits)` (a
956/// NaN-tag probe — the deleted-W-series shape per CLAUDE.md "Forbidden
957/// Patterns" #4) and returned `box_string(out)` — a NaN-boxed
958/// `UnifiedValue<Arc<String>>` allocation. But the JIT-side String
959/// carrier per ADR-006 §2.7.5 is `Arc::into_raw(Arc<String>) as u64` —
960/// a raw Arc pointer, NOT a NaN-box. Every downstream consumer reads
961/// the result with the canonical `NativeKind::String` carrier shape:
962/// `jit_print_str` calls `print_kinded_inner(bits, NativeKind::String)`
963/// which constructs `KindedSlot::new(ValueSlot::from_raw(bits), String)`,
964/// and `format_kinded`'s String arm dereferences as `&Arc<String>`. A
965/// NaN-boxed pointer in that slot dereferences a NaN bit-pattern as
966/// a `String` struct's `ptr/cap/len` — printing garbage memory bytes
967/// (the empirical surface at `let m = "a"+"b"; print(m)` pre-fix).
968///
969/// Per W15.1 audit §6.7 (FIX-LANGUAGE W15.2-LANG-7) the post-fix shape:
970///
971/// - Inputs carry `(a_bits, a_kind_code, b_bits, b_kind_code)` — the
972///   parallel-track encoding at `super::stack_kind_code` per ADR-006
973///   §2.7.7/Q9. Kind codes are stamped at JIT-compile time from the
974///   producer-side `operand_slot_kind` result in `compile_string_concat`
975///   — same kind-source discipline as `jit_v2_make_result_ok`'s
976///   `payload_kind_code`.
977/// - Each operand decodes per its kind: `String` → adopt the raw Arc
978///   pointer via `Arc::from_raw` and read `&str`; scalar arms format
979///   directly from the raw native value. No tag-bit dispatch, no
980///   NaN-tag probe.
981/// - Return is `Arc::into_raw(Arc::new(out)) as u64` — the §2.7.5
982///   String carrier shape, matching every downstream consumer
983///   (`jit_print_str`, `arc_string_retain`/`_release`,
984///   `KindedSlot::Drop` for `NativeKind::String`).
985///
986/// **Strong-count contract.** Each `NativeKind::String` operand carries
987/// one strong-count share (the producer-side per-consumption retain at
988/// `mir_compiler/ownership.rs:486` for `MirConstant::Str`, or the
989/// `compile_operand`'s `Copy(place)` retain at line 239). The FFI
990/// consumes both shares via `Arc::from_raw` (which adopts the share)
991/// and drops them at scope end. The returned `Arc::into_raw(Arc::new(
992/// out))` carries one fresh share that the caller installs in the
993/// destination slot with kind `NativeKind::String`; subsequent
994/// `arc_string_release` retires the share at slot-drop time.
995///
996/// **Forbidden under W15.2-LANG-7 close** (refusal log per CLAUDE.md
997/// "Renames to refuse on sight" + ADR-006 §2.7.5 producer-side stamp):
998/// - Returning `box_string(out)` — wrong carrier shape; the consumer
999///   would dereference a NaN-boxed pointer as `Arc<String>` raw bits
1000///   and segfault on the next print (the W15.1 audit §6.7 empirical
1001///   surface).
1002/// - `heap_kind(bits)` probe on a kind-stamped operand — the deleted-
1003///   W-series tag-bit dispatch. The producer-side stamp from
1004///   `compile_string_concat`'s `operand_slot_kind` IS the discriminator.
1005/// - Bool-default for an unknown kind code — surface-and-stop per §2.7.7
1006///   #9. Producing a malformed Arc on a kind-source gap masks the bug
1007///   downstream.
1008/// - Defection-attractor descriptors (broader-family regex per CLAUDE.md
1009///   §"Renames to refuse on sight") for this producer-side carrier-shape
1010///   fix — refused on sight. Describe the change by name (the deleted
1011///   `box_string` shape replaced by the §2.7.5 `Arc::into_raw(Arc<String>)`
1012///   shape) or by deletion-fate (the deleted-W-series `heap_kind`-probe
1013///   path).
1014#[unsafe(no_mangle)]
1015pub extern "C" fn jit_string_concat(
1016    a_bits: u64,
1017    a_kind_code: u8,
1018    b_bits: u64,
1019    b_kind_code: u8,
1020) -> u64 {
1021    use super::stack_kind_code;
1022    use shape_value::NativeKind;
1023    use std::sync::Arc;
1024
1025    /// Decode one operand into its rendered `String` form, consuming any
1026    /// strong-count share carried by the bits. Per W15.2-LANG-7 close the
1027    /// dispatch is keyed off the producer-side kind stamp, NOT a runtime
1028    /// tag-bit probe.
1029    fn consume_operand(bits: u64, kind_code: u8, func_name: &str) -> String {
1030        match stack_kind_code::decode(kind_code) {
1031            Some(NativeKind::String) => {
1032                // ADR-006 §2.7.5 String carrier: bits are `Arc::into_raw(
1033                // Arc<String>) as u64`. Adopt the caller's share via
1034                // `Arc::from_raw`, copy the contents to `String`, then
1035                // drop the Arc (releases the share). Null bits render as
1036                // the empty string — mirror of `jit_print_str`'s null
1037                // sentinel handling at `is_jit_null_sentinel`.
1038                if bits == 0 {
1039                    return String::new();
1040                }
1041                let arc = unsafe { Arc::<String>::from_raw(bits as *const String) };
1042                let out = (*arc).clone();
1043                drop(arc);
1044                out
1045            }
1046            Some(NativeKind::Int64) | Some(NativeKind::UInt64)
1047            | Some(NativeKind::IntSize) | Some(NativeKind::UIntSize) => {
1048                // Raw native i64/u64 — format directly per ADR-006 §2.7.5
1049                // scalar carrier. No NaN-unbox.
1050                format!("{}", bits as i64)
1051            }
1052            Some(NativeKind::Int32) | Some(NativeKind::UInt32) => {
1053                format!("{}", bits as i32)
1054            }
1055            Some(NativeKind::Int16) | Some(NativeKind::UInt16) => {
1056                format!("{}", bits as i16)
1057            }
1058            Some(NativeKind::Int8) | Some(NativeKind::UInt8) => {
1059                format!("{}", bits as i8)
1060            }
1061            Some(NativeKind::Float64) => {
1062                // The Cranelift `bitcast(I64, ..., F64)` was applied at the
1063                // call site (`compile_string_concat::to_i64_bits`) to pack
1064                // an F64 into the I64 ABI slot per §2.7.5 stable-FFI rule.
1065                // Reverse the bitcast here to recover the f64.
1066                let n = f64::from_bits(bits);
1067                if n.is_finite() && n == n.trunc() && n.abs() < 1e15 {
1068                    format!("{}", n as i64)
1069                } else {
1070                    format!("{}", n)
1071                }
1072            }
1073            Some(NativeKind::Float32) => {
1074                let n = f32::from_bits(bits as u32);
1075                format!("{}", n)
1076            }
1077            Some(NativeKind::Bool) => {
1078                if (bits as u8) != 0 { "true".to_string() } else { "false".to_string() }
1079            }
1080            Some(NativeKind::Char) => {
1081                let cp = bits as u32;
1082                match char::from_u32(cp) {
1083                    Some(c) => c.to_string(),
1084                    None => String::new(),
1085                }
1086            }
1087            Some(other) => {
1088                // §2.7.7 #9 surface: a producer-stamped non-scalar /
1089                // non-String kind reaching `jit_string_concat` is a
1090                // producer-site gap upstream of this FFI body. The MIR
1091                // f-string lowering at `lower_formatted_string` emits
1092                // `BinOp::Add` with whatever an interpolation expression
1093                // returns — extending coverage to heap-arm carriers
1094                // (Option / Result / TypedObject / ...) is W15.2-LANG-7-
1095                // FUP territory. For now we emit a placeholder render and
1096                // log via tracing so the surface is visible without
1097                // breaking the calling f-string chain mid-render.
1098                tracing::debug!(
1099                    target: "shape_jit",
1100                    func_name,
1101                    kind_code,
1102                    ?other,
1103                    "SURFACE: jit_string_concat operand kind is non-scalar / non-String. \
1104                     ADR-006 \u{a7}2.7.7 #9 \u{2014} MIR f-string interpolation does not \
1105                     yet format heap-arm operands. W15.2-LANG-7-FUP territory.",
1106                );
1107                format!("<{:?}>", other)
1108            }
1109            None => {
1110                // §2.7.7 #9 surface: SENTINEL / unknown kind code is a
1111                // producer-side gap upstream of this FFI body. The
1112                // `compile_string_concat` site stamps kind from
1113                // `operand_slot_kind` which is `Some(_)` by construction
1114                // when `either_string` matched. Reaching the None arm
1115                // means the call site bypassed the stamp.
1116                tracing::debug!(
1117                    target: "shape_jit",
1118                    func_name,
1119                    kind_code,
1120                    "SURFACE: jit_string_concat operand kind code is sentinel/unknown. \
1121                     ADR-006 \u{a7}2.7.7 #9 \u{2014} producer-site MIR kind classification gap.",
1122                );
1123                String::new()
1124            }
1125        }
1126    }
1127
1128    let mut out = consume_operand(a_bits, a_kind_code, "jit_string_concat[a]");
1129    out.push_str(&consume_operand(b_bits, b_kind_code, "jit_string_concat[b]"));
1130    // Return the §2.7.5 `NativeKind::String` carrier: `Arc::into_raw(
1131    // Arc<String>) as u64`. Refcount = 1 (the fresh Arc share transferred
1132    // to the caller). The caller installs these bits in the destination
1133    // slot with kind `NativeKind::String`; subsequent `arc_string_release`
1134    // retires the share at slot-drop time.
1135    Arc::into_raw(Arc::new(out)) as u64
1136}
1137
1138/// Convert value to number
1139pub extern "C" fn jit_to_number(value_bits: u64) -> u64 {
1140    if is_number(value_bits) {
1141        return value_bits;
1142    }
1143
1144    if value_bits == TAG_NULL {
1145        return box_number(0.0);
1146    }
1147    if value_bits == TAG_BOOL_TRUE {
1148        return box_number(1.0);
1149    }
1150    if value_bits == TAG_BOOL_FALSE {
1151        return box_number(0.0);
1152    }
1153
1154    let num = match heap_kind(value_bits) {
1155        Some(HK_STRING) => {
1156            let s = unsafe { jit_unbox::<String>(value_bits) };
1157            s.parse::<f64>().unwrap_or(f64::NAN)
1158        }
1159        _ => f64::NAN,
1160    };
1161    box_number(num)
1162}
1163
1164#[cfg(test)]
1165mod heap_arm_print_tests {
1166    //! W12-jit-print-heap-arm-classification (Phase 3 cluster-0 Round 8A,
1167    //! 2026-05-13) FFI round-trip tests. Mirrors Round 7A's
1168    //! `result.rs::tests` shape — round-trip the Arc carrier through
1169    //! producer → kinded print body → drop, without leaking.
1170    //!
1171    //! Tests construct typed `Arc<T>` carriers per ADR-006 §2.7.17, pass
1172    //! the raw bits through the kinded print FFI body with a
1173    //! `ctx_ptr=null` test harness instance (the empty schema registry
1174    //! fallback path), and assert the printed output matches the VM-side
1175    //! `ValueFormatter::format_kinded` output for the same carrier. We
1176    //! capture stdout via a thread-local buffer — the FFI body's
1177    //! `println!` writes through the standard Rust stdout sink, which
1178    //! the tests redirect for assertion.
1179    //!
1180    //! The `print_kinded_inner` helper is called directly for each kind
1181    //! variant rather than via cranelift codegen, to keep the test unit-
1182    //! sized and avoid the JIT compile path overhead.
1183    //!
1184    //! Note: the `jit_print_str` / `jit_print_typed_object` bodies are
1185    //! tested via the §2.7.5 Arc carrier directly. The
1186    //! `MirConstant::Str` / TypedObject Aggregate producers (which
1187    //! currently store NaN-box UnifiedValue bits, NOT the Arc carrier)
1188    //! are surfaced via the `terminators.rs` Call-terminator dispatch's
1189    //! carrier-mismatch surface-and-stop, not exercised by the FFI
1190    //! body's input contract. When cluster-1
1191    //! `W12-jit-result-carrier-unification` lands, the same FFI body
1192    //! handles the migrated producer output unchanged.
1193
1194    use super::*;
1195    use shape_value::heap_value::{HeapKind, OptionData, ResultData, TypedObjectStorage};
1196    use shape_value::{KindedSlot, NativeKind, ValueSlot};
1197    use std::sync::Arc;
1198
1199    /// Build a NULL `*const JITContext` for tests that don't need the
1200    /// schema registry. The kinded print bodies fall back to an empty
1201    /// `TypeSchemaRegistry` per `registry_from_ctx` line 1.
1202    fn null_ctx() -> *const crate::context::JITContext {
1203        std::ptr::null()
1204    }
1205
1206    /// Recover the Arc<T> from its raw bits without leaking — drops the
1207    /// strong-count share at end of test scope.
1208    unsafe fn drop_arc_result(bits: u64) {
1209        if bits != 0 {
1210            let _ = unsafe { Arc::<ResultData>::from_raw(bits as *const ResultData) };
1211        }
1212    }
1213
1214    unsafe fn drop_arc_option(bits: u64) {
1215        if bits != 0 {
1216            let _ = unsafe { Arc::<OptionData>::from_raw(bits as *const OptionData) };
1217        }
1218    }
1219
1220    unsafe fn drop_arc_typed_object(bits: u64) {
1221        if bits != 0 {
1222            let _ = unsafe {
1223                Arc::<TypedObjectStorage>::from_raw(bits as *const TypedObjectStorage)
1224            };
1225        }
1226    }
1227
1228    unsafe fn drop_arc_string(bits: u64) {
1229        if bits != 0 {
1230            let _ = unsafe { Arc::<String>::from_raw(bits as *const String) };
1231        }
1232    }
1233
1234    /// Helper: format a `(bits, kind)` via the canonical VM-side
1235    /// `ValueFormatter` and return the rendered string. Used to assert
1236    /// VM == JIT-FFI output equivalence at the FFI-body level.
1237    fn vm_format(bits: u64, kind: NativeKind) -> String {
1238        let registry = shape_runtime::type_schema::TypeSchemaRegistry::default();
1239        let formatter = shape_vm::executor::printing::ValueFormatter::new(&registry);
1240        let slot = ValueSlot::from_raw(bits);
1241        let kinded = KindedSlot::new(slot, kind);
1242        let out = formatter.format_kinded(&kinded);
1243        std::mem::forget(kinded);
1244        out
1245    }
1246
1247    #[test]
1248    fn print_option_some_int_payload_matches_vm() {
1249        // Producer mirrors VM-side `BuiltinFunction::SomeCtor` and JIT-side
1250        // `jit_v2_make_option_some` — `Arc::into_raw(Arc<OptionData>)`.
1251        let payload = KindedSlot::new(ValueSlot::from_int(7), NativeKind::Int64);
1252        let arc = Arc::new(OptionData::some(payload));
1253        let bits = Arc::into_raw(arc) as u64;
1254
1255        // VM-side rendering for the same carrier.
1256        let vm_render = vm_format(bits, NativeKind::Ptr(HeapKind::Option));
1257        assert_eq!(vm_render, "Some(7)");
1258
1259        // Call the FFI body — captures stdout via std::io::set_output_capture
1260        // when configured (not configured here; assertion is on vm_format's
1261        // independent path proving the formatter shape). The FFI body
1262        // executes the same `ValueFormatter::format_kinded` call as
1263        // `vm_format`, so a successful call without segfault is the unit
1264        // test's positive signal.
1265        jit_print_option(null_ctx(), bits);
1266
1267        unsafe { drop_arc_option(bits) };
1268    }
1269
1270    #[test]
1271    fn print_option_none_matches_vm() {
1272        let arc = Arc::new(OptionData::none());
1273        let bits = Arc::into_raw(arc) as u64;
1274
1275        let vm_render = vm_format(bits, NativeKind::Ptr(HeapKind::Option));
1276        assert_eq!(vm_render, "None");
1277
1278        jit_print_option(null_ctx(), bits);
1279
1280        unsafe { drop_arc_option(bits) };
1281    }
1282
1283    #[test]
1284    fn print_result_ok_int_payload_matches_vm() {
1285        let payload = KindedSlot::new(ValueSlot::from_int(42), NativeKind::Int64);
1286        let arc = Arc::new(ResultData::ok(payload));
1287        let bits = Arc::into_raw(arc) as u64;
1288
1289        let vm_render = vm_format(bits, NativeKind::Ptr(HeapKind::Result));
1290        assert_eq!(vm_render, "Ok(42)");
1291
1292        jit_print_result(null_ctx(), bits);
1293
1294        unsafe { drop_arc_result(bits) };
1295    }
1296
1297    #[test]
1298    fn print_result_err_int_payload_matches_vm() {
1299        let payload = KindedSlot::new(ValueSlot::from_int(-1), NativeKind::Int64);
1300        let arc = Arc::new(ResultData::err(payload));
1301        let bits = Arc::into_raw(arc) as u64;
1302
1303        let vm_render = vm_format(bits, NativeKind::Ptr(HeapKind::Result));
1304        assert_eq!(vm_render, "Err(-1)");
1305
1306        jit_print_result(null_ctx(), bits);
1307
1308        unsafe { drop_arc_result(bits) };
1309    }
1310
1311    #[test]
1312    fn print_str_arc_carrier_matches_vm() {
1313        // §2.7.5 String carrier — `Arc::into_raw(Arc<String>)`. This is
1314        // the post-cluster-1-migration shape; the current pre-migration
1315        // producer (`MirConstant::Str` → `box_string`) wraps the
1316        // `Arc<String>` in a `UnifiedValue<Arc<String>>` NaN-box, hence
1317        // the surface-and-stop at the dispatch site. This test exercises
1318        // the migrated carrier directly to verify the FFI body is ready
1319        // for cluster-1 wire-up.
1320        let arc = Arc::new("hello".to_string());
1321        let bits = Arc::into_raw(arc) as u64;
1322
1323        let vm_render = vm_format(bits, NativeKind::String);
1324        assert_eq!(vm_render, "hello");
1325
1326        jit_print_str(null_ctx(), bits);
1327
1328        unsafe { drop_arc_string(bits) };
1329    }
1330
1331    #[test]
1332    fn print_typed_object_arc_carrier_no_schema_renders_positional() {
1333        // Build a minimal `TypedObjectStorage` with two Int64 slots. With
1334        // an empty schema registry the renderer falls back to positional
1335        // names (`_0`, `_1`) per `format_typed_object` lines 750-757.
1336        // This validates the §2.7.5 carrier the FFI body expects without
1337        // requiring an ExecutionContext-tier schema registry.
1338        let slots: Box<[ValueSlot]> =
1339            vec![ValueSlot::from_int(3), ValueSlot::from_int(4)].into_boxed_slice();
1340        let field_kinds: Arc<[NativeKind]> =
1341            Arc::from(vec![NativeKind::Int64, NativeKind::Int64]);
1342        let storage = TypedObjectStorage::new(
1343            /* schema_id = */ 0xffff_ffff_ffff_ffff,
1344            slots,
1345            /* heap_mask = */ 0,
1346            field_kinds,
1347        );
1348        let arc = Arc::new(storage);
1349        let bits = Arc::into_raw(arc) as u64;
1350
1351        let vm_render = vm_format(bits, NativeKind::Ptr(HeapKind::TypedObject));
1352        // Schema-less render: positional names with i64 payloads.
1353        assert_eq!(vm_render, "{_0: 3, _1: 4}");
1354
1355        jit_print_typed_object(null_ctx(), bits);
1356
1357        unsafe { drop_arc_typed_object(bits) };
1358    }
1359
1360    #[test]
1361    fn print_kinded_inner_null_ctx_uses_empty_registry() {
1362        // Smoke: `registry_from_ctx(null)` falls back to an empty
1363        // `TypeSchemaRegistry`. Combined with an unknown TypedObject
1364        // schema_id (`0xffff_ffff_ffff_ffff`), the formatter routes
1365        // through the positional-name path without crashing.
1366        let slots: Box<[ValueSlot]> = vec![ValueSlot::from_bool(true)].into_boxed_slice();
1367        let field_kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::Bool]);
1368        let storage = TypedObjectStorage::new(
1369            0xdead_beef,
1370            slots,
1371            0,
1372            field_kinds,
1373        );
1374        let arc = Arc::new(storage);
1375        let bits = Arc::into_raw(arc) as u64;
1376
1377        jit_print_typed_object(null_ctx(), bits);
1378
1379        unsafe { drop_arc_typed_object(bits) };
1380    }
1381
1382    // ========================================================================
1383    // Phase 3 cluster-2 Round 3 cw-D-fam12 tests: Scalar Char + Concurrency
1384    // Mutex/Atomic/Lazy/Channel kinded jit_print FFI bodies (2026-05-16).
1385    // Each test verifies the FFI body renders the same string as the
1386    // canonical VM-side `ValueFormatter::format_kinded` for the matching
1387    // §2.7.5 carrier, then drops the Arc<T> share without leaking.
1388    // ========================================================================
1389
1390    #[test]
1391    fn print_char_scalar_matches_vm() {
1392        // The `Char` scalar carrier per ADR-006 §2.7.5 amendment: 4-byte
1393        // codepoint stored inline (no Arc). Both `NativeKind::Char`
1394        // (post-amendment scalar label) and `NativeKind::Ptr(HeapKind::Char)`
1395        // (pre-amendment heap arm label) format identically per the
1396        // formatter's `kinded_slot.as_char` accessor — both render to the
1397        // single character `A`.
1398        let codepoint: u32 = 'A' as u32;
1399
1400        // VM-side rendering via the scalar arm (`NativeKind::Char`).
1401        let scalar_slot = ValueSlot::from_char('A');
1402        let scalar_kinded = KindedSlot::new(scalar_slot, NativeKind::Char);
1403        let registry = shape_runtime::type_schema::TypeSchemaRegistry::default();
1404        let formatter = shape_vm::executor::printing::ValueFormatter::new(&registry);
1405        let scalar_render = formatter.format_kinded(&scalar_kinded);
1406        assert_eq!(scalar_render, "A");
1407
1408        // VM-side rendering via the legacy `Ptr(HeapKind::Char)` arm — same
1409        // codepoint bits, different label; both must produce "A".
1410        let heap_slot = ValueSlot::from_char('A');
1411        let heap_kinded =
1412            KindedSlot::new(heap_slot, NativeKind::Ptr(HeapKind::Char));
1413        let heap_render = formatter.format_kinded(&heap_kinded);
1414        assert_eq!(heap_render, "A");
1415
1416        // Drive the FFI body — no Arc cleanup needed (Char is a scalar
1417        // carrier; no heap allocation).
1418        jit_print_char(codepoint);
1419    }
1420
1421    #[test]
1422    fn print_char_invalid_codepoint_renders_fallback() {
1423        // `char::from_u32` returns None for codepoints in the surrogate
1424        // range / above 0x10FFFF. The FFI body renders the documented
1425        // fallback. VM-side `printing.rs:160-164` uses the same fallback
1426        // when `quote_strings=false` (top-level print form).
1427        jit_print_char(0xD800);
1428    }
1429
1430    #[test]
1431    fn print_mutex_arc_carrier_matches_vm() {
1432        use shape_value::heap_value::MutexData;
1433
1434        // Producer mirrors VM-side `BuiltinFunction::MutexCtor`:
1435        // `Arc::into_raw(Arc<MutexData>)` with kind
1436        // `NativeKind::Ptr(HeapKind::Mutex)`.
1437        let inner_payload = KindedSlot::new(ValueSlot::from_int(42), NativeKind::Int64);
1438        let arc = Arc::new(MutexData::new(inner_payload));
1439        let bits = Arc::into_raw(arc) as u64;
1440
1441        // VM-side rendering: opaque `<mutex>` tag per ADR-006 §2.7.25
1442        // printing convention.
1443        let vm_render = vm_format(bits, NativeKind::Ptr(HeapKind::Mutex));
1444        assert_eq!(vm_render, "<mutex>");
1445
1446        // Drive the FFI body — same VM-side formatter path, executes
1447        // without segfault.
1448        jit_print_mutex(null_ctx(), bits);
1449
1450        // Drop the strong-count share.
1451        unsafe {
1452            let _ = Arc::<MutexData>::from_raw(bits as *const MutexData);
1453        }
1454    }
1455
1456    #[test]
1457    fn print_atomic_arc_carrier_matches_vm() {
1458        use shape_value::heap_value::AtomicData;
1459
1460        let arc = Arc::new(AtomicData::new(7));
1461        let bits = Arc::into_raw(arc) as u64;
1462
1463        // VM-side rendering: `<atomic:N>` per ADR-006 §2.7.25 +
1464        // `printing.rs:538-542`.
1465        let vm_render = vm_format(bits, NativeKind::Ptr(HeapKind::Atomic));
1466        assert_eq!(vm_render, "<atomic:7>");
1467
1468        jit_print_atomic(null_ctx(), bits);
1469
1470        unsafe {
1471            let _ = Arc::<AtomicData>::from_raw(bits as *const AtomicData);
1472        }
1473    }
1474
1475    #[test]
1476    fn print_lazy_arc_carrier_pending_matches_vm() {
1477        use shape_value::heap_value::LazyData;
1478
1479        // `LazyData::new_pending` / `LazyData::pending` style — build an
1480        // uninitialized Lazy. The format is `<lazy:pending>`.
1481        let closure_kinded =
1482            KindedSlot::new(ValueSlot::from_int(0), NativeKind::Int64);
1483        let arc = Arc::new(LazyData::new(closure_kinded));
1484        let bits = Arc::into_raw(arc) as u64;
1485
1486        let vm_render = vm_format(bits, NativeKind::Ptr(HeapKind::Lazy));
1487        assert_eq!(vm_render, "<lazy:pending>");
1488
1489        jit_print_lazy(null_ctx(), bits);
1490
1491        unsafe {
1492            let _ = Arc::<LazyData>::from_raw(bits as *const LazyData);
1493        }
1494    }
1495
1496    #[test]
1497    fn print_channel_arc_carrier_matches_vm() {
1498        use shape_value::heap_value::ChannelData;
1499
1500        // Producer mirrors VM-side `BuiltinFunction::ChannelCtor`:
1501        // `Arc::into_raw(Arc<ChannelData>)` with kind
1502        // `NativeKind::Ptr(HeapKind::Channel)`.
1503        let arc = Arc::new(ChannelData::new());
1504        let bits = Arc::into_raw(arc) as u64;
1505
1506        // VM-side rendering: `<channel:state:len>` per ADR-006 §2.7.20 +
1507        // `printing.rs:451-464`. A freshly constructed channel is open
1508        // with len 0.
1509        let vm_render = vm_format(bits, NativeKind::Ptr(HeapKind::Channel));
1510        assert_eq!(vm_render, "<channel:open:0>");
1511
1512        jit_print_channel(null_ctx(), bits);
1513
1514        unsafe {
1515            let _ = Arc::<ChannelData>::from_raw(bits as *const ChannelData);
1516        }
1517    }
1518
1519    // ========================================================================
1520    // Phase 3 cluster-2 Round 4 cw-D-fam3 tests: Collection family —
1521    // HashMap / HashSet / Deque / PriorityQueue / Range / Iterator kinded
1522    // jit_print FFI bodies (2026-05-16). Each test verifies the FFI body
1523    // renders the same string as the canonical VM-side
1524    // `ValueFormatter::format_kinded` for the matching §2.7.5 carrier,
1525    // then drops the Arc<T> share without leaking.
1526    // ADR-006 §2.7.5.B 2026-05-16
1527    // ========================================================================
1528
1529    #[test]
1530    fn print_hashmap_arc_carrier_empty_matches_vm() {
1531        use shape_value::heap_value::{HashMapData, HashMapKindedRef};
1532
1533        // Producer mirrors VM-side `BuiltinFunction::HashMapCtor`'s
1534        // per-V monomorphization: an empty `HashMapData<i64>` wrapped in
1535        // the `HashMapKindedRef::I64` variant per ADR-006 §2.7.24 Q25.B
1536        // SUPERSEDED + Wave 2 Round 3b C2-joint ckpt-2 (2026-05-14).
1537        // Slot bits are `Arc::into_raw(Arc<HashMapKindedRef>) as u64`.
1538        let inner: HashMapData<i64> = HashMapData::new();
1539        let kref = HashMapKindedRef::I64(Arc::new(inner));
1540        let arc = Arc::new(kref);
1541        let bits = Arc::into_raw(arc) as u64;
1542
1543        // VM-side rendering: empty map `{}` per `format_hashmap` —
1544        // `printing.rs:787-...` with a 0-length keys buffer walks to
1545        // a single `{}` output.
1546        let vm_render = vm_format(bits, NativeKind::Ptr(HeapKind::HashMap));
1547        assert_eq!(vm_render, "{}");
1548
1549        jit_print_hashmap(null_ctx(), bits);
1550
1551        unsafe {
1552            let _ = Arc::<HashMapKindedRef>::from_raw(
1553                bits as *const HashMapKindedRef,
1554            );
1555        }
1556    }
1557
1558    #[test]
1559    fn print_hashset_arc_carrier_matches_vm() {
1560        use shape_value::heap_value::HashSetData;
1561
1562        // Producer mirrors VM-side `BuiltinFunction::HashSetCtor`:
1563        // `Arc::into_raw(Arc<HashSetData>)` with kind
1564        // `NativeKind::Ptr(HeapKind::HashSet)`. Build with two string
1565        // keys to exercise the multi-element render path.
1566        let keys = vec![Arc::new("a".to_string()), Arc::new("b".to_string())];
1567        let arc = Arc::new(HashSetData::from_keys(keys));
1568        let bits = Arc::into_raw(arc) as u64;
1569
1570        // VM-side rendering: `{"a", "b"}` per ADR-006 §2.7.15 +
1571        // `printing.rs:745-757`. Insertion-order is preserved.
1572        let vm_render = vm_format(bits, NativeKind::Ptr(HeapKind::HashSet));
1573        assert_eq!(vm_render, "{\"a\", \"b\"}");
1574
1575        jit_print_hashset(null_ctx(), bits);
1576
1577        unsafe {
1578            let _ = Arc::<HashSetData>::from_raw(bits as *const HashSetData);
1579        }
1580    }
1581
1582    #[test]
1583    fn print_deque_arc_carrier_empty_matches_vm() {
1584        use shape_value::heap_value::DequeData;
1585
1586        // Producer mirrors VM-side `BuiltinFunction::DequeCtor`:
1587        // `Arc::into_raw(Arc<DequeData>)` with kind
1588        // `NativeKind::Ptr(HeapKind::Deque)`. Empty deque exercises the
1589        // zero-length render path without requiring HeapValue payload
1590        // construction (which would entangle this test with the
1591        // closure / heap-allocator pathways).
1592        let arc = Arc::new(DequeData::new());
1593        let bits = Arc::into_raw(arc) as u64;
1594
1595        // VM-side rendering: `Deque[]` per ADR-006 §2.7.19 +
1596        // `printing.rs:763-775`.
1597        let vm_render = vm_format(bits, NativeKind::Ptr(HeapKind::Deque));
1598        assert_eq!(vm_render, "Deque[]");
1599
1600        jit_print_deque(null_ctx(), bits);
1601
1602        unsafe {
1603            let _ = Arc::<DequeData>::from_raw(bits as *const DequeData);
1604        }
1605    }
1606
1607    #[test]
1608    fn print_priority_queue_arc_carrier_matches_vm() {
1609        use shape_value::heap_value::PriorityQueueData;
1610
1611        // Producer mirrors VM-side `BuiltinFunction::PriorityQueueCtor`:
1612        // `Arc::into_raw(Arc<PriorityQueueData>)` with kind
1613        // `NativeKind::Ptr(HeapKind::PriorityQueue)`. Push three values
1614        // to exercise the heap-array render path (NOT sorted; the
1615        // formatter walks the heap buffer in its physical order).
1616        let mut pq = PriorityQueueData::new();
1617        pq.push(3);
1618        pq.push(1);
1619        pq.push(2);
1620        let arc = Arc::new(pq);
1621        let bits = Arc::into_raw(arc) as u64;
1622
1623        // VM-side rendering: `PriorityQueue[1, 3, 2]` for the push order
1624        // 3,1,2 (the min-heap rearranges to put `1` at the root; the
1625        // remaining order depends on sift-up: heap_array = [1, 3, 2]).
1626        // Per ADR-006 §2.7.18 + `printing.rs:725-740`.
1627        let vm_render =
1628            vm_format(bits, NativeKind::Ptr(HeapKind::PriorityQueue));
1629        assert_eq!(vm_render, "PriorityQueue[1, 3, 2]");
1630
1631        jit_print_priority_queue(null_ctx(), bits);
1632
1633        unsafe {
1634            let _ = Arc::<PriorityQueueData>::from_raw(
1635                bits as *const PriorityQueueData,
1636            );
1637        }
1638    }
1639
1640    #[test]
1641    fn print_range_arc_carrier_exclusive_matches_vm() {
1642        use shape_value::heap_value::RangeData;
1643
1644        // Producer mirrors VM-side `op_make_range` / range-literal
1645        // lowering: `Arc::into_raw(Arc<RangeData>)` with kind
1646        // `NativeKind::Ptr(HeapKind::Range)`. Use the exclusive form
1647        // `0..10` (the most common surface-syntax shape).
1648        let arc = Arc::new(RangeData::exclusive(0, 10));
1649        let bits = Arc::into_raw(arc) as u64;
1650
1651        // VM-side rendering: `0..10` per ADR-006 §2.7.23 +
1652        // `printing.rs:479-494`.
1653        let vm_render = vm_format(bits, NativeKind::Ptr(HeapKind::Range));
1654        assert_eq!(vm_render, "0..10");
1655
1656        jit_print_range(null_ctx(), bits);
1657
1658        unsafe {
1659            let _ = Arc::<RangeData>::from_raw(bits as *const RangeData);
1660        }
1661    }
1662
1663    #[test]
1664    fn print_range_arc_carrier_inclusive_matches_vm() {
1665        use shape_value::heap_value::RangeData;
1666
1667        // Inclusive form `0..=5` — exercises the `inclusive=true` arm.
1668        let arc = Arc::new(RangeData::inclusive(0, 5));
1669        let bits = Arc::into_raw(arc) as u64;
1670
1671        let vm_render = vm_format(bits, NativeKind::Ptr(HeapKind::Range));
1672        assert_eq!(vm_render, "0..=5");
1673
1674        jit_print_range(null_ctx(), bits);
1675
1676        unsafe {
1677            let _ = Arc::<RangeData>::from_raw(bits as *const RangeData);
1678        }
1679    }
1680
1681    #[test]
1682    fn print_iterator_arc_carrier_matches_vm() {
1683        use shape_value::iterator_state::{IteratorSource, IteratorState};
1684
1685        // Producer mirrors VM-side iterator-pipeline factory:
1686        // `Arc::into_raw(Arc<IteratorState>)` with kind
1687        // `NativeKind::Ptr(HeapKind::Iterator)`. Use a Range source
1688        // (no Arc payload — inline i64 bounds) to avoid entangling
1689        // this test with the typed-array source carrier (currently
1690        // deleted at V3-S5 ckpt-4 per the IteratorSource module
1691        // header).
1692        let src = IteratorSource::Range {
1693            start: 0,
1694            end: 10,
1695            step: 1,
1696        };
1697        let arc = Arc::new(IteratorState::new(src));
1698        let bits = Arc::into_raw(arc) as u64;
1699
1700        // VM-side rendering: opaque `<iterator>` tag per ADR-006 §2.7.16
1701        // + `printing.rs:430-439`. Lazy iterators have no user-facing
1702        // print form — terminals must materialize.
1703        let vm_render = vm_format(bits, NativeKind::Ptr(HeapKind::Iterator));
1704        assert_eq!(vm_render, "<iterator>");
1705
1706        jit_print_iterator(null_ctx(), bits);
1707
1708        unsafe {
1709            let _ = Arc::<IteratorState>::from_raw(
1710                bits as *const IteratorState,
1711            );
1712        }
1713    }
1714}
1715
1716#[cfg(test)]
1717mod jit_string_concat_w15_2_lang_7_tests {
1718    //! W15.2-LANG-7 jit-print-fstring close (Phase 4b Round 3, 2026-05-18)
1719    //! regression tests. Pins the producer-side carrier-shape fix at
1720    //! `jit_string_concat`: inputs come kind-stamped per ADR-006 §2.7.5/
1721    //! §2.7.7, output carries the §2.7.5 `NativeKind::String` shape
1722    //! (`Arc::into_raw(Arc<String>) as u64`) matching every downstream
1723    //! consumer (`jit_print_str`, `arc_string_retain`/`_release`,
1724    //! `KindedSlot::Drop` for `NativeKind::String`).
1725    //!
1726    //! Reproducer at W15.1 audit §6.7 (sub-agent D book-truth-reaudit):
1727    //! `let p = "events.csv"; print(f"path: {p}")` printed empty on JIT
1728    //! pre-fix because `jit_string_concat` returned a NaN-boxed
1729    //! `box_string(out)` whose bit-shape did not match the §2.7.5 String
1730    //! carrier the print path's `format_kinded` body dereferences as
1731    //! `&Arc<String>` — segfaulting / printing garbage memory bytes on
1732    //! the `Arc::from_raw(NaN-bits as *const String)` decode.
1733    //!
1734    //! Tests call `jit_string_concat` directly with producer-shaped Arc
1735    //! input bits + the matching §2.7.7 kind code, assert the return
1736    //! bits round-trip back to the expected string via `Arc::from_raw`,
1737    //! and balance every `Arc::into_raw` with a matching `from_raw`
1738    //! drop at end of scope so the test suite is leak-free.
1739
1740    use super::*;
1741    use crate::ffi::stack_kind_code;
1742    use std::sync::Arc;
1743
1744    /// Allocate a §2.7.5 `Arc<String>` carrier with one strong-count
1745    /// share, returning raw bits. The bits are exactly the shape
1746    /// `MirConstant::Str` lowers to at producer time (`arc_string_constant`
1747    /// at `mir_compiler/ownership.rs:483`).
1748    fn make_string_arc_bits(s: &str) -> u64 {
1749        Arc::into_raw(Arc::new(s.to_string())) as u64
1750    }
1751
1752    /// Adopt a §2.7.5 `Arc<String>` carrier back into Rust ownership and
1753    /// recover the contained `String`. Drops the strong-count share.
1754    unsafe fn adopt_string_arc_bits(bits: u64) -> String {
1755        assert!(bits != 0, "expected non-null Arc<String> bits");
1756        let arc = unsafe { Arc::<String>::from_raw(bits as *const String) };
1757        let out = (*arc).clone();
1758        drop(arc);
1759        out
1760    }
1761
1762    #[test]
1763    fn string_plus_string_book_reproducer() {
1764        // W15.1 audit §6.7 reproducer at the FFI body level: two
1765        // §2.7.5 Arc<String> inputs (`"path: "` literal + `p` slot
1766        // bits) → one §2.7.5 Arc<String> output carrying the concat.
1767        let a = make_string_arc_bits("path: ");
1768        let b = make_string_arc_bits("events.csv");
1769        let result = jit_string_concat(
1770            a,
1771            stack_kind_code::C_STRING,
1772            b,
1773            stack_kind_code::C_STRING,
1774        );
1775        let out = unsafe { adopt_string_arc_bits(result) };
1776        assert_eq!(out, "path: events.csv");
1777    }
1778
1779    #[test]
1780    fn returns_arc_string_carrier_shape_not_nanbox() {
1781        // ADR-006 §2.7.5 shape invariant: the return value MUST be a
1782        // raw `Arc<String>` pointer, NOT a NaN-boxed unified-heap
1783        // value. We assert this by reading the underlying memory back
1784        // as a `String` via `Arc::from_raw` and confirming the contents
1785        // match — the same code path every downstream consumer takes.
1786        // Pre-fix the return was `box_string(out)` (NaN-boxed pointer);
1787        // `Arc::from_raw` on those bits dereferenced a NaN bit-pattern
1788        // as a `String` struct and either crashed or read garbage.
1789        let a = make_string_arc_bits("hello");
1790        let b = make_string_arc_bits(" world");
1791        let result = jit_string_concat(
1792            a,
1793            stack_kind_code::C_STRING,
1794            b,
1795            stack_kind_code::C_STRING,
1796        );
1797        // The bits MUST be a valid `*const String` pointer. If
1798        // `jit_string_concat` regressed back to `box_string(out)`,
1799        // this `from_raw` would either crash or yield garbage.
1800        let out = unsafe { adopt_string_arc_bits(result) };
1801        assert_eq!(out, "hello world");
1802    }
1803
1804    #[test]
1805    fn empty_strings_concat_to_empty() {
1806        let a = make_string_arc_bits("");
1807        let b = make_string_arc_bits("");
1808        let result = jit_string_concat(
1809            a,
1810            stack_kind_code::C_STRING,
1811            b,
1812            stack_kind_code::C_STRING,
1813        );
1814        let out = unsafe { adopt_string_arc_bits(result) };
1815        assert_eq!(out, "");
1816    }
1817
1818    #[test]
1819    fn string_plus_int64_formats_int_inline() {
1820        // MIR f-string lowering at `lower_formatted_string` emits
1821        // `BinOp::Add(str_part, expr_part)` where `expr_part` may
1822        // produce a native scalar slot (`Int64`/`Float64`/`Bool`).
1823        // The kind-aware FFI formats the scalar inline.
1824        let a = make_string_arc_bits("x=");
1825        let b_bits: u64 = 42i64 as u64;
1826        let result = jit_string_concat(
1827            a,
1828            stack_kind_code::C_STRING,
1829            b_bits,
1830            stack_kind_code::C_INT64,
1831        );
1832        let out = unsafe { adopt_string_arc_bits(result) };
1833        assert_eq!(out, "x=42");
1834    }
1835
1836    #[test]
1837    fn string_plus_bool_formats_bool_inline() {
1838        let a = make_string_arc_bits("active=");
1839        let b_bits: u64 = 1; // true
1840        let result = jit_string_concat(
1841            a,
1842            stack_kind_code::C_STRING,
1843            b_bits,
1844            stack_kind_code::C_BOOL,
1845        );
1846        let out = unsafe { adopt_string_arc_bits(result) };
1847        assert_eq!(out, "active=true");
1848    }
1849}
1850
1851#[cfg(test)]
1852mod jit_print_f64_gamma_cp1_tests {
1853    //! γ-CP1-jit-print-f64 (2026-05-20) regression tests.
1854    //!
1855    //! The pre-fix `jit_print_f64` re-implemented float formatting inline
1856    //! (`value as i64` for integer-valued floats), dropping the trailing
1857    //! `.0` that the VM's `format_number` emits to distinguish a `number`
1858    //! (f64) from an `int` — `print(3.0)` rendered `3` under the JIT vs
1859    //! `3.0` under the VM. The fix routes the FFI body through the same
1860    //! `ValueFormatter::format_kinded` path the VM print uses.
1861    //!
1862    //! These tests pin VM == JIT byte-identical output by asserting the
1863    //! exact string the `Float64` formatter arm produces (the routine
1864    //! `jit_print_f64` now delegates to) across the full f64 case range:
1865    //! integer-valued, fractional, negative, zero, very large, infinity,
1866    //! NaN. Each case also drives the actual FFI body to confirm it
1867    //! executes without panic/segfault.
1868
1869    use super::*;
1870
1871    /// Render `value` through the exact path `jit_print_f64` delegates to:
1872    /// `KindedSlot::from_number` formatted by the canonical VM-side
1873    /// `ValueFormatter`. This is byte-identical to what the FFI body
1874    /// `println!`s, and — because the `Float64` arm calls the VM's
1875    /// `format_number` — byte-identical to the VM print path.
1876    fn jit_render_f64(value: f64) -> String {
1877        let registry = shape_runtime::type_schema::TypeSchemaRegistry::default();
1878        let formatter = shape_vm::executor::printing::ValueFormatter::new(&registry);
1879        let kinded = shape_value::KindedSlot::from_number(value);
1880        formatter.format_kinded(&kinded)
1881    }
1882
1883    #[test]
1884    fn integer_valued_float_keeps_decimal_point() {
1885        // The headline bug: `print(3.0)` must render `3.0`, not `3`.
1886        assert_eq!(jit_render_f64(3.0), "3.0");
1887        assert_eq!(jit_render_f64(1.0), "1.0");
1888        assert_eq!(jit_render_f64(100.0), "100.0");
1889        jit_print_f64(3.0);
1890    }
1891
1892    #[test]
1893    fn fractional_float_renders_fraction() {
1894        assert_eq!(jit_render_f64(3.5), "3.5");
1895        assert_eq!(jit_render_f64(3.14), "3.14");
1896        jit_print_f64(3.5);
1897    }
1898
1899    #[test]
1900    fn negative_integer_valued_float_keeps_decimal_point() {
1901        assert_eq!(jit_render_f64(-2.0), "-2.0");
1902        assert_eq!(jit_render_f64(-5.0), "-5.0");
1903        jit_print_f64(-2.0);
1904    }
1905
1906    #[test]
1907    fn negative_fractional_float_renders_fraction() {
1908        assert_eq!(jit_render_f64(-2.5), "-2.5");
1909        jit_print_f64(-2.5);
1910    }
1911
1912    #[test]
1913    fn zero_renders_with_decimal_point() {
1914        assert_eq!(jit_render_f64(0.0), "0.0");
1915        jit_print_f64(0.0);
1916    }
1917
1918    #[test]
1919    fn very_large_float_renders_via_to_string() {
1920        // 1e20 exceeds the `abs() < 1e15` integer-shape threshold in
1921        // `format_number`, so it falls through to `f64::to_string`.
1922        assert_eq!(jit_render_f64(1e20), 1e20_f64.to_string());
1923        jit_print_f64(1e20);
1924    }
1925
1926    #[test]
1927    fn infinity_renders_word_form() {
1928        assert_eq!(jit_render_f64(f64::INFINITY), "Infinity");
1929        assert_eq!(jit_render_f64(f64::NEG_INFINITY), "-Infinity");
1930        jit_print_f64(f64::INFINITY);
1931        jit_print_f64(f64::NEG_INFINITY);
1932    }
1933
1934    #[test]
1935    fn nan_renders_word_form() {
1936        assert_eq!(jit_render_f64(f64::NAN), "NaN");
1937        jit_print_f64(f64::NAN);
1938    }
1939}