perf: member 复合写路径接入 IC 写命中(depth 0)消除每次 DashMap 查询与回写

set_member_prop 增加 ic_set_hit_own(仅接受 depth==0 槽,跳过读侧学习的
原型条目防原型污染)命中直写槽,ext_pc 由 read_member_prop 显式带出消除
隐式 pc 契约;写新属性 miss 分支(IC_SET 分发 + member 写)统一改
CreateDataProperty 快路径(保留原型链 setter/readonly 检查),砍掉
ordinary_set 全链路的重复查询与 shape 转换开销。
This commit is contained in:
2026-08-15 08:16:49 +08:00
parent abeab6f216
commit aa4c28cdf0
7 changed files with 361 additions and 52 deletions
+18 -18
View File
@@ -126,10 +126,10 @@ impl Vm {
};
let receiver = self.regs[rd];
let obj = unsafe { &mut *obj_ptr };
let prop_val = self.read_member_prop(obj, prop_name_si, receiver)?;
let (prop_val, ext_pc) = self.read_member_prop(obj, prop_name_si, receiver)?;
let n = self.coerce_number_bounded(prop_val)?;
let new_val = JsValue::float(n + 1.0);
self.set_member_prop(obj, prop_name_si, new_val, receiver)?;
self.set_member_prop(obj, prop_name_si, new_val, receiver, ext_pc)?;
self.regs[a] = new_val;
Ok(())
}
@@ -141,10 +141,10 @@ impl Vm {
};
let receiver = self.regs[rd];
let obj = unsafe { &mut *obj_ptr };
let prop_val = self.read_member_prop(obj, prop_name_si, receiver)?;
let (prop_val, ext_pc) = self.read_member_prop(obj, prop_name_si, receiver)?;
let n = self.coerce_number_bounded(prop_val)?;
let new_val = JsValue::float(n - 1.0);
self.set_member_prop(obj, prop_name_si, new_val, receiver)?;
self.set_member_prop(obj, prop_name_si, new_val, receiver, ext_pc)?;
self.regs[a] = new_val;
Ok(())
}
@@ -187,7 +187,7 @@ impl Vm {
return Ok(());
};
let receiver = self.regs[rd];
let prop_raw = {
let (prop_raw, ext_pc) = {
let obj = unsafe { &mut *obj_ptr };
self.read_member_prop(obj, prop_name_si, receiver)?
};
@@ -203,7 +203,7 @@ impl Vm {
JsValue::float(ln + rn)
};
let obj = unsafe { &mut *obj_ptr };
self.set_member_prop(obj, prop_name_si, new_val, receiver)?;
self.set_member_prop(obj, prop_name_si, new_val, receiver, ext_pc)?;
self.regs[a] = new_val;
Ok(())
}
@@ -219,16 +219,16 @@ impl Vm {
return Ok(());
};
let receiver = self.regs[rd];
let ln = {
let (ln, ext_pc) = {
let obj = unsafe { &mut *obj_ptr };
let prop_val = self.read_member_prop(obj, prop_name_si, receiver)?;
self.coerce_number_bounded(prop_val)?
let (prop_val, ext_pc) = self.read_member_prop(obj, prop_name_si, receiver)?;
(self.coerce_number_bounded(prop_val)?, ext_pc)
};
let rn = self.coerce_number_bounded(self.regs[a])?;
let new_val = JsValue::float(op(ln, rn));
self.regs[a] = new_val;
let obj = unsafe { &mut *obj_ptr };
self.set_member_prop(obj, prop_name_si, new_val, receiver)?;
self.set_member_prop(obj, prop_name_si, new_val, receiver, ext_pc)?;
Ok(())
}
@@ -243,16 +243,16 @@ impl Vm {
return Ok(());
};
let receiver = self.regs[rd];
let ln = {
let (ln, ext_pc) = {
let obj = unsafe { &mut *obj_ptr };
let prop_val = self.read_member_prop(obj, prop_name_si, receiver)?;
self.coerce_int32_bounded(prop_val)?
let (prop_val, ext_pc) = self.read_member_prop(obj, prop_name_si, receiver)?;
(self.coerce_int32_bounded(prop_val)?, ext_pc)
};
let rn = self.coerce_int32_bounded(self.regs[a])?;
let new_val = JsValue::int(op(ln, rn));
self.regs[a] = new_val;
let obj = unsafe { &mut *obj_ptr };
self.set_member_prop(obj, prop_name_si, new_val, receiver)?;
self.set_member_prop(obj, prop_name_si, new_val, receiver, ext_pc)?;
Ok(())
}
@@ -265,10 +265,10 @@ impl Vm {
return Ok(());
};
let receiver = self.regs[rd];
let ln = {
let (ln, ext_pc) = {
let obj = unsafe { &mut *obj_ptr };
let prop_val = self.read_member_prop(obj, prop_name_si, receiver)?;
self.coerce_int32_bounded(prop_val)?
let (prop_val, ext_pc) = self.read_member_prop(obj, prop_name_si, receiver)?;
(self.coerce_int32_bounded(prop_val)?, ext_pc)
};
let shift = self.coerce_uint32_bounded(self.regs[a])? & 0x1F;
let new_val = if !is_right {
@@ -285,7 +285,7 @@ impl Vm {
};
self.regs[a] = new_val;
let obj = unsafe { &mut *obj_ptr };
self.set_member_prop(obj, prop_name_si, new_val, receiver)?;
self.set_member_prop(obj, prop_name_si, new_val, receiver, ext_pc)?;
Ok(())
}
}
+7 -17
View File
@@ -1,6 +1,5 @@
use crate::{ic_debug, ic_trace, vm_trace};
use oxide_bytecode::opcode::{OpCode, IC_EXT_WORDS};
use oxide_kernel::prop_forge::PropTemplate;
use oxide_runtime_api as coercion;
use oxide_types::object::JsObject;
use oxide_types::private_key::{int_key_value, is_int_key, make_private_name_id};
@@ -443,23 +442,14 @@ impl Vm {
ic_debug!("IC_SET write-back shape={} slot={}", obj.shape_id(), pos);
} else {
self.profiling.record_ic_miss();
let old_shape = obj.shape_id();
self.ordinary_set_dispatch(obj, prop_name_si, value, receiver)?;
if old_shape != obj.shape_id() {
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
ic_helper::write_ic_back(self.bytecode_mut(), ic_pc, obj.shape_id(), pos, 0);
ic_debug!("IC_SET write-back shape={} slot={}", obj.shape_id(), pos);
self.kernel_core.prop_forge().upsert(
obj.shape_id(),
PropTemplate {
shape_id: obj.shape_id(),
prop_name: prop_name_si,
position: pos,
generation: obj.generation(),
},
);
}
prop_cache_miss();
// 写新属性(shape 转换):数组 length / 整数索引键仍走完整
// ordinary_set_dispatch 语义,其余走 CreateDataProperty 快路径。
if self.named_prop_create_needs_ordinary_set(obj, prop_name_si) {
self.ordinary_set_dispatch(obj, prop_name_si, value, receiver)?;
return Ok(());
}
self.create_named_prop_fast(obj, prop_name_si, value, receiver, ic_pc, true)?;
}
Ok(())
+48
View File
@@ -239,3 +239,51 @@ pub(crate) fn ic_set_hit_poly(obj: &mut JsObject, bytecode: &[Instr], ext_pc: us
}
false
}
/// member 复合写(读改写共用同一扩展字组)的写侧命中判定:顺序遍历 4 槽,只接受
/// depth==0 的数据槽并直写。
///
/// 读侧会把继承属性解析结果以 depth>0 条目写回同一扩展字组;写侧若命中这些条目
/// 会把新值直写原型对象(原型污染,规范要求 shadow 到接收者 own 属性)——因此
/// 原型条目一律跳过,落到慢路径走完整 [[Set]] 语义。
///
/// `pc` 为越过该 IC 指令全部扩展字后的位置(与 [`write_ic_back`] 同一约定),
/// 命中遍历从 `pc - IC_EXT_WORDS` 起的扩展字开始。
#[inline(always)]
pub(crate) fn ic_set_hit_own(obj: &mut JsObject, bytecode: &[Instr], pc: usize, value: JsValue) -> bool {
let base = pc - IC_EXT_WORDS;
let shape_id = bytecode[base] & 0x00FF_FFFF;
if shape_id == 0 {
return false;
}
let proto_depth = (bytecode[base] >> 24) as u8;
let slot_index = bytecode[base + 1];
if proto_depth == 0 && obj.shape_id() == shape_id && slot_index < obj.prop_vec_len() as u32 {
obj.set_prop_shape(slot_index, value);
return true;
}
ic_set_hit_own_poly(obj, bytecode, base, value)
}
/// 写侧多态槽(1..3)遍历,depth==0 only;`inline(never)` 防主循环代码膨胀。
/// `ext_pc` 为扩展字起始位置(首个扩展字处)。
#[inline(never)]
fn ic_set_hit_own_poly(obj: &mut JsObject, bytecode: &[Instr], ext_pc: usize, value: JsValue) -> bool {
for i in 1..IC_SLOTS {
let base = ext_pc + i * 2;
let shape_id = bytecode[base] & 0x00FF_FFFF;
if shape_id == 0 {
break;
}
let proto_depth = (bytecode[base] >> 24) as u8;
if proto_depth != 0 {
continue;
}
let slot_index = bytecode[base + 1];
if obj.shape_id() == shape_id && slot_index < obj.prop_vec_len() as u32 {
obj.set_prop_shape(slot_index, value);
return true;
}
}
false
}
+79 -13
View File
@@ -1,5 +1,6 @@
use crate::vm::{FrameContinuation, Vm, MAX_PROTO_CHAIN_DEPTH};
use crate::{ic_trace, vm_trace};
use oxide_kernel::prop_forge::PropTemplate;
use oxide_runtime_api as coercion;
use oxide_types::object::{JsObject, PropAttributes, PropMetaEntry};
use oxide_types::value::JsValue;
@@ -333,15 +334,19 @@ impl Vm {
Ok(())
}
/// 读取 member 复合操作数(obj.x / obj["x"]),返回 (值, IC 扩展字起始位置)。
///
/// `ext_pc` 供同一指令的写侧 [`Self::set_member_prop`] 使用:读侧负责推进 pc
/// 越过扩展字,写侧显式接收该位置、不再接触 pc,消除隐式 pc 契约。
pub(crate) fn read_member_prop(
&mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue,
) -> Result<JsValue, String> {
) -> Result<(JsValue, usize), String> {
vm_trace!("read_member_prop: shape_id={} prop_name_si={}", obj.shape_id(), prop_name_si);
let (cached_shape_id, cached_slot, cached_depth) =
crate::ic_helper::read_ic_slot0(&self.bytecode, &mut self.pc);
let ic_pc = self.pc;
if obj.has_prop_meta() {
return self.ordinary_get(obj, prop_name_si, receiver);
return self.ordinary_get(obj, prop_name_si, receiver).map(|v| (v, ic_pc));
}
let val = if let Some(v) = crate::ic_helper::ic_get_hit(obj, cached_shape_id, cached_slot, cached_depth) {
@@ -362,7 +367,7 @@ impl Vm {
} else {
self.proto_chain_ic_get(obj, prop_name_si, receiver)?
};
Ok(val)
Ok((val, ic_pc))
}
/// 执行 ordinary_get 并把 IC 按原型链深度写回(own 属性 depth=0,原型链按层计数)。
@@ -398,25 +403,86 @@ impl Vm {
Ok(resolved)
}
/// member 复合写(obj.x += 1 / obj.x++ 等)的写侧:优先 IC 写命中(depth==0 槽)
/// 直写返回,miss 走查表/写新属性快路径。
///
/// `ext_pc` 由调用方从 [`Self::read_member_prop`] 返回值带出(读侧已把 pc 推进
/// 越过本 IC 指令扩展字后的位置);写侧不再接触 pc,避免二次推进导致指令定位错位。
pub(crate) fn set_member_prop(
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue,
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue, ext_pc: usize,
) -> Result<(), String> {
ic_trace!("set_member_prop: shape_id={} prop_name_si={}", obj.shape_id(), prop_name_si);
let ic_pc = self.pc;
let val = self.promote_if_needed_for_write_ptr(obj as *mut JsObject, val);
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
if obj.has_prop_meta() {
self.ordinary_set(obj, prop_name_si, val, receiver)?;
return Ok(());
}
obj.set_prop_shape(pos, val);
crate::ic_helper::write_ic_back(self.bytecode_mut(), ic_pc, obj.shape_id(), pos, 0);
} else {
if obj.has_prop_meta() {
self.ordinary_set(obj, prop_name_si, val, receiver)?;
return Ok(());
}
if crate::ic_helper::ic_set_hit_own(obj, &self.bytecode, ext_pc, val) {
self.profiling.record_ic_hit();
return Ok(());
}
self.profiling.record_ic_miss();
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
obj.set_prop_shape(pos, val);
crate::ic_helper::write_ic_back(self.bytecode_mut(), ext_pc, obj.shape_id(), pos, 0);
} else {
self.create_named_prop_fast(obj, prop_name_si, val, receiver, ext_pc, false)?;
}
Ok(())
}
/// 写新属性的 CreateDataProperty 快路径(shape 转换,IC 命中在数学上无法覆盖):
/// 原型链同名 accessor/只读检查后,一次 make_shape + 追加槽 + IC 写回 + 模板 upsert。
///
/// # 边界与前提
/// - 调用方已确认属性不存在于 shape 链(lookup_position miss)。
/// - 数组 length / 数组与 TA 整数索引键不进入本路径(调用方先分流到 ordinary_set)。
/// - `use_frame_push` 与 ordinary_set* 入口一致(member 写 false / IC_SET 分发 true)。
pub(crate) fn create_named_prop_fast(
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue, ext_pc: usize,
use_frame_push: bool,
) -> Result<(), String> {
// 原型链同名 accessor 须触发 setter、只读 data 须报错(与 ordinary_set 一致),
// 命中时不得创建 own 属性(规范 [[Set]] shadow 语义)。
if let Some(meta) = self.inherited_property_meta(obj, prop_name_si) {
if meta.is_accessor {
if meta.set.is_undefined() {
return self.raise_type_error("property has no setter");
}
return self.call_or_push_setter(meta.set, receiver, val, use_frame_push);
}
if !meta.attributes.writable() {
return self.raise_type_error("cannot assign to read-only property");
}
}
// 新 shape 槽位 = 追加前命名属性数(与 push_prop 的追加位置一致)。
let slot = obj.prop_vec_len() as u32;
let new_shape_id = self.kernel_core.shape_forge().make_shape(obj.shape_id(), prop_name_si);
obj.set_shape_id(new_shape_id);
obj.push_prop(val);
obj.bump_generation();
crate::ic_helper::write_ic_back(self.bytecode_mut(), ext_pc, new_shape_id, slot, 0);
self.kernel_core.prop_forge().upsert(
new_shape_id,
PropTemplate {
shape_id: new_shape_id,
prop_name: prop_name_si,
position: slot,
generation: obj.generation(),
},
);
Ok(())
}
/// 写新属性须分流到完整 ordinary_set 语义的场景:数组 length(ArraySetLength)与
/// 数组/TA 整数索引键(元素区 / buffer 写)——这些键不在 shape 链上,CreateDataProperty
/// 快路径会错误地为其建命名属性。
pub(crate) fn named_prop_create_needs_ordinary_set(&self, obj: &JsObject, prop_name_si: u32) -> bool {
(obj.is_array() && prop_name_si == self.length_si)
|| (obj.is_typed_array_obj() || obj.is_array())
&& self.array_index_from_property_key(prop_name_si).is_some()
}
pub(crate) fn set_or_create_prop_value(&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue) {
vm_trace!("set_or_create_prop_value: shape_id={} prop_name_si={}", obj.shape_id(), prop_name_si);
// TypedArray 整数索引写 buffer(越界忽略),不进入 shape/prop 槽。
@@ -154,10 +154,10 @@ fn rerun_clears_polymorphic_slots() {
);
}
/// member 读侧(obj.x++)多 shape 轮换:read_member_prop 多态命中,
/// 写侧(set_member_prop)暂不接命中——本任务只验证格式同步不错位。
/// member 复合写(obj.x++)多 shape 轮换:读侧与写侧都经 4 槽缓存命中,
/// 每 shape 仅读侧学习 1 次 miss(写侧复用读侧条目直写)。
#[test]
fn member_compound_polymorphic_read_side_hits() {
fn member_compound_polymorphic_read_write_hits() {
let (r, _hits, misses) = run_once(
r#"var a = { x: 1 }, b = { x: 2, y: 3 };
for (var i = 0; i < 10000; i++) {
@@ -167,7 +167,7 @@ fn member_compound_polymorphic_read_side_hits() {
a.x + b.x"#,
);
assert_eq!(r, "10003", "两对象 x 各自增 5000 次(从 1/2 起)");
assert!(misses < 5000, "读侧 4 槽缓存使 miss 远低于迭代数(实际 {misses},写侧新属性路径不计入)");
assert_eq!(misses, 2, "两 shape 各 1 次读侧学习 miss,写侧全命中");
}
/// 非对象 receiver(字符串)经 IC primitive 分支:手动跳越全部扩展字后语义正确。
@@ -0,0 +1,188 @@
//! member 复合写(obj.x += 1 / obj.x++ 等)接入 IC 写命中的行为测试。
//!
//! 覆盖三组语义:①写侧命中(depth==0 槽直写,读写对称);②写新属性走
//! CreateDataProperty 快路径(shape 转换,命中无法覆盖);③继承属性 shadow /
//! 原型 setter / 只读继承属性(写侧 depth==0 限制与原型链检查兜底,防原型污染)。
//!
//! 计数断言基于单次 run 内 miss/hit 对比(run() 每次冷启动,IC 学习不跨 run)。
use oxide_compiler::compiler::Compiler;
use oxide_parser::Allocator;
use oxide_vm::vm::Vm;
/// 编译并单次运行,返回 (结果, hits, misses)。
fn run_once(source: &str) -> (String, u64, u64) {
let allocator = Allocator::default();
let program = oxide_parser::parse(&allocator, source).expect("parse");
let module = Compiler::new().compile(&program).expect("compile");
let mut vm = Vm::new();
let result = vm.run(&module).expect("run");
// 字符串结果经 lookup_str 提取内容(JsValue Display 只打印 {string})。
let r = vm.lookup_str(result).unwrap_or_else(|| format!("{result}"));
(r, vm.ic_hit_count(), vm.ic_miss_count())
}
/// 单态 member 复合写:写侧命中直写(读侧命中不计数,pre-existing 口径——
/// 计数仅来自写侧与独立 IC_GET/IC_SET 分发站点)。
#[test]
fn member_compound_monomorphic_write_hits() {
let (r, hits, misses) = run_once(
r#"var t = { x: 0 };
for (var i = 0; i < 10000; i++) { t.x += 1; }
t.x"#,
);
assert_eq!(r, "10000");
assert_eq!(misses, 1, "仅末尾 `t.x` IC_GET 站点学习 miss,循环写侧零 miss");
assert_eq!(hits, 10000, "循环 10000 次写侧全命中(实际 {hits})");
}
/// 4 shape 轮换 member 复合写:写侧复用读侧学习的 4 槽条目,循环内零写 miss。
#[test]
fn member_compound_poly4_read_write_hits() {
let (r, hits, misses) = run_once(
r#"var objs = [{x:0}, {x:0,y:1}, {x:0,z:1}, {x:0,y:1,z:2}];
for (var i = 0; i < 10000; i++) {
var t = objs[i % 4];
t.x += 1;
}
objs[0].x + objs[1].x + objs[2].x + objs[3].x"#,
);
assert_eq!(r, "10000", "四对象 x 各增 2500 次");
assert_eq!(misses, 4, "末尾 4 个 `objs[i].x` IC_GET 站点各学习 1 次");
assert_eq!(hits, 10000, "循环写侧 10000 全命中(实际 {hits})");
}
/// 继承属性 shadow(核心正确性):写侧只接受 depth==0 槽,原型不被污染。
#[test]
fn inherited_prop_shadows_receiver_not_proto() {
let (r, _hits, _misses) = run_once(
r#"var p = { x: 1 };
var o = Object.create(p);
o.x += 1;
o.x + ":" + p.x"#,
);
assert_eq!(r, "2:1", "o 获得 own x=2,p.x 保持 1(未直写原型)");
}
/// IC_SET 独立赋值路径的继承 shadow:`Object.create(p).x = 5` 同样不写原型。
#[test]
fn ic_set_inherited_shadow_receiver_only() {
let (r, _hits, _misses) = run_once(
r#"var p = { x: 1 };
var o = Object.create(p);
o.x = 5;
p.x"#,
);
assert_eq!(r, "1", "p.x 保持 1,o 得到 own x=5");
}
/// 原型 accessor setter:IC_SET 与 member 复合写都触发 setter,接收者不建新槽。
#[test]
fn proto_setter_called_both_write_paths() {
let (r, _hits, _misses) = run_once(
r#"var calls = 0;
var p = { set x(v) { calls++; } };
var o = Object.create(p);
o.x = 5;
o.x += 2;
calls + ":" + Object.keys(o).length"#,
);
assert_eq!(r, "2:0", "setter 被调 2 次(IC_SET 与 member 各 1),o 无 own 属性");
}
/// 原型只读 data:两条写路径都按 ordinary_set 抛 TypeError。
#[test]
fn readonly_inherited_data_raises() {
let (r, _hits, _misses) = run_once(
r#"var p = {};
Object.defineProperty(p, 'x', { value: 1, writable: false });
var o = Object.create(p);
var msg = '';
try { o.x = 5; } catch (e) { msg = e.message; }
try { o.x += 1; } catch (e) { msg += '|' + e.message; }
msg"#,
);
assert_eq!(
r, "cannot assign to read-only property|cannot assign to read-only property",
"两条路径都拒绝写只读继承属性"
);
}
/// 写新属性(shape 转换):快路径建 shape + 写回,循环内同 shape 写全命中。
#[test]
fn write_new_prop_then_hit() {
let (r, hits, misses) = run_once(
r#"var o = {};
o.y = 0;
for (var i = 0; i < 10000; i++) { o.y += 1; }
o.y"#,
);
assert_eq!(r, "10000");
assert_eq!(misses, 2, "`o.y = 0` 站点 1 次 + 末尾 `o.y` IC_GET 站点 1 次");
assert_eq!(hits, 10000, "compound 写侧 10000 全命中(实际 {hits})");
}
/// ic_cache.js 同款模式(批量建对象 + 分批写 y/z 新属性):快路径建 shape 正确。
#[test]
fn ic_cache_style_new_prop_writes() {
let (r, _hits, _misses) = run_once(
r#"var objs = [];
for (var i = 0; i < 100; i++) {
objs.push({ x: i });
if (i % 3 === 0) objs[i].y = i;
if (i % 5 === 0) objs[i].z = i;
}
var yok = 0, zok = 0;
for (var i = 0; i < 100; i++) {
if (i % 3 === 0 && objs[i].y === i) yok++;
if (i % 5 === 0 && objs[i].z === i) zok++;
}
yok + ":" + zok"#,
);
assert_eq!(r, "34:20", "y/z 全部写入成功(含 i%15==0 的 y+z 同对象场景)");
}
/// has_prop_meta 对象 member 写走 ordinary_set(不缓存,accessor 语义保留)。
#[test]
fn meta_object_member_write_uses_ordinary_set() {
let (r, _hits, _misses) = run_once(
r#"var store = 1;
var o = { get a() { return store; }, set a(v) { store = v; } };
o.a += 5;
store"#,
);
assert_eq!(r, "6", "accessor 对象走 setter,store 更新为 6");
}
/// 命中率口径:member 写 hit/miss 计入后 ic_hit_rate 反映写侧全量命中。
#[test]
fn member_write_counts_in_ic_hit_rate() {
let allocator = Allocator::default();
let program = oxide_parser::parse(
&allocator,
r#"var t = { x: 0 };
for (var i = 0; i < 1000; i++) { t.x += 1; }
t.x"#,
)
.expect("parse");
let module = Compiler::new().compile(&program).expect("compile");
let mut vm = Vm::new();
let result = vm.run(&module).expect("run");
let r = vm.lookup_str(result).unwrap_or_else(|| format!("{result}"));
assert_eq!(r, "1000");
// 循环写侧 1000 命中 + 末尾 IC_GET 1 miss → 命中率 ~0.999(写侧计入后趋近真实)。
assert!(vm.ic_hit_rate() > 0.99, "member 写侧命中计入命中率(实际 {:.4})", vm.ic_hit_rate());
}
/// `__proto__` 赋值 + 继承成员复合写:原型链建立正常,shadow 语义不变(回归锚定)。
#[test]
fn proto_assignment_then_compound_shadow() {
let (r, _hits, _misses) = run_once(
r#"var p = { x: 1 };
var o = {};
o.__proto__ = p;
o.x += 1;
o.x + ":" + p.x"#,
);
assert_eq!(r, "2:1", "__proto__ 赋值建链后复合写 shadow 到 o,p 不被污染");
}
+17
View File
@@ -0,0 +1,17 @@
var ITERATIONS = 100000;
// 单态段:同一对象复合写,IC 读写全命中(写侧直写槽、零查表零回写)。
var t = { x: 0 };
for (var i = 0; i < ITERATIONS; i++) {
t.x += 1;
}
// 多态段:4 种 shape 轮换复合写,4 槽缓存覆盖,仅学习 miss。
var objs = [{ x: 0 }, { x: 0, y: 1 }, { x: 0, z: 1 }, { x: 0, y: 1, z: 2 }];
var s = 0;
for (var i = 0; i < ITERATIONS; i++) {
var o = objs[i % 4];
o.x += 1;
s += o.x;
}
t.x + s