From f284f74cb1de8c65477418fdbf3566469298d886 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: Mon, 10 Aug 2026 16:01:09 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E6=95=B0=E7=BB=84=E5=85=83=E7=B4=A0?= =?UTF-8?q?=E7=8B=AC=E7=AB=8B=E5=AD=98=E5=82=A8=E5=8C=BA=EF=BC=8Cpush=20?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E6=91=8A=E9=94=80=20O(1)=20=E5=B0=BE?= =?UTF-8?q?=E6=8F=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/function.rs | 8 +- crates/oxide_builtins/src/json.rs | 6 +- crates/oxide_builtins/src/object.rs | 7 +- crates/oxide_types/src/object.rs | 329 +++++++++++++++++++++----- crates/oxide_vm/src/session_arena.rs | 36 +++ crates/oxide_vm/src/session_gc.rs | 68 ++++++ 6 files changed, 391 insertions(+), 63 deletions(-) diff --git a/crates/oxide_builtins/src/function.rs b/crates/oxide_builtins/src/function.rs index fa8a002..fc5af59 100644 --- a/crates/oxide_builtins/src/function.rs +++ b/crates/oxide_builtins/src/function.rs @@ -107,14 +107,12 @@ pub fn function_apply(vm: &mut H, args: &[u8]) -> NativeResult { let arr = unsafe { &*arr_ptr }; if arr.is_array() { let max_args = 55usize; // base=200,255 号寄存器之前的安全上限。 - let n = arr.hash_props_vec().map_or(0, |v| v.len()).min(max_args); + let n = arr.prop_count() as usize; + let n = n.min(max_args); let base = 200u8; arg_regs = (0..n).map(|i| base + i as u8).collect(); for i in 0..n { - let vec = arr.hash_props_vec(); - if let Some(v) = vec.and_then(|v| v.get(i)) { - vm.set_reg(base + i as u8, *v); - } + vm.set_reg(base + i as u8, arr.get_prop_at(i)); } } else { arg_regs = Vec::new(); diff --git a/crates/oxide_builtins/src/json.rs b/crates/oxide_builtins/src/json.rs index 9ab0536..e0cc097 100644 --- a/crates/oxide_builtins/src/json.rs +++ b/crates/oxide_builtins/src/json.rs @@ -72,7 +72,7 @@ fn walk_reviver( if !obj_ptr.is_null() { let obj = unsafe { &*obj_ptr }; if obj.is_array() { - let len = obj.prop_vec_len(); + let len = obj.prop_count() as usize; for i in 0..len { let index_str = i.to_string(); let child_si = vm.kernel_core().perm_interner().intern(&index_str).0; @@ -229,7 +229,7 @@ pub fn json_stringify(vm: &mut H, args: &[u8]) -> NativeResult { if robj.is_function() { replacer_fn = Some(replacer_val); } else if robj.is_array() { - let len = robj.prop_vec_len(); + let len = robj.prop_count() as usize; let mut whitelist = HashSet::new(); for i in 0..len { let elem = robj.get_prop_at(i); @@ -446,7 +446,7 @@ fn stringify_array( let has_space = !space.is_empty(); out.push('['); - let len = obj.prop_vec_len(); + let len = obj.prop_count() as usize; for i in 0..len { if i > 0 && !has_space { out.push(','); diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index aba7176..a3c7661 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -223,7 +223,7 @@ pub fn delete_own_property(vm: &mut H, obj: &mut JsObject, key_si: u3 Vec::new() }; let saved_element_meta: Option>> = if obj.is_array() { - obj.prop_meta_vec().map(|meta| meta[..obj.array_prop_count as usize].to_vec()) + Some((0..obj.array_prop_count).map(|i| obj.prop_meta_at(i)).collect()) } else { None }; @@ -259,9 +259,12 @@ pub fn delete_own_property(vm: &mut H, obj: &mut JsObject, key_si: u3 obj.set_prop_at(i, val); } if let Some(saved_meta) = saved_element_meta { - let meta = obj.ensure_prop_meta(); + let meta = obj.ensure_array_elements_meta(); for (i, entry) in saved_meta.into_iter().enumerate() { if let Some(entry) = entry { + while meta.len() <= i { + meta.push(None); + } meta[i] = Some(entry); } } diff --git a/crates/oxide_types/src/object.rs b/crates/oxide_types/src/object.rs index a9af74f..fb98b3b 100644 --- a/crates/oxide_types/src/object.rs +++ b/crates/oxide_types/src/object.rs @@ -282,8 +282,10 @@ impl PropIndex for i32 { /// type_tag: u8 — 标识包装/外来对象种类的 OBJ_TYPE_* 常量 (1 字节) /// is_session_epoch: u8 (1 字节) /// _pad: u8 -/// hash_props: *mut u8 (8 字节,指向 Box\\>) -/// prop_meta: *mut u8 (8 字节,指向 Box\\>\>) +/// array_elements: *mut u8 (8 字节,数组对象元素区 Box\\>) +/// array_elements_meta: *mut u8 (8 字节,数组元素元数据 Box\\>\>) +/// hash_props: *mut u8 (8 字节,命名属性 Box\\>) +/// prop_meta: *mut u8 (8 字节,命名属性元数据 Box\\>\>) /// native_data: *mut u8 (8 字节,VM 拥有的不透明 native/外来载荷) /// proto: JsValue (8 字节) /// generation: u32 (4 字节 + 4 填充) @@ -361,13 +363,17 @@ pub struct JsObject { pub type_tag: u8, is_session_epoch: u8, _pad: u8, + /// 数组元素区(仅数组对象):`Vec`,`len == array_prop_count`。 + array_elements: *mut u8, + /// 数组元素元数据区(仅数组对象,懒分配):`Vec>`。 + array_elements_meta: *mut u8, hash_props: *mut u8, prop_meta: *mut u8, native_data: *mut u8, proto: JsValue, generation: u32, - /// 数组元素数(数组对象)。普通对象恒 0。属性(shape 槽位)存储偏移 - /// `array_prop_count + 槽位`,与元素区分(JS 数组属性不影响 length)。 + /// 数组元素数(数组对象)。普通对象恒 0。命名属性存储在 `hash_props` + /// (与元素区分,JS 数组命名属性不影响 length)。 pub array_prop_count: u32, native_fn: Option, sub_module_index: u32, @@ -481,6 +487,8 @@ impl JsObject { type_tag: 0, is_session_epoch: 0, _pad: 0, + array_elements: std::ptr::null_mut(), + array_elements_meta: std::ptr::null_mut(), hash_props: std::ptr::null_mut(), prop_meta: std::ptr::null_mut(), native_data: std::ptr::null_mut(), @@ -496,7 +504,7 @@ impl JsObject { } } - /// 构造数组对象:预分配 `n_elements` 个 `undefined` 的 dense 向量并置 array 标志。 + /// 构造数组对象:预分配 `n_elements` 个 `undefined` 的独立元素区并置 array 标志。 pub fn new_array(shape_id: ShapeId, proto: JsValue, n_elements: usize, _bump: &bumpalo::Bump) -> Self { let mut obj = Self { header: (shape_id & 0x00FF_FFFF) | (1 << 30) | (1 << 29), @@ -504,6 +512,8 @@ impl JsObject { type_tag: 0, is_session_epoch: 0, _pad: 0, + array_elements: std::ptr::null_mut(), + array_elements_meta: std::ptr::null_mut(), hash_props: std::ptr::null_mut(), prop_meta: std::ptr::null_mut(), native_data: std::ptr::null_mut(), @@ -518,8 +528,8 @@ impl JsObject { upvalues: std::ptr::null_mut(), }; let vec = Box::new(vec![JsValue::undefined(); n_elements.min(MAX_DENSE_PROPS)]); - obj.hash_props = Box::into_raw(vec) as *mut u8; - obj.array_prop_count = n_elements as u32; + obj.array_elements = Box::into_raw(vec) as *mut u8; + obj.array_prop_count = n_elements.min(MAX_DENSE_PROPS) as u32; obj } @@ -567,6 +577,14 @@ impl JsObject { .prop_meta_vec() .map(|meta| Box::into_raw(Box::new(meta.clone())) as *mut u8) .unwrap_or(std::ptr::null_mut()); + let array_elements = self + .array_elements_vec() + .map(|elems| Box::into_raw(Box::new(elems.clone())) as *mut u8) + .unwrap_or(std::ptr::null_mut()); + let array_elements_meta = self + .array_elements_meta_vec() + .map(|meta| Box::into_raw(Box::new(meta.clone())) as *mut u8) + .unwrap_or(std::ptr::null_mut()); Self { header: self.header, @@ -574,6 +592,8 @@ impl JsObject { type_tag: self.type_tag, is_session_epoch: Self::SESSION_EPOCH_BIT, _pad: self._pad, + array_elements, + array_elements_meta, hash_props, prop_meta, native_data: self.native_data, @@ -594,6 +614,16 @@ impl JsObject { self.hash_props } + /// 数组元素区底层指针(未分配时为空指针)。 + pub fn array_elements_raw(&self) -> *mut u8 { + self.array_elements + } + + /// 数组元素元数据区底层指针(未分配时为空指针)。 + pub fn array_elements_meta_raw(&self) -> *mut u8 { + self.array_elements_meta + } + /// 属性元数据向量底层指针(未分配时为空指针)。 pub fn prop_meta_raw(&self) -> *mut u8 { self.prop_meta @@ -639,13 +669,30 @@ impl JsObject { /// 用 `rewrite` 改写对象内引用的所有对象值。 /// - /// 用于 GC 移动 / 世代晋升:遍历 dense 属性、访问器 getter/setter、 + /// 用于 GC 移动 / 世代晋升:遍历数组元素区、dense 属性、访问器 getter/setter、 /// `proto`、`captured_this`、`home_object` 与 upvalue cell 中的对象值, /// 原地替换为新地址。非对象值保持不变。 pub fn rewrite_object_values(&mut self, mut rewrite: F) where F: FnMut(JsValue) -> JsValue, { + if let Some(elements) = self.array_elements_vec_mut() { + for value in elements { + if value.is_object() { + *value = rewrite(*value); + } + } + } + if let Some(meta) = self.array_elements_meta_vec_mut() { + for entry in meta.iter_mut().flatten() { + if entry.get.is_object() { + entry.get = rewrite(entry.get); + } + if entry.set.is_object() { + entry.set = rewrite(entry.set); + } + } + } if let Some(props) = self.hash_props_vec_mut() { for value in props { if value.is_object() { @@ -709,30 +756,27 @@ impl JsObject { } } - /// 设置 hash_props vec 的长度。截断或补 undefined 扩展。 - /// 数组对象只调整元素区(`array_prop_count`),属性区(尾部)整体搬移保持对齐。 + /// 设置数组元素数 / 命名属性向量长度。截断或补 undefined 扩展。 + /// 数组对象只调整独立元素区(`array_elements` + `array_prop_count`),命名属性区 + /// (`hash_props`)零搬移——push 为摊销 O(1) 的 `Vec::push`。 pub fn set_prop_count(&mut self, count: impl PropIndex) { let target = count.to_u32() as usize; if self.is_array() { let old = self.array_prop_count as usize; - let vec = self.ensure_hash_props(); + let vec = self.ensure_array_elements(); if target > old { - for _ in old..target { - vec.insert(old, JsValue::undefined()); - } + vec.extend((old..target).map(|_| JsValue::undefined())); } else if target < old { - vec.drain(target..old); + vec.truncate(target); + } + if let Some(meta) = self.array_elements_meta_vec_mut() { + if target > old { + meta.extend((old..target).map(|_| None)); + } else if target < old { + meta.truncate(target); + } } self.array_prop_count = target as u32; - if let Some(meta) = self.prop_meta_vec_mut() { - if target > old { - for _ in old..target { - meta.insert(old, None); - } - } else if target < old { - meta.drain(target..old); - } - } } else { let vec = self.ensure_hash_props(); if target < vec.len() { @@ -754,9 +798,9 @@ impl JsObject { } } - /// 是否已分配属性元数据向量。 + /// 是否已分配任何属性元数据向量(命名属性区或数组元素区)。 pub fn has_prop_meta(&self) -> bool { - !self.prop_meta.is_null() + !self.prop_meta.is_null() || !self.array_elements_meta.is_null() } /// 同 `set_prop_count`,但假定 `hash_props` 已分配。 @@ -802,9 +846,20 @@ impl JsObject { } } - /// 读取指定下标属性的元数据;无元数据或越界返回 `None`。 + /// 读取绝对存储下标 position 处的元数据;无元数据或越界返回 `None`。 pub fn prop_meta_at(&self, position: impl PropIndex) -> Option { let pos = position.to_u32() as usize; + if self.is_array() { + let count = self.array_prop_count as usize; + if pos < count { + return self + .array_elements_meta_vec() + .and_then(|vec| vec.get(pos).copied().flatten()); + } + return self + .prop_meta_vec() + .and_then(|vec| vec.get(pos - count).copied().flatten()); + } self.prop_meta_vec().and_then(|vec| vec.get(pos).copied().flatten()) } @@ -839,6 +894,25 @@ impl JsObject { fn set_meta_at(&mut self, position: impl PropIndex, entry: PropMetaEntry) { let pos = position.to_u32() as usize; + if self.is_array() { + let count = self.array_prop_count as usize; + if pos < count { + let meta = self.ensure_array_elements_meta(); + while meta.len() <= pos { + meta.push(None); + } + meta[pos] = Some(entry); + return; + } + // 命名属性区:值与长度由 set_prop_storage/push_prop 保证。 + let np = pos - count; + let meta = self.ensure_prop_meta(); + while meta.len() <= np { + meta.push(None); + } + meta[np] = Some(entry); + return; + } let prop_len = self.prop_vec_len(); if pos >= prop_len { self.set_prop_count(pos + 1); @@ -854,6 +928,22 @@ impl JsObject { /// `array_prop_count` 与元素区大小不变(length 保持不变)。 pub fn mark_hole_at(&mut self, position: impl PropIndex) { let pos = position.to_u32() as usize; + if self.is_array() { + let count = self.array_prop_count as usize; + if pos < count { + let vec = self.ensure_array_elements(); + if pos >= vec.len() { + vec.push(JsValue::undefined()); + } + vec[pos] = JsValue::undefined(); + let meta = self.ensure_array_elements_meta(); + while meta.len() <= pos { + meta.push(None); + } + meta[pos] = Some(PropMetaEntry::hole()); + } + return; + } if pos >= self.prop_vec_len() { self.set_prop_count(pos + 1); } @@ -865,8 +955,26 @@ impl JsObject { meta[pos] = Some(PropMetaEntry::hole()); } - /// 若指定下标是 hole 标记则清除(元素被重新写入时恢复为存在)。 + /// 若绝对下标是 hole 标记则清除(元素被重新写入时恢复为存在)。 fn clear_hole_marker(&mut self, pos: usize) { + if self.is_array() { + let count = self.array_prop_count as usize; + if pos < count { + if let Some(meta) = self.array_elements_meta_vec_mut() { + if meta.get(pos).is_some_and(|entry| entry.is_some_and(|e| e.is_hole())) { + meta[pos] = None; + } + } + return; + } + if let Some(meta) = self.prop_meta_vec_mut() { + let np = pos - count; + if meta.get(np).is_some_and(|entry| entry.is_some_and(|e| e.is_hole())) { + meta[np] = None; + } + } + return; + } if let Some(meta) = self.prop_meta_vec_mut() { if meta.get(pos).is_some_and(|entry| entry.is_some_and(|e| e.is_hole())) { meta[pos] = None; @@ -874,11 +982,21 @@ impl JsObject { } } - /// 清空全部属性与数组元素区,`array_prop_count` 归零。 + /// 清空全部命名属性、数组元素区与元数据,`array_prop_count` 归零。 /// /// 供 shape 链重建(如 delete 重排属性表)使用:清空后以 `push_prop` / /// `set_prop_count` 按新形状重填。不清除形状 ID,调用方自行处理。 pub fn clear_props(&mut self) { + if !self.array_elements.is_null() { + // SAFETY: array_elements 在 ensure_array_elements/new_array 中由 Box> 创建。 + let vec = unsafe { &mut *(self.array_elements as *mut Vec) }; + vec.clear(); + } + if !self.array_elements_meta.is_null() { + // SAFETY: array_elements_meta 在 ensure_array_elements_meta 中创建。 + let meta = unsafe { &mut *(self.array_elements_meta as *mut Vec>) }; + meta.clear(); + } if !self.hash_props.is_null() { // SAFETY: hash_props 在 ensure_hash_props/new_array 中由 Box> 创建。 let vec = unsafe { &mut *(self.hash_props as *mut Vec) }; @@ -1010,30 +1128,115 @@ impl JsObject { } } - /// 取下标 position 处的属性值。 - /// hash_props 未分配或越界时返回 JsValue::undefined()。 + /// 若数组元素区为空则初始化,并把长度对齐到 `array_prop_count`,返回其可变引用。 + fn ensure_array_elements(&mut self) -> &mut Vec { + if self.array_elements.is_null() { + let vec = Box::new(Vec::::new()); + self.array_elements = Box::into_raw(vec) as *mut u8; + } + // SAFETY: array_elements 在本方法或 new_array 中由 Box> 创建。 + let vec = unsafe { &mut *(self.array_elements as *mut Vec) }; + while vec.len() < self.array_prop_count as usize { + vec.push(JsValue::undefined()); + } + vec + } + + /// 数组元素区的安全只读访问;未分配时返回 None。 + pub fn array_elements_vec(&self) -> Option<&Vec> { + if self.array_elements.is_null() { + None + } else { + // SAFETY: array_elements 在 ensure_array_elements/new_array 中由 Box> 创建。 + unsafe { Some(&*(self.array_elements as *const Vec)) } + } + } + + fn array_elements_vec_mut(&mut self) -> Option<&mut Vec> { + if self.array_elements.is_null() { + None + } else { + // SAFETY: array_elements 在 ensure_array_elements/new_array 中由 Box> 创建。 + unsafe { Some(&mut *(self.array_elements as *mut Vec)) } + } + } + + /// 确保数组元素元数据向量已分配并返回可变引用(长度对齐当前元素数)。 + pub fn ensure_array_elements_meta(&mut self) -> &mut Vec> { + if self.array_elements_meta.is_null() { + let len = self.array_prop_count as usize; + let vec = Box::new(vec![None::; len]); + self.array_elements_meta = Box::into_raw(vec) as *mut u8; + } + // SAFETY: array_elements_meta 在本方法中由 Box>> 创建。 + let vec = unsafe { &mut *(self.array_elements_meta as *mut Vec>) }; + while vec.len() < self.array_prop_count as usize { + vec.push(None); + } + vec + } + + /// 数组元素元数据区的安全只读访问;未分配时返回 None。 + pub fn array_elements_meta_vec(&self) -> Option<&Vec>> { + if self.array_elements_meta.is_null() { + None + } else { + // SAFETY: array_elements_meta 在 ensure_array_elements_meta 中创建。 + unsafe { Some(&*(self.array_elements_meta as *const Vec>)) } + } + } + + fn array_elements_meta_vec_mut(&mut self) -> Option<&mut Vec>> { + if self.array_elements_meta.is_null() { + None + } else { + // SAFETY: array_elements_meta 在 ensure_array_elements_meta 中创建。 + unsafe { Some(&mut *(self.array_elements_meta as *mut Vec>)) } + } + } + + /// 取绝对存储下标 position 处的值(数组:元素区在前,命名属性区在后)。 + /// 对应存储未分配或越界时返回 JsValue::undefined()。 pub fn get_prop_at(&self, position: impl PropIndex) -> JsValue { + let pos = position.to_u32() as usize; + if self.is_array() { + let count = self.array_prop_count as usize; + if pos < count { + if self.array_elements.is_null() { + return JsValue::undefined(); + } + // SAFETY: array_elements 在 ensure_array_elements/new_array 中由 Box> 创建。 + let vec = unsafe { &*(self.array_elements as *const Vec) }; + return vec.get(pos).copied().unwrap_or(JsValue::undefined()); + } + if self.hash_props.is_null() { + return JsValue::undefined(); + } + // SAFETY: hash_props 在 ensure_hash_props/new_array 中由 Box> 创建。 + let vec = unsafe { &*(self.hash_props as *const Vec) }; + return vec.get(pos - count).copied().unwrap_or(JsValue::undefined()); + } if self.hash_props.is_null() { return JsValue::undefined(); } // SAFETY: hash_props 在 ensure_hash_props/new_array 中由 Box> 创建。 let vec = unsafe { &*(self.hash_props as *const Vec) }; - vec.get(position.to_u32() as usize).copied().unwrap_or(JsValue::undefined()) + vec.get(pos).copied().unwrap_or(JsValue::undefined()) } - /// 设置下标 position 处的属性值。vec 按需自动扩容。 - /// 数组对象元素写入会更新 `array_prop_count`(元素数随最高索引增长)。 + /// 设置数组元素 position 处的值(数组对象);普通对象按绝对下标写入并自动扩容。 + /// 数组元素写入会更新 `array_prop_count`(元素数随最高索引增长)。 pub fn set_prop_at(&mut self, position: impl PropIndex, val: JsValue) { let pos = position.to_u32() as usize; if pos > MAX_DENSE_PROPS { return; } if self.is_array() { - // 元素写入越过元素区:先搬移属性区到 pos+1 之后,保持元素/属性分界不变。 + // 元素写入越过元素区:只扩独立元素区,命名属性区零搬移。 if pos >= self.array_prop_count as usize { self.set_prop_count(pos + 1); } - let vec = self.ensure_hash_props(); + let vec = self.ensure_array_elements(); vec[pos] = val; self.clear_hole_marker(pos); return; @@ -1056,29 +1259,45 @@ impl JsObject { } } - /// 数组对象(shape 槽位 → 存储索引 = `array_prop_count + 槽位`)的 - /// 属性写入;普通对象等价 `set_prop_at`。 + /// 数组对象(shape 槽位 → 命名属性区存储索引)的属性写入;普通对象等价 + /// `set_prop_storage(shape_pos)`。 pub fn set_prop_shape(&mut self, shape_pos: u32, val: JsValue) { let idx = if self.is_array() { self.array_prop_count as usize + shape_pos as usize } else { shape_pos as usize }; - let vec = self.ensure_hash_props(); - while vec.len() <= idx { - vec.push(JsValue::undefined()); - } - vec[idx] = val; - if let Some(meta) = self.prop_meta_vec_mut() { - while meta.len() <= idx { - meta.push(None); - } - } + self.set_prop_storage(idx, val); } - /// 按绝对存储索引写入属性,不触发数组元素区搬移(属性区已在元素之后)。 + /// 按绝对存储索引写入值,数组对象把元素区与命名属性区分派到各自存储。 /// 用于调用方已知属性存储位置(如 `get_own_property_slot` 返回的索引)的场景。 pub fn set_prop_storage(&mut self, idx: usize, val: JsValue) { + if self.is_array() { + let count = self.array_prop_count as usize; + if idx < count { + let vec = self.ensure_array_elements(); + if idx >= vec.len() { + vec.push(JsValue::undefined()); + } + vec[idx] = val; + self.clear_hole_marker(idx); + return; + } + let np = idx - count; + let vec = self.ensure_hash_props(); + while vec.len() <= np { + vec.push(JsValue::undefined()); + } + vec[np] = val; + self.clear_hole_marker(idx); + if let Some(meta) = self.prop_meta_vec_mut() { + while meta.len() <= np { + meta.push(None); + } + } + return; + } let vec = self.ensure_hash_props(); while vec.len() <= idx { vec.push(JsValue::undefined()); @@ -1092,8 +1311,8 @@ impl JsObject { } } - /// 数组对象属性读取(shape 槽位 → 存储索引 = `array_prop_count + 槽位`); - /// 普通对象等价 `get_prop_at`。越界返回 undefined。 + /// 数组对象属性读取(shape 槽位 → 命名属性区存储索引);普通对象等价 + /// `get_prop_at(shape_pos)`。越界返回 undefined。 pub fn get_prop_shape(&self, shape_pos: u32) -> JsValue { let idx = if self.is_array() { self.array_prop_count as usize + shape_pos as usize @@ -1103,10 +1322,14 @@ impl JsObject { self.get_prop_at(idx) } - /// 把值压入 hash_props vec,返回其下标。 + /// 把值压入命名属性区,返回其绝对存储下标(数组对象 = `array_prop_count + 命名下标`)。 pub fn push_prop(&mut self, val: JsValue) -> u32 { + let pos = if self.is_array() { + self.array_prop_count as usize + self.hash_props_vec().map_or(0, Vec::len) + } else { + self.hash_props_vec().map_or(0, Vec::len) + }; let vec = self.ensure_hash_props(); - let pos = vec.len(); vec.push(val); if let Some(meta) = self.prop_meta_vec_mut() { meta.push(None); diff --git a/crates/oxide_vm/src/session_arena.rs b/crates/oxide_vm/src/session_arena.rs index 92241fb..c1b592d 100644 --- a/crates/oxide_vm/src/session_arena.rs +++ b/crates/oxide_vm/src/session_arena.rs @@ -198,6 +198,42 @@ mod tests { assert!(!is_epoch_object(&vm, meta.set)); } + #[test] + fn session_arena_promotes_array_elements_and_element_meta() { + let mut vm = Vm::new(); + let elem_child = plain_object(&mut vm); + let meta_getter = plain_object(&mut vm); + let array_proto = vm.session.builtin_world().array_proto.as_ptr() as *mut JsObject; + let arr = vm.epoch.alloc(JsObject::new_array( + EMPTY_SHAPE_ID, + JsValue::from_js_object(array_proto), + 2, + vm.epoch.bump(), + )); + unsafe { + (*arr).set_prop_at(0, JsValue::from_js_object(elem_child)); + (*arr).set_accessor_meta( + 1, + JsValue::from_js_object(meta_getter), + JsValue::undefined(), + PropAttributes::DEFAULT_DATA, + ); + (*arr).set_prop_shape(0, JsValue::int(99)); + } + + let promoted = vm.promote_object(arr); + let promoted_arr = unsafe { &*promoted }; + + assert!(promoted_arr.is_session_epoch()); + // 数组元素(含访问器 get)与命名属性随 clone + rewrite 迁移到 session。 + assert!(!is_epoch_object(&vm, promoted_arr.get_prop_at(0))); + let meta = promoted_arr.prop_meta_at(1).expect("element accessor meta"); + assert!(!is_epoch_object(&vm, meta.get)); + assert_eq!(promoted_arr.get_prop_shape(0), JsValue::int(99)); + // 元素计数与长度不因 promotion 改变。 + assert_eq!(promoted_arr.prop_count(), 2); + } + #[test] fn session_arena_barrier_promotes_global_root_write() { let mut vm = Vm::new(); diff --git a/crates/oxide_vm/src/session_gc.rs b/crates/oxide_vm/src/session_gc.rs index 7540541..ecfedf2 100644 --- a/crates/oxide_vm/src/session_gc.rs +++ b/crates/oxide_vm/src/session_gc.rs @@ -55,6 +55,19 @@ impl SessionGc { fn object_edges(obj: &JsObject) -> Vec { let mut edges = Vec::new(); + if let Some(elements) = obj.array_elements_vec() { + edges.extend(elements.iter().copied().filter(|val| val.is_object())); + } + if let Some(meta) = obj.array_elements_meta_vec() { + for entry in meta.iter().flatten() { + if entry.get.is_object() { + edges.push(entry.get); + } + if entry.set.is_object() { + edges.push(entry.set); + } + } + } if let Some(props) = obj.hash_props_vec() { edges.extend(props.iter().copied().filter(|val| val.is_object())); } @@ -119,6 +132,13 @@ impl SessionGc { /// 无害地记录;sweep 只遍历 `session_string_ptrs`,`live` 中的非 session 指针 /// 永远不会被查询。 fn record_object_string_edges(live: &mut HashSet<*mut JsString, FxBuildHasher>, obj: &JsObject) { + if let Some(elements) = obj.array_elements_vec() { + for value in elements.iter() { + if value.is_string() { + live.insert(value.as_string_ptr_mut()); + } + } + } if let Some(props) = obj.hash_props_vec() { for value in props.iter() { if value.is_string() { @@ -266,6 +286,21 @@ impl SessionGc { } let mut freed_bytes = 0u64; + let elems_ptr = obj.array_elements_raw() as *mut Vec; + if !elems_ptr.is_null() { + let vec = Box::from_raw(elems_ptr); + freed_bytes += size_of::>() as u64 + (vec.capacity() * size_of::()) as u64; + std::mem::drop(vec); + } + + let elems_meta_ptr = obj.array_elements_meta_raw() as *mut Vec>; + if !elems_meta_ptr.is_null() { + let vec = Box::from_raw(elems_meta_ptr); + freed_bytes += size_of::>>() as u64 + + (vec.capacity() * size_of::>()) as u64; + std::mem::drop(vec); + } + let hash_ptr = obj.hash_props_raw() as *mut Vec; if !hash_ptr.is_null() { let vec = Box::from_raw(hash_ptr); @@ -763,6 +798,39 @@ mod tests { .any(|ptr| unsafe { (*(*ptr)).is_gc_marked() })); } + #[test] + fn sweep_preserves_array_elements_and_collects_dead_element_object() { + let mut vm = Vm::new(); + let array_proto = vm.session.builtin_world().array_proto.as_ptr() as *mut JsObject; + let arr = vm.epoch.alloc(JsObject::new_array( + oxide_kernel::shape_forge::EMPTY_SHAPE_ID, + JsValue::from_js_object(array_proto), + 2, + vm.epoch.bump(), + )); + let live_elem = plain_object(&mut vm); + let dead_elem = plain_object(&mut vm); + unsafe { + (*arr).set_prop_at(0, JsValue::from_js_object(live_elem)); + (*arr).set_prop_at(1, JsValue::from_js_object(dead_elem)); + } + // 仅元素 0 的对象作为 GC 根:元素 1 的对象应被回收。 + let arr_session = vm.promote_object(arr); + vm.regs[0] = JsValue::from_js_object(arr_session); + vm.regs[1] = JsValue::from_js_object(live_elem); + + let mut gc = std::mem::take(&mut vm.gc_state.session_gc); + gc.mark(&vm); + let _ = gc.sweep(&mut vm); + vm.gc_state.session_gc = gc; + + // 存活对象经克隆 + rewrite:数组元素 0 指向晋升后的 live_elem。 + let live_session = unsafe { (*arr_session).get_prop_at(0).as_js_object_ptr() }; + assert_eq!(unsafe { (*live_session).prop_count() }, 0); + assert_eq!(unsafe { (*arr_session).prop_count() }, 2); + assert_eq!(unsafe { (*arr_session).get_prop_at(0).as_js_object_ptr() }, live_session); + } + #[test] fn forwarding_is_cleared_after_sweep() { let mut vm = Vm::new();