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