From 8669f5547074b040e34f1d565dbe404c562b0f49 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 9 Aug 2026 09:35:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20class=20=E5=85=AC=E6=9C=89=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=20define=20=E8=AF=AD=E4=B9=89=20+=20computed=20key=20?= =?UTF-8?q?=E7=B1=BB=E5=AE=9A=E4=B9=89=E6=9C=9F=E6=B1=82=E5=80=BC=E4=B8=80?= =?UTF-8?q?=E6=AC=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_bytecode/src/opcode.rs | 3 + crates/oxide_emit/src/class/emit.rs | 122 ++++++++++++++++++-- crates/oxide_emit/src/class/field.rs | 22 ++-- crates/oxide_emit/src/class/method.rs | 10 +- crates/oxide_emit/src/closure.rs | 67 ++++++++++- crates/oxide_emit/src/emit.rs | 47 +++++++- crates/oxide_emit/src/expr/assignment.rs | 70 ++++++++--- crates/oxide_emit/src/expr/operator/mod.rs | 16 +-- crates/oxide_emit/src/shared/class_field.rs | 58 ++++++---- crates/oxide_emit/src/shared/class_key.rs | 17 +++ crates/oxide_ir/src/contract.rs | 4 +- crates/oxide_ir/src/inst.rs | 6 + crates/oxide_vm/src/vm.rs | 4 + crates/oxide_vm/src/vm_dispatch_ctrl.rs | 17 +++ crates/oxide_vm/tests/class_tests.rs | 74 ++++++++++++ 15 files changed, 470 insertions(+), 67 deletions(-) diff --git a/crates/oxide_bytecode/src/opcode.rs b/crates/oxide_bytecode/src/opcode.rs index befd5d08..dd2f5c78 100644 --- a/crates/oxide_bytecode/src/opcode.rs +++ b/crates/oxide_bytecode/src/opcode.rs @@ -173,6 +173,9 @@ define_opcodes! { // ── 对象 (0x63) ── CREATE_ARGUMENTS = 0x63 => "CREATE_ARGUMENTS", + // ── define 语义属性写入 (0x6D) ── + DEFINE_PROP = 0x6D => "DEFINE_PROP", + // ── 成员复合赋值:位/移位 (0x64-0x69) ── COMPOUND_MEMBER_BIT_AND = 0x64 => "COMPOUND_MEMBER_BIT_AND", COMPOUND_MEMBER_BIT_OR = 0x65 => "COMPOUND_MEMBER_BIT_OR", diff --git a/crates/oxide_emit/src/class/emit.rs b/crates/oxide_emit/src/class/emit.rs index e023c37b..9b7898e7 100644 --- a/crates/oxide_emit/src/class/emit.rs +++ b/crates/oxide_emit/src/class/emit.rs @@ -3,19 +3,21 @@ //! 函数:`emit_class`。 use crate::{CompileCtx, Emitter, FunctionBodyContext}; +use oxide_bytecode::module::Constant; use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; -use oxide_parser::{Class, ClassElement, MethodDefinitionKind, PropertyKey}; +use oxide_parser::{Class, ClassElement, Expression, MethodDefinitionKind, PropertyKey}; impl Emitter { pub(crate) fn emit_class(&self, class: &Class, ctx: &mut CompileCtx) -> Result { + let elements = &class.body.body; let mut constructor_method = None; - let mut instance_fields = Vec::new(); + let mut instance_field_indices = Vec::new(); let mut private_names = Vec::<(String, u32)>::new(); let is_derived = class.super_class.is_some(); - for element in &class.body.body { + for (idx, element) in elements.iter().enumerate() { match element { ClassElement::MethodDefinition(method) => { let method = method.as_ref(); @@ -47,7 +49,7 @@ impl Emitter { private_names.push((name, id)); } if !prop.r#static { - instance_fields.push(prop); + instance_field_indices.push(idx); } } ClassElement::AccessorProperty(_) => return Err("class accessor properties not yet supported".into()), @@ -55,6 +57,29 @@ impl Emitter { } } + // 计算键 slot 分配:全部元素源码序(方法 + 公有字段),私有元素非 computed。 + let mut computed_slot = 0u8; + let key_slots: Vec> = elements + .iter() + .map(|e| { + let computed = match e { + ClassElement::MethodDefinition(m) => m.computed, + ClassElement::PropertyDefinition(p) => { + p.computed && !matches!(p.key, PropertyKey::PrivateIdentifier(_)) + } + _ => false, + }; + if computed { + let s = computed_slot; + computed_slot += 1; + Some(s) + } else { + None + } + }) + .collect(); + let any_computed = computed_slot > 0; + let (ctor_reg, proto_reg, super_reg) = self.emit_class_header(class, ctx)?; let ctor_name = class.id.as_ref().map(|id| id.name.to_string()); let self_binding = ctor_name.as_deref().map(|name| vec![(name, ctor_reg)]).unwrap_or_default(); @@ -63,8 +88,33 @@ impl Emitter { ctx.in_derived_constructor = is_derived; ctx.scopes.private_name_map = private_names.clone(); + // 实例公有字段 computed key 求值于构造器帧外,须类定义期存入数组。构造器以 + // upvalue 捕获该数组:父作用域登记 `@@field_keys`(cell_idx 取现有最大 +1)。 + let has_computed_instance = instance_field_indices.iter().any(|&i| { + let ClassElement::PropertyDefinition(p) = &elements[i] else { return false }; + p.computed && !matches!(p.key, PropertyKey::PrivateIdentifier(_)) + }); + let field_key_cell: Option = if has_computed_instance { + let cell_idx = ctx.captured_bindings.values().copied().max().map_or(0, |m| m.saturating_add(1)); + ctx.captured_bindings.insert("@@field_keys".to_string(), cell_idx); + Some(cell_idx) + } else { + None + }; + // 字段值表达式运行于构造器帧:其自由变量须纳入构造器 upvalue 捕获。 + let field_value_exprs: Vec<&Expression> = instance_field_indices + .iter() + .filter_map(|&i| match &elements[i] { + ClassElement::PropertyDefinition(p) => p.value.as_ref(), + _ => None, + }) + .collect(); + let extra_upvalue_names: Vec<(&str, u8)> = field_key_cell.map(|c| ("@@field_keys", c)).into_iter().collect(); + let emit_instance_fields = |compiler: &Emitter, field_ctx: &mut CompileCtx| -> Result<(), String> { - for field in &instance_fields { + for &idx in &instance_field_indices { + let ClassElement::PropertyDefinition(field) = &elements[idx] else { unreachable!() }; + let field = field.as_ref(); if let PropertyKey::PrivateIdentifier(private) = &field.key { compiler.emit_private_field_init( Operand::This, @@ -78,6 +128,7 @@ impl Emitter { &field.key, field.computed, field.value.as_ref(), + key_slots[idx], field_ctx, )?; } @@ -96,6 +147,8 @@ impl Emitter { FunctionBodyContext::ClassElement, Some(&emit_instance_fields), is_derived, + &field_value_exprs, + &extra_upvalue_names, )? } else { let mut module = self.compile_function_body_with_field_hooks( @@ -107,14 +160,24 @@ impl Emitter { FunctionBodyContext::ClassElement, Some(&emit_instance_fields), is_derived, + &field_value_exprs, + &extra_upvalue_names, )?; if is_derived { module.insts.clear(); module.constants.clear(); module.n_registers = 1; module.insts.push(Inst::super_call(Operand::None, Operand::None, 0)); + // 字段初始化直接重发:构造器 upvalue/内置槽引用须与首轮编译产物对齐。 let mut field_ctx = CompileCtx::new(); field_ctx.scopes.private_name_map = private_names.clone(); + field_ctx.scopes.builtin_reg_map = module.builtin_reg_map.clone(); + field_ctx.current_upvalue_captures = module.upvalue_captures.clone(); + field_ctx.field_keys_uv = module + .upvalue_captures + .iter() + .position(|u| u.name == "@@field_keys") + .map(|i| i as u8); emit_instance_fields(self, &mut field_ctx)?; module.insts.extend(field_ctx.insts); module.constants = field_ctx.constants; @@ -130,10 +193,53 @@ impl Emitter { ctor_module.is_derived_constructor = is_derived; ctor_module.function_name = ctor_name.clone(); ctx.nested.push(ctor_module); + // 键数组构建会在 keys 表达式中压入嵌套模块(箭头/函数/类键), + // 必须在压入后用固定下标引用构造器模块,避免嵌套长度偏移。 + let ctor_sub_idx = ctx.nested.len() as u16; - self.emit_class_prototype(ctor_reg, proto_reg, super_reg, ctx.nested.len() as u16, ctx)?; - self.emit_class_methods(&class.body.body, ctor_reg, proto_reg, &self_binding, ctx)?; - self.emit_class_static_elements(&class.body.body, ctor_reg, ctx)?; + // 类定义期按源码序求值全部 computed key(extends 之后、构造器闭包创建之前), + // 存数组供方法/静态字段/实例字段初始化阶段按 slot 取用。 + if any_computed { + let keys_reg = ctx.alloc_reg(); + ctx.inst(Inst::new( + OpCode::NEW_ARRAY, + Operand::Reg(keys_reg), + Operand::Imm(computed_slot as u16), + Operand::None, + )); + for (idx, element) in elements.iter().enumerate() { + let Some(slot) = key_slots[idx] else { continue }; + let key_expr = match element { + ClassElement::MethodDefinition(m) => m.key.as_expression(), + ClassElement::PropertyDefinition(p) => p.key.as_expression(), + _ => None, + }; + let Some(expr) = key_expr else { continue }; + let key_reg = self.emit_expression(expr, ctx)?; + let idx_reg = ctx.alloc_reg(); + let idx_const = ctx.add_constant(Constant::Int(slot as i32)); + ctx.inst(Inst::load_const(Operand::Reg(idx_reg), idx_const)); + ctx.inst(Inst::new( + OpCode::SET_ELEM, + Operand::Reg(keys_reg), + Operand::Reg(idx_reg), + Operand::Reg(key_reg), + )); + } + if let Some(cell_idx) = field_key_cell { + ctx.inst(Inst::new( + OpCode::MAKE_CELL, + Operand::Reg(keys_reg), + Operand::Imm(cell_idx as u16), + Operand::None, + )); + } + ctx.class_keys_reg = Some(keys_reg); + } + + self.emit_class_prototype(ctor_reg, proto_reg, super_reg, ctor_sub_idx, ctx)?; + self.emit_class_methods(&class.body.body, ctor_reg, proto_reg, &self_binding, &key_slots, ctx)?; + self.emit_class_static_elements(&class.body.body, ctor_reg, &key_slots, ctx)?; ctx.scopes.private_name_map = saved_private_names; Ok(ctor_reg) diff --git a/crates/oxide_emit/src/class/field.rs b/crates/oxide_emit/src/class/field.rs index 8ca1e2d4..9364329d 100644 --- a/crates/oxide_emit/src/class/field.rs +++ b/crates/oxide_emit/src/class/field.rs @@ -1,6 +1,7 @@ //! 类静态字段/元素 emit:`emit_class_static_elements`。 use crate::{CompileCtx, Emitter}; +use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; use oxide_parser::{ClassElement, PropertyKey}; @@ -10,14 +11,14 @@ impl Emitter { /// static z = this.y + 1` 必须按此顺序),若全部字段先于静态块发出, /// `z` 会在 `y` 赋值前读到它。 pub(crate) fn emit_class_static_elements( - &self, elements: &[ClassElement], ctor_reg: u32, ctx: &mut CompileCtx, + &self, elements: &[ClassElement], ctor_reg: u32, key_slots: &[Option], ctx: &mut CompileCtx, ) -> Result<(), String> { let saved_static_this = ctx.static_block_this_reg; // static_block_this_reg 保留 u8(this 槽语义,254 特判),ctor_reg 超 u8 界时报错而非静默截断。 ctx.static_block_this_reg = Some( u8::try_from(ctor_reg).map_err(|_| format!("class constructor register {ctor_reg} exceeds u8 limit"))?, ); - for element in elements { + for (element, &key_slot) in elements.iter().zip(key_slots) { match element { ClassElement::PropertyDefinition(prop) => { let prop = prop.as_ref(); @@ -30,13 +31,18 @@ impl Emitter { ctx, )?; } else { - self.emit_public_field_init( + // 静态字段在类定义期求值:computed key 从类定义期数组取,其余用常量。 + let key_reg = if prop.computed { + self.emit_class_array_key_reg(key_slot.unwrap_or(0), ctx)? + } else { + self.emit_class_key_reg(&prop.key, false, ctx)? + }; + let value_reg = self.emit_field_value(prop.value.as_ref(), ctx)?; + ctx.inst(Inst::define_prop( Operand::Reg(ctor_reg), - &prop.key, - prop.computed, - prop.value.as_ref(), - ctx, - )?; + Operand::Reg(value_reg), + Operand::Reg(key_reg), + )); } } } diff --git a/crates/oxide_emit/src/class/method.rs b/crates/oxide_emit/src/class/method.rs index 557c63ec..ac3484a1 100644 --- a/crates/oxide_emit/src/class/method.rs +++ b/crates/oxide_emit/src/class/method.rs @@ -11,9 +11,9 @@ use oxide_parser::{ClassElement, MethodDefinitionKind, PropertyKey}; impl Emitter { pub(crate) fn emit_class_methods( &self, elements: &[ClassElement], ctor_reg: u32, proto_reg: u32, self_binding: &[(&str, u32)], - ctx: &mut CompileCtx, + key_slots: &[Option], ctx: &mut CompileCtx, ) -> Result<(), String> { - for element in elements { + for (element, &key_slot) in elements.iter().zip(key_slots) { if let ClassElement::MethodDefinition(method) = element { let method = method.as_ref(); if method.kind == MethodDefinitionKind::Constructor { @@ -25,7 +25,11 @@ impl Emitter { continue; } let home_reg = if method.r#static { ctor_reg } else { proto_reg }; - let key_reg = self.emit_class_key_reg(&method.key, method.computed, ctx)?; + let key_reg = if method.computed { + self.emit_class_array_key_reg(key_slot.unwrap_or(0), ctx)? + } else { + self.emit_class_key_reg(&method.key, false, ctx)? + }; let method_name = if method.computed { "".to_string() } else { diff --git a/crates/oxide_emit/src/closure.rs b/crates/oxide_emit/src/closure.rs index 934e20d2..14e44f82 100644 --- a/crates/oxide_emit/src/closure.rs +++ b/crates/oxide_emit/src/closure.rs @@ -7,7 +7,7 @@ use std::collections::{BTreeMap, HashSet}; use oxide_bytecode::module::UpvalueCapture; -use oxide_parser::{Expression, Statement}; +use oxide_parser::{ClassBody, ClassElement, Expression, Statement}; use crate::Emitter; @@ -127,6 +127,15 @@ impl Emitter { self.collect_fn_default_names(&fd.params, ref_set, shadow, out); self.collect_capture_names_shadowed(body, ref_set, &inner, out); } + Statement::ClassDeclaration(cd) => { + // 类体(构造器/方法体、字段键与值、静态块)是嵌套作用域: + // 类名遮蔽外层绑定,方法形参与方法体局部进一步遮蔽。 + let mut class_shadow = shadow.clone(); + if let Some(id) = &cd.id { + class_shadow.insert(id.name.as_str().to_string()); + } + self.collect_class_capture_names(&cd.body, ref_set, &class_shadow, out); + } Statement::IfStatement(is) => { self.collect_capture_names_expr(&is.test, ref_set, shadow, out); self.collect_capture_names_stmt(&is.consequent, ref_set, shadow, out); @@ -211,6 +220,38 @@ impl Emitter { } } + /// 收集类体对 `ref_set` 的引用:方法/构造器体、字段键与值、静态块。 + /// 方法形参与方法体局部声明遮蔽外层绑定(捕获判定用)。 + fn collect_class_capture_names( + &self, class_body: &ClassBody, ref_set: &HashSet, class_shadow: &HashSet, + out: &mut HashSet, + ) { + for element in &class_body.body { + match element { + ClassElement::MethodDefinition(method) => { + let mut inner = class_shadow.clone(); + inner.extend(self.collect_fn_param_names(&method.value.params)); + let body: &[Statement] = method.value.body.as_ref().map(|b| &b.statements[..]).unwrap_or(&[]); + inner.extend(self.collect_own_binding_names(&[], body)); + self.collect_fn_default_names(&method.value.params, ref_set, class_shadow, out); + self.collect_capture_names_shadowed(body, ref_set, &inner, out); + } + ClassElement::PropertyDefinition(prop) => { + if let Some(expr) = prop.key.as_expression() { + self.collect_capture_names_expr(expr, ref_set, class_shadow, out); + } + if let Some(value) = &prop.value { + self.collect_capture_names_expr(value, ref_set, class_shadow, out); + } + } + ClassElement::StaticBlock(block) => { + self.collect_capture_names_shadowed(&block.body, ref_set, class_shadow, out); + } + _ => {} + } + } + } + /// 遍历 catch 参数解构模式:收集模式内表达式引用(默认值等),声明名写入 /// `catch_shadow` 遮蔽 catch 作用域。 fn collect_capture_names_binding_pattern( @@ -302,6 +343,14 @@ impl Emitter { self.collect_fn_default_names(&ae.params, ref_set, shadow, out); self.collect_capture_names_shadowed(&ae.body.statements, ref_set, &inner, out); } + Expression::ClassExpression(class) => { + // 类表达式:方法/构造器体与字段表达式是嵌套作用域,引用须捕获。 + let mut class_shadow = shadow.clone(); + if let Some(id) = &class.id { + class_shadow.insert(id.name.as_str().to_string()); + } + self.collect_class_capture_names(&class.body, ref_set, &class_shadow, out); + } Expression::BinaryExpression(be) => { self.collect_capture_names_expr(&be.left, ref_set, shadow, out); self.collect_capture_names_expr(&be.right, ref_set, shadow, out); @@ -447,6 +496,14 @@ impl Emitter { self.collect_fn_default_captured(&fd.params, own, out); self.collect_capture_names(body, own, out); } + Statement::ClassDeclaration(cd) => { + // 类构造器/方法体与字段表达式引用的父级绑定须建 cell,供子模块 upvalue 捕获。 + let mut class_shadow = HashSet::new(); + if let Some(id) = &cd.id { + class_shadow.insert(id.name.as_str().to_string()); + } + self.collect_class_capture_names(&cd.body, own, &class_shadow, out); + } Statement::ExpressionStatement(es) => self.collect_captured_expr(&es.expression, own, out), Statement::ReturnStatement(rs) => { if let Some(a) = &rs.argument { @@ -584,6 +641,14 @@ impl Emitter { self.collect_fn_default_captured(&ae.params, own, out); self.collect_capture_names_shadowed(&ae.body.statements, own, &inner, out); } + Expression::ClassExpression(class) => { + // 类表达式:构造器/方法体与字段表达式引用的父级绑定须建 cell。 + let mut class_shadow = HashSet::new(); + if let Some(id) = &class.id { + class_shadow.insert(id.name.as_str().to_string()); + } + self.collect_class_capture_names(&class.body, own, &class_shadow, out); + } Expression::CallExpression(ce) => { self.collect_captured_expr(&ce.callee, own, out); for a in &ce.arguments { diff --git a/crates/oxide_emit/src/emit.rs b/crates/oxide_emit/src/emit.rs index d704395d..a933c672 100644 --- a/crates/oxide_emit/src/emit.rs +++ b/crates/oxide_emit/src/emit.rs @@ -167,6 +167,11 @@ pub struct CompileCtx { pub(crate) in_static_method: bool, pub(crate) static_block_this_reg: Option, pub(crate) field_buffer: Option, + /// 类构造器模块中 `@@field_keys` upvalue 下标(实例字段 computed key 数组)。 + /// 类定义期求值一次存入 cell,构造器经此 upvalue 读取。 + pub(crate) field_keys_uv: Option, + /// 类定义期全部 computed key 数组寄存器(方法/静态字段阶段按 slot 读取)。 + pub(crate) class_keys_reg: Option, pub(crate) current_upvalue_captures: Vec, /// 本函数作用域声明的绑定名(参数 + 变量/函数声明,AST 收集,emit 前确定)。 pub(crate) own_bindings: HashSet, @@ -258,6 +263,8 @@ impl CompileCtx { in_static_method: false, static_block_this_reg: None, field_buffer: None, + field_keys_uv: None, + class_keys_reg: None, current_upvalue_captures: Vec::new(), own_bindings: HashSet::new(), captured_bindings: BTreeMap::new(), @@ -718,6 +725,8 @@ impl Emitter { body_context, None:: Result<(), String>>, false, + &[], + &[], ) } @@ -733,6 +742,7 @@ impl Emitter { &self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx, is_expression_body: bool, extra_bindings: &[(&str, u32)], body_context: FunctionBodyContext, mut emit_fields: Option, fields_after_super: bool, + extra_capture_exprs: &[&'a Expression<'a>], extra_upvalue_names: &[(&str, u8)], ) -> Result where E: FnMut(&Emitter, &mut CompileCtx) -> Result<(), String>, @@ -788,7 +798,21 @@ impl Emitter { // 让 next_reg 与 builtin 槽位对齐,参数在 builtin 槽之后分配。 ctx.reset_regs(); - let param_base = self.emit_params_prologue(param_specs, body_stmts, parent_ctx, &mut ctx, body_context)?; + let param_base = self.emit_params_prologue( + param_specs, + body_stmts, + parent_ctx, + &mut ctx, + body_context, + extra_capture_exprs, + extra_upvalue_names, + )?; + // 实例字段 computed key 数组所在 upvalue 下标,供字段初始化 emit 定位。 + ctx.field_keys_uv = ctx + .current_upvalue_captures + .iter() + .position(|u| u.name == "@@field_keys") + .map(|i| i as u8); self.predeclare_function_declarations(body_stmts, &mut ctx); @@ -854,7 +878,8 @@ impl Emitter { /// 参数 prologue:函数作用域 + 参数声明/解构 + 闭包捕获与 upvalue 分析。返回 param_base。 fn emit_params_prologue<'a>( &self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx, - ctx: &mut CompileCtx, body_context: FunctionBodyContext, + ctx: &mut CompileCtx, body_context: FunctionBodyContext, extra_capture_exprs: &[&'a Expression<'a>], + extra_upvalue_names: &[(&str, u8)], ) -> Result { ctx.push_scope_with_kind(ScopeKind::FunctionScope); let param_base = ctx.next_reg; @@ -900,7 +925,10 @@ impl Emitter { arguments_reg = Some(reg); } - ctx.captured_bindings = self.collect_captured_bindings(body_stmts, ¶m_defaults, &ctx.own_bindings); + // 字段初始化表达式(值表达式)与参数默认值一并纳入捕获分析。 + let mut capture_exprs: Vec<&oxide_parser::Expression> = param_defaults.clone(); + capture_exprs.extend_from_slice(extra_capture_exprs); + ctx.captured_bindings = self.collect_captured_bindings(body_stmts, &capture_exprs, &ctx.own_bindings); // 自由变量分析:收集 upvalue 捕获(类方法也是普通函数,可捕获外层变量)。 if matches!( body_context, @@ -908,11 +936,22 @@ impl Emitter { ) { ctx.current_upvalue_captures = self.collect_upvalue_names( body_stmts, - ¶m_defaults, + &capture_exprs, &parent_ctx.captured_bindings, &parent_ctx.current_upvalue_captures, &ctx.own_bindings, ); + // 类字段 computed key 数组等合成捕获:直接追加 upvalue(cell_idx 由父分配)。 + for (name, cell_idx) in extra_upvalue_names { + if !ctx.current_upvalue_captures.iter().any(|u| u.name == *name) { + ctx.current_upvalue_captures.push(UpvalueCapture { + name: (*name).to_string(), + enclosing_reg: 0, + cell_idx: *cell_idx, + parent_uv_idx: None, + }); + } + } } // 创建 arguments 对象:指令须在默认参数求值前发出(默认参数可引用 arguments)。 diff --git a/crates/oxide_emit/src/expr/assignment.rs b/crates/oxide_emit/src/expr/assignment.rs index 7ca85ef6..838a5448 100644 --- a/crates/oxide_emit/src/expr/assignment.rs +++ b/crates/oxide_emit/src/expr/assignment.rs @@ -221,25 +221,65 @@ impl Emitter { let store_label = ctx.next_label_id(); let end_label = ctx.next_label_id(); let name = id_ref.name.as_str(); - let var_reg = ctx.lookup_or_global(name); + // 目标判定:upvalue / 被捕获 cell / 普通槽,读与写须穿透共享单元。 + let uv_idx = ctx.current_upvalue_captures.iter().position(|u| u.name == name); + let captured_cell = ctx.captured_bindings.get(name).copied(); let result_reg = ctx.alloc_reg(); - ctx.inst(Inst::new( - OpCode::LOAD_VAR, - Operand::Reg(result_reg), - Operand::Reg(var_reg), - Operand::None, - )); + if let Some(uv) = uv_idx { + ctx.inst(Inst::new( + OpCode::LOAD_UPVALUE, + Operand::Reg(result_reg), + Operand::Imm(uv as u16), + Operand::None, + )); + } else if let Some(cell_idx) = captured_cell { + let a_operand = match ctx.scopes.symbols.lookup_any_binding(name) { + Some((binding, _)) => Operand::Reg(binding.reg), + None => Operand::None, + }; + ctx.inst(Inst::new( + OpCode::CELL_GET, + Operand::Reg(result_reg), + a_operand, + Operand::Imm(cell_idx as u16), + )); + } else { + let var_reg = ctx.lookup_or_global(name); + ctx.inst(Inst::new( + OpCode::LOAD_VAR, + Operand::Reg(result_reg), + Operand::Reg(var_reg), + Operand::None, + )); + } self.emit_logical_assign_test(logical_op, result_reg, store_label, end_label, ctx)?; ctx.labels.set_label_pos(store_label, ctx.insts.len()); let val_reg = self.emit_expression(&assign.right, ctx)?; - let is_const = ctx.lookup_const_flag(name); - let const_flag = if is_const { 1 } else { 0 }; - ctx.inst(Inst::new( - OpCode::STORE_VAR, - Operand::Reg(var_reg), - Operand::Reg(val_reg), - Operand::Imm(const_flag), - )); + if let Some(uv) = uv_idx { + ctx.inst(Inst::new( + OpCode::STORE_UPVALUE, + Operand::None, + Operand::Reg(val_reg), + Operand::Imm(uv as u16), + )); + } else if let Some(cell_idx) = captured_cell { + ctx.inst(Inst::new( + OpCode::CELL_SET, + Operand::None, + Operand::Reg(val_reg), + Operand::Imm(cell_idx as u16), + )); + } else { + let is_const = ctx.lookup_const_flag(name); + let const_flag = if is_const { 1 } else { 0 }; + let var_reg = ctx.lookup_or_global(name); + ctx.inst(Inst::new( + OpCode::STORE_VAR, + Operand::Reg(var_reg), + Operand::Reg(val_reg), + Operand::Imm(const_flag), + )); + } ctx.inst(Inst::new( OpCode::LOAD_VAR, Operand::Reg(result_reg), diff --git a/crates/oxide_emit/src/expr/operator/mod.rs b/crates/oxide_emit/src/expr/operator/mod.rs index 8c0a4564..d9dfae80 100644 --- a/crates/oxide_emit/src/expr/operator/mod.rs +++ b/crates/oxide_emit/src/expr/operator/mod.rs @@ -451,19 +451,20 @@ impl Emitter { )); Ok(if update.prefix { new_reg } else { old_reg }) } else if let Some(cell_idx) = captured_cell { - // 被捕获 cell:CELL_GET + 常量 1 + ADD/SUB + CELL_SET - let val_reg = ctx.alloc_reg(); + // 被捕获 cell:CELL_GET 旧值 + 常量 1 + ADD/SUB + CELL_SET。 + // 后缀形式须保留旧值(结果寄存器),前缀返回新值。 + let old_reg = ctx.alloc_reg(); if let Some((binding, _)) = ctx.scopes.symbols.lookup_any_binding(name) { ctx.inst(Inst::new( OpCode::CELL_GET, - Operand::Reg(val_reg), + Operand::Reg(old_reg), Operand::Reg(binding.reg), Operand::Imm(cell_idx as u16), )); } else { ctx.inst(Inst::new( OpCode::CELL_GET, - Operand::Reg(val_reg), + Operand::Reg(old_reg), Operand::None, Operand::Imm(cell_idx as u16), )); @@ -476,14 +477,15 @@ impl Emitter { } else { OpCode::SUB }; - ctx.inst(Inst::new(op, Operand::Reg(val_reg), Operand::Reg(val_reg), Operand::Reg(one_reg))); + let new_reg = ctx.alloc_reg(); + ctx.inst(Inst::new(op, Operand::Reg(new_reg), Operand::Reg(old_reg), Operand::Reg(one_reg))); ctx.inst(Inst::new( OpCode::CELL_SET, Operand::None, - Operand::Reg(val_reg), + Operand::Reg(new_reg), Operand::Imm(cell_idx as u16), )); - Ok(val_reg) + Ok(if update.prefix { new_reg } else { old_reg }) } else { let var_reg = ctx.lookup_or_global(name); let result_reg = ctx.alloc_reg(); diff --git a/crates/oxide_emit/src/shared/class_field.rs b/crates/oxide_emit/src/shared/class_field.rs index 7de32ac0..906e5da8 100644 --- a/crates/oxide_emit/src/shared/class_field.rs +++ b/crates/oxide_emit/src/shared/class_field.rs @@ -1,43 +1,61 @@ //! 类字段初始化 emit:公有/私有实例字段与私有方法的实例化初始化。 use crate::{CompileCtx, Emitter}; +use oxide_bytecode::module::Constant; use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; use oxide_parser::{Expression, PropertyKey}; impl Emitter { + /// 实例字段初始化:define 语义(不触发原型 setter)。 + /// 非计算键用字符串常量;计算键从 `@@field_keys` upvalue 数组按 `key_slot` 取(类定义期求值一次)。 pub(crate) fn emit_public_field_init( - &self, target: Operand, key: &PropertyKey, computed: bool, value: Option<&Expression>, ctx: &mut CompileCtx, + &self, target: Operand, key: &PropertyKey, computed: bool, value: Option<&Expression>, + key_slot: Option, ctx: &mut CompileCtx, ) -> Result<(), String> { - let key_reg = self.emit_class_key_reg(key, computed, ctx)?; - let value_reg = if let Some(expr) = value { - self.emit_expression(expr, ctx)? + let key_reg = if computed { + self.emit_instance_field_key(key_slot, ctx)? } else { - self.emit_undefined(ctx) + self.emit_class_key_reg(key, false, ctx)? }; - if computed { - ctx.inst(Inst::new( - OpCode::SET_PROP_DYNAMIC, - target, - Operand::Reg(key_reg), - Operand::Reg(value_reg), - )); - } else { - ctx.inst(Inst::new(OpCode::SET_PROP, target, Operand::Reg(value_reg), Operand::Reg(key_reg))); - } + let value_reg = self.emit_field_value(value, ctx)?; + ctx.inst(Inst::define_prop(target, Operand::Reg(value_reg), Operand::Reg(key_reg))); Ok(()) } + /// 类定义期求值一次后存入 upvalue 数组的计算键读取:`arr[slot]`。 + fn emit_instance_field_key(&self, key_slot: Option, ctx: &mut CompileCtx) -> Result { + let uv = ctx.field_keys_uv.expect("computed instance field without field keys upvalue"); + let arr_reg = ctx.alloc_reg(); + ctx.inst(Inst::new(OpCode::LOAD_UPVALUE, Operand::Reg(arr_reg), Operand::Imm(uv as u16), Operand::None)); + let idx_reg = ctx.alloc_reg(); + let idx = ctx.add_constant(Constant::Int(key_slot.unwrap_or(0) as i32)); + ctx.inst(Inst::load_const(Operand::Reg(idx_reg), idx)); + let key_reg = ctx.alloc_reg(); + ctx.inst(Inst::new( + OpCode::GET_PROP_DYNAMIC, + Operand::Reg(arr_reg), + Operand::Reg(idx_reg), + Operand::Reg(key_reg), + )); + Ok(key_reg) + } + + /// 字段值求值:有值求表达式,无值 define undefined。 + pub(crate) fn emit_field_value(&self, value: Option<&Expression>, ctx: &mut CompileCtx) -> Result { + if let Some(expr) = value { + self.emit_expression(expr, ctx) + } else { + Ok(self.emit_undefined(ctx)) + } + } + pub(crate) fn emit_private_field_init( &self, target: Operand, name: &str, value: Option<&Expression>, ctx: &mut CompileCtx, ) -> Result<(), String> { let key_reg = self.emit_private_id_reg(name, ctx)?; - let value_reg = if let Some(expr) = value { - self.emit_expression(expr, ctx)? - } else { - self.emit_undefined(ctx) - }; + let value_reg = self.emit_field_value(value, ctx)?; ctx.inst(Inst::new(OpCode::INIT_PRIVATE, target, Operand::Reg(value_reg), Operand::Reg(key_reg))); Ok(()) } diff --git a/crates/oxide_emit/src/shared/class_key.rs b/crates/oxide_emit/src/shared/class_key.rs index dfb1013f..c7ec9ed2 100644 --- a/crates/oxide_emit/src/shared/class_key.rs +++ b/crates/oxide_emit/src/shared/class_key.rs @@ -3,6 +3,7 @@ use crate::{CompileCtx, Emitter}; use oxide_bytecode::module::Constant; +use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; use oxide_parser::PropertyKey; @@ -51,6 +52,22 @@ impl Emitter { } } + /// 从类定义期 computed key 数组按 slot 取键(方法/静态字段在类定义期使用)。 + pub(crate) fn emit_class_array_key_reg(&self, slot: u8, ctx: &mut CompileCtx) -> Result { + let arr_reg = ctx.class_keys_reg.expect("class computed keys array missing"); + let idx_reg = ctx.alloc_reg(); + let idx = ctx.add_constant(Constant::Int(slot as i32)); + ctx.inst(Inst::load_const(Operand::Reg(idx_reg), idx)); + let key_reg = ctx.alloc_reg(); + ctx.inst(Inst::new( + OpCode::GET_PROP_DYNAMIC, + Operand::Reg(arr_reg), + Operand::Reg(idx_reg), + Operand::Reg(key_reg), + )); + Ok(key_reg) + } + pub(crate) fn class_property_name(&self, key: &PropertyKey) -> Result { match key { PropertyKey::StaticIdentifier(ident) => Ok(ident.name.as_str().to_string()), diff --git a/crates/oxide_ir/src/contract.rs b/crates/oxide_ir/src/contract.rs index 440fd3c1..dc0c26f7 100644 --- a/crates/oxide_ir/src/contract.rs +++ b/crates/oxide_ir/src/contract.rs @@ -65,6 +65,7 @@ impl Inst { | OpCode::DELETE_PROP_STATIC | OpCode::DELETE_PROP_DYNAMIC | OpCode::DEFINE_ACCESSOR + | OpCode::DEFINE_PROP | OpCode::SET_HOME_OBJECT // SPILL 写 VM spill 栈而非寄存器(恢复由 UNSPILL 写 rd) | OpCode::SPILL @@ -256,7 +257,8 @@ impl Inst { | OpCode::SET_PRIVATE | OpCode::INIT_PRIVATE | OpCode::PRIVATE_BRAND_IN - | OpCode::DEFINE_ACCESSOR => { + | OpCode::DEFINE_ACCESSOR + | OpCode::DEFINE_PROP => { push_operand(&mut uses, &self.rd); push_operand(&mut uses, &self.a); push_operand(&mut uses, &self.b); diff --git a/crates/oxide_ir/src/inst.rs b/crates/oxide_ir/src/inst.rs index 9986f05b..8c6a5949 100644 --- a/crates/oxide_ir/src/inst.rs +++ b/crates/oxide_ir/src/inst.rs @@ -173,6 +173,12 @@ impl Inst { Self::with_ext(OpCode::DEFINE_ACCESSOR, home, get, set, &[key_idx]) } + /// define 数据属性:target 为宿主对象,value/key 为寄存器。 + /// 不触发原型链 setter,与 SET_PROP 语义不同。 + pub fn define_prop(target: Operand, value: Operand, key: Operand) -> Self { + Self::new(OpCode::DEFINE_PROP, target, value, key) + } + /// 静态删除属性:obj 同时放 rd/a 槽,const_idx 为属性名常量下标。 pub fn delete_prop_static(obj: Operand, const_idx: u32) -> Self { Self::with_ext(OpCode::DELETE_PROP_STATIC, obj, obj, Operand::None, &[const_idx]) diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 7f171b08..bc066e13 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -1221,6 +1221,10 @@ impl Vm { self.dispatch_define_accessor(rd, a, b)?; } + OpCode::DEFINE_PROP => { + self.dispatch_define_prop(rd, a, b)?; + } + OpCode::RETURN => match self.dispatch_return(rd) { Ok(Some(result)) => return Ok(result), Ok(None) => {} diff --git a/crates/oxide_vm/src/vm_dispatch_ctrl.rs b/crates/oxide_vm/src/vm_dispatch_ctrl.rs index c1f3cc7a..773d06ee 100644 --- a/crates/oxide_vm/src/vm_dispatch_ctrl.rs +++ b/crates/oxide_vm/src/vm_dispatch_ctrl.rs @@ -130,6 +130,23 @@ impl Vm { } } + /// define 数据属性:rd=目标对象,a=值,b=键。与 SET_PROP 不同,不触发原型链 + /// setter,走 `define_data_property`(默认属性 writable/enumerable/configurable)。 + pub(crate) fn dispatch_define_prop(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { + vm_trace!("DEFINE_PROP rd={} value={} key={}", rd, a, b); + let obj_val = self.regs[rd]; + if !obj_val.is_object() { + return self.raise_type_error("DEFINE_PROP target is not object"); + } + let prop_name_si = self.property_key_si(self.regs[b]); + let value = self.regs[a]; + let obj = unsafe { &mut *obj_val.as_js_object_ptr() }; + match self.define_data_property(obj, prop_name_si, value, PropAttributes::DEFAULT_DATA) { + Ok(()) => Ok(()), + Err(msg) => self.raise_error_kind("TypeError", &msg), + } + } + /// break 完成:`crossed`(rd 槽)为 emit 词法算出的逃出 finally 域数。 /// 逐个穿越 finally 后跳转到目标;crossed 为 0 时直接跳转。 pub(crate) fn dispatch_break(&mut self, instr: u32) { diff --git a/crates/oxide_vm/tests/class_tests.rs b/crates/oxide_vm/tests/class_tests.rs index f4553612..cddc614d 100644 --- a/crates/oxide_vm/tests/class_tests.rs +++ b/crates/oxide_vm/tests/class_tests.rs @@ -352,3 +352,77 @@ fn this_survives_native_call_inside_method() { .unwrap(); assert_eq!(result.as_int(), 7); } + +#[test] +fn computed_field_key_evaluated_once_at_class_definition() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "var n=0; class C { [++n] = 1; [++n] = 2; } n").unwrap(); + assert_num(result, 2.0); +} + +#[test] +fn computed_field_key_once_but_value_per_instance() { + let mut vm = Vm::new(); + let result = + eval(&mut vm, "var n=0; class C { [++n] = ++n; } var a=new C(); var b=new C(); n*100 + a[1]*10 + b[1]") + .unwrap(); + assert_num(result, 323.0); +} + +#[test] +fn computed_keys_evaluate_in_source_order_across_static_and_instance() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var i=0; class C { [i++] = i++; static [i++] = i++; [i++] = i++; } var c = new C(); i*1000 + c[0]*100 + c[2]*10 + C[1]", + ) + .unwrap(); + assert_num(result, 6453.0); +} + +#[test] +fn field_definition_does_not_trigger_inherited_setter() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var log=[]; class B { set x(v){ log.push('setter'); } } class C extends B { x = 1; } var c = new C(); log.length*100 + (c.hasOwnProperty('x')?10:0) + c.x", + ) + .unwrap(); + assert_num(result, 11.0); +} + +#[test] +fn field_value_captures_outer_variable() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "var n=0; class C { x = ++n; } var c = new C(); n*10 + c.x").unwrap(); + assert_num(result, 11.0); +} + +#[test] +fn class_expression_field_value_captures_outer_variable() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "var n=0; var C = class { x = ++n; }; new C(); n").unwrap(); + assert_num(result, 1.0); +} + +#[test] +fn field_initializers_run_once_on_second_super_call() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var n=0; class B {} var C = class extends B { field = ++n; constructor(){ super(); super(); } }; var err; try { new C(); } catch(e) { err = e.name; } (err === 'ReferenceError'?1:0)*10 + n", + ) + .unwrap(); + assert_num(result, 11.0); +} + +#[test] +fn postfix_increment_on_captured_cell_returns_old_value() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var n=0; var arr = (function(){ return [n++, n, n++]; })(); arr[0]*1000 + arr[1]*100 + arr[2]*10 + n", + ) + .unwrap(); + assert_num(result, 112.0); +}