diff --git a/crates/oxide_bytecode/src/opcode.rs b/crates/oxide_bytecode/src/opcode.rs index b2df4024..efc3e5f7 100644 --- a/crates/oxide_bytecode/src/opcode.rs +++ b/crates/oxide_bytecode/src/opcode.rs @@ -425,6 +425,8 @@ define_opcodes! { SET_PROP_DYNAMIC = 0x55 => "SET_PROP_DYNAMIC", def = None, uses = [SlotSpec::Slot(Slot::Rd), SlotSpec::Slot(Slot::A), SlotSpec::Slot(Slot::B)], pure = false, jump = false, term = false, ic = false, + // a 槽编码纯静态数据键前缀的属性数(≤255),ext 为每键的常量池下标; + // 运行时按键序链式预建 shape,后续 SET_PROP_BATCH 纯槽写。 NEW_OBJECT = 0x56 => "NEW_OBJECT", def = Some(SlotSpec::Slot(Slot::Rd)), uses = [], pure = true, jump = false, term = false, ic = false, @@ -434,6 +436,11 @@ define_opcodes! { SET_ELEM = 0x58 => "SET_ELEM", def = None, uses = [SlotSpec::Slot(Slot::Rd), SlotSpec::Slot(Slot::A), SlotSpec::Slot(Slot::B)], pure = false, jump = false, term = false, ic = false, + // 对象字面量批量构造的纯槽写:rd=对象,a=值,b=Imm(slot)。键序预建的 shape 槽, + // 不做任何键解析/形状变更,仅写数据槽。 + SET_PROP_BATCH = 0x92 => "SET_PROP_BATCH", + def = None, uses = [SlotSpec::Slot(Slot::Rd), SlotSpec::Slot(Slot::A), SlotSpec::Slot(Slot::B)], + pure = false, jump = false, term = false, ic = false, // ── 成员更新 (0x59-0x62) ── MEMBER_INC = 0x59 => "MEMBER_INC", // 结果写 a 槽(val 槽原地更新) @@ -766,6 +773,7 @@ mod tests { assert_eq!(OpCode::CREATE_ARGUMENTS as u8, 0x63); assert_eq!(OpCode::CREATE_REST_ARRAY as u8, 0x6E); assert_eq!(OpCode::DEFINE_ACCESSOR_DYNAMIC as u8, 0x7F); + assert_eq!(OpCode::SET_PROP_BATCH as u8, 0x92); 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 e4b8fdef..fead3e84 100644 --- a/crates/oxide_compiler/tests/compile_expr_tests.rs +++ b/crates/oxide_compiler/tests/compile_expr_tests.rs @@ -109,6 +109,104 @@ fn compile_object_literal_setter_emits_define_accessor() { ); } +#[test] +fn compile_object_literal_pure_data_keys_emit_batch_construction() { + let module = compile_source("var o = { a: 1, b: 2 };"); + let new_obj: Vec = module + .bytecode + .iter() + .copied() + .filter(|&i| opcode::opcode(i) == OpCode::NEW_OBJECT) + .collect(); + assert_eq!(new_obj.len(), 1, "exactly one NEW_OBJECT"); + assert_eq!(opcode::a(new_obj[0]), 2, "NEW_OBJECT a 槽编码属性数"); + let batch_writes = module + .bytecode + .iter() + .filter(|&&i| opcode::opcode(i) == OpCode::SET_PROP_BATCH) + .count(); + assert_eq!(batch_writes, 2, "批量前缀逐属性发 SET_PROP_BATCH"); + let slow_writes = module + .bytecode + .iter() + .filter(|&&i| opcode::opcode(i) == OpCode::SET_PROP) + .count(); + assert_eq!(slow_writes, 0, "纯数据键字面量不应再发慢路径 SET_PROP"); +} + +#[test] +fn compile_object_literal_computed_key_terminates_batch_prefix() { + let module = compile_source("var o = { a: 1, [k]: 2 };"); + let new_obj: Vec = module + .bytecode + .iter() + .copied() + .filter(|&i| opcode::opcode(i) == OpCode::NEW_OBJECT) + .collect(); + assert_eq!(opcode::a(new_obj[0]), 1, "computed 键终止批段,前缀仅 a"); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::SET_PROP_BATCH), + "前缀属性仍走批量槽写" + ); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::SET_PROP_DYNAMIC), + "computed 键后续走动态路径" + ); +} + +#[test] +fn compile_object_literal_duplicate_key_falls_back_whole_literal() { + let module = compile_source("var o = { a: 1, a: 2 };"); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::SET_PROP), + "重复键整字面量回退慢路径" + ); + assert!(module.bytecode.iter().all(|&i| opcode::opcode(i) != OpCode::SET_PROP_BATCH), "重复键不批"); +} + +#[test] +fn compile_object_literal_proto_key_falls_back() { + let module = compile_source("var o = { a: 1, __proto__: null };"); + let new_obj: Vec = module + .bytecode + .iter() + .copied() + .filter(|&i| opcode::opcode(i) == OpCode::NEW_OBJECT) + .collect(); + assert_eq!(opcode::a(new_obj[0]), 1, "__proto__ 终止批段,前缀仅 a"); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::SET_PROP), + "__proto__ 键走慢路径拦截" + ); +} + +#[test] +fn compile_object_literal_accessor_not_batched() { + let module = compile_source("var o = { a: 1, get b() { return 2; } };"); + let new_obj: Vec = module + .bytecode + .iter() + .copied() + .filter(|&i| opcode::opcode(i) == OpCode::NEW_OBJECT) + .collect(); + assert_eq!(opcode::a(new_obj[0]), 1, "accessor 终止批段"); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::DEFINE_ACCESSOR), + "accessor 后续走访问器定义路径" + ); +} + +#[test] +fn compile_nested_object_literals_both_batch() { + let module = compile_source("var o = { a: { b: 1 } };"); + let batch_objects = module + .bytecode + .iter() + .filter(|&&i| opcode::opcode(i) == OpCode::NEW_OBJECT && opcode::a(i) > 0) + .count(); + assert_eq!(batch_objects, 2, "内外层字面量都走批量构造"); +} + #[test] fn compile_binary_ops() { let tests = [ diff --git a/crates/oxide_emit/src/expr/object/object_literal.rs b/crates/oxide_emit/src/expr/object/object_literal.rs index 18c3933f..f896ed7a 100644 --- a/crates/oxide_emit/src/expr/object/object_literal.rs +++ b/crates/oxide_emit/src/expr/object/object_literal.rs @@ -12,20 +12,85 @@ impl Emitter { &self, obj: &oxide_parser::ObjectExpression, ctx: &mut CompileCtx, ) -> Result { let obj_reg = ctx.alloc_reg(); - ctx.inst(Inst::new(OpCode::NEW_OBJECT, Operand::Reg(obj_reg), Operand::None, Operand::None)); - for prop in &obj.properties { - let ObjectPropertyKind::SpreadProperty(spread) = prop else { - self.emit_object_property(obj_reg, prop, ctx)?; - continue; - }; - // spread 展开:把源表达式的可枚举自有属性写入目标对象(原地改)。 - // 顺序语义:{...b, a:1} 在 spread 之后定义 a,后者覆盖前者(从左到右求值)。 - let src_reg = self.emit_expression(&spread.argument, ctx)?; - ctx.inst(Inst::spread_object(Operand::Reg(obj_reg), Operand::Reg(src_reg))); + // 预扫可批前缀:前导连续段内全部为纯静态数据键(Init、非计算键、非 __proto__、 + // 无重复键、≤255 个)。命中则 NEW_OBJECT 携带键常量表一次链式预建 shape, + // 逐属性改发 SET_PROP_BATCH 纯槽写(省 checked/intern/__proto__/promote 全套)。 + // 否则整字面量回退逐属性 SET_PROP 慢路径。 + let batch_n = self.batchable_prefix_len(obj)?; + if batch_n > 0 { + let key_idxs: Vec = obj.properties[..batch_n] + .iter() + .map(|prop| { + let ObjectPropertyKind::ObjectProperty(p) = prop else { + unreachable!("批量前缀必为 ObjectProperty"); + }; + let name = self.class_property_name(&p.key).expect("批量前缀键为静态名"); + u32::from(ctx.add_constant(Constant::String(name))) + }) + .collect(); + ctx.inst(Inst::new_object(Operand::Reg(obj_reg), batch_n as u32, &key_idxs)); + } else { + ctx.inst(Inst::new(OpCode::NEW_OBJECT, Operand::Reg(obj_reg), Operand::None, Operand::None)); + } + for (i, prop) in obj.properties.iter().enumerate() { + if i < batch_n { + let ObjectPropertyKind::ObjectProperty(p) = prop else { + unreachable!("批量前缀必为 ObjectProperty"); + }; + let val_reg = self.emit_expression(&p.value, ctx)?; + if crate::is_anonymous_function_definition(&p.value) { + if let Some(sub_mod) = ctx.nested.last_mut() { + sub_mod.function_name = Some(self.class_property_name(&p.key).expect("批量前缀键为静态名")); + } + } + ctx.inst(Inst::set_prop_batch(Operand::Reg(obj_reg), Operand::Reg(val_reg), i as u16)); + } else { + let ObjectPropertyKind::SpreadProperty(spread) = prop else { + self.emit_object_property(obj_reg, prop, ctx)?; + continue; + }; + // spread 展开:把源表达式的可枚举自有属性写入目标对象(原地改)。 + // 顺序语义:{...b, a:1} 在 spread 之后定义 a,后者覆盖前者(从左到右求值)。 + let src_reg = self.emit_expression(&spread.argument, ctx)?; + ctx.inst(Inst::spread_object(Operand::Reg(obj_reg), Operand::Reg(src_reg))); + } } Ok(obj_reg) } + /// 计算对象字面量可批前缀长度:前导连续段内全部为 `ObjectProperty::Init` 且键为 + /// 纯静态字符串(非计算、非 `__proto__`),段内无重复键,且总个数 ≤ 255。 + /// + /// # 边界与前提 + /// - 段内出现重复键或超 255 个时整字面量回退(返回 0):批量会为重复键建第二个槽, + /// 破坏 `Object.keys` 的单键语义;超限则 b 槽无法编码。 + /// - computed/accessor/spread/`__proto__` 键仅终止批段,后续属性仍走现路径。 + fn batchable_prefix_len(&self, obj: &oxide_parser::ObjectExpression) -> Result { + let mut n = 0usize; + let mut seen: Vec = Vec::new(); + for prop in &obj.properties { + let ObjectPropertyKind::ObjectProperty(p) = prop else { + break; + }; + if p.computed || p.kind != PropertyKind::Init { + break; + } + let name = self.class_property_name(&p.key)?; + if name == "__proto__" { + break; + } + if seen.contains(&name) { + return Ok(0); + } + if n == 255 { + return Ok(0); + } + seen.push(name); + n += 1; + } + Ok(n) + } + fn emit_object_property( &self, obj_reg: u32, prop: &oxide_parser::ObjectPropertyKind, ctx: &mut CompileCtx, ) -> Result<(), String> { diff --git a/crates/oxide_ir/src/contract.rs b/crates/oxide_ir/src/contract.rs index 1c5ee56a..de91a39d 100644 --- a/crates/oxide_ir/src/contract.rs +++ b/crates/oxide_ir/src/contract.rs @@ -756,6 +756,8 @@ mod tests { assert_contract(OpCode::IC_GET_PROP, Some(1), &[1, 2], false); assert_contract(OpCode::SET_PROP, None, &[1, 2, 3], false); assert_contract(OpCode::SET_PROP_DYNAMIC, None, &[1, 2, 3], false); + // SET_PROP_BATCH:b 槽是 Imm(slot) 非寄存器,canonical 构造下仍按槽位读。 + assert_contract(OpCode::SET_PROP_BATCH, None, &[1, 2, 3], false); assert_contract(OpCode::IC_SET_PROP, None, &[1, 2, 3], false); assert_contract(OpCode::SET_ELEM, None, &[1, 2, 3], false); assert_group(&[OpCode::NEW_OBJECT, OpCode::NEW_ARRAY], Some(1), &[], true); diff --git a/crates/oxide_ir/src/inst.rs b/crates/oxide_ir/src/inst.rs index bc42bdb5..e6a347f0 100644 --- a/crates/oxide_ir/src/inst.rs +++ b/crates/oxide_ir/src/inst.rs @@ -247,6 +247,19 @@ impl Inst { Self::new(OpCode::SPREAD_OBJECT, target, src, Operand::None) } + /// 对象字面量批量构造:`dst` 为新对象,a 槽编码纯静态数据键前缀的属性数(≤255), + /// ext 为每键的常量池下标(`Constant::String`)。运行时按键序链式预建 shape, + /// 后续逐个 `SET_PROP_BATCH` 纯槽写。 + pub fn new_object(dst: Operand, nprops: u32, key_idxs: &[u32]) -> Self { + Self::with_ext(OpCode::NEW_OBJECT, dst, Operand::Imm(nprops as u16), Operand::None, key_idxs) + } + + /// 对象字面量批量构造的纯槽写:`target[slot] = value`。slot 对应键序预建 shape 的槽位, + /// 不做键解析/形状变更。 + pub fn set_prop_batch(target: Operand, value: Operand, slot: u16) -> Self { + Self::new(OpCode::SET_PROP_BATCH, target, value, Operand::Imm(slot)) + } + /// TEMPLATE_STR:变长 ext。首字打包 `(segment_count<<16) | total_len_hint`, /// 后续每 quasi 一项 `quasi_const_idx & 0x7FFF_FFFF`,其后若跟表达式再一项 `0x8000_0000 | expr_reg`。 pub fn template_str(dst: Operand, segment_count: u32, total_len_hint: u16, parts: &[u32]) -> Self { diff --git a/crates/oxide_ir/tests/lowering_tests.rs b/crates/oxide_ir/tests/lowering_tests.rs index 3c627d6c..5a798f6e 100644 --- a/crates/oxide_ir/tests/lowering_tests.rs +++ b/crates/oxide_ir/tests/lowering_tests.rs @@ -307,3 +307,41 @@ fn operand_this_is_semantic_not_physical_index() { assert_eq!(Operand::This, Operand::This); assert_eq!(Operand::NewTarget, Operand::NewTarget); } + +#[test] +fn new_object_batch_carries_nprops_and_key_table() { + // NEW_OBJECT(a=Imm(2)) → 2 个键常量 ext 字;键序即 shape 槽序。 + let mut f = base_module(); + f.insts.push(Inst::new_object(Operand::Reg(1), 2, &[10, 11])); + let m = lower_ok(&f); + assert_eq!(m.bytecode.len(), 3); + assert_eq!(opcode::opcode(m.bytecode[0]), OpCode::NEW_OBJECT); + assert_eq!(opcode::rd(m.bytecode[0]), 1); + assert_eq!(opcode::a(m.bytecode[0]), 2, "a 槽编码属性数"); + assert_eq!(opcode::b(m.bytecode[0]), 0, "nprops ≤255 时高字节为 0"); + assert_eq!(m.bytecode[1], 10); + assert_eq!(m.bytecode[2], 11); +} + +#[test] +fn new_object_zero_props_has_no_ext_words() { + // 空字面量 {} 仍发 0 属性数 NEW_OBJECT,无键表。 + let mut f = base_module(); + f.insts.push(Inst::new_object(Operand::Reg(1), 0, &[])); + let m = lower_ok(&f); + assert_eq!(m.bytecode.len(), 1); + assert_eq!(opcode::opcode(m.bytecode[0]), OpCode::NEW_OBJECT); + assert_eq!(opcode::a(m.bytecode[0]), 0); +} + +#[test] +fn set_prop_batch_encodes_slot_imm_in_b() { + let mut f = base_module(); + f.insts.push(Inst::set_prop_batch(Operand::Reg(1), Operand::Reg(2), 3)); + let m = lower_ok(&f); + assert_eq!(m.bytecode.len(), 1); + assert_eq!(opcode::opcode(m.bytecode[0]), OpCode::SET_PROP_BATCH); + assert_eq!(opcode::rd(m.bytecode[0]), 1); + assert_eq!(opcode::a(m.bytecode[0]), 2); + assert_eq!(opcode::b(m.bytecode[0]), 3, "slot 编码进 b 槽"); +} diff --git a/crates/oxide_vm/src/dispatch/misc.rs b/crates/oxide_vm/src/dispatch/misc.rs index 4e780b07..e19b5c25 100644 --- a/crates/oxide_vm/src/dispatch/misc.rs +++ b/crates/oxide_vm/src/dispatch/misc.rs @@ -144,14 +144,50 @@ impl Vm { Ok(()) } + /// 创建空对象;带键常量表时(a 槽 = 属性数 ≤255,ext = 每键常量池下标)按纯静态 + /// 数据键前缀批量构造:单次链式 `make_shape` 预建整条 shape、预分配全部数据槽, + /// 后续属性值经 SET_PROP_BATCH 纯槽写。 + /// + /// # 步骤 + /// 1. 逐个键常量取 perm string → `property_key_si`(与逐属性 SET_PROP 路径 + /// 逐字节同键),链式 `make_shape` 累加 shape。 + /// 2. 分配对象、预填充 `nprops` 个数据槽、`generation += nprops`(与逐属性路径终值一致)。 + /// + /// # 边界与前提 + /// - `nprops = 0` 时退化为普通空对象(无 ext 键表)。 + /// - 键常量越界取 undefined 防御(emit 保证合法下标)。 #[inline(always)] - pub(crate) fn dispatch_new_object(&mut self, rd: usize) { - vm_trace!("NEW_OBJECT rd={}", rd); + pub(crate) fn dispatch_new_object(&mut self, rd: usize, instr: u32) -> Result<(), String> { + let nprops = opcode::a(instr) as usize; + vm_trace!("NEW_OBJECT rd={} nprops={}", rd, nprops); let proto_ptr = &*self.object_prototype as *const JsObject as *mut JsObject; + let mut shape_id = EMPTY_SHAPE_ID; + if nprops > 0 { + // 先把键常量值拷出(immutables 借 self,property_key_si 需 &mut self)。 + let key_vals: Vec = { + let imm = self.immutables(); + self.bytecode[self.pc..self.pc + nprops] + .iter() + .map(|w| imm.get(*w as usize).copied().unwrap_or(JsValue::undefined())) + .collect() + }; + self.pc += nprops; + for key_val in key_vals { + let si = self.property_key_si(key_val)?; + shape_id = self.kernel_core.shape_forge().make_shape(shape_id, si); + } + } let obj = self .epoch - .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr))); + .alloc(JsObject::new_empty(shape_id, JsValue::from_js_object(proto_ptr))); + // 预分配数据槽并同步 generation:批量一次性 +nprops,与逐属性路径的终值一致。 + let obj_ref = unsafe { &mut *obj }; + for _ in 0..nprops { + obj_ref.push_prop(JsValue::undefined()); + obj_ref.bump_generation(); + } self.regs[rd] = JsValue::object(obj as *mut u8); + Ok(()) } /// 创建 arguments 对象:索引属性取当前帧(或 inline 同步调用)的完整实参, diff --git a/crates/oxide_vm/src/dispatch/property.rs b/crates/oxide_vm/src/dispatch/property.rs index 8d3e4d98..648490c4 100644 --- a/crates/oxide_vm/src/dispatch/property.rs +++ b/crates/oxide_vm/src/dispatch/property.rs @@ -20,6 +20,7 @@ impl Vm { OpCode::IC_SET_PROP => self.dispatch_ic_set_prop(rd, a, b), OpCode::GET_PROP => self.dispatch_get_prop(rd, a, b), OpCode::SET_PROP => self.dispatch_set_prop(rd, a, b), + OpCode::SET_PROP_BATCH => self.dispatch_set_prop_batch(rd, a, b), OpCode::GET_PROP_DYNAMIC => self.dispatch_get_prop_dynamic(rd, a, b), OpCode::SET_PROP_DYNAMIC => self.dispatch_set_prop_dynamic(rd, a, b), OpCode::SET_ELEM => self.dispatch_set_elem(rd, a, b), @@ -500,6 +501,24 @@ impl Vm { Ok(()) } + /// 对象字面量批量构造的纯槽写:把值写入键序预建 shape 的 `slot` 槽。 + /// 不做键解析/`__proto__` 拦截/promote 之外的任何形状变更——键与槽位由 + /// NEW_OBJECT 的键常量表一次性建好。 + /// + /// # 边界与前提 + /// - `slot` 必须 < NEW_OBJECT 预分配的槽数(emit 保证 ≤255)。 + /// - 值仍需 promote(对象值可能来自 session epoch 且目标是逃逸根)。 + fn dispatch_set_prop_batch(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { + vm_trace!("SET_PROP_BATCH rd={} slot={}", rd, b); + let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "Cannot create property on non-object")? else { + return Ok(()); + }; + let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]); + let obj = unsafe { &mut *obj_ptr }; + obj.set_prop_storage(b, value); + Ok(()) + } + fn dispatch_get_prop_dynamic(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { vm_trace!("GET_PROP_DYNAMIC rd={} a={} b={}", rd, a, b); let prop_name_si = self.property_key_si(self.regs[a])?; diff --git a/crates/oxide_vm/src/ic_helper.rs b/crates/oxide_vm/src/ic_helper.rs index c766485c..96aa761c 100644 --- a/crates/oxide_vm/src/ic_helper.rs +++ b/crates/oxide_vm/src/ic_helper.rs @@ -9,7 +9,7 @@ //! ext2 = proto_depth(u8,0 = 接收者自身属性) use crate::vm_trace; -use oxide_bytecode::opcode::{self, Instr}; +use oxide_bytecode::opcode::{self, Instr, OpCode}; use oxide_types::object::JsObject; use oxide_types::value::JsValue; @@ -33,21 +33,58 @@ pub(crate) fn write_ic_back(bytecode: &mut [Instr], pc: usize, shape_id: u32, sl bytecode[pc - 1] = proto_depth as u32; } +/// 计算 `pc` 处指令之后的扩展字个数(按 opcode 语义推进,逐指令字节序一致)。 +/// +/// 定长族返回固定字数;变长族依指令内容:spread 调用从首字读 nstatic|nspread、 +/// TEMPLATE_STR 从首字读 segment_count、NEW_OBJECT 从 a 槽读属性数(键表)。 +/// `clear_ic_caches` 依赖此函数逐指令定位,任何新增变长 ext opcode 必须在此登记。 +fn ext_word_count(bytecode: &[Instr], pc: usize) -> usize { + let op = opcode::opcode(bytecode[pc]); + // IC 系固定 3 扩展字(shape/slot/proto)。 + if op.has_ic_ext_words() { + return 3; + } + match op { + OpCode::SPILL + | OpCode::UNSPILL + | OpCode::CALL + | OpCode::CALL_NATIVE + | OpCode::NEW_EXPRESSION + | OpCode::SUPER_CALL + | OpCode::DEFINE_ACCESSOR + | OpCode::DEFINE_ACCESSOR_DYNAMIC + | OpCode::DEFINE_PROP_ATTRS + | 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::CALL_SPREAD | OpCode::NEW_EXPRESSION_SPREAD | OpCode::SUPER_CALL_SPREAD => { + let header = bytecode.get(pc + 1).copied().unwrap_or(0); + 1 + (header & 0xFF) as usize + ((header >> 8) & 0xFF) as usize + } + OpCode::TEMPLATE_STR => { + let header = bytecode.get(pc + 1).copied().unwrap_or(0); + 1 + ((header >> 16) & 0xFFFF) as usize + } + OpCode::NEW_OBJECT => opcode::a(bytecode[pc]) as usize, + _ => 0, + } +} + /// 把流中所有 IC 扩展字清零,使全部缓存 shape 失效。 +/// +/// 逐指令按 opcode 的 ext 字数推进,避开变长 ext opcode(CALL_SPREAD/TEMPLATE_STR/ +/// NEW_OBJECT 键表)导致的字节错位——错位会把非 IC 指令误当 IC 扩展字清零。 pub(crate) fn clear_ic_caches(bytecode: &mut [Instr]) { let mut i = 0; while i < bytecode.len() { let op = opcode::opcode(bytecode[i]); - if op.has_ic_ext_words() { - if i + 3 < bytecode.len() { - bytecode[i + 1] = 0; - bytecode[i + 2] = 0; - bytecode[i + 3] = 0; - } - i += 4; - } else { - i += 1; + if op.has_ic_ext_words() && i + 3 < bytecode.len() { + bytecode[i + 1] = 0; + bytecode[i + 2] = 0; + bytecode[i + 3] = 0; } + i += 1 + ext_word_count(bytecode, i); } } diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 6a084951..9636d988 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -1560,6 +1560,7 @@ impl Vm { | OpCode::IC_SET_PROP | OpCode::GET_PROP | OpCode::SET_PROP + | OpCode::SET_PROP_BATCH | OpCode::GET_PROP_DYNAMIC | OpCode::SET_PROP_DYNAMIC | OpCode::SET_ELEM @@ -1571,7 +1572,7 @@ impl Vm { } OpCode::NEW_OBJECT => { - self.dispatch_new_object(rd); + self.dispatch_new_object(rd, instr)?; } OpCode::CREATE_ARGUMENTS => { diff --git a/crates/oxide_vm/tests/object_tests.rs b/crates/oxide_vm/tests/object_tests.rs index 7822b0cb..c81bcee9 100644 --- a/crates/oxide_vm/tests/object_tests.rs +++ b/crates/oxide_vm/tests/object_tests.rs @@ -186,3 +186,57 @@ fn object_literal_spread_string_indexes_and_getters() { assert_eq!(eval("({...{get x(){return 5}}}).x"), "5"); assert_eq!(eval_string("JSON.stringify({...[],a:1})"), r#"{"a":1}"#); } + +#[test] +fn object_literal_batch_slot_writes_keep_key_order() { + assert_eq!(eval_string("JSON.stringify({a:1,b:2,c:3})"), r#"{"a":1,"b":2,"c":3}"#); + assert_eq!(eval("({a:1,b:2,c:3}).b"), "2"); +} + +#[test] +fn object_literal_batch_integer_and_string_keys_are_equivalent() { + assert_eq!(eval_string("({0:'a',1:'b'})[0]"), "a"); + assert_eq!(eval_string("({0:'a'})['0']"), "a"); + assert_eq!(eval_string("({1:'x',2:'y'})['2']"), "y"); +} + +#[test] +fn object_literal_batch_proto_key_still_sets_prototype() { + assert_eq!(eval("Object.getPrototypeOf({__proto__:null})"), "null"); + assert_eq!(eval("({a:1,__proto__:{x:5}}).x"), "5"); + assert_eq!(eval("({__proto__:{x:1}}).x"), "1"); +} + +#[test] +fn object_literal_batch_duplicate_key_falls_back() { + assert_eq!(eval("({a:1,a:2}).a"), "2"); + assert_eq!(eval_string("JSON.stringify({a:1,a:2})"), r#"{"a":2}"#); +} + +#[test] +fn object_literal_batch_accessor_after_prefix() { + assert_eq!(eval("({a:1,get b(){return 2}}).b"), "2"); + assert_eq!(eval("var o={a:1,set b(v){this.c=v}}; o.b=9; o.c"), "9"); +} + +#[test] +fn object_literal_batch_nested_and_gc_survival() { + // 嵌套字面量内层也走批;外层对象经 GC 后嵌套值仍存活。 + assert_eq!(eval("({a:{b:1}}).a.b"), "1"); + assert_eq!(eval_string("JSON.stringify({x:{y:{z:1}},n:0})"), r#"{"x":{"y":{"z":1}},"n":0}"#); + assert_eq!(eval("var h=[]; for(var i=0;i<50;i++) h.push({a:{b:i}}); h[49].a.b"), "49"); +} + +#[test] +fn object_literal_batch_value_expr_throw_is_caught() { + assert_eq!(eval("var g; try { ({a:(()=>{throw 1})(),b:2}); } catch(e) { g=e; } g"), "1"); +} + +#[test] +fn object_literal_batch_mixed_computed_spread_order() { + assert_eq!( + eval_string("var k='k',s={m:3}; JSON.stringify({a:1,[k]:2,...s})"), + r#"{"a":1,"k":2,"m":3}"# + ); + assert_eq!(eval("var k='k',s={m:3}; ({a:1,[k]:2,...s}).k"), "2"); +}