Something went wrong. Try again.
we (web engine): Experimental web browser project to understand the limits of Claude
Something went wrong. Try again.
Rust
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120//! 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,
// ── Iterator / generator ─────────────────────────────── /// Yield dst, src — suspend generator, yield src value. On resume, dst gets the /// value passed to next(). The VM saves the frame state and returns {value, done: false}. Yield = 0x80, /// Spread dst_array, src — iterate src via @@iterator, append all elements to dst_array Spread = 0x81, /// Await dst, src — suspend async function, await src value. On resume, dst gets the /// settled value. The VM saves the frame state and returns to the async driver. Await = 0x82,}
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), 0x80 => Some(Op::Yield), 0x81 => Some(Op::Spread), 0x82 => Some(Op::Await), _ => 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>, /// Whether this is a generator function (function*). pub is_generator: bool, /// Whether this is an async function. pub is_async: bool,}
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(), is_generator: false, is_async: false, } }}
/// 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); }
/// Emit: Yield dst, src — suspend generator pub fn emit_yield(&mut self, dst: Reg, src: Reg) { self.emit_u8(Op::Yield as u8); self.emit_u8(dst); self.emit_u8(src); }
/// Emit: Spread dst_array, src — spread iterable into array pub fn emit_spread(&mut self, dst_array: Reg, src: Reg) { self.emit_u8(Op::Spread as u8); self.emit_u8(dst_array); self.emit_u8(src); }
/// Emit: Await dst, src — suspend async function pub fn emit_await(&mut self, dst: Reg, src: Reg) { self.emit_u8(Op::Await as u8); self.emit_u8(dst); 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}") } Op::Yield => { let dst = code[pc]; let src = code[pc + 1]; pc += 2; format!("Yield r{dst}, r{src}") } Op::Spread => { let dst = code[pc]; let src = code[pc + 1]; pc += 2; format!("Spread r{dst}, r{src}") } Op::Await => { let dst = code[pc]; let src = code[pc + 1]; pc += 2; format!("Await r{dst}, 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, Op::Yield, Op::Spread, Op::Await, ]; 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")); }}