Something went wrong. Try again.
we (web engine): Experimental web browser project to understand the limits of Claude
Something went wrong. Try again.
Rust
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059//! Register-based bytecode format for the JavaScript engine.//!//! Each instruction encodes register operands directly, making it suitable for//! JIT compilation. Instructions are variable-length, encoded as a 1-byte opcode//! followed by operand bytes.
use std::fmt;
/// A register index (0–255).pub type Reg = u8;
/// An index into the constant pool.pub type ConstIdx = u16;
/// An index into the name/string table.pub type NameIdx = u16;
/// Bytecode instruction set.#[derive(Debug, Clone, Copy, PartialEq, Eq)]#[repr(u8)]pub enum Op { // ── Register loads ────────────────────────────────────── /// LoadConst dst, const_idx(u16) — load constant pool entry into register LoadConst = 0x01, /// LoadNull dst LoadNull = 0x02, /// LoadUndefined dst LoadUndefined = 0x03, /// LoadTrue dst LoadTrue = 0x04, /// LoadFalse dst LoadFalse = 0x05, /// Move dst, src Move = 0x06,
// ── Global variable access ────────────────────────────── /// LoadGlobal dst, name_idx(u16) LoadGlobal = 0x07, /// StoreGlobal name_idx(u16), src StoreGlobal = 0x08,
// ── Arithmetic ────────────────────────────────────────── /// Add dst, lhs, rhs Add = 0x10, /// Sub dst, lhs, rhs Sub = 0x11, /// Mul dst, lhs, rhs Mul = 0x12, /// Div dst, lhs, rhs Div = 0x13, /// Rem dst, lhs, rhs Rem = 0x14, /// Exp dst, lhs, rhs Exp = 0x15, /// Neg dst, src (unary minus) Neg = 0x16,
// ── Bitwise ───────────────────────────────────────────── /// BitAnd dst, lhs, rhs BitAnd = 0x20, /// BitOr dst, lhs, rhs BitOr = 0x21, /// BitXor dst, lhs, rhs BitXor = 0x22, /// ShiftLeft dst, lhs, rhs ShiftLeft = 0x23, /// ShiftRight dst, lhs, rhs ShiftRight = 0x24, /// UShiftRight dst, lhs, rhs UShiftRight = 0x25, /// BitNot dst, src BitNot = 0x26,
// ── Comparison ────────────────────────────────────────── /// Eq dst, lhs, rhs (==) Eq = 0x30, /// StrictEq dst, lhs, rhs (===) StrictEq = 0x31, /// NotEq dst, lhs, rhs (!=) NotEq = 0x32, /// StrictNotEq dst, lhs, rhs (!==) StrictNotEq = 0x33, /// LessThan dst, lhs, rhs LessThan = 0x34, /// LessEq dst, lhs, rhs LessEq = 0x35, /// GreaterThan dst, lhs, rhs GreaterThan = 0x36, /// GreaterEq dst, lhs, rhs GreaterEq = 0x37,
// ── Logical / unary ───────────────────────────────────── /// LogicalNot dst, src LogicalNot = 0x38, /// TypeOf dst, src TypeOf = 0x39, /// InstanceOf dst, lhs, rhs InstanceOf = 0x3A, /// In dst, lhs, rhs In = 0x3B, /// Void dst, src — evaluate src, produce undefined Void = 0x3C,
// ── Control flow ──────────────────────────────────────── /// Jump offset(i32) — unconditional relative jump Jump = 0x40, /// JumpIfTrue reg, offset(i32) JumpIfTrue = 0x41, /// JumpIfFalse reg, offset(i32) JumpIfFalse = 0x42, /// JumpIfNullish reg, offset(i32) JumpIfNullish = 0x43,
// ── Functions / calls ─────────────────────────────────── /// Call dst, func_reg, args_start, arg_count(u8) Call = 0x50, /// Return reg Return = 0x51, /// Throw reg Throw = 0x52, /// CreateClosure dst, func_idx(u16) — create a closure from a nested function CreateClosure = 0x53,
// ── Object / property ─────────────────────────────────── /// GetProperty dst, obj_reg, key_reg GetProperty = 0x60, /// SetProperty obj_reg, key_reg, val_reg SetProperty = 0x61, /// CreateObject dst CreateObject = 0x62, /// CreateArray dst CreateArray = 0x63, /// GetPropertyByName dst, obj_reg, name_idx(u16) — optimized named access GetPropertyByName = 0x64, /// SetPropertyByName obj_reg, name_idx(u16), val_reg — optimized named store SetPropertyByName = 0x65,
// ── Misc ──────────────────────────────────────────────── /// Delete dst, obj_reg, key_reg Delete = 0x70, /// LoadInt8 dst, i8 — load small integer without constant pool LoadInt8 = 0x71, /// ForInInit dst_keys, obj_reg — collect enumerable keys of object into an array ForInInit = 0x72, /// ForInNext dst_val, dst_done, keys_reg, idx_reg — get next key or signal done ForInNext = 0x73, /// SetPrototype obj_reg, proto_reg — set [[Prototype]] of obj to proto SetPrototype = 0x74, /// GetPrototype dst, obj_reg — get [[Prototype]] of obj GetPrototype = 0x75, /// PushExceptionHandler catch_reg, offset(i32) — push a try/catch handler PushExceptionHandler = 0x76, /// PopExceptionHandler — remove the current exception handler PopExceptionHandler = 0x77, /// NewCell dst — allocate a new GC cell initialized to undefined NewCell = 0x78, /// CellLoad dst, cell_reg — read the value stored in the cell CellLoad = 0x79, /// CellStore cell_reg, src — write a value into the cell CellStore = 0x7A, /// LoadUpvalue dst, idx(u8) — load from the closure's captured upvalue cell LoadUpvalue = 0x7B, /// StoreUpvalue idx(u8), src — store into the closure's captured upvalue cell StoreUpvalue = 0x7C,}
impl Op { /// Decode an opcode from a byte, returning `None` for unrecognized values. pub fn from_byte(b: u8) -> Option<Op> { match b { 0x01 => Some(Op::LoadConst), 0x02 => Some(Op::LoadNull), 0x03 => Some(Op::LoadUndefined), 0x04 => Some(Op::LoadTrue), 0x05 => Some(Op::LoadFalse), 0x06 => Some(Op::Move), 0x07 => Some(Op::LoadGlobal), 0x08 => Some(Op::StoreGlobal), 0x10 => Some(Op::Add), 0x11 => Some(Op::Sub), 0x12 => Some(Op::Mul), 0x13 => Some(Op::Div), 0x14 => Some(Op::Rem), 0x15 => Some(Op::Exp), 0x16 => Some(Op::Neg), 0x20 => Some(Op::BitAnd), 0x21 => Some(Op::BitOr), 0x22 => Some(Op::BitXor), 0x23 => Some(Op::ShiftLeft), 0x24 => Some(Op::ShiftRight), 0x25 => Some(Op::UShiftRight), 0x26 => Some(Op::BitNot), 0x30 => Some(Op::Eq), 0x31 => Some(Op::StrictEq), 0x32 => Some(Op::NotEq), 0x33 => Some(Op::StrictNotEq), 0x34 => Some(Op::LessThan), 0x35 => Some(Op::LessEq), 0x36 => Some(Op::GreaterThan), 0x37 => Some(Op::GreaterEq), 0x38 => Some(Op::LogicalNot), 0x39 => Some(Op::TypeOf), 0x3A => Some(Op::InstanceOf), 0x3B => Some(Op::In), 0x3C => Some(Op::Void), 0x40 => Some(Op::Jump), 0x41 => Some(Op::JumpIfTrue), 0x42 => Some(Op::JumpIfFalse), 0x43 => Some(Op::JumpIfNullish), 0x50 => Some(Op::Call), 0x51 => Some(Op::Return), 0x52 => Some(Op::Throw), 0x53 => Some(Op::CreateClosure), 0x60 => Some(Op::GetProperty), 0x61 => Some(Op::SetProperty), 0x62 => Some(Op::CreateObject), 0x63 => Some(Op::CreateArray), 0x64 => Some(Op::GetPropertyByName), 0x65 => Some(Op::SetPropertyByName), 0x70 => Some(Op::Delete), 0x71 => Some(Op::LoadInt8), 0x72 => Some(Op::ForInInit), 0x73 => Some(Op::ForInNext), 0x74 => Some(Op::SetPrototype), 0x75 => Some(Op::GetPrototype), 0x76 => Some(Op::PushExceptionHandler), 0x77 => Some(Op::PopExceptionHandler), 0x78 => Some(Op::NewCell), 0x79 => Some(Op::CellLoad), 0x7A => Some(Op::CellStore), 0x7B => Some(Op::LoadUpvalue), 0x7C => Some(Op::StoreUpvalue), _ => None, } }}
/// A constant value stored in the constant pool.#[derive(Debug, Clone, PartialEq)]pub enum Constant { Number(f64), String(String),}
/// Describes how a closure captures a single variable from its enclosing scope.#[derive(Debug, Clone)]pub struct UpvalueDef { /// If true, `index` refers to a register in the immediately enclosing function /// (which must hold a cell GcRef). If false, `index` refers to an upvalue slot /// of the enclosing function (transitive capture). pub is_local: bool, /// Register index (if `is_local`) or upvalue index (if not) in the parent. pub index: u8,}
/// A compiled bytecode function.#[derive(Debug, Clone)]pub struct Function { /// Name of the function (empty for anonymous / top-level). pub name: String, /// Number of named parameters. pub param_count: u8, /// Total register slots needed. pub register_count: u8, /// The bytecode bytes. pub code: Vec<u8>, /// Constant pool (numbers and strings). pub constants: Vec<Constant>, /// Name table for global/property name lookups. pub names: Vec<String>, /// Nested function definitions (referenced by CreateClosure). pub functions: Vec<Function>, /// Source map: bytecode offset → source line (sorted by offset). pub source_map: Vec<(u32, u32)>, /// Upvalue definitions: how this function captures variables from its parent. pub upvalue_defs: Vec<UpvalueDef>,}
impl Function { pub fn new(name: String, param_count: u8) -> Self { Self { name, param_count, register_count: 0, code: Vec::new(), constants: Vec::new(), names: Vec::new(), functions: Vec::new(), source_map: Vec::new(), upvalue_defs: Vec::new(), } }}
/// A builder that emits bytecode into a `Function`.pub struct BytecodeBuilder { pub func: Function,}
impl BytecodeBuilder { pub fn new(name: String, param_count: u8) -> Self { Self { func: Function::new(name, param_count), } }
/// Current bytecode offset (position of next emitted byte). pub fn offset(&self) -> usize { self.func.code.len() }
/// Intern a constant, returning its index. pub fn add_constant(&mut self, c: Constant) -> ConstIdx { // Reuse existing constant if it matches. for (i, existing) in self.func.constants.iter().enumerate() { match (existing, &c) { (Constant::String(a), Constant::String(b)) if a == b => return i as ConstIdx, (Constant::Number(a), Constant::Number(b)) if a.to_bits() == b.to_bits() => { return i as ConstIdx; } _ => {} } } let idx = self.func.constants.len(); assert!(idx <= u16::MAX as usize, "constant pool overflow"); self.func.constants.push(c); idx as ConstIdx }
/// Intern a name, returning its index. pub fn add_name(&mut self, name: &str) -> NameIdx { for (i, existing) in self.func.names.iter().enumerate() { if existing == name { return i as NameIdx; } } let idx = self.func.names.len(); assert!(idx <= u16::MAX as usize, "name table overflow"); self.func.names.push(name.to_string()); idx as NameIdx }
/// Add a nested function, returning its index. pub fn add_function(&mut self, f: Function) -> u16 { let idx = self.func.functions.len(); assert!(idx <= u16::MAX as usize, "function table overflow"); self.func.functions.push(f); idx as u16 }
// ── Emit helpers ────────────────────────────────────────
fn emit_u8(&mut self, v: u8) { self.func.code.push(v); }
fn emit_u16(&mut self, v: u16) { self.func.code.extend_from_slice(&v.to_le_bytes()); }
fn emit_i32(&mut self, v: i32) { self.func.code.extend_from_slice(&v.to_le_bytes()); }
// ── Single-operand instructions ─────────────────────────
/// Emit: LoadNull dst | LoadUndefined dst | LoadTrue dst | LoadFalse dst pub fn emit_reg(&mut self, op: Op, dst: Reg) { self.emit_u8(op as u8); self.emit_u8(dst); }
/// Emit: Move dst, src pub fn emit_reg_reg(&mut self, op: Op, a: Reg, b: Reg) { self.emit_u8(op as u8); self.emit_u8(a); self.emit_u8(b); }
/// Emit: ForInNext dst_val, dst_done, keys, idx (4-register instruction) pub fn emit_reg4(&mut self, op: Op, a: Reg, b: Reg, c: Reg, d: Reg) { self.emit_u8(op as u8); self.emit_u8(a); self.emit_u8(b); self.emit_u8(c); self.emit_u8(d); }
/// Emit: Add dst, lhs, rhs (and other 3-register instructions) pub fn emit_reg3(&mut self, op: Op, dst: Reg, lhs: Reg, rhs: Reg) { self.emit_u8(op as u8); self.emit_u8(dst); self.emit_u8(lhs); self.emit_u8(rhs); }
/// Emit: LoadConst dst, const_idx | CreateClosure dst, func_idx pub fn emit_reg_u16(&mut self, op: Op, dst: Reg, idx: u16) { self.emit_u8(op as u8); self.emit_u8(dst); self.emit_u16(idx); }
/// Emit: LoadGlobal dst, name_idx pub fn emit_load_global(&mut self, dst: Reg, name_idx: NameIdx) { self.emit_u8(Op::LoadGlobal as u8); self.emit_u8(dst); self.emit_u16(name_idx); }
/// Emit: StoreGlobal name_idx, src pub fn emit_store_global(&mut self, name_idx: NameIdx, src: Reg) { self.emit_u8(Op::StoreGlobal as u8); self.emit_u16(name_idx); self.emit_u8(src); }
/// Emit: Jump offset (placeholder, returns patch position) pub fn emit_jump(&mut self, op: Op) -> usize { self.emit_u8(op as u8); if op == Op::JumpIfTrue || op == Op::JumpIfFalse || op == Op::JumpIfNullish { panic!("use emit_cond_jump for conditional jumps"); } let pos = self.offset(); self.emit_i32(0); // placeholder pos }
/// Emit: JumpIfTrue/JumpIfFalse/JumpIfNullish reg, offset (placeholder) pub fn emit_cond_jump(&mut self, op: Op, reg: Reg) -> usize { self.emit_u8(op as u8); self.emit_u8(reg); let pos = self.offset(); self.emit_i32(0); // placeholder pos }
/// Patch a jump offset at the given position to point to the current offset. pub fn patch_jump(&mut self, pos: usize) { self.patch_jump_to(pos, self.offset()); }
/// Patch a jump offset at the given position to point to an arbitrary target. pub fn patch_jump_to(&mut self, pos: usize, target: usize) { let offset = target as i32 - (pos as i32 + 4); // relative to after the i32 let bytes = offset.to_le_bytes(); self.func.code[pos] = bytes[0]; self.func.code[pos + 1] = bytes[1]; self.func.code[pos + 2] = bytes[2]; self.func.code[pos + 3] = bytes[3]; }
/// Emit: Jump with a known target (backward jump). pub fn emit_jump_to(&mut self, target: usize) { self.emit_u8(Op::Jump as u8); let from = self.offset() as i32 + 4; let offset = target as i32 - from; self.emit_i32(offset); }
/// Emit: JumpIfTrue/JumpIfFalse with a known target (backward jump). pub fn emit_cond_jump_to(&mut self, op: Op, reg: Reg, target: usize) { self.emit_u8(op as u8); self.emit_u8(reg); let from = self.offset() as i32 + 4; let offset = target as i32 - from; self.emit_i32(offset); }
/// Emit: Call dst, func_reg, args_start, arg_count pub fn emit_call(&mut self, dst: Reg, func: Reg, args_start: Reg, arg_count: u8) { self.emit_u8(Op::Call as u8); self.emit_u8(dst); self.emit_u8(func); self.emit_u8(args_start); self.emit_u8(arg_count); }
/// Emit: GetPropertyByName dst, obj, name_idx pub fn emit_get_prop_name(&mut self, dst: Reg, obj: Reg, name_idx: NameIdx) { self.emit_u8(Op::GetPropertyByName as u8); self.emit_u8(dst); self.emit_u8(obj); self.emit_u16(name_idx); }
/// Emit: PushExceptionHandler catch_reg, offset (placeholder, returns patch position) pub fn emit_push_exception_handler(&mut self, catch_reg: Reg) -> usize { self.emit_u8(Op::PushExceptionHandler as u8); self.emit_u8(catch_reg); let pos = self.offset(); self.emit_i32(0); // placeholder for catch block offset pos }
/// Emit: PopExceptionHandler pub fn emit_pop_exception_handler(&mut self) { self.emit_u8(Op::PopExceptionHandler as u8); }
/// Emit: SetPropertyByName obj, name_idx, val pub fn emit_set_prop_name(&mut self, obj: Reg, name_idx: NameIdx, val: Reg) { self.emit_u8(Op::SetPropertyByName as u8); self.emit_u8(obj); self.emit_u16(name_idx); self.emit_u8(val); }
/// Emit: LoadInt8 dst, value pub fn emit_load_int8(&mut self, dst: Reg, value: i8) { self.emit_u8(Op::LoadInt8 as u8); self.emit_u8(dst); self.emit_u8(value as u8); }
/// Emit: LoadUpvalue dst, idx pub fn emit_load_upvalue(&mut self, dst: Reg, idx: u8) { self.emit_u8(Op::LoadUpvalue as u8); self.emit_u8(dst); self.emit_u8(idx); }
/// Emit: StoreUpvalue idx, src pub fn emit_store_upvalue(&mut self, idx: u8, src: Reg) { self.emit_u8(Op::StoreUpvalue as u8); self.emit_u8(idx); self.emit_u8(src); }
/// Add a source map entry: current bytecode offset → source line. pub fn add_source_map(&mut self, line: u32) { let offset = self.offset() as u32; self.func.source_map.push((offset, line)); }
/// Finalize and return the compiled function. pub fn finish(self) -> Function { self.func }}
// ── Disassembler ────────────────────────────────────────────
impl Function { /// Disassemble the bytecode to a human-readable string. pub fn disassemble(&self) -> String { let mut out = String::new(); out.push_str(&format!( "function {}({} params, {} regs):\n", if self.name.is_empty() { "<anonymous>" } else { &self.name }, self.param_count, self.register_count, ));
// Constants if !self.constants.is_empty() { out.push_str(" constants:\n"); for (i, c) in self.constants.iter().enumerate() { out.push_str(&format!(" [{i}] {c:?}\n")); } }
// Names if !self.names.is_empty() { out.push_str(" names:\n"); for (i, n) in self.names.iter().enumerate() { out.push_str(&format!(" [{i}] \"{n}\"\n")); } }
// Instructions out.push_str(" code:\n"); let code = &self.code; let mut pc = 0; while pc < code.len() { let offset = pc; let byte = code[pc]; pc += 1; let Some(op) = Op::from_byte(byte) else { out.push_str(&format!(" {offset:04X} <unknown 0x{byte:02X}>\n")); continue; }; let line = match op { Op::LoadConst => { let dst = code[pc]; pc += 1; let idx = u16::from_le_bytes([code[pc], code[pc + 1]]); pc += 2; format!("LoadConst r{dst}, #{idx}") } Op::LoadNull => { let dst = code[pc]; pc += 1; format!("LoadNull r{dst}") } Op::LoadUndefined => { let dst = code[pc]; pc += 1; format!("LoadUndefined r{dst}") } Op::LoadTrue => { let dst = code[pc]; pc += 1; format!("LoadTrue r{dst}") } Op::LoadFalse => { let dst = code[pc]; pc += 1; format!("LoadFalse r{dst}") } Op::Move => { let dst = code[pc]; let src = code[pc + 1]; pc += 2; format!("Move r{dst}, r{src}") } Op::LoadGlobal => { let dst = code[pc]; pc += 1; let idx = u16::from_le_bytes([code[pc], code[pc + 1]]); pc += 2; let name = self .names .get(idx as usize) .map(|s| s.as_str()) .unwrap_or("?"); format!("LoadGlobal r{dst}, @{idx}(\"{name}\")") } Op::StoreGlobal => { let idx = u16::from_le_bytes([code[pc], code[pc + 1]]); pc += 2; let src = code[pc]; pc += 1; let name = self .names .get(idx as usize) .map(|s| s.as_str()) .unwrap_or("?"); format!("StoreGlobal @{idx}(\"{name}\"), r{src}") } Op::Add | Op::Sub | Op::Mul | Op::Div | Op::Rem | Op::Exp => { let dst = code[pc]; let lhs = code[pc + 1]; let rhs = code[pc + 2]; pc += 3; format!("{op:?} r{dst}, r{lhs}, r{rhs}") } Op::Neg => { let dst = code[pc]; let src = code[pc + 1]; pc += 2; format!("Neg r{dst}, r{src}") } Op::BitAnd | Op::BitOr | Op::BitXor | Op::ShiftLeft | Op::ShiftRight | Op::UShiftRight => { let dst = code[pc]; let lhs = code[pc + 1]; let rhs = code[pc + 2]; pc += 3; format!("{op:?} r{dst}, r{lhs}, r{rhs}") } Op::BitNot => { let dst = code[pc]; let src = code[pc + 1]; pc += 2; format!("BitNot r{dst}, r{src}") } Op::Eq | Op::StrictEq | Op::NotEq | Op::StrictNotEq | Op::LessThan | Op::LessEq | Op::GreaterThan | Op::GreaterEq | Op::InstanceOf | Op::In => { let dst = code[pc]; let lhs = code[pc + 1]; let rhs = code[pc + 2]; pc += 3; format!("{op:?} r{dst}, r{lhs}, r{rhs}") } Op::LogicalNot => { let dst = code[pc]; let src = code[pc + 1]; pc += 2; format!("LogicalNot r{dst}, r{src}") } Op::TypeOf => { let dst = code[pc]; let src = code[pc + 1]; pc += 2; format!("TypeOf r{dst}, r{src}") } Op::Void => { let dst = code[pc]; let src = code[pc + 1]; pc += 2; format!("Void r{dst}, r{src}") } Op::Jump => { let off = i32::from_le_bytes([code[pc], code[pc + 1], code[pc + 2], code[pc + 3]]); pc += 4; let target = pc as i32 + off; format!("Jump {off:+} (-> {target:04X})") } Op::JumpIfTrue | Op::JumpIfFalse | Op::JumpIfNullish => { let reg = code[pc]; pc += 1; let off = i32::from_le_bytes([code[pc], code[pc + 1], code[pc + 2], code[pc + 3]]); pc += 4; let target = pc as i32 + off; format!("{op:?} r{reg}, {off:+} (-> {target:04X})") } Op::Call => { let dst = code[pc]; let func = code[pc + 1]; let args_start = code[pc + 2]; let arg_count = code[pc + 3]; pc += 4; format!("Call r{dst}, r{func}, r{args_start}, {arg_count}") } Op::Return => { let reg = code[pc]; pc += 1; format!("Return r{reg}") } Op::Throw => { let reg = code[pc]; pc += 1; format!("Throw r{reg}") } Op::CreateClosure => { let dst = code[pc]; pc += 1; let idx = u16::from_le_bytes([code[pc], code[pc + 1]]); pc += 2; format!("CreateClosure r{dst}, func#{idx}") } Op::GetProperty => { let dst = code[pc]; let obj = code[pc + 1]; let key = code[pc + 2]; pc += 3; format!("GetProperty r{dst}, r{obj}, r{key}") } Op::SetProperty => { let obj = code[pc]; let key = code[pc + 1]; let val = code[pc + 2]; pc += 3; format!("SetProperty r{obj}, r{key}, r{val}") } Op::CreateObject => { let dst = code[pc]; pc += 1; format!("CreateObject r{dst}") } Op::CreateArray => { let dst = code[pc]; pc += 1; format!("CreateArray r{dst}") } Op::GetPropertyByName => { let dst = code[pc]; let obj = code[pc + 1]; pc += 2; let idx = u16::from_le_bytes([code[pc], code[pc + 1]]); pc += 2; let name = self .names .get(idx as usize) .map(|s| s.as_str()) .unwrap_or("?"); format!("GetPropertyByName r{dst}, r{obj}, @{idx}(\"{name}\")") } Op::SetPropertyByName => { let obj = code[pc]; pc += 1; let idx = u16::from_le_bytes([code[pc], code[pc + 1]]); pc += 2; let val = code[pc]; pc += 1; let name = self .names .get(idx as usize) .map(|s| s.as_str()) .unwrap_or("?"); format!("SetPropertyByName r{obj}, @{idx}(\"{name}\"), r{val}") } Op::Delete => { let dst = code[pc]; let obj = code[pc + 1]; let key = code[pc + 2]; pc += 3; format!("Delete r{dst}, r{obj}, r{key}") } Op::LoadInt8 => { let dst = code[pc]; let val = code[pc + 1] as i8; pc += 2; format!("LoadInt8 r{dst}, {val}") } Op::ForInInit => { let dst = code[pc]; let obj = code[pc + 1]; pc += 2; format!("ForInInit r{dst}, r{obj}") } Op::ForInNext => { let dst_val = code[pc]; let dst_done = code[pc + 1]; let keys = code[pc + 2]; let idx = code[pc + 3]; pc += 4; format!("ForInNext r{dst_val}, r{dst_done}, r{keys}, r{idx}") } Op::SetPrototype => { let obj = code[pc]; let proto = code[pc + 1]; pc += 2; format!("SetPrototype r{obj}, r{proto}") } Op::GetPrototype => { let dst = code[pc]; let obj = code[pc + 1]; pc += 2; format!("GetPrototype r{dst}, r{obj}") } Op::PushExceptionHandler => { let catch_reg = code[pc]; let b0 = code[pc + 1] as i32; let b1 = code[pc + 2] as i32; let b2 = code[pc + 3] as i32; let b3 = code[pc + 4] as i32; let off = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24); let target = (offset as i32 + 1 + 5 + off) as usize; pc += 5; format!("PushExceptionHandler r{catch_reg}, @{target:04X}") } Op::PopExceptionHandler => "PopExceptionHandler".to_string(), Op::NewCell => { let dst = code[pc]; pc += 1; format!("NewCell r{dst}") } Op::CellLoad => { let dst = code[pc]; let cell = code[pc + 1]; pc += 2; format!("CellLoad r{dst}, r{cell}") } Op::CellStore => { let cell = code[pc]; let src = code[pc + 1]; pc += 2; format!("CellStore r{cell}, r{src}") } Op::LoadUpvalue => { let dst = code[pc]; let idx = code[pc + 1]; pc += 2; format!("LoadUpvalue r{dst}, uv{idx}") } Op::StoreUpvalue => { let idx = code[pc]; let src = code[pc + 1]; pc += 2; format!("StoreUpvalue uv{idx}, r{src}") } }; out.push_str(&format!(" {offset:04X} {line}\n")); }
// Nested functions for (i, f) in self.functions.iter().enumerate() { out.push_str(&format!("\n --- nested function [{i}] ---\n")); let nested_dis = f.disassemble(); for line in nested_dis.lines() { out.push_str(&format!(" {line}\n")); } }
out }}
impl fmt::Display for Function { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.disassemble()) }}
#[cfg(test)]mod tests { use super::*;
#[test] fn test_op_roundtrip() { // Verify all opcodes survive byte conversion. let ops = [ Op::LoadConst, Op::LoadNull, Op::LoadUndefined, Op::LoadTrue, Op::LoadFalse, Op::Move, Op::LoadGlobal, Op::StoreGlobal, Op::Add, Op::Sub, Op::Mul, Op::Div, Op::Rem, Op::Exp, Op::Neg, Op::BitAnd, Op::BitOr, Op::BitXor, Op::ShiftLeft, Op::ShiftRight, Op::UShiftRight, Op::BitNot, Op::Eq, Op::StrictEq, Op::NotEq, Op::StrictNotEq, Op::LessThan, Op::LessEq, Op::GreaterThan, Op::GreaterEq, Op::LogicalNot, Op::TypeOf, Op::InstanceOf, Op::In, Op::Void, Op::Jump, Op::JumpIfTrue, Op::JumpIfFalse, Op::JumpIfNullish, Op::Call, Op::Return, Op::Throw, Op::CreateClosure, Op::GetProperty, Op::SetProperty, Op::CreateObject, Op::CreateArray, Op::GetPropertyByName, Op::SetPropertyByName, Op::Delete, Op::LoadInt8, Op::ForInInit, Op::ForInNext, Op::SetPrototype, Op::GetPrototype, Op::PushExceptionHandler, Op::PopExceptionHandler, Op::NewCell, Op::CellLoad, Op::CellStore, Op::LoadUpvalue, Op::StoreUpvalue, ]; for op in ops { assert_eq!( Op::from_byte(op as u8), Some(op), "roundtrip failed for {op:?}" ); } }
#[test] fn test_unknown_opcode() { assert_eq!(Op::from_byte(0xFF), None); assert_eq!(Op::from_byte(0x00), None); }
#[test] fn test_constant_pool_dedup() { let mut b = BytecodeBuilder::new("test".into(), 0); let i1 = b.add_constant(Constant::Number(42.0)); let i2 = b.add_constant(Constant::Number(42.0)); assert_eq!(i1, i2); let i3 = b.add_constant(Constant::String("hello".into())); let i4 = b.add_constant(Constant::String("hello".into())); assert_eq!(i3, i4); assert_ne!(i1, i3); assert_eq!(b.func.constants.len(), 2); }
#[test] fn test_name_dedup() { let mut b = BytecodeBuilder::new("test".into(), 0); let i1 = b.add_name("foo"); let i2 = b.add_name("foo"); assert_eq!(i1, i2); let i3 = b.add_name("bar"); assert_ne!(i1, i3); }
#[test] fn test_emit_and_disassemble() { let mut b = BytecodeBuilder::new("test".into(), 0); b.func.register_count = 3; let ci = b.add_constant(Constant::Number(10.0)); b.emit_reg_u16(Op::LoadConst, 0, ci); b.emit_reg(Op::LoadNull, 1); b.emit_reg3(Op::Add, 2, 0, 1); b.emit_reg(Op::Return, 2); let func = b.finish(); let dis = func.disassemble(); assert!(dis.contains("LoadConst r0, #0")); assert!(dis.contains("LoadNull r1")); assert!(dis.contains("Add r2, r0, r1")); assert!(dis.contains("Return r2")); }
#[test] fn test_jump_patching() { let mut b = BytecodeBuilder::new("test".into(), 0); b.func.register_count = 1; b.emit_reg(Op::LoadTrue, 0); let patch = b.emit_cond_jump(Op::JumpIfFalse, 0); b.emit_reg(Op::LoadNull, 0); b.patch_jump(patch); b.emit_reg(Op::Return, 0); let func = b.finish(); let dis = func.disassemble(); // The jump should target the Return instruction assert!(dis.contains("JumpIfFalse")); assert!(dis.contains("Return")); }
#[test] fn test_load_int8() { let mut b = BytecodeBuilder::new("test".into(), 0); b.func.register_count = 1; b.emit_load_int8(0, 42); b.emit_load_int8(0, -1); let func = b.finish(); let dis = func.disassemble(); assert!(dis.contains("LoadInt8 r0, 42")); assert!(dis.contains("LoadInt8 r0, -1")); }}