perf: 对象字面量批量构造键序预建 shape

This commit is contained in:
2026-08-14 04:47:15 +08:00
parent 9758cee544
commit 12ebdb8c0e
11 changed files with 395 additions and 24 deletions
+39 -3
View File
@@ -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<JsValue> = {
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 同步调用)的完整实参,
+19
View File
@@ -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])?;
+47 -10
View File
@@ -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);
}
}
+2 -1
View File
@@ -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 => {
+54
View File
@@ -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");
}