aver/vm/opcode.rs
1// Aver VM bytecode opcodes.
2//
3// Stack-based: operands are pushed/popped from the operand stack.
4// Variable-width encoding: opcode (1 byte) + operands (0-3 bytes).
5
6// -- Stack / locals ----------------------------------------------------------
7
8/// No-op, used as padding after superinstruction fusion.
9pub const NOP: u8 = 0x00;
10
11/// Push `stack[bp + slot]` onto the operand stack.
12pub const LOAD_LOCAL: u8 = 0x01; // slot:u8
13
14/// Pop top and store into `stack[bp + slot]`.
15pub const STORE_LOCAL: u8 = 0x02; // slot:u8
16
17/// Push `constants[idx]` onto the operand stack.
18pub const LOAD_CONST: u8 = 0x03; // idx:u16
19
20/// Push `globals[idx]` onto the operand stack.
21pub const LOAD_GLOBAL: u8 = 0x04; // idx:u16
22
23/// Pop top and store into `globals[idx]`.
24pub const STORE_GLOBAL: u8 = 0x0A; // idx:u16
25
26/// Discard the top value.
27pub const POP: u8 = 0x05;
28
29/// Duplicate the top value.
30pub const DUP: u8 = 0x06;
31
32/// Push `NanValue::UNIT`.
33pub const LOAD_UNIT: u8 = 0x07;
34
35/// Push `NanValue::TRUE`.
36pub const LOAD_TRUE: u8 = 0x08;
37
38/// Push `NanValue::FALSE`.
39pub const LOAD_FALSE: u8 = 0x09;
40
41// -- Arithmetic --------------------------------------------------------------
42
43/// Pop b, pop a, push a + b.
44pub const ADD: u8 = 0x10;
45
46/// Pop b, pop a, push a - b.
47pub const SUB: u8 = 0x11;
48
49/// Pop b, pop a, push a * b.
50pub const MUL: u8 = 0x12;
51
52/// Pop b, pop a, push a / b.
53pub const DIV: u8 = 0x13;
54
55/// Pop b, pop a, push a % b.
56pub const MOD: u8 = 0x14;
57
58/// Pop a, push -a.
59pub const NEG: u8 = 0x15;
60
61/// Pop a, push !a (boolean not).
62pub const NOT: u8 = 0x16;
63
64// -- Comparison --------------------------------------------------------------
65
66/// Pop b, pop a, push a == b.
67pub const EQ: u8 = 0x20;
68
69/// Pop b, pop a, push a < b.
70pub const LT: u8 = 0x21;
71
72/// Pop b, pop a, push a > b.
73pub const GT: u8 = 0x22;
74
75// -- String ------------------------------------------------------------------
76
77/// Pop b, pop a, push str(a) ++ str(b).
78pub const CONCAT: u8 = 0x28;
79
80// -- Control flow ------------------------------------------------------------
81
82/// Unconditional relative jump: ip += offset.
83pub const JUMP: u8 = 0x30; // offset:i16
84
85/// Pop top, if falsy: ip += offset.
86pub const JUMP_IF_FALSE: u8 = 0x31; // offset:i16
87
88// -- Calls -------------------------------------------------------------------
89
90/// Call a known function by id. Args already on stack.
91pub const CALL_KNOWN: u8 = 0x40; // fn_id:u16, argc:u8
92
93/// Call a function value on the stack (under args).
94pub const CALL_VALUE: u8 = 0x41; // argc:u8
95
96/// Call a builtin service function.
97pub const CALL_BUILTIN: u8 = 0x42; // symbol_id:u32, argc:u8
98
99/// Self tail-call: reuse current frame with new args.
100pub const TAIL_CALL_SELF: u8 = 0x43; // argc:u8
101
102/// Mutual tail-call to a known function: reuse frame, switch target.
103pub const TAIL_CALL_KNOWN: u8 = 0x44; // fn_id:u16, argc:u8
104
105/// Return top of stack to caller.
106pub const RETURN: u8 = 0x50;
107
108// -- Structured values -------------------------------------------------------
109
110/// Push Nil (empty cons list).
111pub const LIST_NIL: u8 = 0x60;
112
113/// Pop tail, pop head, push Cons(head, tail).
114pub const LIST_CONS: u8 = 0x61;
115
116/// Pop `count` items, build cons list from them (first item = head), push list.
117pub const LIST_NEW: u8 = 0x62; // count:u8
118
119/// Pop `count` field values, push a new record with `type_id`.
120pub const RECORD_NEW: u8 = 0x63; // type_id:u16, count:u8
121
122/// Pop record, push `fields[field_idx]` (compile-time resolved index).
123pub const RECORD_GET: u8 = 0x64; // field_idx:u8
124
125/// Pop record, lookup field by interned field symbol, push value.
126pub const RECORD_GET_NAMED: u8 = 0x67; // field_symbol_id:u32
127
128/// Pop `count` field values, push a new variant.
129pub const VARIANT_NEW: u8 = 0x65; // type_id:u16, variant_id:u16, count:u8
130
131/// Pop value, push wrapped value. kind: 0=Ok, 1=Err, 2=Some.
132pub const WRAP: u8 = 0x66; // kind:u8
133
134/// Pop `count` items, build a tuple from them, push tuple.
135pub const TUPLE_NEW: u8 = 0x68; // count:u8
136
137/// Update selected fields on a record, preserving the rest from the base value.
138/// Stack: [..., base_record, update_0, ..., update_n-1] -> [..., updated_record]
139pub const RECORD_UPDATE: u8 = 0x69; // type_id:u16, count:u8, field_idx[count]:u8
140
141/// Propagate `Result.Err` to caller or unwrap `Result.Ok` in place.
142pub const PROPAGATE_ERR: u8 = 0x6A;
143
144/// Pop list, push its length as Int.
145pub const LIST_LEN: u8 = 0x6B;
146
147/// Pop index, pop list, push Option.Some(value) or Option.None.
148pub const LIST_GET: u8 = 0x6C;
149
150/// Pop value, pop list, push appended list.
151pub const LIST_APPEND: u8 = 0x6D;
152
153/// Pop list, pop value, push prepended list.
154pub const LIST_PREPEND: u8 = 0x6E;
155
156/// Pop index, pop list. If found, push value then true; otherwise push false.
157pub const LIST_GET_MATCH: u8 = 0x6F;
158
159// -- Pattern matching --------------------------------------------------------
160
161/// Peek top: if NaN tag != expected, ip += fail_offset.
162pub const MATCH_TAG: u8 = 0x70; // expected_tag:u8, fail_offset:i16
163
164/// Peek top (must be variant): if variant_id != expected, ip += fail_offset.
165pub const MATCH_VARIANT: u8 = 0x71; // ctor_id:u16, fail_offset:i16
166
167/// Peek top: if not wrapper of `kind`, ip += fail_offset.
168/// If matches, replace top with inner value (unwrap in-place).
169/// kind: 0=Ok, 1=Err, 2=Some.
170pub const MATCH_UNWRAP: u8 = 0x72; // kind:u8, fail_offset:i16
171
172/// Peek top: if not Nil, ip += fail_offset.
173pub const MATCH_NIL: u8 = 0x73; // fail_offset:i16
174
175/// Peek top: if Nil (not a cons), ip += fail_offset.
176pub const MATCH_CONS: u8 = 0x74; // fail_offset:i16
177
178/// Pop cons cell, push tail then push head.
179pub const LIST_HEAD_TAIL: u8 = 0x75;
180
181/// Peek top (record/variant), push `fields[field_idx]` (non-destructive).
182pub const EXTRACT_FIELD: u8 = 0x76; // field_idx:u8
183
184/// Peek top: if not a tuple of `count` items, ip += fail_offset.
185pub const MATCH_TUPLE: u8 = 0x78; // count:u8, fail_offset:i16
186
187/// Peek top tuple, push `items[item_idx]` (non-destructive).
188pub const EXTRACT_TUPLE_ITEM: u8 = 0x79; // item_idx:u8
189
190/// Non-exhaustive match error at source line.
191pub const MATCH_FAIL: u8 = 0x77; // line:u16
192
193/// Unified prefix/exact dispatch on NanValue bits.
194///
195/// Encoding:
196/// MATCH_DISPATCH count:u8 default_offset:i16
197/// [(kind:u8, expected:u64, offset:i16) × count]
198///
199/// kind=0: exact match — `val.bits() == expected`
200/// kind=1: tag match — `(val.bits() & TAG_MASK_FULL) == expected`
201/// where TAG_MASK_FULL = 0xFFFF_C000_0000_0000 (QNAN 14 bits + tag 4 bits)
202///
203/// Pops subject. Scans entries in order; first match wins → ip += offset.
204/// No match → ip += default_offset.
205/// All offsets are relative to the end of the full instruction.
206pub const MATCH_DISPATCH: u8 = 0x7A;
207
208/// Like MATCH_DISPATCH but every entry carries an inline result instead
209/// of a jump offset. When an entry matches, the result is pushed directly
210/// onto the stack and the match body is skipped entirely.
211///
212/// Encoding:
213/// MATCH_DISPATCH_CONST count:u8 default_offset:i16
214/// [(kind:u8, expected:u64, result:u64) × count]
215///
216/// Hit → pop subject, push result NanValue.
217/// Miss → pop subject, ip += default_offset (execute default arm body).
218///
219/// Emitted when ALL dispatchable arms have constant bodies (literals).
220pub const MATCH_DISPATCH_CONST: u8 = 0x7B;
221
222/// Tail-call self for thin frames: no arena finalization needed.
223/// The compiler emits this instead of TAIL_CALL_SELF when the function
224/// is known to be "thin" (no heap allocations within the frame).
225/// Skips finalize_frame_locals_for_tail_call entirely — just copies
226/// args in-place and resets ip.
227pub const TAIL_CALL_SELF_THIN: u8 = 0x45; // argc:u8
228
229/// Inline Option.withDefault: pop default, pop option → push inner or default.
230/// Stack: [option, default] → [result]
231/// If option is Some → push unwrapped inner value.
232/// If option is None → push default.
233pub const UNWRAP_OR: u8 = 0x7C;
234
235/// Inline Result.withDefault: pop default, pop result → push inner or default.
236/// Stack: [result, default] → [value]
237/// If result is Ok → push unwrapped inner value.
238/// If result is Err → push default.
239pub const UNWRAP_RESULT_OR: u8 = 0x7D;
240
241/// Frameless call to a leaf+thin+args-only function.
242/// No CallFrame is pushed — just saves (fn_id, ip) in the dispatch loop,
243/// sets bp to the args already on stack, and jumps to the target.
244/// On RETURN, restores the caller's state directly.
245/// Format: fn_id:u16, argc:u8 (same as CALL_KNOWN).
246pub const CALL_LEAF: u8 = 0x7E;
247
248// ─── Superinstructions ──────────────────────────────────────
249
250/// Push two locals in one dispatch. Format: slot_a:u8, slot_b:u8.
251pub const LOAD_LOCAL_2: u8 = 0x80;
252
253/// Push one local + one constant in one dispatch. Format: slot:u8, const_idx:u16.
254pub const LOAD_LOCAL_CONST: u8 = 0x81;
255
256/// LIST_GET + UNWRAP_OR fused: pop index, pop list, push value or default.
257/// Format: const_idx:u16 (default value from constants pool).
258/// Equivalent to: LIST_GET, LOAD_CONST idx, UNWRAP_OR.
259pub const LIST_GET_OR: u8 = 0x82;
260
261/// Opcode name for debug/disassembly.
262pub fn opcode_name(op: u8) -> &'static str {
263 match op {
264 LOAD_LOCAL => "LOAD_LOCAL",
265 STORE_LOCAL => "STORE_LOCAL",
266 LOAD_CONST => "LOAD_CONST",
267 LOAD_GLOBAL => "LOAD_GLOBAL",
268 POP => "POP",
269 DUP => "DUP",
270 LOAD_UNIT => "LOAD_UNIT",
271 LOAD_TRUE => "LOAD_TRUE",
272 LOAD_FALSE => "LOAD_FALSE",
273 ADD => "ADD",
274 SUB => "SUB",
275 MUL => "MUL",
276 DIV => "DIV",
277 MOD => "MOD",
278 NEG => "NEG",
279 NOT => "NOT",
280 EQ => "EQ",
281 LT => "LT",
282 GT => "GT",
283 CONCAT => "CONCAT",
284 JUMP => "JUMP",
285 JUMP_IF_FALSE => "JUMP_IF_FALSE",
286 CALL_KNOWN => "CALL_KNOWN",
287 CALL_VALUE => "CALL_VALUE",
288 CALL_BUILTIN => "CALL_BUILTIN",
289 TAIL_CALL_SELF => "TAIL_CALL_SELF",
290 TAIL_CALL_KNOWN => "TAIL_CALL_KNOWN",
291 RETURN => "RETURN",
292 LIST_NIL => "LIST_NIL",
293 LIST_CONS => "LIST_CONS",
294 LIST_NEW => "LIST_NEW",
295 RECORD_NEW => "RECORD_NEW",
296 STORE_GLOBAL => "STORE_GLOBAL",
297 RECORD_GET => "RECORD_GET",
298 RECORD_GET_NAMED => "RECORD_GET_NAMED",
299 VARIANT_NEW => "VARIANT_NEW",
300 WRAP => "WRAP",
301 TUPLE_NEW => "TUPLE_NEW",
302 RECORD_UPDATE => "RECORD_UPDATE",
303 PROPAGATE_ERR => "PROPAGATE_ERR",
304 LIST_LEN => "LIST_LEN",
305 LIST_GET => "LIST_GET",
306 LIST_APPEND => "LIST_APPEND",
307 LIST_PREPEND => "LIST_PREPEND",
308 LIST_GET_MATCH => "LIST_GET_MATCH",
309 MATCH_TAG => "MATCH_TAG",
310 MATCH_VARIANT => "MATCH_VARIANT",
311 MATCH_UNWRAP => "MATCH_UNWRAP",
312 MATCH_NIL => "MATCH_NIL",
313 MATCH_CONS => "MATCH_CONS",
314 LIST_HEAD_TAIL => "LIST_HEAD_TAIL",
315 EXTRACT_FIELD => "EXTRACT_FIELD",
316 MATCH_TUPLE => "MATCH_TUPLE",
317 EXTRACT_TUPLE_ITEM => "EXTRACT_TUPLE_ITEM",
318 MATCH_FAIL => "MATCH_FAIL",
319 MATCH_DISPATCH => "MATCH_DISPATCH",
320 MATCH_DISPATCH_CONST => "MATCH_DISPATCH_CONST",
321 TAIL_CALL_SELF_THIN => "TAIL_CALL_SELF_THIN",
322 UNWRAP_OR => "UNWRAP_OR",
323 UNWRAP_RESULT_OR => "UNWRAP_RESULT_OR",
324 CALL_LEAF => "CALL_LEAF",
325 LOAD_LOCAL_2 => "LOAD_LOCAL_2",
326 LOAD_LOCAL_CONST => "LOAD_LOCAL_CONST",
327 LIST_GET_OR => "LIST_GET_OR",
328 NOP => "NOP",
329 _ => "UNKNOWN",
330 }
331}