From be87e4e21ba7da0e21e4e3807a144591ac1d38ae Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Tue, 23 Jun 2026 17:06:39 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=8B=86=E5=88=86=20bytecode/cache?= =?UTF-8?q?=20crate=20=E5=B9=B6=E6=A8=A1=E5=9D=97=E5=8C=96=20counter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 22 +- Cargo.toml | 2 + crates/oxide_bytecode/Cargo.toml | 9 + crates/oxide_bytecode/src/lib.rs | 7 + .../src/module.rs | 0 .../src/opcode.rs | 0 crates/oxide_cli/Cargo.toml | 2 + crates/oxide_cli/src/main.rs | 5 +- crates/oxide_code_cache/Cargo.toml | 11 + crates/oxide_code_cache/src/lib.rs | 71 ++ crates/oxide_compiler/Cargo.toml | 3 +- crates/oxide_compiler/src/compiler.rs | 6 +- crates/oxide_compiler/src/counter.rs | 1125 ----------------- crates/oxide_compiler/src/counter/expr/mod.rs | 434 +++++++ .../src/counter/helper/binding.rs | 158 +++ .../src/counter/helper/class.rs | 80 ++ .../src/counter/helper/member.rs | 173 +++ .../oxide_compiler/src/counter/helper/mod.rs | 3 + crates/oxide_compiler/src/counter/mod.rs | 10 + crates/oxide_compiler/src/counter/stmt/mod.rs | 303 +++++ crates/oxide_compiler/src/emitter.rs | 5 +- crates/oxide_compiler/src/lib.rs | 2 - .../tests/arrow_template_tests.rs | 2 +- .../tests/compile_expr_tests.rs | 9 +- .../tests/compile_stmt_tests.rs | 7 +- .../tests/structural_hash_tests.rs | 13 +- .../tests/symbol_table_tests.rs | 7 +- crates/oxide_kernel/Cargo.toml | 3 +- crates/oxide_kernel/src/code_forge.rs | 142 +-- crates/oxide_test262/Cargo.toml | 1 + crates/oxide_vm/Cargo.toml | 3 +- crates/oxide_vm/src/dispatch/member.rs | 2 +- crates/oxide_vm/src/dispatch/property.rs | 2 +- crates/oxide_vm/src/vm.rs | 6 +- crates/oxide_vm/src/vm_dispatch_ctrl.rs | 2 +- crates/oxide_vm/src/vm_runtime.rs | 4 +- crates/oxide_vm/src/vm_support.rs | 2 +- crates/oxide_vm/tests/bench_tests.rs | 2 +- 38 files changed, 1332 insertions(+), 1306 deletions(-) create mode 100644 crates/oxide_bytecode/Cargo.toml create mode 100644 crates/oxide_bytecode/src/lib.rs rename crates/{oxide_compiler => oxide_bytecode}/src/module.rs (100%) rename crates/{oxide_compiler => oxide_bytecode}/src/opcode.rs (100%) create mode 100644 crates/oxide_code_cache/Cargo.toml create mode 100644 crates/oxide_code_cache/src/lib.rs delete mode 100644 crates/oxide_compiler/src/counter.rs create mode 100644 crates/oxide_compiler/src/counter/expr/mod.rs create mode 100644 crates/oxide_compiler/src/counter/helper/binding.rs create mode 100644 crates/oxide_compiler/src/counter/helper/class.rs create mode 100644 crates/oxide_compiler/src/counter/helper/member.rs create mode 100644 crates/oxide_compiler/src/counter/helper/mod.rs create mode 100644 crates/oxide_compiler/src/counter/mod.rs create mode 100644 crates/oxide_compiler/src/counter/stmt/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 984a2ee3..96e123d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -889,6 +889,10 @@ dependencies = [ "serde_json", ] +[[package]] +name = "oxide_bytecode" +version = "0.1.0" + [[package]] name = "oxide_cli" version = "0.1.0" @@ -896,6 +900,8 @@ dependencies = [ "ansi_term", "clap", "oxide_api", + "oxide_bytecode", + "oxide_code_cache", "oxide_compiler", "oxide_kernel", "oxide_parser", @@ -904,11 +910,20 @@ dependencies = [ "rustyline", ] +[[package]] +name = "oxide_code_cache" +version = "0.1.0" +dependencies = [ + "dashmap", + "oxide_bytecode", +] + [[package]] name = "oxide_compiler" version = "0.1.0" dependencies = [ - "oxide_kernel", + "oxide_bytecode", + "oxide_code_cache", "oxide_parser", "rustc-hash", "serde", @@ -922,7 +937,8 @@ dependencies = [ "bumpalo", "dashmap", "hashbrown 0.14.5", - "oxide_compiler", + "oxide_bytecode", + "oxide_code_cache", "oxide_parser", "oxide_types", "rustc-hash", @@ -946,6 +962,7 @@ dependencies = [ name = "oxide_test262" version = "0.1.0" dependencies = [ + "oxide_bytecode", "oxide_compiler", "oxide_kernel", "oxide_parser", @@ -974,6 +991,7 @@ dependencies = [ "fast-float", "indexmap", "memchr", + "oxide_bytecode", "oxide_compiler", "oxide_kernel", "oxide_parser", diff --git a/Cargo.toml b/Cargo.toml index 2801c597..0001a246 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,8 @@ resolver = "2" members = [ "crates/oxide_parser", + "crates/oxide_bytecode", + "crates/oxide_code_cache", "crates/oxide_compiler", "crates/oxide_types", "crates/oxide_vm", diff --git a/crates/oxide_bytecode/Cargo.toml b/crates/oxide_bytecode/Cargo.toml new file mode 100644 index 00000000..ed73f348 --- /dev/null +++ b/crates/oxide_bytecode/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "oxide_bytecode" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +description = "OxideJS - Shared bytecode protocol and compiled module ABI" + +[dependencies] diff --git a/crates/oxide_bytecode/src/lib.rs b/crates/oxide_bytecode/src/lib.rs new file mode 100644 index 00000000..62141240 --- /dev/null +++ b/crates/oxide_bytecode/src/lib.rs @@ -0,0 +1,7 @@ +#![doc = "OxideJS - Shared bytecode protocol and compiled module ABI"] + +pub mod module; +pub mod opcode; + +pub use module::{CompiledModule, Constant}; +pub use opcode::{Instr, OpCode}; diff --git a/crates/oxide_compiler/src/module.rs b/crates/oxide_bytecode/src/module.rs similarity index 100% rename from crates/oxide_compiler/src/module.rs rename to crates/oxide_bytecode/src/module.rs diff --git a/crates/oxide_compiler/src/opcode.rs b/crates/oxide_bytecode/src/opcode.rs similarity index 100% rename from crates/oxide_compiler/src/opcode.rs rename to crates/oxide_bytecode/src/opcode.rs diff --git a/crates/oxide_cli/Cargo.toml b/crates/oxide_cli/Cargo.toml index 19f8ff32..b87ca2fc 100644 --- a/crates/oxide_cli/Cargo.toml +++ b/crates/oxide_cli/Cargo.toml @@ -11,6 +11,8 @@ name = "oxide" path = "src/main.rs" [dependencies] +oxide_bytecode = { path = "../oxide_bytecode" } +oxide_code_cache = { path = "../oxide_code_cache" } oxide_parser = { path = "../oxide_parser" } oxide_types = { path = "../oxide_types" } oxide_compiler = { path = "../oxide_compiler" } diff --git a/crates/oxide_cli/src/main.rs b/crates/oxide_cli/src/main.rs index e836436d..ea818ce6 100644 --- a/crates/oxide_cli/src/main.rs +++ b/crates/oxide_cli/src/main.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use ansi_term::Colour::Red; use clap::{Parser, Subcommand}; -use oxide_compiler::compiler::Compiler; +use oxide_compiler::compiler::{compiled_module_hash, Compiler}; use oxide_kernel::kernel::{KernelConfig, KernelCore}; use oxide_kernel::shape_forge::{ShapeForge, EMPTY_SHAPE_ID}; use oxide_kernel::string_forge::StringForge; @@ -91,7 +91,8 @@ fn eval(code: &str, kernel: &Arc, pool: &Arc) -> ExitCode { }; let compiler = Compiler::new(); - let module = match kernel.code_forge().get_or_compile(&program, &compiler) { + let hash = compiled_module_hash(&program); + let module = match kernel.code_forge().get_or_insert_with(hash, || compiler.compile(&program)) { Ok(m) => m, Err(err) => { eprintln!("{}", Red.paint(err)); diff --git a/crates/oxide_code_cache/Cargo.toml b/crates/oxide_code_cache/Cargo.toml new file mode 100644 index 00000000..2e7cadf3 --- /dev/null +++ b/crates/oxide_code_cache/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "oxide_code_cache" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +description = "OxideJS - Compiler-independent compiled module cache" + +[dependencies] +oxide_bytecode = { path = "../oxide_bytecode" } +dashmap.workspace = true diff --git a/crates/oxide_code_cache/src/lib.rs b/crates/oxide_code_cache/src/lib.rs new file mode 100644 index 00000000..06a4aabb --- /dev/null +++ b/crates/oxide_code_cache/src/lib.rs @@ -0,0 +1,71 @@ +#![doc = "OxideJS - Compiler-independent compiled module cache"] + +use std::sync::Arc; + +use dashmap::DashMap; +use oxide_bytecode::CompiledModule; + +/// Shared compiled-module cache keyed by a caller-provided safe module hash. +/// +/// The cache intentionally does not know how to parse or compile JavaScript. +/// Compiler-aware callers compute the key and provide the compile callback. +pub struct CodeForge { + map: DashMap>, +} + +impl CodeForge { + pub fn new() -> Self { + Self { + map: DashMap::with_shard_amount(16), + } + } + + pub fn get(&self, hash: u64) -> Option> { + self.map.get(&hash).map(|entry| Arc::clone(&entry)) + } + + pub fn insert(&self, hash: u64, module: CompiledModule) -> Arc { + let module = Arc::new(module); + Arc::clone(self.map.entry(hash).or_insert(module).value()) + } + + pub fn get_or_insert_with(&self, hash: u64, compile: F) -> Result, String> + where + F: FnOnce() -> Result, + { + if let Some(module) = self.get(hash) { + return Ok(module); + } + + Ok(self.insert(hash, compile()?)) + } +} + +impl Default for CodeForge { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn cache_returns_same_arc_for_same_hash() { + let forge = CodeForge::new(); + let first = forge.insert(1, CompiledModule::new()); + let second = forge.get_or_insert_with(1, || Ok(CompiledModule::new())).expect("cache hit"); + + assert!(Arc::ptr_eq(&first, &second)); + } + + #[test] + fn cache_misses_for_different_hashes() { + let forge = CodeForge::new(); + let first = forge.insert(1, CompiledModule::new()); + let second = forge.insert(2, CompiledModule::new()); + + assert!(!Arc::ptr_eq(&first, &second)); + } +} diff --git a/crates/oxide_compiler/Cargo.toml b/crates/oxide_compiler/Cargo.toml index edf728ec..681882d6 100644 --- a/crates/oxide_compiler/Cargo.toml +++ b/crates/oxide_compiler/Cargo.toml @@ -7,10 +7,11 @@ rust-version.workspace = true description = "OxideJS - AST to bytecode compiler" [dependencies] +oxide_bytecode = { path = "../oxide_bytecode" } oxide_parser = { path = "../oxide_parser" } serde.workspace = true serde_json.workspace = true rustc-hash.workspace = true [dev-dependencies] -oxide_kernel = { path = "../oxide_kernel" } +oxide_code_cache = { path = "../oxide_code_cache" } diff --git a/crates/oxide_compiler/src/compiler.rs b/crates/oxide_compiler/src/compiler.rs index 4525c912..ba246941 100644 --- a/crates/oxide_compiler/src/compiler.rs +++ b/crates/oxide_compiler/src/compiler.rs @@ -1,12 +1,12 @@ use std::collections::HashMap; -use crate::module::CompiledModule; -use crate::opcode::{self, OpCode}; use crate::symbol_table::{Binding, SymbolTable}; +use oxide_bytecode::module::CompiledModule; +use oxide_bytecode::opcode::{self, OpCode}; pub use crate::hash::{compiled_module_hash, structural_hash}; -pub use crate::module::Constant; use crate::symbol_table::ScopeKind; +pub use oxide_bytecode::module::Constant; pub use oxide_parser::VariableDeclarationKind; pub use oxide_parser::{AssignmentOperator, BinaryOperator, Expression, Statement, UnaryOperator}; diff --git a/crates/oxide_compiler/src/counter.rs b/crates/oxide_compiler/src/counter.rs deleted file mode 100644 index 28aa84ef..00000000 --- a/crates/oxide_compiler/src/counter.rs +++ /dev/null @@ -1,1125 +0,0 @@ -use oxide_parser::{ - AssignmentOperator, ChainElement, ClassElement, Expression, ForStatementInit, LogicalOperator, - MethodDefinitionKind, SimpleAssignmentTarget, Statement, UnaryOperator, VariableDeclarationKind, -}; - -use crate::compiler::{is_side_effect_free, CompileCtx, Compiler, Label}; - -impl Compiler { - fn count_object_property_read_static(&self, ctx: &mut CompileCtx) { - ctx.count_load_var(); // prop/object temp - ctx.count_load_const(); // key reg - ctx.count_ic_instr_with_ext(); // IC_GET_PROP + 3 ext words - } - - fn count_object_property_read_key(&self, key: &oxide_parser::PropertyKey, computed: bool, ctx: &mut CompileCtx) { - if !computed { - self.count_object_property_read_static(ctx); - let _ = key; - return; - } - - ctx.count_load_var(); // prop/object temp - match key { - oxide_parser::PropertyKey::Identifier(ident) => { - let name = ident.name.as_str(); - let _ = ctx.lookup_or_builtin(name); - ctx.count_load_var(); // key - } - oxide_parser::PropertyKey::StringLiteral(_) => { - ctx.count_load_const(); // key - } - oxide_parser::PropertyKey::NumericLiteral(_) => { - ctx.count_load_const(); // key - } - oxide_parser::PropertyKey::StaticIdentifier(_) => { - ctx.count_load_const(); // key - } - _ => {} - } - ctx.alloc_reg(); // result reg - ctx.count_instr(); // GET_PROP_DYNAMIC - } - - fn count_optional_guard(&self, ctx: &mut CompileCtx) { - ctx.count_load_var(); // dup - ctx.count_jump(); // JMP_IF_NULLISH - } - - fn count_static_member_get_preserve_base( - &self, member: &oxide_parser::StaticMemberExpression, short_chain: bool, ctx: &mut CompileCtx, - ) { - self.count_chainable_expression(&member.object, short_chain, ctx); - if short_chain && member.optional { - self.count_optional_guard(ctx); - } - ctx.count_load_const(); // key - ctx.count_load_var(); // receiver copy - ctx.count_ic_instr_with_ext(); // IC_GET_PROP + 3 ext words - } - - fn count_computed_member_get_preserve_base( - &self, member: &oxide_parser::ComputedMemberExpression, short_chain: bool, ctx: &mut CompileCtx, - ) { - self.count_chainable_expression(&member.object, short_chain, ctx); - if short_chain && member.optional { - self.count_optional_guard(ctx); - } - self.count_expression(&member.expression, ctx); - ctx.alloc_reg(); - ctx.count_instr(); // GET_PROP_DYNAMIC - } - - fn count_chain_call(&self, call: &oxide_parser::CallExpression, short_chain: bool, ctx: &mut CompileCtx) { - match &call.callee { - Expression::StaticMemberExpression(member) => { - self.count_static_member_get_preserve_base(member, short_chain, ctx) - } - Expression::ComputedMemberExpression(member) => { - self.count_computed_member_get_preserve_base(member, short_chain, ctx); - } - Expression::PrivateFieldExpression(member) => { - self.count_chainable_expression(&member.object, short_chain, ctx); - if short_chain && member.optional { - self.count_optional_guard(ctx); - } - ctx.count_private_access(); - } - _ => { - self.count_chainable_expression(&call.callee, short_chain, ctx); - if short_chain && call.optional { - self.count_optional_guard(ctx); - } - ctx.alloc_reg(); - ctx.projected_pc += 1; // LOAD_CONST undefined this - } - } - if short_chain - && call.optional - && matches!( - &call.callee, - Expression::StaticMemberExpression(_) - | Expression::ComputedMemberExpression(_) - | Expression::PrivateFieldExpression(_) - ) - { - self.count_optional_guard(ctx); - } - for arg in &call.arguments { - if let Some(expr) = arg.as_expression() { - self.count_expression(expr, ctx); - } - } - ctx.count_call_instr_with_arg_ext(); // CALL + arg_count ext word - ctx.count_load_var(); // result <- regs[0] - } - - fn count_chainable_expression(&self, expr: &Expression, short_chain: bool, ctx: &mut CompileCtx) { - match expr { - Expression::StaticMemberExpression(member) => { - self.count_static_member_get_preserve_base(member, short_chain, ctx) - } - Expression::ComputedMemberExpression(member) => { - self.count_computed_member_get_preserve_base(member, short_chain, ctx) - } - Expression::PrivateFieldExpression(member) => { - self.count_chainable_expression(&member.object, short_chain, ctx); - if short_chain && member.optional { - self.count_optional_guard(ctx); - } - ctx.count_private_access(); - } - Expression::CallExpression(call) => self.count_chain_call(call, short_chain, ctx), - Expression::ChainExpression(chain) => self.count_chain_element(&chain.expression, short_chain, ctx), - _ => self.count_expression(expr, ctx), - } - } - - fn count_chain_element(&self, element: &ChainElement, short_chain: bool, ctx: &mut CompileCtx) { - match element { - ChainElement::StaticMemberExpression(member) => { - self.count_static_member_get_preserve_base(member, short_chain, ctx) - } - ChainElement::ComputedMemberExpression(member) => { - self.count_computed_member_get_preserve_base(member, short_chain, ctx) - } - ChainElement::PrivateFieldExpression(member) => { - self.count_chainable_expression(&member.object, short_chain, ctx); - if short_chain && member.optional { - self.count_optional_guard(ctx); - } - ctx.count_private_access(); - } - ChainElement::CallExpression(call) => self.count_chain_call(call, short_chain, ctx), - ChainElement::TSNonNullExpression(_) => {} - } - } - - fn count_logical_assign_test(&self, op: LogicalOperator, id: u32, ctx: &mut CompileCtx) { - match op { - LogicalOperator::And | LogicalOperator::Or => { - ctx.projected_pc += 1; - } - LogicalOperator::Coalesce => { - ctx.projected_pc += 1; // JMP_IF_NULLISH to store body - ctx.projected_pc += 1; // JMP to end on non-nullish - ctx.label_map.insert(Label::TernaryElse(id), ctx.projected_pc); - } - } - } - - fn count_binding_pattern(&self, pattern: &oxide_parser::BindingPattern, ctx: &mut CompileCtx) { - match pattern { - oxide_parser::BindingPattern::BindingIdentifier(_) => { - ctx.alloc_reg(); - ctx.projected_pc += 1; - } - oxide_parser::BindingPattern::ArrayPattern(ap) => { - ctx.count_instr(); // FOR_OF_INIT - for elem in &ap.elements { - ctx.alloc_reg(); - ctx.count_instr(); // FOR_OF_DONE - ctx.alloc_reg(); - ctx.count_instr(); // FOR_OF_NEXT - if let Some(inner) = elem { - self.count_binding_pattern(inner, ctx); - } - } - if let Some(rest) = &ap.rest { - self.count_rest_array(ctx); - self.count_binding_pattern(&rest.argument, ctx); - } - ctx.count_instr(); // FOR_OF_CLOSE - } - oxide_parser::BindingPattern::ObjectPattern(op) => { - for prop in &op.properties { - self.count_object_property_read_key(&prop.key, prop.computed, ctx); - self.count_binding_pattern(&prop.value, ctx); - } - if let Some(rest) = &op.rest { - ctx.alloc_reg(); - ctx.count_instr_with_ext(1); // REST_OBJECT + excluded-keys ext - self.count_binding_pattern(&rest.argument, ctx); - } - } - oxide_parser::BindingPattern::AssignmentPattern(ap) => { - ctx.count_load_const(); // undefined - ctx.count_instr(); // STRICT_EQ - ctx.count_jump(); // JMP_IF_FALSE - self.count_expression(&ap.right, ctx); - ctx.count_instr(); // LOAD_VAR default - self.count_binding_pattern(&ap.left, ctx); - } - } - } - - fn count_rest_array(&self, ctx: &mut CompileCtx) { - ctx.alloc_reg(); // rest - ctx.count_instr(); // NEW_ARRAY - ctx.count_load_const(); // idx = 0 - ctx.alloc_reg(); // has - ctx.count_instr(); // FOR_OF_DONE - ctx.count_jump(); // JMP_IF_FALSE - ctx.alloc_reg(); // val - ctx.count_instr(); // FOR_OF_NEXT - ctx.count_instr(); // SET_ELEM - ctx.alloc_reg(); // inc tmp - ctx.count_instr(); // INC_PRE - ctx.count_jump(); // JMP - } - - fn count_array_assignment(&self, ap: &oxide_parser::ArrayAssignmentTarget, ctx: &mut CompileCtx) { - ctx.count_instr(); // FOR_OF_INIT - for elem in &ap.elements { - ctx.alloc_reg(); - ctx.count_instr(); // FOR_OF_DONE - ctx.alloc_reg(); - ctx.count_instr(); // FOR_OF_NEXT - if elem.is_some() { - ctx.alloc_reg(); - ctx.count_instr(); // STORE_VAR - } - } - if ap.rest.is_some() { - self.count_rest_array(ctx); - ctx.alloc_reg(); - ctx.count_instr(); // STORE_VAR - } - ctx.count_instr(); // FOR_OF_CLOSE - } - - fn count_object_assignment(&self, op: &oxide_parser::ObjectAssignmentTarget, ctx: &mut CompileCtx) { - for prop in &op.properties { - match prop { - oxide_parser::AssignmentTargetProperty::AssignmentTargetPropertyIdentifier(id) => { - self.count_object_property_read_static(ctx); - if let Some(default_expr) = &id.init { - ctx.count_load_const(); // undefined - ctx.count_instr(); // STRICT_EQ - ctx.count_jump(); // JMP_IF_FALSE - self.count_expression(default_expr, ctx); - ctx.count_instr(); // LOAD_VAR default - } - ctx.alloc_reg(); - ctx.count_instr(); // STORE_VAR - } - oxide_parser::AssignmentTargetProperty::AssignmentTargetPropertyProperty(prop) => { - self.count_object_property_read_key(&prop.name, prop.computed, ctx); - self.count_assignment_maybe_default(&prop.binding, ctx); - } - } - } - if op.rest.is_some() { - ctx.alloc_reg(); - ctx.count_instr_with_ext(1); // REST_OBJECT + excluded-keys ext - ctx.alloc_reg(); - ctx.count_instr(); // STORE_VAR - } - } - - fn count_assignment_maybe_default( - &self, target: &oxide_parser::AssignmentTargetMaybeDefault, ctx: &mut CompileCtx, - ) { - match target { - oxide_parser::AssignmentTargetMaybeDefault::AssignmentTargetWithDefault(default) => { - ctx.count_load_const(); // undefined - ctx.count_instr(); // STRICT_EQ - ctx.count_jump(); // JMP_IF_FALSE - self.count_expression(&default.init, ctx); - ctx.count_instr(); // LOAD_VAR default - self.count_assign_target(&default.binding, ctx); - } - oxide_parser::AssignmentTargetMaybeDefault::ArrayAssignmentTarget(ap) => { - self.count_array_assignment(ap, ctx) - } - oxide_parser::AssignmentTargetMaybeDefault::ObjectAssignmentTarget(op) => { - self.count_object_assignment(op, ctx) - } - oxide_parser::AssignmentTargetMaybeDefault::AssignmentTargetIdentifier(_) => { - ctx.alloc_reg(); - ctx.count_instr(); // STORE_VAR - } - _ => {} - } - } - - fn count_assign_target(&self, target: &oxide_parser::AssignmentTarget, ctx: &mut CompileCtx) { - match target { - oxide_parser::AssignmentTarget::AssignmentTargetIdentifier(_) => { - ctx.alloc_reg(); - ctx.count_instr(); // STORE_VAR - } - oxide_parser::AssignmentTarget::ArrayAssignmentTarget(ap) => self.count_array_assignment(ap, ctx), - oxide_parser::AssignmentTarget::ObjectAssignmentTarget(op) => self.count_object_assignment(op, ctx), - _ => {} - } - } - - fn count_class(&self, class: &oxide_parser::Class, ctx: &mut CompileCtx) { - ctx.alloc_reg(); // ctor_reg - ctx.alloc_reg(); // proto_reg - if let Some(super_class) = &class.super_class { - self.count_expression(super_class, ctx); - } - ctx.count_instr(); // LOAD_CONST ctor - ctx.count_instr(); // NEW_OBJECT proto - - if class.super_class.is_some() { - ctx.alloc_reg(); // parent prototype key - ctx.count_instr(); // LOAD_CONST - ctx.alloc_reg(); // parent prototype value - ctx.count_instr(); // GET_PROP - ctx.alloc_reg(); // __proto__ key - ctx.count_instr(); // LOAD_CONST - ctx.count_instr(); // SET_PROP proto.__proto__ - ctx.count_instr(); // SET_PROP ctor.__proto__ - } - - ctx.count_load_const(); // constructor key - ctx.count_instr(); // SET_PROP proto.constructor = ctor - - ctx.count_load_const(); // prototype key - ctx.count_instr(); // SET_PROP ctor.prototype = proto - - for element in &class.body.body { - match element { - ClassElement::MethodDefinition(method) => { - let method = method.as_ref(); - if matches!(method.kind, MethodDefinitionKind::Constructor) { - continue; - } - if matches!(method.key, oxide_parser::PropertyKey::PrivateIdentifier(_)) { - if method.r#static { - ctx.count_load_const(); // private id - ctx.count_load_const(); // method - ctx.count_instr(); // INIT_PRIVATE - } - continue; - } - ctx.count_load_const(); // key_reg - ctx.count_load_const(); // method_reg - ctx.count_instr(); // SET_HOME_OBJECT - match method.kind { - MethodDefinitionKind::Method => { - ctx.count_instr(); // SET_PROP - } - MethodDefinitionKind::Get | MethodDefinitionKind::Set => { - ctx.count_load_const(); // undefined placeholder - ctx.count_define_accessor(); // DEFINE_ACCESSOR + ext - } - MethodDefinitionKind::Constructor => {} - } - } - ClassElement::PropertyDefinition(prop) => { - let prop = prop.as_ref(); - if prop.r#static { - ctx.count_load_const(); // key_reg - if let Some(value) = &prop.value { - self.count_expression(value, ctx); - } else { - ctx.count_load_const(); - } - ctx.count_instr(); // SET_PROP - } - } - ClassElement::StaticBlock(block) => { - for stmt in &block.body { - self.count_statement(stmt, ctx); - } - } - ClassElement::AccessorProperty(_) | ClassElement::TSIndexSignature(_) => {} - } - } - } - - pub(crate) fn count_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) { - match stmt { - Statement::ExpressionStatement(es) => { - self.count_expression(&es.expression, ctx); - } - Statement::VariableDeclaration(decl) => { - for d in &decl.declarations { - if let Some(init) = &d.init { - self.count_expression(init, ctx); - self.count_binding_pattern(&d.id, ctx); - } else { - ctx.alloc_reg(); - ctx.count_words(2); // LOAD_CONST(undefined) + STORE_VAR - } - } - } - Statement::ReturnStatement(ret) => { - if let Some(arg) = &ret.argument { - self.count_expression(arg, ctx); - } - ctx.projected_pc += 1; // RETURN - } - Statement::IfStatement(ifs) => { - let id = ctx.next_label_id(); - let else_label = Label::IfElse(id); - let end_label = Label::IfEnd(id); - - self.count_expression(&ifs.test, ctx); - ctx.projected_pc += 1; // JMP_IF_FALSE - self.count_statement(&ifs.consequent, ctx); - ctx.alloc_reg(); // result register - ctx.projected_pc += 1; // LOAD_VAR result <- consequent - if ifs.alternate.is_some() { - ctx.projected_pc += 1; // JMP (skip else) - } - ctx.label_map.insert(else_label, ctx.projected_pc); - if let Some(alt_stmt) = &ifs.alternate { - self.count_statement(alt_stmt, ctx); - ctx.projected_pc += 1; // LOAD_VAR result <- alternate - } - ctx.label_map.insert(end_label, ctx.projected_pc); - } - Statement::WhileStatement(wh) => { - let id = ctx.next_label_id(); - let start_label = Label::WhileStart(id); - let end_label = Label::WhileEnd(id); - - ctx.label_map.insert(start_label, ctx.projected_pc); - self.count_expression(&wh.test, ctx); - ctx.projected_pc += 1; // JMP_IF_FALSE - self.count_statement(&wh.body, ctx); - ctx.projected_pc += 1; // JMP (backward) - ctx.label_map.insert(end_label, ctx.projected_pc); - } - Statement::DoWhileStatement(dw) => { - let id = ctx.next_label_id(); - let start_label = Label::DoWhileStart(id); - let end_label = Label::DoWhileEnd(id); - - ctx.label_map.insert(start_label, ctx.projected_pc); - self.count_statement(&dw.body, ctx); - self.count_expression(&dw.test, ctx); - ctx.projected_pc += 1; // JMP_IF_TRUE (backward) - ctx.label_map.insert(end_label, ctx.projected_pc); - } - Statement::ForStatement(fr) => { - let id = ctx.next_label_id(); - let start_label = Label::ForStart(id); - let update_label = Label::ForUpdate(id); - let end_label = Label::ForEnd(id); - - if let Some(init) = &fr.init { - if let Some(expr) = init.as_expression() { - self.count_expression(expr, ctx); - } else if let ForStatementInit::VariableDeclaration(decl) = init { - for d in &decl.declarations { - if let Some(init_expr) = &d.init { - self.count_expression(init_expr, ctx); - self.count_binding_pattern(&d.id, ctx); - } else { - ctx.alloc_reg(); - ctx.count_words(2); // LOAD_CONST(undefined) + STORE_VAR - } - } - } - } - ctx.label_map.insert(start_label, ctx.projected_pc); - if let Some(test) = &fr.test { - self.count_expression(test, ctx); - ctx.projected_pc += 1; // JMP_IF_FALSE - } - self.count_statement(&fr.body, ctx); - ctx.label_map.insert(update_label, ctx.projected_pc); - if let Some(update) = &fr.update { - self.count_expression(update, ctx); - } - ctx.projected_pc += 1; // JMP (backward) - ctx.label_map.insert(end_label, ctx.projected_pc); - } - Statement::ForInStatement(fi) => { - let id = ctx.next_label_id(); - let start_label = Label::ForInStart(id); - let end_label = Label::ForInEnd(id); - - self.count_expression(&fi.right, ctx); - ctx.count_instr(); // FOR_IN_INIT - - ctx.label_map.insert(start_label, ctx.projected_pc); - ctx.count_instr(); // FOR_IN_DONE - ctx.count_jump(); // JMP_IF_FALSE - ctx.count_jump(); // JMP cleanup - - ctx.count_instr(); // FOR_IN_NEXT - match &fi.left { - oxide_parser::ForStatementLeft::VariableDeclaration(decl) => { - for _d in &decl.declarations { - ctx.alloc_reg(); - ctx.count_instr(); // STORE_VAR - } - } - oxide_parser::ForStatementLeft::AssignmentTargetIdentifier(_) => { - ctx.alloc_reg(); // key register - ctx.count_instr(); // STORE_VAR - } - _ => {} - } - - self.count_statement(&fi.body, ctx); - ctx.count_jump(); // JMP back to start - - ctx.label_map.insert(end_label, ctx.projected_pc); - ctx.count_instr(); // FOR_IN_CLEANUP - } - Statement::ForOfStatement(fo) => { - let id = ctx.next_label_id(); - let start_label = Label::ForOfStart(id); - let end_label = Label::ForOfEnd(id); - - self.count_expression(&fo.right, ctx); - ctx.count_instr(); // FOR_OF_INIT - ctx.label_map.insert(start_label, ctx.projected_pc); - ctx.alloc_reg(); // has_reg - ctx.count_instr(); // FOR_OF_DONE - ctx.count_jump(); // JMP_IF_FALSE - ctx.alloc_reg(); // val_reg - ctx.count_instr(); // FOR_OF_NEXT - match &fo.left { - oxide_parser::ForStatementLeft::VariableDeclaration(decl) => { - for d in &decl.declarations { - self.count_binding_pattern(&d.id, ctx); - } - } - oxide_parser::ForStatementLeft::AssignmentTargetIdentifier(_) => { - ctx.alloc_reg(); - ctx.count_instr(); // STORE_VAR - } - oxide_parser::ForStatementLeft::ArrayAssignmentTarget(ap) => { - self.count_array_assignment(ap, ctx); - } - oxide_parser::ForStatementLeft::ObjectAssignmentTarget(op) => { - self.count_object_assignment(op, ctx); - } - _ => {} - } - self.count_statement(&fo.body, ctx); - ctx.count_jump(); // JMP back - ctx.label_map.insert(end_label, ctx.projected_pc); - ctx.count_instr(); // FOR_OF_CLOSE - } - Statement::SwitchStatement(sw) => { - let id = ctx.next_label_id(); - let end_label = Label::SwitchEnd(id); - ctx.push_switch(end_label); - - self.count_expression(&sw.discriminant, ctx); - let compare_reg_checkpoint = ctx.reg_checkpoint(); - - let cases = &sw.cases; - for case in cases.iter() { - if let Some(test) = &case.test { - self.count_expression(test, ctx); - ctx.projected_pc += 1; // EQ - ctx.alloc_reg(); // eq result - ctx.projected_pc += 1; // JMP_IF_TRUE - ctx.restore_reg_checkpoint(compare_reg_checkpoint); - } - } - - let has_default = cases.iter().any(|c| c.test.is_none()); - if !has_default { - ctx.projected_pc += 1; // JMP to SwitchEnd (no match) - } - - for (case_idx, case) in cases.iter().enumerate() { - let case_label = Label::SwitchCase(id, case_idx as u32); - ctx.label_map.insert(case_label, ctx.projected_pc); - for s in &case.consequent { - self.count_statement(s, ctx); - } - } - - ctx.label_map.insert(end_label, ctx.projected_pc); - ctx.pop_switch(); - } - Statement::BreakStatement(_) => { - ctx.projected_pc += 1; // JMP - } - Statement::ContinueStatement(_) => { - ctx.projected_pc += 1; // JMP - } - Statement::BlockStatement(block) => { - for s in &block.body { - self.count_statement(s, ctx); - } - } - Statement::FunctionDeclaration(fd) => { - let name = if let Some(id) = &fd.id { - id.name.to_string() - } else { - return; - }; - - // Hoisting: declare function name as initialized - let func_reg = ctx.alloc_reg(); - let _ = ctx.declare_initialized(&name, func_reg, VariableDeclarationKind::Var, false); - - // Body is compiled in the emit pass only. - // FD emits LOAD_CONST(BytecodeFunc) + STORE_VAR - ctx.count_words(2); - } - Statement::ClassDeclaration(class) => { - ctx.alloc_reg(); // class binding reg - self.count_class(class, ctx); - ctx.projected_pc += 1; // STORE_VAR binding <- ctor - } - Statement::ThrowStatement(ts) => { - self.count_expression(&ts.argument, ctx); - ctx.projected_pc += 1; // THROW - } - Statement::TryStatement(ts) => { - let id = ctx.next_label_id(); - let catch_label = Label::CatchBody(id); - let try_end_label = Label::TryEnd(id); - let has_catch = ts.handler.is_some(); - let has_finally = ts.finalizer.is_some(); - - ctx.alloc_reg(); // result_reg - - if has_finally { - ctx.projected_pc += 1; // TRY_FINALLY_BEGIN (before try body) - } - - if has_catch { - ctx.projected_pc += 1; // TRY_BEGIN (before try body) - } - - for s in &ts.block.body { - self.count_statement(s, ctx); - } - ctx.projected_pc += 1; // LOAD_VAR result_reg (if try body has result) - - if has_catch { - ctx.projected_pc += 1; // TRY_END - } - - let jmp_needed = has_catch || has_finally; - if jmp_needed { - ctx.projected_pc += 1; // JMP - } - - ctx.label_map.insert(catch_label, ctx.projected_pc); - if let Some(catch) = &ts.handler { - ctx.push_scope(); - if let Some(_param) = &catch.param { - ctx.alloc_reg(); - ctx.projected_pc += 1; // STORE_VAR - } - for cs in &catch.body.body { - self.count_statement(cs, ctx); - } - ctx.projected_pc += 1; // LOAD_VAR result_reg (if catch body has result) - ctx.pop_scope(); - } - - if let Some(finally) = &ts.finalizer { - let finally_label = Label::FinallyBody(id); - ctx.label_map.insert(finally_label, ctx.projected_pc); - for fs in &finally.body { - self.count_statement(fs, ctx); - } - ctx.projected_pc += 1; // LOAD_VAR result_reg (if finally has result) - ctx.projected_pc += 1; // TRY_FINALLY_END - } - - ctx.label_map.insert(try_end_label, ctx.projected_pc); - } - _ => {} - } - } - - pub(crate) fn count_expression(&self, expr: &Expression, ctx: &mut CompileCtx) { - match expr { - Expression::BinaryExpression(bin) => { - let checkpoint = ctx.reg_checkpoint(); - self.count_expression(&bin.left, ctx); - self.count_expression(&bin.right, ctx); - ctx.projected_pc += 1; // ADD/SUB/MUL/DIV/etc. - if is_side_effect_free(&bin.left) && is_side_effect_free(&bin.right) { - ctx.restore_reg_checkpoint(checkpoint.saturating_add(1)); - } - } - Expression::PrivateInExpression(pin) => { - self.count_expression(&pin.right, ctx); - ctx.count_private_access(); - } - Expression::UnaryExpression(un) => { - match un.operator { - UnaryOperator::Delete => { - match &un.argument { - Expression::Identifier(_) => { - // SyntaxError at compile time, no bytecode cost - } - Expression::StaticMemberExpression(member) => { - self.count_expression(&member.object, ctx); - ctx.count_delete_static(); - } - Expression::ComputedMemberExpression(member) => { - self.count_expression(&member.object, ctx); - self.count_expression(&member.expression, ctx); - ctx.projected_pc += 1; // DELETE_PROP_DYNAMIC - } - Expression::ChainExpression(chain) => { - match &chain.expression { - ChainElement::StaticMemberExpression(member) => { - self.count_expression(&member.object, ctx); - if member.optional { - self.count_optional_guard(ctx); - } - ctx.count_delete_static(); - } - ChainElement::ComputedMemberExpression(member) => { - self.count_expression(&member.object, ctx); - if member.optional { - self.count_optional_guard(ctx); - } - self.count_expression(&member.expression, ctx); - ctx.projected_pc += 1; // DELETE_PROP_DYNAMIC - } - _ => {} - } - ctx.projected_pc += 1; // JMP over nullish true writer - ctx.projected_pc += 1; // LOAD_CONST true - } - _ => {} - } - } - _ => { - self.count_expression(&un.argument, ctx); - ctx.projected_pc += 1; // NEG/TYPEOF/VOID/NOT - } - } - } - Expression::CallExpression(call) => { - if matches!(&call.callee, Expression::Super(_)) { - for arg in &call.arguments { - if let Some(expr) = arg.as_expression() { - self.count_expression(expr, ctx); - } - } - ctx.alloc_reg(); - ctx.count_call_instr_with_arg_ext(); // SUPER_CALL + arg_count ext word - return; - } - match &call.callee { - Expression::PrivateFieldExpression(member) => { - self.count_expression(&member.object, ctx); - ctx.count_private_access(); - } - Expression::StaticMemberExpression(member) => { - if matches!(&member.object, Expression::Super(_)) { - ctx.count_load_var(); // this register - ctx.count_load_const(); // key - ctx.alloc_reg(); // callee - ctx.count_instr(); // SUPER_GET_PROP - } else { - self.count_expression(&member.object, ctx); - ctx.count_load_const(); // key - ctx.count_load_var(); // callee object copy - ctx.count_ic_instr_with_ext(); // IC_GET_PROP + 3 ext words - } - } - _ => { - self.count_expression(&call.callee, ctx); - ctx.alloc_reg(); - ctx.projected_pc += 1; - } - } - for arg in &call.arguments { - if let Some(expr) = arg.as_expression() { - self.count_expression(expr, ctx); - } - } - ctx.count_call_instr_with_arg_ext(); // CALL/CALL_NATIVE + arg_count ext word - ctx.count_load_var(); // result <- regs[0] - } - Expression::AssignmentExpression(assign) => { - if let oxide_parser::AssignmentTarget::StaticMemberExpression(member) = &assign.left { - if let Some(logical_op) = assign.operator.to_logical_operator() { - let id = ctx.next_label_id(); - self.count_expression(&member.object, ctx); - ctx.count_load_const(); // key - ctx.count_load_var(); // current value copy - ctx.count_ic_instr_with_ext(); // IC_GET_PROP + 3 ext - self.count_logical_assign_test(logical_op, id, ctx); - self.count_expression(&assign.right, ctx); - ctx.count_ic_set_with_ext(); // IC_SET_PROP + 3 ext - ctx.projected_pc += 1; // LOAD_VAR result <- rhs - ctx.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc); - return; - } - self.count_expression(&member.object, ctx); - self.count_expression(&assign.right, ctx); - if assign.operator != AssignmentOperator::Assign { - ctx.alloc_reg(); - ctx.projected_pc += 1; - } - ctx.count_load_const(); // key - ctx.count_ic_set_with_ext(); // IC_SET_PROP or COMPOUND_MEMBER_* + 3 ext words - } else if let oxide_parser::AssignmentTarget::ComputedMemberExpression(member) = &assign.left { - if let Some(logical_op) = assign.operator.to_logical_operator() { - let id = ctx.next_label_id(); - self.count_expression(&member.object, ctx); - self.count_expression(&member.expression, ctx); - ctx.alloc_reg(); - ctx.projected_pc += 1; // GET_PROP_DYNAMIC - self.count_logical_assign_test(logical_op, id, ctx); - self.count_expression(&assign.right, ctx); - ctx.projected_pc += 1; // SET_PROP_DYNAMIC - ctx.projected_pc += 1; // LOAD_VAR result <- rhs - ctx.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc); - return; - } - self.count_expression(&member.object, ctx); - self.count_expression(&member.expression, ctx); - self.count_expression(&assign.right, ctx); - ctx.alloc_reg(); - ctx.projected_pc += 1; // SET_PROP_DYNAMIC - } else if let oxide_parser::AssignmentTarget::PrivateFieldExpression(member) = &assign.left { - self.count_expression(&member.object, ctx); - self.count_expression(&assign.right, ctx); - ctx.count_load_const(); // private id - ctx.count_instr(); // SET_PRIVATE - } else if let oxide_parser::AssignmentTarget::AssignmentTargetIdentifier(_) = &assign.left { - if let Some(logical_op) = assign.operator.to_logical_operator() { - let id = ctx.next_label_id(); - ctx.alloc_reg(); - ctx.projected_pc += 1; // LOAD_VAR result <- current var - self.count_logical_assign_test(logical_op, id, ctx); - self.count_expression(&assign.right, ctx); - ctx.projected_pc += 1; // STORE_VAR - ctx.projected_pc += 1; // LOAD_VAR result <- rhs - ctx.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc); - } else { - self.count_expression(&assign.right, ctx); - if assign.operator != AssignmentOperator::Assign { - ctx.projected_pc += 1; // COMPOUND_* on var - } else { - ctx.alloc_reg(); - ctx.projected_pc += 1; // STORE_VAR - } - } - } else if let oxide_parser::AssignmentTarget::ArrayAssignmentTarget(ap) = &assign.left { - self.count_expression(&assign.right, ctx); - self.count_array_assignment(ap, ctx); - } else if let oxide_parser::AssignmentTarget::ObjectAssignmentTarget(op) = &assign.left { - self.count_expression(&assign.right, ctx); - self.count_object_assignment(op, ctx); - } else { - self.count_expression(&assign.right, ctx); - ctx.alloc_reg(); - ctx.projected_pc += 1; - } - } - Expression::ConditionalExpression(cond) => { - let id = ctx.next_label_id(); - let else_label = Label::TernaryElse(id); - let end_label = Label::TernaryEnd(id); - - self.count_expression(&cond.test, ctx); - ctx.projected_pc += 1; // JMP_IF_FALSE - self.count_expression(&cond.consequent, ctx); - ctx.alloc_reg(); // result register - ctx.projected_pc += 1; // LOAD_VAR result <- consequent - ctx.projected_pc += 1; // JMP to end - ctx.label_map.insert(else_label, ctx.projected_pc); - self.count_expression(&cond.alternate, ctx); - ctx.projected_pc += 1; // LOAD_VAR result <- alternate - ctx.label_map.insert(end_label, ctx.projected_pc); - } - Expression::SequenceExpression(seq) => { - for e in &seq.expressions { - self.count_expression(e, ctx); - } - } - Expression::LogicalExpression(log) => { - self.count_expression(&log.left, ctx); - - let is_simple = is_side_effect_free(&log.left) && is_side_effect_free(&log.right); - - if is_simple { - self.count_expression(&log.right, ctx); - ctx.alloc_reg(); - ctx.projected_pc += 1; // AND/OR - } else { - use oxide_parser::LogicalOperator; - let id = ctx.next_label_id(); - ctx.alloc_reg(); // dup register - ctx.projected_pc += 1; // LOAD_VAR (DUP) - ctx.projected_pc += 1; // JMP_IF_FALSE, JMP_IF_TRUE, or JMP_IF_NULLISH - if matches!(log.operator, LogicalOperator::Coalesce) { - ctx.projected_pc += 1; // JMP over RHS on non-nullish - ctx.label_map.insert(Label::TernaryElse(id), ctx.projected_pc); - } - self.count_expression(&log.right, ctx); - ctx.projected_pc += 1; // LOAD_VAR (overwrite) - let skip_label = match log.operator { - LogicalOperator::And => Label::TernaryEnd(id), - LogicalOperator::Or => Label::TernaryElse(id), - LogicalOperator::Coalesce => Label::TernaryEnd(id), - }; - ctx.label_map.insert(skip_label, ctx.projected_pc); - } - } - Expression::ChainExpression(chain) => { - let id = ctx.next_label_id(); - self.count_chain_element(&chain.expression, true, ctx); - ctx.alloc_reg(); - ctx.projected_pc += 1; // LOAD_VAR result <- chain value - ctx.projected_pc += 1; // JMP over short-circuit writer - ctx.label_map.insert(Label::TernaryElse(id), ctx.projected_pc); - ctx.projected_pc += 1; // LOAD_CONST undefined - ctx.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc); - } - Expression::ObjectExpression(obj) => { - ctx.alloc_reg(); - ctx.projected_pc += 1; // NEW_OBJECT - let prop_checkpoint = ctx.reg_checkpoint(); - for prop in &obj.properties { - if let oxide_parser::ObjectPropertyKind::ObjectProperty(p) = prop { - if matches!(p.kind, oxide_parser::PropertyKind::Get | oxide_parser::PropertyKind::Set) { - self.count_expression(&p.value, ctx); - ctx.count_load_const(); // undefined getter/setter placeholder - ctx.count_define_accessor(); // DEFINE_ACCESSOR + ext - } else { - ctx.alloc_reg(); - ctx.projected_pc += 1; // LOAD_CONST key - self.count_expression(&p.value, ctx); - ctx.projected_pc += 1; // SET_PROP - } - ctx.restore_reg_checkpoint(prop_checkpoint); - } - } - } - Expression::TemplateLiteral(tl) => { - // TemplateLiteral: N quasis (strings) + M expressions interleaved. - // Allocate 1 result reg + count expressions. - for expr in &tl.expressions { - self.count_expression(expr, ctx); - } - let segment_count = tl.quasis.len() + tl.expressions.len(); - ctx.count_template_str(segment_count); - } - Expression::TaggedTemplateExpression(tt) => { - // Tagged template: tag expr + quasis as LOAD_CONST + expression args + CALL - // Consecutive arg registers at end (via LOAD_VAR copies) - self.count_expression(&tt.tag, ctx); - let quasi_count = tt.quasi.quasis.len(); - // Each quasi: LOAD_CONST string + LOAD_CONST index + SET_ELEM - // for both cooked and raw arrays - for _ in 0..quasi_count { - ctx.count_load_const(); // string - ctx.count_load_const(); // index - ctx.count_instr(); // SET_ELEM (cooked) - } - // Cooked array: alloc + NEW_ARRAY - ctx.alloc_reg(); // cooked_temp - ctx.count_instr(); // NEW_ARRAY - - for _ in 0..quasi_count { - ctx.count_load_const(); // string - ctx.count_load_const(); // index - ctx.count_instr(); // SET_ELEM (raw) - } - // Raw array: alloc + NEW_ARRAY - ctx.alloc_reg(); // raw_temp - ctx.count_instr(); // NEW_ARRAY - - // Count expression arguments - for expr in &tt.quasi.expressions { - self.count_expression(expr, ctx); - } - - // Consecutive arg slots: cooked_slot, raw_slot, N expr_slots - ctx.alloc_reg(); // cooked_slot - ctx.alloc_reg(); // raw_slot - for _ in &tt.quasi.expressions { - ctx.alloc_reg(); // expr_slot - ctx.count_instr(); // LOAD_VAR - } - // LOAD_VAR for cooked and raw - ctx.count_words(2); - - // undefined this arg - ctx.count_load_const(); - - // CALL + ext word - ctx.count_call_instr_with_arg_ext(); - - // Result reg + LOAD_VAR - ctx.count_load_var(); - } - Expression::ArrowFunctionExpression(_arrow) => { - // Arrow functions: alloc 1 register for the function value. - // Body is compiled in the emit pass only (same pattern as FE). - ctx.alloc_reg(); - ctx.projected_pc += 1; // LOAD_CONST - } - Expression::FunctionExpression(_fe) => { - // No hoisting - function created at expression position. - // Body is compiled in the emit pass only. - ctx.alloc_reg(); - ctx.projected_pc += 1; // LOAD_CONST - } - Expression::ClassExpression(class) => { - self.count_class(class, ctx); - } - Expression::NewExpression(ne) => { - // Count callee expression - self.count_expression(&ne.callee, ctx); - // Count arguments - for arg in &ne.arguments { - if let Some(expr) = arg.as_expression() { - self.count_expression(expr, ctx); - } - } - ctx.alloc_reg(); // result register - ctx.count_instr_with_ext(1); // NEW_EXPRESSION + arg_count ext word - } - Expression::ArrayExpression(arr) => { - ctx.alloc_reg(); - ctx.projected_pc += 1; // NEW_ARRAY - let elem_checkpoint = ctx.reg_checkpoint(); - for elem in &arr.elements { - if let Some(e) = elem.as_expression() { - self.count_expression(e, ctx); - ctx.alloc_reg(); - ctx.projected_pc += 1; // LOAD_CONST index - ctx.projected_pc += 1; // SET_ELEM - ctx.restore_reg_checkpoint(elem_checkpoint); - } - } - } - Expression::StaticMemberExpression(member) => { - if matches!(&member.object, Expression::Super(_)) { - ctx.count_load_const(); // key - ctx.count_load_var(); // this - ctx.alloc_reg(); // result - ctx.count_instr(); // SUPER_GET_PROP - return; - } - self.count_expression(&member.object, ctx); - ctx.count_load_const(); // key - ctx.count_ic_instr_with_ext(); // IC_GET_PROP + 3 ext words - } - Expression::ComputedMemberExpression(member) => { - self.count_expression(&member.object, ctx); - self.count_expression(&member.expression, ctx); - ctx.alloc_reg(); - ctx.projected_pc += 1; // GET_PROP_DYNAMIC - } - Expression::PrivateFieldExpression(member) => { - self.count_expression(&member.object, ctx); - ctx.count_private_access(); - } - Expression::ParenthesizedExpression(p) => { - self.count_expression(&p.expression, ctx); - } - Expression::ThisExpression(_) => { - ctx.alloc_reg(); - ctx.projected_pc += 1; // LOAD_VAR from reg 254 - } - Expression::Identifier(ident) => { - if CompileCtx::is_known_builtin(ident.name.as_str()) { - let _ = ctx.lookup_or_builtin(ident.name.as_str()); - } - ctx.alloc_reg(); - ctx.projected_pc += 1; // LOAD_VAR - } - Expression::UpdateExpression(update) => match &update.argument { - SimpleAssignmentTarget::AssignmentTargetIdentifier(_) => { - ctx.alloc_reg(); - ctx.projected_pc += 1; - } - SimpleAssignmentTarget::StaticMemberExpression(member) => { - self.count_expression(&member.object, ctx); - ctx.count_load_const(); // key - ctx.alloc_reg(); - ctx.count_ic_instr_with_ext(); // MEMBER_INC/MEMBER_DEC + 3 ext words - } - SimpleAssignmentTarget::ComputedMemberExpression(member) => { - self.count_expression(&member.object, ctx); - self.count_expression(&member.expression, ctx); - ctx.alloc_reg(); - ctx.projected_pc += 1; - } - _ => { - ctx.alloc_reg(); - ctx.projected_pc += 1; - } - }, - Expression::RegExpLiteral(_) => { - ctx.alloc_reg(); - ctx.projected_pc += 1; - } - _ => { - ctx.alloc_reg(); - ctx.projected_pc += 1; // LOAD_CONST or LOAD_VAR - } - } - } -} diff --git a/crates/oxide_compiler/src/counter/expr/mod.rs b/crates/oxide_compiler/src/counter/expr/mod.rs new file mode 100644 index 00000000..82aa3dcd --- /dev/null +++ b/crates/oxide_compiler/src/counter/expr/mod.rs @@ -0,0 +1,434 @@ +use super::*; + +impl Compiler { + pub(crate) fn count_expression(&self, expr: &Expression, ctx: &mut CompileCtx) { + match expr { + Expression::BinaryExpression(bin) => { + let checkpoint = ctx.reg_checkpoint(); + self.count_expression(&bin.left, ctx); + self.count_expression(&bin.right, ctx); + ctx.projected_pc += 1; // ADD/SUB/MUL/DIV/etc. + if is_side_effect_free(&bin.left) && is_side_effect_free(&bin.right) { + ctx.restore_reg_checkpoint(checkpoint.saturating_add(1)); + } + } + Expression::PrivateInExpression(pin) => { + self.count_expression(&pin.right, ctx); + ctx.count_private_access(); + } + Expression::UnaryExpression(un) => { + match un.operator { + UnaryOperator::Delete => { + match &un.argument { + Expression::Identifier(_) => { + // SyntaxError at compile time, no bytecode cost + } + Expression::StaticMemberExpression(member) => { + self.count_expression(&member.object, ctx); + ctx.count_delete_static(); + } + Expression::ComputedMemberExpression(member) => { + self.count_expression(&member.object, ctx); + self.count_expression(&member.expression, ctx); + ctx.projected_pc += 1; // DELETE_PROP_DYNAMIC + } + Expression::ChainExpression(chain) => { + match &chain.expression { + ChainElement::StaticMemberExpression(member) => { + self.count_expression(&member.object, ctx); + if member.optional { + self.count_optional_guard(ctx); + } + ctx.count_delete_static(); + } + ChainElement::ComputedMemberExpression(member) => { + self.count_expression(&member.object, ctx); + if member.optional { + self.count_optional_guard(ctx); + } + self.count_expression(&member.expression, ctx); + ctx.projected_pc += 1; // DELETE_PROP_DYNAMIC + } + _ => {} + } + ctx.projected_pc += 1; // JMP over nullish true writer + ctx.projected_pc += 1; // LOAD_CONST true + } + _ => {} + } + } + _ => { + self.count_expression(&un.argument, ctx); + ctx.projected_pc += 1; // NEG/TYPEOF/VOID/NOT + } + } + } + Expression::CallExpression(call) => { + if matches!(&call.callee, Expression::Super(_)) { + for arg in &call.arguments { + if let Some(expr) = arg.as_expression() { + self.count_expression(expr, ctx); + } + } + ctx.alloc_reg(); + ctx.count_call_instr_with_arg_ext(); // SUPER_CALL + arg_count ext word + return; + } + match &call.callee { + Expression::PrivateFieldExpression(member) => { + self.count_expression(&member.object, ctx); + ctx.count_private_access(); + } + Expression::StaticMemberExpression(member) => { + if matches!(&member.object, Expression::Super(_)) { + ctx.count_load_var(); // this register + ctx.count_load_const(); // key + ctx.alloc_reg(); // callee + ctx.count_instr(); // SUPER_GET_PROP + } else { + self.count_expression(&member.object, ctx); + ctx.count_load_const(); // key + ctx.count_load_var(); // callee object copy + ctx.count_ic_instr_with_ext(); // IC_GET_PROP + 3 ext words + } + } + _ => { + self.count_expression(&call.callee, ctx); + ctx.alloc_reg(); + ctx.projected_pc += 1; + } + } + for arg in &call.arguments { + if let Some(expr) = arg.as_expression() { + self.count_expression(expr, ctx); + } + } + ctx.count_call_instr_with_arg_ext(); // CALL/CALL_NATIVE + arg_count ext word + ctx.count_load_var(); // result <- regs[0] + } + Expression::AssignmentExpression(assign) => { + if let oxide_parser::AssignmentTarget::StaticMemberExpression(member) = &assign.left { + if let Some(logical_op) = assign.operator.to_logical_operator() { + let id = ctx.next_label_id(); + self.count_expression(&member.object, ctx); + ctx.count_load_const(); // key + ctx.count_load_var(); // current value copy + ctx.count_ic_instr_with_ext(); // IC_GET_PROP + 3 ext + self.count_logical_assign_test(logical_op, id, ctx); + self.count_expression(&assign.right, ctx); + ctx.count_ic_set_with_ext(); // IC_SET_PROP + 3 ext + ctx.projected_pc += 1; // LOAD_VAR result <- rhs + ctx.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc); + return; + } + self.count_expression(&member.object, ctx); + self.count_expression(&assign.right, ctx); + if assign.operator != AssignmentOperator::Assign { + ctx.alloc_reg(); + ctx.projected_pc += 1; + } + ctx.count_load_const(); // key + ctx.count_ic_set_with_ext(); // IC_SET_PROP or COMPOUND_MEMBER_* + 3 ext words + } else if let oxide_parser::AssignmentTarget::ComputedMemberExpression(member) = &assign.left { + if let Some(logical_op) = assign.operator.to_logical_operator() { + let id = ctx.next_label_id(); + self.count_expression(&member.object, ctx); + self.count_expression(&member.expression, ctx); + ctx.alloc_reg(); + ctx.projected_pc += 1; // GET_PROP_DYNAMIC + self.count_logical_assign_test(logical_op, id, ctx); + self.count_expression(&assign.right, ctx); + ctx.projected_pc += 1; // SET_PROP_DYNAMIC + ctx.projected_pc += 1; // LOAD_VAR result <- rhs + ctx.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc); + return; + } + self.count_expression(&member.object, ctx); + self.count_expression(&member.expression, ctx); + self.count_expression(&assign.right, ctx); + ctx.alloc_reg(); + ctx.projected_pc += 1; // SET_PROP_DYNAMIC + } else if let oxide_parser::AssignmentTarget::PrivateFieldExpression(member) = &assign.left { + self.count_expression(&member.object, ctx); + self.count_expression(&assign.right, ctx); + ctx.count_load_const(); // private id + ctx.count_instr(); // SET_PRIVATE + } else if let oxide_parser::AssignmentTarget::AssignmentTargetIdentifier(_) = &assign.left { + if let Some(logical_op) = assign.operator.to_logical_operator() { + let id = ctx.next_label_id(); + ctx.alloc_reg(); + ctx.projected_pc += 1; // LOAD_VAR result <- current var + self.count_logical_assign_test(logical_op, id, ctx); + self.count_expression(&assign.right, ctx); + ctx.projected_pc += 1; // STORE_VAR + ctx.projected_pc += 1; // LOAD_VAR result <- rhs + ctx.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc); + } else { + self.count_expression(&assign.right, ctx); + if assign.operator != AssignmentOperator::Assign { + ctx.projected_pc += 1; // COMPOUND_* on var + } else { + ctx.alloc_reg(); + ctx.projected_pc += 1; // STORE_VAR + } + } + } else if let oxide_parser::AssignmentTarget::ArrayAssignmentTarget(ap) = &assign.left { + self.count_expression(&assign.right, ctx); + self.count_array_assignment(ap, ctx); + } else if let oxide_parser::AssignmentTarget::ObjectAssignmentTarget(op) = &assign.left { + self.count_expression(&assign.right, ctx); + self.count_object_assignment(op, ctx); + } else { + self.count_expression(&assign.right, ctx); + ctx.alloc_reg(); + ctx.projected_pc += 1; + } + } + Expression::ConditionalExpression(cond) => { + let id = ctx.next_label_id(); + let else_label = Label::TernaryElse(id); + let end_label = Label::TernaryEnd(id); + + self.count_expression(&cond.test, ctx); + ctx.projected_pc += 1; // JMP_IF_FALSE + self.count_expression(&cond.consequent, ctx); + ctx.alloc_reg(); // result register + ctx.projected_pc += 1; // LOAD_VAR result <- consequent + ctx.projected_pc += 1; // JMP to end + ctx.label_map.insert(else_label, ctx.projected_pc); + self.count_expression(&cond.alternate, ctx); + ctx.projected_pc += 1; // LOAD_VAR result <- alternate + ctx.label_map.insert(end_label, ctx.projected_pc); + } + Expression::SequenceExpression(seq) => { + for e in &seq.expressions { + self.count_expression(e, ctx); + } + } + Expression::LogicalExpression(log) => { + self.count_expression(&log.left, ctx); + + let is_simple = is_side_effect_free(&log.left) && is_side_effect_free(&log.right); + + if is_simple { + self.count_expression(&log.right, ctx); + ctx.alloc_reg(); + ctx.projected_pc += 1; // AND/OR + } else { + use oxide_parser::LogicalOperator; + let id = ctx.next_label_id(); + ctx.alloc_reg(); // dup register + ctx.projected_pc += 1; // LOAD_VAR (DUP) + ctx.projected_pc += 1; // JMP_IF_FALSE, JMP_IF_TRUE, or JMP_IF_NULLISH + if matches!(log.operator, LogicalOperator::Coalesce) { + ctx.projected_pc += 1; // JMP over RHS on non-nullish + ctx.label_map.insert(Label::TernaryElse(id), ctx.projected_pc); + } + self.count_expression(&log.right, ctx); + ctx.projected_pc += 1; // LOAD_VAR (overwrite) + let skip_label = match log.operator { + LogicalOperator::And => Label::TernaryEnd(id), + LogicalOperator::Or => Label::TernaryElse(id), + LogicalOperator::Coalesce => Label::TernaryEnd(id), + }; + ctx.label_map.insert(skip_label, ctx.projected_pc); + } + } + Expression::ChainExpression(chain) => { + let id = ctx.next_label_id(); + self.count_chain_element(&chain.expression, true, ctx); + ctx.alloc_reg(); + ctx.projected_pc += 1; // LOAD_VAR result <- chain value + ctx.projected_pc += 1; // JMP over short-circuit writer + ctx.label_map.insert(Label::TernaryElse(id), ctx.projected_pc); + ctx.projected_pc += 1; // LOAD_CONST undefined + ctx.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc); + } + Expression::ObjectExpression(obj) => { + ctx.alloc_reg(); + ctx.projected_pc += 1; // NEW_OBJECT + let prop_checkpoint = ctx.reg_checkpoint(); + for prop in &obj.properties { + if let oxide_parser::ObjectPropertyKind::ObjectProperty(p) = prop { + if matches!(p.kind, oxide_parser::PropertyKind::Get | oxide_parser::PropertyKind::Set) { + self.count_expression(&p.value, ctx); + ctx.count_load_const(); // undefined getter/setter placeholder + ctx.count_define_accessor(); // DEFINE_ACCESSOR + ext + } else { + ctx.alloc_reg(); + ctx.projected_pc += 1; // LOAD_CONST key + self.count_expression(&p.value, ctx); + ctx.projected_pc += 1; // SET_PROP + } + ctx.restore_reg_checkpoint(prop_checkpoint); + } + } + } + Expression::TemplateLiteral(tl) => { + // TemplateLiteral: N quasis (strings) + M expressions interleaved. + // Allocate 1 result reg + count expressions. + for expr in &tl.expressions { + self.count_expression(expr, ctx); + } + let segment_count = tl.quasis.len() + tl.expressions.len(); + ctx.count_template_str(segment_count); + } + Expression::TaggedTemplateExpression(tt) => { + // Tagged template: tag expr + quasis as LOAD_CONST + expression args + CALL + // Consecutive arg registers at end (via LOAD_VAR copies) + self.count_expression(&tt.tag, ctx); + let quasi_count = tt.quasi.quasis.len(); + // Each quasi: LOAD_CONST string + LOAD_CONST index + SET_ELEM + // for both cooked and raw arrays + for _ in 0..quasi_count { + ctx.count_load_const(); // string + ctx.count_load_const(); // index + ctx.count_instr(); // SET_ELEM (cooked) + } + // Cooked array: alloc + NEW_ARRAY + ctx.alloc_reg(); // cooked_temp + ctx.count_instr(); // NEW_ARRAY + + for _ in 0..quasi_count { + ctx.count_load_const(); // string + ctx.count_load_const(); // index + ctx.count_instr(); // SET_ELEM (raw) + } + // Raw array: alloc + NEW_ARRAY + ctx.alloc_reg(); // raw_temp + ctx.count_instr(); // NEW_ARRAY + + // Count expression arguments + for expr in &tt.quasi.expressions { + self.count_expression(expr, ctx); + } + + // Consecutive arg slots: cooked_slot, raw_slot, N expr_slots + ctx.alloc_reg(); // cooked_slot + ctx.alloc_reg(); // raw_slot + for _ in &tt.quasi.expressions { + ctx.alloc_reg(); // expr_slot + ctx.count_instr(); // LOAD_VAR + } + // LOAD_VAR for cooked and raw + ctx.count_words(2); + + // undefined this arg + ctx.count_load_const(); + + // CALL + ext word + ctx.count_call_instr_with_arg_ext(); + + // Result reg + LOAD_VAR + ctx.count_load_var(); + } + Expression::ArrowFunctionExpression(_arrow) => { + // Arrow functions: alloc 1 register for the function value. + // Body is compiled in the emit pass only (same pattern as FE). + ctx.alloc_reg(); + ctx.projected_pc += 1; // LOAD_CONST + } + Expression::FunctionExpression(_fe) => { + // No hoisting - function created at expression position. + // Body is compiled in the emit pass only. + ctx.alloc_reg(); + ctx.projected_pc += 1; // LOAD_CONST + } + Expression::ClassExpression(class) => { + self.count_class(class, ctx); + } + Expression::NewExpression(ne) => { + // Count callee expression + self.count_expression(&ne.callee, ctx); + // Count arguments + for arg in &ne.arguments { + if let Some(expr) = arg.as_expression() { + self.count_expression(expr, ctx); + } + } + ctx.alloc_reg(); // result register + ctx.count_instr_with_ext(1); // NEW_EXPRESSION + arg_count ext word + } + Expression::ArrayExpression(arr) => { + ctx.alloc_reg(); + ctx.projected_pc += 1; // NEW_ARRAY + let elem_checkpoint = ctx.reg_checkpoint(); + for elem in &arr.elements { + if let Some(e) = elem.as_expression() { + self.count_expression(e, ctx); + ctx.alloc_reg(); + ctx.projected_pc += 1; // LOAD_CONST index + ctx.projected_pc += 1; // SET_ELEM + ctx.restore_reg_checkpoint(elem_checkpoint); + } + } + } + Expression::StaticMemberExpression(member) => { + if matches!(&member.object, Expression::Super(_)) { + ctx.count_load_const(); // key + ctx.count_load_var(); // this + ctx.alloc_reg(); // result + ctx.count_instr(); // SUPER_GET_PROP + return; + } + self.count_expression(&member.object, ctx); + ctx.count_load_const(); // key + ctx.count_ic_instr_with_ext(); // IC_GET_PROP + 3 ext words + } + Expression::ComputedMemberExpression(member) => { + self.count_expression(&member.object, ctx); + self.count_expression(&member.expression, ctx); + ctx.alloc_reg(); + ctx.projected_pc += 1; // GET_PROP_DYNAMIC + } + Expression::PrivateFieldExpression(member) => { + self.count_expression(&member.object, ctx); + ctx.count_private_access(); + } + Expression::ParenthesizedExpression(p) => { + self.count_expression(&p.expression, ctx); + } + Expression::ThisExpression(_) => { + ctx.alloc_reg(); + ctx.projected_pc += 1; // LOAD_VAR from reg 254 + } + Expression::Identifier(ident) => { + if CompileCtx::is_known_builtin(ident.name.as_str()) { + let _ = ctx.lookup_or_builtin(ident.name.as_str()); + } + ctx.alloc_reg(); + ctx.projected_pc += 1; // LOAD_VAR + } + Expression::UpdateExpression(update) => match &update.argument { + SimpleAssignmentTarget::AssignmentTargetIdentifier(_) => { + ctx.alloc_reg(); + ctx.projected_pc += 1; + } + SimpleAssignmentTarget::StaticMemberExpression(member) => { + self.count_expression(&member.object, ctx); + ctx.count_load_const(); // key + ctx.alloc_reg(); + ctx.count_ic_instr_with_ext(); // MEMBER_INC/MEMBER_DEC + 3 ext words + } + SimpleAssignmentTarget::ComputedMemberExpression(member) => { + self.count_expression(&member.object, ctx); + self.count_expression(&member.expression, ctx); + ctx.alloc_reg(); + ctx.projected_pc += 1; + } + _ => { + ctx.alloc_reg(); + ctx.projected_pc += 1; + } + }, + Expression::RegExpLiteral(_) => { + ctx.alloc_reg(); + ctx.projected_pc += 1; + } + _ => { + ctx.alloc_reg(); + ctx.projected_pc += 1; // LOAD_CONST or LOAD_VAR + } + } + } +} diff --git a/crates/oxide_compiler/src/counter/helper/binding.rs b/crates/oxide_compiler/src/counter/helper/binding.rs new file mode 100644 index 00000000..9ac83567 --- /dev/null +++ b/crates/oxide_compiler/src/counter/helper/binding.rs @@ -0,0 +1,158 @@ +use super::super::*; + +impl Compiler { + pub(in crate::counter) fn count_binding_pattern( + &self, pattern: &oxide_parser::BindingPattern, ctx: &mut CompileCtx, + ) { + match pattern { + oxide_parser::BindingPattern::BindingIdentifier(_) => { + ctx.alloc_reg(); + ctx.projected_pc += 1; + } + oxide_parser::BindingPattern::ArrayPattern(ap) => { + ctx.count_instr(); // FOR_OF_INIT + for elem in &ap.elements { + ctx.alloc_reg(); + ctx.count_instr(); // FOR_OF_DONE + ctx.alloc_reg(); + ctx.count_instr(); // FOR_OF_NEXT + if let Some(inner) = elem { + self.count_binding_pattern(inner, ctx); + } + } + if let Some(rest) = &ap.rest { + self.count_rest_array(ctx); + self.count_binding_pattern(&rest.argument, ctx); + } + ctx.count_instr(); // FOR_OF_CLOSE + } + oxide_parser::BindingPattern::ObjectPattern(op) => { + for prop in &op.properties { + self.count_object_property_read_key(&prop.key, prop.computed, ctx); + self.count_binding_pattern(&prop.value, ctx); + } + if let Some(rest) = &op.rest { + ctx.alloc_reg(); + ctx.count_instr_with_ext(1); // REST_OBJECT + excluded-keys ext + self.count_binding_pattern(&rest.argument, ctx); + } + } + oxide_parser::BindingPattern::AssignmentPattern(ap) => { + ctx.count_load_const(); // undefined + ctx.count_instr(); // STRICT_EQ + ctx.count_jump(); // JMP_IF_FALSE + self.count_expression(&ap.right, ctx); + ctx.count_instr(); // LOAD_VAR default + self.count_binding_pattern(&ap.left, ctx); + } + } + } + + pub(in crate::counter) fn count_rest_array(&self, ctx: &mut CompileCtx) { + ctx.alloc_reg(); // rest + ctx.count_instr(); // NEW_ARRAY + ctx.count_load_const(); // idx = 0 + ctx.alloc_reg(); // has + ctx.count_instr(); // FOR_OF_DONE + ctx.count_jump(); // JMP_IF_FALSE + ctx.alloc_reg(); // val + ctx.count_instr(); // FOR_OF_NEXT + ctx.count_instr(); // SET_ELEM + ctx.alloc_reg(); // inc tmp + ctx.count_instr(); // INC_PRE + ctx.count_jump(); // JMP + } + + pub(in crate::counter) fn count_array_assignment( + &self, ap: &oxide_parser::ArrayAssignmentTarget, ctx: &mut CompileCtx, + ) { + ctx.count_instr(); // FOR_OF_INIT + for elem in &ap.elements { + ctx.alloc_reg(); + ctx.count_instr(); // FOR_OF_DONE + ctx.alloc_reg(); + ctx.count_instr(); // FOR_OF_NEXT + if elem.is_some() { + ctx.alloc_reg(); + ctx.count_instr(); // STORE_VAR + } + } + if ap.rest.is_some() { + self.count_rest_array(ctx); + ctx.alloc_reg(); + ctx.count_instr(); // STORE_VAR + } + ctx.count_instr(); // FOR_OF_CLOSE + } + + pub(in crate::counter) fn count_object_assignment( + &self, op: &oxide_parser::ObjectAssignmentTarget, ctx: &mut CompileCtx, + ) { + for prop in &op.properties { + match prop { + oxide_parser::AssignmentTargetProperty::AssignmentTargetPropertyIdentifier(id) => { + self.count_object_property_read_static(ctx); + if let Some(default_expr) = &id.init { + ctx.count_load_const(); // undefined + ctx.count_instr(); // STRICT_EQ + ctx.count_jump(); // JMP_IF_FALSE + self.count_expression(default_expr, ctx); + ctx.count_instr(); // LOAD_VAR default + } + ctx.alloc_reg(); + ctx.count_instr(); // STORE_VAR + } + oxide_parser::AssignmentTargetProperty::AssignmentTargetPropertyProperty(prop) => { + self.count_object_property_read_key(&prop.name, prop.computed, ctx); + self.count_assignment_maybe_default(&prop.binding, ctx); + } + } + } + if op.rest.is_some() { + ctx.alloc_reg(); + ctx.count_instr_with_ext(1); // REST_OBJECT + excluded-keys ext + ctx.alloc_reg(); + ctx.count_instr(); // STORE_VAR + } + } + + pub(in crate::counter) fn count_assignment_maybe_default( + &self, target: &oxide_parser::AssignmentTargetMaybeDefault, ctx: &mut CompileCtx, + ) { + match target { + oxide_parser::AssignmentTargetMaybeDefault::AssignmentTargetWithDefault(default) => { + ctx.count_load_const(); // undefined + ctx.count_instr(); // STRICT_EQ + ctx.count_jump(); // JMP_IF_FALSE + self.count_expression(&default.init, ctx); + ctx.count_instr(); // LOAD_VAR default + self.count_assign_target(&default.binding, ctx); + } + oxide_parser::AssignmentTargetMaybeDefault::ArrayAssignmentTarget(ap) => { + self.count_array_assignment(ap, ctx) + } + oxide_parser::AssignmentTargetMaybeDefault::ObjectAssignmentTarget(op) => { + self.count_object_assignment(op, ctx) + } + oxide_parser::AssignmentTargetMaybeDefault::AssignmentTargetIdentifier(_) => { + ctx.alloc_reg(); + ctx.count_instr(); // STORE_VAR + } + _ => {} + } + } + + pub(in crate::counter) fn count_assign_target( + &self, target: &oxide_parser::AssignmentTarget, ctx: &mut CompileCtx, + ) { + match target { + oxide_parser::AssignmentTarget::AssignmentTargetIdentifier(_) => { + ctx.alloc_reg(); + ctx.count_instr(); // STORE_VAR + } + oxide_parser::AssignmentTarget::ArrayAssignmentTarget(ap) => self.count_array_assignment(ap, ctx), + oxide_parser::AssignmentTarget::ObjectAssignmentTarget(op) => self.count_object_assignment(op, ctx), + _ => {} + } + } +} diff --git a/crates/oxide_compiler/src/counter/helper/class.rs b/crates/oxide_compiler/src/counter/helper/class.rs new file mode 100644 index 00000000..bb121365 --- /dev/null +++ b/crates/oxide_compiler/src/counter/helper/class.rs @@ -0,0 +1,80 @@ +use super::super::*; + +impl Compiler { + pub(in crate::counter) fn count_class(&self, class: &oxide_parser::Class, ctx: &mut CompileCtx) { + ctx.alloc_reg(); // ctor_reg + ctx.alloc_reg(); // proto_reg + if let Some(super_class) = &class.super_class { + self.count_expression(super_class, ctx); + } + ctx.count_instr(); // LOAD_CONST ctor + ctx.count_instr(); // NEW_OBJECT proto + + if class.super_class.is_some() { + ctx.alloc_reg(); // parent prototype key + ctx.count_instr(); // LOAD_CONST + ctx.alloc_reg(); // parent prototype value + ctx.count_instr(); // GET_PROP + ctx.alloc_reg(); // __proto__ key + ctx.count_instr(); // LOAD_CONST + ctx.count_instr(); // SET_PROP proto.__proto__ + ctx.count_instr(); // SET_PROP ctor.__proto__ + } + + ctx.count_load_const(); // constructor key + ctx.count_instr(); // SET_PROP proto.constructor = ctor + + ctx.count_load_const(); // prototype key + ctx.count_instr(); // SET_PROP ctor.prototype = proto + + for element in &class.body.body { + match element { + ClassElement::MethodDefinition(method) => { + let method = method.as_ref(); + if matches!(method.kind, MethodDefinitionKind::Constructor) { + continue; + } + if matches!(method.key, oxide_parser::PropertyKey::PrivateIdentifier(_)) { + if method.r#static { + ctx.count_load_const(); // private id + ctx.count_load_const(); // method + ctx.count_instr(); // INIT_PRIVATE + } + continue; + } + ctx.count_load_const(); // key_reg + ctx.count_load_const(); // method_reg + ctx.count_instr(); // SET_HOME_OBJECT + match method.kind { + MethodDefinitionKind::Method => { + ctx.count_instr(); // SET_PROP + } + MethodDefinitionKind::Get | MethodDefinitionKind::Set => { + ctx.count_load_const(); // undefined placeholder + ctx.count_define_accessor(); // DEFINE_ACCESSOR + ext + } + MethodDefinitionKind::Constructor => {} + } + } + ClassElement::PropertyDefinition(prop) => { + let prop = prop.as_ref(); + if prop.r#static { + ctx.count_load_const(); // key_reg + if let Some(value) = &prop.value { + self.count_expression(value, ctx); + } else { + ctx.count_load_const(); + } + ctx.count_instr(); // SET_PROP + } + } + ClassElement::StaticBlock(block) => { + for stmt in &block.body { + self.count_statement(stmt, ctx); + } + } + ClassElement::AccessorProperty(_) | ClassElement::TSIndexSignature(_) => {} + } + } + } +} diff --git a/crates/oxide_compiler/src/counter/helper/member.rs b/crates/oxide_compiler/src/counter/helper/member.rs new file mode 100644 index 00000000..e36b1517 --- /dev/null +++ b/crates/oxide_compiler/src/counter/helper/member.rs @@ -0,0 +1,173 @@ +use super::super::*; + +impl Compiler { + pub(in crate::counter) fn count_object_property_read_static(&self, ctx: &mut CompileCtx) { + ctx.count_load_var(); // prop/object temp + ctx.count_load_const(); // key reg + ctx.count_ic_instr_with_ext(); // IC_GET_PROP + 3 ext words + } + + pub(in crate::counter) fn count_object_property_read_key( + &self, key: &oxide_parser::PropertyKey, computed: bool, ctx: &mut CompileCtx, + ) { + if !computed { + self.count_object_property_read_static(ctx); + let _ = key; + return; + } + + ctx.count_load_var(); // prop/object temp + match key { + oxide_parser::PropertyKey::Identifier(ident) => { + let name = ident.name.as_str(); + let _ = ctx.lookup_or_builtin(name); + ctx.count_load_var(); // key + } + oxide_parser::PropertyKey::StringLiteral(_) => { + ctx.count_load_const(); // key + } + oxide_parser::PropertyKey::NumericLiteral(_) => { + ctx.count_load_const(); // key + } + oxide_parser::PropertyKey::StaticIdentifier(_) => { + ctx.count_load_const(); // key + } + _ => {} + } + ctx.alloc_reg(); // result reg + ctx.count_instr(); // GET_PROP_DYNAMIC + } + + pub(in crate::counter) fn count_optional_guard(&self, ctx: &mut CompileCtx) { + ctx.count_load_var(); // dup + ctx.count_jump(); // JMP_IF_NULLISH + } + + pub(in crate::counter) fn count_static_member_get_preserve_base( + &self, member: &oxide_parser::StaticMemberExpression, short_chain: bool, ctx: &mut CompileCtx, + ) { + self.count_chainable_expression(&member.object, short_chain, ctx); + if short_chain && member.optional { + self.count_optional_guard(ctx); + } + ctx.count_load_const(); // key + ctx.count_load_var(); // receiver copy + ctx.count_ic_instr_with_ext(); // IC_GET_PROP + 3 ext words + } + + pub(in crate::counter) fn count_computed_member_get_preserve_base( + &self, member: &oxide_parser::ComputedMemberExpression, short_chain: bool, ctx: &mut CompileCtx, + ) { + self.count_chainable_expression(&member.object, short_chain, ctx); + if short_chain && member.optional { + self.count_optional_guard(ctx); + } + self.count_expression(&member.expression, ctx); + ctx.alloc_reg(); + ctx.count_instr(); // GET_PROP_DYNAMIC + } + + pub(in crate::counter) fn count_chain_call( + &self, call: &oxide_parser::CallExpression, short_chain: bool, ctx: &mut CompileCtx, + ) { + match &call.callee { + Expression::StaticMemberExpression(member) => { + self.count_static_member_get_preserve_base(member, short_chain, ctx) + } + Expression::ComputedMemberExpression(member) => { + self.count_computed_member_get_preserve_base(member, short_chain, ctx); + } + Expression::PrivateFieldExpression(member) => { + self.count_chainable_expression(&member.object, short_chain, ctx); + if short_chain && member.optional { + self.count_optional_guard(ctx); + } + ctx.count_private_access(); + } + _ => { + self.count_chainable_expression(&call.callee, short_chain, ctx); + if short_chain && call.optional { + self.count_optional_guard(ctx); + } + ctx.alloc_reg(); + ctx.projected_pc += 1; // LOAD_CONST undefined this + } + } + if short_chain + && call.optional + && matches!( + &call.callee, + Expression::StaticMemberExpression(_) + | Expression::ComputedMemberExpression(_) + | Expression::PrivateFieldExpression(_) + ) + { + self.count_optional_guard(ctx); + } + for arg in &call.arguments { + if let Some(expr) = arg.as_expression() { + self.count_expression(expr, ctx); + } + } + ctx.count_call_instr_with_arg_ext(); // CALL + arg_count ext word + ctx.count_load_var(); // result <- regs[0] + } + + pub(in crate::counter) fn count_chainable_expression( + &self, expr: &Expression, short_chain: bool, ctx: &mut CompileCtx, + ) { + match expr { + Expression::StaticMemberExpression(member) => { + self.count_static_member_get_preserve_base(member, short_chain, ctx) + } + Expression::ComputedMemberExpression(member) => { + self.count_computed_member_get_preserve_base(member, short_chain, ctx) + } + Expression::PrivateFieldExpression(member) => { + self.count_chainable_expression(&member.object, short_chain, ctx); + if short_chain && member.optional { + self.count_optional_guard(ctx); + } + ctx.count_private_access(); + } + Expression::CallExpression(call) => self.count_chain_call(call, short_chain, ctx), + Expression::ChainExpression(chain) => self.count_chain_element(&chain.expression, short_chain, ctx), + _ => self.count_expression(expr, ctx), + } + } + + pub(in crate::counter) fn count_chain_element( + &self, element: &ChainElement, short_chain: bool, ctx: &mut CompileCtx, + ) { + match element { + ChainElement::StaticMemberExpression(member) => { + self.count_static_member_get_preserve_base(member, short_chain, ctx) + } + ChainElement::ComputedMemberExpression(member) => { + self.count_computed_member_get_preserve_base(member, short_chain, ctx) + } + ChainElement::PrivateFieldExpression(member) => { + self.count_chainable_expression(&member.object, short_chain, ctx); + if short_chain && member.optional { + self.count_optional_guard(ctx); + } + ctx.count_private_access(); + } + ChainElement::CallExpression(call) => self.count_chain_call(call, short_chain, ctx), + ChainElement::TSNonNullExpression(_) => {} + } + } + + pub(in crate::counter) fn count_logical_assign_test(&self, op: LogicalOperator, id: u32, ctx: &mut CompileCtx) { + match op { + LogicalOperator::And | LogicalOperator::Or => { + ctx.projected_pc += 1; + } + LogicalOperator::Coalesce => { + ctx.projected_pc += 1; // JMP_IF_NULLISH to store body + ctx.projected_pc += 1; // JMP to end on non-nullish + ctx.label_map.insert(Label::TernaryElse(id), ctx.projected_pc); + } + } + } +} diff --git a/crates/oxide_compiler/src/counter/helper/mod.rs b/crates/oxide_compiler/src/counter/helper/mod.rs new file mode 100644 index 00000000..a9ad5213 --- /dev/null +++ b/crates/oxide_compiler/src/counter/helper/mod.rs @@ -0,0 +1,3 @@ +mod binding; +mod class; +mod member; diff --git a/crates/oxide_compiler/src/counter/mod.rs b/crates/oxide_compiler/src/counter/mod.rs new file mode 100644 index 00000000..7600a400 --- /dev/null +++ b/crates/oxide_compiler/src/counter/mod.rs @@ -0,0 +1,10 @@ +use oxide_parser::{ + AssignmentOperator, ChainElement, ClassElement, Expression, ForStatementInit, LogicalOperator, + MethodDefinitionKind, SimpleAssignmentTarget, Statement, UnaryOperator, VariableDeclarationKind, +}; + +use crate::compiler::{is_side_effect_free, CompileCtx, Compiler, Label}; + +mod expr; +mod helper; +mod stmt; diff --git a/crates/oxide_compiler/src/counter/stmt/mod.rs b/crates/oxide_compiler/src/counter/stmt/mod.rs new file mode 100644 index 00000000..7157f80f --- /dev/null +++ b/crates/oxide_compiler/src/counter/stmt/mod.rs @@ -0,0 +1,303 @@ +use super::*; + +impl Compiler { + pub(crate) fn count_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) { + match stmt { + Statement::ExpressionStatement(es) => { + self.count_expression(&es.expression, ctx); + } + Statement::VariableDeclaration(decl) => { + for d in &decl.declarations { + if let Some(init) = &d.init { + self.count_expression(init, ctx); + self.count_binding_pattern(&d.id, ctx); + } else { + ctx.alloc_reg(); + ctx.count_words(2); // LOAD_CONST(undefined) + STORE_VAR + } + } + } + Statement::ReturnStatement(ret) => { + if let Some(arg) = &ret.argument { + self.count_expression(arg, ctx); + } + ctx.projected_pc += 1; // RETURN + } + Statement::IfStatement(ifs) => { + let id = ctx.next_label_id(); + let else_label = Label::IfElse(id); + let end_label = Label::IfEnd(id); + + self.count_expression(&ifs.test, ctx); + ctx.projected_pc += 1; // JMP_IF_FALSE + self.count_statement(&ifs.consequent, ctx); + ctx.alloc_reg(); // result register + ctx.projected_pc += 1; // LOAD_VAR result <- consequent + if ifs.alternate.is_some() { + ctx.projected_pc += 1; // JMP (skip else) + } + ctx.label_map.insert(else_label, ctx.projected_pc); + if let Some(alt_stmt) = &ifs.alternate { + self.count_statement(alt_stmt, ctx); + ctx.projected_pc += 1; // LOAD_VAR result <- alternate + } + ctx.label_map.insert(end_label, ctx.projected_pc); + } + Statement::WhileStatement(wh) => { + let id = ctx.next_label_id(); + let start_label = Label::WhileStart(id); + let end_label = Label::WhileEnd(id); + + ctx.label_map.insert(start_label, ctx.projected_pc); + self.count_expression(&wh.test, ctx); + ctx.projected_pc += 1; // JMP_IF_FALSE + self.count_statement(&wh.body, ctx); + ctx.projected_pc += 1; // JMP (backward) + ctx.label_map.insert(end_label, ctx.projected_pc); + } + Statement::DoWhileStatement(dw) => { + let id = ctx.next_label_id(); + let start_label = Label::DoWhileStart(id); + let end_label = Label::DoWhileEnd(id); + + ctx.label_map.insert(start_label, ctx.projected_pc); + self.count_statement(&dw.body, ctx); + self.count_expression(&dw.test, ctx); + ctx.projected_pc += 1; // JMP_IF_TRUE (backward) + ctx.label_map.insert(end_label, ctx.projected_pc); + } + Statement::ForStatement(fr) => { + let id = ctx.next_label_id(); + let start_label = Label::ForStart(id); + let update_label = Label::ForUpdate(id); + let end_label = Label::ForEnd(id); + + if let Some(init) = &fr.init { + if let Some(expr) = init.as_expression() { + self.count_expression(expr, ctx); + } else if let ForStatementInit::VariableDeclaration(decl) = init { + for d in &decl.declarations { + if let Some(init_expr) = &d.init { + self.count_expression(init_expr, ctx); + self.count_binding_pattern(&d.id, ctx); + } else { + ctx.alloc_reg(); + ctx.count_words(2); // LOAD_CONST(undefined) + STORE_VAR + } + } + } + } + ctx.label_map.insert(start_label, ctx.projected_pc); + if let Some(test) = &fr.test { + self.count_expression(test, ctx); + ctx.projected_pc += 1; // JMP_IF_FALSE + } + self.count_statement(&fr.body, ctx); + ctx.label_map.insert(update_label, ctx.projected_pc); + if let Some(update) = &fr.update { + self.count_expression(update, ctx); + } + ctx.projected_pc += 1; // JMP (backward) + ctx.label_map.insert(end_label, ctx.projected_pc); + } + Statement::ForInStatement(fi) => { + let id = ctx.next_label_id(); + let start_label = Label::ForInStart(id); + let end_label = Label::ForInEnd(id); + + self.count_expression(&fi.right, ctx); + ctx.count_instr(); // FOR_IN_INIT + + ctx.label_map.insert(start_label, ctx.projected_pc); + ctx.count_instr(); // FOR_IN_DONE + ctx.count_jump(); // JMP_IF_FALSE + ctx.count_jump(); // JMP cleanup + + ctx.count_instr(); // FOR_IN_NEXT + match &fi.left { + oxide_parser::ForStatementLeft::VariableDeclaration(decl) => { + for _d in &decl.declarations { + ctx.alloc_reg(); + ctx.count_instr(); // STORE_VAR + } + } + oxide_parser::ForStatementLeft::AssignmentTargetIdentifier(_) => { + ctx.alloc_reg(); // key register + ctx.count_instr(); // STORE_VAR + } + _ => {} + } + + self.count_statement(&fi.body, ctx); + ctx.count_jump(); // JMP back to start + + ctx.label_map.insert(end_label, ctx.projected_pc); + ctx.count_instr(); // FOR_IN_CLEANUP + } + Statement::ForOfStatement(fo) => { + let id = ctx.next_label_id(); + let start_label = Label::ForOfStart(id); + let end_label = Label::ForOfEnd(id); + + self.count_expression(&fo.right, ctx); + ctx.count_instr(); // FOR_OF_INIT + ctx.label_map.insert(start_label, ctx.projected_pc); + ctx.alloc_reg(); // has_reg + ctx.count_instr(); // FOR_OF_DONE + ctx.count_jump(); // JMP_IF_FALSE + ctx.alloc_reg(); // val_reg + ctx.count_instr(); // FOR_OF_NEXT + match &fo.left { + oxide_parser::ForStatementLeft::VariableDeclaration(decl) => { + for d in &decl.declarations { + self.count_binding_pattern(&d.id, ctx); + } + } + oxide_parser::ForStatementLeft::AssignmentTargetIdentifier(_) => { + ctx.alloc_reg(); + ctx.count_instr(); // STORE_VAR + } + oxide_parser::ForStatementLeft::ArrayAssignmentTarget(ap) => { + self.count_array_assignment(ap, ctx); + } + oxide_parser::ForStatementLeft::ObjectAssignmentTarget(op) => { + self.count_object_assignment(op, ctx); + } + _ => {} + } + self.count_statement(&fo.body, ctx); + ctx.count_jump(); // JMP back + ctx.label_map.insert(end_label, ctx.projected_pc); + ctx.count_instr(); // FOR_OF_CLOSE + } + Statement::SwitchStatement(sw) => { + let id = ctx.next_label_id(); + let end_label = Label::SwitchEnd(id); + ctx.push_switch(end_label); + + self.count_expression(&sw.discriminant, ctx); + let compare_reg_checkpoint = ctx.reg_checkpoint(); + + let cases = &sw.cases; + for case in cases.iter() { + if let Some(test) = &case.test { + self.count_expression(test, ctx); + ctx.projected_pc += 1; // EQ + ctx.alloc_reg(); // eq result + ctx.projected_pc += 1; // JMP_IF_TRUE + ctx.restore_reg_checkpoint(compare_reg_checkpoint); + } + } + + let has_default = cases.iter().any(|c| c.test.is_none()); + if !has_default { + ctx.projected_pc += 1; // JMP to SwitchEnd (no match) + } + + for (case_idx, case) in cases.iter().enumerate() { + let case_label = Label::SwitchCase(id, case_idx as u32); + ctx.label_map.insert(case_label, ctx.projected_pc); + for s in &case.consequent { + self.count_statement(s, ctx); + } + } + + ctx.label_map.insert(end_label, ctx.projected_pc); + ctx.pop_switch(); + } + Statement::BreakStatement(_) => { + ctx.projected_pc += 1; // JMP + } + Statement::ContinueStatement(_) => { + ctx.projected_pc += 1; // JMP + } + Statement::BlockStatement(block) => { + for s in &block.body { + self.count_statement(s, ctx); + } + } + Statement::FunctionDeclaration(fd) => { + let name = if let Some(id) = &fd.id { + id.name.to_string() + } else { + return; + }; + + // Hoisting: declare function name as initialized + let func_reg = ctx.alloc_reg(); + let _ = ctx.declare_initialized(&name, func_reg, VariableDeclarationKind::Var, false); + + // Body is compiled in the emit pass only. + // FD emits LOAD_CONST(BytecodeFunc) + STORE_VAR + ctx.count_words(2); + } + Statement::ClassDeclaration(class) => { + ctx.alloc_reg(); // class binding reg + self.count_class(class, ctx); + ctx.projected_pc += 1; // STORE_VAR binding <- ctor + } + Statement::ThrowStatement(ts) => { + self.count_expression(&ts.argument, ctx); + ctx.projected_pc += 1; // THROW + } + Statement::TryStatement(ts) => { + let id = ctx.next_label_id(); + let catch_label = Label::CatchBody(id); + let try_end_label = Label::TryEnd(id); + let has_catch = ts.handler.is_some(); + let has_finally = ts.finalizer.is_some(); + + ctx.alloc_reg(); // result_reg + + if has_finally { + ctx.projected_pc += 1; // TRY_FINALLY_BEGIN (before try body) + } + + if has_catch { + ctx.projected_pc += 1; // TRY_BEGIN (before try body) + } + + for s in &ts.block.body { + self.count_statement(s, ctx); + } + ctx.projected_pc += 1; // LOAD_VAR result_reg (if try body has result) + + if has_catch { + ctx.projected_pc += 1; // TRY_END + } + + let jmp_needed = has_catch || has_finally; + if jmp_needed { + ctx.projected_pc += 1; // JMP + } + + ctx.label_map.insert(catch_label, ctx.projected_pc); + if let Some(catch) = &ts.handler { + ctx.push_scope(); + if let Some(_param) = &catch.param { + ctx.alloc_reg(); + ctx.projected_pc += 1; // STORE_VAR + } + for cs in &catch.body.body { + self.count_statement(cs, ctx); + } + ctx.projected_pc += 1; // LOAD_VAR result_reg (if catch body has result) + ctx.pop_scope(); + } + + if let Some(finally) = &ts.finalizer { + let finally_label = Label::FinallyBody(id); + ctx.label_map.insert(finally_label, ctx.projected_pc); + for fs in &finally.body { + self.count_statement(fs, ctx); + } + ctx.projected_pc += 1; // LOAD_VAR result_reg (if finally has result) + ctx.projected_pc += 1; // TRY_FINALLY_END + } + + ctx.label_map.insert(try_end_label, ctx.projected_pc); + } + _ => {} + } + } +} diff --git a/crates/oxide_compiler/src/emitter.rs b/crates/oxide_compiler/src/emitter.rs index dded7494..d18b6527 100644 --- a/crates/oxide_compiler/src/emitter.rs +++ b/crates/oxide_compiler/src/emitter.rs @@ -1,5 +1,6 @@ use crate::compiler::Label; -use crate::opcode::{self, OpCode}; +use oxide_bytecode::module::Constant; +use oxide_bytecode::opcode::{self, OpCode}; use oxide_parser::{ AssignmentOperator, AssignmentTarget, AssignmentTargetMaybeDefault, AssignmentTargetProperty, BindingPattern, ChainElement, Class, ClassElement, Expression, ForStatementInit, ForStatementLeft, LogicalOperator, @@ -10,8 +11,6 @@ use oxide_parser::{ use crate::compiler::{ is_int_literal, is_side_effect_free, BinaryOperator, CompileCtx, Compiler, FunctionBodyContext, ParamSpec, }; -use crate::module::Constant; - impl Compiler { fn emit_optional_guard(&self, reg: u8, short_label: Label, ctx: &mut CompileCtx) -> Result<(), String> { let short_pos = ctx.resolve_label(short_label)?; diff --git a/crates/oxide_compiler/src/lib.rs b/crates/oxide_compiler/src/lib.rs index a73f3f7f..b9c1b48a 100644 --- a/crates/oxide_compiler/src/lib.rs +++ b/crates/oxide_compiler/src/lib.rs @@ -4,6 +4,4 @@ pub mod compiler; pub mod counter; pub mod emitter; pub mod hash; -pub mod module; -pub mod opcode; pub mod symbol_table; diff --git a/crates/oxide_compiler/tests/arrow_template_tests.rs b/crates/oxide_compiler/tests/arrow_template_tests.rs index 1c1546ed..5480a331 100644 --- a/crates/oxide_compiler/tests/arrow_template_tests.rs +++ b/crates/oxide_compiler/tests/arrow_template_tests.rs @@ -7,7 +7,7 @@ fn parse_to_hash(source: &str) -> u64 { oxide_compiler::compiler::structural_hash(&program) } -fn compile_source(source: &str) -> oxide_compiler::module::CompiledModule { +fn compile_source(source: &str) -> oxide_bytecode::module::CompiledModule { let allocator = Allocator::default(); let program = oxide_parser::parse(&allocator, source).expect("parse failed"); let compiler = Compiler::new(); diff --git a/crates/oxide_compiler/tests/compile_expr_tests.rs b/crates/oxide_compiler/tests/compile_expr_tests.rs index c34a9d98..31030b0b 100644 --- a/crates/oxide_compiler/tests/compile_expr_tests.rs +++ b/crates/oxide_compiler/tests/compile_expr_tests.rs @@ -1,8 +1,9 @@ +use oxide_bytecode::module::{CompiledModule, Constant}; +use oxide_bytecode::opcode::{self, OpCode}; use oxide_compiler::compiler::Compiler; -use oxide_compiler::opcode::{self, OpCode}; use oxide_parser::Allocator; -fn compile_source(source: &str) -> oxide_compiler::module::CompiledModule { +fn compile_source(source: &str) -> CompiledModule { let allocator = Allocator::default(); let program = oxide_parser::parse(&allocator, source).expect("parse failed"); let compiler = Compiler::new(); @@ -58,7 +59,7 @@ fn compile_constants() { let module = compile_source("42"); assert!(!module.constants.is_empty()); - assert_eq!(module.constants[0], oxide_compiler::compiler::Constant::Int(42)); + assert_eq!(module.constants[0], Constant::Int(42)); } #[test] @@ -67,7 +68,7 @@ fn compile_dedups_identical_string_constants() { let count = module .constants .iter() - .filter(|c| matches!(c, oxide_compiler::compiler::Constant::String(s) if s == "hello")) + .filter(|c| matches!(c, Constant::String(s) if s == "hello")) .count(); assert_eq!(count, 1, "identical string constants should be interned once per module"); } diff --git a/crates/oxide_compiler/tests/compile_stmt_tests.rs b/crates/oxide_compiler/tests/compile_stmt_tests.rs index dc300163..60733371 100644 --- a/crates/oxide_compiler/tests/compile_stmt_tests.rs +++ b/crates/oxide_compiler/tests/compile_stmt_tests.rs @@ -1,8 +1,9 @@ +use oxide_bytecode::module::{CompiledModule, Constant}; +use oxide_bytecode::opcode::{self, OpCode}; use oxide_compiler::compiler::Compiler; -use oxide_compiler::opcode::{self, OpCode}; use oxide_parser::Allocator; -fn compile_source(source: &str) -> oxide_compiler::module::CompiledModule { +fn compile_source(source: &str) -> CompiledModule { let allocator = Allocator::default(); let program = oxide_parser::parse(&allocator, source).expect("parse failed"); let compiler = Compiler::new(); @@ -13,7 +14,7 @@ fn compile_source(source: &str) -> oxide_compiler::module::CompiledModule { fn compile_var_declaration() { let module = compile_source("var x = 42;"); assert!(!module.bytecode.is_empty()); - assert_eq!(module.constants[0], oxide_compiler::compiler::Constant::Int(42)); + assert_eq!(module.constants[0], Constant::Int(42)); } #[test] diff --git a/crates/oxide_compiler/tests/structural_hash_tests.rs b/crates/oxide_compiler/tests/structural_hash_tests.rs index ead44ce3..ed059349 100644 --- a/crates/oxide_compiler/tests/structural_hash_tests.rs +++ b/crates/oxide_compiler/tests/structural_hash_tests.rs @@ -1,9 +1,9 @@ +use oxide_code_cache::CodeForge; use oxide_compiler::compiler::Compiler; -use oxide_kernel::code_forge::CodeForge; use oxide_parser::Allocator; #[allow(dead_code)] -fn compile_source(source: &str) -> oxide_compiler::module::CompiledModule { +fn compile_source(source: &str) -> oxide_bytecode::module::CompiledModule { let allocator = Allocator::default(); let program = oxide_parser::parse(&allocator, source).expect("parse failed"); let compiler = Compiler::new(); @@ -29,8 +29,13 @@ fn structural_hash_hit() { let allocator = Allocator::default(); let program = oxide_parser::parse(&allocator, "1 + 2").expect("parse failed"); - let m1 = forge.get_or_compile(&program, &compiler).expect("first compile"); - let m2 = forge.get_or_compile(&program, &compiler).expect("second compile"); + let hash = oxide_compiler::compiler::compiled_module_hash(&program); + let m1 = forge + .get_or_insert_with(hash, || compiler.compile(&program)) + .expect("first compile"); + let m2 = forge + .get_or_insert_with(hash, || compiler.compile(&program)) + .expect("second compile"); assert_eq!((m1.bytecode.len(), m1.n_registers), (m2.bytecode.len(), m2.n_registers)); } diff --git a/crates/oxide_compiler/tests/symbol_table_tests.rs b/crates/oxide_compiler/tests/symbol_table_tests.rs index 13dce6e3..f75c8169 100644 --- a/crates/oxide_compiler/tests/symbol_table_tests.rs +++ b/crates/oxide_compiler/tests/symbol_table_tests.rs @@ -1,8 +1,9 @@ +use oxide_bytecode::module::{CompiledModule, Constant}; +use oxide_bytecode::opcode::{self, OpCode}; use oxide_compiler::compiler::Compiler; -use oxide_compiler::opcode::{self, OpCode}; use oxide_parser::Allocator; -fn compile_source(source: &str) -> oxide_compiler::module::CompiledModule { +fn compile_source(source: &str) -> CompiledModule { let allocator = Allocator::default(); let program = oxide_parser::parse(&allocator, source).expect("parse failed"); let compiler = Compiler::new(); @@ -13,7 +14,7 @@ fn compile_source(source: &str) -> oxide_compiler::module::CompiledModule { fn symbol_table_declare_and_lookup() { let module = compile_source("var x = 42;"); assert!(!module.bytecode.is_empty()); - assert_eq!(module.constants[0], oxide_compiler::compiler::Constant::Int(42)); + assert_eq!(module.constants[0], Constant::Int(42)); } #[test] diff --git a/crates/oxide_kernel/Cargo.toml b/crates/oxide_kernel/Cargo.toml index a6cb0977..da0cb050 100644 --- a/crates/oxide_kernel/Cargo.toml +++ b/crates/oxide_kernel/Cargo.toml @@ -7,7 +7,8 @@ rust-version.workspace = true description = "OxideJS - Shared kernel (CodeForge, ShapeForge, PropForge, StringForge, BuiltinWorld)" [dependencies] -oxide_compiler = { path = "../oxide_compiler" } +oxide_bytecode = { path = "../oxide_bytecode" } +oxide_code_cache = { path = "../oxide_code_cache" } oxide_parser = { path = "../oxide_parser" } oxide_types = { path = "../oxide_types" } dashmap.workspace = true diff --git a/crates/oxide_kernel/src/code_forge.rs b/crates/oxide_kernel/src/code_forge.rs index 3aef38d2..3ab24e76 100644 --- a/crates/oxide_kernel/src/code_forge.rs +++ b/crates/oxide_kernel/src/code_forge.rs @@ -1,141 +1 @@ -use std::sync::Arc; - -use dashmap::DashMap; - -use oxide_compiler::compiler::Compiler; -use oxide_compiler::module::CompiledModule; - -use crate::{kernel_debug, kernel_info}; - -pub use oxide_compiler::compiler::{compiled_module_hash, structural_hash}; - -/// Shared compiled-module cache. -/// -/// Concurrency model: -/// - Read-heavy after warmup: cache hits dominate steady-state execution. -/// - Backed by a fixed-shard `DashMap` so lookup contention stays predictable across -/// developer machines and CI hosts. -pub struct CodeForge { - map: DashMap>, -} - -impl CodeForge { - pub fn new() -> Self { - Self { - map: DashMap::with_shard_amount(16), - } - } - - pub fn get_or_compile( - &self, program: &oxide_parser::Program, compiler: &Compiler, - ) -> Result, String> { - let hash = compiled_module_hash(program); - - if let Some(module) = self.map.get(&hash) { - kernel_info!("CodeForge hit compiled_hash={:#x}", hash); - return Ok(Arc::clone(&module)); - } - - let module = compiler.compile(program)?; - let module = Arc::new(module); - kernel_debug!("CodeForge miss compiled_hash={:#x}", hash); - - Ok(Arc::clone(self.map.entry(hash).or_insert(module).value())) - } -} - -impl Default for CodeForge { - fn default() -> Self { - Self::new() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use oxide_parser::Allocator; - - #[test] - fn test_structural_hash_same_input() { - let a1 = Allocator::default(); - let a2 = Allocator::default(); - let p1 = oxide_parser::parse(&a1, "1 + 2").expect("parse failed"); - let p2 = oxide_parser::parse(&a2, "1 + 2").expect("parse failed"); - assert_eq!(structural_hash(&p1), structural_hash(&p2)); - } - - #[test] - fn test_structural_hash_same_structure() { - let a1 = Allocator::default(); - let a2 = Allocator::default(); - let p1 = oxide_parser::parse(&a1, "var x = 1; var y = 2;").expect("parse failed"); - let p2 = oxide_parser::parse(&a2, "var a = 1; var b = 2;").expect("parse failed"); - assert_eq!(structural_hash(&p1), structural_hash(&p2)); - } - - #[test] - fn test_compiled_module_hash_distinguishes_identifier_loads() { - let a1 = Allocator::default(); - let a2 = Allocator::default(); - let p1 = oxide_parser::parse(&a1, "var x = 1; var y = 2; x").expect("parse failed"); - let p2 = oxide_parser::parse(&a2, "var x = 1; var y = 2; y").expect("parse failed"); - - assert_eq!(structural_hash(&p1), structural_hash(&p2)); - assert_ne!(compiled_module_hash(&p1), compiled_module_hash(&p2)); - } - - #[test] - fn test_structural_hash_different_ops() { - let a1 = Allocator::default(); - let a2 = Allocator::default(); - let p1 = oxide_parser::parse(&a1, "1 + 2").expect("parse failed"); - let p2 = oxide_parser::parse(&a2, "1 - 2").expect("parse failed"); - assert_ne!(structural_hash(&p1), structural_hash(&p2)); - } - - #[test] - fn test_cache_hit() { - let forge = CodeForge::new(); - let compiler = Compiler::new(); - let allocator = Allocator::default(); - let program = oxide_parser::parse(&allocator, "1 + 2").expect("parse failed"); - - let first = forge.get_or_compile(&program, &compiler).expect("first compile"); - let second = forge.get_or_compile(&program, &compiler).expect("second compile"); - - assert_eq!((first.bytecode.len(), first.n_registers), (second.bytecode.len(), second.n_registers)); - } - - #[test] - fn test_cache_miss_new_program() { - let forge = CodeForge::new(); - let compiler = Compiler::new(); - - let a1 = Allocator::default(); - let p1 = oxide_parser::parse(&a1, "1 + 2").expect("parse failed"); - let a2 = Allocator::default(); - let p2 = oxide_parser::parse(&a2, "3 * 4").expect("parse failed"); - - let m1 = forge.get_or_compile(&p1, &compiler).expect("compile p1"); - let m2 = forge.get_or_compile(&p2, &compiler).expect("compile p2"); - - assert_ne!(m1.bytecode, m2.bytecode); - } - - #[test] - fn test_cache_miss_same_shape_different_binding_program() { - let forge = CodeForge::new(); - let compiler = Compiler::new(); - - let a1 = Allocator::default(); - let p1 = oxide_parser::parse(&a1, "var x = 1; var y = 2; x").expect("parse failed"); - let a2 = Allocator::default(); - let p2 = oxide_parser::parse(&a2, "var x = 1; var y = 2; y").expect("parse failed"); - - let m1 = forge.get_or_compile(&p1, &compiler).expect("compile p1"); - let m2 = forge.get_or_compile(&p2, &compiler).expect("compile p2"); - - assert!(!Arc::ptr_eq(&m1, &m2)); - assert_ne!(m1.bytecode, m2.bytecode); - } -} +pub use oxide_code_cache::CodeForge; diff --git a/crates/oxide_test262/Cargo.toml b/crates/oxide_test262/Cargo.toml index aa5d2784..95ba05af 100644 --- a/crates/oxide_test262/Cargo.toml +++ b/crates/oxide_test262/Cargo.toml @@ -11,6 +11,7 @@ name = "test262-runner" path = "src/main.rs" [dependencies] +oxide_bytecode = { path = "../oxide_bytecode" } oxide_compiler = { path = "../oxide_compiler" } oxide_vm = { path = "../oxide_vm" } oxide_parser = { path = "../oxide_parser" } diff --git a/crates/oxide_vm/Cargo.toml b/crates/oxide_vm/Cargo.toml index 245560ba..3d501f1e 100644 --- a/crates/oxide_vm/Cargo.toml +++ b/crates/oxide_vm/Cargo.toml @@ -7,7 +7,7 @@ rust-version.workspace = true description = "OxideJS - Register-based VM with epoch arena memory" [dependencies] -oxide_compiler = { path = "../oxide_compiler" } +oxide_bytecode = { path = "../oxide_bytecode" } oxide_kernel = { path = "../oxide_kernel" } oxide_types = { path = "../oxide_types" } bumpalo.workspace = true @@ -23,5 +23,6 @@ unicode-normalization.workspace = true tracing.workspace = true [dev-dependencies] +oxide_compiler = { path = "../oxide_compiler" } proptest.workspace = true oxide_parser = { path = "../oxide_parser" } diff --git a/crates/oxide_vm/src/dispatch/member.rs b/crates/oxide_vm/src/dispatch/member.rs index 387f3fab..986add06 100644 --- a/crates/oxide_vm/src/dispatch/member.rs +++ b/crates/oxide_vm/src/dispatch/member.rs @@ -1,4 +1,4 @@ -use oxide_compiler::opcode::OpCode; +use oxide_bytecode::opcode::OpCode; use oxide_types::object::JsObject; use oxide_types::value::JsValue; diff --git a/crates/oxide_vm/src/dispatch/property.rs b/crates/oxide_vm/src/dispatch/property.rs index e3f61b38..f68f418f 100644 --- a/crates/oxide_vm/src/dispatch/property.rs +++ b/crates/oxide_vm/src/dispatch/property.rs @@ -1,4 +1,4 @@ -use oxide_compiler::opcode::OpCode; +use oxide_bytecode::opcode::OpCode; use oxide_kernel::prop_forge::PropTemplate; use oxide_kernel::{ic_debug, ic_trace}; use oxide_types::object::JsObject; diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 75613f2b..cccd1350 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -3,8 +3,8 @@ use std::collections::HashMap; use std::sync::Arc; -use oxide_compiler::module::CompiledModule; -use oxide_compiler::opcode::{self, OpCode}; +use oxide_bytecode::module::CompiledModule; +use oxide_bytecode::opcode::{self, OpCode}; use smallvec::SmallVec; pub use crate::bindings::init_kernel_builtins; @@ -1286,7 +1286,7 @@ impl Default for Vm { mod tests { use super::{opcode, JsValue, TryHandler, Vm}; use crate::native::NativeResult; - use oxide_compiler::module::{CompiledModule, Constant}; + use oxide_bytecode::module::{CompiledModule, Constant}; use oxide_types::object::NativeFnPtr; use oxide_types::object::{JsObject, PropAttributes}; diff --git a/crates/oxide_vm/src/vm_dispatch_ctrl.rs b/crates/oxide_vm/src/vm_dispatch_ctrl.rs index 6cf844d9..635b342c 100644 --- a/crates/oxide_vm/src/vm_dispatch_ctrl.rs +++ b/crates/oxide_vm/src/vm_dispatch_ctrl.rs @@ -1,5 +1,5 @@ use crate::vm::{FrameContinuation, TryHandler, Vm}; -use oxide_compiler::opcode; +use oxide_bytecode::opcode; use oxide_types::object::PropAttributes; use oxide_types::value::JsValue; diff --git a/crates/oxide_vm/src/vm_runtime.rs b/crates/oxide_vm/src/vm_runtime.rs index 7f421b86..8272fb23 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -1,5 +1,5 @@ -use oxide_compiler::module::CompiledModule; -use oxide_compiler::opcode; +use oxide_bytecode::module::CompiledModule; +use oxide_bytecode::opcode; use crate::vm::{CallFrame, InlineSyncState, Vm}; use oxide_types::object::JsObject; diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index 1e532baf..44c9163b 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -2,7 +2,7 @@ use std::sync::Arc; -use oxide_compiler::compiler::Constant; +use oxide_bytecode::module::Constant; use crate::bindings; use crate::vm::{native_fn_ptr_to_fn, Vm}; diff --git a/crates/oxide_vm/tests/bench_tests.rs b/crates/oxide_vm/tests/bench_tests.rs index fca0dfbd..8be9a88a 100644 --- a/crates/oxide_vm/tests/bench_tests.rs +++ b/crates/oxide_vm/tests/bench_tests.rs @@ -1,5 +1,5 @@ +use oxide_bytecode::module::CompiledModule; use oxide_compiler::compiler::Compiler; -use oxide_compiler::module::CompiledModule; use oxide_parser::Allocator; use oxide_vm::vm::Vm;