Skip to main content

shape_vm/bytecode/
opcode_defs.rs

1//! Bytecode instruction set for Shape VM
2
3use serde::{Deserialize, Serialize};
4
5/// Re-export `StringId` from `shape-value` — the canonical definition.
6pub use shape_value::StringId;
7
8/// Opcode category for classification and tooling.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum OpcodeCategory {
11    Stack,
12    Arithmetic,
13    Comparison,
14    Logical,
15    Control,
16    Variable,
17    Object,
18    Loop,
19    Builtin,
20    Exception,
21    DataFrame,
22    Async,
23    Trait,
24    Special,
25}
26
27/// Macro to define the OpCode enum with metadata (category, stack effects).
28///
29/// Generates:
30/// - `OpCode` enum with `#[repr(u8)]` and explicit byte values
31/// - `OpCode::category()` returning `OpcodeCategory`
32/// - `OpCode::stack_pops()` and `OpCode::stack_pushes()` returning `u8`
33///
34/// For opcodes with variable stack effects (Call, CallMethod, NewArray, etc.),
35/// use 0/0 since the actual effect depends on runtime arity.
36macro_rules! define_opcodes {
37    ($($(#[doc = $doc:expr])* $name:ident = $byte:literal, $cat:ident, pops: $pops:expr, pushes: $pushes:expr);* $(;)?) => {
38        #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
39        #[repr(u16)]
40        pub enum OpCode {
41            $(
42                $(#[doc = $doc])*
43                $name = $byte,
44            )*
45        }
46
47        impl OpCode {
48            /// Returns the category this opcode belongs to.
49            pub const fn category(self) -> OpcodeCategory {
50                match self {
51                    $( OpCode::$name => OpcodeCategory::$cat, )*
52                }
53            }
54
55            /// Returns the number of values this opcode pops from the stack.
56            /// Returns 0 for variable-arity opcodes (Call, CallMethod, NewArray, etc.).
57            pub const fn stack_pops(self) -> u8 {
58                match self {
59                    $( OpCode::$name => $pops, )*
60                }
61            }
62
63            /// Returns the number of values this opcode pushes onto the stack.
64            /// Returns 0 for variable-arity opcodes.
65            pub const fn stack_pushes(self) -> u8 {
66                match self {
67                    $( OpCode::$name => $pushes, )*
68                }
69            }
70        }
71    };
72}
73
74define_opcodes! {
75    // ===== Stack Operations =====
76    /// Push a constant onto the stack
77    PushConst = 0x00, Stack, pops: 0, pushes: 1;
78    /// Push null onto the stack
79    PushNull = 0x01, Stack, pops: 0, pushes: 1;
80    /// Pop value from stack
81    Pop = 0x02, Stack, pops: 1, pushes: 0;
82    /// Duplicate top of stack
83    Dup = 0x03, Stack, pops: 1, pushes: 2;
84    /// Swap top two values
85    Swap = 0x04, Stack, pops: 2, pushes: 2;
86
87    // ===== Dynamic Arithmetic Operations (DELETED - strict-typing sweep Phase 2) =====
88    // 0x10 (AddDynamic), 0x11 (SubDynamic), 0x12 (MulDynamic),
89    // 0x13 (DivDynamic), 0x14 (ModDynamic), 0x16 (PowDynamic)
90    // were deleted; the compiler now emits typed opcodes (AddInt/AddNumber/...)
91    // exclusively, or fails with a strict-typing error.
92    /// Bitwise AND
93    BitAnd = 0x17, Arithmetic, pops: 2, pushes: 1;
94    /// Bitwise OR
95    BitOr = 0x18, Arithmetic, pops: 2, pushes: 1;
96    /// Bitwise shift left
97    BitShl = 0x19, Arithmetic, pops: 2, pushes: 1;
98    /// Bitwise shift right
99    BitShr = 0x1A, Arithmetic, pops: 2, pushes: 1;
100    /// Bitwise NOT
101    BitNot = 0x1B, Arithmetic, pops: 1, pushes: 1;
102    /// Bitwise XOR
103    BitXor = 0x1C, Arithmetic, pops: 2, pushes: 1;
104
105    // ===== Dynamic Comparison Operations (DELETED - strict-typing sweep Phase 2) =====
106    // 0x20 (GtDynamic), 0x21 (LtDynamic), 0x22 (GteDynamic),
107    // 0x23 (LteDynamic), 0x24 (EqDynamic), 0x25 (NeqDynamic)
108    // were deleted; the compiler now emits typed comparison opcodes
109    // (GtInt/EqString/...) exclusively, or fails with a strict-typing error.
110
111    // ===== Typed Comparison Operations (compiler-guaranteed types, zero dispatch) =====
112    /// Greater than (int × int → bool)
113    GtInt = 0x26, Comparison, pops: 2, pushes: 1;
114    /// Greater than (f64 × f64 → bool)
115    GtNumber = 0x27, Comparison, pops: 2, pushes: 1;
116    /// Greater than (decimal × decimal → bool)
117    GtDecimal = 0x28, Comparison, pops: 2, pushes: 1;
118    /// Less than (int × int → bool)
119    LtInt = 0x29, Comparison, pops: 2, pushes: 1;
120    /// Less than (f64 × f64 → bool)
121    LtNumber = 0x2A, Comparison, pops: 2, pushes: 1;
122    /// Less than (decimal × decimal → bool)
123    LtDecimal = 0x2B, Comparison, pops: 2, pushes: 1;
124    /// Greater than or equal (int × int → bool)
125    GteInt = 0x2C, Comparison, pops: 2, pushes: 1;
126    /// Greater than or equal (f64 × f64 → bool)
127    GteNumber = 0x2D, Comparison, pops: 2, pushes: 1;
128    /// Greater than or equal (decimal × decimal → bool)
129    GteDecimal = 0x2E, Comparison, pops: 2, pushes: 1;
130    /// Less than or equal (int × int → bool)
131    LteInt = 0x2F, Comparison, pops: 2, pushes: 1;
132
133    // ===== Logical Operations =====
134    /// Logical AND
135    And = 0x30, Logical, pops: 2, pushes: 1;
136    /// Logical OR
137    Or = 0x31, Logical, pops: 2, pushes: 1;
138    /// Logical NOT
139    Not = 0x32, Logical, pops: 1, pushes: 1;
140
141    // ===== Typed Arithmetic Operations (compiler-guaranteed types, zero dispatch) =====
142    /// Add (int × int → int)
143    AddInt = 0x33, Arithmetic, pops: 2, pushes: 1;
144    /// Add (f64 × f64 → f64)
145    AddNumber = 0x34, Arithmetic, pops: 2, pushes: 1;
146    /// Add (decimal × decimal → decimal)
147    AddDecimal = 0x35, Arithmetic, pops: 2, pushes: 1;
148    /// Subtract (int × int → int)
149    SubInt = 0x36, Arithmetic, pops: 2, pushes: 1;
150    /// Subtract (f64 × f64 → f64)
151    SubNumber = 0x37, Arithmetic, pops: 2, pushes: 1;
152    /// Subtract (decimal × decimal → decimal)
153    SubDecimal = 0x38, Arithmetic, pops: 2, pushes: 1;
154    /// Multiply (int × int → int)
155    MulInt = 0x39, Arithmetic, pops: 2, pushes: 1;
156    /// Multiply (f64 × f64 → f64)
157    MulNumber = 0x3A, Arithmetic, pops: 2, pushes: 1;
158    /// Multiply (decimal × decimal → decimal)
159    MulDecimal = 0x3B, Arithmetic, pops: 2, pushes: 1;
160    /// Divide (int × int → int)
161    DivInt = 0x3C, Arithmetic, pops: 2, pushes: 1;
162    /// Divide (f64 × f64 → f64)
163    DivNumber = 0x3D, Arithmetic, pops: 2, pushes: 1;
164    /// Divide (decimal × decimal → decimal)
165    DivDecimal = 0x3E, Arithmetic, pops: 2, pushes: 1;
166    /// Modulo (int × int → int)
167    ModInt = 0x3F, Arithmetic, pops: 2, pushes: 1;
168
169    // ===== Control Flow =====
170    /// Unconditional jump
171    Jump = 0x40, Control, pops: 0, pushes: 0;
172    /// Jump if false (pop condition)
173    JumpIfFalse = 0x41, Control, pops: 1, pushes: 0;
174    /// Jump if true (pop condition)
175    JumpIfTrue = 0x42, Control, pops: 1, pushes: 0;
176    /// Function call
177    Call = 0x43, Control, pops: 0, pushes: 0;
178    /// Return from function
179    Return = 0x44, Control, pops: 0, pushes: 0;
180    /// Return with value
181    ReturnValue = 0x45, Control, pops: 1, pushes: 0;
182    /// Call a value (function/closure) from the stack
183    CallValue = 0x46, Control, pops: 0, pushes: 0;
184
185    // ===== Variable Operations =====
186    /// Load local variable
187    LoadLocal = 0x50, Variable, pops: 0, pushes: 1;
188    /// Store local variable
189    StoreLocal = 0x51, Variable, pops: 1, pushes: 0;
190    /// Load module_binding variable
191    LoadModuleBinding = 0x52, Variable, pops: 0, pushes: 1;
192    /// Store module_binding variable
193    StoreModuleBinding = 0x53, Variable, pops: 1, pushes: 0;
194    /// Load from closure upvalue
195    LoadClosure = 0x54, Variable, pops: 0, pushes: 1;
196    /// Store to closure upvalue
197    StoreClosure = 0x55, Variable, pops: 1, pushes: 0;
198    /// Create a closure with captured upvalues.
199    ///
200    /// Operand encoding:
201    /// - `Operand::Function(fid)`: non-escaping closure (stack-safe in JIT Phase E).
202    /// - `Operand::ClosureAlloc { fid, escapes: true }`: escaping closure — always
203    ///   heap-allocated via `TypedClosureHeader` (JIT Phase H2 path).
204    /// - `Operand::ClosureAlloc { fid, escapes: false }`: non-escaping closure
205    ///   (equivalent to `Function(fid)`; supported for uniform operand readers).
206    ///
207    /// Closure spec H5 merged the former `MakeClosureHeap` into this opcode.
208    /// See `docs/v2-closure-specialization.md` §13 H5.
209    MakeClosure = 0x56, Variable, pops: 0, pushes: 1;
210    /// Close upvalue - moves stack local to heap when leaving scope
211    CloseUpvalue = 0x57, Variable, pops: 0, pushes: 0;
212    /// Create a reference to a local variable's stack slot
213    MakeRef = 0x58, Variable, pops: 0, pushes: 1;
214    /// Load the value that a reference points to
215    DerefLoad = 0x59, Variable, pops: 0, pushes: 1;
216    /// Store a value through a reference
217    DerefStore = 0x5A, Variable, pops: 1, pushes: 0;
218    /// Set an index on the array that a reference points to (in-place mutation)
219    SetIndexRef = 0x5B, Variable, pops: 2, pushes: 0;
220    /// Create a projected typed-field reference from a base reference on the stack.
221    MakeFieldRef = 0x5E, Variable, pops: 1, pushes: 1;
222    /// Create a projected index reference: pops [base_ref, index] and pushes a
223    /// projected reference whose `RefProjection::Index` stores the index value.
224    MakeIndexRef = 0x5F, Variable, pops: 2, pushes: 1;
225
226    // ===== Object/Array Operations =====
227    /// Create new array
228    NewArray = 0x60, Object, pops: 0, pushes: 1;
229    /// Create new object
230    NewObject = 0x61, Object, pops: 0, pushes: 1;
231    /// Get property/index
232    GetProp = 0x62, Object, pops: 2, pushes: 1;
233    /// Set property/index
234    SetProp = 0x63, Object, pops: 3, pushes: 0;
235    /// Get array/object length
236    Length = 0x64, Object, pops: 1, pushes: 1;
237    /// Push value to array
238    ArrayPush = 0x65, Object, pops: 2, pushes: 0;
239    /// Pop value from array
240    ArrayPop = 0x66, Object, pops: 1, pushes: 1;
241    /// Merge object fields from stack into another object
242    MergeObject = 0x67, Object, pops: 2, pushes: 1;
243    /// Set index on a local array without loading/cloning through the stack
244    SetLocalIndex = 0x68, Object, pops: 2, pushes: 0;
245    /// Set index on a module_binding array without loading/cloning through the stack
246    SetModuleBindingIndex = 0x69, Object, pops: 2, pushes: 0;
247    /// Push value to array stored in a local variable, mutating in-place
248    ArrayPushLocal = 0x6A, Object, pops: 1, pushes: 0;
249    /// Create a new Matrix from rows*cols f64 values on the stack
250    NewMatrix = 0x6B, Object, pops: 0, pushes: 1;
251    /// Create a typed array (IntArray/FloatArray/BoolArray) from N homogeneous elements
252    /// Operand: Count(n) — number of elements to pop
253    /// At runtime, inspects element types and packs into the appropriate typed array
254    NewTypedArray = 0x6C, Object, pops: 0, pushes: 1;
255
256    // ===== Loop Operations =====
257    /// Start of loop (for break/continue)
258    LoopStart = 0x70, Loop, pops: 0, pushes: 0;
259    /// End of loop
260    LoopEnd = 0x71, Loop, pops: 0, pushes: 0;
261    /// Break from loop
262    Break = 0x72, Loop, pops: 0, pushes: 0;
263    /// Continue to next iteration
264    Continue = 0x73, Loop, pops: 0, pushes: 0;
265    /// Iterator next: pops iterator + index, pushes next value
266    IterNext = 0x74, Loop, pops: 2, pushes: 1;
267    /// Check if iterator done: pops iterator + index, pushes bool
268    IterDone = 0x75, Loop, pops: 2, pushes: 1;
269
270    // ===== Typed Conversion Operations (direct, zero-dispatch) =====
271    /// Convert value to int (infallible, panics on failure)
272    ConvertToInt = 0x76, Arithmetic, pops: 1, pushes: 1;
273    /// Convert value to number (infallible, panics on failure)
274    ConvertToNumber = 0x77, Arithmetic, pops: 1, pushes: 1;
275    /// Convert value to string (infallible, always succeeds)
276    ConvertToString = 0x78, Arithmetic, pops: 1, pushes: 1;
277    /// Convert value to bool (infallible, panics on failure)
278    ConvertToBool = 0x79, Arithmetic, pops: 1, pushes: 1;
279    /// Convert value to decimal (infallible, panics on failure)
280    ConvertToDecimal = 0x7A, Arithmetic, pops: 1, pushes: 1;
281    /// Convert value to char (infallible, panics on failure)
282    ConvertToChar = 0x7B, Arithmetic, pops: 1, pushes: 1;
283    /// Try convert value to int (fallible, pushes Result<int, AnyError>)
284    TryConvertToInt = 0x7C, Arithmetic, pops: 1, pushes: 1;
285    /// Try convert value to number (fallible, pushes Result<number, AnyError>)
286    TryConvertToNumber = 0x7D, Arithmetic, pops: 1, pushes: 1;
287    /// Try convert value to string (fallible, pushes Result<string, AnyError>)
288    TryConvertToString = 0x7E, Arithmetic, pops: 1, pushes: 1;
289    /// Try convert value to bool (fallible, pushes Result<bool, AnyError>)
290    TryConvertToBool = 0x7F, Arithmetic, pops: 1, pushes: 1;
291    /// Try convert value to decimal (fallible, pushes Result<decimal, AnyError>)
292    TryConvertToDecimal = 0x80, Arithmetic, pops: 1, pushes: 1;
293    /// Try convert value to char (fallible, pushes Result<char, AnyError>)
294    TryConvertToChar = 0x81, Arithmetic, pops: 1, pushes: 1;
295
296    // ===== Method Call =====
297    /// Call method on value (array.map(), string.len(), etc.)
298    CallMethod = 0x88, Builtin, pops: 0, pushes: 0;
299    /// Push timeframe context
300    PushTimeframe = 0x89, Builtin, pops: 1, pushes: 0;
301    /// Pop timeframe context
302    PopTimeframe = 0x8A, Builtin, pops: 0, pushes: 0;
303
304    // ===== Built-in Functions =====
305    /// Call built-in function
306    BuiltinCall = 0x90, Builtin, pops: 0, pushes: 0;
307    /// Type check
308    TypeCheck = 0x91, Builtin, pops: 1, pushes: 1;
309    /// Convert type
310    Convert = 0x92, Builtin, pops: 1, pushes: 1;
311
312    // ===== Typed Arithmetic (continued from 0x3F) =====
313    /// Modulo (f64 × f64 → f64)
314    ModNumber = 0x93, Arithmetic, pops: 2, pushes: 1;
315    /// Modulo (decimal × decimal → decimal)
316    ModDecimal = 0x94, Arithmetic, pops: 2, pushes: 1;
317    /// Power (int × int → int)
318    PowInt = 0x95, Arithmetic, pops: 2, pushes: 1;
319    /// Power (f64 × f64 → f64)
320    PowNumber = 0x96, Arithmetic, pops: 2, pushes: 1;
321    /// Power (decimal × decimal → decimal)
322    PowDecimal = 0x97, Arithmetic, pops: 2, pushes: 1;
323
324    /// Negate int (i64 → i64)
325    NegInt = 0xCA, Arithmetic, pops: 1, pushes: 1;
326    /// Negate number (f64 → f64)
327    NegNumber = 0xCB, Arithmetic, pops: 1, pushes: 1;
328    /// Negate decimal
329    NegDecimal = 0xCC, Arithmetic, pops: 1, pushes: 1;
330
331    // ===== Typed Comparison (continued from 0x2F) =====
332    /// Less than or equal (f64 × f64 → bool)
333    LteNumber = 0x98, Comparison, pops: 2, pushes: 1;
334    /// Less than or equal (decimal × decimal → bool)
335    LteDecimal = 0x99, Comparison, pops: 2, pushes: 1;
336    /// Equal (int × int → bool)
337    EqInt = 0x9A, Comparison, pops: 2, pushes: 1;
338    /// Equal (f64 × f64 → bool)
339    EqNumber = 0x9B, Comparison, pops: 2, pushes: 1;
340    /// Not equal (int × int → bool)
341    NeqInt = 0x9C, Comparison, pops: 2, pushes: 1;
342    /// Not equal (f64 × f64 → bool)
343    NeqNumber = 0x9D, Comparison, pops: 2, pushes: 1;
344
345    // ===== Exception Handling =====
346    /// Set up try/catch block (operand: offset to catch handler)
347    SetupTry = 0xA0, Exception, pops: 0, pushes: 0;
348    /// Pop exception handler (successful try block completion)
349    PopHandler = 0xA1, Exception, pops: 0, pushes: 0;
350    /// Throw an exception (push error value first)
351    Throw = 0xA2, Exception, pops: 1, pushes: 0;
352    /// Try operator: unified Result/Option propagation with early return on Err/None
353    TryUnwrap = 0xA3, Exception, pops: 1, pushes: 1;
354    /// Unwrap Option: extract inner value from Some, panic on None
355    UnwrapOption = 0xA4, Exception, pops: 1, pushes: 1;
356    /// Add context to Result/Option failures and lift success into Result
357    ErrorContext = 0xA5, Exception, pops: 2, pushes: 1;
358    /// Check whether Result is Ok(value)
359    IsOk = 0xA6, Exception, pops: 1, pushes: 1;
360    /// Check whether Result is Err(error)
361    IsErr = 0xA7, Exception, pops: 1, pushes: 1;
362    /// Extract inner payload from Ok(value)
363    UnwrapOk = 0xA8, Exception, pops: 1, pushes: 1;
364    /// Extract inner payload from Err(error)
365    UnwrapErr = 0xA9, Exception, pops: 1, pushes: 1;
366
367    // ===== Additional Operations =====
368    /// Slice access (array[start:end])
369    SliceAccess = 0xB0, Object, pops: 3, pushes: 1;
370    /// Null coalescing (a ?? b)
371    NullCoalesce = 0xB1, Logical, pops: 2, pushes: 1;
372    /// Range constructor (start..end / start..=end). Pops 3 operands:
373    /// (start, end, inclusive_flag); pushes one Range value.
374    /// W15-range (ADR-006 §2.7.23 / Q24, 2026-05-10) — corrected pops
375    /// from 2 to 3 (the compiler in `expressions/misc.rs:362-369`
376    /// emits 3 pushes — start, end, PushConst<Bool>(inclusive)).
377    MakeRange = 0xB2, Object, pops: 3, pushes: 1;
378
379    // ===== Compact Typed Arithmetic (width-parameterised, ABI-stable) =====
380    /// Width-typed add: Operand::Width selects I8..F64
381    AddTyped = 0xB3, Arithmetic, pops: 2, pushes: 1;
382    /// Width-typed subtract: Operand::Width selects I8..F64
383    SubTyped = 0xB4, Arithmetic, pops: 2, pushes: 1;
384    /// Width-typed multiply: Operand::Width selects I8..F64
385    MulTyped = 0xB5, Arithmetic, pops: 2, pushes: 1;
386    /// Width-typed divide: Operand::Width selects I8..F64
387    DivTyped = 0xB6, Arithmetic, pops: 2, pushes: 1;
388    /// Width-typed modulo: Operand::Width selects I8..F64
389    ModTyped = 0xB7, Arithmetic, pops: 2, pushes: 1;
390    /// Width-typed comparison (ordered): Operand::Width selects I8..F64
391    /// Result semantics: pushes -1 (a<b), 0 (a==b), or 1 (a>b)
392    CmpTyped = 0xB8, Comparison, pops: 2, pushes: 1;
393
394    // ===== DataFrame Operations =====
395    /// Get field from data row by column index (generic, industry-agnostic)
396    GetDataField = 0xC0, DataFrame, pops: 1, pushes: 1;
397    /// Get row reference (lightweight, no data copy)
398    GetDataRow = 0xC1, DataFrame, pops: 1, pushes: 1;
399
400    // ===== Type-Specialized Operations (JIT Optimization) =====
401    /// Get field from typed object using precomputed offset
402    GetFieldTyped = 0xD0, Object, pops: 1, pushes: 1;
403    /// Set field on typed object using precomputed offset
404    SetFieldTyped = 0xD1, Object, pops: 2, pushes: 1;
405    /// Create a new typed object with fields from stack
406    NewTypedObject = 0xD2, Object, pops: 0, pushes: 1;
407    /// Merge two typed objects into a new typed object
408    TypedMergeObject = 0xD3, Object, pops: 2, pushes: 1;
409    /// Wrap a value with a type annotation for meta formatting
410    WrapTypeAnnotation = 0xD4, Object, pops: 1, pushes: 1;
411
412    // ===== Async Operations (0xE0-0xEF) =====
413    /// Yield to event loop for cooperative scheduling
414    Yield = 0xE0, Async, pops: 0, pushes: 0;
415    /// Suspend until a condition is met
416    Suspend = 0xE1, Async, pops: 0, pushes: 0;
417    /// Resume from suspension (internal use)
418    Resume = 0xE2, Async, pops: 1, pushes: 0;
419    /// Poll event queue
420    Poll = 0xE3, Async, pops: 0, pushes: 1;
421    /// Await next data bar from a source
422    AwaitBar = 0xE4, Async, pops: 0, pushes: 1;
423    /// Await next timer tick
424    AwaitTick = 0xE5, Async, pops: 0, pushes: 0;
425    /// General-purpose await: suspends on Future values
426    Await = 0xE6, Async, pops: 1, pushes: 1;
427    /// Spawn an async task from the expression on top of stack
428    SpawnTask = 0xE7, Async, pops: 1, pushes: 1;
429
430    // ===== Event Emission Operations =====
431    /// Emit an alert to the alert pipeline
432    EmitAlert = 0xE8, Async, pops: 1, pushes: 0;
433    /// Emit a generic event to the event queue
434    EmitEvent = 0xE9, Async, pops: 1, pushes: 0;
435    /// Initialize a join group from spawned tasks on the stack
436    JoinInit = 0xEA, Async, pops: 0, pushes: 1;
437    /// Await a TaskGroup to completion according to its join strategy
438    JoinAwait = 0xEB, Async, pops: 1, pushes: 1;
439    /// Cancel a running task
440    CancelTask = 0xEC, Async, pops: 1, pushes: 0;
441    /// Enter an async scope (structured concurrency boundary)
442    AsyncScopeEnter = 0xED, Async, pops: 0, pushes: 0;
443    /// Exit an async scope (structured concurrency boundary)
444    AsyncScopeExit = 0xEE, Async, pops: 0, pushes: 0;
445
446    // ===== Typed Column Access (Arrow DataTable) =====
447    /// Load f64 from typed column on a RowView
448    LoadColF64 = 0xC2, DataFrame, pops: 1, pushes: 1;
449    /// Load i64 from typed column on a RowView
450    LoadColI64 = 0xC3, DataFrame, pops: 1, pushes: 1;
451    /// Load bool from typed column on a RowView
452    LoadColBool = 0xC4, DataFrame, pops: 1, pushes: 1;
453    /// Load string from typed column on a RowView
454    LoadColStr = 0xC5, DataFrame, pops: 1, pushes: 1;
455    /// Bind a DataTable to a TypeSchema at runtime (safety net for dynamic paths)
456    BindSchema = 0xC6, DataFrame, pops: 1, pushes: 1;
457
458    // ===== Trait Object Operations =====
459    /// Box a concrete value into a trait object with a vtable
460    BoxTraitObject = 0xEF, Trait, pops: 1, pushes: 1;
461    /// Call a method on a trait object via vtable dispatch
462    DynMethodCall = 0xC7, Trait, pops: 0, pushes: 0;
463    /// Call Drop::drop on the value at the top of stack (sync)
464    DropCall = 0xC8, Trait, pops: 1, pushes: 0;
465    /// Call Drop::drop on the value at the top of stack (async)
466    DropCallAsync = 0xC9, Trait, pops: 1, pushes: 0;
467
468    // NOTE: Trusted arithmetic opcodes (0xCA-0xCF, 0xD5-0xD6) were removed.
469    // They were functionally identical to the typed variants (AddInt, etc.)
470    // in release builds. The typed opcodes already skip runtime dispatch.
471
472    // ===== Trusted Variable Operations (compiler-proved types, zero guard) =====
473    /// LoadLocal (trusted) -- skips tag validation, reads slot directly
474    LoadLocalTrusted = 0xD7, Variable, pops: 0, pushes: 1;
475
476    // ===== Trusted Control Flow (compiler-proved types, zero guard) =====
477    /// JumpIfFalse (trusted) -- condition is known bool, direct bool check
478    JumpIfFalseTrusted = 0xD8, Control, pops: 1, pushes: 0;
479
480    // NOTE: Trusted comparison opcodes (0xD9-0xDF, 0xF9) were removed.
481    // They were functionally identical to the typed variants (GtInt, etc.)
482    // in release builds. The typed opcodes already skip runtime dispatch.
483
484    // ===== Special Operations =====
485    /// No operation
486    Nop = 0xF0, Special, pops: 0, pushes: 0;
487    /// Halt execution
488    Halt = 0xF1, Special, pops: 0, pushes: 0;
489    // Slot 0xF2 reclaimed by Stage 2.6.5.0 (was: Debug breakpoint with no
490    // compiler emission and only stale JIT classifier references). Reused
491    // by IsNull in Stage 2.6.5.1.
492    /// Stage 2.6.5: typed absence check. Pops one value, pushes a bool
493    /// indicating whether the value is the None or Unit sentinel. Replaces
494    /// the legacy `PushNull; Eq` and `emit_unit; Eq` patterns at the 16
495    /// null/unit-check sites in the compiler.
496    IsNull = 0xF2, Comparison, pops: 1, pushes: 1;
497
498    // ===== Numeric Coercion Operations =====
499    /// Coerce int to number (i64 -> f64)
500    IntToNumber = 0xF3, Arithmetic, pops: 1, pushes: 1;
501    /// Coerce number to int (f64 -> i64, truncating)
502    NumberToInt = 0xF4, Arithmetic, pops: 1, pushes: 1;
503
504    // ===== Foreign Function Operations =====
505    /// Call a linked foreign function.
506    /// Dispatches through language runtime extensions or the VM native C ABI path.
507    /// Operand: ForeignFunction(u16) — index into program.foreign_functions
508    /// Stack: pops N args (count pushed as a constant by the stub), pushes 1 result
509    CallForeign = 0xF5, Control, pops: 0, pushes: 0;
510
511    /// Store a local with width truncation.
512    /// Operand: TypedLocal(u16, NumericWidth) — local index + width
513    /// Pops one value, truncates to declared width, stores to local.
514    StoreLocalTyped = 0xF6, Variable, pops: 1, pushes: 0;
515
516    /// Cast a value to a specific integer width (bit-truncation, Rust-style `as`).
517    /// Operand: Width(NumericWidth) — target width
518    /// Pops one value, truncates, pushes result.
519    CastWidth = 0xF7, Arithmetic, pops: 1, pushes: 1;
520
521    /// Store a module binding with width truncation.
522    /// Operand: TypedModuleBinding(u16, NumericWidth) — binding index + width
523    /// Pops one value, truncates to declared width, stores to module binding.
524    StoreModuleBindingTyped = 0xF8, Variable, pops: 1, pushes: 0;
525
526    // ===== v2 Typed Array Operations =====
527    /// Create a new TypedArray<f64> with given capacity. Operand: Count(capacity). Pushes ptr.
528    NewTypedArrayF64 = 0x05, Object, pops: 0, pushes: 1;
529    /// Create a new TypedArray<i64> with given capacity. Operand: Count(capacity). Pushes ptr.
530    NewTypedArrayI64 = 0x06, Object, pops: 0, pushes: 1;
531    /// Create a new TypedArray<i32> with given capacity. Operand: Count(capacity). Pushes ptr.
532    NewTypedArrayI32 = 0x07, Object, pops: 0, pushes: 1;
533    /// Get element from TypedArray<f64>: pops (arr_ptr, index), pushes f64 value
534    TypedArrayGetF64 = 0x08, Object, pops: 2, pushes: 1;
535    /// Get element from TypedArray<i64>: pops (arr_ptr, index), pushes i64 value
536    TypedArrayGetI64 = 0x09, Object, pops: 2, pushes: 1;
537    /// Get element from TypedArray<i32>: pops (arr_ptr, index), pushes i32 value
538    TypedArrayGetI32 = 0x0A, Object, pops: 2, pushes: 1;
539    /// Set element in TypedArray<f64>: pops (arr_ptr, index, value), pushes nothing
540    TypedArraySetF64 = 0x0B, Object, pops: 3, pushes: 0;
541    /// Push element to TypedArray<f64>: pops (arr_ptr, value), pushes nothing
542    TypedArrayPushF64 = 0x0C, Object, pops: 2, pushes: 0;
543    /// Push element to TypedArray<i64>: pops (arr_ptr, value), pushes nothing
544    TypedArrayPushI64 = 0x0D, Object, pops: 2, pushes: 0;
545    /// Get length of TypedArray: pops (arr_ptr), pushes len as int
546    TypedArrayLen = 0x0E, Object, pops: 1, pushes: 1;
547    /// Create a new TypedArray<bool> with given capacity. Operand: Count(capacity). Pushes ptr.
548    NewTypedArrayBool = 0x0F, Object, pops: 0, pushes: 1;
549    /// Get element from TypedArray<bool>: pops (arr_ptr, index), pushes bool value
550    TypedArrayGetBool = 0x47, Object, pops: 2, pushes: 1;
551    /// Push element to TypedArray<i32>: pops (arr_ptr, value), pushes nothing
552    TypedArrayPushI32 = 0x48, Object, pops: 2, pushes: 0;
553    /// Push element to TypedArray<bool>: pops (arr_ptr, value), pushes nothing
554    TypedArrayPushBool = 0x49, Object, pops: 2, pushes: 0;
555    /// Set element in TypedArray<i64>: pops (arr_ptr, index, value), pushes nothing
556    TypedArraySetI64 = 0x4A, Object, pops: 3, pushes: 0;
557    /// Set element in TypedArray<i32>: pops (arr_ptr, index, value), pushes nothing
558    TypedArraySetI32 = 0x4B, Object, pops: 3, pushes: 0;
559    /// Set element in TypedArray<bool>: pops (arr_ptr, index, value), pushes nothing
560    TypedArraySetBool = 0x4C, Object, pops: 3, pushes: 0;
561
562    // ===== W12 S1 — sized-integer TypedArray<T> producer migration (2026-05-13) =====
563    // 6 new scalar element kinds extending the F64/I64/I32/Bool fast-path
564    // to I8/U8/I16/U16/U32/U64 per ADR-006 §2.7.24 Q25.A scalar-variant
565    // migration. Each kind gets New/Get/Push/Set; Len is shared via the
566    // existing OpCode::TypedArrayLen (layout is T-invariant). Byte values
567    // reuse deleted Dynamic-arithmetic/comparison slots (0x10-0x16,
568    // 0x20-0x25) and the previously-unallocated 0x118..0x122 range.
569    //
570    // U8 has its own ELEM_TYPE_U8 byte distinct from ELEM_TYPE_BOOL so the
571    // v2_array_detect dispatch can route U8 reads back as Int8/UInt8 vs
572    // Bool — runtime semantics differ even when the underlying buffer is
573    // byte-equivalent.
574
575    /// Create a new TypedArray<i8> with given capacity. Operand: Count(capacity). Pushes ptr.
576    NewTypedArrayI8 = 0x10, Object, pops: 0, pushes: 1;
577    /// Get element from TypedArray<i8>: pops (arr_ptr, index), pushes i8 value (sign-extended to i64).
578    TypedArrayGetI8 = 0x11, Object, pops: 2, pushes: 1;
579    /// Push element to TypedArray<i8>: pops (arr_ptr, value), pushes nothing.
580    TypedArrayPushI8 = 0x12, Object, pops: 2, pushes: 0;
581    /// Set element in TypedArray<i8>: pops (arr_ptr, index, value), pushes nothing.
582    TypedArraySetI8 = 0x13, Object, pops: 3, pushes: 0;
583
584    /// Create a new TypedArray<u8> with given capacity. Operand: Count(capacity). Pushes ptr.
585    NewTypedArrayU8 = 0x14, Object, pops: 0, pushes: 1;
586    /// Get element from TypedArray<u8>: pops (arr_ptr, index), pushes u8 value (zero-extended to i64).
587    TypedArrayGetU8 = 0x15, Object, pops: 2, pushes: 1;
588    /// Push element to TypedArray<u8>: pops (arr_ptr, value), pushes nothing.
589    TypedArrayPushU8 = 0x16, Object, pops: 2, pushes: 0;
590    /// Set element in TypedArray<u8>: pops (arr_ptr, index, value), pushes nothing.
591    TypedArraySetU8 = 0x20, Object, pops: 3, pushes: 0;
592
593    /// Create a new TypedArray<i16> with given capacity. Operand: Count(capacity). Pushes ptr.
594    NewTypedArrayI16 = 0x21, Object, pops: 0, pushes: 1;
595    /// Get element from TypedArray<i16>: pops (arr_ptr, index), pushes i16 value (sign-extended to i64).
596    TypedArrayGetI16 = 0x22, Object, pops: 2, pushes: 1;
597    /// Push element to TypedArray<i16>: pops (arr_ptr, value), pushes nothing.
598    TypedArrayPushI16 = 0x23, Object, pops: 2, pushes: 0;
599    /// Set element in TypedArray<i16>: pops (arr_ptr, index, value), pushes nothing.
600    TypedArraySetI16 = 0x24, Object, pops: 3, pushes: 0;
601
602    /// Create a new TypedArray<u16> with given capacity. Operand: Count(capacity). Pushes ptr.
603    NewTypedArrayU16 = 0x25, Object, pops: 0, pushes: 1;
604    /// Get element from TypedArray<u16>: pops (arr_ptr, index), pushes u16 value (zero-extended to i64).
605    TypedArrayGetU16 = 0x118, Object, pops: 2, pushes: 1;
606    /// Push element to TypedArray<u16>: pops (arr_ptr, value), pushes nothing.
607    TypedArrayPushU16 = 0x119, Object, pops: 2, pushes: 0;
608    /// Set element in TypedArray<u16>: pops (arr_ptr, index, value), pushes nothing.
609    TypedArraySetU16 = 0x11A, Object, pops: 3, pushes: 0;
610
611    /// Create a new TypedArray<u32> with given capacity. Operand: Count(capacity). Pushes ptr.
612    NewTypedArrayU32 = 0x11B, Object, pops: 0, pushes: 1;
613    /// Get element from TypedArray<u32>: pops (arr_ptr, index), pushes u32 value (zero-extended to i64).
614    TypedArrayGetU32 = 0x11C, Object, pops: 2, pushes: 1;
615    /// Push element to TypedArray<u32>: pops (arr_ptr, value), pushes nothing.
616    TypedArrayPushU32 = 0x11D, Object, pops: 2, pushes: 0;
617    /// Set element in TypedArray<u32>: pops (arr_ptr, index, value), pushes nothing.
618    TypedArraySetU32 = 0x11E, Object, pops: 3, pushes: 0;
619
620    // U64 typed-array opcodes intentionally NOT minted. Per the
621    // supervisor's S1 reopen (2026-05-13), `TypedArray<u64>` migration
622    // is gated on the §2.7.7 / Q9 NativeKind-track extension that
623    // discriminates "pointer to TypedArray<T>" from "scalar u64" — both
624    // currently share `NativeKind::UInt64` at HEAD. The defensive
625    // low-address-pointer guard at `as_v2_typed_array` (introduced in
626    // the pre-reopen S1 commit `4bcae991`) was a memory-region heuristic
627    // substituting for the missing compile-time discriminator — an
628    // `is_heap()` probe in different framing — which the CLAUDE.md
629    // §"Parallel-implementation across producer/consumer carrier-shape
630    // boundaries" entry (e55b8e71) names as the 6th instance of that
631    // defection-attractor class. Removed and deferred to sub-cluster
632    // S1.5 (W12-nativekind-typed-array-ptr-extension or equivalent per
633    // team-lead's pre-dispatch audit). See AGENTS.md S1 row's surface-
634    // and-stop list. Byte values 0x11F..0x122 left unallocated for
635    // S1.5's re-mint.
636    //
637    // ===== Wave 2 Agent A1 (2026-05-14) — F32 + Char monomorphizations =====
638    //
639    // R19 S1.5 amendment (W12-nativekind-scalar-additions, 2026-05-14)
640    // introduced `NativeKind::Float32` and `NativeKind::Char` as scalar
641    // bucket carriers per ADR-006 §2.7.5. F32 and Char are `Copy + 4-byte`
642    // scalars with no heap indirection — same recipe as I8/I16/U16/U32 in
643    // S1. No new HeapKind variants, no parametric NativeKind shapes.
644    // Audit §2.1 + §3.1 row.
645
646    /// Create a new TypedArray<f32> with given capacity. Operand: Count(capacity). Pushes ptr.
647    NewTypedArrayF32 = 0x1A3, Object, pops: 0, pushes: 1;
648    /// Get element from TypedArray<f32>: pops (arr_ptr, index), pushes f32 value (zero-extended into f64 bit pattern).
649    TypedArrayGetF32 = 0x1A4, Object, pops: 2, pushes: 1;
650    /// Push element to TypedArray<f32>: pops (arr_ptr, value), pushes nothing.
651    TypedArrayPushF32 = 0x1A5, Object, pops: 2, pushes: 0;
652    /// Set element in TypedArray<f32>: pops (arr_ptr, index, value), pushes nothing.
653    TypedArraySetF32 = 0x1A6, Object, pops: 3, pushes: 0;
654
655    /// Create a new TypedArray<char> with given capacity. Operand: Count(capacity). Pushes ptr.
656    NewTypedArrayChar = 0x1A7, Object, pops: 0, pushes: 1;
657    /// Get element from TypedArray<char>: pops (arr_ptr, index), pushes char codepoint as u32.
658    TypedArrayGetChar = 0x1A8, Object, pops: 2, pushes: 1;
659    /// Push element to TypedArray<char>: pops (arr_ptr, value), pushes nothing.
660    TypedArrayPushChar = 0x1A9, Object, pops: 2, pushes: 0;
661    /// Set element in TypedArray<char>: pops (arr_ptr, index, value), pushes nothing.
662    TypedArraySetChar = 0x1AA, Object, pops: 3, pushes: 0;
663
664    // ===== Wave 2 Agent A2 (2026-05-14) — String + Decimal heap-element monomorphizations =====
665    //
666    // Per ADR-006 §2.7.24 Q25.A SUPERSEDED + audit §3.2 sub-cluster S2-prime, the
667    // String and Decimal element kinds migrate to v2-raw `TypedArray<*const StringObj>` /
668    // `TypedArray<*const DecimalObj>` shape. The 8 new opcodes mirror the §2.1
669    // scalar-monomorphization recipe (New / Get / Push / Set per kind) but the
670    // element-read path pushes `NativeKind::StringV2` / `NativeKind::DecimalV2`
671    // (Agent B's Round 1 carrier-shape variants) — distinct from the legacy
672    // `NativeKind::String` (Phase-2c `Arc<String>` carrier; ADR-005 §2 exception).
673    // Per-element retain via `v2_retain(&(*elem_ptr).header)` at read time before
674    // pushing the StringV2/DecimalV2-kind slot per audit §4.1.B.4 migration recipe.
675    //
676    // Heap element discipline: `T: HeapElement` constraint per audit §4.1.B (the
677    // §4.1.B Option (a) ratification) is satisfied structurally — both StringObj
678    // and DecimalObj have `HeapHeader` at offset 0 with refcount initialized to 1
679    // by their ::new constructors. `TypedArray<*const T>::drop_array_heap` walks
680    // the element buffer and calls `T::release_elem(elem_ptr)` per-T at drop time,
681    // monomorphized at compile time (no runtime kind probe).
682    //
683    // Architectural surface only — landed in A2 close. Producer-site migration
684    // (~29 construction sites) + consumer-arm cascade (~158 references across 35
685    // files) surface-and-stop to A2 follow-up sub-cluster per ~100-site cascade
686    // ceiling + Q25.A SUPERSEDED #3 mixed-migration forbidden pattern.
687
688    /// Create a new TypedArray<*const StringObj> with given capacity. Operand: Count(capacity). Pushes ptr.
689    NewTypedArrayString = 0x1AB, Object, pops: 0, pushes: 1;
690    /// Get element from TypedArray<*const StringObj>: pops (arr_ptr, index), pushes (*const StringObj) bits with NativeKind::StringV2 (retains element).
691    TypedArrayGetString = 0x1AC, Object, pops: 2, pushes: 1;
692    /// Push element to TypedArray<*const StringObj>: pops (arr_ptr, value), pushes nothing. Caller transfers their refcount share to the array.
693    TypedArrayPushString = 0x1AD, Object, pops: 2, pushes: 0;
694    /// Set element in TypedArray<*const StringObj>: pops (arr_ptr, index, value), pushes nothing. Releases prior element, transfers new value's refcount share.
695    TypedArraySetString = 0x1AE, Object, pops: 3, pushes: 0;
696
697    /// Create a new TypedArray<*const DecimalObj> with given capacity. Operand: Count(capacity). Pushes ptr.
698    NewTypedArrayDecimal = 0x1AF, Object, pops: 0, pushes: 1;
699    /// Get element from TypedArray<*const DecimalObj>: pops (arr_ptr, index), pushes (*const DecimalObj) bits with NativeKind::DecimalV2 (retains element).
700    TypedArrayGetDecimal = 0x1B0, Object, pops: 2, pushes: 1;
701    /// Push element to TypedArray<*const DecimalObj>: pops (arr_ptr, value), pushes nothing. Caller transfers their refcount share to the array.
702    TypedArrayPushDecimal = 0x1B1, Object, pops: 2, pushes: 0;
703    /// Set element in TypedArray<*const DecimalObj>: pops (arr_ptr, index, value), pushes nothing. Releases prior element, transfers new value's refcount share.
704    TypedArraySetDecimal = 0x1B2, Object, pops: 3, pushes: 0;
705
706    // ── Wave 3 Stabilize Round 1 V3-A2-followup-producer-cascade (2026-05-15) ──
707    //
708    // v2-raw heap-element literal constructors. The producer side of the Wave 2
709    // Round 3a' gate-flip: `Array<string>` / `Array<decimal>` literals route
710    // through `NewTypedArrayString` / `NewTypedArrayDecimal` followed by per-
711    // element `TypedArrayPushString` / `TypedArrayPushDecimal`, which require
712    // the element value to carry `NativeKind::StringV2` / `NativeKind::DecimalV2`
713    // (per `v2_handlers/array.rs:687/703` strict-kind invariants). The legacy
714    // `LoadConst` path produces `NativeKind::String` (Arc<String> carrier) /
715    // `NativeKind::Decimal` (Arc<Decimal>), so a literal-element upgrade opcode
716    // is required to round-trip through the typed-array push handler.
717    //
718    // `NewStringV2` reads `program.strings[id]` and pushes a fresh
719    // `StringObj::new(&s) as *const StringObj` with `NativeKind::StringV2`,
720    // refcount = 1 (caller transfers share to the array on `TypedArrayPushString`).
721    //
722    // `NewDecimalV2` reads `program.constants[id]` (must be `Constant::Decimal`)
723    // and pushes a fresh `DecimalObj::new(d) as *const DecimalObj` with
724    // `NativeKind::DecimalV2`, refcount = 1 (same transfer-share discipline).
725    //
726    // Per ADR-006 §2.7.5 stamp-at-compile-time: the compiler proves the element
727    // type at literal-emission time; no runtime kind probe at the FFI boundary.
728
729    /// Create a v2-raw StringObj from a constant string. Operand: Property(string_id). Pushes (*const StringObj) bits with NativeKind::StringV2 (refcount = 1, owned by caller).
730    NewStringV2 = 0x1B3, Object, pops: 0, pushes: 1;
731    /// Create a v2-raw DecimalObj from a constant decimal. Operand: Const(constant_id). Pushes (*const DecimalObj) bits with NativeKind::DecimalV2 (refcount = 1, owned by caller).
732    NewDecimalV2 = 0x1B4, Object, pops: 0, pushes: 1;
733
734    // ── Phase 4b Round 4 W16.2-A op_new_array-typed-object-element (2026-05-18) ──
735    //
736    // Per ADR-006 §2.7.5 stamp-at-compile-time + §2.7.24 Q25.A SUPERSEDED +
737    // audit `v0.3-w16-v3s5-ckpt56-strict-close-audit.md` §2.1 + §3.A row 1:
738    // `Array<UserStruct>` literals route through the v2-raw
739    // `TypedArray<*const TypedObjectStorage>` element carrier. Mirror of the
740    // Wave 2 Agent A2 String + Decimal opcodes (0x1AB..0x1B2 above), swapping
741    // `StringObj`/`DecimalObj` → `TypedObjectStorage` and `NativeKind::StringV2`/
742    // `NativeKind::DecimalV2` → `NativeKind::Ptr(HeapKind::TypedObject)`.
743    //
744    // The TypedObjectStorage HeapElement impl is wired at
745    // `crates/shape-value/src/heap_value.rs:4058`; `TypedObjectStorage::_new`
746    // / `_drop` raw-pointer allocators at `:3584`/`:3637`. Generic
747    // `impl<T: HeapElement> TypedArray<*const T>::drop_array_heap` at
748    // `crates/shape-value/src/v2/typed_array.rs:296` provides the per-element
749    // release walk at array drop time (W12 audit §2.2 Obstacle O-3 RESOLVED
750    // at HEAD).
751
752    /// Create a new TypedArray<*const TypedObjectStorage> with given capacity.
753    /// Operand: Count(capacity). Pushes ptr.
754    NewTypedArrayTypedObject = 0x1B5, Object, pops: 0, pushes: 1;
755    /// Get element from TypedArray<*const TypedObjectStorage>: pops (arr_ptr, index),
756    /// pushes (*const TypedObjectStorage) bits with
757    /// NativeKind::Ptr(HeapKind::TypedObject) (retains element).
758    TypedArrayGetTypedObject = 0x1B6, Object, pops: 2, pushes: 1;
759    /// Push element to TypedArray<*const TypedObjectStorage>: pops (arr_ptr, value),
760    /// pushes nothing. Caller transfers their refcount share to the array.
761    TypedArrayPushTypedObject = 0x1B7, Object, pops: 2, pushes: 0;
762    /// Set element in TypedArray<*const TypedObjectStorage>: pops (arr_ptr, index, value),
763    /// pushes nothing. Releases prior element, transfers new value's refcount share.
764    TypedArraySetTypedObject = 0x1B8, Object, pops: 3, pushes: 0;
765
766    // ===== v2 Typed Map Operations =====
767    /// Allocate a new TypedMap<*const StringObj, f64>. Pushes ptr.
768    NewTypedMapStringF64 = 0xCD, Object, pops: 0, pushes: 1;
769    /// Allocate a new TypedMap<*const StringObj, i64>. Pushes ptr.
770    NewTypedMapStringI64 = 0xCE, Object, pops: 0, pushes: 1;
771    /// Allocate a new TypedMap<*const StringObj, *const u8>. Pushes ptr.
772    NewTypedMapStringPtr = 0xCF, Object, pops: 0, pushes: 1;
773    /// Allocate a new TypedMap<i64, f64>. Pushes ptr.
774    NewTypedMapI64F64 = 0xD5, Object, pops: 0, pushes: 1;
775    /// Allocate a new TypedMap<i64, i64>. Pushes ptr.
776    NewTypedMapI64I64 = 0xD6, Object, pops: 0, pushes: 1;
777    /// Allocate a new TypedMap<i64, *const u8>. Pushes ptr.
778    NewTypedMapI64Ptr = 0xD9, Object, pops: 0, pushes: 1;
779    /// String→f64 get: pops (map_ptr, key), pushes f64 (or null).
780    TypedMapStringF64Get = 0xDA, Object, pops: 2, pushes: 1;
781    /// String→i64 get: pops (map_ptr, key), pushes i64 (or null).
782    TypedMapStringI64Get = 0xDB, Object, pops: 2, pushes: 1;
783    /// String→Ptr get: pops (map_ptr, key), pushes ptr (or null).
784    TypedMapStringPtrGet = 0xDC, Object, pops: 2, pushes: 1;
785    /// I64→f64 get: pops (map_ptr, key), pushes f64 (or null).
786    TypedMapI64F64Get = 0xDD, Object, pops: 2, pushes: 1;
787    /// I64→i64 get: pops (map_ptr, key), pushes i64 (or null).
788    TypedMapI64I64Get = 0xDE, Object, pops: 2, pushes: 1;
789    /// I64→Ptr get: pops (map_ptr, key), pushes ptr (or null).
790    TypedMapI64PtrGet = 0xDF, Object, pops: 2, pushes: 1;
791    /// String→f64 set: pops (map_ptr, key, value).
792    TypedMapStringF64Set = 0x4D, Object, pops: 3, pushes: 0;
793    /// String→i64 set: pops (map_ptr, key, value).
794    TypedMapStringI64Set = 0x4E, Object, pops: 3, pushes: 0;
795    /// String→Ptr set: pops (map_ptr, key, value).
796    TypedMapStringPtrSet = 0x4F, Object, pops: 3, pushes: 0;
797    /// I64→f64 set: pops (map_ptr, key, value).
798    TypedMapI64F64Set = 0x6D, Object, pops: 3, pushes: 0;
799    /// I64→i64 set: pops (map_ptr, key, value).
800    TypedMapI64I64Set = 0x6E, Object, pops: 3, pushes: 0;
801    /// I64→Ptr set: pops (map_ptr, key, value).
802    TypedMapI64PtrSet = 0x6F, Object, pops: 3, pushes: 0;
803    /// String→f64 has: pops (map_ptr, key), pushes bool.
804    TypedMapStringF64Has = 0x8E, Object, pops: 2, pushes: 1;
805    /// String→i64 has: pops (map_ptr, key), pushes bool.
806    TypedMapStringI64Has = 0x8F, Object, pops: 2, pushes: 1;
807    /// String→Ptr has: pops (map_ptr, key), pushes bool.
808    TypedMapStringPtrHas = 0xB9, Object, pops: 2, pushes: 1;
809    /// I64→f64 has: pops (map_ptr, key), pushes bool.
810    TypedMapI64F64Has = 0xBA, Object, pops: 2, pushes: 1;
811    /// I64→i64 has: pops (map_ptr, key), pushes bool.
812    TypedMapI64I64Has = 0xBB, Object, pops: 2, pushes: 1;
813    /// I64→Ptr has: pops (map_ptr, key), pushes bool.
814    TypedMapI64PtrHas = 0xBC, Object, pops: 2, pushes: 1;
815    /// String→f64 delete: pops (map_ptr, key).
816    TypedMapStringF64Delete = 0xBD, Object, pops: 2, pushes: 0;
817    /// String→i64 delete: pops (map_ptr, key).
818    TypedMapStringI64Delete = 0xBE, Object, pops: 2, pushes: 0;
819    /// String→Ptr delete: pops (map_ptr, key).
820    TypedMapStringPtrDelete = 0xBF, Object, pops: 2, pushes: 0;
821    /// I64→f64 delete: pops (map_ptr, key).
822    TypedMapI64F64Delete = 0xF9, Object, pops: 2, pushes: 0;
823    /// I64→i64 delete: pops (map_ptr, key).
824    TypedMapI64I64Delete = 0xFA, Object, pops: 2, pushes: 0;
825    /// I64→Ptr delete: pops (map_ptr, key).
826    TypedMapI64PtrDelete = 0xFB, Object, pops: 2, pushes: 0;
827
828    // ===== v2 Concatenation Operations =====
829    /// Concatenate two heap strings/chars, pushing a new string. Pops (a, b).
830    StringConcat = 0xFC, Object, pops: 2, pushes: 1;
831    /// Concatenate two arrays, pushing a new array. Pops (a, b).
832    ArrayConcat = 0xFD, Object, pops: 2, pushes: 1;
833
834    // ===== v2 Stage 2.6.3: Typed Equality for Heap Types =====
835    /// Equal (string × string → bool). Pops two `*const StringObj`,
836    /// content-compares the UTF-8 bytes, pushes bool. Both operands must be
837    /// non-null v2 StringObj pointers. Use Neq via `EqString; Not`.
838    EqString = 0xFE, Comparison, pops: 2, pushes: 1;
839    /// Equal (decimal × decimal → bool). Pops two `*const DecimalObj`,
840    /// content-compares the decimal payloads, pushes bool. Both operands
841    /// must be non-null v2 DecimalObj pointers. Use Neq via `EqDecimal; Not`.
842    EqDecimal = 0xFF, Comparison, pops: 2, pushes: 1;
843
844    // ===== v2 Stage 4.2: Typed Ordered Comparison for Strings =====
845    /// Greater than (string × string → bool). Lexicographic comparison.
846    GtString = 0x100, Comparison, pops: 2, pushes: 1;
847    /// Less than (string × string → bool). Lexicographic comparison.
848    LtString = 0x101, Comparison, pops: 2, pushes: 1;
849    /// Greater than or equal (string × string → bool). Lexicographic comparison.
850    GteString = 0x102, Comparison, pops: 2, pushes: 1;
851    /// Less than or equal (string × string → bool). Lexicographic comparison.
852    LteString = 0x103, Comparison, pops: 2, pushes: 1;
853
854    // ===== Ownership-Aware Variable Operations =====
855    /// Load local with Move semantics — transfers ownership, source slot is zeroed.
856    /// Used when the compiler proves the source binding is dead after this point.
857    LoadLocalMove = 0x104, Variable, pops: 0, pushes: 1;
858    /// Load local with Clone semantics — clones the value, source stays live.
859    /// For heap-tagged values, this bumps the Arc refcount.
860    LoadLocalClone = 0x105, Variable, pops: 0, pushes: 1;
861    /// Store local with Drop semantics — drops the old value before storing.
862    /// Respects ownership: if old value is uniquely owned, frees immediately.
863    StoreLocalDrop = 0x106, Variable, pops: 1, pushes: 0;
864    /// Promote top-of-stack from shared (Arc) to owned (Box) allocation if
865    /// the refcount is 1.  No-op for inline values or already-owned values.
866    /// Used by the compiler before StoreLocal for uniquely-owned let bindings.
867    PromoteToOwned = 0x107, Stack, pops: 0, pushes: 0;
868
869    // ===== Typed Array Element Access (local-slot based, skip HeapValue dispatch) =====
870    /// Get i64 element from typed int array. Operand: local slot. Index on stack.
871    GetElemI64 = 0x108, Object, pops: 1, pushes: 1;
872    /// Get f64 element from typed float array. Operand: local slot. Index on stack.
873    GetElemF64 = 0x109, Object, pops: 1, pushes: 1;
874    /// Set i64 element in typed int array. Operand: local slot. Index and value on stack.
875    SetElemI64 = 0x10A, Object, pops: 2, pushes: 0;
876    /// Set f64 element in typed float array. Operand: local slot. Index and value on stack.
877    SetElemF64 = 0x10B, Object, pops: 2, pushes: 0;
878    /// Push i64 to typed int array. Operand: local slot. Value on stack.
879    ArrayPushI64 = 0x10C, Object, pops: 1, pushes: 0;
880    /// Push f64 to typed float array. Operand: local slot. Value on stack.
881    ArrayPushF64 = 0x10D, Object, pops: 1, pushes: 0;
882    /// Get length of typed array (any element type). Operand: local slot.
883    ArrayLenTyped = 0x10E, Object, pops: 0, pushes: 1;
884
885    // ===== Typed HashMap Access (local-slot based) =====
886    /// Get value from HashMap<string, int>. Key on stack. Operand: map slot.
887    MapGetStrI64 = 0x10F, Object, pops: 1, pushes: 1;
888    /// Get value from HashMap<string, float>. Key on stack. Operand: map slot.
889    MapGetStrF64 = 0x110, Object, pops: 1, pushes: 1;
890    /// Set value in HashMap<string, int>. Key and value on stack. Operand: map slot.
891    MapSetStrI64 = 0x111, Object, pops: 2, pushes: 0;
892    /// Check if key exists in HashMap<string, *>. Key on stack. Operand: map slot.
893    MapHasStr = 0x112, Object, pops: 1, pushes: 1;
894    /// Get HashMap length. Operand: map slot.
895    MapLenTyped = 0x113, Object, pops: 0, pushes: 1;
896
897    // ===== Typed String Access (local-slot based) =====
898    /// Get string length (chars). Operand: string slot.
899    StringLenTyped = 0x114, Object, pops: 0, pushes: 1;
900    /// Get char at index. Index on stack. Operand: string slot.
901    StringCharAt = 0x115, Object, pops: 1, pushes: 1;
902    /// Concatenate two strings. Both on stack.
903    StringConcatTyped = 0x116, Object, pops: 2, pushes: 1;
904
905    /// Phase 5.C: Return with owned semantics. Pops the top-of-stack return
906    /// value, promotes Arc→Box when refcount is exactly 1 (as `PromoteToOwned`
907    /// does), then falls through to the normal return path. Emitted by the
908    /// compiler in place of the implicit return-slot store for functions
909    /// whose inferred `ReturnOwnershipMode` is `NewlyOwned`, so the callee
910    /// already hands a uniquely-owned value to the caller and the caller
911    /// can skip its own `PromoteToOwned`.
912    ///
913    /// Stack effect is identical to `PromoteToOwned` — it operates on the
914    /// value already on the stack and leaves it in place; the control flow
915    /// is handled by the subsequent `Return` instruction or by the function
916    /// epilogue, not by this opcode itself.
917    ReturnOwned = 0x117, Stack, pops: 0, pushes: 0;
918
919    // NOTE: Byte range 0x118..=0x121 was formerly occupied by the
920    // Closure Spec Phase D typed mutable-capture opcodes
921    // (`LoadCaptureMutPtr<T>` / `StoreCaptureMutPtr<T>` for
922    // F64/I64/I32/Bool/Ptr). Track A.1C.3 retired them in favour of the
923    // dynamic-cell path on the A.1B opcodes below (which handle every
924    // let-mut / var capture uniformly). These byte values are
925    // intentionally left unassigned to keep the A.1B opcodes below at
926    // their original values.
927
928    // ===== Track A.1B: CaptureKind::OwnedMutable / CaptureKind::Shared =====
929    //
930    // These opcodes implement Track A's three-way CaptureKind split (see
931    // `crates/shape-value/src/v2/closure_layout.rs` — `CaptureKind`).
932    //
933    // The closure cell layout grew a parallel-`NativeKind` track per
934    // ADR-006 §2.7.8 / Q10: each cell records the kind of its 8-byte
935    // payload (e.g. `*mut i64`, `*mut f64`, raw heap-Arc pointer bits +
936    // `NativeKind::Ptr(HeapKind::*)`) alongside the bits, so cell load /
937    // store dispatches via `clone_with_kind` / `drop_with_kind` without
938    // re-probing tag bits. The pre-Wave-6.5 dynamic-tag word that these
939    // cells held has been deleted along with `ValueWord`; per-FieldKind
940    // typed cells (Wave B and the typed counterpart opcodes
941    // 0x140-0x166 below) are the canonical storage shape, with the
942    // Track A.1B opcodes here remaining for the migration tail until
943    // every emit site flips per Wave E.
944    //
945    // - `OwnedMutable`: `let mut` by-move captures. The closure's
946    //   capture slot holds raw `*mut <T>` cell pointer bits paired with
947    //   the cell's payload-kind from `ClosureLayout::capture_inner_kinds`.
948    //   Exactly one closure owns the cell; no sharing, no lock.
949    //   Released by `release_typed_closure` via the matching kind's
950    //   `Box::from_raw`.
951    // - `Shared`: `var` captures shared across nested closures. The
952    //   slot holds `*const SharedCell` pointer bits + the inner-payload
953    //   `NativeKind`. Each reader/writer acquires the parking_lot mutex;
954    //   refcount released by `Arc::from_raw` on closure Drop.
955    //
956    // A.1B's interpreter binds the raw pointer bits into
957    // `frame.upvalues[i]` (bypassing `Upvalue::get`/`set`'s SharedCell
958    // auto-deref — those are for the retired-in-A.1C legacy variant).
959    // The new opcodes below read the raw bits directly and dereference
960    // the pointer. Operand width: `Local(u16)` like every other capture
961    // op.
962    //
963    // SAFETY invariants (enforced per-opcode via `ClosureLayout`
964    // `owned_mutable_capture_mask` / `shared_capture_mask` at compile
965    // time):
966    //   * `LoadOwnedMutableCapture{i}` / `StoreOwnedMutableCapture{i}`
967    //     are only emitted when the current function's capture `i` has
968    //     `CaptureKind::OwnedMutable` in its layout. The upvalue at
969    //     index `i` MUST contain raw `*mut <T>` pointer bits matching
970    //     the cell's `inner_kind` (see A.1B `op_make_closure` allocation
971    //     path + A.1B `call_closure_with_nb_args` upvalue plumbing).
972    //   * Likewise for `LoadSharedCapture` / `StoreSharedCapture` — the
973    //     upvalue holds `*const SharedCell` bits and reads/writes take
974    //     the parking_lot mutex.
975    //
976    // A.1D and A.1E add Cranelift lowerings; until then the JIT bails
977    // to the interpreter for any function that contains these opcodes.
978    //
979    // See `docs/v2-closure-specialization.md` §14.7 for the landed
980    // Track A plan and ADR-006 §2.7.8 / Q10 for the cell-storage
981    // parallel-kind discipline.
982
983    /// Load through an `OwnedMutable` capture's `*mut <T>` cell.
984    /// Operand: Local(idx). Pushes the dereferenced cell payload as
985    /// raw 8 bytes paired with the cell's `inner_kind` (sourced from
986    /// `ClosureLayout::capture_inner_kinds`).
987    LoadOwnedMutableCapture = 0x132, Variable, pops: 0, pushes: 1;
988    /// Store through an `OwnedMutable` capture's `*mut <T>` cell.
989    /// Operand: Local(idx). Pops the value to write (raw 8 bytes +
990    /// payload kind from the kinded stack ABI; ADR-006 §2.7.7).
991    StoreOwnedMutableCapture = 0x133, Variable, pops: 1, pushes: 0;
992    /// Load through a `Shared` capture's `*const SharedCell` cell —
993    /// takes the parking_lot mutex for the read only. Operand:
994    /// Local(idx). Pushes the inner payload as raw 8 bytes + cell's
995    /// inner-payload kind.
996    LoadSharedCapture = 0x134, Variable, pops: 0, pushes: 1;
997    /// Store through a `Shared` capture's `*const SharedCell` cell —
998    /// takes the parking_lot mutex for the write only. Operand:
999    /// Local(idx). Pops the value to write (raw bits + inner-payload
1000    /// kind).
1001    StoreSharedCapture = 0x135, Variable, pops: 1, pushes: 0;
1002
1003    // ===== Phase 3c Wave D.1: per-FieldKind OwnedMutable capture opcodes =====
1004    //
1005    // Typed counterparts of the dynamic-cell `LoadOwnedMutableCapture`
1006    // (0x132) / `StoreOwnedMutableCapture` (0x133) above, which dispatch
1007    // via the cell's `inner_kind` recorded on the closure layout.
1008    //
1009    // The Wave B storage migration replaced the dynamic-cell payload
1010    // with per-FieldKind `Box<T>` cells (see
1011    // `shape_value::v2::closure_raw::alloc_owned_mutable_<kind>`). Each
1012    // typed cell holds a native scalar (`i64`, `f64`, `i8`, `bool`,
1013    // raw heap-Arc pointer bits, ...) without any tag overhead. The 22
1014    // opcodes below are the typed load/store path for those cells, one
1015    // pair per FieldKind, in the canonical order:
1016    //
1017    //   I64, U64, F64, I32, U32, I16, U16, I8, U8, Bool, Ptr
1018    //
1019    // Operand layout: `Local(u16)` — same capture-array index as the
1020    // 0x132/0x133 opcodes. The capture's `inner_kind` (recorded in
1021    // `ClosureLayout::capture_inner_kinds`) selects the typed opcode the
1022    // compiler emits at this site (Wave E).
1023    //
1024    // SAFETY invariants — enforced per-opcode at compile time via the
1025    // closure layout's `capture_inner_kind(i)` selector:
1026    //   * `Load/StoreOwnedMutableCapture<Kind>{i}` is emitted only when
1027    //     the current function's capture `i` has
1028    //     `CaptureKind::OwnedMutable` AND `inner_kind == FieldKind::<Kind>`.
1029    //     The upvalue at index `i` MUST contain raw `*mut <T>` bits
1030    //     matching `<Kind>` (see Wave B's `alloc_owned_mutable_<kind>`
1031    //     allocator and the per-kind init path in `op_make_closure`).
1032    //   * The dynamic-cell `LoadOwnedMutableCapture` /
1033    //     `StoreOwnedMutableCapture` (0x132/0x133) remain live and
1034    //     emit-compatible until Wave E flips every emit site to the
1035    //     typed path; Wave G then removes 0x132/0x133.
1036    //
1037    // Stack effect: Load reads the typed cell and pushes a raw native
1038    // value onto the stack via `push_kinded(bits, NativeKind::<Kind>)`
1039    // (sub-i64 ints sign- or zero-extended into the i64 path, matching
1040    // the existing typed-opcode convention in `arithmetic/`). Store
1041    // pops a native value via `pop_kinded() -> (bits, NativeKind::<Kind>)`,
1042    // truncates as needed for sub-i64 widths, and writes through the
1043    // typed cell. The pre-Wave-6.5 transitional stack shims (the W-series
1044    // "borrowed slot with call-pattern invariants" defection-attractor)
1045    // were deleted per ADR-006 §2.7.7; every push site sources kind
1046    // locally from the opcode's payload-kind suffix.
1047
1048    /// Load `i64` through an `OwnedMutable` capture's `*mut i64` cell.
1049    /// Operand: Local(idx). Pushes the dereferenced i64 onto the stack as
1050    /// a raw i64.
1051    LoadOwnedMutableCaptureI64 = 0x140, Variable, pops: 0, pushes: 1;
1052    /// Load `u64` through an `OwnedMutable` capture's `*mut u64` cell.
1053    /// Operand: Local(idx). Pushes the dereferenced u64 bits onto the
1054    /// stack as raw u64.
1055    LoadOwnedMutableCaptureU64 = 0x141, Variable, pops: 0, pushes: 1;
1056    /// Load `f64` through an `OwnedMutable` capture's `*mut f64` cell.
1057    /// Operand: Local(idx). Pushes the dereferenced f64 onto the stack as
1058    /// a raw f64.
1059    LoadOwnedMutableCaptureF64 = 0x142, Variable, pops: 0, pushes: 1;
1060    /// Load `i32` through an `OwnedMutable` capture's `*mut i32` cell.
1061    /// Operand: Local(idx). Sign-extends the i32 to i64 and pushes it as
1062    /// a raw i64 (sub-i64 ints share the i64 stack convention).
1063    LoadOwnedMutableCaptureI32 = 0x143, Variable, pops: 0, pushes: 1;
1064    /// Load `u32` through an `OwnedMutable` capture's `*mut u32` cell.
1065    /// Operand: Local(idx). Zero-extends the u32 to i64 and pushes it as
1066    /// a raw i64.
1067    LoadOwnedMutableCaptureU32 = 0x144, Variable, pops: 0, pushes: 1;
1068    /// Load `i16` through an `OwnedMutable` capture's `*mut i16` cell.
1069    /// Operand: Local(idx). Sign-extends the i16 to i64 and pushes it as
1070    /// a raw i64.
1071    LoadOwnedMutableCaptureI16 = 0x145, Variable, pops: 0, pushes: 1;
1072    /// Load `u16` through an `OwnedMutable` capture's `*mut u16` cell.
1073    /// Operand: Local(idx). Zero-extends the u16 to i64 and pushes it as
1074    /// a raw i64.
1075    LoadOwnedMutableCaptureU16 = 0x146, Variable, pops: 0, pushes: 1;
1076    /// Load `i8` through an `OwnedMutable` capture's `*mut i8` cell.
1077    /// Operand: Local(idx). Sign-extends the i8 to i64 and pushes it as
1078    /// a raw i64.
1079    LoadOwnedMutableCaptureI8 = 0x147, Variable, pops: 0, pushes: 1;
1080    /// Load `u8` through an `OwnedMutable` capture's `*mut u8` cell.
1081    /// Operand: Local(idx). Zero-extends the u8 to i64 and pushes it as
1082    /// a raw i64.
1083    LoadOwnedMutableCaptureU8 = 0x148, Variable, pops: 0, pushes: 1;
1084    /// Load `bool` through an `OwnedMutable` capture's `*mut bool` cell.
1085    /// Operand: Local(idx). Pushes the dereferenced bool onto the stack
1086    /// via the typed bool-push helper.
1087    LoadOwnedMutableCaptureBool = 0x149, Variable, pops: 0, pushes: 1;
1088    /// Load `Ptr` through an `OwnedMutable` capture's `*mut u64` cell.
1089    /// Operand: Local(idx). Pushes the dereferenced 8-byte pointer-shaped
1090    /// payload as raw u64 (raw heap-Arc pointer bits paired with the
1091    /// cell's `NativeKind::Ptr(HeapKind::*)` per ADR-006 §2.7.8). Refcount
1092    /// retain semantics for `Ptr` payloads are the caller's responsibility
1093    /// — matches the `read_owned_mutable_ptr` contract: this opcode does
1094    /// NOT clone.
1095    LoadOwnedMutableCapturePtr = 0x14A, Variable, pops: 0, pushes: 1;
1096
1097    /// Store `i64` through an `OwnedMutable` capture's `*mut i64` cell.
1098    /// Operand: Local(idx). Pops a raw i64 and writes it into the cell.
1099    StoreOwnedMutableCaptureI64 = 0x14B, Variable, pops: 1, pushes: 0;
1100    /// Store `u64` through an `OwnedMutable` capture's `*mut u64` cell.
1101    /// Operand: Local(idx). Pops a raw u64 and writes it into the cell.
1102    StoreOwnedMutableCaptureU64 = 0x14C, Variable, pops: 1, pushes: 0;
1103    /// Store `f64` through an `OwnedMutable` capture's `*mut f64` cell.
1104    /// Operand: Local(idx). Pops a raw f64 and writes it into the cell.
1105    StoreOwnedMutableCaptureF64 = 0x14D, Variable, pops: 1, pushes: 0;
1106    /// Store `i32` through an `OwnedMutable` capture's `*mut i32` cell.
1107    /// Operand: Local(idx). Pops a raw i64 from the stack, truncates to
1108    /// the low 32 bits, and writes the i32 payload.
1109    StoreOwnedMutableCaptureI32 = 0x14E, Variable, pops: 1, pushes: 0;
1110    /// Store `u32` through an `OwnedMutable` capture's `*mut u32` cell.
1111    /// Operand: Local(idx). Pops a raw i64, truncates to the low 32 bits,
1112    /// and writes the u32 payload.
1113    StoreOwnedMutableCaptureU32 = 0x14F, Variable, pops: 1, pushes: 0;
1114    /// Store `i16` through an `OwnedMutable` capture's `*mut i16` cell.
1115    /// Operand: Local(idx). Pops a raw i64, truncates to the low 16 bits,
1116    /// and writes the i16 payload.
1117    StoreOwnedMutableCaptureI16 = 0x150, Variable, pops: 1, pushes: 0;
1118    /// Store `u16` through an `OwnedMutable` capture's `*mut u16` cell.
1119    /// Operand: Local(idx). Pops a raw i64, truncates to the low 16 bits,
1120    /// and writes the u16 payload.
1121    StoreOwnedMutableCaptureU16 = 0x151, Variable, pops: 1, pushes: 0;
1122    /// Store `i8` through an `OwnedMutable` capture's `*mut i8` cell.
1123    /// Operand: Local(idx). Pops a raw i64, truncates to the low 8 bits,
1124    /// and writes the i8 payload.
1125    StoreOwnedMutableCaptureI8 = 0x152, Variable, pops: 1, pushes: 0;
1126    /// Store `u8` through an `OwnedMutable` capture's `*mut u8` cell.
1127    /// Operand: Local(idx). Pops a raw i64, truncates to the low 8 bits,
1128    /// and writes the u8 payload.
1129    StoreOwnedMutableCaptureU8 = 0x153, Variable, pops: 1, pushes: 0;
1130    /// Store `bool` through an `OwnedMutable` capture's `*mut bool` cell.
1131    /// Operand: Local(idx). Pops a bool via the typed bool-pop helper and
1132    /// writes it into the cell.
1133    StoreOwnedMutableCaptureBool = 0x154, Variable, pops: 1, pushes: 0;
1134    /// Store `Ptr` through an `OwnedMutable` capture's `*mut u64` cell.
1135    /// Operand: Local(idx). Pops a raw u64 (heap-Arc pointer bits +
1136    /// `NativeKind::Ptr(HeapKind::*)` per ADR-006 §2.7.7) and writes
1137    /// it into the cell. Refcount semantics are the caller's
1138    /// responsibility — matches the `write_owned_mutable_ptr` contract:
1139    /// this opcode does NOT release the previous payload nor retain the
1140    /// new one.
1141    StoreOwnedMutableCapturePtr = 0x155, Variable, pops: 1, pushes: 0;
1142
1143    // ===== Track D.2: per-FieldKind typed Shared capture opcodes =====
1144    //
1145    // These are the typed counterparts of the legacy
1146    // `LoadSharedCapture` / `StoreSharedCapture` (0x134 / 0x135). For each
1147    // payload `FieldKind` (I64, U64, F64, I32, U32, I16, U16, I8, U8,
1148    // Bool, Ptr) we have a Load/Store pair that:
1149    //
1150    // * recovers the `*const SharedCell` pointer bits from the capture
1151    //   slot via `read_capture_raw_pointer_bits(idx)`,
1152    // * delegates to the lock-gated `read_shared_<kind>` /
1153    //   `write_shared_<kind>` helper in
1154    //   `shape_value::v2::closure_raw`. The helper acquires the
1155    //   `parking_lot::Mutex` internally, performs the typed access, and
1156    //   releases the lock before returning. The handler MUST NOT take
1157    //   the lock externally — that would double-lock.
1158    //
1159    // Stack effect mirrors D.1 (typed OwnedMutable opcodes 0x140-0x155):
1160    // Load reads from the lock-gated cell and pushes a raw native value
1161    // onto the stack via `push_kinded(bits, NativeKind::<Kind>)`. Store
1162    // pops a native value via `pop_kinded() -> (bits, NativeKind::<Kind>)`,
1163    // then writes it through the lock-gated helper. The pre-Wave-6.5
1164    // transitional stack shims (the W-series "borrowed slot with
1165    // call-pattern invariants" defection-attractor) were deleted per
1166    // ADR-006 §2.7.7; every push site sources kind locally from the
1167    // opcode's payload-kind suffix.
1168    //
1169    // SAFETY invariants — enforced by the compiler (Wave E codegen):
1170    //   * `LoadSharedCapture<Kind>` / `StoreSharedCapture<Kind>` are only
1171    //     emitted when the current function's capture `i` has
1172    //     `CaptureKind::Shared` and a payload `FieldKind` matching
1173    //     `<Kind>` in its layout.
1174    //   * The upvalue slot at index `i` MUST hold raw `*const SharedCell`
1175    //     bits produced by `Arc::into_raw(Arc::new(SharedCell::new(...)))`.
1176    //   * The cell's interior `FieldKind` must equal `<Kind>` — the
1177    //     helper writes the bit pattern matching the declared kind, and
1178    //     a mismatched reader will reinterpret bytes incorrectly.
1179    //
1180    // Opcode codes 0x156..=0x16B (22 codes total). Ordering matches D.1:
1181    // I64, U64, F64, I32, U32, I16, U16, I8, U8, Bool, Ptr — Load then
1182    // Store paired (LoadI64 = 0x156, StoreI64 = 0x161, LoadU64 = 0x157,
1183    // StoreU64 = 0x162, ...). We keep the kinds contiguous so the
1184    // dispatch table reads as two parallel ranges.
1185
1186    /// Load an `i64` through a `Shared` capture cell — locks the mutex,
1187    /// reads the i64 payload, unlocks, pushes the raw i64 onto the stack.
1188    /// Operand: Local(idx).
1189    LoadSharedCaptureI64 = 0x156, Variable, pops: 0, pushes: 1;
1190    /// Load a `u64` through a `Shared` capture cell — locks, reads the
1191    /// u64 payload, unlocks, pushes the raw u64 bits.
1192    /// Operand: Local(idx).
1193    LoadSharedCaptureU64 = 0x157, Variable, pops: 0, pushes: 1;
1194    /// Load an `f64` through a `Shared` capture cell — locks, reads the
1195    /// f64 payload, unlocks, pushes the raw f64.
1196    /// Operand: Local(idx).
1197    LoadSharedCaptureF64 = 0x158, Variable, pops: 0, pushes: 1;
1198    /// Load an `i32` through a `Shared` capture cell — locks, reads the
1199    /// low 4 bytes of the payload as i32, unlocks, sign-extends, pushes.
1200    /// Operand: Local(idx).
1201    LoadSharedCaptureI32 = 0x159, Variable, pops: 0, pushes: 1;
1202    /// Load a `u32` through a `Shared` capture cell — locks, reads the
1203    /// low 4 bytes of the payload as u32, unlocks, zero-extends, pushes.
1204    /// Operand: Local(idx).
1205    LoadSharedCaptureU32 = 0x15A, Variable, pops: 0, pushes: 1;
1206    /// Load an `i16` through a `Shared` capture cell — locks, reads the
1207    /// low 2 bytes of the payload as i16, unlocks, sign-extends, pushes.
1208    /// Operand: Local(idx).
1209    LoadSharedCaptureI16 = 0x15B, Variable, pops: 0, pushes: 1;
1210    /// Load a `u16` through a `Shared` capture cell — locks, reads the
1211    /// low 2 bytes of the payload as u16, unlocks, zero-extends, pushes.
1212    /// Operand: Local(idx).
1213    LoadSharedCaptureU16 = 0x15C, Variable, pops: 0, pushes: 1;
1214    /// Load an `i8` through a `Shared` capture cell — locks, reads the
1215    /// low byte of the payload as i8, unlocks, sign-extends, pushes.
1216    /// Operand: Local(idx).
1217    LoadSharedCaptureI8 = 0x15D, Variable, pops: 0, pushes: 1;
1218    /// Load a `u8` through a `Shared` capture cell — locks, reads the
1219    /// low byte of the payload as u8, unlocks, zero-extends, pushes.
1220    /// Operand: Local(idx).
1221    LoadSharedCaptureU8 = 0x15E, Variable, pops: 0, pushes: 1;
1222    /// Load a `bool` through a `Shared` capture cell — locks, reads the
1223    /// low byte of the payload (zero ⇒ false; non-zero ⇒ true), unlocks,
1224    /// pushes a raw NaN-tagged bool onto the stack.
1225    /// Operand: Local(idx).
1226    LoadSharedCaptureBool = 0x15F, Variable, pops: 0, pushes: 1;
1227    /// Load a `Ptr` through a `Shared` capture cell — locks, reads the 8
1228    /// payload bytes as a raw u64 (heap-Arc pointer bits paired with the
1229    /// cell's `NativeKind::Ptr(HeapKind::*)` per ADR-006 §2.7.8 / Q10),
1230    /// unlocks, pushes the raw bits. Refcount
1231    /// retain semantics for `Ptr` payloads are the caller's
1232    /// responsibility (the helper does NOT clone — match the
1233    /// `read_shared_ptr` contract).
1234    /// Operand: Local(idx).
1235    LoadSharedCapturePtr = 0x160, Variable, pops: 0, pushes: 1;
1236
1237    /// Store an `i64` through a `Shared` capture cell — pops a raw i64
1238    /// from the stack, locks, writes the 8-byte i64 payload, unlocks.
1239    /// Operand: Local(idx).
1240    StoreSharedCaptureI64 = 0x161, Variable, pops: 1, pushes: 0;
1241    /// Store a `u64` through a `Shared` capture cell — pops a raw u64,
1242    /// locks, writes the 8-byte u64 payload, unlocks.
1243    /// Operand: Local(idx).
1244    StoreSharedCaptureU64 = 0x162, Variable, pops: 1, pushes: 0;
1245    /// Store an `f64` through a `Shared` capture cell — pops a raw f64,
1246    /// locks, writes the 8-byte f64 payload, unlocks.
1247    /// Operand: Local(idx).
1248    StoreSharedCaptureF64 = 0x163, Variable, pops: 1, pushes: 0;
1249    /// Store an `i32` through a `Shared` capture cell — pops a raw i32,
1250    /// sign-extends to 8 bytes, locks, writes payload, unlocks.
1251    /// Operand: Local(idx).
1252    StoreSharedCaptureI32 = 0x164, Variable, pops: 1, pushes: 0;
1253    /// Store a `u32` through a `Shared` capture cell — pops a raw u32,
1254    /// zero-extends to 8 bytes, locks, writes payload, unlocks.
1255    /// Operand: Local(idx).
1256    StoreSharedCaptureU32 = 0x165, Variable, pops: 1, pushes: 0;
1257    /// Store an `i16` through a `Shared` capture cell — pops a raw i16,
1258    /// sign-extends to 8 bytes, locks, writes payload, unlocks.
1259    /// Operand: Local(idx).
1260    StoreSharedCaptureI16 = 0x166, Variable, pops: 1, pushes: 0;
1261    /// Store a `u16` through a `Shared` capture cell — pops a raw u16,
1262    /// zero-extends to 8 bytes, locks, writes payload, unlocks.
1263    /// Operand: Local(idx).
1264    StoreSharedCaptureU16 = 0x167, Variable, pops: 1, pushes: 0;
1265    /// Store an `i8` through a `Shared` capture cell — pops a raw i8,
1266    /// sign-extends to 8 bytes, locks, writes payload, unlocks.
1267    /// Operand: Local(idx).
1268    StoreSharedCaptureI8 = 0x168, Variable, pops: 1, pushes: 0;
1269    /// Store a `u8` through a `Shared` capture cell — pops a raw u8,
1270    /// zero-extends to 8 bytes, locks, writes payload, unlocks.
1271    /// Operand: Local(idx).
1272    StoreSharedCaptureU8 = 0x169, Variable, pops: 1, pushes: 0;
1273    /// Store a `bool` through a `Shared` capture cell — pops a raw bool,
1274    /// locks, writes the 8-byte payload as 0 or 1, unlocks.
1275    /// Operand: Local(idx).
1276    StoreSharedCaptureBool = 0x16A, Variable, pops: 1, pushes: 0;
1277    /// Store a `Ptr` through a `Shared` capture cell — pops raw 8-byte
1278    /// bits (heap-Arc pointer bits + `NativeKind::Ptr(HeapKind::*)` per
1279    /// ADR-006 §2.7.7), locks, writes the payload, unlocks. The
1280    /// caller is responsible for refcount semantics on Ptr payloads —
1281    /// matches the `write_shared_ptr` contract: this opcode does NOT
1282    /// release the previous payload nor retain the new one.
1283    /// Operand: Local(idx).
1284    StoreSharedCapturePtr = 0x16B, Variable, pops: 1, pushes: 0;
1285
1286    // ===== Wave E+3: per-FieldKind typed local load/store opcodes =====
1287    //
1288    // These are the typed counterparts of the legacy `LoadLocal` (0x50) /
1289    // `StoreLocal` (0x51). For each `FieldKind` (I64, U64, F64, I32, U32,
1290    // I16, U16, I8, U8, Bool, Ptr) we have a Load/Store pair that:
1291    //
1292    // * reads / writes the local slot at `bp + idx` directly as raw 8-byte
1293    //   bits paired with the slot's `NativeKind` from
1294    //   `FrameDescriptor.slots[idx]`, with no `clone_from_bits`, no
1295    //   SharedCell auto-deref, and no tag-bit decode (the deleted
1296    //   ValueWord dispatch path).
1297    // * skips refcount management even for the `Ptr` kind — refcount
1298    //   semantics are the IR's responsibility (matches D.1 / D.2 Ptr
1299    //   contract; the c-stdlib-msgpack pattern from commit afb1651 is the
1300    //   precedent).
1301    //
1302    // SAFETY invariants — enforced by the compiler (Wave E+ codegen):
1303    //   * The emitter only fires `LoadLocal<Kind>` / `StoreLocal<Kind>` on
1304    //     a slot whose proven NativeKind matches `<Kind>`. The slot's bits
1305    //     were last written by a matching-Kind `StoreLocal<Kind>` (or a
1306    //     producer that emitted matching native bits), so a raw read
1307    //     reinterprets the correct bit pattern.
1308    //   * Sub-i64 kinds (I32/U32/I16/U16/I8/U8/Bool) carry the value in
1309    //     the low N bits of the 8-byte slot; the upper bits are
1310    //     unspecified. Producers must zero/sign-extend appropriately
1311    //     (matches D.1 store-side truncation convention).
1312    //   * For `Ptr` slots, neither Load nor Store performs the deleted
1313    //     `vw_clone` / `vw_drop` (their post-§2.7.7 replacements
1314    //     `clone_with_kind` / `drop_with_kind` are not auto-invoked
1315    //     here either). The IR pairs each typed Ptr load/store with the
1316    //     matching retain/release before/after.
1317    //
1318    // Stack effect mirrors D.1 (typed OwnedMutable opcodes 0x140-0x155):
1319    // Load reads from the local slot and pushes a raw native value onto
1320    // the stack via `push_kinded(bits, NativeKind::<Kind>)`. Store pops a
1321    // native value via `pop_kinded() -> (bits, NativeKind::<Kind>)`, then
1322    // writes the raw 8-byte bits to the slot. The pre-Wave-6.5
1323    // transitional stack shims (the W-series "borrowed slot with
1324    // call-pattern invariants" defection-attractor) were deleted per
1325    // ADR-006 §2.7.7; every push site sources kind locally from the
1326    // opcode's payload-kind suffix.
1327    //
1328    // The legacy `LoadLocal` (0x50) / `StoreLocal` (0x51) stay live for
1329    // unproven-type positions; the typed forms are dead until Wave E+4
1330    // flips the emitter.
1331    //
1332    // Code range: 0x16C..=0x181 (22 codes total). Ordering: I64, U64, F64,
1333    // I32, U32, I16, U16, I8, U8, Bool, Ptr — Loads first (0x16C..=0x176),
1334    // Stores second (0x177..=0x181).
1335
1336    /// Load `i64` from local slot — reads raw 8 bytes, pushes as i64.
1337    /// Operand: Local(idx).
1338    LoadLocalI64 = 0x16C, Variable, pops: 0, pushes: 1;
1339    /// Load `u64` from local slot — reads raw 8 bytes, pushes as u64.
1340    /// Operand: Local(idx).
1341    LoadLocalU64 = 0x16D, Variable, pops: 0, pushes: 1;
1342    /// Load `f64` from local slot — reads raw 8 bytes, pushes as f64.
1343    /// Operand: Local(idx).
1344    LoadLocalF64 = 0x16E, Variable, pops: 0, pushes: 1;
1345    /// Load `i32` from local slot — reads low 4 bytes, sign-extends to
1346    /// i64 in the 8-byte stack slot. Operand: Local(idx).
1347    LoadLocalI32 = 0x16F, Variable, pops: 0, pushes: 1;
1348    /// Load `u32` from local slot — reads low 4 bytes, zero-extends to
1349    /// u64 in the 8-byte stack slot. Operand: Local(idx).
1350    LoadLocalU32 = 0x170, Variable, pops: 0, pushes: 1;
1351    /// Load `i16` from local slot — reads low 2 bytes, sign-extends to
1352    /// i64 in the 8-byte stack slot. Operand: Local(idx).
1353    LoadLocalI16 = 0x171, Variable, pops: 0, pushes: 1;
1354    /// Load `u16` from local slot — reads low 2 bytes, zero-extends to
1355    /// u64 in the 8-byte stack slot. Operand: Local(idx).
1356    LoadLocalU16 = 0x172, Variable, pops: 0, pushes: 1;
1357    /// Load `i8` from local slot — reads low byte, sign-extends to i64
1358    /// in the 8-byte stack slot. Operand: Local(idx).
1359    LoadLocalI8 = 0x173, Variable, pops: 0, pushes: 1;
1360    /// Load `u8` from local slot — reads low byte, zero-extends to u64
1361    /// in the 8-byte stack slot. Operand: Local(idx).
1362    LoadLocalU8 = 0x174, Variable, pops: 0, pushes: 1;
1363    /// Load `bool` from local slot — reads low byte (zero ⇒ false;
1364    /// non-zero ⇒ true) and pushes the raw 8 bytes back. Operand: Local(idx).
1365    LoadLocalBool = 0x175, Variable, pops: 0, pushes: 1;
1366    /// Load `Ptr` from local slot — reads raw 8 bytes (heap-Arc pointer
1367    /// bits paired with the slot's `NativeKind::Ptr(HeapKind::*)` per
1368    /// ADR-006 §2.7.7) and pushes them. The handler does NOT clone /
1369    /// retain — refcount semantics are the caller's responsibility.
1370    /// Operand: Local(idx).
1371    LoadLocalPtr = 0x176, Variable, pops: 0, pushes: 1;
1372
1373    /// Store `i64` to local slot — pops raw i64, writes 8 bytes to slot.
1374    /// Operand: Local(idx).
1375    StoreLocalI64 = 0x177, Variable, pops: 1, pushes: 0;
1376    /// Store `u64` to local slot — pops raw u64, writes 8 bytes to slot.
1377    /// Operand: Local(idx).
1378    StoreLocalU64 = 0x178, Variable, pops: 1, pushes: 0;
1379    /// Store `f64` to local slot — pops raw f64, writes 8 bytes to slot.
1380    /// Operand: Local(idx).
1381    StoreLocalF64 = 0x179, Variable, pops: 1, pushes: 0;
1382    /// Store `i32` to local slot — pops 8-byte slot, truncates to i32
1383    /// (low 4 bytes, sign-extended back into 8-byte slot for storage).
1384    /// Operand: Local(idx).
1385    StoreLocalI32 = 0x17A, Variable, pops: 1, pushes: 0;
1386    /// Store `u32` to local slot — pops 8-byte slot, truncates to u32
1387    /// (low 4 bytes, zero-extended back into 8-byte slot for storage).
1388    /// Operand: Local(idx).
1389    StoreLocalU32 = 0x17B, Variable, pops: 1, pushes: 0;
1390    /// Store `i16` to local slot — pops 8-byte slot, truncates to i16
1391    /// (low 2 bytes, sign-extended back into 8-byte slot for storage).
1392    /// Operand: Local(idx).
1393    StoreLocalI16 = 0x17C, Variable, pops: 1, pushes: 0;
1394    /// Store `u16` to local slot — pops 8-byte slot, truncates to u16
1395    /// (low 2 bytes, zero-extended back into 8-byte slot for storage).
1396    /// Operand: Local(idx).
1397    StoreLocalU16 = 0x17D, Variable, pops: 1, pushes: 0;
1398    /// Store `i8` to local slot — pops 8-byte slot, truncates to i8
1399    /// (low byte, sign-extended back into 8-byte slot for storage).
1400    /// Operand: Local(idx).
1401    StoreLocalI8 = 0x17E, Variable, pops: 1, pushes: 0;
1402    /// Store `u8` to local slot — pops 8-byte slot, truncates to u8
1403    /// (low byte, zero-extended back into 8-byte slot for storage).
1404    /// Operand: Local(idx).
1405    StoreLocalU8 = 0x17F, Variable, pops: 1, pushes: 0;
1406    /// Store `bool` to local slot — pops raw 8-byte slot, writes a
1407    /// canonical 0 or 1 in the slot's low byte (any nonzero pop ⇒ 1).
1408    /// Operand: Local(idx).
1409    StoreLocalBool = 0x180, Variable, pops: 1, pushes: 0;
1410    /// Store `Ptr` to local slot — pops raw 8 bytes (heap-Arc pointer
1411    /// bits + `NativeKind::Ptr(HeapKind::*)` per ADR-006 §2.7.7) and
1412    /// writes them to the slot. The handler does NOT release the
1413    /// previous payload nor retain the new one — refcount semantics are
1414    /// the caller's responsibility (matches the D.1 / D.2 Ptr contract).
1415    /// Operand: Local(idx).
1416    StoreLocalPtr = 0x181, Variable, pops: 1, pushes: 0;
1417
1418    // ===== Wave E+3: per-FieldKind typed module-binding opcodes =====
1419    //
1420    // These are the typed counterparts of the legacy
1421    // `LoadModuleBinding` (0x52) / `StoreModuleBinding` (0x53). For each
1422    // payload `FieldKind` (I64, U64, F64, I32, U32, I16, U16, I8, U8,
1423    // Bool, Ptr) we have a Load/Store pair that:
1424    //
1425    //   * Load reads the 8-byte slot at `module_bindings[idx]` and pushes
1426    //     a raw native value with the appropriate width interpretation.
1427    //   * Store pops a raw native value, optionally truncates/extends to
1428    //     the declared width, and writes the 8-byte slot at
1429    //     `module_bindings[idx]`.
1430    //
1431    // Stack convention mirrors Wave D (typed OwnedMutable opcodes
1432    // 0x140-0x155): Load pushes via `push_kinded(bits, NativeKind::<Kind>)`
1433    // with the native value sign- or zero-extended into the 8-byte stack
1434    // slot. Store pops via `pop_kinded() -> (bits, NativeKind::<Kind>)`
1435    // and reinterprets the low bits as the declared type. The pre-Wave-6.5
1436    // transitional stack shims (the W-series "borrowed slot with
1437    // call-pattern invariants" defection-attractor) were deleted per
1438    // ADR-006 §2.7.7; every push site sources kind locally from the
1439    // opcode's payload-kind suffix.
1440    //
1441    // The legacy `LoadModuleBinding` (0x52) / `StoreModuleBinding` (0x53)
1442    // remain live for unproven-type module bindings; the typed opcodes
1443    // below stay dead until Wave E+4 flips the emitter to dispatch them
1444    // for statically-typed module-binding positions.
1445    //
1446    // SAFETY invariants — enforced by the compiler (Wave E+4 codegen):
1447    //   * `LoadModuleBinding<Kind>` / `StoreModuleBinding<Kind>` are only
1448    //     emitted when the module binding at index `idx` has a static
1449    //     type matching `<Kind>` and has only ever been written by typed
1450    //     stores of the same kind (i.e. never aliased through the legacy
1451    //     `StoreModuleBinding` polymorphic path that may install a
1452    //     heap-tagged payload subject to refcount-dispatch semantics on
1453    //     subsequent legacy writes).
1454    //   * For Ptr, refcount management lives in the IR — the load does
1455    //     not retain and the store does not release. Wave E+4 pairs
1456    //     `LoadModuleBindingPtr` with `clone_with_kind` and
1457    //     `StoreModuleBindingPtr` with `drop_with_kind` of the prior
1458    //     payload (the post-§2.7.7 dispatch table; the deleted
1459    //     `vw_clone` / `vw_drop` precursors used the now-removed
1460    //     tag_bits dispatch internally and were retired with that path).
1461    //
1462    // Opcode codes 0x182..=0x197 (22 codes total). Ordering matches Wave
1463    // D: I64, U64, F64, I32, U32, I16, U16, I8, U8, Bool, Ptr — Loads
1464    // first (0x182..=0x18C), then Stores (0x18D..=0x197).
1465
1466    /// Load an `i64` from `module_bindings[idx]` — reads the 8-byte slot
1467    /// as i64 and pushes the raw bits onto the stack. Operand:
1468    /// ModuleBinding(idx).
1469    LoadModuleBindingI64 = 0x182, Variable, pops: 0, pushes: 1;
1470    /// Load a `u64` from `module_bindings[idx]` — reads the 8-byte slot
1471    /// as u64 and pushes the raw bits. Operand: ModuleBinding(idx).
1472    LoadModuleBindingU64 = 0x183, Variable, pops: 0, pushes: 1;
1473    /// Load an `f64` from `module_bindings[idx]` — reads the 8-byte slot
1474    /// as f64 and pushes the raw bits. Operand: ModuleBinding(idx).
1475    LoadModuleBindingF64 = 0x184, Variable, pops: 0, pushes: 1;
1476    /// Load an `i32` from `module_bindings[idx]` — reads the low 4 bytes
1477    /// of the slot as i32, sign-extends to i64, pushes the raw bits.
1478    /// Operand: ModuleBinding(idx).
1479    LoadModuleBindingI32 = 0x185, Variable, pops: 0, pushes: 1;
1480    /// Load a `u32` from `module_bindings[idx]` — reads the low 4 bytes
1481    /// of the slot as u32, zero-extends to u64, pushes. Operand:
1482    /// ModuleBinding(idx).
1483    LoadModuleBindingU32 = 0x186, Variable, pops: 0, pushes: 1;
1484    /// Load an `i16` from `module_bindings[idx]` — reads the low 2 bytes
1485    /// of the slot as i16, sign-extends to i64, pushes. Operand:
1486    /// ModuleBinding(idx).
1487    LoadModuleBindingI16 = 0x187, Variable, pops: 0, pushes: 1;
1488    /// Load a `u16` from `module_bindings[idx]` — reads the low 2 bytes
1489    /// of the slot as u16, zero-extends to u64, pushes. Operand:
1490    /// ModuleBinding(idx).
1491    LoadModuleBindingU16 = 0x188, Variable, pops: 0, pushes: 1;
1492    /// Load an `i8` from `module_bindings[idx]` — reads the low byte of
1493    /// the slot as i8, sign-extends to i64, pushes. Operand:
1494    /// ModuleBinding(idx).
1495    LoadModuleBindingI8 = 0x189, Variable, pops: 0, pushes: 1;
1496    /// Load a `u8` from `module_bindings[idx]` — reads the low byte of
1497    /// the slot as u8, zero-extends to u64, pushes. Operand:
1498    /// ModuleBinding(idx).
1499    LoadModuleBindingU8 = 0x18A, Variable, pops: 0, pushes: 1;
1500    /// Load a `bool` from `module_bindings[idx]` — reads the low byte
1501    /// (zero ⇒ false; non-zero ⇒ true), pushes 0/1 as raw u64. Operand:
1502    /// ModuleBinding(idx).
1503    LoadModuleBindingBool = 0x18B, Variable, pops: 0, pushes: 1;
1504    /// Load a `Ptr` from `module_bindings[idx]` — reads the 8-byte slot
1505    /// as raw u64 (heap-Arc pointer bits paired with the binding's
1506    /// `NativeKind::Ptr(HeapKind::*)` per ADR-006 §2.7.7) and pushes.
1507    /// Refcount retain semantics for Ptr payloads are the caller's
1508    /// responsibility — this opcode does NOT `clone_with_kind`.
1509    /// Operand: ModuleBinding(idx).
1510    LoadModuleBindingPtr = 0x18C, Variable, pops: 0, pushes: 1;
1511
1512    /// Store an `i64` to `module_bindings[idx]` — pops raw i64 bits,
1513    /// writes the full 8-byte slot. Operand: ModuleBinding(idx).
1514    StoreModuleBindingI64 = 0x18D, Variable, pops: 1, pushes: 0;
1515    /// Store a `u64` to `module_bindings[idx]` — pops raw u64 bits,
1516    /// writes the full 8-byte slot. Operand: ModuleBinding(idx).
1517    StoreModuleBindingU64 = 0x18E, Variable, pops: 1, pushes: 0;
1518    /// Store an `f64` to `module_bindings[idx]` — pops raw f64 bits,
1519    /// writes the full 8-byte slot. Operand: ModuleBinding(idx).
1520    StoreModuleBindingF64 = 0x18F, Variable, pops: 1, pushes: 0;
1521    /// Store an `i32` to `module_bindings[idx]` — pops raw u64, truncates
1522    /// to i32, sign-extends to i64, writes the 8-byte slot. Operand:
1523    /// ModuleBinding(idx).
1524    StoreModuleBindingI32 = 0x190, Variable, pops: 1, pushes: 0;
1525    /// Store a `u32` to `module_bindings[idx]` — pops raw u64, truncates
1526    /// to u32, zero-extends to u64, writes the 8-byte slot. Operand:
1527    /// ModuleBinding(idx).
1528    StoreModuleBindingU32 = 0x191, Variable, pops: 1, pushes: 0;
1529    /// Store an `i16` to `module_bindings[idx]` — pops raw u64, truncates
1530    /// to i16, sign-extends to i64, writes the 8-byte slot. Operand:
1531    /// ModuleBinding(idx).
1532    StoreModuleBindingI16 = 0x192, Variable, pops: 1, pushes: 0;
1533    /// Store a `u16` to `module_bindings[idx]` — pops raw u64, truncates
1534    /// to u16, zero-extends to u64, writes the 8-byte slot. Operand:
1535    /// ModuleBinding(idx).
1536    StoreModuleBindingU16 = 0x193, Variable, pops: 1, pushes: 0;
1537    /// Store an `i8` to `module_bindings[idx]` — pops raw u64, truncates
1538    /// to i8, sign-extends to i64, writes the 8-byte slot. Operand:
1539    /// ModuleBinding(idx).
1540    StoreModuleBindingI8 = 0x194, Variable, pops: 1, pushes: 0;
1541    /// Store a `u8` to `module_bindings[idx]` — pops raw u64, truncates
1542    /// to u8, zero-extends to u64, writes the 8-byte slot. Operand:
1543    /// ModuleBinding(idx).
1544    StoreModuleBindingU8 = 0x195, Variable, pops: 1, pushes: 0;
1545    /// Store a `bool` to `module_bindings[idx]` — pops raw u64 (any
1546    /// non-zero bit pattern ⇒ true), writes the 8-byte slot as 0 or 1.
1547    /// Operand: ModuleBinding(idx).
1548    StoreModuleBindingBool = 0x196, Variable, pops: 1, pushes: 0;
1549    /// Store a `Ptr` to `module_bindings[idx]` — pops raw 8-byte bits
1550    /// (heap-Arc pointer bits + `NativeKind::Ptr(HeapKind::*)` per
1551    /// ADR-006 §2.7.7) and writes the slot. The caller is responsible
1552    /// for refcount semantics — this opcode does NOT release the
1553    /// previous payload nor retain the new one. Operand:
1554    /// ModuleBinding(idx).
1555    StoreModuleBindingPtr = 0x197, Variable, pops: 1, pushes: 0;
1556
1557    // ===== Wave E+3: per-FieldKind typed `ReturnValue<Kind>` opcodes =====
1558    //
1559    // Typed counterparts of the legacy `ReturnValue` (0x45). The handler
1560    // body is identical to `op_return_value` — pops the return value as
1561    // raw 8-byte bits, pops the call frame, releases the callee's
1562    // register window, then pushes the return value onto the caller's
1563    // stack. The encoded `<Kind>` carries no runtime difference; it is
1564    // a *static* annotation for the JIT and downstream consumers so the
1565    // caller's stack discipline is known at the call site.
1566    //
1567    // Stack effect: pops 1 (the return value of the matching native
1568    // kind), pushes 1 onto the caller's frame after frame cleanup. The
1569    // legacy `ReturnValue` (0x45) stays live for unproven-type return
1570    // positions.
1571    //
1572    // Code range: 0x198..=0x1A2 (11 codes total). Ordering matches the
1573    // FieldKind canonical ordering used elsewhere in this file (D.1 /
1574    // D.2): I64, U64, F64, I32, U32, I16, U16, I8, U8, Bool, Ptr.
1575    /// Return with `i64` value — pops 1 raw i64, frame-cleans, pushes 1.
1576    ReturnValueI64 = 0x198, Control, pops: 1, pushes: 0;
1577    /// Return with `u64` value — pops 1 raw u64, frame-cleans, pushes 1.
1578    ReturnValueU64 = 0x199, Control, pops: 1, pushes: 0;
1579    /// Return with `f64` value — pops 1 raw f64, frame-cleans, pushes 1.
1580    ReturnValueF64 = 0x19A, Control, pops: 1, pushes: 0;
1581    /// Return with `i32` value — pops 1 raw i32 (in i64 slot),
1582    /// frame-cleans, pushes 1.
1583    ReturnValueI32 = 0x19B, Control, pops: 1, pushes: 0;
1584    /// Return with `u32` value — pops 1 raw u32 (in i64 slot),
1585    /// frame-cleans, pushes 1.
1586    ReturnValueU32 = 0x19C, Control, pops: 1, pushes: 0;
1587    /// Return with `i16` value — pops 1 raw i16 (in i64 slot),
1588    /// frame-cleans, pushes 1.
1589    ReturnValueI16 = 0x19D, Control, pops: 1, pushes: 0;
1590    /// Return with `u16` value — pops 1 raw u16 (in i64 slot),
1591    /// frame-cleans, pushes 1.
1592    ReturnValueU16 = 0x19E, Control, pops: 1, pushes: 0;
1593    /// Return with `i8` value — pops 1 raw i8 (in i64 slot),
1594    /// frame-cleans, pushes 1.
1595    ReturnValueI8 = 0x19F, Control, pops: 1, pushes: 0;
1596    /// Return with `u8` value — pops 1 raw u8 (in i64 slot),
1597    /// frame-cleans, pushes 1.
1598    ReturnValueU8 = 0x1A0, Control, pops: 1, pushes: 0;
1599    /// Return with `bool` value — pops 1 raw bool, frame-cleans, pushes 1.
1600    ReturnValueBool = 0x1A1, Control, pops: 1, pushes: 0;
1601    /// Return with `Ptr` value — pops 1 raw 8-byte heap-Arc pointer
1602    /// payload (paired with `NativeKind::Ptr(HeapKind::*)` per ADR-006
1603    /// §2.7.7), frame-cleans, pushes 1. Ownership transfer is by raw
1604    /// bit-level pass-through; the handler does NOT retain or release.
1605    ReturnValuePtr = 0x1A2, Control, pops: 1, pushes: 0;
1606
1607    // ===== Track A.1C.1: Shared outer-scope (`var`) cell opcodes =====
1608    //
1609    // These are the *outer-scope* counterpart to A.1B's capture-side
1610    // `LoadSharedCapture` / `StoreSharedCapture`. A.1B handles reads and
1611    // writes seen from *inside* a nested closure. A.1C handles the
1612    // owning-frame side of the same `Arc<parking_lot::Mutex<SharedCell>>`
1613    // cell: allocating it when the `var` binding is introduced, reading
1614    // and writing it from code executing in the declaring frame, and
1615    // releasing the Arc strong share at scope exit. The `SharedCell`
1616    // payload carries an inner-payload `NativeKind` per ADR-006 §2.7.8
1617    // / Q10 (the cell-storage parallel-kind extension); the deleted
1618    // dynamic-tag word that the cell historically held was retired with
1619    // the rest of the ValueWord layer.
1620    //
1621    // Operand layout: `Local(u16)` — indexes the declaring frame's
1622    // **stack slots** (not a capture-array index), i.e. the same
1623    // addressing mode as `LoadLocal` / `StoreLocal`. After
1624    // `AllocSharedLocal`, slot `slot` holds raw `*const SharedCell`
1625    // pointer bits (NOT inline-scalar bits — the slot's
1626    // `NativeKind::Ptr(HeapKind::SharedCell)` is the parallel-track
1627    // discriminator). Neither `LoadLocal` nor
1628    // `StoreLocal` may be used on such a slot — only the four opcodes
1629    // below. The compiler in A.1C.2 is responsible for emitting the
1630    // right opcode per reference after a `var` binding is promoted to
1631    // Shared storage.
1632    //
1633    // Lifecycle:
1634    //   1. `AllocSharedLocal { slot }` — sole allocator. Pops the
1635    //      initial value, boxes it in an `Arc<SharedCell>`, writes the
1636    //      `Arc::into_raw`-produced pointer bits into `slot`. The slot
1637    //      now owns one strong-count share.
1638    //   2. `LoadSharedLocal { slot }` / `StoreSharedLocal { slot }` —
1639    //      ordinary read/write through the mutex. The slot's pointer
1640    //      bits are never modified by these opcodes. Concurrency is
1641    //      mediated by the parking_lot mutex; the slot is the sole
1642    //      legal entry point for data access after Alloc.
1643    //   3. `DropSharedLocal { slot }` — sole releaser. Reads pointer
1644    //      bits, reconstructs `Arc::from_raw`, drops it (one atomic
1645    //      strong-count decrement), then overwrites the slot with
1646    //      `NONE_BITS` to mark it spent. The compiler emits this at
1647    //      scope exit for every `var` binding that was promoted.
1648    //
1649    // SAFETY invariants (enforced by the compiler A.1C.2 — these
1650    // opcodes trust the emitter):
1651    //   * `AllocSharedLocal` is emitted exactly once per `var` slot.
1652    //   * `LoadSharedLocal` / `StoreSharedLocal` only fire on a slot
1653    //     whose bits were installed by `AllocSharedLocal` and not yet
1654    //     consumed by `DropSharedLocal`.
1655    //   * `DropSharedLocal` is emitted exactly once per `var` slot on
1656    //     every path that leaves the owning scope (normal, break,
1657    //     return, panic-via-unwind — handled by scope-exit bytecode).
1658    //
1659    // A.1D / A.1E will lower these into Cranelift IR. Until then, the
1660    // JIT preflight gate (see `vm_only_opcode_reason` in
1661    // `crates/shape-jit/src/compiler/accessors.rs`) rejects functions
1662    // containing any of these four opcodes so they run on the
1663    // interpreter.
1664
1665    /// Pop the top-of-stack value (raw bits + payload `NativeKind`),
1666    /// allocate a fresh `Arc<parking_lot::Mutex<SharedCell>>`, and store
1667    /// the `Arc::into_raw` pointer bits into local slot `slot`.
1668    /// Operand: Local(idx). Sole allocator for Shared locals.
1669    AllocSharedLocal = 0x136, Variable, pops: 1, pushes: 0;
1670    /// Read the SharedCell pointer bits from local slot `slot`, take
1671    /// the parking_lot mutex for a read, clone the inner cell payload
1672    /// (raw bits + the cell's inner `NativeKind` per ADR-006 §2.7.8),
1673    /// drop the guard, push onto the stack. Operand: Local(idx).
1674    LoadSharedLocal = 0x137, Variable, pops: 0, pushes: 1;
1675    /// Pop a value (raw bits + payload `NativeKind`), read the
1676    /// SharedCell pointer bits from local slot `slot`, take the
1677    /// parking_lot mutex for a write, overwrite the inner cell
1678    /// payload, drop the guard. The slot's pointer bits are NOT
1679    /// modified. Operand: Local(idx).
1680    StoreSharedLocal = 0x138, Variable, pops: 1, pushes: 0;
1681    /// Read the SharedCell pointer bits from local slot `slot`,
1682    /// reconstruct `Arc::from_raw`, drop the Arc (one atomic
1683    /// strong-count decrement), and overwrite the slot with NONE_BITS
1684    /// to mark it spent. Operand: Local(idx). Sole releaser for Shared
1685    /// locals — emitted by the compiler at scope exit.
1686    DropSharedLocal = 0x139, Variable, pops: 0, pushes: 0;
1687
1688    // ===== Track A.1C.3: Shared outer-scope (`var`) cell opcodes =====
1689    // =====                for module-binding slots             =====
1690    //
1691    // Parallel module-binding counterpart to A.1C.1's local-slot Shared
1692    // opcodes. Module `var` bindings captured mutably by closures are
1693    // promoted into `Arc<parking_lot::Mutex<SharedCell>>` (same
1694    // `SharedCell` type — see ADR-006 §2.7.8 / Q10 for the cell-storage
1695    // parallel-kind extension that replaces the deleted dynamic-tag
1696    // word) stored in `module_bindings[idx]` as raw pointer bits. The
1697    // addressing mode is `Operand::ModuleBinding(u16)` instead of
1698    // `Operand::Local(u16)`; semantics otherwise mirror the local
1699    // counterparts.
1700    //
1701    // Lifecycle:
1702    //   1. `AllocSharedModuleBinding { idx }` — sole allocator. Pops
1703    //      the initial value, boxes it in an `Arc<SharedCell>`, writes
1704    //      the `Arc::into_raw`-produced pointer bits into
1705    //      `module_bindings[idx]`. Registers `idx` with the VM so
1706    //      VM-drop releases the Arc.
1707    //   2. `LoadSharedModuleBinding { idx }` /
1708    //      `StoreSharedModuleBinding { idx }` — ordinary read/write
1709    //      through the mutex.
1710    //
1711    // Unlike the local-scope counterparts, there is no explicit
1712    // `DropSharedModuleBinding` opcode: module bindings live for the
1713    // program's lifetime, so their Arcs are released once, at VM drop,
1714    // via the `shared_module_bindings` side-table on the VM.
1715    //
1716    // SAFETY invariants (enforced by the compiler — these opcodes trust
1717    // the emitter):
1718    //   * `AllocSharedModuleBinding` is emitted exactly once per
1719    //     module-binding slot that gets promoted.
1720    //   * `LoadSharedModuleBinding` / `StoreSharedModuleBinding` only
1721    //     fire on a slot whose bits were installed by
1722    //     `AllocSharedModuleBinding`. Plain `LoadModuleBinding` /
1723    //     `StoreModuleBinding` must not be emitted for a promoted
1724    //     slot — they would treat the raw `*const SharedCell`
1725    //     pointer bits as inline-scalar payload bits and dispatch
1726    //     would mis-route via the parallel-kind track.
1727    //
1728    // A.1D / A.1E will lower these into Cranelift IR. Until then, the
1729    // JIT preflight gate rejects functions containing any of these
1730    // three opcodes so they run on the interpreter.
1731
1732    /// Pop the top-of-stack value (raw bits + payload `NativeKind`),
1733    /// allocate a fresh `Arc<parking_lot::Mutex<SharedCell>>`, and
1734    /// store the `Arc::into_raw` pointer bits into
1735    /// `module_bindings[idx]`. Operand: ModuleBinding(idx). Sole
1736    /// allocator for Shared module bindings.
1737    AllocSharedModuleBinding = 0x13A, Variable, pops: 1, pushes: 0;
1738    /// Read the SharedCell pointer bits from `module_bindings[idx]`,
1739    /// take the parking_lot mutex for a read, clone the inner cell
1740    /// payload (raw bits + the cell's inner `NativeKind` per ADR-006
1741    /// §2.7.8), drop the guard, push onto the stack. Operand:
1742    /// ModuleBinding(idx).
1743    LoadSharedModuleBinding = 0x13B, Variable, pops: 0, pushes: 1;
1744    /// Pop a value (raw bits + payload `NativeKind`), read the
1745    /// SharedCell pointer bits from `module_bindings[idx]`, take the
1746    /// parking_lot mutex for a write, overwrite the inner cell
1747    /// payload, drop the guard. The slot's pointer bits are NOT
1748    /// modified. Operand: ModuleBinding(idx).
1749    StoreSharedModuleBinding = 0x13C, Variable, pops: 1, pushes: 0;
1750
1751    // ===== Closure Spec Phase F: escape-fallback dispatch =====
1752    //
1753    // These opcodes implement the v2 escape-fallback ABI (see
1754    // `docs/v2-closure-specialization.md` §1.3, §5.3, §5.4).
1755    //
1756    // The former `MakeClosureHeap` opcode was merged into `MakeClosure` in
1757    // Phase H5 — escape status is now carried by the operand variant
1758    // (`Operand::ClosureAlloc { escapes }`). See `MakeClosure` above.
1759    //
1760    // - `CallClosure(arity)`: direct dispatch on a closure value whose
1761    //   `ClosureTypeId` is known at the call site. Pops the closure pointer
1762    //   and `arity` args, binds captures + args to the callee's leading
1763    //   locals, and jumps to the callee's entry point.
1764    //
1765    // - `CallFunctionIndirect(arity)`: polymorphic dispatch through a
1766    //   `Function<A, R>` value. The operand carries the number of args; the
1767    //   `FunctionTypeId` is implicit from type inference (the JIT uses it to
1768    //   pick a `call_indirect` signature; the VM treats it as a sanity tag).
1769    //   Falls back to the same runtime path as `CallClosure` when the callee
1770    //   is a closure or `CallValue` when it's a bare function id.
1771    /// Direct dispatch on a closure value whose `ClosureTypeId` is statically
1772    /// known at the call site. Operand: Count(arity).
1773    ///
1774    /// Stack layout before: `[closure, arg0, arg1, ..., argN-1]`.
1775    /// Stack layout after: `[result]`.
1776    CallClosure = 0x123, Control, pops: 0, pushes: 0;
1777    /// Polymorphic dispatch through a `Function<A, R>` value. Operand:
1778    /// Count(arity). Same stack layout as `CallClosure`.
1779    CallFunctionIndirect = 0x124, Control, pops: 0, pushes: 0;
1780
1781    // ===== V1.1A: Ownership-aware Move/Clone/Drop (UNWIRED — V1.1B adds handlers) =====
1782    //
1783    // These opcodes are added to the enum table per the staged A/B/C/D gating
1784    // pattern described in `/home/dev/.claude/plans/i-want-a-complete-foamy-eich.md`
1785    // §V1.1A. V1.1A lands the enum variants only. V1.1B adds executor handlers,
1786    // V1.1C adds compiler emission behind a flag, V1.1D flips the default.
1787    //
1788    // See `docs/ownership-aware-runtime-v2.md` §Phase 1.1 for semantics.
1789    //
1790    // Operand: `Operand::Local(u16)` — the local slot to move/clone/drop.
1791    /// V1.1A (UNWIRED): Move value out of a local slot — transfers ownership
1792    /// without refcount bump, source slot is invalidated. Pushes the value.
1793    /// Executor handler added in V1.1B; currently unreachable via dispatch.
1794    MoveLocal = 0x125, Variable, pops: 0, pushes: 1;
1795    /// V1.1A (UNWIRED): Clone value from a local slot with refcount-aware
1796    /// semantics — for heap-tagged shared values bumps Arc refcount; for owned
1797    /// values performs a deep clone. Source stays live. Pushes the value.
1798    /// Executor handler added in V1.1B; currently unreachable via dispatch.
1799    CloneLocal = 0x126, Variable, pops: 0, pushes: 1;
1800    /// V1.1A (UNWIRED): Explicit drop of a local slot at scope exit — for
1801    /// owned heap values frees immediately; for shared values decrements the
1802    /// refcount. No stack effect.
1803    /// Executor handler added in V1.1B; currently unreachable via dispatch.
1804    DropLocal = 0x127, Variable, pops: 0, pushes: 0;
1805
1806    // ===== V1.2A: PromoteToShared (UNWIRED — V1.2B adds handler) =====
1807    //
1808    // Inverse of `PromoteToOwned` (0x107). Converts a Box-owned heap value on
1809    // top-of-stack into an Arc-shared one on demand (used when the value is
1810    // captured by an escaping closure, stored into a SharedCow slot, or
1811    // passed to a function expecting Arc-shared ownership).
1812    //
1813    // Staged A/B/C/D rollout per
1814    // `/home/dev/.claude/plans/i-want-a-complete-foamy-eich.md` §V1.2A:
1815    //   - V1.2A (this commit): enum variant only, dead.
1816    //   - V1.2B: executor handler wired to dispatch, still unused.
1817    //   - V1.2C: compiler emission behind a gating flag.
1818    //   - V1.2D: default flip after soak.
1819    //
1820    // See `docs/ownership-aware-runtime-v2.md` §Phase 3 for semantics.
1821    //
1822    // Operand: none — operates on the value already at top-of-stack and
1823    // mutates it in place (identical stack shape to `PromoteToOwned`).
1824    /// V1.2A (UNWIRED): Demote/promote the top-of-stack value from Box-owned
1825    /// to Arc-shared allocation. No-op for inline values or already-shared
1826    /// heap values. Executor handler added in V1.2B; currently unreachable
1827    /// via dispatch — reaching this opcode panics.
1828    PromoteToShared = 0x128, Stack, pops: 0, pushes: 0;
1829
1830    // ===== v2 Typed Field Access Operations =====
1831    /// Load f64 field from typed struct at byte offset. Operand: FieldOffset(u16). Pops struct_ptr, pushes f64.
1832    FieldLoadF64 = 0x82, Object, pops: 1, pushes: 1;
1833    /// Load i64 field from typed struct at byte offset. Operand: FieldOffset(u16). Pops struct_ptr, pushes i64.
1834    FieldLoadI64 = 0x83, Object, pops: 1, pushes: 1;
1835    /// Load i32 field from typed struct at byte offset. Operand: FieldOffset(u16). Pops struct_ptr, pushes i32.
1836    FieldLoadI32 = 0x84, Object, pops: 1, pushes: 1;
1837    /// Load bool field from typed struct at byte offset. Operand: FieldOffset(u16). Pops struct_ptr, pushes bool.
1838    FieldLoadBool = 0x85, Object, pops: 1, pushes: 1;
1839    /// Load ptr field from typed struct at byte offset. Operand: FieldOffset(u16). Pops struct_ptr, pushes ptr.
1840    FieldLoadPtr = 0x86, Object, pops: 1, pushes: 1;
1841    /// Store f64 field to typed struct at byte offset. Operand: FieldOffset(u16). Pops (struct_ptr, value).
1842    FieldStoreF64 = 0x87, Object, pops: 2, pushes: 0;
1843    /// Store i64 field to typed struct at byte offset. Operand: FieldOffset(u16). Pops (struct_ptr, value).
1844    FieldStoreI64 = 0x8B, Object, pops: 2, pushes: 0;
1845    /// Store i32 field to typed struct at byte offset. Operand: FieldOffset(u16). Pops (struct_ptr, value).
1846    FieldStoreI32 = 0x8C, Object, pops: 2, pushes: 0;
1847    /// Allocate a new typed struct. Operand: TypedObjectAlloc{schema_id, field_count}. Pushes ptr.
1848    NewTypedStruct = 0x8D, Object, pops: 0, pushes: 1;
1849
1850    // ===== v2 Sized Integer (i32) Arithmetic & Comparison =====
1851    /// Add (i32 x i32 -> i32)
1852    AddI32 = 0x1D, Arithmetic, pops: 2, pushes: 1;
1853    /// Subtract (i32 x i32 -> i32)
1854    SubI32 = 0x1E, Arithmetic, pops: 2, pushes: 1;
1855    /// Multiply (i32 x i32 -> i32)
1856    MulI32 = 0x1F, Arithmetic, pops: 2, pushes: 1;
1857    /// Divide (i32 x i32 -> i32)
1858    DivI32 = 0x9E, Arithmetic, pops: 2, pushes: 1;
1859    /// Modulo (i32 x i32 -> i32)
1860    ModI32 = 0x9F, Arithmetic, pops: 2, pushes: 1;
1861    /// Equal (i32 x i32 -> bool)
1862    EqI32 = 0xAA, Comparison, pops: 2, pushes: 1;
1863    /// Not equal (i32 x i32 -> bool)
1864    NeqI32 = 0xAB, Comparison, pops: 2, pushes: 1;
1865    /// Less than (i32 x i32 -> bool)
1866    LtI32 = 0xAC, Comparison, pops: 2, pushes: 1;
1867    /// Greater than (i32 x i32 -> bool)
1868    GtI32 = 0xAD, Comparison, pops: 2, pushes: 1;
1869    /// Less than or equal (i32 x i32 -> bool)
1870    LteI32 = 0xAE, Comparison, pops: 2, pushes: 1;
1871    /// Greater than or equal (i32 x i32 -> bool)
1872    GteI32 = 0xAF, Comparison, pops: 2, pushes: 1;
1873
1874    // ===== R5.1A: Typed bitwise opcodes (UNWIRED — R5.1B adds handlers) =====
1875    //
1876    // Phase R5.1A of the v2 residuals closeout. Mirrors the V1.1A staging
1877    // pattern: enum variants only, dead. R5.1B wires executor handlers,
1878    // R5.1C adds compiler emission behind `SHAPE_V2_TYPED_BITWISE=1`.
1879    //
1880    // These opcodes are the int-typed siblings of the existing dynamic
1881    // `BitAnd`/`BitOr`/`BitXor`/`BitShl`/`BitShr`/`BitNot` operations,
1882    // closing out the bitwise slice that historically routed through
1883    // `exec_arithmetic_dynamic_fallback` (the deleted dynamic-arith
1884    // handler family).
1885    //
1886    // Operand: none (simple instruction). Binary variants pop 2 / push 1;
1887    // `BitNotInt` is unary: pop 1 / push 1. Shift semantics match Shape's
1888    // existing `>>`/`<<` — `BitShrInt` is an arithmetic right-shift on i64,
1889    // matching the `a_int >> b_int` used by the dynamic `BitShr` handler.
1890    /// R5.1A (UNWIRED): Bitwise AND on two i64 values (int × int → int).
1891    /// Executor handler added in R5.1B; currently unreachable via dispatch.
1892    BitAndInt = 0x129, Arithmetic, pops: 2, pushes: 1;
1893    /// R5.1A (UNWIRED): Bitwise OR on two i64 values (int × int → int).
1894    /// Executor handler added in R5.1B; currently unreachable via dispatch.
1895    BitOrInt = 0x12A, Arithmetic, pops: 2, pushes: 1;
1896    /// R5.1A (UNWIRED): Bitwise XOR on two i64 values (int × int → int).
1897    /// Executor handler added in R5.1B; currently unreachable via dispatch.
1898    BitXorInt = 0x12B, Arithmetic, pops: 2, pushes: 1;
1899    /// R5.1A (UNWIRED): Bitwise shift-left on two i64 values (int × int → int).
1900    /// Executor handler added in R5.1B; currently unreachable via dispatch.
1901    BitShlInt = 0x12C, Arithmetic, pops: 2, pushes: 1;
1902    /// R5.1A (UNWIRED): Bitwise arithmetic shift-right on two i64 values
1903    /// (int × int → int). Matches Shape's `>>` operator semantics.
1904    /// Executor handler added in R5.1B; currently unreachable via dispatch.
1905    BitShrInt = 0x12D, Arithmetic, pops: 2, pushes: 1;
1906    /// R5.1A (UNWIRED): Bitwise NOT on an i64 value (int → int).
1907    /// Executor handler added in R5.1B; currently unreachable via dispatch.
1908    BitNotInt = 0x12E, Arithmetic, pops: 1, pushes: 1;
1909
1910    // ===== R5.5: Typed string+scalar concatenation =====
1911    //
1912    // Typed siblings of the legacy `AddDynamic` handler's string-coercion
1913    // branch (the deleted `exec_arithmetic_dynamic_fallback` →
1914    // `try_heap_arithmetic` Case 2 "string + scalar" path). Pops a heap
1915    // string LHS and a raw-scalar RHS, pushes a newly-allocated string.
1916    // The compiler emits these (R5.5) when `BinaryOp::Add` is proved to
1917    // have a `string` LHS and an `int` / `number` / `bool` RHS, in lieu
1918    // of the deleted dynamic fallback.
1919    //
1920    // Semantics match the pre-R5.5 fallback for int/number:
1921    //   * int → `format!("{}{}", lhs, rhs_i64)`
1922    //   * number → integer-formatted if `rhs.fract() == 0.0`, else default
1923    //     float formatting (mirrors the `n.fract() == 0.0` branch in the
1924    //     legacy fallback at arithmetic/mod.rs:1821).
1925    //   * bool → `"true"` / `"false"` (pre-R5.5 fell through the fallback
1926    //     and returned a garbage numeric coercion; R5.5 produces the
1927    //     canonical textual form). See R5.5 commit body.
1928    //
1929    // Category: Object (shared with `StringConcat` / `StringConcatTyped`,
1930    // matches their single-allocation heap-producing shape). Operand: none
1931    // (simple instruction). Stack effect: pop 2 / push 1.
1932    /// R5.5: Concatenate a heap string with an `int` scalar. Pops (string,
1933    /// i64 raw int), formats the int via `format!("{}{}", s, i)`, pushes a
1934    /// newly-allocated string. Compile-time proof of operand types — no tag
1935    /// checks beyond the string decode.
1936    StringConcatInt = 0x12F, Object, pops: 2, pushes: 1;
1937    /// R5.5: Concatenate a heap string with a `number` scalar. Pops (string,
1938    /// raw f64), formats the number via the same integer-fast-path logic as
1939    /// the legacy fallback (whole numbers render without a decimal), pushes a
1940    /// newly-allocated string. Compile-time proof of operand types.
1941    StringConcatNumber = 0x130, Object, pops: 2, pushes: 1;
1942    /// R5.5: Concatenate a heap string with a `bool` scalar. Pops (string,
1943    /// raw bool), formats the bool as `"true"` / `"false"`, pushes a
1944    /// newly-allocated string. Compile-time proof of operand types.
1945    StringConcatBool = 0x131, Object, pops: 2, pushes: 1;
1946
1947}
1948
1949impl OpCode {
1950    /// Returns true if this is a trusted opcode variant (compiler-proved types, no runtime guard).
1951    pub const fn is_trusted(self) -> bool {
1952        matches!(
1953            self,
1954            OpCode::LoadLocalTrusted | OpCode::JumpIfFalseTrusted
1955        )
1956    }
1957
1958    /// Map a trusted opcode back to its guarded (runtime-checked) counterpart.
1959    ///
1960    /// This is the inverse of `trusted_variant()`: given a trusted opcode, it
1961    /// returns the equivalent guarded opcode. Used for differential testing and
1962    /// bytecode post-processing.
1963    pub const fn guarded_variant(self) -> Option<OpCode> {
1964        match self {
1965            OpCode::LoadLocalTrusted => Some(OpCode::LoadLocal),
1966            OpCode::JumpIfFalseTrusted => Some(OpCode::JumpIfFalse),
1967            _ => None,
1968        }
1969    }
1970
1971    /// Returns true if this is a v2 typed opcode (typed arrays, typed fields, sized integers).
1972    /// These opcodes carry their type in the opcode name and require the v2 runtime path.
1973    pub const fn is_v2_typed(self) -> bool {
1974        matches!(
1975            self,
1976            // Typed array operations
1977            OpCode::NewTypedArrayF64
1978            | OpCode::NewTypedArrayI64
1979            | OpCode::NewTypedArrayI32
1980            | OpCode::NewTypedArrayBool
1981            | OpCode::TypedArrayGetF64
1982            | OpCode::TypedArrayGetI64
1983            | OpCode::TypedArrayGetI32
1984            | OpCode::TypedArrayGetBool
1985            | OpCode::TypedArraySetF64
1986            | OpCode::TypedArraySetI64
1987            | OpCode::TypedArraySetI32
1988            | OpCode::TypedArraySetBool
1989            | OpCode::TypedArrayPushF64
1990            | OpCode::TypedArrayPushI64
1991            | OpCode::TypedArrayPushI32
1992            | OpCode::TypedArrayPushBool
1993            | OpCode::TypedArrayLen
1994            // W12 S1 — sized-integer typed array opcodes (I8/U8/I16/U16/U32/U64)
1995            | OpCode::NewTypedArrayI8
1996            | OpCode::TypedArrayGetI8
1997            | OpCode::TypedArrayPushI8
1998            | OpCode::TypedArraySetI8
1999            | OpCode::NewTypedArrayU8
2000            | OpCode::TypedArrayGetU8
2001            | OpCode::TypedArrayPushU8
2002            | OpCode::TypedArraySetU8
2003            | OpCode::NewTypedArrayI16
2004            | OpCode::TypedArrayGetI16
2005            | OpCode::TypedArrayPushI16
2006            | OpCode::TypedArraySetI16
2007            | OpCode::NewTypedArrayU16
2008            | OpCode::TypedArrayGetU16
2009            | OpCode::TypedArrayPushU16
2010            | OpCode::TypedArraySetU16
2011            | OpCode::NewTypedArrayU32
2012            | OpCode::TypedArrayGetU32
2013            | OpCode::TypedArrayPushU32
2014            | OpCode::TypedArraySetU32
2015            // U64 typed-array opcodes intentionally NOT minted — deferred to
2016            // S1.5 per supervisor's S1 reopen; see comment block in the
2017            // opcode-definition table above.
2018            // Wave 2 Agent A1 (2026-05-14) — F32 + Char monomorphizations.
2019            | OpCode::NewTypedArrayF32
2020            | OpCode::TypedArrayGetF32
2021            | OpCode::TypedArrayPushF32
2022            | OpCode::TypedArraySetF32
2023            | OpCode::NewTypedArrayChar
2024            | OpCode::TypedArrayGetChar
2025            | OpCode::TypedArrayPushChar
2026            | OpCode::TypedArraySetChar
2027            // Wave 2 Agent A2 (2026-05-14) — String + Decimal heap-element monomorphizations.
2028            | OpCode::NewTypedArrayString
2029            | OpCode::TypedArrayGetString
2030            | OpCode::TypedArrayPushString
2031            | OpCode::TypedArraySetString
2032            | OpCode::NewTypedArrayDecimal
2033            | OpCode::TypedArrayGetDecimal
2034            | OpCode::TypedArrayPushDecimal
2035            | OpCode::TypedArraySetDecimal
2036            // Phase 4b Round 4 W16.2-A op_new_array-typed-object-element (2026-05-18).
2037            | OpCode::NewTypedArrayTypedObject
2038            | OpCode::TypedArrayGetTypedObject
2039            | OpCode::TypedArrayPushTypedObject
2040            | OpCode::TypedArraySetTypedObject
2041            // Local-slot-based typed array element access
2042            | OpCode::GetElemI64
2043            | OpCode::GetElemF64
2044            | OpCode::SetElemI64
2045            | OpCode::SetElemF64
2046            | OpCode::ArrayPushI64
2047            | OpCode::ArrayPushF64
2048            | OpCode::ArrayLenTyped
2049            // Local-slot-based typed HashMap access
2050            | OpCode::MapGetStrI64
2051            | OpCode::MapGetStrF64
2052            | OpCode::MapSetStrI64
2053            | OpCode::MapHasStr
2054            | OpCode::MapLenTyped
2055            // Local-slot-based typed String access
2056            | OpCode::StringLenTyped
2057            | OpCode::StringCharAt
2058            | OpCode::StringConcatTyped
2059            // Typed field access
2060            | OpCode::FieldLoadF64
2061            | OpCode::FieldLoadI64
2062            | OpCode::FieldLoadI32
2063            | OpCode::FieldLoadBool
2064            | OpCode::FieldLoadPtr
2065            | OpCode::FieldStoreF64
2066            | OpCode::FieldStoreI64
2067            | OpCode::FieldStoreI32
2068            | OpCode::NewTypedStruct
2069            // Sized integer i32 arithmetic
2070            | OpCode::AddI32
2071            | OpCode::SubI32
2072            | OpCode::MulI32
2073            | OpCode::DivI32
2074            | OpCode::ModI32
2075            | OpCode::EqI32
2076            | OpCode::NeqI32
2077            | OpCode::LtI32
2078            | OpCode::GtI32
2079            | OpCode::LteI32
2080            | OpCode::GteI32
2081        )
2082    }
2083
2084    /// Map a guarded typed opcode to its trusted variant (if one exists).
2085    pub const fn trusted_variant(self) -> Option<OpCode> {
2086        match self {
2087            OpCode::LoadLocal => Some(OpCode::LoadLocalTrusted),
2088            OpCode::JumpIfFalse => Some(OpCode::JumpIfFalseTrusted),
2089            _ => None,
2090        }
2091    }
2092}
2093
2094/// Numeric width tag for compact typed opcodes (AddTyped, SubTyped, etc.).
2095///
2096/// Encodes the operand width so a single opcode family can handle all
2097/// numeric types.  The discriminant values are part of the bytecode ABI
2098/// and must remain stable.
2099#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2100#[repr(u8)]
2101pub enum NumericWidth {
2102    I8 = 0,
2103    I16 = 1,
2104    I32 = 2,
2105    I64 = 3,
2106    U8 = 4,
2107    U16 = 5,
2108    U32 = 6,
2109    U64 = 7,
2110    F32 = 8,
2111    F64 = 9,
2112}
2113
2114impl NumericWidth {
2115    pub const ALL: [Self; 10] = [
2116        Self::I8,
2117        Self::I16,
2118        Self::I32,
2119        Self::I64,
2120        Self::U8,
2121        Self::U16,
2122        Self::U32,
2123        Self::U64,
2124        Self::F32,
2125        Self::F64,
2126    ];
2127
2128    #[inline(always)]
2129    pub const fn is_integer(self) -> bool {
2130        matches!(
2131            self,
2132            Self::I8
2133                | Self::I16
2134                | Self::I32
2135                | Self::I64
2136                | Self::U8
2137                | Self::U16
2138                | Self::U32
2139                | Self::U64
2140        )
2141    }
2142
2143    #[inline(always)]
2144    pub const fn is_float(self) -> bool {
2145        matches!(self, Self::F32 | Self::F64)
2146    }
2147
2148    /// Whether this is a signed integer type.
2149    #[inline(always)]
2150    pub const fn is_signed(self) -> bool {
2151        matches!(self, Self::I8 | Self::I16 | Self::I32 | Self::I64)
2152    }
2153
2154    /// Whether this is an unsigned integer type.
2155    #[inline(always)]
2156    pub const fn is_unsigned(self) -> bool {
2157        matches!(self, Self::U8 | Self::U16 | Self::U32 | Self::U64)
2158    }
2159
2160    /// Number of bits for this width.
2161    #[inline(always)]
2162    pub const fn bits(self) -> u32 {
2163        match self {
2164            Self::I8 | Self::U8 => 8,
2165            Self::I16 | Self::U16 => 16,
2166            Self::I32 | Self::U32 | Self::F32 => 32,
2167            Self::I64 | Self::U64 | Self::F64 => 64,
2168        }
2169    }
2170
2171    /// Bit mask for the integer value range.
2172    #[inline(always)]
2173    pub const fn mask(self) -> u64 {
2174        match self {
2175            Self::I8 | Self::U8 => 0xFF,
2176            Self::I16 | Self::U16 => 0xFFFF,
2177            Self::I32 | Self::U32 | Self::F32 => 0xFFFF_FFFF,
2178            Self::I64 | Self::U64 | Self::F64 => u64::MAX,
2179        }
2180    }
2181
2182    /// Convert from IntWidth (shape-ast) to NumericWidth.
2183    #[inline]
2184    pub fn from_int_width(w: shape_ast::IntWidth) -> Self {
2185        match w {
2186            shape_ast::IntWidth::I8 => Self::I8,
2187            shape_ast::IntWidth::U8 => Self::U8,
2188            shape_ast::IntWidth::I16 => Self::I16,
2189            shape_ast::IntWidth::U16 => Self::U16,
2190            shape_ast::IntWidth::I32 => Self::I32,
2191            shape_ast::IntWidth::U32 => Self::U32,
2192            shape_ast::IntWidth::U64 => Self::U64,
2193        }
2194    }
2195
2196    /// Convert to IntWidth (shape-ast). Returns None for F32/F64/I64.
2197    #[inline]
2198    pub fn to_int_width(self) -> Option<shape_ast::IntWidth> {
2199        match self {
2200            Self::I8 => Some(shape_ast::IntWidth::I8),
2201            Self::U8 => Some(shape_ast::IntWidth::U8),
2202            Self::I16 => Some(shape_ast::IntWidth::I16),
2203            Self::U16 => Some(shape_ast::IntWidth::U16),
2204            Self::I32 => Some(shape_ast::IntWidth::I32),
2205            Self::U32 => Some(shape_ast::IntWidth::U32),
2206            Self::U64 => Some(shape_ast::IntWidth::U64),
2207            Self::I64 | Self::F32 | Self::F64 => None,
2208        }
2209    }
2210}
2211
2212/// A bytecode instruction with its operands
2213#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
2214pub struct Instruction {
2215    pub opcode: OpCode,
2216    pub operand: Option<Operand>,
2217}
2218
2219/// Instruction operands
2220#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
2221pub enum Operand {
2222    /// Constant pool index
2223    Const(u16),
2224    /// Local variable index
2225    Local(u16),
2226    /// ModuleBinding variable index
2227    ModuleBinding(u16),
2228    /// Jump offset (can be negative)
2229    Offset(i32),
2230    /// Function index
2231    Function(shape_value::FunctionId),
2232    /// Built-in function ID
2233    Builtin(BuiltinFunction),
2234    /// Number of arguments/elements
2235    Count(u16),
2236    /// Property name index
2237    Property(u16),
2238    /// Column index for DataFrame field access (compile-time resolved)
2239    ColumnIndex(u32),
2240    /// Typed field access (type_id, field_idx, field_type_tag)
2241    /// Used with GetFieldTyped/SetFieldTyped for optimized field access.
2242    /// field_type_tag encodes the FieldType so the executor can read slots
2243    /// without a runtime schema lookup.
2244    TypedField {
2245        type_id: u16,
2246        field_idx: u16,
2247        field_type_tag: u16,
2248    },
2249    /// Typed object allocation
2250    /// Used with NewTypedObject for creating TypedObject instances
2251    TypedObjectAlloc {
2252        /// Schema ID identifying the type layout
2253        schema_id: u16,
2254        /// Number of fields to pop from stack
2255        field_count: u16,
2256    },
2257    /// Typed object merge (compile-time registered intersection schema)
2258    /// Used with TypedMergeObject for O(1) merge operations
2259    TypedMerge {
2260        /// Schema ID for the merged result (pre-registered at compile time)
2261        target_schema_id: u16,
2262        /// Byte size of left operand data
2263        left_size: u16,
2264        /// Byte size of right operand data
2265        right_size: u16,
2266    },
2267    /// Typed column access on a RowView
2268    /// Used with LoadColF64/I64/Bool/Str for direct Arrow buffer reads
2269    ColumnAccess {
2270        /// Column index in the Arrow schema
2271        col_id: u32,
2272    },
2273    /// A named reference (e.g., trait name for BoxTraitObject)
2274    Name(StringId),
2275    /// Typed method call using compile-time resolved MethodId.
2276    /// For `MethodId::DYNAMIC`, the VM falls back to string lookup
2277    /// using `string_id` from the string pool.
2278    TypedMethodCall {
2279        /// Compile-time resolved method identifier
2280        method_id: u16,
2281        /// Number of arguments (not counting receiver)
2282        arg_count: u16,
2283        /// String pool index for the method name (used for dynamic fallback
2284        /// and error messages)
2285        string_id: u16,
2286        /// Compile-time resolved receiver type tag (ConcreteType::type_tag()).
2287        /// 0xFF = unknown (triggers runtime tag/HeapKind dispatch fallback).
2288        receiver_type_tag: u8,
2289    },
2290    /// Foreign function index — indexes into program.foreign_functions
2291    ForeignFunction(u16),
2292    /// Matrix dimensions (rows, cols) for NewMatrix opcode
2293    MatrixDims { rows: u16, cols: u16 },
2294    /// Numeric width tag for compact typed opcodes (AddTyped, SubTyped, etc.)
2295    Width(NumericWidth),
2296    /// Local index + width for StoreLocalTyped
2297    TypedLocal(u16, NumericWidth),
2298    /// Module binding index + width for StoreModuleBindingTyped
2299    TypedModuleBinding(u16, NumericWidth),
2300    /// Byte offset into a typed struct for FieldLoad/FieldStore v2 opcodes
2301    FieldOffset(u16),
2302    /// Closure allocation operand used exclusively by `MakeClosure` (Phase H5).
2303    ///
2304    /// Carries both the function id and a compile-time escape flag. The flag is
2305    /// read at MIR lowering time to pick between stack-allocated (Phase E) and
2306    /// heap-allocated (Phase H2) codegen; the interpreter ignores it (both
2307    /// variants build a heap closure in the VM).
2308    ///
2309    /// `Operand::Function(fid)` is also accepted by `MakeClosure` and is
2310    /// equivalent to `ClosureAlloc { fid, escapes: false }` — the compiler
2311    /// emits the richer form only when the storage planner has concluded the
2312    /// closure escapes.
2313    ClosureAlloc {
2314        fid: shape_value::FunctionId,
2315        escapes: bool,
2316    },
2317}
2318
2319/// Built-in functions
2320#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2321pub enum BuiltinFunction {
2322    // Math functions
2323    Abs,
2324    Sqrt,
2325    Ln,
2326    Pow,
2327    Exp,
2328    Log,
2329    Min,
2330    Max,
2331    Floor,
2332    Ceil,
2333    Round,
2334    Sin,
2335    Cos,
2336    Tan,
2337    Asin,
2338    Acos,
2339    Atan,
2340
2341    // Statistical functions
2342    StdDev,
2343
2344    // Array functions
2345    Range,
2346    Slice,
2347    Push,
2348    Pop,
2349    First,
2350    Last,
2351    Zip,
2352    Filled,
2353
2354    // Array method-style functions
2355    Map,
2356    Filter,
2357    Reduce,
2358    ForEach,
2359    Find,
2360    FindIndex,
2361    Some,
2362    Every,
2363
2364    // Utility functions
2365    Print,
2366    Format,
2367    // Len removed: use x.len() method form via per-type PHF dispatch
2368    // Throw removed: Shape uses Result types
2369    Snapshot,
2370    Exit,
2371
2372    // Object functions
2373    ObjectRest,
2374
2375    // Control flow functions
2376    ControlFold,
2377
2378    // Type functions
2379    TypeOf,
2380    IsNumber,
2381    IsString,
2382    IsBool,
2383    IsArray,
2384    IsObject,
2385    IsDataRow,
2386
2387    // Conversion
2388    ToString,
2389    ToNumber,
2390    ToBool,
2391
2392    // Native C/Arrow interop helpers
2393    NativePtrSize,
2394    NativePtrNewCell,
2395    NativePtrFreeCell,
2396    NativePtrReadPtr,
2397    NativePtrWritePtr,
2398    NativeTableFromArrowC,
2399    NativeTableFromArrowCTyped,
2400    NativeTableBindType,
2401    /// Format a value respecting meta formatting for TypeAnnotatedValues.
2402    /// Used by string interpolation to apply custom formatters.
2403    FormatValueWithMeta,
2404    /// Format a value using a typed interpolation format spec.
2405    /// Used by string interpolation for spec-aware rendering (fixed/table).
2406    FormatValueWithSpec,
2407    /// R8 W4 W18.4 (supervisor 2026-05-24 D1 + (a-modified) REVIVE-WITH-
2408    /// SHARED-MODULE): wrap a stack-top string as a plain (unstyled)
2409    /// `ContentNode::Text` for f-string content-lowering. Pops `[string]`,
2410    /// pushes `Ptr(HeapKind::Content)`. Used for literal segments of a
2411    /// styled f-string (e.g. the `"hello "` in `f"hello {x:bold,red}"`).
2412    FStringContentText,
2413    /// R8 W4 W18.4: wrap a stack-top string as a styled `ContentNode::Text`
2414    /// with the encoded style. Pops `[value_str, fg_kind, fg_payload,
2415    /// bg_kind, bg_payload, flags]` (6 args). `fg_kind`/`bg_kind`: -1=none,
2416    /// 0=named, 1=rgb. `fg_payload`/`bg_payload`: named-color id 0..7 or
2417    /// `(r<<16)|(g<<8)|b`. `flags`: bitmask (bold=1, italic=2, underline=4,
2418    /// dim=8). Pushes `Ptr(HeapKind::Content)`.
2419    FStringContentStyledText,
2420    /// R8 W4 W18.4: combine N stack content values into a
2421    /// `ContentNode::Fragment`. Pops N `Ptr(HeapKind::Content)` slots,
2422    /// pushes the fragment. The arg-count slot encodes N per the standard
2423    /// `pop_builtin_args` ABI.
2424    FStringContentFragment,
2425
2426    // Optimization
2427    IntrinsicMinimize,
2428    // Math intrinsics (6 functions)
2429    // W12-stdlib-intrinsic-collapse (Wave-2-Agent-G, 2026-05-14): deleted
2430    // `IntrinsicSum` per the parallel-implementation defection close —
2431    // PHF `.sum()` (array_aggregation::handle_sum_v2, typed_array_methods
2432    // ::v2_int_sum/v2_float_sum, matrix_methods::v2_sum, etc.) is the
2433    // canonical single-discriminator dispatch surface (ADR-005 §1).
2434    IntrinsicBspline2_3dBatch,
2435    IntrinsicMean,
2436    IntrinsicMin,
2437    IntrinsicMax,
2438    IntrinsicStd,
2439    IntrinsicVariance,
2440
2441    // Random number generation intrinsics (5 functions)
2442    IntrinsicRandom,
2443    IntrinsicRandomInt,
2444    IntrinsicRandomSeed,
2445    IntrinsicRandomNormal,
2446    IntrinsicRandomArray,
2447
2448    // Distribution intrinsics (5 functions)
2449    IntrinsicDistUniform,
2450    IntrinsicDistLognormal,
2451    IntrinsicDistExponential,
2452    IntrinsicDistPoisson,
2453    IntrinsicDistSampleN,
2454
2455    // Stochastic process intrinsics (4 functions)
2456    IntrinsicBrownianMotion,
2457    IntrinsicGbm,
2458    IntrinsicOuProcess,
2459    IntrinsicRandomWalk,
2460
2461    // Rolling window intrinsics (6 functions)
2462    IntrinsicRollingSum,
2463    IntrinsicRollingMean,
2464    IntrinsicRollingStd,
2465    IntrinsicRollingMin,
2466    IntrinsicRollingMax,
2467    IntrinsicEma,
2468    IntrinsicLinearRecurrence,
2469
2470    // Series transformation intrinsics (7 functions)
2471    IntrinsicShift,
2472    IntrinsicDiff,
2473    IntrinsicPctChange,
2474    IntrinsicFillna,
2475    IntrinsicCumsum,
2476    IntrinsicCumprod,
2477    IntrinsicClip,
2478
2479    // Statistical intrinsics (4 functions)
2480    IntrinsicCorrelation,
2481    IntrinsicCovariance,
2482    IntrinsicPercentile,
2483    IntrinsicMedian,
2484
2485    // Trigonometric intrinsics (4 functions)
2486    IntrinsicAtan2,
2487    IntrinsicSinh,
2488    IntrinsicCosh,
2489    IntrinsicTanh,
2490
2491    // Character code intrinsics
2492    IntrinsicCharCode,
2493    IntrinsicFromCharCode,
2494
2495    // Series access (critical for backtesting!)
2496    IntrinsicSeries,
2497
2498    // Vector intrinsics (10 functions)
2499    IntrinsicVecAbs,
2500    IntrinsicVecSqrt,
2501    IntrinsicVecLn,
2502    IntrinsicVecExp,
2503    IntrinsicVecAdd,
2504    IntrinsicVecSub,
2505    IntrinsicVecMul,
2506    IntrinsicVecDiv,
2507    IntrinsicVecMax,
2508    IntrinsicVecMin,
2509    IntrinsicVecSelect,
2510    /// `Vec<int> + Vec<int>` — element-wise, overflow-checked (R5.4D).
2511    /// Mirrors the dynamic-fallback `TypedArrayData::I64 + I64` arm:
2512    /// returns an `IntArray` and surfaces an overflow error when any
2513    /// element pair saturates (see `simd_vec_add_i64`). Wired up here as
2514    /// scaffolding; compiler emission arrives in R5.4E.
2515    IntrinsicVecAddI64,
2516
2517    // Matrix intrinsics (4 functions)
2518    IntrinsicMatMulVec,
2519    IntrinsicMatMulMat,
2520    /// `Mat<number> + Mat<number>` — element-wise (R5.4D). Dispatches to
2521    /// `matrix_kernels::matrix_add` after extracting nested-array input
2522    /// via `extract_matrix_f64`. Returns a matrix in the nested-array
2523    /// shape that R5.4B's `Mat<number>` literals produce. Unwired from
2524    /// the compiler side; emission lands in R5.4E.
2525    IntrinsicMatAdd,
2526    /// `Mat<number> - Mat<number>` — element-wise (R5.4D). Companion to
2527    /// `IntrinsicMatAdd`, dispatches to `matrix_kernels::matrix_sub`.
2528    IntrinsicMatSub,
2529
2530    // Internal evaluation helpers
2531    EvalTimeRef,
2532    EvalDateTimeExpr,
2533    EvalDataDateTimeRef,
2534    EvalDataSet,
2535    EvalDataRelative,
2536    EvalDataRelativeRange,
2537
2538    // Option type constructors
2539    SomeCtor,
2540    OkCtor,
2541    ErrCtor,
2542
2543    // Collection constructors
2544    HashMapCtor,
2545    SetCtor,
2546    DequeCtor,
2547    PriorityQueueCtor,
2548
2549    // Json navigation helpers (used by std::core::json_value extend block)
2550    JsonObjectGet,
2551    JsonArrayAt,
2552    JsonObjectKeys,
2553    JsonArrayLen,
2554    JsonObjectLen,
2555
2556    // Window functions (SQL-style)
2557    WindowRowNumber,
2558    WindowRank,
2559    WindowDenseRank,
2560    WindowNtile,
2561    WindowLag,
2562    WindowLead,
2563    WindowFirstValue,
2564    WindowLastValue,
2565    WindowNthValue,
2566    WindowSum,
2567    WindowAvg,
2568    WindowMin,
2569    WindowMax,
2570    WindowCount,
2571
2572    // JOIN operations
2573    JoinExecute,
2574
2575    // Reflection
2576    Reflect,
2577
2578    // Content namespace constructors
2579    /// Content.chart(type_str) — create a chart ContentNode
2580    ContentChart,
2581    /// Content.text(str) — create a plain text ContentNode
2582    ContentTextCtor,
2583    /// Content.table(headers, rows) — create a table ContentNode
2584    ContentTableCtor,
2585    /// Content.code(language, source) — create a code block ContentNode
2586    ContentCodeCtor,
2587    /// Content.kv(pairs) — create a key-value ContentNode
2588    ContentKvCtor,
2589    /// Content.fragment(parts) — create a fragment ContentNode
2590    ContentFragmentCtor,
2591
2592    // DateTime constructors
2593    /// DateTime.now() — current local time as DateTime<FixedOffset>
2594    DateTimeNow,
2595    /// DateTime.utc() — current UTC time as DateTime<FixedOffset> at +00:00
2596    DateTimeUtc,
2597    /// DateTime.parse(str) — parse from string (ISO 8601, RFC 2822, common formats)
2598    DateTimeParse,
2599    /// DateTime.from_epoch(ms) — from milliseconds since Unix epoch
2600    DateTimeFromEpoch,
2601    /// DateTime.from_parts(year, month, day, hour?, minute?, second?) — construct from components
2602    DateTimeFromParts,
2603    /// DateTime.from_unix_secs(secs) — from seconds since Unix epoch
2604    DateTimeFromUnixSecs,
2605
2606    // Concurrency primitive constructors
2607    /// Mutex(value) — create a new mutex wrapping the given value
2608    MutexCtor,
2609    /// Atomic(value) — create a new atomic integer with the given initial value
2610    AtomicCtor,
2611    /// Lazy(initializer) — create a lazy value with the given initializer closure
2612    LazyCtor,
2613    /// Channel() — create a new MPSC channel, returns [sender, receiver] array
2614    ChannelCtor,
2615
2616    // Additional math builtins
2617    /// sign(x) — returns -1, 0, or 1
2618    Sign,
2619    /// gcd(a, b) — greatest common divisor
2620    Gcd,
2621    /// lcm(a, b) — least common multiple
2622    Lcm,
2623    /// hypot(a, b) — hypotenuse sqrt(a^2 + b^2)
2624    Hypot,
2625    /// clamp(x, min, max) — clamp value between min and max
2626    Clamp,
2627    /// isNaN(x) — check if value is NaN
2628    IsNaN,
2629    /// isFinite(x) — check if value is finite
2630    IsFinite,
2631
2632    /// mat(rows, cols, ...values) — create a Matrix from flat f64 values
2633    MatFromFlat,
2634
2635    // Table construction (1)
2636    /// Build a TypedTable from inline row values: args = [schema_id, row_count, field_count, val1, val2, ...]
2637    MakeTableFromRows,
2638
2639    // W18.5 per-type content builder constructors (supervisor D4, 2026-05-24)
2640    //
2641    // `Table::new()` / `Code::new()` / `KeyValue::new()` — return an empty
2642    // ContentNode of the matching variant as a `Ptr(HeapKind::Content)`
2643    // slot. Chainable methods (`headers`, `row`, `border`, `language`,
2644    // `source`, `pair`, `build`) are dispatched via `CONTENT_METHODS` PHF
2645    // on the receiver (`content_methods.rs`). `.build()` is identity —
2646    // returns the receiver unchanged. The build → content → renderer path
2647    // is the "shortest path" deliverable per supervisor D4 (Chart builder
2648    // remains scoped to v0.4 / ECharts integration).
2649    //
2650    // Sibling-ctor pattern follows W18.6 ContentTextCtor / ContentCodeCtor
2651    // (shipped at R8 W3, builtins.rs:769–824). The receiver Content slot
2652    // round-trips through the `KindedSlot::from_content` constructor; the
2653    // chainable methods take Content receivers, immutably clone + mutate
2654    // the underlying ContentNode, and return a fresh Content slot —
2655    // ADR-005 §1 / ADR-006 §2.3 typed-Arc dispatch, no parallel
2656    // discriminator.
2657    /// Table::new() — return an empty `ContentNode::Table` builder seed
2658    TableBuilderNew,
2659    /// Code::new() — return an empty `ContentNode::Code` builder seed
2660    CodeBuilderNew,
2661    /// KeyValue::new() — return an empty `ContentNode::KeyValue` builder seed
2662    KeyValueBuilderNew,
2663}
2664
2665impl BuiltinFunction {
2666    /// Convert a discriminant value back to a BuiltinFunction variant.
2667    ///
2668    /// Used by the JIT generic builtin trampoline: the translator encodes
2669    /// the builtin as `*builtin as u16` and the FFI function converts it
2670    /// back at runtime.
2671    pub fn from_discriminant(id: u16) -> Option<Self> {
2672        // Ordered to match the enum declaration order (discriminants 0, 1, 2, ...)
2673        const VARIANTS: &[BuiltinFunction] = &[
2674            // Math (18) — discriminants 0..17
2675            BuiltinFunction::Abs,
2676            BuiltinFunction::Sqrt,
2677            BuiltinFunction::Ln,
2678            BuiltinFunction::Pow,
2679            BuiltinFunction::Exp,
2680            BuiltinFunction::Log,
2681            BuiltinFunction::Min,
2682            BuiltinFunction::Max,
2683            BuiltinFunction::Floor,
2684            BuiltinFunction::Ceil,
2685            BuiltinFunction::Round,
2686            BuiltinFunction::Sin,
2687            BuiltinFunction::Cos,
2688            BuiltinFunction::Tan,
2689            BuiltinFunction::Asin,
2690            BuiltinFunction::Acos,
2691            BuiltinFunction::Atan,
2692            // Stats (1)
2693            BuiltinFunction::StdDev,
2694            // Array (8)
2695            BuiltinFunction::Range,
2696            BuiltinFunction::Slice,
2697            BuiltinFunction::Push,
2698            BuiltinFunction::Pop,
2699            BuiltinFunction::First,
2700            BuiltinFunction::Last,
2701            BuiltinFunction::Zip,
2702            BuiltinFunction::Filled,
2703            // HOF (8)
2704            BuiltinFunction::Map,
2705            BuiltinFunction::Filter,
2706            BuiltinFunction::Reduce,
2707            BuiltinFunction::ForEach,
2708            BuiltinFunction::Find,
2709            BuiltinFunction::FindIndex,
2710            BuiltinFunction::Some,
2711            BuiltinFunction::Every,
2712            // Utility (4)
2713            BuiltinFunction::Print,
2714            BuiltinFunction::Format,
2715            BuiltinFunction::Snapshot,
2716            BuiltinFunction::Exit,
2717            // Object (1)
2718            BuiltinFunction::ObjectRest,
2719            // Control (1)
2720            BuiltinFunction::ControlFold,
2721            // Type (7)
2722            BuiltinFunction::TypeOf,
2723            BuiltinFunction::IsNumber,
2724            BuiltinFunction::IsString,
2725            BuiltinFunction::IsBool,
2726            BuiltinFunction::IsArray,
2727            BuiltinFunction::IsObject,
2728            BuiltinFunction::IsDataRow,
2729            // Conversion (3)
2730            BuiltinFunction::ToString,
2731            BuiltinFunction::ToNumber,
2732            BuiltinFunction::ToBool,
2733            // Native ptr (8)
2734            BuiltinFunction::NativePtrSize,
2735            BuiltinFunction::NativePtrNewCell,
2736            BuiltinFunction::NativePtrFreeCell,
2737            BuiltinFunction::NativePtrReadPtr,
2738            BuiltinFunction::NativePtrWritePtr,
2739            BuiltinFunction::NativeTableFromArrowC,
2740            BuiltinFunction::NativeTableFromArrowCTyped,
2741            BuiltinFunction::NativeTableBindType,
2742            // Format (2)
2743            BuiltinFunction::FormatValueWithMeta,
2744            BuiltinFunction::FormatValueWithSpec,
2745            // R8 W4 W18.4: f-string content-lowering builtins (3)
2746            BuiltinFunction::FStringContentText,
2747            BuiltinFunction::FStringContentStyledText,
2748            BuiltinFunction::FStringContentFragment,
2749            // Optimization
2750            BuiltinFunction::IntrinsicMinimize,
2751            // Math intrinsics (6) — W12-stdlib-intrinsic-collapse close
2752            // deleted `IntrinsicSum`; PHF `.sum()` is canonical.
2753            BuiltinFunction::IntrinsicBspline2_3dBatch,
2754            BuiltinFunction::IntrinsicMean,
2755            BuiltinFunction::IntrinsicMin,
2756            BuiltinFunction::IntrinsicMax,
2757            BuiltinFunction::IntrinsicStd,
2758            BuiltinFunction::IntrinsicVariance,
2759            // Random (5)
2760            BuiltinFunction::IntrinsicRandom,
2761            BuiltinFunction::IntrinsicRandomInt,
2762            BuiltinFunction::IntrinsicRandomSeed,
2763            BuiltinFunction::IntrinsicRandomNormal,
2764            BuiltinFunction::IntrinsicRandomArray,
2765            // Distribution (5)
2766            BuiltinFunction::IntrinsicDistUniform,
2767            BuiltinFunction::IntrinsicDistLognormal,
2768            BuiltinFunction::IntrinsicDistExponential,
2769            BuiltinFunction::IntrinsicDistPoisson,
2770            BuiltinFunction::IntrinsicDistSampleN,
2771            // Stochastic (4)
2772            BuiltinFunction::IntrinsicBrownianMotion,
2773            BuiltinFunction::IntrinsicGbm,
2774            BuiltinFunction::IntrinsicOuProcess,
2775            BuiltinFunction::IntrinsicRandomWalk,
2776            // Rolling (7)
2777            BuiltinFunction::IntrinsicRollingSum,
2778            BuiltinFunction::IntrinsicRollingMean,
2779            BuiltinFunction::IntrinsicRollingStd,
2780            BuiltinFunction::IntrinsicRollingMin,
2781            BuiltinFunction::IntrinsicRollingMax,
2782            BuiltinFunction::IntrinsicEma,
2783            BuiltinFunction::IntrinsicLinearRecurrence,
2784            // Series transform (7)
2785            BuiltinFunction::IntrinsicShift,
2786            BuiltinFunction::IntrinsicDiff,
2787            BuiltinFunction::IntrinsicPctChange,
2788            BuiltinFunction::IntrinsicFillna,
2789            BuiltinFunction::IntrinsicCumsum,
2790            BuiltinFunction::IntrinsicCumprod,
2791            BuiltinFunction::IntrinsicClip,
2792            // Statistics (4)
2793            BuiltinFunction::IntrinsicCorrelation,
2794            BuiltinFunction::IntrinsicCovariance,
2795            BuiltinFunction::IntrinsicPercentile,
2796            BuiltinFunction::IntrinsicMedian,
2797            // Trigonometric (4)
2798            BuiltinFunction::IntrinsicAtan2,
2799            BuiltinFunction::IntrinsicSinh,
2800            BuiltinFunction::IntrinsicCosh,
2801            BuiltinFunction::IntrinsicTanh,
2802            // Char codes (2)
2803            BuiltinFunction::IntrinsicCharCode,
2804            BuiltinFunction::IntrinsicFromCharCode,
2805            // Series (1)
2806            BuiltinFunction::IntrinsicSeries,
2807            // Vector (12 — includes R5.4D IntrinsicVecAddI64)
2808            BuiltinFunction::IntrinsicVecAbs,
2809            BuiltinFunction::IntrinsicVecSqrt,
2810            BuiltinFunction::IntrinsicVecLn,
2811            BuiltinFunction::IntrinsicVecExp,
2812            BuiltinFunction::IntrinsicVecAdd,
2813            BuiltinFunction::IntrinsicVecSub,
2814            BuiltinFunction::IntrinsicVecMul,
2815            BuiltinFunction::IntrinsicVecDiv,
2816            BuiltinFunction::IntrinsicVecMax,
2817            BuiltinFunction::IntrinsicVecMin,
2818            BuiltinFunction::IntrinsicVecSelect,
2819            BuiltinFunction::IntrinsicVecAddI64,
2820            // Matrix (4 — includes R5.4D IntrinsicMatAdd / IntrinsicMatSub)
2821            BuiltinFunction::IntrinsicMatMulVec,
2822            BuiltinFunction::IntrinsicMatMulMat,
2823            BuiltinFunction::IntrinsicMatAdd,
2824            BuiltinFunction::IntrinsicMatSub,
2825            // Eval helpers (6)
2826            BuiltinFunction::EvalTimeRef,
2827            BuiltinFunction::EvalDateTimeExpr,
2828            BuiltinFunction::EvalDataDateTimeRef,
2829            BuiltinFunction::EvalDataSet,
2830            BuiltinFunction::EvalDataRelative,
2831            BuiltinFunction::EvalDataRelativeRange,
2832            // Ctors (7)
2833            BuiltinFunction::SomeCtor,
2834            BuiltinFunction::OkCtor,
2835            BuiltinFunction::ErrCtor,
2836            BuiltinFunction::HashMapCtor,
2837            BuiltinFunction::SetCtor,
2838            BuiltinFunction::DequeCtor,
2839            BuiltinFunction::PriorityQueueCtor,
2840            // JSON (5)
2841            BuiltinFunction::JsonObjectGet,
2842            BuiltinFunction::JsonArrayAt,
2843            BuiltinFunction::JsonObjectKeys,
2844            BuiltinFunction::JsonArrayLen,
2845            BuiltinFunction::JsonObjectLen,
2846            // Window (14)
2847            BuiltinFunction::WindowRowNumber,
2848            BuiltinFunction::WindowRank,
2849            BuiltinFunction::WindowDenseRank,
2850            BuiltinFunction::WindowNtile,
2851            BuiltinFunction::WindowLag,
2852            BuiltinFunction::WindowLead,
2853            BuiltinFunction::WindowFirstValue,
2854            BuiltinFunction::WindowLastValue,
2855            BuiltinFunction::WindowNthValue,
2856            BuiltinFunction::WindowSum,
2857            BuiltinFunction::WindowAvg,
2858            BuiltinFunction::WindowMin,
2859            BuiltinFunction::WindowMax,
2860            BuiltinFunction::WindowCount,
2861            // Join/Reflect (2)
2862            BuiltinFunction::JoinExecute,
2863            BuiltinFunction::Reflect,
2864            // Content (6 constructors)
2865            BuiltinFunction::ContentChart,
2866            BuiltinFunction::ContentTextCtor,
2867            BuiltinFunction::ContentTableCtor,
2868            BuiltinFunction::ContentCodeCtor,
2869            BuiltinFunction::ContentKvCtor,
2870            BuiltinFunction::ContentFragmentCtor,
2871            // DateTime (6)
2872            BuiltinFunction::DateTimeNow,
2873            BuiltinFunction::DateTimeUtc,
2874            BuiltinFunction::DateTimeParse,
2875            BuiltinFunction::DateTimeFromEpoch,
2876            BuiltinFunction::DateTimeFromParts,
2877            BuiltinFunction::DateTimeFromUnixSecs,
2878            // Concurrency (4)
2879            BuiltinFunction::MutexCtor,
2880            BuiltinFunction::AtomicCtor,
2881            BuiltinFunction::LazyCtor,
2882            BuiltinFunction::ChannelCtor,
2883            // Math extras (7)
2884            BuiltinFunction::Sign,
2885            BuiltinFunction::Gcd,
2886            BuiltinFunction::Lcm,
2887            BuiltinFunction::Hypot,
2888            BuiltinFunction::Clamp,
2889            BuiltinFunction::IsNaN,
2890            BuiltinFunction::IsFinite,
2891            // Matrix (1)
2892            BuiltinFunction::MatFromFlat,
2893            // Table construction (1)
2894            BuiltinFunction::MakeTableFromRows,
2895            // W18.5 content builder ctors (3)
2896            BuiltinFunction::TableBuilderNew,
2897            BuiltinFunction::CodeBuilderNew,
2898            BuiltinFunction::KeyValueBuilderNew,
2899        ];
2900        VARIANTS.get(id as usize).copied()
2901    }
2902}
2903
2904#[cfg(test)]
2905mod tests {
2906    use super::*;
2907
2908    /// V1.1A: verify the new ownership opcodes round-trip through their u16
2909    /// discriminants — the opcode byte table assigns 0x125/0x126/0x127, and
2910    /// the `#[repr(u16)]` enum must produce exactly those values.
2911    #[test]
2912    fn v11a_move_local_discriminant() {
2913        let op = OpCode::MoveLocal;
2914        assert_eq!(op as u16, 0x125);
2915    }
2916
2917    #[test]
2918    fn v11a_clone_local_discriminant() {
2919        let op = OpCode::CloneLocal;
2920        assert_eq!(op as u16, 0x126);
2921    }
2922
2923    #[test]
2924    fn v11a_drop_local_discriminant() {
2925        let op = OpCode::DropLocal;
2926        assert_eq!(op as u16, 0x127);
2927    }
2928
2929    /// V1.1A: the three ownership opcodes are classified as Variable-category
2930    /// (they operate on a local slot by index).
2931    #[test]
2932    fn v11a_ownership_opcodes_are_variable_category() {
2933        assert_eq!(OpCode::MoveLocal.category(), OpcodeCategory::Variable);
2934        assert_eq!(OpCode::CloneLocal.category(), OpcodeCategory::Variable);
2935        assert_eq!(OpCode::DropLocal.category(), OpcodeCategory::Variable);
2936    }
2937
2938    /// V1.1A: stack effects — Move/Clone push one value, Drop is zero-effect.
2939    #[test]
2940    fn v11a_ownership_opcode_stack_effects() {
2941        // MoveLocal: reads local, pushes onto stack → 0 pops, 1 push
2942        assert_eq!(OpCode::MoveLocal.stack_pops(), 0);
2943        assert_eq!(OpCode::MoveLocal.stack_pushes(), 1);
2944        // CloneLocal: reads local, pushes onto stack → 0 pops, 1 push
2945        assert_eq!(OpCode::CloneLocal.stack_pops(), 0);
2946        assert_eq!(OpCode::CloneLocal.stack_pushes(), 1);
2947        // DropLocal: drops a local slot in place → 0 pops, 0 pushes
2948        assert_eq!(OpCode::DropLocal.stack_pops(), 0);
2949        assert_eq!(OpCode::DropLocal.stack_pushes(), 0);
2950    }
2951
2952    /// V1.1A: the new opcodes are neither trusted nor v2-typed.
2953    /// They're ownership-aware variants that will be validated by a dedicated
2954    /// verifier pass once V1.1B/C land.
2955    #[test]
2956    fn v11a_ownership_opcodes_not_classified_as_trusted_or_v2() {
2957        for op in [OpCode::MoveLocal, OpCode::CloneLocal, OpCode::DropLocal] {
2958            assert!(!op.is_trusted(), "{:?} should not be trusted", op);
2959            assert!(!op.is_v2_typed(), "{:?} should not be v2_typed", op);
2960        }
2961    }
2962
2963    /// V1.1A: Instruction-level construction round-trips the opcode and
2964    /// preserves the `Local(u16)` operand. Mirrors the "decoder on
2965    /// manually-encoded bytes" check requested by the V1.1A plan — Shape
2966    /// constructs Instruction values directly rather than byte-decoding.
2967    #[test]
2968    fn v11a_ownership_instructions_preserve_operand() {
2969        let m = Instruction::new(OpCode::MoveLocal, Some(Operand::Local(7)));
2970        assert_eq!(m.opcode, OpCode::MoveLocal);
2971        assert!(matches!(m.operand, Some(Operand::Local(7))));
2972
2973        let c = Instruction::new(OpCode::CloneLocal, Some(Operand::Local(3)));
2974        assert_eq!(c.opcode, OpCode::CloneLocal);
2975        assert!(matches!(c.operand, Some(Operand::Local(3))));
2976
2977        let d = Instruction::new(OpCode::DropLocal, Some(Operand::Local(42)));
2978        assert_eq!(d.opcode, OpCode::DropLocal);
2979        assert!(matches!(d.operand, Some(Operand::Local(42))));
2980    }
2981
2982    /// V1.2A: the new `PromoteToShared` opcode is assigned 0x128, immediately
2983    /// after `DropLocal` (0x127). Mirrors the V1.1A discriminant pins.
2984    #[test]
2985    fn v12a_promote_to_shared_discriminant() {
2986        assert_eq!(OpCode::PromoteToShared as u16, 0x128);
2987    }
2988
2989    /// V1.2A: `PromoteToShared` is the inverse of `PromoteToOwned` and shares
2990    /// its categorization — both live in the `Stack` category because they
2991    /// operate on top-of-stack without an operand.
2992    #[test]
2993    fn v12a_promote_to_shared_is_stack_category() {
2994        assert_eq!(OpCode::PromoteToShared.category(), OpcodeCategory::Stack);
2995        // Symmetry: PromoteToOwned is the companion and must share the category.
2996        assert_eq!(OpCode::PromoteToOwned.category(), OpcodeCategory::Stack);
2997    }
2998
2999    /// V1.2A: stack effect is zero/zero — the opcode mutates the top-of-stack
3000    /// value in place (identical to `PromoteToOwned`).
3001    #[test]
3002    fn v12a_promote_to_shared_stack_effect() {
3003        assert_eq!(OpCode::PromoteToShared.stack_pops(), 0);
3004        assert_eq!(OpCode::PromoteToShared.stack_pushes(), 0);
3005        // Symmetry with the inverse opcode.
3006        assert_eq!(OpCode::PromoteToOwned.stack_pops(), 0);
3007        assert_eq!(OpCode::PromoteToOwned.stack_pushes(), 0);
3008    }
3009
3010    /// V1.2A: `PromoteToShared` is neither a trusted opcode nor a v2-typed
3011    /// opcode. Ownership-aware opcodes will be validated by a dedicated
3012    /// ownership verifier pass in a later phase.
3013    #[test]
3014    fn v12a_promote_to_shared_not_trusted_or_v2() {
3015        let op = OpCode::PromoteToShared;
3016        assert!(!op.is_trusted(), "PromoteToShared should not be trusted");
3017        assert!(!op.is_v2_typed(), "PromoteToShared should not be v2_typed");
3018    }
3019
3020    /// V1.2A: `Instruction::simple` constructs a `PromoteToShared` with no
3021    /// operand (same shape as `PromoteToOwned`) and round-trips the opcode.
3022    #[test]
3023    fn v12a_promote_to_shared_instruction_roundtrip() {
3024        let instr = Instruction::simple(OpCode::PromoteToShared);
3025        assert_eq!(instr.opcode, OpCode::PromoteToShared);
3026        assert!(
3027            instr.operand.is_none(),
3028            "PromoteToShared should have no operand (like PromoteToOwned)"
3029        );
3030    }
3031
3032    // ===== R5.1A: Typed bitwise opcode tests =====
3033
3034    /// R5.1A: pin each new bitwise opcode's u16 discriminant. The bytecode
3035    /// ABI is stable, so these IDs must not drift across phases. IDs were
3036    /// chosen sequentially above the highest existing discriminant at the
3037    /// time of landing (0x128 PromoteToShared).
3038    #[test]
3039    fn r51a_typed_bitwise_discriminants() {
3040        assert_eq!(OpCode::BitAndInt as u16, 0x129);
3041        assert_eq!(OpCode::BitOrInt as u16, 0x12A);
3042        assert_eq!(OpCode::BitXorInt as u16, 0x12B);
3043        assert_eq!(OpCode::BitShlInt as u16, 0x12C);
3044        assert_eq!(OpCode::BitShrInt as u16, 0x12D);
3045        assert_eq!(OpCode::BitNotInt as u16, 0x12E);
3046    }
3047
3048    /// R5.1A: all six typed bitwise opcodes are Arithmetic-category, matching
3049    /// both their dynamic fallback counterparts (BitAnd/BitOr/BitXor/...) and
3050    /// the typed integer arithmetic opcodes (AddInt/SubInt/MulInt).
3051    #[test]
3052    fn r51a_typed_bitwise_opcodes_are_arithmetic_category() {
3053        assert_eq!(OpCode::BitAndInt.category(), OpcodeCategory::Arithmetic);
3054        assert_eq!(OpCode::BitOrInt.category(), OpcodeCategory::Arithmetic);
3055        assert_eq!(OpCode::BitXorInt.category(), OpcodeCategory::Arithmetic);
3056        assert_eq!(OpCode::BitShlInt.category(), OpcodeCategory::Arithmetic);
3057        assert_eq!(OpCode::BitShrInt.category(), OpcodeCategory::Arithmetic);
3058        assert_eq!(OpCode::BitNotInt.category(), OpcodeCategory::Arithmetic);
3059    }
3060
3061    /// R5.1A: stack effects — binary bitwise ops pop two and push one;
3062    /// `BitNotInt` is unary (pop 1, push 1). Mirrors `AddInt`/`NegInt`.
3063    #[test]
3064    fn r51a_typed_bitwise_opcode_stack_effects() {
3065        for op in [
3066            OpCode::BitAndInt,
3067            OpCode::BitOrInt,
3068            OpCode::BitXorInt,
3069            OpCode::BitShlInt,
3070            OpCode::BitShrInt,
3071        ] {
3072            assert_eq!(op.stack_pops(), 2, "{:?} should pop 2", op);
3073            assert_eq!(op.stack_pushes(), 1, "{:?} should push 1", op);
3074        }
3075        assert_eq!(OpCode::BitNotInt.stack_pops(), 1);
3076        assert_eq!(OpCode::BitNotInt.stack_pushes(), 1);
3077    }
3078
3079    /// R5.1A: the six new typed bitwise opcodes are neither trusted nor
3080    /// v2-typed, mirroring `AddInt`/`SubInt`/`MulInt` (the other int-typed
3081    /// arithmetic opcodes). The v2-typed classification is reserved for the
3082    /// sized-integer (i32) family and typed-array/typed-field ops, which
3083    /// require a FrameDescriptor. R5.1B/R5.1C may extend classification once
3084    /// handlers and compiler emission exist.
3085    #[test]
3086    fn r51a_typed_bitwise_opcodes_not_classified_as_trusted_or_v2() {
3087        for op in [
3088            OpCode::BitAndInt,
3089            OpCode::BitOrInt,
3090            OpCode::BitXorInt,
3091            OpCode::BitShlInt,
3092            OpCode::BitShrInt,
3093            OpCode::BitNotInt,
3094        ] {
3095            assert!(!op.is_trusted(), "{:?} should not be trusted", op);
3096            assert!(!op.is_v2_typed(), "{:?} should not be v2_typed", op);
3097        }
3098    }
3099
3100    /// R5.1A: `Instruction::simple` constructs every typed bitwise opcode
3101    /// with no operand (same shape as `AddInt`/`BitAnd`) and round-trips the
3102    /// opcode field.
3103    #[test]
3104    fn r51a_typed_bitwise_instructions_have_no_operand() {
3105        for op in [
3106            OpCode::BitAndInt,
3107            OpCode::BitOrInt,
3108            OpCode::BitXorInt,
3109            OpCode::BitShlInt,
3110            OpCode::BitShrInt,
3111            OpCode::BitNotInt,
3112        ] {
3113            let instr = Instruction::simple(op);
3114            assert_eq!(instr.opcode, op);
3115            assert!(
3116                instr.operand.is_none(),
3117                "{:?} should have no operand (like AddInt/BitAnd)",
3118                op
3119            );
3120        }
3121    }
3122
3123    // ===== R5.5: Typed string+scalar concat discriminant & shape tests =====
3124
3125    /// R5.5: pin each new string+scalar concat opcode's u16 discriminant.
3126    /// The bytecode ABI is stable, so these IDs must not drift across phases.
3127    /// IDs were chosen sequentially above the last R5.1A discriminant (0x12E).
3128    #[test]
3129    fn r55_string_scalar_concat_discriminants() {
3130        assert_eq!(OpCode::StringConcatInt as u16, 0x12F);
3131        assert_eq!(OpCode::StringConcatNumber as u16, 0x130);
3132        assert_eq!(OpCode::StringConcatBool as u16, 0x131);
3133    }
3134
3135    /// R5.5: the three new string+scalar concat opcodes belong to the
3136    /// `Object` category, matching their siblings `StringConcat` (0xFC)
3137    /// and `StringConcatTyped` (0x116).
3138    #[test]
3139    fn r55_string_scalar_concat_opcodes_are_object_category() {
3140        assert_eq!(OpCode::StringConcatInt.category(), OpcodeCategory::Object);
3141        assert_eq!(OpCode::StringConcatNumber.category(), OpcodeCategory::Object);
3142        assert_eq!(OpCode::StringConcatBool.category(), OpcodeCategory::Object);
3143    }
3144
3145    /// R5.5: each typed string+scalar concat opcode pops two (string, scalar)
3146    /// and pushes one new string. Same stack effect as `StringConcatTyped`.
3147    #[test]
3148    fn r55_string_scalar_concat_stack_effects() {
3149        for op in [
3150            OpCode::StringConcatInt,
3151            OpCode::StringConcatNumber,
3152            OpCode::StringConcatBool,
3153        ] {
3154            assert_eq!(op.stack_pops(), 2, "{:?} should pop 2", op);
3155            assert_eq!(op.stack_pushes(), 1, "{:?} should push 1", op);
3156        }
3157    }
3158
3159    /// R5.5: neither trusted nor v2-typed (they still allocate a heap
3160    /// `StringObj` through the regular polymorphic-allocate pipeline,
3161    /// the post-§2.7.7 successor to the deleted ValueWord-backed alloc
3162    /// path). Mirrors `StringConcatTyped`'s classification.
3163    #[test]
3164    fn r55_string_scalar_concat_opcodes_not_classified_as_trusted_or_v2() {
3165        for op in [
3166            OpCode::StringConcatInt,
3167            OpCode::StringConcatNumber,
3168            OpCode::StringConcatBool,
3169        ] {
3170            assert!(!op.is_trusted(), "{:?} should not be trusted", op);
3171            assert!(!op.is_v2_typed(), "{:?} should not be v2_typed", op);
3172        }
3173    }
3174
3175    /// R5.5: `Instruction::simple` constructs every string+scalar concat
3176    /// opcode with no operand (same shape as `StringConcatTyped`).
3177    #[test]
3178    fn r55_string_scalar_concat_instructions_have_no_operand() {
3179        for op in [
3180            OpCode::StringConcatInt,
3181            OpCode::StringConcatNumber,
3182            OpCode::StringConcatBool,
3183        ] {
3184            let instr = Instruction::simple(op);
3185            assert_eq!(instr.opcode, op);
3186            assert!(
3187                instr.operand.is_none(),
3188                "{:?} should have no operand (like StringConcatTyped)",
3189                op
3190            );
3191        }
3192    }
3193}