fix: 封装字节码计数并修复 for-of 解构偏移

This commit is contained in:
2026-06-23 16:21:39 +08:00
parent b1cd4661ed
commit ba5e095a83
3 changed files with 292 additions and 164 deletions
+61
View File
@@ -237,6 +237,67 @@ impl CompileCtx {
self.emit(opcode::encode(OpCode::LOAD_CONST, reg, (idx & 0xFF) as u8, ((idx >> 8) & 0xFF) as u8)); 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 { pub(crate) fn alloc_reg(&mut self) -> u8 {
let r = self.next_reg; let r = self.next_reg;
// Registers 254 (this) and 255 (new.target) are reserved by the VM. // Registers 254 (this) and 255 (new.target) are reserved by the VM.
+217 -164
View File
@@ -6,9 +6,44 @@ use oxide_parser::{
use crate::compiler::{is_side_effect_free, CompileCtx, Compiler, Label}; use crate::compiler::{is_side_effect_free, CompileCtx, Compiler, Label};
impl Compiler { 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) { fn count_optional_guard(&self, ctx: &mut CompileCtx) {
ctx.alloc_reg(); ctx.count_load_var(); // dup
ctx.projected_pc += 2; // LOAD_VAR dup + JMP_IF_NULLISH ctx.count_jump(); // JMP_IF_NULLISH
} }
fn count_static_member_get_preserve_base( fn count_static_member_get_preserve_base(
@@ -18,10 +53,9 @@ impl Compiler {
if short_chain && member.optional { if short_chain && member.optional {
self.count_optional_guard(ctx); self.count_optional_guard(ctx);
} }
ctx.alloc_reg(); ctx.count_load_const(); // key
ctx.projected_pc += 1; // LOAD_CONST key ctx.count_load_var(); // receiver copy
ctx.alloc_reg(); ctx.count_ic_instr_with_ext(); // IC_GET_PROP + 3 ext words
ctx.projected_pc += 5; // LOAD_VAR copy + IC_GET_PROP + 3 ext words
} }
fn count_computed_member_get_preserve_base( fn count_computed_member_get_preserve_base(
@@ -33,7 +67,7 @@ impl Compiler {
} }
self.count_expression(&member.expression, ctx); self.count_expression(&member.expression, ctx);
ctx.alloc_reg(); 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) { 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 { if short_chain && member.optional {
self.count_optional_guard(ctx); self.count_optional_guard(ctx);
} }
ctx.alloc_reg(); ctx.count_private_access();
ctx.alloc_reg();
ctx.projected_pc += 2;
} }
_ => { _ => {
self.count_chainable_expression(&call.callee, short_chain, ctx); self.count_chainable_expression(&call.callee, short_chain, ctx);
@@ -78,9 +110,8 @@ impl Compiler {
self.count_expression(expr, ctx); self.count_expression(expr, ctx);
} }
} }
ctx.projected_pc += 2; // CALL + arg_count ext word ctx.count_call_instr_with_arg_ext(); // CALL + arg_count ext word
ctx.alloc_reg(); ctx.count_load_var(); // result <- regs[0]
ctx.projected_pc += 1; // LOAD_VAR result <- regs[0]
} }
fn count_chainable_expression(&self, expr: &Expression, short_chain: bool, ctx: &mut CompileCtx) { fn count_chainable_expression(&self, expr: &Expression, short_chain: bool, ctx: &mut CompileCtx) {
@@ -96,9 +127,7 @@ impl Compiler {
if short_chain && member.optional { if short_chain && member.optional {
self.count_optional_guard(ctx); self.count_optional_guard(ctx);
} }
ctx.alloc_reg(); ctx.count_private_access();
ctx.alloc_reg();
ctx.projected_pc += 2;
} }
Expression::CallExpression(call) => self.count_chain_call(call, short_chain, ctx), Expression::CallExpression(call) => self.count_chain_call(call, short_chain, ctx),
Expression::ChainExpression(chain) => self.count_chain_element(&chain.expression, 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 { if short_chain && member.optional {
self.count_optional_guard(ctx); self.count_optional_guard(ctx);
} }
ctx.alloc_reg(); ctx.count_private_access();
ctx.alloc_reg();
ctx.projected_pc += 2;
} }
ChainElement::CallExpression(call) => self.count_chain_call(call, short_chain, ctx), ChainElement::CallExpression(call) => self.count_chain_call(call, short_chain, ctx),
ChainElement::TSNonNullExpression(_) => {} ChainElement::TSNonNullExpression(_) => {}
@@ -148,12 +175,12 @@ impl Compiler {
ctx.projected_pc += 1; ctx.projected_pc += 1;
} }
oxide_parser::BindingPattern::ArrayPattern(ap) => { oxide_parser::BindingPattern::ArrayPattern(ap) => {
ctx.projected_pc += 1; // FOR_OF_INIT ctx.count_instr(); // FOR_OF_INIT
for elem in &ap.elements { for elem in &ap.elements {
ctx.alloc_reg(); ctx.alloc_reg();
ctx.projected_pc += 1; // FOR_OF_DONE ctx.count_instr(); // FOR_OF_DONE
ctx.alloc_reg(); ctx.alloc_reg();
ctx.projected_pc += 1; // FOR_OF_NEXT ctx.count_instr(); // FOR_OF_NEXT
if let Some(inner) = elem { if let Some(inner) = elem {
self.count_binding_pattern(inner, ctx); self.count_binding_pattern(inner, ctx);
} }
@@ -162,27 +189,25 @@ impl Compiler {
self.count_rest_array(ctx); self.count_rest_array(ctx);
self.count_binding_pattern(&rest.argument, 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) => { oxide_parser::BindingPattern::ObjectPattern(op) => {
for prop in &op.properties { for prop in &op.properties {
ctx.alloc_reg(); self.count_object_property_read_key(&prop.key, prop.computed, ctx);
ctx.projected_pc += 5; // LOAD_VAR + LOAD_CONST + IC_GET_PROP + 3 ext
ctx.alloc_reg();
self.count_binding_pattern(&prop.value, ctx); self.count_binding_pattern(&prop.value, ctx);
} }
if let Some(rest) = &op.rest { if let Some(rest) = &op.rest {
ctx.alloc_reg(); 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); self.count_binding_pattern(&rest.argument, ctx);
} }
} }
oxide_parser::BindingPattern::AssignmentPattern(ap) => { oxide_parser::BindingPattern::AssignmentPattern(ap) => {
ctx.alloc_reg(); ctx.count_load_const(); // undefined
ctx.projected_pc += 2; // LOAD_CONST undefined + STRICT_EQ ctx.count_instr(); // STRICT_EQ
ctx.projected_pc += 1; // JMP_IF_FALSE ctx.count_jump(); // JMP_IF_FALSE
self.count_expression(&ap.right, ctx); 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); self.count_binding_pattern(&ap.left, ctx);
} }
} }
@@ -190,50 +215,103 @@ impl Compiler {
fn count_rest_array(&self, ctx: &mut CompileCtx) { fn count_rest_array(&self, ctx: &mut CompileCtx) {
ctx.alloc_reg(); // rest ctx.alloc_reg(); // rest
ctx.projected_pc += 1; // NEW_ARRAY ctx.count_instr(); // NEW_ARRAY
ctx.alloc_reg(); // idx ctx.count_load_const(); // idx = 0
ctx.projected_pc += 1; // LOAD_CONST 0
ctx.alloc_reg(); // has 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.alloc_reg(); // val
ctx.projected_pc += 1; // FOR_OF_NEXT ctx.count_instr(); // FOR_OF_NEXT
ctx.projected_pc += 1; // SET_ELEM ctx.count_instr(); // SET_ELEM
ctx.alloc_reg(); // inc tmp 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) { 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 { for elem in &ap.elements {
ctx.alloc_reg(); ctx.alloc_reg();
ctx.projected_pc += 1; // FOR_OF_DONE ctx.count_instr(); // FOR_OF_DONE
ctx.alloc_reg(); ctx.alloc_reg();
ctx.projected_pc += 1; // FOR_OF_NEXT ctx.count_instr(); // FOR_OF_NEXT
if elem.is_some() { if elem.is_some() {
ctx.alloc_reg(); ctx.alloc_reg();
ctx.projected_pc += 1; ctx.count_instr(); // STORE_VAR
} }
} }
if ap.rest.is_some() { if ap.rest.is_some() {
self.count_rest_array(ctx); self.count_rest_array(ctx);
ctx.alloc_reg(); 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) { fn count_object_assignment(&self, op: &oxide_parser::ObjectAssignmentTarget, ctx: &mut CompileCtx) {
for _ in &op.properties { for prop in &op.properties {
ctx.alloc_reg(); match prop {
ctx.projected_pc += 5; // LOAD_VAR + LOAD_CONST + IC_GET_PROP + 3 ext oxide_parser::AssignmentTargetProperty::AssignmentTargetPropertyIdentifier(id) => {
ctx.alloc_reg(); self.count_object_property_read_static(ctx);
ctx.projected_pc += 1; 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() { if op.rest.is_some() {
ctx.alloc_reg(); 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.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 { if let Some(super_class) = &class.super_class {
self.count_expression(super_class, ctx); 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() { if class.super_class.is_some() {
ctx.alloc_reg(); // parent prototype key ctx.alloc_reg(); // parent prototype key
ctx.count_instr(); // LOAD_CONST
ctx.alloc_reg(); // parent prototype value ctx.alloc_reg(); // parent prototype value
ctx.count_instr(); // GET_PROP
ctx.alloc_reg(); // __proto__ key 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.count_load_const(); // constructor key
ctx.projected_pc += 1; // LOAD_CONST "constructor" ctx.count_instr(); // SET_PROP proto.constructor = ctor
ctx.projected_pc += 1; // SET_PROP proto.constructor = ctor
ctx.alloc_reg(); // prototype key ctx.count_load_const(); // prototype key
ctx.projected_pc += 1; // LOAD_CONST "prototype" ctx.count_instr(); // SET_PROP ctor.prototype = proto
ctx.projected_pc += 1; // SET_PROP ctor.prototype = proto
for element in &class.body.body { for element in &class.body.body {
match element { match element {
@@ -269,23 +350,22 @@ impl Compiler {
} }
if matches!(method.key, oxide_parser::PropertyKey::PrivateIdentifier(_)) { if matches!(method.key, oxide_parser::PropertyKey::PrivateIdentifier(_)) {
if method.r#static { if method.r#static {
ctx.alloc_reg(); ctx.count_load_const(); // private id
ctx.alloc_reg(); ctx.count_load_const(); // method
ctx.projected_pc += 3; ctx.count_instr(); // INIT_PRIVATE
} }
continue; continue;
} }
ctx.alloc_reg(); // key_reg ctx.count_load_const(); // key_reg
ctx.projected_pc += 1; ctx.count_load_const(); // method_reg
ctx.alloc_reg(); // method_reg ctx.count_instr(); // SET_HOME_OBJECT
ctx.projected_pc += 2; // LOAD_CONST method + SET_HOME_OBJECT
match method.kind { match method.kind {
MethodDefinitionKind::Method => { MethodDefinitionKind::Method => {
ctx.projected_pc += 1; // SET_PROP ctx.count_instr(); // SET_PROP
} }
MethodDefinitionKind::Get | MethodDefinitionKind::Set => { MethodDefinitionKind::Get | MethodDefinitionKind::Set => {
ctx.alloc_reg(); // undefined placeholder ctx.count_load_const(); // undefined placeholder
ctx.projected_pc += 3; // LOAD_CONST undefined + DEFINE_ACCESSOR + ext ctx.count_define_accessor(); // DEFINE_ACCESSOR + ext
} }
MethodDefinitionKind::Constructor => {} MethodDefinitionKind::Constructor => {}
} }
@@ -293,15 +373,13 @@ impl Compiler {
ClassElement::PropertyDefinition(prop) => { ClassElement::PropertyDefinition(prop) => {
let prop = prop.as_ref(); let prop = prop.as_ref();
if prop.r#static { if prop.r#static {
ctx.alloc_reg(); // key_reg ctx.count_load_const(); // key_reg
ctx.projected_pc += 1;
if let Some(value) = &prop.value { if let Some(value) = &prop.value {
self.count_expression(value, ctx); self.count_expression(value, ctx);
} else { } else {
ctx.alloc_reg(); ctx.count_load_const();
ctx.projected_pc += 1;
} }
ctx.projected_pc += 1; // SET_PROP ctx.count_instr(); // SET_PROP
} }
} }
ClassElement::StaticBlock(block) => { ClassElement::StaticBlock(block) => {
@@ -326,7 +404,7 @@ impl Compiler {
self.count_binding_pattern(&d.id, ctx); self.count_binding_pattern(&d.id, ctx);
} else { } else {
ctx.alloc_reg(); 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); self.count_binding_pattern(&d.id, ctx);
} else { } else {
ctx.alloc_reg(); 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); let end_label = Label::ForInEnd(id);
self.count_expression(&fi.right, ctx); 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.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 { match &fi.left {
oxide_parser::ForStatementLeft::VariableDeclaration(decl) => { oxide_parser::ForStatementLeft::VariableDeclaration(decl) => {
for _d in &decl.declarations { for _d in &decl.declarations {
ctx.alloc_reg(); ctx.alloc_reg();
ctx.projected_pc += 1; // STORE_VAR (value from FOR_IN_NEXT) ctx.count_instr(); // STORE_VAR
} }
} }
oxide_parser::ForStatementLeft::AssignmentTargetIdentifier(_) => { oxide_parser::ForStatementLeft::AssignmentTargetIdentifier(_) => {
ctx.alloc_reg(); // key register ctx.alloc_reg(); // key register
ctx.projected_pc += 1; // STORE_VAR ctx.count_instr(); // STORE_VAR
} }
_ => {} _ => {}
} }
self.count_statement(&fi.body, ctx); 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.label_map.insert(end_label, ctx.projected_pc);
ctx.projected_pc += 1; // FOR_IN_CLEANUP ctx.count_instr(); // FOR_IN_CLEANUP
} }
Statement::ForOfStatement(fo) => { Statement::ForOfStatement(fo) => {
let id = ctx.next_label_id(); let id = ctx.next_label_id();
@@ -451,12 +531,13 @@ impl Compiler {
let end_label = Label::ForOfEnd(id); let end_label = Label::ForOfEnd(id);
self.count_expression(&fo.right, ctx); 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.label_map.insert(start_label, ctx.projected_pc);
ctx.alloc_reg(); // has_reg 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.alloc_reg(); // val_reg
ctx.projected_pc += 1; // FOR_OF_NEXT ctx.count_instr(); // FOR_OF_NEXT
match &fo.left { match &fo.left {
oxide_parser::ForStatementLeft::VariableDeclaration(decl) => { oxide_parser::ForStatementLeft::VariableDeclaration(decl) => {
for d in &decl.declarations { for d in &decl.declarations {
@@ -465,7 +546,7 @@ impl Compiler {
} }
oxide_parser::ForStatementLeft::AssignmentTargetIdentifier(_) => { oxide_parser::ForStatementLeft::AssignmentTargetIdentifier(_) => {
ctx.alloc_reg(); ctx.alloc_reg();
ctx.projected_pc += 1; ctx.count_instr(); // STORE_VAR
} }
oxide_parser::ForStatementLeft::ArrayAssignmentTarget(ap) => { oxide_parser::ForStatementLeft::ArrayAssignmentTarget(ap) => {
self.count_array_assignment(ap, ctx); self.count_array_assignment(ap, ctx);
@@ -476,9 +557,9 @@ impl Compiler {
_ => {} _ => {}
} }
self.count_statement(&fo.body, ctx); 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.label_map.insert(end_label, ctx.projected_pc);
ctx.projected_pc += 1; // FOR_OF_CLOSE ctx.count_instr(); // FOR_OF_CLOSE
} }
Statement::SwitchStatement(sw) => { Statement::SwitchStatement(sw) => {
let id = ctx.next_label_id(); let id = ctx.next_label_id();
@@ -539,7 +620,7 @@ impl Compiler {
// Body is compiled in the emit pass only. // Body is compiled in the emit pass only.
// FD emits LOAD_CONST(BytecodeFunc) + STORE_VAR // FD emits LOAD_CONST(BytecodeFunc) + STORE_VAR
ctx.projected_pc += 2; ctx.count_words(2);
} }
Statement::ClassDeclaration(class) => { Statement::ClassDeclaration(class) => {
ctx.alloc_reg(); // class binding reg ctx.alloc_reg(); // class binding reg
@@ -624,9 +705,7 @@ impl Compiler {
} }
Expression::PrivateInExpression(pin) => { Expression::PrivateInExpression(pin) => {
self.count_expression(&pin.right, ctx); self.count_expression(&pin.right, ctx);
ctx.alloc_reg(); ctx.count_private_access();
ctx.alloc_reg();
ctx.projected_pc += 2;
} }
Expression::UnaryExpression(un) => { Expression::UnaryExpression(un) => {
match un.operator { match un.operator {
@@ -637,7 +716,7 @@ impl Compiler {
} }
Expression::StaticMemberExpression(member) => { Expression::StaticMemberExpression(member) => {
self.count_expression(&member.object, ctx); self.count_expression(&member.object, ctx);
ctx.projected_pc += 2; // DELETE_PROP_STATIC + ext word ctx.count_delete_static();
} }
Expression::ComputedMemberExpression(member) => { Expression::ComputedMemberExpression(member) => {
self.count_expression(&member.object, ctx); self.count_expression(&member.object, ctx);
@@ -651,7 +730,7 @@ impl Compiler {
if member.optional { if member.optional {
self.count_optional_guard(ctx); self.count_optional_guard(ctx);
} }
ctx.projected_pc += 2; // DELETE_PROP_STATIC + ext word ctx.count_delete_static();
} }
ChainElement::ComputedMemberExpression(member) => { ChainElement::ComputedMemberExpression(member) => {
self.count_expression(&member.object, ctx); self.count_expression(&member.object, ctx);
@@ -683,30 +762,25 @@ impl Compiler {
} }
} }
ctx.alloc_reg(); 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; return;
} }
match &call.callee { match &call.callee {
Expression::PrivateFieldExpression(member) => { Expression::PrivateFieldExpression(member) => {
self.count_expression(&member.object, ctx); self.count_expression(&member.object, ctx);
ctx.alloc_reg(); ctx.count_private_access();
ctx.alloc_reg();
ctx.projected_pc += 2;
} }
Expression::StaticMemberExpression(member) => { Expression::StaticMemberExpression(member) => {
if matches!(&member.object, Expression::Super(_)) { if matches!(&member.object, Expression::Super(_)) {
ctx.alloc_reg(); // this register ctx.count_load_var(); // this register
ctx.projected_pc += 1; // LOAD_VAR this ctx.count_load_const(); // key
ctx.alloc_reg(); // key
ctx.projected_pc += 1; // LOAD_CONST key
ctx.alloc_reg(); // callee ctx.alloc_reg(); // callee
ctx.projected_pc += 1; // SUPER_GET_PROP ctx.count_instr(); // SUPER_GET_PROP
} else { } else {
self.count_expression(&member.object, ctx); self.count_expression(&member.object, ctx);
ctx.alloc_reg(); ctx.count_load_const(); // key
ctx.projected_pc += 1; ctx.count_load_var(); // callee object copy
ctx.alloc_reg(); ctx.count_ic_instr_with_ext(); // IC_GET_PROP + 3 ext words
ctx.projected_pc += 5; // LOAD_VAR + IC_GET_PROP + 3 ext words
} }
} }
_ => { _ => {
@@ -720,23 +794,20 @@ impl Compiler {
self.count_expression(expr, ctx); self.count_expression(expr, ctx);
} }
} }
// CALL and CALL_NATIVE both carry 1 ext word for arg_count. ctx.count_call_instr_with_arg_ext(); // CALL/CALL_NATIVE + arg_count ext word
ctx.projected_pc += 2; ctx.count_load_var(); // result <- regs[0]
ctx.alloc_reg(); // result register
ctx.projected_pc += 1; // LOAD_VAR result <- regs[0]
} }
Expression::AssignmentExpression(assign) => { Expression::AssignmentExpression(assign) => {
if let oxide_parser::AssignmentTarget::StaticMemberExpression(member) = &assign.left { if let oxide_parser::AssignmentTarget::StaticMemberExpression(member) = &assign.left {
if let Some(logical_op) = assign.operator.to_logical_operator() { if let Some(logical_op) = assign.operator.to_logical_operator() {
let id = ctx.next_label_id(); let id = ctx.next_label_id();
self.count_expression(&member.object, ctx); self.count_expression(&member.object, ctx);
ctx.alloc_reg(); ctx.count_load_const(); // key
ctx.projected_pc += 1; // LOAD_CONST key ctx.count_load_var(); // current value copy
ctx.alloc_reg(); ctx.count_ic_instr_with_ext(); // IC_GET_PROP + 3 ext
ctx.projected_pc += 5; // LOAD_VAR + IC_GET_PROP + 3 ext
self.count_logical_assign_test(logical_op, id, ctx); self.count_logical_assign_test(logical_op, id, ctx);
self.count_expression(&assign.right, 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.projected_pc += 1; // LOAD_VAR result <- rhs
ctx.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc); ctx.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc);
return; return;
@@ -747,10 +818,8 @@ impl Compiler {
ctx.alloc_reg(); ctx.alloc_reg();
ctx.projected_pc += 1; ctx.projected_pc += 1;
} }
ctx.alloc_reg(); ctx.count_load_const(); // key
ctx.projected_pc += 1; // LOAD_CONST key ctx.count_ic_set_with_ext(); // IC_SET_PROP or COMPOUND_MEMBER_* + 3 ext words
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 { } else if let oxide_parser::AssignmentTarget::ComputedMemberExpression(member) = &assign.left {
if let Some(logical_op) = assign.operator.to_logical_operator() { if let Some(logical_op) = assign.operator.to_logical_operator() {
let id = ctx.next_label_id(); let id = ctx.next_label_id();
@@ -773,8 +842,8 @@ impl Compiler {
} else if let oxide_parser::AssignmentTarget::PrivateFieldExpression(member) = &assign.left { } else if let oxide_parser::AssignmentTarget::PrivateFieldExpression(member) = &assign.left {
self.count_expression(&member.object, ctx); self.count_expression(&member.object, ctx);
self.count_expression(&assign.right, ctx); self.count_expression(&assign.right, ctx);
ctx.alloc_reg(); ctx.count_load_const(); // private id
ctx.projected_pc += 2; // LOAD_CONST private id + SET_PRIVATE ctx.count_instr(); // SET_PRIVATE
} else if let oxide_parser::AssignmentTarget::AssignmentTargetIdentifier(_) = &assign.left { } else if let oxide_parser::AssignmentTarget::AssignmentTargetIdentifier(_) = &assign.left {
if let Some(logical_op) = assign.operator.to_logical_operator() { if let Some(logical_op) = assign.operator.to_logical_operator() {
let id = ctx.next_label_id(); let id = ctx.next_label_id();
@@ -874,8 +943,8 @@ impl Compiler {
if let oxide_parser::ObjectPropertyKind::ObjectProperty(p) = prop { if let oxide_parser::ObjectPropertyKind::ObjectProperty(p) = prop {
if matches!(p.kind, oxide_parser::PropertyKind::Get | oxide_parser::PropertyKind::Set) { if matches!(p.kind, oxide_parser::PropertyKind::Get | oxide_parser::PropertyKind::Set) {
self.count_expression(&p.value, ctx); self.count_expression(&p.value, ctx);
ctx.alloc_reg(); // undefined getter/setter placeholder ctx.count_load_const(); // undefined getter/setter placeholder
ctx.projected_pc += 3; // LOAD_CONST undefined + DEFINE_ACCESSOR + ext ctx.count_define_accessor(); // DEFINE_ACCESSOR + ext
} else { } else {
ctx.alloc_reg(); ctx.alloc_reg();
ctx.projected_pc += 1; // LOAD_CONST key ctx.projected_pc += 1; // LOAD_CONST key
@@ -892,10 +961,8 @@ impl Compiler {
for expr in &tl.expressions { for expr in &tl.expressions {
self.count_expression(expr, ctx); self.count_expression(expr, ctx);
} }
ctx.alloc_reg(); // result reg
let segment_count = tl.quasis.len() + tl.expressions.len(); let segment_count = tl.quasis.len() + tl.expressions.len();
// TEMPLATE_STR opcode + ext words (1 header + ceil(segments/1) per u32) ctx.count_template_str(segment_count);
ctx.projected_pc += 1 + 1 + segment_count;
} }
Expression::TaggedTemplateExpression(tt) => { Expression::TaggedTemplateExpression(tt) => {
// Tagged template: tag expr + quasis as LOAD_CONST + expression args + CALL // 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 // Each quasi: LOAD_CONST string + LOAD_CONST index + SET_ELEM
// for both cooked and raw arrays // for both cooked and raw arrays
for _ in 0..quasi_count { for _ in 0..quasi_count {
ctx.alloc_reg(); // str_reg ctx.count_load_const(); // string
ctx.projected_pc += 1; // LOAD_CONST string ctx.count_load_const(); // index
ctx.alloc_reg(); // idx_reg ctx.count_instr(); // SET_ELEM (cooked)
ctx.projected_pc += 1; // LOAD_CONST index
ctx.projected_pc += 1; // SET_ELEM (cooked)
} }
// Cooked array: alloc + NEW_ARRAY // Cooked array: alloc + NEW_ARRAY
ctx.alloc_reg(); // cooked_temp ctx.alloc_reg(); // cooked_temp
ctx.projected_pc += 1; // NEW_ARRAY ctx.count_instr(); // NEW_ARRAY
for _ in 0..quasi_count { for _ in 0..quasi_count {
ctx.alloc_reg(); // str_reg ctx.count_load_const(); // string
ctx.projected_pc += 1; // LOAD_CONST string ctx.count_load_const(); // index
ctx.alloc_reg(); // idx_reg ctx.count_instr(); // SET_ELEM (raw)
ctx.projected_pc += 1; // LOAD_CONST index
ctx.projected_pc += 1; // SET_ELEM (raw)
} }
// Raw array: alloc + NEW_ARRAY // Raw array: alloc + NEW_ARRAY
ctx.alloc_reg(); // raw_temp ctx.alloc_reg(); // raw_temp
ctx.projected_pc += 1; // NEW_ARRAY ctx.count_instr(); // NEW_ARRAY
// Count expression arguments // Count expression arguments
for expr in &tt.quasi.expressions { for expr in &tt.quasi.expressions {
@@ -936,21 +999,19 @@ impl Compiler {
ctx.alloc_reg(); // raw_slot ctx.alloc_reg(); // raw_slot
for _ in &tt.quasi.expressions { for _ in &tt.quasi.expressions {
ctx.alloc_reg(); // expr_slot ctx.alloc_reg(); // expr_slot
ctx.projected_pc += 1; // LOAD_VAR ctx.count_instr(); // LOAD_VAR
} }
// LOAD_VAR for cooked and raw // LOAD_VAR for cooked and raw
ctx.projected_pc += 2; ctx.count_words(2);
// undefined this arg // undefined this arg
ctx.alloc_reg(); ctx.count_load_const();
ctx.projected_pc += 1; // LOAD_CONST
// CALL + ext word // CALL + ext word
ctx.projected_pc += 2; ctx.count_call_instr_with_arg_ext();
// Result reg + LOAD_VAR // Result reg + LOAD_VAR
ctx.alloc_reg(); ctx.count_load_var();
ctx.projected_pc += 1; // LOAD_VAR
} }
Expression::ArrowFunctionExpression(_arrow) => { Expression::ArrowFunctionExpression(_arrow) => {
// Arrow functions: alloc 1 register for the function value. // Arrow functions: alloc 1 register for the function value.
@@ -977,7 +1038,7 @@ impl Compiler {
} }
} }
ctx.alloc_reg(); // result register 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) => { Expression::ArrayExpression(arr) => {
ctx.alloc_reg(); ctx.alloc_reg();
@@ -995,19 +1056,15 @@ impl Compiler {
} }
Expression::StaticMemberExpression(member) => { Expression::StaticMemberExpression(member) => {
if matches!(&member.object, Expression::Super(_)) { if matches!(&member.object, Expression::Super(_)) {
ctx.alloc_reg(); // key ctx.count_load_const(); // key
ctx.projected_pc += 1; // LOAD_CONST key ctx.count_load_var(); // this
ctx.alloc_reg(); // this
ctx.projected_pc += 1; // LOAD_VAR this
ctx.alloc_reg(); // result ctx.alloc_reg(); // result
ctx.projected_pc += 1; // SUPER_GET_PROP ctx.count_instr(); // SUPER_GET_PROP
return; return;
} }
self.count_expression(&member.object, ctx); self.count_expression(&member.object, ctx);
ctx.alloc_reg(); ctx.count_load_const(); // key
ctx.projected_pc += 1; // LOAD_CONST key ctx.count_ic_instr_with_ext(); // IC_GET_PROP + 3 ext words
ctx.projected_pc += 1; // IC_GET_PROP
ctx.projected_pc += 3; // 3 IC ext words
} }
Expression::ComputedMemberExpression(member) => { Expression::ComputedMemberExpression(member) => {
self.count_expression(&member.object, ctx); self.count_expression(&member.object, ctx);
@@ -1017,9 +1074,7 @@ impl Compiler {
} }
Expression::PrivateFieldExpression(member) => { Expression::PrivateFieldExpression(member) => {
self.count_expression(&member.object, ctx); self.count_expression(&member.object, ctx);
ctx.alloc_reg(); ctx.count_private_access();
ctx.alloc_reg();
ctx.projected_pc += 2;
} }
Expression::ParenthesizedExpression(p) => { Expression::ParenthesizedExpression(p) => {
self.count_expression(&p.expression, ctx); self.count_expression(&p.expression, ctx);
@@ -1042,11 +1097,9 @@ impl Compiler {
} }
SimpleAssignmentTarget::StaticMemberExpression(member) => { SimpleAssignmentTarget::StaticMemberExpression(member) => {
self.count_expression(&member.object, ctx); self.count_expression(&member.object, ctx);
ctx.count_load_const(); // key
ctx.alloc_reg(); ctx.alloc_reg();
ctx.projected_pc += 1; ctx.count_ic_instr_with_ext(); // MEMBER_INC/MEMBER_DEC + 3 ext words
ctx.alloc_reg();
ctx.projected_pc += 1; // MEMBER_INC/MEMBER_DEC opcode
ctx.projected_pc += 3; // 3 IC ext words
} }
SimpleAssignmentTarget::ComputedMemberExpression(member) => { SimpleAssignmentTarget::ComputedMemberExpression(member) => {
self.count_expression(&member.object, ctx); self.count_expression(&member.object, ctx);
@@ -63,6 +63,20 @@ fn for_of_destructuring_left_side() {
assert_num(result, 10.0); 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] #[test]
fn function_and_method_parameter_destructuring() { fn function_and_method_parameter_destructuring() {
let mut vm = Vm::new(); let mut vm = Vm::new();