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