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