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
1418        let rendered = {
1419            let formatter =
1420                super::super::printing::ValueFormatter::new(&self.program.type_schema_registry);
1421            to_format
1422                .iter()
1423                .map(|a| formatter.format_kinded(a))
1424                .collect::<Vec<_>>()
1425                .join(" ")
1426        };
1427
1428        let result = shape_runtime::print_result::PrintResult {
1429            rendered,
1430            spans: Vec::new(),
1431        };
1432        // Route to the active `OutputAdapter` when an `ExecutionContext`
1433        // is plumbed (script runner, REPL, shape-server playground /
1434        // notebook — all of which install a capture/REPL/stdout adapter).
1435        // Fall back to stdout only when no context was supplied, e.g.
1436        // the bytecode-level `eval_*` helpers in tests.
1437        //
1438        // W18.6 (R8 W3 2026-05-24) originally dropped `ctx` here because
1439        // the Display dispatch loop took `&mut self` for an opaque
1440        // duration — but `try_dispatch_display` does not need `ctx` (the
1441        // Display body is expected to be pure per the inline doc on
1442        // `try_dispatch_display`), so the `&mut` borrow on `ctx` is
1443        // free at this point. Routing the rendered line to the adapter
1444        // restores hosted-embedder capture (`SharedCaptureAdapter` for
1445        // shape-server, `ReplAdapter` for REPL spans) without touching
1446        // the W18.6 Display-trait dispatch above.
1447        if let Some(ctx) = ctx {
1448            ctx.output_adapter_mut().print(result);
1449        } else {
1450            println!("{}", result.rendered);
1451        }
1452        Ok(())
1453    }
1454
1455    /// W18.6 (R8 W3 2026-05-24 — supervisor D3+D4): if `arg` is a
1456    /// TypedObject whose source-level schema name has a registered
1457    /// `Display::display` trait impl, invoke `<TypeName>::display()` and
1458    /// return the produced `content` KindedSlot. Returns `Ok(None)` when
1459    /// the arg is not a TypedObject, the schema has no name, or no
1460    /// Display impl is registered.
1461    ///
1462    /// `_ctx` is currently dropped — `execute_function_by_name` accepts
1463    /// an `Option<&mut ExecutionContext>`, but the borrow lifetime in
1464    /// the caller's loop is incompatible with re-acquiring `ctx` per
1465    /// iteration. Pre-W18.6 the print path also took `ctx` by &mut and
1466    /// did not pass it to nested calls; the Display body is expected to
1467    /// be pure / cheap (a single `Content.text(...)` wrap is the
1468    /// canonical pattern).
1469    fn try_dispatch_display(
1470        &mut self,
1471        arg: &KindedSlot,
1472    ) -> Result<Option<KindedSlot>, VMError> {
1473        use shape_value::heap_value::HeapKind;
1474        // Only TypedObject receivers can have user-defined Display impls.
1475        let shape_value::NativeKind::Ptr(HeapKind::TypedObject) = arg.kind else {
1476            return Ok(None);
1477        };
1478        let bits = arg.slot.raw();
1479        if bits == 0 {
1480            return Ok(None);
1481        }
1482        // SAFETY: per the `KindedSlot::from_typed_object` construction-
1483        // side contract, `Ptr(TypedObject)` bits are
1484        // `Arc::into_raw(Arc<TypedObjectStorage>)`. The borrow is
1485        // bounded by the caller's `args` lifetime.
1486        let storage: &shape_value::heap_value::TypedObjectStorage =
1487            unsafe { &*(bits as *const shape_value::heap_value::TypedObjectStorage) };
1488        let schema = self
1489            .program
1490            .type_schema_registry
1491            .get_by_id(storage.schema_id as u32);
1492        let Some(schema) = schema else {
1493            return Ok(None);
1494        };
1495        let type_name = schema.name.clone();
1496        if type_name.is_empty() {
1497            return Ok(None);
1498        }
1499        // Skip enum types — their `format_enum_typed_object` path
1500        // already produces the right `Variant(payload)` shape and
1501        // W18.0 enum-variant-display did not introduce a Display impl
1502        // for enums.
1503        if schema.is_enum() {
1504            return Ok(None);
1505        }
1506        let Some(func_name) = self
1507            .program
1508            .find_default_trait_impl_for_type_method(&type_name, "display")
1509            .map(|s| s.to_string())
1510        else {
1511            return Ok(None);
1512        };
1513        // Build the args vector: receiver (self) is the TypedObject
1514        // arg. `execute_function_by_id` takes `Vec<KindedSlot>` by value
1515        // and drops each slot at scope exit, retiring one share per
1516        // arg. `.clone()` bumps the receiver's refcount so the caller's
1517        // share is preserved.
1518        let receiver = arg.clone();
1519        let result = self.execute_function_by_name(&func_name, vec![receiver], None)?;
1520        Ok(Some(result))
1521    }
1522
1523    /// Format every arg via `ValueFormatter::format_kinded` and
1524    /// concatenate (no separator). Returns the rendered text wrapped in
1525    /// a `String`-kinded `KindedSlot`. Used by `format(…)` (multi-arg
1526    /// concat) and by `FormatValueWithMeta` (single-arg
1527    /// `expr.to_string()` / interpolation).
1528    pub(crate) fn builtin_format(
1529        &mut self,
1530        args: &[KindedSlot],
1531    ) -> Result<KindedSlot, VMError> {
1532        let formatter =
1533            super::super::printing::ValueFormatter::new(&self.program.type_schema_registry);
1534        let mut out = String::new();
1535        for a in args {
1536            out.push_str(&formatter.format_kinded(a));
1537        }
1538        Ok(KindedSlot::from_string_arc(std::sync::Arc::new(out)))
1539    }
1540
1541    /// `FormatValueWithSpec`: `[value, spec_tag, …spec-payload]`. Routes
1542    /// the FORMAT_SPEC_FIXED arm (precision-controlled f64 rendering);
1543    /// the Table arm surfaces per W13 playbook §7.4 surface-and-stop.
1544    pub(crate) fn builtin_format_with_spec(
1545        &mut self,
1546        args: &[KindedSlot],
1547    ) -> Result<KindedSlot, VMError> {
1548        const FORMAT_SPEC_FIXED: i64 = 1;
1549        const FORMAT_SPEC_TABLE: i64 = 2;
1550
1551        if args.is_empty() {
1552            return Err(VMError::RuntimeError(
1553                "FormatValueWithSpec requires at least 1 argument".to_string(),
1554            ));
1555        }
1556
1557        // The spec_tag arrives as an `int` constant (`PushConst(Constant::Int(_))`)
1558        // — kind `Int64` in the post-§2.7.7 stack ABI. Read defensively:
1559        // kind-mismatch falls through to the meta path so a malformed
1560        // dispatch still produces a string rather than crashing.
1561        let spec_tag = args.get(1).and_then(|s| match s.kind {
1562            shape_value::NativeKind::Int64
1563            | shape_value::NativeKind::Int32
1564            | shape_value::NativeKind::Int16
1565            | shape_value::NativeKind::Int8
1566            | shape_value::NativeKind::IntSize => Some(s.slot.as_i64()),
1567            _ => None,
1568        });
1569
1570        match spec_tag {
1571            Some(tag) if tag == FORMAT_SPEC_FIXED => {
1572                let precision = args.get(2).and_then(|s| match s.kind {
1573                    shape_value::NativeKind::Int64
1574                    | shape_value::NativeKind::Int32
1575                    | shape_value::NativeKind::Int16
1576                    | shape_value::NativeKind::Int8
1577                    | shape_value::NativeKind::IntSize => Some(s.slot.as_i64()),
1578                    _ => None,
1579                });
1580                let v = &args[0];
1581                // Coerce numeric kinds; non-numeric fall back to default
1582                // formatting so the spec is a no-op rather than an error.
1583                let f = match v.kind {
1584                    shape_value::NativeKind::Float64
1585                    | shape_value::NativeKind::NullableFloat64 => Some(v.slot.as_f64()),
1586                    shape_value::NativeKind::Int64
1587                    | shape_value::NativeKind::Int32
1588                    | shape_value::NativeKind::Int16
1589                    | shape_value::NativeKind::Int8
1590                    | shape_value::NativeKind::IntSize => Some(v.slot.as_i64() as f64),
1591                    shape_value::NativeKind::UInt64
1592                    | shape_value::NativeKind::UInt32
1593                    | shape_value::NativeKind::UInt16
1594                    | shape_value::NativeKind::UInt8
1595                    | shape_value::NativeKind::UIntSize => Some(v.slot.as_u64() as f64),
1596                    _ => None,
1597                };
1598                let rendered = match (f, precision) {
1599                    (Some(f), Some(p)) if p >= 0 => {
1600                        format!("{:.*}", p as usize, f)
1601                    }
1602                    _ => self.builtin_format(&args[..1])?.as_str().unwrap_or("").to_string(),
1603                };
1604                Ok(KindedSlot::from_string_arc(std::sync::Arc::new(rendered)))
1605            }
1606            Some(tag) if tag == FORMAT_SPEC_TABLE => {
1607                Err(VMError::NotImplemented(
1608                    "FormatValueWithSpec: FORMAT_SPEC_TABLE rendering deferred — \
1609                     W13-print-formatter scope is the FORMAT_SPEC_FIXED + \
1610                     no-spec path. Table rendering reuses the DataTable / \
1611                     TableView Display impls; surface-and-stop pending the \
1612                     next pass per W13 playbook §7.4."
1613                        .to_string(),
1614                ))
1615            }
1616            _ => self.builtin_format(&args[..1]),
1617        }
1618    }
1619
1620    // Runtime bridge functions (pop_builtin_args impl, eval_runtime_*)
1621    // moved to builtins/runtime_bridge.rs.
1622    // map_runtime_error and type_of_name moved to module_registry module.
1623
1624    // ===== Helper Methods =====
1625    // binary_arithmetic, eval_runtime_binary_op_value, binary_comparison
1626    // moved to arithmetic/mod.rs
1627}
1628
1629// ─────────────────────────────────────────────────────────────────────────
1630// W18.5 content builder helpers (R8 W4, 2026-05-24 — supervisor D4).
1631//
1632// Free-function helpers (not VirtualMachine methods) used by the
1633// `Content.table` / `Content.kv` / `Content.fragment` constructor arms and
1634// the Content method handlers in `objects/content_methods.rs`. The
1635// builder pattern relies on these helpers to:
1636//   - read a `Ptr(HeapKind::Content)` slot back into a `ContentNode`
1637//   - read string elements from a v2 `TypedArray<*const StringObj>`
1638//   - format a heterogeneous `KindedSlot` value into a `ContentNode::plain`
1639//     cell (for `Table.row(...)` / `KeyValue.pair("k", v)` value coercion)
1640//
1641// Per supervisor D4 "shortest path builder → content → renderer", these
1642// helpers stay at the dispatch shell — no cross-crate detour into
1643// shape-runtime, no parallel-implementation of styling spec types (the
1644// W18.4 shared spec module is a follow-up — see commit message).
1645// ─────────────────────────────────────────────────────────────────────────
1646
1647/// Read string elements from a v2 `TypedArray<*const StringObj>` slot.
1648///
1649/// Returns `None` if the slot is not a v2-raw typed array of strings; the
1650/// caller surfaces a typed error. The returned `Vec<String>` owns its
1651/// contents — each element is copied out of the array's interned UTF-8.
1652pub(in crate::executor) fn read_string_array(slot: &KindedSlot) -> Option<Vec<String>> {
1653    use crate::executor::v2_handlers::v2_array_detect::{
1654        as_v2_typed_array, read_element, V2ElemType,
1655    };
1656    use shape_value::{HeapKind, NativeKind};
1657    if slot.kind != NativeKind::Ptr(HeapKind::TypedArray) {
1658        return None;
1659    }
1660    let view = as_v2_typed_array(slot.slot.raw(), slot.kind)?;
1661    if view.elem_type != V2ElemType::String {
1662        return None;
1663    }
1664    let mut out = Vec::with_capacity(view.len as usize);
1665    for i in 0..view.len {
1666        let (bits, kind) = read_element(&view, i)?;
1667        // `read_element` retains a fresh share on the element header per
1668        // its Wave-2-Agent-A2 contract (see
1669        // `executor/v2_handlers/v2_array_detect.rs:378-388`); wrap into
1670        // a `KindedSlot` so the share retires on drop after we've copied
1671        // the UTF-8 out via `as_str`.
1672        let elem_slot = KindedSlot::new(ValueSlot::from_raw(bits), kind);
1673        let s = elem_slot.as_str()?.to_string();
1674        // `elem_slot` drops at end of iteration — releases the share.
1675        let _ = elem_slot;
1676        out.push(s);
1677    }
1678    Some(out)
1679}
1680
1681/// Read a `Ptr(HeapKind::Content)` slot as an `Arc<ContentNode>`.
1682///
1683/// Returns `None` if the kind doesn't match. The returned Arc is a fresh
1684/// strong-count share (incremented from the slot's bits); the caller is
1685/// responsible for the share-accounting of the returned Arc.
1686pub(in crate::executor) fn read_content_arc(
1687    slot: &KindedSlot,
1688) -> Option<std::sync::Arc<shape_value::content::ContentNode>> {
1689    use shape_value::{HeapKind, NativeKind};
1690    if slot.kind != NativeKind::Ptr(HeapKind::Content) {
1691        return None;
1692    }
1693    let bits = slot.slot.raw();
1694    if bits == 0 {
1695        return None;
1696    }
1697    // SAFETY: by construction `Ptr(HeapKind::Content)` slot bits are
1698    // `Arc::into_raw(Arc<ContentNode>) as u64` (set by
1699    // `KindedSlot::from_content` and its producers — `ContentTextCtor`,
1700    // `ContentCodeCtor`, this module's W18.5 ctors, Display.display()
1701    // returns, etc.). The slot owns one strong-count share for the
1702    // dispatch duration. We borrow the inner Arc by reconstituting it,
1703    // cloning to get a fresh share, then `mem::forget`-ing the original
1704    // reconstitution so the slot's share remains intact.
1705    unsafe {
1706        let raw = bits as *const shape_value::content::ContentNode;
1707        let arc = std::sync::Arc::from_raw(raw);
1708        let cloned = arc.clone();
1709        std::mem::forget(arc);
1710        Some(cloned)
1711    }
1712}
1713
1714/// Build a `ContentTable` from a `Content.table(headers, rows)` arg list.
1715///
1716/// `args[0]` is the headers array (`Array<string>`), `args[1]` is the
1717/// rows array (`Array<Array<string>>`). Cell values render as
1718/// `ContentNode::plain` strings — string-typed MVP per supervisor D4
1719/// "string-typed-MVP follow-up surfaced for W18.4 spec-types swap".
1720fn build_table_from_headers_and_rows(
1721    args: &[KindedSlot],
1722) -> Result<shape_value::content::ContentTable, VMError> {
1723    if args.len() != 2 {
1724        return Err(VMError::RuntimeError(format!(
1725            "Content.table(headers, rows) requires exactly 2 arguments, \
1726             got {}",
1727            args.len()
1728        )));
1729    }
1730    let headers = read_string_array(&args[0]).ok_or_else(|| {
1731        VMError::RuntimeError(format!(
1732            "Content.table: headers argument must be Array<string>, got \
1733             kind {:?}",
1734            args[0].kind
1735        ))
1736    })?;
1737    // Rows is an Array<Array<string>>. The outer array carries
1738    // TypedArray-of-TypedArray pointers. Read each inner row via
1739    // `read_string_array`.
1740    use crate::executor::v2_handlers::v2_array_detect::{
1741        as_v2_typed_array, read_element,
1742    };
1743    use shape_value::{HeapKind, NativeKind};
1744    if args[1].kind != NativeKind::Ptr(HeapKind::TypedArray) {
1745        return Err(VMError::RuntimeError(format!(
1746            "Content.table: rows argument must be Array<Array<string>>, \
1747             got kind {:?}",
1748            args[1].kind
1749        )));
1750    }
1751    let outer_view = as_v2_typed_array(args[1].slot.raw(), args[1].kind)
1752        .ok_or_else(|| {
1753            VMError::RuntimeError(
1754                "Content.table: rows array has invalid v2 header".to_string(),
1755            )
1756        })?;
1757    let mut rows: Vec<Vec<shape_value::content::ContentNode>> =
1758        Vec::with_capacity(outer_view.len as usize);
1759    for i in 0..outer_view.len {
1760        let (bits, kind) = read_element(&outer_view, i).ok_or_else(|| {
1761            VMError::RuntimeError(format!(
1762                "Content.table: failed to read row {} from rows array",
1763                i
1764            ))
1765        })?;
1766        let row_slot = KindedSlot::new(ValueSlot::from_raw(bits), kind);
1767        let cells = read_string_array(&row_slot).ok_or_else(|| {
1768            VMError::RuntimeError(format!(
1769                "Content.table: row {} must be Array<string>, got kind {:?}",
1770                i, row_slot.kind
1771            ))
1772        })?;
1773        // Drop row_slot to release the share held by `read_element`.
1774        drop(row_slot);
1775        let row_nodes: Vec<shape_value::content::ContentNode> = cells
1776            .into_iter()
1777            .map(shape_value::content::ContentNode::plain)
1778            .collect();
1779        rows.push(row_nodes);
1780    }
1781    Ok(shape_value::content::ContentTable {
1782        headers,
1783        rows,
1784        border: shape_value::content::BorderStyle::default(),
1785        max_rows: None,
1786        column_types: None,
1787        total_rows: None,
1788        sortable: false,
1789    })
1790}
1791
1792/// Build a `Vec<(String, ContentNode)>` from a `Content.kv(keys, values)`
1793/// arg list — keys array is `Array<string>`, values array is the parallel
1794/// `Array<*>` whose elements coerce through `format_kinded`.
1795fn build_kv_pairs_from_keys_values(
1796    vm: &VirtualMachine,
1797    args: &[KindedSlot],
1798) -> Result<Vec<(String, shape_value::content::ContentNode)>, VMError> {
1799    if args.len() != 2 {
1800        return Err(VMError::RuntimeError(format!(
1801            "Content.kv(keys, values) requires exactly 2 arguments, got {}",
1802            args.len()
1803        )));
1804    }
1805    let keys = read_string_array(&args[0]).ok_or_else(|| {
1806        VMError::RuntimeError(format!(
1807            "Content.kv: keys argument must be Array<string>, got kind {:?}",
1808            args[0].kind
1809        ))
1810    })?;
1811    use crate::executor::v2_handlers::v2_array_detect::{
1812        as_v2_typed_array, read_element,
1813    };
1814    use shape_value::{HeapKind, NativeKind};
1815    if args[1].kind != NativeKind::Ptr(HeapKind::TypedArray) {
1816        return Err(VMError::RuntimeError(format!(
1817            "Content.kv: values argument must be an Array, got kind {:?}",
1818            args[1].kind
1819        )));
1820    }
1821    let view = as_v2_typed_array(args[1].slot.raw(), args[1].kind).ok_or_else(|| {
1822        VMError::RuntimeError("Content.kv: values array has invalid v2 header".to_string())
1823    })?;
1824    if (view.len as usize) != keys.len() {
1825        return Err(VMError::RuntimeError(format!(
1826            "Content.kv: keys.len() ({}) != values.len() ({})",
1827            keys.len(),
1828            view.len
1829        )));
1830    }
1831    let formatter = super::super::printing::ValueFormatter::new(&vm.program.type_schema_registry);
1832    let mut pairs = Vec::with_capacity(keys.len());
1833    for (i, key) in keys.into_iter().enumerate() {
1834        let (bits, kind) = read_element(&view, i as u32).ok_or_else(|| {
1835            VMError::RuntimeError(format!("Content.kv: failed to read value at index {}", i))
1836        })?;
1837        let val_slot = KindedSlot::new(ValueSlot::from_raw(bits), kind);
1838        let rendered = formatter.format_kinded(&val_slot);
1839        // val_slot drops here — releases the share.
1840        drop(val_slot);
1841        pairs.push((key, shape_value::content::ContentNode::plain(rendered)));
1842    }
1843    Ok(pairs)
1844}
1845
1846/// Read an `Array<content>` argument and collect each element as an
1847/// owned `ContentNode`. Each element of the array must have kind
1848/// `Ptr(HeapKind::Content)`; the function clones the inner ContentNode
1849/// out of the read-share Arc returned by `read_content_arc`.
1850fn collect_content_nodes_from_array_arg(
1851    args: &[KindedSlot],
1852) -> Result<Vec<shape_value::content::ContentNode>, VMError> {
1853    if args.len() != 1 {
1854        return Err(VMError::RuntimeError(format!(
1855            "Content.fragment(parts) requires exactly 1 argument \
1856             (Array<content>), got {}",
1857            args.len()
1858        )));
1859    }
1860    use crate::executor::v2_handlers::v2_array_detect::{
1861        as_v2_typed_array, read_element,
1862    };
1863    use shape_value::{HeapKind, NativeKind};
1864    if args[0].kind != NativeKind::Ptr(HeapKind::TypedArray) {
1865        return Err(VMError::RuntimeError(format!(
1866            "Content.fragment: parts argument must be Array<content>, got \
1867             kind {:?}",
1868            args[0].kind
1869        )));
1870    }
1871    let view = as_v2_typed_array(args[0].slot.raw(), args[0].kind).ok_or_else(|| {
1872        VMError::RuntimeError(
1873            "Content.fragment: parts array has invalid v2 header".to_string(),
1874        )
1875    })?;
1876    let mut parts = Vec::with_capacity(view.len as usize);
1877    for i in 0..view.len {
1878        let (bits, kind) = read_element(&view, i).ok_or_else(|| {
1879            VMError::RuntimeError(format!(
1880                "Content.fragment: failed to read element {}",
1881                i
1882            ))
1883        })?;
1884        let elem_slot = KindedSlot::new(ValueSlot::from_raw(bits), kind);
1885        let arc = read_content_arc(&elem_slot).ok_or_else(|| {
1886            VMError::RuntimeError(format!(
1887                "Content.fragment: element {} must be a content value, got \
1888                 kind {:?}",
1889                i, elem_slot.kind
1890            ))
1891        })?;
1892        // Clone the underlying ContentNode out of the Arc (cheap — most
1893        // variants are themselves Arc-shaped vectors / structs that share
1894        // their interior storage on clone).
1895        parts.push((*arc).clone());
1896        drop(elem_slot);
1897    }
1898    Ok(parts)
1899}
1900
1901// W12-stdlib-intrinsic-collapse (Wave-2-Agent-G, 2026-05-14): the
1902// `intrinsic_sum_tests` module previously here exercised the deleted
1903// `BuiltinFunction::IntrinsicSum` opcode body. Equivalent coverage lives
1904// in the PHF method-dispatch handlers' own test surface
1905// (`typed_array_methods` / `array_aggregation` / `typed_int_array_methods`
1906// / `typed_number_array_methods`) — single discriminator per ADR-005 §1.
1907
1908// ===== R8 W4 W18.4: f-string styled-content lowering helpers =====
1909//
1910// (supervisor 2026-05-24 D1 + (a-modified) REVIVE-WITH-SHARED-MODULE)
1911//
1912// The compiler at `compiler/string_interpolation.rs` lowers each styled
1913// interpolation `{x:bold,red}` to a `FStringContentStyledText` builtin
1914// call whose i64-encoded payload is decoded here back into a
1915// `shape_value::content::Style`.
1916
1917const FSTRING_COLOR_NONE: i64 = -1;
1918const FSTRING_COLOR_NAMED: i64 = 0;
1919const FSTRING_COLOR_RGB: i64 = 1;
1920
1921const FSTRING_FLAG_BOLD: i64 = 1;
1922const FSTRING_FLAG_ITALIC: i64 = 2;
1923const FSTRING_FLAG_UNDERLINE: i64 = 4;
1924const FSTRING_FLAG_DIM: i64 = 8;
1925
1926fn decode_fstring_color(kind: i64, payload: i64) -> Result<Option<shape_value::content::Color>, VMError> {
1927    use shape_value::content::{Color, NamedColor};
1928    match kind {
1929        FSTRING_COLOR_NONE => Ok(None),
1930        FSTRING_COLOR_NAMED => {
1931            let named = match payload {
1932                0 => NamedColor::Red,
1933                1 => NamedColor::Green,
1934                2 => NamedColor::Blue,
1935                3 => NamedColor::Yellow,
1936                4 => NamedColor::Magenta,
1937                5 => NamedColor::Cyan,
1938                6 => NamedColor::White,
1939                7 => NamedColor::Default,
1940                other => {
1941                    return Err(VMError::RuntimeError(format!(
1942                        "decode_fstring_color: invalid named-color id {}",
1943                        other
1944                    )));
1945                }
1946            };
1947            Ok(Some(Color::Named(named)))
1948        }
1949        FSTRING_COLOR_RGB => {
1950            let r = ((payload >> 16) & 0xFF) as u8;
1951            let g = ((payload >> 8) & 0xFF) as u8;
1952            let b = (payload & 0xFF) as u8;
1953            Ok(Some(Color::Rgb(r, g, b)))
1954        }
1955        other => Err(VMError::RuntimeError(format!(
1956            "decode_fstring_color: invalid color kind {}",
1957            other
1958        ))),
1959    }
1960}
1961
1962fn decode_fstring_style(
1963    fg_kind: i64,
1964    fg_payload: i64,
1965    bg_kind: i64,
1966    bg_payload: i64,
1967    flags: i64,
1968) -> Result<shape_value::content::Style, VMError> {
1969    Ok(shape_value::content::Style {
1970        fg: decode_fstring_color(fg_kind, fg_payload)?,
1971        bg: decode_fstring_color(bg_kind, bg_payload)?,
1972        bold: (flags & FSTRING_FLAG_BOLD) != 0,
1973        italic: (flags & FSTRING_FLAG_ITALIC) != 0,
1974        underline: (flags & FSTRING_FLAG_UNDERLINE) != 0,
1975        dim: (flags & FSTRING_FLAG_DIM) != 0,
1976    })
1977}
1978
1979#[cfg(test)]
1980mod fstring_decode_tests {
1981    use super::*;
1982
1983    #[test]
1984    fn decode_color_none() {
1985        assert_eq!(decode_fstring_color(FSTRING_COLOR_NONE, 0).unwrap(), None);
1986    }
1987
1988    #[test]
1989    fn decode_color_named_red() {
1990        use shape_value::content::{Color, NamedColor};
1991        assert_eq!(
1992            decode_fstring_color(FSTRING_COLOR_NAMED, 0).unwrap(),
1993            Some(Color::Named(NamedColor::Red))
1994        );
1995    }
1996
1997    #[test]
1998    fn decode_color_rgb_roundtrip() {
1999        use shape_value::content::Color;
2000        // (10 << 16) | (20 << 8) | 30
2001        let payload = (10 << 16) | (20 << 8) | 30;
2002        assert_eq!(
2003            decode_fstring_color(FSTRING_COLOR_RGB, payload).unwrap(),
2004            Some(Color::Rgb(10, 20, 30))
2005        );
2006    }
2007
2008    #[test]
2009    fn decode_style_bold_red() {
2010        use shape_value::content::{Color, NamedColor};
2011        let style = decode_fstring_style(
2012            FSTRING_COLOR_NAMED,
2013            0, // Red
2014            FSTRING_COLOR_NONE,
2015            0,
2016            FSTRING_FLAG_BOLD,
2017        )
2018        .unwrap();
2019        assert!(style.bold);
2020        assert!(!style.italic);
2021        assert_eq!(style.fg, Some(Color::Named(NamedColor::Red)));
2022        assert_eq!(style.bg, None);
2023    }
2024
2025    #[test]
2026    fn decode_style_all_flags() {
2027        let style = decode_fstring_style(
2028            FSTRING_COLOR_NONE,
2029            0,
2030            FSTRING_COLOR_NONE,
2031            0,
2032            FSTRING_FLAG_BOLD
2033                | FSTRING_FLAG_ITALIC
2034                | FSTRING_FLAG_UNDERLINE
2035                | FSTRING_FLAG_DIM,
2036        )
2037        .unwrap();
2038        assert!(style.bold);
2039        assert!(style.italic);
2040        assert!(style.underline);
2041        assert!(style.dim);
2042    }
2043
2044    #[test]
2045    fn decode_color_invalid_kind() {
2046        assert!(decode_fstring_color(99, 0).is_err());
2047    }
2048
2049    #[test]
2050    fn decode_color_invalid_named_id() {
2051        assert!(decode_fstring_color(FSTRING_COLOR_NAMED, 99).is_err());
2052    }
2053}