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:
2026-08-15 06:40:19 +08:00
parent 461b18fa05
commit 54b6955f6b
9 changed files with 388 additions and 112 deletions
+11 -11
View File
@@ -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
View File
@@ -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);
}
}
+6 -6
View File
@@ -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]
+30 -26
View File
@@ -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() {
+100 -24
View File
@@ -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
View File
@@ -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]
+10 -9
View File
@@ -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},写侧新属性路径不计入)");
}