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