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(®istry);
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(®istry);
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(®istry);
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(®istry);
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(®istry);
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(®istry);
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}