diff --git a/crates/oxide_compiler/src/counter/expr/assignment.rs b/crates/oxide_compiler/src/counter/expr/assignment.rs new file mode 100644 index 0000000..17d5147 --- /dev/null +++ b/crates/oxide_compiler/src/counter/expr/assignment.rs @@ -0,0 +1,106 @@ +use super::*; + +impl Compiler { + pub(in crate::counter) fn count_assignment_expression(&self, expr: &Expression, ctx: &mut CompileCtx) { + let Expression::AssignmentExpression(assign) = expr else { + return; + }; + + if let oxide_parser::AssignmentTarget::StaticMemberExpression(member) = &assign.left { + if let Some(logical_op) = assign.operator.to_logical_operator() { + self.count_static_member_logical_assignment(member, logical_op, &assign.right, ctx); + 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() { + self.count_computed_member_logical_assignment(member, logical_op, &assign.right, ctx); + 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() { + self.count_identifier_logical_assignment(logical_op, &assign.right, ctx); + } 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; + } + } + + fn count_static_member_logical_assignment( + &self, member: &oxide_parser::StaticMemberExpression<'_>, logical_op: LogicalOperator, right: &Expression, + ctx: &mut CompileCtx, + ) { + 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(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); + } + + fn count_computed_member_logical_assignment( + &self, member: &oxide_parser::ComputedMemberExpression<'_>, logical_op: LogicalOperator, right: &Expression, + ctx: &mut CompileCtx, + ) { + 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(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); + } + + fn count_identifier_logical_assignment( + &self, logical_op: LogicalOperator, right: &Expression, ctx: &mut CompileCtx, + ) { + 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(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); + } +} diff --git a/crates/oxide_compiler/src/counter/expr/call.rs b/crates/oxide_compiler/src/counter/expr/call.rs new file mode 100644 index 0000000..9407318 --- /dev/null +++ b/crates/oxide_compiler/src/counter/expr/call.rs @@ -0,0 +1,66 @@ +use super::*; + +impl Compiler { + pub(in crate::counter) fn count_call_expression(&self, expr: &Expression, ctx: &mut CompileCtx) { + let Expression::CallExpression(call) = expr else { + return; + }; + 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] + } + + pub(in crate::counter) fn count_new_expression(&self, expr: &Expression, ctx: &mut CompileCtx) { + let Expression::NewExpression(ne) = expr else { + return; + }; + self.count_expression(&ne.callee, ctx); + 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 + } +} diff --git a/crates/oxide_compiler/src/counter/expr/chain.rs b/crates/oxide_compiler/src/counter/expr/chain.rs new file mode 100644 index 0000000..81d6c03 --- /dev/null +++ b/crates/oxide_compiler/src/counter/expr/chain.rs @@ -0,0 +1,17 @@ +use super::*; + +impl Compiler { + pub(in crate::counter) fn count_chain_expression(&self, expr: &Expression, ctx: &mut CompileCtx) { + let Expression::ChainExpression(chain) = expr else { + return; + }; + 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); + } +} diff --git a/crates/oxide_compiler/src/counter/expr/control.rs b/crates/oxide_compiler/src/counter/expr/control.rs new file mode 100644 index 0000000..d21a608 --- /dev/null +++ b/crates/oxide_compiler/src/counter/expr/control.rs @@ -0,0 +1,64 @@ +use super::*; + +impl Compiler { + pub(in crate::counter) fn count_conditional_expression(&self, expr: &Expression, ctx: &mut CompileCtx) { + let Expression::ConditionalExpression(cond) = expr else { + return; + }; + 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); + } + + pub(in crate::counter) fn count_sequence_expression(&self, expr: &Expression, ctx: &mut CompileCtx) { + let Expression::SequenceExpression(seq) = expr else { + return; + }; + for e in &seq.expressions { + self.count_expression(e, ctx); + } + } + + pub(in crate::counter) fn count_logical_expression(&self, expr: &Expression, ctx: &mut CompileCtx) { + let Expression::LogicalExpression(log) = expr else { + return; + }; + 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 { + 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); + } + } +} diff --git a/crates/oxide_compiler/src/counter/expr/function.rs b/crates/oxide_compiler/src/counter/expr/function.rs new file mode 100644 index 0000000..d79c833 --- /dev/null +++ b/crates/oxide_compiler/src/counter/expr/function.rs @@ -0,0 +1,20 @@ +use super::*; + +impl Compiler { + pub(in crate::counter) fn count_arrow_function_expression(&self, ctx: &mut CompileCtx) { + ctx.alloc_reg(); + ctx.projected_pc += 1; // LOAD_CONST + } + + pub(in crate::counter) fn count_function_expression(&self, ctx: &mut CompileCtx) { + ctx.alloc_reg(); + ctx.projected_pc += 1; // LOAD_CONST + } + + pub(in crate::counter) fn count_class_expression(&self, expr: &Expression, ctx: &mut CompileCtx) { + let Expression::ClassExpression(class) = expr else { + return; + }; + self.count_class(class, ctx); + } +} diff --git a/crates/oxide_compiler/src/counter/expr/literal.rs b/crates/oxide_compiler/src/counter/expr/literal.rs new file mode 100644 index 0000000..e323442 --- /dev/null +++ b/crates/oxide_compiler/src/counter/expr/literal.rs @@ -0,0 +1,29 @@ +use super::*; + +impl Compiler { + pub(in crate::counter) fn count_this_expression(&self, ctx: &mut CompileCtx) { + ctx.alloc_reg(); + ctx.projected_pc += 1; // LOAD_VAR from reg 254 + } + + pub(in crate::counter) fn count_identifier_expression(&self, expr: &Expression, ctx: &mut CompileCtx) { + let Expression::Identifier(ident) = expr else { + return; + }; + 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 + } + + pub(in crate::counter) fn count_regexp_literal(&self, ctx: &mut CompileCtx) { + ctx.alloc_reg(); + ctx.projected_pc += 1; + } + + pub(in crate::counter) fn count_default_expression(&self, ctx: &mut CompileCtx) { + ctx.alloc_reg(); + ctx.projected_pc += 1; // LOAD_CONST or LOAD_VAR + } +} diff --git a/crates/oxide_compiler/src/counter/expr/member.rs b/crates/oxide_compiler/src/counter/expr/member.rs new file mode 100644 index 0000000..4b10e3b --- /dev/null +++ b/crates/oxide_compiler/src/counter/expr/member.rs @@ -0,0 +1,44 @@ +use super::*; + +impl Compiler { + pub(in crate::counter) fn count_static_member_expression(&self, expr: &Expression, ctx: &mut CompileCtx) { + let Expression::StaticMemberExpression(member) = expr else { + return; + }; + 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 + } + + pub(in crate::counter) fn count_computed_member_expression(&self, expr: &Expression, ctx: &mut CompileCtx) { + let Expression::ComputedMemberExpression(member) = expr else { + return; + }; + self.count_expression(&member.object, ctx); + self.count_expression(&member.expression, ctx); + ctx.alloc_reg(); + ctx.projected_pc += 1; // GET_PROP_DYNAMIC + } + + pub(in crate::counter) fn count_private_field_expression(&self, expr: &Expression, ctx: &mut CompileCtx) { + let Expression::PrivateFieldExpression(member) = expr else { + return; + }; + self.count_expression(&member.object, ctx); + ctx.count_private_access(); + } + + pub(in crate::counter) fn count_parenthesized_expression(&self, expr: &Expression, ctx: &mut CompileCtx) { + let Expression::ParenthesizedExpression(p) = expr else { + return; + }; + self.count_expression(&p.expression, ctx); + } +} diff --git a/crates/oxide_compiler/src/counter/expr/mod.rs b/crates/oxide_compiler/src/counter/expr/mod.rs index 82aa3dc..1efb4c5 100644 --- a/crates/oxide_compiler/src/counter/expr/mod.rs +++ b/crates/oxide_compiler/src/counter/expr/mod.rs @@ -1,434 +1,45 @@ use super::*; +mod assignment; +mod call; +mod chain; +mod control; +mod function; +mod literal; +mod member; +mod object; +mod operator; +mod template; + 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 - } + Expression::BinaryExpression(_) => self.count_binary_expression(expr, ctx), + Expression::PrivateInExpression(_) => self.count_private_in_expression(expr, ctx), + Expression::UnaryExpression(_) => self.count_unary_expression(expr, ctx), + Expression::CallExpression(_) => self.count_call_expression(expr, ctx), + Expression::AssignmentExpression(_) => self.count_assignment_expression(expr, ctx), + Expression::ConditionalExpression(_) => self.count_conditional_expression(expr, ctx), + Expression::SequenceExpression(_) => self.count_sequence_expression(expr, ctx), + Expression::LogicalExpression(_) => self.count_logical_expression(expr, ctx), + Expression::ChainExpression(_) => self.count_chain_expression(expr, ctx), + Expression::ObjectExpression(_) => self.count_object_expression(expr, ctx), + Expression::TemplateLiteral(_) => self.count_template_literal(expr, ctx), + Expression::TaggedTemplateExpression(_) => self.count_tagged_template_expression(expr, ctx), + Expression::ArrowFunctionExpression(_) => self.count_arrow_function_expression(ctx), + Expression::FunctionExpression(_) => self.count_function_expression(ctx), + Expression::ClassExpression(_) => self.count_class_expression(expr, ctx), + Expression::NewExpression(_) => self.count_new_expression(expr, ctx), + Expression::ArrayExpression(_) => self.count_array_expression(expr, ctx), + Expression::StaticMemberExpression(_) => self.count_static_member_expression(expr, ctx), + Expression::ComputedMemberExpression(_) => self.count_computed_member_expression(expr, ctx), + Expression::PrivateFieldExpression(_) => self.count_private_field_expression(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::UpdateExpression(_) => self.count_update_expression(expr, ctx), + Expression::RegExpLiteral(_) => self.count_regexp_literal(ctx), + _ => self.count_default_expression(ctx), } } } diff --git a/crates/oxide_compiler/src/counter/expr/object.rs b/crates/oxide_compiler/src/counter/expr/object.rs new file mode 100644 index 0000000..1a55565 --- /dev/null +++ b/crates/oxide_compiler/src/counter/expr/object.rs @@ -0,0 +1,45 @@ +use super::*; + +impl Compiler { + pub(in crate::counter) 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); + } + } + } + + pub(in crate::counter) 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); + } + } + } +} diff --git a/crates/oxide_compiler/src/counter/expr/operator.rs b/crates/oxide_compiler/src/counter/expr/operator.rs new file mode 100644 index 0000000..c4141de --- /dev/null +++ b/crates/oxide_compiler/src/counter/expr/operator.rs @@ -0,0 +1,109 @@ +use super::*; + +impl Compiler { + pub(in crate::counter) fn count_binary_expression(&self, expr: &Expression, ctx: &mut CompileCtx) { + let Expression::BinaryExpression(bin) = expr else { + return; + }; + 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)); + } + } + + pub(in crate::counter) fn count_private_in_expression(&self, expr: &Expression, ctx: &mut CompileCtx) { + let Expression::PrivateInExpression(pin) = expr else { + return; + }; + self.count_expression(&pin.right, ctx); + ctx.count_private_access(); + } + + pub(in crate::counter) fn count_unary_expression(&self, expr: &Expression, ctx: &mut CompileCtx) { + let Expression::UnaryExpression(un) = expr else { + return; + }; + match un.operator { + UnaryOperator::Delete => self.count_delete_expression(&un.argument, ctx), + _ => { + self.count_expression(&un.argument, ctx); + ctx.projected_pc += 1; // NEG/TYPEOF/VOID/NOT + } + } + } + + fn count_delete_expression(&self, argument: &Expression, ctx: &mut CompileCtx) { + match 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) => { + self.count_delete_chain_expression(&chain.expression, ctx); + } + _ => {} + } + } + + fn count_delete_chain_expression(&self, chain: &ChainElement, ctx: &mut CompileCtx) { + match chain { + 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 + } + + pub(in crate::counter) fn count_update_expression(&self, expr: &Expression, ctx: &mut CompileCtx) { + let Expression::UpdateExpression(update) = expr else { + return; + }; + 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; + } + } + } +} diff --git a/crates/oxide_compiler/src/counter/expr/template.rs b/crates/oxide_compiler/src/counter/expr/template.rs new file mode 100644 index 0000000..b4f064c --- /dev/null +++ b/crates/oxide_compiler/src/counter/expr/template.rs @@ -0,0 +1,53 @@ +use super::*; + +impl Compiler { + pub(in crate::counter) 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); + } + + pub(in crate::counter) 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] + } +}