Skip to main content

shape_vm/executor/
dispatch.rs

1//! Main execution loop and opcode dispatch.
2
3use std::sync::Arc;
4use std::sync::atomic::Ordering;
5
6use crate::bytecode::{Instruction, OpCode};
7use shape_value::{KindedSlot, NativeKind, VMError, ValueSlot};
8
9use super::debugger_integration::DebuggerIntegration;
10use super::{DebugVMState, ExecutionResult, VirtualMachine, async_ops};
11
12impl VirtualMachine {
13    /// Execute the loaded program.
14    ///
15    /// # Arguments
16    /// * `ctx` - Optional ExecutionContext for trading operations (rows, indicators, etc.)
17    ///
18    /// Returns a [`KindedSlot`] — the canonical post-`ValueWord`
19    /// runtime-value carrier (ADR-006 §2.7 / Q7). The slot's
20    /// `NativeKind` is sourced from `BytecodeProgram::top_level_frame.
21    /// return_kind` when present (the compiler-proven kind), with the
22    /// raw u64 bits taken from the top of the value stack. Hosts
23    /// dispatch on `result.kind()` and use the per-variant
24    /// `KindedSlot::as_*` accessors per §2.7.6.
25    pub fn execute(
26        &mut self,
27        ctx: Option<&mut shape_runtime::context::ExecutionContext>,
28    ) -> Result<KindedSlot, VMError> {
29        match self.execute_with_suspend(ctx)? {
30            ExecutionResult::Completed(slot) => Ok(slot),
31            ExecutionResult::Suspended { future_id, .. } => Err(VMError::Suspended {
32                future_id,
33                resume_ip: 0,
34            }),
35        }
36    }
37
38    /// Execute the loaded program and return the raw u64 bits at the top
39    /// of stack. Top-level emission pushes raw native values
40    /// (e.g. `i64`, `f64::to_bits()`, `0u64`/`1u64` for bool, raw heap
41    /// pointer for ptr); the kind is recovered from
42    /// `program_top_level_return_kind()`. Use this when the host wants
43    /// the raw bits without a `KindedSlot` wrapper.
44    pub fn execute_raw(
45        &mut self,
46        ctx: Option<&mut shape_runtime::context::ExecutionContext>,
47    ) -> Result<u64, VMError> {
48        match self.execute_with_suspend(ctx)? {
49            ExecutionResult::Completed(slot) => Ok(slot.raw()),
50            ExecutionResult::Suspended { future_id, .. } => Err(VMError::Suspended {
51                future_id,
52                resume_ip: 0,
53            }),
54        }
55    }
56
57    /// Read the program's declared top-level return kind, if present.
58    ///
59    /// Returns `Some(kind)` when `top_level_frame.return_kind` is set —
60    /// after the strict-typing bulldozer the compiler proves a kind for
61    /// every program at compile time, so this should always be `Some`
62    /// for a well-formed `BytecodeProgram`. Returns `None` only when no
63    /// `top_level_frame` is attached (legacy programs, partial linker
64    /// state). The deleted `NativeKind::Unknown` sentinel is no longer
65    /// observable here per ADR-006 §2.7.5.1 (wire-format is post-proof).
66    #[inline]
67    pub(crate) fn program_top_level_return_kind(&self) -> Option<NativeKind> {
68        // `FrameDescriptor.return_kind` is `Option<NativeKind>` (single-slot
69        // §2.7.8 / Q10 cell-storage shape — `None` ≡ "kind not stamped").
70        // Flatten via `?` so callers see `None` for either "no frame
71        // descriptor" or "frame present but no proven return kind".
72        self.program.top_level_frame.as_ref()?.return_kind
73    }
74
75    /// Execute the loaded program, returning either a completed value or suspension info.
76    ///
77    /// Unlike `execute()`, this method distinguishes between completion and suspension,
78    /// allowing the host to resume execution after resolving a future.
79    pub fn execute_with_suspend(
80        &mut self,
81        mut ctx: Option<&mut shape_runtime::context::ExecutionContext>,
82    ) -> Result<ExecutionResult, VMError> {
83        // Install this VM's ShapeTableHandle as the ambient current
84        // shape table for the duration of this execution. Mirrors B1's
85        // pattern for TypeSchemaRegistry. The guard restores any outer
86        // scope (e.g. a host-installed async scope) on drop, so nested
87        // or re-entrant VM execution composes correctly.
88        let _shape_scope =
89            shape_value::SyncShapeTableScope::enter(self.shape_table.clone());
90
91        self.clear_last_uncaught_exception();
92
93        // Fast path: when no debugger is attached and tracing is off, use the
94        // streamlined loop that skips per-instruction debug/trace checks.
95        if self.debugger.is_none() && !self.config.trace_execution {
96            return self.execute_fast_with_exceptions(ctx);
97        }
98
99        // Start debugger if enabled
100        if let Some(ref mut debugger) = self.debugger {
101            debugger.start();
102        }
103
104        while self.ip < self.program.instructions.len() {
105            // Check for debug break
106            let should_break = if let Some(ref mut debugger) = self.debugger {
107                debugger.should_break(
108                    &DebugVMState {
109                        ip: self.ip,
110                        call_stack_depth: self.call_stack.len(),
111                    },
112                    self.ip,
113                )
114            } else {
115                false
116            };
117
118            if should_break {
119                if let Some(ref mut debugger) = self.debugger {
120                    debugger.debug_break(
121                        &DebugVMState {
122                            ip: self.ip,
123                            call_stack_depth: self.call_stack.len(),
124                        },
125                        &self.program,
126                    );
127                }
128            }
129
130            let instruction = self.program.instructions[self.ip];
131
132            // Record instruction in metrics (opt-in, near-zero cost when None)
133            if let Some(ref mut metrics) = self.metrics {
134                metrics.record_instruction();
135            }
136
137            // Trace instruction if enabled
138            if self.config.trace_execution {
139                if let Some(ref debugger) = self.debugger {
140                    debugger.trace_instruction(
141                        &DebugVMState {
142                            ip: self.ip,
143                            call_stack_depth: self.call_stack.len(),
144                        },
145                        &self.program,
146                        &instruction,
147                    );
148                } else {
149                    self.trace_state();
150                }
151            }
152
153            self.ip += 1;
154            self.instruction_count += 1;
155
156            // Check for Ctrl+C interrupt every 1024 instructions
157            if self.instruction_count & 0x3FF == 0 && self.interrupt.load(Ordering::Relaxed) > 0 {
158                return Err(VMError::Interrupted);
159            }
160
161            // Resource limit check (sandboxed execution)
162            if let Some(ref mut usage) = self.resource_usage {
163                usage
164                    .tick_instruction()
165                    .map_err(|e| VMError::RuntimeError(e.to_string()))?;
166            }
167
168            // Poll for completed tier promotions every 1024 instructions.
169            if self.instruction_count & 0x3FF == 0 {
170                self.poll_tier_completions();
171            }
172
173            // GC safepoint poll (gc feature only)
174            #[cfg(feature = "gc")]
175            if self.instruction_count & 0x3FF == 0 {
176                self.gc_safepoint_poll();
177
178                // Incremental marking: make bounded progress on the gray worklist
179                // when a marking cycle is active, without stopping the world.
180                if self.gc_heap.as_ref().map_or(false, |h| h.is_marking()) {
181                    self.gc_incremental_mark_step();
182                }
183            }
184
185            // Time-travel capture check (debug path).
186            if let Some(ref mut tt) = self.time_travel {
187                let current_ip = self.ip.saturating_sub(1);
188                let is_call_or_return = matches!(
189                    instruction.opcode,
190                    OpCode::Call
191                        | OpCode::CallValue
192                        | OpCode::CallClosure
193                        | OpCode::CallFunctionIndirect
194                        | OpCode::Return
195                        | OpCode::ReturnValue
196                        | OpCode::ReturnValueI64
197                        | OpCode::ReturnValueU64
198                        | OpCode::ReturnValueF64
199                        | OpCode::ReturnValueI32
200                        | OpCode::ReturnValueU32
201                        | OpCode::ReturnValueI16
202                        | OpCode::ReturnValueU16
203                        | OpCode::ReturnValueI8
204                        | OpCode::ReturnValueU8
205                        | OpCode::ReturnValueBool
206                        | OpCode::ReturnValuePtr
207                );
208                if tt.should_capture(current_ip, self.instruction_count as u64, is_call_or_return) {
209                    if let Ok(store) = tt.snapshot_store() {
210                        let store_ptr = store as *const shape_runtime::snapshot::SnapshotStore;
211                        if let Ok(snap) = self.snapshot(unsafe { &*store_ptr }) {
212                            let call_depth = self.call_stack.len();
213                            self.time_travel.as_mut().unwrap().record(
214                                snap,
215                                current_ip,
216                                self.instruction_count as u64,
217                                call_depth,
218                            );
219                        }
220                    }
221                }
222            }
223
224            // Track instruction index before execution for error reporting
225            let error_ip = self.ip.saturating_sub(1);
226
227            if let Err(err) = self.execute_instruction(&instruction, ctx.as_deref_mut()) {
228                // Check for suspension (not a real error)
229                if let VMError::Suspended {
230                    future_id,
231                    resume_ip,
232                } = err
233                {
234                    return Ok(ExecutionResult::Suspended {
235                        future_id,
236                        resume_ip,
237                    });
238                }
239
240                // Check for state.resume() request
241                if matches!(err, VMError::ResumeRequested) {
242                    self.apply_pending_resume()?;
243                    continue;
244                }
245
246                if !self.exception_handlers.is_empty() {
247                    // W8-EX: construct the exception payload as a
248                    // `KindedSlot` carrier per §2.7.6 / Q8. The
249                    // underlying `Arc<String>` strong-count share
250                    // transfers from `Arc::into_raw` into the carrier
251                    // via `KindedSlot::from_string_arc`.
252                    //
253                    // W13-anyerror (close, 2026-05-10): wrap the
254                    // String-kinded payload into an AnyError
255                    // TypedObject via `normalize_err_payload` per
256                    // playbook §10 E-exceptions row, so the catch
257                    // block sees `NativeKind::Ptr(HeapKind::TypedObject)`
258                    // and `e.message` reads back via the existing
259                    // `op_get_prop` TypedObject path. Ownership of
260                    // the String share transfers into the AnyError's
261                    // payload/message field slots.
262                    let raw_payload = KindedSlot::from_string_arc(Arc::new(err.to_string()));
263                    let payload = self.normalize_err_payload(raw_payload)?;
264                    self.handle_exception(payload)?;
265                } else {
266                    // Enrich error with source location before returning
267                    return Err(self.enrich_error_with_location(err, error_ip));
268                }
269            }
270
271            // Check for pending frame resume (from state.resume_frame)
272            if self.pending_frame_resume.is_some() {
273                self.apply_pending_frame_resume()?;
274            }
275
276            // Check for halt
277            if matches!(instruction.opcode, OpCode::Halt) {
278                break;
279            }
280        }
281
282        // Return top of stack or sentinel `none` (only if sp is above
283        // top-level locals region). The slot transfers ownership: the
284        // returned `KindedSlot` owns the strong-count share previously
285        // held by the stack slot.
286        let tl = self.program.top_level_locals_count as usize;
287        Ok(ExecutionResult::Completed(if self.sp > tl {
288            self.sp -= 1;
289            let (bits, kind) = self.stack_take_kinded(self.sp);
290            KindedSlot::new(ValueSlot::from_raw(bits), kind)
291        } else {
292            KindedSlot::new(ValueSlot::none(), NativeKind::Bool)
293        }))
294    }
295
296    /// Fast execution loop: no debugger/trace checks, but full exception handling
297    /// and halt/suspension support. This is the default hot path for production code.
298    fn execute_fast_with_exceptions(
299        &mut self,
300        mut ctx: Option<&mut shape_runtime::context::ExecutionContext>,
301    ) -> Result<ExecutionResult, VMError> {
302        while self.ip < self.program.instructions.len() {
303            let ip = self.ip;
304            self.ip += 1;
305            self.instruction_count += 1;
306
307            // Check for Ctrl+C interrupt every 1024 instructions
308            if self.instruction_count & 0x3FF == 0 && self.interrupt.load(Ordering::Relaxed) > 0 {
309                return Err(VMError::Interrupted);
310            }
311
312            // Poll for completed tier promotions every 1024 instructions.
313            if self.instruction_count & 0x3FF == 0 {
314                self.poll_tier_completions();
315            }
316
317            // GC safepoint poll (gc feature only)
318            #[cfg(feature = "gc")]
319            if self.instruction_count & 0x3FF == 0 {
320                self.gc_safepoint_poll();
321
322                // Incremental marking: make bounded progress on the gray worklist
323                // when a marking cycle is active, without stopping the world.
324                if self.gc_heap.as_ref().map_or(false, |h| h.is_marking()) {
325                    self.gc_incremental_mark_step();
326                }
327            }
328
329            let instruction = self.program.instructions[ip];
330
331            // Record instruction in metrics (opt-in, near-zero cost when None)
332            if let Some(ref mut metrics) = self.metrics {
333                metrics.record_instruction();
334            }
335
336            // Time-travel capture check (cheap: just a mode check + counter).
337            if let Some(ref mut tt) = self.time_travel {
338                let is_call_or_return = matches!(
339                    instruction.opcode,
340                    OpCode::Call
341                        | OpCode::CallValue
342                        | OpCode::CallClosure
343                        | OpCode::CallFunctionIndirect
344                        | OpCode::Return
345                        | OpCode::ReturnValue
346                        | OpCode::ReturnValueI64
347                        | OpCode::ReturnValueU64
348                        | OpCode::ReturnValueF64
349                        | OpCode::ReturnValueI32
350                        | OpCode::ReturnValueU32
351                        | OpCode::ReturnValueI16
352                        | OpCode::ReturnValueU16
353                        | OpCode::ReturnValueI8
354                        | OpCode::ReturnValueU8
355                        | OpCode::ReturnValueBool
356                        | OpCode::ReturnValuePtr
357                );
358                if tt.should_capture(ip, self.instruction_count as u64, is_call_or_return) {
359                    if let Ok(store) = tt.snapshot_store() {
360                        let store_ptr = store as *const shape_runtime::snapshot::SnapshotStore;
361                        if let Ok(snap) = self.snapshot(unsafe { &*store_ptr }) {
362                            let call_depth = self.call_stack.len();
363                            self.time_travel.as_mut().unwrap().record(
364                                snap,
365                                ip,
366                                self.instruction_count as u64,
367                                call_depth,
368                            );
369                        }
370                    }
371                }
372            }
373
374            if let Err(err) = self.execute_instruction(&instruction, ctx.as_deref_mut()) {
375                // Check for suspension (not a real error)
376                if let VMError::Suspended {
377                    future_id,
378                    resume_ip,
379                } = err
380                {
381                    return Ok(ExecutionResult::Suspended {
382                        future_id,
383                        resume_ip,
384                    });
385                }
386
387                // Check for state.resume() request
388                if matches!(err, VMError::ResumeRequested) {
389                    self.apply_pending_resume()?;
390                    continue;
391                }
392
393                if !self.exception_handlers.is_empty() {
394                    // W8-EX: see the matching call site above —
395                    // `Arc<String>` payload built into a `KindedSlot`
396                    // carrier per §2.7.6 / Q8.
397                    //
398                    // W13-anyerror (close, 2026-05-10): wrap into
399                    // AnyError TypedObject via `normalize_err_payload`
400                    // so the catch block sees the canonical
401                    // `Ptr(HeapKind::TypedObject)` payload kind per
402                    // playbook §10 E-exceptions row.
403                    let raw_payload = KindedSlot::from_string_arc(Arc::new(err.to_string()));
404                    let payload = self.normalize_err_payload(raw_payload)?;
405                    self.handle_exception(payload)?;
406                } else {
407                    return Err(self.enrich_error_with_location(err, ip));
408                }
409            }
410
411            // Check for pending frame resume (from state.resume_frame)
412            if self.pending_frame_resume.is_some() {
413                self.apply_pending_frame_resume()?;
414            }
415
416            if matches!(instruction.opcode, OpCode::Halt) {
417                break;
418            }
419        }
420
421        let tl = self.program.top_level_locals_count as usize;
422        Ok(ExecutionResult::Completed(if self.sp > tl {
423            self.sp -= 1;
424            let (bits, kind) = self.stack_take_kinded(self.sp);
425            KindedSlot::new(ValueSlot::from_raw(bits), kind)
426        } else {
427            KindedSlot::new(ValueSlot::none(), NativeKind::Bool)
428        }))
429    }
430
431    /// Fast execution loop without debugging overhead or exception handling.
432    /// Used for hot inner loops (e.g., function calls) where we need maximum performance
433    /// and exceptions propagate via `?`.
434    #[inline]
435    pub(crate) fn execute_fast(
436        &mut self,
437        mut ctx: Option<&mut shape_runtime::context::ExecutionContext>,
438    ) -> Result<KindedSlot, VMError> {
439        while self.ip < self.program.instructions.len() {
440            // Get index first, then increment
441            let ip = self.ip;
442            self.ip += 1;
443            self.instruction_count += 1;
444
445            // Check for Ctrl+C interrupt every 1024 instructions
446            if self.instruction_count & 0x3FF == 0 && self.interrupt.load(Ordering::Relaxed) > 0 {
447                return Err(VMError::Interrupted);
448            }
449
450            // GC safepoint poll (gc feature only)
451            #[cfg(feature = "gc")]
452            if self.instruction_count & 0x3FF == 0 {
453                self.gc_safepoint_poll();
454
455                // Incremental marking: make bounded progress on the gray worklist
456                // when a marking cycle is active, without stopping the world.
457                if self.gc_heap.as_ref().map_or(false, |h| h.is_marking()) {
458                    self.gc_incremental_mark_step();
459                }
460            }
461
462            let instruction = self.program.instructions[ip];
463
464            // Record instruction in metrics (opt-in, near-zero cost when None)
465            if let Some(ref mut metrics) = self.metrics {
466                metrics.record_instruction();
467            }
468
469            self.execute_instruction(&instruction, ctx.as_deref_mut())?;
470
471            if matches!(instruction.opcode, OpCode::Halt) {
472                break;
473            }
474        }
475
476        let tl = self.program.top_level_locals_count as usize;
477        Ok(if self.sp > tl {
478            self.sp -= 1;
479            let (bits, kind) = self.stack_take_kinded(self.sp);
480            KindedSlot::new(ValueSlot::from_raw(bits), kind)
481        } else {
482            KindedSlot::new(ValueSlot::none(), NativeKind::Bool)
483        })
484    }
485
486    pub(crate) fn execute_until_call_depth(
487        &mut self,
488        target_depth: usize,
489        mut ctx: Option<&mut shape_runtime::context::ExecutionContext>,
490    ) -> Result<(), VMError> {
491        loop {
492            if self.ip >= self.program.instructions.len() {
493                break;
494            }
495
496            let instruction = self.program.instructions[self.ip];
497            self.ip += 1;
498            self.instruction_count += 1;
499
500            match self.execute_instruction(&instruction, ctx.as_deref_mut()) {
501                Ok(()) => {}
502                Err(VMError::ResumeRequested) => {
503                    self.apply_pending_resume()?;
504                    continue;
505                }
506                Err(err) => return Err(err),
507            }
508
509            if self.pending_frame_resume.is_some() {
510                self.apply_pending_frame_resume()?;
511            }
512
513            if matches!(instruction.opcode, OpCode::Halt) || self.call_stack.len() == target_depth {
514                break;
515            }
516        }
517        Ok(())
518    }
519
520    /// Execute a single instruction
521    pub(crate) fn execute_instruction(
522        &mut self,
523        instruction: &Instruction,
524        ctx: Option<&mut shape_runtime::context::ExecutionContext>,
525    ) -> Result<(), VMError> {
526        use OpCode::*;
527
528        match instruction.opcode {
529            // Stack operations
530            PushConst | PushNull | Pop | Dup | Swap | PromoteToOwned | ReturnOwned
531            | PromoteToShared => {
532                return self.exec_stack_ops(instruction);
533            }
534
535            // Bitwise dynamic ops (int-typed bitwise still routes here when operand
536            // types aren't proven at compile time). The strict-typing sweep
537            // (Phase 1+2) deleted the `*Dynamic` arithmetic/comparison opcodes;
538            // the bitwise variants remain because typed `BitAndInt`/etc. only
539            // fire when both operands are proven `int`.
540            BitAnd | BitOr | BitXor | BitShl | BitShr | BitNot => {
541                return self.exec_dyn_bit_dispatch(instruction);
542            }
543
544            // Typed arithmetic (compiler-guaranteed types, zero dispatch).
545            //
546            // R5.1B adds the six int-typed bitwise opcodes to this arm.
547            // They are structurally identical to the other typed int ops
548            // (raw i48-tagged operand slots, zero dispatch) and therefore
549            // share the same exec_typed_arithmetic handler.
550            AddInt | AddNumber | AddDecimal | SubInt | SubNumber | SubDecimal | MulInt
551            | MulNumber | MulDecimal | DivInt | DivNumber | DivDecimal | ModInt | ModNumber
552            | ModDecimal | PowInt | PowNumber | PowDecimal | IntToNumber | NumberToInt
553            | NegInt | NegNumber | NegDecimal | BitAndInt | BitOrInt | BitXorInt
554            | BitShlInt | BitShrInt | BitNotInt => {
555                return self.exec_typed_arithmetic(instruction);
556            }
557
558            // NOTE: Trusted arithmetic/comparison opcodes removed — the typed
559            // variants (AddInt, GtInt, etc.) already provide zero-dispatch execution.
560
561            // Compact typed arithmetic (width-parameterised, ABI-stable)
562            AddTyped | SubTyped | MulTyped | DivTyped | ModTyped | CmpTyped => {
563                return self.exec_compact_typed_arithmetic(instruction);
564            }
565
566            // CastWidth: integer width casting (bit truncation)
567            CastWidth => {
568                return self.op_cast_width(instruction);
569            }
570
571            // Typed comparison (compiler-guaranteed types, zero dispatch)
572            GtInt | GtNumber | GtDecimal | LtInt | LtNumber | LtDecimal | GteInt | GteNumber
573            | GteDecimal | LteInt | LteNumber | LteDecimal | EqInt | EqNumber | NeqInt
574            | NeqNumber | EqString | EqDecimal | IsNull | GtString | LtString | GteString
575            | LteString => {
576                return self.exec_typed_comparison(instruction);
577            }
578
579            // Logical
580            And | Or | Not => {
581                return self.exec_logical(instruction);
582            }
583
584            // Control flow
585            Jump | JumpIfFalse | JumpIfTrue | JumpIfFalseTrusted | Call | CallValue
586            | CallClosure | CallFunctionIndirect | CallForeign | Return | ReturnValue
587            | ReturnValueI64 | ReturnValueU64 | ReturnValueF64 | ReturnValueI32
588            | ReturnValueU32 | ReturnValueI16 | ReturnValueU16 | ReturnValueI8
589            | ReturnValueU8 | ReturnValueBool | ReturnValuePtr => {
590                return self.exec_control_flow(instruction, ctx);
591            }
592
593            // Variables (including reference operations)
594            LoadLocal
595            | LoadLocalTrusted
596            | LoadLocalMove
597            | LoadLocalClone
598            | StoreLocal
599            | StoreLocalTyped
600            | StoreLocalDrop
601            | LoadLocalI64
602            | LoadLocalU64
603            | LoadLocalF64
604            | LoadLocalI32
605            | LoadLocalU32
606            | LoadLocalI16
607            | LoadLocalU16
608            | LoadLocalI8
609            | LoadLocalU8
610            | LoadLocalBool
611            | LoadLocalPtr
612            | StoreLocalI64
613            | StoreLocalU64
614            | StoreLocalF64
615            | StoreLocalI32
616            | StoreLocalU32
617            | StoreLocalI16
618            | StoreLocalU16
619            | StoreLocalI8
620            | StoreLocalU8
621            | StoreLocalBool
622            | StoreLocalPtr
623            | LoadModuleBinding
624            | StoreModuleBinding
625            | StoreModuleBindingTyped
626            | LoadModuleBindingI64
627            | LoadModuleBindingU64
628            | LoadModuleBindingF64
629            | LoadModuleBindingI32
630            | LoadModuleBindingU32
631            | LoadModuleBindingI16
632            | LoadModuleBindingU16
633            | LoadModuleBindingI8
634            | LoadModuleBindingU8
635            | LoadModuleBindingBool
636            | LoadModuleBindingPtr
637            | StoreModuleBindingI64
638            | StoreModuleBindingU64
639            | StoreModuleBindingF64
640            | StoreModuleBindingI32
641            | StoreModuleBindingU32
642            | StoreModuleBindingI16
643            | StoreModuleBindingU16
644            | StoreModuleBindingI8
645            | StoreModuleBindingU8
646            | StoreModuleBindingBool
647            | StoreModuleBindingPtr
648            | LoadClosure
649            | StoreClosure
650            | CloseUpvalue
651            | MakeRef
652            | MakeFieldRef
653            | MakeIndexRef
654            | DerefLoad
655            | DerefStore
656            | SetIndexRef
657            | LoadOwnedMutableCapture
658            | StoreOwnedMutableCapture
659            | LoadOwnedMutableCaptureI64
660            | LoadOwnedMutableCaptureU64
661            | LoadOwnedMutableCaptureF64
662            | LoadOwnedMutableCaptureI32
663            | LoadOwnedMutableCaptureU32
664            | LoadOwnedMutableCaptureI16
665            | LoadOwnedMutableCaptureU16
666            | LoadOwnedMutableCaptureI8
667            | LoadOwnedMutableCaptureU8
668            | LoadOwnedMutableCaptureBool
669            | LoadOwnedMutableCapturePtr
670            | StoreOwnedMutableCaptureI64
671            | StoreOwnedMutableCaptureU64
672            | StoreOwnedMutableCaptureF64
673            | StoreOwnedMutableCaptureI32
674            | StoreOwnedMutableCaptureU32
675            | StoreOwnedMutableCaptureI16
676            | StoreOwnedMutableCaptureU16
677            | StoreOwnedMutableCaptureI8
678            | StoreOwnedMutableCaptureU8
679            | StoreOwnedMutableCaptureBool
680            | StoreOwnedMutableCapturePtr
681            | LoadSharedCapture
682            | StoreSharedCapture
683            | LoadSharedCaptureI64
684            | LoadSharedCaptureU64
685            | LoadSharedCaptureF64
686            | LoadSharedCaptureI32
687            | LoadSharedCaptureU32
688            | LoadSharedCaptureI16
689            | LoadSharedCaptureU16
690            | LoadSharedCaptureI8
691            | LoadSharedCaptureU8
692            | LoadSharedCaptureBool
693            | LoadSharedCapturePtr
694            | StoreSharedCaptureI64
695            | StoreSharedCaptureU64
696            | StoreSharedCaptureF64
697            | StoreSharedCaptureI32
698            | StoreSharedCaptureU32
699            | StoreSharedCaptureI16
700            | StoreSharedCaptureU16
701            | StoreSharedCaptureI8
702            | StoreSharedCaptureU8
703            | StoreSharedCaptureBool
704            | StoreSharedCapturePtr
705            | AllocSharedLocal
706            | LoadSharedLocal
707            | StoreSharedLocal
708            | DropSharedLocal
709            | AllocSharedModuleBinding
710            | LoadSharedModuleBinding
711            | StoreSharedModuleBinding => {
712                return self.exec_variables(instruction);
713            }
714
715            // Objects/Arrays
716            NewArray
717            | NewMatrix
718            | NewObject
719            | GetProp
720            | SetProp
721            | SetLocalIndex
722            | SetModuleBindingIndex
723            | Length
724            | ArrayPush
725            | ArrayPushLocal
726            | ArrayPop
727            | MakeClosure
728            | MergeObject
729            | NewTypedObject
730            | NewTypedArray
731            | TypedMergeObject
732            | WrapTypeAnnotation => {
733                return self.exec_objects(instruction, ctx);
734            }
735
736            // Built-in functions
737            BuiltinCall | TypeCheck | Convert => {
738                return self.exec_builtins(instruction, ctx);
739            }
740
741            // Typed conversion opcodes (zero-dispatch, no operand)
742            ConvertToInt => return self.op_convert_to_int(),
743            ConvertToNumber => return self.op_convert_to_number(),
744            ConvertToString => return self.op_convert_to_string(),
745            ConvertToBool => return self.op_convert_to_bool(),
746            ConvertToDecimal => return self.op_convert_to_decimal(),
747            ConvertToChar => return self.op_convert_to_char(),
748            TryConvertToInt => return self.op_try_convert_to_int(),
749            TryConvertToNumber => return self.op_try_convert_to_number(),
750            TryConvertToString => return self.op_try_convert_to_string(),
751            TryConvertToBool => return self.op_try_convert_to_bool(),
752            TryConvertToDecimal => return self.op_try_convert_to_decimal(),
753            TryConvertToChar => return self.op_try_convert_to_char(),
754
755            // Exception handling
756            SetupTry | PopHandler | Throw | TryUnwrap | UnwrapOption | ErrorContext | IsOk
757            | IsErr | UnwrapOk | UnwrapErr => {
758                return self.exec_exceptions(instruction);
759            }
760
761            // Additional operations
762            SliceAccess | NullCoalesce | MakeRange => {
763                return self.exec_additional(instruction);
764            }
765
766            // Loop control
767            LoopStart | LoopEnd | Break | Continue | IterNext | IterDone => {
768                return self.exec_loops(instruction);
769            }
770
771            // Method calls on values
772            CallMethod => {
773                return self.op_call_method(instruction, ctx);
774            }
775
776            // Dedicated concatenation opcodes (Phase 2.3 / 2.4): replace the
777            // generic Add overload for built-in heap types whose operand types
778            // the compiler can prove statically.
779            StringConcat => {
780                return self.op_string_concat();
781            }
782            ArrayConcat => {
783                return self.op_array_concat();
784            }
785
786            PushTimeframe => {
787                return Err(VMError::NotImplemented(
788                    "Opcode 'PushTimeframe' is reserved but not yet implemented".into(),
789                ));
790            }
791            PopTimeframe => {
792                return Err(VMError::NotImplemented(
793                    "Opcode 'PopTimeframe' is reserved but not yet implemented".into(),
794                ));
795            }
796
797            // Typed column access on RowView values
798            LoadColF64 | LoadColI64 | LoadColBool | LoadColStr => {
799                return self.exec_load_col(instruction);
800            }
801
802            // Bind DataTable to TypeSchema (runtime safety net)
803            BindSchema => {
804                return self.exec_bind_schema(instruction);
805            }
806
807            // Type-specialized operations (JIT optimization)
808            GetFieldTyped | SetFieldTyped => {
809                return self.exec_jit_ops(instruction);
810            }
811
812            // v2 typed struct field operations
813            FieldLoadF64 | FieldLoadI64 | FieldLoadI32 | FieldLoadBool | FieldLoadPtr
814            | FieldStoreF64 | FieldStoreI64 | FieldStoreI32 | NewTypedStruct => {
815                return self.exec_v2_typed_field(instruction);
816            }
817
818            // v2 sized integer (i32) arithmetic and comparison
819            AddI32 | SubI32 | MulI32 | DivI32 | ModI32 | EqI32 | NeqI32 | LtI32 | GtI32
820            | LteI32 | GteI32 => {
821                return self.exec_v2_sized_int(instruction);
822            }
823
824            // Async operations
825            Yield | Suspend | Resume | Poll | AwaitBar | AwaitTick | EmitAlert | EmitEvent
826            | Await | SpawnTask | JoinInit | JoinAwait | CancelTask | AsyncScopeEnter
827            | AsyncScopeExit => {
828                match self.exec_async_op(instruction) {
829                    Ok(async_ops::AsyncExecutionResult::Continue) => return Ok(()),
830                    Ok(async_ops::AsyncExecutionResult::Yielded) => {
831                        return Ok(());
832                    }
833                    Ok(async_ops::AsyncExecutionResult::Suspended(info)) => {
834                        // Propagate suspension as VMError::Suspended so execute() can catch it
835                        match info.wait_type {
836                            async_ops::WaitType::Future { id } => {
837                                return Err(VMError::Suspended {
838                                    future_id: id,
839                                    resume_ip: info.resume_ip,
840                                });
841                            }
842                            async_ops::WaitType::TaskGroup { kind, task_ids } => {
843                                // TaskGroup suspension: propagate with first task_id as marker
844                                // The host resolves the group based on kind + task_ids
845                                let marker_id = task_ids.first().copied().unwrap_or(0);
846                                let _ = (kind, task_ids); // Host retrieves from SuspensionInfo
847                                return Err(VMError::Suspended {
848                                    future_id: marker_id,
849                                    resume_ip: info.resume_ip,
850                                });
851                            }
852                            _ => {
853                                // Non-future suspensions (NextBar, Timer, AnyEvent) cannot be
854                                // resumed by the host via future_id. Drain any open async scopes
855                                // to prevent leaked task tracking, then continue execution.
856                                while let Some(mut scope_tasks) = self.async_scope_stack.pop() {
857                                    scope_tasks.reverse();
858                                    for task_id in scope_tasks {
859                                        self.task_scheduler.cancel(task_id);
860                                    }
861                                }
862                                return Ok(());
863                            }
864                        }
865                    }
866                    Err(e) => return Err(e),
867                }
868            }
869
870            // Trait object operations
871            BoxTraitObject | DynMethodCall | DropCall | DropCallAsync => {
872                return self.exec_trait_object_ops(instruction, ctx);
873            }
874
875            // v2 typed array operations
876            NewTypedArrayF64
877            | NewTypedArrayI64
878            | NewTypedArrayI32
879            | NewTypedArrayBool
880            | TypedArrayGetF64
881            | TypedArrayGetI64
882            | TypedArrayGetI32
883            | TypedArrayGetBool
884            | TypedArraySetF64
885            | TypedArraySetI64
886            | TypedArraySetI32
887            | TypedArraySetBool
888            | TypedArrayPushF64
889            | TypedArrayPushI64
890            | TypedArrayPushI32
891            | TypedArrayPushBool
892            | TypedArrayLen
893            // W12 S1 — sized-integer typed array opcodes (2026-05-13)
894            | NewTypedArrayI8
895            | TypedArrayGetI8
896            | TypedArrayPushI8
897            | TypedArraySetI8
898            | NewTypedArrayU8
899            | TypedArrayGetU8
900            | TypedArrayPushU8
901            | TypedArraySetU8
902            | NewTypedArrayI16
903            | TypedArrayGetI16
904            | TypedArrayPushI16
905            | TypedArraySetI16
906            | NewTypedArrayU16
907            | TypedArrayGetU16
908            | TypedArrayPushU16
909            | TypedArraySetU16
910            | NewTypedArrayU32
911            | TypedArrayGetU32
912            | TypedArrayPushU32
913            | TypedArraySetU32
914            // Wave 2 Agent A1 (2026-05-14) — F32 + Char monomorphizations
915            // (drive-by routing fix: opcode handlers landed in array.rs at A1 close
916            // but were never added to the dispatch table — instructions fell
917            // through to the default branch).
918            | NewTypedArrayF32
919            | TypedArrayGetF32
920            | TypedArrayPushF32
921            | TypedArraySetF32
922            | NewTypedArrayChar
923            | TypedArrayGetChar
924            | TypedArrayPushChar
925            | TypedArraySetChar
926            // Wave 2 Agent A2 (2026-05-14) — String + Decimal heap-element monomorphizations.
927            | NewTypedArrayString
928            | TypedArrayGetString
929            | TypedArrayPushString
930            | TypedArraySetString
931            | NewTypedArrayDecimal
932            | TypedArrayGetDecimal
933            | TypedArrayPushDecimal
934            | TypedArraySetDecimal
935            // Phase 4b Round 4 W16.2-A op_new_array-typed-object-element (2026-05-18) —
936            // v2-raw TypedArray<*const TypedObjectStorage> heap-element carrier per
937            // ADR-006 §2.7.5 + audit `v0.3-w16-v3s5-ckpt56-strict-close-audit.md`
938            // §2.1 + §3.A row 1. Mirror of the Wave 2 A2 String/Decimal arms above.
939            | NewTypedArrayTypedObject
940            | TypedArrayGetTypedObject
941            | TypedArrayPushTypedObject
942            | TypedArraySetTypedObject
943            // Wave 3 Stabilize Round 1 V3-A2-followup-producer-cascade (2026-05-15) —
944            // v2-raw String/Decimal literal constructors (closes the literal-element
945            // kind mismatch surfaced at Round 3a' gate-flip: `let xs: Array<string>
946            // = ["a","b"]` previously emitted `LoadConst` of Arc<String> with
947            // NativeKind::String, which `TypedArrayPushString` rejected at the
948            // strict-kind check in `v2_handlers/array.rs:687`).
949            | NewStringV2
950            | NewDecimalV2 => {
951                return self.exec_v2_typed_array(instruction);
952            }
953
954            // Typed array element access (local-slot based, skip HeapValue dispatch)
955            GetElemI64 | GetElemF64 | SetElemI64 | SetElemF64
956            | ArrayPushI64 | ArrayPushF64 | ArrayLenTyped => {
957                return self.exec_typed_array_elem_ops(instruction);
958            }
959
960            // Typed HashMap access (local-slot based, skip HeapValue dispatch)
961            MapGetStrI64 | MapGetStrF64 | MapSetStrI64 | MapHasStr | MapLenTyped => {
962                return self.exec_typed_map_access(instruction);
963            }
964
965            // Typed String access (local-slot based or stack-based).
966            //
967            // R5.5 adds the three typed string+scalar concat opcodes to this
968            // arm; they share the `exec_typed_string_access` handler since
969            // they are structurally identical to `StringConcatTyped` (stack-
970            // based, single heap allocation, no local-slot operand).
971            StringLenTyped | StringCharAt | StringConcatTyped
972            | StringConcatInt | StringConcatNumber | StringConcatBool => {
973                return self.exec_typed_string_access(instruction);
974            }
975
976            // v2 typed map operations
977            NewTypedMapStringF64
978            | NewTypedMapStringI64
979            | NewTypedMapStringPtr
980            | NewTypedMapI64F64
981            | NewTypedMapI64I64
982            | NewTypedMapI64Ptr
983            | TypedMapStringF64Get
984            | TypedMapStringI64Get
985            | TypedMapStringPtrGet
986            | TypedMapI64F64Get
987            | TypedMapI64I64Get
988            | TypedMapI64PtrGet
989            | TypedMapStringF64Set
990            | TypedMapStringI64Set
991            | TypedMapStringPtrSet
992            | TypedMapI64F64Set
993            | TypedMapI64I64Set
994            | TypedMapI64PtrSet
995            | TypedMapStringF64Has
996            | TypedMapStringI64Has
997            | TypedMapStringPtrHas
998            | TypedMapI64F64Has
999            | TypedMapI64I64Has
1000            | TypedMapI64PtrHas
1001            | TypedMapStringF64Delete
1002            | TypedMapStringI64Delete
1003            | TypedMapStringPtrDelete
1004            | TypedMapI64F64Delete
1005            | TypedMapI64I64Delete
1006            | TypedMapI64PtrDelete => {
1007                return self.exec_v2_typed_map(instruction);
1008            }
1009
1010            // Special
1011            Nop => {}
1012            Halt => {}
1013            // Stage 2.6.5.0: Debug opcode removed; slot 0xF2 reused for IsNull.
1014            // No compiler ever emitted Debug — it was a stale runtime hook.
1015
1016            // V1.1B: ownership-aware local opcodes.
1017            //
1018            // Phase 1 of `docs/ownership-aware-runtime-v2.md`: these
1019            // handlers read the local slot bits directly and delegate
1020            // refcount adjustment to `raw_helpers::{clone,drop}_raw_bits`.
1021            // The compiler does not yet emit them — V1.1C adds emission
1022            // behind the `SHAPE_V2_OWNERSHIP_MOVES` flag — so at this
1023            // stage only hand-crafted bytecode exercises these arms.
1024            MoveLocal => self.op_move_local(instruction)?,
1025            CloneLocal => self.op_clone_local(instruction)?,
1026            DropLocal => self.op_drop_local(instruction)?,
1027
1028            // V1.2B: `PromoteToShared` is dispatched above through the
1029            // Stack-category arm alongside `PromoteToOwned`; no separate
1030            // arm is needed here. V1.2C will add compiler emission.
1031
1032            // R5.1B: typed bitwise opcodes (BitAndInt/BitOrInt/BitXorInt/
1033            // BitShlInt/BitShrInt/BitNotInt) are dispatched above via the
1034            // typed-arithmetic arm alongside the other typed int ops; no
1035            // separate arm is needed here. R5.1C will add compiler
1036            // emission behind `SHAPE_V2_TYPED_BITWISE=1`.
1037
1038            _ => return Err(VMError::InvalidOperand),
1039        }
1040
1041        Ok(())
1042    }
1043
1044    /// Enrich an error with source location context
1045    ///
1046    /// Uses debug_info from the program to add line numbers and source context
1047    /// to the error message for better debugging.
1048    pub(crate) fn enrich_error_with_location(&mut self, error: VMError, ip: usize) -> VMError {
1049        let debug_info = &self.program.debug_info;
1050
1051        // Try to get line number and file for this instruction
1052        let location = debug_info.get_location_for_instruction(ip);
1053
1054        if let Some((file_id, line_num)) = location {
1055            // Store the line number and file for LSP integration
1056            self.last_error_line = Some(line_num);
1057            self.last_error_file = debug_info
1058                .source_map
1059                .get_file(file_id)
1060                .map(|s| s.to_string());
1061
1062            // Try to get the source line from the correct file
1063            let source_context = debug_info
1064                .get_source_line_from_file(file_id, line_num as usize)
1065                .map(|s| s.trim())
1066                .filter(|s| !s.is_empty());
1067
1068            let base_msg = match &error {
1069                VMError::RuntimeError(msg) => msg.clone(),
1070                VMError::TypeError { expected, got } => {
1071                    format!("TypeError: expected {}, got {}", expected, got)
1072                }
1073                VMError::StackUnderflow => "Stack underflow".to_string(),
1074                VMError::StackOverflow => "Stack overflow".to_string(),
1075                VMError::DivisionByZero => "Division by zero".to_string(),
1076                VMError::UndefinedVariable(name) => format!("Undefined variable: {}", name),
1077                VMError::UndefinedProperty(name) => format!("Undefined property: {}", name),
1078                VMError::InvalidCall => "Invalid function call".to_string(),
1079                VMError::IndexOutOfBounds { index, length } => {
1080                    format!("Index {} out of bounds (length {})", index, length)
1081                }
1082                VMError::InvalidOperand => "Invalid operand".to_string(),
1083                VMError::ArityMismatch {
1084                    function,
1085                    expected,
1086                    got,
1087                } => {
1088                    format!(
1089                        "{}() expects {} argument(s), got {}",
1090                        function, expected, got
1091                    )
1092                }
1093                VMError::InvalidArgument { function, message } => {
1094                    format!("{}(): {}", function, message)
1095                }
1096                VMError::NotImplemented(feature) => format!("Not implemented: {}", feature),
1097                VMError::Suspended { .. } | VMError::Interrupted | VMError::ResumeRequested => {
1098                    return error;
1099                } // Don't enrich suspension/interrupt/resume signals
1100            };
1101
1102            // Build enhanced error message with source context
1103            let enhanced = if let Some(source) = source_context {
1104                format!(
1105                    "{}\n  --> line {}\n   |\n{:>3} | {}\n   |",
1106                    base_msg, line_num, line_num, source
1107                )
1108            } else {
1109                format!("{} (line {})", base_msg, line_num)
1110            };
1111
1112            VMError::RuntimeError(enhanced)
1113        } else {
1114            self.last_error_line = None;
1115            self.last_error_file = None;
1116            error
1117        }
1118    }
1119
1120    // apply_pending_resume() and apply_pending_frame_resume() moved to resume.rs
1121}