we (web engine): Experimental web browser project to understand the limits of Claude
at main 1098 lines 39 kB view raw
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}