perf: 数组元素独立存储区,push 改为摊销 O(1) 尾插

This commit is contained in:
爱特每羊队
2026-08-10 16:01:09 +08:00
parent eaf4825e4f
commit f284f74cb1
6 changed files with 391 additions and 63 deletions
+3 -5
View File
@@ -107,14 +107,12 @@ pub fn function_apply<H: VmHost>(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();
+3 -3
View File
@@ -72,7 +72,7 @@ fn walk_reviver<H: VmHost>(
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<H: VmHost>(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<H: VmHost>(
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(',');
+5 -2
View File
@@ -223,7 +223,7 @@ pub fn delete_own_property<H: VmHost>(vm: &mut H, obj: &mut JsObject, key_si: u3
Vec::new()
};
let saved_element_meta: Option<Vec<Option<PropMetaEntry>>> = 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<H: VmHost>(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);
}
}
+276 -53
View File
@@ -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\<Vec\<JsValue\>\>)
/// prop_meta: *mut u8 (8 字节,指向 Box\<Vec\<Option\<PropMetaEntry\>\>\>)
/// array_elements: *mut u8 (8 字节,数组对象元素区 Box\<Vec\<JsValue\>\>)
/// array_elements_meta: *mut u8 (8 字节,数组元素元数据 Box\<Vec\<Option\<PropMetaEntry\>\>\>)
/// hash_props: *mut u8 (8 字节,命名属性 Box\<Vec\<JsValue\>\>)
/// prop_meta: *mut u8 (8 字节,命名属性元数据 Box\<Vec\<Option\<PropMetaEntry\>\>\>)
/// 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<JsValue>`,`len == array_prop_count`。
array_elements: *mut u8,
/// 数组元素元数据区(仅数组对象,懒分配):`Vec<Option<PropMetaEntry>>`。
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<NativeFnPtr>,
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<F>(&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<PropMetaEntry> {
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<Vec<JsValue>> 创建。
let vec = unsafe { &mut *(self.array_elements as *mut Vec<JsValue>) };
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<Option<PropMetaEntry>>) };
meta.clear();
}
if !self.hash_props.is_null() {
// SAFETY: hash_props 在 ensure_hash_props/new_array 中由 Box<Vec<JsValue>> 创建。
let vec = unsafe { &mut *(self.hash_props as *mut Vec<JsValue>) };
@@ -1010,30 +1128,115 @@ impl JsObject {
}
}
/// 取下标 position 处的属性值。
/// hash_props 未分配或越界时返回 JsValue::undefined()。
/// 若数组元素区为空则初始化,并把长度对齐到 `array_prop_count`,返回其可变引用。
fn ensure_array_elements(&mut self) -> &mut Vec<JsValue> {
if self.array_elements.is_null() {
let vec = Box::new(Vec::<JsValue>::new());
self.array_elements = Box::into_raw(vec) as *mut u8;
}
// SAFETY: array_elements 在本方法或 new_array 中由 Box<Vec<JsValue>> 创建。
let vec = unsafe { &mut *(self.array_elements as *mut Vec<JsValue>) };
while vec.len() < self.array_prop_count as usize {
vec.push(JsValue::undefined());
}
vec
}
/// 数组元素区的安全只读访问;未分配时返回 None。
pub fn array_elements_vec(&self) -> Option<&Vec<JsValue>> {
if self.array_elements.is_null() {
None
} else {
// SAFETY: array_elements 在 ensure_array_elements/new_array 中由 Box<Vec<JsValue>> 创建。
unsafe { Some(&*(self.array_elements as *const Vec<JsValue>)) }
}
}
fn array_elements_vec_mut(&mut self) -> Option<&mut Vec<JsValue>> {
if self.array_elements.is_null() {
None
} else {
// SAFETY: array_elements 在 ensure_array_elements/new_array 中由 Box<Vec<JsValue>> 创建。
unsafe { Some(&mut *(self.array_elements as *mut Vec<JsValue>)) }
}
}
/// 确保数组元素元数据向量已分配并返回可变引用(长度对齐当前元素数)。
pub fn ensure_array_elements_meta(&mut self) -> &mut Vec<Option<PropMetaEntry>> {
if self.array_elements_meta.is_null() {
let len = self.array_prop_count as usize;
let vec = Box::new(vec![None::<PropMetaEntry>; len]);
self.array_elements_meta = Box::into_raw(vec) as *mut u8;
}
// SAFETY: array_elements_meta 在本方法中由 Box<Vec<Option<PropMetaEntry>>> 创建。
let vec = unsafe { &mut *(self.array_elements_meta as *mut Vec<Option<PropMetaEntry>>) };
while vec.len() < self.array_prop_count as usize {
vec.push(None);
}
vec
}
/// 数组元素元数据区的安全只读访问;未分配时返回 None。
pub fn array_elements_meta_vec(&self) -> Option<&Vec<Option<PropMetaEntry>>> {
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<Option<PropMetaEntry>>)) }
}
}
fn array_elements_meta_vec_mut(&mut self) -> Option<&mut Vec<Option<PropMetaEntry>>> {
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<Option<PropMetaEntry>>)) }
}
}
/// 取绝对存储下标 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<Vec<JsValue>> 创建。
let vec = unsafe { &*(self.array_elements as *const Vec<JsValue>) };
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<Vec<JsValue>> 创建。
let vec = unsafe { &*(self.hash_props as *const Vec<JsValue>) };
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<Vec<JsValue>> 创建。
let vec = unsafe { &*(self.hash_props as *const Vec<JsValue>) };
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);
+36
View File
@@ -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();
+68
View File
@@ -55,6 +55,19 @@ impl SessionGc {
fn object_edges(obj: &JsObject) -> Vec<JsValue> {
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<JsValue>;
if !elems_ptr.is_null() {
let vec = Box::from_raw(elems_ptr);
freed_bytes += size_of::<Vec<JsValue>>() as u64 + (vec.capacity() * size_of::<JsValue>()) as u64;
std::mem::drop(vec);
}
let elems_meta_ptr = obj.array_elements_meta_raw() as *mut Vec<Option<PropMetaEntry>>;
if !elems_meta_ptr.is_null() {
let vec = Box::from_raw(elems_meta_ptr);
freed_bytes += size_of::<Vec<Option<PropMetaEntry>>>() as u64
+ (vec.capacity() * size_of::<Option<PropMetaEntry>>()) as u64;
std::mem::drop(vec);
}
let hash_ptr = obj.hash_props_raw() as *mut Vec<JsValue>;
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();