diff --git a/crates/oxide_bytecode/src/opcode.rs b/crates/oxide_bytecode/src/opcode.rs index ac1879ee..717a10e1 100644 --- a/crates/oxide_bytecode/src/opcode.rs +++ b/crates/oxide_bytecode/src/opcode.rs @@ -586,6 +586,12 @@ define_opcodes! { DEFINE_GLOBAL_FUNC_BIND = 0x9E => "DEFINE_GLOBAL_FUNC_BIND", def = None, uses = [SlotSpec::Slot(Slot::Rd), SlotSpec::Slot(Slot::A), SlotSpec::Slot(Slot::B)], pure = false, jump = false, term = false, ic = false, + // 计算键访问器并指定描述符:同形 DEFINE_ACCESSOR_ATTRS,但 ext[0] 为高位标记 + // 键寄存器(`0x8000_0000 | key_reg`,键值运行期从寄存器读取)、ext[1] = attrs。 + // 发射方仅 class 体计算 get/set(规范 DefineMethod,enumerable=false)。 + DEFINE_ACCESSOR_ATTRS_DYNAMIC = 0x9F => "DEFINE_ACCESSOR_ATTRS_DYNAMIC", + def = None, uses = [SlotSpec::Slot(Slot::Rd), SlotSpec::Slot(Slot::A), SlotSpec::Slot(Slot::B), SlotSpec::ExtReg], + pure = false, jump = false, term = false, ic = false, // ── 成员复合赋值:位/移位 (0x64-0x69) ── COMPOUND_MEMBER_BIT_AND = 0x64 => "COMPOUND_MEMBER_BIT_AND", @@ -860,6 +866,7 @@ mod tests { assert_eq!(OpCode::DEFINE_ACCESSOR_DYNAMIC as u8, 0x7F); assert_eq!(OpCode::SET_PROP_BATCH as u8, 0x92); assert_eq!(OpCode::CONCAT_N as u8, 0x93); + assert_eq!(OpCode::DEFINE_ACCESSOR_ATTRS_DYNAMIC as u8, 0x9F); assert_eq!(OpCode::ADD.to_string(), "ADD"); assert_eq!(OpCode::COMPOUND_MEMBER_EXP.to_string(), "COMPOUND_MEMBER_EXP"); assert_eq!(OpCode::MOV.to_string(), "MOV"); diff --git a/crates/oxide_compiler/tests/compile_expr_tests.rs b/crates/oxide_compiler/tests/compile_expr_tests.rs index 2c7ced53..7542afd0 100644 --- a/crates/oxide_compiler/tests/compile_expr_tests.rs +++ b/crates/oxide_compiler/tests/compile_expr_tests.rs @@ -701,6 +701,7 @@ fn scan_opcodes(module: &CompiledModule) -> Vec { | OpCode::REST_OBJECT | OpCode::INIT_PRIVATE => 1, OpCode::DEFINE_ACCESSOR_ATTRS + | OpCode::DEFINE_ACCESSOR_ATTRS_DYNAMIC | OpCode::GET_PRIVATE | OpCode::SET_PRIVATE | OpCode::PRIVATE_BRAND_IN => 2, diff --git a/crates/oxide_emit/src/class/method.rs b/crates/oxide_emit/src/class/method.rs index 1ca19c88..d7a9e2b0 100644 --- a/crates/oxide_emit/src/class/method.rs +++ b/crates/oxide_emit/src/class/method.rs @@ -65,24 +65,33 @@ impl Emitter { )); } MethodDefinitionKind::Get | MethodDefinitionKind::Set => { - if method.computed { - return Err("computed class accessors not yet supported".into()); - } let undef_reg = self.emit_undefined(ctx); let (get_reg, set_reg) = if method.kind == MethodDefinitionKind::Get { (accessor_reg, undef_reg) } else { (undef_reg, accessor_reg) }; - let key_name = crate::shared::string_pool::pool_key_property(&method.key)?; - let key_idx = ctx.add_constant(Constant::String(key_name)); - ctx.inst(Inst::define_accessor_attrs( - Operand::Reg(home_reg), - Operand::Reg(get_reg), - Operand::Reg(set_reg), - key_idx as u32, - CLASS_METHOD_ATTRS, - )); + if method.computed { + // 计算键:键值在类定义期已入键数组,此处取 slot 键寄存器, + // 发运行期取键的动态指令(描述符同静态臂)。 + ctx.inst(Inst::define_accessor_attrs_dynamic( + Operand::Reg(home_reg), + Operand::Reg(get_reg), + Operand::Reg(set_reg), + key_reg, + CLASS_METHOD_ATTRS, + )); + } else { + let key_name = crate::shared::string_pool::pool_key_property(&method.key)?; + let key_idx = ctx.add_constant(Constant::String(key_name)); + ctx.inst(Inst::define_accessor_attrs( + Operand::Reg(home_reg), + Operand::Reg(get_reg), + Operand::Reg(set_reg), + key_idx as u32, + CLASS_METHOD_ATTRS, + )); + } } MethodDefinitionKind::Constructor => continue, } diff --git a/crates/oxide_ir/src/inst.rs b/crates/oxide_ir/src/inst.rs index 6ead67d3..8b62c7e7 100644 --- a/crates/oxide_ir/src/inst.rs +++ b/crates/oxide_ir/src/inst.rs @@ -206,6 +206,12 @@ impl Inst { pub fn define_accessor_attrs(home: Operand, get: Operand, set: Operand, key_idx: u32, attrs: u32) -> Self { Self::with_ext(OpCode::DEFINE_ACCESSOR_ATTRS, home, get, set, &[key_idx, attrs]) } + + /// 定义计算键访问器属性并指定描述符:ext = [key 寄存器(高位标记 `0x8000_0000 | key_reg`), + /// attrs](attrs 位同 DEFINE_PROP_ATTRS),键值运行期从寄存器读取。 + pub fn define_accessor_attrs_dynamic(home: Operand, get: Operand, set: Operand, key_reg: u32, attrs: u32) -> Self { + Self::with_ext(OpCode::DEFINE_ACCESSOR_ATTRS_DYNAMIC, home, get, set, &[0x8000_0000 | key_reg, attrs]) + } /// 定义访问器属性(运行时计算键):home 为宿主对象,get/set 为访问器函数寄存器, /// key_reg 为键值寄存器(编码进 ext[0],高位标记 `0x8000_0000 | key_reg`)。 pub fn define_accessor_dynamic(home: Operand, get: Operand, set: Operand, key_reg: u32) -> Self { diff --git a/crates/oxide_ir/tests/contract_tests.rs b/crates/oxide_ir/tests/contract_tests.rs index 09090b12..d64fa92f 100644 --- a/crates/oxide_ir/tests/contract_tests.rs +++ b/crates/oxide_ir/tests/contract_tests.rs @@ -415,6 +415,9 @@ fn canonical_inst(op: OpCode) -> Inst { OpCode::DEFINE_ACCESSOR_DYNAMIC => { Inst::define_accessor_dynamic(Operand::Reg(1), Operand::Reg(2), Operand::Reg(3), 7) } + OpCode::DEFINE_ACCESSOR_ATTRS_DYNAMIC => { + Inst::define_accessor_attrs_dynamic(Operand::Reg(1), Operand::Reg(2), Operand::Reg(3), 7, 0b101) + } OpCode::DELETE_PROP_STATIC => Inst::delete_prop_static(Operand::Reg(1), 9), _ => Inst::new(op, Operand::Reg(1), Operand::Reg(2), Operand::Reg(3)), } @@ -598,6 +601,7 @@ fn opcode_semantics_golden_table() { assert_contract(OpCode::SET_HOME_OBJECT, None, &[1, 2], false); assert_contract(OpCode::DEFINE_ACCESSOR, None, &[1, 2, 3], false); assert_contract(OpCode::DEFINE_ACCESSOR_DYNAMIC, None, &[1, 2, 3, 7], false); + assert_contract(OpCode::DEFINE_ACCESSOR_ATTRS_DYNAMIC, None, &[1, 2, 3, 7], false); // Object Property:GET_PROP 写 a 槽、DYNAMIC 写 b 槽 assert_contract(OpCode::GET_PROP, Some(2), &[1, 3], false); assert_contract(OpCode::GET_PROP_DYNAMIC, Some(3), &[1, 2], false); diff --git a/crates/oxide_ir/tests/inst_constructors_tests.rs b/crates/oxide_ir/tests/inst_constructors_tests.rs index d3a55341..971a40e0 100644 --- a/crates/oxide_ir/tests/inst_constructors_tests.rs +++ b/crates/oxide_ir/tests/inst_constructors_tests.rs @@ -79,6 +79,14 @@ fn single_ext_word_instructions() { assert_eq!(dyn_accessor.a, Operand::Reg(1)); assert_eq!(dyn_accessor.b, Operand::Reg(2)); + // 计算键访问器带描述符:ext = [key 寄存器高位标记, attrs] 两字。 + let attrs_dyn = Inst::define_accessor_attrs_dynamic(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2), 7, 0b101); + assert_eq!(attrs_dyn.op, OpCode::DEFINE_ACCESSOR_ATTRS_DYNAMIC); + assert_eq!(attrs_dyn.ext.as_slice(), &[0x8000_0000 | 7, 0b101]); + assert_eq!(attrs_dyn.rd, Operand::Reg(0)); + assert_eq!(attrs_dyn.a, Operand::Reg(1)); + assert_eq!(attrs_dyn.b, Operand::Reg(2)); + let rest = Inst::rest_object(Operand::Reg(0), Operand::Reg(1), 7, None); assert_eq!(rest.ext.as_slice(), &[7]); diff --git a/crates/oxide_regalloc/src/graph.rs b/crates/oxide_regalloc/src/graph.rs index 10a5897a..f3d84eb3 100644 --- a/crates/oxide_regalloc/src/graph.rs +++ b/crates/oxide_regalloc/src/graph.rs @@ -80,7 +80,7 @@ pub(super) fn collect_real_vregs(f: &IRFunction) -> BTreeSet { } } // 计算键访问器:ext[0] 是 key 寄存器(高位标记 `0x8000_0000 | vreg`)。 - if inst.op == OpCode::DEFINE_ACCESSOR_DYNAMIC { + if matches!(inst.op, OpCode::DEFINE_ACCESSOR_DYNAMIC | OpCode::DEFINE_ACCESSOR_ATTRS_DYNAMIC) { let r = inst.ext.first().copied().unwrap_or(0) & 0x7FFF_FFFF; if r != 0 { real.insert(r); diff --git a/crates/oxide_regalloc/src/rewrite.rs b/crates/oxide_regalloc/src/rewrite.rs index 5aa1d7aa..b3a4f6d0 100644 --- a/crates/oxide_regalloc/src/rewrite.rs +++ b/crates/oxide_regalloc/src/rewrite.rs @@ -238,9 +238,9 @@ fn rewrite_inst( } ext } - // DEFINE_ACCESSOR_DYNAMIC:ext[0] 是 key 寄存器(高位标记 `0x8000_0000 | vreg`), + // 计算键访问器:ext[0] 是 key 寄存器(高位标记 `0x8000_0000 | vreg`), // 低 31 位重映射后保留高位标记。 - OpCode::DEFINE_ACCESSOR_DYNAMIC => { + OpCode::DEFINE_ACCESSOR_DYNAMIC | OpCode::DEFINE_ACCESSOR_ATTRS_DYNAMIC => { let mut ext = inst.ext.clone(); if let Some(word) = ext.first_mut() { let nr = remap_ext_reg(*word & 0x7FFF_FFFF, slot_color, map); diff --git a/crates/oxide_vm/src/ic_helper.rs b/crates/oxide_vm/src/ic_helper.rs index 7416779e..82ad7b3a 100644 --- a/crates/oxide_vm/src/ic_helper.rs +++ b/crates/oxide_vm/src/ic_helper.rs @@ -72,7 +72,11 @@ fn ext_word_count(bytecode: &[Instr], pc: usize) -> usize { | OpCode::DELETE_PROP_STATIC | OpCode::REST_OBJECT | OpCode::INIT_PRIVATE => 1, - OpCode::DEFINE_ACCESSOR_ATTRS | OpCode::GET_PRIVATE | OpCode::SET_PRIVATE | OpCode::PRIVATE_BRAND_IN => 2, + OpCode::DEFINE_ACCESSOR_ATTRS + | OpCode::DEFINE_ACCESSOR_ATTRS_DYNAMIC + | OpCode::GET_PRIVATE + | OpCode::SET_PRIVATE + | OpCode::PRIVATE_BRAND_IN => 2, // 逃出计数 ext:BREAK/CONTINUE/RETURN 恒带 1 个 pack_escape_counts 字 // (for-of/for-in 逃出层数打包);lower 对这三条无条件落 ext 字, // dispatch 经 read_escape_counts 消费,扫描必须同步跳过以免错位。 diff --git a/crates/oxide_vm/src/vm/dispatch_loop.rs b/crates/oxide_vm/src/vm/dispatch_loop.rs index 402ce824..9773782c 100644 --- a/crates/oxide_vm/src/vm/dispatch_loop.rs +++ b/crates/oxide_vm/src/vm/dispatch_loop.rs @@ -517,6 +517,13 @@ impl Vm { self.dispatch_define_accessor_attrs(rd, a, b, key_word, attrs)?; } + OpCode::DEFINE_ACCESSOR_ATTRS_DYNAMIC => { + let key_word = self.bytecode[self.pc]; + let attrs = self.bytecode[self.pc + 1] as u8; + self.pc += 2; + self.dispatch_define_accessor_attrs_dynamic(rd, a, b, key_word, attrs)?; + } + OpCode::RETURN => match self.dispatch_return(instr) { 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 8ffeb2db..79c2cc1f 100644 --- a/crates/oxide_vm/src/vm_dispatch_ctrl.rs +++ b/crates/oxide_vm/src/vm_dispatch_ctrl.rs @@ -153,7 +153,8 @@ impl Vm { } else { self.property_key_si(key_val)? }; - self.dispatch_define_accessor_common(rd, a, b, prop_name_si) + // 对象字面量访问器:可枚举数据语义(DEFAULT_DATA)。 + self.dispatch_define_accessor_common(rd, a, b, prop_name_si, PropAttributes::DEFAULT_DATA) } /// 计算键访问器:ext[0] 高位标记 `0x8000_0000 | key_reg`,键值运行时从寄存器读取。 @@ -163,12 +164,26 @@ impl Vm { self.pc += 1; let key_reg = (key_word & 0x7FFF_FFFF) as usize; let prop_name_si = self.property_key_si(self.regs[key_reg])?; - self.dispatch_define_accessor_common(rd, a, b, prop_name_si) + // 对象字面量访问器:可枚举数据语义(DEFAULT_DATA)。 + self.dispatch_define_accessor_common(rd, a, b, prop_name_si, PropAttributes::DEFAULT_DATA) } - /// 访问器定义公共路径:读 get/set 槽、合并已有访问器、写属性。 + /// 计算键访问器并指定描述符:ext = [key 寄存器(高位标记 `0x8000_0000 | key_reg`), + /// attrs],attrs 位同 DEFINE_PROP_ATTRS(bit0=writable, bit1=enumerable, + /// bit2=configurable)。发射方仅 class 体计算 get/set(规范 DefineMethod, + /// enumerable=false)。 + pub(crate) fn dispatch_define_accessor_attrs_dynamic( + &mut self, rd: usize, a: usize, b: usize, key_word: u32, attrs: u8, + ) -> Result<(), String> { + vm_trace!("DEFINE_ACCESSOR_ATTRS_DYNAMIC rd={} getter={} setter={} attrs={:#04b}", rd, a, b, attrs); + let key_reg = (key_word & 0x7FFF_FFFF) as usize; + let prop_name_si = self.property_key_si(self.regs[key_reg])?; + self.dispatch_define_accessor_common(rd, a, b, prop_name_si, PropAttributes(attrs)) + } + + /// 访问器定义公共路径:读 get/set 槽、合并已有访问器、按 `attrs` 写属性。 fn dispatch_define_accessor_common( - &mut self, rd: usize, a: usize, b: usize, prop_name_si: u32, + &mut self, rd: usize, a: usize, b: usize, prop_name_si: u32, attrs: PropAttributes, ) -> Result<(), String> { let obj_val = self.regs[rd]; if !obj_val.is_object() { @@ -191,7 +206,7 @@ impl Vm { } else { setter }; - match self.define_accessor_property(obj, prop_name_si, get, set, PropAttributes::DEFAULT_DATA) { + match self.define_accessor_property(obj, prop_name_si, get, set, attrs) { Ok(()) => Ok(()), Err(msg) => self.raise_error_kind("TypeError", &msg), } diff --git a/crates/oxide_vm/tests/class_tests.rs b/crates/oxide_vm/tests/class_tests.rs index 88842a62..a4cd3189 100644 --- a/crates/oxide_vm/tests/class_tests.rs +++ b/crates/oxide_vm/tests/class_tests.rs @@ -933,3 +933,114 @@ fn delete_super_property_uninitialized_this_key_not_evaluated_throws() { assert!(err.contains("ReferenceError"), "expected ReferenceError, got: {err}"); assert!(!err.contains("base constructor called"), "base constructor must not run: {err}"); } + +// ── 计算键访问器(get/set [dyn]):类定义期键数组取键 + 运行期取键定义 ── + +// 计算键实例访问器对:string 键读写。 +#[test] +fn computed_instance_accessor_pair() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var k = 'p'; class A { get [k]() { return this.v + 1; } set [k](x) { this.v = x; } } var a = new A(); a.p = 41; a.p", + ) + .unwrap(); + assert_num(result, 42.0); +} + +// 计算键静态访问器:Symbol.species 键(Promise 同形)。 +#[test] +fn computed_static_accessor_symbol_species_key() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "class A { static get [Symbol.species]() { return 7; } } A[Symbol.species]").unwrap(); + assert_eq!(result.as_int(), 7); +} + +// 半对:只有 getter,setter 边为 undefined。 +#[test] +fn computed_accessor_get_only_set_undefined() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var k = 'g'; class A { get [k]() { return 3; } } Object.getOwnPropertyDescriptor(Object.getPrototypeOf(new A()), 'g').set === undefined", + ) + .unwrap(); + assert!(result.is_bool() && result.as_bool(), "set should be undefined"); +} + +// 半对:只有 setter,getter 边为 undefined。 +#[test] +fn computed_accessor_set_only_get_undefined() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var k = 's'; class A { set [k](v) { this._v = v; } } Object.getOwnPropertyDescriptor(Object.getPrototypeOf(new A()), 's').get === undefined", + ) + .unwrap(); + assert!(result.is_bool() && result.as_bool(), "get should be undefined"); +} + +// 与既有访问器对合并:静态 get b 后被计算键 get ['b'] 覆盖,getter 替换、setter 保留。 +#[test] +fn computed_accessor_merges_existing_pair() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var k = 'b'; class A { get b() { return 1; } set b(v) { this._v = v; } get [k]() { return 9; } } var a = new A(); a.b = 5; a.b * 10 + a._v", + ) + .unwrap(); + assert_num(result, 95.0); +} + +// 描述符核:enumerable=false、configurable=true(规范 DefineMethod,区别于对象 +// 字面量访问器的可枚举)。 +#[test] +fn computed_accessor_descriptor_non_enumerable_configurable() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var k = 'd'; class A { get [k]() { return 1; } } var d = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(new A()), 'd'); (d.enumerable === false) * 10 + (d.configurable === true)", + ) + .unwrap(); + assert_num(result, 11.0); +} + +// 计算键 'constructor':覆写原型 constructor 数据属性为访问器。 +#[test] +fn computed_accessor_constructor_key_overrides_prototype() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "class A { get ['constructor']() { return 6; } } var a = new A(); Object.getOwnPropertyDescriptor(Object.getPrototypeOf(a), 'constructor').get !== undefined ? a.constructor : -1", + ) + .unwrap(); + assert_num(result, 6.0); +} + +// static get ['prototype'] 抛 TypeError(prototype 属性不可配置,定义被拒)。 +#[test] +fn computed_static_accessor_prototype_key_throws() { + let mut vm = Vm::new(); + let err = eval(&mut vm, "class A { static get ['prototype']() { return 1; } }").unwrap_err(); + assert!(err.contains("TypeError"), "expected TypeError, got: {err}"); +} + +// 键表达式副作用恰执行一次(类定义期键数组构建,方法发射不重求值)。 +#[test] +fn computed_accessor_key_evaluated_once() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "var n = 0; class A { get [(n++, 'z')]() { return 1; } } new A().z; n").unwrap(); + assert_eq!(result.as_int(), 1); +} + +// 命名 symbol 计算键:运行期键走 symbol 键面读写。 +#[test] +fn computed_accessor_symbol_key() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var s = Symbol('tag'); class A { get [s]() { return 4; } set [s](v) { this._v = v; } } var a = new A(); a[s] = 3; a[s] * 10 + a._v", + ) + .unwrap(); + assert_num(result, 43.0); +}