diff --git a/crates/oxide_bytecode/src/opcode.rs b/crates/oxide_bytecode/src/opcode.rs index efc3e5f7..e819db50 100644 --- a/crates/oxide_bytecode/src/opcode.rs +++ b/crates/oxide_bytecode/src/opcode.rs @@ -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`、`Display` 与语义访问器。 /// /// 每行同时提供枚举判别值、`TryFrom` 分支、`Display` 名称与语义字段 @@ -802,24 +808,18 @@ mod tests { "is_terminator 集合(跳转族去 TRY_* 加 RETURN/HALT/THROW)" ); - let ic_set: Vec = all_opcodes() - .filter(|op| op.has_ic_ext_words()) - .map(|op| op as u8) - .collect(); + let ic_set: Vec = 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 = all_opcodes() - .filter(|op| op.has_ic_ext_words()) - .map(|op| op as u8) - .collect(); + let tbl: Vec = all_opcodes().filter(|op| op.has_ic_ext_words()).map(|op| op as u8).collect(); assert_eq!(tbl, legacy); } } diff --git a/crates/oxide_compiler/tests/compile_expr_tests.rs b/crates/oxide_compiler/tests/compile_expr_tests.rs index 1ccdf21b..35a70a84 100644 --- a/crates/oxide_compiler/tests/compile_expr_tests.rs +++ b/crates/oxide_compiler/tests/compile_expr_tests.rs @@ -668,7 +668,7 @@ fn compile_dynamic_member_ops() { ); } -/// 收集字节码指令序列,跳过 IC 扩展字(3 字)与其它扩展字,逐指令定位。 +/// 收集字节码指令序列,跳过 IC 扩展字(IC_EXT_WORDS 字)与其它扩展字,逐指令定位。 fn scan_opcodes(module: &CompiledModule) -> Vec { let mut ops = Vec::new(); let mut i = 0; @@ -676,7 +676,7 @@ fn scan_opcodes(module: &CompiledModule) -> Vec { 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 diff --git a/crates/oxide_ir/src/inst.rs b/crates/oxide_ir/src/inst.rs index e6a347f0..1df75fcd 100644 --- a/crates/oxide_ir/src/inst.rs +++ b/crates/oxide_ir/src/inst.rs @@ -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); } } diff --git a/crates/oxide_ir/tests/lowering_tests.rs b/crates/oxide_ir/tests/lowering_tests.rs index 5a798f6e..0f3594c6 100644 --- a/crates/oxide_ir/tests/lowering_tests.rs +++ b/crates/oxide_ir/tests/lowering_tests.rs @@ -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] diff --git a/crates/oxide_vm/src/dispatch/property.rs b/crates/oxide_vm/src/dispatch/property.rs index 5ebea6c3..af49af1a 100644 --- a/crates/oxide_vm/src/dispatch/property.rs +++ b/crates/oxide_vm/src/dispatch/property.rs @@ -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() { diff --git a/crates/oxide_vm/src/ic_helper.rs b/crates/oxide_vm/src/ic_helper.rs index 96aa761c..484f7b6c 100644 --- a/crates/oxide_vm/src/ic_helper.rs +++ b/crates/oxide_vm/src/ic_helper.rs @@ -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 { + 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 +} diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 80a1992f..8ef4bbf5 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -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] diff --git a/crates/oxide_vm/src/vm_props.rs b/crates/oxide_vm/src/vm_props.rs index b115cbed..1b61f9d5 100644 --- a/crates/oxide_vm/src/vm_props.rs +++ b/crates/oxide_vm/src/vm_props.rs @@ -337,20 +337,19 @@ impl Vm { &mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue, ) -> Result { 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 { + /// 执行 ordinary_get 并把 IC 按原型链深度写回(own 属性 depth=0,原型链按层计数)。 + pub(crate) fn proto_chain_ic_get( + &mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue, + ) -> Result { let ic_pc = self.pc; let resolved = self.ordinary_get(obj, prop_name_si, receiver)?; // 快路径:自身属性(depth=0)。 diff --git a/crates/oxide_vm/tests/ic_polymorphic_tests.rs b/crates/oxide_vm/tests/ic_polymorphic_tests.rs new file mode 100644 index 00000000..e6e6ace4 --- /dev/null +++ b/crates/oxide_vm/tests/ic_polymorphic_tests.rs @@ -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},写侧新属性路径不计入)"); +}