Skip to main content

shape_vm/executor/vm_impl/
builtins.rs

1//! Builtin dispatch slice (ADR-006 §2.7.6 / Q8).
2//!
3//! Wave 5a (phase-1b-vm) flipped the dispatch SHAPE here: every arm now
4//! produces / consumes `Vec<KindedSlot>` (and `&[KindedSlot]`), aligned
5//! with the carrier-API bound spec'd at §2.7.6. The body interiors
6//! (math kernels, array kernels, content builders, type-introspection,
7//! stats, intrinsics, JSON helpers, table builders, content / DateTime /
8//! concurrency constructors) are deferred to Waves 5b-5e.
9//!
10//! - **Wave 5b (LANDED)**: math + array + utility bodies (`builtin_abs`,
11//!   `builtin_push`, `builtin_object_rest`, `builtin_snapshot`,
12//!   `builtin_exit`, etc.) are now `Fn(&[KindedSlot], ...) -> Result<KindedSlot, VMError>`
13//!   and the dispatch arms call them directly.
14//! - **Wave 5c**: type-introspection + conversion + native-interop bodies
15//!   (`builtin_is_*`, `builtin_to_*`, `dispatch_native_interop_builtin`).
16//! - **Wave 5d**: closure-driven array builtins (`map`, `filter`, `reduce`,
17//!   etc.) + intrinsic dispatch (`handle_intrinsic_builtin`,
18//!   `handle_vector_intrinsic`, `handle_matrix_intrinsic`).
19//! - **Wave 5e**: content + DateTime + concurrency constructors + window /
20//!   join / reflect / state-builtin bodies + `executor/printing.rs` formatter.
21//!
22//! The companion §2.7.6 / Q8 carrier-API bound: NO per-heap-variant
23//! accessors on `KindedSlot`; bodies that inspect heap payloads use
24//! `slot.as_heap_value()` + `HeapValue` match. NO cross-kind accessors
25//! (`as_number_coerce`, etc.) on the carrier; coercion lives at
26//! `executor/builtins/kind_coerce.rs` (free helper at the body site).
27//!
28//! # `pop_builtin_args` runtime semantics (Wave 6: kinded stack ABI)
29//!
30//! Wave 6 (ADR-006 §2.7.7 / Q9) added a parallel `Vec<NativeKind>` track
31//! to the VM stack. `pop_builtin_args` now reads the per-arg `NativeKind`
32//! directly from the parallel track via `pop_kinded()`. Wave 5b's
33//! transitional `NativeKind::Bool` sentinel is removed — every arg's kind
34//! is the kind that the producing opcode emitted into the parallel track
35//! at push time.
36//!
37//! **Ownership transfer**: `pop_kinded()` moves one strong-count share
38//! (for heap-bearing kinds) out of the stack slot into the returned
39//! tuple. Wrapping it in a `KindedSlot` transfers that share to the
40//! carrier; `KindedSlot::Drop` retires the share when the args `Vec` is
41//! dropped at the end of the builtin call. **No `clone_with_kind`
42//! needed** here — that's only for `read_owned_kinded` (which keeps the
43//! slot live on the stack while handing a share out).
44
45use super::super::*;
46use shape_value::{KindedSlot, VMError, ValueSlot};
47
48impl VirtualMachine {
49    /// Pop the builtin call's args off the typed VM stack into a
50    /// `Vec<KindedSlot>` (ADR-006 §2.7.7 / Q9).
51    ///
52    /// The topmost stack slot is the arg count (pushed as a numeric
53    /// constant by the compiler). Each subsequent pop hands back the raw
54    /// u64 bits **plus** the `NativeKind` recorded by the producing opcode
55    /// in the parallel kinds track.
56    ///
57    /// **Ownership**: `pop_kinded()` transfers the slot's strong-count
58    /// share into the returned tuple; wrapping it in a `KindedSlot`
59    /// transfers ownership to the carrier. `KindedSlot::Drop` retires the
60    /// share when the returned `Vec` goes out of scope.
61    pub(crate) fn pop_builtin_args(&mut self) -> Result<Vec<KindedSlot>, VMError> {
62        // Top of stack: the arg count, pushed as a typed integer constant
63        // by the compiler (`PushConst(Int(arg_count as i64))`). The count
64        // slot is an integer-family inline scalar; `int_operand` dispatches
65        // per the §2.7.6 heterogeneous-kind body pattern (same shape as
66        // `op_call` / `op_call_value` use).
67        //
68        // Historical note (W17-make-closure): prior to the arg-count emit
69        // migration the compiler emitted `Number(arg_count as f64)` and
70        // this body decoded `f64::from_bits(count_bits) as usize`. That
71        // shape made `op_call` (which uses `int_operand`) reject the same
72        // arg-count slot, surfacing as the smoke-2 "Expected integer for
73        // arg count" failure. The fix landed here together with the
74        // call-site emit changes in `compiler/expressions/function_calls.rs`.
75        let (count_bits, count_kind) = self.pop_kinded()?;
76        let count_slot = KindedSlot::new(ValueSlot::from_raw(count_bits), count_kind);
77        let count = crate::executor::builtins::kind_coerce::int_operand(&count_slot)
78            .map_err(|_| {
79                VMError::RuntimeError(format!(
80                    "pop_builtin_args: arg-count slot must be integer-family, got kind {:?}",
81                    count_kind
82                ))
83            })? as usize;
84        // Drop the arg-count's share (inline scalar — no-op for integer
85        // kinds, but the discipline lives at the §2.7.7 parallel-kind
86        // boundary).
87        crate::executor::vm_impl::stack::drop_with_kind(count_bits, count_kind);
88
89        let mut args: Vec<KindedSlot> = Vec::with_capacity(count);
90        for _ in 0..count {
91            let (bits, kind) = self.pop_kinded()?;
92            // The pop transferred the slot's share to us; wrap it in a
93            // KindedSlot which will Drop-retire the share when the
94            // builtin call's arg vec is dropped.
95            args.push(KindedSlot::new(ValueSlot::from_raw(bits), kind));
96        }
97        args.reverse();
98        Ok(args)
99    }
100
101    /// Push a `KindedSlot` result back onto the stack. The carrier's
102    /// share transfers into the slot; we `mem::forget` the carrier so its
103    /// `Drop` does not retire the share that the slot now owns.
104    #[inline]
105    pub(crate) fn push_kinded_slot(&mut self, slot: KindedSlot) -> Result<(), VMError> {
106        let bits = slot.slot().raw();
107        let kind = slot.kind();
108        std::mem::forget(slot);
109        self.push_kinded(bits, kind)
110    }
111
112    // ========================================================================
113    // Builtin Dispatch
114    //
115    // Wave 5a flipped the dispatch SHAPE: every arm produces /
116    // consumes `Vec<KindedSlot>`. Wave 5b lands the math/array/utility
117    // body migrations and wires the dispatch arms.
118
119    pub fn op_builtin_call(
120        &mut self,
121        instruction: &Instruction,
122        ctx: Option<&mut shape_runtime::context::ExecutionContext>,
123    ) -> Result<(), VMError> {
124        if let Some(Operand::Builtin(builtin)) = instruction.operand {
125            let _ctx = ctx;
126            match builtin {
127                // ── Wave 5b: math builtins ────────────────────────────────
128                BuiltinFunction::Abs => {
129                    let args = self.pop_builtin_args()?;
130                    let r = super::super::builtins::math::builtin_abs(&args)?;
131                    self.push_kinded_slot(r)?;
132                }
133                BuiltinFunction::Sqrt => {
134                    let args = self.pop_builtin_args()?;
135                    let r = super::super::builtins::math::builtin_sqrt(&args)?;
136                    self.push_kinded_slot(r)?;
137                }
138                BuiltinFunction::Ln => {
139                    let args = self.pop_builtin_args()?;
140                    let r = super::super::builtins::math::builtin_ln(&args)?;
141                    self.push_kinded_slot(r)?;
142                }
143                BuiltinFunction::Pow => {
144                    let args = self.pop_builtin_args()?;
145                    let r = super::super::builtins::math::builtin_pow(&args)?;
146                    self.push_kinded_slot(r)?;
147                }
148                BuiltinFunction::Exp => {
149                    let args = self.pop_builtin_args()?;
150                    let r = super::super::builtins::math::builtin_exp(&args)?;
151                    self.push_kinded_slot(r)?;
152                }
153                BuiltinFunction::Log => {
154                    let args = self.pop_builtin_args()?;
155                    let r = super::super::builtins::math::builtin_log(&args)?;
156                    self.push_kinded_slot(r)?;
157                }
158                BuiltinFunction::Floor => {
159                    let args = self.pop_builtin_args()?;
160                    let r = super::super::builtins::math::builtin_floor(&args)?;
161                    self.push_kinded_slot(r)?;
162                }
163                BuiltinFunction::Ceil => {
164                    let args = self.pop_builtin_args()?;
165                    let r = super::super::builtins::math::builtin_ceil(&args)?;
166                    self.push_kinded_slot(r)?;
167                }
168                BuiltinFunction::Round => {
169                    let args = self.pop_builtin_args()?;
170                    let r = super::super::builtins::math::builtin_round(&args)?;
171                    self.push_kinded_slot(r)?;
172                }
173                BuiltinFunction::Sin => {
174                    let args = self.pop_builtin_args()?;
175                    let r = super::super::builtins::math::builtin_sin(&args)?;
176                    self.push_kinded_slot(r)?;
177                }
178                BuiltinFunction::Cos => {
179                    let args = self.pop_builtin_args()?;
180                    let r = super::super::builtins::math::builtin_cos(&args)?;
181                    self.push_kinded_slot(r)?;
182                }
183                BuiltinFunction::Tan => {
184                    let args = self.pop_builtin_args()?;
185                    let r = super::super::builtins::math::builtin_tan(&args)?;
186                    self.push_kinded_slot(r)?;
187                }
188                BuiltinFunction::Asin => {
189                    let args = self.pop_builtin_args()?;
190                    let r = super::super::builtins::math::builtin_asin(&args)?;
191                    self.push_kinded_slot(r)?;
192                }
193                BuiltinFunction::Acos => {
194                    let args = self.pop_builtin_args()?;
195                    let r = super::super::builtins::math::builtin_acos(&args)?;
196                    self.push_kinded_slot(r)?;
197                }
198                BuiltinFunction::Atan => {
199                    let args = self.pop_builtin_args()?;
200                    let r = super::super::builtins::math::builtin_atan(&args)?;
201                    self.push_kinded_slot(r)?;
202                }
203                BuiltinFunction::Min => {
204                    let args = self.pop_builtin_args()?;
205                    let r = super::super::builtins::math::builtin_min(&args)?;
206                    self.push_kinded_slot(r)?;
207                }
208                BuiltinFunction::Max => {
209                    let args = self.pop_builtin_args()?;
210                    let r = super::super::builtins::math::builtin_max(&args)?;
211                    self.push_kinded_slot(r)?;
212                }
213                BuiltinFunction::StdDev => {
214                    let args = self.pop_builtin_args()?;
215                    let r = super::super::builtins::math::builtin_stddev(&args)?;
216                    self.push_kinded_slot(r)?;
217                }
218                BuiltinFunction::Sign => {
219                    let args = self.pop_builtin_args()?;
220                    let r = super::super::builtins::math::builtin_sign(&args)?;
221                    self.push_kinded_slot(r)?;
222                }
223                BuiltinFunction::Gcd => {
224                    let args = self.pop_builtin_args()?;
225                    let r = super::super::builtins::math::builtin_gcd(&args)?;
226                    self.push_kinded_slot(r)?;
227                }
228                BuiltinFunction::Lcm => {
229                    let args = self.pop_builtin_args()?;
230                    let r = super::super::builtins::math::builtin_lcm(&args)?;
231                    self.push_kinded_slot(r)?;
232                }
233                BuiltinFunction::Hypot => {
234                    let args = self.pop_builtin_args()?;
235                    let r = super::super::builtins::math::builtin_hypot(&args)?;
236                    self.push_kinded_slot(r)?;
237                }
238                BuiltinFunction::Clamp => {
239                    let args = self.pop_builtin_args()?;
240                    let r = super::super::builtins::math::builtin_clamp(&args)?;
241                    self.push_kinded_slot(r)?;
242                }
243                BuiltinFunction::IsNaN => {
244                    let args = self.pop_builtin_args()?;
245                    let r = super::super::builtins::math::builtin_is_nan(&args)?;
246                    self.push_kinded_slot(r)?;
247                }
248                BuiltinFunction::IsFinite => {
249                    let args = self.pop_builtin_args()?;
250                    let r = super::super::builtins::math::builtin_is_finite(&args)?;
251                    self.push_kinded_slot(r)?;
252                }
253
254                // ── Wave 5b: array builtins ───────────────────────────────
255                BuiltinFunction::Push => {
256                    let args = self.pop_builtin_args()?;
257                    let r = super::super::builtins::array_ops::builtin_push(&args)?;
258                    self.push_kinded_slot(r)?;
259                }
260                BuiltinFunction::Pop => {
261                    let args = self.pop_builtin_args()?;
262                    let r = super::super::builtins::array_ops::builtin_pop(&args)?;
263                    self.push_kinded_slot(r)?;
264                }
265                BuiltinFunction::First => {
266                    let args = self.pop_builtin_args()?;
267                    let r = super::super::builtins::array_ops::builtin_first(&args)?;
268                    self.push_kinded_slot(r)?;
269                }
270                BuiltinFunction::Last => {
271                    let args = self.pop_builtin_args()?;
272                    let r = super::super::builtins::array_ops::builtin_last(&args)?;
273                    self.push_kinded_slot(r)?;
274                }
275                BuiltinFunction::Zip => {
276                    let args = self.pop_builtin_args()?;
277                    let r = super::super::builtins::array_ops::builtin_zip(&args)?;
278                    self.push_kinded_slot(r)?;
279                }
280                BuiltinFunction::Filled => {
281                    let args = self.pop_builtin_args()?;
282                    let r = super::super::builtins::array_ops::builtin_filled(&args)?;
283                    self.push_kinded_slot(r)?;
284                }
285                BuiltinFunction::Range => {
286                    let args = self.pop_builtin_args()?;
287                    let r = super::super::builtins::array_ops::builtin_range(&args)?;
288                    self.push_kinded_slot(r)?;
289                }
290                BuiltinFunction::Slice => {
291                    let args = self.pop_builtin_args()?;
292                    let r = super::super::builtins::array_ops::builtin_slice(&args)?;
293                    self.push_kinded_slot(r)?;
294                }
295
296                // ── Wave 5b: utility builtins ─────────────────────────────
297                BuiltinFunction::ObjectRest => {
298                    let args = self.pop_builtin_args()?;
299                    let r = self.builtin_object_rest(&args)?;
300                    self.push_kinded_slot(r)?;
301                }
302                BuiltinFunction::Snapshot => {
303                    // Snapshot suspends execution; never returns a value.
304                    let _args = self.pop_builtin_args()?;
305                    return Err(VMError::Suspended {
306                        future_id: SNAPSHOT_FUTURE_ID,
307                        resume_ip: self.ip,
308                    });
309                }
310                BuiltinFunction::Exit => {
311                    let args = self.pop_builtin_args()?;
312                    let code = if args.is_empty() {
313                        0
314                    } else {
315                        // Best-effort code extraction. The arg comes in as
316                        // Bool-kinded (Wave 6 stack-ABI gap); reinterpret the
317                        // raw bits as i64 since `exit(code)` is documented to
318                        // take an int.
319                        args[0].slot.raw() as i64 as i32
320                    };
321                    std::process::exit(code);
322                }
323                BuiltinFunction::Print => {
324                    // ADR-006 §2.7.4 — pop the kinded args, format each
325                    // through `ValueFormatter::format_kinded` (top-level
326                    // unquoted-string rendering, nested quotes inside
327                    // containers), join with spaces, surface to the
328                    // `OutputAdapter::print` of the active
329                    // `ExecutionContext`. Returns the unit/null sentinel
330                    // per the §2.7.4 GENERIC_CARRIER ABI.
331                    //
332                    // The pushed result is a `Ptr(HeapKind::String)`-kind
333                    // null slot rather than `KindedSlot::none()`'s
334                    // `Bool=0` shape: `wire_conversion::slot_to_wire`
335                    // projects `Ptr(_)` with bits=0 to `WireValue::Null`,
336                    // which the script runner suppresses when printing
337                    // the program's final value (`script_cmd.rs:1353`).
338                    // The `Bool=0` sentinel would otherwise surface as a
339                    // spurious `false` line after every `print()`.
340                    let args = self.pop_builtin_args()?;
341                    self.builtin_print(&args, _ctx)?;
342                    let null_slot = KindedSlot::new(
343                        ValueSlot::from_raw(0),
344                        shape_value::NativeKind::Ptr(
345                            shape_value::HeapKind::String,
346                        ),
347                    );
348                    self.push_kinded_slot(null_slot)?;
349                }
350                BuiltinFunction::Format
351                | BuiltinFunction::FormatValueWithMeta => {
352                    // Universal value-to-string. `Format` joins multiple
353                    // args without separator (Shape's `format("a", "b")`
354                    // → `"ab"` legacy semantics); `FormatValueWithMeta`
355                    // is the single-arg `expr.to_string()` /
356                    // `f"{expr}"` interpolation path.
357                    let args = self.pop_builtin_args()?;
358                    let r = self.builtin_format(&args)?;
359                    self.push_kinded_slot(r)?;
360                }
361                BuiltinFunction::FormatValueWithSpec => {
362                    // Args: [value, spec_tag, …spec-payload]. Currently
363                    // routes the basic FORMAT_SPEC_FIXED path; the Table
364                    // arm surfaces as `NotImplemented` per W13 playbook
365                    // §7.4 surface-and-stop.
366                    let args = self.pop_builtin_args()?;
367                    let r = self.builtin_format_with_spec(&args)?;
368                    self.push_kinded_slot(r)?;
369                }
370
371                // ── Wave 5c: type-introspection + conversion + native-interop ──
372                BuiltinFunction::IsNumber
373                | BuiltinFunction::IsString
374                | BuiltinFunction::IsBool
375                | BuiltinFunction::IsArray
376                | BuiltinFunction::IsObject
377                | BuiltinFunction::IsDataRow => {
378                    // SURFACE per ADR-006 §2.7.14: phase-1b-vm wave 5c —
379                    // is_* type-check body migration deferred. Drain args
380                    // to keep the §2.7.7 parallel-kind track balanced,
381                    // then return a structured `NotImplemented` so the
382                    // dispatch arm fails loudly without panicking. The
383                    // re-fill rebuilds bodies against the `KindedSlot`
384                    // carrier (Q8 carrier-API-bound).
385                    let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
386                    return Err(VMError::NotImplemented(format!(
387                        "phase-1b-vm-wave-5c-is-type-check: {:?} body \
388                         migration to kinded carrier (ADR-006 §2.7.6) \
389                         pending (v0.4 / planned)",
390                        builtin
391                    )));
392                }
393                BuiltinFunction::ToString
394                | BuiltinFunction::ToNumber
395                | BuiltinFunction::ToBool => {
396                    // SURFACE per ADR-006 §2.7.14: phase-1b-vm wave 5c —
397                    // conversion body migration (`dispatch_conversion_builtin`)
398                    // deferred. Drain args to balance the §2.7.7 parallel-
399                    // kind track before surfacing.
400                    let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
401                    return Err(VMError::NotImplemented(format!(
402                        "phase-1b-vm-wave-5c-conversion: {:?} body \
403                         migration to kinded carrier (dispatch_conversion_builtin) \
404                         pending (v0.4 / planned)",
405                        builtin
406                    )));
407                }
408                BuiltinFunction::NativePtrSize
409                | BuiltinFunction::NativePtrNewCell
410                | BuiltinFunction::NativePtrFreeCell
411                | BuiltinFunction::NativePtrReadPtr
412                | BuiltinFunction::NativePtrWritePtr
413                | BuiltinFunction::NativeTableFromArrowC
414                | BuiltinFunction::NativeTableFromArrowCTyped
415                | BuiltinFunction::NativeTableBindType => {
416                    // SURFACE per ADR-006 §2.7.14: phase-1b-vm wave 5c —
417                    // native-interop body migration
418                    // (`dispatch_native_interop_builtin`) deferred. Drain
419                    // args to balance the §2.7.7 parallel-kind track.
420                    let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
421                    return Err(VMError::NotImplemented(format!(
422                        "phase-1b-vm-wave-5c-native-interop: {:?} body \
423                         migration to kinded carrier (dispatch_native_interop_builtin) \
424                         pending (v0.4 / planned)",
425                        builtin
426                    )));
427                }
428                BuiltinFunction::TypeOf => {
429                    // SURFACE per ADR-006 §2.7.14: phase-1b-vm wave 5c —
430                    // `TypeOf` body migration pending. The legacy body
431                    // popped via the deleted raw-bits stack shim; needs a
432                    // kinded-carrier rebuild per ADR-006 §2.7.6. No args
433                    // are popped because the legacy emit shape did not
434                    // emit an arity prefix here.
435                    return Err(VMError::NotImplemented(
436                        "phase-1b-vm-wave-5c-typeof: TypeOf body migration \
437                         to kinded carrier (ADR-006 §2.7.6) pending \
438                         (v0.4 / planned)"
439                            .to_string(),
440                    ));
441                }
442
443                // ── Wave 5d: closure-driven array builtins + intrinsics ──────
444                BuiltinFunction::Map
445                | BuiltinFunction::Filter
446                | BuiltinFunction::Reduce
447                | BuiltinFunction::ForEach
448                | BuiltinFunction::Find
449                | BuiltinFunction::FindIndex
450                | BuiltinFunction::Some
451                | BuiltinFunction::Every
452                | BuiltinFunction::ControlFold => {
453                    // SURFACE per ADR-006 §2.7.14: phase-1b-vm wave 5d —
454                    // closure-driven array builtin body migration deferred.
455                    // The rebuild routes through the §2.7.11 / Q12 kinded
456                    // value-call ABI for the closure invocation. Drain
457                    // args to balance the §2.7.7 parallel-kind track.
458                    let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
459                    return Err(VMError::NotImplemented(format!(
460                        "phase-1b-vm-wave-5d-closure-array: {:?} body \
461                         migration to kinded carrier + value-call ABI \
462                         (ADR-006 §2.7.11/Q12) pending (v0.4 / planned)",
463                        builtin
464                    )));
465                }
466                BuiltinFunction::IntrinsicVecAbs
467                | BuiltinFunction::IntrinsicVecSqrt
468                | BuiltinFunction::IntrinsicVecLn
469                | BuiltinFunction::IntrinsicVecExp
470                | BuiltinFunction::IntrinsicVecAdd
471                | BuiltinFunction::IntrinsicVecSub
472                | BuiltinFunction::IntrinsicVecMul
473                | BuiltinFunction::IntrinsicVecDiv
474                | BuiltinFunction::IntrinsicVecMax
475                | BuiltinFunction::IntrinsicVecMin
476                | BuiltinFunction::IntrinsicVecSelect
477                | BuiltinFunction::IntrinsicVecAddI64 => {
478                    // SURFACE per ADR-006 §2.7.14: phase-1b-vm wave 5d —
479                    // SIMD vector intrinsic body migration
480                    // (`handle_vector_intrinsic`) deferred. No arg pop
481                    // because the legacy dispatcher handled it internally;
482                    // surface immediately so the operator sees a clean
483                    // error before any state mutation. Stack rebalancing
484                    // is the rebuild's responsibility per
485                    // `executor/builtins/vector_intrinsics.rs`.
486                    return Err(VMError::NotImplemented(format!(
487                        "phase-1b-vm-wave-5d-vec-intrinsic: {:?} body \
488                         migration to kinded carrier (handle_vector_intrinsic) \
489                         pending (v0.4 / planned)",
490                        builtin
491                    )));
492                }
493                BuiltinFunction::IntrinsicMatMulVec
494                | BuiltinFunction::IntrinsicMatMulMat
495                | BuiltinFunction::IntrinsicMatAdd
496                | BuiltinFunction::IntrinsicMatSub => {
497                    // SURFACE per ADR-006 §2.7.14: phase-1b-vm wave 5d —
498                    // matrix intrinsic body migration
499                    // (`handle_matrix_intrinsic`) deferred. See sibling
500                    // vector arm above for arg-pop rationale.
501                    return Err(VMError::NotImplemented(format!(
502                        "phase-1b-vm-wave-5d-mat-intrinsic: {:?} body \
503                         migration to kinded carrier (handle_matrix_intrinsic) \
504                         pending (v0.4 / planned)",
505                        builtin
506                    )));
507                }
508                BuiltinFunction::IntrinsicMinimize => {
509                    // SURFACE per ADR-006 §2.7.14: phase-1b-vm wave 5d —
510                    // `minimize` intrinsic body deferred. Drain args to
511                    // balance the §2.7.7 parallel-kind track.
512                    let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
513                    return Err(VMError::NotImplemented(
514                        "phase-1b-vm-wave-5d-minimize: minimize() intrinsic \
515                         body migration to kinded carrier + value-call ABI \
516                         (ADR-006 §2.7.11/Q12) pending (v0.4 / planned)"
517                            .to_string(),
518                    ));
519                }
520                // W12-stdlib-intrinsic-collapse (Wave-2-Agent-G, 2026-05-14):
521                // `BuiltinFunction::IntrinsicSum` deleted as the canonical
522                // 7th defection-attractor instance (parallel-implementation
523                // across producer/consumer carrier-shape boundaries — the
524                // old handler body's own comment said "mirror of
525                // v2_int_sum/v2_float_sum exactly"). Stdlib
526                // `pub fn sum(series) { series.sum() }` now routes through
527                // the PHF `.sum()` method dispatch — single discriminator
528                // per ADR-005 §1, `MethodFnV2` ABI per ADR-006 §2.7.10/Q11.
529                BuiltinFunction::IntrinsicBspline2_3dBatch
530                | BuiltinFunction::IntrinsicMean
531                | BuiltinFunction::IntrinsicMin
532                | BuiltinFunction::IntrinsicMax
533                | BuiltinFunction::IntrinsicStd
534                | BuiltinFunction::IntrinsicVariance
535                | BuiltinFunction::IntrinsicRandom
536                | BuiltinFunction::IntrinsicRandomInt
537                | BuiltinFunction::IntrinsicRandomSeed
538                | BuiltinFunction::IntrinsicRandomNormal
539                | BuiltinFunction::IntrinsicRandomArray
540                | BuiltinFunction::IntrinsicDistUniform
541                | BuiltinFunction::IntrinsicDistLognormal
542                | BuiltinFunction::IntrinsicDistExponential
543                | BuiltinFunction::IntrinsicDistPoisson
544                | BuiltinFunction::IntrinsicDistSampleN
545                | BuiltinFunction::IntrinsicBrownianMotion
546                | BuiltinFunction::IntrinsicGbm
547                | BuiltinFunction::IntrinsicOuProcess
548                | BuiltinFunction::IntrinsicRandomWalk
549                | BuiltinFunction::IntrinsicRollingSum
550                | BuiltinFunction::IntrinsicRollingMean
551                | BuiltinFunction::IntrinsicRollingStd
552                | BuiltinFunction::IntrinsicRollingMin
553                | BuiltinFunction::IntrinsicRollingMax
554                | BuiltinFunction::IntrinsicEma
555                | BuiltinFunction::IntrinsicLinearRecurrence
556                | BuiltinFunction::IntrinsicShift
557                | BuiltinFunction::IntrinsicDiff
558                | BuiltinFunction::IntrinsicPctChange
559                | BuiltinFunction::IntrinsicFillna
560                | BuiltinFunction::IntrinsicCumsum
561                | BuiltinFunction::IntrinsicCumprod
562                | BuiltinFunction::IntrinsicClip
563                | BuiltinFunction::IntrinsicCorrelation
564                | BuiltinFunction::IntrinsicCovariance
565                | BuiltinFunction::IntrinsicPercentile
566                | BuiltinFunction::IntrinsicMedian
567                | BuiltinFunction::IntrinsicAtan2
568                | BuiltinFunction::IntrinsicSinh
569                | BuiltinFunction::IntrinsicCosh
570                | BuiltinFunction::IntrinsicTanh
571                | BuiltinFunction::IntrinsicCharCode
572                | BuiltinFunction::IntrinsicFromCharCode
573                | BuiltinFunction::IntrinsicSeries => {
574                    // SURFACE per ADR-006 §2.7.14: phase-1b-vm wave 5d —
575                    // stats / random / stochastic / rolling / scalar-math /
576                    // series intrinsic body migration
577                    // (`handle_intrinsic_builtin`) deferred. Covers the
578                    // ~37-variant cluster (Bspline2_3dBatch, Mean, Min,
579                    // Max, Std, Variance, Random*, Dist*, BrownianMotion,
580                    // Gbm, OuProcess, RandomWalk, Rolling*, Ema,
581                    // LinearRecurrence, Shift, Diff, PctChange, Fillna,
582                    // Cumsum, Cumprod, Clip, Correlation, Covariance,
583                    // Percentile, Median, Atan2, Sinh, Cosh, Tanh,
584                    // CharCode, FromCharCode, Series). No arg pop because
585                    // the legacy dispatcher handled per-variant arity; the
586                    // rebuild lives at `executor/builtins/intrinsics/`
587                    // (math.rs / statistical.rs / signal.rs).
588                    return Err(VMError::NotImplemented(format!(
589                        "phase-1b-vm-wave-5d-intrinsic: {:?} body migration \
590                         to kinded carrier (handle_intrinsic_builtin) \
591                         pending (v0.4 / planned)",
592                        builtin
593                    )));
594                }
595
596                // ── Wave 5e: constructors (Result/Option, Set, Deque,
597                // PriorityQueue, HashMap, Mutex/Atomic/Lazy/Channel),
598                // Content builders, DateTime constructors, Table from
599                // rows, JSON navigation helpers, Window functions, Join,
600                // Reflect, MatFromFlat, MakeContent*. ─────────────────────
601                BuiltinFunction::SomeCtor => {
602                    // Wave 14 W14-variant-codegen (ADR-006 §2.7.17 / Q18,
603                    // 2026-05-10): `Some(x)` builds a fresh
604                    // `Arc<OptionData>` carrier with `is_some=true` and
605                    // the popped argument as the typed payload share.
606                    // The `pop_builtin_args` carrier owns one strong-
607                    // count share per heap-bearing kind; ownership
608                    // transfers into the `OptionData::payload` slot
609                    // verbatim (the carrier is moved, not cloned).
610                    let mut args: Vec<KindedSlot> = self.pop_builtin_args()?;
611                    if args.len() != 1 {
612                        return Err(VMError::RuntimeError(format!(
613                            "Some() expects 1 argument, got {}",
614                            args.len()
615                        )));
616                    }
617                    let payload = args.remove(0);
618                    let opt = std::sync::Arc::new(
619                        shape_value::heap_value::OptionData::some(payload),
620                    );
621                    self.push_kinded_slot(KindedSlot::from_option(opt))?;
622                }
623                BuiltinFunction::OkCtor => {
624                    // Wave 14 W14-variant-codegen (ADR-006 §2.7.17 / Q18,
625                    // 2026-05-10): `Ok(x)` builds a fresh
626                    // `Arc<ResultData>` carrier with `is_ok=true` and
627                    // the popped argument as the typed payload share.
628                    let mut args: Vec<KindedSlot> = self.pop_builtin_args()?;
629                    if args.len() != 1 {
630                        return Err(VMError::RuntimeError(format!(
631                            "Ok() expects 1 argument, got {}",
632                            args.len()
633                        )));
634                    }
635                    let payload = args.remove(0);
636                    let res = std::sync::Arc::new(
637                        shape_value::heap_value::ResultData::ok(payload),
638                    );
639                    self.push_kinded_slot(KindedSlot::from_result(res))?;
640                }
641                BuiltinFunction::ErrCtor => {
642                    // Wave 14 W14-variant-codegen (ADR-006 §2.7.17 / Q18,
643                    // 2026-05-10): `Err(e)` builds a fresh
644                    // `Arc<ResultData>` carrier with `is_ok=false` and
645                    // the popped argument as the typed payload share.
646                    let mut args: Vec<KindedSlot> = self.pop_builtin_args()?;
647                    if args.len() != 1 {
648                        return Err(VMError::RuntimeError(format!(
649                            "Err() expects 1 argument, got {}",
650                            args.len()
651                        )));
652                    }
653                    let payload = args.remove(0);
654                    let res = std::sync::Arc::new(
655                        shape_value::heap_value::ResultData::err(payload),
656                    );
657                    self.push_kinded_slot(KindedSlot::from_result(res))?;
658                }
659                BuiltinFunction::HashMapCtor => {
660                    // Wave 2 Round 3b C2-joint ckpt-2 (2026-05-14): per
661                    // ADR-006 §2.7.24 Q25.B SUPERSEDED, `let m = HashMap()`
662                    // produces a fresh empty `Arc<HashMapKindedRef>` slot.
663                    // Default empty variant chosen is `String` (the typical
664                    // initial element type in user code; the variant tag
665                    // gets specialized on first insert via clone-on-write
666                    // — ckpt-3 mutation-API rebuild). Reader contract: kind
667                    // == Ptr(HeapKind::HashMap), bits =
668                    // Arc::into_raw::<HashMapKindedRef>.
669                    let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
670                    let empty_kref = shape_value::heap_value::HashMapKindedRef::String(
671                        std::sync::Arc::new(
672                            shape_value::heap_value::HashMapData::<
673                                *const shape_value::v2::string_obj::StringObj,
674                            >::new(),
675                        ),
676                    );
677                    let hm = std::sync::Arc::new(empty_kref);
678                    self.push_kinded_slot(KindedSlot::from_hashmap(hm))?;
679                }
680                BuiltinFunction::SetCtor => {
681                    // Wave 13 W13-hashset-rebuild (ADR-006 §2.7.15 / Q16,
682                    // 2026-05-10): empty Set ctor — `Set()` takes no
683                    // args at landing; `Set([elements])` initialization
684                    // is a follow-up. Build empty Arc<HashSetData> and
685                    // push via KindedSlot::from_hashset.
686                    let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
687                    let empty = std::sync::Arc::new(
688                        shape_value::heap_value::HashSetData::new(),
689                    );
690                    let result = KindedSlot::from_hashset(empty);
691                    self.push_kinded_slot(result)?;
692                }
693                BuiltinFunction::DequeCtor => {
694                    // Wave 15 W15-deque (ADR-006 §2.7.19 / Q20,
695                    // 2026-05-10): empty Deque ctor — `Deque()` takes
696                    // no args at landing; `Deque([elements])`
697                    // initialization is a follow-up. Build empty
698                    // Arc<DequeData> and push via KindedSlot::from_deque.
699                    // Reader contract: kind == Ptr(HeapKind::Deque),
700                    // bits = Arc::into_raw::<DequeData>.
701                    let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
702                    let empty = std::sync::Arc::new(
703                        shape_value::heap_value::DequeData::new(),
704                    );
705                    let result = KindedSlot::from_deque(empty);
706                    self.push_kinded_slot(result)?;
707                }
708                BuiltinFunction::PriorityQueueCtor => {
709                    // Wave 15 W15-priority-queue (ADR-006 §2.7.18 /
710                    // Q19, 2026-05-10): empty PriorityQueue ctor —
711                    // discard any args (the surface form
712                    // `PriorityQueue()` takes no args at landing;
713                    // `PriorityQueue([elements])` initialization is a
714                    // follow-up). Build an empty
715                    // `Arc::new(PriorityQueueData::new())` and push as
716                    // a `KindedSlot::from_priority_queue(...)`.
717                    let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
718                    let empty = std::sync::Arc::new(
719                        shape_value::heap_value::PriorityQueueData::new(),
720                    );
721                    let result = KindedSlot::from_priority_queue(empty);
722                    self.push_kinded_slot(result)?;
723                }
724                BuiltinFunction::ChannelCtor => {
725                    // Wave 15 W15-channel-rebuild (ADR-006 §2.7.20 / Q21,
726                    // 2026-05-10): empty Channel ctor — `Channel()`
727                    // takes no args at landing; bounded-capacity
728                    // initialization is a follow-up. Build empty
729                    // `Arc<ChannelData>` (interior `Mutex<ChannelInner>`)
730                    // and push via `KindedSlot::from_channel`. Reader
731                    // contract: kind == Ptr(HeapKind::Channel),
732                    // bits = Arc::into_raw::<ChannelData>.
733                    //
734                    // Cross-task blocking `recv()` requires the §2.7.4
735                    // task-scheduler boundary and is SURFACE'd at the
736                    // method body — see
737                    // `executor/objects/channel_methods.rs`.
738                    let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
739                    let empty = std::sync::Arc::new(
740                        shape_value::heap_value::ChannelData::new(),
741                    );
742                    let result = KindedSlot::from_channel(empty);
743                    self.push_kinded_slot(result)?;
744                }
745                BuiltinFunction::MutexCtor => {
746                    // W17-concurrency (ADR-006 §2.7.25, 2026-05-11):
747                    // `Mutex(initial_value)` builds an `Arc<MutexData>`
748                    // wrapping the initial value `KindedSlot` (any
749                    // kind). The initial-value share moves into the
750                    // MutexInner cell — `pop_builtin_args` already
751                    // consumed the arg's stack share, and
752                    // `MutexData::new` takes ownership of the slot.
753                    let mut args: Vec<KindedSlot> = self.pop_builtin_args()?;
754                    if args.len() != 1 {
755                        return Err(VMError::RuntimeError(format!(
756                            "Mutex() requires exactly 1 argument \
757                             (initial value), got {}",
758                            args.len()
759                        )));
760                    }
761                    let initial = args.remove(0);
762                    let m = std::sync::Arc::new(
763                        shape_value::heap_value::MutexData::new(initial),
764                    );
765                    let result = KindedSlot::from_mutex(m);
766                    self.push_kinded_slot(result)?;
767                }
768                BuiltinFunction::AtomicCtor => {
769                    // W17-concurrency (ADR-006 §2.7.25, 2026-05-11):
770                    // `Atomic(initial)` builds an `Arc<AtomicData>`
771                    // wrapping a `std::sync::atomic::AtomicI64`.
772                    // i64-only at landing — non-int args error.
773                    let args: Vec<KindedSlot> = self.pop_builtin_args()?;
774                    if args.len() != 1 {
775                        return Err(VMError::RuntimeError(format!(
776                            "Atomic() requires exactly 1 argument \
777                             (initial int value), got {}",
778                            args.len()
779                        )));
780                    }
781                    let initial = args[0].as_i64().ok_or_else(|| {
782                        VMError::RuntimeError(format!(
783                            "Atomic() argument must be an int (got \
784                             kind {:?}); typed-payload Atomic<T> is a \
785                             future amendment per ADR-006 §2.7.25",
786                            args[0].kind
787                        ))
788                    })?;
789                    let a = std::sync::Arc::new(
790                        shape_value::heap_value::AtomicData::new(initial),
791                    );
792                    let result = KindedSlot::from_atomic(a);
793                    self.push_kinded_slot(result)?;
794                }
795                BuiltinFunction::LazyCtor => {
796                    // W17-concurrency (ADR-006 §2.7.25, 2026-05-11):
797                    // `Lazy(|| ...)` builds an `Arc<LazyData>` wrapping
798                    // the initializer closure. The closure share moves
799                    // into the LazyInner cell — the handler tier's
800                    // `lazy.get()` takes the initializer back out via
801                    // `take_initializer()` for the
802                    // `vm.call_value_immediate_nb` invocation.
803                    let mut args: Vec<KindedSlot> = self.pop_builtin_args()?;
804                    if args.len() != 1 {
805                        return Err(VMError::RuntimeError(format!(
806                            "Lazy() requires exactly 1 argument \
807                             (initializer closure), got {}",
808                            args.len()
809                        )));
810                    }
811                    let initializer = args.remove(0);
812                    // Kind-validate: must be a Closure (closure-call
813                    // path goes through `call_value_immediate_nb` which
814                    // requires Ptr(HeapKind::Closure) callee kind).
815                    if !matches!(
816                        initializer.kind,
817                        shape_value::NativeKind::Ptr(
818                            shape_value::heap_value::HeapKind::Closure
819                        )
820                    ) {
821                        return Err(VMError::RuntimeError(format!(
822                            "Lazy() argument must be a closure (got \
823                             kind {:?})",
824                            initializer.kind
825                        )));
826                    }
827                    let l = std::sync::Arc::new(
828                        shape_value::heap_value::LazyData::new(initializer),
829                    );
830                    let result = KindedSlot::from_lazy(l);
831                    self.push_kinded_slot(result)?;
832                }
833                BuiltinFunction::ContentTextCtor => {
834                    // W18.6 (R8 W3 2026-05-24 — supervisor D3+D4):
835                    // `Content.text(s: string) -> content` user-facing
836                    // constructor. Pops one string arg, wraps as
837                    // `ContentNode::plain(s)`, pushes as a
838                    // `Ptr(HeapKind::Content)` kinded slot via the new
839                    // `KindedSlot::from_content` constructor. The slot
840                    // owns one `Arc<ContentNode>` strong-count share;
841                    // Drop / Clone arms for `HeapKind::Content` already
842                    // dispatch the matching retain/release.
843                    let args: Vec<KindedSlot> = self.pop_builtin_args()?;
844                    if args.len() != 1 {
845                        return Err(VMError::RuntimeError(format!(
846                            "Content.text() requires exactly 1 argument \
847                             (string), got {}",
848                            args.len()
849                        )));
850                    }
851                    let s = args[0].as_str().ok_or_else(|| {
852                        VMError::RuntimeError(format!(
853                            "Content.text() argument must be a string (got \
854                             kind {:?})",
855                            args[0].kind
856                        ))
857                    })?;
858                    let node = shape_value::content::ContentNode::plain(s);
859                    let result = KindedSlot::from_content(std::sync::Arc::new(node));
860                    self.push_kinded_slot(result)?;
861                }
862                BuiltinFunction::ContentCodeCtor => {
863                    // W18.6 (R8 W3 2026-05-24): `Content.code(source: string)
864                    // -> content` minimum-viable constructor (single-arg
865                    // form; no language label). Mirror of ContentTextCtor
866                    // for the `ContentNode::Code` variant. Per-renderer
867                    // dispatch handles the styling.
868                    let args: Vec<KindedSlot> = self.pop_builtin_args()?;
869                    if args.len() != 1 {
870                        return Err(VMError::RuntimeError(format!(
871                            "Content.code() requires exactly 1 argument \
872                             (source string), got {}",
873                            args.len()
874                        )));
875                    }
876                    let s = args[0].as_str().ok_or_else(|| {
877                        VMError::RuntimeError(format!(
878                            "Content.code() argument must be a string (got \
879                             kind {:?})",
880                            args[0].kind
881                        ))
882                    })?;
883                    let node = shape_value::content::ContentNode::Code {
884                        language: None,
885                        source: s.to_string(),
886                    };
887                    let result = KindedSlot::from_content(std::sync::Arc::new(node));
888                    self.push_kinded_slot(result)?;
889                }
890                BuiltinFunction::ContentChart => {
891                    // Chart constructor is v0.4 scope per supervisor D4
892                    // (R8 W3, 2026-05-24) — ECharts integration is its own
893                    // workstream. W18.5 ships Table / Code / KeyValue only;
894                    // surfacing the Chart MVP keeps the dispatch arm honest
895                    // (no Bool-default kinded shim per playbook §4 #9).
896                    let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
897                    return Err(VMError::NotImplemented(
898                        "Content.chart(...) is v0.4 scope per supervisor D4 \
899                         (R8 W3, 2026-05-24) — ECharts integration is its \
900                         own workstream. W18.5 ships Table / Code / KeyValue \
901                         builders only."
902                            .to_string(),
903                    ));
904                }
905                BuiltinFunction::ContentTableCtor => {
906                    // W18.5 (R8 W4, 2026-05-24 — supervisor D4):
907                    // `Content.table(headers: Array<string>, rows: Array<Array<string>>)`
908                    // direct ctor. Builds a `ContentNode::Table` with the
909                    // provided headers + rows + default border. Sibling to
910                    // the per-type `Table::new().headers(...).row(...).build()`
911                    // builder (TableBuilderNew + Content method chain). Per
912                    // supervisor D4 "shortest path builder → content →
913                    // renderer", returns a `Ptr(HeapKind::Content)` slot
914                    // directly — no intermediate typed Table value.
915                    let args: Vec<KindedSlot> = self.pop_builtin_args()?;
916                    let table = build_table_from_headers_and_rows(&args)?;
917                    let node = shape_value::content::ContentNode::Table(table);
918                    let result = KindedSlot::from_content(std::sync::Arc::new(node));
919                    self.push_kinded_slot(result)?;
920                }
921                BuiltinFunction::ContentKvCtor => {
922                    // W18.5 (R8 W4, 2026-05-24 — supervisor D4):
923                    // `Content.kv(keys: Array<string>, values: Array<*>)`
924                    // direct ctor. Pairs each key with its corresponding
925                    // value formatted as a `ContentNode::plain`. Mirrors
926                    // the per-type `KeyValue::new().pair("k", v).build()`
927                    // builder. ContentNode::KeyValue stores `Vec<(String,
928                    // ContentNode)>` so heterogeneous value types coerce
929                    // through `format_kinded` (numeric / bool / string /
930                    // nested content all render).
931                    let args: Vec<KindedSlot> = self.pop_builtin_args()?;
932                    let pairs = build_kv_pairs_from_keys_values(self, &args)?;
933                    let node = shape_value::content::ContentNode::KeyValue(pairs);
934                    let result = KindedSlot::from_content(std::sync::Arc::new(node));
935                    self.push_kinded_slot(result)?;
936                }
937                BuiltinFunction::ContentFragmentCtor => {
938                    // W18.5 (R8 W4, 2026-05-24 — supervisor D4):
939                    // `Content.fragment(parts: Array<content>)` direct
940                    // ctor. Wraps a sequence of Content nodes into a
941                    // single `ContentNode::Fragment` for composition.
942                    let args: Vec<KindedSlot> = self.pop_builtin_args()?;
943                    let parts = collect_content_nodes_from_array_arg(&args)?;
944                    let node = shape_value::content::ContentNode::Fragment(parts);
945                    let result = KindedSlot::from_content(std::sync::Arc::new(node));
946                    self.push_kinded_slot(result)?;
947                }
948                BuiltinFunction::TableBuilderNew => {
949                    // W18.5 (R8 W4, 2026-05-24 — supervisor D4):
950                    // `Table::new()` returns an empty `ContentNode::Table`
951                    // seed. Chainable methods (`headers`, `row`, `border`,
952                    // `build`) are registered in `CONTENT_METHODS` PHF and
953                    // dispatched on the Content receiver. `.build()` is
954                    // identity — returns the receiver. Each chained method
955                    // immutably clones + mutates the underlying ContentNode
956                    // and pushes a fresh `Ptr(HeapKind::Content)` slot.
957                    let args: Vec<KindedSlot> = self.pop_builtin_args()?;
958                    if !args.is_empty() {
959                        return Err(VMError::RuntimeError(format!(
960                            "Table::new() takes no arguments, got {}",
961                            args.len()
962                        )));
963                    }
964                    let empty = shape_value::content::ContentTable {
965                        headers: Vec::new(),
966                        rows: Vec::new(),
967                        border: shape_value::content::BorderStyle::default(),
968                        max_rows: None,
969                        column_types: None,
970                        total_rows: None,
971                        sortable: false,
972                    };
973                    let node = shape_value::content::ContentNode::Table(empty);
974                    let result = KindedSlot::from_content(std::sync::Arc::new(node));
975                    self.push_kinded_slot(result)?;
976                }
977                BuiltinFunction::CodeBuilderNew => {
978                    // W18.5 (R8 W4, 2026-05-24 — supervisor D4):
979                    // `Code::new()` returns an empty `ContentNode::Code`
980                    // seed with no language and empty source. Chainable
981                    // methods (`language`, `source`, `build`) live in
982                    // `CONTENT_METHODS`. The W18.6 `Content.code(s)`
983                    // one-liner ctor coexists — keep both per task spec:
984                    // Content.code(s) is single-arg, Code::new() builder
985                    // is the multi-property form.
986                    let args: Vec<KindedSlot> = self.pop_builtin_args()?;
987                    if !args.is_empty() {
988                        return Err(VMError::RuntimeError(format!(
989                            "Code::new() takes no arguments, got {}",
990                            args.len()
991                        )));
992                    }
993                    let node = shape_value::content::ContentNode::Code {
994                        language: None,
995                        source: String::new(),
996                    };
997                    let result = KindedSlot::from_content(std::sync::Arc::new(node));
998                    self.push_kinded_slot(result)?;
999                }
1000                BuiltinFunction::KeyValueBuilderNew => {
1001                    // W18.5 (R8 W4, 2026-05-24 — supervisor D4):
1002                    // `KeyValue::new()` returns an empty
1003                    // `ContentNode::KeyValue` seed with no pairs. Chainable
1004                    // `.pair(key, value)` accumulates the pair; `.build()`
1005                    // is identity.
1006                    let args: Vec<KindedSlot> = self.pop_builtin_args()?;
1007                    if !args.is_empty() {
1008                        return Err(VMError::RuntimeError(format!(
1009                            "KeyValue::new() takes no arguments, got {}",
1010                            args.len()
1011                        )));
1012                    }
1013                    let node = shape_value::content::ContentNode::KeyValue(Vec::new());
1014                    let result = KindedSlot::from_content(std::sync::Arc::new(node));
1015                    self.push_kinded_slot(result)?;
1016                }
1017                BuiltinFunction::FStringContentText => {
1018                    // R8 W4 W18.4: wrap a string as `ContentNode::plain` for
1019                    // literal segments of a styled f-string.
1020                    let args: Vec<KindedSlot> = self.pop_builtin_args()?;
1021                    if args.len() != 1 {
1022                        return Err(VMError::RuntimeError(format!(
1023                            "FStringContentText requires exactly 1 argument \
1024                             (string), got {}",
1025                            args.len()
1026                        )));
1027                    }
1028                    let s = args[0].as_str().ok_or_else(|| {
1029                        VMError::RuntimeError(format!(
1030                            "FStringContentText argument must be a string \
1031                             (got kind {:?})",
1032                            args[0].kind
1033                        ))
1034                    })?;
1035                    let node = shape_value::content::ContentNode::plain(s);
1036                    let result =
1037                        KindedSlot::from_content(std::sync::Arc::new(node));
1038                    self.push_kinded_slot(result)?;
1039                }
1040                BuiltinFunction::FStringContentStyledText => {
1041                    // R8 W4 W18.4: wrap a string as a styled
1042                    // `ContentNode::Text` (single span). Args:
1043                    // `[value_str, fg_kind, fg_payload, bg_kind, bg_payload,
1044                    //   flags]` (see opcode_defs comment for encoding).
1045                    let args: Vec<KindedSlot> = self.pop_builtin_args()?;
1046                    if args.len() != 6 {
1047                        return Err(VMError::RuntimeError(format!(
1048                            "FStringContentStyledText requires 6 arguments \
1049                             (value, fg_kind, fg_payload, bg_kind, \
1050                             bg_payload, flags), got {}",
1051                            args.len()
1052                        )));
1053                    }
1054                    let value = args[0].as_str().ok_or_else(|| {
1055                        VMError::RuntimeError(format!(
1056                            "FStringContentStyledText value must be a string \
1057                             (got kind {:?})",
1058                            args[0].kind
1059                        ))
1060                    })?;
1061                    let fg_kind = args[1].as_i64().ok_or_else(|| {
1062                        VMError::RuntimeError(
1063                            "FStringContentStyledText fg_kind must be int"
1064                                .to_string(),
1065                        )
1066                    })?;
1067                    let fg_payload = args[2].as_i64().ok_or_else(|| {
1068                        VMError::RuntimeError(
1069                            "FStringContentStyledText fg_payload must be int"
1070                                .to_string(),
1071                        )
1072                    })?;
1073                    let bg_kind = args[3].as_i64().ok_or_else(|| {
1074                        VMError::RuntimeError(
1075                            "FStringContentStyledText bg_kind must be int"
1076                                .to_string(),
1077                        )
1078                    })?;
1079                    let bg_payload = args[4].as_i64().ok_or_else(|| {
1080                        VMError::RuntimeError(
1081                            "FStringContentStyledText bg_payload must be int"
1082                                .to_string(),
1083                        )
1084                    })?;
1085                    let flags = args[5].as_i64().ok_or_else(|| {
1086                        VMError::RuntimeError(
1087                            "FStringContentStyledText flags must be int"
1088                                .to_string(),
1089                        )
1090                    })?;
1091
1092                    let style = decode_fstring_style(
1093                        fg_kind, fg_payload, bg_kind, bg_payload, flags,
1094                    )?;
1095                    let node = shape_value::content::ContentNode::styled(
1096                        value, style,
1097                    );
1098                    let result =
1099                        KindedSlot::from_content(std::sync::Arc::new(node));
1100                    self.push_kinded_slot(result)?;
1101                }
1102                BuiltinFunction::FStringContentFragment => {
1103                    // R8 W4 W18.4: combine N content nodes into a Fragment.
1104                    // Per ADR-006 §2.3 v2-raw-heap: `Ptr(HeapKind::Content)`
1105                    // slots store `Arc::into_raw(Arc<ContentNode>)` bits
1106                    // directly (NOT a `Box<HeapValue>` wrapper). Classify
1107                    // on `args[i].kind`, then deref the raw `*const
1108                    // ContentNode` (mirror of `printing.rs::format_heap_
1109                    // kind`'s Content arm and the `Arc::decrement_strong_
1110                    // count::<ContentNode>` in `heap_value.rs::drop_with_
1111                    // kind` HeapKind::Content arm).
1112                    let args: Vec<KindedSlot> = self.pop_builtin_args()?;
1113                    let mut nodes: Vec<shape_value::content::ContentNode> =
1114                        Vec::with_capacity(args.len());
1115                    for (i, arg) in args.iter().enumerate() {
1116                        if arg.kind
1117                            != shape_value::NativeKind::Ptr(
1118                                shape_value::HeapKind::Content,
1119                            )
1120                        {
1121                            return Err(VMError::RuntimeError(format!(
1122                                "FStringContentFragment arg #{} must be a \
1123                                 content value (got kind {:?})",
1124                                i, arg.kind
1125                            )));
1126                        }
1127                        let bits = arg.slot.raw();
1128                        if bits == 0 {
1129                            return Err(VMError::RuntimeError(format!(
1130                                "FStringContentFragment arg #{} is a null \
1131                                 content pointer",
1132                                i
1133                            )));
1134                        }
1135                        // SAFETY: per the `KindedSlot::from_content`
1136                        // construction contract (and §heap_value.rs::
1137                        // drop_with_kind HeapKind::Content arm), a
1138                        // `Ptr(HeapKind::Content)` slot's bits are
1139                        // `Arc::into_raw(Arc<ContentNode>)`. The borrow is
1140                        // bounded by `args`'s lifetime; the underlying
1141                        // share survives because `args[i]` still owns it.
1142                        // We deep-clone the `ContentNode` (cheap — the
1143                        // node enum carries owned strings + Vecs) into the
1144                        // Fragment because the receiving Vec owns its
1145                        // elements.
1146                        let node: &shape_value::content::ContentNode = unsafe {
1147                            &*(bits as *const shape_value::content::ContentNode)
1148                        };
1149                        nodes.push(node.clone());
1150                    }
1151                    let node = shape_value::content::ContentNode::Fragment(
1152                        nodes,
1153                    );
1154                    let result =
1155                        KindedSlot::from_content(std::sync::Arc::new(node));
1156                    self.push_kinded_slot(result)?;
1157                }
1158                // ── Wave 5e: DateTime constructor builtins ────────────────
1159                //
1160                // DateTime values are `HeapValue::Temporal` carrying
1161                // `TemporalData::DateTime` (ADR-006 §2.3 typed-Arc payload);
1162                // the constructor bodies live in
1163                // `executor/builtins/datetime_builtins.rs` on the
1164                // `&[KindedSlot] -> Result<KindedSlot, VMError>` carrier ABI.
1165                BuiltinFunction::DateTimeNow => {
1166                    let args = self.pop_builtin_args()?;
1167                    let r = super::super::builtins::datetime_builtins::builtin_datetime_now(
1168                        &args,
1169                    )?;
1170                    self.push_kinded_slot(r)?;
1171                }
1172                BuiltinFunction::DateTimeUtc => {
1173                    let args = self.pop_builtin_args()?;
1174                    let r = super::super::builtins::datetime_builtins::builtin_datetime_utc(
1175                        &args,
1176                    )?;
1177                    self.push_kinded_slot(r)?;
1178                }
1179                BuiltinFunction::DateTimeParse => {
1180                    let args = self.pop_builtin_args()?;
1181                    let r = super::super::builtins::datetime_builtins::builtin_datetime_parse(
1182                        &args,
1183                    )?;
1184                    self.push_kinded_slot(r)?;
1185                }
1186                BuiltinFunction::DateTimeFromEpoch => {
1187                    let args = self.pop_builtin_args()?;
1188                    let r =
1189                        super::super::builtins::datetime_builtins::builtin_datetime_from_epoch(
1190                            &args,
1191                        )?;
1192                    self.push_kinded_slot(r)?;
1193                }
1194                BuiltinFunction::DateTimeFromParts => {
1195                    let args = self.pop_builtin_args()?;
1196                    let r =
1197                        super::super::builtins::datetime_builtins::builtin_datetime_from_parts(
1198                            &args,
1199                        )?;
1200                    self.push_kinded_slot(r)?;
1201                }
1202                BuiltinFunction::DateTimeFromUnixSecs => {
1203                    let args = self.pop_builtin_args()?;
1204                    let r = super::super::builtins::datetime_builtins
1205                        ::builtin_datetime_from_unix_secs(&args)?;
1206                    self.push_kinded_slot(r)?;
1207                }
1208                // ── Wave 5e: mat() row-major matrix constructor ───────────
1209                BuiltinFunction::MatFromFlat => {
1210                    let args = self.pop_builtin_args()?;
1211                    let r = super::super::builtins::datetime_builtins::builtin_mat_from_flat(
1212                        &args,
1213                    )?;
1214                    self.push_kinded_slot(r)?;
1215                }
1216                // ── Wave 5e: Table<T> from-rows constructor ───────────────
1217                BuiltinFunction::MakeTableFromRows => {
1218                    let args = self.pop_builtin_args()?;
1219                    let r = self.builtin_make_table_from_rows(&args)?;
1220                    self.push_kinded_slot(r)?;
1221                }
1222                BuiltinFunction::JsonObjectGet
1223                | BuiltinFunction::JsonArrayAt
1224                | BuiltinFunction::JsonObjectKeys
1225                | BuiltinFunction::JsonArrayLen
1226                | BuiltinFunction::JsonObjectLen => {
1227                    // SURFACE per ADR-006 §2.7.14: phase-1b-vm wave 5e —
1228                    // JSON navigation helper body migration deferred.
1229                    // Rebuild target lives at
1230                    // `executor/builtins/json_helpers.rs`. Drain args to
1231                    // balance the §2.7.7 parallel-kind track.
1232                    let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
1233                    return Err(VMError::NotImplemented(format!(
1234                        "phase-1b-vm-wave-5e-json-nav: {:?} body migration \
1235                         to kinded carrier (executor/builtins/json_helpers.rs) \
1236                         pending (v0.4 / planned)",
1237                        builtin
1238                    )));
1239                }
1240                // ── W8-WJ: window function dispatch (ADR-006 §2.7.10/Q11) ──
1241                //
1242                // Each handler is a free fn matching the MethodFnV2 body
1243                // shape: `fn(&mut VM, &[KindedSlot], Option<&mut Ctx>) ->
1244                // Result<KindedSlot, VMError>`. The dispatch shell pops
1245                // builtin args via `pop_builtin_args` (which constructs
1246                // `Vec<KindedSlot>` from the §2.7.7 stack parallel-kind
1247                // track), borrows it as `&[KindedSlot]` to the handler,
1248                // then re-pushes the kinded result via `push_kinded_slot`.
1249                BuiltinFunction::WindowRowNumber
1250                | BuiltinFunction::WindowRank
1251                | BuiltinFunction::WindowDenseRank
1252                | BuiltinFunction::WindowNtile => {
1253                    let args = self.pop_builtin_args()?;
1254                    let r = super::super::window_join::handle_window_row_number_v2(
1255                        self, &args, _ctx,
1256                    )?;
1257                    self.push_kinded_slot(r)?;
1258                }
1259                BuiltinFunction::WindowLag | BuiltinFunction::WindowLead => {
1260                    let args = self.pop_builtin_args()?;
1261                    let r = super::super::window_join::handle_window_lag_v2(
1262                        self, &args, _ctx,
1263                    )?;
1264                    self.push_kinded_slot(r)?;
1265                }
1266                BuiltinFunction::WindowFirstValue
1267                | BuiltinFunction::WindowLastValue
1268                | BuiltinFunction::WindowNthValue => {
1269                    let args = self.pop_builtin_args()?;
1270                    let r = super::super::window_join::handle_window_first_value_v2(
1271                        self, &args, _ctx,
1272                    )?;
1273                    self.push_kinded_slot(r)?;
1274                }
1275                BuiltinFunction::WindowSum => {
1276                    let args = self.pop_builtin_args()?;
1277                    let r = super::super::window_join::handle_window_sum_v2(
1278                        self, &args, _ctx,
1279                    )?;
1280                    self.push_kinded_slot(r)?;
1281                }
1282                BuiltinFunction::WindowAvg => {
1283                    let args = self.pop_builtin_args()?;
1284                    let r = super::super::window_join::handle_window_avg_v2(
1285                        self, &args, _ctx,
1286                    )?;
1287                    self.push_kinded_slot(r)?;
1288                }
1289                BuiltinFunction::WindowMin => {
1290                    let args = self.pop_builtin_args()?;
1291                    let r = super::super::window_join::handle_window_min_v2(
1292                        self, &args, _ctx,
1293                    )?;
1294                    self.push_kinded_slot(r)?;
1295                }
1296                BuiltinFunction::WindowMax => {
1297                    let args = self.pop_builtin_args()?;
1298                    let r = super::super::window_join::handle_window_max_v2(
1299                        self, &args, _ctx,
1300                    )?;
1301                    self.push_kinded_slot(r)?;
1302                }
1303                BuiltinFunction::WindowCount => {
1304                    let args = self.pop_builtin_args()?;
1305                    let r = super::super::window_join::handle_window_count_v2(
1306                        self, &args, _ctx,
1307                    )?;
1308                    self.push_kinded_slot(r)?;
1309                }
1310                BuiltinFunction::JoinExecute => {
1311                    // SURFACE — cross-cluster cascade with
1312                    // `datatable_methods::joins` ABI flip (W9 method-body
1313                    // re-fill). Drains stack args to keep the parallel-
1314                    // kind track balanced, then surfaces.
1315                    let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
1316                    return self.handle_join_execute();
1317                }
1318                BuiltinFunction::Reflect => {
1319                    // SURFACE per ADR-006 §2.7.14: phase-1b-vm wave 5e —
1320                    // `reflect()` builtin body migration deferred. No arg
1321                    // pop because the legacy emit shape did not emit an
1322                    // arity prefix at this dispatch arm; the rebuild
1323                    // wires arg-popping along with the body re-fill.
1324                    return Err(VMError::NotImplemented(
1325                        "phase-1b-vm-wave-5e-reflect: reflect() body \
1326                         migration to kinded carrier pending \
1327                         (v0.4 / planned)"
1328                            .to_string(),
1329                    ));
1330                }
1331
1332                // ── Eval-* removed-feature stubs (preserved as runtime
1333                // errors per pre-Wave 5a behaviour). These do not need
1334                // body migration; their semantics is already terminal. ──
1335                BuiltinFunction::EvalTimeRef => {
1336                    return Err(VMError::NotImplemented(
1337                        "eval_time_ref() (VM-only mode)".to_string(),
1338                    ));
1339                }
1340                BuiltinFunction::EvalDateTimeExpr => {
1341                    // C1-temporal-lowering (Phase 2d Wave 2): the
1342                    // `compiler/expressions/temporal.rs::compile_expr_datetime`
1343                    // emit sequence is PushConst(DateTimeExpr) +
1344                    // BuiltinCall(EvalDateTimeExpr). The
1345                    // `Constant::DateTimeExpr` arm in `op_push_const`
1346                    // (`stack_ops/mod.rs`) now evaluates the AST via
1347                    // `eval_datetime_expr_recursive` and pushes a
1348                    // `NativeKind::Ptr(HeapKind::Temporal)` Temporal::DateTime
1349                    // slot directly. There is therefore no work for this
1350                    // builtin to do — the value the legacy semantics
1351                    // produced ("pop DateTimeExpr Temporal, evaluate, push
1352                    // DateTime Temporal") is already on the stack. Skip
1353                    // arg-count pop: the compiler does not emit one, and
1354                    // re-adding it would require changing the legacy emit
1355                    // shape compiler-side without an upstream benefit.
1356                    // ADR-006 §2.7.4.
1357                }
1358                BuiltinFunction::EvalDataDateTimeRef
1359                | BuiltinFunction::EvalDataSet
1360                | BuiltinFunction::EvalDataRelative
1361                | BuiltinFunction::EvalDataRelativeRange => {
1362                    return Err(VMError::RuntimeError(
1363                        "DataReference / DataRow type has been removed"
1364                            .to_string(),
1365                    ));
1366                }
1367            }
1368        } else {
1369            return Err(VMError::InvalidOperand);
1370        }
1371        Ok(())
1372    }
1373
1374    // ===== Print / Format helpers (ADR-006 §2.7.4) =====
1375
1376    /// Format every arg via `ValueFormatter::format_kinded`, join the
1377    /// rendered fragments with a space, then route through the active
1378    /// `ExecutionContext`'s [`OutputAdapter::print`] (or fall back to
1379    /// stdout when no context is plumbed — e.g. the bytecode-level
1380    /// `eval_*` helpers used by tests).
1381    ///
1382    /// W18.6 (R8 W3 2026-05-24 — supervisor D3+D4): TypedObject args
1383    /// whose schema's source-level type has a user-defined `Display` impl
1384    /// dispatch through `<TypeName>::display() -> content`. The returned
1385    /// `Content` KindedSlot is then routed through the formatter's
1386    /// `HeapKind::Content` arm (W18.2-wired TerminalRenderer path), so
1387    /// `print(Point { x: 1, y: 2 })` produces the user's
1388    /// `Content.text("(1, 2)")` projection rather than the schema-walk
1389    /// `{x: 1, y: 2}` fallback.
1390    pub(crate) fn builtin_print(
1391        &mut self,
1392        args: &[KindedSlot],
1393        ctx: Option<&mut shape_runtime::context::ExecutionContext>,
1394    ) -> Result<(), VMError> {
1395        // The TypedObject schema names live on `self.program.type_schema_registry`
1396        // (the BytecodeProgram-bound registry that `lookup_schema` reads).
1397        // The ExecutionContext's registry is the runtime-tier copy populated
1398        // via stdlib loading; both are searched so user-defined types and
1399        // stdlib types both resolve.
1400        //
1401        // W18.6: walk args; for each TypedObject arg whose source-level
1402        // schema name has a registered `Display::display` impl, invoke
1403        // it and substitute the returned `content` carrier before formatting.
1404        // We need to materialize owned KindedSlots either way (the
1405        // formatter borrows; Display dispatch produces fresh ones), so
1406        // the loop builds a `Vec<KindedSlot>` of values-to-format.
1407        let mut to_format: Vec<KindedSlot> = Vec::with_capacity(args.len());
1408        for a in args {
1409            if let Some(replaced) = self.try_dispatch_display(a)? {
1410                to_format.push(replaced);
1411            } else {
1412                // Borrow-share: Clone bumps refcount so the owned slot
1413                // can drop without disturbing the caller's share.
1414                to_format.push(a.clone());
1415            }
1416        }
1417        let _ = ctx;
1418
1419        let rendered = {
1420            let formatter =
1421                super::super::printing::ValueFormatter::new(&self.program.type_schema_registry);
1422            to_format
1423                .iter()
1424                .map(|a| formatter.format_kinded(a))
1425                .collect::<Vec<_>>()
1426                .join(" ")
1427        };
1428
1429        let result = shape_runtime::print_result::PrintResult {
1430            rendered,
1431            spans: Vec::new(),
1432        };
1433        // `ctx` was consumed by the dispatch loop above; re-acquire via
1434        // the shared output adapter on the VM-level executor context if
1435        // present. For W18.6 we route to stdout unconditionally when no
1436        // adapter was supplied — same fallback as the pre-W18.6 path.
1437        println!("{}", result.rendered);
1438        Ok(())
1439    }
1440
1441    /// W18.6 (R8 W3 2026-05-24 — supervisor D3+D4): if `arg` is a
1442    /// TypedObject whose source-level schema name has a registered
1443    /// `Display::display` trait impl, invoke `<TypeName>::display()` and
1444    /// return the produced `content` KindedSlot. Returns `Ok(None)` when
1445    /// the arg is not a TypedObject, the schema has no name, or no
1446    /// Display impl is registered.
1447    ///
1448    /// `_ctx` is currently dropped — `execute_function_by_name` accepts
1449    /// an `Option<&mut ExecutionContext>`, but the borrow lifetime in
1450    /// the caller's loop is incompatible with re-acquiring `ctx` per
1451    /// iteration. Pre-W18.6 the print path also took `ctx` by &mut and
1452    /// did not pass it to nested calls; the Display body is expected to
1453    /// be pure / cheap (a single `Content.text(...)` wrap is the
1454    /// canonical pattern).
1455    fn try_dispatch_display(
1456        &mut self,
1457        arg: &KindedSlot,
1458    ) -> Result<Option<KindedSlot>, VMError> {
1459        use shape_value::heap_value::HeapKind;
1460        // Only TypedObject receivers can have user-defined Display impls.
1461        let shape_value::NativeKind::Ptr(HeapKind::TypedObject) = arg.kind else {
1462            return Ok(None);
1463        };
1464        let bits = arg.slot.raw();
1465        if bits == 0 {
1466            return Ok(None);
1467        }
1468        // SAFETY: per the `KindedSlot::from_typed_object` construction-
1469        // side contract, `Ptr(TypedObject)` bits are
1470        // `Arc::into_raw(Arc<TypedObjectStorage>)`. The borrow is
1471        // bounded by the caller's `args` lifetime.
1472        let storage: &shape_value::heap_value::TypedObjectStorage =
1473            unsafe { &*(bits as *const shape_value::heap_value::TypedObjectStorage) };
1474        let schema = self
1475            .program
1476            .type_schema_registry
1477            .get_by_id(storage.schema_id as u32);
1478        let Some(schema) = schema else {
1479            return Ok(None);
1480        };
1481        let type_name = schema.name.clone();
1482        if type_name.is_empty() {
1483            return Ok(None);
1484        }
1485        // Skip enum types — their `format_enum_typed_object` path
1486        // already produces the right `Variant(payload)` shape and
1487        // W18.0 enum-variant-display did not introduce a Display impl
1488        // for enums.
1489        if schema.is_enum() {
1490            return Ok(None);
1491        }
1492        let Some(func_name) = self
1493            .program
1494            .find_default_trait_impl_for_type_method(&type_name, "display")
1495            .map(|s| s.to_string())
1496        else {
1497            return Ok(None);
1498        };
1499        // Build the args vector: receiver (self) is the TypedObject
1500        // arg. `execute_function_by_id` takes `Vec<KindedSlot>` by value
1501        // and drops each slot at scope exit, retiring one share per
1502        // arg. `.clone()` bumps the receiver's refcount so the caller's
1503        // share is preserved.
1504        let receiver = arg.clone();
1505        let result = self.execute_function_by_name(&func_name, vec![receiver], None)?;
1506        Ok(Some(result))
1507    }
1508
1509    /// Format every arg via `ValueFormatter::format_kinded` and
1510    /// concatenate (no separator). Returns the rendered text wrapped in
1511    /// a `String`-kinded `KindedSlot`. Used by `format(…)` (multi-arg
1512    /// concat) and by `FormatValueWithMeta` (single-arg
1513    /// `expr.to_string()` / interpolation).
1514    pub(crate) fn builtin_format(
1515        &mut self,
1516        args: &[KindedSlot],
1517    ) -> Result<KindedSlot, VMError> {
1518        let formatter =
1519            super::super::printing::ValueFormatter::new(&self.program.type_schema_registry);
1520        let mut out = String::new();
1521        for a in args {
1522            out.push_str(&formatter.format_kinded(a));
1523        }
1524        Ok(KindedSlot::from_string_arc(std::sync::Arc::new(out)))
1525    }
1526
1527    /// `FormatValueWithSpec`: `[value, spec_tag, …spec-payload]`. Routes
1528    /// the FORMAT_SPEC_FIXED arm (precision-controlled f64 rendering);
1529    /// the Table arm surfaces per W13 playbook §7.4 surface-and-stop.
1530    pub(crate) fn builtin_format_with_spec(
1531        &mut self,
1532        args: &[KindedSlot],
1533    ) -> Result<KindedSlot, VMError> {
1534        const FORMAT_SPEC_FIXED: i64 = 1;
1535        const FORMAT_SPEC_TABLE: i64 = 2;
1536
1537        if args.is_empty() {
1538            return Err(VMError::RuntimeError(
1539                "FormatValueWithSpec requires at least 1 argument".to_string(),
1540            ));
1541        }
1542
1543        // The spec_tag arrives as an `int` constant (`PushConst(Constant::Int(_))`)
1544        // — kind `Int64` in the post-§2.7.7 stack ABI. Read defensively:
1545        // kind-mismatch falls through to the meta path so a malformed
1546        // dispatch still produces a string rather than crashing.
1547        let spec_tag = args.get(1).and_then(|s| match s.kind {
1548            shape_value::NativeKind::Int64
1549            | shape_value::NativeKind::Int32
1550            | shape_value::NativeKind::Int16
1551            | shape_value::NativeKind::Int8
1552            | shape_value::NativeKind::IntSize => Some(s.slot.as_i64()),
1553            _ => None,
1554        });
1555
1556        match spec_tag {
1557            Some(tag) if tag == FORMAT_SPEC_FIXED => {
1558                let precision = args.get(2).and_then(|s| match s.kind {
1559                    shape_value::NativeKind::Int64
1560                    | shape_value::NativeKind::Int32
1561                    | shape_value::NativeKind::Int16
1562                    | shape_value::NativeKind::Int8
1563                    | shape_value::NativeKind::IntSize => Some(s.slot.as_i64()),
1564                    _ => None,
1565                });
1566                let v = &args[0];
1567                // Coerce numeric kinds; non-numeric fall back to default
1568                // formatting so the spec is a no-op rather than an error.
1569                let f = match v.kind {
1570                    shape_value::NativeKind::Float64
1571                    | shape_value::NativeKind::NullableFloat64 => Some(v.slot.as_f64()),
1572                    shape_value::NativeKind::Int64
1573                    | shape_value::NativeKind::Int32
1574                    | shape_value::NativeKind::Int16
1575                    | shape_value::NativeKind::Int8
1576                    | shape_value::NativeKind::IntSize => Some(v.slot.as_i64() as f64),
1577                    shape_value::NativeKind::UInt64
1578                    | shape_value::NativeKind::UInt32
1579                    | shape_value::NativeKind::UInt16
1580                    | shape_value::NativeKind::UInt8
1581                    | shape_value::NativeKind::UIntSize => Some(v.slot.as_u64() as f64),
1582                    _ => None,
1583                };
1584                let rendered = match (f, precision) {
1585                    (Some(f), Some(p)) if p >= 0 => {
1586                        format!("{:.*}", p as usize, f)
1587                    }
1588                    _ => self.builtin_format(&args[..1])?.as_str().unwrap_or("").to_string(),
1589                };
1590                Ok(KindedSlot::from_string_arc(std::sync::Arc::new(rendered)))
1591            }
1592            Some(tag) if tag == FORMAT_SPEC_TABLE => {
1593                Err(VMError::NotImplemented(
1594                    "FormatValueWithSpec: FORMAT_SPEC_TABLE rendering deferred — \
1595                     W13-print-formatter scope is the FORMAT_SPEC_FIXED + \
1596                     no-spec path. Table rendering reuses the DataTable / \
1597                     TableView Display impls; surface-and-stop pending the \
1598                     next pass per W13 playbook §7.4."
1599                        .to_string(),
1600                ))
1601            }
1602            _ => self.builtin_format(&args[..1]),
1603        }
1604    }
1605
1606    // Runtime bridge functions (pop_builtin_args impl, eval_runtime_*)
1607    // moved to builtins/runtime_bridge.rs.
1608    // map_runtime_error and type_of_name moved to module_registry module.
1609
1610    // ===== Helper Methods =====
1611    // binary_arithmetic, eval_runtime_binary_op_value, binary_comparison
1612    // moved to arithmetic/mod.rs
1613}
1614
1615// ─────────────────────────────────────────────────────────────────────────
1616// W18.5 content builder helpers (R8 W4, 2026-05-24 — supervisor D4).
1617//
1618// Free-function helpers (not VirtualMachine methods) used by the
1619// `Content.table` / `Content.kv` / `Content.fragment` constructor arms and
1620// the Content method handlers in `objects/content_methods.rs`. The
1621// builder pattern relies on these helpers to:
1622//   - read a `Ptr(HeapKind::Content)` slot back into a `ContentNode`
1623//   - read string elements from a v2 `TypedArray<*const StringObj>`
1624//   - format a heterogeneous `KindedSlot` value into a `ContentNode::plain`
1625//     cell (for `Table.row(...)` / `KeyValue.pair("k", v)` value coercion)
1626//
1627// Per supervisor D4 "shortest path builder → content → renderer", these
1628// helpers stay at the dispatch shell — no cross-crate detour into
1629// shape-runtime, no parallel-implementation of styling spec types (the
1630// W18.4 shared spec module is a follow-up — see commit message).
1631// ─────────────────────────────────────────────────────────────────────────
1632
1633/// Read string elements from a v2 `TypedArray<*const StringObj>` slot.
1634///
1635/// Returns `None` if the slot is not a v2-raw typed array of strings; the
1636/// caller surfaces a typed error. The returned `Vec<String>` owns its
1637/// contents — each element is copied out of the array's interned UTF-8.
1638pub(in crate::executor) fn read_string_array(slot: &KindedSlot) -> Option<Vec<String>> {
1639    use crate::executor::v2_handlers::v2_array_detect::{
1640        as_v2_typed_array, read_element, V2ElemType,
1641    };
1642    use shape_value::{HeapKind, NativeKind};
1643    if slot.kind != NativeKind::Ptr(HeapKind::TypedArray) {
1644        return None;
1645    }
1646    let view = as_v2_typed_array(slot.slot.raw(), slot.kind)?;
1647    if view.elem_type != V2ElemType::String {
1648        return None;
1649    }
1650    let mut out = Vec::with_capacity(view.len as usize);
1651    for i in 0..view.len {
1652        let (bits, kind) = read_element(&view, i)?;
1653        // `read_element` retains a fresh share on the element header per
1654        // its Wave-2-Agent-A2 contract (see
1655        // `executor/v2_handlers/v2_array_detect.rs:378-388`); wrap into
1656        // a `KindedSlot` so the share retires on drop after we've copied
1657        // the UTF-8 out via `as_str`.
1658        let elem_slot = KindedSlot::new(ValueSlot::from_raw(bits), kind);
1659        let s = elem_slot.as_str()?.to_string();
1660        // `elem_slot` drops at end of iteration — releases the share.
1661        let _ = elem_slot;
1662        out.push(s);
1663    }
1664    Some(out)
1665}
1666
1667/// Read a `Ptr(HeapKind::Content)` slot as an `Arc<ContentNode>`.
1668///
1669/// Returns `None` if the kind doesn't match. The returned Arc is a fresh
1670/// strong-count share (incremented from the slot's bits); the caller is
1671/// responsible for the share-accounting of the returned Arc.
1672pub(in crate::executor) fn read_content_arc(
1673    slot: &KindedSlot,
1674) -> Option<std::sync::Arc<shape_value::content::ContentNode>> {
1675    use shape_value::{HeapKind, NativeKind};
1676    if slot.kind != NativeKind::Ptr(HeapKind::Content) {
1677        return None;
1678    }
1679    let bits = slot.slot.raw();
1680    if bits == 0 {
1681        return None;
1682    }
1683    // SAFETY: by construction `Ptr(HeapKind::Content)` slot bits are
1684    // `Arc::into_raw(Arc<ContentNode>) as u64` (set by
1685    // `KindedSlot::from_content` and its producers — `ContentTextCtor`,
1686    // `ContentCodeCtor`, this module's W18.5 ctors, Display.display()
1687    // returns, etc.). The slot owns one strong-count share for the
1688    // dispatch duration. We borrow the inner Arc by reconstituting it,
1689    // cloning to get a fresh share, then `mem::forget`-ing the original
1690    // reconstitution so the slot's share remains intact.
1691    unsafe {
1692        let raw = bits as *const shape_value::content::ContentNode;
1693        let arc = std::sync::Arc::from_raw(raw);
1694        let cloned = arc.clone();
1695        std::mem::forget(arc);
1696        Some(cloned)
1697    }
1698}
1699
1700/// Build a `ContentTable` from a `Content.table(headers, rows)` arg list.
1701///
1702/// `args[0]` is the headers array (`Array<string>`), `args[1]` is the
1703/// rows array (`Array<Array<string>>`). Cell values render as
1704/// `ContentNode::plain` strings — string-typed MVP per supervisor D4
1705/// "string-typed-MVP follow-up surfaced for W18.4 spec-types swap".
1706fn build_table_from_headers_and_rows(
1707    args: &[KindedSlot],
1708) -> Result<shape_value::content::ContentTable, VMError> {
1709    if args.len() != 2 {
1710        return Err(VMError::RuntimeError(format!(
1711            "Content.table(headers, rows) requires exactly 2 arguments, \
1712             got {}",
1713            args.len()
1714        )));
1715    }
1716    let headers = read_string_array(&args[0]).ok_or_else(|| {
1717        VMError::RuntimeError(format!(
1718            "Content.table: headers argument must be Array<string>, got \
1719             kind {:?}",
1720            args[0].kind
1721        ))
1722    })?;
1723    // Rows is an Array<Array<string>>. The outer array carries
1724    // TypedArray-of-TypedArray pointers. Read each inner row via
1725    // `read_string_array`.
1726    use crate::executor::v2_handlers::v2_array_detect::{
1727        as_v2_typed_array, read_element,
1728    };
1729    use shape_value::{HeapKind, NativeKind};
1730    if args[1].kind != NativeKind::Ptr(HeapKind::TypedArray) {
1731        return Err(VMError::RuntimeError(format!(
1732            "Content.table: rows argument must be Array<Array<string>>, \
1733             got kind {:?}",
1734            args[1].kind
1735        )));
1736    }
1737    let outer_view = as_v2_typed_array(args[1].slot.raw(), args[1].kind)
1738        .ok_or_else(|| {
1739            VMError::RuntimeError(
1740                "Content.table: rows array has invalid v2 header".to_string(),
1741            )
1742        })?;
1743    let mut rows: Vec<Vec<shape_value::content::ContentNode>> =
1744        Vec::with_capacity(outer_view.len as usize);
1745    for i in 0..outer_view.len {
1746        let (bits, kind) = read_element(&outer_view, i).ok_or_else(|| {
1747            VMError::RuntimeError(format!(
1748                "Content.table: failed to read row {} from rows array",
1749                i
1750            ))
1751        })?;
1752        let row_slot = KindedSlot::new(ValueSlot::from_raw(bits), kind);
1753        let cells = read_string_array(&row_slot).ok_or_else(|| {
1754            VMError::RuntimeError(format!(
1755                "Content.table: row {} must be Array<string>, got kind {:?}",
1756                i, row_slot.kind
1757            ))
1758        })?;
1759        // Drop row_slot to release the share held by `read_element`.
1760        drop(row_slot);
1761        let row_nodes: Vec<shape_value::content::ContentNode> = cells
1762            .into_iter()
1763            .map(shape_value::content::ContentNode::plain)
1764            .collect();
1765        rows.push(row_nodes);
1766    }
1767    Ok(shape_value::content::ContentTable {
1768        headers,
1769        rows,
1770        border: shape_value::content::BorderStyle::default(),
1771        max_rows: None,
1772        column_types: None,
1773        total_rows: None,
1774        sortable: false,
1775    })
1776}
1777
1778/// Build a `Vec<(String, ContentNode)>` from a `Content.kv(keys, values)`
1779/// arg list — keys array is `Array<string>`, values array is the parallel
1780/// `Array<*>` whose elements coerce through `format_kinded`.
1781fn build_kv_pairs_from_keys_values(
1782    vm: &VirtualMachine,
1783    args: &[KindedSlot],
1784) -> Result<Vec<(String, shape_value::content::ContentNode)>, VMError> {
1785    if args.len() != 2 {
1786        return Err(VMError::RuntimeError(format!(
1787            "Content.kv(keys, values) requires exactly 2 arguments, got {}",
1788            args.len()
1789        )));
1790    }
1791    let keys = read_string_array(&args[0]).ok_or_else(|| {
1792        VMError::RuntimeError(format!(
1793            "Content.kv: keys argument must be Array<string>, got kind {:?}",
1794            args[0].kind
1795        ))
1796    })?;
1797    use crate::executor::v2_handlers::v2_array_detect::{
1798        as_v2_typed_array, read_element,
1799    };
1800    use shape_value::{HeapKind, NativeKind};
1801    if args[1].kind != NativeKind::Ptr(HeapKind::TypedArray) {
1802        return Err(VMError::RuntimeError(format!(
1803            "Content.kv: values argument must be an Array, got kind {:?}",
1804            args[1].kind
1805        )));
1806    }
1807    let view = as_v2_typed_array(args[1].slot.raw(), args[1].kind).ok_or_else(|| {
1808        VMError::RuntimeError("Content.kv: values array has invalid v2 header".to_string())
1809    })?;
1810    if (view.len as usize) != keys.len() {
1811        return Err(VMError::RuntimeError(format!(
1812            "Content.kv: keys.len() ({}) != values.len() ({})",
1813            keys.len(),
1814            view.len
1815        )));
1816    }
1817    let formatter = super::super::printing::ValueFormatter::new(&vm.program.type_schema_registry);
1818    let mut pairs = Vec::with_capacity(keys.len());
1819    for (i, key) in keys.into_iter().enumerate() {
1820        let (bits, kind) = read_element(&view, i as u32).ok_or_else(|| {
1821            VMError::RuntimeError(format!("Content.kv: failed to read value at index {}", i))
1822        })?;
1823        let val_slot = KindedSlot::new(ValueSlot::from_raw(bits), kind);
1824        let rendered = formatter.format_kinded(&val_slot);
1825        // val_slot drops here — releases the share.
1826        drop(val_slot);
1827        pairs.push((key, shape_value::content::ContentNode::plain(rendered)));
1828    }
1829    Ok(pairs)
1830}
1831
1832/// Read an `Array<content>` argument and collect each element as an
1833/// owned `ContentNode`. Each element of the array must have kind
1834/// `Ptr(HeapKind::Content)`; the function clones the inner ContentNode
1835/// out of the read-share Arc returned by `read_content_arc`.
1836fn collect_content_nodes_from_array_arg(
1837    args: &[KindedSlot],
1838) -> Result<Vec<shape_value::content::ContentNode>, VMError> {
1839    if args.len() != 1 {
1840        return Err(VMError::RuntimeError(format!(
1841            "Content.fragment(parts) requires exactly 1 argument \
1842             (Array<content>), got {}",
1843            args.len()
1844        )));
1845    }
1846    use crate::executor::v2_handlers::v2_array_detect::{
1847        as_v2_typed_array, read_element,
1848    };
1849    use shape_value::{HeapKind, NativeKind};
1850    if args[0].kind != NativeKind::Ptr(HeapKind::TypedArray) {
1851        return Err(VMError::RuntimeError(format!(
1852            "Content.fragment: parts argument must be Array<content>, got \
1853             kind {:?}",
1854            args[0].kind
1855        )));
1856    }
1857    let view = as_v2_typed_array(args[0].slot.raw(), args[0].kind).ok_or_else(|| {
1858        VMError::RuntimeError(
1859            "Content.fragment: parts array has invalid v2 header".to_string(),
1860        )
1861    })?;
1862    let mut parts = Vec::with_capacity(view.len as usize);
1863    for i in 0..view.len {
1864        let (bits, kind) = read_element(&view, i).ok_or_else(|| {
1865            VMError::RuntimeError(format!(
1866                "Content.fragment: failed to read element {}",
1867                i
1868            ))
1869        })?;
1870        let elem_slot = KindedSlot::new(ValueSlot::from_raw(bits), kind);
1871        let arc = read_content_arc(&elem_slot).ok_or_else(|| {
1872            VMError::RuntimeError(format!(
1873                "Content.fragment: element {} must be a content value, got \
1874                 kind {:?}",
1875                i, elem_slot.kind
1876            ))
1877        })?;
1878        // Clone the underlying ContentNode out of the Arc (cheap — most
1879        // variants are themselves Arc-shaped vectors / structs that share
1880        // their interior storage on clone).
1881        parts.push((*arc).clone());
1882        drop(elem_slot);
1883    }
1884    Ok(parts)
1885}
1886
1887// W12-stdlib-intrinsic-collapse (Wave-2-Agent-G, 2026-05-14): the
1888// `intrinsic_sum_tests` module previously here exercised the deleted
1889// `BuiltinFunction::IntrinsicSum` opcode body. Equivalent coverage lives
1890// in the PHF method-dispatch handlers' own test surface
1891// (`typed_array_methods` / `array_aggregation` / `typed_int_array_methods`
1892// / `typed_number_array_methods`) — single discriminator per ADR-005 §1.
1893
1894// ===== R8 W4 W18.4: f-string styled-content lowering helpers =====
1895//
1896// (supervisor 2026-05-24 D1 + (a-modified) REVIVE-WITH-SHARED-MODULE)
1897//
1898// The compiler at `compiler/string_interpolation.rs` lowers each styled
1899// interpolation `{x:bold,red}` to a `FStringContentStyledText` builtin
1900// call whose i64-encoded payload is decoded here back into a
1901// `shape_value::content::Style`.
1902
1903const FSTRING_COLOR_NONE: i64 = -1;
1904const FSTRING_COLOR_NAMED: i64 = 0;
1905const FSTRING_COLOR_RGB: i64 = 1;
1906
1907const FSTRING_FLAG_BOLD: i64 = 1;
1908const FSTRING_FLAG_ITALIC: i64 = 2;
1909const FSTRING_FLAG_UNDERLINE: i64 = 4;
1910const FSTRING_FLAG_DIM: i64 = 8;
1911
1912fn decode_fstring_color(kind: i64, payload: i64) -> Result<Option<shape_value::content::Color>, VMError> {
1913    use shape_value::content::{Color, NamedColor};
1914    match kind {
1915        FSTRING_COLOR_NONE => Ok(None),
1916        FSTRING_COLOR_NAMED => {
1917            let named = match payload {
1918                0 => NamedColor::Red,
1919                1 => NamedColor::Green,
1920                2 => NamedColor::Blue,
1921                3 => NamedColor::Yellow,
1922                4 => NamedColor::Magenta,
1923                5 => NamedColor::Cyan,
1924                6 => NamedColor::White,
1925                7 => NamedColor::Default,
1926                other => {
1927                    return Err(VMError::RuntimeError(format!(
1928                        "decode_fstring_color: invalid named-color id {}",
1929                        other
1930                    )));
1931                }
1932            };
1933            Ok(Some(Color::Named(named)))
1934        }
1935        FSTRING_COLOR_RGB => {
1936            let r = ((payload >> 16) & 0xFF) as u8;
1937            let g = ((payload >> 8) & 0xFF) as u8;
1938            let b = (payload & 0xFF) as u8;
1939            Ok(Some(Color::Rgb(r, g, b)))
1940        }
1941        other => Err(VMError::RuntimeError(format!(
1942            "decode_fstring_color: invalid color kind {}",
1943            other
1944        ))),
1945    }
1946}
1947
1948fn decode_fstring_style(
1949    fg_kind: i64,
1950    fg_payload: i64,
1951    bg_kind: i64,
1952    bg_payload: i64,
1953    flags: i64,
1954) -> Result<shape_value::content::Style, VMError> {
1955    Ok(shape_value::content::Style {
1956        fg: decode_fstring_color(fg_kind, fg_payload)?,
1957        bg: decode_fstring_color(bg_kind, bg_payload)?,
1958        bold: (flags & FSTRING_FLAG_BOLD) != 0,
1959        italic: (flags & FSTRING_FLAG_ITALIC) != 0,
1960        underline: (flags & FSTRING_FLAG_UNDERLINE) != 0,
1961        dim: (flags & FSTRING_FLAG_DIM) != 0,
1962    })
1963}
1964
1965#[cfg(test)]
1966mod fstring_decode_tests {
1967    use super::*;
1968
1969    #[test]
1970    fn decode_color_none() {
1971        assert_eq!(decode_fstring_color(FSTRING_COLOR_NONE, 0).unwrap(), None);
1972    }
1973
1974    #[test]
1975    fn decode_color_named_red() {
1976        use shape_value::content::{Color, NamedColor};
1977        assert_eq!(
1978            decode_fstring_color(FSTRING_COLOR_NAMED, 0).unwrap(),
1979            Some(Color::Named(NamedColor::Red))
1980        );
1981    }
1982
1983    #[test]
1984    fn decode_color_rgb_roundtrip() {
1985        use shape_value::content::Color;
1986        // (10 << 16) | (20 << 8) | 30
1987        let payload = (10 << 16) | (20 << 8) | 30;
1988        assert_eq!(
1989            decode_fstring_color(FSTRING_COLOR_RGB, payload).unwrap(),
1990            Some(Color::Rgb(10, 20, 30))
1991        );
1992    }
1993
1994    #[test]
1995    fn decode_style_bold_red() {
1996        use shape_value::content::{Color, NamedColor};
1997        let style = decode_fstring_style(
1998            FSTRING_COLOR_NAMED,
1999            0, // Red
2000            FSTRING_COLOR_NONE,
2001            0,
2002            FSTRING_FLAG_BOLD,
2003        )
2004        .unwrap();
2005        assert!(style.bold);
2006        assert!(!style.italic);
2007        assert_eq!(style.fg, Some(Color::Named(NamedColor::Red)));
2008        assert_eq!(style.bg, None);
2009    }
2010
2011    #[test]
2012    fn decode_style_all_flags() {
2013        let style = decode_fstring_style(
2014            FSTRING_COLOR_NONE,
2015            0,
2016            FSTRING_COLOR_NONE,
2017            0,
2018            FSTRING_FLAG_BOLD
2019                | FSTRING_FLAG_ITALIC
2020                | FSTRING_FLAG_UNDERLINE
2021                | FSTRING_FLAG_DIM,
2022        )
2023        .unwrap();
2024        assert!(style.bold);
2025        assert!(style.italic);
2026        assert!(style.underline);
2027        assert!(style.dim);
2028    }
2029
2030    #[test]
2031    fn decode_color_invalid_kind() {
2032        assert!(decode_fstring_color(99, 0).is_err());
2033    }
2034
2035    #[test]
2036    fn decode_color_invalid_named_id() {
2037        assert!(decode_fstring_color(FSTRING_COLOR_NAMED, 99).is_err());
2038    }
2039}