From 1016f432f1a2b334965b691dd272fc23513f1c1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=B1=E7=89=B9=E6=AF=8F=E7=BE=8A=E9=98=9F?= Date: Thu, 18 Jun 2026 15:26:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=8F=AF=E9=80=89?= =?UTF-8?q?=E9=93=BE=E7=A9=BA=E5=80=BC=E5=90=88=E5=B9=B6=E4=B8=8E=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E8=B5=8B=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_compiler/src/compiler.rs | 33 +- crates/oxide_compiler/src/counter.rs | 223 +++++++++- crates/oxide_compiler/src/emitter.rs | 391 ++++++++++++++++-- crates/oxide_compiler/src/hash.rs | 50 ++- crates/oxide_compiler/src/opcode.rs | 12 + .../tests/compile_expr_tests.rs | 57 ++- .../tests/structural_hash_tests.rs | 31 ++ crates/oxide_test262/src/main.rs | 1 - crates/oxide_types/src/value.rs | 13 + crates/oxide_vm/src/dispatch/control_flow.rs | 6 + crates/oxide_vm/src/vm.rs | 36 +- crates/oxide_vm/tests/logical_tests.rs | 73 ++++ crates/oxide_vm/tests/regression_tests.rs | 22 +- crates/oxide_vm/tests/scoping_tests.rs | 8 +- 14 files changed, 869 insertions(+), 87 deletions(-) diff --git a/crates/oxide_compiler/src/compiler.rs b/crates/oxide_compiler/src/compiler.rs index 3f8e9b3..7032054 100644 --- a/crates/oxide_compiler/src/compiler.rs +++ b/crates/oxide_compiler/src/compiler.rs @@ -17,21 +17,28 @@ pub(crate) fn is_int_literal(value: f64) -> bool { } pub(crate) fn is_side_effect_free(expr: &Expression) -> bool { - match expr { - Expression::NumericLiteral(_) - | Expression::StringLiteral(_) - | Expression::BooleanLiteral(_) - | Expression::NullLiteral(_) - | Expression::Identifier(_) - | Expression::RegExpLiteral(_) - | Expression::ThisExpression(_) => true, - Expression::ParenthesizedExpression(p) => is_side_effect_free(&p.expression), - Expression::BinaryExpression(bin) => is_side_effect_free(&bin.left) && is_side_effect_free(&bin.right), - Expression::UnaryExpression(un) => { - !matches!(un.operator, UnaryOperator::Delete) && is_side_effect_free(&un.argument) + let mut stack = vec![expr]; + while let Some(expr) = stack.pop() { + match expr { + Expression::NumericLiteral(_) + | Expression::StringLiteral(_) + | Expression::BooleanLiteral(_) + | Expression::NullLiteral(_) + | Expression::Identifier(_) + | Expression::RegExpLiteral(_) + | Expression::ThisExpression(_) => {} + Expression::ParenthesizedExpression(p) => stack.push(&p.expression), + Expression::BinaryExpression(bin) => { + stack.push(&bin.left); + stack.push(&bin.right); + } + Expression::UnaryExpression(un) if !matches!(un.operator, UnaryOperator::Delete) => { + stack.push(&un.argument); + } + _ => return false, } - _ => false, } + true } const BUILTIN_GLOBALS: &[&str] = &[ diff --git a/crates/oxide_compiler/src/counter.rs b/crates/oxide_compiler/src/counter.rs index 695dabf..d70bad6 100644 --- a/crates/oxide_compiler/src/counter.rs +++ b/crates/oxide_compiler/src/counter.rs @@ -1,11 +1,146 @@ use oxide_parser::{ - AssignmentOperator, ClassElement, Expression, ForStatementInit, MethodDefinitionKind, SimpleAssignmentTarget, - Statement, UnaryOperator, VariableDeclarationKind, + 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_optional_guard(&self, ctx: &mut CompileCtx) { + ctx.alloc_reg(); + ctx.projected_pc += 2; // LOAD_VAR dup + 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.alloc_reg(); + ctx.projected_pc += 1; // LOAD_CONST key + ctx.alloc_reg(); + ctx.projected_pc += 5; // LOAD_VAR copy + 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.projected_pc += 1; // 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.alloc_reg(); + ctx.alloc_reg(); + ctx.projected_pc += 2; + } + _ => { + 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.projected_pc += 2; // CALL + arg_count ext word + ctx.alloc_reg(); + ctx.projected_pc += 1; // 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.alloc_reg(); + ctx.alloc_reg(); + ctx.projected_pc += 2; + } + 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.alloc_reg(); + ctx.alloc_reg(); + ctx.projected_pc += 2; + } + 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(_) => { @@ -509,6 +644,28 @@ impl Compiler { 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.projected_pc += 2; // DELETE_PROP_STATIC + ext word + } + 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 + } _ => {} } } @@ -570,6 +727,20 @@ impl Compiler { } 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.alloc_reg(); + ctx.projected_pc += 1; // LOAD_CONST key + ctx.alloc_reg(); + ctx.projected_pc += 5; // LOAD_VAR + IC_GET_PROP + 3 ext + self.count_logical_assign_test(logical_op, id, ctx); + self.count_expression(&assign.right, ctx); + ctx.projected_pc += 4; // 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 { @@ -581,6 +752,19 @@ impl Compiler { ctx.projected_pc += 1; // IC_SET_PROP or COMPOUND_MEMBER_* ctx.projected_pc += 3; // 3 IC 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); @@ -591,6 +775,25 @@ impl Compiler { self.count_expression(&assign.right, ctx); ctx.alloc_reg(); ctx.projected_pc += 2; // LOAD_CONST private id + 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); @@ -638,7 +841,11 @@ impl Compiler { 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 or JMP_IF_TRUE + 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 { @@ -649,6 +856,16 @@ impl Compiler { 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 diff --git a/crates/oxide_compiler/src/emitter.rs b/crates/oxide_compiler/src/emitter.rs index 8556d3a..0c24482 100644 --- a/crates/oxide_compiler/src/emitter.rs +++ b/crates/oxide_compiler/src/emitter.rs @@ -2,9 +2,9 @@ use crate::compiler::Label; use crate::opcode::{self, OpCode}; use oxide_parser::{ AssignmentOperator, AssignmentTarget, AssignmentTargetMaybeDefault, AssignmentTargetProperty, BindingPattern, - Class, ClassElement, Expression, ForStatementInit, ForStatementLeft, MethodDefinitionKind, ObjectAssignmentTarget, - PropertyKey, PropertyKind, SimpleAssignmentTarget, Statement, UnaryOperator, UpdateOperator, - VariableDeclarationKind, + ChainElement, Class, ClassElement, Expression, ForStatementInit, ForStatementLeft, LogicalOperator, + MethodDefinitionKind, ObjectAssignmentTarget, PropertyKey, PropertyKind, SimpleAssignmentTarget, Statement, + UnaryOperator, UpdateOperator, VariableDeclarationKind, }; use crate::compiler::{ @@ -13,6 +13,201 @@ use crate::compiler::{ 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)?; + 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); + ctx.emit(opcode::encode_jmp_if_nullish(dup_reg, offset as i16)); + Ok(()) + } + + fn emit_static_member_get_preserve_base( + &self, member: &oxide_parser::StaticMemberExpression, short_label: Option