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