Skip to main content

just_engine/runner/jit/
reg_bytecode.rs

1//! Register-based bytecode instruction set and chunk structure.
2//!
3//! Defines a flat, register-based bytecode IR that the register VM will execute.
4
5use crate::runner::ds::value::JsValue;
6
7/// Bytecode opcodes for the register-based VM.
8#[derive(Debug, Clone, Copy, PartialEq)]
9#[repr(u8)]
10pub enum RegOpCode {
11    // ── Constants & Literals ──────────────────────────────────
12    /// dst = constant pool value.
13    LoadConst,
14    /// dst = undefined.
15    LoadUndefined,
16    /// dst = null.
17    LoadNull,
18    /// dst = true.
19    LoadTrue,
20    /// dst = false.
21    LoadFalse,
22
23    // ── Register moves ───────────────────────────────────────
24    /// dst = src1.
25    Move,
26
27    // ── Arithmetic ───────────────────────────────────────────
28    /// dst = src1 + src2.
29    Add,
30    /// dst = src1 - src2.
31    Sub,
32    /// dst = src1 * src2.
33    Mul,
34    /// dst = src1 / src2.
35    Div,
36    /// dst = src1 % src2.
37    Mod,
38    /// dst = -src1.
39    Negate,
40
41    // ── Bitwise ──────────────────────────────────────────────
42    /// dst = src1 & src2.
43    BitAnd,
44    /// dst = src1 | src2.
45    BitOr,
46    /// dst = src1 ^ src2.
47    BitXor,
48    /// dst = ~src1.
49    BitNot,
50    /// dst = src1 << src2.
51    ShiftLeft,
52    /// dst = src1 >> src2.
53    ShiftRight,
54    /// dst = src1 >>> src2.
55    UShiftRight,
56
57    // ── Comparison ───────────────────────────────────────────
58    /// dst = (src1 === src2).
59    StrictEqual,
60    /// dst = (src1 !== src2).
61    StrictNotEqual,
62    /// dst = (src1 == src2).
63    Equal,
64    /// dst = (src1 != src2).
65    NotEqual,
66    /// dst = (src1 < src2).
67    LessThan,
68    /// dst = (src1 <= src2).
69    LessEqual,
70    /// dst = (src1 > src2).
71    GreaterThan,
72    /// dst = (src1 >= src2).
73    GreaterEqual,
74
75    // ── Logical / Unary ──────────────────────────────────────
76    /// dst = !src1.
77    Not,
78    /// dst = typeof src1.
79    TypeOf,
80    /// dst = +src1.
81    UnaryPlus,
82
83    // ── Variables / Bindings ─────────────────────────────────
84    /// dst = get var by name (imm = name index).
85    GetVar,
86    /// set var by name (src1 = value, imm = name index).
87    SetVar,
88    /// declare var (imm = name index).
89    DeclareVar,
90    /// declare let (imm = name index).
91    DeclareLet,
92    /// declare const (imm = name index).
93    DeclareConst,
94    /// init var (src1 = value, imm = name index).
95    InitVar,
96    /// init let/const (src1 = value, imm = name index).
97    InitBinding,
98
99    // ── Objects & Properties ─────────────────────────────────
100    /// dst = obj.prop (src1 = obj, imm = name index).
101    GetProp,
102    /// obj.prop = value (src1 = obj, src2 = value, imm = name index).
103    SetProp,
104    /// dst = obj[key] (src1 = obj, src2 = key).
105    GetElem,
106    /// obj[key] = value (dst = value, src1 = obj, src2 = key).
107    SetElem,
108
109    // ── Control Flow ─────────────────────────────────────────
110    /// jump to imm (absolute index).
111    Jump,
112    /// if !src1 jump to imm.
113    JumpIfFalse,
114    /// if src1 jump to imm.
115    JumpIfTrue,
116
117    // ── Function calls ───────────────────────────────────────
118    /// dst = call callee (src1 = callee, imm = arg count).
119    Call,
120    /// dst = call method (src1 = object, imm = arg count, src2 = name index).
121    CallMethod,
122
123    // ── Misc ─────────────────────────────────────────────────
124    /// return src1.
125    Return,
126    /// halt (optional src1 as result).
127    Halt,
128}
129
130/// A single register bytecode instruction.
131#[derive(Debug, Clone)]
132pub struct RegInstruction {
133    pub op: RegOpCode,
134    /// Destination register (when applicable).
135    pub dst: u32,
136    /// First source register.
137    pub src1: u32,
138    /// Second source register.
139    pub src2: u32,
140    /// Immediate operand (constant index, name index, jump target, arg count).
141    pub imm: u32,
142}
143
144impl RegInstruction {
145    pub fn new(op: RegOpCode, dst: u32, src1: u32, src2: u32, imm: u32) -> Self {
146        RegInstruction { op, dst, src1, src2, imm }
147    }
148
149    pub fn simple(op: RegOpCode) -> Self {
150        RegInstruction::new(op, 0, 0, 0, 0)
151    }
152
153    pub fn with_dst(op: RegOpCode, dst: u32) -> Self {
154        RegInstruction::new(op, dst, 0, 0, 0)
155    }
156
157    pub fn with_dst_src(op: RegOpCode, dst: u32, src1: u32) -> Self {
158        RegInstruction::new(op, dst, src1, 0, 0)
159    }
160
161    pub fn with_dst_srcs(op: RegOpCode, dst: u32, src1: u32, src2: u32) -> Self {
162        RegInstruction::new(op, dst, src1, src2, 0)
163    }
164
165    pub fn with_dst_imm(op: RegOpCode, dst: u32, imm: u32) -> Self {
166        RegInstruction::new(op, dst, 0, 0, imm)
167    }
168
169    pub fn with_src_imm(op: RegOpCode, src1: u32, imm: u32) -> Self {
170        RegInstruction::new(op, 0, src1, 0, imm)
171    }
172
173    pub fn with_srcs_imm(op: RegOpCode, src1: u32, src2: u32, imm: u32) -> Self {
174        RegInstruction::new(op, 0, src1, src2, imm)
175    }
176}
177
178/// A compiled chunk of register bytecode with its constant pool.
179#[derive(Debug, Clone)]
180pub struct RegChunk {
181    /// The register bytecode instructions.
182    pub code: Vec<RegInstruction>,
183    /// Constant pool — holds literal values.
184    pub constants: Vec<JsValue>,
185    /// Deduplicated name table for variable/property names.
186    pub names: Vec<String>,
187    /// Local register table (name index -> register index).
188    pub locals: Vec<RegLocal>,
189    /// Register count required by this chunk.
190    pub register_count: u32,
191}
192
193impl RegChunk {
194    pub fn new() -> Self {
195        RegChunk {
196            code: Vec::new(),
197            constants: Vec::new(),
198            names: Vec::new(),
199            locals: Vec::new(),
200            register_count: 0,
201        }
202    }
203
204    /// Emit an instruction and return its index.
205    pub fn emit(&mut self, instr: RegInstruction) -> usize {
206        let idx = self.code.len();
207        self.code.push(instr);
208        idx
209    }
210
211    /// Emit a simple (no-operand) instruction.
212    pub fn emit_op(&mut self, op: RegOpCode) -> usize {
213        self.emit(RegInstruction::simple(op))
214    }
215
216    /// Add a constant to the pool and return its index.
217    pub fn add_constant(&mut self, value: JsValue) -> u32 {
218        let idx = self.constants.len();
219        self.constants.push(value);
220        idx as u32
221    }
222
223    /// Add a name to the deduplicated name table and return its index.
224    pub fn add_name(&mut self, s: &str) -> u32 {
225        for (i, existing) in self.names.iter().enumerate() {
226            if existing == s {
227                return i as u32;
228            }
229        }
230        let idx = self.names.len();
231        self.names.push(s.to_string());
232        idx as u32
233    }
234
235    /// Get a name by index (zero-copy reference).
236    #[inline]
237    pub fn get_name(&self, idx: u32) -> &str {
238        &self.names[idx as usize]
239    }
240
241    /// Add a local register for a name index and return its local index.
242    pub fn add_local(&mut self, name_idx: u32, reg: u32) -> u32 {
243        let idx = self.locals.len();
244        self.locals.push(RegLocal { name_idx, reg });
245        idx as u32
246    }
247
248    /// Get a local register's name.
249    #[inline]
250    pub fn get_local_name(&self, local_idx: u32) -> &str {
251        let name_idx = self.locals[local_idx as usize].name_idx;
252        self.get_name(name_idx)
253    }
254
255    /// Update the register count for this chunk.
256    pub fn set_register_count(&mut self, count: u32) {
257        self.register_count = count;
258    }
259
260    /// Disassemble the chunk for debugging.
261    pub fn disassemble(&self, name: &str) -> String {
262        let mut out = format!("== {} ==\n", name);
263        for (i, instr) in self.code.iter().enumerate() {
264            out.push_str(&format!("{:04}  {:?} dst={} src1={} src2={} imm={}", i, instr.op, instr.dst, instr.src1, instr.src2, instr.imm));
265            match instr.op {
266                RegOpCode::LoadConst => {
267                    if let Some(val) = self.constants.get(instr.imm as usize) {
268                        out.push_str(&format!("  const={}", val));
269                    }
270                }
271                RegOpCode::GetVar | RegOpCode::SetVar
272                | RegOpCode::DeclareVar | RegOpCode::DeclareLet
273                | RegOpCode::DeclareConst | RegOpCode::InitVar
274                | RegOpCode::InitBinding | RegOpCode::GetProp
275                | RegOpCode::SetProp => {
276                    if let Some(name) = self.names.get(instr.imm as usize) {
277                        out.push_str(&format!("  name=\"{}\"", name));
278                    }
279                }
280                RegOpCode::CallMethod => {
281                    if let Some(name) = self.names.get(instr.src2 as usize) {
282                        out.push_str(&format!("  method=\"{}\"", name));
283                    }
284                }
285                RegOpCode::Jump | RegOpCode::JumpIfFalse | RegOpCode::JumpIfTrue => {
286                    out.push_str(&format!("  -> {:04}", instr.imm));
287                }
288                _ => {}
289            }
290            out.push('\n');
291        }
292        out
293    }
294}
295
296/// Local register metadata for name sync.
297#[derive(Debug, Clone)]
298pub struct RegLocal {
299    pub name_idx: u32,
300    pub reg: u32,
301}