Something went wrong. Try again.
we (web engine): Experimental web browser project to understand the limits of Claude
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555//! 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 crate::shape::ShapeId;use std::fmt;
/// A register index (0–65535).pub type Reg = u16;
/// An index into the constant pool.pub type ConstIdx = u16;
/// An index into the name/string table.pub type NameIdx = u16;
/// An index into the inline cache vector.pub type IcIdx = u16;
/// Maximum number of entries in a polymorphic inline cache before going megamorphic.const IC_MAX_POLY: usize = 4;
/// A single (shape, slot) mapping cached at a property-access site.#[derive(Debug, Clone, Copy)]pub struct InlineCacheEntry { pub shape_id: ShapeId, pub slot_index: u32,}
/// State of an inline cache site.#[derive(Debug, Clone)]pub enum InlineCacheState { /// No shape cached yet. Uninitialized, /// Exactly one shape seen. Monomorphic(InlineCacheEntry), /// 2–4 shapes seen. Polymorphic(Vec<InlineCacheEntry>), /// Too many shapes — always use the slow path. Megamorphic,}
/// An inline cache for a single property-access bytecode site.#[derive(Debug, Clone)]pub struct InlineCache { pub state: InlineCacheState,}
impl InlineCache { /// Create an empty (uninitialized) inline cache. pub fn new() -> Self { Self { state: InlineCacheState::Uninitialized, } }
/// Try to find a cached slot index for the given shape. /// Returns `Some(slot_index)` on a cache hit, `None` on miss. pub fn lookup(&self, shape: ShapeId) -> Option<u32> { match &self.state { InlineCacheState::Uninitialized | InlineCacheState::Megamorphic => None, InlineCacheState::Monomorphic(entry) => { if entry.shape_id == shape { Some(entry.slot_index) } else { None } } InlineCacheState::Polymorphic(entries) => { for entry in entries { if entry.shape_id == shape { return Some(entry.slot_index); } } None } } }
/// Record a (shape, slot_index) mapping after a cache miss. /// Does nothing if already megamorphic. pub fn update(&mut self, shape: ShapeId, slot_index: u32) { match &mut self.state { InlineCacheState::Megamorphic => {} InlineCacheState::Uninitialized => { self.state = InlineCacheState::Monomorphic(InlineCacheEntry { shape_id: shape, slot_index, }); } InlineCacheState::Monomorphic(existing) => { if existing.shape_id == shape { return; } let entries = vec![ *existing, InlineCacheEntry { shape_id: shape, slot_index, }, ]; self.state = InlineCacheState::Polymorphic(entries); } InlineCacheState::Polymorphic(entries) => { for e in entries.iter() { if e.shape_id == shape { return; } } if entries.len() >= IC_MAX_POLY { self.state = InlineCacheState::Megamorphic; } else { entries.push(InlineCacheEntry { shape_id: shape, slot_index, }); } } } }
/// Whether this IC has gone megamorphic. pub fn is_megamorphic(&self) -> bool { matches!(self.state, InlineCacheState::Megamorphic) }}
impl Default for InlineCache { fn default() -> Self { Self::new() }}
/// 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, /// NewCall dst, func_reg, args_start, arg_count(u8) NewCall = 0x54, /// CallSpread dst, func_reg, args_array_reg — call with arguments taken from /// a pre-built array (used when the call site contains a spread argument). CallSpread = 0x55, /// NewCallSpread dst, func_reg, args_array_reg — construct with arguments /// taken from a pre-built array (spread in a `new` argument list). NewCallSpread = 0x56,
// ── 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, /// DefineMethod obj_reg, name_idx(u16), fn_reg — install a non-enumerable /// method (data property) on a class constructor or its prototype. DefineMethod = 0x66, /// DefineAccessor obj_reg, name_idx(u16), kind(u8: 0=get,1=set), fn_reg — /// install/merge a getter or setter accessor on a class constructor or its /// prototype. DefineAccessor = 0x67, /// SetHomeObject fn_reg, obj_reg — record `obj` as the [[HomeObject]] of the /// method/constructor function in `fn_reg`, so `super` references inside it /// resolve against `obj`'s prototype. SetHomeObject = 0x68, /// LoadSuperBase dst — load the prototype of the current function's /// [[HomeObject]] (the object `super` property lookups start from). LoadSuperBase = 0x69, /// GetPrivate dst, obj_reg, name_idx(u16) — read a private field/method /// (`obj.#x`). Throws a TypeError if `obj` does not carry the private name /// (the ES2022 brand check). GetPrivate = 0x6A, /// SetPrivate obj_reg, name_idx(u16), val_reg — define-or-update a private /// field (`obj.#x = v`, and class field/method installation). Throws a /// TypeError if `obj` is not an object. SetPrivate = 0x6B, /// HasPrivate dst, obj_reg, name_idx(u16) — `#x in obj`: true iff `obj` /// carries the private name. Never throws. HasPrivate = 0x6C, /// CopyDataProperties target_reg, source_reg — object literal spread: /// copy source's own enumerable properties into target. CopyDataProperties = 0x6D,
// ── 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, /// BuildArguments dst — materialize the current frame's `arguments` object into dst. /// The resulting object has indexed properties 0..N-1, a `length` property of N, /// and inherits from Object.prototype. BuildArguments = 0x83, /// BuildRestArguments dst, start_idx(u16) — materialize arguments[start_idx..] /// as an Array object for a rest parameter. BuildRestArguments = 0x84,}
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), 0x54 => Some(Op::NewCall), 0x55 => Some(Op::CallSpread), 0x56 => Some(Op::NewCallSpread), 0x60 => Some(Op::GetProperty), 0x61 => Some(Op::SetProperty), 0x62 => Some(Op::CreateObject), 0x63 => Some(Op::CreateArray), 0x64 => Some(Op::GetPropertyByName), 0x65 => Some(Op::SetPropertyByName), 0x66 => Some(Op::DefineMethod), 0x67 => Some(Op::DefineAccessor), 0x68 => Some(Op::SetHomeObject), 0x69 => Some(Op::LoadSuperBase), 0x6A => Some(Op::GetPrivate), 0x6B => Some(Op::SetPrivate), 0x6C => Some(Op::HasPrivate), 0x6D => Some(Op::CopyDataProperties), 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), 0x83 => Some(Op::BuildArguments), 0x84 => Some(Op::BuildRestArguments), _ => 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: u16,}
/// 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: u16, /// 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, source column) sorted by /// offset. Lines and columns are 1-based, matching the lexer's Span. pub source_map: Vec<(u32, 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, /// Inline caches for property access sites. pub inline_caches: Vec<InlineCache>,}
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, inline_caches: Vec::new(), } }
/// Resolve a bytecode offset to its source line via `source_map`. /// Returns the line associated with the highest source_map entry whose /// offset is less than or equal to `pc`, or `None` if no entry covers it. pub fn source_line_for(&self, pc: usize) -> Option<u32> { self.source_location_for(pc).map(|(line, _)| line) }
/// Resolve a bytecode offset to `(source_line, source_column)`. Useful /// when an error needs both pieces of information (e.g. minified bundles /// where everything sits on line 1). pub fn source_location_for(&self, pc: usize) -> Option<(u32, u32)> { if self.source_map.is_empty() { return None; } let pc = pc as u32; match self .source_map .binary_search_by_key(&pc, |(offset, _, _)| *offset) { Ok(i) => Some((self.source_map[i].1, self.source_map[i].2)), Err(0) => None, Err(i) => Some((self.source_map[i - 1].1, self.source_map[i - 1].2)), } }}
/// 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_reg_operand(&mut self, reg: Reg) { self.emit_u16(reg); }
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_reg_operand(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_reg_operand(a); self.emit_reg_operand(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_reg_operand(a); self.emit_reg_operand(b); self.emit_reg_operand(c); self.emit_reg_operand(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_reg_operand(dst); self.emit_reg_operand(lhs); self.emit_reg_operand(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_reg_operand(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_reg_operand(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_reg_operand(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_reg_operand(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_reg_operand(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_reg_operand(dst); self.emit_reg_operand(func); self.emit_reg_operand(args_start); self.emit_u8(arg_count); }
/// Emit: NewCall dst, func_reg, args_start, arg_count pub fn emit_new_call(&mut self, dst: Reg, func: Reg, args_start: Reg, arg_count: u8) { self.emit_u8(Op::NewCall as u8); self.emit_reg_operand(dst); self.emit_reg_operand(func); self.emit_reg_operand(args_start); self.emit_u8(arg_count); }
/// Emit: CallSpread dst, func_reg, args_array_reg pub fn emit_call_spread(&mut self, dst: Reg, func: Reg, args_array: Reg) { self.emit_u8(Op::CallSpread as u8); self.emit_reg_operand(dst); self.emit_reg_operand(func); self.emit_reg_operand(args_array); }
/// Emit: NewCallSpread dst, func_reg, args_array_reg pub fn emit_new_call_spread(&mut self, dst: Reg, func: Reg, args_array: Reg) { self.emit_u8(Op::NewCallSpread as u8); self.emit_reg_operand(dst); self.emit_reg_operand(func); self.emit_reg_operand(args_array); }
/// Allocate a new inline cache slot, returning its index. pub fn alloc_ic(&mut self) -> IcIdx { let idx = self.func.inline_caches.len(); assert!(idx <= u16::MAX as usize, "inline cache overflow"); self.func.inline_caches.push(InlineCache::new()); idx as IcIdx }
/// Emit: GetPropertyByName dst, obj, name_idx, ic_idx pub fn emit_get_prop_name(&mut self, dst: Reg, obj: Reg, name_idx: NameIdx) { let ic = self.alloc_ic(); self.emit_u8(Op::GetPropertyByName as u8); self.emit_reg_operand(dst); self.emit_reg_operand(obj); self.emit_u16(name_idx); self.emit_u16(ic); }
/// 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_reg_operand(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, ic_idx pub fn emit_set_prop_name(&mut self, obj: Reg, name_idx: NameIdx, val: Reg) { let ic = self.alloc_ic(); self.emit_u8(Op::SetPropertyByName as u8); self.emit_reg_operand(obj); self.emit_u16(name_idx); self.emit_reg_operand(val); self.emit_u16(ic); }
/// Emit: DefineMethod obj, name_idx, fn_reg pub fn emit_define_method(&mut self, obj: Reg, name_idx: NameIdx, fn_reg: Reg) { self.emit_u8(Op::DefineMethod as u8); self.emit_reg_operand(obj); self.emit_u16(name_idx); self.emit_reg_operand(fn_reg); }
/// Emit: DefineAccessor obj, name_idx, kind (0=get, 1=set), fn_reg pub fn emit_define_accessor(&mut self, obj: Reg, name_idx: NameIdx, kind: u8, fn_reg: Reg) { self.emit_u8(Op::DefineAccessor as u8); self.emit_reg_operand(obj); self.emit_u16(name_idx); self.emit_u8(kind); self.emit_reg_operand(fn_reg); }
/// Emit: GetPrivate dst, obj, name_idx pub fn emit_get_private(&mut self, dst: Reg, obj: Reg, name_idx: NameIdx) { self.emit_u8(Op::GetPrivate as u8); self.emit_reg_operand(dst); self.emit_reg_operand(obj); self.emit_u16(name_idx); }
/// Emit: SetPrivate obj, name_idx, val pub fn emit_set_private(&mut self, obj: Reg, name_idx: NameIdx, val: Reg) { self.emit_u8(Op::SetPrivate as u8); self.emit_reg_operand(obj); self.emit_u16(name_idx); self.emit_reg_operand(val); }
/// Emit: HasPrivate dst, obj, name_idx pub fn emit_has_private(&mut self, dst: Reg, obj: Reg, name_idx: NameIdx) { self.emit_u8(Op::HasPrivate as u8); self.emit_reg_operand(dst); self.emit_reg_operand(obj); self.emit_u16(name_idx); }
/// Emit: CopyDataProperties target, source pub fn emit_copy_data_properties(&mut self, target: Reg, source: Reg) { self.emit_u8(Op::CopyDataProperties as u8); self.emit_reg_operand(target); self.emit_reg_operand(source); }
/// Emit: BuildRestArguments dst, start_idx pub fn emit_build_rest_arguments(&mut self, dst: Reg, start_idx: u16) { self.emit_u8(Op::BuildRestArguments as u8); self.emit_reg_operand(dst); self.emit_u16(start_idx); }
/// Emit: LoadInt8 dst, value pub fn emit_load_int8(&mut self, dst: Reg, value: i8) { self.emit_u8(Op::LoadInt8 as u8); self.emit_reg_operand(dst); self.emit_u8(value as u8); }
/// Emit: LoadUpvalue dst, idx pub fn emit_load_upvalue(&mut self, dst: Reg, idx: u16) { self.emit_u8(Op::LoadUpvalue as u8); self.emit_reg_operand(dst); self.emit_u16(idx); }
/// Emit: StoreUpvalue idx, src pub fn emit_store_upvalue(&mut self, idx: u16, src: Reg) { self.emit_u8(Op::StoreUpvalue as u8); self.emit_u16(idx); self.emit_reg_operand(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_reg_operand(dst); self.emit_reg_operand(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_reg_operand(dst_array); self.emit_reg_operand(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_reg_operand(dst); self.emit_reg_operand(src); }
/// Add a source map entry: current bytecode offset → (source line, column). /// Both line and column are 1-based. pub fn add_source_map(&mut self, line: u32, col: u32) { let offset = self.offset() as u32; self.func.source_map.push((offset, line, col)); }
/// Finalize and return the compiled function. pub fn finish(self) -> Function { self.func }}
// ── Disassembler ────────────────────────────────────────────
fn read_u16_operand(code: &[u8], pc: &mut usize) -> u16 { let value = u16::from_le_bytes([code[*pc], code[*pc + 1]]); *pc += 2; value}
fn read_i32_operand(code: &[u8], pc: &mut usize) -> i32 { let value = i32::from_le_bytes([code[*pc], code[*pc + 1], code[*pc + 2], code[*pc + 3]]); *pc += 4; value}
fn read_reg_operand(code: &[u8], pc: &mut usize) -> Reg { read_u16_operand(code, pc)}
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 = read_reg_operand(code, &mut pc); let idx = read_u16_operand(code, &mut pc); format!("LoadConst r{dst}, #{idx}") } Op::LoadNull => { let dst = read_reg_operand(code, &mut pc); format!("LoadNull r{dst}") } Op::LoadUndefined => { let dst = read_reg_operand(code, &mut pc); format!("LoadUndefined r{dst}") } Op::LoadTrue => { let dst = read_reg_operand(code, &mut pc); format!("LoadTrue r{dst}") } Op::LoadFalse => { let dst = read_reg_operand(code, &mut pc); format!("LoadFalse r{dst}") } Op::Move => { let dst = read_reg_operand(code, &mut pc); let src = read_reg_operand(code, &mut pc); format!("Move r{dst}, r{src}") } Op::LoadGlobal => { let dst = read_reg_operand(code, &mut pc); let idx = read_u16_operand(code, &mut pc); 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 = read_u16_operand(code, &mut pc); let src = read_reg_operand(code, &mut pc); 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 = read_reg_operand(code, &mut pc); let lhs = read_reg_operand(code, &mut pc); let rhs = read_reg_operand(code, &mut pc); format!("{op:?} r{dst}, r{lhs}, r{rhs}") } Op::Neg => { let dst = read_reg_operand(code, &mut pc); let src = read_reg_operand(code, &mut pc); format!("Neg r{dst}, r{src}") } Op::BitAnd | Op::BitOr | Op::BitXor | Op::ShiftLeft | Op::ShiftRight | Op::UShiftRight => { let dst = read_reg_operand(code, &mut pc); let lhs = read_reg_operand(code, &mut pc); let rhs = read_reg_operand(code, &mut pc); format!("{op:?} r{dst}, r{lhs}, r{rhs}") } Op::BitNot => { let dst = read_reg_operand(code, &mut pc); let src = read_reg_operand(code, &mut pc); 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 = read_reg_operand(code, &mut pc); let lhs = read_reg_operand(code, &mut pc); let rhs = read_reg_operand(code, &mut pc); format!("{op:?} r{dst}, r{lhs}, r{rhs}") } Op::LogicalNot => { let dst = read_reg_operand(code, &mut pc); let src = read_reg_operand(code, &mut pc); format!("LogicalNot r{dst}, r{src}") } Op::TypeOf => { let dst = read_reg_operand(code, &mut pc); let src = read_reg_operand(code, &mut pc); format!("TypeOf r{dst}, r{src}") } Op::Void => { let dst = read_reg_operand(code, &mut pc); let src = read_reg_operand(code, &mut pc); format!("Void r{dst}, r{src}") } Op::Jump => { let off = read_i32_operand(code, &mut pc); let target = pc as i32 + off; format!("Jump {off:+} (-> {target:04X})") } Op::JumpIfTrue | Op::JumpIfFalse | Op::JumpIfNullish => { let reg = read_reg_operand(code, &mut pc); let off = read_i32_operand(code, &mut pc); let target = pc as i32 + off; format!("{op:?} r{reg}, {off:+} (-> {target:04X})") } Op::Call | Op::NewCall => { let dst = read_reg_operand(code, &mut pc); let func = read_reg_operand(code, &mut pc); let args_start = read_reg_operand(code, &mut pc); let arg_count = code[pc]; pc += 1; format!("{op:?} r{dst}, r{func}, r{args_start}, {arg_count}") } Op::CallSpread | Op::NewCallSpread => { let dst = read_reg_operand(code, &mut pc); let func = read_reg_operand(code, &mut pc); let args_array = read_reg_operand(code, &mut pc); format!("{op:?} r{dst}, r{func}, r{args_array}") } Op::Return => { let reg = read_reg_operand(code, &mut pc); format!("Return r{reg}") } Op::Throw => { let reg = read_reg_operand(code, &mut pc); format!("Throw r{reg}") } Op::CreateClosure => { let dst = read_reg_operand(code, &mut pc); let idx = read_u16_operand(code, &mut pc); format!("CreateClosure r{dst}, func#{idx}") } Op::GetProperty => { let dst = read_reg_operand(code, &mut pc); let obj = read_reg_operand(code, &mut pc); let key = read_reg_operand(code, &mut pc); format!("GetProperty r{dst}, r{obj}, r{key}") } Op::SetProperty => { let obj = read_reg_operand(code, &mut pc); let key = read_reg_operand(code, &mut pc); let val = read_reg_operand(code, &mut pc); format!("SetProperty r{obj}, r{key}, r{val}") } Op::CreateObject => { let dst = read_reg_operand(code, &mut pc); format!("CreateObject r{dst}") } Op::CreateArray => { let dst = read_reg_operand(code, &mut pc); format!("CreateArray r{dst}") } Op::GetPropertyByName => { let dst = read_reg_operand(code, &mut pc); let obj = read_reg_operand(code, &mut pc); let idx = read_u16_operand(code, &mut pc); let ic = read_u16_operand(code, &mut pc); let name = self .names .get(idx as usize) .map(|s| s.as_str()) .unwrap_or("?"); format!("GetPropertyByName r{dst}, r{obj}, @{idx}(\"{name}\"), ic{ic}") } Op::SetPropertyByName => { let obj = read_reg_operand(code, &mut pc); let idx = read_u16_operand(code, &mut pc); let val = read_reg_operand(code, &mut pc); let ic = read_u16_operand(code, &mut pc); let name = self .names .get(idx as usize) .map(|s| s.as_str()) .unwrap_or("?"); format!("SetPropertyByName r{obj}, @{idx}(\"{name}\"), r{val}, ic{ic}") } Op::DefineMethod => { let obj = read_reg_operand(code, &mut pc); let idx = read_u16_operand(code, &mut pc); let f = read_reg_operand(code, &mut pc); let name = self .names .get(idx as usize) .map(|s| s.as_str()) .unwrap_or("?"); format!("DefineMethod r{obj}, @{idx}(\"{name}\"), r{f}") } Op::DefineAccessor => { let obj = read_reg_operand(code, &mut pc); let idx = read_u16_operand(code, &mut pc); let kind = code[pc]; pc += 1; let f = read_reg_operand(code, &mut pc); let name = self .names .get(idx as usize) .map(|s| s.as_str()) .unwrap_or("?"); let k = if kind & 1 == 0 { "get" } else { "set" }; format!("DefineAccessor r{obj}, @{idx}(\"{name}\"), {k}, r{f}") } Op::SetHomeObject => { let f = read_reg_operand(code, &mut pc); let obj = read_reg_operand(code, &mut pc); format!("SetHomeObject r{f}, r{obj}") } Op::LoadSuperBase => { let dst = read_reg_operand(code, &mut pc); format!("LoadSuperBase r{dst}") } Op::GetPrivate => { let dst = read_reg_operand(code, &mut pc); let obj = read_reg_operand(code, &mut pc); let idx = read_u16_operand(code, &mut pc); let name = self .names .get(idx as usize) .map(|s| s.as_str()) .unwrap_or("?"); format!("GetPrivate r{dst}, r{obj}, @{idx}(\"{name}\")") } Op::SetPrivate => { let obj = read_reg_operand(code, &mut pc); let idx = read_u16_operand(code, &mut pc); let val = read_reg_operand(code, &mut pc); let name = self .names .get(idx as usize) .map(|s| s.as_str()) .unwrap_or("?"); format!("SetPrivate r{obj}, @{idx}(\"{name}\"), r{val}") } Op::HasPrivate => { let dst = read_reg_operand(code, &mut pc); let obj = read_reg_operand(code, &mut pc); let idx = read_u16_operand(code, &mut pc); let name = self .names .get(idx as usize) .map(|s| s.as_str()) .unwrap_or("?"); format!("HasPrivate r{dst}, r{obj}, @{idx}(\"{name}\")") } Op::CopyDataProperties => { let target = read_reg_operand(code, &mut pc); let source = read_reg_operand(code, &mut pc); format!("CopyDataProperties r{target}, r{source}") } Op::Delete => { let dst = read_reg_operand(code, &mut pc); let obj = read_reg_operand(code, &mut pc); let key = read_reg_operand(code, &mut pc); format!("Delete r{dst}, r{obj}, r{key}") } Op::LoadInt8 => { let dst = read_reg_operand(code, &mut pc); let val = code[pc] as i8; pc += 1; format!("LoadInt8 r{dst}, {val}") } Op::ForInInit => { let dst = read_reg_operand(code, &mut pc); let obj = read_reg_operand(code, &mut pc); format!("ForInInit r{dst}, r{obj}") } Op::ForInNext => { let dst_val = read_reg_operand(code, &mut pc); let dst_done = read_reg_operand(code, &mut pc); let keys = read_reg_operand(code, &mut pc); let idx = read_reg_operand(code, &mut pc); format!("ForInNext r{dst_val}, r{dst_done}, r{keys}, r{idx}") } Op::SetPrototype => { let obj = read_reg_operand(code, &mut pc); let proto = read_reg_operand(code, &mut pc); format!("SetPrototype r{obj}, r{proto}") } Op::GetPrototype => { let dst = read_reg_operand(code, &mut pc); let obj = read_reg_operand(code, &mut pc); format!("GetPrototype r{dst}, r{obj}") } Op::PushExceptionHandler => { let catch_reg = read_reg_operand(code, &mut pc); let off = read_i32_operand(code, &mut pc); let target = pc as i32 + off; format!("PushExceptionHandler r{catch_reg}, @{target:04X}") } Op::PopExceptionHandler => "PopExceptionHandler".to_string(), Op::NewCell => { let dst = read_reg_operand(code, &mut pc); format!("NewCell r{dst}") } Op::CellLoad => { let dst = read_reg_operand(code, &mut pc); let cell = read_reg_operand(code, &mut pc); format!("CellLoad r{dst}, r{cell}") } Op::CellStore => { let cell = read_reg_operand(code, &mut pc); let src = read_reg_operand(code, &mut pc); format!("CellStore r{cell}, r{src}") } Op::LoadUpvalue => { let dst = read_reg_operand(code, &mut pc); let idx = read_u16_operand(code, &mut pc); format!("LoadUpvalue r{dst}, uv{idx}") } Op::StoreUpvalue => { let idx = read_u16_operand(code, &mut pc); let src = read_reg_operand(code, &mut pc); format!("StoreUpvalue uv{idx}, r{src}") } Op::Yield => { let dst = read_reg_operand(code, &mut pc); let src = read_reg_operand(code, &mut pc); format!("Yield r{dst}, r{src}") } Op::Spread => { let dst = read_reg_operand(code, &mut pc); let src = read_reg_operand(code, &mut pc); format!("Spread r{dst}, r{src}") } Op::Await => { let dst = read_reg_operand(code, &mut pc); let src = read_reg_operand(code, &mut pc); format!("Await r{dst}, r{src}") } Op::BuildArguments => { let dst = read_reg_operand(code, &mut pc); format!("BuildArguments r{dst}") } Op::BuildRestArguments => { let dst = read_reg_operand(code, &mut pc); let start = read_u16_operand(code, &mut pc); format!("BuildRestArguments r{dst}, {start}") } }; 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::NewCall, Op::CallSpread, Op::NewCallSpread, Op::GetProperty, Op::SetProperty, Op::CreateObject, Op::CreateArray, Op::GetPropertyByName, Op::SetPropertyByName, Op::DefineMethod, Op::DefineAccessor, Op::SetHomeObject, Op::LoadSuperBase, Op::GetPrivate, Op::SetPrivate, Op::HasPrivate, Op::CopyDataProperties, 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, Op::BuildArguments, Op::BuildRestArguments, ]; 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")); }
#[test] fn test_inline_cache_uninitialized() { let ic = InlineCache::new(); assert!(ic.lookup(ShapeId::ROOT).is_none()); assert!(!ic.is_megamorphic()); }
#[test] fn test_inline_cache_monomorphic() { let mut ic = InlineCache::new(); let shape = ShapeId::ROOT; ic.update(shape, 0); assert_eq!(ic.lookup(shape), Some(0)); assert!(matches!(ic.state, InlineCacheState::Monomorphic(_))); }
#[test] fn test_inline_cache_polymorphic() { use crate::shape::{PropertyAttrs, ShapeTable}; let mut table = ShapeTable::new(); let s1 = table.add_property(ShapeId::ROOT, "x", PropertyAttrs::DEFAULT); let s2 = table.add_property(ShapeId::ROOT, "y", PropertyAttrs::DEFAULT);
let mut ic = InlineCache::new(); ic.update(s1, 0); ic.update(s2, 0); assert!(matches!(ic.state, InlineCacheState::Polymorphic(_))); assert_eq!(ic.lookup(s1), Some(0)); assert_eq!(ic.lookup(s2), Some(0)); }
#[test] fn test_inline_cache_megamorphic() { use crate::shape::{PropertyAttrs, ShapeTable}; let mut table = ShapeTable::new(); let shapes: Vec<_> = (0..5) .map(|i| table.add_property(ShapeId::ROOT, &format!("p{i}"), PropertyAttrs::DEFAULT)) .collect();
let mut ic = InlineCache::new(); for (i, &s) in shapes.iter().enumerate() { ic.update(s, i as u32); } assert!(ic.is_megamorphic()); // Megamorphic always returns None. for &s in &shapes { assert!(ic.lookup(s).is_none()); } }
#[test] fn test_inline_cache_duplicate_update() { let mut ic = InlineCache::new(); ic.update(ShapeId::ROOT, 0); ic.update(ShapeId::ROOT, 0); assert!(matches!(ic.state, InlineCacheState::Monomorphic(_))); }
#[test] fn test_emit_prop_name_allocates_ic() { let mut b = BytecodeBuilder::new("test".into(), 0); b.func.register_count = 2; let ni = b.add_name("x"); b.emit_get_prop_name(0, 1, ni); b.emit_set_prop_name(1, ni, 0); let func = b.finish(); assert_eq!(func.inline_caches.len(), 2); let dis = func.disassemble(); assert!(dis.contains("ic0")); assert!(dis.contains("ic1")); }}