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}