Skip to main content

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}