From 7291fb237ea947f40c3e6ca591ed1322479bebac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=B1=E7=89=B9=E6=AF=8F=E7=BE=8A=E9=98=9F?= Date: Tue, 30 Jun 2026 21:41:05 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E5=8E=9F=E5=9E=8B=E9=93=BE=E7=9A=84=20IC=20=E5=91=BD=E4=B8=AD?= =?UTF-8?q?=E5=8A=A0=E9=80=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_vm/src/dispatch/property.rs | 37 ++++++++--- crates/oxide_vm/src/ic_helper.rs | 85 +++++++++++++++++++----- crates/oxide_vm/src/vm.rs | 13 +++- crates/oxide_vm/src/vm_props.rs | 60 +++++++++++------ 4 files changed, 145 insertions(+), 50 deletions(-) diff --git a/crates/oxide_vm/src/dispatch/property.rs b/crates/oxide_vm/src/dispatch/property.rs index 68f8b05..fe1ff94 100644 --- a/crates/oxide_vm/src/dispatch/property.rs +++ b/crates/oxide_vm/src/dispatch/property.rs @@ -169,7 +169,7 @@ impl Vm { let obj = unsafe { &*obj_ptr }; let prop_name_si = self.property_key_si(self.regs[b]); - let (cached_shape_id, cached_slot) = ic_helper::read_ic_entry(&self.bytecode, &mut self.pc); + let (cached_shape_id, cached_slot, cached_depth) = ic_helper::read_ic_entry(&self.bytecode, &mut self.pc); if obj.has_prop_meta() { let val = self.ordinary_get_with_target(obj, prop_name_si, val, a as u8)?; if self.accessor_frame_target_reg.take().is_none() { @@ -178,16 +178,16 @@ impl Vm { return Ok(()); } - if let Some(value) = ic_get_hit(obj, cached_shape_id, cached_slot) { + if let Some(value) = ic_get_hit(obj, cached_shape_id, cached_slot, cached_depth) { self.regs[a] = value; self.profiling.record_ic_hit(); - ic_trace!("IC_GET hit shape={} slot={}", cached_shape_id, cached_slot); + ic_trace!("IC_GET hit shape={} slot={} depth={}", cached_shape_id, cached_slot, cached_depth); } else 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(&mut self.bytecode, self.pc, obj.shape_id(), template.position); + ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), template.position, 0); ic_debug!( "IC_GET propforge hit shape={} prop={} slot={}", obj.shape_id(), @@ -207,8 +207,23 @@ impl Vm { 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)?; - if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) { - ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), pos); + // Walk proto chain to find which object actually owns this property. + let mut cursor = obj_ptr; + let mut depth = 0u8; + loop { + let co = unsafe { &*cursor }; + if let Some(pos) = self.kernel_core.shape_forge().lookup_position(co.shape_id(), prop_name_si) { + if !co.is_accessor_meta(pos) { + ic_helper::write_ic_back(&mut self.bytecode, self.pc, co.shape_id(), pos, depth); + ic_debug!("IC_GET write-back shape={} slot={} depth={}", co.shape_id(), pos, depth); + } + break; + } + if !co.proto().is_object() { + break; + } + cursor = co.proto().as_js_object_ptr(); + depth += 1; } self.regs[a] = resolved; } @@ -235,7 +250,7 @@ impl Vm { return Ok(()); } - let (cached_shape_id, cached_slot) = ic_helper::read_ic_entry(&self.bytecode, &mut self.pc); + let (cached_shape_id, cached_slot, cached_depth) = ic_helper::read_ic_entry(&self.bytecode, &mut 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 }; @@ -244,14 +259,14 @@ impl Vm { return Ok(()); } - if ic_set_hit(obj, cached_shape_id, cached_slot, value) { + if ic_set_hit(obj, cached_shape_id, cached_slot, cached_depth, value) { self.profiling.record_ic_hit(); - ic_trace!("IC_SET hit shape={} slot={}", cached_shape_id, cached_slot); + ic_trace!("IC_SET hit shape={} slot={} depth={}", cached_shape_id, cached_slot, cached_depth); } else if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) { self.profiling.record_ic_miss(); prop_cache_miss(); obj.set_prop_at(pos, value); - ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), pos); + ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), pos, 0); ic_debug!("IC_SET write-back shape={} slot={}", obj.shape_id(), pos); } else { self.profiling.record_ic_miss(); @@ -260,7 +275,7 @@ impl Vm { self.ordinary_set_dispatch(obj, prop_name_si, value, receiver)?; if old_shape != obj.shape_id() { if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) { - ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), pos); + ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), pos, 0); ic_debug!("IC_SET write-back shape={} slot={}", obj.shape_id(), pos); self.kernel_core.prop_forge().upsert( obj.shape_id(), diff --git a/crates/oxide_vm/src/ic_helper.rs b/crates/oxide_vm/src/ic_helper.rs index f2d3d2c..0fa3b86 100644 --- a/crates/oxide_vm/src/ic_helper.rs +++ b/crates/oxide_vm/src/ic_helper.rs @@ -3,6 +3,11 @@ //! Contains ALL IC read/write/clear logic — property dispatch and member //! update handlers route through here, so IC format changes (like the future //! side-table migration) require editing only this file. +//! +//! IC entry format (3 extension words after opcode): +//! ext0 = shape_id (24 bits) +//! ext1 = slot_index (32 bits) +//! ext2 = proto_depth (u8, 0 = own property on receiver) use crate::vm_trace; use oxide_bytecode::opcode::{self, Instr}; @@ -11,27 +16,27 @@ use oxide_types::value::JsValue; /// Read the three IC extension words at `pc` (the bytes following an /// IC-bearing opcode), advance `pc` past them, and return the decoded -/// `(shape_id, slot)`. -pub(crate) fn read_ic_entry(bytecode: &[Instr], pc: &mut usize) -> (u32, u32) { +/// `(shape_id, slot, proto_depth)`. +pub(crate) fn read_ic_entry(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) + (ext0 & 0x00FF_FFFF, ext1, (ext2 & 0xFF) as u8) } -/// Write a resolved `(shape_id, slot)` back into the three IC extension words -/// at `pc - 3` (they precede the current instruction). Called on an IC miss -/// after resolving a property, so the next execution of this site hits the cache. -pub(crate) fn write_ic_back(bytecode: &mut [Instr], pc: usize, shape_id: u32, slot_index: u32) { +/// Write a resolved `(shape_id, slot, proto_depth)` back into the three IC +/// extension words at `pc - 3` (they precede the current instruction). +/// Called on an IC miss after resolving a property. +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"); - vm_trace!("write_ic_back: pc={} shape_id={} slot={}", pc, shape_id, slot_index); + 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] = 0; + bytecode[pc - 1] = proto_depth as u32; } /// Zero every IC extension word in the stream, invalidating all cached shapes. -/// Used on `rerun()` so re-execution never derefs a stale cached pointer. pub(crate) fn clear_ic_caches(bytecode: &mut [Instr]) { let mut i = 0; while i < bytecode.len() { @@ -49,19 +54,67 @@ pub(crate) fn clear_ic_caches(bytecode: &mut [Instr]) { } } +/// Walk `proto_depth` steps up the prototype chain, returning a raw pointer +/// to the target object (or null if the chain is shorter). #[inline(always)] -pub(crate) fn ic_get_hit(obj: &JsObject, shape_id: u32, slot_index: u32) -> Option { - if shape_id != 0 && obj.shape_id() == shape_id && slot_index < obj.prop_vec_len() as u32 { - Some(obj.get_prop_at(slot_index)) +fn resolve_proto_target_raw(obj: *const JsObject, proto_depth: u8) -> *const JsObject { + if proto_depth == 0 { + return obj; + } + let obj_ref = unsafe { &*obj }; + let mut cursor = obj_ref.proto(); + for _ in 0..proto_depth { + if !cursor.is_object() { + return std::ptr::null(); + } + let proto_ptr = cursor.as_js_object_ptr(); + if proto_ptr.is_null() { + return std::ptr::null(); + } + cursor = unsafe { (*proto_ptr).proto() }; + } + if cursor.is_object() { + let ptr = cursor.as_js_object_ptr(); + if !ptr.is_null() { + return ptr as *const JsObject; + } + } + std::ptr::null() +} + +#[inline(always)] +pub(crate) fn ic_get_hit( + obj: &JsObject, shape_id: u32, slot_index: u32, proto_depth: u8, +) -> Option { + if shape_id == 0 { + return None; + } + let target = resolve_proto_target_raw(obj as *const JsObject, proto_depth); + if target.is_null() { + return None; + } + let target_ref = unsafe { &*target }; + if target_ref.shape_id() == shape_id && slot_index < target_ref.prop_vec_len() as u32 { + Some(target_ref.get_prop_at(slot_index)) } else { None } } #[inline(always)] -pub(crate) fn ic_set_hit(obj: &mut JsObject, shape_id: u32, slot_index: u32, value: JsValue) -> bool { - if shape_id != 0 && obj.shape_id() == shape_id && slot_index < obj.prop_vec_len() as u32 { - obj.set_prop_at(slot_index, value); +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 { + return false; + } + let target = resolve_proto_target_raw(obj as *const JsObject, proto_depth); + if target.is_null() { + return false; + } + 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_at(slot_index, value); true } else { false diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 72bdd35..a82453a 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -1504,12 +1504,23 @@ mod tests { let mut vm = Vm::new(); vm.bytecode = vec![0, 0, 0]; vm.pc = 3; - crate::ic_helper::write_ic_back(&mut vm.bytecode, vm.pc, 0x1234_5678, 7); + crate::ic_helper::write_ic_back(&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() { + let mut vm = Vm::new(); + vm.bytecode = vec![0, 0, 0]; + vm.pc = 3; + crate::ic_helper::write_ic_back(&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); + } + #[test] fn unimplemented_profile_opcode_fails_explicitly() { let module = CompiledModule { diff --git a/crates/oxide_vm/src/vm_props.rs b/crates/oxide_vm/src/vm_props.rs index 1d249f6..f2c532b 100644 --- a/crates/oxide_vm/src/vm_props.rs +++ b/crates/oxide_vm/src/vm_props.rs @@ -209,38 +209,54 @@ impl Vm { 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]; + let ext2 = self.bytecode[self.pc + 2]; self.pc += 3; 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 cached_shape_id != 0 && cached_shape_id == obj.shape_id() && cached_slot < obj.prop_vec_len() as u32 { - obj.get_prop_at(cached_slot) - } else if let Some(template) = self.kernel_core.prop_forge().get_template(obj.shape_id()) { - if template.prop_name != prop_name_si { - self.ordinary_get(obj, prop_name_si, receiver)? - } else if template.position < obj.prop_vec_len() as u32 { - crate::ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), template.position); - obj.get_prop_at(template.position) - } else { - self.ordinary_get(obj, prop_name_si, receiver)? - } + 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(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)? + } else if template.position < obj.prop_vec_len() as u32 { + crate::ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), template.position, 0); + obj.get_prop_at(template.position) } else { - let resolved = self.ordinary_get(obj, prop_name_si, receiver)?; - if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) { - if !obj.is_accessor_meta(pos) { - crate::ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), pos); - } - } - resolved - }; + self.proto_chain_ic_get(obj, prop_name_si, receiver)? + } + } else { + self.proto_chain_ic_get(obj, prop_name_si, receiver)? + }; Ok(val) } + /// Perform ordinary_get and write back IC with proto chain depth. + fn proto_chain_ic_get(&mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue) -> Result { + let resolved = self.ordinary_get(obj, prop_name_si, receiver)?; + let mut cursor: *mut JsObject = obj as *const JsObject as *mut JsObject; + let mut depth = 0u8; + loop { + let co = unsafe { &*cursor }; + if let Some(pos) = self.kernel_core.shape_forge().lookup_position(co.shape_id(), prop_name_si) { + if !co.is_accessor_meta(pos) { + crate::ic_helper::write_ic_back(&mut self.bytecode, self.pc, co.shape_id(), pos, depth); + } + break; + } + if !co.proto().is_object() { + break; + } + cursor = co.proto().as_js_object_ptr(); + depth += 1; + } + Ok(resolved) + } + pub(crate) fn set_member_prop( &mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue, ) -> Result<(), String> { @@ -252,7 +268,7 @@ impl Vm { return Ok(()); } obj.set_prop_at(pos, val); - crate::ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), pos); + crate::ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), pos, 0); } else { self.ordinary_set(obj, prop_name_si, val, receiver)?; }