From ba5e095a838f3248b14009456fd7e934510d49ec Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Tue, 23 Jun 2026 16:21:39 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=B0=81=E8=A3=85=E5=AD=97=E8=8A=82?= =?UTF-8?q?=E7=A0=81=E8=AE=A1=E6=95=B0=E5=B9=B6=E4=BF=AE=E5=A4=8D=20for-of?= =?UTF-8?q?=20=E8=A7=A3=E6=9E=84=E5=81=8F=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_compiler/src/compiler.rs | 61 +++ crates/oxide_compiler/src/counter.rs | 381 +++++++++++-------- crates/oxide_vm/tests/destructuring_tests.rs | 14 + 3 files changed, 292 insertions(+), 164 deletions(-) diff --git a/crates/oxide_compiler/src/compiler.rs b/crates/oxide_compiler/src/compiler.rs index d024af41..4525c912 100644 --- a/crates/oxide_compiler/src/compiler.rs +++ b/crates/oxide_compiler/src/compiler.rs @@ -237,6 +237,67 @@ impl CompileCtx { self.emit(opcode::encode(OpCode::LOAD_CONST, reg, (idx & 0xFF) as u8, ((idx >> 8) & 0xFF) as u8)); } + pub(crate) fn count_word(&mut self) { + self.projected_pc += 1; + } + + pub(crate) fn count_words(&mut self, words: usize) { + self.projected_pc += words; + } + + pub(crate) fn count_instr(&mut self) { + self.count_word(); + } + + pub(crate) fn count_instr_with_ext(&mut self, ext_words: usize) { + self.count_words(1 + ext_words); + } + + pub(crate) fn count_load_const(&mut self) { + self.alloc_reg(); + self.count_instr(); + } + + pub(crate) fn count_load_var(&mut self) { + self.alloc_reg(); + self.count_instr(); + } + + pub(crate) fn count_ic_instr_with_ext(&mut self) { + self.count_instr_with_ext(3); + } + + pub(crate) fn count_ic_set_with_ext(&mut self) { + self.count_ic_instr_with_ext(); + } + + pub(crate) fn count_call_instr_with_arg_ext(&mut self) { + self.count_instr_with_ext(1); + } + + pub(crate) fn count_delete_static(&mut self) { + self.count_instr_with_ext(1); + } + + pub(crate) fn count_define_accessor(&mut self) { + self.count_instr_with_ext(1); + } + + pub(crate) fn count_private_access(&mut self) { + self.count_load_const(); + self.alloc_reg(); + self.count_instr(); + } + + pub(crate) fn count_template_str(&mut self, segment_count: usize) { + self.alloc_reg(); + self.count_instr_with_ext(1 + segment_count); + } + + pub(crate) fn count_jump(&mut self) { + self.count_instr(); + } + pub(crate) fn alloc_reg(&mut self) -> u8 { let r = self.next_reg; // Registers 254 (this) and 255 (new.target) are reserved by the VM. diff --git a/crates/oxide_compiler/src/counter.rs b/crates/oxide_compiler/src/counter.rs index d70bad63..28aa84ef 100644 --- a/crates/oxide_compiler/src/counter.rs +++ b/crates/oxide_compiler/src/counter.rs @@ -6,9 +6,44 @@ use oxide_parser::{ use crate::compiler::{is_side_effect_free, CompileCtx, Compiler, Label}; impl Compiler { + fn count_object_property_read_static(&self, ctx: &mut CompileCtx) { + ctx.count_load_var(); // prop/object temp + ctx.count_load_const(); // key reg + ctx.count_ic_instr_with_ext(); // IC_GET_PROP + 3 ext words + } + + fn count_object_property_read_key(&self, key: &oxide_parser::PropertyKey, computed: bool, ctx: &mut CompileCtx) { + if !computed { + self.count_object_property_read_static(ctx); + let _ = key; + return; + } + + ctx.count_load_var(); // prop/object temp + match key { + oxide_parser::PropertyKey::Identifier(ident) => { + let name = ident.name.as_str(); + let _ = ctx.lookup_or_builtin(name); + ctx.count_load_var(); // key + } + oxide_parser::PropertyKey::StringLiteral(_) => { + ctx.count_load_const(); // key + } + oxide_parser::PropertyKey::NumericLiteral(_) => { + ctx.count_load_const(); // key + } + oxide_parser::PropertyKey::StaticIdentifier(_) => { + ctx.count_load_const(); // key + } + _ => {} + } + ctx.alloc_reg(); // result reg + ctx.count_instr(); // GET_PROP_DYNAMIC + } + fn count_optional_guard(&self, ctx: &mut CompileCtx) { - ctx.alloc_reg(); - ctx.projected_pc += 2; // LOAD_VAR dup + JMP_IF_NULLISH + ctx.count_load_var(); // dup + ctx.count_jump(); // JMP_IF_NULLISH } fn count_static_member_get_preserve_base( @@ -18,10 +53,9 @@ impl Compiler { 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 + ctx.count_load_const(); // key + ctx.count_load_var(); // receiver copy + ctx.count_ic_instr_with_ext(); // IC_GET_PROP + 3 ext words } fn count_computed_member_get_preserve_base( @@ -33,7 +67,7 @@ impl Compiler { } self.count_expression(&member.expression, ctx); ctx.alloc_reg(); - ctx.projected_pc += 1; // GET_PROP_DYNAMIC + ctx.count_instr(); // GET_PROP_DYNAMIC } fn count_chain_call(&self, call: &oxide_parser::CallExpression, short_chain: bool, ctx: &mut CompileCtx) { @@ -49,9 +83,7 @@ impl Compiler { if short_chain && member.optional { self.count_optional_guard(ctx); } - ctx.alloc_reg(); - ctx.alloc_reg(); - ctx.projected_pc += 2; + ctx.count_private_access(); } _ => { self.count_chainable_expression(&call.callee, short_chain, ctx); @@ -78,9 +110,8 @@ impl Compiler { 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] + ctx.count_call_instr_with_arg_ext(); // CALL + arg_count ext word + ctx.count_load_var(); // result <- regs[0] } fn count_chainable_expression(&self, expr: &Expression, short_chain: bool, ctx: &mut CompileCtx) { @@ -96,9 +127,7 @@ impl Compiler { if short_chain && member.optional { self.count_optional_guard(ctx); } - ctx.alloc_reg(); - ctx.alloc_reg(); - ctx.projected_pc += 2; + 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), @@ -119,9 +148,7 @@ impl Compiler { if short_chain && member.optional { self.count_optional_guard(ctx); } - ctx.alloc_reg(); - ctx.alloc_reg(); - ctx.projected_pc += 2; + ctx.count_private_access(); } ChainElement::CallExpression(call) => self.count_chain_call(call, short_chain, ctx), ChainElement::TSNonNullExpression(_) => {} @@ -148,12 +175,12 @@ impl Compiler { ctx.projected_pc += 1; } oxide_parser::BindingPattern::ArrayPattern(ap) => { - ctx.projected_pc += 1; // FOR_OF_INIT + ctx.count_instr(); // FOR_OF_INIT for elem in &ap.elements { ctx.alloc_reg(); - ctx.projected_pc += 1; // FOR_OF_DONE + ctx.count_instr(); // FOR_OF_DONE ctx.alloc_reg(); - ctx.projected_pc += 1; // FOR_OF_NEXT + ctx.count_instr(); // FOR_OF_NEXT if let Some(inner) = elem { self.count_binding_pattern(inner, ctx); } @@ -162,27 +189,25 @@ impl Compiler { self.count_rest_array(ctx); self.count_binding_pattern(&rest.argument, ctx); } - ctx.projected_pc += 1; // FOR_OF_CLOSE + ctx.count_instr(); // FOR_OF_CLOSE } oxide_parser::BindingPattern::ObjectPattern(op) => { for prop in &op.properties { - ctx.alloc_reg(); - ctx.projected_pc += 5; // LOAD_VAR + LOAD_CONST + IC_GET_PROP + 3 ext - ctx.alloc_reg(); + 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.projected_pc += 2; // REST_OBJECT + ext + ctx.count_instr_with_ext(1); // REST_OBJECT + excluded-keys ext self.count_binding_pattern(&rest.argument, ctx); } } oxide_parser::BindingPattern::AssignmentPattern(ap) => { - ctx.alloc_reg(); - ctx.projected_pc += 2; // LOAD_CONST undefined + STRICT_EQ - ctx.projected_pc += 1; // JMP_IF_FALSE + ctx.count_load_const(); // undefined + ctx.count_instr(); // STRICT_EQ + ctx.count_jump(); // JMP_IF_FALSE self.count_expression(&ap.right, ctx); - ctx.projected_pc += 1; // LOAD_VAR default + ctx.count_instr(); // LOAD_VAR default self.count_binding_pattern(&ap.left, ctx); } } @@ -190,50 +215,103 @@ impl Compiler { fn count_rest_array(&self, ctx: &mut CompileCtx) { ctx.alloc_reg(); // rest - ctx.projected_pc += 1; // NEW_ARRAY - ctx.alloc_reg(); // idx - ctx.projected_pc += 1; // LOAD_CONST 0 + ctx.count_instr(); // NEW_ARRAY + ctx.count_load_const(); // idx = 0 ctx.alloc_reg(); // has - ctx.projected_pc += 2; // FOR_OF_DONE + JMP_IF_FALSE + ctx.count_instr(); // FOR_OF_DONE + ctx.count_jump(); // JMP_IF_FALSE ctx.alloc_reg(); // val - ctx.projected_pc += 1; // FOR_OF_NEXT - ctx.projected_pc += 1; // SET_ELEM + ctx.count_instr(); // FOR_OF_NEXT + ctx.count_instr(); // SET_ELEM ctx.alloc_reg(); // inc tmp - ctx.projected_pc += 2; // INC_PRE + JMP + ctx.count_instr(); // INC_PRE + ctx.count_jump(); // JMP } fn count_array_assignment(&self, ap: &oxide_parser::ArrayAssignmentTarget, ctx: &mut CompileCtx) { - ctx.projected_pc += 1; // FOR_OF_INIT + ctx.count_instr(); // FOR_OF_INIT for elem in &ap.elements { ctx.alloc_reg(); - ctx.projected_pc += 1; // FOR_OF_DONE + ctx.count_instr(); // FOR_OF_DONE ctx.alloc_reg(); - ctx.projected_pc += 1; // FOR_OF_NEXT + ctx.count_instr(); // FOR_OF_NEXT if elem.is_some() { ctx.alloc_reg(); - ctx.projected_pc += 1; + ctx.count_instr(); // STORE_VAR } } if ap.rest.is_some() { self.count_rest_array(ctx); ctx.alloc_reg(); - ctx.projected_pc += 1; + ctx.count_instr(); // STORE_VAR } - ctx.projected_pc += 1; // FOR_OF_CLOSE + ctx.count_instr(); // FOR_OF_CLOSE } fn count_object_assignment(&self, op: &oxide_parser::ObjectAssignmentTarget, ctx: &mut CompileCtx) { - for _ in &op.properties { - ctx.alloc_reg(); - ctx.projected_pc += 5; // LOAD_VAR + LOAD_CONST + IC_GET_PROP + 3 ext - ctx.alloc_reg(); - ctx.projected_pc += 1; + for prop in &op.properties { + match prop { + oxide_parser::AssignmentTargetProperty::AssignmentTargetPropertyIdentifier(id) => { + self.count_object_property_read_static(ctx); + if let Some(default_expr) = &id.init { + ctx.count_load_const(); // undefined + ctx.count_instr(); // STRICT_EQ + ctx.count_jump(); // JMP_IF_FALSE + self.count_expression(default_expr, ctx); + ctx.count_instr(); // LOAD_VAR default + } + ctx.alloc_reg(); + ctx.count_instr(); // STORE_VAR + } + oxide_parser::AssignmentTargetProperty::AssignmentTargetPropertyProperty(prop) => { + self.count_object_property_read_key(&prop.name, prop.computed, ctx); + self.count_assignment_maybe_default(&prop.binding, ctx); + } + } } if op.rest.is_some() { ctx.alloc_reg(); - ctx.projected_pc += 2; // REST_OBJECT + ext + ctx.count_instr_with_ext(1); // REST_OBJECT + excluded-keys ext ctx.alloc_reg(); - ctx.projected_pc += 1; + ctx.count_instr(); // STORE_VAR + } + } + + fn count_assignment_maybe_default( + &self, target: &oxide_parser::AssignmentTargetMaybeDefault, ctx: &mut CompileCtx, + ) { + match target { + oxide_parser::AssignmentTargetMaybeDefault::AssignmentTargetWithDefault(default) => { + ctx.count_load_const(); // undefined + ctx.count_instr(); // STRICT_EQ + ctx.count_jump(); // JMP_IF_FALSE + self.count_expression(&default.init, ctx); + ctx.count_instr(); // LOAD_VAR default + self.count_assign_target(&default.binding, ctx); + } + oxide_parser::AssignmentTargetMaybeDefault::ArrayAssignmentTarget(ap) => { + self.count_array_assignment(ap, ctx) + } + oxide_parser::AssignmentTargetMaybeDefault::ObjectAssignmentTarget(op) => { + self.count_object_assignment(op, ctx) + } + oxide_parser::AssignmentTargetMaybeDefault::AssignmentTargetIdentifier(_) => { + ctx.alloc_reg(); + ctx.count_instr(); // STORE_VAR + } + _ => {} + } + } + + fn count_assign_target(&self, target: &oxide_parser::AssignmentTarget, ctx: &mut CompileCtx) { + match target { + oxide_parser::AssignmentTarget::AssignmentTargetIdentifier(_) => { + ctx.alloc_reg(); + ctx.count_instr(); // STORE_VAR + } + oxide_parser::AssignmentTarget::ArrayAssignmentTarget(ap) => self.count_array_assignment(ap, ctx), + oxide_parser::AssignmentTarget::ObjectAssignmentTarget(op) => self.count_object_assignment(op, ctx), + _ => {} } } @@ -243,22 +321,25 @@ impl Compiler { if let Some(super_class) = &class.super_class { self.count_expression(super_class, ctx); } - ctx.projected_pc += 2; // LOAD_CONST ctor + NEW_OBJECT proto + ctx.count_instr(); // LOAD_CONST ctor + ctx.count_instr(); // NEW_OBJECT proto if class.super_class.is_some() { ctx.alloc_reg(); // parent prototype key + ctx.count_instr(); // LOAD_CONST ctx.alloc_reg(); // parent prototype value + ctx.count_instr(); // GET_PROP ctx.alloc_reg(); // __proto__ key - ctx.projected_pc += 5; // LOAD_CONST + GET_PROP + LOAD_CONST + two SET_PROP + ctx.count_instr(); // LOAD_CONST + ctx.count_instr(); // SET_PROP proto.__proto__ + ctx.count_instr(); // SET_PROP ctor.__proto__ } - ctx.alloc_reg(); // constructor key - ctx.projected_pc += 1; // LOAD_CONST "constructor" - ctx.projected_pc += 1; // SET_PROP proto.constructor = ctor + ctx.count_load_const(); // constructor key + ctx.count_instr(); // SET_PROP proto.constructor = ctor - ctx.alloc_reg(); // prototype key - ctx.projected_pc += 1; // LOAD_CONST "prototype" - ctx.projected_pc += 1; // SET_PROP ctor.prototype = proto + ctx.count_load_const(); // prototype key + ctx.count_instr(); // SET_PROP ctor.prototype = proto for element in &class.body.body { match element { @@ -269,23 +350,22 @@ impl Compiler { } if matches!(method.key, oxide_parser::PropertyKey::PrivateIdentifier(_)) { if method.r#static { - ctx.alloc_reg(); - ctx.alloc_reg(); - ctx.projected_pc += 3; + ctx.count_load_const(); // private id + ctx.count_load_const(); // method + ctx.count_instr(); // INIT_PRIVATE } continue; } - ctx.alloc_reg(); // key_reg - ctx.projected_pc += 1; - ctx.alloc_reg(); // method_reg - ctx.projected_pc += 2; // LOAD_CONST method + SET_HOME_OBJECT + ctx.count_load_const(); // key_reg + ctx.count_load_const(); // method_reg + ctx.count_instr(); // SET_HOME_OBJECT match method.kind { MethodDefinitionKind::Method => { - ctx.projected_pc += 1; // SET_PROP + ctx.count_instr(); // SET_PROP } MethodDefinitionKind::Get | MethodDefinitionKind::Set => { - ctx.alloc_reg(); // undefined placeholder - ctx.projected_pc += 3; // LOAD_CONST undefined + DEFINE_ACCESSOR + ext + ctx.count_load_const(); // undefined placeholder + ctx.count_define_accessor(); // DEFINE_ACCESSOR + ext } MethodDefinitionKind::Constructor => {} } @@ -293,15 +373,13 @@ impl Compiler { ClassElement::PropertyDefinition(prop) => { let prop = prop.as_ref(); if prop.r#static { - ctx.alloc_reg(); // key_reg - ctx.projected_pc += 1; + ctx.count_load_const(); // key_reg if let Some(value) = &prop.value { self.count_expression(value, ctx); } else { - ctx.alloc_reg(); - ctx.projected_pc += 1; + ctx.count_load_const(); } - ctx.projected_pc += 1; // SET_PROP + ctx.count_instr(); // SET_PROP } } ClassElement::StaticBlock(block) => { @@ -326,7 +404,7 @@ impl Compiler { self.count_binding_pattern(&d.id, ctx); } else { ctx.alloc_reg(); - ctx.projected_pc += 2; // LOAD_CONST(undefined) + STORE_VAR + ctx.count_words(2); // LOAD_CONST(undefined) + STORE_VAR } } } @@ -395,7 +473,7 @@ impl Compiler { self.count_binding_pattern(&d.id, ctx); } else { ctx.alloc_reg(); - ctx.projected_pc += 2; // LOAD_CONST(undefined) + STORE_VAR + ctx.count_words(2); // LOAD_CONST(undefined) + STORE_VAR } } } @@ -419,31 +497,33 @@ impl Compiler { let end_label = Label::ForInEnd(id); self.count_expression(&fi.right, ctx); - ctx.projected_pc += 1; // FOR_IN_INIT + ctx.count_instr(); // FOR_IN_INIT ctx.label_map.insert(start_label, ctx.projected_pc); - ctx.projected_pc += 3; // FOR_IN_DONE + JMP_IF_FALSE + JMP cleanup + ctx.count_instr(); // FOR_IN_DONE + ctx.count_jump(); // JMP_IF_FALSE + ctx.count_jump(); // JMP cleanup - ctx.projected_pc += 1; // FOR_IN_NEXT + ctx.count_instr(); // FOR_IN_NEXT match &fi.left { oxide_parser::ForStatementLeft::VariableDeclaration(decl) => { for _d in &decl.declarations { ctx.alloc_reg(); - ctx.projected_pc += 1; // STORE_VAR (value from FOR_IN_NEXT) + ctx.count_instr(); // STORE_VAR } } oxide_parser::ForStatementLeft::AssignmentTargetIdentifier(_) => { ctx.alloc_reg(); // key register - ctx.projected_pc += 1; // STORE_VAR + ctx.count_instr(); // STORE_VAR } _ => {} } self.count_statement(&fi.body, ctx); - ctx.projected_pc += 1; // JMP back to start + ctx.count_jump(); // JMP back to start ctx.label_map.insert(end_label, ctx.projected_pc); - ctx.projected_pc += 1; // FOR_IN_CLEANUP + ctx.count_instr(); // FOR_IN_CLEANUP } Statement::ForOfStatement(fo) => { let id = ctx.next_label_id(); @@ -451,12 +531,13 @@ impl Compiler { let end_label = Label::ForOfEnd(id); self.count_expression(&fo.right, ctx); - ctx.projected_pc += 1; // FOR_OF_INIT + ctx.count_instr(); // FOR_OF_INIT ctx.label_map.insert(start_label, ctx.projected_pc); ctx.alloc_reg(); // has_reg - ctx.projected_pc += 2; // FOR_OF_DONE + JMP_IF_FALSE + ctx.count_instr(); // FOR_OF_DONE + ctx.count_jump(); // JMP_IF_FALSE ctx.alloc_reg(); // val_reg - ctx.projected_pc += 1; // FOR_OF_NEXT + ctx.count_instr(); // FOR_OF_NEXT match &fo.left { oxide_parser::ForStatementLeft::VariableDeclaration(decl) => { for d in &decl.declarations { @@ -465,7 +546,7 @@ impl Compiler { } oxide_parser::ForStatementLeft::AssignmentTargetIdentifier(_) => { ctx.alloc_reg(); - ctx.projected_pc += 1; + ctx.count_instr(); // STORE_VAR } oxide_parser::ForStatementLeft::ArrayAssignmentTarget(ap) => { self.count_array_assignment(ap, ctx); @@ -476,9 +557,9 @@ impl Compiler { _ => {} } self.count_statement(&fo.body, ctx); - ctx.projected_pc += 1; // JMP back + ctx.count_jump(); // JMP back ctx.label_map.insert(end_label, ctx.projected_pc); - ctx.projected_pc += 1; // FOR_OF_CLOSE + ctx.count_instr(); // FOR_OF_CLOSE } Statement::SwitchStatement(sw) => { let id = ctx.next_label_id(); @@ -539,7 +620,7 @@ impl Compiler { // Body is compiled in the emit pass only. // FD emits LOAD_CONST(BytecodeFunc) + STORE_VAR - ctx.projected_pc += 2; + ctx.count_words(2); } Statement::ClassDeclaration(class) => { ctx.alloc_reg(); // class binding reg @@ -624,9 +705,7 @@ impl Compiler { } Expression::PrivateInExpression(pin) => { self.count_expression(&pin.right, ctx); - ctx.alloc_reg(); - ctx.alloc_reg(); - ctx.projected_pc += 2; + ctx.count_private_access(); } Expression::UnaryExpression(un) => { match un.operator { @@ -637,7 +716,7 @@ impl Compiler { } Expression::StaticMemberExpression(member) => { self.count_expression(&member.object, ctx); - ctx.projected_pc += 2; // DELETE_PROP_STATIC + ext word + ctx.count_delete_static(); } Expression::ComputedMemberExpression(member) => { self.count_expression(&member.object, ctx); @@ -651,7 +730,7 @@ impl Compiler { if member.optional { self.count_optional_guard(ctx); } - ctx.projected_pc += 2; // DELETE_PROP_STATIC + ext word + ctx.count_delete_static(); } ChainElement::ComputedMemberExpression(member) => { self.count_expression(&member.object, ctx); @@ -683,30 +762,25 @@ impl Compiler { } } ctx.alloc_reg(); - ctx.projected_pc += 2; // SUPER_CALL + arg_count ext word + 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.alloc_reg(); - ctx.alloc_reg(); - ctx.projected_pc += 2; + ctx.count_private_access(); } Expression::StaticMemberExpression(member) => { if matches!(&member.object, Expression::Super(_)) { - ctx.alloc_reg(); // this register - ctx.projected_pc += 1; // LOAD_VAR this - ctx.alloc_reg(); // key - ctx.projected_pc += 1; // LOAD_CONST key + ctx.count_load_var(); // this register + ctx.count_load_const(); // key ctx.alloc_reg(); // callee - ctx.projected_pc += 1; // SUPER_GET_PROP + ctx.count_instr(); // SUPER_GET_PROP } else { self.count_expression(&member.object, ctx); - ctx.alloc_reg(); - ctx.projected_pc += 1; - ctx.alloc_reg(); - ctx.projected_pc += 5; // LOAD_VAR + IC_GET_PROP + 3 ext words + ctx.count_load_const(); // key + ctx.count_load_var(); // callee object copy + ctx.count_ic_instr_with_ext(); // IC_GET_PROP + 3 ext words } } _ => { @@ -720,23 +794,20 @@ impl Compiler { self.count_expression(expr, ctx); } } - // CALL and CALL_NATIVE both carry 1 ext word for arg_count. - ctx.projected_pc += 2; - ctx.alloc_reg(); // result register - ctx.projected_pc += 1; // LOAD_VAR result <- regs[0] + 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.alloc_reg(); - ctx.projected_pc += 1; // LOAD_CONST key - ctx.alloc_reg(); - ctx.projected_pc += 5; // LOAD_VAR + IC_GET_PROP + 3 ext + 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.projected_pc += 4; // IC_SET_PROP + 3 ext + 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; @@ -747,10 +818,8 @@ impl Compiler { ctx.alloc_reg(); ctx.projected_pc += 1; } - ctx.alloc_reg(); - ctx.projected_pc += 1; // LOAD_CONST key - ctx.projected_pc += 1; // IC_SET_PROP or COMPOUND_MEMBER_* - ctx.projected_pc += 3; // 3 IC ext words + 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(); @@ -773,8 +842,8 @@ impl Compiler { } else if let oxide_parser::AssignmentTarget::PrivateFieldExpression(member) = &assign.left { self.count_expression(&member.object, ctx); self.count_expression(&assign.right, ctx); - ctx.alloc_reg(); - ctx.projected_pc += 2; // LOAD_CONST private id + SET_PRIVATE + 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(); @@ -874,8 +943,8 @@ impl Compiler { 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.alloc_reg(); // undefined getter/setter placeholder - ctx.projected_pc += 3; // LOAD_CONST undefined + DEFINE_ACCESSOR + ext + 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 @@ -892,10 +961,8 @@ impl Compiler { for expr in &tl.expressions { self.count_expression(expr, ctx); } - ctx.alloc_reg(); // result reg let segment_count = tl.quasis.len() + tl.expressions.len(); - // TEMPLATE_STR opcode + ext words (1 header + ceil(segments/1) per u32) - ctx.projected_pc += 1 + 1 + segment_count; + ctx.count_template_str(segment_count); } Expression::TaggedTemplateExpression(tt) => { // Tagged template: tag expr + quasis as LOAD_CONST + expression args + CALL @@ -905,26 +972,22 @@ impl Compiler { // Each quasi: LOAD_CONST string + LOAD_CONST index + SET_ELEM // for both cooked and raw arrays for _ in 0..quasi_count { - ctx.alloc_reg(); // str_reg - ctx.projected_pc += 1; // LOAD_CONST string - ctx.alloc_reg(); // idx_reg - ctx.projected_pc += 1; // LOAD_CONST index - ctx.projected_pc += 1; // SET_ELEM (cooked) + 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.projected_pc += 1; // NEW_ARRAY + ctx.count_instr(); // NEW_ARRAY for _ in 0..quasi_count { - ctx.alloc_reg(); // str_reg - ctx.projected_pc += 1; // LOAD_CONST string - ctx.alloc_reg(); // idx_reg - ctx.projected_pc += 1; // LOAD_CONST index - ctx.projected_pc += 1; // SET_ELEM (raw) + 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.projected_pc += 1; // NEW_ARRAY + ctx.count_instr(); // NEW_ARRAY // Count expression arguments for expr in &tt.quasi.expressions { @@ -936,21 +999,19 @@ impl Compiler { ctx.alloc_reg(); // raw_slot for _ in &tt.quasi.expressions { ctx.alloc_reg(); // expr_slot - ctx.projected_pc += 1; // LOAD_VAR + ctx.count_instr(); // LOAD_VAR } // LOAD_VAR for cooked and raw - ctx.projected_pc += 2; + ctx.count_words(2); // undefined this arg - ctx.alloc_reg(); - ctx.projected_pc += 1; // LOAD_CONST + ctx.count_load_const(); // CALL + ext word - ctx.projected_pc += 2; + ctx.count_call_instr_with_arg_ext(); // Result reg + LOAD_VAR - ctx.alloc_reg(); - ctx.projected_pc += 1; // LOAD_VAR + ctx.count_load_var(); } Expression::ArrowFunctionExpression(_arrow) => { // Arrow functions: alloc 1 register for the function value. @@ -977,7 +1038,7 @@ impl Compiler { } } ctx.alloc_reg(); // result register - ctx.projected_pc += 2; // NEW_EXPRESSION + ext word + ctx.count_instr_with_ext(1); // NEW_EXPRESSION + arg_count ext word } Expression::ArrayExpression(arr) => { ctx.alloc_reg(); @@ -995,19 +1056,15 @@ impl Compiler { } Expression::StaticMemberExpression(member) => { if matches!(&member.object, Expression::Super(_)) { - ctx.alloc_reg(); // key - ctx.projected_pc += 1; // LOAD_CONST key - ctx.alloc_reg(); // this - ctx.projected_pc += 1; // LOAD_VAR this + ctx.count_load_const(); // key + ctx.count_load_var(); // this ctx.alloc_reg(); // result - ctx.projected_pc += 1; // SUPER_GET_PROP + ctx.count_instr(); // SUPER_GET_PROP return; } self.count_expression(&member.object, ctx); - ctx.alloc_reg(); - ctx.projected_pc += 1; // LOAD_CONST key - ctx.projected_pc += 1; // IC_GET_PROP - ctx.projected_pc += 3; // 3 IC ext words + 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); @@ -1017,9 +1074,7 @@ impl Compiler { } Expression::PrivateFieldExpression(member) => { self.count_expression(&member.object, ctx); - ctx.alloc_reg(); - ctx.alloc_reg(); - ctx.projected_pc += 2; + ctx.count_private_access(); } Expression::ParenthesizedExpression(p) => { self.count_expression(&p.expression, ctx); @@ -1042,11 +1097,9 @@ impl Compiler { } SimpleAssignmentTarget::StaticMemberExpression(member) => { self.count_expression(&member.object, ctx); + ctx.count_load_const(); // key ctx.alloc_reg(); - ctx.projected_pc += 1; - ctx.alloc_reg(); - ctx.projected_pc += 1; // MEMBER_INC/MEMBER_DEC opcode - ctx.projected_pc += 3; // 3 IC ext words + ctx.count_ic_instr_with_ext(); // MEMBER_INC/MEMBER_DEC + 3 ext words } SimpleAssignmentTarget::ComputedMemberExpression(member) => { self.count_expression(&member.object, ctx); diff --git a/crates/oxide_vm/tests/destructuring_tests.rs b/crates/oxide_vm/tests/destructuring_tests.rs index 9c651882..285f59d8 100644 --- a/crates/oxide_vm/tests/destructuring_tests.rs +++ b/crates/oxide_vm/tests/destructuring_tests.rs @@ -63,6 +63,20 @@ fn for_of_destructuring_left_side() { assert_num(result, 10.0); } +#[test] +fn for_of_object_destructuring_assignment_target_runs_once() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "var x = null; var count = 0; for ({ x } of [{ x: 3 }]) { count += x; } count").unwrap(); + assert_num(result, 3.0); +} + +#[test] +fn for_of_object_destructuring_lexical_binding_runs_once() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "let count = 0; for (let { x: [y], } of [{ x: [45] }]) { count += y; } count").unwrap(); + assert_num(result, 45.0); +} + #[test] fn function_and_method_parameter_destructuring() { let mut vm = Vm::new();