1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
//! Bytecode instruction set per spec §8.2.
use serde::{Deserialize, Serialize};
/// Constant pool entry.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum Const {
Int(i64),
Float(f64),
Bool(bool),
Str(String),
Bytes(Vec<u8>),
Unit,
/// A field name, used by MAKE_RECORD/GET_FIELD.
FieldName(String),
/// A variant tag, used by MAKE_VARIANT/TEST_VARIANT/GET_VARIANT.
VariantName(String),
/// An AST NodeId, attached to Call / EffectCall for trace keying (§10.1).
NodeId(String),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum Op {
// stack manipulation
PushConst(u32),
Pop,
Dup,
// locals
LoadLocal(u16),
StoreLocal(u16),
// constructors / pattern matching
/// Builds a record from `count` (name_const_idx, value) pairs on the stack.
/// Stack: [name_idx_n, val_n, ..., name_idx_1, val_1] but encoded as
/// alternating `<name_idx_const_u32> <value popped from stack>` — for
/// simplicity we instead push `count` values and `count` field name
/// constants in the same op as a `Vec<u32>` of name indices.
MakeRecord { field_name_indices: Vec<u32> },
MakeTuple(u16),
MakeList(u32),
MakeVariant { name_idx: u32, arity: u16 },
GetField(u32), // field name const idx
GetElem(u16), // tuple element index
TestVariant(u32), // pushes Bool: top-of-stack matches variant name?
GetVariant(u32), // extracts payload (replaces variant on stack with its args list)
GetVariantArg(u16), // pop variant, push its i'th arg
GetListLen,
GetListElem(u32),
/// Pop [list, value]; push list with `value` appended.
ListAppend,
/// Pop list; push it indexed by the integer on top.
/// Stack: [list, idx] → [list[idx]]. (Like GetListElem(u32) but
/// the index is dynamic.)
GetListElemDyn,
// control flow
Jump(i32),
JumpIf(i32), // pops Bool
JumpIfNot(i32),
Call { fn_id: u32, arity: u16, node_id_idx: u32 },
TailCall { fn_id: u32, arity: u16, node_id_idx: u32 },
/// Build a Value::Closure: pop `capture_count` values (in order) and
/// pair them with `fn_id`.
MakeClosure { fn_id: u32, capture_count: u16 },
/// Call a closure: pop `arity` args + 1 closure (top of stack), invoke.
CallClosure { arity: u16, node_id_idx: u32 },
/// EFFECT_CALL `<effect_kind_const_idx>` `<op_name_const_idx>` `<arity>`.
/// Pops `arity` args, dispatches to a host effect handler, pushes result.
/// `node_id_idx` points to a `Const::NodeId` for trace keying.
EffectCall { kind_idx: u32, op_idx: u32, arity: u16, node_id_idx: u32 },
Return,
Panic(u32), // pushes constant message and aborts
// arithmetic — typed (per spec §8.2). `NumAdd`/etc. dispatch on operand
// type at runtime; emitted when the compiler doesn't have type info.
// The post-M5 plan is to lower NumAdd → IntAdd|FloatAdd in a typed pass.
IntAdd, IntSub, IntMul, IntDiv, IntMod, IntNeg,
IntEq, IntLt, IntLe,
FloatAdd, FloatSub, FloatMul, FloatDiv, FloatNeg,
FloatEq, FloatLt, FloatLe,
NumAdd, NumSub, NumMul, NumDiv, NumMod, NumNeg,
NumEq, NumLt, NumLe,
BoolAnd, BoolOr, BoolNot,
// strings
StrConcat, StrLen, StrEq,
BytesLen, BytesEq,
}