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}