diff --git a/crates/oxide_vm/src/dispatch/property.rs b/crates/oxide_vm/src/dispatch/property.rs index d46bd8d0..d7947638 100644 --- a/crates/oxide_vm/src/dispatch/property.rs +++ b/crates/oxide_vm/src/dispatch/property.rs @@ -5,7 +5,7 @@ use oxide_types::object::JsObject; use oxide_types::private_key::{int_key_value, is_int_key, make_int_key, make_private_name_id}; use oxide_types::value::JsValue; -use crate::ic_helper::{self, ic_get_hit, ic_set_hit}; +use crate::ic_helper::{self, ic_get_hit, ic_set_hit, VIRTUAL_LENGTH_SLOT}; use crate::vm::{Vm, MAX_PROTO_CHAIN_DEPTH}; #[cold] @@ -377,6 +377,14 @@ impl Vm { let prop_name_si = self.property_key_si(self.regs[b])?; self.profiling.record_ic_miss(); prop_cache_miss(); + // 数组 length 虚拟槽:Array.prototype 无 own length,写回恒落空; + // 直接学入标记并读逻辑长度,跳过模板查 / ordinary_get / proto 空跑。 + if obj.is_array() && prop_name_si == self.length_si { + ic_helper::write_ic_back(self.bytecode_mut(), ic_pc, obj.shape_id(), VIRTUAL_LENGTH_SLOT, 0); + ic_debug!("IC_GET virtual length hit shape={}", obj.shape_id()); + self.regs[a] = obj.logical_len_value(); + return Ok(()); + } // 模板快路径:prop_forge 只缓存每 shape 最后新增的属性,恰好覆盖该属性时直取槽位。 if let Some(template) = self.kernel_core.prop_forge().get_template(obj.shape_id()) { if template.prop_name == prop_name_si && template.position < obj.prop_vec_len() as u32 { diff --git a/crates/oxide_vm/src/ic_helper.rs b/crates/oxide_vm/src/ic_helper.rs index aa74bb24..c6fbbc5e 100644 --- a/crates/oxide_vm/src/ic_helper.rs +++ b/crates/oxide_vm/src/ic_helper.rs @@ -12,12 +12,19 @@ //! - miss 写回 FIFO 滚动:新条目进槽 0、原槽 0..2 顺移到槽 1..3、槽 3 丢弃, //! 空槽恒在尾部连续——命中遍历遇空槽即可 break。 //! - 命中路径零写:只读扩展字 + Cell 计数,不触发 bytecode COW。 +//! - `slot_index == [`VIRTUAL_LENGTH_SLOT`]` 为数组虚拟长度标记,不对应任何实际槽, +//! 命中判定必须带 `is_array()` 守卫(`EMPTY_SHAPE_ID` 跨对象类型共享)。 use crate::vm_trace; use oxide_bytecode::opcode::{self, ext_word_count, Instr, IC_EXT_WORDS, IC_SLOTS}; use oxide_types::object::JsObject; use oxide_types::value::JsValue; +/// 数组虚拟长度标记槽:与真实槽空间不相交(`prop_vec_len` 远小于 `u32::MAX`), +/// 供 `arr.length` 读快路径使用——Array.prototype 无 own length 槽,写回恒落空, +/// miss 时直接学入本标记,命中时读逻辑长度。 +pub(crate) const VIRTUAL_LENGTH_SLOT: u32 = u32::MAX; + /// 读取 `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) { @@ -96,6 +103,11 @@ pub(crate) fn ic_get_hit(obj: &JsObject, shape_id: u32, slot_index: u32, proto_d return None; } if proto_depth == 0 { + // 虚拟长度标记:EMPTY_SHAPE_ID 跨对象类型共享,守卫必须带 is_array(), + // 防无属性普通对象(shape 1)误命中返回 0。 + if obj.is_array() && obj.shape_id() == shape_id && slot_index == VIRTUAL_LENGTH_SLOT { + return Some(obj.logical_len_value()); + } 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); @@ -127,6 +139,9 @@ pub(crate) fn ic_get_hit_poly(obj: &JsObject, bytecode: &[Instr], ext_pc: usize) let slot_index = bytecode[base + 1]; let proto_depth = (bytecode[base] >> 24) as u8; if proto_depth == 0 { + if obj.is_array() && obj.shape_id() == shape_id && slot_index == VIRTUAL_LENGTH_SLOT { + return Some(obj.logical_len_value()); + } 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); @@ -254,6 +269,7 @@ fn ic_set_hit_own_poly(obj: &mut JsObject, bytecode: &[Instr], ext_pc: usize, va mod tests { use super::*; use oxide_bytecode::opcode::OpCode; + use oxide_types::shape::EMPTY_SHAPE_ID; #[test] fn concat_n_ext_word_count_advances_by_n() { @@ -367,4 +383,19 @@ mod tests { assert_eq!(bc[6], 0xA3); assert_eq!(bc[7], 3); } + + #[test] + fn ic_get_hit_virtual_length_marker_hits_array() { + // shape 1(空形状)数组经虚拟长度标记命中:返回逻辑长度。 + let bump = bumpalo::Bump::new(); + let arr = JsObject::new_array(EMPTY_SHAPE_ID, JsValue::null(), 3, &bump); + assert_eq!(ic_get_hit(&arr, EMPTY_SHAPE_ID, VIRTUAL_LENGTH_SLOT, 0), Some(JsValue::int(3))); + } + + #[test] + fn ic_get_hit_virtual_length_marker_rejects_non_array() { + // 同 shape 1 的普通对象(EMPTY_SHAPE_ID 跨对象类型共享)不得误命中虚拟长度标记。 + let plain = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()); + assert_eq!(ic_get_hit(&plain, EMPTY_SHAPE_ID, VIRTUAL_LENGTH_SLOT, 0), None); + } }