just_engine/runner/jit/bytecode.rs
1//! Bytecode instruction set and chunk structure for the JIT compiler.
2//!
3//! Defines a flat, stack-based bytecode IR that the compiler emits
4//! and the VM executes.
5
6use crate::runner::ds::value::JsValue;
7
8/// Bytecode opcodes for the stack-based VM.
9#[derive(Debug, Clone, Copy, PartialEq)]
10#[repr(u8)]
11pub enum OpCode {
12 // ── Constants & Literals ──────────────────────────────────
13 /// Push a constant from the constant pool onto the stack.
14 Constant,
15 /// Push `undefined` onto the stack.
16 Undefined,
17 /// Push `null` onto the stack.
18 Null,
19 /// Push `true` onto the stack.
20 True,
21 /// Push `false` onto the stack.
22 False,
23
24 // ── Arithmetic ───────────────────────────────────────────
25 /// Pop two values, push their sum.
26 Add,
27 /// Pop two values, push their difference.
28 Sub,
29 /// Pop two values, push their product.
30 Mul,
31 /// Pop two values, push their quotient.
32 Div,
33 /// Pop two values, push their remainder.
34 Mod,
35 /// Pop one value, push its numeric negation.
36 Negate,
37
38 // ── Bitwise ──────────────────────────────────────────────
39 /// Bitwise AND.
40 BitAnd,
41 /// Bitwise OR.
42 BitOr,
43 /// Bitwise XOR.
44 BitXor,
45 /// Bitwise NOT.
46 BitNot,
47 /// Left shift.
48 ShiftLeft,
49 /// Signed right shift.
50 ShiftRight,
51 /// Unsigned right shift.
52 UShiftRight,
53
54 // ── Comparison ───────────────────────────────────────────
55 /// Strict equality (`===`).
56 StrictEqual,
57 /// Strict inequality (`!==`).
58 StrictNotEqual,
59 /// Abstract equality (`==`).
60 Equal,
61 /// Abstract inequality (`!=`).
62 NotEqual,
63 /// Less than.
64 LessThan,
65 /// Less than or equal.
66 LessEqual,
67 /// Greater than.
68 GreaterThan,
69 /// Greater than or equal.
70 GreaterEqual,
71
72 // ── Logical / Unary ──────────────────────────────────────
73 /// Logical NOT.
74 Not,
75 /// `typeof` operator — pops value, pushes type string.
76 TypeOf,
77 /// `void` operator — pops value, pushes undefined.
78 Void,
79 /// Unary `+` — converts to number.
80 UnaryPlus,
81
82 // ── Variables ────────────────────────────────────────────
83 /// Get a global/local variable by name (operand: constant pool index of name string).
84 GetVar,
85 /// Set a variable by name (operand: constant pool index of name string).
86 /// The value to assign is on top of the stack.
87 SetVar,
88 /// Declare a `var` binding (operand: constant pool index of name string).
89 DeclareVar,
90 /// Declare a `let` binding (operand: constant pool index of name string).
91 DeclareLet,
92 /// Declare a `const` binding (operand: constant pool index of name string).
93 DeclareConst,
94 /// Initialize a binding with the value on top of the stack.
95 InitVar,
96 /// Initialize a let/const binding with the value on top of the stack.
97 InitBinding,
98
99 /// Get a local slot by index (operand: local slot index).
100 GetLocal,
101 /// Set a local slot by index (operand: local slot index).
102 SetLocal,
103 /// Initialize a local slot (operand: local slot index).
104 InitLocal,
105
106 // ── Control Flow ─────────────────────────────────────────
107 /// Unconditional jump (operand: absolute offset).
108 Jump,
109 /// Jump if top of stack is falsy (operand: absolute offset). Pops the value.
110 JumpIfFalse,
111 /// Jump if top of stack is truthy (operand: absolute offset). Pops the value.
112 JumpIfTrue,
113
114 // ── Loops ────────────────────────────────────────────────
115 /// Marker for loop start (used by break/continue resolution).
116 LoopStart,
117
118 // ── Stack manipulation ───────────────────────────────────
119 /// Pop and discard the top of the stack.
120 Pop,
121 /// Duplicate the top of the stack.
122 Dup,
123 /// Duplicate the top two stack values (a,b -> a,b,a,b).
124 Dup2,
125
126 // ── Scope ────────────────────────────────────────────────
127 /// Push a new block scope.
128 PushScope,
129 /// Pop a block scope.
130 PopScope,
131
132 // ── Objects & Properties ─────────────────────────────────
133 /// Get a property: pop object and push object.property.
134 /// Operand: constant pool index of property name.
135 GetProp,
136 /// Set a property: stack has [value, object].
137 /// Operand: constant pool index of property name.
138 SetProp,
139 /// Get a computed property: stack has [key, object].
140 GetElem,
141 /// Set a computed property: stack has [value, key, object].
142 SetElem,
143
144 // ── Function calls ───────────────────────────────────────
145 /// Call a function. Operand: argument count.
146 /// Stack: [arg_n, ..., arg_1, callee]
147 Call,
148 /// Call a method. Operand: argument count.
149 /// Stack: [arg_n, ..., arg_1, object]
150 /// Second operand: constant pool index of method name.
151 CallMethod,
152
153 // ── Misc ─────────────────────────────────────────────────
154 /// Return from the current function/script. Pops return value from stack.
155 Return,
156 /// Halt execution (end of script).
157 Halt,
158
159 // ── Pre/Post increment/decrement ─────────────────────────
160 /// Pre-increment a variable (++x). Operand: constant pool index of name.
161 PreIncVar,
162 /// Pre-decrement a variable (--x). Operand: constant pool index of name.
163 PreDecVar,
164 /// Post-increment a variable (x++). Operand: constant pool index of name.
165 PostIncVar,
166 /// Post-decrement a variable (x--). Operand: constant pool index of name.
167 PostDecVar,
168
169 // ── Compound assignment helpers ──────────────────────────
170 /// Get a variable for compound update. Operand: constant pool index of name.
171 GetVarForUpdate,
172
173 // ── Closures ────────────────────────────────────────────
174 /// Create a closure from a function template.
175 /// Operand: index into the chunk's `functions` table.
176 /// Pushes the resulting function object onto the stack.
177 MakeClosure,
178}
179
180/// A single bytecode instruction with optional operands.
181#[derive(Debug, Clone)]
182pub struct Instruction {
183 pub op: OpCode,
184 /// Primary operand (constant pool index, jump offset, arg count, etc.).
185 pub operand: u32,
186 /// Secondary operand (used by CallMethod for method name index).
187 pub operand2: u32,
188 /// Tertiary operand (used by CallMethod for object name index).
189 pub operand3: u32,
190}
191
192impl Instruction {
193 pub fn simple(op: OpCode) -> Self {
194 Instruction { op, operand: 0, operand2: 0, operand3: 0 }
195 }
196
197 pub fn with_operand(op: OpCode, operand: u32) -> Self {
198 Instruction { op, operand, operand2: 0, operand3: 0 }
199 }
200
201 pub fn with_two_operands(op: OpCode, operand: u32, operand2: u32) -> Self {
202 Instruction { op, operand, operand2, operand3: 0 }
203 }
204
205 pub fn with_three_operands(op: OpCode, operand: u32, operand2: u32, operand3: u32) -> Self {
206 Instruction { op, operand, operand2, operand3 }
207 }
208}
209
210/// A stored function template — raw pointers to AST nodes captured at compile time.
211#[derive(Debug, Clone, Copy)]
212pub struct FunctionTemplate {
213 /// Pointer to the function body AST node.
214 pub body_ptr: *const crate::parser::ast::FunctionBodyData,
215 /// Pointer to the formal parameter list.
216 pub params_ptr: *const Vec<crate::parser::ast::PatternType>,
217}
218
219// Safety: FunctionTemplate is only used within a single thread.
220unsafe impl Send for FunctionTemplate {}
221unsafe impl Sync for FunctionTemplate {}
222
223/// A compiled chunk of bytecode with its constant pool.
224#[derive(Debug, Clone)]
225pub struct Chunk {
226 /// The bytecode instructions.
227 pub code: Vec<Instruction>,
228 /// Constant pool — holds literal values.
229 pub constants: Vec<JsValue>,
230 /// Deduplicated name table for variable/property names.
231 /// Indexed by operand in GetVar/SetVar/etc. opcodes.
232 /// Separate from constants to allow zero-copy `&str` access in the VM.
233 pub names: Vec<String>,
234 /// Local slots table. Each entry stores an index into `names`.
235 /// Slot index is used by GetLocal/SetLocal/InitLocal opcodes.
236 pub locals: Vec<u32>,
237 /// Function templates for MakeClosure.
238 pub functions: Vec<FunctionTemplate>,
239}
240
241impl Chunk {
242 pub fn new() -> Self {
243 Chunk {
244 code: Vec::new(),
245 constants: Vec::new(),
246 names: Vec::new(),
247 locals: Vec::new(),
248 functions: Vec::new(),
249 }
250 }
251
252 /// Add a function template and return its index.
253 pub fn add_function(&mut self, template: FunctionTemplate) -> u32 {
254 let idx = self.functions.len();
255 self.functions.push(template);
256 idx as u32
257 }
258
259 /// Emit an instruction and return its index.
260 pub fn emit(&mut self, instr: Instruction) -> usize {
261 let idx = self.code.len();
262 self.code.push(instr);
263 idx
264 }
265
266 /// Emit a simple (no-operand) instruction.
267 pub fn emit_op(&mut self, op: OpCode) -> usize {
268 self.emit(Instruction::simple(op))
269 }
270
271 /// Emit an instruction with one operand.
272 pub fn emit_with(&mut self, op: OpCode, operand: u32) -> usize {
273 self.emit(Instruction::with_operand(op, operand))
274 }
275
276 /// Add a constant to the pool and return its index.
277 pub fn add_constant(&mut self, value: JsValue) -> u32 {
278 let idx = self.constants.len();
279 self.constants.push(value);
280 idx as u32
281 }
282
283 /// Add a name to the deduplicated name table and return its index.
284 /// Used for variable names, property names — allows zero-copy `&str` access in the VM.
285 pub fn add_name(&mut self, s: &str) -> u32 {
286 for (i, existing) in self.names.iter().enumerate() {
287 if existing == s {
288 return i as u32;
289 }
290 }
291 let idx = self.names.len();
292 self.names.push(s.to_string());
293 idx as u32
294 }
295
296 /// Add a local slot for a name index and return its slot index.
297 pub fn add_local(&mut self, name_idx: u32) -> u32 {
298 let slot = self.locals.len();
299 self.locals.push(name_idx);
300 slot as u32
301 }
302
303 /// Get a local slot's name (zero-copy reference).
304 #[inline]
305 pub fn get_local_name(&self, slot: u32) -> &str {
306 let name_idx = self.locals[slot as usize];
307 self.get_name(name_idx)
308 }
309
310 /// Get a name by index (zero-copy reference).
311 #[inline]
312 pub fn get_name(&self, idx: u32) -> &str {
313 &self.names[idx as usize]
314 }
315
316 /// Patch a jump instruction's operand to point to the current code position.
317 pub fn patch_jump(&mut self, jump_idx: usize) {
318 self.code[jump_idx].operand = self.code.len() as u32;
319 }
320
321 /// Get the current code position (for jump targets).
322 pub fn current_pos(&self) -> usize {
323 self.code.len()
324 }
325
326 /// Disassemble the chunk for debugging.
327 pub fn disassemble(&self, name: &str) -> String {
328 let mut out = format!("== {} ==\n", name);
329 for (i, instr) in self.code.iter().enumerate() {
330 out.push_str(&format!("{:04} {:?}", i, instr.op));
331 match instr.op {
332 OpCode::Constant => {
333 let val = &self.constants[instr.operand as usize];
334 out.push_str(&format!(" {} ({})", instr.operand, val));
335 }
336 OpCode::GetVar | OpCode::SetVar | OpCode::DeclareVar
337 | OpCode::DeclareLet | OpCode::DeclareConst
338 | OpCode::InitVar | OpCode::InitBinding
339 | OpCode::GetProp | OpCode::SetProp
340 | OpCode::PreIncVar | OpCode::PreDecVar
341 | OpCode::PostIncVar | OpCode::PostDecVar
342 | OpCode::GetVarForUpdate => {
343 let name_str = self.get_name(instr.operand);
344 out.push_str(&format!(" \"{}\"", name_str));
345 }
346 OpCode::GetLocal | OpCode::SetLocal | OpCode::InitLocal => {
347 let name_str = self.get_local_name(instr.operand);
348 out.push_str(&format!(" slot={} \"{}\"", instr.operand, name_str));
349 }
350 OpCode::Jump | OpCode::JumpIfFalse | OpCode::JumpIfTrue => {
351 out.push_str(&format!(" -> {:04}", instr.operand));
352 }
353 OpCode::Call => {
354 out.push_str(&format!(" argc={}", instr.operand));
355 }
356 OpCode::MakeClosure => {
357 out.push_str(&format!(" func_idx={}", instr.operand));
358 }
359 OpCode::CallMethod => {
360 let method = self.get_name(instr.operand2);
361 let obj_name = self.get_name(instr.operand3);
362 out.push_str(&format!(" argc={} method=\"{}\" obj=\"{}\"", instr.operand, method, obj_name));
363 }
364 _ => {}
365 }
366 out.push('\n');
367 }
368 out
369 }
370}