diff --git a/crates/oxide_compiler/src/passes/class/core.rs b/crates/oxide_compiler/src/class/core.rs similarity index 100% rename from crates/oxide_compiler/src/passes/class/core.rs rename to crates/oxide_compiler/src/class/core.rs diff --git a/crates/oxide_compiler/src/class/emit.rs b/crates/oxide_compiler/src/class/emit.rs new file mode 100644 index 00000000..bb33290f --- /dev/null +++ b/crates/oxide_compiler/src/class/emit.rs @@ -0,0 +1,326 @@ +use crate::compiler::{CompileCtx, Compiler, FunctionBodyContext}; +use oxide_bytecode::module::Constant; +use oxide_bytecode::opcode::{self, OpCode}; +use oxide_parser::{Class, ClassElement, MethodDefinitionKind, PropertyKey}; + +impl Compiler { + pub(crate) fn emit_class(&self, class: &Class, ctx: &mut CompileCtx) -> Result { + let mut constructor_method = None; + let mut instance_fields = Vec::new(); + let mut private_names = Vec::<(String, u32)>::new(); + let is_derived = class.super_class.is_some(); + + for element in &class.body.body { + match element { + ClassElement::MethodDefinition(method) => { + let method = method.as_ref(); + if let PropertyKey::PrivateIdentifier(private) = &method.key { + let name = private.name.as_str().to_string(); + if private_names.iter().any(|(existing, _)| existing == &name) { + return Err(format!("duplicate private name #{name}")); + } + let id = ctx.scopes.next_private_name_id; + ctx.scopes.next_private_name_id = ctx.scopes.next_private_name_id.saturating_add(1); + private_names.push((name, id)); + } + if method.kind == MethodDefinitionKind::Constructor { + if constructor_method.is_some() { + return Err("duplicate class constructor".into()); + } + constructor_method = Some(method); + } + } + ClassElement::PropertyDefinition(prop) => { + let prop = prop.as_ref(); + if let PropertyKey::PrivateIdentifier(private) = &prop.key { + let name = private.name.as_str().to_string(); + if private_names.iter().any(|(existing, _)| existing == &name) { + return Err(format!("duplicate private name #{name}")); + } + let id = ctx.scopes.next_private_name_id; + ctx.scopes.next_private_name_id = ctx.scopes.next_private_name_id.saturating_add(1); + private_names.push((name, id)); + } + if !prop.r#static { + instance_fields.push(prop); + } + } + ClassElement::AccessorProperty(_) => return Err("class accessor properties not yet supported".into()), + ClassElement::StaticBlock(_) | ClassElement::TSIndexSignature(_) => {} + } + } + + let ctor_name = class.id.as_ref().map(|id| id.name.to_string()); + let ctor_reg = ctx.alloc_reg(); + let proto_reg = ctx.alloc_reg(); + let self_binding = ctor_name.as_deref().map(|name| vec![(name, ctor_reg)]).unwrap_or_default(); + let super_reg = if let Some(super_expr) = &class.super_class { + Some(self.emit_expression(super_expr, ctx)?) + } else { + None + }; + + let saved_derived = ctx.in_derived_constructor; + let saved_private_names = ctx.scopes.private_name_map.clone(); + ctx.in_derived_constructor = is_derived; + ctx.scopes.private_name_map = private_names.clone(); + + let mut ctor_module = if let Some(method) = constructor_method { + let (param_names, body_stmts) = self.extract_function_parts(method.value.as_ref())?; + self.compile_function_body_with_field_hooks( + ¶m_names, + body_stmts, + ctx, + false, + &self_binding, + FunctionBodyContext::ClassElement, + Some(|compiler: &Compiler, field_ctx: &mut CompileCtx| { + for field in &instance_fields { + if matches!(field.key, PropertyKey::PrivateIdentifier(_)) { + compiler.count_private_field_init(field.value.as_ref(), field_ctx); + } else { + compiler.count_public_field_init( + &field.key, + field.computed, + field.value.as_ref(), + field_ctx, + ); + } + } + }), + Some(|compiler: &Compiler, field_ctx: &mut CompileCtx| -> Result<(), String> { + for field in &instance_fields { + if let PropertyKey::PrivateIdentifier(private) = &field.key { + compiler.emit_private_field_init( + 254, + private.name.as_str(), + field.value.as_ref(), + field_ctx, + )?; + } else { + compiler.emit_public_field_init( + 254, + &field.key, + field.computed, + field.value.as_ref(), + field_ctx, + )?; + } + } + Ok(()) + }), + is_derived, + )? + } else { + let mut module = self.compile_function_body_with_field_hooks( + &[], + &[], + ctx, + false, + &self_binding, + FunctionBodyContext::ClassElement, + Some(|compiler: &Compiler, field_ctx: &mut CompileCtx| { + for field in &instance_fields { + if matches!(field.key, PropertyKey::PrivateIdentifier(_)) { + compiler.count_private_field_init(field.value.as_ref(), field_ctx); + } else { + compiler.count_public_field_init( + &field.key, + field.computed, + field.value.as_ref(), + field_ctx, + ); + } + } + }), + Some(|compiler: &Compiler, field_ctx: &mut CompileCtx| -> Result<(), String> { + for field in &instance_fields { + if let PropertyKey::PrivateIdentifier(private) = &field.key { + compiler.emit_private_field_init( + 254, + private.name.as_str(), + field.value.as_ref(), + field_ctx, + )?; + } else { + compiler.emit_public_field_init( + 254, + &field.key, + field.computed, + field.value.as_ref(), + field_ctx, + )?; + } + } + Ok(()) + }), + is_derived, + )?; + if is_derived { + module.bytecode.clear(); + module.constants.clear(); + module.n_registers = 1; + module.bytecode.push(opcode::encode(OpCode::SUPER_CALL, 0, 0, 0)); + module.bytecode.push(0); + let mut field_ctx = CompileCtx::new(); + field_ctx.scopes.private_name_map = private_names.clone(); + for field in &instance_fields { + if let PropertyKey::PrivateIdentifier(private) = &field.key { + self.emit_private_field_init(254, private.name.as_str(), field.value.as_ref(), &mut field_ctx)?; + } else { + self.emit_public_field_init( + 254, + &field.key, + field.computed, + field.value.as_ref(), + &mut field_ctx, + )?; + } + } + module.bytecode.extend(field_ctx.bytecode); + module.constants = field_ctx.constants; + module.n_registers = field_ctx.max_regs.max(1); + module.bytecode.push(opcode::encode(OpCode::RETURN, 0, 0, 0)); + } + module + }; + ctx.in_derived_constructor = saved_derived; + ctor_module.is_class_constructor = true; + ctor_module.is_derived_constructor = is_derived; + ctor_module.function_name = ctor_name.clone(); + ctx.sub_modules.push(ctor_module); + + ctx.emit_create_closure(ctor_reg, ctx.sub_modules.len() as u32); + ctx.emit(opcode::encode(OpCode::NEW_OBJECT, proto_reg, 0, 0)); + + if let Some(super_reg) = super_reg { + let proto_key_idx = ctx.add_constant(Constant::String("prototype".to_string())); + let parent_proto_key_reg = ctx.alloc_reg(); + ctx.emit_load_const(parent_proto_key_reg, proto_key_idx); + let parent_proto_reg = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::GET_PROP, super_reg, parent_proto_reg, parent_proto_key_reg)); + + let proto_link_idx = ctx.add_constant(Constant::String("__proto__".to_string())); + let proto_link_key_reg = ctx.alloc_reg(); + ctx.emit_load_const(proto_link_key_reg, proto_link_idx); + ctx.emit(opcode::encode(OpCode::SET_PROP, proto_reg, parent_proto_reg, proto_link_key_reg)); + ctx.emit(opcode::encode(OpCode::SET_PROP, ctor_reg, super_reg, proto_link_key_reg)); + } + + let ctor_key_idx = ctx.add_constant(Constant::String("constructor".to_string())); + let ctor_key_reg = ctx.alloc_reg(); + ctx.emit_load_const(ctor_key_reg, ctor_key_idx); + ctx.emit(opcode::encode(OpCode::SET_PROP, proto_reg, ctor_reg, ctor_key_reg)); + + let proto_key_idx = ctx.add_constant(Constant::String("prototype".to_string())); + let proto_key_reg = ctx.alloc_reg(); + ctx.emit_load_const(proto_key_reg, proto_key_idx); + ctx.emit(opcode::encode(OpCode::SET_PROP, ctor_reg, proto_reg, proto_key_reg)); + + for element in &class.body.body { + match element { + ClassElement::MethodDefinition(method) => { + let method = method.as_ref(); + if method.kind == MethodDefinitionKind::Constructor { + continue; + } + if matches!(method.key, PropertyKey::PrivateIdentifier(_)) { + let home_reg = if method.r#static { ctor_reg } else { proto_reg }; + self.emit_private_method_init(home_reg, method, home_reg, ctx)?; + continue; + } + let home_reg = if method.r#static { ctor_reg } else { proto_reg }; + let key_reg = self.emit_class_key_reg(&method.key, method.computed, ctx)?; + let method_name = if method.computed { + "".to_string() + } else { + self.class_property_name(&method.key)? + }; + let accessor_reg = + self.emit_class_method_function(method, &method_name, home_reg, ctx, &self_binding)?; + match method.kind { + MethodDefinitionKind::Method => { + if method.computed { + ctx.emit(opcode::encode(OpCode::SET_PROP_DYNAMIC, home_reg, key_reg, accessor_reg)); + } else { + ctx.emit(opcode::encode(OpCode::SET_PROP, home_reg, accessor_reg, key_reg)); + } + } + MethodDefinitionKind::Get | MethodDefinitionKind::Set => { + if method.computed { + return Err("computed class accessors not yet supported".into()); + } + let undef_reg = self.emit_undefined(ctx); + let (get_reg, set_reg) = if method.kind == MethodDefinitionKind::Get { + (accessor_reg, undef_reg) + } else { + (undef_reg, accessor_reg) + }; + let key_name = self.class_property_name(&method.key)?; + let key_idx = ctx.add_constant(Constant::String(key_name)); + ctx.emit(opcode::encode(OpCode::DEFINE_ACCESSOR, home_reg, get_reg, set_reg)); + ctx.emit(key_idx as u32); + } + MethodDefinitionKind::Constructor => continue, + } + } + ClassElement::PropertyDefinition(prop) => { + let prop = prop.as_ref(); + if prop.r#static { + let saved_static_this = ctx.static_block_this_reg; + ctx.static_block_this_reg = Some(ctor_reg); + if let PropertyKey::PrivateIdentifier(private) = &prop.key { + self.emit_private_field_init(ctor_reg, private.name.as_str(), prop.value.as_ref(), ctx)?; + } else { + self.emit_public_field_init(ctor_reg, &prop.key, prop.computed, prop.value.as_ref(), ctx)?; + } + ctx.static_block_this_reg = saved_static_this; + } + } + ClassElement::StaticBlock(block) => { + let saved_static_this = ctx.static_block_this_reg; + ctx.static_block_this_reg = Some(ctor_reg); + ctx.push_scope(); + for stmt in &block.body { + self.emit_statement(stmt, ctx)?; + } + ctx.pop_scope(); + ctx.static_block_this_reg = saved_static_this; + } + ClassElement::AccessorProperty(_) | ClassElement::TSIndexSignature(_) => {} + } + } + + ctx.scopes.private_name_map = saved_private_names; + Ok(ctor_reg) + } + + pub(crate) fn emit_class_method_function( + &self, method: &oxide_parser::MethodDefinition, method_name: &str, home_reg: u8, ctx: &mut CompileCtx, + self_binding: &[(&str, u8)], + ) -> Result { + let (param_names, body_stmts) = self.extract_function_parts(method.value.as_ref())?; + let saved_instance = ctx.in_instance_method; + let saved_static = ctx.in_static_method; + ctx.in_instance_method = !method.r#static; + ctx.in_static_method = method.r#static; + let mut method_module = self.compile_function_body_with_bindings( + ¶m_names, + body_stmts, + ctx, + false, + self_binding, + FunctionBodyContext::ClassElement, + )?; + ctx.in_instance_method = saved_instance; + ctx.in_static_method = saved_static; + method_module.function_name = Some(method_name.to_string()); + method_module.needs_home_object = true; + ctx.sub_modules.push(method_module); + + let method_reg = ctx.alloc_reg(); + ctx.emit_create_closure(method_reg, ctx.sub_modules.len() as u32); + ctx.emit(opcode::encode(OpCode::SET_HOME_OBJECT, method_reg, home_reg, 0)); + Ok(method_reg) + } +} diff --git a/crates/oxide_compiler/src/passes/class/mod.rs b/crates/oxide_compiler/src/class/mod.rs similarity index 50% rename from crates/oxide_compiler/src/passes/class/mod.rs rename to crates/oxide_compiler/src/class/mod.rs index 5a7ca06a..c6fa3a48 100644 --- a/crates/oxide_compiler/src/passes/class/mod.rs +++ b/crates/oxide_compiler/src/class/mod.rs @@ -1 +1,2 @@ pub mod core; +pub mod emit; diff --git a/crates/oxide_compiler/src/compiler.rs b/crates/oxide_compiler/src/compiler.rs index ffa196c9..b7d67263 100644 --- a/crates/oxide_compiler/src/compiler.rs +++ b/crates/oxide_compiler/src/compiler.rs @@ -1227,6 +1227,122 @@ impl Compiler { }) } + pub(crate) fn count_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) { + match stmt { + Statement::ExpressionStatement(_) + | Statement::ReturnStatement(_) + | Statement::BreakStatement(_) + | Statement::ContinueStatement(_) + | Statement::LabeledStatement(_) => self.count_basic(stmt, ctx), + Statement::BlockStatement(_) => self.count_block_domain(stmt, ctx), + Statement::VariableDeclaration(_) | Statement::FunctionDeclaration(_) | Statement::ClassDeclaration(_) => { + self.count_declaration_domain(stmt, ctx) + } + Statement::IfStatement(_) => self.count_control_domain(stmt, ctx), + Statement::WhileStatement(_) + | Statement::DoWhileStatement(_) + | Statement::ForStatement(_) + | Statement::ForInStatement(_) + | Statement::ForOfStatement(_) => self.count_iteration_domain(stmt, ctx), + Statement::SwitchStatement(_) => self.count_switch_domain(stmt, ctx), + Statement::ThrowStatement(_) | Statement::TryStatement(_) => self.count_exception_domain(stmt, ctx), + _ => {} + } + } + + pub(crate) fn count_expression(&self, expr: &Expression, ctx: &mut CompileCtx) { + match expr { + Expression::BinaryExpression(_) + | Expression::PrivateInExpression(_) + | Expression::UnaryExpression(_) + | Expression::UpdateExpression(_) => self.count_operator(expr, ctx), + Expression::CallExpression(_) | Expression::NewExpression(_) => self.count_call_domain(expr, ctx), + Expression::AssignmentExpression(_) => self.count_assignment(expr, ctx), + Expression::ConditionalExpression(_) + | Expression::SequenceExpression(_) + | Expression::LogicalExpression(_) => self.count_conditional_chain(expr, ctx), + Expression::ChainExpression(_) => self.count_chain_expression(expr, ctx), + Expression::ObjectExpression(_) | Expression::ArrayExpression(_) => self.count_object_domain(expr, ctx), + Expression::TemplateLiteral(_) | Expression::TaggedTemplateExpression(_) => { + self.count_template_domain(expr, ctx) + } + Expression::ArrowFunctionExpression(_) + | Expression::FunctionExpression(_) + | Expression::ClassExpression(_) => self.count_function_domain(expr, ctx), + Expression::StaticMemberExpression(_) + | Expression::ComputedMemberExpression(_) + | Expression::PrivateFieldExpression(_) => self.count_member_domain(expr, ctx), + Expression::ParenthesizedExpression(_) => self.count_parenthesized_expression(expr, ctx), + Expression::ThisExpression(_) => self.count_this_expression(ctx), + Expression::Identifier(_) => self.count_identifier_expression(expr, ctx), + Expression::NumericLiteral(_) + | Expression::StringLiteral(_) + | Expression::BooleanLiteral(_) + | Expression::NullLiteral(_) + | Expression::RegExpLiteral(_) => self.count_literal(expr, ctx), + _ => self.count_default_expression(ctx), + } + } + + pub(crate) fn emit_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result, String> { + match stmt { + Statement::ExpressionStatement(_) | Statement::ReturnStatement(_) | Statement::EmptyStatement(_) => { + self.emit_basic_domain(stmt, ctx) + } + Statement::BlockStatement(_) => self.emit_block_domain(stmt, ctx), + Statement::VariableDeclaration(_) | Statement::FunctionDeclaration(_) | Statement::ClassDeclaration(_) => { + self.emit_declaration_domain(stmt, ctx) + } + Statement::IfStatement(_) => self.emit_control_domain(stmt, ctx), + Statement::WhileStatement(_) + | Statement::DoWhileStatement(_) + | Statement::ForStatement(_) + | Statement::ForInStatement(_) + | Statement::ForOfStatement(_) => self.emit_iteration_domain(stmt, ctx), + Statement::SwitchStatement(_) => self.emit_switch_domain(stmt, ctx), + Statement::ThrowStatement(_) | Statement::TryStatement(_) => self.emit_exception_domain(stmt, ctx), + Statement::BreakStatement(b) => self.emit_break_statement(b, ctx), + Statement::ContinueStatement(c) => self.emit_continue_statement(c, ctx), + Statement::LabeledStatement(ls) => self.emit_labeled_statement(ls, ctx), + _ => Ok(None), + } + } + + pub(crate) fn emit_expression(&self, expr: &Expression, ctx: &mut CompileCtx) -> Result { + match expr { + Expression::NumericLiteral(_) + | Expression::StringLiteral(_) + | Expression::BooleanLiteral(_) + | Expression::NullLiteral(_) + | Expression::RegExpLiteral(_) => self.emit_literal(expr, ctx), + Expression::BinaryExpression(_) + | Expression::PrivateInExpression(_) + | Expression::UnaryExpression(_) + | Expression::ConditionalExpression(_) + | Expression::LogicalExpression(_) + | Expression::UpdateExpression(_) => self.emit_operator(expr, ctx), + Expression::StaticMemberExpression(_) + | Expression::ComputedMemberExpression(_) + | Expression::PrivateFieldExpression(_) + | Expression::ChainExpression(_) => self.emit_member_domain(expr, ctx), + Expression::ObjectExpression(_) | Expression::ArrayExpression(_) => self.emit_object_domain(expr, ctx), + Expression::AssignmentExpression(assign) => self.emit_assignment_expression(assign, ctx), + Expression::TemplateLiteral(_) | Expression::TaggedTemplateExpression(_) => { + self.emit_template_domain(expr, ctx) + } + Expression::ArrowFunctionExpression(_) + | Expression::FunctionExpression(_) + | Expression::ClassExpression(_) + | Expression::NewExpression(_) => self.emit_function_domain(expr, ctx), + Expression::Identifier(ident) => self.emit_identifier_expression(ident, ctx), + Expression::CallExpression(_) => self.emit_call_domain(expr, ctx), + Expression::ThisExpression(_) => self.emit_this_expression(ctx), + Expression::SequenceExpression(seq) => self.emit_sequence_expression(seq, ctx), + Expression::ParenthesizedExpression(p) => self.emit_parenthesized_expression(p, ctx), + _ => self.emit_unsupported_expression(expr, ctx), + } + } + pub fn compile(&self, program: &oxide_parser::Program) -> Result { crate::compiler_debug!("compile: starting..."); let mut ctx = CompileCtx::new(); diff --git a/crates/oxide_compiler/src/counter/expr/object.rs b/crates/oxide_compiler/src/counter/expr/object.rs deleted file mode 100644 index 52c3640d..00000000 --- a/crates/oxide_compiler/src/counter/expr/object.rs +++ /dev/null @@ -1,53 +0,0 @@ -use super::*; - -impl Compiler { - fn count_object_expression(&self, expr: &Expression, ctx: &mut CompileCtx) { - let Expression::ObjectExpression(obj) = expr else { - return; - }; - 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); - } - } - } - - fn count_array_expression(&self, expr: &Expression, ctx: &mut CompileCtx) { - let Expression::ArrayExpression(arr) = expr else { - return; - }; - 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); - } - } - } - - pub(in crate::counter) fn count_object_domain(&self, expr: &Expression, ctx: &mut CompileCtx) { - match expr { - Expression::ObjectExpression(_) => self.count_object_expression(expr, ctx), - Expression::ArrayExpression(_) => self.count_array_expression(expr, ctx), - _ => {} - } - } -} diff --git a/crates/oxide_compiler/src/counter/expr/template.rs b/crates/oxide_compiler/src/counter/expr/template.rs deleted file mode 100644 index f638704e..00000000 --- a/crates/oxide_compiler/src/counter/expr/template.rs +++ /dev/null @@ -1,61 +0,0 @@ -use super::*; - -impl Compiler { - fn count_template_literal(&self, expr: &Expression, ctx: &mut CompileCtx) { - let Expression::TemplateLiteral(tl) = expr else { - return; - }; - 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); - } - - fn count_tagged_template_expression(&self, expr: &Expression, ctx: &mut CompileCtx) { - let Expression::TaggedTemplateExpression(tt) = expr else { - return; - }; - self.count_expression(&tt.tag, ctx); - let quasi_count = tt.quasi.quasis.len(); - - for _ in 0..quasi_count { - ctx.count_load_const(); // string - ctx.count_load_const(); // index - ctx.count_instr(); // SET_ELEM (cooked) - } - 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) - } - ctx.alloc_reg(); // raw_temp - ctx.count_instr(); // NEW_ARRAY - - for expr in &tt.quasi.expressions { - self.count_expression(expr, ctx); - } - - 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 - } - ctx.count_words(2); // LOAD_VAR for cooked and raw - ctx.count_load_const(); // undefined this arg - ctx.count_call_instr_with_arg_ext(); - ctx.count_load_var(); // result <- regs[0] - } - - pub(in crate::counter) fn count_template_domain(&self, expr: &Expression, ctx: &mut CompileCtx) { - match expr { - Expression::TemplateLiteral(_) => self.count_template_literal(expr, ctx), - Expression::TaggedTemplateExpression(_) => self.count_tagged_template_expression(expr, ctx), - _ => {} - } - } -} diff --git a/crates/oxide_compiler/src/counter/helper/binding.rs b/crates/oxide_compiler/src/counter/helper/binding.rs deleted file mode 100644 index a870aafe..00000000 --- a/crates/oxide_compiler/src/counter/helper/binding.rs +++ /dev/null @@ -1,154 +0,0 @@ -use super::super::*; - -impl Compiler { - pub(crate) 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(crate) 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(crate) 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 let Some(elem) = elem { - // Mirror emit_array_assignment: an element may be a default (`[a = 1]`) or a - // nested pattern (`[[a], b]`), not just an identifier — recurse through the - // shared dispatch instead of hard-coding one STORE_VAR. - self.count_assignment_maybe_default(elem, ctx); - } - } - if let Some(rest) = &ap.rest { - self.count_rest_array(ctx); - // Mirror the emitter (emit_array_assignment -> emit_assign_target): the rest - // target may itself be a nested pattern, not just an identifier. - self.count_assign_target(&rest.target, ctx); - } - ctx.count_instr(); // FOR_OF_CLOSE - } - - pub(crate) 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 let Some(rest) = &op.rest { - ctx.alloc_reg(); - ctx.count_instr_with_ext(1); // REST_OBJECT + excluded-keys ext - // Mirror the emitter (emit_object_assignment -> emit_assign_target): the rest - // target may itself be a nested pattern, not just an identifier. - self.count_assign_target(&rest.target, ctx); - } - } - - pub(crate) 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(crate) 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/member.rs b/crates/oxide_compiler/src/counter/helper/member.rs deleted file mode 100644 index 68c066d2..00000000 --- a/crates/oxide_compiler/src/counter/helper/member.rs +++ /dev/null @@ -1,169 +0,0 @@ -use super::super::*; - -impl Compiler { - pub(crate) 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(crate) 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(crate) fn count_optional_guard(&self, ctx: &mut CompileCtx) { - ctx.count_load_var(); // dup - ctx.count_jump(); // JMP_IF_NULLISH - } - - pub(crate) 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(crate) 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(crate) 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(crate) 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(crate) 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(crate) 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.labels.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 deleted file mode 100644 index 7ebbb069..00000000 --- a/crates/oxide_compiler/src/counter/helper/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod binding; -mod member; diff --git a/crates/oxide_compiler/src/counter/mod.rs b/crates/oxide_compiler/src/counter/mod.rs deleted file mode 100644 index 2d74bfb4..00000000 --- a/crates/oxide_compiler/src/counter/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -#[allow(unused_imports)] -use crate::compiler::{ - is_int_literal, is_side_effect_free, BinaryOperator, CompileCtx, Compiler, FunctionBodyContext, Label, ParamSpec, -}; -#[allow(unused_imports)] -use oxide_bytecode::module::Constant; -#[allow(unused_imports)] -use oxide_bytecode::opcode::{self, OpCode}; -#[allow(unused_imports)] -use oxide_parser::{ - AssignmentOperator, AssignmentTarget, AssignmentTargetMaybeDefault, AssignmentTargetProperty, BindingPattern, - ChainElement, Class, ClassElement, Expression, ForStatementInit, ForStatementLeft, LogicalOperator, - MethodDefinitionKind, ObjectAssignmentTarget, PropertyKey, PropertyKind, SimpleAssignmentTarget, Statement, - UnaryOperator, UpdateOperator, VariableDeclarationKind, -}; - -pub mod helper; diff --git a/crates/oxide_compiler/src/counter/stmt/declaration.rs b/crates/oxide_compiler/src/counter/stmt/declaration.rs deleted file mode 100644 index e2acec3f..00000000 --- a/crates/oxide_compiler/src/counter/stmt/declaration.rs +++ /dev/null @@ -1,57 +0,0 @@ -use super::*; - -impl Compiler { - fn count_variable_declaration(&self, decl: &oxide_parser::VariableDeclaration<'_>, ctx: &mut CompileCtx) { - let is_var = matches!(decl.kind, VariableDeclarationKind::Var); - for d in &decl.declarations { - if let Some(init) = &d.init { - self.count_expression(init, ctx); - if is_var { - if let oxide_parser::BindingPattern::BindingIdentifier(bi) = &d.id { - // `var` hoisting: register the name during the count pass (mirrors - // count_function_declaration) so function bodies emitted earlier in the - // hoisting sub-pass can resolve it. count_binding_pattern would only - // allocate a register without declaring the name. - let reg = ctx.alloc_reg(); - let _ = ctx.declare_initialized(bi.name.as_str(), reg, VariableDeclarationKind::Var, false); - ctx.projected_pc += 1; // STORE_VAR - continue; - } - } - self.count_binding_pattern(&d.id, ctx); - } else { - ctx.alloc_reg(); - ctx.count_words(2); // LOAD_CONST(undefined) + STORE_VAR - } - } - } - - fn count_function_declaration(&self, decl: &oxide_parser::Function<'_>, ctx: &mut CompileCtx) { - let name = if let Some(id) = &decl.id { - id.name.to_string() - } else { - return; - }; - - let func_reg = ctx.alloc_reg(); - let _ = ctx.declare_initialized(&name, func_reg, VariableDeclarationKind::Var, false); - - // Body is compiled in the emit pass only. - ctx.count_words(2); // LOAD_CONST(BytecodeFunc) + STORE_VAR - } - - fn count_class_declaration(&self, class: &oxide_parser::Class<'_>, ctx: &mut CompileCtx) { - ctx.alloc_reg(); // class binding reg - self.count_class(class, ctx); - ctx.projected_pc += 1; // STORE_VAR binding <- ctor - } - - pub(in crate::counter) fn count_declaration_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) { - match stmt { - Statement::VariableDeclaration(decl) => self.count_variable_declaration(decl, ctx), - Statement::FunctionDeclaration(fd) => self.count_function_declaration(fd, ctx), - Statement::ClassDeclaration(class) => self.count_class_declaration(class, ctx), - _ => {} - } - } -} diff --git a/crates/oxide_compiler/src/counter/stmt/exception.rs b/crates/oxide_compiler/src/counter/stmt/exception.rs deleted file mode 100644 index 313282b5..00000000 --- a/crates/oxide_compiler/src/counter/stmt/exception.rs +++ /dev/null @@ -1,79 +0,0 @@ -use super::*; - -impl Compiler { - fn count_throw_statement(&self, stmt: &oxide_parser::ThrowStatement<'_>, ctx: &mut CompileCtx) { - self.count_expression(&stmt.argument, ctx); - ctx.projected_pc += 1; // THROW - } - - fn count_try_statement(&self, stmt: &oxide_parser::TryStatement<'_>, ctx: &mut CompileCtx) { - let id = ctx.next_label_id(); - let catch_label = Label::CatchBody(id); - let try_end_label = Label::TryEnd(id); - let has_catch = stmt.handler.is_some(); - let has_finally = stmt.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 &stmt.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.labels.label_map.insert(catch_label, ctx.projected_pc); - if let Some(catch) = &stmt.handler { - ctx.push_scope(); - if let Some(param) = &catch.param { - // Mirror the emitter: the catch register is always allocated, but STORE_VAR is - // emitted only for an identifier binding — a destructuring catch param emits no - // store (that binding form is not implemented yet). - ctx.alloc_reg(); - if let oxide_parser::BindingPattern::BindingIdentifier(_) = ¶m.pattern { - 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) = &stmt.finalizer { - let finally_label = Label::FinallyBody(id); - ctx.labels.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.labels.label_map.insert(try_end_label, ctx.projected_pc); - } - - pub(in crate::counter) fn count_exception_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) { - match stmt { - Statement::ThrowStatement(ts) => self.count_throw_statement(ts, ctx), - Statement::TryStatement(ts) => self.count_try_statement(ts, ctx), - _ => {} - } - } -} diff --git a/crates/oxide_compiler/src/counter/stmt/iteration.rs b/crates/oxide_compiler/src/counter/stmt/iteration.rs deleted file mode 100644 index 0eb6a0b9..00000000 --- a/crates/oxide_compiler/src/counter/stmt/iteration.rs +++ /dev/null @@ -1,147 +0,0 @@ -use super::*; - -impl Compiler { - fn count_while_statement(&self, stmt: &oxide_parser::WhileStatement<'_>, ctx: &mut CompileCtx) { - let id = ctx.next_label_id(); - let start_label = Label::WhileStart(id); - let end_label = Label::WhileEnd(id); - - ctx.labels.label_map.insert(start_label, ctx.projected_pc); - self.count_expression(&stmt.test, ctx); - ctx.projected_pc += 1; // JMP_IF_FALSE - self.count_statement(&stmt.body, ctx); - ctx.projected_pc += 1; // JMP (backward) - ctx.labels.label_map.insert(end_label, ctx.projected_pc); - } - - fn count_do_while_statement(&self, stmt: &oxide_parser::DoWhileStatement<'_>, ctx: &mut CompileCtx) { - let id = ctx.next_label_id(); - let start_label = Label::DoWhileStart(id); - let end_label = Label::DoWhileEnd(id); - - ctx.labels.label_map.insert(start_label, ctx.projected_pc); - self.count_statement(&stmt.body, ctx); - self.count_expression(&stmt.test, ctx); - ctx.projected_pc += 1; // JMP_IF_TRUE (backward) - ctx.labels.label_map.insert(end_label, ctx.projected_pc); - } - - fn count_for_statement(&self, stmt: &oxide_parser::ForStatement<'_>, ctx: &mut CompileCtx) { - 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) = &stmt.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(); // tmp holding undefined - ctx.alloc_reg(); // var register - ctx.count_words(2); // LOAD_CONST(undefined) + STORE_VAR - } - } - } - } - ctx.labels.label_map.insert(start_label, ctx.projected_pc); - if let Some(test) = &stmt.test { - self.count_expression(test, ctx); - ctx.projected_pc += 1; // JMP_IF_FALSE - } - self.count_statement(&stmt.body, ctx); - ctx.labels.label_map.insert(update_label, ctx.projected_pc); - if let Some(update) = &stmt.update { - self.count_expression(update, ctx); - } - ctx.projected_pc += 1; // JMP (backward) - ctx.labels.label_map.insert(end_label, ctx.projected_pc); - } - - fn count_for_in_statement(&self, stmt: &oxide_parser::ForInStatement<'_>, ctx: &mut CompileCtx) { - let id = ctx.next_label_id(); - let start_label = Label::ForInStart(id); - let end_label = Label::ForInEnd(id); - - self.count_expression(&stmt.right, ctx); - ctx.count_instr(); // FOR_IN_INIT - - ctx.labels.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 &stmt.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(&stmt.body, ctx); - ctx.count_jump(); // JMP back to start - - ctx.labels.label_map.insert(end_label, ctx.projected_pc); - ctx.count_instr(); // FOR_IN_CLEANUP - } - - fn count_for_of_statement(&self, stmt: &oxide_parser::ForOfStatement<'_>, ctx: &mut CompileCtx) { - let id = ctx.next_label_id(); - let start_label = Label::ForOfStart(id); - let end_label = Label::ForOfEnd(id); - - self.count_expression(&stmt.right, ctx); - ctx.count_instr(); // FOR_OF_INIT - ctx.labels.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 &stmt.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(&stmt.body, ctx); - ctx.count_jump(); // JMP back - ctx.labels.label_map.insert(end_label, ctx.projected_pc); - ctx.count_instr(); // FOR_OF_CLOSE - } - - pub(in crate::counter) fn count_iteration_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) { - match stmt { - Statement::WhileStatement(wh) => self.count_while_statement(wh, ctx), - Statement::DoWhileStatement(dw) => self.count_do_while_statement(dw, ctx), - Statement::ForStatement(fr) => self.count_for_statement(fr, ctx), - Statement::ForInStatement(fi) => self.count_for_in_statement(fi, ctx), - Statement::ForOfStatement(fo) => self.count_for_of_statement(fo, ctx), - _ => {} - } - } -} diff --git a/crates/oxide_compiler/src/emitter/expr/object.rs b/crates/oxide_compiler/src/emitter/expr/object.rs deleted file mode 100644 index 55c7f5ab..00000000 --- a/crates/oxide_compiler/src/emitter/expr/object.rs +++ /dev/null @@ -1,80 +0,0 @@ -use super::*; - -impl Compiler { - fn emit_object_expression(&self, obj: &oxide_parser::ObjectExpression, ctx: &mut CompileCtx) -> Result { - let obj_reg = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::NEW_OBJECT, obj_reg, 0, 0)); - let prop_checkpoint = ctx.reg_checkpoint(); - for prop in &obj.properties { - let oxide_parser::ObjectPropertyKind::ObjectProperty(p) = prop else { - return Err("spread properties not yet supported".into()); - }; - let prop_name = match &p.key { - oxide_parser::PropertyKey::StaticIdentifier(ident) => ident.name.as_str().to_string(), - oxide_parser::PropertyKey::StringLiteral(s) => s.value.to_string(), - _ => return Err("unsupported object property key type".into()), - }; - match p.kind { - PropertyKind::Get | PropertyKind::Set => { - let accessor_reg = self.emit_expression(&p.value, ctx)?; - if let Some(sub_mod) = ctx.sub_modules.last_mut() { - sub_mod.function_name = Some(prop_name.to_string()); - } - let undef_reg = self.emit_undefined(ctx); - let (get_reg, set_reg) = if p.kind == PropertyKind::Get { - (accessor_reg, undef_reg) - } else { - (undef_reg, accessor_reg) - }; - let idx = ctx.add_constant(Constant::String(prop_name.to_string())); - ctx.emit(opcode::encode(OpCode::DEFINE_ACCESSOR, obj_reg, get_reg, set_reg)); - ctx.emit(idx as u32); - ctx.restore_reg_checkpoint(prop_checkpoint); - } - _ => { - let idx = ctx.add_constant(Constant::String(prop_name.to_string())); - let key_reg = ctx.alloc_reg(); - ctx.emit_load_const(key_reg, idx); - let val_reg = self.emit_expression(&p.value, ctx)?; - // Name inference: if property value is an arrow function, - // set the compiled sub_module's function_name to the property key. - if matches!(&p.value, Expression::ArrowFunctionExpression(_)) { - if let Some(sub_mod) = ctx.sub_modules.last_mut() { - sub_mod.function_name = Some(prop_name.to_string()); - } - } - ctx.emit(opcode::encode(OpCode::SET_PROP, obj_reg, val_reg, key_reg)); - ctx.restore_reg_checkpoint(prop_checkpoint); - } - } - } - Ok(obj_reg) - } - - fn emit_array_expression(&self, arr: &oxide_parser::ArrayExpression, ctx: &mut CompileCtx) -> Result { - let arr_reg = ctx.alloc_reg(); - let n = arr.elements.len() as u16; - ctx.emit(opcode::encode(OpCode::NEW_ARRAY, arr_reg, (n & 0xFF) as u8, ((n >> 8) & 0xFF) as u8)); - let elem_checkpoint = ctx.reg_checkpoint(); - for (i, elem) in arr.elements.iter().enumerate() { - let Some(e) = elem.as_expression() else { - return Err("spread not supported".into()); - }; - let val_reg = self.emit_expression(e, ctx)?; - let idx_reg = ctx.alloc_reg(); - let idx = ctx.add_constant(Constant::Int(i as i32)); - ctx.emit_load_const(idx_reg, idx); - ctx.emit(opcode::encode(OpCode::SET_ELEM, arr_reg, idx_reg, val_reg)); - ctx.restore_reg_checkpoint(elem_checkpoint); - } - Ok(arr_reg) - } - - pub(in crate::emitter) fn emit_object_domain(&self, expr: &Expression, ctx: &mut CompileCtx) -> Result { - match expr { - Expression::ObjectExpression(obj) => self.emit_object_expression(obj, ctx), - Expression::ArrayExpression(arr) => self.emit_array_expression(arr, ctx), - _ => self.emit_unsupported_expression(expr, ctx), - } - } -} diff --git a/crates/oxide_compiler/src/emitter/expr/template.rs b/crates/oxide_compiler/src/emitter/expr/template.rs deleted file mode 100644 index 3a7aa948..00000000 --- a/crates/oxide_compiler/src/emitter/expr/template.rs +++ /dev/null @@ -1,138 +0,0 @@ -use super::*; - -impl Compiler { - fn emit_template_literal_expression( - &self, tl: &oxide_parser::TemplateLiteral, ctx: &mut CompileCtx, - ) -> Result { - let r = ctx.alloc_reg(); - let quasis = &tl.quasis; - let expressions = &tl.expressions; - let segment_count = quasis.len() + expressions.len(); - - // Evaluate each expression, collecting registers - let expr_regs: Vec = expressions - .iter() - .map(|e| self.emit_expression(e, ctx)) - .collect::, _>>()?; - - // Add each quasi string to constant pool - let quasi_const_idxs: Vec = quasis - .iter() - .map(|q| { - let s = q.value.cooked.as_ref().map(|c| c.to_string()).unwrap_or_default(); - ctx.add_constant(Constant::String(s)) - }) - .collect(); - - // Compute total length hint - let total_len_hint: usize = quasis - .iter() - .map(|q| q.value.cooked.as_ref().map(|c| c.len()).unwrap_or(0)) - .sum(); - - // Emit TEMPLATE_STR rd, 0, 0 - ctx.emit(opcode::encode(OpCode::TEMPLATE_STR, r, 0, 0)); - - // Ext word 0: (segment_count << 16) | (total_len_hint & 0xFFFF) - ctx.emit(((segment_count as u32) << 16) | (total_len_hint as u32 & 0xFFFF)); - - // Interleave segments: quasi[0], expr[0], quasi[1], expr[1], ... - let mut expr_iter = expr_regs.iter(); - for const_idx in quasi_const_idxs.iter() { - // Quasi: is_expression=0, bits 0-15 = const_idx - ctx.emit(*const_idx as u32 & 0x7FFF_FFFF); - - // Expression (if any remaining) - if let Some(expr_reg) = expr_iter.next() { - // Expression: is_expression=1, bits 0-15 = reg - ctx.emit(0x8000_0000u32 | (*expr_reg as u32)); - } - } - - Ok(r) - } - - fn emit_tagged_template_expression( - &self, tt: &oxide_parser::TaggedTemplateExpression, ctx: &mut CompileCtx, - ) -> Result { - let quasis = &tt.quasi.quasis; - let expressions = &tt.quasi.expressions; - - // 1. Evaluate tag expression - let tag_reg = self.emit_expression(&tt.tag, ctx)?; - - // 2. Build cooked strings array (into a temp register) - let cooked_temp = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::NEW_ARRAY, cooked_temp, quasis.len() as u8, 0)); - for (i, quasi) in quasis.iter().enumerate() { - let s = quasi.value.cooked.as_ref().map(|c| c.to_string()).unwrap_or_default(); - let const_idx = ctx.add_constant(Constant::String(s)); - let str_reg = ctx.alloc_reg(); - ctx.emit_load_const(str_reg, const_idx); - let idx_const = ctx.add_constant(Constant::Int(i as i32)); - let idx_reg = ctx.alloc_reg(); - ctx.emit_load_const(idx_reg, idx_const); - ctx.emit(opcode::encode(OpCode::SET_ELEM, cooked_temp, idx_reg, str_reg)); - } - - // 3. Build raw strings array (into a temp register) - let raw_temp = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::NEW_ARRAY, raw_temp, quasis.len() as u8, 0)); - for (i, quasi) in quasis.iter().enumerate() { - let raw = quasi.value.raw.to_string(); - let const_idx = ctx.add_constant(Constant::String(raw)); - let str_reg = ctx.alloc_reg(); - ctx.emit_load_const(str_reg, const_idx); - let idx_const = ctx.add_constant(Constant::Int(i as i32)); - let idx_reg = ctx.alloc_reg(); - ctx.emit_load_const(idx_reg, idx_const); - ctx.emit(opcode::encode(OpCode::SET_ELEM, raw_temp, idx_reg, str_reg)); - } - - // 4. Evaluate expression arguments (into temp registers) - let mut expr_temps = Vec::new(); - for expr in expressions { - expr_temps.push(self.emit_expression(expr, ctx)?); - } - - // 5. Allocate consecutive argument slots: cooked, raw, expr[0], expr[1], ... - let cooked_slot = ctx.alloc_reg(); - let raw_slot = ctx.alloc_reg(); - let mut expr_slots = Vec::new(); - for _ in expressions { - expr_slots.push(ctx.alloc_reg()); - } - - // Copy temps to consecutive slots using LOAD_VAR (register-to-register move) - ctx.emit(opcode::encode(OpCode::LOAD_VAR, cooked_slot, cooked_temp, 0)); - ctx.emit(opcode::encode(OpCode::LOAD_VAR, raw_slot, raw_temp, 0)); - for (slot, temp) in expr_slots.iter().zip(expr_temps.iter()) { - ctx.emit(opcode::encode(OpCode::LOAD_VAR, *slot, *temp, 0)); - } - - // 6. Emit undefined as this_arg - let undef_idx = ctx.add_constant(Constant::Undefined); - let undef_reg = ctx.alloc_reg(); - ctx.emit_load_const(undef_reg, undef_idx); - - // 7. Emit CALL(tag, undefined, cooked_slot) - let arg_count = 2 + expressions.len(); - ctx.emit(opcode::encode(OpCode::CALL, tag_reg, undef_reg, cooked_slot)); - ctx.emit(arg_count as u32); - - // 8. Result from regs[0] - let result_reg = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, 0, 0)); - Ok(result_reg) - } - - pub(in crate::emitter) fn emit_template_domain( - &self, expr: &Expression, ctx: &mut CompileCtx, - ) -> Result { - match expr { - Expression::TemplateLiteral(tl) => self.emit_template_literal_expression(tl, ctx), - Expression::TaggedTemplateExpression(tt) => self.emit_tagged_template_expression(tt, ctx), - _ => self.emit_unsupported_expression(expr, ctx), - } - } -} diff --git a/crates/oxide_compiler/src/emitter/mod.rs b/crates/oxide_compiler/src/emitter/mod.rs deleted file mode 100644 index af52c918..00000000 --- a/crates/oxide_compiler/src/emitter/mod.rs +++ /dev/null @@ -1,969 +0,0 @@ -#![allow(unused_imports)] -use crate::compiler::Label; -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, - MethodDefinitionKind, ObjectAssignmentTarget, PropertyKey, PropertyKind, SimpleAssignmentTarget, Statement, - UnaryOperator, UpdateOperator, VariableDeclarationKind, -}; - -use crate::compiler::{ - is_int_literal, is_side_effect_free, BinaryOperator, CompileCtx, Compiler, FunctionBodyContext, ParamSpec, -}; - -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)?; - let dup_reg = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, reg, 0)); - let offset = (short_pos as isize) - (ctx.bytecode.len() as isize); - let offset = ctx.checked_jump_offset(offset); - ctx.emit(opcode::encode_jmp_if_nullish(dup_reg, offset)); - Ok(()) - } - - fn emit_static_member_get_preserve_base( - &self, member: &oxide_parser::StaticMemberExpression, short_label: Option