Skip to main content

shape_jit/ffi/call_method/
mod.rs

1// Heap allocation audit (PR-9 V8 Gap Closure):
2//   Category A (NaN-boxed returns): 2 sites
3//     jit_box(HK_JIT_OBJECT, ...) — group/groupBy result object
4//     jit_box(HK_ARRAY, ...) — group values inside object
5//   Category B (intermediate/consumed): 0 sites
6//   Category C (heap islands): 1 site (group/groupBy)
7//!
8//! Method Call FFI Functions for JIT
9//!
10//! Dispatches method calls on various types (array, string, object, series, etc.)
11//! Split into type-specific helper modules for maintainability.
12
13use crate::context::JITContext;
14// crate::jit_array::JitArray removed — see jit_array.rs SURFACE comment.
15// Method dispatch on HK_ARRAY receivers surfaces per ADR-006 §2.7.4 /
16// W10 jit-playbook §5.
17use crate::ffi::jit_kinds::*;
18use crate::ffi::value_ffi::*;
19use shape_runtime::context::ExecutionContext;
20use shape_value::{HeapKind, NativeKind};
21use std::collections::HashMap;
22
23// Module declarations
24pub mod array;
25pub mod duration;
26pub mod matrix;
27pub mod number;
28pub mod object;
29pub mod result;
30pub mod string;
31pub mod time;
32
33// Re-export the individual method handlers
34pub use array::call_array_method;
35pub use duration::call_duration_method;
36pub use matrix::call_matrix_method;
37pub use number::call_number_method;
38pub use object::call_object_method;
39pub use result::call_result_method;
40pub use string::call_string_method;
41pub use time::call_time_method;
42
43// ============================================================================
44// User-Defined Method Support
45// ============================================================================
46
47/// Determine the type name of a JIT receiver value via kind-from-parallel-
48/// track dispatch (ADR-006 §2.7.5 / §2.7.7 / Q9, §2.7.9 / Q11, §2.7.10).
49///
50/// W17-narrow (Phase 3 cluster-0 Round 15, 2026-05-13): replaces the prior
51/// 5-arm NaN-box tag-bit cascade (`is_number` / `TAG_BOOL_*` / `TAG_NULL` /
52/// `heap_kind` match) with classification driven by the receiver's
53/// `NativeKind` companion popped from the §2.7.7 / Q9 parallel-kind track
54/// at `jit_call_method`'s dispatch entry (line 332-350). The prior tag-bit
55/// predicates all return wrong answers on raw `Box::into_raw` carriers
56/// because the §2.7.5 stamp-at-compile-time discipline removed the NaN-
57/// box tag wrap (empirically verified by the W17-narrow audit §6:
58/// `box_typed_object` returns `0x56c5…` with high bits clear, so
59/// `is_number()` returned true on every TypedObject receiver and dispatch
60/// fell through to `"number"`).
61///
62/// For `Ptr(HeapKind::TypedObject)` the schema id is recovered via a
63/// direct `(*ptr).schema_id` field read after unboxing the JIT-internal
64/// UnifiedValue prefix — same kind-from-parallel-track path the field-
65/// access fast path uses (`field_access.rs::jit_typed_object_get_field`).
66/// For UInt64-carrier opaque-bits receivers the inner kind discriminator
67/// is read directly from the heap-allocation prefix at offset 0 via
68/// `read_heap_kind` (a field-load on the JitAlloc / UnifiedValue prefix,
69/// NOT a tag-bit predicate on raw bits — §2.7.5 explicitly carves this
70/// out as "*not* tag-bit dispatch — it reads a field from a heap-resident
71/// struct that the producing call placed there").
72unsafe fn receiver_type_name(
73    receiver_bits: u64,
74    receiver_kind: NativeKind,
75    exec_ctx: &ExecutionContext,
76) -> Option<String> {
77    use crate::ffi::typed_object::jit_typed_object_schema_id;
78
79    match receiver_kind {
80        // Scalar kinds — fixed type names.
81        NativeKind::Float64
82        | NativeKind::NullableFloat64
83        | NativeKind::Int8
84        | NativeKind::NullableInt8
85        | NativeKind::UInt8
86        | NativeKind::NullableUInt8
87        | NativeKind::Int16
88        | NativeKind::NullableInt16
89        | NativeKind::UInt16
90        | NativeKind::NullableUInt16
91        | NativeKind::Int32
92        | NativeKind::NullableInt32
93        | NativeKind::UInt32
94        | NativeKind::NullableUInt32
95        | NativeKind::Int64
96        | NativeKind::NullableInt64
97        | NativeKind::NullableUInt64
98        | NativeKind::IntSize
99        | NativeKind::NullableIntSize
100        | NativeKind::UIntSize
101        | NativeKind::NullableUIntSize => Some("number".to_string()),
102        NativeKind::Bool => Some("bool".to_string()),
103        // R5b-2-bool-null-sentinel-cluster (ADR-006 §2.7 + §2.7.7/Q9,
104        // 2026-05-19): null receivers have no method dispatch; surface
105        // type-name as `"null"`.
106        NativeKind::Null => Some("null".to_string()),
107        NativeKind::String => Some("string".to_string()),
108        // Round 19 S1.5 W12-nativekind-scalar-additions (2026-05-14):
109        // ADR-006 §2.7.5 amendment adds F32 + Char as scalar variants.
110        // F32 receivers report as `"number"` (same fix-point as F64);
111        // Char receivers report as `"char"` (matches the existing
112        // `NativeKind::Ptr(HeapKind::Char)` arm below).
113        NativeKind::Float32 => Some("number".to_string()),
114        NativeKind::Char => Some("char".to_string()),
115        // Wave 2 Agent B W12-StringV2-DecimalV2-NativeKind-additions
116        // (2026-05-14): v2-raw heap-pointer carriers report the same
117        // type-name surface as their Arc-wrapped siblings.
118        NativeKind::StringV2 => Some("string".to_string()),
119        NativeKind::DecimalV2 => Some("decimal".to_string()),
120
121        // Typed heap pointer kinds — straight kind→name map per the
122        // surviving HeapKind discriminants.
123        NativeKind::Ptr(HeapKind::String) => Some("string".to_string()),
124        NativeKind::Ptr(HeapKind::TypedObject) => {
125            // Resolve the schema name via the JIT-internal TypedObject's
126            // `(*ptr).schema_id` field — `jit_typed_object_schema_id` is
127            // post-W17-narrow correct on raw `Box::into_raw` carriers
128            // (its prior `is_typed_object` gate was dropped in the same
129            // round). Schema lookup follows the same two-tier shape as
130            // `object/property_access.rs::HK_TYPED_OBJECT` (the W12-jit-
131            // binop-after-heap-read-kind-tracker close): try the global
132            // stdlib registry first, then fall back to the trampoline VM's
133            // bytecode program registry (where user-defined types like X
134            // live). Both halves are required because `ExecutionContext`'s
135            // direct registry only covers global stdlib schemas, not the
136            // per-program user-defined ones.
137            let schema_id = jit_typed_object_schema_id(receiver_bits);
138            if schema_id == 0 {
139                return None;
140            }
141            let global = shape_runtime::type_schema::lookup_schema_by_id_public(schema_id)
142                .map(|s| s.name.clone());
143            if global.is_some() {
144                return global;
145            }
146            let _ = exec_ctx;
147            super::control::with_trampoline_vm(|vm| {
148                vm.program()
149                    .type_schema_registry
150                    .get_by_id(schema_id)
151                    .map(|s| s.name.clone())
152            })
153            .flatten()
154        }
155        NativeKind::Ptr(HeapKind::TypedArray) => Some("Array".to_string()),
156        NativeKind::Ptr(HeapKind::Decimal) => Some("decimal".to_string()),
157        NativeKind::Ptr(HeapKind::BigInt) => Some("bigint".to_string()),
158        NativeKind::Ptr(HeapKind::DataTable) => Some("Table".to_string()),
159        NativeKind::Ptr(HeapKind::HashMap) => Some("HashMap".to_string()),
160        NativeKind::Ptr(HeapKind::HashSet) => Some("Set".to_string()),
161        NativeKind::Ptr(HeapKind::Future) => Some("Future".to_string()),
162        NativeKind::Ptr(HeapKind::TaskGroup) => Some("TaskGroup".to_string()),
163        NativeKind::Ptr(HeapKind::Closure) => Some("Closure".to_string()),
164        NativeKind::Ptr(HeapKind::Temporal) => Some("Temporal".to_string()),
165        NativeKind::Ptr(HeapKind::TableView) => Some("TableView".to_string()),
166        NativeKind::Ptr(HeapKind::Content) => Some("Content".to_string()),
167        NativeKind::Ptr(HeapKind::Instant) => Some("Instant".to_string()),
168        NativeKind::Ptr(HeapKind::IoHandle) => Some("IoHandle".to_string()),
169        NativeKind::Ptr(HeapKind::Char) => Some("char".to_string()),
170        NativeKind::Ptr(HeapKind::Iterator) => Some("Iterator".to_string()),
171        NativeKind::Ptr(HeapKind::Deque) => Some("Deque".to_string()),
172        NativeKind::Ptr(HeapKind::Channel) => Some("Channel".to_string()),
173        NativeKind::Ptr(HeapKind::PriorityQueue) => Some("PriorityQueue".to_string()),
174        NativeKind::Ptr(HeapKind::Range) => Some("Range".to_string()),
175        NativeKind::Ptr(HeapKind::Result) => Some("Result".to_string()),
176        NativeKind::Ptr(HeapKind::Option) => Some("Option".to_string()),
177        NativeKind::Ptr(HeapKind::TraitObject) => Some("TraitObject".to_string()),
178        NativeKind::Ptr(HeapKind::Mutex) => Some("Mutex".to_string()),
179        NativeKind::Ptr(HeapKind::Atomic) => Some("Atomic".to_string()),
180        NativeKind::Ptr(HeapKind::Lazy) => Some("Lazy".to_string()),
181        NativeKind::Ptr(HeapKind::ModuleFn) => Some("ModuleFn".to_string()),
182        // ADR-006 §2.7.22 amendment (Round 18 S3, 2026-05-13).
183        NativeKind::Ptr(HeapKind::Matrix) => Some("Matrix".to_string()),
184        NativeKind::Ptr(HeapKind::MatrixSlice) => Some("Vec<number>".to_string()),
185        // Pure-discriminator kinds with no method receiver shape — see
186        // ADR-006 §2.7.9 (FilterExpr), §2.7.12 (SharedCell), §2.7.13
187        // (Reference), §2.7.14 (NativeScalar / NativeView).
188        NativeKind::Ptr(HeapKind::FilterExpr)
189        | NativeKind::Ptr(HeapKind::Reference)
190        | NativeKind::Ptr(HeapKind::SharedCell)
191        | NativeKind::Ptr(HeapKind::NativeScalar)
192        | NativeKind::Ptr(HeapKind::NativeView) => None,
193
194        // UInt64 carrier — opaque JIT-format bits whose inner kind lives in
195        // the JitAlloc / UnifiedValue prefix at offset 0. Read the prefix
196        // via `read_heap_kind` (§2.7.5 "not tag-bit dispatch — field-load
197        // from a heap-resident struct"). The null-pointer check guards
198        // against UInt64-carrier callers that legitimately stamp a
199        // sentinel value (e.g. arg_count) — those don't reach this
200        // function in practice but the defensive null guard is cheap.
201        NativeKind::UInt64 => {
202            if receiver_bits == 0 || receiver_bits == TAG_NULL || receiver_bits == TAG_NONE {
203                return None;
204            }
205            match read_heap_kind(receiver_bits) {
206                HK_STRING => Some("string".to_string()),
207                HK_ARRAY => Some("Array".to_string()),
208                HK_TYPED_OBJECT => {
209                    let schema_id = jit_typed_object_schema_id(receiver_bits);
210                    if schema_id == 0 {
211                        return None;
212                    }
213                    let global = shape_runtime::type_schema::lookup_schema_by_id_public(schema_id)
214                        .map(|s| s.name.clone());
215                    if global.is_some() {
216                        return global;
217                    }
218                    let _ = exec_ctx;
219                    super::control::with_trampoline_vm(|vm| {
220                        vm.program()
221                            .type_schema_registry
222                            .get_by_id(schema_id)
223                            .map(|s| s.name.clone())
224                    })
225                    .flatten()
226                }
227                HK_JIT_OBJECT => Some("object".to_string()),
228                HK_DURATION => Some("Duration".to_string()),
229                HK_TIME => Some("DateTime".to_string()),
230                _ => None,
231            }
232        }
233    }
234}
235
236/// Search the JITContext's function_names table for a function with the given
237/// UFCS name (e.g. "Point::distance") and return its index.
238unsafe fn find_function_by_name(ctx_ref: &JITContext, ufcs_name: &str) -> Option<usize> {
239    if ctx_ref.function_names_ptr.is_null() || ctx_ref.function_names_len == 0 {
240        return None;
241    }
242    let names = unsafe {
243        std::slice::from_raw_parts(ctx_ref.function_names_ptr, ctx_ref.function_names_len)
244    };
245    for (idx, name) in names.iter().enumerate() {
246        if name == ufcs_name {
247            return Some(idx);
248        }
249    }
250    None
251}
252
253/// Try to call a user-defined method from impl blocks via UFCS dispatch.
254///
255/// User-defined methods (from `extend` / `impl` blocks) are compiled as functions
256/// named `"TypeName::method_name"`. This function:
257/// 1. Determines the receiver type name from the receiver's `NativeKind`
258///    (kind-from-parallel-track per §2.7.7 / Q9) and, for typed-object /
259///    UInt64 carriers, the schema id / heap-prefix `kind: u16` field at
260///    offset 0 of the JIT allocation (§2.7.5 "*not* tag-bit dispatch —
261///    field-load from a heap-resident struct").
262/// 2. Constructs the UFCS name `"TypeName::method_name"`
263/// 3. Looks up the function index in function_names
264/// 4. Calls the function via function_table, passing (receiver, ...args)
265/// 5. Returns the result as raw u64 bits
266///
267/// Returns Some(result) if the method was found and executed, None otherwise.
268///
269/// W17-narrow (Phase 3 cluster-0 Round 15, 2026-05-13): `receiver_kind`
270/// is threaded through from `jit_call_method`'s dispatch entry's
271/// parallel-kind pop (line 332-350) so `receiver_type_name` can classify
272/// without re-decoding tag bits (the W-series defection-attractor pattern).
273unsafe fn try_call_user_method(
274    ctx: *const JITContext,
275    receiver_bits: u64,
276    receiver_kind: NativeKind,
277    method_name: &str,
278    arg_pairs: &[(u64, NativeKind)],
279) -> Option<u64> {
280    use crate::ffi::stack_kind_code;
281
282    let ctx_ref = unsafe { &*ctx };
283
284    // Need execution context to access the type schema registry
285    if ctx_ref.exec_context_ptr.is_null() {
286        return None;
287    }
288    let exec_ctx = unsafe { &*(ctx_ref.exec_context_ptr as *const ExecutionContext) };
289
290    // Determine the receiver's type name
291    let type_name = unsafe { receiver_type_name(receiver_bits, receiver_kind, exec_ctx) }?;
292
293    // Construct UFCS function name: "TypeName::method_name"
294    let ufcs_name = format!("{}::{}", type_name, method_name);
295
296    // Look up the function index in the JIT function table
297    let func_idx = unsafe { find_function_by_name(ctx_ref, &ufcs_name) }?;
298
299    // Check that we have a valid function table entry
300    if ctx_ref.function_table.is_null() || func_idx >= ctx_ref.function_table_len {
301        return None;
302    }
303
304    // Read the raw pointer from the function table. A null entry means the
305    // function was not JIT-compiled (interpreted only).
306    let raw_fn_ptr = unsafe { *(ctx_ref.function_table as *const *const u8).add(func_idx) };
307    if raw_fn_ptr.is_null() {
308        return None;
309    }
310
311    // W14.2-E-followup-jit-trait-method-arity-soundness fix (2026-05-19,
312    // v0.3-gating SOUNDNESS BUG): the JIT-compiled UFCS callee was emitted
313    // with the extended Cranelift signature
314    // `fn(ctx_ptr, capture_0..N, param_0..M) -> i32`
315    // (`compile_function_with_user_funcs` at `compiler/program.rs:258-265`,
316    // appended params per `effective_arity = captures_count + arity`). Its
317    // entry-block parameter init at `compiler/program.rs:496-528` reads
318    // each MIR param slot from `entry_params[native_idx]` — the SYSTEM V
319    // register/stack ABI, NOT `ctx.stack`. The prior `fn_ptr(ctx_mut)`
320    // call transmuted the function pointer as `JittedStrategyFn` (a single-
321    // arg shape) and silently dropped every receiver/arg slot. The callee
322    // then read uninitialized SystemV-passing registers/stack frame for
323    // `self` and each user param — the empirical garbage NaN-bits for
324    // n>=1 (e.g. `d.dbl(21)` = `189861470636784`) and SEGFAULT for string
325    // args (registers held callee-saved garbage that decoded to wild
326    // `*const Arc<String>` pointers).
327    //
328    // Per ADR-006 §2.7.5 producer-side classification: the receiver +
329    // each `arg_pairs[i]` already carry the kind stamped at the
330    // `mir_compiler/terminators.rs` push (line ~342-372 for args, line
331    // ~510-535 for receiver). The kind half is sourced from §2.7.7/Q9
332    // parallel-track decode at the dispatch shell entry (lines ~482-501,
333    // ~513-527). The data half flows through this helper's typed-fn
334    // transmute selector via the kinded raw-bits slice — identical shape
335    // to `jit_call_value`'s bare-function fast path at
336    // `ffi/control/mod.rs:534-545` and `:709-732`.
337    //
338    // §2.7.7/Q9 lockstep invariant: the callee's own MIR-compiled body
339    // re-establishes its parallel-kind track from its own FrameDescriptor
340    // when it begins execution (same shape as the bare-function path's
341    // contract). The dispatch shell's parallel-kind track at indices
342    // popped (receiver / arg_pairs / method_name / arg_count) was already
343    // reset to SENTINEL at the pop sites above; we don't write the JIT-
344    // stack push half of the lockstep here because the callee doesn't
345    // read from `ctx.stack` — passing through the native ABI bypasses
346    // the stack entirely.
347
348    // Reset the JIT stack frame for the callee. The callee's first action
349    // is to write its return value to `ctx.stack[0]` and bump `stack_ptr`
350    // to 1 (see `mir_compiler/terminators.rs::TerminatorKind::Return` at
351    // line 1714-1718). Matches the §2.7.11/Q12 bare-function dispatch
352    // contract at `ffi/control/mod.rs:716`.
353    let ctx_mut = unsafe { &mut *(ctx as *mut JITContext) };
354    let _ = stack_kind_code::SENTINEL; // silence unused-import warning in this fn
355    ctx_mut.stack_ptr = 0;
356
357    // Build the native-arg slice: receiver as the first user param
358    // (`self`), followed by each user arg. Trait-impl bodies in Shape
359    // are compiled as functions named `"TypeName::method_name"` with
360    // `self` as their first formal parameter (when present); for
361    // n=0-arg methods the receiver is still the first param. Matches
362    // the JIT-compiled callee's `effective_arity = captures_count +
363    // arity` per `compile_function_with_user_funcs` (captures_count = 0
364    // for non-closure trait-impl bodies).
365    let mut native_args: Vec<u64> = Vec::with_capacity(arg_pairs.len() + 1);
366    native_args.push(receiver_bits);
367    for &(bits, _kind) in arg_pairs {
368        native_args.push(bits);
369    }
370
371    // Call the JIT-compiled function through the native ABI dispatch
372    // helper. The signal value is ignored — error-path deopt is not yet
373    // routed through this trait-method surface (the bare-function path
374    // ignores it identically at `ffi/control/mod.rs:535,:717`).
375    let _result_code = unsafe {
376        crate::ffi::control::call_jit_fn_with_args(raw_fn_ptr, ctx_mut, &native_args)
377    };
378
379    // Pop result from stack. The callee stored the return value at
380    // `ctx.stack[0]` and set `stack_ptr = 1` per the §2.7.5 typed-return
381    // contract; clear the kind track slot back to SENTINEL on pop to
382    // preserve the §2.7.7 / Q9 invariant for the slot the caller will
383    // reuse.
384    if ctx_mut.stack_ptr > 0 {
385        ctx_mut.stack_ptr -= 1;
386        let result = ctx_mut.stack[ctx_mut.stack_ptr];
387        ctx_mut.stack_kinds[ctx_mut.stack_ptr] = stack_kind_code::SENTINEL;
388        Some(result)
389    } else {
390        Some(TAG_NULL)
391    }
392}
393
394// ============================================================================
395// Main Dispatcher
396// ============================================================================
397//
398// W12-jit-call-method-shell-rebuild (Phase 3 cluster-0 Round 10 / 8B.2,
399// 2026-05-13). The shell now reads receiver + args kinds from the
400// §2.7.7 / Q9 `JITContext.stack_kinds` parallel-kind track at every pop,
401// per the producer-side classification at MIR-emit time
402// (`mir_compiler/terminators.rs:202-247`). When the receiver kind decodes
403// to a delegated-to-VM kind (the 8 Round 9 typed-Arc collection kinds +
404// Round 7A Result/Option Arc carriers + scalar kinds for unified VM
405// method dispatch), the shell builds `(u64, NativeKind)` pair-slices and
406// calls into the new public `VirtualMachine::jit_trampoline_call_method`
407// (sibling to `jit_trampoline_call_closure` at
408// `crates/shape-vm/src/executor/call_convention.rs`) — the §2.7.5
409// cross-crate stable FFI consumer.
410//
411// **Deleted in this rebuild:**
412//
413// - The kind-blind `heap_kind(receiver_bits)`-driven NaN-box dispatch
414//   cascade (pre-§2.7.10 `match heap_kind(receiver_bits)` at the prior
415//   shell body) — forbidden under §2.7.7 #4 / #7 (`is_heap()` probe on
416//   raw bits). Kind comes from the producing call signature now.
417// - The `dispatch_method_via_trampoline` extern-C `todo!()` stub —
418//   replaced by the principled `VirtualMachine::jit_trampoline_call_method`
419//   delegation per audit §2.1's load-bearing delegation insight.
420//
421// **Preserved fast path (JIT-internal kind, not a kind-decode):**
422//
423// The higher-order JIT array methods (find/filter/map/etc.) special-case
424// stays IF the receiver kind on the parallel track tells us the slot
425// carries opaque JIT-format bits (kind = `UInt64`, the documented §2.7.5
426// I64-wide raw bits carrier). For JIT-format `HK_ARRAY` NaN-boxed
427// receivers paired with closure callbacks, the `jit_control_*` FFI bodies
428// dispatch callback execution via the JIT function table — VM delegation
429// would lose this perf path. The receiver's JIT-format-array
430// classification still uses `is_heap_kind(receiver_bits, HK_ARRAY)` for
431// the inner discrimination, but only under the `UInt64` carrier-kind
432// guard — i.e. only when the producing site explicitly stamped the slot
433// as opaque-bits-no-classification. Not a §2.7.7 #4 / #7 violation: the
434// outer dispatch comes from the parallel-kind track; the inner read is
435// a JIT-format struct-field load on a known-opaque-bits slot. Migrating
436// to fully kinded arrays is W10 jit-playbook §5 territory.
437
438pub extern "C" fn jit_call_method(ctx: *mut JITContext, stack_count: usize) -> u64 {
439    use crate::ffi::stack_kind_code;
440    use shape_value::{HeapKind, NativeKind};
441
442    unsafe {
443        if ctx.is_null() || stack_count < 3 {
444            return TAG_NULL;
445        }
446
447        let ctx_ref = &mut *ctx;
448
449        // ── Pop arg_count ──────────────────────────────────────────────
450        // ABI: the MIR producer stores `arg_count` as a raw i64 with
451        // parallel-kind `UInt64` (sentinel slot — `terminators.rs:259`).
452        // We decode it directly as usize — no NaN-box.
453        if ctx_ref.stack_ptr == 0 {
454            return TAG_NULL;
455        }
456        ctx_ref.stack_ptr -= 1;
457        let arg_count = ctx_ref.stack[ctx_ref.stack_ptr] as usize;
458        ctx_ref.stack_kinds[ctx_ref.stack_ptr] = stack_kind_code::SENTINEL;
459
460        // ── Pop method_name ────────────────────────────────────────────
461        // The MIR producer pushes the method name as a raw
462        // `Box::into_raw(Box::new(UnifiedValue<Arc<String>>))` pointer
463        // (via `box_string` at `terminators.rs:235`) with the parallel-
464        // kind track stamped `NativeKind::String` per §2.7.7 / Q9 at
465        // `terminators.rs:243-246`. The JIT-internal `unbox_string`
466        // reads `&Arc<String>` from the unified-heap allocation. This is
467        // a field read on a known-classified slot (kind track says
468        // String), NOT a §2.7.7 #4 / #7 tag-decode on raw bits — the
469        // kind IS the discriminator. Pre-Round-10 the bits were validated
470        // via `is_heap_kind(method_bits, HK_STRING)` (a NaN-box
471        // discrimination); under §2.7.5 strict-typed unified-heap the
472        // bits are raw `Box::into_raw` pointers without the NaN-box
473        // wrapper, so the parallel-kind track is the producer-side
474        // classification source.
475        if ctx_ref.stack_ptr == 0 {
476            return TAG_NULL;
477        }
478        ctx_ref.stack_ptr -= 1;
479        let method_bits = ctx_ref.stack[ctx_ref.stack_ptr];
480        let method_kind_code = ctx_ref.stack_kinds[ctx_ref.stack_ptr];
481        ctx_ref.stack_kinds[ctx_ref.stack_ptr] = stack_kind_code::SENTINEL;
482        let method_kind = match stack_kind_code::decode(method_kind_code) {
483            Some(k) => k,
484            None => {
485                tracing::debug!(
486                    target: "shape_jit",
487                    method_kind_code,
488                    stack_ptr = ctx_ref.stack_ptr,
489                    "jit-call-method SURFACE \u{a7}2.7.7 / Q9: method-name \
490                     kind-byte is SENTINEL / reserved. The producing call \
491                     site at terminators.rs:243 must stamp NativeKind::String \
492                     \u{2014} no Bool-default.",
493                );
494                return TAG_NULL;
495            }
496        };
497        if !matches!(method_kind, NativeKind::String) {
498            tracing::debug!(
499                target: "shape_jit",
500                method_kind = ?method_kind,
501                "jit-call-method SURFACE: method-name kind != \
502                 NativeKind::String. Producer-site contract violated \
503                 (terminators.rs:243 must stamp String).",
504            );
505            return TAG_NULL;
506        }
507        let method_name: String = unbox_string(method_bits).to_string();
508        tracing::debug!(
509            target: "shape_jit",
510            arg_count,
511            method_name = %method_name,
512            stack_ptr = ctx_ref.stack_ptr,
513            "jit-call-method dispatch",
514        );
515
516        // ── Pop args paired with their parallel-track kinds ───────────
517        // Reverse pop order, then reverse to source order. The §2.7.7 /
518        // Q9 lockstep invariant: each `(bits, kind)` pair lives at the
519        // same slot index.
520        let mut arg_pairs: Vec<(u64, NativeKind)> = Vec::with_capacity(arg_count);
521        for _ in 0..arg_count {
522            if ctx_ref.stack_ptr == 0 {
523                return TAG_NULL;
524            }
525            ctx_ref.stack_ptr -= 1;
526            let bits = ctx_ref.stack[ctx_ref.stack_ptr];
527            let code = ctx_ref.stack_kinds[ctx_ref.stack_ptr];
528            ctx_ref.stack_kinds[ctx_ref.stack_ptr] = stack_kind_code::SENTINEL;
529            let kind = match stack_kind_code::decode(code) {
530                Some(k) => k,
531                None => {
532                    tracing::debug!(
533                        target: "shape_jit",
534                        code,
535                        stack_ptr = ctx_ref.stack_ptr,
536                        "jit-call-method SURFACE \u{a7}2.7.7 / Q9: arg \
537                         kind-byte is SENTINEL / reserved. The producing \
538                         call site at `mir_compiler/terminators.rs` must \
539                         stamp a concrete NativeKind per ADR-006 \u{a7}2.7.5 \
540                         producer-side classification \u{2014} no Bool-default \
541                         fallback (\u{a7}2.7.7 #9).",
542                    );
543                    return TAG_NULL;
544                }
545            };
546            arg_pairs.push((bits, kind));
547        }
548        arg_pairs.reverse();
549
550        // ── Pop receiver paired with its parallel-track kind ──────────
551        if ctx_ref.stack_ptr == 0 {
552            return TAG_NULL;
553        }
554        ctx_ref.stack_ptr -= 1;
555        let receiver_bits = ctx_ref.stack[ctx_ref.stack_ptr];
556        let receiver_code = ctx_ref.stack_kinds[ctx_ref.stack_ptr];
557        ctx_ref.stack_kinds[ctx_ref.stack_ptr] = stack_kind_code::SENTINEL;
558        let receiver_kind = match stack_kind_code::decode(receiver_code) {
559            Some(k) => k,
560            None => {
561                tracing::debug!(
562                    target: "shape_jit",
563                    receiver_code,
564                    stack_ptr = ctx_ref.stack_ptr,
565                    "jit-call-method SURFACE \u{a7}2.7.7 / Q9: receiver \
566                     kind-byte is SENTINEL / reserved. The producing call \
567                     site must stamp the receiver's NativeKind per ADR-006 \
568                     \u{a7}2.7.5. No Bool-default fallback (\u{a7}2.7.7 #9).",
569                );
570                return TAG_NULL;
571            }
572        };
573        tracing::debug!(
574            target: "shape_jit",
575            method_name = %method_name,
576            receiver_kind = ?receiver_kind,
577            receiver_code,
578            receiver_bits,
579            "jit-call-method receiver classified",
580        );
581
582        // ── Classification: delegate to VM or fall back to JIT-format ──
583        //
584        // The receiver kind from the §2.7.7 / Q9 parallel-kind track is
585        // the §2.7.10 / Q11 dispatch discriminator. Kinds whose carriers
586        // are kinded `Arc::into_raw(Arc<XData>)` (Round 7A Result/Option
587        // + Round 9 typed-Arc collections HashSet/HashMap/Deque/
588        // PriorityQueue/Channel/Mutex/Atomic/Lazy) route through the VM
589        // trampoline's PHF dispatch tables in
590        // `crates/shape-vm/src/executor/objects/method_registry.rs` —
591        // ~73 already-kinded `MethodFnV2` entries per audit §2.1.
592        //
593        // Scalar kinds (Int64/Float64/Bool/String) also delegate to VM
594        // for uniformity — the VM has full scalar method registries
595        // (`NUMBER_METHODS` / `BOOL_METHODS` / `STRING_METHODS`).
596        //
597        // `UInt64` carrier kind: this is the §2.7.5 documented "I64-wide
598        // raw bits without further classification" carrier. JIT-format
599        // arrays / objects / etc. land here when MIR cannot prove a
600        // precise kind. Fall back to legacy JIT-format dispatch — the
601        // JIT-internal `is_heap_kind(receiver_bits, HK_*)` probe on
602        // the heap-allocation kind field discriminates these.
603        let delegated = match receiver_kind {
604            NativeKind::Ptr(HeapKind::HashSet)
605            | NativeKind::Ptr(HeapKind::HashMap)
606            | NativeKind::Ptr(HeapKind::Deque)
607            | NativeKind::Ptr(HeapKind::PriorityQueue)
608            | NativeKind::Ptr(HeapKind::Channel)
609            | NativeKind::Ptr(HeapKind::Mutex)
610            | NativeKind::Ptr(HeapKind::Atomic)
611            | NativeKind::Ptr(HeapKind::Lazy)
612            | NativeKind::Ptr(HeapKind::Result)
613            | NativeKind::Ptr(HeapKind::Option)
614            // r5c-2-gz-CP9 (v0.3 NO-KNOWN-INCORRECTNESS γ item-9): typed-
615            // array receivers that reach this dispatch shell delegate to
616            // the VM trampoline. The structurally cheap typed-array
617            // methods (`length`/`len`/`push`/`first`/`last`/`sum`/`min`/
618            // `max`/...) are intercepted inline by `try_emit_v2_array_
619            // method` in `mir_compiler/terminators.rs` and never reach
620            // here. The methods that DO fall through to `jit_call_method`
621            // with a `Ptr(TypedArray)` receiver — `count` / `group` /
622            // `groupBy` / `contains` — previously hit the legacy
623            // JIT-format dispatch, where the `builtin_result` cascade
624            // has no JIT-format registry for `Ptr(_)` carriers and the
625            // `Ptr(_) => TAG_NULL` arm returned a silent placeholder.
626            // The JIT-compiled caller then wrote `TAG_NULL` into a
627            // heap-kinded destination: `groupBy().sum()` SIGSEGV'd
628            // (ec=139), `count(pred)` printed garbage
629            // (`-1407374883553280`), `contains(x)` silently returned
630            // `false` — every one a VM/JIT divergence producing garbage
631            // where the bytecode VM cleanly errors (`handle_count_v2` /
632            // `handle_group_by_v2` ckpt2 SURFACE; "no method" for
633            // `contains`). Delegating to the VM trampoline routes these
634            // through `dispatch_method_kinded` — the VM's authoritative
635            // PHF registry — so an unimplemented/missing method surfaces
636            // a clean `Err`, which the trampoline's `Some(Err(_))` arm
637            // turns into a `pending_call_error` deopt (W12 compile-
638            // failure → interpreter fall-through). Net result: VM == JIT.
639            // The full kinded typed-array JIT-format method registry is
640            // W10 jit-playbook §5 / §2.7.4 territory; until it lands,
641            // VM delegation is the correct (non-garbage) behaviour.
642            | NativeKind::Ptr(HeapKind::TypedArray)
643            | NativeKind::Float64
644            | NativeKind::NullableFloat64
645            | NativeKind::Int8
646            | NativeKind::NullableInt8
647            | NativeKind::UInt8
648            | NativeKind::NullableUInt8
649            | NativeKind::Int16
650            | NativeKind::NullableInt16
651            | NativeKind::UInt16
652            | NativeKind::NullableUInt16
653            | NativeKind::Int32
654            | NativeKind::NullableInt32
655            | NativeKind::UInt32
656            | NativeKind::NullableUInt32
657            | NativeKind::Int64
658            | NativeKind::NullableInt64
659            | NativeKind::NullableUInt64
660            | NativeKind::IntSize
661            | NativeKind::NullableIntSize
662            | NativeKind::UIntSize
663            | NativeKind::NullableUIntSize
664            | NativeKind::Bool
665            // Round 19 S1.5 W12-nativekind-scalar-additions (2026-05-14):
666            // F32 receivers delegate to VM (NUMBER_METHODS); Char
667            // receivers delegate to VM (CHAR_METHODS — the existing
668            // receiver kind for char methods).
669            | NativeKind::Float32
670            | NativeKind::Char => true,
671            // Wave 2 Agent B W12-StringV2-DecimalV2-NativeKind-additions
672            // (2026-05-14): v2-raw heap-pointer carriers delegate to VM —
673            // same routing rationale as the §H.4 H-c amendment: producer
674            // (Agent A2) emits v2-raw slots, consumer (this dispatch
675            // shell) routes them to the VM-side method registry where the
676            // method-handler bodies dispatch on the StringV2 / DecimalV2
677            // kind label to read the carrier's payload. The JIT-format
678            // path expects Arc-wrapped carriers; VM-side handlers are
679            // carrier-aware.
680            NativeKind::StringV2 | NativeKind::DecimalV2 => true,
681            // String: deliberately NOT delegated — JIT-format string
682            // method registries (`call_string_method`) operate on
683            // NaN-boxed JIT String carriers (`box_string` returns
684            // `Arc<String>` raw pointer with the JIT NaN-box tag wrapper
685            // for kind classification at the heap-header `kind` field).
686            // VM-side `STRING_METHODS` would expect the kinded Arc
687            // shape. Routing through JIT-format path preserves the
688            // existing string method tests. This is a §2.7.5 carrier-
689            // shape mismatch territory — full kinded String migration
690            // is W10 jit-playbook §5.
691            NativeKind::String => false,
692            // UInt64: §2.7.5 carrier kind for opaque JIT bits. Fall
693            // through to legacy JIT-format dispatch.
694            NativeKind::UInt64 => false,
695            // Other Ptr(*) kinds — TypedArray, TypedObject, String
696            // (heap), Closure, TraitObject, etc. — fall through to
697            // legacy JIT-format dispatch. The kinded path for these
698            // is W10 jit-playbook §5 / §2.7.4 territory.
699            NativeKind::Ptr(_) => false,
700            // R5b-2-bool-null-sentinel-cluster (ADR-006 §2.7 +
701            // §2.7.7/Q9, 2026-05-19): null receivers delegate to VM
702            // which surfaces a TypeError uniformly.
703            NativeKind::Null => true,
704        };
705
706        // ── Surface-and-stop: JIT-format closure arg cannot cross to VM ──
707        //
708        // r5c-2-gz-CP9 (v0.3 NO-KNOWN-INCORRECTNESS γ item-9). A
709        // higher-order method on a typed-array receiver (`groupBy` /
710        // `count` / `find` / `filter` / ... with a `|x| ...` predicate)
711        // carries the closure as an argument. The MIR producer stamps
712        // that arg's `NativeKind` from `slot_kinds`; for some call sites
713        // the inferred kind is `Ptr(HeapKind::Closure)` even though the
714        // JIT lowered the closure to a JIT-format NaN-boxed inline-
715        // function carrier (a tagged `0xfffd…` bit-pattern), NOT a
716        // v2-raw `*mut ClosureRaw` heap pointer. Delegating such an arg
717        // to the VM trampoline builds `KindedSlot::new(from_raw(bits),
718        // Ptr(Closure))`; when the VM-side method handler surfaces an
719        // `Err` and the transient `kinded_args` Vec drops, the
720        // `Ptr(Closure)` arm of `drop_with_kind` dereferences the
721        // NaN-boxed bits as a heap pointer → SIGSEGV (empirically: array
722        // `groupBy(|x| ...)` crashed ec=139 inside the trampoline's
723        // `kinded_args` drop). The JIT-format closure carrier and the
724        // VM's v2-raw `Ptr(Closure)` carrier are structurally distinct;
725        // they cannot meet at the FFI boundary without a forbidden
726        // carrier-translation bridge (CLAUDE.md §Renames to refuse).
727        //
728        // The honest fix is the W12 compile-failure → interpreter
729        // fall-through: raise a structured `pending_call_error` and
730        // deopt the JIT frame. The bytecode interpreter then re-runs the
731        // method call with its own (carrier-correct) closure handling
732        // and produces the VM's behaviour — for array `groupBy` that is
733        // a clean `Stack overflow` (the in-Shape `vec.shape` `groupBy`
734        // self-recurses) / for unimplemented methods a clean SURFACE
735        // `Err`. Net result: VM == JIT — both cleanly error, neither
736        // SIGSEGVs. A real JIT-format typed-array higher-order method
737        // path is W10 jit-playbook §5 / §2.7.4 territory.
738        if matches!(receiver_kind, NativeKind::Ptr(HeapKind::TypedArray))
739            && arg_pairs
740                .iter()
741                .any(|(_, k)| matches!(k, NativeKind::Ptr(HeapKind::Closure)))
742        {
743            tracing::debug!(
744                target: "shape_jit",
745                method_name = %method_name,
746                "jit-call-method SURFACE: typed-array higher-order method \
747                 with a JIT-format closure arg cannot delegate to the VM \
748                 trampoline (carrier-shape mismatch) \u{2014} raising \
749                 pending_call_error for MIR-emitted deopt to interpreter \
750                 fall-through (W12 pattern)",
751            );
752            super::control::set_jit_runtime_error(format!(
753                "JIT codegen for typed-array `.{}()` with a closure \
754                 argument is unimplemented \u{2014} deopting to interpreter",
755                method_name,
756            ));
757            ctx_ref.pending_call_error = 1;
758            return TAG_NULL;
759        }
760
761        if delegated {
762            tracing::debug!(
763                target: "shape_jit",
764                method_name = %method_name,
765                receiver_kind = ?receiver_kind,
766                receiver_bits,
767                arg_count,
768                "jit-call-method delegating to VM",
769            );
770            // VM-trampoline delegation per §2.7.5 cross-crate stable FFI.
771            // The pair-slice form is single-direction at the boundary;
772            // the VM converts to `&[KindedSlot]` internally before
773            // `dispatch_method_kinded`. The JIT pre-incremented each
774            // share via `retain_func_for_place` on the producing read;
775            // the VM's transient KindedSlot carriers adopt those shares
776            // and release on scope exit per §2.7.7 retain-on-read +
777            // drop-on-write discipline (see
778            // `VirtualMachine::jit_trampoline_call_method`'s ownership
779            // contract docstring).
780            let receiver_pair = (receiver_bits, receiver_kind);
781            let result = super::control::with_trampoline_vm_mut(|vm| {
782                vm.jit_trampoline_call_method(
783                    &method_name,
784                    receiver_pair,
785                    &arg_pairs,
786                    None,
787                )
788            });
789            match result {
790                Some(Ok(bits)) => return bits,
791                Some(Err(e)) => {
792                    // r5c-2-bz-b-jit-err-surface: the VM-side method handler
793                    // surfaced a clean `Err` (e.g. `Set.add()` with a non-
794                    // string key). The JIT-compiled caller must NOT continue
795                    // with a value-shaped placeholder — a heap-kinded
796                    // destination place would feed it into a refcount-retain
797                    // (`jit_arc_result_retain`'s `bits != 0` guard passes
798                    // `TAG_NULL`, then dereferences `TAG_NULL - 16`). Instead
799                    // raise `pending_call_error` so the MIR-emitted post-call
800                    // check deopts the JIT frame; the VM produces the clean
801                    // error. The returned bits are never consumed.
802                    tracing::debug!(
803                        target: "shape_jit",
804                        method_name = %method_name,
805                        receiver_kind = ?receiver_kind,
806                        error = ?e,
807                        "jit-call-method VM trampoline returned error \u{2014} \
808                         raising pending_call_error for MIR-emitted deopt",
809                    );
810                    super::control::set_jit_runtime_error(e.to_string());
811                    ctx_ref.pending_call_error = 1;
812                    return TAG_NULL;
813                }
814                None => {
815                    tracing::debug!(
816                        target: "shape_jit",
817                        method_name = %method_name,
818                        receiver_kind = ?receiver_kind,
819                        "jit-call-method VM trampoline unavailable \u{2014} \
820                         TRAMPOLINE_VM is null. Surfaces.",
821                    );
822                    super::control::set_jit_runtime_error(format!(
823                        "JIT method dispatch for `{}` could not reach the \
824                         interpreter trampoline",
825                        method_name,
826                    ));
827                    ctx_ref.pending_call_error = 1;
828                    return TAG_NULL;
829                }
830            }
831        }
832
833        // ── Legacy JIT-format dispatch (UInt64 carrier kind path) ─────
834        //
835        // The receiver kind on the §2.7.7 / Q9 parallel-kind track is
836        // `UInt64` (or another non-delegated kind) — the slot carries
837        // opaque JIT-format bits. The JIT-internal heap allocator
838        // (`jit_box(HK_*, ...)` / `unified_box`) embeds the `kind: u16`
839        // discriminator at offset 0 of the heap allocation per ADR-006
840        // §2.7.5; the inner `heap_kind(receiver_bits)` probe is a
841        // field-load on that known-opaque-bits allocation, NOT a
842        // §2.7.7 #4 / #7 forbidden tag-decode on raw bits for kind
843        // determination.
844        let args: Vec<u64> = arg_pairs.iter().map(|(b, _)| *b).collect();
845
846        // Higher-order array methods (find/filter/map/reduce/...) need
847        // closure callback execution via `jit_control_*` FFI bodies —
848        // preserved for JIT-format `HK_ARRAY` receivers.
849        if is_heap_kind(receiver_bits, HK_ARRAY) {
850            // ── Surface-and-stop: unimplemented JIT-format array methods ──
851            //
852            // r5c-2-gz-CP9 (v0.3 NO-KNOWN-INCORRECTNESS γ item-9): the
853            // JIT-format `count` / `group` / `groupBy` legacy paths were
854            // `todo!()` stubs. `jit_call_method` is an `extern "C"`
855            // function — a `todo!()` panic unwinding across the FFI
856            // boundary is undefined behaviour: empirically `groupBy` +
857            // `.sum()`/`.len()` SIGSEGV'd (ec=139) and `count` printed
858            // garbage (`-1407374883553280`, ec=0) where the bytecode VM
859            // cleanly SURFACEs (`handle_group_by_v2` / the `count` PHF
860            // SURFACE error). That is a VM/JIT divergence producing
861            // garbage/crashes.
862            //
863            // The honest fix is the W12 compile-failure → interpreter
864            // fall-through pattern (`docs/cluster-audits/v0.3-w12-jit-
865            // mode-semantics-close.md`), NOT partial codegen: a real
866            // JIT implementation would only re-create the divergence
867            // while the VM still SURFACEs. We raise a structured
868            // `pending_call_error` BEFORE touching the JIT stack (the
869            // prior `count` arm pushed onto + part-consumed the stack via
870            // `jit_control_filter` then `todo!()`'d, corrupting it). The
871            // MIR-emitted post-call check deopts the JIT frame; the
872            // bytecode interpreter then produces the VM's clean SURFACE
873            // error. Net result: VM == JIT — both cleanly error, neither
874            // produces garbage or SIGSEGVs.
875            match method_name.as_str() {
876                "count" | "group" | "groupBy" => {
877                    tracing::debug!(
878                        target: "shape_jit",
879                        method_name = %method_name,
880                        "jit-call-method SURFACE: array `count`/`group`/\
881                         `groupBy` JIT-format codegen unimplemented \u{2014} \
882                         raising pending_call_error for MIR-emitted deopt \
883                         to interpreter fall-through (W12 pattern)",
884                    );
885                    super::control::set_jit_runtime_error(format!(
886                        "JIT codegen for array `.{}()` is unimplemented \
887                         \u{2014} deopting to interpreter",
888                        method_name,
889                    ));
890                    ctx_ref.pending_call_error = 1;
891                    return TAG_NULL;
892                }
893                _ => {}
894            }
895            match method_name.as_str() {
896                "find" | "findIndex" | "some" | "every" | "filter" | "map"
897                | "reduce" => {
898                    if args.is_empty() {
899                        return TAG_NULL;
900                    }
901                    let predicate = args[0];
902                    let working_array_bits = receiver_bits;
903
904                    if method_name == "reduce" {
905                        let (callback, initial) = if args.len() > 1 {
906                            (args[1], args[0])
907                        } else {
908                            (args[0], box_number(0.0))
909                        };
910                        ctx_ref.stack[ctx_ref.stack_ptr] = working_array_bits;
911                        ctx_ref.stack_ptr += 1;
912                        ctx_ref.stack[ctx_ref.stack_ptr] = callback;
913                        ctx_ref.stack_ptr += 1;
914                        ctx_ref.stack[ctx_ref.stack_ptr] = initial;
915                        ctx_ref.stack_ptr += 1;
916                        ctx_ref.stack[ctx_ref.stack_ptr] = box_number(3.0);
917                        ctx_ref.stack_ptr += 1;
918                        return super::control::jit_control_reduce(ctx);
919                    }
920
921                    ctx_ref.stack[ctx_ref.stack_ptr] = working_array_bits;
922                    ctx_ref.stack_ptr += 1;
923                    ctx_ref.stack[ctx_ref.stack_ptr] = predicate;
924                    ctx_ref.stack_ptr += 1;
925                    ctx_ref.stack[ctx_ref.stack_ptr] = box_number(2.0);
926                    ctx_ref.stack_ptr += 1;
927
928                    let result = match method_name.as_str() {
929                        "find" => super::control::jit_control_find(ctx),
930                        "findIndex" => super::control::jit_control_find_index(ctx),
931                        "some" => super::control::jit_control_some(ctx),
932                        "every" => super::control::jit_control_every(ctx),
933                        "filter" => super::control::jit_control_filter(ctx),
934                        "map" => super::control::jit_control_map(ctx),
935                        // `count` / `group` / `groupBy` are surfaced-and-
936                        // stopped above before the JIT stack is touched.
937                        _ => TAG_NULL,
938                    };
939
940                    return result;
941                }
942                _ => {}
943            }
944        }
945
946        // Built-in JIT-format method dispatch — kind-from-parallel-track
947        // per ADR-006 §2.7.5 / §2.7.7 / Q9, §2.7.10 / Q11.
948        //
949        // W17-narrow (Phase 3 cluster-0 Round 15, 2026-05-13): replaced
950        // the prior 6-arm tag-bit cascade (`is_ok_tag` / `is_err_tag` /
951        // `is_number` / `is_inline_function` / `heap_kind` cascade for
952        // HK_ARRAY / HK_STRING / HK_JIT_OBJECT / …) with classification
953        // driven by the receiver's `NativeKind` companion (already
954        // popped from the §2.7.7 / Q9 parallel-kind track at line
955        // 332-350). The prior predicates all required `is_heap()` /
956        // `is_tagged()` / `is_number()` checks on raw bits — those
957        // return wrong answers on §2.7.5 raw `Box::into_raw` carriers
958        // (audit §6 empirical evidence). For UInt64-carrier opaque-bits
959        // receivers the inner discriminator is read directly from the
960        // JitAlloc / UnifiedValue prefix at offset 0 via `read_heap_kind`
961        // — a field-load on the heap-resident struct, NOT a tag-bit
962        // predicate (§2.7.5 carves this out: "*not* tag-bit dispatch —
963        // it reads a field from a heap-resident struct that the producing
964        // call placed there").
965        let builtin_result = match receiver_kind {
966            // §2.7.5 typed Arc<String> raw-pointer carrier. The JIT-
967            // format `call_string_method` still expects the legacy
968            // NaN-boxed UnifiedValue<Arc<String>> wrapper shape; the
969            // kinded String migration is W10 jit-playbook §5 territory.
970            // Routing through call_string_method preserves the existing
971            // JIT-format string method tests.
972            NativeKind::String => call_string_method(receiver_bits, &method_name, &args),
973            // §2.7.5 typed-Arc heap carriers — these are the non-
974            // delegated `Ptr(_)` arms (TypedObject / TypedArray / Closure
975            // / TraitObject / etc.). Method dispatch on these via the
976            // JIT-format legacy path lands at the user-method UFCS
977            // fallback below — there are no JIT-format builtin method
978            // registries for these kinds. The W10 jit-playbook §5
979            // kinded-array migration will fill this surface in a
980            // future cluster.
981            NativeKind::Ptr(_) => TAG_NULL,
982            // UInt64 carrier — discriminate via the heap-prefix
983            // `kind: u16` field-load. This is the canonical path for
984            // legacy JIT-format kinds (HK_ARRAY / HK_JIT_OBJECT /
985            // HK_DURATION / HK_TIME / HK_MATRIX / HK_OK / HK_ERR / …)
986            // whose producing allocator (`jit_box` / `unified_box`)
987            // places the kind discriminator at offset 0 of the
988            // allocation.
989            NativeKind::UInt64 => {
990                if receiver_bits == 0
991                    || receiver_bits == TAG_NULL
992                    || receiver_bits == TAG_NONE
993                {
994                    TAG_NULL
995                } else {
996                    match read_heap_kind(receiver_bits) {
997                        HK_OK | HK_ERR => {
998                            call_result_method(receiver_bits, &method_name, &args)
999                        }
1000                        HK_ARRAY => call_array_method(receiver_bits, &method_name, &args),
1001                        HK_STRING => call_string_method(receiver_bits, &method_name, &args),
1002                        HK_JIT_OBJECT => call_object_method(receiver_bits, &method_name, &args),
1003                        HK_DURATION => {
1004                            call_duration_method(receiver_bits, &method_name, &args)
1005                        }
1006                        HK_COLUMN_REF => TAG_NULL,
1007                        HK_MATRIX => call_matrix_method(receiver_bits, &method_name, &args),
1008                        HK_TIME => call_time_method(receiver_bits, &method_name, &args),
1009                        _ => TAG_NULL,
1010                    }
1011                }
1012            }
1013            // Scalar / numeric kinds — all delegated to VM above (lines
1014            // 380-432) so they don't reach this cascade in practice;
1015            // returning TAG_NULL is defensive (a stack-pop-then-re-
1016            // classification bug would have surfaced before here).
1017            NativeKind::Float64
1018            | NativeKind::NullableFloat64
1019            | NativeKind::Int8
1020            | NativeKind::NullableInt8
1021            | NativeKind::UInt8
1022            | NativeKind::NullableUInt8
1023            | NativeKind::Int16
1024            | NativeKind::NullableInt16
1025            | NativeKind::UInt16
1026            | NativeKind::NullableUInt16
1027            | NativeKind::Int32
1028            | NativeKind::NullableInt32
1029            | NativeKind::UInt32
1030            | NativeKind::NullableUInt32
1031            | NativeKind::Int64
1032            | NativeKind::NullableInt64
1033            | NativeKind::NullableUInt64
1034            | NativeKind::IntSize
1035            | NativeKind::NullableIntSize
1036            | NativeKind::UIntSize
1037            | NativeKind::NullableUIntSize
1038            | NativeKind::Bool
1039            // Round 19 S1.5 W12-nativekind-scalar-additions (2026-05-14):
1040            // F32 / Char already delegated to VM above; defensive
1041            // TAG_NULL arm if they reach this fallthrough.
1042            | NativeKind::Float32
1043            | NativeKind::Char
1044            // Wave 2 Agent B W12-StringV2-DecimalV2-NativeKind-additions
1045            // (2026-05-14): StringV2 / DecimalV2 already delegated to VM
1046            // above; defensive TAG_NULL arm if they reach this
1047            // fallthrough.
1048            | NativeKind::StringV2
1049            | NativeKind::DecimalV2 => TAG_NULL,
1050            // R5b-2-bool-null-sentinel-cluster (ADR-006 §2.7 +
1051            // §2.7.7/Q9, 2026-05-19): null receivers delegate to VM
1052            // (defensive TAG_NULL arm).
1053            NativeKind::Null => TAG_NULL,
1054        };
1055
1056        // User-defined method dispatch (UFCS — `"TypeName::method"`
1057        // functions in the JIT function table). The receiver kind from
1058        // the §2.7.7 / Q9 parallel-kind track flows into
1059        // `receiver_type_name` so dispatch classifies on the producing
1060        // call's stamp, not on tag-bit decode.
1061        if builtin_result == TAG_NULL {
1062            if let Some(user_result) = try_call_user_method(
1063                ctx,
1064                receiver_bits,
1065                receiver_kind,
1066                &method_name,
1067                &arg_pairs,
1068            ) {
1069                return user_result;
1070            }
1071        }
1072
1073        // The pre-§2.7.10 `dispatch_method_via_trampoline` extern-C
1074        // `todo!()` (and the `_ => TAG_NULL` cascade fall-through to it)
1075        // is deleted. Method dispatch on VM-allocated objects now routes
1076        // through the §2.7.10 / Q11 kinded `vm.jit_trampoline_call_method`
1077        // path above when the receiver kind is one of the delegated-to-VM
1078        // kinds; the legacy JIT-format dispatch handles JIT-internal
1079        // opaque receivers (UInt64 carrier kind) per the producer-side
1080        // classification.
1081
1082        builtin_result
1083    }
1084}