Skip to main content

sema_vm/
opcodes.rs

1/// Bytecode opcodes for the Sema VM.
2///
3/// Stack-based: operands are pushed/popped from the value stack.
4/// Variable-length encoding: opcode (1 byte) + operands (u16/u32/i32).
5#[repr(u8)]
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum Op {
8    // Constants & stack
9    Const, // u16 const_index → push constants[i]
10    Nil,   // push nil
11    True,  // push #t
12    False, // push #f
13    Pop,   // discard TOS
14    Dup,   // duplicate TOS
15
16    // Locals (slot-addressed within call frame)
17    LoadLocal,  // u16 slot → push locals[slot]
18    StoreLocal, // u16 slot → locals[slot] = pop
19
20    // Upvalues (captured variables from enclosing scopes)
21    LoadUpvalue,  // u16 index → push upvalues[i].get()
22    StoreUpvalue, // u16 index → upvalues[i].set(pop)
23
24    // Globals (module-level bindings, keyed by Spur)
25    LoadGlobal,   // u32 spur → push globals[spur]
26    StoreGlobal,  // u32 spur → globals[spur] = pop
27    DefineGlobal, // u32 spur → globals[spur] = pop (define, not set!)
28
29    // Control flow
30    Jump,        // i32 relative offset
31    JumpIfFalse, // i32 relative offset (pop condition)
32    JumpIfTrue,  // i32 relative offset (pop condition)
33
34    // Function calls
35    Call,     // u16 argc → call TOS-argc with argc args
36    TailCall, // u16 argc → tail call (reuse frame)
37    Return,   // return TOS
38
39    // Closures
40    MakeClosure, // u16 func_id, u16 n_upvalues, then n * (u16 is_local, u16 idx)
41
42    // Native function calls (fast path)
43    CallNative, // u16 native_id, u16 argc
44
45    // Data constructors
46    MakeList,    // u16 n → pop n values, push list
47    MakeVector,  // u16 n → pop n values, push vector
48    MakeMap,     // u16 n_pairs → pop 2n values, push map
49    MakeHashMap, // u16 n_pairs → pop 2n values, push hashmap
50
51    // Exception handling
52    Throw, // pop value, throw as exception
53
54    // Generic arithmetic & comparison
55    Add,
56    Sub,
57    Mul,
58    Div,
59    Negate,
60    Not,
61    Eq,
62    Lt,
63    Gt,
64    Le,
65    Ge,
66
67    // Specialized int arithmetic (fast paths)
68    AddInt,
69    SubInt,
70    MulInt,
71    LtInt,
72    EqInt,
73
74    // Specialized zero-operand locals (most common slots)
75    LoadLocal0,  // = 42
76    LoadLocal1,  // = 43
77    LoadLocal2,  // = 44
78    LoadLocal3,  // = 45
79    StoreLocal0, // = 46
80    StoreLocal1, // = 47
81    StoreLocal2, // = 48
82    StoreLocal3, // = 49
83
84    // Fused global call (LOAD_GLOBAL + CALL in one instruction)
85    CallGlobal, // u32 spur, u16 argc → lookup global, call with argc args
86}
87
88impl Op {
89    /// Convert a raw byte to an Op. Valid because the enum is `#[repr(u8)]` with
90    /// dense variants from 0 through `CallGlobal`. If new variants are added with
91    /// gaps, this must be updated.
92    pub fn from_u8(byte: u8) -> Option<Op> {
93        if byte <= Op::CallGlobal as u8 {
94            // SAFETY: Op is #[repr(u8)] with dense, contiguous variants 0..=CallGlobal.
95            Some(unsafe { std::mem::transmute::<u8, Op>(byte) })
96        } else {
97            None
98        }
99    }
100}
101
102/// Opcode constants for use in match patterns (avoids `Op::from_u8` overhead).
103pub mod op {
104    use super::Op;
105    pub const CONST: u8 = Op::Const as u8;
106    pub const NIL: u8 = Op::Nil as u8;
107    pub const TRUE: u8 = Op::True as u8;
108    pub const FALSE: u8 = Op::False as u8;
109    pub const POP: u8 = Op::Pop as u8;
110    pub const DUP: u8 = Op::Dup as u8;
111    pub const LOAD_LOCAL: u8 = Op::LoadLocal as u8;
112    pub const STORE_LOCAL: u8 = Op::StoreLocal as u8;
113    pub const LOAD_UPVALUE: u8 = Op::LoadUpvalue as u8;
114    pub const STORE_UPVALUE: u8 = Op::StoreUpvalue as u8;
115    pub const LOAD_GLOBAL: u8 = Op::LoadGlobal as u8;
116    pub const STORE_GLOBAL: u8 = Op::StoreGlobal as u8;
117    pub const DEFINE_GLOBAL: u8 = Op::DefineGlobal as u8;
118    pub const JUMP: u8 = Op::Jump as u8;
119    pub const JUMP_IF_FALSE: u8 = Op::JumpIfFalse as u8;
120    pub const JUMP_IF_TRUE: u8 = Op::JumpIfTrue as u8;
121    pub const CALL: u8 = Op::Call as u8;
122    pub const TAIL_CALL: u8 = Op::TailCall as u8;
123    pub const RETURN: u8 = Op::Return as u8;
124    pub const MAKE_CLOSURE: u8 = Op::MakeClosure as u8;
125    pub const CALL_NATIVE: u8 = Op::CallNative as u8;
126    pub const MAKE_LIST: u8 = Op::MakeList as u8;
127    pub const MAKE_VECTOR: u8 = Op::MakeVector as u8;
128    pub const MAKE_MAP: u8 = Op::MakeMap as u8;
129    pub const MAKE_HASH_MAP: u8 = Op::MakeHashMap as u8;
130    pub const THROW: u8 = Op::Throw as u8;
131    pub const ADD: u8 = Op::Add as u8;
132    pub const SUB: u8 = Op::Sub as u8;
133    pub const MUL: u8 = Op::Mul as u8;
134    pub const DIV: u8 = Op::Div as u8;
135    pub const NEGATE: u8 = Op::Negate as u8;
136    pub const NOT: u8 = Op::Not as u8;
137    pub const EQ: u8 = Op::Eq as u8;
138    pub const LT: u8 = Op::Lt as u8;
139    pub const GT: u8 = Op::Gt as u8;
140    pub const LE: u8 = Op::Le as u8;
141    pub const GE: u8 = Op::Ge as u8;
142    pub const ADD_INT: u8 = Op::AddInt as u8;
143    pub const SUB_INT: u8 = Op::SubInt as u8;
144    pub const MUL_INT: u8 = Op::MulInt as u8;
145    pub const LT_INT: u8 = Op::LtInt as u8;
146    pub const EQ_INT: u8 = Op::EqInt as u8;
147    pub const LOAD_LOCAL0: u8 = Op::LoadLocal0 as u8;
148    pub const LOAD_LOCAL1: u8 = Op::LoadLocal1 as u8;
149    pub const LOAD_LOCAL2: u8 = Op::LoadLocal2 as u8;
150    pub const LOAD_LOCAL3: u8 = Op::LoadLocal3 as u8;
151    pub const STORE_LOCAL0: u8 = Op::StoreLocal0 as u8;
152    pub const STORE_LOCAL1: u8 = Op::StoreLocal1 as u8;
153    pub const STORE_LOCAL2: u8 = Op::StoreLocal2 as u8;
154    pub const STORE_LOCAL3: u8 = Op::StoreLocal3 as u8;
155    pub const CALL_GLOBAL: u8 = Op::CallGlobal as u8;
156
157    // Instruction sizes (opcode byte + operand bytes)
158    /// Size of a bare opcode with no operands: 1
159    pub const SIZE_OP: usize = 1;
160    /// Size of an instruction with a u16 operand (e.g., CALL, LOAD_LOCAL): 1 + 2 = 3
161    pub const SIZE_OP_U16: usize = 3;
162    /// Size of an instruction with a u32 operand (e.g., LOAD_GLOBAL): 1 + 4 = 5
163    pub const SIZE_OP_U32: usize = 5;
164    /// Size of CALL_GLOBAL: 1 + u32 spur + u16 argc = 7
165    pub const SIZE_CALL_GLOBAL: usize = 7;
166}