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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
//! Bytecode opcodes for the Tish VM.
/// Stack-based bytecode opcodes.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Opcode {
/// No operation
Nop = 0,
/// Push constant from constants table (operand: u16 index)
LoadConst = 1,
/// Load variable from scope (operand: u16 name index)
LoadVar = 2,
/// Store top of stack to variable (operand: u16 name index)
StoreVar = 3,
/// Discard top of stack
Pop = 4,
/// Duplicate top of stack
Dup = 5,
/// Call function with n args (operand: u16 arg count). Callee and args on stack.
Call = 6,
/// Return from function. Top of stack is return value.
Return = 7,
/// Unconditional jump forward (operand: u16 byte offset)
Jump = 8,
/// Pop top; if falsy, jump forward (operand: u16 byte offset)
JumpIfFalse = 9,
/// Unconditional jump backward (operand: u16 byte offset)
JumpBack = 10,
/// Binary operation (operand: u8 BinOp variant). Pops left, right; pushes result.
BinOp = 11,
/// Unary operation (operand: u8 UnaryOp variant). Pops operand; pushes result.
UnaryOp = 12,
/// Get property: obj.prop (operand: u16 prop name index). Pops obj; pushes value.
GetMember = 13,
/// Set property: obj.prop = val (operand: u16 prop name index). Pops obj, val.
SetMember = 14,
/// Get index: obj[idx]. Pops obj, idx; pushes value.
GetIndex = 15,
/// Set index: obj[idx] = val. Pops obj, idx, val.
SetIndex = 16,
/// Create array with n elements (operand: u16 count). Elements on stack.
NewArray = 17,
/// Create object with n key-value pairs (operand: u16 count). Keys and vals interleaved.
NewObject = 18,
/// Load from global scope (operand: u16 name index)
LoadGlobal = 19,
/// Store to global scope (operand: u16 name index)
StoreGlobal = 20,
/// Create closure: push function (operand: u16 chunk index for nested function)
Closure = 21,
/// Pop and discard n values (operand: u16 count)
PopN = 22,
/// Load `this` or receiver (for method calls)
LoadThis = 23,
/// Throw: pop value, unwind to catch handler, push value, jump
Throw = 24,
/// EnterTry: push handler (catch offset u16). Catch offset = bytes from end of this insn.
EnterTry = 25,
/// ExitTry: pop try handler
ExitTry = 26,
/// Concat arrays: pop right, pop left, push left.concat(right). For spread.
ConcatArray = 27,
/// Merge objects: pop right, pop left, push Object.assign({}, left, right). For object spread.
MergeObject = 28,
/// Call with spread: pop args array, pop callee, call callee(...args).
CallSpread = 29,
/// Get property optional: like GetMember but returns Null if obj is null or prop missing.
GetMemberOptional = 30,
/// Pop array, sort numerically in place (operand: u8 0=asc, 1=desc), push array.
/// Fast path for arr.sort((a,b)=>a-b) / arr.sort((a,b)=>b-a).
ArraySortNumeric = 31,
/// Pop array, sort by numeric property (operands: u16 prop_name_const_idx, u16 0=asc/1=desc).
/// Fast path for arr.sort((a,b)=>a.prop-b.prop).
ArraySortByProperty = 32,
/// arr.map(x => x) - identity, returns array clone.
ArrayMapIdentity = 33,
/// arr.map(x => x op const) or arr.map(x => const op x). Operands: u8 binop, u16 const_idx, u8 param_left (0=param on left e.g. x*2, 1=param on right e.g. 2*x).
ArrayMapBinOp = 34,
/// arr.filter(x => x op const) or arr.filter(x => const op x). Operands: u8 binop, u16 const_idx, u8 param_left. Keeps elements where result is truthy.
ArrayFilterBinOp = 35,
/// Load built-in module export. Operands: u16 spec_const_idx, u16 export_name_const_idx. Pushes Value.
LoadNativeExport = 36,
/// `new callee(...args)` (operand: u16 arg count). Stack: callee, then args — same as Call.
Construct = 37,
/// `new callee(...spread)` — stack: args array, then callee (same order as CallSpread).
ConstructSpread = 38,
}
impl Opcode {
/// Decode byte to opcode. Safe for b in 0..=38 (matches #[repr(u8)] discriminants).
#[inline]
pub fn from_u8(b: u8) -> Option<Opcode> {
if b <= 38 {
Some(unsafe { std::mem::transmute(b) })
} else {
None
}
}
/// Size in bytes of this instruction at `ip` (including operands). Returns None if truncated.
pub fn instruction_size(self, code: &[u8], ip: usize) -> Option<usize> {
let size = match self {
Opcode::Nop | Opcode::Pop | Opcode::Dup | Opcode::Return | Opcode::ExitTry
| Opcode::ArrayMapIdentity | Opcode::CallSpread | Opcode::ConstructSpread => 1,
Opcode::ArraySortByProperty | Opcode::ArrayMapBinOp | Opcode::ArrayFilterBinOp
| Opcode::LoadNativeExport => 5,
_ => 3,
};
if ip + size > code.len() {
return None;
}
Some(size)
}
}