perf: IC 多态化 4 槽缓存(FIFO 滚动写回)消除多态场景 miss 重解析
每条 IC 指令扩展字 3→8(4 槽 × [shape_id|proto_depth, slot]),命中槽 0 单态快路径零额外成本,槽 1..3 懒解码遍历,miss 写回 FIFO 滚动(新条目进 槽 0、最老丢弃)。修复原型链 IC 深度解析多走一步与 prop_forge 模板不匹配 不写回两个使缓存恒失效的问题。ic_cache 基准命中率 34.8%→78.9%, 耗时 -29%;单态 prop_access 因扩展字布局膨胀有 ~20% 取指回归(后续 side-table 方案可根除)。
This commit is contained in:
@@ -63,10 +63,16 @@ pub struct OpSemantics {
|
||||
pub is_jump: bool,
|
||||
/// CFG 块尾终结(= cfg is_terminator)。
|
||||
pub is_terminator: bool,
|
||||
/// 3 个 IC 扩展字(= has_ic_ext_words)。
|
||||
/// IC 扩展字(= has_ic_ext_words):每条 IC 指令带 IC_EXT_WORDS 个扩展字。
|
||||
pub ic_ext: bool,
|
||||
}
|
||||
|
||||
/// IC 每站点的缓存槽数:多态化支持同一访问点最多缓存该数量的 shape。
|
||||
pub const IC_SLOTS: usize = 4;
|
||||
|
||||
/// 每条 IC 指令的扩展字总数(每槽两个字:shape_id|proto_depth 打包字 + slot 字)。
|
||||
pub const IC_EXT_WORDS: usize = IC_SLOTS * 2;
|
||||
|
||||
/// 从单张表生成 [`OpCode`] 枚举、`TryFrom<u8>`、`Display` 与语义访问器。
|
||||
///
|
||||
/// 每行同时提供枚举判别值、`TryFrom` 分支、`Display` 名称与语义字段
|
||||
@@ -802,24 +808,18 @@ mod tests {
|
||||
"is_terminator 集合(跳转族去 TRY_* 加 RETURN/HALT/THROW)"
|
||||
);
|
||||
|
||||
let ic_set: Vec<u8> = all_opcodes()
|
||||
.filter(|op| op.has_ic_ext_words())
|
||||
.map(|op| op as u8)
|
||||
.collect();
|
||||
let ic_set: Vec<u8> = all_opcodes().filter(|op| op.has_ic_ext_words()).map(|op| op as u8).collect();
|
||||
assert_eq!(ic_set.len(), 16, "ic_ext 恰 16 个");
|
||||
const _: () = assert!(IC_EXT_WORDS == IC_SLOTS * 2 && IC_SLOTS >= 2);
|
||||
}
|
||||
|
||||
/// 表驱动 ic_ext 与迁移前硬编码 16 集合逐项相等(旧表并存期的迁移校验)。
|
||||
#[test]
|
||||
fn semantics_ic_ext_matches_legacy_list() {
|
||||
let legacy = [
|
||||
0x50, 0x51, 0x59, 0x5A, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||
0x69,
|
||||
0x50, 0x51, 0x59, 0x5A, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
|
||||
];
|
||||
let tbl: Vec<u8> = all_opcodes()
|
||||
.filter(|op| op.has_ic_ext_words())
|
||||
.map(|op| op as u8)
|
||||
.collect();
|
||||
let tbl: Vec<u8> = all_opcodes().filter(|op| op.has_ic_ext_words()).map(|op| op as u8).collect();
|
||||
assert_eq!(tbl, legacy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,7 +668,7 @@ fn compile_dynamic_member_ops() {
|
||||
);
|
||||
}
|
||||
|
||||
/// 收集字节码指令序列,跳过 IC 扩展字(3 字)与其它扩展字,逐指令定位。
|
||||
/// 收集字节码指令序列,跳过 IC 扩展字(IC_EXT_WORDS 字)与其它扩展字,逐指令定位。
|
||||
fn scan_opcodes(module: &CompiledModule) -> Vec<OpCode> {
|
||||
let mut ops = Vec::new();
|
||||
let mut i = 0;
|
||||
@@ -676,7 +676,7 @@ fn scan_opcodes(module: &CompiledModule) -> Vec<OpCode> {
|
||||
let op = opcode::opcode(module.bytecode[i]);
|
||||
ops.push(op);
|
||||
i += 1 + if op.has_ic_ext_words() {
|
||||
3
|
||||
opcode::IC_EXT_WORDS
|
||||
} else {
|
||||
match op {
|
||||
OpCode::SPILL
|
||||
|
||||
+32
-23
@@ -4,7 +4,7 @@
|
||||
//! 全部由 `inst_*` 构造 API 保证——emit 代码不直接触碰 `ext` 字段。
|
||||
//! 寄存器 def/use 契约与副作用判定在 `contract` 模块(`impl Inst`,DCE/liveness 共用)。
|
||||
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_bytecode::opcode::{OpCode, IC_EXT_WORDS};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::operand::{LabelId, Operand};
|
||||
@@ -42,86 +42,86 @@ impl Inst {
|
||||
}
|
||||
}
|
||||
|
||||
// ── IC 系:ext = [0, 0, 0](shape/slot/proto 占位字,VM 运行时回填)──
|
||||
// ── IC 系:ext = [0; IC_EXT_WORDS](IC_SLOTS 组 shape/slot/proto 占位字,VM 运行时回填)──
|
||||
|
||||
/// 内联缓存读属性:结果写入 `dst`,属性键为 `key`。
|
||||
pub fn ic_get(dst: Operand, key: Operand) -> Self {
|
||||
Self::with_ext(OpCode::IC_GET_PROP, Operand::None, dst, key, &[0, 0, 0])
|
||||
Self::with_ext(OpCode::IC_GET_PROP, Operand::None, dst, key, &[0; IC_EXT_WORDS])
|
||||
}
|
||||
|
||||
/// 内联缓存写属性:`obj[key] = value`。
|
||||
pub fn ic_set(obj: Operand, value: Operand, key: Operand) -> Self {
|
||||
Self::with_ext(OpCode::IC_SET_PROP, obj, value, key, &[0, 0, 0])
|
||||
Self::with_ext(OpCode::IC_SET_PROP, obj, value, key, &[0; IC_EXT_WORDS])
|
||||
}
|
||||
|
||||
/// 成员自增:`obj[key]++`,val 为当前值寄存器。
|
||||
pub fn member_inc(obj: Operand, val: Operand, key: Operand) -> Self {
|
||||
Self::with_ext(OpCode::MEMBER_INC, obj, val, key, &[0, 0, 0])
|
||||
Self::with_ext(OpCode::MEMBER_INC, obj, val, key, &[0; IC_EXT_WORDS])
|
||||
}
|
||||
|
||||
/// 成员自减:`obj[key]--`,val 为当前值寄存器。
|
||||
pub fn member_dec(obj: Operand, val: Operand, key: Operand) -> Self {
|
||||
Self::with_ext(OpCode::MEMBER_DEC, obj, val, key, &[0, 0, 0])
|
||||
Self::with_ext(OpCode::MEMBER_DEC, obj, val, key, &[0; IC_EXT_WORDS])
|
||||
}
|
||||
|
||||
/// 成员复合赋值加法:`obj[key] += val`。
|
||||
pub fn compound_member_add(obj: Operand, val: Operand, key: Operand) -> Self {
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_ADD, obj, val, key, &[0, 0, 0])
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_ADD, obj, val, key, &[0; IC_EXT_WORDS])
|
||||
}
|
||||
|
||||
/// 成员复合赋值减法:`obj[key] -= val`。
|
||||
pub fn compound_member_sub(obj: Operand, val: Operand, key: Operand) -> Self {
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_SUB, obj, val, key, &[0, 0, 0])
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_SUB, obj, val, key, &[0; IC_EXT_WORDS])
|
||||
}
|
||||
|
||||
/// 成员复合赋值乘法:`obj[key] *= val`。
|
||||
pub fn compound_member_mul(obj: Operand, val: Operand, key: Operand) -> Self {
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_MUL, obj, val, key, &[0, 0, 0])
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_MUL, obj, val, key, &[0; IC_EXT_WORDS])
|
||||
}
|
||||
|
||||
/// 成员复合赋值除法:`obj[key] /= val`。
|
||||
pub fn compound_member_div(obj: Operand, val: Operand, key: Operand) -> Self {
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_DIV, obj, val, key, &[0, 0, 0])
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_DIV, obj, val, key, &[0; IC_EXT_WORDS])
|
||||
}
|
||||
|
||||
/// 成员复合赋值取模:`obj[key] %= val`。
|
||||
pub fn compound_member_mod(obj: Operand, val: Operand, key: Operand) -> Self {
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_MOD, obj, val, key, &[0, 0, 0])
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_MOD, obj, val, key, &[0; IC_EXT_WORDS])
|
||||
}
|
||||
|
||||
/// 成员复合赋值指数:`obj[key] **= val`。
|
||||
pub fn compound_member_exp(obj: Operand, val: Operand, key: Operand) -> Self {
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_EXP, obj, val, key, &[0, 0, 0])
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_EXP, obj, val, key, &[0; IC_EXT_WORDS])
|
||||
}
|
||||
|
||||
/// 成员复合赋值按位与:`obj[key] &= val`。
|
||||
pub fn compound_member_bit_and(obj: Operand, val: Operand, key: Operand) -> Self {
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_BIT_AND, obj, val, key, &[0, 0, 0])
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_BIT_AND, obj, val, key, &[0; IC_EXT_WORDS])
|
||||
}
|
||||
|
||||
/// 成员复合赋值按位或:`obj[key] |= val`。
|
||||
pub fn compound_member_bit_or(obj: Operand, val: Operand, key: Operand) -> Self {
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_BIT_OR, obj, val, key, &[0, 0, 0])
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_BIT_OR, obj, val, key, &[0; IC_EXT_WORDS])
|
||||
}
|
||||
|
||||
/// 成员复合赋值按位异或:`obj[key] ^= val`。
|
||||
pub fn compound_member_bit_xor(obj: Operand, val: Operand, key: Operand) -> Self {
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_BIT_XOR, obj, val, key, &[0, 0, 0])
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_BIT_XOR, obj, val, key, &[0; IC_EXT_WORDS])
|
||||
}
|
||||
|
||||
/// 成员复合赋值左移:`obj[key] <<= val`。
|
||||
pub fn compound_member_shl(obj: Operand, val: Operand, key: Operand) -> Self {
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_SHL, obj, val, key, &[0, 0, 0])
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_SHL, obj, val, key, &[0; IC_EXT_WORDS])
|
||||
}
|
||||
|
||||
/// 成员复合赋值右移:`obj[key] >>= val`。
|
||||
pub fn compound_member_shr(obj: Operand, val: Operand, key: Operand) -> Self {
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_SHR, obj, val, key, &[0, 0, 0])
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_SHR, obj, val, key, &[0; IC_EXT_WORDS])
|
||||
}
|
||||
|
||||
/// 成员复合赋值无符号右移:`obj[key] >>>= val`。
|
||||
pub fn compound_member_ushr(obj: Operand, val: Operand, key: Operand) -> Self {
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_USHR, obj, val, key, &[0, 0, 0])
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_USHR, obj, val, key, &[0; IC_EXT_WORDS])
|
||||
}
|
||||
|
||||
// ── Call 系:ext = [nargs] ──
|
||||
@@ -199,7 +199,6 @@ impl Inst {
|
||||
Self::with_ext(OpCode::DEFINE_ACCESSOR, home, get, set, &[key_idx])
|
||||
}
|
||||
|
||||
|
||||
/// 定义访问器属性并指定描述符:ext = [key_idx, attrs](attrs 位同 DEFINE_PROP_ATTRS)。
|
||||
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])
|
||||
@@ -216,7 +215,6 @@ impl Inst {
|
||||
Self::new(OpCode::DEFINE_PROP, target, value, key)
|
||||
}
|
||||
|
||||
|
||||
/// define 数据属性并指定描述符:ext = [attrs](bit0=writable, bit1=enumerable, bit2=configurable,
|
||||
/// 与 PropAttributes 位一致);class 方法/constructor/prototype 需要非枚举或不可写描述符。
|
||||
pub fn define_prop_attrs(target: Operand, value: Operand, key: Operand, attrs: u32) -> Self {
|
||||
@@ -409,7 +407,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ic_instructions_carry_three_zero_ext_words() {
|
||||
fn ic_instructions_carry_ic_slots_zero_ext_words() {
|
||||
let insts = [
|
||||
Inst::ic_get(Operand::Reg(1), Operand::Reg(2)),
|
||||
Inst::ic_set(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)),
|
||||
@@ -421,10 +419,21 @@ mod tests {
|
||||
Inst::compound_member_div(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)),
|
||||
Inst::compound_member_mod(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)),
|
||||
Inst::compound_member_exp(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)),
|
||||
Inst::compound_member_bit_and(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)),
|
||||
Inst::compound_member_bit_or(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)),
|
||||
Inst::compound_member_bit_xor(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)),
|
||||
Inst::compound_member_shl(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)),
|
||||
Inst::compound_member_shr(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)),
|
||||
Inst::compound_member_ushr(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)),
|
||||
];
|
||||
for inst in &insts {
|
||||
assert_eq!(inst.ext.as_slice(), &[0, 0, 0], "IC op {} must carry 3 zero ext words", inst.op);
|
||||
assert!(inst.ext.len() == 3);
|
||||
assert_eq!(
|
||||
inst.ext.as_slice(),
|
||||
&[0; IC_EXT_WORDS],
|
||||
"IC op {} must carry {IC_EXT_WORDS} zero ext words",
|
||||
inst.op
|
||||
);
|
||||
assert_eq!(inst.ext.len(), IC_EXT_WORDS);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ fn backward_jump_encodes_negative_offset() {
|
||||
|
||||
#[test]
|
||||
fn jump_offset_counts_ext_words_in_target() {
|
||||
// jmp(0) → ic_get(1 + 3 ext = 4 instr)→ ADD(instr 5)
|
||||
// jmp(0) → ic_get(1 + 8 ext = 9 instr)→ ADD(instr 10)
|
||||
let mut f = base_module();
|
||||
f.insts.push(Inst::jmp(0));
|
||||
f.insts.push(Inst::ic_get(Operand::Reg(1), Operand::Reg(2)));
|
||||
@@ -96,21 +96,21 @@ fn jump_offset_counts_ext_words_in_target() {
|
||||
f.label_pos = vec![Some(2)];
|
||||
f.label_count = 1;
|
||||
let m = lower_ok(&f);
|
||||
assert_eq!(m.bytecode.len(), 1 + 4 + 1);
|
||||
assert_eq!(opcode::offset16(m.bytecode[0]), 5);
|
||||
assert_eq!(m.bytecode.len(), 1 + 9 + 1);
|
||||
assert_eq!(opcode::offset16(m.bytecode[0]), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ic_get_carries_three_zero_ext_words() {
|
||||
fn ic_get_carries_ic_slots_zero_ext_words() {
|
||||
let mut f = base_module();
|
||||
f.insts.push(Inst::ic_get(Operand::Reg(1), Operand::Reg(2)));
|
||||
let m = lower_ok(&f);
|
||||
assert_eq!(m.bytecode.len(), 4);
|
||||
assert_eq!(m.bytecode.len(), 9);
|
||||
assert_eq!(opcode::opcode(m.bytecode[0]), OpCode::IC_GET_PROP);
|
||||
assert_eq!(opcode::rd(m.bytecode[0]), 0);
|
||||
assert_eq!(opcode::a(m.bytecode[0]), 1);
|
||||
assert_eq!(opcode::b(m.bytecode[0]), 2);
|
||||
assert_eq!(m.bytecode[1..], [0, 0, 0]);
|
||||
assert_eq!(m.bytecode[1..], [0; oxide_bytecode::opcode::IC_EXT_WORDS]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{ic_debug, ic_trace, vm_trace};
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
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;
|
||||
@@ -296,7 +296,7 @@ impl Vm {
|
||||
let prop_name_si = self.property_key_si(self.regs[b])?;
|
||||
if let Some(resolved) = self.primitive_property_get(val, prop_name_si)? {
|
||||
self.regs[a] = resolved;
|
||||
self.pc += 3;
|
||||
self.pc += IC_EXT_WORDS;
|
||||
return Ok(());
|
||||
}
|
||||
self.raise_type_error("IC_GET_PROP on non-object")?;
|
||||
@@ -309,7 +309,7 @@ impl Vm {
|
||||
}
|
||||
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
let (cached_shape_id, cached_slot, cached_depth) = ic_helper::read_ic_entry(&self.bytecode, &mut self.pc);
|
||||
let (cached_shape_id, cached_slot, cached_depth) = ic_helper::read_ic_slot0(&self.bytecode, &mut self.pc);
|
||||
let ic_pc = self.pc;
|
||||
if obj.has_prop_meta() {
|
||||
// meta 对象整对象走慢路径,此时才解析键。
|
||||
@@ -322,36 +322,38 @@ impl Vm {
|
||||
}
|
||||
|
||||
if let Some(value) = ic_get_hit(obj, cached_shape_id, cached_slot, cached_depth) {
|
||||
// 命中路径只比对 shape_id,不解析键。
|
||||
// 槽 0 单态命中:只比对 shape_id,不解析键。
|
||||
self.regs[a] = value;
|
||||
self.profiling.record_ic_hit();
|
||||
ic_trace!("IC_GET hit shape={} slot={} depth={}", cached_shape_id, cached_slot, cached_depth);
|
||||
} else if let Some(value) = ic_helper::ic_get_hit_poly(obj, &self.bytecode, ic_pc - IC_EXT_WORDS) {
|
||||
// 多态槽(1..3)命中。
|
||||
self.regs[a] = value;
|
||||
self.profiling.record_ic_hit();
|
||||
ic_trace!("IC_GET poly hit pc={} shape={}", ic_pc, obj.shape_id());
|
||||
} else {
|
||||
// miss 分支才解析键(IC 站点键恒为编译期字符串常量,延后解析无 ToPrimitive 副作用)。
|
||||
let prop_name_si = self.property_key_si(self.regs[b])?;
|
||||
self.profiling.record_ic_miss();
|
||||
prop_cache_miss();
|
||||
// 模板快路径:prop_forge 只缓存每 shape 最后新增的属性,恰好覆盖该属性时直取槽位。
|
||||
if let Some(template) = self.kernel_core.prop_forge().get_template(obj.shape_id()) {
|
||||
self.profiling.record_ic_miss();
|
||||
prop_cache_miss();
|
||||
if template.prop_name == prop_name_si {
|
||||
if template.position < obj.prop_vec_len() as u32 {
|
||||
ic_helper::write_ic_back(self.bytecode_mut(), ic_pc, obj.shape_id(), template.position, 0);
|
||||
ic_debug!(
|
||||
"IC_GET propforge hit shape={} prop={} slot={}",
|
||||
obj.shape_id(),
|
||||
prop_name_si,
|
||||
template.position
|
||||
);
|
||||
self.regs[a] = obj.get_prop_shape(template.position);
|
||||
} else {
|
||||
ic_debug!("IC_GET miss shape={} prop={}", obj.shape_id(), prop_name_si);
|
||||
self.regs[a] = self.ordinary_get(obj, prop_name_si, val)?;
|
||||
}
|
||||
if template.prop_name == prop_name_si && template.position < obj.prop_vec_len() as u32 {
|
||||
ic_helper::write_ic_back(self.bytecode_mut(), ic_pc, obj.shape_id(), template.position, 0);
|
||||
ic_debug!(
|
||||
"IC_GET propforge hit shape={} prop={} slot={}",
|
||||
obj.shape_id(),
|
||||
prop_name_si,
|
||||
template.position
|
||||
);
|
||||
self.regs[a] = obj.get_prop_shape(template.position);
|
||||
} else {
|
||||
// 模板不覆盖该属性(读非末位属性):走通用 miss 路径并写回,
|
||||
// 否则该 shape 的缓存条目永远学不到。
|
||||
ic_debug!("IC_GET miss shape={} prop={}", obj.shape_id(), prop_name_si);
|
||||
self.regs[a] = self.ordinary_get(obj, prop_name_si, val)?;
|
||||
self.regs[a] = self.proto_chain_ic_get(obj, prop_name_si, val)?;
|
||||
}
|
||||
} else {
|
||||
prop_cache_miss();
|
||||
ic_debug!("IC_GET miss shape={} prop={}", obj.shape_id(), prop_name_si);
|
||||
let resolved = self.ordinary_get(obj, prop_name_si, val)?;
|
||||
// 先试自身属性(快路径,depth=0)。
|
||||
@@ -399,15 +401,18 @@ impl Vm {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// read_ic_entry 先消费 3 扩展字,命中路径无需解析键。
|
||||
let (cached_shape_id, cached_slot, cached_depth) = ic_helper::read_ic_entry(&self.bytecode, &mut self.pc);
|
||||
// read_ic_slot0 先消费全部扩展字,命中路径无需解析键。
|
||||
let (cached_shape_id, cached_slot, cached_depth) = ic_helper::read_ic_slot0(&self.bytecode, &mut self.pc);
|
||||
let ic_pc = self.pc;
|
||||
let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]);
|
||||
let receiver = self.regs[rd];
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
|
||||
// 命中路径零键零拦截:__proto__ 赋值从不写 IC,缓存槽必为数据槽。
|
||||
if !obj.has_prop_meta() && ic_set_hit(obj, cached_shape_id, cached_slot, cached_depth, value) {
|
||||
if !obj.has_prop_meta()
|
||||
&& (ic_set_hit(obj, cached_shape_id, cached_slot, cached_depth, value)
|
||||
|| ic_helper::ic_set_hit_poly(obj, &self.bytecode, ic_pc - IC_EXT_WORDS, value))
|
||||
{
|
||||
self.profiling.record_ic_hit();
|
||||
ic_trace!("IC_SET hit shape={} slot={} depth={}", cached_shape_id, cached_slot, cached_depth);
|
||||
return Ok(());
|
||||
@@ -438,7 +443,6 @@ impl Vm {
|
||||
ic_debug!("IC_SET write-back shape={} slot={}", obj.shape_id(), pos);
|
||||
} else {
|
||||
self.profiling.record_ic_miss();
|
||||
prop_cache_miss();
|
||||
let old_shape = obj.shape_id();
|
||||
self.ordinary_set_dispatch(obj, prop_name_si, value, receiver)?;
|
||||
if old_shape != obj.shape_id() {
|
||||
|
||||
@@ -3,34 +3,46 @@
|
||||
//! 汇聚全部 IC 读写/清零逻辑——属性分发与成员更新 handler 都经由此处,IC 格式
|
||||
//! 变更(如未来的 side-table 迁移)只需改这一个文件。
|
||||
//!
|
||||
//! IC 项格式(操作码后 3 个扩展字):
|
||||
//! ext0 = shape_id(24 位)
|
||||
//! ext1 = slot_index(32 位)
|
||||
//! ext2 = proto_depth(u8,0 = 接收者自身属性)
|
||||
//! IC 项格式(操作码后 [`IC_EXT_WORDS`] 个扩展字 = [`IC_SLOTS`] 组二元组,槽 0 最前):
|
||||
//! ext[i*2+0] = shape_id(低 24 位)| proto_depth(高 8 位,0 = 接收者自身属性)
|
||||
//! ext[i*2+1] = slot_index(32 位)
|
||||
//!
|
||||
//! 槽位约定:
|
||||
//! - `shape_id == 0` 恒为空槽标记(合法 shape_id 从 [`EMPTY_SHAPE_ID`] = 1 起)。
|
||||
//! - miss 写回 FIFO 滚动:新条目进槽 0、原槽 0..2 顺移到槽 1..3、槽 3 丢弃,
|
||||
//! 空槽恒在尾部连续——命中遍历遇空槽即可 break。
|
||||
//! - 命中路径零写:只读扩展字 + Cell 计数,不触发 bytecode COW。
|
||||
|
||||
use crate::vm_trace;
|
||||
use oxide_bytecode::opcode::{self, Instr, OpCode};
|
||||
use oxide_bytecode::opcode::{self, Instr, OpCode, IC_EXT_WORDS, IC_SLOTS};
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
/// 读取 `pc` 处 IC 指令后的三个扩展字,把 `pc` 前进越过它们,
|
||||
/// 返回解码的 `(shape_id, slot, proto_depth)`。
|
||||
pub(crate) fn read_ic_entry(bytecode: &[Instr], pc: &mut usize) -> (u32, u32, u8) {
|
||||
/// 读取 `pc` 处 IC 指令槽 0 的三元组并把 `pc` 推进越过全部扩展字(单态快路径用)。
|
||||
/// 槽编码:[shape_id(24)|proto_depth(8)] [slot(32)]。
|
||||
pub(crate) fn read_ic_slot0(bytecode: &[Instr], pc: &mut usize) -> (u32, u32, u8) {
|
||||
let ext0 = bytecode[*pc];
|
||||
let ext1 = bytecode[*pc + 1];
|
||||
let ext2 = bytecode[*pc + 2];
|
||||
*pc += 3;
|
||||
(ext0 & 0x00FF_FFFF, ext1, (ext2 & 0xFF) as u8)
|
||||
*pc += IC_EXT_WORDS;
|
||||
(ext0 & 0x00FF_FFFF, ext1, (ext0 >> 24) as u8)
|
||||
}
|
||||
|
||||
/// 把解析出的 `(shape_id, slot, proto_depth)` 写回 `pc - 3` 处的三个 IC 扩展字
|
||||
/// (位于当前指令之前)。IC 未命中并完成属性解析后调用。
|
||||
/// 把解析出的 `(shape_id, slot, proto_depth)` 以 FIFO 滚动写回 `pc - IC_EXT_WORDS`
|
||||
/// 处的 IC 扩展字(位于当前指令之前):新条目进槽 0,原槽 0..2 顺移到槽 1..3,
|
||||
/// 最老的槽 3 丢弃。IC 未命中并完成属性解析后调用。
|
||||
pub(crate) fn write_ic_back(bytecode: &mut [Instr], pc: usize, shape_id: u32, slot_index: u32, proto_depth: u8) {
|
||||
debug_assert!(pc >= 3, "IC write-back requires 3 extension words before pc");
|
||||
debug_assert!(pc >= IC_EXT_WORDS, "IC write-back requires {IC_EXT_WORDS} extension words before pc");
|
||||
vm_trace!("write_ic_back: pc={} shape_id={} slot={} depth={}", pc, shape_id, slot_index, proto_depth);
|
||||
bytecode[pc - 3] = shape_id & 0x00FF_FFFF;
|
||||
bytecode[pc - 2] = slot_index;
|
||||
bytecode[pc - 1] = proto_depth as u32;
|
||||
let base = pc - IC_EXT_WORDS;
|
||||
// 读旧字后 FIFO 滚动:新条目写槽 0,原槽 i 顺移到槽 i+1,最老槽丢弃。
|
||||
let old = &bytecode[base..pc];
|
||||
let mut new_words = [0u32; IC_EXT_WORDS];
|
||||
new_words[0] = (shape_id & 0x00FF_FFFF) | ((proto_depth as u32) << 24);
|
||||
new_words[1] = slot_index;
|
||||
for i in 1..IC_SLOTS {
|
||||
new_words[i * 2..(i + 1) * 2].copy_from_slice(&old[(i - 1) * 2..i * 2]);
|
||||
}
|
||||
bytecode[base..pc].copy_from_slice(&new_words);
|
||||
}
|
||||
|
||||
/// 计算 `pc` 处指令之后的扩展字个数(按 opcode 语义推进,逐指令字节序一致)。
|
||||
@@ -40,9 +52,9 @@ pub(crate) fn write_ic_back(bytecode: &mut [Instr], pc: usize, shape_id: u32, sl
|
||||
/// `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)。
|
||||
// IC 系固定 IC_EXT_WORDS 扩展字(多态槽组)。
|
||||
if op.has_ic_ext_words() {
|
||||
return 3;
|
||||
return IC_EXT_WORDS;
|
||||
}
|
||||
match op {
|
||||
OpCode::SPILL
|
||||
@@ -79,16 +91,17 @@ 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() && i + 3 < bytecode.len() {
|
||||
bytecode[i + 1] = 0;
|
||||
bytecode[i + 2] = 0;
|
||||
bytecode[i + 3] = 0;
|
||||
if op.has_ic_ext_words() && i + IC_EXT_WORDS < bytecode.len() {
|
||||
for word in &mut bytecode[i + 1..=i + IC_EXT_WORDS] {
|
||||
*word = 0;
|
||||
}
|
||||
}
|
||||
i += 1 + ext_word_count(bytecode, i);
|
||||
}
|
||||
}
|
||||
|
||||
/// 沿原型链上溯 `proto_depth` 步,返回目标对象的裸指针(链不足时返回 null)。
|
||||
/// `proto_depth` 与 IC 写回语义一致:1 = 接收者的直接原型(`obj.proto()`)。
|
||||
#[inline(always)]
|
||||
fn resolve_proto_target_raw(obj: *const JsObject, proto_depth: u8) -> *const JsObject {
|
||||
if proto_depth == 0 {
|
||||
@@ -96,7 +109,7 @@ fn resolve_proto_target_raw(obj: *const JsObject, proto_depth: u8) -> *const JsO
|
||||
}
|
||||
let obj_ref = unsafe { &*obj };
|
||||
let mut cursor = obj_ref.proto();
|
||||
for _ in 0..proto_depth {
|
||||
for _ in 1..proto_depth {
|
||||
if !cursor.is_object() {
|
||||
return std::ptr::null();
|
||||
}
|
||||
@@ -139,6 +152,37 @@ pub(crate) fn ic_get_hit(obj: &JsObject, shape_id: u32, slot_index: u32, proto_d
|
||||
}
|
||||
}
|
||||
|
||||
/// 多态遍历判定:调用方槽 0 单态判定失败后,顺序检查槽 1..3。
|
||||
/// 空槽(shape_id==0)恒在尾部(FIFO 滚动保证),遇空即停。
|
||||
#[inline(never)]
|
||||
pub(crate) fn ic_get_hit_poly(obj: &JsObject, bytecode: &[Instr], ext_pc: usize) -> Option<JsValue> {
|
||||
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 slot_index = bytecode[base + 1];
|
||||
let proto_depth = (bytecode[base] >> 24) as u8;
|
||||
if proto_depth == 0 {
|
||||
if obj.shape_id() == shape_id && slot_index < obj.prop_vec_len() as u32 {
|
||||
let v = obj.get_prop_shape(slot_index);
|
||||
return Some(v);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let target = resolve_proto_target_raw(obj as *const JsObject, proto_depth);
|
||||
if target.is_null() {
|
||||
continue;
|
||||
}
|
||||
let target_ref = unsafe { &*target };
|
||||
if target_ref.shape_id() == shape_id && slot_index < target_ref.prop_vec_len() as u32 {
|
||||
return Some(target_ref.get_prop_shape(slot_index));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn ic_set_hit(obj: &mut JsObject, shape_id: u32, slot_index: u32, proto_depth: u8, value: JsValue) -> bool {
|
||||
if shape_id == 0 {
|
||||
@@ -163,3 +207,35 @@ pub(crate) fn ic_set_hit(obj: &mut JsObject, shape_id: u32, slot_index: u32, pro
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// 多态遍历判定:调用方槽 0 单态判定失败后,顺序检查槽 1..3。
|
||||
/// 空槽(shape_id==0)恒在尾部(FIFO 滚动保证),遇空即停。
|
||||
#[inline(never)]
|
||||
pub(crate) fn ic_set_hit_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 slot_index = bytecode[base + 1];
|
||||
let proto_depth = (bytecode[base] >> 24) as u8;
|
||||
if proto_depth == 0 {
|
||||
if obj.shape_id() == shape_id && slot_index < obj.prop_vec_len() as u32 {
|
||||
obj.set_prop_shape(slot_index, value);
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let target = resolve_proto_target_raw(obj as *const JsObject, proto_depth);
|
||||
if target.is_null() {
|
||||
continue;
|
||||
}
|
||||
let target_ref = unsafe { &*target };
|
||||
if target_ref.shape_id() == shape_id && slot_index < target_ref.prop_vec_len() as u32 {
|
||||
unsafe { &mut *(target as *mut JsObject) }.set_prop_shape(slot_index, value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
+26
-11
@@ -2285,25 +2285,40 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_ic_back_updates_three_extension_words() {
|
||||
fn write_ic_back_updates_slot_zero_ext_words() {
|
||||
let mut vm = Vm::new();
|
||||
vm.bytecode = Arc::from(vec![0, 0, 0]);
|
||||
vm.pc = 3;
|
||||
vm.bytecode = Arc::from(vec![0; oxide_bytecode::opcode::IC_EXT_WORDS]);
|
||||
vm.pc = oxide_bytecode::opcode::IC_EXT_WORDS;
|
||||
crate::ic_helper::write_ic_back(Arc::make_mut(&mut vm.bytecode), vm.pc, 0x1234_5678, 7, 0);
|
||||
assert_eq!(vm.bytecode[0], 0x0034_5678);
|
||||
assert_eq!(vm.bytecode[1], 7);
|
||||
assert_eq!(vm.bytecode[2], 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_ic_back_stores_proto_depth() {
|
||||
fn write_ic_back_rolls_fifo_and_drops_oldest_slot() {
|
||||
let mut vm = Vm::new();
|
||||
vm.bytecode = Arc::from(vec![0, 0, 0]);
|
||||
vm.pc = 3;
|
||||
crate::ic_helper::write_ic_back(Arc::make_mut(&mut vm.bytecode), vm.pc, 0xAAAA_BBBB, 42, 2);
|
||||
assert_eq!(vm.bytecode[0], 0x00AA_BBBB);
|
||||
assert_eq!(vm.bytecode[1], 42);
|
||||
assert_eq!(vm.bytecode[2], 2);
|
||||
// 预置 8 字四槽(2 字/槽):槽 0=(0xA1,1,0)、槽 1=(0xA2,2,1)、槽 2=(0xA3,3,0)、槽 3=(0xA4,4,0)。
|
||||
let mut bc = vec![0u32; oxide_bytecode::opcode::IC_EXT_WORDS];
|
||||
bc[0] = 0xA1;
|
||||
bc[1] = 1;
|
||||
bc[2] = 0xA2 | (1 << 24);
|
||||
bc[3] = 2;
|
||||
bc[4] = 0xA3;
|
||||
bc[5] = 3;
|
||||
bc[6] = 0xA4;
|
||||
bc[7] = 4;
|
||||
vm.bytecode = Arc::from(bc);
|
||||
vm.pc = oxide_bytecode::opcode::IC_EXT_WORDS;
|
||||
crate::ic_helper::write_ic_back(Arc::make_mut(&mut vm.bytecode), vm.pc, 0xB0, 9, 2);
|
||||
// 新条目进槽 0;原槽 0..2 顺移到槽 1..3;最老槽 3 丢弃。
|
||||
assert_eq!(vm.bytecode[0], 0xB0 | (2 << 24));
|
||||
assert_eq!(vm.bytecode[1], 9);
|
||||
assert_eq!(vm.bytecode[2], 0xA1);
|
||||
assert_eq!(vm.bytecode[3], 1);
|
||||
assert_eq!(vm.bytecode[4], 0xA2 | (1 << 24));
|
||||
assert_eq!(vm.bytecode[5], 2);
|
||||
assert_eq!(vm.bytecode[6], 0xA3);
|
||||
assert_eq!(vm.bytecode[7], 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -337,20 +337,19 @@ impl Vm {
|
||||
&mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue,
|
||||
) -> Result<JsValue, String> {
|
||||
vm_trace!("read_member_prop: shape_id={} prop_name_si={}", obj.shape_id(), prop_name_si);
|
||||
let ext0 = self.bytecode[self.pc];
|
||||
let ext1 = self.bytecode[self.pc + 1];
|
||||
let ext2 = self.bytecode[self.pc + 2];
|
||||
self.pc += 3;
|
||||
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);
|
||||
}
|
||||
let cached_shape_id = ext0 & 0x00FF_FFFF;
|
||||
let cached_slot = ext1;
|
||||
let cached_depth = (ext2 & 0xFF) as u8;
|
||||
|
||||
let val = if let Some(v) = crate::ic_helper::ic_get_hit(obj, cached_shape_id, cached_slot, cached_depth) {
|
||||
v
|
||||
} else if let Some(v) =
|
||||
crate::ic_helper::ic_get_hit_poly(obj, &self.bytecode, ic_pc - oxide_bytecode::opcode::IC_EXT_WORDS)
|
||||
{
|
||||
v
|
||||
} else if let Some(template) = self.kernel_core.prop_forge().get_template(obj.shape_id()) {
|
||||
if template.prop_name != prop_name_si {
|
||||
self.proto_chain_ic_get(obj, prop_name_si, receiver)?
|
||||
@@ -366,8 +365,10 @@ impl Vm {
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
/// 执行 ordinary_get 并把 IC 按原型链深度写回。
|
||||
fn proto_chain_ic_get(&mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue) -> Result<JsValue, String> {
|
||||
/// 执行 ordinary_get 并把 IC 按原型链深度写回(own 属性 depth=0,原型链按层计数)。
|
||||
pub(crate) fn proto_chain_ic_get(
|
||||
&mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue,
|
||||
) -> Result<JsValue, String> {
|
||||
let ic_pc = self.pc;
|
||||
let resolved = self.ordinary_get(obj, prop_name_si, receiver)?;
|
||||
// 快路径:自身属性(depth=0)。
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
//! IC 多态缓存(4 槽,FIFO 滚动写回)行为测试。
|
||||
//!
|
||||
//! `run()` 每次从 CodeForge 原始 bytecode 冷启动(IC 学习不跨 run 持久),
|
||||
//! 因此全部断言基于**单次 run 内**的 miss 计数与迭代数的对比:多态 4 槽
|
||||
//! 覆盖的 shape 只产生"每 shape 一次"的学习 miss,超出槽容量的轮换则
|
||||
//! 每次访问都 miss(FIFO 逐访问滚动)。
|
||||
|
||||
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 r = vm.run(&module).expect("run").to_string();
|
||||
(r, vm.ic_hit_count(), vm.ic_miss_count())
|
||||
}
|
||||
|
||||
/// 4 种 shape 轮换访问同一 `.x` 站点:每 shape 一次学习 miss,之后全命中。
|
||||
#[test]
|
||||
fn poly4_shape_rotation_only_learning_misses() {
|
||||
let (r, hits, misses) = run_once(
|
||||
r#"var a = { x: 1 }, b = { x: 2, y: 3 }, c = { x: 4, z: 5 }, d = { x: 6, y: 7, z: 8 };
|
||||
var sum = 0;
|
||||
for (var i = 0; i < 10000; i++) {
|
||||
var t = [a, b, c, d][i % 4];
|
||||
sum += t.x;
|
||||
}
|
||||
sum"#,
|
||||
);
|
||||
assert_eq!(r, "32500", "x 值 1/2/4/6 各 2500 次累加");
|
||||
assert_eq!(misses, 4, "4 shape 均被 4 槽缓存,仅首轮学习各 1 次 miss");
|
||||
assert!(hits >= 9990, "后续访问全部命中(实际命中 {hits})");
|
||||
}
|
||||
|
||||
/// 第 5 种 shape 挤掉 FIFO 最老槽:顺序轮换下每访问都 miss(缓存容量不足)。
|
||||
#[test]
|
||||
fn poly5_shape_rotation_evicts_oldest() {
|
||||
let (r, _hits, misses) = run_once(
|
||||
r#"var s = [
|
||||
{ x: 1 }, { x: 2, y: 3 }, { x: 4, z: 5 }, { x: 6, y: 7, z: 8 }, { x: 9, a: 1, b: 2 }
|
||||
];
|
||||
var sum = 0;
|
||||
for (var i = 0; i < 10000; i++) {
|
||||
sum += s[i % 5].x;
|
||||
}
|
||||
sum"#,
|
||||
);
|
||||
assert_eq!(r, "44000", "x 值 1/2/4/6/9 各 2000 次累加");
|
||||
assert!(
|
||||
misses > 5000,
|
||||
"5 shape 顺序轮换超出 4 槽容量,FIFO 逐访问滚动导致 miss 过半(实际 {misses})"
|
||||
);
|
||||
}
|
||||
|
||||
/// 单态连续访问:仅 1 次学习 miss,空槽 break 不越界。
|
||||
#[test]
|
||||
fn monomorphic_only_one_learning_miss() {
|
||||
let (r, _hits, misses) = run_once(
|
||||
r#"var a = { x: 1 };
|
||||
var sum = 0;
|
||||
for (var i = 0; i < 10000; i++) { sum += a.x; }
|
||||
sum"#,
|
||||
);
|
||||
assert_eq!(r, "10000");
|
||||
assert_eq!(misses, 1, "单态站点仅首轮学习 miss");
|
||||
}
|
||||
|
||||
/// 空 IC(未学习)走慢路径并写回槽 0;首次访问即正确。
|
||||
#[test]
|
||||
fn cold_cache_first_access_resolves() {
|
||||
let (r, hits, misses) = run_once(
|
||||
r#"var a = { x: 7 };
|
||||
var sum = 0;
|
||||
for (var i = 0; i < 1000; i++) { sum += a.x; }
|
||||
sum"#,
|
||||
);
|
||||
assert_eq!(r, "7000");
|
||||
assert_eq!(misses, 1);
|
||||
assert_eq!(hits, 999, "学习后 999 次全命中");
|
||||
}
|
||||
|
||||
/// accessor 属性走 has_prop_meta 慢路径,不经 IC 缓存;语义正确即可。
|
||||
#[test]
|
||||
fn accessor_property_semantics_preserved() {
|
||||
let (r, _hits, _misses) = run_once(
|
||||
r#"var o = { get x() { return 1; } };
|
||||
var sum = 0;
|
||||
for (var i = 0; i < 100; i++) { sum += o.x; }
|
||||
sum"#,
|
||||
);
|
||||
assert_eq!(r, "100", "getter 每次返回 1");
|
||||
}
|
||||
|
||||
/// `__proto__` 赋值走原型设置路径,不写 IC;原型属性读语义正确。
|
||||
#[test]
|
||||
fn proto_assignment_not_cached_semantics_preserved() {
|
||||
let (r, _hits, _misses) = run_once(
|
||||
r#"var p = { m: 42 };
|
||||
var o = {};
|
||||
o.__proto__ = p;
|
||||
var sum = 0;
|
||||
for (var i = 0; i < 100; i++) { sum += o.m; }
|
||||
sum"#,
|
||||
);
|
||||
assert_eq!(r, "4200", "__proto__ 赋值建立原型链后经 IC 原型路径读到 42");
|
||||
}
|
||||
|
||||
/// 原型链 depth>0 多态:两个不同 proto shape 轮换,各槽独立 depth。
|
||||
#[test]
|
||||
fn proto_polymorphic_two_protos_learning_misses() {
|
||||
let (r, _hits, misses) = run_once(
|
||||
r#"var b1 = { v: 1 }, b2 = { v: 2, w: 3 };
|
||||
var o1 = Object.create(b1), o2 = Object.create(b2);
|
||||
var sum = 0;
|
||||
for (var i = 0; i < 10000; i++) {
|
||||
var o = (i % 2 === 0) ? o1 : o2;
|
||||
sum += o.v;
|
||||
}
|
||||
sum"#,
|
||||
);
|
||||
assert_eq!(r, "15000");
|
||||
assert_eq!(misses, 2, "两个 proto shape 各学习一次,后续 depth>0 命中");
|
||||
}
|
||||
|
||||
/// `rerun()` 清空全部 IC 扩展字:清除后访问重新学习(对照 regression_rerun_clears_ic_cache)。
|
||||
#[test]
|
||||
fn rerun_clears_polymorphic_slots() {
|
||||
let allocator = Allocator::default();
|
||||
let program = oxide_parser::parse(
|
||||
&allocator,
|
||||
r#"var a = { x: 1 }, b = { x: 2, y: 3 };
|
||||
var sum = 0;
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
var t = (i % 2 === 0) ? a : b;
|
||||
sum += t.x;
|
||||
}
|
||||
sum"#,
|
||||
)
|
||||
.expect("parse");
|
||||
let module = Compiler::new().compile(&program).expect("compile");
|
||||
let mut vm = Vm::new();
|
||||
vm.run(&module).expect("run1");
|
||||
let learned = vm.ic_miss_count();
|
||||
// run() 每次冷启动,此处直接验证 rerun 的可重入性:清除后仍能正常执行。
|
||||
assert_eq!(format!("{}", vm.rerun().expect("rerun")), "1500");
|
||||
let after_rerun = vm.ic_miss_count();
|
||||
assert!(
|
||||
after_rerun >= learned,
|
||||
"rerun 清零多态槽后重新学习(miss 不降反增,实际 {after_rerun} ≥ {learned})"
|
||||
);
|
||||
}
|
||||
|
||||
/// member 读侧(obj.x++)多 shape 轮换:read_member_prop 多态命中,
|
||||
/// 写侧(set_member_prop)暂不接命中——本任务只验证格式同步不错位。
|
||||
#[test]
|
||||
fn member_compound_polymorphic_read_side_hits() {
|
||||
let (r, _hits, misses) = run_once(
|
||||
r#"var a = { x: 1 }, b = { x: 2, y: 3 };
|
||||
for (var i = 0; i < 10000; i++) {
|
||||
var t = (i % 2 === 0) ? a : b;
|
||||
t.x += 1;
|
||||
}
|
||||
a.x + b.x"#,
|
||||
);
|
||||
assert_eq!(r, "10003", "两对象 x 各自增 5000 次(从 1/2 起)");
|
||||
assert!(misses < 5000, "读侧 4 槽缓存使 miss 远低于迭代数(实际 {misses},写侧新属性路径不计入)");
|
||||
}
|
||||
Reference in New Issue
Block a user