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