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