perf: 优化调用原型链的 IC 命中加速

This commit is contained in:
爱特每羊队
2026-06-30 21:41:05 +08:00
parent b6ce0f95bf
commit 7291fb237e
4 changed files with 145 additions and 50 deletions
+26 -11
View File
@@ -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(),
+69 -16
View File
@@ -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<JsValue> {
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<JsValue> {
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
+12 -1
View File
@@ -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 {
+31 -15
View File
@@ -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)
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.ordinary_get(obj, prop_name_si, receiver)?
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);
crate::ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), template.position, 0);
obj.get_prop_at(template.position)
} else {
self.ordinary_get(obj, prop_name_si, receiver)?
self.proto_chain_ic_get(obj, prop_name_si, receiver)?
}
} 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)?
};
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<JsValue, String> {
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)?;
}