we (web engine): Experimental web browser project to understand the limits of Claude
1//! Register-based bytecode format for the JavaScript engine.
2//!
3//! Each instruction encodes register operands directly, making it suitable for
4//! JIT compilation. Instructions are variable-length, encoded as a 1-byte opcode
5//! followed by operand bytes.
6
7use std::fmt;
8
9/// A register index (0–255).
10pub type Reg = u8;
11
12/// An index into the constant pool.
13pub type ConstIdx = u16;
14
15/// An index into the name/string table.
16pub type NameIdx = u16;
17
18/// Bytecode instruction set.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20#[repr(u8)]
21pub enum Op {
22 // ── Register loads ──────────────────────────────────────
23 /// LoadConst dst, const_idx(u16) — load constant pool entry into register
24 LoadConst = 0x01,
25 /// LoadNull dst
26 LoadNull = 0x02,
27 /// LoadUndefined dst
28 LoadUndefined = 0x03,
29 /// LoadTrue dst
30 LoadTrue = 0x04,
31 /// LoadFalse dst
32 LoadFalse = 0x05,
33 /// Move dst, src
34 Move = 0x06,
35
36 // ── Global variable access ──────────────────────────────
37 /// LoadGlobal dst, name_idx(u16)
38 LoadGlobal = 0x07,
39 /// StoreGlobal name_idx(u16), src
40 StoreGlobal = 0x08,
41
42 // ── Arithmetic ──────────────────────────────────────────
43 /// Add dst, lhs, rhs
44 Add = 0x10,
45 /// Sub dst, lhs, rhs
46 Sub = 0x11,
47 /// Mul dst, lhs, rhs
48 Mul = 0x12,
49 /// Div dst, lhs, rhs
50 Div = 0x13,
51 /// Rem dst, lhs, rhs
52 Rem = 0x14,
53 /// Exp dst, lhs, rhs
54 Exp = 0x15,
55 /// Neg dst, src (unary minus)
56 Neg = 0x16,
57
58 // ── Bitwise ─────────────────────────────────────────────
59 /// BitAnd dst, lhs, rhs
60 BitAnd = 0x20,
61 /// BitOr dst, lhs, rhs
62 BitOr = 0x21,
63 /// BitXor dst, lhs, rhs
64 BitXor = 0x22,
65 /// ShiftLeft dst, lhs, rhs
66 ShiftLeft = 0x23,
67 /// ShiftRight dst, lhs, rhs
68 ShiftRight = 0x24,
69 /// UShiftRight dst, lhs, rhs
70 UShiftRight = 0x25,
71 /// BitNot dst, src
72 BitNot = 0x26,
73
74 // ── Comparison ──────────────────────────────────────────
75 /// Eq dst, lhs, rhs (==)
76 Eq = 0x30,
77 /// StrictEq dst, lhs, rhs (===)
78 StrictEq = 0x31,
79 /// NotEq dst, lhs, rhs (!=)
80 NotEq = 0x32,
81 /// StrictNotEq dst, lhs, rhs (!==)
82 StrictNotEq = 0x33,
83 /// LessThan dst, lhs, rhs
84 LessThan = 0x34,
85 /// LessEq dst, lhs, rhs
86 LessEq = 0x35,
87 /// GreaterThan dst, lhs, rhs
88 GreaterThan = 0x36,
89 /// GreaterEq dst, lhs, rhs
90 GreaterEq = 0x37,
91
92 // ── Logical / unary ─────────────────────────────────────
93 /// LogicalNot dst, src
94 LogicalNot = 0x38,
95 /// TypeOf dst, src
96 TypeOf = 0x39,
97 /// InstanceOf dst, lhs, rhs
98 InstanceOf = 0x3A,
99 /// In dst, lhs, rhs
100 In = 0x3B,
101 /// Void dst, src — evaluate src, produce undefined
102 Void = 0x3C,
103
104 // ── Control flow ────────────────────────────────────────
105 /// Jump offset(i32) — unconditional relative jump
106 Jump = 0x40,
107 /// JumpIfTrue reg, offset(i32)
108 JumpIfTrue = 0x41,
109 /// JumpIfFalse reg, offset(i32)
110 JumpIfFalse = 0x42,
111 /// JumpIfNullish reg, offset(i32)
112 JumpIfNullish = 0x43,
113
114 // ── Functions / calls ───────────────────────────────────
115 /// Call dst, func_reg, args_start, arg_count(u8)
116 Call = 0x50,
117 /// Return reg
118 Return = 0x51,
119 /// Throw reg
120 Throw = 0x52,
121 /// CreateClosure dst, func_idx(u16) — create a closure from a nested function
122 CreateClosure = 0x53,
123
124 // ── Object / property ───────────────────────────────────
125 /// GetProperty dst, obj_reg, key_reg
126 GetProperty = 0x60,
127 /// SetProperty obj_reg, key_reg, val_reg
128 SetProperty = 0x61,
129 /// CreateObject dst
130 CreateObject = 0x62,
131 /// CreateArray dst
132 CreateArray = 0x63,
133 /// GetPropertyByName dst, obj_reg, name_idx(u16) — optimized named access
134 GetPropertyByName = 0x64,
135 /// SetPropertyByName obj_reg, name_idx(u16), val_reg — optimized named store
136 SetPropertyByName = 0x65,
137
138 // ── Misc ────────────────────────────────────────────────
139 /// Delete dst, obj_reg, key_reg
140 Delete = 0x70,
141 /// LoadInt8 dst, i8 — load small integer without constant pool
142 LoadInt8 = 0x71,
143 /// ForInInit dst_keys, obj_reg — collect enumerable keys of object into an array
144 ForInInit = 0x72,
145 /// ForInNext dst_val, dst_done, keys_reg, idx_reg — get next key or signal done
146 ForInNext = 0x73,
147 /// SetPrototype obj_reg, proto_reg — set [[Prototype]] of obj to proto
148 SetPrototype = 0x74,
149 /// GetPrototype dst, obj_reg — get [[Prototype]] of obj
150 GetPrototype = 0x75,
151 /// PushExceptionHandler catch_reg, offset(i32) — push a try/catch handler
152 PushExceptionHandler = 0x76,
153 /// PopExceptionHandler — remove the current exception handler
154 PopExceptionHandler = 0x77,
155 /// NewCell dst — allocate a new GC cell initialized to undefined
156 NewCell = 0x78,
157 /// CellLoad dst, cell_reg — read the value stored in the cell
158 CellLoad = 0x79,
159 /// CellStore cell_reg, src — write a value into the cell
160 CellStore = 0x7A,
161 /// LoadUpvalue dst, idx(u8) — load from the closure's captured upvalue cell
162 LoadUpvalue = 0x7B,
163 /// StoreUpvalue idx(u8), src — store into the closure's captured upvalue cell
164 StoreUpvalue = 0x7C,
165
166 // ── Iterator / generator ───────────────────────────────
167 /// Yield dst, src — suspend generator, yield src value. On resume, dst gets the
168 /// value passed to next(). The VM saves the frame state and returns {value, done: false}.
169 Yield = 0x80,
170 /// Spread dst_array, src — iterate src via @@iterator, append all elements to dst_array
171 Spread = 0x81,
172}
173
174impl Op {
175 /// Decode an opcode from a byte, returning `None` for unrecognized values.
176 pub fn from_byte(b: u8) -> Option<Op> {
177 match b {
178 0x01 => Some(Op::LoadConst),
179 0x02 => Some(Op::LoadNull),
180 0x03 => Some(Op::LoadUndefined),
181 0x04 => Some(Op::LoadTrue),
182 0x05 => Some(Op::LoadFalse),
183 0x06 => Some(Op::Move),
184 0x07 => Some(Op::LoadGlobal),
185 0x08 => Some(Op::StoreGlobal),
186 0x10 => Some(Op::Add),
187 0x11 => Some(Op::Sub),
188 0x12 => Some(Op::Mul),
189 0x13 => Some(Op::Div),
190 0x14 => Some(Op::Rem),
191 0x15 => Some(Op::Exp),
192 0x16 => Some(Op::Neg),
193 0x20 => Some(Op::BitAnd),
194 0x21 => Some(Op::BitOr),
195 0x22 => Some(Op::BitXor),
196 0x23 => Some(Op::ShiftLeft),
197 0x24 => Some(Op::ShiftRight),
198 0x25 => Some(Op::UShiftRight),
199 0x26 => Some(Op::BitNot),
200 0x30 => Some(Op::Eq),
201 0x31 => Some(Op::StrictEq),
202 0x32 => Some(Op::NotEq),
203 0x33 => Some(Op::StrictNotEq),
204 0x34 => Some(Op::LessThan),
205 0x35 => Some(Op::LessEq),
206 0x36 => Some(Op::GreaterThan),
207 0x37 => Some(Op::GreaterEq),
208 0x38 => Some(Op::LogicalNot),
209 0x39 => Some(Op::TypeOf),
210 0x3A => Some(Op::InstanceOf),
211 0x3B => Some(Op::In),
212 0x3C => Some(Op::Void),
213 0x40 => Some(Op::Jump),
214 0x41 => Some(Op::JumpIfTrue),
215 0x42 => Some(Op::JumpIfFalse),
216 0x43 => Some(Op::JumpIfNullish),
217 0x50 => Some(Op::Call),
218 0x51 => Some(Op::Return),
219 0x52 => Some(Op::Throw),
220 0x53 => Some(Op::CreateClosure),
221 0x60 => Some(Op::GetProperty),
222 0x61 => Some(Op::SetProperty),
223 0x62 => Some(Op::CreateObject),
224 0x63 => Some(Op::CreateArray),
225 0x64 => Some(Op::GetPropertyByName),
226 0x65 => Some(Op::SetPropertyByName),
227 0x70 => Some(Op::Delete),
228 0x71 => Some(Op::LoadInt8),
229 0x72 => Some(Op::ForInInit),
230 0x73 => Some(Op::ForInNext),
231 0x74 => Some(Op::SetPrototype),
232 0x75 => Some(Op::GetPrototype),
233 0x76 => Some(Op::PushExceptionHandler),
234 0x77 => Some(Op::PopExceptionHandler),
235 0x78 => Some(Op::NewCell),
236 0x79 => Some(Op::CellLoad),
237 0x7A => Some(Op::CellStore),
238 0x7B => Some(Op::LoadUpvalue),
239 0x7C => Some(Op::StoreUpvalue),
240 0x80 => Some(Op::Yield),
241 0x81 => Some(Op::Spread),
242 _ => None,
243 }
244 }
245}
246
247/// A constant value stored in the constant pool.
248#[derive(Debug, Clone, PartialEq)]
249pub enum Constant {
250 Number(f64),
251 String(String),
252}
253
254/// Describes how a closure captures a single variable from its enclosing scope.
255#[derive(Debug, Clone)]
256pub struct UpvalueDef {
257 /// If true, `index` refers to a register in the immediately enclosing function
258 /// (which must hold a cell GcRef). If false, `index` refers to an upvalue slot
259 /// of the enclosing function (transitive capture).
260 pub is_local: bool,
261 /// Register index (if `is_local`) or upvalue index (if not) in the parent.
262 pub index: u8,
263}
264
265/// A compiled bytecode function.
266#[derive(Debug, Clone)]
267pub struct Function {
268 /// Name of the function (empty for anonymous / top-level).
269 pub name: String,
270 /// Number of named parameters.
271 pub param_count: u8,
272 /// Total register slots needed.
273 pub register_count: u8,
274 /// The bytecode bytes.
275 pub code: Vec<u8>,
276 /// Constant pool (numbers and strings).
277 pub constants: Vec<Constant>,
278 /// Name table for global/property name lookups.
279 pub names: Vec<String>,
280 /// Nested function definitions (referenced by CreateClosure).
281 pub functions: Vec<Function>,
282 /// Source map: bytecode offset → source line (sorted by offset).
283 pub source_map: Vec<(u32, u32)>,
284 /// Upvalue definitions: how this function captures variables from its parent.
285 pub upvalue_defs: Vec<UpvalueDef>,
286 /// Whether this is a generator function (function*).
287 pub is_generator: bool,
288}
289
290impl Function {
291 pub fn new(name: String, param_count: u8) -> Self {
292 Self {
293 name,
294 param_count,
295 register_count: 0,
296 code: Vec::new(),
297 constants: Vec::new(),
298 names: Vec::new(),
299 functions: Vec::new(),
300 source_map: Vec::new(),
301 upvalue_defs: Vec::new(),
302 is_generator: false,
303 }
304 }
305}
306
307/// A builder that emits bytecode into a `Function`.
308pub struct BytecodeBuilder {
309 pub func: Function,
310}
311
312impl BytecodeBuilder {
313 pub fn new(name: String, param_count: u8) -> Self {
314 Self {
315 func: Function::new(name, param_count),
316 }
317 }
318
319 /// Current bytecode offset (position of next emitted byte).
320 pub fn offset(&self) -> usize {
321 self.func.code.len()
322 }
323
324 /// Intern a constant, returning its index.
325 pub fn add_constant(&mut self, c: Constant) -> ConstIdx {
326 // Reuse existing constant if it matches.
327 for (i, existing) in self.func.constants.iter().enumerate() {
328 match (existing, &c) {
329 (Constant::String(a), Constant::String(b)) if a == b => return i as ConstIdx,
330 (Constant::Number(a), Constant::Number(b)) if a.to_bits() == b.to_bits() => {
331 return i as ConstIdx;
332 }
333 _ => {}
334 }
335 }
336 let idx = self.func.constants.len();
337 assert!(idx <= u16::MAX as usize, "constant pool overflow");
338 self.func.constants.push(c);
339 idx as ConstIdx
340 }
341
342 /// Intern a name, returning its index.
343 pub fn add_name(&mut self, name: &str) -> NameIdx {
344 for (i, existing) in self.func.names.iter().enumerate() {
345 if existing == name {
346 return i as NameIdx;
347 }
348 }
349 let idx = self.func.names.len();
350 assert!(idx <= u16::MAX as usize, "name table overflow");
351 self.func.names.push(name.to_string());
352 idx as NameIdx
353 }
354
355 /// Add a nested function, returning its index.
356 pub fn add_function(&mut self, f: Function) -> u16 {
357 let idx = self.func.functions.len();
358 assert!(idx <= u16::MAX as usize, "function table overflow");
359 self.func.functions.push(f);
360 idx as u16
361 }
362
363 // ── Emit helpers ────────────────────────────────────────
364
365 fn emit_u8(&mut self, v: u8) {
366 self.func.code.push(v);
367 }
368
369 fn emit_u16(&mut self, v: u16) {
370 self.func.code.extend_from_slice(&v.to_le_bytes());
371 }
372
373 fn emit_i32(&mut self, v: i32) {
374 self.func.code.extend_from_slice(&v.to_le_bytes());
375 }
376
377 // ── Single-operand instructions ─────────────────────────
378
379 /// Emit: LoadNull dst | LoadUndefined dst | LoadTrue dst | LoadFalse dst
380 pub fn emit_reg(&mut self, op: Op, dst: Reg) {
381 self.emit_u8(op as u8);
382 self.emit_u8(dst);
383 }
384
385 /// Emit: Move dst, src
386 pub fn emit_reg_reg(&mut self, op: Op, a: Reg, b: Reg) {
387 self.emit_u8(op as u8);
388 self.emit_u8(a);
389 self.emit_u8(b);
390 }
391
392 /// Emit: ForInNext dst_val, dst_done, keys, idx (4-register instruction)
393 pub fn emit_reg4(&mut self, op: Op, a: Reg, b: Reg, c: Reg, d: Reg) {
394 self.emit_u8(op as u8);
395 self.emit_u8(a);
396 self.emit_u8(b);
397 self.emit_u8(c);
398 self.emit_u8(d);
399 }
400
401 /// Emit: Add dst, lhs, rhs (and other 3-register instructions)
402 pub fn emit_reg3(&mut self, op: Op, dst: Reg, lhs: Reg, rhs: Reg) {
403 self.emit_u8(op as u8);
404 self.emit_u8(dst);
405 self.emit_u8(lhs);
406 self.emit_u8(rhs);
407 }
408
409 /// Emit: LoadConst dst, const_idx | CreateClosure dst, func_idx
410 pub fn emit_reg_u16(&mut self, op: Op, dst: Reg, idx: u16) {
411 self.emit_u8(op as u8);
412 self.emit_u8(dst);
413 self.emit_u16(idx);
414 }
415
416 /// Emit: LoadGlobal dst, name_idx
417 pub fn emit_load_global(&mut self, dst: Reg, name_idx: NameIdx) {
418 self.emit_u8(Op::LoadGlobal as u8);
419 self.emit_u8(dst);
420 self.emit_u16(name_idx);
421 }
422
423 /// Emit: StoreGlobal name_idx, src
424 pub fn emit_store_global(&mut self, name_idx: NameIdx, src: Reg) {
425 self.emit_u8(Op::StoreGlobal as u8);
426 self.emit_u16(name_idx);
427 self.emit_u8(src);
428 }
429
430 /// Emit: Jump offset (placeholder, returns patch position)
431 pub fn emit_jump(&mut self, op: Op) -> usize {
432 self.emit_u8(op as u8);
433 if op == Op::JumpIfTrue || op == Op::JumpIfFalse || op == Op::JumpIfNullish {
434 panic!("use emit_cond_jump for conditional jumps");
435 }
436 let pos = self.offset();
437 self.emit_i32(0); // placeholder
438 pos
439 }
440
441 /// Emit: JumpIfTrue/JumpIfFalse/JumpIfNullish reg, offset (placeholder)
442 pub fn emit_cond_jump(&mut self, op: Op, reg: Reg) -> usize {
443 self.emit_u8(op as u8);
444 self.emit_u8(reg);
445 let pos = self.offset();
446 self.emit_i32(0); // placeholder
447 pos
448 }
449
450 /// Patch a jump offset at the given position to point to the current offset.
451 pub fn patch_jump(&mut self, pos: usize) {
452 self.patch_jump_to(pos, self.offset());
453 }
454
455 /// Patch a jump offset at the given position to point to an arbitrary target.
456 pub fn patch_jump_to(&mut self, pos: usize, target: usize) {
457 let offset = target as i32 - (pos as i32 + 4); // relative to after the i32
458 let bytes = offset.to_le_bytes();
459 self.func.code[pos] = bytes[0];
460 self.func.code[pos + 1] = bytes[1];
461 self.func.code[pos + 2] = bytes[2];
462 self.func.code[pos + 3] = bytes[3];
463 }
464
465 /// Emit: Jump with a known target (backward jump).
466 pub fn emit_jump_to(&mut self, target: usize) {
467 self.emit_u8(Op::Jump as u8);
468 let from = self.offset() as i32 + 4;
469 let offset = target as i32 - from;
470 self.emit_i32(offset);
471 }
472
473 /// Emit: JumpIfTrue/JumpIfFalse with a known target (backward jump).
474 pub fn emit_cond_jump_to(&mut self, op: Op, reg: Reg, target: usize) {
475 self.emit_u8(op as u8);
476 self.emit_u8(reg);
477 let from = self.offset() as i32 + 4;
478 let offset = target as i32 - from;
479 self.emit_i32(offset);
480 }
481
482 /// Emit: Call dst, func_reg, args_start, arg_count
483 pub fn emit_call(&mut self, dst: Reg, func: Reg, args_start: Reg, arg_count: u8) {
484 self.emit_u8(Op::Call as u8);
485 self.emit_u8(dst);
486 self.emit_u8(func);
487 self.emit_u8(args_start);
488 self.emit_u8(arg_count);
489 }
490
491 /// Emit: GetPropertyByName dst, obj, name_idx
492 pub fn emit_get_prop_name(&mut self, dst: Reg, obj: Reg, name_idx: NameIdx) {
493 self.emit_u8(Op::GetPropertyByName as u8);
494 self.emit_u8(dst);
495 self.emit_u8(obj);
496 self.emit_u16(name_idx);
497 }
498
499 /// Emit: PushExceptionHandler catch_reg, offset (placeholder, returns patch position)
500 pub fn emit_push_exception_handler(&mut self, catch_reg: Reg) -> usize {
501 self.emit_u8(Op::PushExceptionHandler as u8);
502 self.emit_u8(catch_reg);
503 let pos = self.offset();
504 self.emit_i32(0); // placeholder for catch block offset
505 pos
506 }
507
508 /// Emit: PopExceptionHandler
509 pub fn emit_pop_exception_handler(&mut self) {
510 self.emit_u8(Op::PopExceptionHandler as u8);
511 }
512
513 /// Emit: SetPropertyByName obj, name_idx, val
514 pub fn emit_set_prop_name(&mut self, obj: Reg, name_idx: NameIdx, val: Reg) {
515 self.emit_u8(Op::SetPropertyByName as u8);
516 self.emit_u8(obj);
517 self.emit_u16(name_idx);
518 self.emit_u8(val);
519 }
520
521 /// Emit: LoadInt8 dst, value
522 pub fn emit_load_int8(&mut self, dst: Reg, value: i8) {
523 self.emit_u8(Op::LoadInt8 as u8);
524 self.emit_u8(dst);
525 self.emit_u8(value as u8);
526 }
527
528 /// Emit: LoadUpvalue dst, idx
529 pub fn emit_load_upvalue(&mut self, dst: Reg, idx: u8) {
530 self.emit_u8(Op::LoadUpvalue as u8);
531 self.emit_u8(dst);
532 self.emit_u8(idx);
533 }
534
535 /// Emit: StoreUpvalue idx, src
536 pub fn emit_store_upvalue(&mut self, idx: u8, src: Reg) {
537 self.emit_u8(Op::StoreUpvalue as u8);
538 self.emit_u8(idx);
539 self.emit_u8(src);
540 }
541
542 /// Emit: Yield dst, src — suspend generator
543 pub fn emit_yield(&mut self, dst: Reg, src: Reg) {
544 self.emit_u8(Op::Yield as u8);
545 self.emit_u8(dst);
546 self.emit_u8(src);
547 }
548
549 /// Emit: Spread dst_array, src — spread iterable into array
550 pub fn emit_spread(&mut self, dst_array: Reg, src: Reg) {
551 self.emit_u8(Op::Spread as u8);
552 self.emit_u8(dst_array);
553 self.emit_u8(src);
554 }
555
556 /// Add a source map entry: current bytecode offset → source line.
557 pub fn add_source_map(&mut self, line: u32) {
558 let offset = self.offset() as u32;
559 self.func.source_map.push((offset, line));
560 }
561
562 /// Finalize and return the compiled function.
563 pub fn finish(self) -> Function {
564 self.func
565 }
566}
567
568// ── Disassembler ────────────────────────────────────────────
569
570impl Function {
571 /// Disassemble the bytecode to a human-readable string.
572 pub fn disassemble(&self) -> String {
573 let mut out = String::new();
574 out.push_str(&format!(
575 "function {}({} params, {} regs):\n",
576 if self.name.is_empty() {
577 "<anonymous>"
578 } else {
579 &self.name
580 },
581 self.param_count,
582 self.register_count,
583 ));
584
585 // Constants
586 if !self.constants.is_empty() {
587 out.push_str(" constants:\n");
588 for (i, c) in self.constants.iter().enumerate() {
589 out.push_str(&format!(" [{i}] {c:?}\n"));
590 }
591 }
592
593 // Names
594 if !self.names.is_empty() {
595 out.push_str(" names:\n");
596 for (i, n) in self.names.iter().enumerate() {
597 out.push_str(&format!(" [{i}] \"{n}\"\n"));
598 }
599 }
600
601 // Instructions
602 out.push_str(" code:\n");
603 let code = &self.code;
604 let mut pc = 0;
605 while pc < code.len() {
606 let offset = pc;
607 let byte = code[pc];
608 pc += 1;
609 let Some(op) = Op::from_byte(byte) else {
610 out.push_str(&format!(" {offset:04X} <unknown 0x{byte:02X}>\n"));
611 continue;
612 };
613 let line = match op {
614 Op::LoadConst => {
615 let dst = code[pc];
616 pc += 1;
617 let idx = u16::from_le_bytes([code[pc], code[pc + 1]]);
618 pc += 2;
619 format!("LoadConst r{dst}, #{idx}")
620 }
621 Op::LoadNull => {
622 let dst = code[pc];
623 pc += 1;
624 format!("LoadNull r{dst}")
625 }
626 Op::LoadUndefined => {
627 let dst = code[pc];
628 pc += 1;
629 format!("LoadUndefined r{dst}")
630 }
631 Op::LoadTrue => {
632 let dst = code[pc];
633 pc += 1;
634 format!("LoadTrue r{dst}")
635 }
636 Op::LoadFalse => {
637 let dst = code[pc];
638 pc += 1;
639 format!("LoadFalse r{dst}")
640 }
641 Op::Move => {
642 let dst = code[pc];
643 let src = code[pc + 1];
644 pc += 2;
645 format!("Move r{dst}, r{src}")
646 }
647 Op::LoadGlobal => {
648 let dst = code[pc];
649 pc += 1;
650 let idx = u16::from_le_bytes([code[pc], code[pc + 1]]);
651 pc += 2;
652 let name = self
653 .names
654 .get(idx as usize)
655 .map(|s| s.as_str())
656 .unwrap_or("?");
657 format!("LoadGlobal r{dst}, @{idx}(\"{name}\")")
658 }
659 Op::StoreGlobal => {
660 let idx = u16::from_le_bytes([code[pc], code[pc + 1]]);
661 pc += 2;
662 let src = code[pc];
663 pc += 1;
664 let name = self
665 .names
666 .get(idx as usize)
667 .map(|s| s.as_str())
668 .unwrap_or("?");
669 format!("StoreGlobal @{idx}(\"{name}\"), r{src}")
670 }
671 Op::Add | Op::Sub | Op::Mul | Op::Div | Op::Rem | Op::Exp => {
672 let dst = code[pc];
673 let lhs = code[pc + 1];
674 let rhs = code[pc + 2];
675 pc += 3;
676 format!("{op:?} r{dst}, r{lhs}, r{rhs}")
677 }
678 Op::Neg => {
679 let dst = code[pc];
680 let src = code[pc + 1];
681 pc += 2;
682 format!("Neg r{dst}, r{src}")
683 }
684 Op::BitAnd
685 | Op::BitOr
686 | Op::BitXor
687 | Op::ShiftLeft
688 | Op::ShiftRight
689 | Op::UShiftRight => {
690 let dst = code[pc];
691 let lhs = code[pc + 1];
692 let rhs = code[pc + 2];
693 pc += 3;
694 format!("{op:?} r{dst}, r{lhs}, r{rhs}")
695 }
696 Op::BitNot => {
697 let dst = code[pc];
698 let src = code[pc + 1];
699 pc += 2;
700 format!("BitNot r{dst}, r{src}")
701 }
702 Op::Eq
703 | Op::StrictEq
704 | Op::NotEq
705 | Op::StrictNotEq
706 | Op::LessThan
707 | Op::LessEq
708 | Op::GreaterThan
709 | Op::GreaterEq
710 | Op::InstanceOf
711 | Op::In => {
712 let dst = code[pc];
713 let lhs = code[pc + 1];
714 let rhs = code[pc + 2];
715 pc += 3;
716 format!("{op:?} r{dst}, r{lhs}, r{rhs}")
717 }
718 Op::LogicalNot => {
719 let dst = code[pc];
720 let src = code[pc + 1];
721 pc += 2;
722 format!("LogicalNot r{dst}, r{src}")
723 }
724 Op::TypeOf => {
725 let dst = code[pc];
726 let src = code[pc + 1];
727 pc += 2;
728 format!("TypeOf r{dst}, r{src}")
729 }
730 Op::Void => {
731 let dst = code[pc];
732 let src = code[pc + 1];
733 pc += 2;
734 format!("Void r{dst}, r{src}")
735 }
736 Op::Jump => {
737 let off =
738 i32::from_le_bytes([code[pc], code[pc + 1], code[pc + 2], code[pc + 3]]);
739 pc += 4;
740 let target = pc as i32 + off;
741 format!("Jump {off:+} (-> {target:04X})")
742 }
743 Op::JumpIfTrue | Op::JumpIfFalse | Op::JumpIfNullish => {
744 let reg = code[pc];
745 pc += 1;
746 let off =
747 i32::from_le_bytes([code[pc], code[pc + 1], code[pc + 2], code[pc + 3]]);
748 pc += 4;
749 let target = pc as i32 + off;
750 format!("{op:?} r{reg}, {off:+} (-> {target:04X})")
751 }
752 Op::Call => {
753 let dst = code[pc];
754 let func = code[pc + 1];
755 let args_start = code[pc + 2];
756 let arg_count = code[pc + 3];
757 pc += 4;
758 format!("Call r{dst}, r{func}, r{args_start}, {arg_count}")
759 }
760 Op::Return => {
761 let reg = code[pc];
762 pc += 1;
763 format!("Return r{reg}")
764 }
765 Op::Throw => {
766 let reg = code[pc];
767 pc += 1;
768 format!("Throw r{reg}")
769 }
770 Op::CreateClosure => {
771 let dst = code[pc];
772 pc += 1;
773 let idx = u16::from_le_bytes([code[pc], code[pc + 1]]);
774 pc += 2;
775 format!("CreateClosure r{dst}, func#{idx}")
776 }
777 Op::GetProperty => {
778 let dst = code[pc];
779 let obj = code[pc + 1];
780 let key = code[pc + 2];
781 pc += 3;
782 format!("GetProperty r{dst}, r{obj}, r{key}")
783 }
784 Op::SetProperty => {
785 let obj = code[pc];
786 let key = code[pc + 1];
787 let val = code[pc + 2];
788 pc += 3;
789 format!("SetProperty r{obj}, r{key}, r{val}")
790 }
791 Op::CreateObject => {
792 let dst = code[pc];
793 pc += 1;
794 format!("CreateObject r{dst}")
795 }
796 Op::CreateArray => {
797 let dst = code[pc];
798 pc += 1;
799 format!("CreateArray r{dst}")
800 }
801 Op::GetPropertyByName => {
802 let dst = code[pc];
803 let obj = code[pc + 1];
804 pc += 2;
805 let idx = u16::from_le_bytes([code[pc], code[pc + 1]]);
806 pc += 2;
807 let name = self
808 .names
809 .get(idx as usize)
810 .map(|s| s.as_str())
811 .unwrap_or("?");
812 format!("GetPropertyByName r{dst}, r{obj}, @{idx}(\"{name}\")")
813 }
814 Op::SetPropertyByName => {
815 let obj = code[pc];
816 pc += 1;
817 let idx = u16::from_le_bytes([code[pc], code[pc + 1]]);
818 pc += 2;
819 let val = code[pc];
820 pc += 1;
821 let name = self
822 .names
823 .get(idx as usize)
824 .map(|s| s.as_str())
825 .unwrap_or("?");
826 format!("SetPropertyByName r{obj}, @{idx}(\"{name}\"), r{val}")
827 }
828 Op::Delete => {
829 let dst = code[pc];
830 let obj = code[pc + 1];
831 let key = code[pc + 2];
832 pc += 3;
833 format!("Delete r{dst}, r{obj}, r{key}")
834 }
835 Op::LoadInt8 => {
836 let dst = code[pc];
837 let val = code[pc + 1] as i8;
838 pc += 2;
839 format!("LoadInt8 r{dst}, {val}")
840 }
841 Op::ForInInit => {
842 let dst = code[pc];
843 let obj = code[pc + 1];
844 pc += 2;
845 format!("ForInInit r{dst}, r{obj}")
846 }
847 Op::ForInNext => {
848 let dst_val = code[pc];
849 let dst_done = code[pc + 1];
850 let keys = code[pc + 2];
851 let idx = code[pc + 3];
852 pc += 4;
853 format!("ForInNext r{dst_val}, r{dst_done}, r{keys}, r{idx}")
854 }
855 Op::SetPrototype => {
856 let obj = code[pc];
857 let proto = code[pc + 1];
858 pc += 2;
859 format!("SetPrototype r{obj}, r{proto}")
860 }
861 Op::GetPrototype => {
862 let dst = code[pc];
863 let obj = code[pc + 1];
864 pc += 2;
865 format!("GetPrototype r{dst}, r{obj}")
866 }
867 Op::PushExceptionHandler => {
868 let catch_reg = code[pc];
869 let b0 = code[pc + 1] as i32;
870 let b1 = code[pc + 2] as i32;
871 let b2 = code[pc + 3] as i32;
872 let b3 = code[pc + 4] as i32;
873 let off = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
874 let target = (offset as i32 + 1 + 5 + off) as usize;
875 pc += 5;
876 format!("PushExceptionHandler r{catch_reg}, @{target:04X}")
877 }
878 Op::PopExceptionHandler => "PopExceptionHandler".to_string(),
879 Op::NewCell => {
880 let dst = code[pc];
881 pc += 1;
882 format!("NewCell r{dst}")
883 }
884 Op::CellLoad => {
885 let dst = code[pc];
886 let cell = code[pc + 1];
887 pc += 2;
888 format!("CellLoad r{dst}, r{cell}")
889 }
890 Op::CellStore => {
891 let cell = code[pc];
892 let src = code[pc + 1];
893 pc += 2;
894 format!("CellStore r{cell}, r{src}")
895 }
896 Op::LoadUpvalue => {
897 let dst = code[pc];
898 let idx = code[pc + 1];
899 pc += 2;
900 format!("LoadUpvalue r{dst}, uv{idx}")
901 }
902 Op::StoreUpvalue => {
903 let idx = code[pc];
904 let src = code[pc + 1];
905 pc += 2;
906 format!("StoreUpvalue uv{idx}, r{src}")
907 }
908 Op::Yield => {
909 let dst = code[pc];
910 let src = code[pc + 1];
911 pc += 2;
912 format!("Yield r{dst}, r{src}")
913 }
914 Op::Spread => {
915 let dst = code[pc];
916 let src = code[pc + 1];
917 pc += 2;
918 format!("Spread r{dst}, r{src}")
919 }
920 };
921 out.push_str(&format!(" {offset:04X} {line}\n"));
922 }
923
924 // Nested functions
925 for (i, f) in self.functions.iter().enumerate() {
926 out.push_str(&format!("\n --- nested function [{i}] ---\n"));
927 let nested_dis = f.disassemble();
928 for line in nested_dis.lines() {
929 out.push_str(&format!(" {line}\n"));
930 }
931 }
932
933 out
934 }
935}
936
937impl fmt::Display for Function {
938 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
939 write!(f, "{}", self.disassemble())
940 }
941}
942
943#[cfg(test)]
944mod tests {
945 use super::*;
946
947 #[test]
948 fn test_op_roundtrip() {
949 // Verify all opcodes survive byte conversion.
950 let ops = [
951 Op::LoadConst,
952 Op::LoadNull,
953 Op::LoadUndefined,
954 Op::LoadTrue,
955 Op::LoadFalse,
956 Op::Move,
957 Op::LoadGlobal,
958 Op::StoreGlobal,
959 Op::Add,
960 Op::Sub,
961 Op::Mul,
962 Op::Div,
963 Op::Rem,
964 Op::Exp,
965 Op::Neg,
966 Op::BitAnd,
967 Op::BitOr,
968 Op::BitXor,
969 Op::ShiftLeft,
970 Op::ShiftRight,
971 Op::UShiftRight,
972 Op::BitNot,
973 Op::Eq,
974 Op::StrictEq,
975 Op::NotEq,
976 Op::StrictNotEq,
977 Op::LessThan,
978 Op::LessEq,
979 Op::GreaterThan,
980 Op::GreaterEq,
981 Op::LogicalNot,
982 Op::TypeOf,
983 Op::InstanceOf,
984 Op::In,
985 Op::Void,
986 Op::Jump,
987 Op::JumpIfTrue,
988 Op::JumpIfFalse,
989 Op::JumpIfNullish,
990 Op::Call,
991 Op::Return,
992 Op::Throw,
993 Op::CreateClosure,
994 Op::GetProperty,
995 Op::SetProperty,
996 Op::CreateObject,
997 Op::CreateArray,
998 Op::GetPropertyByName,
999 Op::SetPropertyByName,
1000 Op::Delete,
1001 Op::LoadInt8,
1002 Op::ForInInit,
1003 Op::ForInNext,
1004 Op::SetPrototype,
1005 Op::GetPrototype,
1006 Op::PushExceptionHandler,
1007 Op::PopExceptionHandler,
1008 Op::NewCell,
1009 Op::CellLoad,
1010 Op::CellStore,
1011 Op::LoadUpvalue,
1012 Op::StoreUpvalue,
1013 Op::Yield,
1014 Op::Spread,
1015 ];
1016 for op in ops {
1017 assert_eq!(
1018 Op::from_byte(op as u8),
1019 Some(op),
1020 "roundtrip failed for {op:?}"
1021 );
1022 }
1023 }
1024
1025 #[test]
1026 fn test_unknown_opcode() {
1027 assert_eq!(Op::from_byte(0xFF), None);
1028 assert_eq!(Op::from_byte(0x00), None);
1029 }
1030
1031 #[test]
1032 fn test_constant_pool_dedup() {
1033 let mut b = BytecodeBuilder::new("test".into(), 0);
1034 let i1 = b.add_constant(Constant::Number(42.0));
1035 let i2 = b.add_constant(Constant::Number(42.0));
1036 assert_eq!(i1, i2);
1037 let i3 = b.add_constant(Constant::String("hello".into()));
1038 let i4 = b.add_constant(Constant::String("hello".into()));
1039 assert_eq!(i3, i4);
1040 assert_ne!(i1, i3);
1041 assert_eq!(b.func.constants.len(), 2);
1042 }
1043
1044 #[test]
1045 fn test_name_dedup() {
1046 let mut b = BytecodeBuilder::new("test".into(), 0);
1047 let i1 = b.add_name("foo");
1048 let i2 = b.add_name("foo");
1049 assert_eq!(i1, i2);
1050 let i3 = b.add_name("bar");
1051 assert_ne!(i1, i3);
1052 }
1053
1054 #[test]
1055 fn test_emit_and_disassemble() {
1056 let mut b = BytecodeBuilder::new("test".into(), 0);
1057 b.func.register_count = 3;
1058 let ci = b.add_constant(Constant::Number(10.0));
1059 b.emit_reg_u16(Op::LoadConst, 0, ci);
1060 b.emit_reg(Op::LoadNull, 1);
1061 b.emit_reg3(Op::Add, 2, 0, 1);
1062 b.emit_reg(Op::Return, 2);
1063 let func = b.finish();
1064 let dis = func.disassemble();
1065 assert!(dis.contains("LoadConst r0, #0"));
1066 assert!(dis.contains("LoadNull r1"));
1067 assert!(dis.contains("Add r2, r0, r1"));
1068 assert!(dis.contains("Return r2"));
1069 }
1070
1071 #[test]
1072 fn test_jump_patching() {
1073 let mut b = BytecodeBuilder::new("test".into(), 0);
1074 b.func.register_count = 1;
1075 b.emit_reg(Op::LoadTrue, 0);
1076 let patch = b.emit_cond_jump(Op::JumpIfFalse, 0);
1077 b.emit_reg(Op::LoadNull, 0);
1078 b.patch_jump(patch);
1079 b.emit_reg(Op::Return, 0);
1080 let func = b.finish();
1081 let dis = func.disassemble();
1082 // The jump should target the Return instruction
1083 assert!(dis.contains("JumpIfFalse"));
1084 assert!(dis.contains("Return"));
1085 }
1086
1087 #[test]
1088 fn test_load_int8() {
1089 let mut b = BytecodeBuilder::new("test".into(), 0);
1090 b.func.register_count = 1;
1091 b.emit_load_int8(0, 42);
1092 b.emit_load_int8(0, -1);
1093 let func = b.finish();
1094 let dis = func.disassemble();
1095 assert!(dis.contains("LoadInt8 r0, 42"));
1096 assert!(dis.contains("LoadInt8 r0, -1"));
1097 }
1098}